From 858edf5c4def032e6020e1468901581b599c89b1 Mon Sep 17 00:00:00 2001 From: srosse <none@none> Date: Fri, 19 Jan 2018 16:02:19 +0100 Subject: [PATCH] OO-3217: add archive excel for check list, add assessment documents to archives of course elements --- .../core/logging/activity/ActionObject.java | 3 +- .../activity/StringResourceableType.java | 4 + .../java/org/olat/course/CourseFactory.java | 8 +- .../ScoreAccountingArchiveController.java | 18 +- .../archiver/ScoreAccountingHelper.java | 43 ++++ .../archiver/_i18n/LocalStrings_de.properties | 1 + .../archiver/_i18n/LocalStrings_en.properties | 1 + .../assessment/AssessmentLoggingAction.java | 10 + .../manager/CourseAssessmentManagerImpl.java | 22 ++ .../course/nodes/CheckListCourseNode.java | 35 ++- .../org/olat/course/nodes/GTACourseNode.java | 40 ++- .../org/olat/course/nodes/MSCourseNode.java | 7 +- .../org/olat/course/nodes/TACourseNode.java | 20 +- .../cl/ui/CheckListAssessmentController.java | 2 +- .../nodes/cl/ui/CheckListExcelExport.java | 231 ++++++++++++++++++ .../nodes/cl/ui/CheckboxEditController.java | 11 +- .../cl/ui/_i18n/LocalStrings_de.properties | 5 + .../cl/ui/_i18n/LocalStrings_en.properties | 4 + .../cl/ui/_i18n/LocalStrings_fr.properties | 4 + .../cl/ui/_i18n/LocalStrings_it.properties | 2 + .../cl/ui/_i18n/LocalStrings_pt_BR.properties | 4 + .../gta/ui/GroupBulkDownloadResource.java | 14 +- .../course/nodes/ms/MSEditFormController.java | 7 +- .../course/statistic/AsyncExportManager.java | 3 +- .../manager/archive/QTI21ArchiveFormat.java | 4 +- .../QTI21ResultsExportMediaResource.java | 40 ++- .../resultexport/_content/qtiListing.html | 31 ++- .../_i18n/LocalStrings_de.properties | 3 +- .../_i18n/LocalStrings_en.properties | 1 + .../_i18n/LocalStrings_fr.properties | 1 + .../_i18n/LocalStrings_pt_BR.properties | 1 + .../ui/ParticipantListSortDelegate.java | 19 ++ 32 files changed, 533 insertions(+), 66 deletions(-) create mode 100644 src/main/java/org/olat/course/nodes/cl/ui/CheckListExcelExport.java diff --git a/src/main/java/org/olat/core/logging/activity/ActionObject.java b/src/main/java/org/olat/core/logging/activity/ActionObject.java index d1850b0ba63..08d3d67f336 100644 --- a/src/main/java/org/olat/core/logging/activity/ActionObject.java +++ b/src/main/java/org/olat/core/logging/activity/ActionObject.java @@ -98,6 +98,7 @@ public enum ActionObject { waitingperson, bulkassessment, lectures, - lecturesRollcall; + lecturesRollcall, + assessmentdocument; } diff --git a/src/main/java/org/olat/core/logging/activity/StringResourceableType.java b/src/main/java/org/olat/core/logging/activity/StringResourceableType.java index f34f873e0c9..2f282583c65 100644 --- a/src/main/java/org/olat/core/logging/activity/StringResourceableType.java +++ b/src/main/java/org/olat/core/logging/activity/StringResourceableType.java @@ -108,6 +108,9 @@ public enum StringResourceableType implements ILoggingResourceableType { /** the param part of the URI during a QTI test - equivalent to what was passed to IQDisplayController.logAudit before **/ qtiParams, + + /** assessment document **/ + assessmentDocument, /** Context sensitive help **/ csHelp, @@ -129,4 +132,5 @@ public enum StringResourceableType implements ILoggingResourceableType { /** Edubase book section */ bookSection; + } \ No newline at end of file diff --git a/src/main/java/org/olat/course/CourseFactory.java b/src/main/java/org/olat/course/CourseFactory.java index 8be9355f24c..8f186134620 100644 --- a/src/main/java/org/olat/course/CourseFactory.java +++ b/src/main/java/org/olat/course/CourseFactory.java @@ -716,9 +716,10 @@ public class CourseFactory { List<Identity> users = ScoreAccountingHelper.loadUsers(course.getCourseEnvironment()); List<AssessableCourseNode> nodes = ScoreAccountingHelper.loadAssessableNodes(course.getCourseEnvironment()); - String fileName = ExportUtil.createFileNameWithTimeStamp(course.getCourseTitle(), "xlsx"); - try(OutputStream out = new FileOutputStream(new File(exportDirectory, fileName))) { - ScoreAccountingHelper.createCourseResultsOverviewXMLTable(users, nodes, course, locale, out); + String fileName = ExportUtil.createFileNameWithTimeStamp(course.getCourseTitle(), "zip"); + try(OutputStream out = new FileOutputStream(new File(exportDirectory, fileName)); + ZipOutputStream zout = new ZipOutputStream(out)) { + ScoreAccountingHelper.createCourseResultsOverview(users, nodes, course, locale, zout); } catch(IOException e) { log.error("", e); } @@ -742,6 +743,7 @@ public class CourseFactory { // rework when backgroundjob infrastructure exists DBFactory.getInstance().intermediateCommit(); AsyncExportManager.getInstance().asyncArchiveCourseLogFiles(archiveOnBehalfOf, new Runnable() { + @Override public void run() { // that's fine, I dont need to do anything here }; diff --git a/src/main/java/org/olat/course/archiver/ScoreAccountingArchiveController.java b/src/main/java/org/olat/course/archiver/ScoreAccountingArchiveController.java index 86af1721840..7a38f0f02d9 100644 --- a/src/main/java/org/olat/course/archiver/ScoreAccountingArchiveController.java +++ b/src/main/java/org/olat/course/archiver/ScoreAccountingArchiveController.java @@ -30,6 +30,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.List; +import java.util.zip.ZipOutputStream; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; @@ -80,10 +81,7 @@ public class ScoreAccountingArchiveController extends BasicController { myPanel.setContent(myContent); } - /** - * @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest, - * org.olat.core.gui.components.Component, org.olat.core.gui.control.Event) - */ + @Override public void event(UserRequest ureq, Component source, Event event) { if (source == startButton) { doStartExport(); @@ -102,12 +100,13 @@ public class ScoreAccountingArchiveController extends BasicController { List<AssessableCourseNode> nodes = ScoreAccountingHelper.loadAssessableNodes(course.getCourseEnvironment()); String courseTitle = course.getCourseTitle(); - String fileName = ExportUtil.createFileNameWithTimeStamp(courseTitle, "xlsx"); + String fileName = ExportUtil.createFileNameWithTimeStamp(courseTitle, "zip"); // location for data export File exportDirectory = CourseFactory.getOrCreateDataExportDirectory(getIdentity(), courseTitle); File downloadFile = new File(exportDirectory, fileName); - try(OutputStream out=new FileOutputStream(downloadFile)) { - ScoreAccountingHelper.createCourseResultsOverviewXMLTable(users, nodes, course, getLocale(), out); + try(OutputStream fOut = new FileOutputStream(downloadFile); + ZipOutputStream zout = new ZipOutputStream(fOut)) { + ScoreAccountingHelper.createCourseResultsOverview(users, nodes, course, getLocale(), zout); } catch(IOException e) { logError("", e); } @@ -119,11 +118,8 @@ public class ScoreAccountingArchiveController extends BasicController { myPanel.setContent(vcFeedback); } - /** - * @see org.olat.core.gui.control.DefaultController#doDispose(boolean) - */ + @Override protected void doDispose() { // nothing to dispose } - } \ No newline at end of file diff --git a/src/main/java/org/olat/course/archiver/ScoreAccountingHelper.java b/src/main/java/org/olat/course/archiver/ScoreAccountingHelper.java index 70ba2d73896..9a5b432084b 100644 --- a/src/main/java/org/olat/course/archiver/ScoreAccountingHelper.java +++ b/src/main/java/org/olat/course/archiver/ScoreAccountingHelper.java @@ -25,6 +25,8 @@ package org.olat.course.archiver; +import java.io.File; +import java.io.IOException; import java.io.OutputStream; import java.text.DateFormat; import java.text.SimpleDateFormat; @@ -36,6 +38,8 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; import org.apache.commons.io.IOUtils; import org.olat.basesecurity.GroupRoles; @@ -45,8 +49,12 @@ import org.olat.core.id.Identity; import org.olat.core.id.IdentityEnvironment; import org.olat.core.id.context.BusinessControlFactory; import org.olat.core.id.context.ContextEntry; +import org.olat.core.logging.OLog; +import org.olat.core.logging.Tracing; import org.olat.core.util.StringHelper; import org.olat.core.util.Util; +import org.olat.core.util.ZipUtil; +import org.olat.core.util.io.ShieldOutputStream; import org.olat.core.util.openxml.OpenXMLWorkbook; import org.olat.core.util.openxml.OpenXMLWorksheet; import org.olat.core.util.openxml.OpenXMLWorksheet.Row; @@ -58,6 +66,8 @@ import org.olat.course.groupsandrights.CourseGroupManager; import org.olat.course.nodes.ArchiveOptions; import org.olat.course.nodes.AssessableCourseNode; import org.olat.course.nodes.CourseNode; +import org.olat.course.nodes.IQTESTCourseNode; +import org.olat.course.nodes.MSCourseNode; import org.olat.course.nodes.STCourseNode; import org.olat.course.run.environment.CourseEnvironment; import org.olat.course.run.scoring.ScoreAccounting; @@ -78,6 +88,39 @@ import org.olat.user.propertyhandlers.UserPropertyHandler; */ public class ScoreAccountingHelper { + private static final OLog log = Tracing.createLoggerFor(ScoreAccountingHelper.class); + + public static void createCourseResultsOverview(List<Identity> identities, List<AssessableCourseNode> nodes, ICourse course, Locale locale, ZipOutputStream zout) { + try(OutputStream out = new ShieldOutputStream(zout)) { + zout.putNextEntry(new ZipEntry("Course_results.xlsx")); + createCourseResultsOverviewXMLTable(identities, nodes, course, locale, out); + zout.closeEntry(); + } catch(IOException e) { + log.error("", e); + } + + for(AssessableCourseNode node:nodes) { + String dir = "Assessment_documents/" + StringHelper.transformDisplayNameToFileSystemName(node.getShortName()); + if(node instanceof IQTESTCourseNode + || node.getModuleConfiguration().getBooleanSafe(MSCourseNode.CONFIG_KEY_HAS_INDIVIDUAL_ASSESSMENT_DOCS, false)) { + for(Identity assessedIdentity:identities) { + List<File> assessmentDocuments = course.getCourseEnvironment() + .getAssessmentManager().getIndividualAssessmentDocuments(node, assessedIdentity); + if(assessmentDocuments != null && !assessmentDocuments.isEmpty()) { + String name = assessedIdentity.getUser().getLastName() + + "_" + assessedIdentity.getUser().getFirstName() + + "_" + assessedIdentity.getName(); + String userDirName = dir + "/" + StringHelper.transformDisplayNameToFileSystemName(name); + for(File document:assessmentDocuments) { + String path = userDirName + "/" + document.getName(); + ZipUtil.addFileToZip(path, document, zout); + } + } + } + } + } + } + /** * The results from assessable nodes are written to one row per user into an excel-sheet. An * assessable node will only appear if it is producing at least one of the diff --git a/src/main/java/org/olat/course/archiver/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/archiver/_i18n/LocalStrings_de.properties index ca82bf41db3..c7b3ac5e504 100644 --- a/src/main/java/org/olat/course/archiver/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/course/archiver/_i18n/LocalStrings_de.properties @@ -92,6 +92,7 @@ nodechoose.intro.projectbroker=W\u00E4hlen Sie einen Kursbaustein aus, um dessen nodechoose.intro.scorm=W\u00E4hlen Sie im folgenden Dialog den SCORM Baustein aus, der ausgewertet werden soll nodechoose.intro.ta=W\u00E4hlen Sie einen Kursbaustein aus, um dessen Aufgaben/L\u00F6sungen zu archivieren. nodechoose.intro.wiki=W\u00E4hlen Sie einen Kursbaustein aus, um dessen Wikis zu archivieren. +nodechoose.intro.checklist=W\u00E4hlen Sie einen Kursbaustein aus, um dessen Checkliste zu archivieren. overview.nonodes.checklist=Dieser Kurs enth\u00E4lt keine Checkliste. overview.nonodes.iqtest=Dieser Kurs enth\u00E4lt keine Tests. overview.nonodes.iqsurv=Dieser Kurs enth\u00E4lt keine Umfragen. diff --git a/src/main/java/org/olat/course/archiver/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/archiver/_i18n/LocalStrings_en.properties index 4f2f1db14f1..4f67233c035 100644 --- a/src/main/java/org/olat/course/archiver/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/course/archiver/_i18n/LocalStrings_en.properties @@ -92,6 +92,7 @@ nodechoose.intro.projectbroker=Please select a course element to archive its top nodechoose.intro.scorm=In the following dialog please select the SCORM element to be assessed. nodechoose.intro.ta=Select a course element in order to archive its tasks/solutions. nodechoose.intro.wiki=Select a course element to archive its Wiki. +nodechoose.intro.checklist=Select a course element to archive its checklists. overview.nonodes.iqtest=This course does not contain any tests. overview.nonodes.iqsurv=This course does not contain any surveys. overview.nonodes.checklist=This course does not contain any checklists. diff --git a/src/main/java/org/olat/course/assessment/AssessmentLoggingAction.java b/src/main/java/org/olat/course/assessment/AssessmentLoggingAction.java index 2c0c8d171af..686e6ec3473 100644 --- a/src/main/java/org/olat/course/assessment/AssessmentLoggingAction.java +++ b/src/main/java/org/olat/course/assessment/AssessmentLoggingAction.java @@ -80,6 +80,16 @@ public class AssessmentLoggingAction extends BaseLoggingAction { public static final ILoggingAction ASSESSMENT_COACHCOMMENT_UPDATED = new AssessmentLoggingAction(ActionType.admin, CrudAction.update, ActionVerb.edit, ActionObject.testcomment).setTypeList( new ResourceableTypeList().addMandatory(OlatResourceableType.course, OlatResourceableType.node, StringResourceableType.qtiCoachComment).addOptional(StringResourceableType.targetIdentity)); + + public static final ILoggingAction ASSESSMENT_DOCUMENT_ADDED = + new AssessmentLoggingAction(ActionType.admin, CrudAction.update, ActionVerb.add, ActionObject.assessmentdocument).setTypeList( + new ResourceableTypeList().addMandatory(OlatResourceableType.course, OlatResourceableType.node, StringResourceableType.assessmentDocument) + .addOptional(StringResourceableType.targetIdentity)); + public static final ILoggingAction ASSESSMENT_DOCUMENT_REMOVED = + new AssessmentLoggingAction(ActionType.admin, CrudAction.update, ActionVerb.remove, ActionObject.assessmentdocument).setTypeList( + new ResourceableTypeList().addMandatory(OlatResourceableType.course, OlatResourceableType.node, StringResourceableType.assessmentDocument) + .addOptional(StringResourceableType.targetIdentity)); + public static final ILoggingAction ASSESSMENT_BULK = new AssessmentLoggingAction(ActionType.admin, CrudAction.update, ActionVerb.edit, ActionObject.bulkassessment).setTypeList( new ResourceableTypeList().addMandatory(OlatResourceableType.course, OlatResourceableType.node)); diff --git a/src/main/java/org/olat/course/assessment/manager/CourseAssessmentManagerImpl.java b/src/main/java/org/olat/course/assessment/manager/CourseAssessmentManagerImpl.java index 1bc14f280c1..571328ff38d 100644 --- a/src/main/java/org/olat/course/assessment/manager/CourseAssessmentManagerImpl.java +++ b/src/main/java/org/olat/course/assessment/manager/CourseAssessmentManagerImpl.java @@ -237,6 +237,17 @@ public class CourseAssessmentManagerImpl implements AssessmentManager { int numOfDocs = docs == null ? 0 : docs.length; nodeAssessment.setNumberOfAssessmentDocuments(numOfDocs); assessmentService.updateAssessmentEntry(nodeAssessment); + + // node log + ICourse course = CourseFactory.loadCourse(cgm.getCourseEntry()); + UserNodeAuditManager am = course.getCourseEnvironment().getAuditManager(); + am.appendToUserNodeLog(courseNode, identity, assessedIdentity, "assessment document added: " + filename); + + // user activity logging + ThreadLocalUserActivityLogger.log(AssessmentLoggingAction.ASSESSMENT_DOCUMENT_ADDED, + getClass(), + LoggingResourceable.wrap(assessedIdentity), + LoggingResourceable.wrapNonOlatResource(StringResourceableType.assessmentDocument, "", StringHelper.stripLineBreaks(filename))); } catch (IOException e) { log.error("", e); } @@ -254,6 +265,17 @@ public class CourseAssessmentManagerImpl implements AssessmentManager { int numOfDocs = docs == null ? 0 : docs.length; nodeAssessment.setNumberOfAssessmentDocuments(numOfDocs); assessmentService.updateAssessmentEntry(nodeAssessment); + + // node log + ICourse course = CourseFactory.loadCourse(cgm.getCourseEntry()); + UserNodeAuditManager am = course.getCourseEnvironment().getAuditManager(); + am.appendToUserNodeLog(courseNode, identity, assessedIdentity, "assessment document removed: " + document.getName()); + + // user activity logging + ThreadLocalUserActivityLogger.log(AssessmentLoggingAction.ASSESSMENT_DOCUMENT_REMOVED, + getClass(), + LoggingResourceable.wrap(assessedIdentity), + LoggingResourceable.wrapNonOlatResource(StringResourceableType.assessmentDocument, "", StringHelper.stripLineBreaks(document.getName()))); } } diff --git a/src/main/java/org/olat/course/nodes/CheckListCourseNode.java b/src/main/java/org/olat/course/nodes/CheckListCourseNode.java index 5b8ac41f6e0..802d7c569cd 100644 --- a/src/main/java/org/olat/course/nodes/CheckListCourseNode.java +++ b/src/main/java/org/olat/course/nodes/CheckListCourseNode.java @@ -69,6 +69,7 @@ import org.olat.course.nodes.cl.model.Checkbox; import org.olat.course.nodes.cl.model.CheckboxList; import org.olat.course.nodes.cl.ui.AssessedIdentityCheckListController; import org.olat.course.nodes.cl.ui.CheckListEditController; +import org.olat.course.nodes.cl.ui.CheckListExcelExport; import org.olat.course.nodes.cl.ui.CheckListRunController; import org.olat.course.nodes.cl.ui.CheckListRunForCoachController; import org.olat.course.properties.CoursePropertyManager; @@ -203,6 +204,7 @@ public class CheckListCourseNode extends AbstractAccessableCourseNode implements /** * @see org.olat.course.nodes.CourseNode#isConfigValid() */ + @Override public StatusDescription isConfigValid() { if (oneClickStatusCache != null) { return oneClickStatusCache[0]; @@ -545,6 +547,9 @@ public class CheckListCourseNode extends AbstractAccessableCourseNode implements return true; } + /** + * Make an archive of all datas. + */ @Override public boolean archiveNodeData(Locale locale, ICourse course, ArchiveOptions options, ZipOutputStream exportStream, String charset) { @@ -557,7 +562,7 @@ public class CheckListCourseNode extends AbstractAccessableCourseNode implements CheckboxList list = (CheckboxList)config.get(CONFIG_KEY_CHECKBOX); CheckboxManager checkboxManager = CoreSpringFactory.getImpl(CheckboxManager.class); if(list != null && list.getList() != null) { - Set<String> usedNames = new HashSet<String>(); + Set<String> usedNames = new HashSet<>(); for(Checkbox checkbox:list.getList()) { VFSContainer dir = checkboxManager.getFileContainer(course.getCourseEnvironment(), this); @@ -574,6 +579,34 @@ public class CheckListCourseNode extends AbstractAccessableCourseNode implements } } } + + String filename = dirName + "/" + StringHelper.transformDisplayNameToFileSystemName(getShortName()); + new CheckListExcelExport(this, course, locale).exportAll(filename, exportStream); + } + + //assessment documents + if(config.getBooleanSafe(MSCourseNode.CONFIG_KEY_HAS_INDIVIDUAL_ASSESSMENT_DOCS, false)) { + List<AssessmentEntry> assessmentEntries = course.getCourseEnvironment() + .getAssessmentManager().getAssessmentEntries(this); + if(assessmentEntries != null && !assessmentEntries.isEmpty()) { + String assessmentDirName = dirName + "/Assessment_documents"; + for(AssessmentEntry assessmentEntry:assessmentEntries) { + Identity assessedIdentity = assessmentEntry.getIdentity(); + List<File> assessmentDocuments = course.getCourseEnvironment() + .getAssessmentManager().getIndividualAssessmentDocuments(this, assessedIdentity); + + String name = assessedIdentity.getUser().getLastName() + + "_" + assessedIdentity.getUser().getFirstName() + + "_" + assessedIdentity.getName(); + String userDirName = assessmentDirName + "/" + StringHelper.transformDisplayNameToFileSystemName(name); + if(assessmentDocuments != null && !assessmentDocuments.isEmpty()) { + for(File document:assessmentDocuments) { + String path = userDirName + "/" + document.getName(); + ZipUtil.addFileToZip(path, document, exportStream); + } + } + } + } } return super.archiveNodeData(locale, course, options, exportStream, charset); diff --git a/src/main/java/org/olat/course/nodes/GTACourseNode.java b/src/main/java/org/olat/course/nodes/GTACourseNode.java index 8f54bc4352e..1f21fa68a2f 100644 --- a/src/main/java/org/olat/course/nodes/GTACourseNode.java +++ b/src/main/java/org/olat/course/nodes/GTACourseNode.java @@ -20,6 +20,7 @@ package org.olat.course.nodes; import java.io.File; +import java.io.OutputStream; import java.util.ArrayList; import java.util.Collections; import java.util.Date; @@ -30,6 +31,7 @@ import java.util.Set; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; +import org.olat.basesecurity.GroupRoles; import org.olat.basesecurity.IdentityRef; import org.olat.core.CoreSpringFactory; import org.olat.core.commons.services.notifications.NotificationsManager; @@ -86,6 +88,7 @@ import org.olat.course.run.scoring.ScoreEvaluation; import org.olat.course.run.userview.NodeEvaluation; import org.olat.course.run.userview.UserCourseEnvironment; import org.olat.group.BusinessGroup; +import org.olat.group.BusinessGroupService; import org.olat.modules.ModuleConfiguration; import org.olat.modules.assessment.AssessmentEntry; import org.olat.modules.assessment.Role; @@ -540,9 +543,9 @@ public class GTACourseNode extends AbstractAccessableCourseNode implements Persi String courseTitle = course.getCourseTitle(); String fileName = ExportUtil.createFileNameWithTimeStamp(courseTitle, "xlsx"); List<AssessableCourseNode> nodes = Collections.<AssessableCourseNode>singletonList(this); - try { + try(OutputStream out = new ShieldOutputStream(exportStream)) { exportStream.putNextEntry(new ZipEntry(dirName + "/" + fileName)); - ScoreAccountingHelper.createCourseResultsOverviewXMLTable(users, nodes, course, locale, new ShieldOutputStream(exportStream)); + ScoreAccountingHelper.createCourseResultsOverviewXMLTable(users, nodes, course, locale, out); exportStream.closeEntry(); } catch (Exception e) { log.error("", e); @@ -633,6 +636,18 @@ public class GTACourseNode extends AbstractAccessableCourseNode implements Persi ZipUtil.addDirectoryToZip(correctionDirectory.toPath(), correctionDirName, exportStream); } } + + //assessment documents + if(config.getBooleanSafe(MSCourseNode.CONFIG_KEY_HAS_INDIVIDUAL_ASSESSMENT_DOCS, false)) { + List<File> assessmentDocuments = course.getCourseEnvironment() + .getAssessmentManager().getIndividualAssessmentDocuments(this, assessedIdentity); + if(assessmentDocuments != null && !assessmentDocuments.isEmpty()) { + for(File document:assessmentDocuments) { + String path = userDirName + "/" + (++flow) + "_assessment/" + document.getName(); + ZipUtil.addFileToZip(path, document, exportStream); + } + } + } } public void archiveNodeData(ICourse course, BusinessGroup businessGroup, TaskList taskList, String dirName, ZipOutputStream exportStream) { @@ -678,6 +693,27 @@ public class GTACourseNode extends AbstractAccessableCourseNode implements Persi ZipUtil.addDirectoryToZip(correctionDirectory.toPath(), correctionDirName, exportStream); } } + + //assessment documents for all participants of the group + if(config.getBooleanSafe(MSCourseNode.CONFIG_KEY_HAS_INDIVIDUAL_ASSESSMENT_DOCS, false)) { + List<Identity> assessedIdentities = CoreSpringFactory.getImpl(BusinessGroupService.class) + .getMembers(businessGroup, GroupRoles.participant.name()); + String assessmentDirName = groupDirName + "/" + (++flow) + "_assessment"; + for(Identity assessedIdentity:assessedIdentities) { + List<File> assessmentDocuments = course.getCourseEnvironment() + .getAssessmentManager().getIndividualAssessmentDocuments(this, assessedIdentity); + if(assessmentDocuments != null && !assessmentDocuments.isEmpty()) { + String name = assessedIdentity.getUser().getLastName() + + "_" + assessedIdentity.getUser().getFirstName() + + "_" + assessedIdentity.getName(); + String userDirName = assessmentDirName + "/" + StringHelper.transformDisplayNameToFileSystemName(name); + for(File document:assessmentDocuments) { + String path = userDirName + "/" + document.getName(); + ZipUtil.addFileToZip(path, document, exportStream); + } + } + } + } } @Override diff --git a/src/main/java/org/olat/course/nodes/MSCourseNode.java b/src/main/java/org/olat/course/nodes/MSCourseNode.java index 629641a2ba2..1848e93bd1d 100644 --- a/src/main/java/org/olat/course/nodes/MSCourseNode.java +++ b/src/main/java/org/olat/course/nodes/MSCourseNode.java @@ -167,6 +167,7 @@ public class MSCourseNode extends AbstractAccessableCourseNode implements Persis /** * @see org.olat.course.nodes.CourseNode#getReferencedRepositoryEntry() */ + @Override public RepositoryEntry getReferencedRepositoryEntry() { return null; } @@ -174,6 +175,7 @@ public class MSCourseNode extends AbstractAccessableCourseNode implements Persis /** * @see org.olat.course.nodes.CourseNode#needsReferenceToARepositoryEntry() */ + @Override public boolean needsReferenceToARepositoryEntry() { return false; } @@ -181,6 +183,7 @@ public class MSCourseNode extends AbstractAccessableCourseNode implements Persis /** * @see org.olat.course.nodes.CourseNode#isConfigValid() */ + @Override public StatusDescription isConfigValid() { /* * first check the one click cache @@ -402,9 +405,9 @@ public class MSCourseNode extends AbstractAccessableCourseNode implements Persis */ @Override public void updateUserCoachComment(String coachComment, UserCourseEnvironment userCourseEnvironment) { - AssessmentManager am = userCourseEnvironment.getCourseEnvironment().getAssessmentManager(); - Identity mySelf = userCourseEnvironment.getIdentityEnvironment().getIdentity(); if (coachComment != null) { + AssessmentManager am = userCourseEnvironment.getCourseEnvironment().getAssessmentManager(); + Identity mySelf = userCourseEnvironment.getIdentityEnvironment().getIdentity(); am.saveNodeCoachComment(this, mySelf, coachComment); } } diff --git a/src/main/java/org/olat/course/nodes/TACourseNode.java b/src/main/java/org/olat/course/nodes/TACourseNode.java index d047e2acc08..7b1b79fc9ef 100644 --- a/src/main/java/org/olat/course/nodes/TACourseNode.java +++ b/src/main/java/org/olat/course/nodes/TACourseNode.java @@ -27,6 +27,7 @@ package org.olat.course.nodes; import java.io.File; import java.io.IOException; +import java.io.OutputStream; import java.util.ArrayList; import java.util.Collections; import java.util.Date; @@ -904,10 +905,9 @@ public class TACourseNode extends GenericCourseNode implements PersistentAssessa String fileName = ExportUtil.createFileNameWithTimeStamp(courseTitle, "xlsx"); List<AssessableCourseNode> nodes = Collections.<AssessableCourseNode>singletonList(this); // write course results overview table to filesystem - try { + try(OutputStream out = new ShieldOutputStream(exportStream)) { exportStream.putNextEntry(new ZipEntry(dirName + "/" + fileName)); - ScoreAccountingHelper.createCourseResultsOverviewXMLTable(users, nodes, course, locale, - new ShieldOutputStream(exportStream)); + ScoreAccountingHelper.createCourseResultsOverviewXMLTable(users, nodes, course, locale, out); exportStream.closeEntry(); } catch (IOException e) { log.error("", e); @@ -960,6 +960,20 @@ public class TACourseNode extends GenericCourseNode implements PersistentAssessa } } } + + //assessment documents + if(getModuleConfiguration().getBooleanSafe(MSCourseNode.CONFIG_KEY_HAS_INDIVIDUAL_ASSESSMENT_DOCS, false)) { + for(Identity assessedIdentity:users) { + List<File> assessmentDocuments = course.getCourseEnvironment() + .getAssessmentManager().getIndividualAssessmentDocuments(this, assessedIdentity); + if(assessmentDocuments != null && !assessmentDocuments.isEmpty()) { + for(File document:assessmentDocuments) { + String path = dirName + "/assessment_documents/" + assessedIdentity.getName() + "/" + document.getName(); + ZipUtil.addFileToZip(path, document, exportStream); + } + } + } + } } return dataFound; } diff --git a/src/main/java/org/olat/course/nodes/cl/ui/CheckListAssessmentController.java b/src/main/java/org/olat/course/nodes/cl/ui/CheckListAssessmentController.java index c2a03d10908..b51cf3aeae4 100644 --- a/src/main/java/org/olat/course/nodes/cl/ui/CheckListAssessmentController.java +++ b/src/main/java/org/olat/course/nodes/cl/ui/CheckListAssessmentController.java @@ -364,7 +364,7 @@ public class CheckListAssessmentController extends FormBasicController implement List<CheckListAssessmentRow> dataViews = new ArrayList<>(); int numOfcheckbox = checkbox.size(); - Map<String,Integer> indexed = new HashMap<String,Integer>(); + Map<String,Integer> indexed = new HashMap<>(); for(int i=numOfcheckbox; i-->0; ) { indexed.put(checkbox.get(i).getCheckboxId(), new Integer(i)); } diff --git a/src/main/java/org/olat/course/nodes/cl/ui/CheckListExcelExport.java b/src/main/java/org/olat/course/nodes/cl/ui/CheckListExcelExport.java new file mode 100644 index 00000000000..7730d6ee15f --- /dev/null +++ b/src/main/java/org/olat/course/nodes/cl/ui/CheckListExcelExport.java @@ -0,0 +1,231 @@ +/** + * <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.nodes.cl.ui; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import org.olat.core.CoreSpringFactory; +import org.olat.core.gui.translator.Translator; +import org.olat.core.id.Identity; +import org.olat.core.id.User; +import org.olat.core.id.context.BusinessControlFactory; +import org.olat.core.id.context.ContextEntry; +import org.olat.core.logging.OLog; +import org.olat.core.logging.Tracing; +import org.olat.core.util.Util; +import org.olat.core.util.io.ShieldOutputStream; +import org.olat.core.util.openxml.OpenXMLWorkbook; +import org.olat.core.util.openxml.OpenXMLWorksheet; +import org.olat.core.util.openxml.OpenXMLWorksheet.Row; +import org.olat.core.util.openxml.workbookstyle.CellStyle; +import org.olat.course.ICourse; +import org.olat.course.nodes.CheckListCourseNode; +import org.olat.course.nodes.cl.CheckboxManager; +import org.olat.course.nodes.cl.model.AssessmentData; +import org.olat.course.nodes.cl.model.Checkbox; +import org.olat.course.nodes.cl.model.CheckboxList; +import org.olat.course.nodes.cl.model.DBCheck; +import org.olat.modules.ModuleConfiguration; +import org.olat.modules.assessment.AssessmentEntry; +import org.olat.user.UserManager; +import org.olat.user.propertyhandlers.UserPropertyHandler; + +/** + * + * This export all the data and it doesn't filter the datas + * based on their roles. + * + * Initial date: 19 janv. 2018<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class CheckListExcelExport { + + private static final OLog log = Tracing.createLoggerFor(CheckListExcelExport.class); + + private Translator translator; + private final ICourse course; + private final CheckListCourseNode courseNode; + private final List<UserPropertyHandler> userPropertyHandlers; + + private final UserManager userManager; + private final CheckboxManager checkboxManager; + + public CheckListExcelExport(CheckListCourseNode courseNode, ICourse course, Locale locale) { + this.courseNode = courseNode; + this.course = course; + + userManager = CoreSpringFactory.getImpl(UserManager.class); + checkboxManager = CoreSpringFactory.getImpl(CheckboxManager.class); + userPropertyHandlers = userManager.getUserPropertyHandlersFor(CheckListAssessmentController.USER_PROPS_ID, true); + + translator = Util.createPackageTranslator(CheckListAssessmentController.class, locale); + translator = userManager.getPropertyHandlerTranslator(translator); + } + + public void exportAll(String filename, ZipOutputStream exportStream) { + List<AssessmentData> dataList = checkboxManager.getAssessmentDatas(course, courseNode.getIdent(), null, null); + try(OutputStream out = new ShieldOutputStream(exportStream)) { + exportStream.putNextEntry(new ZipEntry(filename + ".xlsx")); + exportWorkbook(dataList, out); + exportStream.closeEntry(); + } catch (IOException e) { + log.error("", e); + } + } + + public void exportWorkbook(List<AssessmentData> dataList, OutputStream exportStream) { + try(OpenXMLWorkbook workbook = new OpenXMLWorkbook(exportStream, 1)) { + //headers + OpenXMLWorksheet exportSheet = workbook.nextWorksheet(); + exportSheet.setHeaderRows(1); + writeHeaders(exportSheet, workbook); + writeData(dataList, exportSheet); + } catch(Exception e) { + log.error("", e); + } + } + + private void writeHeaders(OpenXMLWorksheet exportSheet, OpenXMLWorkbook workbook) { + CellStyle headerStyle = workbook.getStyles().getHeaderStyle(); + //second header + int col = 0;//reset column counter + Row header2Row = exportSheet.newRow(); + String sequentialNumber = translator.translate("column.header.seqnum"); + header2Row.addCell(col++, sequentialNumber, headerStyle); + + for (UserPropertyHandler userPropertyHandler : userPropertyHandlers) { + if (userPropertyHandler == null) { + continue; + } + String header = translator.translate(userPropertyHandler.i18nFormElementLabelKey()); + header2Row.addCell(col++, header, headerStyle); + } + + // add other user and session information + header2Row.addCell(col++, translator.translate("column.header.homepage"), headerStyle); + + // course node points and passed + if(courseNode.hasScoreConfigured()) { + header2Row.addCell(col++, translator.translate("column.header.node.points"), headerStyle); + } + if(courseNode.hasPassedConfigured()) { + header2Row.addCell(col++, translator.translate("column.header.node.passed"), headerStyle); + } + + ModuleConfiguration config = courseNode.getModuleConfiguration(); + CheckboxList list = (CheckboxList)config.get(CheckListCourseNode.CONFIG_KEY_CHECKBOX); + if(list != null && list.getList() != null) { + List<Checkbox> checkboxList = list.getList(); + for(Checkbox checkbox:checkboxList) { + String boxTitle = checkbox.getTitle(); + header2Row.addCell(col++, boxTitle, headerStyle); + if(courseNode.hasScoreConfigured() && checkbox.getPoints() != null) { + header2Row.addCell(col++, translator.translate("column.header.points"), headerStyle); + } + } + } + } + + private void writeData(List<AssessmentData> dataList, OpenXMLWorksheet exportSheet) { + List<AssessmentEntry> entries = course.getCourseEnvironment().getAssessmentManager().getAssessmentEntries(courseNode); + Map<Identity,AssessmentEntry> entryMap = new HashMap<>(); + for(AssessmentEntry entry:entries) { + entryMap.put(entry.getIdentity(), entry); + } + + int num = 1; + for(AssessmentData data:dataList) { + AssessmentEntry entry = entryMap.get(data.getIdentity()); + writeDataRow(data, entry, num++, exportSheet); + } + } + + private void writeDataRow(AssessmentData data, AssessmentEntry entry, int num, OpenXMLWorksheet exportSheet) { + int col = 0; + Identity assessedIdentity = data.getIdentity(); + User assessedUser = assessedIdentity.getUser(); + + Row dataRow = exportSheet.newRow(); + dataRow.addCell(col++, num, null);//sequence number + + for (UserPropertyHandler userPropertyHandler : userPropertyHandlers) { + if (userPropertyHandler != null) { + String property = userPropertyHandler.getUserProperty(assessedUser, translator.getLocale()); + dataRow.addCell(col++, property, null); + } + } + + //homepage + ContextEntry ce = BusinessControlFactory.getInstance().createContextEntry(assessedIdentity); + String homepage = BusinessControlFactory.getInstance().getAsURIString(Collections.singletonList(ce), false); + dataRow.addCell(col++, homepage, null); + + // course node points and passed + if(courseNode.hasScoreConfigured()) { + if(entry != null && entry.getScore() != null) { + dataRow.addCell(col++, entry.getScore(), null); + } else { + col++; + } + } + if(courseNode.hasPassedConfigured()) { + if(entry != null && entry.getPassed() != null) { + dataRow.addCell(col++, entry.getPassed().toString(), null); + } else { + col++; + } + } + + ModuleConfiguration config = courseNode.getModuleConfiguration(); + CheckboxList list = (CheckboxList)config.get(CheckListCourseNode.CONFIG_KEY_CHECKBOX); + if(list != null && list.getList() != null && data.getChecks() != null) { + Map<String,DBCheck> checkMap = data.getChecks() + .stream().collect(Collectors.toMap(c -> c.getCheckbox().getCheckboxId(), c -> c)); + List<Checkbox> checkboxList = list.getList(); + for(Checkbox checkbox:checkboxList) { + String boxId = checkbox.getCheckboxId(); + DBCheck check = checkMap.get(boxId); + if(check != null && check.getChecked() != null && check.getChecked().booleanValue()) { + dataRow.addCell(col++, "x", null); + } else { + col++; + } + + if(courseNode.hasScoreConfigured() && checkbox.getPoints() != null) { + if(check != null && check.getScore() != null ) { + dataRow.addCell(col++, check.getScore(), null); + } else { + col++; + } + } + } + } + } +} diff --git a/src/main/java/org/olat/course/nodes/cl/ui/CheckboxEditController.java b/src/main/java/org/olat/course/nodes/cl/ui/CheckboxEditController.java index 598f08e17d3..1da0894938d 100644 --- a/src/main/java/org/olat/course/nodes/cl/ui/CheckboxEditController.java +++ b/src/main/java/org/olat/course/nodes/cl/ui/CheckboxEditController.java @@ -21,7 +21,7 @@ package org.olat.course.nodes.cl.ui; import java.io.File; import java.io.FileInputStream; -import java.io.FileNotFoundException; +import java.io.IOException; import java.io.InputStream; import org.olat.core.CoreSpringFactory; @@ -231,12 +231,11 @@ public class CheckboxEditController extends FormBasicController { String filename = fileEl.getUploadFileName(); checkbox.setFilename(filename); - try { - VFSContainer container = getFileContainer(); - VFSLeaf leaf = container.createChildLeaf(filename); - InputStream inStream = new FileInputStream(uploadedFile); + VFSContainer container = getFileContainer(); + VFSLeaf leaf = container.createChildLeaf(filename); + try(InputStream inStream = new FileInputStream(uploadedFile)) { VFSManager.copyContent(inStream, leaf); - } catch (FileNotFoundException e) { + } catch (IOException e) { logError("", e); } } diff --git a/src/main/java/org/olat/course/nodes/cl/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/nodes/cl/ui/_i18n/LocalStrings_de.properties index 63531f6e32d..7e1c9be78b5 100644 --- a/src/main/java/org/olat/course/nodes/cl/ui/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/course/nodes/cl/ui/_i18n/LocalStrings_de.properties @@ -15,6 +15,11 @@ checklist.update.assessment=Aktualisierung der Bewertungsinformationen aller Ben checklist.update.efficiencystatements=Aktualisierung der Leistungsnachweise aller Benutzer coach.desc=In der unten stehenden Listen finden Sie die von Ihnen betreuten Teilnehmer dieses Kurses. W\u00E4hlen Sie "$\:table.header.edit.checkbox" um die Checkbox-Auswahl oder Punkte eines Teilnehmers zu ver\u00E4ndern. coach.due.date.desc=Diese Checkliste ist mit einem Abgabedatum versehen. Als Betreuer sollten Sie \u00C4nderungen erst nach Ablauf des Abgabedatums vornehmen. +column.header.seqnum=Laufnummer +column.header.homepage=Homepage +column.header.node.passed=Kursbaustein bestanden +column.header.node.points=Kursbaustein Punkte +column.header.points=$\:box.points comment.nocomment=$org.olat.course.nodes.ms\:comment.nocomment comment.title=$org.olat.course.nodes.ms\:comment.title condition.accessibility.title=Zugang diff --git a/src/main/java/org/olat/course/nodes/cl/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/nodes/cl/ui/_i18n/LocalStrings_en.properties index b6027b44d7e..b4e906e64aa 100644 --- a/src/main/java/org/olat/course/nodes/cl/ui/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/course/nodes/cl/ui/_i18n/LocalStrings_en.properties @@ -15,6 +15,10 @@ checklist.update.assessment=Updating the assessment information of all course pa checklist.update.efficiencystatements=Updating of certificates of all users. coach.desc=In the list below you will find all participants of this course coached by you. Select "$\:table.header.edit.checkbox" in order to change check marks or the score of a participant. coach.due.date.desc=Please notice that this checklist has a deadline. As a coach, you should make changes only after the deadline expiration. +column.header.homepage=Home page +column.header.node.passed=Passed course element +column.header.node.points=Score course element +column.header.points=$\:box.points comment.nocomment=$org.olat.course.nodes.ms\:comment.nocomment comment.title=$org.olat.course.nodes.ms\:comment.title condition.accessibility.title=Access diff --git a/src/main/java/org/olat/course/nodes/cl/ui/_i18n/LocalStrings_fr.properties b/src/main/java/org/olat/course/nodes/cl/ui/_i18n/LocalStrings_fr.properties index 060217e5752..ba3aeee3911 100644 --- a/src/main/java/org/olat/course/nodes/cl/ui/_i18n/LocalStrings_fr.properties +++ b/src/main/java/org/olat/course/nodes/cl/ui/_i18n/LocalStrings_fr.properties @@ -15,6 +15,10 @@ checklist.update.assessment=Mettre \u00E0 jour les \u00E9valuations de tous les checklist.update.efficiencystatements=Mettre \u00E0 jour les certificats de tous les utilisateurs. coach.desc=Dans la liste ci-dessous vous trouverez tous les participants du cours que vous supervisez. S\u00E9lectionnez "$\:table.header.edit.checkbox" pour changer les cases \u00E0 cocher ou le score d'un participant. coach.due.date.desc=S'il vous pla\u00EEt noter que cette liste de contr\u00F4le fixe un d\u00E9lai. En tant que coach, vous ne devez effectuer des changements qu'apr\u00E8s la date limite d'expiration. +column.header.homepage=Page d'accueil +column.header.node.passed=Element de cours r\u00E9ussi +column.header.node.points=Points \u00E9l\u00E9ment de cours +column.header.points=$\:box.points comment.nocomment=$org.olat.course.nodes.ms\:comment.nocomment comment.title=$org.olat.course.nodes.ms\:comment.title condition.accessibility.title=Acc\u00E8s diff --git a/src/main/java/org/olat/course/nodes/cl/ui/_i18n/LocalStrings_it.properties b/src/main/java/org/olat/course/nodes/cl/ui/_i18n/LocalStrings_it.properties index 33b6ea4a5b5..210c111a50f 100644 --- a/src/main/java/org/olat/course/nodes/cl/ui/_i18n/LocalStrings_it.properties +++ b/src/main/java/org/olat/course/nodes/cl/ui/_i18n/LocalStrings_it.properties @@ -14,6 +14,8 @@ checklist.update.assessment=Aggiornare la valutazione per tutti i partecipanti a checklist.update.efficiencystatements=Aggiornare i certificati di tutti gli utenti. coach.desc=Nell'elenco sottostante troverai tutti i partecipanti al corso tutorati da te. Selezionare "$\:table.header.edit.checkbox" per modificare le checkbox o il punteggio di un partecipante. coach.due.date.desc=Prego notare che questa checklist ha una scadenza. Come tutore, dovresti effettuare modifiche solo dopo la scadenza. +column.header.score=$\:box.points +column.header.homepage=Home page comment.nocomment=$org.olat.course.nodes.ms\:comment.nocomment comment.title=$org.olat.course.nodes.ms\:comment.title condition.accessibility.title=Accesso diff --git a/src/main/java/org/olat/course/nodes/cl/ui/_i18n/LocalStrings_pt_BR.properties b/src/main/java/org/olat/course/nodes/cl/ui/_i18n/LocalStrings_pt_BR.properties index 1a7b369881d..c82ca1f961b 100644 --- a/src/main/java/org/olat/course/nodes/cl/ui/_i18n/LocalStrings_pt_BR.properties +++ b/src/main/java/org/olat/course/nodes/cl/ui/_i18n/LocalStrings_pt_BR.properties @@ -15,6 +15,10 @@ checklist.update.assessment=Atualizando as informa\u00E7\u00F5es de avalia\u00E7 checklist.update.efficiencystatements=Atualizando os certificados de todos os usu\u00E1rios. coach.desc=Na lista abaixo voc\u00EA vai encontrar todos os participantes deste curso treinados por voc\u00EA. Selecione "$\:table.header.edit.checkbox", a fim de alterar as marcas de sele\u00E7\u00E3o ou a pontua\u00E7\u00E3o de um participante. coach.due.date.desc=Por favor note que este Checklist tem um prazo. Como treinador, voc\u00EA deve fazer altera\u00E7\u00F5es somente ap\u00F3s a expira\u00E7\u00E3o do prazo. +column.header.homepage=Home page +column.header.node.passed=Elemento de curso aprovado +column.header.node.points=Pontua\u00E7\u00E3o do elemento do curso +column.header.points=$\:box.points comment.nocomment=$org.olat.course.nodes.ms\:comment.nocomment comment.title=$org.olat.course.nodes.ms\:comment.title condition.accessibility.title=Acesso diff --git a/src/main/java/org/olat/course/nodes/gta/ui/GroupBulkDownloadResource.java b/src/main/java/org/olat/course/nodes/gta/ui/GroupBulkDownloadResource.java index 4e42c89e406..cfc3efea9e7 100644 --- a/src/main/java/org/olat/course/nodes/gta/ui/GroupBulkDownloadResource.java +++ b/src/main/java/org/olat/course/nodes/gta/ui/GroupBulkDownloadResource.java @@ -20,6 +20,7 @@ package org.olat.course.nodes.gta.ui; import java.io.InputStream; +import java.io.OutputStream; import java.util.Collections; import java.util.Date; import java.util.List; @@ -29,7 +30,6 @@ import java.util.zip.ZipOutputStream; import javax.servlet.http.HttpServletResponse; -import org.apache.commons.io.IOUtils; import org.olat.basesecurity.GroupRoles; import org.olat.core.CoreSpringFactory; import org.olat.core.gui.media.MediaResource; @@ -113,10 +113,8 @@ public class GroupBulkDownloadResource implements MediaResource { String urlEncodedLabel = StringHelper.urlEncodeUTF8(label); hres.setHeader("Content-Disposition","attachment; filename*=UTF-8''" + urlEncodedLabel); hres.setHeader("Content-Description", urlEncodedLabel); - - ZipOutputStream zout = null; - try { - zout = new ZipOutputStream(hres.getOutputStream()); + + try(ZipOutputStream zout = new ZipOutputStream(hres.getOutputStream())) { zout.setLevel(9); ICourse course = CourseFactory.loadCourse(courseOres); GTAManager gtaManager = CoreSpringFactory.getImpl(GTAManager.class); @@ -127,9 +125,9 @@ public class GroupBulkDownloadResource implements MediaResource { String courseTitle = course.getCourseTitle(); String fileName = ExportUtil.createFileNameWithTimeStamp(courseTitle, "xlsx"); List<AssessableCourseNode> nodes = Collections.<AssessableCourseNode>singletonList(courseNode); - try { + try(OutputStream out = new ShieldOutputStream(zout)) { zout.putNextEntry(new ZipEntry(fileName)); - ScoreAccountingHelper.createCourseResultsOverviewXMLTable(assessableIdentities, nodes, course, locale, new ShieldOutputStream(zout)); + ScoreAccountingHelper.createCourseResultsOverviewXMLTable(assessableIdentities, nodes, course, locale, out); zout.closeEntry(); } catch (Exception e) { log.error("", e); @@ -142,8 +140,6 @@ public class GroupBulkDownloadResource implements MediaResource { } } catch (Exception e) { log.error("", e); - } finally { - IOUtils.closeQuietly(zout); } } diff --git a/src/main/java/org/olat/course/nodes/ms/MSEditFormController.java b/src/main/java/org/olat/course/nodes/ms/MSEditFormController.java index a9df554dd31..c49d01242ab 100644 --- a/src/main/java/org/olat/course/nodes/ms/MSEditFormController.java +++ b/src/main/java/org/olat/course/nodes/ms/MSEditFormController.java @@ -49,7 +49,7 @@ import org.olat.modules.ModuleConfiguration; public class MSEditFormController extends FormBasicController { /** Configuration this controller will modify. */ - private ModuleConfiguration modConfig; + private final ModuleConfiguration modConfig; /** whether score will be awarded or not. */ private MultipleSelectionElement scoreGranted; @@ -100,9 +100,8 @@ public class MSEditFormController extends FormBasicController { public MSEditFormController(UserRequest ureq, WindowControl wControl, ModuleConfiguration modConfig) { super(ureq, wControl, FormBasicController.LAYOUT_DEFAULT); this.modConfig = modConfig; - this.trueFalseKeys = new String[] { Boolean.TRUE.toString(), Boolean.FALSE.toString() }; - - this.passedTypeValues = new String[] { translate("form.passedtype.cutval"), translate("form.passedtype.manual") }; + trueFalseKeys = new String[] { Boolean.TRUE.toString(), Boolean.FALSE.toString() }; + passedTypeValues = new String[] { translate("form.passedtype.cutval"), translate("form.passedtype.manual") }; initForm(ureq); } diff --git a/src/main/java/org/olat/course/statistic/AsyncExportManager.java b/src/main/java/org/olat/course/statistic/AsyncExportManager.java index 9e910db52ea..dc97d933cbb 100644 --- a/src/main/java/org/olat/course/statistic/AsyncExportManager.java +++ b/src/main/java/org/olat/course/statistic/AsyncExportManager.java @@ -33,7 +33,6 @@ import org.olat.core.commons.services.taskexecutor.TaskExecutorManager; import org.olat.core.id.Identity; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; -import org.olat.core.manager.BasicManager; /** * Handles asynchronous aspects of log export - including limiting the @@ -42,7 +41,7 @@ import org.olat.core.manager.BasicManager; * Initial Date: 13.01.2010 <br> * @author Stefan */ -public class AsyncExportManager extends BasicManager { +public class AsyncExportManager { /** the logging object used in this class **/ private static final OLog log_ = Tracing.createLoggerFor(AsyncExportManager.class); diff --git a/src/main/java/org/olat/ims/qti21/manager/archive/QTI21ArchiveFormat.java b/src/main/java/org/olat/ims/qti21/manager/archive/QTI21ArchiveFormat.java index 672ac0ddbe5..ea3f5c7ce2a 100644 --- a/src/main/java/org/olat/ims/qti21/manager/archive/QTI21ArchiveFormat.java +++ b/src/main/java/org/olat/ims/qti21/manager/archive/QTI21ArchiveFormat.java @@ -230,9 +230,9 @@ public class QTI21ArchiveFormat { } public void export(String filename, ZipOutputStream exportStream) { - try { + try(OutputStream out = new ShieldOutputStream(exportStream)) { exportStream.putNextEntry(new ZipEntry(filename)); - exportWorkbook(new ShieldOutputStream(exportStream)); + exportWorkbook(out); exportStream.closeEntry(); } catch (IOException e) { log.error("", e); diff --git a/src/main/java/org/olat/ims/qti21/resultexport/QTI21ResultsExportMediaResource.java b/src/main/java/org/olat/ims/qti21/resultexport/QTI21ResultsExportMediaResource.java index ffd36428540..5fc79e9972d 100644 --- a/src/main/java/org/olat/ims/qti21/resultexport/QTI21ResultsExportMediaResource.java +++ b/src/main/java/org/olat/ims/qti21/resultexport/QTI21ResultsExportMediaResource.java @@ -73,6 +73,7 @@ import org.olat.core.util.UserSession; import org.olat.core.util.Util; import org.olat.core.util.WebappHelper; import org.olat.core.util.ZipUtil; +import org.olat.course.assessment.AssessmentManager; import org.olat.course.nodes.ArchiveOptions; import org.olat.course.nodes.QTICourseNode; import org.olat.course.run.environment.CourseEnvironment; @@ -266,7 +267,8 @@ public class QTI21ResultsExportMediaResource implements MediaResource { } private List<AssessedMember> createAssessedMembersDetail(ZipOutputStream zout) throws IOException { - List<AssessmentEntry> assessmentEntries = courseEnv.getAssessmentManager().getAssessmentEntries(courseNode); + AssessmentManager assessmentManager = courseEnv.getAssessmentManager(); + List<AssessmentEntry> assessmentEntries = assessmentManager.getAssessmentEntries(courseNode); Map<Identity,AssessmentEntry> assessmentEntryMap = new HashMap<>(); for(AssessmentEntry assessmentEntry:assessmentEntries) { assessmentEntryMap.put(assessmentEntry.getIdentity(), assessmentEntry); @@ -280,6 +282,8 @@ public class QTI21ResultsExportMediaResource implements MediaResource { //content of single assessed member List<ResultDetail> assessments = createResultDetail(identity, zout, idDir); + List<File> assessmentDocuments = assessmentManager.getIndividualAssessmentDocuments(courseNode, identity); + Boolean passed = null; BigDecimal score = null; if(assessmentEntryMap.containsKey(identity)) { @@ -294,9 +298,16 @@ public class QTI21ResultsExportMediaResource implements MediaResource { identity.getUser().getLastName(), identity.getUser().getFirstName(), memberEmail, assessments.size(), passed, score, linkToUser); - String singleUserInfoHTML = createResultListingHTML(assessments, member); - convertToZipEntry(zout, exportFolderName + "/" + DATA + identity.getName() + "/index.html", singleUserInfoHTML); + String userDataDir = exportFolderName + "/" + DATA + identity.getName(); + String singleUserInfoHTML = createResultListingHTML(assessments, assessmentDocuments, member); + convertToZipEntry(zout, userDataDir + "/index.html", singleUserInfoHTML); assessedMembers.add(member); + + //assessment documents + for(File document:assessmentDocuments) { + String assessmentDocDir = userDataDir + "/Assessment_documents/" + document.getName(); + ZipUtil.addFileToZip(assessmentDocDir, document, zout); + } } return assessedMembers; } @@ -316,7 +327,6 @@ public class QTI21ResultsExportMediaResource implements MediaResource { } private String createResultHTML(Component results) { - StringOutput sb = new StringOutput(32000); String pagePath = Util.getPackageVelocityRoot(this.getClass()) + "/qti21results.html"; URLBuilder ubu = new URLBuilder("auth", "1", "0"); //generate VelocityContainer and put Component @@ -326,13 +336,19 @@ public class QTI21ResultsExportMediaResource implements MediaResource { //render VelocityContainer to StringOutPut Renderer renderer = Renderer.getInstance(mainVC, translator, ubu, new RenderResult(), new EmptyGlobalSettings()); - VelocityRenderDecorator vrdec = new VelocityRenderDecorator(renderer, mainVC, sb); - mainVC.contextPut("r", vrdec); - renderer.render(sb, mainVC, null); - return sb.toString(); + try(StringOutput sb = new StringOutput(32000); + VelocityRenderDecorator vrdec = new VelocityRenderDecorator(renderer, mainVC, sb)) { + mainVC.contextPut("r", vrdec); + renderer.render(sb, mainVC, null); + vrdec.close(); + return sb.toString(); + } catch(Exception e) { + log.error("", e); + return ""; + } } - private String createResultListingHTML(List<ResultDetail> assessments, AssessedMember assessedMember) { + private String createResultListingHTML(List<ResultDetail> assessments, List<File> assessmentDocs, AssessedMember assessedMember) { // now put values to velocityContext VelocityContext ctx = new VelocityContext(); ctx.put("t", translator); @@ -340,8 +356,10 @@ public class QTI21ResultsExportMediaResource implements MediaResource { ctx.put("return", translator.translate("button.return")); ctx.put("assessments", assessments); ctx.put("assessedMember", assessedMember); - if (assessments.size() > 0) ctx.put("hasResults", Boolean.TRUE); - + ctx.put("assessmentDocs", assessmentDocs); + ctx.put("hasResults", Boolean.valueOf(!assessments.isEmpty())); + ctx.put("hasDocuments", Boolean.valueOf(!assessmentDocs.isEmpty())); + String template = FileUtils.load(QTI21ResultsExportMediaResource.class .getResourceAsStream("_content/qtiListing.html"), "utf-8"); diff --git a/src/main/java/org/olat/ims/qti21/resultexport/_content/qtiListing.html b/src/main/java/org/olat/ims/qti21/resultexport/_content/qtiListing.html index 3d8dee15a90..c18ced758e0 100644 --- a/src/main/java/org/olat/ims/qti21/resultexport/_content/qtiListing.html +++ b/src/main/java/org/olat/ims/qti21/resultexport/_content/qtiListing.html @@ -6,13 +6,11 @@ <title>$title</title> <script type="text/javascript" src="../../css/offline/qti/jquery-2.1.3.min.js" ></script> <script > - $.noConflict(); - jQuery(document).ready(function(){ - - jQuery("#results tbody tr").click(function (){ - jQuery(this).addClass("success"); - jQuery(this).siblings().removeClass("success"); - + $.noConflict(); + jQuery(document).ready(function(){ + jQuery("#results tbody tr").click(function (){ + jQuery(this).addClass("success"); + jQuery(this).siblings().removeClass("success"); });}); </script> </head> @@ -80,5 +78,24 @@ </table> </div> #end + + #if($hasDocuments) + <div> + <table id='assessmentDocuments' class='table table-striped table-hover'> + <thead> + <tr> + <th>$t.translate("assessment.docs")</th> + </tr> + </thead> + <tbody> + #foreach($assessmentDoc in $assessmentDocs) + <tr> + <td><a href='Assessment_documents/${assessmentDoc.getName()}' target='_blank'>${assessmentDoc.getName()}</td> + </tr> + #end + </tbody> + </table> + </div> + #end </body> </html> diff --git a/src/main/java/org/olat/ims/qti21/resultexport/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/ims/qti21/resultexport/_i18n/LocalStrings_de.properties index a8f71b2ceed..86e7f44be1f 100644 --- a/src/main/java/org/olat/ims/qti21/resultexport/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/ims/qti21/resultexport/_i18n/LocalStrings_de.properties @@ -25,4 +25,5 @@ table.user.manualScore=Manuell Punkte table.user.score=Punkte table.user.trial=Versuch table.test.sessions=Test Versuche -export.folder.name=Resultate \ No newline at end of file +export.folder.name=Resultate +assessment.docs=Bewertungsdokumente \ No newline at end of file diff --git a/src/main/java/org/olat/ims/qti21/resultexport/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/ims/qti21/resultexport/_i18n/LocalStrings_en.properties index 2f89c6200fd..85e5f0a0706 100644 --- a/src/main/java/org/olat/ims/qti21/resultexport/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/ims/qti21/resultexport/_i18n/LocalStrings_en.properties @@ -26,3 +26,4 @@ table.user.id=ID table.user.manualScore=Manual score table.user.score=Score table.user.trial=Attempt +assessment.docs=Assessment documents diff --git a/src/main/java/org/olat/ims/qti21/resultexport/_i18n/LocalStrings_fr.properties b/src/main/java/org/olat/ims/qti21/resultexport/_i18n/LocalStrings_fr.properties index 69e44960f76..13e7fab78b0 100644 --- a/src/main/java/org/olat/ims/qti21/resultexport/_i18n/LocalStrings_fr.properties +++ b/src/main/java/org/olat/ims/qti21/resultexport/_i18n/LocalStrings_fr.properties @@ -26,3 +26,4 @@ table.user.id=ID table.user.manualScore=Points manuels table.user.score=Points table.user.trial=Essai +assessment.docs=Documents d'\u00E9valuation diff --git a/src/main/java/org/olat/ims/qti21/resultexport/_i18n/LocalStrings_pt_BR.properties b/src/main/java/org/olat/ims/qti21/resultexport/_i18n/LocalStrings_pt_BR.properties index 0e08f7742fb..d62b7b809ed 100644 --- a/src/main/java/org/olat/ims/qti21/resultexport/_i18n/LocalStrings_pt_BR.properties +++ b/src/main/java/org/olat/ims/qti21/resultexport/_i18n/LocalStrings_pt_BR.properties @@ -26,3 +26,4 @@ table.user.id=ID table.user.manualScore=Pontua\u00E7\u00E3o manual table.user.score=Pontua\u00E7\u00E3o table.user.trial=Tentativa +assessment.docs=Documentos de avalia\u00E7\u00E3o diff --git a/src/main/java/org/olat/modules/lecture/ui/ParticipantListSortDelegate.java b/src/main/java/org/olat/modules/lecture/ui/ParticipantListSortDelegate.java index f40df7ce02e..fe9bc385401 100644 --- a/src/main/java/org/olat/modules/lecture/ui/ParticipantListSortDelegate.java +++ b/src/main/java/org/olat/modules/lecture/ui/ParticipantListSortDelegate.java @@ -1,3 +1,22 @@ +/** + * <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.modules.lecture.ui; import java.util.Locale; -- GitLab