diff --git a/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/table/StaticFlexiCellRenderer.java b/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/table/StaticFlexiCellRenderer.java index f161fe9851753fa2406bb31b40b19a223e6c5e96..cd312f11f7187c3b781ecd13f58ac92c131098e5 100644 --- a/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/table/StaticFlexiCellRenderer.java +++ b/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/table/StaticFlexiCellRenderer.java @@ -51,6 +51,7 @@ public class StaticFlexiCellRenderer implements FlexiCellRenderer, ActionDelegat private String iconRightCSS; private String linkCSS; private String linkTitle; + private boolean push = false; private boolean newWindow = false; private boolean dirtyCheck = true; private FlexiCellRenderer labelDelegate; @@ -129,6 +130,14 @@ public class StaticFlexiCellRenderer implements FlexiCellRenderer, ActionDelegat this.action = action; } + public boolean isPush() { + return push; + } + + public void setPush(boolean push) { + this.push = push; + } + @Override public List<String> getActions() { if(StringHelper.containsNonWhitespace(action)) { @@ -190,7 +199,7 @@ public class StaticFlexiCellRenderer implements FlexiCellRenderer, ActionDelegat if(!StringHelper.containsNonWhitespace(href)) { href = "javascript:;"; } - String jsCode = FormJSHelper.getXHRFnCallFor(rootForm, id, 1, dirtyCheck, true, false, pair); + String jsCode = FormJSHelper.getXHRFnCallFor(rootForm, id, 1, dirtyCheck, true, push, pair); target.append("<a href=\"").append(href).append("\" onclick=\"").append(jsCode).append("; return false;\""); } diff --git a/src/main/java/org/olat/course/certificate/_spring/certificateContext.xml b/src/main/java/org/olat/course/certificate/_spring/certificateContext.xml index 23558cb5126cb6c8c09e29f85e7bc3454c98e812..cc5d67c80d586523f94c6884214026eaee64bf4d 100644 --- a/src/main/java/org/olat/course/certificate/_spring/certificateContext.xml +++ b/src/main/java/org/olat/course/certificate/_spring/certificateContext.xml @@ -1,9 +1,33 @@ <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://www.springframework.org/schema/beans - http://www.springframework.org/schema/beans/spring-beans.xsd"> + xmlns:context="http://www.springframework.org/schema/context" + xsi:schemaLocation=" + http://www.springframework.org/schema/beans + http://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/context + http://www.springframework.org/schema/context/spring-context.xsd"> <import resource="classpath:org/olat/course/certificate/_spring/certificateJms_${jms.provider}.xml" /> + + <!-- Certificates report panel --> + <bean class="org.olat.core.extensions.action.GenericActionExtension" init-method="initExtensionPoints"> + <property name="order" value="8211" /> + <property name="actionController"> + <bean class="org.olat.core.gui.control.creator.AutoCreator" scope="prototype"> + <property name="className" value="org.olat.course.certificate.ui.report.CertificatesReportController"/> + </bean> + </property> + <property name="navigationKey" value="reportCertificates" /> + <property name="i18nActionKey" value="admin.menu.report.certificates.title"/> + <property name="i18nDescriptionKey" value="admin.menu.report.certificates.title.alt"/> + <property name="translationPackage" value="org.olat.course.certificate.ui.report"/> + <property name="parentTreeNodeIdentifier" value="reportsParent" /> + <property name="extensionPoints"> + <list> + <value>org.olat.admin.SystemAdminMainController</value> + </list> + </property> + </bean> </beans> \ No newline at end of file diff --git a/src/main/java/org/olat/course/certificate/model/AbstractCertificate.java b/src/main/java/org/olat/course/certificate/model/AbstractCertificate.java index 7a27e75093bb274c7969020d85b340ff482922f8..a98644e1ab945e632baf5eaa966503e6e3b5a792 100644 --- a/src/main/java/org/olat/course/certificate/model/AbstractCertificate.java +++ b/src/main/java/org/olat/course/certificate/model/AbstractCertificate.java @@ -182,10 +182,12 @@ public abstract class AbstractCertificate implements Certificate, Persistable { this.last = last; } + @Override public Date getNextRecertificationDate() { return nextRecertificationDate; } + @Override public void setNextRecertificationDate(Date nextRecertificationDate) { this.nextRecertificationDate = nextRecertificationDate; } diff --git a/src/main/java/org/olat/course/certificate/ui/UploadCertificateController.java b/src/main/java/org/olat/course/certificate/ui/UploadCertificateController.java index 05054d12c7c4a2de6c2e30f32b4fea14906e1ffb..94f86630edf7cafe0073b0864f9e4a8ca0f71643 100644 --- a/src/main/java/org/olat/course/certificate/ui/UploadCertificateController.java +++ b/src/main/java/org/olat/course/certificate/ui/UploadCertificateController.java @@ -30,7 +30,6 @@ import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.List; -import org.apache.commons.io.IOUtils; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocumentCatalog; import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; @@ -210,9 +209,9 @@ public class UploadCertificateController extends FormBasicController { private boolean validatePdf(File template) { boolean allOk = true; - PDDocument document = null; - try (InputStream in = Files.newInputStream(template.toPath())) { - document = PDDocument.load(in); + try (InputStream in = Files.newInputStream(template.toPath()); + PDDocument document = PDDocument.load(in)) { + if (document.isEncrypted()) { fileEl.setErrorKey("upload.error.encrypted", null); allOk &= false; @@ -221,7 +220,6 @@ public class UploadCertificateController extends FormBasicController { PDDocumentCatalog docCatalog = document.getDocumentCatalog(); PDAcroForm acroForm = docCatalog.getAcroForm(); if (acroForm != null) { - @SuppressWarnings("unchecked") List<PDField> fields = acroForm.getFields(); for(PDField field:fields) { field.setValue("test"); @@ -241,8 +239,6 @@ public class UploadCertificateController extends FormBasicController { logError("", ex); fileEl.setErrorKey("upload.unkown.error", null); allOk &= false; - } finally { - IOUtils.closeQuietly(document); } return allOk; diff --git a/src/main/java/org/olat/course/certificate/ui/report/CertificatesReportController.java b/src/main/java/org/olat/course/certificate/ui/report/CertificatesReportController.java new file mode 100644 index 0000000000000000000000000000000000000000..c66326e3197b8a0c1ad069cff4afe977ceec01cf --- /dev/null +++ b/src/main/java/org/olat/course/certificate/ui/report/CertificatesReportController.java @@ -0,0 +1,239 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.course.certificate.ui.report; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Set; + +import org.olat.NewControllerFactory; +import org.olat.commons.calendar.CalendarUtils; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.FormItem; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.elements.DateChooser; +import org.olat.core.gui.components.form.flexible.elements.FlexiTableElement; +import org.olat.core.gui.components.form.flexible.elements.FormLink; +import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement; +import org.olat.core.gui.components.form.flexible.elements.TextElement; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.components.form.flexible.impl.FormEvent; +import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; +import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiColumnModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiColumnModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableDataModelFactory; +import org.olat.core.gui.components.form.flexible.impl.elements.table.SelectionEvent; +import org.olat.core.gui.components.form.flexible.impl.elements.table.StaticFlexiCellRenderer; +import org.olat.core.gui.components.link.Link; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.media.MediaResource; +import org.olat.core.util.Formatter; +import org.olat.core.util.StringHelper; +import org.olat.core.util.Util; +import org.olat.course.certificate.ui.report.CertificatesReportParameters.With; +import org.olat.ims.qti21.ui.AssessmentTestDisplayController; +import org.olat.repository.RepositoryEntry; +import org.olat.repository.RepositoryManager; +import org.olat.repository.RepositoryService; +import org.olat.repository.model.SearchRepositoryEntryParameters; +import org.olat.repository.ui.RepositoryEntryACColumnDescriptor; +import org.olat.repository.ui.RepositoryFlexiTableModel; +import org.olat.repository.ui.RepositoryFlexiTableModel.RepoCols; +import org.olat.repository.ui.author.TypeRenderer; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 24 juin 2020<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class CertificatesReportController extends FormBasicController { + + private static final String[] searchWithKeys = new String[] { + With.withoutCertificate.name(), With.validCertificate.name(), With.expiredCertificate.name() + }; + private static final String[] passedKeys = new String[] { "passed" }; + + private TextElement searchStringEl; + private FormLink generateReportButton; + private DateChooser certificatesDateEl; + private MultipleSelectionElement withEl; + private MultipleSelectionElement passedEl; + private FlexiTableElement tableEl; + private RepositoryFlexiTableModel tableModel; + + @Autowired + private RepositoryManager repositoryManager; + + public CertificatesReportController(UserRequest ureq, WindowControl wControl) { + super(ureq, wControl, "reports", Util.createPackageTranslator(AssessmentTestDisplayController.class, ureq.getLocale(), + Util.createPackageTranslator(RepositoryService.class, ureq.getLocale()))); + + initForm(ureq); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + FormLayoutContainer searchCont = FormLayoutContainer.createDefaultFormLayout("search.cont", getTranslator()); + formLayout.add("search.cont", searchCont); + + searchStringEl = uifactory.addTextElement("search.text", 2000, null, searchCont); + certificatesDateEl = uifactory.addDateChooser("search.dates", "search.dates", null, searchCont); + certificatesDateEl.setSecondDate(true); + certificatesDateEl.setSeparator("search.dates.separator"); + + String[] searchWithValues = new String[] { + translate("search.without"), translate("search.with.valid"), translate("search.with.expired") + }; + withEl = uifactory.addCheckboxesVertical("search.with", "search.with", searchCont, searchWithKeys, searchWithValues, 1); + withEl.select(searchWithKeys[1], true); + + String[] passedValues = new String[] { translate("search.course.passed") }; + passedEl = uifactory.addCheckboxesVertical("search.passed", "search.passed", searchCont, passedKeys, passedValues, 1); + passedEl.select(passedKeys[0], true); + uifactory.addFormSubmitButton("search", searchCont); + + FlexiTableColumnModel columnsModel = FlexiTableDataModelFactory.createFlexiTableColumnModel(); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(RepoCols.ac, new RepositoryEntryACColumnDescriptor())); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(RepoCols.repoEntry, new TypeRenderer())); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(false, RepoCols.externalId));// visible if managed + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(RepoCols.externalRef)); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(RepoCols.displayname, "select")); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(RepoCols.author)); + StaticFlexiCellRenderer reportRenderer = new StaticFlexiCellRenderer(translate("report"), "report"); + reportRenderer.setPush(true); + reportRenderer.setDirtyCheck(false); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(true, true, "report", null, -1, "report", false, null, FlexiColumnModel.ALIGNMENT_LEFT, reportRenderer)); + + tableModel = new RepositoryFlexiTableModel(columnsModel, getLocale()); + tableEl = uifactory.addTableElement(getWindowControl(), "table", tableModel, 20, false, getTranslator(), formLayout); + tableEl.setExportEnabled(true); + tableEl.setSelectAllEnable(true); + tableEl.setMultiSelect(true); + tableEl.setAndLoadPersistedPreferences(ureq, "certificates-reports-courses-list"); + tableEl.setEmtpyTableMessageKey("search.empty"); + + generateReportButton = uifactory.addFormLink("report.certificates", "report.certificates", null, formLayout, Link.BUTTON); + + tableEl.addBatchButton(generateReportButton); + } + + @Override + protected void doDispose() { + // + } + + protected void loadModel(UserRequest ureq, String searchString) { + SearchRepositoryEntryParameters params = new SearchRepositoryEntryParameters(); + params.addResourceTypes("CourseModule"); + params.setIdentity(getIdentity()); + params.setRoles(ureq.getUserSession().getRoles()); + params.setIdRefsAndTitle(searchString); + + List<RepositoryEntry> entries = repositoryManager.genericANDQueryWithRolesRestriction(params, 0, -1, true); + tableModel.setObjects(entries); + tableEl.reset(true, true, true); + } + + @Override + protected void formOK(UserRequest ureq) { + doSearch(ureq); + } + + @Override + protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { + if(generateReportButton == source) { + doReport(ureq); + } else if(tableEl == source) { + if(event instanceof SelectionEvent) { + SelectionEvent se = (SelectionEvent)event; + if("select".equals(se.getCommand())) { + doSelect(ureq, tableModel.getObject(se.getIndex())); + } else if("report".equals(se.getCommand())) { + doReport(ureq, tableModel.getObject(se.getIndex())); + } + } + } + super.formInnerEvent(ureq, source, event); + } + + private void doSearch(UserRequest ureq) { + String searchString = searchStringEl.getValue(); + loadModel(ureq, searchString); + } + + private void doSelect(UserRequest ureq, RepositoryEntry re) { + String businessPath = "[RepositoryEntry:" + re.getKey() + "]"; + NewControllerFactory.getInstance().launch(businessPath, ureq, getWindowControl()); + } + + private void doReport(UserRequest ureq, RepositoryEntry re) { + String filename = re.getDisplayname() + "_Certificates_" + Formatter.formatDatetimeWithMinutes(ureq.getRequestTimestamp()); + filename = StringHelper.transformDisplayNameToFileSystemName(filename); + + List<RepositoryEntry> entries = Collections.singletonList(re); + CertificatesReportParameters reportParams = getReportParameters(); + MediaResource report = new CertificatesReportMediaResource(filename, entries, reportParams, getTranslator()); + ureq.getDispatchResult().setResultingMediaResource(report); + } + + private void doReport(UserRequest ureq) { + List<RepositoryEntry> entries = getSelectedEntries(); + if(entries.isEmpty()) { + showWarning("warning.at.least.one.test"); + } else { + String filename = "Certificates_" + Formatter.formatDatetimeWithMinutes(ureq.getRequestTimestamp()); + filename = StringHelper.transformDisplayNameToFileSystemName(filename); + + CertificatesReportParameters reportParams = getReportParameters(); + MediaResource report = new CertificatesReportMediaResource(filename, entries, reportParams, getTranslator()); + ureq.getDispatchResult().setResultingMediaResource(report); + } + } + + private CertificatesReportParameters getReportParameters() { + List<With> with = With.values(withEl.getSelectedKeys()); + boolean onlyPassed = passedEl.isAtLeastSelected(1); + Date certificatesStart = certificatesDateEl.getDate(); + if(certificatesStart != null) { + certificatesStart = CalendarUtils.startOfDay(certificatesStart); + } + Date certificatesEnd = certificatesDateEl.getSecondDate(); + if(certificatesEnd != null) { + certificatesEnd = CalendarUtils.endOfDay(certificatesEnd); + } + return new CertificatesReportParameters(certificatesStart, certificatesEnd, with, onlyPassed); + } + + private List<RepositoryEntry> getSelectedEntries() { + Set<Integer> selectedIndexes = tableEl.getMultiSelectedIndex(); + List<RepositoryEntry> selectedEntries = new ArrayList<>(selectedIndexes.size()); + for(Integer selectedIndex:selectedIndexes) { + RepositoryEntry entry = tableModel.getObject(selectedIndex.intValue()); + selectedEntries.add(entry); + } + return selectedEntries; + } +} diff --git a/src/main/java/org/olat/course/certificate/ui/report/CertificatesReportMediaResource.java b/src/main/java/org/olat/course/certificate/ui/report/CertificatesReportMediaResource.java new file mode 100644 index 0000000000000000000000000000000000000000..ed3ad0ea344f75c83f71e449c073303eac2de761 --- /dev/null +++ b/src/main/java/org/olat/course/certificate/ui/report/CertificatesReportMediaResource.java @@ -0,0 +1,257 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.course.certificate.ui.report; + +import java.io.OutputStream; +import java.text.Collator; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.apache.logging.log4j.Logger; +import org.olat.core.CoreSpringFactory; +import org.olat.core.commons.persistence.DB; +import org.olat.core.gui.translator.Translator; +import org.olat.core.id.Identity; +import org.olat.core.logging.Tracing; +import org.olat.core.util.openxml.OpenXMLWorkbook; +import org.olat.core.util.openxml.OpenXMLWorkbookResource; +import org.olat.core.util.openxml.OpenXMLWorksheet; +import org.olat.core.util.openxml.OpenXMLWorksheet.Row; +import org.olat.course.CourseFactory; +import org.olat.course.assessment.AssessmentToolManager; +import org.olat.course.assessment.manager.UserCourseInformationsManager; +import org.olat.course.assessment.model.SearchAssessedIdentityParams; +import org.olat.course.certificate.CertificateLight; +import org.olat.course.certificate.CertificatesManager; +import org.olat.course.certificate.ui.report.CertificatesReportParameters.With; +import org.olat.modules.assessment.AssessmentEntry; +import org.olat.modules.assessment.ui.AssessmentToolSecurityCallback; +import org.olat.repository.RepositoryEntry; +import org.olat.resource.OLATResource; +import org.olat.user.UserManager; +import org.olat.user.propertyhandlers.UserPropertyHandler; +import org.springframework.beans.factory.annotation.Autowired; + + +/** + * + * Initial date: 11 juin 2020<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class CertificatesReportMediaResource extends OpenXMLWorkbookResource { + + private static final Logger log = Tracing.createLoggerFor(CertificatesReportMediaResource.class); + + + private static final String usageIdentifyer = CertificatesReportMediaResource.class.getName(); + + private final Date now = new Date(); + private final Translator translator; + private final List<RepositoryEntry> entries; + private List<UserPropertyHandler> userPropertyHandlers; + private final CertificatesReportParameters reportParams; + private final AssessmentToolSecurityCallback secCallback = new AssessmentToolSecurityCallback(true, false, true, true, true, null); + + @Autowired + private DB dbInstance; + @Autowired + private UserManager userManager; + @Autowired + private CertificatesManager certificatesManager; + @Autowired + private UserCourseInformationsManager userInfosMgr; + @Autowired + private AssessmentToolManager assessmentToolManager; + + public CertificatesReportMediaResource(String label, List<RepositoryEntry> entries, + CertificatesReportParameters reportParams, Translator translator) { + super(label); + CoreSpringFactory.autowireObject(this); + this.entries = entries; + this.reportParams = reportParams; + this.translator = userManager.getPropertyHandlerTranslator(translator); + userPropertyHandlers = userManager.getUserPropertyHandlersFor(usageIdentifyer, true); + } + + @Override + protected void generate(OutputStream out) { + try(OpenXMLWorkbook workbook = new OpenXMLWorkbook(out, 1)) { + OpenXMLWorksheet sheet = workbook.nextWorksheet(); + sheet.setHeaderRows(1); + generateHeaders(sheet); + generateData(sheet, workbook); + } catch (Exception e) { + log.error("", e); + } + } + + protected void generateHeaders(OpenXMLWorksheet sheet) { + Row headerRow = sheet.newRow(); + int col = 0; + + // course + headerRow.addCell(col++, translator.translate("report.course.displayname")); + + // user properties + for(UserPropertyHandler userPropertyHandler:userPropertyHandlers) { + if(userPropertyHandler == null) continue; + headerRow.addCell(col++, translator.translate(userPropertyHandler.i18nColumnDescriptorLabelKey())); + } + + //passed + headerRow.addCell(col++, translator.translate("report.course.passed")); + + // initial launch date + headerRow.addCell(col++, translator.translate("report.initialLaunchDate")); + + // certificate + headerRow.addCell(col, translator.translate("report.certificate")); + } + + protected void generateData(OpenXMLWorksheet sheet, OpenXMLWorkbook workbook) { + Collections.sort(entries, new RepositoryEntryComparator(translator.getLocale())); + + for(RepositoryEntry entry:entries) { + try { + generateData(entry, sheet, workbook); + } catch (Exception e) { + log.error("", e); + } finally { + dbInstance.commitAndCloseSession(); + } + } + } + + protected void generateData(RepositoryEntry entry, OpenXMLWorksheet sheet, OpenXMLWorkbook workbook) { + OLATResource resource = entry.getOlatResource(); + List<CertificateLight> certificates = certificatesManager.getLastCertificates(resource); + Map<Long, CertificateLight> certificateMap = certificates.stream() + .collect(Collectors.toMap(CertificateLight::getIdentityKey, Function.identity())); + + Map<Long, Date> initialLaunchDates = userInfosMgr + .getInitialLaunchDates(resource); + + String rootIdent = CourseFactory.loadCourse(entry).getRunStructure().getRootNode().getIdent(); + SearchAssessedIdentityParams params = new SearchAssessedIdentityParams(entry, rootIdent, null, secCallback); + List<Identity> assessedIdentities = assessmentToolManager.getAssessedIdentities(null, params); + List<AssessmentEntry> assessmentEntries = assessmentToolManager.getAssessmentEntries(null, params, null); + Map<Long,AssessmentEntry> entryMap = new HashMap<>(); + assessmentEntries.stream() + .filter(assessmentEntry -> assessmentEntry.getIdentity() != null) + .forEach(assessmentEntry -> entryMap.put(assessmentEntry.getIdentity().getKey(), assessmentEntry)); + + dbInstance.commitAndCloseSession(); + + for(Identity participant:assessedIdentities) { + AssessmentEntry assessmentEntry = entryMap.get(participant.getKey()); + CertificateLight certificate = certificateMap.get(participant.getKey()); + if(!accept(assessmentEntry, certificate)) { + continue; + } + + Date launchDate = initialLaunchDates.get(participant.getKey()); + + int col = 0; + Row row = sheet.newRow(); + row.addCell(col++, entry.getDisplayname()); + + for(UserPropertyHandler userPropertyHandler:userPropertyHandlers) { + if(userPropertyHandler == null) continue; + + String val = participant.getUser().getProperty(userPropertyHandler.getName(), translator.getLocale()); + row.addCell(col++, val); + } + + //passed + if(assessmentEntry == null || assessmentEntry.getPassed() == null) { + col++; + } else { + String val = assessmentEntry.getPassed().booleanValue() + ? translator.translate("report.course.passed.passed") : translator.translate("report.course.passed.failed"); + row.addCell(col++, val); + } + + row.addCell(col++, launchDate, workbook.getStyles().getDateStyle()); + + if(certificate != null) { + row.addCell(col, certificate.getCreationDate(), workbook.getStyles().getDateStyle()); + } + } + } + + private boolean accept(AssessmentEntry assessmentEntry, CertificateLight certificate) { + if(reportParams.getCertificateStart() != null && (certificate == null || certificate.getCreationDate().before(reportParams.getCertificateStart()))) { + return false; + } + if(reportParams.getCertificateEnd() != null && (certificate == null || certificate.getCreationDate().after(reportParams.getCertificateEnd()))) { + return false; + } + if(reportParams.isOnlyPassed() && (assessmentEntry == null || assessmentEntry.getPassed() == null || !assessmentEntry.getPassed().booleanValue())) { + return false; + } + + List<With> withList = reportParams.getWith(); + if(withList == null || withList.isEmpty()) { + return true; + } + + for(With with:withList) { + if(with == With.withoutCertificate && certificate == null) { + return true; + } + if(with == With.validCertificate && (certificate != null && (certificate.getNextRecertificationDate() == null || certificate.getNextRecertificationDate().after(now)))) { + return true; + } + if(with == With.expiredCertificate && (certificate != null && (certificate.getNextRecertificationDate() != null || certificate.getNextRecertificationDate().before(now)))) { + return true; + } + } + + return false; + } + + private static class RepositoryEntryComparator implements Comparator<RepositoryEntry> { + + private final Collator collator; + + public RepositoryEntryComparator(Locale locale) { + collator = Collator.getInstance(locale); + } + + @Override + public int compare(RepositoryEntry o1, RepositoryEntry o2) { + String d1 = o1.getDisplayname(); + String d2 = o2.getDisplayname(); + int c = collator.compare(d1, d2); + if(c == 0) { + c = o1.getKey().compareTo(o2.getKey()); + } + return c; + } + } +} diff --git a/src/main/java/org/olat/course/certificate/ui/report/CertificatesReportParameters.java b/src/main/java/org/olat/course/certificate/ui/report/CertificatesReportParameters.java new file mode 100644 index 0000000000000000000000000000000000000000..95cf11c31ed375a4031ff908346d1dbcd7353917 --- /dev/null +++ b/src/main/java/org/olat/course/certificate/ui/report/CertificatesReportParameters.java @@ -0,0 +1,74 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.course.certificate.ui.report; + +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; + +/** + * + * Initial date: 26 juin 2020<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class CertificatesReportParameters { + + private final List<With> with; + private final boolean onlyPassed; + private final Date certificateStart; + private final Date certificateEnd; + + public CertificatesReportParameters(Date certificateStart, Date certificateEnd, List<With> with, boolean onlyPassed) { + this.with = with; + this.onlyPassed = onlyPassed; + this.certificateEnd = certificateEnd; + this.certificateStart = certificateStart; + } + + public List<With> getWith() { + return with; + } + + public boolean isOnlyPassed() { + return onlyPassed; + } + + public Date getCertificateStart() { + return certificateStart; + } + + public Date getCertificateEnd() { + return certificateEnd; + } + + public enum With { + withoutCertificate, + validCertificate, + expiredCertificate; + + public static List<With> values(Collection<String> keys) { + return keys.stream() + .map(With::valueOf) + .collect(Collectors.toList()); + } + } +} diff --git a/src/main/java/org/olat/course/certificate/ui/report/_content/reports.html b/src/main/java/org/olat/course/certificate/ui/report/_content/reports.html new file mode 100644 index 0000000000000000000000000000000000000000..2182de2f0f4e283fda31f874cf329698fbd6d476 --- /dev/null +++ b/src/main/java/org/olat/course/certificate/ui/report/_content/reports.html @@ -0,0 +1,3 @@ +<div class="o_info">$r.translate("report.explain")</div> +$r.render("search.cont") +$r.render("table") \ No newline at end of file diff --git a/src/main/java/org/olat/course/certificate/ui/report/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/certificate/ui/report/_i18n/LocalStrings_de.properties new file mode 100644 index 0000000000000000000000000000000000000000..85e5106b9f9bda504dbd05e90a2350069071e758 --- /dev/null +++ b/src/main/java/org/olat/course/certificate/ui/report/_i18n/LocalStrings_de.properties @@ -0,0 +1,22 @@ +#Thu Sep 03 11:09:03 CEST 2015 +admin.menu.report.certificates.title=Zertifikaten +admin.menu.report.certificates.title.alt=Zertifikaten +report=Report +report.certificates=Report Zertifikaten +report.explain=Liste von Zertifikaten +report.course.displayname=Module +report.course.passed=$org.olat.course.assessment\:table.header.passed +report.course.passed.failed=Nicht bestanden +report.course.passed.passed=Bestanden +report.initialLaunchDate=$org.olat.course.assessment\:table.header.initialLaunchDate +report.certificate=$org.olat.course.assessment\:table.header.certificate +search.course.passed=Kurs bestanden +search.dates=Zertifikat Datum +search.dates.separator=bis +search.empty=Es wurden keine Kurse gefunden, die ihren Kriterien entsprechen. +search.passed=Benutzer haben +search.text=Titel +search.with=Benutzer +search.without=ohne Zertifikat +search.with.valid=mit g\u00FCltigem Zertifikat +search.with.expired=mit abgelaufenem Zertifikat diff --git a/src/main/java/org/olat/course/certificate/ui/report/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/certificate/ui/report/_i18n/LocalStrings_en.properties new file mode 100644 index 0000000000000000000000000000000000000000..9edf7abde9840bb8c37e5ead93ee660e26c7ca41 --- /dev/null +++ b/src/main/java/org/olat/course/certificate/ui/report/_i18n/LocalStrings_en.properties @@ -0,0 +1,23 @@ +#Thu Sep 03 11:09:03 CEST 2015 +admin.menu.report.certificates.title=Certificates +admin.menu.report.certificates.title.alt=Certificates +report=Report +report.certificates=Report certificates +report.explain=List of certificates +report.course.displayname=Module +report.course.passed=$org.olat.course.assessment\:table.header.passed +report.course.passed.failed=Failed +report.course.passed.passed=Passed +report.initialLaunchDate=$org.olat.course.assessment\:table.header.initialLaunchDate +report.certificate=$org.olat.course.assessment\:table.header.certificate +search.course.passed=passed the course +search.dates=Certificate date +search.dates.separator=until +search.empty=No courses were found that met your criteria. +search.passed=Users have +search.text=Title +search.with=Users +search.without=without certificate +search.with.valid=with valid certificate +search.with.expired=with expired certificate + diff --git a/src/main/java/org/olat/user/propertyhandlers/_spring/userPropertiesContext.xml b/src/main/java/org/olat/user/propertyhandlers/_spring/userPropertiesContext.xml index f9a838cadc7383c6a38528a8958f4e7b91e4848b..83208014f973e706ccdfd11d7a80f9a49ea0b9cc 100644 --- a/src/main/java/org/olat/user/propertyhandlers/_spring/userPropertiesContext.xml +++ b/src/main/java/org/olat/user/propertyhandlers/_spring/userPropertiesContext.xml @@ -1698,6 +1698,22 @@ </bean> </entry> + <!-- User properties for certificates report --> + <entry key="org.olat.course.certificate.ui.report.CertificatesReportMediaResource"> + <bean class="org.olat.user.propertyhandlers.UserPropertyUsageContext"> + <property name="description" value="Properties for the certificates report" /> + <property name="propertyHandlers"> + <list> + <ref bean="userPropertyGenericText" /> + <ref bean="userPropertyLastName" /> + <ref bean="userPropertyFirstName" /> + <ref bean="userPropertyEmail" /> + <ref bean="userPropertyCountry" /> + </list> + </property> + </bean> + </entry> + <!-- Default configuration in case nothing else matches.