diff --git a/src/main/java/org/olat/core/commons/chiefcontrollers/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/core/commons/chiefcontrollers/_i18n/LocalStrings_de.properties index 9edea981ea6ab4b38f2d6f564dbf5a869d7a96d9..833dcbf36501782d2e8074bf50efe1cb78d6a1bc 100644 --- a/src/main/java/org/olat/core/commons/chiefcontrollers/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/core/commons/chiefcontrollers/_i18n/LocalStrings_de.properties @@ -42,6 +42,7 @@ Inbox=Inbox EPStructuredMap=ePortfolio InfoMessage=Mitteilungen LibrarySite=Bibliothek +PFCourseNode=Drop Box ReturnboxController=R\u00FCckgabeordner SolutionController=Musterl\u00F6sungen User=Benutzer diff --git a/src/main/java/org/olat/core/commons/chiefcontrollers/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/core/commons/chiefcontrollers/_i18n/LocalStrings_en.properties index 3db68c5b3249eb8f72fb040198651f05eda05ee0..4e9ecd9657aa77d8980888c328a5465ca528fa2b 100644 --- a/src/main/java/org/olat/core/commons/chiefcontrollers/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/core/commons/chiefcontrollers/_i18n/LocalStrings_en.properties @@ -42,6 +42,7 @@ GroupTask=Task Inbox=Inbox InfoMessage=Messages LibrarySite=Library +PFCourseNode=Drop Box ReturnboxController=Return box SolutionController=Sample solutions User=User diff --git a/src/main/java/org/olat/core/commons/modules/bc/components/ListRenderer.java b/src/main/java/org/olat/core/commons/modules/bc/components/ListRenderer.java index afdf5ea86468e08e9211844f094be1440c6429ba..12aaad485b091dba3811f074ee2d94a8406e1749 100644 --- a/src/main/java/org/olat/core/commons/modules/bc/components/ListRenderer.java +++ b/src/main/java/org/olat/core/commons/modules/bc/components/ListRenderer.java @@ -53,6 +53,7 @@ import org.olat.core.util.vfs.VFSContainer; import org.olat.core.util.vfs.VFSItem; import org.olat.core.util.vfs.VFSLeaf; import org.olat.core.util.vfs.VFSLockManager; +import org.olat.core.util.vfs.VirtualContainer; import org.olat.core.util.vfs.lock.LockInfo; import org.olat.core.util.vfs.version.Versionable; import org.olat.core.util.vfs.version.Versions; @@ -173,6 +174,8 @@ public class ListRenderer { // assume full access unless security callback tells us something different. boolean canWrite = child.getParentContainer().canWrite() == VFSConstants.YES; + // special case: virtual folders are always read only. parent of child =! the current container + canWrite = canWrite && !(fc.getCurrentContainer() instanceof VirtualContainer); boolean isAbstract = (child instanceof AbstractVirtualContainer); Versions versions = null; diff --git a/src/main/java/org/olat/core/commons/modules/bc/meta/MetaInfoFormController.java b/src/main/java/org/olat/core/commons/modules/bc/meta/MetaInfoFormController.java index d1b606f3a4735ab95ef6b90bb778336220526e3d..4547083bc51ea1952f1a55e019ddacc379a88b73 100644 --- a/src/main/java/org/olat/core/commons/modules/bc/meta/MetaInfoFormController.java +++ b/src/main/java/org/olat/core/commons/modules/bc/meta/MetaInfoFormController.java @@ -438,7 +438,8 @@ public class MetaInfoFormController extends FormBasicController { } } - MetaInfo meta = CoreSpringFactory.getImpl(MetaInfoFactory.class).createMetaInfoFor((OlatRelPathImpl)item); + MetaInfo meta = item instanceof OlatRelPathImpl ? + CoreSpringFactory.getImpl(MetaInfoFactory.class).createMetaInfoFor((OlatRelPathImpl)item) : null; if(meta == null) { return null; } diff --git a/src/main/java/org/olat/course/MergedCourseContainer.java b/src/main/java/org/olat/course/MergedCourseContainer.java index 81b128d6e6eaa7f47fe43928cfad9eed1a36cbfc..5167de38893b30c409892e3af5a1b82e14012bc0 100644 --- a/src/main/java/org/olat/course/MergedCourseContainer.java +++ b/src/main/java/org/olat/course/MergedCourseContainer.java @@ -37,7 +37,9 @@ import org.olat.core.util.vfs.filters.VFSItemFilter; import org.olat.course.config.CourseConfig; import org.olat.course.nodes.BCCourseNode; import org.olat.course.nodes.CourseNode; +import org.olat.course.nodes.PFCourseNode; import org.olat.course.nodes.bc.BCCourseNodeEditController; +import org.olat.course.nodes.pf.manager.PFManager; import org.olat.course.run.userview.NodeEvaluation; import org.olat.course.run.userview.TreeEvaluation; import org.olat.course.run.userview.UserCourseEnvironment; @@ -217,6 +219,21 @@ public class MergedCourseContainer extends MergeSource { nodesContainer.addContainer(courseNodeContainer); } } + } else if (courseNodeChild instanceof PFCourseNode) { + final PFCourseNode pfNode = (PFCourseNode) courseNodeChild; + // add folder not to merge source. Use name and node id to have unique name + PFManager pfManager = CoreSpringFactory.getImpl(PFManager.class); + folderName = getBCFolderName(nodesContainer, pfNode, folderName); + MergeSource courseNodeContainer = new MergeSource(nodesContainer, folderName); + UserCourseEnvironment userCourseEnv = new UserCourseEnvironmentImpl(identityEnv, course.getCourseEnvironment()); + VFSContainer rootFolder = pfManager.provideCoachOrParticipantContainer(pfNode, userCourseEnv, identityEnv.getIdentity()); + VFSContainer nodeContentContainer = new NamedContainerImpl(folderName, rootFolder); + courseNodeContainer.addContainersChildren(nodeContentContainer, true); + + addFolderBuildingBlocks(course, courseNodeContainer, child); + + nodesContainer.addContainer(courseNodeContainer); + } else { // For non-folder course nodes, add merge source (no files to show) ... MergeSource courseNodeContainer = new MergeSource(null, folderName); @@ -266,6 +283,8 @@ public class MergedCourseContainer extends MergeSource { // Do recursion for all children addFolderBuildingBlocks(course, courseNodeContainer, child); } + } else if (child instanceof PFCourseNode) { + //FIXME check if something is to be done here } else { // For non-folder course nodes, add merge source (no files to show) ... MergeSource courseNodeContainer = new MergeSource(null, folderName); @@ -287,7 +306,7 @@ public class MergedCourseContainer extends MergeSource { * @param folderName * @return */ - private String getBCFolderName(MergeSource nodesContainer, BCCourseNode bcNode, String folderName) { + private String getBCFolderName(MergeSource nodesContainer, CourseNode bcNode, String folderName) { // add node ident if multiple files have same name if (nodesContainer.getItems(new VFSItemFilter() { @Override diff --git a/src/main/java/org/olat/course/nodes/PFCourseNode.java b/src/main/java/org/olat/course/nodes/PFCourseNode.java new file mode 100644 index 0000000000000000000000000000000000000000..51172d35a0307b920daf636494e7c5d862b36fef --- /dev/null +++ b/src/main/java/org/olat/course/nodes/PFCourseNode.java @@ -0,0 +1,279 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.course.nodes; + +import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Date; + +import org.olat.core.CoreSpringFactory; +import org.olat.core.commons.modules.bc.vfs.OlatRootFolderImpl; +import org.olat.core.commons.services.notifications.NotificationsManager; +import org.olat.core.commons.services.notifications.SubscriptionContext; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.stack.BreadcrumbPanel; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.control.generic.tabbable.TabbableController; +import org.olat.core.id.Identity; +import org.olat.core.util.FileUtils; +import org.olat.core.util.Util; +import org.olat.core.util.vfs.LocalFolderImpl; +import org.olat.core.util.vfs.VFSContainer; +import org.olat.course.CourseModule; +import org.olat.course.ICourse; +import org.olat.course.editor.CourseEditorEnv; +import org.olat.course.editor.NodeEditController; +import org.olat.course.editor.StatusDescription; +import org.olat.course.nodes.pf.manager.PFManager; +import org.olat.course.nodes.pf.ui.PFEditController; +import org.olat.course.nodes.pf.ui.PFPeekviewController; +import org.olat.course.nodes.pf.ui.PFPreviewController; +import org.olat.course.nodes.pf.ui.PFRunController; +import org.olat.course.run.environment.CourseEnvironment; +import org.olat.course.run.navigation.NodeRunConstructionResult; +import org.olat.course.run.userview.NodeEvaluation; +import org.olat.course.run.userview.UserCourseEnvironment; +import org.olat.modules.ModuleConfiguration; +import org.olat.repository.RepositoryEntry; + +public class PFCourseNode extends AbstractAccessableCourseNode { + + public static final String TYPE = "pf"; + + public static final String CONFIG_KEY_PARTICIPANTBOX = "participantbox"; + public static final String CONFIG_KEY_COACHBOX = "coachbox"; + public static final String CONFIG_KEY_ALTERFILE = "alterfile"; + public static final String CONFIG_KEY_LIMITCOUNT = "limitcount"; + public static final String CONFIG_KEY_FILECOUNT = "filecount"; + public static final String CONFIG_KEY_TIMEFRAME = "timeframe"; + public static final String CONFIG_KEY_DATESTART = "datestart"; + public static final String CONFIG_KEY_DATEEND = "dateend"; + + /** + * + */ + public static final long serialVersionUID = 1L; + + + public PFCourseNode(String type) { + super(type); + + } + + public PFCourseNode() { + super(TYPE); + + } + + @Override + public RepositoryEntry getReferencedRepositoryEntry() { + return null; + } + + @Override + public boolean needsReferenceToARepositoryEntry() { + return false; + } + + + public void updateModuleConfig(boolean participantbox, boolean coachbox, boolean alterfile, + boolean limitcount, int filecount, boolean timeframe, Date start, Date end) { + ModuleConfiguration config = getModuleConfiguration(); + + config.setBooleanEntry(CONFIG_KEY_PARTICIPANTBOX, participantbox); + config.setBooleanEntry(CONFIG_KEY_COACHBOX, coachbox); + config.setBooleanEntry(CONFIG_KEY_ALTERFILE, alterfile); + config.setBooleanEntry(CONFIG_KEY_LIMITCOUNT, limitcount); + if (limitcount){ + config.set(CONFIG_KEY_FILECOUNT, filecount); + } + + config.setBooleanEntry(CONFIG_KEY_TIMEFRAME, timeframe); + if (timeframe){ + config.set(CONFIG_KEY_DATESTART, start); + config.set(CONFIG_KEY_DATEEND, end); + } + } + + public boolean hasParticipantBoxConfigured() { + boolean hasStundentBox = getModuleConfiguration().getBooleanSafe(CONFIG_KEY_PARTICIPANTBOX); + return hasStundentBox; + } + + public boolean hasCoachBoxConfigured() { + boolean hasTeacherBox = getModuleConfiguration().getBooleanSafe(CONFIG_KEY_COACHBOX); + return hasTeacherBox; + } + + public boolean hasAlterFileConfigured() { + boolean hasStundentBox = getModuleConfiguration().getBooleanSafe(CONFIG_KEY_PARTICIPANTBOX); + if (hasStundentBox) { + boolean hasAlterFile = getModuleConfiguration().getBooleanSafe(CONFIG_KEY_ALTERFILE); + return hasAlterFile; + } + return false; + } + + public boolean hasLimitCountConfigured() { + boolean hasStundentBox = getModuleConfiguration().getBooleanSafe(CONFIG_KEY_PARTICIPANTBOX); + if (hasStundentBox) { + boolean hasLimitCount = getModuleConfiguration().getBooleanSafe(CONFIG_KEY_LIMITCOUNT); + return hasLimitCount; + } + return false; + } + + public boolean isGreaterOrEqualToLimit (int count) { + ModuleConfiguration config = getModuleConfiguration(); + int limit = config.getBooleanEntry(CONFIG_KEY_FILECOUNT) != null ? + (int) config.get(CONFIG_KEY_FILECOUNT) : 0; + return count >= limit; + } + + public boolean hasDropboxTimeFrameConfigured() { + boolean hasStundentBox = getModuleConfiguration().getBooleanSafe(CONFIG_KEY_PARTICIPANTBOX); + if (hasStundentBox) { + boolean hasTimeFrame = getModuleConfiguration().getBooleanSafe(CONFIG_KEY_TIMEFRAME); + return hasTimeFrame; + } + return false; + } + + public boolean isInDropboxTimeFrame () { + ModuleConfiguration config = getModuleConfiguration(); + Date start = config.getBooleanEntry(CONFIG_KEY_DATESTART) != null ? + (Date) config.getDateValue(CONFIG_KEY_DATESTART) : new Date(); + Date end = config.getBooleanEntry(CONFIG_KEY_DATEEND) != null ? + (Date) config.getDateValue(CONFIG_KEY_DATEEND) : new Date(); + Date current = new Date(); + + return start.before(current) && end.after(current); + } + + + public int getLimitCount() { + ModuleConfiguration config = getModuleConfiguration(); + return config.getBooleanEntry(CONFIG_KEY_FILECOUNT) != null ? + (int) config.get(CONFIG_KEY_FILECOUNT) : 0; + } + + public Date getDateStart() { + ModuleConfiguration config = getModuleConfiguration(); + return config.getBooleanEntry(CONFIG_KEY_DATESTART) != null ? + (Date) config.getDateValue(CONFIG_KEY_DATESTART) : new Date(); + } + + public Date getDateEnd() { + ModuleConfiguration config = getModuleConfiguration(); + return config.getBooleanEntry(CONFIG_KEY_DATEEND) != null ? + (Date) config.getDateValue(CONFIG_KEY_DATEEND) : new Date(); + } + + + @Override + public StatusDescription isConfigValid() { + StatusDescription sd = StatusDescription.NOERROR; + boolean isValid = hasCoachBoxConfigured() || hasParticipantBoxConfigured(); + if (!isValid) { + String shortKey = "error.noreference.short"; + String longKey = "error.noreference.long"; + String[] params = new String[] { this.getShortTitle() }; + @SuppressWarnings("deprecation") + String translPackage = Util.getPackageName(PFEditController.class); + sd = new StatusDescription(StatusDescription.ERROR, shortKey, longKey, params, translPackage); + sd.setDescriptionForUnit(getIdent()); + // set which pane is affected by error + sd.setActivateableViewIdentifier(CONFIG_KEY_PARTICIPANTBOX); + } + return sd; + } + + @Override + public TabbableController createEditController(UserRequest ureq, WindowControl wControl, BreadcrumbPanel stackPanel, + ICourse course, UserCourseEnvironment euce) { + PFEditController ordnerCtr = new PFEditController(ureq, wControl, this, course, euce); + CourseNode chosenNode = course.getEditorTreeModel().getCourseNode(euce.getCourseEditorEnv().getCurrentCourseNodeId()); + return new NodeEditController(ureq, wControl, course.getEditorTreeModel(), course, chosenNode, euce, ordnerCtr); + } + + @Override + public NodeRunConstructionResult createNodeRunConstructionResult(UserRequest ureq, WindowControl wControl, + UserCourseEnvironment userCourseEnv, NodeEvaluation ne, String nodecmd) { + PFRunController runController = new PFRunController(ureq, wControl, this, userCourseEnv); + return runController.createNodeRunConstructionResult(ureq); + } + + @Override + public Controller createPeekViewRunController(UserRequest ureq, WindowControl wControl, + UserCourseEnvironment userCourseEnv, NodeEvaluation ne) { + VFSContainer rootFolder = null; + CourseEnvironment courseEnv = userCourseEnv.getCourseEnvironment(); + Identity identity = userCourseEnv.getIdentityEnvironment().getIdentity(); + Path folderRelPath = null; + OlatRootFolderImpl baseContainer = courseEnv.getCourseBaseContainer(); + PFManager pfManager = CoreSpringFactory.getImpl(PFManager.class); + if (userCourseEnv.isCoach() || userCourseEnv.isAdmin()) { + folderRelPath = Paths.get(baseContainer.getBasefile().toPath().toString(), + PFManager.FILENAME_PARTICIPANTFOLDER, getIdent()); + rootFolder = new LocalFolderImpl(folderRelPath.toFile()); + } else if (userCourseEnv.isParticipant()) { + folderRelPath = Paths.get(baseContainer.getBasefile().toPath().toString(), + PFManager.FILENAME_PARTICIPANTFOLDER, getIdent(), + pfManager.getIdFolderName(identity)); + rootFolder = new LocalFolderImpl(folderRelPath.toFile()); + } + + if (rootFolder == null) { + return super.createPeekViewRunController(ureq, wControl, userCourseEnv, ne); + } else { + return new PFPeekviewController(ureq, wControl, rootFolder, getIdent(), 4); + } + } + + @Override + public Controller createPreviewController(UserRequest ureq, WindowControl wControl, + UserCourseEnvironment userCourseEnv, NodeEvaluation ne) { + return new PFPreviewController(ureq, wControl, this, userCourseEnv); + + } + + @Override + public void cleanupOnDelete(ICourse course) { + // mark the subscription to this node as deleted + SubscriptionContext folderSubContext = CourseModule.createTechnicalSubscriptionContext(course.getCourseEnvironment(), this); + NotificationsManager.getInstance().delete(folderSubContext); + // delete filesystem + + CourseEnvironment courseEnv = course.getCourseEnvironment(); + File root = Paths.get(courseEnv.getCourseBaseContainer().getRelPath(), + PFManager.FILENAME_PARTICIPANTFOLDER, getIdent()).toFile(); + if (root.exists()){ + FileUtils.deleteDirsAndFiles(root, true, true); + } + } + + @Override + public StatusDescription[] isConfigValid(CourseEditorEnv cev) { + return new StatusDescription[]{StatusDescription.NOERROR}; + } + +} diff --git a/src/main/java/org/olat/course/nodes/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/nodes/_i18n/LocalStrings_de.properties index f868b883f846027c9478a0dae69bd4d294acd624..860fa1e35cf068b8dd0bc6c973b3ce491c1adabf 100644 --- a/src/main/java/org/olat/course/nodes/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/course/nodes/_i18n/LocalStrings_de.properties @@ -22,6 +22,7 @@ title_iqtest=Test title_ms=Bewertung title_qti21assessment=Test (QTI 2.1) title_scorm=SCORM-Lerninhalt +title_pf=Teilnehmer Ordner title_sp=Einzelne Seite title_st=Struktur title_ta=<s>Aufgabe (deprecated)</s> diff --git a/src/main/java/org/olat/course/nodes/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/nodes/_i18n/LocalStrings_en.properties index e826ab771577ee83fc241348603cebe0f8f54d5a..5c747375341c770563e48c38bb8c75ce64555179 100644 --- a/src/main/java/org/olat/course/nodes/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/course/nodes/_i18n/LocalStrings_en.properties @@ -29,6 +29,7 @@ title_podcast=Podcast title_projectbroker=Topic assignment title_qti21assessment=Test (QTI 2.1) title_scorm=SCORM learning content +title_pf=Participant Folder title_sp=Single page title_st=Structure title_ta=<s>Task (deprecated)</s> diff --git a/src/main/java/org/olat/course/nodes/_spring/buildingblockContext.xml b/src/main/java/org/olat/course/nodes/_spring/buildingblockContext.xml index 9c91f7dfb363927f6176844b70c9331d6f634594..76aaa6d4ee002f9e58059b7c151c522fa5fddb6a 100644 --- a/src/main/java/org/olat/course/nodes/_spring/buildingblockContext.xml +++ b/src/main/java/org/olat/course/nodes/_spring/buildingblockContext.xml @@ -8,7 +8,7 @@ http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> - <context:component-scan base-package="org.olat.course.nodes.en,org.olat.course.nodes.cl,org.olat.course.nodes.projectbroker.service,org.olat.course.nodes.gta" /> + <context:component-scan base-package="org.olat.course.nodes.en,org.olat.course.nodes.cl,org.olat.course.nodes.projectbroker.service,org.olat.course.nodes.gta,org.olat.course.nodes.pf" /> <!-- Course node spring config: Course Nodes are searched on the whole classpath, just place your CourceNodeConfiguration somewhere on the classpath best as a jar. The xml file with ending ...Context.xml has do to be outside of the jar to get automatically loaded --> @@ -20,6 +20,10 @@ --> <bean id="bbfactory" class="org.olat.course.nodes.CourseNodeFactory"></bean> + + <bean id="pf" class="org.olat.course.nodes.pf.PFCourseNodeConfiguration" scope="prototype"> + <property name="order" value="111"/> + </bean> <bean id="st" class="org.olat.course.nodes.st.STCourseNodeConfiguration" scope="prototype"> <property name="order" value="1" /> diff --git a/src/main/java/org/olat/course/nodes/pf/PFCourseNodeConfiguration.java b/src/main/java/org/olat/course/nodes/pf/PFCourseNodeConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..45c4a6772fb2da39a7f1bce4a5c40e2a61369036 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/pf/PFCourseNodeConfiguration.java @@ -0,0 +1,69 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.course.nodes.pf; + +import java.util.Locale; + +import org.olat.core.gui.translator.Translator; +import org.olat.core.util.Util; +import org.olat.course.nodes.AbstractCourseNodeConfiguration; +import org.olat.course.nodes.CourseNode; +import org.olat.course.nodes.CourseNodeConfiguration; +import org.olat.course.nodes.CourseNodeGroup; +import org.olat.course.nodes.PFCourseNode; +/** +* +* @author Fabian Kiefer, fabian.kiefer@frentix.com, http://www.frentix.com +* +*/ +public class PFCourseNodeConfiguration extends AbstractCourseNodeConfiguration { + + public PFCourseNodeConfiguration() { + super(); + } + + @Override + public String getAlias() { + return PFCourseNode.TYPE; + } + + @Override + public String getGroup() { + return CourseNodeGroup.content.name(); + } + + @Override + public CourseNode getInstance() { + return new PFCourseNode(); + } + + @Override + public String getLinkText(Locale locale) { + Translator fallback = Util.createPackageTranslator(CourseNodeConfiguration.class, locale); + Translator translator = Util.createPackageTranslator(this.getClass(), locale, fallback); + return translator.translate("title_pf"); + } + + @Override + public String getIconCSSClass() { + return "o_pf_icon"; + } + +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..950f3139e51bf23bd3d7096779a7bbbbede43a31 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/pf/manager/FileSystemExport.java @@ -0,0 +1,161 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.course.nodes.pf.manager; + +import java.io.IOException; +import java.io.InputStream; +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.HashSet; +import java.util.List; +import java.util.Set; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import javax.servlet.http.HttpServletResponse; + +import org.olat.core.CoreSpringFactory; +import org.olat.core.gui.media.MediaResource; +import org.olat.core.id.Identity; +import org.olat.core.logging.OLog; +import org.olat.core.logging.Tracing; +import org.olat.course.nodes.PFCourseNode; +import org.olat.course.run.environment.CourseEnvironment; +import org.olat.user.UserManager; +/** +* +* Initial date: 15.12.2016<br> +* @author Fabian Kiefer, fabian.kiefer@frentix.com, http://www.frentix.com +* +*/ +public class FileSystemExport implements MediaResource { + + private static final OLog log = Tracing.createLoggerFor(FileSystemExport.class); + + private List<Identity> identities; + private PFCourseNode pfNode; + private CourseEnvironment courseEnv; + + public FileSystemExport(List<Identity> identities, PFCourseNode pfNode, CourseEnvironment courseEnv) { + super(); + this.identities = identities; + this.pfNode = pfNode; + this.courseEnv = courseEnv; + } + + @Override + public boolean acceptRanges() { + return false; + } + + @Override + public String getContentType() { + return "application/zip"; + } + + @Override + public Long getSize() { + return null; + } + + @Override + public InputStream getInputStream() { + return null; + } + + @Override + public Long getLastModified() { + return null; + } + + @Override + public void prepare(HttpServletResponse hres) { + try (ZipOutputStream zout = new ZipOutputStream(hres.getOutputStream())) { + zout.setLevel(9); + + String pfolder = "participantfolder/"; + + Path relPath = Paths.get(courseEnv.getCourseBaseContainer().getBasefile().getAbsolutePath(), + pfolder, pfNode.getIdent()); + + fsToZip(zout, relPath, pfolder); + + zout.close(); + + } catch (IOException e) { + log.error("", e); + } + } + + @Override + public void release() { + // + } + + private void fsToZip(ZipOutputStream zout, final Path sourceFolder, final String targetPath) throws IOException { + UserManager userManager = CoreSpringFactory.getImpl(UserManager.class); + Set<Long> idKeys = new HashSet<>(); + for (Identity identity : identities) { + idKeys.add(identity.getKey()); + } + Files.walkFileTree(sourceFolder, new SimpleFileVisitor<Path>() { + private String containsID (String relPath) { + for (Long key : idKeys) { + if (relPath.contains(key.toString())) { + String exportFolderName = userManager.getUserDisplayName(key).replace(", ", "_") + "_" + key; + return relPath.replace(key.toString(), exportFolderName); + } + } + return null; + } + + private boolean boxesEnabled(String relPath) { + return pfNode.hasParticipantBoxConfigured() && relPath.contains(PFManager.FILENAME_DROPBOX) + || pfNode.hasCoachBoxConfigured() && relPath.contains(PFManager.FILENAME_RETURNBOX); + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + String relPath = sourceFolder.relativize(file).toString(); + if ((relPath = containsID(relPath)) != null && boxesEnabled(relPath)) { + zout.putNextEntry(new ZipEntry(targetPath + relPath)); + Files.copy(file, zout); + zout.closeEntry(); + } + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + String relPath = sourceFolder.relativize(dir).toString() + "/"; + if ((relPath = containsID(relPath)) != null && boxesEnabled(relPath)) { + zout.putNextEntry(new ZipEntry(targetPath + relPath)); + zout.closeEntry(); + } + return FileVisitResult.CONTINUE; + } + }); + } + +} \ No newline at end of file diff --git a/src/main/java/org/olat/course/nodes/pf/manager/PFEvent.java b/src/main/java/org/olat/course/nodes/pf/manager/PFEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..dc665476479022e962423b7f896f811911bdf10b --- /dev/null +++ b/src/main/java/org/olat/course/nodes/pf/manager/PFEvent.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.course.nodes.pf.manager; + +import org.olat.core.gui.components.form.flexible.FormItem; +import org.olat.core.gui.components.form.flexible.impl.FormEvent; +import org.olat.core.gui.control.Event; +import org.olat.core.id.Identity; +/** +* +* Initial date: 20.12.2016<br> +* @author Fabian Kiefer, fabian.kiefer@frentix.com, http://www.frentix.com +* +*/ +public class PFEvent extends FormEvent { + /** + * + */ + private static final long serialVersionUID = 1L; + + private Identity identity; + + public PFEvent(Event event, FormItem source, int action) { + super(event, source, action); + } + + public PFEvent(String command, FormItem source, Identity identity) { + super(command, source); + this.identity = identity; + } + + public Identity getIdentity() { + return identity; + } + + + + +} diff --git a/src/main/java/org/olat/course/nodes/pf/manager/PFManager.java b/src/main/java/org/olat/course/nodes/pf/manager/PFManager.java new file mode 100644 index 0000000000000000000000000000000000000000..a30be0b771d21c0fe5041dc25b00a96f82ad5b9d --- /dev/null +++ b/src/main/java/org/olat/course/nodes/pf/manager/PFManager.java @@ -0,0 +1,588 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.course.nodes.pf.manager; + +import java.io.File; +import java.io.IOException; +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.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import org.olat.basesecurity.GroupRoles; +import org.olat.core.commons.modules.bc.vfs.OlatRootFolderImpl; +import org.olat.core.commons.services.notifications.SubscriptionContext; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.media.MediaResource; +import org.olat.core.gui.translator.Translator; +import org.olat.core.id.Identity; +import org.olat.core.logging.OLog; +import org.olat.core.logging.Tracing; +import org.olat.core.util.FileUtils; +import org.olat.core.util.Util; +import org.olat.core.util.i18n.I18nManager; +import org.olat.core.util.vfs.NamedContainerImpl; +import org.olat.core.util.vfs.VFSContainer; +import org.olat.core.util.vfs.VFSItem; +import org.olat.core.util.vfs.VFSLeaf; +import org.olat.core.util.vfs.VFSManager; +import org.olat.core.util.vfs.VirtualContainer; +import org.olat.core.util.vfs.callbacks.VFSSecurityCallback; +import org.olat.course.CourseModule; +import org.olat.course.groupsandrights.CourseGroupManager; +import org.olat.course.nodes.PFCourseNode; +import org.olat.course.nodes.pf.ui.DropBoxRow; +import org.olat.course.nodes.pf.ui.PFRunController; +import org.olat.course.run.environment.CourseEnvironment; +import org.olat.course.run.userview.UserCourseEnvironment; +import org.olat.group.BusinessGroup; +import org.olat.group.BusinessGroupService; +import org.olat.user.UserManager; +import org.olat.user.UserPropertiesRow; +import org.olat.user.propertyhandlers.UserPropertyHandler; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + + +/** +* +* Initial date: 09.12.2016<br> +* @author Fabian Kiefer, fabian.kiefer@frentix.com, http://www.frentix.com +* +*/ +@Service +public class PFManager { + + private static final OLog log = Tracing.createLoggerFor(PFManager.class); + + public static final String FILENAME_PARTICIPANTFOLDER = "participantfolder"; + public static final String FILENAME_RETURNBOX = "returnbox"; + public static final String FILENAME_DROPBOX = "dropbox"; + + @Autowired + private BusinessGroupService groupService; + @Autowired + private UserManager userManager; + + /** + * Resolve or create drop folder. + * + * @param courseEnv + * @param pfNode + * @param identity + * @return the VFSContainer + */ + private VFSContainer resolveOrCreateDropFolder(CourseEnvironment courseEnv, PFCourseNode pfNode, Identity identity) { + Path relPath = Paths.get(FILENAME_PARTICIPANTFOLDER, pfNode.getIdent(), getIdFolderName(identity), FILENAME_DROPBOX); + OlatRootFolderImpl baseContainer = courseEnv.getCourseBaseContainer(); + VFSContainer dropboxContainer = VFSManager.resolveOrCreateContainerFromPath(baseContainer, relPath.toString()); + return dropboxContainer; + } + + /** + * Resolve or create return folder. + * + * @param courseEnv + * @param pfNode + * @param identity + * @return the VFSContainer + */ + private VFSContainer resolveOrCreateReturnFolder(CourseEnvironment courseEnv, PFCourseNode pfNode, Identity identity) { + Path relPath = Paths.get(FILENAME_PARTICIPANTFOLDER, pfNode.getIdent(), getIdFolderName(identity), FILENAME_RETURNBOX); + OlatRootFolderImpl baseContainer = courseEnv.getCourseBaseContainer(); + VFSContainer returnboxContainer = VFSManager.resolveOrCreateContainerFromPath(baseContainer, relPath.toString()); + return returnboxContainer; + } + + /** + * Count files recursively for each participant. + * + * @param vfsContainer the root folder + * @return the count + */ + private int countFiles(VFSContainer vfsContainer) { + int counter = 0; + if (vfsContainer.exists()) { + List<VFSItem> children = vfsContainer.getItems(); + for (VFSItem vfsItem : children) { + if (vfsItem instanceof VFSContainer){ + counter += countFiles((VFSContainer)vfsItem); + } else { + String filename = vfsItem.getName(); + String suffix = FileUtils.getFileSuffix(filename); + if (!filename.startsWith(".") && !"zip".equals(suffix)){ + counter++; + } + } + } + } + return counter; + } + + + /** + * Gets the last updated file for each participant. + * + * @param courseEnv + * @param pfNode + * @param identity + * @return the last updated file as Date + */ + private Date getLastUpdated(CourseEnvironment courseEnv, PFCourseNode pfNode, Identity identity, String fileName) { + Date latest = null; + List<Long> lastUpdated = new ArrayList<>(); + OlatRootFolderImpl baseContainer = courseEnv.getCourseBaseContainer(); + Path path = Paths.get(baseContainer.getBasefile().toPath().toString(), FILENAME_PARTICIPANTFOLDER, + pfNode.getIdent(), getIdFolderName(identity), fileName); + try { + Files.walkFileTree(path, new SimpleFileVisitor<Path>() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + lastUpdated.add(attrs.lastModifiedTime().toMillis()); + return FileVisitResult.CONTINUE; + } + }); + } catch (IOException e) { + log.error("Unknown IOE",e); + } + Collections.sort(lastUpdated); + if (lastUpdated.size() > 0) { + latest = new Date(lastUpdated.get(lastUpdated.size() - 1)); + } + return latest; + } + + /** + * Upload file to drop box. + * + * @param uploadFile + * @param fileName + * @param limitFileCount + * @param courseEnv + * @param pfNode + * @param identity + * @return true, if successful + */ + public boolean uploadFileToDropBox(File uploadFile, String fileName, int limitFileCount, + CourseEnvironment courseEnv, PFCourseNode pfNode, Identity identity) { + if (uploadFile.exists() && uploadFile.isFile() && uploadFile.length() > 0){ + VFSContainer dropbox = resolveOrCreateDropFolder(courseEnv, pfNode, identity); + int fileCount = countFiles(dropbox); + if (fileCount <= limitFileCount) { + VFSLeaf uploadedFile = dropbox.createChildLeaf(fileName); + VFSManager.copyContent(uploadFile, uploadedFile); + return true; + } + + } + return false; + } + + /** + * Upload file to return box. + * + * @param uploadFile + * @param fileName + * @param courseEnv + * @param pfNode + * @param identity + */ + private void uploadFileToReturnBox(File uploadFile, String fileName, CourseEnvironment courseEnv, + PFCourseNode pfNode, Identity identity) { + if (uploadFile.exists() && uploadFile.isFile() && uploadFile.length() > 0) { + VFSContainer dropbox = resolveOrCreateReturnFolder(courseEnv, pfNode, identity); + VFSLeaf uploadedFile = dropbox.createChildLeaf(fileName); + VFSManager.copyContent(uploadFile, uploadedFile); + } + } + + /** + * Upload file to all return boxes of a given list of identities. + * + * @param uploadFile + * @param fileName + * @param courseEnv + * @param pfNode + * @param identities + */ + public void uploadFileToAllReturnBoxes (File uploadFile, String fileName, CourseEnvironment courseEnv, + PFCourseNode pfNode, List<Identity> identities) { + for(Identity identity : identities){ + uploadFileToReturnBox(uploadFile, fileName, courseEnv, pfNode, identity); + } + } + + + /** + * Export media resource as folder download. + * + * @param ureq + * @param identities + * @param pfNode + * @param courseEnv + */ + public MediaResource exportMediaResource (UserRequest ureq, List<Identity> identities, PFCourseNode pfNode, CourseEnvironment courseEnv) { + MediaResource resource = new FileSystemExport (identities, pfNode, courseEnv); + ureq.getDispatchResult().setResultingMediaResource(resource); + return resource; + } + + /** + * Calculate callback dependent on CourseModule Settings. + * + * @param pfNode + * @param dropbox + * @return the VFSSecurityCallback + */ + private VFSSecurityCallback calculateCallback (CourseEnvironment courseEnv, PFCourseNode pfNode, VFSContainer dropbox) { + VFSSecurityCallback callback; + SubscriptionContext folderSubContext = CourseModule.createSubscriptionContext(courseEnv, pfNode); + int count = countFiles(dropbox); + boolean limitCount = pfNode.hasLimitCountConfigured() && pfNode.isGreaterOrEqualToLimit(count); + boolean timeFrame = pfNode.hasDropboxTimeFrameConfigured() && !pfNode.isInDropboxTimeFrame(); + boolean alterFile = pfNode.hasAlterFileConfigured(); + if (timeFrame || limitCount && !alterFile){ + callback = new ReadOnlyCallback(folderSubContext); + } else if (limitCount && alterFile) { + callback = new ReadDeleteCallback(folderSubContext); + } else if (!limitCount && !alterFile) { + callback = new ReadWriteCallback(folderSubContext); + } else { + callback = new ReadWriteDeleteCallback(folderSubContext); + } + return callback; + } + + + /** + * Provide coach or participant view for webdav. + * + * @param pfNode + * @param userCourseEnv + * @param identity + * @return the VFSContainer + */ + public VFSContainer provideCoachOrParticipantContainer (PFCourseNode pfNode, UserCourseEnvironment userCourseEnv, Identity identity) { + VFSContainer vfsContainer = null; + if (userCourseEnv.isCoach() || userCourseEnv.isAdmin()) { + vfsContainer = provideCoachContainer(pfNode, userCourseEnv.getCourseEnvironment(), identity); + } else if (userCourseEnv.isParticipant()) { + vfsContainer = provideParticipantContainer(pfNode, userCourseEnv, identity); + } + return vfsContainer; + } + + /** + * Provide participant view in webdav. + * + * @param pfNode + * @param courseEnv + * @param identity + * @return the VFS container + */ + private VFSContainer provideParticipantContainer (PFCourseNode pfNode, UserCourseEnvironment userCourseEnv, Identity identity) { + Locale locale = I18nManager.getInstance().getLocaleOrDefault(identity.getUser().getPreferences().getLanguage()); + Translator translator = Util.createPackageTranslator(PFRunController.class, locale); + CourseEnvironment courseEnv = userCourseEnv.getCourseEnvironment(); + boolean readOnly = userCourseEnv.isCourseReadOnly(); + SubscriptionContext subsContext = CourseModule.createSubscriptionContext(courseEnv, pfNode); + String path = courseEnv.getCourseBaseContainer().getRelPath() + "/" + FILENAME_PARTICIPANTFOLDER; + VFSContainer courseElementBaseContainer = new OlatRootFolderImpl(path, null); + VirtualContainer namedCourseFolder = new VirtualContainer(identity.getName()); + Path relPath = Paths.get(pfNode.getIdent(), getIdFolderName(identity)); + VFSContainer userBaseContainer = VFSManager.resolveOrCreateContainerFromPath(courseElementBaseContainer, relPath.toString()); + if (pfNode.hasParticipantBoxConfigured()){ + VFSContainer dropContainer = new NamedContainerImpl(translator.translate("drop.box"), + VFSManager.resolveOrCreateContainerFromPath(userBaseContainer, FILENAME_DROPBOX)); + if (readOnly) { + dropContainer.setLocalSecurityCallback(new ReadOnlyCallback(subsContext)); + } else { + VFSContainer dropbox = resolveOrCreateDropFolder(courseEnv, pfNode, identity); + VFSSecurityCallback callback = calculateCallback(courseEnv, pfNode, dropbox); + dropContainer.setLocalSecurityCallback(callback); + } + namedCourseFolder.addItem(dropContainer); + } + if (pfNode.hasCoachBoxConfigured()){ + VFSContainer returnContainer = new NamedContainerImpl(translator.translate("return.box"), + VFSManager.resolveOrCreateContainerFromPath(userBaseContainer, FILENAME_RETURNBOX)); + returnContainer.setLocalSecurityCallback(new ReadOnlyCallback(subsContext)); + namedCourseFolder.addItem(returnContainer); + } + return namedCourseFolder; + } + + /** + * Provide coach view in webdav. + * + * @param pfNode + * @param courseEnv + * @param identity + * @return the VFSContainer + */ + private VFSContainer provideCoachContainer (PFCourseNode pfNode, CourseEnvironment courseEnv, Identity identity) { + Locale locale = I18nManager.getInstance().getLocaleOrDefault(identity.getUser().getPreferences().getLanguage()); + Translator translator = Util.createPackageTranslator(PFRunController.class, locale); + SubscriptionContext nodefolderSubContext = CourseModule.createSubscriptionContext(courseEnv, pfNode); + List<Identity> participants = getParticipants(identity, courseEnv); + String path = courseEnv.getCourseBaseContainer().getRelPath() + "/" + FILENAME_PARTICIPANTFOLDER; + VFSContainer courseElementBaseContainer = new OlatRootFolderImpl(path, null); + VirtualContainer namedCourseFolder = new VirtualContainer(identity.getName()); + for (Identity participant : participants) { + Path relPath = Paths.get(pfNode.getIdent(), getIdFolderName(participant)); + VFSContainer userBaseContainer = VFSManager.resolveOrCreateContainerFromPath(courseElementBaseContainer, relPath.toString()); + VirtualContainer participantFolder = new VirtualContainer(participant.getName()); + namedCourseFolder.addItem(participantFolder); + + if (pfNode.hasParticipantBoxConfigured()){ + VFSContainer dropContainer = new NamedContainerImpl(translator.translate("drop.box"), + VFSManager.resolveOrCreateContainerFromPath(userBaseContainer, FILENAME_DROPBOX)); + if (identity.equals(participant)){ + VFSContainer dropbox = resolveOrCreateDropFolder(courseEnv, pfNode, identity); + VFSSecurityCallback callback = calculateCallback(courseEnv, pfNode, dropbox); + dropContainer.setLocalSecurityCallback(callback); + } else { + dropContainer.setLocalSecurityCallback(new ReadOnlyCallback(nodefolderSubContext)); + } + participantFolder.addItem(dropContainer); + } + + if (pfNode.hasCoachBoxConfigured()){ + VFSContainer returnContainer = new NamedContainerImpl(translator.translate("return.box"), + VFSManager.resolveOrCreateContainerFromPath(userBaseContainer, FILENAME_RETURNBOX)); + returnContainer.setLocalSecurityCallback(new ReadWriteDeleteCallback(nodefolderSubContext)); + participantFolder.addItem(returnContainer); + } + } + + return namedCourseFolder; + } + + + /** + * Provide participant folder in GUI. + * + * @param pfNode + * @param pfView + * @param courseEnv + * @param identity + * @param isCoach + * @return the VFS container + */ + public VFSContainer provideParticipantFolder (PFCourseNode pfNode, PFView pfView, + CourseEnvironment courseEnv, Identity identity, boolean isCoach, boolean readOnly) { + Locale locale = I18nManager.getInstance().getLocaleOrDefault(identity.getUser().getPreferences().getLanguage()); + SubscriptionContext nodefolderSubContext = CourseModule.createSubscriptionContext(courseEnv, pfNode); + Translator translator = Util.createPackageTranslator(PFRunController.class, locale); + + String path = courseEnv.getCourseBaseContainer().getRelPath() + "/" + FILENAME_PARTICIPANTFOLDER; + VFSContainer courseElementBaseContainer = new OlatRootFolderImpl(path, null); + + Path relPath = Paths.get(pfNode.getIdent(), getIdFolderName(identity)); + + VFSContainer userBaseContainer = VFSManager.resolveOrCreateContainerFromPath(courseElementBaseContainer, relPath.toString()); + + String baseContainerName = userManager.getUserDisplayName(identity); + + VirtualContainer namedCourseFolder = new VirtualContainer(baseContainerName); + namedCourseFolder.setLocalSecurityCallback(new ReadOnlyCallback(nodefolderSubContext)); + + VFSContainer dropContainer = new NamedContainerImpl(PFView.onlyDrop.equals(pfView) || PFView.onlyReturn.equals(pfView) ? + baseContainerName : translator.translate("drop.box"), + VFSManager.resolveOrCreateContainerFromPath(userBaseContainer, FILENAME_DROPBOX)); + + if (pfNode.hasParticipantBoxConfigured()){ + namedCourseFolder.addItem(dropContainer); + } + + VFSContainer returnContainer = new NamedContainerImpl(PFView.onlyDrop.equals(pfView) || PFView.onlyReturn.equals(pfView) ? + baseContainerName : translator.translate("return.box"), + VFSManager.resolveOrCreateContainerFromPath(userBaseContainer, FILENAME_RETURNBOX)); + + if (pfNode.hasCoachBoxConfigured()){ + namedCourseFolder.addItem(returnContainer); + } + + + if (readOnly) { + dropContainer.setLocalSecurityCallback(new ReadOnlyCallback(nodefolderSubContext)); + returnContainer.setLocalSecurityCallback(new ReadOnlyCallback(nodefolderSubContext)); + } else { + if (isCoach) { + dropContainer.setLocalSecurityCallback(new ReadOnlyCallback(nodefolderSubContext)); + returnContainer.setLocalSecurityCallback(new ReadWriteDeleteCallback(nodefolderSubContext)); + } else { + VFSContainer dropbox = resolveOrCreateDropFolder(courseEnv, pfNode, identity); + VFSSecurityCallback callback = calculateCallback(courseEnv, pfNode, dropbox); + dropContainer.setLocalSecurityCallback(callback); + returnContainer.setLocalSecurityCallback(new ReadOnlyCallback(nodefolderSubContext)); + } + } + + VFSContainer folderRunContainer; + switch (pfView) { + case dropAndReturn: + folderRunContainer = namedCourseFolder; + break; + case onlyDrop: + folderRunContainer = dropContainer; + break; + case onlyReturn: + folderRunContainer = returnContainer; + break; + default: + folderRunContainer = namedCourseFolder; + break; + } + + return folderRunContainer; + } + + /** + * Gets the participants for different group or course coaches. + * + * @param id the identity + * @param pfNode + * @param locale + * @param courseEnv + * @return the participants + */ + public List<Identity> getParticipants (Identity id, CourseEnvironment courseEnv) { + CourseGroupManager groupManager = courseEnv.getCourseGroupManager(); + Set<Identity> identitiesSet = new HashSet<>(); + // iterate course members + if (groupManager.isIdentityCourseCoach(id) || groupManager.isIdentityCourseAdministrator(id)) { + List<Identity> identities = groupManager.getParticipants(); + for (Identity identity : identities) { + identitiesSet.add(identity); + } + } + //iterate group members + List<BusinessGroup> bgroups = groupManager.getOwnedBusinessGroups(id); + if (bgroups != null) { + for (BusinessGroup bgroup : bgroups) { + List<Identity> identities = groupService.getMembers(bgroup, GroupRoles.participant.toString()); + for (Identity identity : identities) { + identitiesSet.add(identity); + } + } + } + List<Identity> participants = identitiesSet.stream().collect(Collectors.toList()); + return participants; + } + + /** + * Gets the participants for different group or course coaches as TableModel. + * + * @param id the identity + * @param pfNode + * @param userPropertyHandlers + * @param locale + * @param courseEnv + * @return the participants + */ + public List<DropBoxRow> getParticipants (Identity id, PFCourseNode pfNode, List<UserPropertyHandler> userPropertyHandlers, + Locale locale, CourseEnvironment courseEnv) { + Map<Identity, DropBoxRow> participantsMap = new HashMap<>(); + CourseGroupManager groupManager = courseEnv.getCourseGroupManager(); + // iterate course members + if (groupManager.isIdentityCourseCoach(id) || groupManager.isIdentityCourseAdministrator(id)) { + List<Identity> identities = groupManager.getParticipants(); + for (Identity identity : identities) { + VFSContainer dropbox = resolveOrCreateDropFolder(courseEnv, pfNode, identity); + int filecount = countFiles(dropbox); + VFSContainer returnbox = resolveOrCreateReturnFolder(courseEnv, pfNode, identity); + int filecountR = countFiles(returnbox); + Date lastModified = getLastUpdated(courseEnv, pfNode, identity, FILENAME_DROPBOX); + Date lastModifiedR = getLastUpdated(courseEnv, pfNode, identity, FILENAME_RETURNBOX); + UserPropertiesRow urow = new UserPropertiesRow(identity, userPropertyHandlers, locale); + participantsMap.put(identity, new DropBoxRow(urow, "status", + filecount, filecountR, pfNode.getLimitCount(), lastModified, lastModifiedR)); + + } + } + //iterate group members + List<BusinessGroup> bgroups = groupManager.getOwnedBusinessGroups(id); + if (bgroups != null) { + for (BusinessGroup bgroup : bgroups) { + List<Identity> identities = groupService.getMembers(bgroup, GroupRoles.participant.toString()); + for (Identity identity : identities) { + VFSContainer dropbox = resolveOrCreateDropFolder(courseEnv, pfNode, identity); + int filecount = countFiles(dropbox); + VFSContainer returnbox = resolveOrCreateReturnFolder(courseEnv, pfNode, identity); + int filecountR = countFiles(returnbox); + Date lastModified = getLastUpdated(courseEnv, pfNode, identity, FILENAME_DROPBOX); + Date lastModifiedR = getLastUpdated(courseEnv, pfNode, identity, FILENAME_RETURNBOX); + UserPropertiesRow urow = new UserPropertiesRow(identity, userPropertyHandlers, locale); + participantsMap.put(identity, new DropBoxRow(urow, "status", + filecount, filecountR, pfNode.getLimitCount(), lastModified, lastModifiedR)); + + } + } + } + List<DropBoxRow> participants = new ArrayList<>(participantsMap.values()); + return participants; + } + + /** + * Provide pfView dependent on ModuleConfiguration. + * + * @param pfNode + * @return the PF view + */ + public PFView providePFView (PFCourseNode pfNode) { + boolean hasParticipantBox = pfNode.hasParticipantBoxConfigured(); + boolean hasCoachBox = pfNode.hasCoachBoxConfigured(); + PFView pfView = PFView.dropAndReturn; + if (hasParticipantBox && hasCoachBox) { + pfView = PFView.dropAndReturn; + } else if (!hasParticipantBox && hasCoachBox) { + pfView = PFView.onlyReturn; + } else if (hasParticipantBox && !hasCoachBox) { + pfView = PFView.onlyDrop; + } else if (!hasParticipantBox && !hasCoachBox) { + pfView = PFView.nothingToDisplay; + } + return pfView; + } + + /** + * Gets the id folder name. + * + * @param identity the identity + * @return the id folder name + */ + public String getIdFolderName(Identity identity) { + return identity.getKey().toString(); + } + +} diff --git a/src/main/java/org/olat/course/nodes/pf/manager/PFNotifications.java b/src/main/java/org/olat/course/nodes/pf/manager/PFNotifications.java new file mode 100644 index 0000000000000000000000000000000000000000..99d30c3238f8eb302c4272873bf1ed02ecc40ce1 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/pf/manager/PFNotifications.java @@ -0,0 +1,159 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.course.nodes.pf.manager; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; + +import org.olat.core.commons.modules.bc.FileInfo; +import org.olat.core.commons.modules.bc.FolderManager; +import org.olat.core.commons.modules.bc.meta.MetaInfo; +import org.olat.core.commons.services.notifications.NotificationsManager; +import org.olat.core.commons.services.notifications.Publisher; +import org.olat.core.commons.services.notifications.Subscriber; +import org.olat.core.commons.services.notifications.model.SubscriptionListItem; +import org.olat.core.gui.translator.Translator; +import org.olat.core.id.Identity; +import org.olat.core.id.context.BusinessControlFactory; +import org.olat.core.logging.OLog; +import org.olat.core.logging.Tracing; +import org.olat.core.util.Util; +import org.olat.course.CourseFactory; +import org.olat.course.ICourse; +import org.olat.course.groupsandrights.CourseGroupManager; +import org.olat.course.nodes.CourseNode; +import org.olat.course.nodes.pf.ui.PFRunController; +import org.olat.course.run.environment.CourseEnvironment; +import org.olat.repository.RepositoryEntry; +/** +* +* Initial date: 05.01.2017<br> +* @author Fabian Kiefer, fabian.kiefer@frentix.com, http://www.frentix.com +* +*/ +public class PFNotifications { + private static final OLog log = Tracing.createLoggerFor(PFNotifications.class); + + private final Date compareDate; + private final Subscriber subscriber; + + private final Translator translator; + + private final List<SubscriptionListItem> items = new ArrayList<>(); + + private String displayname; + + private NotificationsManager notificationsManager; + private PFManager pfManager; + + public PFNotifications(Subscriber subscriber, Locale locale, Date compareDate, PFManager pfManager, + NotificationsManager notificationsManager) { + this.subscriber = subscriber; + this.compareDate = compareDate; + this.notificationsManager = notificationsManager; + this.pfManager = pfManager; + translator = Util.createPackageTranslator(PFRunController.class, locale); + } + + public List<SubscriptionListItem> getItems() throws Exception { + Publisher p = subscriber.getPublisher(); + Identity identity = subscriber.getIdentity(); + ICourse course = CourseFactory.loadCourse(p.getResId()); + CourseEnvironment courseEnv = course.getCourseEnvironment(); + CourseGroupManager groupManager = courseEnv.getCourseGroupManager(); + CourseNode node = course.getRunStructure().getNode(p.getSubidentifier()); + RepositoryEntry entry = course.getCourseEnvironment().getCourseGroupManager().getCourseEntry(); + Date latestNews = p.getLatestNewsDate(); + + if (notificationsManager.isPublisherValid(p) && compareDate.before(latestNews)) { + this.displayname = entry.getDisplayname(); + + if (groupManager.isIdentityCourseCoach(identity) || groupManager.isIdentityCourseAdministrator(identity)) { + List<Identity> participants = pfManager.getParticipants(identity, courseEnv); + + for (Identity participant : participants) { + gatherItems(participant, p, courseEnv, node); + } + } else { + gatherItems(identity, p, courseEnv, node); + } + } + return items; + } + + private void gatherItems (Identity participant, Publisher p, + CourseEnvironment courseEnv, CourseNode node) { + Path folderRoot = Paths.get(courseEnv.getCourseBaseContainer().getRelPath(), + PFManager.FILENAME_PARTICIPANTFOLDER, node.getIdent(), + pfManager.getIdFolderName(participant)); + + final List<FileInfo> fInfos = FolderManager.getFileInfos(folderRoot.toString(), compareDate); + + SubscriptionListItem subListItem; + for (Iterator<FileInfo> it_infos = fInfos.iterator(); it_infos.hasNext();) { + FileInfo fi = it_infos.next(); + MetaInfo metaInfo = fi.getMetaInfo(); + String filePath = fi.getRelPath(); + Date modDate = fi.getLastModified(); + String action = "upload"; + try { + Path basepath = courseEnv.getCourseBaseContainer().getBasefile().toPath(); + Path completepath = Paths.get(basepath.toString(), PFManager.FILENAME_PARTICIPANTFOLDER, + node.getIdent(), pfManager.getIdFolderName(participant), filePath); + BasicFileAttributes attrs = Files.readAttributes(completepath, BasicFileAttributes.class); + if (attrs.creationTime().toMillis() < attrs.lastModifiedTime().toMillis()) { + action = "modify"; + } + } catch (IOException ioe) { + log.error("IOException", ioe); + } + String forby = translator.translate("notifications.entry." + + (filePath.contains(PFManager.FILENAME_DROPBOX) ? "by" : "for")); + + String desc = translator.translate("notifications.entry." + action, + new String[] { filePath, forby, participant.getName() }); + String businessPath = p.getBusinessPath(); + String urlToSend = BusinessControlFactory.getInstance() + .getURLFromBusinessPathString(businessPath); + + String iconCssClass = null; + if (metaInfo != null) { + iconCssClass = metaInfo.getIconCssClass(); + } + if (metaInfo != null && !metaInfo.getName().startsWith(".")) { + subListItem = new SubscriptionListItem(desc, urlToSend, businessPath, modDate, iconCssClass); + items.add(subListItem); + } + } + } + + public String getDisplayname() { + return displayname; + } + +} diff --git a/src/main/java/org/olat/course/nodes/pf/manager/PFNotificationsHandler.java b/src/main/java/org/olat/course/nodes/pf/manager/PFNotificationsHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..a2db4064d27ac786e1df0fe369a839701d8eddaa --- /dev/null +++ b/src/main/java/org/olat/course/nodes/pf/manager/PFNotificationsHandler.java @@ -0,0 +1,154 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.course.nodes.pf.manager; + +import java.util.Date; +import java.util.List; +import java.util.Locale; + +import org.olat.core.CoreSpringFactory; +import org.olat.core.commons.services.notifications.NotificationsHandler; +import org.olat.core.commons.services.notifications.NotificationsManager; +import org.olat.core.commons.services.notifications.Publisher; +import org.olat.core.commons.services.notifications.Subscriber; +import org.olat.core.commons.services.notifications.SubscriptionContext; +import org.olat.core.commons.services.notifications.SubscriptionInfo; +import org.olat.core.commons.services.notifications.manager.NotificationsUpgradeHelper; +import org.olat.core.commons.services.notifications.model.SubscriptionListItem; +import org.olat.core.commons.services.notifications.model.TitleItem; +import org.olat.core.gui.translator.Translator; +import org.olat.core.gui.util.CSSHelper; +import org.olat.core.logging.OLog; +import org.olat.core.logging.Tracing; +import org.olat.core.util.Util; +import org.olat.course.CourseModule; +import org.olat.course.nodes.CourseNode; +import org.olat.course.nodes.pf.ui.PFRunController; +import org.olat.course.run.environment.CourseEnvironment; +import org.olat.group.BusinessGroup; +import org.olat.group.BusinessGroupService; +import org.olat.repository.RepositoryManager; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +/** +* +* Initial date: 05.01.2017<br> +* @author Fabian Kiefer, fabian.kiefer@frentix.com, http://www.frentix.com +* +*/ +@Service +public class PFNotificationsHandler implements NotificationsHandler { + + private static final OLog log = Tracing.createLoggerFor(PFNotificationsHandler.class); + protected static final String CSS_CLASS_ICON = "o_gta_icon"; + + @Autowired + private NotificationsManager notificationsManager; + @Autowired + private PFManager pfManager; + + public PFNotificationsHandler() { + + } + + protected static SubscriptionContext getSubscriptionContext(CourseEnvironment courseEnv, CourseNode node) { + return CourseModule.createSubscriptionContext(courseEnv, node, node.getIdent()); + } + + @Override + public SubscriptionInfo createSubscriptionInfo(Subscriber subscriber, Locale locale, Date compareDate) { + SubscriptionInfo si = null; + Publisher p = subscriber.getPublisher(); + + try { + final Translator translator = Util.createPackageTranslator(PFRunController.class, locale); + + PFNotifications notifications = new PFNotifications(subscriber, locale, compareDate, + pfManager, notificationsManager); + List<SubscriptionListItem> items = notifications.getItems(); + + if (items.isEmpty()) { + si = notificationsManager.getNoSubscriptionInfo(); + } else { + String displayName = notifications.getDisplayname(); + String title = translator.translate("notifications.header", new String[]{ displayName }); + si = new SubscriptionInfo(subscriber.getKey(), p.getType(), new TitleItem(title, CSS_CLASS_ICON), items); + } + + } catch (Exception e) { + log.error("Unknown Exception", e); + si = notificationsManager.getNoSubscriptionInfo(); + } + return si; + } + + private void checkPublisher(Publisher p) { + try { + if("BusinessGroup".equals(p.getResName())) { + BusinessGroup bg = CoreSpringFactory.getImpl(BusinessGroupService.class).loadBusinessGroup(p.getResId()); + if(bg == null) { + log.info("deactivating publisher with key; " + p.getKey(), null); + NotificationsManager.getInstance().deactivate(p); + } + } else if ("CourseModule".equals(p.getResName())) { + if(!NotificationsUpgradeHelper.checkCourse(p)) { + log.info("deactivating publisher with key; " + p.getKey(), null); + NotificationsManager.getInstance().deactivate(p); + } + } + } catch (Exception e) { + log.error("", e); + } + } + + private TitleItem getTitleItem(Publisher p, Translator translator) { + String title; + try { + String resName = p.getResName(); + if("BusinessGroup".equals(resName)) { + BusinessGroup bg = CoreSpringFactory.getImpl(BusinessGroupService.class).loadBusinessGroup(p.getResId()); + title = translator.translate("notifications.header.group", new String[]{bg.getName()}); + } else if("CourseModule".equals(resName)) { + String displayName = RepositoryManager.getInstance().lookupDisplayNameByOLATResourceableId(p.getResId()); + title = translator.translate("notifications.header.course", new String[]{displayName}); + } else { + title = translator.translate("notifications.header"); + } + } catch (Exception e) { + log.error("", e); + checkPublisher(p); + title = translator.translate("notifications.header"); + } + return new TitleItem(title, CSSHelper.CSS_CLASS_FILETYPE_FOLDER); + } + + @Override + public String createTitleInfo(Subscriber subscriber, Locale locale) { + Translator translator = Util.createPackageTranslator(PFRunController.class, locale); + TitleItem title = getTitleItem(subscriber.getPublisher(), translator); + return title.getInfoContent("text/plain"); + } + + @Override + public String getType() { + return "PFCourseNode"; + } + +} diff --git a/src/main/java/org/olat/course/nodes/pf/manager/PFView.java b/src/main/java/org/olat/course/nodes/pf/manager/PFView.java new file mode 100644 index 0000000000000000000000000000000000000000..5c091cd0d71f8e7a54363abf5d1dcc24c7e605a8 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/pf/manager/PFView.java @@ -0,0 +1,33 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.course.nodes.pf.manager; +/** +* +* @author Fabian Kiefer, fabian.kiefer@frentix.com, http://www.frentix.com +* +*/ +public enum PFView{ + onlyDrop, + onlyReturn, + displayDrop, + displayReturn, + dropAndReturn, + nothingToDisplay; +} \ No newline at end of file diff --git a/src/main/java/org/olat/course/nodes/pf/manager/ReadDeleteCallback.java b/src/main/java/org/olat/course/nodes/pf/manager/ReadDeleteCallback.java new file mode 100644 index 0000000000000000000000000000000000000000..57ddfb7c7bd3ec2733f0f7fadf41f08023f8a47f --- /dev/null +++ b/src/main/java/org/olat/course/nodes/pf/manager/ReadDeleteCallback.java @@ -0,0 +1,90 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.course.nodes.pf.manager; + +import org.olat.core.commons.services.notifications.SubscriptionContext; +import org.olat.core.util.vfs.Quota; +import org.olat.core.util.vfs.callbacks.VFSSecurityCallback; +/** +* +* @author Fabian Kiefer, fabian.kiefer@frentix.com, http://www.frentix.com +* +*/ +public class ReadDeleteCallback implements VFSSecurityCallback { + + private SubscriptionContext subsContext; + + public ReadDeleteCallback(SubscriptionContext subsContext) { + super(); + this.subsContext = subsContext; + } + + @Override + public boolean canRead() { + return true; + } + + @Override + public boolean canWrite() { + return false; + } + + @Override + public boolean canCreateFolder() { + return false; + } + + @Override + public boolean canDelete() { + return true; + } + + @Override + public boolean canList() { + return false; + } + + @Override + public boolean canCopy() { + return false; + } + + @Override + public boolean canDeleteRevisionsPermanently() { + return false; + } + + @Override + public Quota getQuota() { + return null; + } + + @Override + public void setQuota(Quota quota) { + + + } + + @Override + public SubscriptionContext getSubscriptionContext() { + return subsContext; + } + +} diff --git a/src/main/java/org/olat/course/nodes/pf/manager/ReadOnlyCallback.java b/src/main/java/org/olat/course/nodes/pf/manager/ReadOnlyCallback.java new file mode 100644 index 0000000000000000000000000000000000000000..058fb735e8234bb3b63146dbea4159755cd38e21 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/pf/manager/ReadOnlyCallback.java @@ -0,0 +1,89 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.course.nodes.pf.manager; + +import org.olat.core.commons.services.notifications.SubscriptionContext; +import org.olat.core.util.vfs.Quota; +import org.olat.core.util.vfs.callbacks.VFSSecurityCallback; +/** +* +* @author Fabian Kiefer, fabian.kiefer@frentix.com, http://www.frentix.com +* +*/ +public class ReadOnlyCallback implements VFSSecurityCallback { + + private SubscriptionContext subsContext; + + public ReadOnlyCallback(SubscriptionContext subsContext) { + super(); + this.subsContext = subsContext; + } + + @Override + public boolean canRead() { + return true; + } + + @Override + public boolean canWrite() { + return false; + } + + @Override + public boolean canCreateFolder() { + return false; + } + + @Override + public boolean canDelete() { + return false; + } + + @Override + public boolean canList() { + return false; + } + + @Override + public boolean canCopy() { + return false; + } + + @Override + public boolean canDeleteRevisionsPermanently() { + return false; + } + + @Override + public Quota getQuota() { + return null; + } + + @Override + public void setQuota(Quota quota) { + + } + + @Override + public SubscriptionContext getSubscriptionContext() { + return subsContext; + } + +} diff --git a/src/main/java/org/olat/course/nodes/pf/manager/ReadWriteCallback.java b/src/main/java/org/olat/course/nodes/pf/manager/ReadWriteCallback.java new file mode 100644 index 0000000000000000000000000000000000000000..c06c9a826066fba1af5396147f1953c0c3cb0207 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/pf/manager/ReadWriteCallback.java @@ -0,0 +1,90 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.course.nodes.pf.manager; + +import org.olat.core.commons.services.notifications.SubscriptionContext; +import org.olat.core.util.vfs.Quota; +import org.olat.core.util.vfs.callbacks.VFSSecurityCallback; +/** +* +* @author Fabian Kiefer, fabian.kiefer@frentix.com, http://www.frentix.com +* +*/ +public class ReadWriteCallback implements VFSSecurityCallback { + + private SubscriptionContext subsContext; + + public ReadWriteCallback(SubscriptionContext subsContext) { + super(); + this.subsContext = subsContext; + } + + @Override + public boolean canRead() { + return true; + } + + @Override + public boolean canWrite() { + return true; + } + + @Override + public boolean canCreateFolder() { + return false; + } + + @Override + public boolean canDelete() { + return false; + } + + @Override + public boolean canList() { + return false; + } + + @Override + public boolean canCopy() { + return false; + } + + @Override + public boolean canDeleteRevisionsPermanently() { + return false; + } + + @Override + public Quota getQuota() { + return null; + } + + @Override + public void setQuota(Quota quota) { + + + } + + @Override + public SubscriptionContext getSubscriptionContext() { + return subsContext; + } + +} diff --git a/src/main/java/org/olat/course/nodes/pf/manager/ReadWriteDeleteCallback.java b/src/main/java/org/olat/course/nodes/pf/manager/ReadWriteDeleteCallback.java new file mode 100644 index 0000000000000000000000000000000000000000..c25aeb64592747b60692a054bffdc9c22d4933af --- /dev/null +++ b/src/main/java/org/olat/course/nodes/pf/manager/ReadWriteDeleteCallback.java @@ -0,0 +1,89 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.course.nodes.pf.manager; + +import org.olat.core.commons.services.notifications.SubscriptionContext; +import org.olat.core.util.vfs.Quota; +import org.olat.core.util.vfs.callbacks.VFSSecurityCallback; +/** +* +* @author Fabian Kiefer, fabian.kiefer@frentix.com, http://www.frentix.com +* +*/ +public class ReadWriteDeleteCallback implements VFSSecurityCallback { + + private SubscriptionContext subsContext; + + public ReadWriteDeleteCallback(SubscriptionContext subsContext) { + super(); + this.subsContext = subsContext; + } + + @Override + public boolean canRead() { + return true; + } + + @Override + public boolean canWrite() { + return true; + } + + @Override + public boolean canCreateFolder() { + return true; + } + + @Override + public boolean canDelete() { + return true; + } + + @Override + public boolean canList() { + return true; + } + + @Override + public boolean canCopy() { + return true; + } + + @Override + public boolean canDeleteRevisionsPermanently() { + return false; + } + + @Override + public Quota getQuota() { + return null; + } + + @Override + public void setQuota(Quota quota) { + + } + + @Override + public SubscriptionContext getSubscriptionContext() { + return subsContext; + } + +} diff --git a/src/main/java/org/olat/course/nodes/pf/ui/DropBoxRow.java b/src/main/java/org/olat/course/nodes/pf/ui/DropBoxRow.java new file mode 100644 index 0000000000000000000000000000000000000000..6fba106bf0db43003dabc2121e8e98b2df57d695 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/pf/ui/DropBoxRow.java @@ -0,0 +1,78 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.course.nodes.pf.ui; + +import java.util.Date; + +import org.olat.user.UserPropertiesRow; + +/** + * The Class TranscodingRow. + * Initial date: 07.12.2016<br> + * @author Fabian Kiefer, fabian.kiefer@frentix.com, http://www.frentix.com + */ +public class DropBoxRow { + + private String status; + private int filecount, filecountReturn, newfiles; + private Date lastupdate, lastupdateReturn; + private final UserPropertiesRow identity; + + public DropBoxRow(UserPropertiesRow identity, String status, int foldercount, int filecountReturn, + int newfiles, Date lastupdate, Date lastupdateReturn) { + super(); + this.identity = identity; + this.status = status; + this.filecount = foldercount; + this.filecountReturn = filecountReturn; + this.newfiles = newfiles; + this.lastupdate = lastupdate; + this.lastupdateReturn = lastupdateReturn; + } + + + public UserPropertiesRow getIdentity () { + return identity; + } + public String getStatus() { + return status; + } + public int getFilecount() { + return filecount; + } + public int getFilecountReturn () { + return filecountReturn; + } + public int getNewfolders() { + return newfiles; + } + public Date getLastupdate() { + return lastupdate; + } + public Date getLastupdateReturn() { + return lastupdateReturn; + } + + + + + + +} diff --git a/src/main/java/org/olat/course/nodes/pf/ui/DropBoxTableModel.java b/src/main/java/org/olat/course/nodes/pf/ui/DropBoxTableModel.java new file mode 100644 index 0000000000000000000000000000000000000000..8c01c2b7d695f8d126a7b2d627dfc60c917940d7 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/pf/ui/DropBoxTableModel.java @@ -0,0 +1,126 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.course.nodes.pf.ui; + +import java.util.List; + +import org.olat.core.commons.persistence.SortKey; +import org.olat.core.gui.components.form.flexible.FormUIFactory; +import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiTableDataModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiSortableColumnDef; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.SortableFlexiTableDataModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.SortableFlexiTableModelDelegate; +import org.olat.core.gui.translator.Translator; + +/** + * + * Initial date: 07.12.2016<br> + * @author Fabian Kiefer, fabian.kiefer@frentix.com, http://www.frentix.com + * + */ +public class DropBoxTableModel extends DefaultFlexiTableDataModel<DropBoxRow> implements SortableFlexiTableDataModel<DropBoxRow>{ + + protected FormUIFactory uifactory = FormUIFactory.getInstance(); + private Translator translator; + public DropBoxTableModel(FlexiTableColumnModel columnModel, Translator translator) { + super(columnModel); + this.translator = translator; + } + + @Override + public DropBoxTableModel createCopyWithEmptyList() { + return new DropBoxTableModel(getTableColumnModel(), translator); + } + + + @Override + public Object getValueAt(int row, int col) { + DropBoxRow content = getObject(row); + return getValueAt(content, col); + } + + @Override + public Object getValueAt(DropBoxRow content, int col) { + if (col >= 0 && col < DropBoxCols.values().length) { + switch(DropBoxCols.values()[col]) { + case numberFiles: return content.getFilecount(); + case numberFilesReturn: return content.getFilecountReturn(); + case newFiles: return content.getNewfolders(); + case lastUpdate: return content.getLastupdate(); + case lastUpdateReturn: return content.getLastupdateReturn(); + case status: return content.getStatus(); + default: return ""; + } + } else if (col >= PFCoachController.USER_PROPS_OFFSET) { + int userCol = col - PFCoachController.USER_PROPS_OFFSET; + return content.getIdentity().getIdentityProp(userCol); + } + return "ERROR"; + } + + @Override + public void sort(SortKey sortKey) { + if(sortKey != null) { + List<DropBoxRow> views = new SortableFlexiTableModelDelegate<>(sortKey, this, null).sort(); + super.setObjects(views); + } + } + + public enum DropBoxCols implements FlexiSortableColumnDef { + + numberFiles("table.cols.numFiles", true), + numberFilesReturn("table.cols.numReturn", true), + newFiles("table.cols.newFiles", true), + lastUpdate("table.cols.lastUpdate", true), + lastUpdateReturn("table.cols.lastUpdate", true), + status("table.cols.status", true), + openbox("table.cols.openbox", false); + + private final String i18nKey; + + private final boolean sortable; + + private DropBoxCols(String i18nKey, boolean sortable) { + this.i18nKey = i18nKey; + this.sortable = sortable; + } + + @Override + public String i18nHeaderKey() { + return i18nKey; + } + + @Override + public boolean sortable() { + return sortable; + } + + @Override + public String sortKey() { + return name(); + } + + public String i18nKey() { + return i18nKey; + } + } + +} \ No newline at end of file diff --git a/src/main/java/org/olat/course/nodes/pf/ui/PFCoachController.java b/src/main/java/org/olat/course/nodes/pf/ui/PFCoachController.java new file mode 100644 index 0000000000000000000000000000000000000000..a70979b8d1288ca3d05eebbd29e6c000d79b161f --- /dev/null +++ b/src/main/java/org/olat/course/nodes/pf/ui/PFCoachController.java @@ -0,0 +1,351 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.course.nodes.pf.ui; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import org.olat.basesecurity.BaseSecurity; +import org.olat.basesecurity.BaseSecurityModule; +import org.olat.core.commons.services.notifications.PublisherData; +import org.olat.core.commons.services.notifications.SubscriptionContext; +import org.olat.core.commons.services.notifications.ui.ContextualSubscriptionController; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.form.flexible.FormItem; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.elements.FlexiTableElement; +import org.olat.core.gui.components.form.flexible.elements.FormLink; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.components.form.flexible.impl.FormEvent; +import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; +import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiColumnModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiColumnModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableDataModelFactory; +import org.olat.core.gui.components.form.flexible.impl.elements.table.SelectionEvent; +import org.olat.core.gui.components.form.flexible.impl.elements.table.StaticFlexiCellRenderer; +import org.olat.core.gui.components.form.flexible.impl.elements.table.TextFlexiCellRenderer; +import org.olat.core.gui.components.link.Link; +import org.olat.core.gui.components.link.LinkFactory; +import org.olat.core.gui.components.velocity.VelocityContainer; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.ControllerEventListener; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController; +import org.olat.core.id.Identity; +import org.olat.core.id.Roles; +import org.olat.core.id.UserConstants; +import org.olat.core.util.resource.OresHelper; +import org.olat.course.nodes.PFCourseNode; +import org.olat.course.nodes.TitledWrapperHelper; +import org.olat.course.nodes.pf.manager.PFManager; +import org.olat.course.nodes.pf.manager.PFView; +import org.olat.course.nodes.pf.ui.DropBoxTableModel.DropBoxCols; +import org.olat.course.run.environment.CourseEnvironment; +import org.olat.course.run.navigation.NodeRunConstructionResult; +import org.olat.course.run.userview.UserCourseEnvironment; +import org.olat.resource.OLATResource; +import org.olat.user.HomePageConfig; +import org.olat.user.HomePageDisplayController; +import org.olat.user.UserManager; +import org.olat.user.UserPropertiesRow; +import org.olat.user.propertyhandlers.UserPropertyHandler; +import org.springframework.beans.factory.annotation.Autowired; +/** +* +* @author Fabian Kiefer, fabian.kiefer@frentix.com, http://www.frentix.com +* +*/ +public class PFCoachController extends FormBasicController implements ControllerEventListener { + + protected static final String USER_PROPS_ID = PFCoachController.class.getCanonicalName(); + + protected static final int USER_PROPS_OFFSET = 500; + + private PFCourseNode pfNode; + + private FormLink downloadLink, uploadLink, uploadAllLink; + private Link backLink; + private DropBoxTableModel tableModel; + private FlexiTableElement dropboxTable; + private VelocityContainer mainVC; + + private CloseableModalController cmc; + private PFFileUploadController pfFileUploadCtr; + private PFParticipantController pfParticipantController; + private HomePageDisplayController homePageDisplayController; + private ContextualSubscriptionController contextualSubscriptionCtr; + + private final List<UserPropertyHandler> userPropertyHandlers; + private final boolean isAdministrativeUser; + + private UserCourseEnvironment userCourseEnv; + private CourseEnvironment courseEnv; + private Identity identity; + private PFView pfView; + @Autowired + private PFManager pfManager; + @Autowired + private UserManager userManager; + @Autowired + private BaseSecurityModule securityModule; + @Autowired + private BaseSecurity securityManager; + + public PFCoachController(UserRequest ureq, WindowControl wControl, PFCourseNode sfNode, + UserCourseEnvironment userCourseEnv, PFView pfView) { + super(ureq, wControl, "coach"); + + this.userCourseEnv = userCourseEnv; + this.courseEnv = userCourseEnv.getCourseEnvironment(); + this.pfNode = sfNode; + this.identity = ureq.getIdentity(); + this.pfView = pfView; + + Roles roles = ureq.getUserSession().getRoles(); + isAdministrativeUser = securityModule.isUserAllowedAdminProps(roles); + userPropertyHandlers = userManager.getUserPropertyHandlersFor(USER_PROPS_ID, isAdministrativeUser); + setTranslator(userManager.getPropertyHandlerTranslator(getTranslator())); + + initForm(ureq); + } + + @Override + public void event(UserRequest ureq, Component source, Event event) { + if (source == backLink) { + back(); + } + super.event(ureq, source, event); + } + + @Override + protected void event(UserRequest ureq, Controller source, Event event) { + if (source == pfFileUploadCtr) { + if (event == Event.DONE_EVENT) { + if (pfFileUploadCtr.isUploadToAll()) { + uploadToSelection(pfFileUploadCtr.getUpLoadFile(), pfFileUploadCtr.getUploadFileName()); + showInfo("upload.success"); + fireEvent(ureq, Event.CHANGED_EVENT); + } else { + pfManager.uploadFileToDropBox(pfFileUploadCtr.getUpLoadFile(), + pfFileUploadCtr.getUploadFileName(), 4, courseEnv, pfNode, identity); + } + cmc.deactivate(); + cleanUpCMC(); + } + } else if (source == cmc) { + cleanUpCMC(); + } + super.event(ureq, source, event); + + } + + @Override + protected void doDispose() { + // nothing to dispose + + } + + public NodeRunConstructionResult createNodeRunConstructionResult(UserRequest ureq) { + // integrate it into the olat menu + Controller ctrl = TitledWrapperHelper.getWrapper(ureq, getWindowControl(), this, pfNode, "o_pf_icon"); + return new NodeRunConstructionResult(ctrl); + } + + @Override + protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { + if (source == uploadLink) { + doOpenUploadController(ureq, false); + } else if (source == uploadAllLink) { + if (dropboxTable.getMultiSelectedIndex().size() > 0) { + doOpenUploadController(ureq, true); + } else { + showWarning("table.no.selection"); + } + } else if (source == downloadLink) { + if (dropboxTable.getMultiSelectedIndex().size() > 0) { + downloadFromSelection(ureq); + } else { + showWarning("table.no.selection"); + } + } else if(source == dropboxTable) { + if(event instanceof SelectionEvent) { + SelectionEvent se = (SelectionEvent)event; + DropBoxRow currentObject = (DropBoxRow) tableModel.getObject(se.getIndex()); + if ("drop.box".equals(se.getCommand())){ + doSelectParticipantFolder (ureq, currentObject.getIdentity(), PFView.displayDrop); + } else if ("return.box".equals(se.getCommand())){ + doSelectParticipantFolder (ureq, currentObject.getIdentity(), PFView.displayReturn); + } else if ("open.box".equals(se.getCommand())){ + doSelectParticipantFolder (ureq, currentObject.getIdentity(), PFView.dropAndReturn); + } else if ("firstName".equals(se.getCommand()) || "lastName".equals(se.getCommand())) { + doOpenHomePage(ureq, currentObject.getIdentity()); + } + } + } + + } + + private void doSelectParticipantFolder (UserRequest ureq, UserPropertiesRow row, PFView pfView) { + Identity identity = securityManager.loadIdentityByKey(row.getIdentityKey()); + removeAsListenerAndDispose(pfParticipantController); + pfParticipantController = new PFParticipantController(ureq, getWindowControl(), pfNode, + userCourseEnv, identity, pfView, true, false); + listenTo(pfParticipantController); + mainVC.put("single", pfParticipantController.getInitialComponent()); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + mainVC = ((FormLayoutContainer) formLayout).getFormItemComponent(); + + OLATResource course = courseEnv.getCourseGroupManager().getCourseResource(); + String businessPath = getWindowControl().getBusinessControl().getAsString(); + SubscriptionContext subsContext = new SubscriptionContext(course, pfNode.getIdent()); + PublisherData publisherData = new PublisherData(OresHelper.calculateTypeName(PFCourseNode.class), + String.valueOf(course.getResourceableId()), businessPath); + contextualSubscriptionCtr = new ContextualSubscriptionController(ureq, getWindowControl(), subsContext, + publisherData); + listenTo(contextualSubscriptionCtr); + mainVC.put("contextualSubscription", contextualSubscriptionCtr.getInitialComponent()); + + backLink = LinkFactory.createLinkBack(mainVC, this); + FlexiTableColumnModel columnsModel = FlexiTableDataModelFactory.createFlexiTableColumnModel(); + + int i = 0; + for (UserPropertyHandler userPropertyHandler : userPropertyHandlers) { + int colIndex = USER_PROPS_OFFSET + i++; + if (userPropertyHandler == null) continue; + + String propName = userPropertyHandler.getName(); + boolean visible = userManager.isMandatoryUserProperty(USER_PROPS_ID , userPropertyHandler); + + FlexiColumnModel col; + if(UserConstants.FIRSTNAME.equals(propName) + || UserConstants.LASTNAME.equals(propName)) { + col = new DefaultFlexiColumnModel(userPropertyHandler.i18nColumnDescriptorLabelKey(), + colIndex, userPropertyHandler.getName(), true, propName, + new StaticFlexiCellRenderer(userPropertyHandler.getName(), new TextFlexiCellRenderer())); + } else { + col = new DefaultFlexiColumnModel(visible, userPropertyHandler.i18nColumnDescriptorLabelKey(), colIndex, true, propName); + } + columnsModel.addFlexiColumnModel(col); + } + + if (pfNode.hasParticipantBoxConfigured()){ + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(DropBoxCols.numberFiles,"drop.box")); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(DropBoxCols.lastUpdate)); + } + if (pfNode.hasCoachBoxConfigured()) { + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(DropBoxCols.numberFilesReturn,"return.box")); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(DropBoxCols.lastUpdateReturn)); + } + StaticFlexiCellRenderer openCellRenderer = new StaticFlexiCellRenderer(translate("open.box"), "open.box"); + openCellRenderer.setIconRightCSS("o_icon_start o_icon-fw"); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(DropBoxCols.openbox, + "open.box", openCellRenderer)); + tableModel = new DropBoxTableModel(columnsModel, getTranslator()); + + dropboxTable = uifactory.addTableElement(getWindowControl(), "table", tableModel, getTranslator(), formLayout); + + dropboxTable.setMultiSelect(true); + dropboxTable.setSelectAllEnable(true); + dropboxTable.setExportEnabled(true); + dropboxTable.setAndLoadPersistedPreferences(ureq, this.getClass().getName() + "_" + pfView.name()); + + tableModel.setObjects(pfManager.getParticipants(identity, pfNode, userPropertyHandlers, getLocale(), courseEnv)); + + + FormLayoutContainer buttonGroupLayout = FormLayoutContainer.createButtonLayout("buttons", getTranslator()); + buttonGroupLayout.setElementCssClass("o_button_group"); + formLayout.add(buttonGroupLayout); + + downloadLink = uifactory.addFormLink("download.link", buttonGroupLayout, Link.BUTTON); + uploadAllLink = uifactory.addFormLink("upload.link", buttonGroupLayout, Link.BUTTON); + } + + + private void uploadToSelection (File uploadFile, String fileName) { + List<Long> identitykeys = new ArrayList<>(); + for (int i : dropboxTable.getMultiSelectedIndex()) { + identitykeys.add(tableModel.getObject(i).getIdentity().getIdentityKey()); + } + List<Identity> identities = securityManager.loadIdentityByKeys(identitykeys); + + pfManager.uploadFileToAllReturnBoxes(uploadFile, fileName, courseEnv, pfNode, identities); + } + + private void downloadFromSelection (UserRequest ureq) { + List<Long> identitykeys = new ArrayList<>(); + for (int i : dropboxTable.getMultiSelectedIndex()) { + identitykeys.add(tableModel.getObject(i).getIdentity().getIdentityKey()); + } + List<Identity> identities = securityManager.loadIdentityByKeys(identitykeys); + + pfManager.exportMediaResource(ureq, identities, pfNode, courseEnv); + } + + + @Override + protected void formOK(UserRequest ureq) { + + } + + private void doOpenUploadController (UserRequest ureq, boolean uploadToAll) { + pfFileUploadCtr = new PFFileUploadController(ureq, getWindowControl(), uploadToAll); + listenTo(pfFileUploadCtr); + cmc = new CloseableModalController(getWindowControl(), translate("close"), + pfFileUploadCtr.getInitialComponent(), true, translate("upload.link")); + listenTo(cmc); + + cmc.activate(); + } + + private void doOpenHomePage (UserRequest ureq, UserPropertiesRow row) { + + Identity identity = securityManager.loadIdentityByKey(row.getIdentityKey()); + homePageDisplayController = new HomePageDisplayController(ureq, getWindowControl(), identity, new HomePageConfig()); + listenTo(homePageDisplayController); + cmc = new CloseableModalController(getWindowControl(), translate("close"), + homePageDisplayController.getInitialComponent(), true, translate("upload.link")); + listenTo(cmc); + + cmc.activate(); + } + + private void cleanUpCMC(){ + removeAsListenerAndDispose(cmc); + removeAsListenerAndDispose(pfFileUploadCtr); + cmc = null; + pfFileUploadCtr = null; + } + + private void back() { + if(pfParticipantController != null) { + mainVC.remove(pfParticipantController.getInitialComponent()); + removeAsListenerAndDispose(pfParticipantController); + pfParticipantController = null; + } + } + +} diff --git a/src/main/java/org/olat/course/nodes/pf/ui/PFEditController.java b/src/main/java/org/olat/course/nodes/pf/ui/PFEditController.java new file mode 100644 index 0000000000000000000000000000000000000000..db32f10cb0e8a0442ca3776c023ee3ab688d53d3 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/pf/ui/PFEditController.java @@ -0,0 +1,113 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.course.nodes.pf.ui; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.tabbedpane.TabbedPane; +import org.olat.core.gui.components.velocity.VelocityContainer; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.ControllerEventListener; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.control.generic.tabbable.ActivateableTabbableDefaultController; +import org.olat.course.ICourse; +import org.olat.course.assessment.AssessmentHelper; +import org.olat.course.condition.Condition; +import org.olat.course.condition.ConditionEditController; +import org.olat.course.editor.NodeEditController; +import org.olat.course.nodes.PFCourseNode; +import org.olat.course.run.userview.UserCourseEnvironment; +import org.olat.course.tree.CourseEditorTreeModel; +/** +* +* @author Fabian Kiefer, fabian.kiefer@frentix.com, http://www.frentix.com +* +*/ +public class PFEditController extends ActivateableTabbableDefaultController implements ControllerEventListener { + + private static final String PANE_TAB_CONFIGURATION = "pane.tab.configuration"; + private static final String PANE_TAB_ACCESSIBILITY = "pane.tab.accessibility"; + private static final String[] paneKeys = { PANE_TAB_CONFIGURATION, PANE_TAB_ACCESSIBILITY }; + + private VelocityContainer configVC; + private PFEditFormController modConfigCtr; + private TabbedPane myTabbedPane; + private ConditionEditController accessibilityCondCtr; + + + public PFEditController(UserRequest ureq, WindowControl wControl, + PFCourseNode pfNode, ICourse course, UserCourseEnvironment euce) { + super(ureq, wControl); + + configVC = createVelocityContainer("edit"); + // Accessibility precondition + CourseEditorTreeModel editorModel = course.getEditorTreeModel(); + Condition accessCondition = pfNode.getPreConditionAccess(); + accessibilityCondCtr = new ConditionEditController(ureq, getWindowControl(), euce, accessCondition, + AssessmentHelper.getAssessableNodes(editorModel, pfNode)); + listenTo(accessibilityCondCtr); + + modConfigCtr = new PFEditFormController(ureq, wControl, pfNode); + listenTo(modConfigCtr); + configVC.put("sfeditform", modConfigCtr.getInitialComponent()); + + } + + @Override + public void addTabs(TabbedPane tabbedPane) { + myTabbedPane = tabbedPane; + tabbedPane.addTab(translate(PANE_TAB_ACCESSIBILITY), accessibilityCondCtr.getWrappedDefaultAccessConditionVC(translate("condition.accessibility.title"))); + tabbedPane.addTab(translate(PANE_TAB_CONFIGURATION), configVC); + } + + @Override + public String[] getPaneKeys() { + return paneKeys; + } + + @Override + public TabbedPane getTabbedPane() { + return myTabbedPane; + } + + @Override + protected void event(UserRequest ureq, Component source, Event event) { + // TODO Auto-generated method stub + + } + + @Override + protected void event(UserRequest ureq, Controller source, Event event) { + if (source == modConfigCtr) { + if (Event.DONE_EVENT.equals(event)) { + fireEvent(ureq, NodeEditController.NODECONFIG_CHANGED_EVENT); + } + } + super.event(ureq, source, event); + } + + @Override + protected void doDispose() { + // TODO Auto-generated method stub + + } + +} diff --git a/src/main/java/org/olat/course/nodes/pf/ui/PFEditFormController.java b/src/main/java/org/olat/course/nodes/pf/ui/PFEditFormController.java new file mode 100644 index 0000000000000000000000000000000000000000..d1ea916fa0a08e4391d03a34757c42d025447c7a --- /dev/null +++ b/src/main/java/org/olat/course/nodes/pf/ui/PFEditFormController.java @@ -0,0 +1,219 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.course.nodes.pf.ui; + +import java.util.Date; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.FormItem; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.elements.IntegerElement; +import org.olat.core.gui.components.form.flexible.elements.SelectionElement; +import org.olat.core.gui.components.form.flexible.elements.SpacerElement; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.components.form.flexible.impl.FormEvent; +import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; +import org.olat.core.gui.components.form.flexible.impl.elements.JSDateChooser; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.course.nodes.PFCourseNode; +/** +* +* @author Fabian Kiefer, fabian.kiefer@frentix.com, http://www.frentix.com +* +*/ +public class PFEditFormController extends FormBasicController { + + private SelectionElement studentDropBox, teacherDropBox, alterFiles, limitFileCount, timeFrame; + private IntegerElement fileCount; + private JSDateChooser dateStart, dateEnd; + private SpacerElement spacerEl; + + private PFCourseNode pfNode; + + public PFEditFormController(UserRequest ureq, WindowControl wControl, PFCourseNode pfNode) { + super(ureq, wControl); + this.pfNode = pfNode; + + initForm(ureq); + } + + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + + studentDropBox = uifactory.addCheckboxesHorizontal("participant.drop", formLayout, new String[]{"xx"}, new String[]{null}); + studentDropBox.addActionListener(FormEvent.ONCLICK); + + String[] alterfile = new String[]{ translate("alter.file") }; + alterFiles = uifactory.addCheckboxesHorizontal("alter.file", "blank.label", formLayout, new String[]{"xx"}, alterfile); + alterFiles.addActionListener(FormEvent.ONCLICK); + + String[] limitcount = new String[]{ translate("limit.count") }; + limitFileCount = uifactory.addCheckboxesHorizontal("limit.count", "blank.label", formLayout, new String[]{"xx"}, limitcount); + limitFileCount.addActionListener(FormEvent.ONCLICK); + limitFileCount.showLabel(Boolean.FALSE); + fileCount = uifactory.addIntegerElement("file.count", 3, formLayout); + fileCount.showLabel(Boolean.FALSE); + + String[] timeframe = new String[]{ translate("time.frame") }; + timeFrame = uifactory.addCheckboxesHorizontal("time.frame", "blank.label", formLayout, new String[]{"xx"}, timeframe); + timeFrame.addActionListener(FormEvent.ONCLICK); + timeFrame.showLabel(Boolean.FALSE); + + + dateStart = new JSDateChooser("dateStart", getLocale()); + dateStart.setLabel("date.start", null); + dateStart.setDateChooserTimeEnabled(true); + dateStart.setValidDateCheck("valid.date"); + dateStart.setMandatory(true); + formLayout.add(dateStart); + + dateEnd = new JSDateChooser("dateEnd", getLocale()); + dateEnd.setLabel("date.end", null); + dateEnd.setDateChooserTimeEnabled(true); + dateEnd.setValidDateCheck("valid.date"); + dateEnd.setMandatory(true); + formLayout.add(dateEnd); + + spacerEl = uifactory.addSpacerElement("spacer1", formLayout, false); + + teacherDropBox = uifactory.addCheckboxesHorizontal("coach.drop", formLayout, new String[]{"xx"}, new String[]{null}); + + // Create submit button + final FormLayoutContainer buttonLayout = FormLayoutContainer.createButtonLayout("buttonLayout", getTranslator()); + formLayout.add(buttonLayout); + uifactory.addFormSubmitButton("save", buttonLayout) + .setElementCssClass("o_sel_node_editor_submit"); + + applyModuleConfig(); + + + } + + private void applyModuleConfig () { + boolean hasStudentBox = pfNode.hasParticipantBoxConfigured(); + studentDropBox.select("xx", hasStudentBox); + alterFiles.select("xx", pfNode.hasAlterFileConfigured()); + alterFiles.setVisible(hasStudentBox); + spacerEl.setVisible(hasStudentBox); + boolean hasLimitCount = pfNode.hasLimitCountConfigured(); + limitFileCount.select("xx", hasLimitCount); + limitFileCount.setVisible(hasStudentBox); + fileCount.setIntValue(pfNode.getLimitCount()); + fileCount.setVisible(hasLimitCount); + boolean hasTimeFrame = pfNode.hasDropboxTimeFrameConfigured(); + timeFrame.select("xx", hasTimeFrame); + timeFrame.setVisible(hasStudentBox); + dateStart.setDate(pfNode.getDateStart()); + dateStart.setVisible(hasTimeFrame); + dateEnd.setDate(pfNode.getDateEnd()); + dateEnd.setVisible(hasTimeFrame); + teacherDropBox.select("xx", pfNode.hasCoachBoxConfigured()); + } + + @Override + protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { + //this is to navigate through it all + if (source == studentDropBox) { + activateSettings(); + } else if (source == limitFileCount) { + activateFileCount(); + } else if (source == timeFrame) { + activateTimeFrame(); + } + } + + private void activateSettings () { + boolean studentDropBoxEnabled = studentDropBox.isSelected(0); + boolean fileCountEnabled = limitFileCount.isSelected(0); + boolean timeFrameEnabled = timeFrame.isSelected(0); + alterFiles.setVisible(studentDropBoxEnabled); + limitFileCount.setVisible(studentDropBoxEnabled); + timeFrame.setVisible(studentDropBoxEnabled); + fileCount.setVisible(studentDropBoxEnabled && fileCountEnabled); + dateStart.setVisible(studentDropBoxEnabled && timeFrameEnabled); + dateEnd.setVisible(studentDropBoxEnabled && timeFrameEnabled); + } + + private void activateFileCount () { + boolean fileCountEnabled = limitFileCount.isSelected(0); + fileCount.setVisible(fileCountEnabled); + } + + private void activateTimeFrame () { + boolean timeFrameEnabled = timeFrame.isSelected(0); + dateStart.setVisible(timeFrameEnabled); + dateEnd.setVisible(timeFrameEnabled); + } + + private boolean checkTimeFrameValid () { + if (timeFrame.isSelected(0)){ + return dateStart.getDate().before(dateEnd.getDate()) && dateEnd.getDate().after(new Date()); + } + return true; + } + + + + @Override + protected boolean validateFormLogic(UserRequest ureq) { + boolean allOk = true; + dateEnd.clearError(); + dateStart.clearError(); + + if(timeFrame.isSelected(0)) { + if(dateEnd.getDate() == null) { + dateEnd.setErrorKey("form.legende.mandatory", null); + allOk &= false; + } + if(dateStart.getDate() == null) { + dateStart.setErrorKey("form.legende.mandatory", null); + allOk &= false; + } + } + return allOk & super.validateFormLogic(ureq); + } + + + @Override + protected void formOK(UserRequest ureq) { + if (!checkTimeFrameValid()) { + dateEnd.setErrorKey("timeframe.error", null); + } else { + pfNode.updateModuleConfig(studentDropBox.isSelected(0), + teacherDropBox.isSelected(0), + alterFiles.isSelected(0), + limitFileCount.isSelected(0), + fileCount.getIntValue(), + timeFrame.isSelected(0), + dateStart.getDate(), + dateEnd.getDate()); + fireEvent(ureq, Event.DONE_EVENT); + } + } + + @Override + protected void doDispose() { + + } + +} diff --git a/src/main/java/org/olat/course/nodes/pf/ui/PFFileUploadController.java b/src/main/java/org/olat/course/nodes/pf/ui/PFFileUploadController.java new file mode 100644 index 0000000000000000000000000000000000000000..d936e2a6249e26d7c368fdff445b120d06eb37e3 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/pf/ui/PFFileUploadController.java @@ -0,0 +1,97 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.course.nodes.pf.ui; + +import java.io.File; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.elements.FileElement; +import org.olat.core.gui.components.form.flexible.elements.StaticTextElement; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.components.form.flexible.impl.FormEvent; +import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +/** +* +* @author Fabian Kiefer, fabian.kiefer@frentix.com, http://www.frentix.com +* +*/ +public class PFFileUploadController extends FormBasicController { + + private FileElement uploadFileEl; + private StaticTextElement typeEl; + + private File uploadFile; + private String uploadFileName; + private boolean uploadToAll; + + public PFFileUploadController(UserRequest ureq, WindowControl wControl, boolean uploadToall) { + super(ureq, wControl); + this.uploadToAll = uploadToall; + + initForm(ureq); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + + uploadFileEl = uifactory.addFileElement(getWindowControl(), "upload", "textfield.upload", formLayout); + uploadFileEl.addActionListener(FormEvent.ONCHANGE); + + + typeEl = uifactory.addStaticTextElement("video.mime.type", "video.mime.type", "", formLayout); + typeEl.setVisible(false); + + FormLayoutContainer buttonGroupLayout = FormLayoutContainer.createButtonLayout("buttons", getTranslator()); + formLayout.add(buttonGroupLayout); + uifactory.addFormSubmitButton("submit", "upload.link", buttonGroupLayout); + } + + @Override + protected void formOK(UserRequest ureq) { + if (uploadFileEl.isUploadSuccess()) { + this.uploadFile = uploadFileEl.getUploadFile(); + this.uploadFileName = uploadFileEl.getUploadFileName(); + this.fireEvent(ureq, Event.DONE_EVENT); + } + + } + + @Override + protected void doDispose() { + + } + + protected File getUpLoadFile () { + return uploadFile; + } + + protected String getUploadFileName () { + return uploadFileName; + } + + protected boolean isUploadToAll () { + return uploadToAll; + } + +} diff --git a/src/main/java/org/olat/course/nodes/pf/ui/PFParticipantController.java b/src/main/java/org/olat/course/nodes/pf/ui/PFParticipantController.java new file mode 100644 index 0000000000000000000000000000000000000000..3723502004537880d4beaacaa1651356dfd362db --- /dev/null +++ b/src/main/java/org/olat/course/nodes/pf/ui/PFParticipantController.java @@ -0,0 +1,131 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.course.nodes.pf.ui; + +import org.olat.core.commons.modules.bc.FolderRunController; +import org.olat.core.commons.services.notifications.PublisherData; +import org.olat.core.commons.services.notifications.SubscriptionContext; +import org.olat.core.commons.services.notifications.ui.ContextualSubscriptionController; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.Component; +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; +import org.olat.core.gui.control.controller.BasicController; +import org.olat.core.id.Identity; +import org.olat.core.util.resource.OresHelper; +import org.olat.core.util.vfs.VFSContainer; +import org.olat.course.nodes.PFCourseNode; +import org.olat.course.nodes.pf.manager.PFManager; +import org.olat.course.nodes.pf.manager.PFView; +import org.olat.course.run.environment.CourseEnvironment; +import org.olat.course.run.userview.UserCourseEnvironment; +import org.olat.resource.OLATResource; +import org.springframework.beans.factory.annotation.Autowired; +/** +* +* @author Fabian Kiefer, fabian.kiefer@frentix.com, http://www.frentix.com +* +*/ +public class PFParticipantController extends BasicController { + + private VelocityContainer mainVC; + private FolderRunController folderRunController; + private ContextualSubscriptionController contextualSubscriptionCtr; + @Autowired + private PFManager pfManager; + + @SuppressWarnings("incomplete-switch") + public PFParticipantController(UserRequest ureq, WindowControl wControl, PFCourseNode pfNode, + UserCourseEnvironment userCourseEnv, Identity identity, PFView pfView, boolean isCoach, boolean readOnly) { + super(ureq, wControl); + mainVC = createVelocityContainer("participant"); + + CourseEnvironment courseEnv = userCourseEnv.getCourseEnvironment(); + + if (pfNode.hasLimitCountConfigured()){ + mainVC.contextPut("limit", pfNode.getLimitCount()); + } + + if (!(userCourseEnv.isCoach() || userCourseEnv.isAdmin()) && !(isCoach && readOnly)) { + OLATResource course = courseEnv.getCourseGroupManager().getCourseResource(); + String businessPath = wControl.getBusinessControl().getAsString(); + SubscriptionContext subsContext = new SubscriptionContext(course, pfNode.getIdent()); + PublisherData publisherData = new PublisherData(OresHelper.calculateTypeName(PFCourseNode.class), + String.valueOf(course.getResourceableId()), businessPath); + contextualSubscriptionCtr = new ContextualSubscriptionController(ureq, getWindowControl(), subsContext, + publisherData); + listenTo(contextualSubscriptionCtr); + mainVC.put("contextualSubscription", contextualSubscriptionCtr.getInitialComponent()); + } + //CourseFreeze + readOnly = readOnly ? true : userCourseEnv.isCourseReadOnly(); + + VFSContainer frc = pfManager.provideParticipantFolder(pfNode, pfView, courseEnv, identity, isCoach, readOnly); + folderRunController = new FolderRunController(frc, false, false, false, false, ureq, wControl, null, null, null); + folderRunController.disableSubscriptionController(); + listenTo(folderRunController); + mainVC.put("folder", folderRunController.getInitialComponent()); + + switch (pfView) { + case displayDrop: + folderRunController.activatePath(ureq, translate("drop.box")); + break; + case displayReturn: + folderRunController.activatePath(ureq, translate("return.box")); + break; + } + + putInitialPanel(mainVC); + + } + + + + @Override + protected void doDispose() { + + } + + @Override + protected void event(UserRequest ureq, Component source, Event event) { + + } + + @Override + protected void event(UserRequest ureq, Controller source, Event event) { + if (source == folderRunController) { + fireEvent(ureq, Event.CHANGED_EVENT); + } + super.event(ureq, source, event); + } + + /** + * Remove the subscription panel but let the subscription context active + */ + public void disableSubscriptionController() { + if (contextualSubscriptionCtr != null) { + mainVC.remove(contextualSubscriptionCtr.getInitialComponent()); + } + } + + +} diff --git a/src/main/java/org/olat/course/nodes/pf/ui/PFPeekviewController.java b/src/main/java/org/olat/course/nodes/pf/ui/PFPeekviewController.java new file mode 100644 index 0000000000000000000000000000000000000000..3f19fb01faa0555fcd5dcbdc35d4dc7f3c60591b --- /dev/null +++ b/src/main/java/org/olat/course/nodes/pf/ui/PFPeekviewController.java @@ -0,0 +1,143 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.course.nodes.pf.ui; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import org.olat.core.CoreSpringFactory; +import org.olat.core.commons.modules.bc.FolderModule; +import org.olat.core.commons.modules.bc.components.FolderComponent; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.download.DownloadComponent; +import org.olat.core.gui.components.htmlsite.OlatCmdEvent; +import org.olat.core.gui.components.link.Link; +import org.olat.core.gui.components.link.LinkFactory; +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; +import org.olat.core.gui.control.controller.BasicController; +import org.olat.core.gui.util.CSSHelper; +import org.olat.core.util.vfs.LocalFileImpl; +import org.olat.core.util.vfs.VFSContainer; +import org.olat.core.util.vfs.VFSItem; +import org.olat.core.util.vfs.VFSLeaf; +import org.olat.core.util.vfs.filters.VFSItemExcludePrefixFilter; +import org.olat.core.util.vfs.filters.VFSItemFilter; +/** +* +* @author Fabian Kiefer, fabian.kiefer@frentix.com, http://www.frentix.com +* +*/ +public class PFPeekviewController extends BasicController implements Controller { + + // comparator to sort the messages list by creation date + private static final Comparator<VFSLeaf> dateSortingComparator = new Comparator<VFSLeaf>(){ + public int compare(final VFSLeaf leaf1, final VFSLeaf leaf2) { + return Long.valueOf(leaf2.getLastModified()).compareTo(leaf1.getLastModified()); //last first + }}; + // the current course node id + private final String nodeId; + + private static final VFSItemFilter attachmentExcludeFilter = new VFSItemExcludePrefixFilter(FolderComponent.ATTACHMENT_EXCLUDE_PREFIXES); + + public PFPeekviewController(UserRequest ureq, WindowControl wControl, VFSContainer rootFolder, String nodeId, int itemsToDisplay) { + super(ureq, wControl); + this.nodeId = nodeId; + + VelocityContainer peekviewVC = createVelocityContainer("peekview"); + // add items, only as many as configured + List<VFSLeaf> allLeafs = new ArrayList<VFSLeaf>(); + addItems(rootFolder, allLeafs); + // Sort messages by last modified date + Collections.sort(allLeafs, dateSortingComparator); + boolean forceDownload = CoreSpringFactory.getImpl(FolderModule.class).isForceDownload(); + + // only take the configured amount of messages + List<VFSLeaf> leafs = new ArrayList<VFSLeaf>(); + for (int i = 0; i < allLeafs.size(); i++) { + if (leafs.size() == itemsToDisplay) { + break; + } + VFSLeaf leaf = allLeafs.get(i); + leafs.add(leaf); + // add link to item + // Add link to jump to course node + if (leaf instanceof LocalFileImpl) { + DownloadComponent dlComp = new DownloadComponent("nodeLinkDL_"+(i+1), leaf, forceDownload, + leaf.getName(), translate("preview.downloadfile"), + CSSHelper.createFiletypeIconCssClassFor(leaf.getName())); + dlComp.setElementCssClass("o_gotoNode"); + peekviewVC.put("nodeLinkDL_"+(i+1),dlComp); + } else { + // hu? don't konw how to work with non-local impls + } + } + peekviewVC.contextPut("leafs", leafs); + // Add link to show all items (go to node) + Link allItemsLink = LinkFactory.createLink("peekview.allItemsLink", peekviewVC, this); + allItemsLink.setIconRightCSS("o_icon o_icon_start"); + allItemsLink.setElementCssClass("pull-right"); + putInitialPanel(peekviewVC); + } + + + + @Override + protected void event(UserRequest ureq, Component source, Event event) { + if (source instanceof Link) { + Link nodeLink = (Link) source; + String relPath = (String) nodeLink.getUserObject(); + if (relPath == null) { + fireEvent(ureq, new OlatCmdEvent(OlatCmdEvent.GOTONODE_CMD, nodeId)); + } else { + fireEvent(ureq, new OlatCmdEvent(OlatCmdEvent.GOTONODE_CMD, nodeId + "/" + relPath)); + } + } + } + + @Override + protected void doDispose() { + + } + + private void addItems(VFSContainer container, List<VFSLeaf> allLeafs) { + // exclude files which are also excluded in FolderComponent + for (VFSItem vfsItem : container.getItems(attachmentExcludeFilter)) { + if (vfsItem instanceof VFSLeaf) { + // add leaf to our list + VFSLeaf leaf = (VFSLeaf) vfsItem; + allLeafs.add(leaf); + } else if (vfsItem instanceof VFSContainer) { + // do it recursively for all children + VFSContainer childContainer = (VFSContainer) vfsItem; + addItems(childContainer, allLeafs); + } else { + // hu? + } + } + } + + +} diff --git a/src/main/java/org/olat/course/nodes/pf/ui/PFPreviewController.java b/src/main/java/org/olat/course/nodes/pf/ui/PFPreviewController.java new file mode 100644 index 0000000000000000000000000000000000000000..6df6743e6d575c62519b4c451ff442541a3218b0 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/pf/ui/PFPreviewController.java @@ -0,0 +1,78 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.course.nodes.pf.ui; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.velocity.VelocityContainer; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.control.controller.BasicController; +import org.olat.course.nodes.PFCourseNode; +import org.olat.course.nodes.pf.manager.PFManager; +import org.olat.course.run.userview.UserCourseEnvironment; +import org.springframework.beans.factory.annotation.Autowired; +/** +* +* @author Fabian Kiefer, fabian.kiefer@frentix.com, http://www.frentix.com +* +*/ +public class PFPreviewController extends BasicController { + + @Autowired + private PFManager pfManager; + + public PFPreviewController(UserRequest ureq, WindowControl wControl) { + super(ureq, wControl); + } + + public PFPreviewController(UserRequest ureq, WindowControl wControl, PFCourseNode pfNode, + UserCourseEnvironment userCourseEnv) { + super(ureq, wControl); + + VelocityContainer previewVC = createVelocityContainer("preview"); + + previewVC.contextPut("drop", pfNode.hasParticipantBoxConfigured()); + previewVC.contextPut("return", pfNode.hasCoachBoxConfigured()); + previewVC.contextPut("limit", pfNode.hasLimitCountConfigured()); + String timeframe = pfNode.hasDropboxTimeFrameConfigured() ? + pfNode.getDateStart().toString() + " - " + pfNode.getDateEnd().toString() : "-"; + previewVC.contextPut("timeframe", timeframe); + + PFParticipantController participantController = new PFParticipantController(ureq, getWindowControl(), + pfNode, userCourseEnv, ureq.getIdentity(), pfManager.providePFView(pfNode), true, true); + listenTo(participantController); + participantController.disableSubscriptionController(); + previewVC.put("folder", participantController.getInitialComponent()); + + setInitialComponent(previewVC); + } + + @Override + protected void event(UserRequest ureq, Component source, Event event) { + + } + + @Override + protected void doDispose() { + + } + +} diff --git a/src/main/java/org/olat/course/nodes/pf/ui/PFRunController.java b/src/main/java/org/olat/course/nodes/pf/ui/PFRunController.java new file mode 100644 index 0000000000000000000000000000000000000000..ac5ec8d8569186fe9630fbbacd24e9cea7e78116 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/pf/ui/PFRunController.java @@ -0,0 +1,156 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.course.nodes.pf.ui; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.link.Link; +import org.olat.core.gui.components.link.LinkFactory; +import org.olat.core.gui.components.segmentedview.SegmentViewComponent; +import org.olat.core.gui.components.segmentedview.SegmentViewEvent; +import org.olat.core.gui.components.segmentedview.SegmentViewFactory; +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; +import org.olat.core.gui.control.controller.BasicController; +import org.olat.core.id.Identity; +import org.olat.course.nodes.PFCourseNode; +import org.olat.course.nodes.TitledWrapperHelper; +import org.olat.course.nodes.pf.manager.PFEvent; +import org.olat.course.nodes.pf.manager.PFManager; +import org.olat.course.nodes.pf.manager.PFView; +import org.olat.course.run.navigation.NodeRunConstructionResult; +import org.olat.course.run.userview.UserCourseEnvironment; +import org.springframework.beans.factory.annotation.Autowired; +/** +* +* @author Fabian Kiefer, fabian.kiefer@frentix.com, http://www.frentix.com +* +*/ +public class PFRunController extends BasicController { + + private PFCourseNode pfNode; + private UserCourseEnvironment userCourseEnv; + private PFView pfView; + + private PFCoachController coachController; + private PFParticipantController participantController; + + private Link coachLink, participantLink; + + private VelocityContainer mainVC; + private SegmentViewComponent segmentView; + + + @Autowired + private PFManager pfManager; + + public PFRunController(UserRequest ureq, WindowControl wControl, PFCourseNode pfNode, UserCourseEnvironment userCourseEnv) { + super(ureq, wControl); + this.pfNode = pfNode; + this.userCourseEnv = userCourseEnv; + + this.pfView = pfManager.providePFView(pfNode); + + mainVC = createVelocityContainer("run"); + + if (userCourseEnv.isCoach() || userCourseEnv.isAdmin()) { + if ((userCourseEnv.isCoach() || userCourseEnv.isAdmin()) && userCourseEnv.isParticipant()) { + segmentView = SegmentViewFactory.createSegmentView("segments", mainVC, this); + coachLink = LinkFactory.createLink("tab.coach", mainVC, this); + segmentView.addSegment(coachLink, true); + participantLink = LinkFactory.createLink("tab.participant", mainVC, this); + segmentView.addSegment(participantLink, false); + } + doOpenCoachView(ureq); + } else { + doOpenParticipantsView(ureq); + } + + + putInitialPanel(mainVC); + + } + + @Override + protected void event(UserRequest ureq, Component source, Event event) { + if(source == segmentView) { + if(event instanceof SegmentViewEvent) { + SegmentViewEvent sve = (SegmentViewEvent)event; + String segmentCName = sve.getComponentName(); + Component clickedLink = mainVC.getComponent(segmentCName); + if (clickedLink == coachLink) { + doOpenCoachView(ureq); + } else if (clickedLink == participantLink) { + doOpenParticipantsView(ureq); + } + } + } + } + + @Override + protected void event(UserRequest ureq, Controller source, Event event) { + + if (source == coachController) { + if (event instanceof PFEvent) { + segmentView.select(participantLink); + PFEvent sfe = (PFEvent)event; + doOpenParticipantsView(ureq, sfe.getIdentity(), pfView); + } else if (event == Event.CHANGED_EVENT) { + doOpenCoachView(ureq); + } + } else if (source == participantController) { + if (event == Event.CHANGED_EVENT) { + doOpenParticipantsView(ureq, ureq.getIdentity(), PFView.displayDrop); + } + } + super.event(ureq, source, event); + } + + @Override + protected void doDispose() { + + } + + private void doOpenCoachView(UserRequest ureq) { + coachController = new PFCoachController(ureq, getWindowControl(), pfNode, userCourseEnv, pfView); + listenTo(coachController); + mainVC.put("segmentCmp", coachController.getInitialComponent()); + } + + private void doOpenParticipantsView (UserRequest ureq) { + doOpenParticipantsView(ureq, ureq.getIdentity(), pfView); + } + + private void doOpenParticipantsView(UserRequest ureq, Identity identity, PFView pfView) { + participantController = new PFParticipantController(ureq, getWindowControl(), pfNode, + userCourseEnv, identity, pfView, false, false); + listenTo(participantController); + mainVC.put("segmentCmp", participantController.getInitialComponent()); + } + + public NodeRunConstructionResult createNodeRunConstructionResult(UserRequest ureq) { + // integrate it into the olat menu + Controller ctrl = TitledWrapperHelper.getWrapper(ureq, getWindowControl(), this, pfNode, "o_ta_icon"); + return new NodeRunConstructionResult(ctrl); + } + +} diff --git a/src/main/java/org/olat/course/nodes/pf/ui/_content/coach.html b/src/main/java/org/olat/course/nodes/pf/ui/_content/coach.html new file mode 100644 index 0000000000000000000000000000000000000000..fdb13af006b75e13a824e2f1ef5ac9aa535de366 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/pf/ui/_content/coach.html @@ -0,0 +1,15 @@ +<div class="clearfix"> +#if($r.available("single")) + $r.render("backLink") + <div class="o_block_bottom">$r.render("single")</div> +#else + #if ($r.available("contextualSubscription")) + <div class="clearfix o_block_bottom"> + $r.render("contextualSubscription") + </div> + #end + $r.render("table") + $r.render("buttons") +#end +</div> + diff --git a/src/main/java/org/olat/course/nodes/pf/ui/_content/edit.html b/src/main/java/org/olat/course/nodes/pf/ui/_content/edit.html new file mode 100644 index 0000000000000000000000000000000000000000..e9fe3446e68f78188a136b32a0d60d4d9721a74f --- /dev/null +++ b/src/main/java/org/olat/course/nodes/pf/ui/_content/edit.html @@ -0,0 +1,21 @@ +<fieldset class="o_sel_course_ms"> + <legend>$r.contextHelpWithWrapper("Assessment#_bewertung_kursbaustein") + $r.translate("form.configuration")</legend> + + #if ($hasLogEntries) + #if ($isOverwriting) + <div class="o_warning"> + <i>$r.translate("scoring.overwriting.note")</i> + </div> + #else + <div class="o_warning"> + <i>$r.translate("scoring.overwriting")</i> + </div> + <div class="o_button_group"> + $r.render("scoring.config.enable.button") + </div> + #end + #end +</fieldset> +$r.render("sfeditform") + diff --git a/src/main/java/org/olat/course/nodes/pf/ui/_content/participant.html b/src/main/java/org/olat/course/nodes/pf/ui/_content/participant.html new file mode 100644 index 0000000000000000000000000000000000000000..7a8e73f89fd2c04e5c784ccfe1a836ec2103dbdf --- /dev/null +++ b/src/main/java/org/olat/course/nodes/pf/ui/_content/participant.html @@ -0,0 +1,19 @@ +<!-- <div class="panel panel-default o_personal"> --> +<!-- <div class="panel-heading">$name $r.translate("drop.box")</div> --> +<!-- <div>$r.translate("drop.info")</div> --> +<!-- <div>$r.render("upload")</div> --> +<!-- </div> --> +<!-- <div class="panel panel-default o_personal"> --> +<!-- <div class="panel-heading">$r.translate("return.box")</div> --> +<!-- <div>$r.translate("return.info")</div> --> +<!-- </div> --> +#if ($limit) + <div class="o_note">$r.translate("limit.count.info", $limit)</div> +#end + +#if ($r.available("contextualSubscription")) + $r.render("contextualSubscription") +#end +$r.render("folder") + + diff --git a/src/main/java/org/olat/course/nodes/pf/ui/_content/peekview.html b/src/main/java/org/olat/course/nodes/pf/ui/_content/peekview.html new file mode 100644 index 0000000000000000000000000000000000000000..217673e4adea0dfafdab2647a814ce9301f44559 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/pf/ui/_content/peekview.html @@ -0,0 +1,8 @@ +<div class="o_briefcase_peekview clearfix"> +#foreach( $leaf in $leafs ) + <div class="o_briefcase_peekview_file"> + $r.render("nodeLinkDL_${velocityCount}") + </div> +#end + $r.render("peekview.allItemsLink") +</div> \ No newline at end of file diff --git a/src/main/java/org/olat/course/nodes/pf/ui/_content/preview.html b/src/main/java/org/olat/course/nodes/pf/ui/_content/preview.html new file mode 100644 index 0000000000000000000000000000000000000000..49789a2c133c57585499f66c31d2a145c57fbfde --- /dev/null +++ b/src/main/java/org/olat/course/nodes/pf/ui/_content/preview.html @@ -0,0 +1,32 @@ +<div class="panel-group" id="o_preview_bc_details"><div class="panel panel-default"> + <div class="panel-heading"> + <h4 class="panel-title"> + <i id="collapseToggler" class="o_icon o_icon-fw o_icon_close_togglebox"> </i> + <a data-toggle="collapse" data-parent="#o_preview_bc_details" href="#collapseBc">$r.translate("preview.header")</a> + </h4> + </div> + <div id="collapseBc" class="panel-collapse collapse in"> + <table class="table table-bordered"> + <tbody> + <tr><td>$r.translate("preview.drop")</td><td>#if ($drop) $r.translate("yes") #else $r.translate("no") #end</td></tr> + <tr><td>$r.translate("preview.return")</td><td>#if ($return) $r.translate("yes") #else $r.translate("no") #end</td></tr> + <tr><td>$r.translate("preview.limit")</td><td>#if ($limit) $r.translate("yes") #else $r.translate("no") #end</td></tr> + <tr><td>$r.translate("preview.timeframe")</td><td>#if ($timeframe) $timeframe #else $r.translate("no") #end</td></tr> + </tbody> + </table> + <div class="panel-body">$r.translate("preview.info")</div> + </div> +</div></div> +<script type="text/javascript"> + /* <![CDATA[ */ + jQuery('#o_preview_bc_details').on('hide.bs.collapse', function () { + jQuery('#collapseToggler').removeClass('o_icon_close_togglebox').addClass('o_icon_open_togglebox'); + }) + jQuery('#o_preview_bc_details').on('show.bs.collapse', function () { + jQuery('#collapseToggler').removeClass('o_icon_open_togglebox').addClass('o_icon_close_togglebox'); + }) + /* ]]> */ +</script> +#if ($r.available("folder")) + $r.render("folder") +#end \ No newline at end of file diff --git a/src/main/java/org/olat/course/nodes/pf/ui/_content/run.html b/src/main/java/org/olat/course/nodes/pf/ui/_content/run.html new file mode 100644 index 0000000000000000000000000000000000000000..5aad1fca2df87bc2025ab0da7e6a646623d3c8e8 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/pf/ui/_content/run.html @@ -0,0 +1,11 @@ +<div class="clearfix"> + #if($r.available("segments")) + <div class="o_block_bottom"> + $r.render("segments") + </div> + #end + + #if($r.available("segmentCmp")) + $r.render("segmentCmp") + #end +</div> \ No newline at end of file diff --git a/src/main/java/org/olat/course/nodes/pf/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/nodes/pf/ui/_i18n/LocalStrings_de.properties new file mode 100644 index 0000000000000000000000000000000000000000..c897391fe1c51f23e50d8f3f13cb7fb6955b93d3 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/pf/ui/_i18n/LocalStrings_de.properties @@ -0,0 +1,49 @@ +participant.drop=Abgabeordner aktivieren +coach.drop=R\u00FCckgabeordner aktivieren +drop.box=Abgabeordner +drop.info=Bitte w\u00E4hlen Sie den untenstehenden Link um ein Dokument einzustellen. +drop.empty=Sie haben noch kein Dokument eingestellt. +return.box=R\u00FCckgabeordner +return.info=In der Liste finden Sie alle Dokumente, welche von Ihren Betreuer f\u00FCr Sie eingestellt wurden. +limit.count=Anzahl der einstellbaren Dokumente einschr\u00E4nken +limit.count.info=Sie k\u00F6nnen nur insgesamt {0} Dokumente einstellen. +file.count=Limite +alter.file=L\u00F6schen und \u00DCœberschreiben von Dokumenten erlauben +time.frame=Zeitfenster f\u00FCr Abgabe festlegen +date.start=von +date.end=bis +valid.date=Bitte geben Sie ein g\u00FCltiges Datum an +textfield.upload=Datei hochladen +form.configuration=Konfiguration des Abgabe- und R\u00FCckgabeordners +pane.tab.accessibility=Zugang +pane.tab.configuration=Ordner Einstellungen +blank.label= +table.cols.numFiles=Abgabeordner +table.cols.numReturn=R\u00FCckgabeordner +table.cols.newFiles=Neu +table.cols.lastUpdate=Letzte \u00C4nderung +table.cols.lastUpdateReturn=Letzte \u00C4„nderung +table.cols.status=Status +table.cols.openbox=Aktion +open.box=Ordner \u00F6ffnen +table.no.selection=Es wurde kein Benutzer ausgew\u00E4hlt! +upload.link=Dokument mehreren verteilen +download.link=Mehrere Ordner herunterladen +tab.coach=Betreuer Ansicht +tab.participant=Teilnehmer Ansicht +notifications.header.course=Ordner in Kurs "{0}" +notifications.entry.upload=Das Dokument {0}" wurde eingestellt {1} {2}. +notifications.entry.modify=Das Dokument "{0}" wurde ge\u00E4ndert {1} {2}. +notifications.entry.for=f\u00FCr +notifications.entry.by=von +notifications.header=Teilnehmer des Kurses "{0}" haben neue Dokumente eingestellt. +timeframe.error=Bitte \u00FCberpr\u00FCfen Sie ob das limitierende Zeitfenster korrekt ist. +condition.accessibility.title=Zugang +upload.success=Es wurden Dokumente eingestellt +peekview.allItemsLink=Alle Dokumente +preview.drop=Abgabeordner aktiviert +preview.return=R\u00FCckgabeordner aktiviert +preview.limit=Dokumentenlimite aktiviert +preview.timeframe=Zeitfenster +preview.info=Ein Beispiel finden Sie untenstehend: +preview.header=Konfiguration f\u00FCr den simulierten Benutzer diff --git a/src/main/java/org/olat/course/nodes/pf/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/nodes/pf/ui/_i18n/LocalStrings_en.properties new file mode 100644 index 0000000000000000000000000000000000000000..a4c7ca39bf3867d3924013813176f48743237d2e --- /dev/null +++ b/src/main/java/org/olat/course/nodes/pf/ui/_i18n/LocalStrings_en.properties @@ -0,0 +1,49 @@ +participant.drop=Enable Participant Folder +coach.drop=Enable Coach Folder +drop.box=Drop Box +drop.info=Please select the link below to hand in a file. +drop.empty=You have not uploaded any file yet. +return.box=Return Box +return.info=Below you will find any files returned by your coach. +limit.count=Limit number of uploadable documents +limit.count.info=You may only upload or create {0} files total. +file.count=Limit +alter.file=Enable overwrite/delete of uploaded documents +time.frame=Limit upload to time interval +date.start=from +date.end=until +valid.date=Provide a valid date +textfield.upload=Upload file +form.configuration=Drop Box and Return Box configuration +pane.tab.accessibility=Access +pane.tab.configuration=Folder settings +blank.label= +table.cols.numFiles=Drop box +table.cols.numReturn=Return box +table.cols.newFiles=New +table.cols.lastUpdate=Last modified +table.cols.lastUpdateReturn=Last modified +table.cols.status=Status +table.cols.openbox=Action +open.box=Open folder +table.no.selection=No User has been selected! +upload.link=Bulk upload +download.link=Bulk download +tab.coach=Coach view +tab.participant=Participant view +notifications.header.course=Folder in course "{0}" +notifications.entry.upload=A new file "{0}" has been uploaded {1} {2}. +notifications.entry.modify=File "{0}" has been modified by {1} {2}. +notifications.entry.for=for +notifications.entry.by=by +notifications.header=Participants of of the course "{0}" uploaded new files. +timeframe.error=Please check if your time interval is valid! +condition.accessibility.title=Access Control +upload.success=Files have been uploaded +peekview.allItemsLink=All documents +preview.drop=Dropbox enabled +preview.return=Returnbox enabled +preview.limit=Filelimit active +preview.timeframe=Time interval +preview.info=An example is shown below: +preview.header=Configuration folder for simulated user \ No newline at end of file diff --git a/src/main/java/org/olat/user/propertyhandlers/_spring/userPropertiesContext.xml b/src/main/java/org/olat/user/propertyhandlers/_spring/userPropertiesContext.xml index 0b8028b698b1fa65df9a7cbcaeb4539e75d8f9c3..cc777dfb0b356a77a4ff996b57bb60d90f2ab434 100644 --- a/src/main/java/org/olat/user/propertyhandlers/_spring/userPropertiesContext.xml +++ b/src/main/java/org/olat/user/propertyhandlers/_spring/userPropertiesContext.xml @@ -253,6 +253,37 @@ </bean> </entry> + <entry key="org.olat.course.nodes.pf.ui.PFCoachController"> + <bean class="org.olat.user.propertyhandlers.UserPropertyUsageContext"> + <property name="propertyHandlers"> + <list> + <ref bean="userPropertyUserName" /> + <ref bean="userPropertyFirstName" /> + <ref bean="userPropertyLastName" /> + <ref bean="userPropertyEmail" /> + <ref bean="userPropertyInstitutionalName" /> + <ref bean="userPropertyInstitutionalUserIdentifier" /> + <ref bean="userPropertyInstitutionalEmail" /> + </list> + </property> + <property name="mandatoryProperties"> + <set> + <ref bean="userPropertyLastName" /> + <ref bean="userPropertyFirstName" /> + </set> + </property> + <property name="adminViewOnlyProperties"> + <set> + <ref bean="userPropertyUserName" /> + <ref bean="userPropertyEmail" /> + <ref bean="userPropertyInstitutionalName" /> + <ref bean="userPropertyInstitutionalUserIdentifier" /> + <ref bean="userPropertyInstitutionalEmail" /> + </set> + </property> + </bean> + </entry> + <entry key="org.olat.course.member.MemberListController"> <bean class="org.olat.user.propertyhandlers.UserPropertyUsageContext"> <property name="propertyHandlers"> diff --git a/src/test/java/org/olat/course/nodes/pf/manager/PFManagerTest.java b/src/test/java/org/olat/course/nodes/pf/manager/PFManagerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..a3aa6ff62a0a2092103d3624872066682ae28a55 --- /dev/null +++ b/src/test/java/org/olat/course/nodes/pf/manager/PFManagerTest.java @@ -0,0 +1,172 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.course.nodes.pf.manager; + +import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import org.jcodec.common.Assert; +import org.junit.Before; +import org.junit.Test; +import org.olat.basesecurity.GroupRoles; +import org.olat.core.commons.modules.bc.vfs.OlatRootFolderImpl; +import org.olat.core.gui.media.MediaResource; +import org.olat.core.gui.util.SyntheticUserRequest; +import org.olat.core.id.Identity; +import org.olat.core.id.IdentityEnvironment; +import org.olat.core.util.i18n.I18nManager; +import org.olat.core.util.vfs.VFSContainer; +import org.olat.core.util.vfs.VFSManager; +import org.olat.course.CourseFactory; +import org.olat.course.ICourse; +import org.olat.course.nodes.PFCourseNode; +import org.olat.course.run.environment.CourseEnvironment; +import org.olat.course.run.userview.UserCourseEnvironment; +import org.olat.course.run.userview.UserCourseEnvironmentImpl; +import org.olat.repository.RepositoryEntry; +import org.olat.repository.manager.RepositoryEntryRelationDAO; +import org.olat.test.JunitTestHelper; +import org.olat.test.OlatTestCase; +import org.springframework.beans.factory.annotation.Autowired; +/** +* +* @author Fabian Kiefer, fabian.kiefer@frentix.com, http://www.frentix.com +* +*/ +public class PFManagerTest extends OlatTestCase { + + private Identity identity; + private ICourse course; + private RepositoryEntry repositoryEntry; + private PFCourseNode pfNode; + private CourseEnvironment courseEnv; + private UserCourseEnvironment userCourseEnv; + + @Autowired + private PFManager pfManager; + @Autowired + private RepositoryEntryRelationDAO repositoryEntryRelationDao; + + @Before + public void setUp() { + // prepare + identity = JunitTestHelper.createAndPersistIdentityAsRndUser("check-1"); + IdentityEnvironment ienv = new IdentityEnvironment(); + pfNode = new PFCourseNode(); + pfNode.getModuleConfiguration().setBooleanEntry(PFCourseNode.CONFIG_KEY_COACHBOX, true); + pfNode.getModuleConfiguration().setBooleanEntry(PFCourseNode.CONFIG_KEY_PARTICIPANTBOX, true); + + ienv.setIdentity(identity); + // import "Demo course" into the bcroot_junittest + repositoryEntry = JunitTestHelper.deployDemoCourse(identity); + Long resourceableId = repositoryEntry.getOlatResource().getResourceableId(); + + course = CourseFactory.loadCourse(resourceableId); + userCourseEnv = new UserCourseEnvironmentImpl(ienv, course.getCourseEnvironment()); + } + + + @Test + public void provideParticipantView_test () { + Identity check3 = JunitTestHelper.createAndPersistIdentityAsRndUser("check-3"); + repositoryEntryRelationDao.addRole(check3, repositoryEntry, GroupRoles.participant.name()); + VFSContainer vfsContainer = pfManager.provideCoachOrParticipantContainer(pfNode, userCourseEnv, check3); + Assert.assertNotNull(vfsContainer); + } + + @Test + public void provideCoachView_test () { + Identity check4 = JunitTestHelper.createAndPersistIdentityAsRndUser("check-4"); + repositoryEntryRelationDao.addRole(check4, repositoryEntry, GroupRoles.coach.name()); + VFSContainer vfsContainer = pfManager.provideCoachOrParticipantContainer(pfNode, userCourseEnv, check4); + Assert.assertNotNull(vfsContainer); + } + + @Test + public void uploadFileToDropBox_test () { + //create files + boolean fileCreated = pfManager.uploadFileToDropBox(new File("text1.txt"), "textfile1", + 1, courseEnv, pfNode, identity); + boolean fileNotCreated = pfManager.uploadFileToDropBox(new File("text2.txt"), "textfile2", + 0, courseEnv, pfNode, identity); + + Path relPath = Paths.get(PFManager.FILENAME_PARTICIPANTFOLDER, pfNode.getIdent(), + pfManager.getIdFolderName(identity), PFManager.FILENAME_DROPBOX); + OlatRootFolderImpl baseContainer = courseEnv.getCourseBaseContainer(); + VFSContainer dropboxContainer = VFSManager.resolveOrCreateContainerFromPath(baseContainer, relPath.toString()); + + //check + Assert.assertTrue(fileCreated); + Assert.assertTrue(!fileNotCreated); + Assert.assertTrue("textfile1".equals(dropboxContainer.getItems().get(0).getName())); + + } + + @Test + public void uploadFileToAllReturnBoxes_test () { + // prepare + List<Identity> identities = new ArrayList<>(); + for (int i = 0; i < 5; i++) { + identities.add(JunitTestHelper.createAndPersistIdentityAsRndUser("pf-user-" + i)); + } + pfManager.uploadFileToAllReturnBoxes(new File("text3.txt"), "textfile3", courseEnv, pfNode, identities); + //check + for (Identity identity : identities) { + Path relPath = Paths.get(PFManager.FILENAME_PARTICIPANTFOLDER, pfNode.getIdent(), + pfManager.getIdFolderName(identity), PFManager.FILENAME_RETURNBOX); + OlatRootFolderImpl baseContainer = courseEnv.getCourseBaseContainer(); + VFSContainer returnboxContainer = VFSManager.resolveOrCreateContainerFromPath(baseContainer, relPath.toString()); + Assert.assertTrue("textfile3".equals(returnboxContainer.getItems().get(0).getName())); + } + } + + @Test + public void exportMediaResource_test () { + //prepare + List<Identity> identities = new ArrayList<>(); + for (int i = 0; i < 5; i++) { + identities.add(JunitTestHelper.createAndPersistIdentityAsRndUser("pf-user-" + (i + 6))); + } + Identity check2 = JunitTestHelper.createAndPersistIdentityAsRndUser("check-2"); + Locale locale = I18nManager.getInstance().getLocaleOrDefault(check2.getUser().getPreferences().getLanguage()); + MediaResource resource = pfManager.exportMediaResource(new SyntheticUserRequest(check2, locale), identities, pfNode, courseEnv); + //check + Assert.assertNotNull(resource); + } + + @Test + public void getParticipants_test () { + //prepare + repositoryEntryRelationDao.addRole(identity, repositoryEntry, GroupRoles.coach.name()); + for (int i = 0; i < 5; i++) { + Identity id = JunitTestHelper.createAndPersistIdentityAsRndUser("pf-user-" + (i+12)); + repositoryEntryRelationDao.addRole(id, repositoryEntry, GroupRoles.participant.name()); + } + List<Identity> ids = pfManager.getParticipants(identity, courseEnv); + //check + Assert.assertEquals(ids.size(), 5); + } + + +} diff --git a/src/test/java/org/olat/test/AllTestsJunit4.java b/src/test/java/org/olat/test/AllTestsJunit4.java index 8f0ad4ca2b5ed0861838d26f23db72328317fdd7..7690f83ca95e78bbc057f19584dc4f28007d33d0 100644 --- a/src/test/java/org/olat/test/AllTestsJunit4.java +++ b/src/test/java/org/olat/test/AllTestsJunit4.java @@ -135,6 +135,7 @@ import org.junit.runners.Suite; org.olat.course.nodes.en.EnrollmentManagerConcurrentTest.class, org.olat.course.nodes.gta.manager.GTAManagerTest.class, org.olat.course.nodes.gta.rule.GTAReminderRuleTest.class, + org.olat.course.nodes.pf.manager.PFManager.class, org.olat.course.assessment.AssessmentManagerTest.class, org.olat.course.assessment.manager.UserCourseInformationsManagerTest.class, org.olat.course.assessment.manager.AssessmentModeManagerTest.class,