diff --git a/src/main/java/org/olat/course/archiver/ArchiverMainController.java b/src/main/java/org/olat/course/archiver/ArchiverMainController.java index be25252b68ed6df2d1f3cc0ac7d2427ff70b5a88..994230984f5886853038208799c8f87f8eac4d67 100644 --- a/src/main/java/org/olat/course/archiver/ArchiverMainController.java +++ b/src/main/java/org/olat/course/archiver/ArchiverMainController.java @@ -52,6 +52,8 @@ import org.olat.course.nodes.CheckListCourseNode; import org.olat.course.nodes.DialogCourseNode; import org.olat.course.nodes.FOCourseNode; import org.olat.course.nodes.GTACourseNode; +import org.olat.course.nodes.IQTESTCourseNode; +import org.olat.course.nodes.PFCourseNode; import org.olat.course.nodes.ProjectBrokerCourseNode; import org.olat.course.nodes.ScormCourseNode; import org.olat.course.nodes.TACourseNode; @@ -78,6 +80,8 @@ public class ArchiverMainController extends MainLayoutBasicController { private static final String CMD_WIKIS = "wikis"; private static final String CMD_SCORM = "scorm"; private static final String CMD_CHECKLIST = "checklist"; + private static final String CMD_PARTICIPANTFOLDER = "participantfolder"; + private IArchiverCallback archiverCallback; @@ -228,6 +232,13 @@ public class ArchiverMainController extends MainLayoutBasicController { gtn.setAltText(translate("menu.dialogs.alt")); root.addChild(gtn); } + if (archiverCallback.mayArchiveParticipantFolder()) { + gtn = new GenericTreeNode(); + gtn.setTitle(translate("menu.participantfolder")); + gtn.setUserObject(CMD_PARTICIPANTFOLDER); + gtn.setAltText("menu.participantfolder.alt"); + root.addChild(gtn); + } if (archiverCallback.mayArchiveWikis()) { gtn = new GenericTreeNode(); gtn.setTitle(translate("menu.wikis")); @@ -250,6 +261,7 @@ public class ArchiverMainController extends MainLayoutBasicController { root.addChild(gtn); } + //add extension menues ExtManager extm = ExtManager.getInstance(); Class extensionPointMenu = this.getClass(); @@ -284,7 +296,7 @@ public class ArchiverMainController extends MainLayoutBasicController { contentCtr = new CourseQTIArchiveController(ureq, getWindowControl(), ores); main.setContent(contentCtr.getInitialComponent()); } else if (menuCommand.equals(CMD_SCOREACCOUNTING)) { - contentCtr = new ScoreAccountingArchiveController(ureq, getWindowControl(), ores); + contentCtr = new ScoreAccountingArchiveController(ureq, getWindowControl(), ores, new IQTESTCourseNode()); main.setContent(contentCtr.getInitialComponent()); } else if (menuCommand.equals(CMD_ARCHIVELOGFILES)) { contentCtr = new CourseLogsArchiveController(ureq, getWindowControl(), ores); @@ -313,7 +325,11 @@ public class ArchiverMainController extends MainLayoutBasicController { } else if (menuCommand.equals(CMD_CHECKLIST)) { contentCtr = new GenericArchiveController(ureq, getWindowControl(), ores, new CheckListCourseNode()); main.setContent(contentCtr.getInitialComponent()); + } else if (menuCommand.equals(CMD_PARTICIPANTFOLDER)) { + contentCtr = new GenericArchiveController(ureq, getWindowControl(), ores, new PFCourseNode()); + main.setContent(contentCtr.getInitialComponent()); } + listenTo(contentCtr); } } diff --git a/src/main/java/org/olat/course/archiver/FullAccessArchiverCallback.java b/src/main/java/org/olat/course/archiver/FullAccessArchiverCallback.java index d9252e6c9d9d15fda9eb85cca20d37a56ee0a842..82ee1c57c35ad9e23874ba69bbe70cf129960b4a 100644 --- a/src/main/java/org/olat/course/archiver/FullAccessArchiverCallback.java +++ b/src/main/java/org/olat/course/archiver/FullAccessArchiverCallback.java @@ -76,6 +76,11 @@ public class FullAccessArchiverCallback implements IArchiverCallback { public boolean mayArchiveChecklist() { return true; } + + @Override + public boolean mayArchiveParticipantFolder() { + return true; + } @Override public boolean mayArchiveProjectBroker() { diff --git a/src/main/java/org/olat/course/archiver/IArchiverCallback.java b/src/main/java/org/olat/course/archiver/IArchiverCallback.java index f90054ca929c4ea9511231a8890d5b8f583b0b38..22557ec956ff696f1c0d8c759490d6d760144bf0 100644 --- a/src/main/java/org/olat/course/archiver/IArchiverCallback.java +++ b/src/main/java/org/olat/course/archiver/IArchiverCallback.java @@ -69,6 +69,8 @@ public interface IArchiverCallback { public boolean mayArchiveChecklist(); + public boolean mayArchiveParticipantFolder(); + /** * @return true if user has rights to archive project-broker data */ 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 799e2dac78c75beb5aed1c428d2e38b5ba3fb9c6..7ba7306c49c331637f9c65c5a28e61e5efb1a08e 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 @@ -33,6 +33,8 @@ course.res.feedback=Die Datei <b>{0}</b> mit den Resultaten liegt in Ihrem pers\ course.res.intro=Klicken Sie den untenstehenden 'Start'-Knopf um Endresultate von Tests, Bewertungen und Aufgaben zu archivieren. course.res.title=Kursresultate dialog=Dateidiskussionen +iqtest=Testergebnisse +pf=Teilnehmer Ordner fo=Forums gta=Aufgaben und Gruppenaufgaben index.intro=Mit diesem Werkzeug k\u00F6nnen verschiedene Daten dieses OLAT-Kurses archiviert werden.<p> W\u00E4hlen Sie links im Menu einen Eintrag aus um zu beginnen. @@ -50,6 +52,8 @@ menu.archivelogfiles=Logfiles menu.archivelogfiles.alt=Administrator-, Kurs- und Userlogfiles menu.checklist=Checklisten menu.checklist.alt=Checklisten +menu.participantfolder=Teilnehmer Ordner +menu.participantfolder.alt=Teilnehmer Ordner menu.dialogs=Dateidiskussionen menu.dialogs.alt=Dateidiskussionen archivieren menu.forums=Foren @@ -70,7 +74,9 @@ menu.scorm=SCORM Resultate menu.scorm.alt=SCORM Resultate menu.wikis=Wikis menu.wikis.alt=Wikis archivieren +nodechoose.intro.pf=W\u00E4hlen Sie einen Kursbaustein aus, um dessen Ordnerinhalte zu archivieren. nodechoose.intro.cl=W\u00E4hlen Sie im folgenden Dialog den Checklisten Baustein aus, der ausgewertet werden soll +nodechoose.intro.iqtest=W\u00E4hlen Sie einen Kursbaustein aus, um dessen Testergebnisse zu archivieren. nodechoose.intro.dialog=W\u00E4hlen Sie einen Kursbaustein aus, um dessen Dateidiskussion zu archivieren. nodechoose.intro.fo=W\u00E4hlen Sie einen Kursbaustein aus, um dessen Forum zu archivieren. nodechoose.intro.gta=W\u00E4hlen Sie einen Kursbaustein aus, um dessen Gruppenaufgaben zu archivieren. @@ -79,9 +85,11 @@ nodechoose.intro.scorm=W\u00E4hlen Sie im folgenden Dialog den SCORM Baustein au 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. overview.nonodes.checklist=Dieser Kurs enth\u00E4lt keine Checkliste. +overview.nonodes.iqtest=No QTI overview.nonodes.dialog=Dieser Kurs enth\u00E4lt keine Dateidiskussionen. overview.nonodes.fo=Dieser Kurs enth\u00E4lt keine Foren overview.nonodes.gta=Dieser Kurs enth\u00E4lt keine Aufgabenkursbausteine. +overview.nonodes.pf=Dieser Kurs enth\u00E4lt keine Teilnehmer Ordner. overview.nonodes.projectbroker=Dieser Kurs enth\u00E4lt keinen Baustein "Themenvergabe". overview.nonodes.scorm=Dieser Kurs hat keinen SCORM Baustein. overview.nonodes.ta=Dieser Kurs enth\u00E4lt keine Aufgabenkursbausteine 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 a10e2168d93f916260f5f6b1f2ce8d32cedfc382..2d44657ca3986a39cdfe671620bf5cf71b6006ec 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 @@ -32,7 +32,9 @@ course.logs.title=Course log data course.res.feedback=You will find the file <b>{0}</b> along with some results in your personal folder of the private/archive/ section. course.res.intro=Click the 'Start' button below in order to archive final results from tests, assessments, and tasks. course.res.title=Course results +pf=Participant Folder dialog=File dialogs +iqtest=Test results fo=Forums gta=Tasks and group tasks index.intro=By means of this tool you can archive various data from your OLAT course.<p> Choose a topic from the menu on the left to start. @@ -50,6 +52,8 @@ menu.archivelogfiles=Log files menu.archivelogfiles.alt=Administrator, course, and user log files menu.checklist=Checklists menu.checklist.alt=Checklists +menu.participantfolder=Participant Folder +menu.participantfolder.alt=Participant Folder menu.dialogs=File dialogs menu.dialogs.alt=Archive file dialogs menu.forums=Forums @@ -70,7 +74,9 @@ menu.scorm=SCORM results menu.scorm.alt=SCORM results menu.wikis=Wikis menu.wikis.alt=Archive Wikis +nodechoose.intro.pf=Choose a course element to archive the content of the participant folder. nodechoose.intro.cl=Please select the checklist element you wish to archive. +nodechoose.intro.iqtest=Select a course element to archive its test results. nodechoose.intro.dialog=Select a course element to archive its file dialog. nodechoose.intro.fo=Select a course element to archive its forum. nodechoose.intro.gta=Choose a course element to archive the group tasks. @@ -82,6 +88,7 @@ overview.nonodes.checklist=This course does not contain any checklists. overview.nonodes.dialog=This course does not contain any file dialogs. overview.nonodes.fo=This course does not contain any forums. overview.nonodes.gta=This course does not contain any task elements. +overview.nonodes.pf=This course does not contain any participant folders. overview.nonodes.projectbroker=This course does not contain any topic assignments. overview.nonodes.scorm=This course does not contain any SCORM element. overview.nonodes.ta=This course does not contain any task elements. diff --git a/src/main/java/org/olat/course/nodes/PFCourseNode.java b/src/main/java/org/olat/course/nodes/PFCourseNode.java index 8a3bb9dc1f928e0de277871bf5ebd0550f56dd45..6be5b9fd952213f0e1b0d750f10be236d0fde88e 100644 --- a/src/main/java/org/olat/course/nodes/PFCourseNode.java +++ b/src/main/java/org/olat/course/nodes/PFCourseNode.java @@ -23,6 +23,8 @@ import java.io.File; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Date; +import java.util.Locale; +import java.util.zip.ZipOutputStream; import org.olat.core.CoreSpringFactory; import org.olat.core.commons.modules.bc.vfs.OlatRootFolderImpl; @@ -33,6 +35,7 @@ import org.olat.core.gui.components.stack.BreadcrumbPanel; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.generic.tabbable.TabbableController; +import org.olat.core.gui.translator.Translator; import org.olat.core.id.Identity; import org.olat.core.util.FileUtils; import org.olat.core.util.Util; @@ -43,6 +46,7 @@ import org.olat.course.ICourse; import org.olat.course.editor.CourseEditorEnv; import org.olat.course.editor.NodeEditController; import org.olat.course.editor.StatusDescription; +import org.olat.course.nodes.pf.manager.FileSystemExport; import org.olat.course.nodes.pf.manager.PFManager; import org.olat.course.nodes.pf.ui.PFEditController; import org.olat.course.nodes.pf.ui.PFPeekviewController; @@ -275,6 +279,16 @@ public class PFCourseNode extends AbstractAccessableCourseNode { FileUtils.deleteDirsAndFiles(root, true, true); } } + + @Override + public boolean archiveNodeData(Locale locale, ICourse course, ArchiveOptions options, ZipOutputStream exportStream, + String charset) { + CourseEnvironment courseEnv = course.getCourseEnvironment(); + Path sourceFolder = Paths.get(courseEnv.getCourseBaseContainer().getBasefile().getAbsolutePath(), + PFManager.FILENAME_PARTICIPANTFOLDER, getIdent()); + Translator translator = Util.createPackageTranslator(PFRunController.class, locale); + return FileSystemExport.fsToZip(exportStream, sourceFolder, this, null, translator); + } @Override public StatusDescription[] isConfigValid(CourseEditorEnv cev) { diff --git a/src/main/java/org/olat/course/nodes/pf/manager/FileSystemExport.java b/src/main/java/org/olat/course/nodes/pf/manager/FileSystemExport.java index f440cabb98938a7db35ce930ef75f02454dc7b31..47a8b55feb7765b9ae9cf319739d018a1805255d 100644 --- a/src/main/java/org/olat/course/nodes/pf/manager/FileSystemExport.java +++ b/src/main/java/org/olat/course/nodes/pf/manager/FileSystemExport.java @@ -19,6 +19,7 @@ */ package org.olat.course.nodes.pf.manager; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.nio.file.FileVisitResult; @@ -42,6 +43,7 @@ import org.olat.core.gui.translator.Translator; import org.olat.core.id.Identity; 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.course.nodes.PFCourseNode; import org.olat.course.nodes.pf.ui.PFRunController; @@ -101,14 +103,10 @@ public class FileSystemExport implements MediaResource { try (ZipOutputStream zout = new ZipOutputStream(hres.getOutputStream())) { zout.setLevel(9); - String pfolder = translator.translate("participant.folder") + "/"; - Path relPath = Paths.get(courseEnv.getCourseBaseContainer().getBasefile().getAbsolutePath(), PFManager.FILENAME_PARTICIPANTFOLDER, pfNode.getIdent()); - fsToZip(zout, relPath, pfolder); - - zout.close(); + fsToZip(zout, relPath, pfNode, identities, translator); } catch (IOException e) { log.error("", e); @@ -120,49 +118,84 @@ public class FileSystemExport implements MediaResource { // } - private void fsToZip(ZipOutputStream zout, final Path sourceFolder, final String targetPath) throws IOException { + /** + * Exports a given filesystem as Zip-Outputstream + * + * @param zout the Zip-Outputstream + * @param sourceFolder the source folder + * @param pfNode the PFCourseNode + * @param identities + * @param translator + * @throws IOException Signals that an I/O exception has occurred. + */ + public static boolean fsToZip(ZipOutputStream zout, final Path sourceFolder, PFCourseNode pfNode, + List<Identity> identities, Translator translator) { + String targetPath = translator.translate("participant.folder") + "/"; UserManager userManager = CoreSpringFactory.getImpl(UserManager.class); - Set<Long> idKeys = new HashSet<>(); - for (Identity identity : identities) { - idKeys.add(identity.getKey()); + Set<String> idKeys = new HashSet<>(); + if (identities != null) { + for (Identity identity : identities) { + idKeys.add(identity.getKey().toString()); + } + } else { + for (File file : sourceFolder.toFile().listFiles()) { + if (file.isDirectory()) { + idKeys.add(file.getName()); + } + } } - Files.walkFileTree(sourceFolder, new SimpleFileVisitor<Path>() { - private String containsID (String relPath) { - for (Long key : idKeys) { - if (relPath.contains(key.toString())) { - String exportFolderName = userManager.getUserDisplayName(key).replace(", ", "_") + "_" + key; - return relPath.replace(key.toString(), exportFolderName); + try { + Files.walkFileTree(sourceFolder, new SimpleFileVisitor<Path>() { + //contains identity check and changes identity key to user display name + private String containsID (String relPath) { + for (String key : idKeys) { + //additional check if folder is a identity-key (coming from fs) + if (relPath.contains(key) && StringHelper.isLong(key)) { + String exportFolderName = userManager.getUserDisplayName(Long.parseLong(key)).replace(", ", "_") + + "_" + key; + return relPath.replace(key.toString(), exportFolderName); + } } + return null; } - return null; - } - - private boolean boxesEnabled(String relPath) { - return pfNode.hasParticipantBoxConfigured() && relPath.contains(PFManager.FILENAME_DROPBOX) - || pfNode.hasCoachBoxConfigured() && relPath.contains(PFManager.FILENAME_RETURNBOX); - } - - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - String relPath = sourceFolder.relativize(file).toString(); - if ((relPath = containsID(relPath)) != null && boxesEnabled(relPath)) { - zout.putNextEntry(new ZipEntry(targetPath + relPath)); - Files.copy(file, zout); - zout.closeEntry(); + //checks module config and translates folder name + private String boxesEnabled(String relPath) { + if (pfNode.hasParticipantBoxConfigured() && relPath.contains(PFManager.FILENAME_DROPBOX)) { + return relPath.replace(PFManager.FILENAME_DROPBOX, translator.translate("drop.box")); + } else if (pfNode.hasCoachBoxConfigured() && relPath.contains(PFManager.FILENAME_RETURNBOX)) { + return relPath.replace(PFManager.FILENAME_RETURNBOX, translator.translate("return.box")); + } else { + return null; + } + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + String relPath = sourceFolder.relativize(file).toString(); + if ((relPath = containsID(relPath)) != null && (relPath = boxesEnabled(relPath)) != null) { + zout.putNextEntry(new ZipEntry(targetPath + relPath)); + Files.copy(file, zout); + zout.closeEntry(); + } + return FileVisitResult.CONTINUE; } - return FileVisitResult.CONTINUE; - } - @Override - public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { - String relPath = sourceFolder.relativize(dir).toString() + "/"; - if ((relPath = containsID(relPath)) != null && boxesEnabled(relPath)) { - zout.putNextEntry(new ZipEntry(targetPath + relPath)); - zout.closeEntry(); + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + String relPath = sourceFolder.relativize(dir).toString() + "/"; + if ((relPath = containsID(relPath)) != null && (relPath = boxesEnabled(relPath)) != null) { + zout.putNextEntry(new ZipEntry(targetPath + relPath)); + zout.closeEntry(); + } + return FileVisitResult.CONTINUE; } - return FileVisitResult.CONTINUE; - } - }); + }); + zout.close(); + return true; + } catch (IOException e) { + log.error("Unable to export zip",e); + return false; + } } } \ No newline at end of file