diff --git a/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/richText/RichTextElementComponent.java b/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/richText/RichTextElementComponent.java index 71e1470b1e354e1d83b4d3eaaad7372796b71a21..b8814ce068015e1ad0e5c8a01405ebbd02cfdfac 100644 --- a/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/richText/RichTextElementComponent.java +++ b/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/richText/RichTextElementComponent.java @@ -189,8 +189,9 @@ class RichTextElementComponent extends FormBaseComponentImpl implements Controll escapedUrl = escapeUrl(url); } - StringBuilder cmd = new StringBuilder(); - cmd.append("BTinyHelper.writeLinkSelectionToTiny('").append(escapedUrl).append("'"); + StringBuilder cmd = new StringBuilder(128); + String apo = escapedUrl.contains("'") ? "\"" : "'"; + cmd.append("BTinyHelper.writeLinkSelectionToTiny(").append(apo).append(escapedUrl).append(apo); if(urlChoosenEvent.getWidth() > 0) { cmd.append(",").append(urlChoosenEvent.getWidth()); } @@ -200,7 +201,7 @@ class RichTextElementComponent extends FormBaseComponentImpl implements Controll cmd.append(");"); JSCommand writeLinkSelectionToTiny = new JSCommand(cmd.toString()); - Windows.getWindows(ureq).getWindow(ureq).getWindowBackOffice().sendCommandTo(writeLinkSelectionToTiny); + Windows.getWindows(ureq).getWindow(ureq).getWindowBackOffice().sendCommandTo(writeLinkSelectionToTiny); } private String escapeUrl(String url) { 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 16698cec761bca1108ee72345824eb23c895cbc5..02c49ab77c6540f3a10d11c5988a913fa7d48df3 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 @@ -22,12 +22,14 @@ package org.olat.course.nodes.pf.manager; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; +import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Locale; @@ -44,12 +46,15 @@ import org.olat.core.gui.media.ServletUtil; import org.olat.core.gui.translator.Translator; import org.olat.core.id.Identity; import org.olat.core.logging.Tracing; +import org.olat.core.util.Formatter; import org.olat.core.util.StringHelper; import org.olat.core.util.Util; +import org.olat.core.util.io.ShieldOutputStream; import org.olat.core.util.io.SystemFileFilter; import org.olat.course.nodes.PFCourseNode; import org.olat.course.nodes.pf.ui.PFParticipantController; import org.olat.course.run.environment.CourseEnvironment; +import org.olat.repository.RepositoryEntry; import org.olat.user.UserManager; /** * @@ -107,6 +112,14 @@ public class FileSystemExport implements MediaResource { @Override public void prepare(HttpServletResponse hres) { + RepositoryEntry entry = courseEnv.getCourseGroupManager().getCourseEntry(); + String label = StringHelper.transformDisplayNameToFileSystemName(pfNode.getShortName() + "_" + entry.getDisplayname()) + + "_" + Formatter.formatDatetimeWithMinutes(new Date()) + + ".zip"; + String urlEncodedLabel = StringHelper.urlEncodeUTF8(label); + hres.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + urlEncodedLabel); + hres.setHeader("Content-Description", urlEncodedLabel); + try (ZipOutputStream zout = new ZipOutputStream(hres.getOutputStream())) { zout.setLevel(9); Path relPath = Paths.get(courseEnv.getCourseBaseContainer().getBasefile().getAbsolutePath(), @@ -132,8 +145,8 @@ public class FileSystemExport implements MediaResource { * @param translator * @throws IOException Signals that an I/O exception has occurred. */ - public static boolean fsToZip(ZipOutputStream zout, String zipPath, final Path sourceFolder, PFCourseNode pfNode, - List<Identity> identities, Translator translator) { + public static boolean fsToZip(final ZipOutputStream zout, String zipPath, final Path sourceFolder, PFCourseNode pfNode, + List<Identity> identities, final Translator translator) { if(StringHelper.containsNonWhitespace(zipPath)) { if(!zipPath.endsWith("/")) { @@ -143,7 +156,7 @@ public class FileSystemExport implements MediaResource { zipPath = ""; } - final String targetPath = zipPath + translator.translate("participant.folder") + "/"; + final String targetPath = zipPath; final UserManager userManager = CoreSpringFactory.getImpl(UserManager.class); Set<String> idKeys = new HashSet<>(); if (identities != null) { @@ -188,7 +201,7 @@ public class FileSystemExport implements MediaResource { 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); + copyFile(file, zout); zout.closeEntry(); } return FileVisitResult.CONTINUE; @@ -204,7 +217,6 @@ public class FileSystemExport implements MediaResource { return FileVisitResult.CONTINUE; } }); - zout.close(); return true; } catch (IOException e) { log.error("Unable to export zip",e); @@ -212,4 +224,12 @@ public class FileSystemExport implements MediaResource { } } + private static void copyFile(Path file, ZipOutputStream zout) { + try(OutputStream out= new ShieldOutputStream(zout)) { + Files.copy(file, zout); + } catch(Exception e) { + log.error("Cannot zip {}", file, e); + } + } + } \ No newline at end of file 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 9b4de7fbac4e687590c7221c0537923e98530285..61cb2f9675e0d114864c7547137c019160aa995b 100644 --- a/src/main/java/org/olat/ims/qti21/resultexport/QTI21ResultsExportMediaResource.java +++ b/src/main/java/org/olat/ims/qti21/resultexport/QTI21ResultsExportMediaResource.java @@ -63,7 +63,6 @@ import org.olat.core.gui.util.SyntheticUserRequest; import org.olat.core.gui.util.WindowControlMocker; import org.olat.core.id.Identity; import org.olat.core.id.Roles; -import org.olat.core.id.UserConstants; import org.olat.core.logging.Tracing; import org.olat.core.util.FileUtils; import org.olat.core.util.Formatter; @@ -300,12 +299,24 @@ public class QTI21ResultsExportMediaResource implements MediaResource { if(identity == null || identity.getStatus() == null || identity.getStatus().equals(Identity.STATUS_DELETED)) { continue; } - String lastNameOrAnoymous = identity.getUser().getLastName(); - if(!StringHelper.containsNonWhitespace(lastNameOrAnoymous)) { - lastNameOrAnoymous = "anonym"; + String nickname = identity.getUser().getNickName(); + String firstName = identity.getUser().getFirstName(); + String lastNameOrAnonymous = identity.getUser().getLastName(); + if(!StringHelper.containsNonWhitespace(lastNameOrAnonymous)) { + lastNameOrAnonymous = "anonym"; } - String lastname = StringHelper.transformDisplayNameToFileSystemName(lastNameOrAnoymous); - String idDir = exportFolderName + "/" + DATA + lastname + "_" + identity.getKey(); + String nameOrAnonymous = lastNameOrAnonymous; + if(StringHelper.containsNonWhitespace(firstName)) { + nameOrAnonymous += "_" + firstName; + } + if(StringHelper.containsNonWhitespace(nickname)) { + nameOrAnonymous += "_" + nickname; + } else { + nameOrAnonymous += "_" + identity.getKey(); + } + + String names = StringHelper.transformDisplayNameToFileSystemName(nameOrAnonymous); + String idDir = exportFolderName + "/" + DATA + names; idDir = idDir.endsWith(SEP) ? idDir : idDir + SEP; createZipDirectory(zout, idDir); @@ -323,8 +334,7 @@ public class QTI21ResultsExportMediaResource implements MediaResource { String linkToUser = idDir.replace(exportFolderName + "/", "") + "index.html"; String memberEmail = userManager.getUserDisplayEmail(identity, ureq.getLocale()); - AssessedMember member = new AssessedMember(identity.getUser().getProperty(UserConstants.NICKNAME, null), - lastNameOrAnoymous, identity.getUser().getFirstName(), + AssessedMember member = new AssessedMember(nickname, lastNameOrAnonymous, firstName, memberEmail, assessments.size(), passed, score, linkToUser); String singleUserInfoHTML = createResultListingHTML(assessments, assessmentDocuments, member); 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 ffeffa5b9c1c3d5114735cd9596b39d472c51d3e..ec085c0cccc04b7f8091311683ed2205ba8eafc2 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 @@ -21,13 +21,13 @@ <table id='oneUser' class='table table-striped table-hover'> <thead> <tr> - <th>$t.translate("table.all.username")</th> - <th>$t.translate("table.all.lastname")</th> - <th>$t.translate("table.all.firstname")</th> - <th>$t.translate("table.user.email")</th> - <th>$t.translate("table.all.node.score")</th> - <th>$t.translate("table.all.node.passed")</th> - <th>$t.translate("table.all.tries")</th> + <th scope="col">$t.translate("table.all.username")</th> + <th scope="col">$t.translate("table.all.lastname")</th> + <th scope="col">$t.translate("table.all.firstname")</th> + <th scope="col">$t.translate("table.user.email")</th> + <th scope="col">$t.translate("table.all.node.score")</th> + <th scope="col">$t.translate("table.all.node.passed")</th> + <th scope="col">$t.translate("table.all.tries")</th> </tr> </thead> <tbody> @@ -48,17 +48,17 @@ <table id='results' class='table table-striped table-hover'> <thead> <tr> - <th colspan="8">$t.translate("table.test.sessions")</th> + <th colspan="8" scope="col">$t.translate("table.test.sessions")</th> </tr> <tr> - <th>$t.translate("table.user.id")</th> - <th>$t.translate("table.user.date")</th> - <th>$t.translate("table.user.duration")</th> - <th>$t.translate("table.user.score")</th> - <th>$t.translate("table.user.manualScore")</th> - <th>$t.translate("table.user.finalScore")</th> - <th>$t.translate("table.all.passed")</th> - <th>$t.translate("button.show")</th> + <th scope="col">$t.translate("table.user.id")</th> + <th scope="col">$t.translate("table.user.date")</th> + <th scope="col">$t.translate("table.user.duration")</th> + <th scope="col">$t.translate("table.user.score")</th> + <th scope="col">$t.translate("table.user.manualScore")</th> + <th scope="col">$t.translate("table.user.finalScore")</th> + <th scope="col">$t.translate("table.all.passed")</th> + <th scope="col">$t.translate("button.show")</th> </tr> </thead> <tbody> @@ -84,7 +84,7 @@ <table id='assessmentDocuments' class='table table-striped table-hover'> <thead> <tr> - <th>$t.translate("assessment.docs")</th> + <th scope="col">$t.translate("assessment.docs")</th> </tr> </thead> <tbody> diff --git a/src/main/java/org/olat/ims/qti21/resultexport/_content/qtiUserlisting.html b/src/main/java/org/olat/ims/qti21/resultexport/_content/qtiUserlisting.html index bf87ab2ad0238e573010ebd3017368cc26ccc3e3..7a0ba6e2d4ae4a617c464395e13ad2605ff4e6c5 100644 --- a/src/main/java/org/olat/ims/qti21/resultexport/_content/qtiUserlisting.html +++ b/src/main/java/org/olat/ims/qti21/resultexport/_content/qtiUserlisting.html @@ -11,12 +11,12 @@ <table id='allUsers' class='table table-striped table-hover'> <thead> <tr> - <th>$t.translate("table.all.username")</th> - <th>$t.translate("table.all.lastname")</th> - <th>$t.translate("table.all.firstname")</th> - <th>$t.translate("table.all.score")</th> - <th>$t.translate("table.all.passed")</th> - <th>$t.translate("table.all.tries")</th> + <th scope="col">$t.translate("table.all.username")</th> + <th scope="col">$t.translate("table.all.lastname")</th> + <th scope="col">$t.translate("table.all.firstname")</th> + <th scope="col">$t.translate("table.all.score")</th> + <th scope="col">$t.translate("table.all.passed")</th> + <th scope="col">$t.translate("table.all.tries")</th> </tr> </thead> <tbody> diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/modules/bigbluebutton/ui/_i18n/LocalStrings_de.properties index 99230e77fb387b5aad6d177a46b5a6d9458d5545..5e2bc84a87e2ced0b6f99a4baf3f2dde38914440 100644 --- a/src/main/java/org/olat/modules/bigbluebutton/ui/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/modules/bigbluebutton/ui/_i18n/LocalStrings_de.properties @@ -51,8 +51,8 @@ error.end.past=Der Online-Termin kann nicht in der Vergangenheit geplant werden. error.first.date.in.past=Der erste Termin kann sich nicht in der Vergangenheit befinden. error.identifier.in.use=Name ist schon verwendet. Bitte einen anderen w\u00e4hlen. error.identifier.url.not.valid=Die URL ist nicht g\u00fcltig. Bitte Sonderzeichen wie $, ?, Leerschl\u00e4ge entfernen. -error.slides.size=Total Gr\u00f6sse den Pr\u00e4sentationsfolien muss kleiner als {0}MB sein. -error.slides.type=Pr\u00e4sentationsfolien sind limitiert zu Office und PDF Dokumenten und Bilder. +error.slides.size=Die Gesamtgr\u00F6\u00DFe aller Pr\u00E4sentationsdateien pro Meeting darf <strong>{0} MB</strong> nicht \u00FCberschreiten. +error.slides.type=Folgende Formate f\u00FCr Pr\u00E4sentationsfolien werden unterst\u00FCtzt: Office-Dokumente, PDF, jpeg, jpg, png. error.password=Passwort ist falsch. error.prefix=Ein Fehler ist aufgetreten\: error.same.day=Sie haben schon ein Meeting an diesem Tag geplant. @@ -157,7 +157,7 @@ servers.title=Server server.status.available=Verf\u00fcgbar server.status.offline=Scheint offline zu sein server.status.disabled=Abgeschaltet -slides.upload.limit=Max. Grösse den Pr\u00e4sentationsfolien (in MB) +slides.upload.limit=Limit aller Pr\u00E4sentationsdateien pro Meeting (MB) Max. table.header.actions=Aktionen table.header.available=Verf\u00fcgbarkeit table.header.breakout.meetings=\# Breakout diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/modules/bigbluebutton/ui/_i18n/LocalStrings_en.properties index cab812549adca2935e5514b1cbfa1d5fdb711eb0..10aca06b5b28f67641e778cb95452d8efe197a88 100644 --- a/src/main/java/org/olat/modules/bigbluebutton/ui/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/modules/bigbluebutton/ui/_i18n/LocalStrings_en.properties @@ -57,8 +57,8 @@ error.same.day=You already have a meeting planed at this date. error.server.exists=A server with this URL already exists. error.server.raw={1} <small>Key {0}</small> error.serverDisabled=Server is currently not available. -error.slides.size=Total size of the slides needs to be smaller than {0}MB. -error.slides.type=Slides are limited to office and PDF documents and images. +error.slides.size=The total size of all presentation files per meeting must not exceed <strong>{0} MB</strong>. +error.slides.type=The following formats are supported for presentation slides: Office documents, PDF, jpeg, jpg, png. error.start.after.end=The end date must not be before the start date. error.too.long.time=Time is too long. It is limited to {0} minutes. error.url.invalid=Invalid server URL @@ -158,7 +158,7 @@ server.status.available=Available server.status.offline=Seems to be offline server.status.disabled=Disabled servers.title=Servers -slides.upload.limit=Max. size of slides (in MB) +slides.upload.limit=Limit of all presentation files per meeting (MB) table.header.actions=Aktionen table.header.available=Availability table.header.breakout.meetings=\# Breakout diff --git a/src/main/java/org/olat/modules/bigbluebutton/ui/_i18n/LocalStrings_fr.properties b/src/main/java/org/olat/modules/bigbluebutton/ui/_i18n/LocalStrings_fr.properties index b4efc8fd11a28eb48d4dc8d01b95e7be6bd4d465..0563895e7be2d1bc758eaf3f73ba88e062e97467 100644 --- a/src/main/java/org/olat/modules/bigbluebutton/ui/_i18n/LocalStrings_fr.properties +++ b/src/main/java/org/olat/modules/bigbluebutton/ui/_i18n/LocalStrings_fr.properties @@ -58,7 +58,7 @@ error.server.exists=Un serveur avec cette URL existe d\u00E9j\u00E0. error.server.raw={1} <small>Cl\u00E9\: {0}</small> error.serverDisabled=Le serveur n'est pas disponible pour l'instant. error.slides.size=La taille totale des diapositives ne doit pas d\u00E9passer {0}MB. -error.slides.type=Les diapositives sont limit\u00E9s aux documents Office et PDF ainsi que les images. +error.slides.type=Les diapositives sont limit\u00E9s aux documents Office, PDF, jpeg, jpg, png. error.start.after.end=La date de fin du rendez-vous ne peut se trouver avant la date de d\u00E9but. error.too.long.time=Le temps est trop long. Il est limit\u00E9 \u00E0 {0} minutes. error.url.invalid=L'URL du serveur n'est pas valide. @@ -160,7 +160,7 @@ server.status.available=Disponible server.status.disabled=D\u00E9sactiv\u00E9 server.status.offline=Semble entre hors-ligne servers.title=Serveurs -slides.upload.limit=Taille max. des diapositives (en MB) +slides.upload.limit=Limite des fichiers de pr\u00E9sentation par meeting (MB) table.header.actions=Actions table.header.available=Disponibilit\u00E9 table.header.breakout.meetings=\# R\u00E9unions priv\u00E9es