diff --git a/pom.xml b/pom.xml index 31803666d7ec4278ce728496ca890fd6c1f993f1..ca31bb174d3e5e8fcd45815c372cb242cacd791c 100644 --- a/pom.xml +++ b/pom.xml @@ -71,7 +71,7 @@ <jackson.version>2.10.2</jackson.version> <org.mysql.version>5.1.46</org.mysql.version> <org.postgresql.version>42.2.9</org.postgresql.version> - <org.infinispan.version>10.1.0.Final</org.infinispan.version> + <org.infinispan.version>10.1.1.Final</org.infinispan.version> <lucene.version>7.7.0</lucene.version> <version.selenium>3.141.59</version.selenium> <version.drone>2.5.1</version.drone> diff --git a/src/main/java/org/olat/_spring/mainContext.xml b/src/main/java/org/olat/_spring/mainContext.xml index 7407d055155c7f314f1f447980b85f2deb055bc4..1a40e3a4b93f709ca8af057624cc3b4363a6c9a9 100644 --- a/src/main/java/org/olat/_spring/mainContext.xml +++ b/src/main/java/org/olat/_spring/mainContext.xml @@ -9,7 +9,7 @@ http://www.springframework.org/schema/context/spring-context.xsd"> <context:property-placeholder location="classpath:serviceconfig/olat.properties, classpath:olat.local.properties" system-properties-mode="OVERRIDE"/> - <context:component-scan base-package="org.olat.basesecurity,org.olat.note,org.olat.social,org.olat.commons.memberlist,org.olat.commons.info" /> + <context:component-scan base-package="org.olat.basesecurity,org.olat.note,org.olat.social,org.olat.commons.memberlist,org.olat.commons.info,org.olat.collaboration" /> <context:annotation-config /> <import resource="classpath:/org/olat/core/_spring/mainCorecontext.xml"/> diff --git a/src/main/java/org/olat/admin/sysinfo/LargeFilesController.java b/src/main/java/org/olat/admin/sysinfo/LargeFilesController.java index 9ae57138866463935e8689e131dedada0c8ea2fa..388d345e32de53d794754f5cb57fe1f2cc8a6c5f 100644 --- a/src/main/java/org/olat/admin/sysinfo/LargeFilesController.java +++ b/src/main/java/org/olat/admin/sysinfo/LargeFilesController.java @@ -37,6 +37,7 @@ import org.olat.admin.sysinfo.model.LargeFilesTableModel; import org.olat.admin.sysinfo.model.LargeFilesTableModel.LargeFilesTableColumns; import org.olat.core.commons.persistence.SortKey; import org.olat.core.commons.services.taskexecutor.TaskExecutorManager; +import org.olat.core.commons.services.vfs.VFSContextInfo; import org.olat.core.commons.services.vfs.VFSFilterKeys; import org.olat.core.commons.services.vfs.VFSMetadata; import org.olat.core.commons.services.vfs.VFSRepositoryModule; @@ -60,8 +61,8 @@ import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTable import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableDataModelFactory; import org.olat.core.gui.components.form.flexible.impl.elements.table.SelectionEvent; import org.olat.core.gui.components.link.Link; -import org.olat.core.gui.components.text.TextFactory; import org.olat.core.gui.components.util.KeyValues; +import org.olat.core.gui.components.velocity.VelocityContainer; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; @@ -160,7 +161,7 @@ public class LargeFilesController extends FormBasicController implements Extende downloadCountMin, revisionsCountMin, minSize); for(VFSMetadata file:files) { - LargeFilesTableContentRow contentRow = new LargeFilesTableContentRow(file); + LargeFilesTableContentRow contentRow = new LargeFilesTableContentRow(file, getLocale()); String[] path = contentRow.getPath().split("/"); @@ -188,7 +189,7 @@ public class LargeFilesController extends FormBasicController implements Extende downloadCountMin, revisionsCountMin, minSize); for(VFSRevision revision:revisions) { - LargeFilesTableContentRow contentRow = new LargeFilesTableContentRow(revision); + LargeFilesTableContentRow contentRow = new LargeFilesTableContentRow(revision, getLocale()); String[] path = contentRow.getPath().split("/"); @@ -306,6 +307,7 @@ public class LargeFilesController extends FormBasicController implements Extende columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(false, LargeFilesTableColumns.uuid)); columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(true, LargeFilesTableColumns.name, new LargeFilesNameCellRenderer())); columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(true, LargeFilesTableColumns.size, new LargeFilesSizeCellRenderer(vfsRepositoryModule))); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(true, LargeFilesTableColumns.context)); columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(true, LargeFilesTableColumns.path)); columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(false, LargeFilesTableColumns.age, new LargeFilesAgeCellRenderer())); @@ -401,9 +403,12 @@ public class LargeFilesController extends FormBasicController implements Extende LargeFilesTableContentRow row = (LargeFilesTableContentRow) link.getUserObject(); CalloutSettings settings = new CalloutSettings(false); + VFSContextInfo contextInfo = vfsRepositoryService.getContextInfoFor(row.getPath(), getLocale()); + VelocityContainer pathInfoVC = createVelocityContainer("large_files_path_info"); + pathInfoVC.contextPut("contextInfo", contextInfo); + pathInfoVC.contextPut("row", row); - pathInfoCalloutCtrl = new CloseableCalloutWindowController(ureq, getWindowControl(), - TextFactory.createTextComponentFromString("pathInfo", row.getPath(), "", true, null), link.getFormDispatchId(), "", true, "", settings); + pathInfoCalloutCtrl = new CloseableCalloutWindowController(ureq, getWindowControl(),pathInfoVC, link.getFormDispatchId(), "", true, "", settings); listenTo(pathInfoCalloutCtrl); pathInfoCalloutCtrl.activate(); } diff --git a/src/main/java/org/olat/admin/sysinfo/_content/large_files_path_info.html b/src/main/java/org/olat/admin/sysinfo/_content/large_files_path_info.html new file mode 100644 index 0000000000000000000000000000000000000000..da1fc91d28868db5768219757a8faa01c29ddb21 --- /dev/null +++ b/src/main/java/org/olat/admin/sysinfo/_content/large_files_path_info.html @@ -0,0 +1,7 @@ +<div class='o_nowrap'><strong>$r.translate("vfs.context.name"):</strong> $contextInfo.getContextTitle()</div> +<div class='o_nowrap'><strong>$r.translate("vfs.context.type"):</strong> $contextInfo.getContextTypeName()</div> +<div class='o_nowrap'><strong>$r.translate("largefiles.path"):</strong> $row.getPath() $row.getName()</div> +#if ($contextInfo.getContextUrl()) +<div class="o_nowrap"><strong>$r.translate("vfs.context.url"):</strong> <a href='$contextInfo.getContextUrl()'>$contextInfo.getContextUrl()</a></div> +#end + \ No newline at end of file diff --git a/src/main/java/org/olat/admin/sysinfo/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/admin/sysinfo/_i18n/LocalStrings_de.properties index 0e6fed3885a9c3a063adf25d87ffd52cad26bc1d..cd7e57e480b12239ee2c38e40948feb5f09c563c 100644 --- a/src/main/java/org/olat/admin/sysinfo/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/admin/sysinfo/_i18n/LocalStrings_de.properties @@ -88,6 +88,7 @@ java.threads.title=Gleichzeitige Threads largefiles.age=Alter largefiles.author=Autor largefiles.comment=Kommentar +largefiles.context=Kontext largefiles.createdat=Erstellt am largefiles.creator=Ersteller largefiles.downloads=Downloads @@ -252,3 +253,6 @@ table.header.trigger=Expression title.hibernate.statistics=Hibernate Datenbank Zugriff Statistik title.connection.statistics=Datenbank Connections Statistik usersession.title=Information \u00FCber Benutzer-Sessions +vfs.context.name=Name +vfs.context.type=Typ +vfs.context.url=URL diff --git a/src/main/java/org/olat/admin/sysinfo/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/admin/sysinfo/_i18n/LocalStrings_en.properties index 95c61f2abb6e91ba6b4ff5bf29ed211c9bc7a010..6cfeb16e9848162c8dbbea180c510a2e429141cd 100644 --- a/src/main/java/org/olat/admin/sysinfo/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/admin/sysinfo/_i18n/LocalStrings_en.properties @@ -88,6 +88,7 @@ java.threads.title=Current threads largefiles.age=Age largefiles.author=Author largefiles.comment=Comment +largefiles.context=Context largefiles.createdat=Created largefiles.creator=Creator largefiles.downloads=Downloads @@ -252,3 +253,7 @@ table.header.trigger=Expression title.connection.statistics=Database connections statistics title.hibernate.statistics=Hibernate database access statistics usersession.title=Information on user sessions +vfs.context.name=Name +vfs.context.type=Type +vfs.context.url=URL + diff --git a/src/main/java/org/olat/admin/sysinfo/model/LargeFilesTableContentRow.java b/src/main/java/org/olat/admin/sysinfo/model/LargeFilesTableContentRow.java index 7d7fcd18cbcb8b2c0fe1fff6674ff5cdac970f8c..684b46817d024c5f17f9ccf0e039235dbf6a5f10 100644 --- a/src/main/java/org/olat/admin/sysinfo/model/LargeFilesTableContentRow.java +++ b/src/main/java/org/olat/admin/sysinfo/model/LargeFilesTableContentRow.java @@ -21,10 +21,14 @@ package org.olat.admin.sysinfo.model; import java.util.Calendar; import java.util.Date; +import java.util.Locale; +import org.olat.core.CoreSpringFactory; import org.olat.core.commons.services.license.LicenseType; import org.olat.core.commons.services.vfs.VFSMetadata; +import org.olat.core.commons.services.vfs.VFSRepositoryService; import org.olat.core.commons.services.vfs.VFSRevision; +import org.olat.core.commons.services.vfs.impl.VFSContextInfoUnknownPathResolver; import org.olat.core.gui.components.form.flexible.elements.FormLink; import org.olat.core.id.Identity; import org.olat.core.util.WebappHelper; @@ -42,6 +46,7 @@ public class LargeFilesTableContentRow { private final Long size; private FormLink pathInfo; private final String path; + private final String context; private final Identity author; private final boolean revision; private final String fileType; @@ -65,12 +70,14 @@ public class LargeFilesTableContentRow { private final long revisionNr; private final String revisionComment; - public LargeFilesTableContentRow(VFSMetadata metadata) { + public LargeFilesTableContentRow(VFSMetadata metadata, Locale locale) { key = metadata.getKey(); name = metadata.getFilename(); size = metadata.getFileSize(); author = metadata.getAuthor(); path = metadata.getRelativePath(); + VFSRepositoryService vfsRepositoryService = CoreSpringFactory.getImpl(VFSRepositoryService.class); + context = vfsRepositoryService.getContextTypeFor(path, locale); fileType = WebappHelper.getMimeType(metadata.getFilename()) != null ? WebappHelper.getMimeType(metadata.getFilename()).split("/")[1] : "Unknown"; fileCategory = WebappHelper.getMimeType(metadata.getFilename()) != null ? WebappHelper.getMimeType(metadata.getFilename()).split("/")[0] : "Unknown"; revision = false; @@ -94,12 +101,14 @@ public class LargeFilesTableContentRow { revisionComment = metadata.getRevisionComment(); } - public LargeFilesTableContentRow(VFSRevision rev) { + public LargeFilesTableContentRow(VFSRevision rev, Locale locale) { key = rev.getMetadata().getKey(); name = rev.getFilename(); size = rev.getSize(); author = rev.getAuthor(); path = rev.getMetadata().getRelativePath(); + VFSRepositoryService vfsRepositoryService = CoreSpringFactory.getImpl(VFSRepositoryService.class); + context = vfsRepositoryService.getContextTypeFor(path, locale); revision = true; fileType = WebappHelper.getMimeType(rev.getFilename()) != null ? WebappHelper.getMimeType(rev.getFilename()).split("/")[1] : "Unknown"; fileCategory = WebappHelper.getMimeType(rev.getFilename()) != null ? WebappHelper.getMimeType(rev.getFilename()).split("/")[0] : "Unknown"; @@ -172,6 +181,13 @@ public class LargeFilesTableContentRow { return path; } + public String getContext() { + if (context == null || context.equals(VFSContextInfoUnknownPathResolver.UNKNOWN_TYPE)) { + return path; + } + return context; + } + public Boolean isRevision() { return revision; } diff --git a/src/main/java/org/olat/admin/sysinfo/model/LargeFilesTableModel.java b/src/main/java/org/olat/admin/sysinfo/model/LargeFilesTableModel.java index eb5c1d2d6703d4189095bb4a569f0132b347920e..943c83a61313d1ed4fa96d92ed5652d4cb6a5d92 100644 --- a/src/main/java/org/olat/admin/sysinfo/model/LargeFilesTableModel.java +++ b/src/main/java/org/olat/admin/sysinfo/model/LargeFilesTableModel.java @@ -69,6 +69,7 @@ implements SortableFlexiTableDataModel<LargeFilesTableContentRow> { case name: return row.getName(); case size: return row.getSize(); case path: return row.getPathInfo(); + case context: return row.getContext(); case author: return row.getAuthor() != null ? row.getAuthor().getUser().getFirstName() + " " + row.getAuthor().getUser().getLastName() : null; case revision: return row.isRevision(); case fileCategory: return row.getFileCategory(); @@ -115,6 +116,7 @@ implements SortableFlexiTableDataModel<LargeFilesTableContentRow> { name("largefiles.name"), size("largefiles.size"), path("largefiles.path"), + context("largefiles.context"), author("largefiles.author"), revision("largefiles.revision"), fileType("largefiles.filetype"), diff --git a/src/main/java/org/olat/collaboration/CollaborationToolsFolderVFSContextInfoResolver.java b/src/main/java/org/olat/collaboration/CollaborationToolsFolderVFSContextInfoResolver.java new file mode 100644 index 0000000000000000000000000000000000000000..878ddffd3efce42c19145b9eefbdff1dba578919 --- /dev/null +++ b/src/main/java/org/olat/collaboration/CollaborationToolsFolderVFSContextInfoResolver.java @@ -0,0 +1,114 @@ +/** + * <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.collaboration; + +import java.util.List; +import java.util.Locale; + +import org.apache.logging.log4j.Logger; +import org.olat.core.commons.services.vfs.VFSContextInfo; +import org.olat.core.commons.services.vfs.VFSContextInfoResolver; +import org.olat.core.commons.services.vfs.impl.VFSContextInfoImpl; +import org.olat.core.id.context.BusinessControlFactory; +import org.olat.core.id.context.ContextEntry; +import org.olat.core.logging.Tracing; +import org.olat.core.util.StringHelper; +import org.olat.core.util.Util; +import org.olat.group.BusinessGroup; +import org.olat.group.BusinessGroupService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Service; + +/** + * + * Initial date: 16 Jan 2020<br> + * + * @author gnaegi, gnaegi@frentix.com, http://www.frentix.com + * + */ +@Service +@Order(value=300) +public class CollaborationToolsFolderVFSContextInfoResolver implements VFSContextInfoResolver { + private static final Logger log = Tracing.createLoggerFor(CollaborationToolsFolderVFSContextInfoResolver.class); + + @Autowired + private BusinessGroupService bgService; + + + @Override + public String resolveContextTypeName(String vfsMetadataRelativePath, Locale locale) { + if (vfsMetadataRelativePath == null) { + return null; + } + String type = null; + // Is either a transcoding or the master video + if (vfsMetadataRelativePath.startsWith("cts")) { + if (vfsMetadataRelativePath.startsWith("cts/folders")) { + type = Util.createPackageTranslator(CollaborationToolsFolderVFSContextInfoResolver.class, locale).translate("vfs.context.cts.folders"); + } else if (vfsMetadataRelativePath.startsWith("cts/wikis")) { + type = Util.createPackageTranslator(CollaborationToolsFolderVFSContextInfoResolver.class, locale).translate("vfs.context.cts.wikis"); + } + } + return type; + } + + @Override + public VFSContextInfo resolveContextInfo(String vfsMetadataRelativePath, Locale locale) { + String type = resolveContextTypeName(vfsMetadataRelativePath, locale); + if (type == null) { + return null; + } + + // Try finding detail infos. Path looks something like this cts/[tooltype]/BusinessGroup/[groupid]/... + String name = "Unknown"; + String url = null; + + String[] path = vfsMetadataRelativePath.split("/"); + if (path.length < 4) { + return null; // no idea + } + String keyString = path[3]; + // lookup group resource + if (StringHelper.isLong(keyString)) { + + BusinessGroup businessGroup = bgService.loadBusinessGroup(Long.valueOf(keyString)); + + if (businessGroup == null) { + log.warn("No group found for id::" + keyString + " for path::" + vfsMetadataRelativePath); + } else { + name = businessGroup.getName(); + BusinessControlFactory bcf = BusinessControlFactory.getInstance(); + List<ContextEntry> entries = bcf.createCEListFromString("[BusinessGroup:" + businessGroup.getKey() + "]"); + if (path[1].equals("folders")) { + entries.addAll(bcf.createCEListFromString("[toolfolder:0]")); + // TODO: add other path elements to subdirectory + } else if (path[1].equals("wikis")) { + entries.addAll(bcf.createCEListFromString("[wiki:0]")); + } + url = bcf.getAsURIString(entries, true); + } + } else { + log.warn("Can not parse group id for path::{}", vfsMetadataRelativePath); + } + return new VFSContextInfoImpl(type, name, url); + } + +} diff --git a/src/main/java/org/olat/collaboration/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/collaboration/_i18n/LocalStrings_de.properties index d77c2b366231cc3bc5abc1f7fc85e21022cfeca5..fbdd32ab0629c30b7cfee9ab6e3de25b7e598952 100644 --- a/src/main/java/org/olat/collaboration/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/collaboration/_i18n/LocalStrings_de.properties @@ -22,3 +22,5 @@ folder.access.title=Ordner Schreibberechtigung konfigurieren news.access=Informationen Schreibberechtigung news.content=Information an Mitglieder selection=Auswahl +vfs.context.cts.folders=Gruppen-Ordner +vfs.context.cts.wikis=Gruppen-Wiki \ No newline at end of file diff --git a/src/main/java/org/olat/collaboration/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/collaboration/_i18n/LocalStrings_en.properties index f20d59f6f3dc57f7530a66173796104e9610a3be..98095422dd12a084de2858b16228d4a4e4edc7a3 100644 --- a/src/main/java/org/olat/collaboration/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/collaboration/_i18n/LocalStrings_en.properties @@ -22,3 +22,5 @@ folder.access.title=Configure folder write permission news.access=News write permission news.content=Information for members selection=Selection +vfs.context.cts.folders=Group folder +vfs.context.cts.wikis=Group wiki \ No newline at end of file diff --git a/src/main/java/org/olat/core/commons/fullWebApp/_content/main_3cols.html b/src/main/java/org/olat/core/commons/fullWebApp/_content/main_3cols.html index e68357d1e819ce09ad6006b17df0a89d3036eaaa..968b6814c05cc91415396dd3b1b7454d62e933d2 100644 --- a/src/main/java/org/olat/core/commons/fullWebApp/_content/main_3cols.html +++ b/src/main/java/org/olat/core/commons/fullWebApp/_content/main_3cols.html @@ -56,7 +56,7 @@ jQuery().ready(function() { if (!menuState.open) { jQuery('#o_main_left_content').show(); var l = jQuery('#o_main_left'); - l.transition({'x': '0', 'left': '0'}, duration, function() { + l.transition({'x': 0, 'y': 0, 'left': 0}, duration, function() { menuState.open = true; }); } @@ -66,7 +66,7 @@ jQuery().ready(function() { if (!menuState.docked && menuState.open) { var l = jQuery('#o_main_left'); jQuery('#o_main_left_content').hide(); - l.transition({'x': '-' + l.css('width'), 'left': '5px'}, duration, function() { + l.transition({'x': '-' + l.css('width'), 'y': 0, 'left': '5px'}, duration, function() { menuState.open = false; }); } diff --git a/src/main/java/org/olat/core/commons/services/vfs/VFSContextInfo.java b/src/main/java/org/olat/core/commons/services/vfs/VFSContextInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..d2003faa67ae755032fdab093808f142bfb902e0 --- /dev/null +++ b/src/main/java/org/olat/core/commons/services/vfs/VFSContextInfo.java @@ -0,0 +1,54 @@ +/** + * <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.core.commons.services.vfs; + +/** + * Describes the context of a VFS item for display purposes. The + * context info is localized for the current user. + * + * Initial date: 16 Jan 2020<br> + * + * @author gnaegi, gnaegi@frentix.com, http://www.frentix.com + * + */ +public interface VFSContextInfo { + + /** + * The type of the context / file, e.g. "Home folder" + * + * @return + */ + public String getContextTypeName(); + + /** + * The specific name, e.g. course element + * + * @return + */ + public String getContextTitle(); + + /** + * The URL to open this context within OpenOlat or NULL if not applicable + * + * @return + */ + public String getContextUrl(); + +} diff --git a/src/main/java/org/olat/core/commons/services/vfs/VFSContextInfoResolver.java b/src/main/java/org/olat/core/commons/services/vfs/VFSContextInfoResolver.java new file mode 100644 index 0000000000000000000000000000000000000000..b41933ece68707a0005fc6936ec1eaeed79e2e1d --- /dev/null +++ b/src/main/java/org/olat/core/commons/services/vfs/VFSContextInfoResolver.java @@ -0,0 +1,55 @@ +/** + * <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.core.commons.services.vfs; + +import java.util.Locale; + +/** + * + * Initial date: 16 Jan 2020<br> + * + * @author gnaegi, gnaegi@frentix.com, http://www.frentix.com + * + */ +public interface VFSContextInfoResolver { + + /** + * Resolve the localized type name for a give file path. This method is "light + * weight" without any db access, resolving is done by applying some regexp on + * the path. + * + * @param VFSMetadataRelativePath the relative file path + * @param locale The user locale + * @return The localized type name or NULL if not resolvable by this resolver + */ + public String resolveContextTypeName(String VFSMetadataRelativePath, Locale locale); + + + /** + * Resolve the localized context info for a give file path. This method will do + * db queries to build the context info. + * + * @param VFSMetadataRelativePath the relative file path + * @param locale The user locale + * @return The localized context info or NULL if not resolvable by this resolver + */ + public VFSContextInfo resolveContextInfo(String VFSMetadataRelativePath, Locale locale); + +} diff --git a/src/main/java/org/olat/core/commons/services/vfs/VFSRepositoryService.java b/src/main/java/org/olat/core/commons/services/vfs/VFSRepositoryService.java index 889c0278cdfc07679bc620c4c925eee6b9d9877a..d3c1c590f4a19cf46ed5f4b277d1a029c08ccfaa 100644 --- a/src/main/java/org/olat/core/commons/services/vfs/VFSRepositoryService.java +++ b/src/main/java/org/olat/core/commons/services/vfs/VFSRepositoryService.java @@ -23,6 +23,7 @@ import java.io.File; import java.io.InputStream; import java.util.Date; import java.util.List; +import java.util.Locale; import org.olat.core.commons.services.license.License; import org.olat.core.commons.services.vfs.model.VFSFileStatistics; @@ -45,6 +46,29 @@ public interface VFSRepositoryService { public VFSMetadata getMetadataFor(File file); + /** + * Resolve the context type for the given metadata. This method is "light + * weight" and does not trigger database queries + * + * @param relativePath The vfs metadada relative path for which the context is + * resolved + * @param locale The users locale + * @return A localized string or "Unknown" if not resolved + */ + public String getContextTypeFor(String relativePath, Locale locale); + + /** + * Resolve and build the detailed context information for the given metadata. + * This methods is "heavy weight" and might user multiple database queries to + * lookup all information. Use this only to lookup individual items. + * + * @param relativePath The vfs metadada relative path for which the context is + * resolved + * @param locale The users locale + * @return The resolved and localized context or the VFSContextInfoUnknown + */ + public VFSContextInfo getContextInfoFor(String relativePath, Locale locale); + public VFSMetadata getMetadata(VFSMetadataRef ref); public VFSMetadata getMetadataByUUID(String uuid); diff --git a/src/main/java/org/olat/core/commons/services/vfs/impl/VFSContextInfoImpl.java b/src/main/java/org/olat/core/commons/services/vfs/impl/VFSContextInfoImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..01f831d34d3777a8f4bfa51573278988d058ba60 --- /dev/null +++ b/src/main/java/org/olat/core/commons/services/vfs/impl/VFSContextInfoImpl.java @@ -0,0 +1,56 @@ +/** + * <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.core.commons.services.vfs.impl; + +import org.olat.core.commons.services.vfs.VFSContextInfo; + +/** + * + * Initial date: 16 Jan 2020<br> + * + * @author gnaegi, gnaegi@frentix.com, http://www.frentix.com + * + */ +public class VFSContextInfoImpl implements VFSContextInfo { + private String contextTypeName; + private String contextTitle; + private String contextUrl; + + public VFSContextInfoImpl(String contextTypeName, String contextTitle, String contextUrl) { + this.contextTypeName = contextTypeName; + this.contextTitle = contextTitle; + this.contextUrl = contextUrl; + } + + @Override + public String getContextTypeName() { + return contextTypeName; + } + + @Override + public String getContextTitle() { + return contextTitle; + } + + @Override + public String getContextUrl() { + return contextUrl; + } +} diff --git a/src/main/java/org/olat/core/commons/services/vfs/impl/VFSContextInfoUnknown.java b/src/main/java/org/olat/core/commons/services/vfs/impl/VFSContextInfoUnknown.java new file mode 100644 index 0000000000000000000000000000000000000000..09eec69e281c9373140224254c817c256bca85d3 --- /dev/null +++ b/src/main/java/org/olat/core/commons/services/vfs/impl/VFSContextInfoUnknown.java @@ -0,0 +1,34 @@ +/** + * <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.core.commons.services.vfs.impl; + +/** + * Default context resolver for contexts that are not yet implemented + * + * Initial date: 16 Jan 2020<br> + * @author gnaegi, gnaegi@frentix.com, http://www.frentix.com + * + */ +public class VFSContextInfoUnknown extends VFSContextInfoImpl { + + public VFSContextInfoUnknown(String reason) { + super("Unknown", reason, null); + } +} diff --git a/src/main/java/org/olat/core/commons/services/vfs/impl/VFSContextInfoUnknownPathResolver.java b/src/main/java/org/olat/core/commons/services/vfs/impl/VFSContextInfoUnknownPathResolver.java new file mode 100644 index 0000000000000000000000000000000000000000..c7cafc09e6e8fb9876534442b5133662850cd5c8 --- /dev/null +++ b/src/main/java/org/olat/core/commons/services/vfs/impl/VFSContextInfoUnknownPathResolver.java @@ -0,0 +1,55 @@ +/** + * <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.core.commons.services.vfs.impl; + +import java.util.Locale; + +import org.olat.core.commons.services.vfs.VFSContextInfo; +import org.olat.core.commons.services.vfs.VFSContextInfoResolver; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +/** + * Don't remove this, is used as default resolver when no other resolver matches + * + * Initial date: 16 Jan 2020<br> + * + * @author gnaegi, gnaegi@frentix.com, http://www.frentix.com + * + */ +@Component +@Order(Ordered.LOWEST_PRECEDENCE) +public class VFSContextInfoUnknownPathResolver implements VFSContextInfoResolver { + public static final String UNKNOWN_TYPE = "Unknown"; + public static final String UNKNOWN_CONTEXT = "Unknown Context"; + + + @Override + public String resolveContextTypeName(String VFSMetadataRelativePath, Locale locale) { + return UNKNOWN_TYPE; + } + + @Override + public VFSContextInfo resolveContextInfo(String VFSMetadataRelativePath, Locale locale) { + return new VFSContextInfoUnknown(UNKNOWN_CONTEXT); + } + +} diff --git a/src/main/java/org/olat/core/commons/services/vfs/manager/VFSRepositoryServiceImpl.java b/src/main/java/org/olat/core/commons/services/vfs/manager/VFSRepositoryServiceImpl.java index 20706f2a363a476a2d9a99c1d3b4b2600723fd3d..d6ff01b2a498c6ed37808b053dda13c71819b3c8 100644 --- a/src/main/java/org/olat/core/commons/services/vfs/manager/VFSRepositoryServiceImpl.java +++ b/src/main/java/org/olat/core/commons/services/vfs/manager/VFSRepositoryServiceImpl.java @@ -41,6 +41,7 @@ import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; @@ -61,6 +62,8 @@ import org.olat.core.commons.services.license.LicenseType; import org.olat.core.commons.services.thumbnail.CannotGenerateThumbnailException; import org.olat.core.commons.services.thumbnail.FinalSize; import org.olat.core.commons.services.thumbnail.ThumbnailService; +import org.olat.core.commons.services.vfs.VFSContextInfo; +import org.olat.core.commons.services.vfs.VFSContextInfoResolver; import org.olat.core.commons.services.vfs.VFSFilterKeys; import org.olat.core.commons.services.vfs.VFSMetadata; import org.olat.core.commons.services.vfs.VFSMetadataRef; @@ -70,6 +73,8 @@ import org.olat.core.commons.services.vfs.VFSRevision; import org.olat.core.commons.services.vfs.VFSRevisionRef; import org.olat.core.commons.services.vfs.VFSThumbnailMetadata; import org.olat.core.commons.services.vfs.VFSVersionModule; +import org.olat.core.commons.services.vfs.impl.VFSContextInfoUnknown; +import org.olat.core.commons.services.vfs.impl.VFSContextInfoUnknownPathResolver; import org.olat.core.commons.services.vfs.manager.MetaInfoReader.Thumbnail; import org.olat.core.commons.services.vfs.model.VFSFileStatistics; import org.olat.core.commons.services.vfs.model.VFSMetadataImpl; @@ -141,6 +146,8 @@ public class VFSRepositoryServiceImpl implements VFSRepositoryService, GenericEv private CoordinatorManager coordinatorManager; @Autowired private BaseSecurity securityManager; + // Autowired liste by setVfsContextInfoResolver() method + private List<VFSContextInfoResolver> vfsContextInfoResolver; @Override public void afterPropertiesSet() throws Exception { @@ -285,6 +292,34 @@ public class VFSRepositoryServiceImpl implements VFSRepositoryService, GenericEv return metadata; } + @Override + public String getContextTypeFor(String relativePath, Locale locale) { + if (relativePath == null) { + return "No path"; + } + for (VFSContextInfoResolver resolver : vfsContextInfoResolver) { + String contextType = resolver.resolveContextTypeName(relativePath, locale); + if (contextType != null) { + return contextType; + } + } + return VFSContextInfoUnknownPathResolver.UNKNOWN_TYPE; + } + + @Override + public VFSContextInfo getContextInfoFor(String relativePath, Locale locale) { + if (relativePath == null) { + new VFSContextInfoUnknown("No Relative Path"); + } + for (VFSContextInfoResolver resolver : vfsContextInfoResolver) { + VFSContextInfo contextInfo = resolver.resolveContextInfo(relativePath, locale); + if (contextInfo != null) { + return contextInfo; + } + } + return new VFSContextInfoUnknown(VFSContextInfoUnknownPathResolver.UNKNOWN_CONTEXT); + } + @Override public VFSItem getItemFor(VFSMetadata metadata) { if(metadata == null) return null; @@ -1540,4 +1575,15 @@ public class VFSRepositoryServiceImpl implements VFSRepositoryService, GenericEv public VFSThumbnailStatistics getThumbnailStats() { return statsDao.getThumbnailStats(); } + + + /** + * Set list of context info resolver. Used to autowire resolvers from various implementers + * @param vfsContextInfoResolver + */ + @Autowired + public void setVfsContextInfoResolvers(List<VFSContextInfoResolver> vfsContextInfoResolver) { + this.vfsContextInfoResolver = vfsContextInfoResolver; + } + } diff --git a/src/main/java/org/olat/course/CourseVFSContextInfoResolver.java b/src/main/java/org/olat/course/CourseVFSContextInfoResolver.java new file mode 100644 index 0000000000000000000000000000000000000000..8bdd820e5b27d6f0a89a961f4384351ffe01e820 --- /dev/null +++ b/src/main/java/org/olat/course/CourseVFSContextInfoResolver.java @@ -0,0 +1,116 @@ +/** + * <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; + +import java.util.List; +import java.util.Locale; + +import org.apache.logging.log4j.Logger; +import org.olat.core.commons.services.vfs.VFSContextInfo; +import org.olat.core.commons.services.vfs.impl.VFSContextInfoImpl; +import org.olat.core.helpers.Settings; +import org.olat.core.logging.Tracing; +import org.olat.core.util.StringHelper; +import org.olat.core.util.Util; +import org.olat.repository.RepositoryEntry; +import org.olat.repository.RepositoryEntryVFSContextInfoResolver; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Service; + +/** + * + * A specialized version of the repository entry resolver that catches the + * course resources. Since courses are special, they are not located in the + * standard repository directory but have their own. + * <br /> + * The class inheritence is not necessary, it just shows that this belongs to + * the repository. + * + * + * Initial date: 16 Jan 2020<br> + * + * @author gnaegi, gnaegi@frentix.com, http://www.frentix.com + * + */ +@Service +@Order(value=100) +public class CourseVFSContextInfoResolver extends RepositoryEntryVFSContextInfoResolver { + private static final Logger log = Tracing.createLoggerFor(CourseVFSContextInfoResolver.class); + + @Override + public String resolveContextTypeName(String vfsMetadataRelativePath, Locale locale) { + if (vfsMetadataRelativePath == null) { + return null; + } + String type = null; + // Is either a transcoding or the master video + if (vfsMetadataRelativePath.startsWith(PersistingCourseImpl.COURSE_ROOT_DIR_NAME)) { + if (vfsMetadataRelativePath.contains(PersistingCourseImpl.COURSEFOLDER)) { + type = Util.createPackageTranslator(CourseVFSContextInfoResolver.class, locale).translate("vfs.context.coursefolder"); + } else if (vfsMetadataRelativePath.contains("foldernodes")) { + type = Util.createPackageTranslator(CourseVFSContextInfoResolver.class, locale).translate("vfs.context.foldernodes"); + } + } + return type; + } + + @Override + public VFSContextInfo resolveContextInfo(String vfsMetadataRelativePath, Locale locale) { + String type = resolveContextTypeName(vfsMetadataRelativePath, locale); + if (type == null) { + return null; + } + + // Try finding detail infos + String name = "Unknown"; + String url = null; + + String[] path = vfsMetadataRelativePath.split("/"); + String keyString = path[1]; + if (StringHelper.isLong(keyString)) { + List<RepositoryEntry> repoEntries = repositoryService.searchByIdAndRefs(keyString); + if (repoEntries.size() != 1) { + log.warn("No olat resource resource found for id::{} for path::{}", keyString, vfsMetadataRelativePath); + } else { + RepositoryEntry re = repoEntries.get(0); + if (re == null) { + log.warn("No repository entry found for key::{} for path::{}", keyString, vfsMetadataRelativePath); + } else { + name = re.getDisplayname(); + url = Settings.getServerContextPathURI() + "/url/RepositoryEntry/" + re.getKey(); + + if (path.length >= 4 && PersistingCourseImpl.COURSEFOLDER.equals(path[2])) { + // Add direct path to course folder + // TODO: add other path elements to subdirectory + url += "/path%3D~~/0"; + } else if (path.length >= 4 && "foldernodes".equals(path[2])) { + // Add course node jump in if available + url += "/CourseNode" + path[3]; + } + } + } + } else { + log.warn("Can not parse repo entry id for path::{}", vfsMetadataRelativePath); + } + + return new VFSContextInfoImpl(type, name, url); + } + +} diff --git a/src/main/java/org/olat/course/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/_i18n/LocalStrings_de.properties index 21005030ac3e81e323790ec81c8cf3add44074ac..798b99d915b555ad9bc49a2a224ebf072a5637db 100644 --- a/src/main/java/org/olat/course/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/course/_i18n/LocalStrings_de.properties @@ -12,3 +12,5 @@ course.times.loaded=Aufrufe course.title=Titel current.active.user=Aktiv error.helpcourse.not.configured=In diesem System steht keine Hilfe zur Verf\u00FCgung. +vfs.context.coursefolder=Kurs Ablageordner +vfs.context.foldernodes=Kursbaustein Daten \ No newline at end of file diff --git a/src/main/java/org/olat/course/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/_i18n/LocalStrings_en.properties index 42e18705d2209d6660e770ee6de70b5f78773e6a..4bab508f42523d28ae1b31589b1d93694ef42523 100644 --- a/src/main/java/org/olat/course/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/course/_i18n/LocalStrings_en.properties @@ -12,3 +12,5 @@ course.times.loaded=Calls course.title=Title current.active.user=Active error.helpcourse.not.configured=This system does not provide any help feature. +vfs.context.coursefolder=Course folder +vfs.context.foldernodes=Course element data diff --git a/src/main/java/org/olat/home/PersonalFolderVFSContextInfoResolver.java b/src/main/java/org/olat/home/PersonalFolderVFSContextInfoResolver.java new file mode 100644 index 0000000000000000000000000000000000000000..7d45b7d61a136f3240dc6cac6a1846624188e8c1 --- /dev/null +++ b/src/main/java/org/olat/home/PersonalFolderVFSContextInfoResolver.java @@ -0,0 +1,92 @@ +/** + * <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.home; + +import java.util.Locale; + +import org.apache.logging.log4j.Logger; +import org.olat.basesecurity.BaseSecurityManager; +import org.olat.core.commons.services.vfs.VFSContextInfo; +import org.olat.core.commons.services.vfs.VFSContextInfoResolver; +import org.olat.core.commons.services.vfs.impl.VFSContextInfoImpl; +import org.olat.core.helpers.Settings; +import org.olat.core.id.Identity; +import org.olat.core.logging.Tracing; +import org.olat.core.util.Util; +import org.olat.user.UserManager; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +/** + * + * Initial date: 16 Jan 2020<br> + * + * @author gnaegi, gnaegi@frentix.com, http://www.frentix.com + * + */ +@Component +@Order(value=400) +public class PersonalFolderVFSContextInfoResolver implements VFSContextInfoResolver { + private static final Logger log = Tracing.createLoggerFor(PersonalFolderVFSContextInfoResolver.class); + + @Autowired + private BaseSecurityManager baseSecurityMgr; + + @Override + public String resolveContextTypeName(String vfsMetadataRelativePath, Locale locale) { + if (vfsMetadataRelativePath == null) { + return null; + } + String type = null; + // Is either a transcoding or the master video + if (vfsMetadataRelativePath.startsWith("homes")) { + type = Util.createPackageTranslator(PersonalFolderVFSContextInfoResolver.class, locale).translate("vfs.context.homes"); + } + return type; + } + + @Override + public VFSContextInfo resolveContextInfo(String vfsMetadataRelativePath, Locale locale) { + String type = resolveContextTypeName(vfsMetadataRelativePath, locale); + if (type == null) { + return null; + } + + // Try finding detail infos + String name = "Unknown"; + String url = null; + + String[] path = vfsMetadataRelativePath.split("/"); + String keyString = path[1]; + Identity identity = baseSecurityMgr.findIdentityByName(keyString); + if (identity == null) { + log.warn("No identity found for id::{} for path::{}", keyString, vfsMetadataRelativePath); + } else { + name = UserManager.getInstance().getUserDisplayName(identity); + url = Settings.getServerContextPathURI() + "/auth/HomeSite/" + identity.getKey() + "/userfolder/0"; + // TODO: add other path elements to subdirectory + + } + + return new VFSContextInfoImpl(type, name, url); + } + +} diff --git a/src/main/java/org/olat/home/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/home/_i18n/LocalStrings_de.properties index c5515fa8ee83cc648285a395315aaaeef1537b21..2ba24380fb79074588174006a0076aca9406ffe2 100644 --- a/src/main/java/org/olat/home/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/home/_i18n/LocalStrings_de.properties @@ -59,3 +59,5 @@ warn.session.was.killed=Sie hatten OpenOlat bereits in einem anderen Browser off welcome.header=Willkommen bei OpenOlat welcome.intro=<\!-- --> welcome.rss=Lesen Sie Ihre pers\u00F6nlichen News als RSS-Feed +vfs.context.homes=$\:menu.bc + diff --git a/src/main/java/org/olat/home/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/home/_i18n/LocalStrings_en.properties index 4251c1857e5793c1e747eb20934c24326ca08b8f..a8e9c582aee7e088dbaa12e412edff33ece20fbb 100644 --- a/src/main/java/org/olat/home/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/home/_i18n/LocalStrings_en.properties @@ -59,3 +59,4 @@ warn.session.was.killed=You are using OpenOlat in another browser. You cannot lo welcome.header=Welcome to OpenOlat welcome.intro=<\!-- --> welcome.rss=Read your personal news as RSS Feed +vfs.context.homes=$\:menu.bc diff --git a/src/main/java/org/olat/ims/qti21/model/xml/interactions/FIBAssessmentItemBuilder.java b/src/main/java/org/olat/ims/qti21/model/xml/interactions/FIBAssessmentItemBuilder.java index 461ce4e571e3ddc334c168f972d73a09230ed170..1bfa7136caa93a573f4c4c104a062249844ef8b3 100644 --- a/src/main/java/org/olat/ims/qti21/model/xml/interactions/FIBAssessmentItemBuilder.java +++ b/src/main/java/org/olat/ims/qti21/model/xml/interactions/FIBAssessmentItemBuilder.java @@ -499,6 +499,11 @@ public class FIBAssessmentItemBuilder extends AssessmentItemBuilder { this.scoreEvaluation = scoreEvaluation; } + /** + * This method only applies to score per answer. + * + * @return true if some variant hasn't the same score as the main response. + */ public boolean alternativesWithSpecificScore() { for(Map.Entry<String, AbstractEntry> entry:responseIdentifierToTextEntry.entrySet()) { AbstractEntry e = entry.getValue(); @@ -508,7 +513,7 @@ public class FIBAssessmentItemBuilder extends AssessmentItemBuilder { if(textEntry.getAlternatives() != null && !textEntry.getAlternatives().isEmpty()) { for(TextEntryAlternative alternative:textEntry.getAlternatives()) { double altScore = alternative.getScore(); - if(altScore >= 0.0d && score != null && score.doubleValue() != altScore) { + if(score != null && score.doubleValue() != altScore) { return true; } } diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/FIBScoreController.java b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/FIBScoreController.java index 48d06b47d9c7f3e4806e388fb96982534a3391eb..e7497eeedc0e8f85e329436ee59f0a59a989fbd4 100644 --- a/src/main/java/org/olat/ims/qti21/ui/editor/interactions/FIBScoreController.java +++ b/src/main/java/org/olat/ims/qti21/ui/editor/interactions/FIBScoreController.java @@ -285,7 +285,7 @@ public class FIBScoreController extends AssessmentItemRefEditorController implem private FIBAlternativeWrapper createAlternativeWrapper(TextEntryAlternative alternative) { String altPointElId = "points_" + counter++; - String altScoreStr = alternative.getScore() == -1.0d ? "" : Double.toString(alternative.getScore()); + String altScoreStr = Double.toString(alternative.getScore()); TextElement altPointEl = uifactory.addTextElement(altPointElId, null, 5, altScoreStr, scoreCont); altPointEl.setDisplaySize(5); altPointEl.setEnabled(!restrictedEdit && !readOnly); @@ -321,23 +321,25 @@ public class FIBScoreController extends AssessmentItemRefEditorController implem } private void updateScoresUI() { - boolean perAnswer = assessmentModeEl.isSelected(1) || assessmentModeEl.isSelected(2); - scoreCont.setVisible(perAnswer); - scoreCont.contextPut("withAlternatives", Boolean.valueOf(assessmentModeEl.isSelected(2))); - if(perAnswer) { + boolean perAnswer = assessmentModeEl.isSelected(1); + boolean perAnswerAndVariants = assessmentModeEl.isSelected(2); + scoreCont.setVisible(perAnswer || perAnswerAndVariants); + scoreCont.contextPut("withAlternatives", Boolean.valueOf(perAnswerAndVariants)); + if(perAnswer || perAnswerAndVariants) { for(FIBEntryWrapper wrapper:wrappers) { AbstractEntry entry = wrapper.getEntry(); Double points = entry.getScore(); if(points != null && points.doubleValue() == -1.0d) {//replace the all answers score wrapper.getEntry().setScore(1.0d); wrapper.getPointsEl().setValue("1.0"); + points = Double.valueOf(1.0d); } if(entry instanceof TextEntry && wrapper.getAlternatives() != null && !wrapper.getAlternatives().isEmpty()) { for(FIBAlternativeWrapper alternativeWrapper:wrapper.getAlternatives()) { TextEntryAlternative alternative = alternativeWrapper.getAlternative(); if(StringHelper.containsNonWhitespace(alternativeWrapper.getPointsEl().getValue())) { - if(points != null && points.doubleValue() >= 0.0d && alternative.getScore() == 1.0d) { + if(points != null && (alternative.getScore() == 1.0d || alternative.getScore() == -1.0d)) { alternative.setScore(points.doubleValue()); alternativeWrapper.getPointsEl().setValue(points.toString()); } diff --git a/src/main/java/org/olat/ims/qti21/ui/statistics/QTI21StatisticResourceResult.java b/src/main/java/org/olat/ims/qti21/ui/statistics/QTI21StatisticResourceResult.java index f28262382cda2bd3d187315253ddb2cfad972fd3..c10e0cc35bebe8ec86b75322271484922d78f0c7 100644 --- a/src/main/java/org/olat/ims/qti21/ui/statistics/QTI21StatisticResourceResult.java +++ b/src/main/java/org/olat/ims/qti21/ui/statistics/QTI21StatisticResourceResult.java @@ -33,6 +33,9 @@ import org.olat.core.gui.components.tree.TreeModel; import org.olat.core.gui.components.tree.TreeNode; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.control.generic.messages.MessageUIFactory; +import org.olat.core.gui.translator.Translator; +import org.olat.core.util.Util; import org.olat.course.nodes.CourseNodeConfiguration; import org.olat.course.nodes.CourseNodeFactory; import org.olat.course.nodes.QTICourseNode; @@ -47,6 +50,7 @@ import org.olat.ims.qti21.QTI21StatisticsManager; import org.olat.ims.qti21.model.QTI21QuestionType; import org.olat.ims.qti21.model.QTI21StatisticSearchParams; import org.olat.ims.qti21.model.xml.QtiNodesExtractor; +import org.olat.ims.qti21.ui.AssessmentTestDisplayController; import org.olat.repository.RepositoryEntry; import uk.ac.ed.ph.jqtiplus.node.item.AssessmentItem; @@ -359,6 +363,12 @@ public class QTI21StatisticResourceResult implements StatisticResourceResult { private Controller createAssessmentItemController(UserRequest ureq, WindowControl wControl, AssessmentItemRef assessmentItemRef, String sectionTitle, boolean printMode) { ResolvedAssessmentItem resolvedAssessmentItem = resolvedAssessmentTest.getResolvedAssessmentItem(assessmentItemRef); + if(resolvedAssessmentItem == null || resolvedAssessmentItem.getItemLookup() == null) { + Translator translator = Util.createPackageTranslator(AssessmentTestDisplayController.class, ureq.getLocale()); + String text = translator.translate("error.assessment.item.missing"); + Controller errorCtrl = MessageUIFactory.createErrorMessage(ureq, wControl, "", text); + return TitledWrapperHelper.getWrapper(ureq, wControl, errorCtrl, courseNode, "o_icon_error"); + } Controller ctrl = new QTI21AssessmentItemStatisticsController(ureq, wControl, assessmentItemRef, resolvedAssessmentItem, sectionTitle, this, withFilter, printMode); diff --git a/src/main/java/org/olat/modules/curriculum/restapi/CurriculumElementTypeVO.java b/src/main/java/org/olat/modules/curriculum/restapi/CurriculumElementTypeVO.java index 9e73c82f7eb27ec292f05c34a3a45bce1d271024..4e57b733aa0d5335765247dc42aefa2e6fdd2c82 100644 --- a/src/main/java/org/olat/modules/curriculum/restapi/CurriculumElementTypeVO.java +++ b/src/main/java/org/olat/modules/curriculum/restapi/CurriculumElementTypeVO.java @@ -21,6 +21,7 @@ package org.olat.modules.curriculum.restapi; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlRootElement; import org.olat.modules.curriculum.CurriculumElementType; @@ -57,7 +58,18 @@ public class CurriculumElementTypeVO { "copy(all)", "delete(all)"}) private String managedFlagsString; + @Schema(required = false, description = "Enable or disable the calendars aggregation", allowableValues = { + "enabled", + "disabled", + "inherited"}) + @XmlAttribute(name="calendars", required=false) private String calendars; + @Schema(required = false, description = "Enable or disable the lecture block overview and aggregation", allowableValues = { + "enabled", + "disabled", + "inherited"}) + @XmlAttribute(name="lectures", required=false) + private String lectures; public CurriculumElementTypeVO() { // @@ -73,6 +85,7 @@ public class CurriculumElementTypeVO { vo.setExternalId(type.getExternalId()); vo.setManagedFlagsString(CurriculumElementTypeManagedFlag.toString(type.getManagedFlags())); vo.setCalendars(type.getCalendars().name()); + vo.setLectures(type.getLectures().name()); return vo; } @@ -138,5 +151,13 @@ public class CurriculumElementTypeVO { public void setCalendars(String calendars) { this.calendars = calendars; - } + } + + public String getLectures() { + return lectures; + } + + public void setLectures(String lectures) { + this.lectures = lectures; + } } diff --git a/src/main/java/org/olat/modules/curriculum/restapi/CurriculumElementTypesWebService.java b/src/main/java/org/olat/modules/curriculum/restapi/CurriculumElementTypesWebService.java index 2b102c65c198ba773919df7b10da6ff16559b050..ea98c3116f3fd5395fee3b4718fa74929a9092e6 100644 --- a/src/main/java/org/olat/modules/curriculum/restapi/CurriculumElementTypesWebService.java +++ b/src/main/java/org/olat/modules/curriculum/restapi/CurriculumElementTypesWebService.java @@ -20,7 +20,6 @@ package org.olat.modules.curriculum.restapi; import java.util.ArrayList; - import java.util.List; import java.util.Set; @@ -41,6 +40,7 @@ import org.olat.modules.curriculum.CurriculumCalendars; import org.olat.modules.curriculum.CurriculumElementType; import org.olat.modules.curriculum.CurriculumElementTypeManagedFlag; import org.olat.modules.curriculum.CurriculumElementTypeToType; +import org.olat.modules.curriculum.CurriculumLectures; import org.olat.modules.curriculum.CurriculumService; import org.olat.modules.curriculum.model.CurriculumElementTypeRefImpl; import org.springframework.beans.factory.annotation.Autowired; @@ -237,6 +237,12 @@ public class CurriculumElementTypesWebService { } else { elementType.setCalendars(CurriculumCalendars.disabled); } + if(StringHelper.containsNonWhitespace(elementTypeVo.getLectures())) { + elementType.setLectures(CurriculumLectures.valueOf(elementTypeVo.getLectures())); + } else { + elementType.setLectures(CurriculumLectures.disabled); + } + elementType.setManagedFlags(CurriculumElementTypeManagedFlag.toEnum(elementTypeVo.getManagedFlagsString())); return curriculumService.updateCurriculumElementType(elementType); } diff --git a/src/main/java/org/olat/modules/lecture/ui/TeacherRollCallController.java b/src/main/java/org/olat/modules/lecture/ui/TeacherRollCallController.java index c3b70ae17115de14fc95c2d16ab21803a76d4c38..3e9feb4cd630b96435c73956dc67964c121afc5c 100644 --- a/src/main/java/org/olat/modules/lecture/ui/TeacherRollCallController.java +++ b/src/main/java/org/olat/modules/lecture/ui/TeacherRollCallController.java @@ -596,7 +596,7 @@ public class TeacherRollCallController extends FormBasicController { check.select(onKeys[0], true); } row.setRollCall(rollCall); - if(authorizedAbsenceEnabled) { + if(authorizedAbsenceEnabled && row.getAuthorizedAbsence() != null) { if(rollCall.getAbsenceAuthorized() != null && rollCall.getAbsenceAuthorized().booleanValue()) { row.getAuthorizedAbsence().select(onKeys[0], true); } else { diff --git a/src/main/java/org/olat/modules/quality/analysis/MultiTrendSeries.java b/src/main/java/org/olat/modules/quality/analysis/MultiTrendSeries.java index 72c970e671df2ce12d615e07b4d811e058e53ad9..6b56fca37e022dca20c5bcf9c11c61824a722091 100644 --- a/src/main/java/org/olat/modules/quality/analysis/MultiTrendSeries.java +++ b/src/main/java/org/olat/modules/quality/analysis/MultiTrendSeries.java @@ -43,9 +43,11 @@ public class MultiTrendSeries<V> { this.temporalKeys = Collections.emptyList(); } - public MultiTrendSeries(TemporalGroupBy temporalGroupBy, TemporalKey min, TemporalKey max) { + public MultiTrendSeries(TemporalGroupBy temporalGroupBy, TemporalMinMaxKeys minMaxKeys) { this.temporalGroupBy = temporalGroupBy; this.temporalKeys = new ArrayList<>(); + TemporalKey min = minMaxKeys.getMinKey(); + TemporalKey max = minMaxKeys.getMaxKey(); if (!TemporalKey.none().equals(min) && !TemporalKey.none().equals(max)) { generateTemporalKeys(temporalKeys, min, max); } @@ -92,6 +94,35 @@ public class MultiTrendSeries<V> { } return series; } - + + public static class TemporalMinMaxKeys { + + private static final TemporalMinMaxKeys NONES = new TemporalMinMaxKeys(TemporalKey.none(), TemporalKey.none()); + + private final TemporalKey minKey; + private final TemporalKey maxKey; + + public static TemporalMinMaxKeys nones() { + return NONES; + } + + public static TemporalMinMaxKeys of(TemporalKey minKey, TemporalKey maxKey) { + return new TemporalMinMaxKeys(minKey, maxKey); + } + + private TemporalMinMaxKeys(TemporalKey minKey, TemporalKey maxKey) { + this.minKey = minKey; + this.maxKey = maxKey; + } + + public TemporalKey getMinKey() { + return minKey; + } + + public TemporalKey getMaxKey() { + return maxKey; + } + + } } diff --git a/src/main/java/org/olat/modules/quality/analysis/manager/StatisticsCalculator.java b/src/main/java/org/olat/modules/quality/analysis/manager/StatisticsCalculator.java index 8faba390c22f4295590f4c84e8110de3bde59c33..6ba36341c40608720eab5e697d7b84f6931e1fe3 100644 --- a/src/main/java/org/olat/modules/quality/analysis/manager/StatisticsCalculator.java +++ b/src/main/java/org/olat/modules/quality/analysis/manager/StatisticsCalculator.java @@ -42,6 +42,7 @@ import org.olat.modules.quality.analysis.GroupedStatistics; import org.olat.modules.quality.analysis.HeatMapStatistic; import org.olat.modules.quality.analysis.MultiKey; import org.olat.modules.quality.analysis.MultiTrendSeries; +import org.olat.modules.quality.analysis.MultiTrendSeries.TemporalMinMaxKeys; import org.olat.modules.quality.analysis.RawGroupedStatistic; import org.olat.modules.quality.analysis.TemporalGroupBy; import org.olat.modules.quality.analysis.TemporalKey; @@ -236,18 +237,10 @@ public class StatisticsCalculator { MultiTrendSeries<String> getTrendsByIdentifiers(GroupedStatistics<GroupedStatistic> statistics, TemporalGroupBy temporalGroupBy) { - Set<TemporalKey> temporalKeys = new HashSet<>(); - for (GroupedStatisticKeys groupedStatistic : statistics.getStatistics()) { - temporalKeys.add(groupedStatistic.getTemporalKey()); - } - List<TemporalKey> sortedTemporalKeys = new ArrayList<>(temporalKeys); - Collections.sort(sortedTemporalKeys); - TemporalKey minKey = sortedTemporalKeys.get(0); - TemporalKey maxKey = sortedTemporalKeys.get(sortedTemporalKeys.size() - 1); - + TemporalMinMaxKeys minMaxKeys = getTemporalMinMax(statistics); Set<String> identifiers = statistics.getIdentifiers(); - MultiTrendSeries<String> multiTrendSeries = new MultiTrendSeries<>(temporalGroupBy, minKey, maxKey); + MultiTrendSeries<String> multiTrendSeries = new MultiTrendSeries<>(temporalGroupBy, minMaxKeys); for (String identifier: identifiers) { GroupedStatistic lastStatistic = null; for (TemporalKey temporalKey: multiTrendSeries.getTemporalKeys()) { @@ -266,18 +259,10 @@ public class StatisticsCalculator { } MultiTrendSeries<MultiKey> getTrendsByMultiKey(GroupedStatistics<GroupedStatistic> statistics, TemporalGroupBy temporalGroupBy) { - Set<TemporalKey> temporalKeys = new HashSet<>(); - for (GroupedStatisticKeys groupedStatistic : statistics.getStatistics()) { - temporalKeys.add(groupedStatistic.getTemporalKey()); - } - List<TemporalKey> sortedTemporalKeys = new ArrayList<>(temporalKeys); - Collections.sort(sortedTemporalKeys); - TemporalKey minKey = sortedTemporalKeys.get(0); - TemporalKey maxKey = sortedTemporalKeys.get(sortedTemporalKeys.size() - 1); - + TemporalMinMaxKeys minMaxKeys = getTemporalMinMax(statistics); Set<MultiKey> multiKeys = statistics.getMultiKeys(); - MultiTrendSeries<MultiKey> multiTrendSeries = new MultiTrendSeries<>(temporalGroupBy, minKey, maxKey); + MultiTrendSeries<MultiKey> multiTrendSeries = new MultiTrendSeries<>(temporalGroupBy, minMaxKeys); for (MultiKey multiKey: multiKeys) { GroupedStatistic lastStatistic = null; for (TemporalKey temporalKey: multiTrendSeries.getTemporalKeys()) { @@ -295,6 +280,21 @@ public class StatisticsCalculator { return multiTrendSeries; } + private TemporalMinMaxKeys getTemporalMinMax(GroupedStatistics<GroupedStatistic> statistics) { + if (statistics.getStatistics().isEmpty()) return TemporalMinMaxKeys.nones(); + + Set<TemporalKey> temporalKeys = new HashSet<>(); + for (GroupedStatisticKeys groupedStatistic : statistics.getStatistics()) { + temporalKeys.add(groupedStatistic.getTemporalKey()); + } + List<TemporalKey> sortedTemporalKeys = new ArrayList<>(temporalKeys); + Collections.sort(sortedTemporalKeys); + TemporalKey minKey = sortedTemporalKeys.get(0); + TemporalKey maxKey = sortedTemporalKeys.get(sortedTemporalKeys.size() - 1); + + return TemporalMinMaxKeys.of(minKey, maxKey); + } + Double getAvgDiffAbsolute(GroupedStatistic prev, GroupedStatistic current) { if (prev == null || prev.getAvg() == null || current == null || current.getAvg() == null) return null; diff --git a/src/main/java/org/olat/modules/video/manager/VideoVFSContextInfoResolver.java b/src/main/java/org/olat/modules/video/manager/VideoVFSContextInfoResolver.java new file mode 100644 index 0000000000000000000000000000000000000000..4a64f6ff86914adc5d9c68b52dcb02fe3d49b3a2 --- /dev/null +++ b/src/main/java/org/olat/modules/video/manager/VideoVFSContextInfoResolver.java @@ -0,0 +1,103 @@ +/** + * <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.video.manager; + +import java.util.List; +import java.util.Locale; + +import org.apache.logging.log4j.Logger; +import org.olat.core.commons.services.vfs.VFSContextInfo; +import org.olat.core.commons.services.vfs.impl.VFSContextInfoImpl; +import org.olat.core.helpers.Settings; +import org.olat.core.logging.Tracing; +import org.olat.core.util.StringHelper; +import org.olat.core.util.Util; +import org.olat.modules.video.ui.VideoDisplayController; +import org.olat.repository.RepositoryEntry; +import org.olat.repository.RepositoryEntryVFSContextInfoResolver; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Service; + +/** + * A specialized version of the repository entry resolver that catches also the + * transcoded video resources. This resolver has a higher order value to be + * executed before the default repository resolver. + * <br /> + * The class inheritence is not necessary, it just shows that this belongs to the repository + * + * Initial date: 16 Jan 2020<br> + * + * @author gnaegi, gnaegi@frentix.com, http://www.frentix.com + * + */ +@Service +@Order(value=200) +public class VideoVFSContextInfoResolver extends RepositoryEntryVFSContextInfoResolver { + private static final Logger log = Tracing.createLoggerFor(VideoVFSContextInfoResolver.class); + + @Override + public String resolveContextTypeName(String vfsMetadataRelativePath, Locale locale) { + if (vfsMetadataRelativePath == null) { + return null; + } + String type = null; + // Is either a transcoding or the master video + if (vfsMetadataRelativePath.startsWith("transcodedVideos")) { + type = Util.createPackageTranslator(VideoDisplayController.class, locale).translate("admin.menu.transcoding.title"); + } else if (vfsMetadataRelativePath.startsWith("repository") && vfsMetadataRelativePath.endsWith("master")) { + type = Util.createPackageTranslator(VideoDisplayController.class, locale).translate("quality.master"); + } + return type; + } + + @Override + public VFSContextInfo resolveContextInfo(String vfsMetadataRelativePath, Locale locale) { + String type = resolveContextTypeName(vfsMetadataRelativePath, locale); + if (type == null) { + return null; + } + + // Try finding detail infos + String name = "Unknown"; + String url = null; + + String[] path = vfsMetadataRelativePath.split("/"); + String keyString = path[1]; + if (StringHelper.isLong(keyString)) { + List<RepositoryEntry> repoEntries = repositoryService.searchByIdAndRefs(keyString); + if (repoEntries.size() != 1) { + log.warn("No olat resource resource found for id::" + keyString + " for path::" + vfsMetadataRelativePath); + } else { + RepositoryEntry re = repoEntries.get(0); + if (re == null) { + log.warn("No repository entry found for key::" + keyString + " for path::" + vfsMetadataRelativePath); + } else { + name = re.getDisplayname(); + url = Settings.getServerContextPathURI() + "/url/RepositoryEntry/" + re.getKey(); + } + } + } else { + log.warn("Can not parse repo entry id for path::{}", vfsMetadataRelativePath); + } + + return new VFSContextInfoImpl(type, name, url); + } + +} diff --git a/src/main/java/org/olat/repository/RepositoryEntryVFSContextInfoResolver.java b/src/main/java/org/olat/repository/RepositoryEntryVFSContextInfoResolver.java new file mode 100644 index 0000000000000000000000000000000000000000..b4357c356c3760d0280444439ccef332550d4468 --- /dev/null +++ b/src/main/java/org/olat/repository/RepositoryEntryVFSContextInfoResolver.java @@ -0,0 +1,98 @@ +/** + * <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.repository; + +import java.util.List; +import java.util.Locale; + +import org.apache.logging.log4j.Logger; +import org.olat.core.commons.services.vfs.VFSContextInfo; +import org.olat.core.commons.services.vfs.VFSContextInfoResolver; +import org.olat.core.commons.services.vfs.impl.VFSContextInfoImpl; +import org.olat.core.helpers.Settings; +import org.olat.core.logging.Tracing; +import org.olat.core.util.StringHelper; +import org.olat.core.util.Util; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +/** + * A generic repository resolver that catches everything that has not been + * resolved by a more specific resolver with the higher order + * + * Initial date: 16 Jan 2020<br> + * + * @author gnaegi, gnaegi@frentix.com, http://www.frentix.com + * + */ +@Component +@Order(value=10000) +public class RepositoryEntryVFSContextInfoResolver implements VFSContextInfoResolver { + private static final Logger log = Tracing.createLoggerFor(RepositoryEntryVFSContextInfoResolver.class); + + @Autowired + protected RepositoryService repositoryService; + + @Override + public String resolveContextTypeName(String vfsMetadataRelativePath, Locale locale) { + if (vfsMetadataRelativePath == null) { + return null; + } + String type = null; + // Catch all repo entry path + if (vfsMetadataRelativePath.startsWith("repository")){ + type = Util.createPackageTranslator(RepositoryEntryVFSContextInfoResolver.class, locale).translate("vfs.context.repositoryentry"); + } + return type; + } + + @Override + public VFSContextInfo resolveContextInfo(String vfsMetadataRelativePath, Locale locale) { + String type = resolveContextTypeName(vfsMetadataRelativePath, locale); + if (type == null) { + return null; + } + + // Try finding detail infos + String name = "Unknown"; + String url = null; + + String[] path = vfsMetadataRelativePath.split("/"); + String keyString = path[1]; + if (StringHelper.isLong(keyString)) { + + List<RepositoryEntry> repoEntries = repositoryService.searchByIdAndRefs(keyString); + if (repoEntries.size() != 1) { + log.warn("No olat resource resource found for id::" + keyString + " for path::" + vfsMetadataRelativePath); + } else { + RepositoryEntry re = repoEntries.get(0); + name = re.getDisplayname(); + url = Settings.getServerContextPathURI() + "/url/RepositoryEntry/" + re.getKey(); + type = Util.createPackageTranslator(RepositoryEntryVFSContextInfoResolver.class, locale).translate(re.getOlatResource().getResourceableTypeName()); + } + } else { + log.warn("Can not parse repo entry id for path::{}", vfsMetadataRelativePath); + } + + return new VFSContextInfoImpl(type, name, url); + } + +} diff --git a/src/main/java/org/olat/repository/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/repository/_i18n/LocalStrings_de.properties index a638e30795a7a99d3541ba133b44a65ca1756c97..3d882a3eca3989399fc4c63db3abb694b4e0fb06 100644 --- a/src/main/java/org/olat/repository/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/repository/_i18n/LocalStrings_de.properties @@ -761,3 +761,4 @@ wizardsteptitledescription=Beschreibung eintragen wizardsteptitleproperties=Einstellungen vornehmen wizardsteptitleupload=Datei hochladen year=Jahr +vfs.context.repositoryentry=Lernressource diff --git a/src/main/java/org/olat/repository/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/repository/_i18n/LocalStrings_en.properties index b3239642cde26d7429c8717a271cf54f6362efc9..22dbde6c45563dad5a17db10177a6f9bf54c6339 100644 --- a/src/main/java/org/olat/repository/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/repository/_i18n/LocalStrings_en.properties @@ -756,3 +756,5 @@ wizardsteptitledescription=Indicate description wizardsteptitleproperties=Set properties wizardsteptitleupload=Upload file year=Year +vfs.context.repositoryentry=Learning resource + diff --git a/src/test/java/org/olat/modules/quality/analysis/MultiTrendSeriesTest.java b/src/test/java/org/olat/modules/quality/analysis/MultiTrendSeriesTest.java index e2456593e67aed6ed47d685e4f367ba36ca33e23..2b8ffba2de913b7134a4d14f5049c9bd0fb65340 100644 --- a/src/test/java/org/olat/modules/quality/analysis/MultiTrendSeriesTest.java +++ b/src/test/java/org/olat/modules/quality/analysis/MultiTrendSeriesTest.java @@ -24,6 +24,7 @@ import static org.assertj.core.api.Assertions.assertThat; import java.util.List; import org.junit.Test; +import org.olat.modules.quality.analysis.MultiTrendSeries.TemporalMinMaxKeys; import org.olat.modules.quality.analysis.model.TrendImpl; /** @@ -40,16 +41,18 @@ public class MultiTrendSeriesTest { TemporalKey tk2001 = TemporalKey.of(2001); TemporalKey tk2002 = TemporalKey.of(2002); TemporalKey tk2003 = TemporalKey.of(2003); + TemporalMinMaxKeys minMaxKeys = TemporalMinMaxKeys.of(tk2000, tk2003); - MultiTrendSeries<String> multiTrendSeries = new MultiTrendSeries<>(TemporalGroupBy.DATA_COLLECTION_DEADLINE_YEAR, tk2000, tk2003); + MultiTrendSeries<String> multiTrendSeries = new MultiTrendSeries<>(TemporalGroupBy.DATA_COLLECTION_DEADLINE_YEAR, minMaxKeys); assertThat(multiTrendSeries.getTemporalKeys()).containsExactly(tk2000, tk2001, tk2002, tk2003); } @Test public void shouldGenerateTemporalKeysIfMinEqualsMax() { TemporalKey tk2000 = TemporalKey.of(2000); + TemporalMinMaxKeys minMaxKeys = TemporalMinMaxKeys.of(tk2000, tk2000); - MultiTrendSeries<String> multiTrendSeries = new MultiTrendSeries<>(TemporalGroupBy.DATA_COLLECTION_DEADLINE_YEAR, tk2000, tk2000); + MultiTrendSeries<String> multiTrendSeries = new MultiTrendSeries<>(TemporalGroupBy.DATA_COLLECTION_DEADLINE_YEAR, minMaxKeys); assertThat(multiTrendSeries.getTemporalKeys()).containsExactly(tk2000); } @@ -59,8 +62,9 @@ public class MultiTrendSeriesTest { String identifier = "1"; TemporalKey tk2000 = TemporalKey.of(2000); TemporalKey tk2003 = TemporalKey.of(2003); + TemporalMinMaxKeys minMaxKeys = TemporalMinMaxKeys.of(tk2000, tk2003); - MultiTrendSeries<String> multiTrendSeries = new MultiTrendSeries<>(TemporalGroupBy.DATA_COLLECTION_DEADLINE_YEAR, tk2000, tk2003); + MultiTrendSeries<String> multiTrendSeries = new MultiTrendSeries<>(TemporalGroupBy.DATA_COLLECTION_DEADLINE_YEAR, minMaxKeys); multiTrendSeries.put(identifier, tk2000, null); List<Trend> trendList = multiTrendSeries.getSeries(identifier).toList(); @@ -72,9 +76,10 @@ public class MultiTrendSeriesTest { String identifier = "1"; TemporalKey tk2000 = TemporalKey.of(2000); TemporalKey tk2003 = TemporalKey.of(2003); + TemporalMinMaxKeys minMaxKeys = TemporalMinMaxKeys.of(tk2000, tk2003); Trend trend = new TrendImpl(null, null, null, null); - MultiTrendSeries<String> multiTrendSeries = new MultiTrendSeries<>(TemporalGroupBy.DATA_COLLECTION_DEADLINE_YEAR, tk2000, tk2003); + MultiTrendSeries<String> multiTrendSeries = new MultiTrendSeries<>(TemporalGroupBy.DATA_COLLECTION_DEADLINE_YEAR, minMaxKeys); multiTrendSeries.put(identifier, tk2003, trend); List<Trend> trendList = multiTrendSeries.getSeries(identifier).toList(); @@ -86,8 +91,9 @@ public class MultiTrendSeriesTest { String identifier = "1"; TemporalKey tk2000 = TemporalKey.of(2000); TemporalKey tk2003 = TemporalKey.of(2003); + TemporalMinMaxKeys minMaxKeys = TemporalMinMaxKeys.of(tk2000, tk2003); - MultiTrendSeries<String> multiTrendSeries = new MultiTrendSeries<>(TemporalGroupBy.DATA_COLLECTION_DEADLINE_YEAR, tk2000, tk2003); + MultiTrendSeries<String> multiTrendSeries = new MultiTrendSeries<>(TemporalGroupBy.DATA_COLLECTION_DEADLINE_YEAR, minMaxKeys); List<Trend> trendList = multiTrendSeries.getSeries(identifier).toList(); assertThat(trendList).containsExactly(null, null, null, null); diff --git a/src/test/java/org/olat/modules/quality/analysis/manager/StatisticsCalculatorTest.java b/src/test/java/org/olat/modules/quality/analysis/manager/StatisticsCalculatorTest.java index d97883d2066ad506dd75bd0c7325ad4b5138ca99..c266b18559b8c90c9a3f023eaf2295006b726dd0 100644 --- a/src/test/java/org/olat/modules/quality/analysis/manager/StatisticsCalculatorTest.java +++ b/src/test/java/org/olat/modules/quality/analysis/manager/StatisticsCalculatorTest.java @@ -326,6 +326,24 @@ public class StatisticsCalculatorTest { assertThat(trend3.getDirection()).isEqualTo(DIRECTION.UP); } + @Test + public void shouldCalculateTrendsByMultiKeyWithoutData() { + GroupedStatistics<GroupedStatistic> statistics = new GroupedStatistics<>(); + + MultiTrendSeries<MultiKey> multiTrendSeries = sut.getTrendsByMultiKey(statistics, TemporalGroupBy.DATA_COLLECTION_DEADLINE_YEAR); + + assertThat(multiTrendSeries.getTemporalKeys()).hasSize(0); + } + + @Test + public void shouldCalculateTrendssByIdentifiersWithoutData() { + GroupedStatistics<GroupedStatistic> statistics = new GroupedStatistics<>(); + + MultiTrendSeries<String> multiTrendSeries = sut.getTrendsByIdentifiers(statistics, TemporalGroupBy.DATA_COLLECTION_DEADLINE_YEAR); + + assertThat(multiTrendSeries.getTemporalKeys()).hasSize(0); + } + @Test public void shouldReduceIdentifier() { // Rubric 1 diff --git a/src/test/java/org/olat/restapi/CurriculumElementTypesWebServiceTest.java b/src/test/java/org/olat/restapi/CurriculumElementTypesWebServiceTest.java index f68a3295de70fee720767c6e5f6de92f1b319173..06aa424232b76fe2e44f615607f9668e60882d1b 100644 --- a/src/test/java/org/olat/restapi/CurriculumElementTypesWebServiceTest.java +++ b/src/test/java/org/olat/restapi/CurriculumElementTypesWebServiceTest.java @@ -40,6 +40,7 @@ import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.util.EntityUtils; +import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; import org.junit.Assert; import org.junit.Test; @@ -135,7 +136,7 @@ public class CurriculumElementTypesWebServiceTest extends OlatRestTestCase { conn.addJsonEntity(method, vo); HttpResponse response = conn.execute(method); - Assert.assertThat(response.getStatusLine().getStatusCode(), Matchers.either(Matchers.is(200)).or(Matchers.is(201))); + MatcherAssert.assertThat(response.getStatusLine().getStatusCode(), Matchers.either(Matchers.is(200)).or(Matchers.is(201))); // checked VO CurriculumElementTypeVO savedVo = conn.parse(response, CurriculumElementTypeVO.class); diff --git a/src/test/java/org/olat/restapi/CurriculumElementsWebServiceTest.java b/src/test/java/org/olat/restapi/CurriculumElementsWebServiceTest.java index 4dbd9b3d39232f13c0478f5f2a5d5e4f7430bcd7..c3a1d19bd4718aa7c6fa71828cd2c04fbb88b5be 100644 --- a/src/test/java/org/olat/restapi/CurriculumElementsWebServiceTest.java +++ b/src/test/java/org/olat/restapi/CurriculumElementsWebServiceTest.java @@ -40,6 +40,7 @@ import org.apache.http.client.methods.HttpHead; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.util.EntityUtils; +import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; import org.junit.Assert; import org.junit.Test; @@ -241,7 +242,7 @@ public class CurriculumElementsWebServiceTest extends OlatRestTestCase { conn.addJsonEntity(method, vo); HttpResponse response = conn.execute(method); - Assert.assertThat(response.getStatusLine().getStatusCode(), Matchers.either(Matchers.is(200)).or(Matchers.is(201))); + MatcherAssert.assertThat(response.getStatusLine().getStatusCode(), Matchers.either(Matchers.is(200)).or(Matchers.is(201))); // checked VO CurriculumElementVO savedVo = conn.parse(response, CurriculumElementVO.class); diff --git a/src/test/java/org/olat/restapi/CurriculumsWebServiceTest.java b/src/test/java/org/olat/restapi/CurriculumsWebServiceTest.java index 0337bdf09a9832b5fa8bb6e2e4c9829f47bcdca2..513c22cf1b9359fe2a9318e97da7cb39db0cfcdd 100644 --- a/src/test/java/org/olat/restapi/CurriculumsWebServiceTest.java +++ b/src/test/java/org/olat/restapi/CurriculumsWebServiceTest.java @@ -39,6 +39,7 @@ import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.util.EntityUtils; import org.apache.logging.log4j.Logger; +import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; import org.junit.Assert; import org.junit.Test; @@ -157,7 +158,7 @@ public class CurriculumsWebServiceTest extends OlatRestTestCase { conn.addJsonEntity(method, vo); HttpResponse response = conn.execute(method); - Assert.assertThat(response.getStatusLine().getStatusCode(), Matchers.either(Matchers.is(200)).or(Matchers.is(201))); + MatcherAssert.assertThat(response.getStatusLine().getStatusCode(), Matchers.either(Matchers.is(200)).or(Matchers.is(201))); // checked VO CurriculumVO savedVo = conn.parse(response, CurriculumVO.class); diff --git a/src/test/java/org/olat/restapi/IdentityToIdentityRelationsWebServiceTest.java b/src/test/java/org/olat/restapi/IdentityToIdentityRelationsWebServiceTest.java index 6fd345f9fe213ce6b60fc3e82a39e9aab7b8a05c..447e763cb9e30f2e1dd27f7e8fbd675d30d3076e 100644 --- a/src/test/java/org/olat/restapi/IdentityToIdentityRelationsWebServiceTest.java +++ b/src/test/java/org/olat/restapi/IdentityToIdentityRelationsWebServiceTest.java @@ -37,6 +37,7 @@ import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; +import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; import org.junit.Assert; import org.junit.Test; @@ -149,7 +150,7 @@ public class IdentityToIdentityRelationsWebServiceTest extends OlatRestTestCase conn.addJsonEntity(method, relationVo); HttpResponse response = conn.execute(method); - Assert.assertThat(response.getStatusLine().getStatusCode(), Matchers.either(Matchers.is(200)).or(Matchers.is(201))); + MatcherAssert.assertThat(response.getStatusLine().getStatusCode(), Matchers.either(Matchers.is(200)).or(Matchers.is(201))); // checked VO IdentityToIdentityRelationVO savedRelationVo = conn.parse(response, IdentityToIdentityRelationVO.class); @@ -187,7 +188,7 @@ public class IdentityToIdentityRelationsWebServiceTest extends OlatRestTestCase conn.addJsonEntity(method, relationVo); HttpResponse response = conn.execute(method); - Assert.assertThat(response.getStatusLine().getStatusCode(), Matchers.either(Matchers.is(200)).or(Matchers.is(201))); + MatcherAssert.assertThat(response.getStatusLine().getStatusCode(), Matchers.either(Matchers.is(200)).or(Matchers.is(201))); // checked VO IdentityToIdentityRelationVO savedRelationVo = conn.parse(response, IdentityToIdentityRelationVO.class); diff --git a/src/test/java/org/olat/restapi/OrganisationTypesWebServiceTest.java b/src/test/java/org/olat/restapi/OrganisationTypesWebServiceTest.java index 26a9a587bfe6c20c10ecb38c60376ad09800b64a..1d94def04f56c6da3c70b0316131d0a5d724ae8c 100644 --- a/src/test/java/org/olat/restapi/OrganisationTypesWebServiceTest.java +++ b/src/test/java/org/olat/restapi/OrganisationTypesWebServiceTest.java @@ -39,6 +39,7 @@ import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.util.EntityUtils; +import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; import org.junit.Assert; import org.junit.Test; @@ -134,7 +135,7 @@ public class OrganisationTypesWebServiceTest extends OlatRestTestCase { conn.addJsonEntity(method, vo); HttpResponse response = conn.execute(method); - Assert.assertThat(response.getStatusLine().getStatusCode(), Matchers.either(Matchers.is(200)).or(Matchers.is(201))); + MatcherAssert.assertThat(response.getStatusLine().getStatusCode(), Matchers.either(Matchers.is(200)).or(Matchers.is(201))); // checked VO OrganisationTypeVO savedVo = conn.parse(response, OrganisationTypeVO.class); diff --git a/src/test/java/org/olat/restapi/OrganisationsWebServiceTest.java b/src/test/java/org/olat/restapi/OrganisationsWebServiceTest.java index 8bd51dae2de97b1000b3fa4c879494b0645a8e44..ca10c465dfda730dac70ff5eb3c202c359c9684d 100644 --- a/src/test/java/org/olat/restapi/OrganisationsWebServiceTest.java +++ b/src/test/java/org/olat/restapi/OrganisationsWebServiceTest.java @@ -19,8 +19,8 @@ */ package org.olat.restapi; -import static org.junit.Assert.assertTrue; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertTrue; import java.io.IOException; import java.io.InputStream; @@ -38,6 +38,8 @@ import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.util.EntityUtils; +import org.apache.logging.log4j.Logger; +import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; import org.junit.Assert; import org.junit.Test; @@ -52,7 +54,6 @@ import org.olat.basesecurity.model.OrganisationRefImpl; import org.olat.core.commons.persistence.DB; import org.olat.core.id.Identity; import org.olat.core.id.Organisation; -import org.apache.logging.log4j.Logger; import org.olat.core.logging.Tracing; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryService; @@ -154,7 +155,7 @@ public class OrganisationsWebServiceTest extends OlatRestTestCase { conn.addJsonEntity(method, vo); HttpResponse response = conn.execute(method); - Assert.assertThat(response.getStatusLine().getStatusCode(), Matchers.either(Matchers.is(200)).or(Matchers.is(201))); + MatcherAssert.assertThat(response.getStatusLine().getStatusCode(), Matchers.either(Matchers.is(200)).or(Matchers.is(201))); // checked VO OrganisationVO savedVo = conn.parse(response, OrganisationVO.class); @@ -214,7 +215,7 @@ public class OrganisationsWebServiceTest extends OlatRestTestCase { conn.addJsonEntity(method, vo); HttpResponse response = conn.execute(method); - Assert.assertThat(response.getStatusLine().getStatusCode(), Matchers.either(Matchers.is(200)).or(Matchers.is(201))); + MatcherAssert.assertThat(response.getStatusLine().getStatusCode(), Matchers.either(Matchers.is(200)).or(Matchers.is(201))); // checked VO OrganisationVO savedVo = conn.parse(response, OrganisationVO.class); @@ -310,7 +311,7 @@ public class OrganisationsWebServiceTest extends OlatRestTestCase { conn.addJsonEntity(method, vo); HttpResponse response = conn.execute(method); - Assert.assertThat(response.getStatusLine().getStatusCode(), Matchers.either(Matchers.is(200)).or(Matchers.is(201))); + MatcherAssert.assertThat(response.getStatusLine().getStatusCode(), Matchers.either(Matchers.is(200)).or(Matchers.is(201))); // checked VO OrganisationVO savedVo = conn.parse(response, OrganisationVO.class); @@ -357,7 +358,7 @@ public class OrganisationsWebServiceTest extends OlatRestTestCase { conn.addJsonEntity(method, organisationVo2); HttpResponse response = conn.execute(method); - Assert.assertThat(response.getStatusLine().getStatusCode(), Matchers.either(Matchers.is(200)).or(Matchers.is(201))); + MatcherAssert.assertThat(response.getStatusLine().getStatusCode(), Matchers.either(Matchers.is(200)).or(Matchers.is(201))); // checked VO OrganisationVO savedVo = conn.parse(response, OrganisationVO.class); @@ -428,7 +429,7 @@ public class OrganisationsWebServiceTest extends OlatRestTestCase { conn.addJsonEntity(method, vo); HttpResponse response = conn.execute(method); - Assert.assertThat(response.getStatusLine().getStatusCode(), Matchers.either(Matchers.is(200)).or(Matchers.is(201))); + MatcherAssert.assertThat(response.getStatusLine().getStatusCode(), Matchers.either(Matchers.is(200)).or(Matchers.is(201))); // checked VO OrganisationVO savedVo = conn.parse(response, OrganisationVO.class); diff --git a/src/test/java/org/olat/restapi/RelationRolesWebServiceTest.java b/src/test/java/org/olat/restapi/RelationRolesWebServiceTest.java index 4348cae0623903795bdb6504127c1dee8d3fcac1..8b350228fb8fcef8e63ecc395c9a2f54633a8cc3 100644 --- a/src/test/java/org/olat/restapi/RelationRolesWebServiceTest.java +++ b/src/test/java/org/olat/restapi/RelationRolesWebServiceTest.java @@ -40,6 +40,7 @@ import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.util.EntityUtils; +import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; import org.junit.Assert; import org.junit.Test; @@ -237,7 +238,7 @@ public class RelationRolesWebServiceTest extends OlatRestTestCase { conn.addJsonEntity(method, vo); HttpResponse response = conn.execute(method); - Assert.assertThat(response.getStatusLine().getStatusCode(), Matchers.either(Matchers.is(200)).or(Matchers.is(201))); + MatcherAssert.assertThat(response.getStatusLine().getStatusCode(), Matchers.either(Matchers.is(200)).or(Matchers.is(201))); // checked VO RelationRoleVO savedVo = conn.parse(response, RelationRoleVO.class);