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,