diff --git a/src/main/java/org/olat/commons/memberlist/ui/MembersMailController.java b/src/main/java/org/olat/commons/memberlist/ui/MembersMailController.java
index baed593873ffdbe84c88fd9079db876bac75a2fc..fa98d08681b1fd4c00671df67dd0a0cc4c779f6c 100644
--- a/src/main/java/org/olat/commons/memberlist/ui/MembersMailController.java
+++ b/src/main/java/org/olat/commons/memberlist/ui/MembersMailController.java
@@ -21,7 +21,6 @@ package org.olat.commons.memberlist.ui;
 
 import java.io.File;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -112,8 +111,8 @@ public class MembersMailController extends FormBasicController {
 	
 	public MembersMailController(UserRequest ureq, WindowControl wControl, Translator translator, CourseEnvironment courseEnv,
 			List<Member> ownerList, List<Member> coachList, List<Member> participantList, List<Member> waitingList, String bodyTemplate) {
-		super(ureq, wControl);
-		setTranslator(Util.createPackageTranslator(translator, MailHelper.class, ureq.getLocale()));
+		super(ureq, wControl, Util.createPackageTranslator(translator, MailHelper.class, ureq.getLocale()));
+		
 		
 		this.courseEnv = courseEnv;
 		this.ownerList = ownerList;
@@ -477,7 +476,7 @@ public class MembersMailController extends FormBasicController {
 	}
 	
 	private void doSend(UserRequest ureq) {
-		ContactList contactList = new ContactList("");
+		List<ContactList> contactList = new ArrayList<>();
 		if (courseEnv == null) {
 			if(coachEl != null && coachEl.isAtLeastSelected(1)) {
 				List<Long> identityKeys = new ArrayList<>(coachList.size());
@@ -485,7 +484,9 @@ public class MembersMailController extends FormBasicController {
 					identityKeys.add(coach.getKey());
 				}
 				List<Identity> coaches = securityManager.loadIdentityByKeys(identityKeys);
-				contactList.addAllIdentites(coaches);
+				ContactList coachList = new ContactList(translate("contact.list.coaches"));
+				coachList.addAllIdentites(coaches);
+				contactList.add(coachList);
 			}
 			
 			if(participantEl != null && participantEl.isAtLeastSelected(1)) {
@@ -494,7 +495,9 @@ public class MembersMailController extends FormBasicController {
 					identityKeys.add(participant.getKey());
 				}
 				List<Identity> participants = securityManager.loadIdentityByKeys(identityKeys);
-				contactList.addAllIdentites(participants);
+				ContactList participantList = new ContactList(translate("contact.list.participants"));
+				participantList.addAllIdentites(participants);
+				contactList.add(participantList);
 			}
 			
 			if(waitingEl != null && waitingEl.isAtLeastSelected(1)) {
@@ -503,13 +506,17 @@ public class MembersMailController extends FormBasicController {
 					identityKeys.add(waiter.getKey());
 				}
 				List<Identity> waiters = securityManager.loadIdentityByKeys(identityKeys);
-				contactList.addAllIdentites(waiters);
+				ContactList waitingList = new ContactList(translate("contact.list.waiting"));
+				waitingList.addAllIdentites(waiters);
+				contactList.add(waitingList);
 			}
 		} else {			
 			if(ownerEl != null && ownerEl.isAtLeastSelected(1)) {
 				RepositoryEntry courseRepositoryEntry = courseEnv.getCourseGroupManager().getCourseEntry();
 				List<Identity> owners = repositoryService.getMembers(courseRepositoryEntry, GroupRoles.owner.name());
-				contactList.addAllIdentites(owners);
+				ContactList ownerList = new ContactList(translate("contact.list.owners"));
+				ownerList.addAllIdentites(owners);
+				contactList.add(ownerList);
 			}
 			
 			if(coachEl != null && coachEl.isAtLeastSelected(1)) {
@@ -518,8 +525,10 @@ public class MembersMailController extends FormBasicController {
 					sendToWhatYouSee.add(coach.getKey());
 				}
 				CourseGroupManager cgm = courseEnv.getCourseGroupManager();
-				avoidInvisibleMember(cgm.getCoachesFromBusinessGroups(), contactList, sendToWhatYouSee);
-				avoidInvisibleMember(cgm.getCoaches(), contactList, sendToWhatYouSee);
+				ContactList coachList = new ContactList(translate("contact.list.coaches"));
+				avoidInvisibleMember(cgm.getCoachesFromBusinessGroups(), coachList, sendToWhatYouSee);
+				avoidInvisibleMember(cgm.getCoaches(), coachList, sendToWhatYouSee);
+				contactList.add(coachList);
 			}
 			
 			if(participantEl != null && participantEl.isAtLeastSelected(1)) {
@@ -528,8 +537,10 @@ public class MembersMailController extends FormBasicController {
 					sendToWhatYouSee.add(participant.getKey());
 				}
 				CourseGroupManager cgm = courseEnv.getCourseGroupManager();
-				avoidInvisibleMember(cgm.getParticipantsFromBusinessGroups(), contactList, sendToWhatYouSee);
-				avoidInvisibleMember(cgm.getParticipants(), contactList, sendToWhatYouSee);
+				ContactList participantList = new ContactList(translate("contact.list.participants"));
+				avoidInvisibleMember(cgm.getParticipantsFromBusinessGroups(), participantList, sendToWhatYouSee);
+				avoidInvisibleMember(cgm.getParticipants(), participantList, sendToWhatYouSee);
+				contactList.add(participantList);
 			}
 		}
 		
@@ -540,23 +551,27 @@ public class MembersMailController extends FormBasicController {
 				identityKeys.add(member.getKey());
 			}
 			List<Identity> selectedIdentities = securityManager.loadIdentityByKeys(identityKeys);
-			contactList.addAllIdentites(selectedIdentities);
+			ContactList otherList = new ContactList(translate("contact.list.others"));
+			otherList.addAllIdentites(selectedIdentities);
+			contactList.add(otherList);
 		}
 		
 		if(externalEl != null && externalEl.isAtLeastSelected(1)) {
 			String value = externalAddressesEl.getValue();
 			if(StringHelper.containsNonWhitespace(value)) {
+				ContactList externalList = new ContactList(translate("contact.list.external"));
 				for(StringTokenizer tokenizer= new StringTokenizer(value, ",\r\n", false); tokenizer.hasMoreTokens(); ) {
 					String email = tokenizer.nextToken().trim();
-					contactList.add(email);
+					externalList.add(email);
 				}
+				contactList.add(externalList);
 			}
 		}
 
 		doSendEmailToMember(ureq, contactList);
 	}
 	
-	private void doSendEmailToMember(UserRequest ureq, ContactList contactList) {
+	private void doSendEmailToMember(UserRequest ureq, List<ContactList> contactList) {
 		boolean success = false;
 		try {
 			File[] attachmentArr = getAttachments();
@@ -564,7 +579,7 @@ public class MembersMailController extends FormBasicController {
 			MailBundle bundle = new MailBundle();
 			bundle.setContext(context);
 			bundle.setFromId(getIdentity());						
-			bundle.setContactLists(Collections.singletonList(contactList));
+			bundle.setContactLists(contactList);
 			bundle.setContent(subjectEl.getValue(), bodyEl.getValue(), attachmentArr);
 			MailerResult result = mailService.sendMessage(bundle);
 			if(copyFromEl.isAtLeastSelected(1)) {
diff --git a/src/main/java/org/olat/commons/memberlist/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/commons/memberlist/ui/_i18n/LocalStrings_de.properties
index c1d6e89cbe9e68879482b2e8ff8a357bf7fdf164..e47919630ff25e0742a3f22c66155541d6c1a191 100644
--- a/src/main/java/org/olat/commons/memberlist/ui/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/commons/memberlist/ui/_i18n/LocalStrings_de.properties
@@ -3,3 +3,9 @@ table.header.firstTime=Beitritt
 table.header.lastTime=Zuletzt besucht
 table.header.online=$org.olat.group.ui.main\:table.header.online
 nomembers=Keine Mitglieder
+contact.list.external=Externe Empf\u00E4nger
+contact.list.others=Diverse
+contact.list.participants=Teilnehmer
+contact.list.coaches=Betreuer
+contact.list.owners=Besitzer
+contact.list.waiting=Warteliste
diff --git a/src/main/java/org/olat/commons/memberlist/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/commons/memberlist/ui/_i18n/LocalStrings_en.properties
index 3ffdcd55cddf5440d3d344fefa89c2fa91898c30..95178e11b1e14d43d8076ee1b08c37ffb88a638f 100644
--- a/src/main/java/org/olat/commons/memberlist/ui/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/commons/memberlist/ui/_i18n/LocalStrings_en.properties
@@ -2,4 +2,10 @@
 table.header.firstTime=Registration
 table.header.lastTime=Last visit
 table.header.online=$org.olat.group.ui.main\:table.header.online
-nomembers=No members
\ No newline at end of file
+nomembers=No members
+contact.list.external=External recipients
+contact.list.others=Divers
+contact.list.participants=Participants
+contact.list.coaches=Coaches
+contact.list.owners=Owners
+contact.list.waiting=Waiting list
\ No newline at end of file
diff --git a/src/main/java/org/olat/core/util/mail/ContactList.java b/src/main/java/org/olat/core/util/mail/ContactList.java
index 75ec7da502a72e4aab279bdabcd2d87408abe830..01e516f469ad306b022bd4102741ec553ff30e55 100644
--- a/src/main/java/org/olat/core/util/mail/ContactList.java
+++ b/src/main/java/org/olat/core/util/mail/ContactList.java
@@ -38,7 +38,8 @@ import javax.mail.internet.InternetAddress;
 
 import org.olat.core.id.Identity;
 import org.olat.core.id.UserConstants;
-import org.olat.core.logging.LogDelegator;
+import org.olat.core.logging.OLog;
+import org.olat.core.logging.Tracing;
 import org.olat.core.util.StringHelper;
 
 /**
@@ -56,7 +57,10 @@ import org.olat.core.util.StringHelper;
  * @author patrick
  */
 
-public class ContactList extends LogDelegator {
+public class ContactList {
+	
+	private static final OLog log = Tracing.createLoggerFor(ContactList.class);
+	
 	private String name;
 	private String description;
 	//container for addresses contributed as strings
@@ -152,7 +156,7 @@ public class ContactList extends LogDelegator {
 			rfc2047name = javax.mail.internet.MimeUtility.encodeWord(name, "UTF-8", null);
 		}
 		catch (java.io.UnsupportedEncodingException e) {
-			logWarn("Error MIME-encoding name: " + e, e);
+			log.warn("Error MIME-encoding name: " + e, e);
 			rfc2047name = name;
 		}
 
@@ -303,7 +307,7 @@ public class ContactList extends LogDelegator {
 
 	private void setName(String nameP) {
 		if (!StringHelper.containsNoneOfCoDouSemi(nameP)){
-			logWarn("Contact list name \"" + nameP + "\" doesn't match "+ StringHelper.ALL_WITHOUT_COMMA_2POINT_STRPNT, null);
+			log.warn("Contact list name \"" + nameP + "\" doesn't match "+ StringHelper.ALL_WITHOUT_COMMA_2POINT_STRPNT, null);
 			//replace bad chars with bad char in rfc compliant comments
 			nameP = nameP.replaceAll(":","¦");
 			nameP = nameP.replaceAll(";","_");
diff --git a/src/main/java/org/olat/core/util/vfs/MergeSource.java b/src/main/java/org/olat/core/util/vfs/MergeSource.java
index d57476827fdf8b692996d0547468f4734416c9fa..125af08fd991b325c27d36364199988951749682 100644
--- a/src/main/java/org/olat/core/util/vfs/MergeSource.java
+++ b/src/main/java/org/olat/core/util/vfs/MergeSource.java
@@ -36,12 +36,8 @@ import org.olat.core.util.vfs.callbacks.VFSSecurityCallback;
 import org.olat.core.util.vfs.filters.VFSItemFilter;
 
 /**
- * Description: <br>
- * TODO: Felix Jost Class Description for MultiSource
- * <P>
- * 
- *  Initial Date: 23.06.2005 <br>
-  * @author Felix Jost
+ * Initial Date: 23.06.2005 <br>
+ * @author Felix Jost
  */
 public class MergeSource extends AbstractVirtualContainer {
 
@@ -57,16 +53,16 @@ public class MergeSource extends AbstractVirtualContainer {
 	public MergeSource(VFSContainer parentContainer, String name) {
 		super(name);
 		this.parentContainer = parentContainer;
-		mergedContainers = new ArrayList<VFSContainer>();
-		mergedContainersChildren = new ArrayList<VFSContainer>();
+		mergedContainers = new ArrayList<>();
+		mergedContainersChildren = new ArrayList<>();
 	}
 	
 	protected void init() {
 		if(mergedContainers == null) {
-			mergedContainers = new ArrayList<VFSContainer>();
+			mergedContainers = new ArrayList<>();
 		}
 		if(mergedContainersChildren == null) {
-			mergedContainersChildren = new ArrayList<VFSContainer>(2);
+			mergedContainersChildren = new ArrayList<>(2);
 		}
 	}
 	
diff --git a/src/main/java/org/olat/course/MergedCourseContainer.java b/src/main/java/org/olat/course/MergedCourseContainer.java
index b4fe6b4ea50936c4f37298524c50fed7db9b4ee9..f7675f732ebd43169493115d7dce8f2b333e27f3 100644
--- a/src/main/java/org/olat/course/MergedCourseContainer.java
+++ b/src/main/java/org/olat/course/MergedCourseContainer.java
@@ -19,37 +19,18 @@
  */
 package org.olat.course;
 
-import org.olat.admin.quota.QuotaConstants;
 import org.olat.core.CoreSpringFactory;
 import org.olat.core.commons.modules.bc.vfs.OlatRootFolderImpl;
-import org.olat.core.commons.services.notifications.SubscriptionContext;
-import org.olat.core.commons.services.webdav.servlets.RequestUtil;
-import org.olat.core.gui.components.tree.GenericTreeModel;
-import org.olat.core.gui.components.tree.TreeNode;
 import org.olat.core.id.IdentityEnvironment;
 import org.olat.core.logging.OLog;
 import org.olat.core.logging.Tracing;
 import org.olat.core.util.StringHelper;
 import org.olat.core.util.vfs.MergeSource;
 import org.olat.core.util.vfs.NamedContainerImpl;
-import org.olat.core.util.vfs.Quota;
-import org.olat.core.util.vfs.QuotaManager;
 import org.olat.core.util.vfs.VFSContainer;
-import org.olat.core.util.vfs.VFSManager;
 import org.olat.core.util.vfs.callbacks.ReadOnlyCallback;
-import org.olat.core.util.vfs.callbacks.VFSSecurityCallback;
 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.bc.FolderNodeCallback;
-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;
-import org.olat.course.run.userview.UserCourseEnvironmentImpl;
-import org.olat.course.run.userview.VisibleTreeFilter;
+import org.olat.course.folder.MergedCourseElementDataContainer;
 import org.olat.modules.sharedfolder.SharedFolderManager;
 import org.olat.repository.RepositoryEntry;
 import org.olat.repository.RepositoryManager;
@@ -124,22 +105,8 @@ public class MergedCourseContainer extends MergeSource {
 		initSharedFolder(persistingCourse);
 			
 		// add all course building blocks of type BC to a virtual folder
-		MergeSource nodesContainer = new MergeSource(null, "_courseelementdata");
-		if(identityEnv == null) {
-			CourseNode rootNode = persistingCourse.getRunStructure().getRootNode();
-			addFoldersForAdmin(persistingCourse, nodesContainer, rootNode);
-		} else {
-			TreeEvaluation treeEval = new TreeEvaluation();
-			GenericTreeModel treeModel = new GenericTreeModel();
-			UserCourseEnvironment userCourseEnv = new UserCourseEnvironmentImpl(identityEnv, persistingCourse.getCourseEnvironment());
-			CourseNode rootCn = userCourseEnv.getCourseEnvironment().getRunStructure().getRootNode();
-			NodeEvaluation rootNodeEval = rootCn.eval(userCourseEnv.getConditionInterpreter(), treeEval, new VisibleTreeFilter());
-			TreeNode treeRoot = rootNodeEval.getTreeNode();
-			treeModel.setRootNode(treeRoot);
-			addFolders(persistingCourse, nodesContainer, treeRoot);
-		}
-		
-		if (nodesContainer.getItems().size() > 0) {
+		MergedCourseElementDataContainer nodesContainer = new MergedCourseElementDataContainer(courseId, identityEnv);
+		if (!nodesContainer.isEmpty()) {
 			addContainer(nodesContainer);
 		}
 	}
@@ -171,218 +138,6 @@ public class MergedCourseContainer extends MergeSource {
 			}
 		}
 	}
-	
-	private void addFolders(PersistingCourseImpl course, MergeSource nodesContainer, TreeNode courseNode) {
-		if(courseNode == null) return;
-		
-		for (int i = 0; i < courseNode.getChildCount(); i++) {
-			TreeNode child = (TreeNode)courseNode.getChildAt(i);
-			
-			NodeEvaluation nodeEval;
-			if(child.getUserObject() instanceof NodeEvaluation) {
-				nodeEval = (NodeEvaluation)child.getUserObject();
-			} else {
-				continue;
-			}
-			
-			if(nodeEval != null && nodeEval.getCourseNode() != null) {
-				CourseNode courseNodeChild = nodeEval.getCourseNode();
-				String folderName = RequestUtil.normalizeFilename(courseNodeChild.getShortTitle());
-				 
-				if (courseNodeChild instanceof BCCourseNode) {
-					final BCCourseNode bcNode = (BCCourseNode) courseNodeChild;
-					// add folder not to merge source. Use name and node id to have unique name
-					VFSContainer rootFolder = getBCContainer(course, bcNode, nodeEval, false);
-
-					boolean canDownload = nodeEval.isCapabilityAccessible("download");
-					if(canDownload && rootFolder != null) {
-						if(courseReadOnly) {
-							rootFolder.setLocalSecurityCallback(new ReadOnlyCallback());
-						} else if(nodeEval.isCapabilityAccessible("upload")) {
-							//inherit the security callback from the course as for author
-						} else {
-							rootFolder.setLocalSecurityCallback(new ReadOnlyCallback());
-						}
-						
-						folderName = getFolderName(nodesContainer, bcNode, folderName);
-						
-						// Create a container for this node content and wrap it with a merge source which is attached to tree
-						VFSContainer nodeContentContainer = new NamedContainerImpl(folderName, rootFolder);
-						MergeSource courseNodeContainer = new MergeSource(nodesContainer, folderName);
-						courseNodeContainer.addContainersChildren(nodeContentContainer, true);
-						nodesContainer.addContainer(courseNodeContainer);	
-						// Do recursion for all children
-						addFolders(course, courseNodeContainer, child);
-		
-					} else {
-						// For non-folder course nodes, add merge source (no files to show) ...
-						MergeSource courseNodeContainer = new MergeSource(null, folderName);
-						// , then do recursion for all children ...
-						addFolders(course, courseNodeContainer, child);
-						// ... but only add this container if it contains any children with at least one BC course node
-						if (courseNodeContainer.getItems().size() > 0) {
-							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 = getFolderName(nodesContainer, pfNode, folderName);
-					MergeSource courseNodeContainer = new MergeSource(nodesContainer, folderName);					
-					UserCourseEnvironment userCourseEnv = new UserCourseEnvironmentImpl(identityEnv, course.getCourseEnvironment());
-					VFSContainer rootFolder = pfManager.provideCoachOrParticipantContainer(pfNode, userCourseEnv,
-							identityEnv.getIdentity(), courseReadOnly);
-					VFSContainer nodeContentContainer = new NamedContainerImpl(folderName, rootFolder);
-					courseNodeContainer.addContainersChildren(nodeContentContainer, true);
-		
-					addFolders(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);
-					// , then do recursion for all children ...
-					addFolders(course, courseNodeContainer, child);
-					// ... but only add this container if it contains any children with at least one BC course node
-					if (courseNodeContainer.getItems().size() > 0) {
-						nodesContainer.addContainer(courseNodeContainer);
-					}
-				}
-			}
-		}
-	}
-
-	
-	/**
-	 * Internal method to recursively add all course building blocks of type
-	 * BC to a given VFS container. This should only be used for an author view,
-	 * it does not test for security.
-	 * 
-	 * @param course
-	 * @param nodesContainer
-	 * @param courseNode
-	 * @return container for the current course node
-	 */
-	private void addFoldersForAdmin(PersistingCourseImpl course, MergeSource nodesContainer, CourseNode courseNode) {
-		for (int i = 0; i < courseNode.getChildCount(); i++) {
-			CourseNode child = (CourseNode) courseNode.getChildAt(i);
-			String folderName = RequestUtil.normalizeFilename(child.getShortTitle());
-			
-			if (child instanceof BCCourseNode) {
-				final BCCourseNode bcNode = (BCCourseNode) child;
-				// add folder not to merge source. Use name and node id to have unique name
-				VFSContainer rootFolder = getBCContainer(course, bcNode, null, true);
-				if(courseReadOnly) {
-					rootFolder.setLocalSecurityCallback(new ReadOnlyCallback());	
-				}
-				folderName = getFolderName(nodesContainer, bcNode, folderName);
-
- 				if(rootFolder != null) {
- 					// Create a container for this node content and wrap it with a merge source which is attached to tree
- 					VFSContainer nodeContentContainer = new NamedContainerImpl(folderName, rootFolder);
- 					MergeSource courseNodeContainer = new MergeSource(nodesContainer, folderName);
- 					courseNodeContainer.addContainersChildren(nodeContentContainer, true);
- 					nodesContainer.addContainer(courseNodeContainer);	
- 					// Do recursion for all children
- 					addFoldersForAdmin(course, courseNodeContainer, child);
- 				}
-			} else if (child instanceof PFCourseNode) {
-				final PFCourseNode pfNode = (PFCourseNode) child;					
-				// add folder not to merge source. Use name and node id to have unique name
-				PFManager pfManager = CoreSpringFactory.getImpl(PFManager.class);
-				folderName = getFolderName(nodesContainer, pfNode, folderName);
-				MergeSource courseNodeContainer = new MergeSource(nodesContainer, folderName);					
-				VFSContainer rootFolder = pfManager.provideAdminContainer(pfNode, course.getCourseEnvironment());
-				VFSContainer nodeContentContainer = new NamedContainerImpl(folderName, rootFolder);
-				courseNodeContainer.addContainersChildren(nodeContentContainer, true);
-				nodesContainer.addContainer(courseNodeContainer);
-				// Do recursion for all children
-				addFoldersForAdmin(course, courseNodeContainer, child);
-			} else {
-				// For non-folder course nodes, add merge source (no files to show) ...
-				MergeSource courseNodeContainer = new MergeSource(null, folderName);
-				// , then do recursion for all children ...
-				addFoldersForAdmin(course, courseNodeContainer, child);
-				// ... but only add this container if it contains any children with at least one BC course node
-				if (!courseNodeContainer.getItems().isEmpty()) {
-					nodesContainer.addContainer(courseNodeContainer);
-				}
-			}
-		}
-	}
-	
-	/**
-	 * Add node ident if multiple files have same name
-	 * 
-	 * @param nodesContainer
-	 * @param bcNode
-	 * @param folderName
-	 * @return
-	 */
-	private String getFolderName(MergeSource nodesContainer, CourseNode bcNode, String folderName) {
-		// add node ident if multiple files have same name
-		if (!nodesContainer.getItems(vfsItem -> vfsItem.getName().equals(RequestUtil.normalizeFilename(bcNode.getShortTitle()))).isEmpty()) {
-			folderName = folderName + " (" + bcNode.getIdent() + ")";
-		}
-		return folderName;
-	}
-	
-	private VFSContainer getBCContainer(ICourse course, BCCourseNode bcNode, NodeEvaluation nodeEval, boolean isOlatAdmin) {
-		bcNode.updateModuleConfigDefaults(false);
-		// add folder not to merge source. Use name and node id to have unique name
-		VFSContainer rootFolder = null;
-		String subpath = bcNode.getModuleConfiguration().getStringValue(BCCourseNodeEditController.CONFIG_SUBPATH);
-		if(StringHelper.containsNonWhitespace(subpath)){
-			if(bcNode.isSharedFolder()){
-				// grab any shared folder that is configured
-				OlatRootFolderImpl sharedFolder = null;
-				String sfSoftkey = course.getCourseConfig().getSharedFolderSoftkey();
-				if (StringHelper.containsNonWhitespace(sfSoftkey) && !CourseConfig.VALUE_EMPTY_SHAREDFOLDER_SOFTKEY.equals(sfSoftkey)) {
-					RepositoryManager rm = RepositoryManager.getInstance();
-					RepositoryEntry re = rm.lookupRepositoryEntryBySoftkey(sfSoftkey, false);
-					if (re != null) {
-						sharedFolder = SharedFolderManager.getInstance().getSharedFolder(re.getOlatResource());
-						VFSContainer courseBase = sharedFolder;
-						subpath = subpath.replaceFirst("/_sharedfolder", "");
-						rootFolder = (VFSContainer) courseBase.resolve(subpath);
-						if(rootFolder != null) {
-							if(course.getCourseConfig().isSharedFolderReadOnlyMount() || courseReadOnly) {
-								rootFolder.setLocalSecurityCallback(new ReadOnlyCallback());
-							} else if(rootFolder.getLocalSecurityCallback() != null) {
-								SubscriptionContext subContext = CourseModule.createSubscriptionContext(course.getCourseEnvironment(), bcNode);
-								rootFolder.setLocalSecurityCallback(new OverrideSubscriptionSecurityCallback(rootFolder.getLocalSecurityCallback(), subContext));
-							}
-						}
-					}
-				}
-			} else {
-				VFSContainer courseBase = course.getCourseBaseContainer();
-				rootFolder = (VFSContainer) courseBase.resolve("/coursefolder" + subpath);
-				if(rootFolder != null && rootFolder.getLocalSecurityCallback() != null) {
-					SubscriptionContext subContext = CourseModule.createSubscriptionContext(course.getCourseEnvironment(), bcNode);
-					rootFolder.setLocalSecurityCallback(new OverrideSubscriptionSecurityCallback(rootFolder.getLocalSecurityCallback(), subContext));
-				}
-			}
-		}
-		
-		if(bcNode.getModuleConfiguration().getBooleanSafe(BCCourseNodeEditController.CONFIG_AUTO_FOLDER)){
-			String path = BCCourseNode.getFoldernodePathRelToFolderBase(course.getCourseEnvironment(), bcNode);
-			rootFolder = new OlatRootFolderImpl(path, null);
-			if(nodeEval != null) {
-				SubscriptionContext subContext = CourseModule.createSubscriptionContext(course.getCourseEnvironment(), bcNode);
-				rootFolder.setLocalSecurityCallback(new FolderNodeCallback(path, nodeEval, isOlatAdmin, false, subContext));
-			} else {
-				VFSSecurityCallback secCallback = VFSManager.findInheritedSecurityCallback(this);
-				if(secCallback != null) {
-					SubscriptionContext subContext = CourseModule.createSubscriptionContext(course.getCourseEnvironment(), bcNode);
-					rootFolder.setLocalSecurityCallback(new OverrideQuotaSecurityCallback(path, secCallback, subContext));
-				}
-			}
-		}
-		return rootFolder;
-	}
 
 	private Object readResolve() {
 		try {
@@ -393,138 +148,4 @@ public class MergedCourseContainer extends MergeSource {
 			return null;
 		}
 	}
-	
-	private static class OverrideQuotaSecurityCallback implements VFSSecurityCallback {
-		
-		private final String relPath;
-		private Quota overridenQuota;
-		private final SubscriptionContext subContext;
-		private final VFSSecurityCallback secCallback;
-		
-		public OverrideQuotaSecurityCallback(String relPath, VFSSecurityCallback secCallback, SubscriptionContext subContext) {
-			this.relPath = relPath;
-			this.subContext = subContext;
-			this.secCallback = secCallback;
-		}
-
-		@Override
-		public boolean canRead() {
-			return secCallback.canRead();
-		}
-
-		@Override
-		public boolean canWrite() {
-			return secCallback.canWrite();
-		}
-
-		@Override
-		public boolean canCreateFolder() {
-			return secCallback.canCreateFolder();
-		}
-
-		@Override
-		public boolean canDelete() {
-			return secCallback.canDelete();
-		}
-
-		@Override
-		public boolean canList() {
-			return secCallback.canList();
-		}
-
-		@Override
-		public boolean canCopy() {
-			return secCallback.canCopy();
-		}
-
-		@Override
-		public boolean canDeleteRevisionsPermanently() {
-			return secCallback.canDeleteRevisionsPermanently();
-		}
-
-		@Override
-		public Quota getQuota() {
-			if(overridenQuota == null) {
-				QuotaManager qm = CoreSpringFactory.getImpl(QuotaManager.class);
-				overridenQuota = qm.getCustomQuota(relPath);
-				if (overridenQuota == null) {
-					Quota defQuota = qm.getDefaultQuota(QuotaConstants.IDENTIFIER_DEFAULT_NODES);
-					overridenQuota = qm.createQuota(relPath, defQuota.getQuotaKB(), defQuota.getUlLimitKB());
-				}
-			}
-			return overridenQuota;
-		}
-
-		@Override
-		public void setQuota(Quota quota) {
-			//
-		}
-
-		@Override
-		public SubscriptionContext getSubscriptionContext() {
-			return subContext == null ? secCallback.getSubscriptionContext() : subContext;
-		}	
-	}
-	
-
-	private static class OverrideSubscriptionSecurityCallback implements VFSSecurityCallback {
-
-		private final SubscriptionContext subContext;
-		private final VFSSecurityCallback secCallback;
-		
-		public OverrideSubscriptionSecurityCallback(VFSSecurityCallback secCallback, SubscriptionContext subContext) {
-			this.subContext = subContext;
-			this.secCallback = secCallback;
-		}
-
-		@Override
-		public boolean canRead() {
-			return secCallback.canRead();
-		}
-
-		@Override
-		public boolean canWrite() {
-			return secCallback.canWrite();
-		}
-
-		@Override
-		public boolean canCreateFolder() {
-			return secCallback.canCreateFolder();
-		}
-
-		@Override
-		public boolean canDelete() {
-			return secCallback.canDelete();
-		}
-
-		@Override
-		public boolean canList() {
-			return secCallback.canList();
-		}
-
-		@Override
-		public boolean canCopy() {
-			return secCallback.canCopy();
-		}
-
-		@Override
-		public boolean canDeleteRevisionsPermanently() {
-			return secCallback.canDeleteRevisionsPermanently();
-		}
-
-		@Override
-		public Quota getQuota() {
-			return secCallback.getQuota();
-		}
-
-		@Override
-		public void setQuota(Quota quota) {
-			//
-		}
-
-		@Override
-		public SubscriptionContext getSubscriptionContext() {
-			return subContext == null ? secCallback.getSubscriptionContext() : subContext;
-		}
-	}
 }
diff --git a/src/main/java/org/olat/course/folder/MergedCourseElementDataContainer.java b/src/main/java/org/olat/course/folder/MergedCourseElementDataContainer.java
new file mode 100644
index 0000000000000000000000000000000000000000..dc91df140d2014d9348dada1ccb41932f7c1249e
--- /dev/null
+++ b/src/main/java/org/olat/course/folder/MergedCourseElementDataContainer.java
@@ -0,0 +1,517 @@
+/**
+ * <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.folder;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.olat.admin.quota.QuotaConstants;
+import org.olat.core.CoreSpringFactory;
+import org.olat.core.commons.modules.bc.vfs.OlatRootFolderImpl;
+import org.olat.core.commons.services.notifications.SubscriptionContext;
+import org.olat.core.commons.services.webdav.servlets.RequestUtil;
+import org.olat.core.gui.components.tree.TreeNode;
+import org.olat.core.id.IdentityEnvironment;
+import org.olat.core.logging.OLog;
+import org.olat.core.logging.Tracing;
+import org.olat.core.util.StringHelper;
+import org.olat.core.util.nodes.INode;
+import org.olat.core.util.tree.TreeVisitor;
+import org.olat.core.util.tree.Visitor;
+import org.olat.core.util.vfs.MergeSource;
+import org.olat.core.util.vfs.NamedContainerImpl;
+import org.olat.core.util.vfs.Quota;
+import org.olat.core.util.vfs.QuotaManager;
+import org.olat.core.util.vfs.VFSContainer;
+import org.olat.core.util.vfs.VFSItem;
+import org.olat.core.util.vfs.VFSManager;
+import org.olat.core.util.vfs.callbacks.ReadOnlyCallback;
+import org.olat.core.util.vfs.callbacks.VFSSecurityCallback;
+import org.olat.core.util.vfs.filters.VFSItemFilter;
+import org.olat.course.CourseFactory;
+import org.olat.course.CourseModule;
+import org.olat.course.ICourse;
+import org.olat.course.PersistingCourseImpl;
+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.bc.FolderNodeCallback;
+import org.olat.course.run.userview.NodeEvaluation;
+import org.olat.course.run.userview.TreeEvaluation;
+import org.olat.course.run.userview.UserCourseEnvironment;
+import org.olat.course.run.userview.UserCourseEnvironmentImpl;
+import org.olat.course.run.userview.VisibleTreeFilter;
+import org.olat.modules.sharedfolder.SharedFolderManager;
+import org.olat.repository.RepositoryEntry;
+import org.olat.repository.RepositoryManager;
+
+/**
+ * 
+ * Initial date: 28 juin 2018<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class MergedCourseElementDataContainer extends MergeSource {
+	
+	private static final OLog log = Tracing.createLoggerFor(MergedCourseElementDataContainer.class);
+	
+	private final Long courseId;
+	private boolean initialized = false;
+	private boolean courseReadOnly = false;
+	private final IdentityEnvironment identityEnv;
+	
+	public MergedCourseElementDataContainer(Long courseId, IdentityEnvironment identityEnv) {
+		super(null, "_courseelementdata");
+		this.courseId = courseId;
+		this.identityEnv = identityEnv;
+	}
+	
+	public boolean isEmpty() {
+		if(initialized) {
+			return getItems().isEmpty();
+		}
+		
+		ICourse course = CourseFactory.loadCourse(courseId);
+		AtomicInteger count = new AtomicInteger(0);
+		if(identityEnv == null) {
+			new TreeVisitor(new Visitor() {
+				@Override
+				public void visit(INode node) {
+					if(node instanceof PFCourseNode || node instanceof BCCourseNode) {
+						count.incrementAndGet();
+					}
+				}
+			}, course.getRunStructure().getRootNode(), true).visitAll();
+		} else if(course instanceof PersistingCourseImpl) {
+			TreeEvaluation treeEval = new TreeEvaluation();
+			PersistingCourseImpl persistingCourse = (PersistingCourseImpl)course;
+			UserCourseEnvironment userCourseEnv = new UserCourseEnvironmentImpl(identityEnv, persistingCourse.getCourseEnvironment());
+			CourseNode rootCn = userCourseEnv.getCourseEnvironment().getRunStructure().getRootNode();
+			NodeEvaluation rootNodeEval = rootCn.eval(userCourseEnv.getConditionInterpreter(), treeEval, new VisibleTreeFilter());
+	
+			new TreeVisitor(new Visitor() {
+				@Override
+				public void visit(INode node) {
+					if(node instanceof TreeNode) {
+						
+						if(((TreeNode)node).getUserObject() instanceof NodeEvaluation) {
+							NodeEvaluation nodeEval = (NodeEvaluation)((TreeNode)node).getUserObject();
+							if(nodeEval != null
+									&& (nodeEval.getCourseNode() instanceof PFCourseNode || nodeEval.getCourseNode() instanceof BCCourseNode)) {
+								count.incrementAndGet();
+							}
+						}
+					}
+				}
+			}, rootNodeEval.getTreeNode(), true).visitAll();
+			
+		}
+		return count.get() == 0;
+	}
+	
+	@Override
+	public List<VFSItem> getItems(VFSItemFilter filter) {
+		if(!initialized) {
+			init();
+		}
+		return super.getItems(filter);
+	}
+
+	@Override
+	public VFSItem resolve(String path) {
+		if(!initialized) {
+			init();
+		}
+		return super.resolve(path);
+	}
+
+	@Override
+	protected void init() {
+		if(initialized) return;
+		
+		ICourse course = CourseFactory.loadCourse(courseId);
+		if(course instanceof PersistingCourseImpl) {
+			initialized = true;
+			init((PersistingCourseImpl)course);
+		}
+	}
+	
+	protected void init(PersistingCourseImpl persistingCourse) {
+		if(identityEnv == null) {
+			CourseNode rootNode = persistingCourse.getRunStructure().getRootNode();
+			addFoldersForAdmin(persistingCourse, this, rootNode);
+		} else {
+			TreeEvaluation treeEval = new TreeEvaluation();
+			UserCourseEnvironment userCourseEnv = new UserCourseEnvironmentImpl(identityEnv, persistingCourse.getCourseEnvironment());
+			CourseNode rootCn = userCourseEnv.getCourseEnvironment().getRunStructure().getRootNode();
+			NodeEvaluation rootNodeEval = rootCn.eval(userCourseEnv.getConditionInterpreter(), treeEval, new VisibleTreeFilter());
+			TreeNode treeRoot = rootNodeEval.getTreeNode();
+			addFolders(persistingCourse, this, treeRoot);
+		}
+	}
+	
+	private void addFolders(PersistingCourseImpl course, MergeSource nodesContainer, TreeNode courseNode) {
+		if(courseNode == null) return;
+		
+		for (int i = 0; i < courseNode.getChildCount(); i++) {
+			TreeNode child = (TreeNode)courseNode.getChildAt(i);
+			
+			NodeEvaluation nodeEval;
+			if(child.getUserObject() instanceof NodeEvaluation) {
+				nodeEval = (NodeEvaluation)child.getUserObject();
+			} else {
+				continue;
+			}
+			
+			if(nodeEval != null && nodeEval.getCourseNode() != null) {
+				CourseNode courseNodeChild = nodeEval.getCourseNode();
+				String folderName = RequestUtil.normalizeFilename(courseNodeChild.getShortTitle());
+				 
+				if (courseNodeChild instanceof BCCourseNode) {
+					final BCCourseNode bcNode = (BCCourseNode) courseNodeChild;
+					// add folder not to merge source. Use name and node id to have unique name
+					VFSContainer rootFolder = getBCContainer(course, bcNode, nodeEval, false);
+
+					boolean canDownload = nodeEval.isCapabilityAccessible("download");
+					if(canDownload && rootFolder != null) {
+						if(courseReadOnly) {
+							rootFolder.setLocalSecurityCallback(new ReadOnlyCallback());
+						} else if(nodeEval.isCapabilityAccessible("upload")) {
+							//inherit the security callback from the course as for author
+						} else {
+							rootFolder.setLocalSecurityCallback(new ReadOnlyCallback());
+						}
+						
+						folderName = getFolderName(nodesContainer, bcNode, folderName);
+						
+						// Create a container for this node content and wrap it with a merge source which is attached to tree
+						VFSContainer nodeContentContainer = new NamedContainerImpl(folderName, rootFolder);
+						MergeSource courseNodeContainer = new MergeSource(nodesContainer, folderName);
+						courseNodeContainer.addContainersChildren(nodeContentContainer, true);
+						nodesContainer.addContainer(courseNodeContainer);	
+						// Do recursion for all children
+						addFolders(course, courseNodeContainer, child);
+		
+					} else {
+						// For non-folder course nodes, add merge source (no files to show) ...
+						MergeSource courseNodeContainer = new MergeSource(null, folderName);
+						// , then do recursion for all children ...
+						addFolders(course, courseNodeContainer, child);
+						// ... but only add this container if it contains any children with at least one BC course node
+						if (courseNodeContainer.getItems().size() > 0) {
+							nodesContainer.addContainer(courseNodeContainer);
+						}
+					}	
+				} else if (courseNodeChild instanceof PFCourseNode) {
+					PFCourseNode pfNode = (PFCourseNode) courseNodeChild;					
+					// add folder not to merge source. Use name and node id to have unique name
+					folderName = getFolderName(nodesContainer, pfNode, folderName);
+					MergedPFCourseNodeContainer courseNodeContainer = new MergedPFCourseNodeContainer(nodesContainer, folderName,
+							courseId, pfNode, identityEnv, courseReadOnly, false);					
+					addFolders(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);
+					// , then do recursion for all children ...
+					addFolders(course, courseNodeContainer, child);
+					// ... but only add this container if it contains any children with at least one BC course node
+					if (courseNodeContainer.getItems().size() > 0) {
+						nodesContainer.addContainer(courseNodeContainer);
+					}
+				}
+			}
+		}
+	}
+
+	
+	/**
+	 * Internal method to recursively add all course building blocks of type
+	 * BC to a given VFS container. This should only be used for an author view,
+	 * it does not test for security.
+	 * 
+	 * @param course
+	 * @param nodesContainer
+	 * @param courseNode
+	 * @return container for the current course node
+	 */
+	private void addFoldersForAdmin(PersistingCourseImpl course, MergeSource nodesContainer, CourseNode courseNode) {
+		for (int i = 0; i < courseNode.getChildCount(); i++) {
+			CourseNode child = (CourseNode) courseNode.getChildAt(i);
+			String folderName = RequestUtil.normalizeFilename(child.getShortTitle());
+			
+			if (child instanceof BCCourseNode) {
+				final BCCourseNode bcNode = (BCCourseNode) child;
+				// add folder not to merge source. Use name and node id to have unique name
+				VFSContainer rootFolder = getBCContainer(course, bcNode, null, true);
+				if(courseReadOnly) {
+					rootFolder.setLocalSecurityCallback(new ReadOnlyCallback());	
+				}
+				folderName = getFolderName(nodesContainer, bcNode, folderName);
+
+ 				if(rootFolder != null) {
+ 					// Create a container for this node content and wrap it with a merge source which is attached to tree
+ 					VFSContainer nodeContentContainer = new NamedContainerImpl(folderName, rootFolder);
+ 					MergeSource courseNodeContainer = new MergeSource(nodesContainer, folderName);
+ 					courseNodeContainer.addContainersChildren(nodeContentContainer, true);
+ 					nodesContainer.addContainer(courseNodeContainer);	
+ 					// Do recursion for all children
+ 					addFoldersForAdmin(course, courseNodeContainer, child);
+ 				}
+			} else if (child instanceof PFCourseNode) {
+				final PFCourseNode pfNode = (PFCourseNode) child;					
+				// add folder not to merge source. Use name and node id to have unique name
+				folderName = getFolderName(nodesContainer, pfNode, folderName);
+				MergedPFCourseNodeContainer courseNodeContainer = new MergedPFCourseNodeContainer(nodesContainer, folderName,
+						courseId, pfNode, identityEnv, courseReadOnly, true);
+				nodesContainer.addContainer(courseNodeContainer);
+				// Do recursion for all children
+				addFoldersForAdmin(course, courseNodeContainer, child);
+			} else {
+				// For non-folder course nodes, add merge source (no files to show) ...
+				MergeSource courseNodeContainer = new MergeSource(null, folderName);
+				// , then do recursion for all children ...
+				addFoldersForAdmin(course, courseNodeContainer, child);
+				// ... but only add this container if it contains any children with at least one BC course node
+				if (!courseNodeContainer.getItems().isEmpty()) {
+					nodesContainer.addContainer(courseNodeContainer);
+				}
+			}
+		}
+	}
+	
+	/**
+	 * Add node ident if multiple files have same name
+	 * 
+	 * @param nodesContainer
+	 * @param bcNode
+	 * @param folderName
+	 * @return
+	 */
+	private String getFolderName(MergeSource nodesContainer, CourseNode bcNode, String folderName) {
+		// add node ident if multiple files have same name
+		if (!nodesContainer.getItems(vfsItem -> vfsItem.getName().equals(RequestUtil.normalizeFilename(bcNode.getShortTitle()))).isEmpty()) {
+			folderName = folderName + " (" + bcNode.getIdent() + ")";
+		}
+		return folderName;
+	}
+	
+	private VFSContainer getBCContainer(ICourse course, BCCourseNode bcNode, NodeEvaluation nodeEval, boolean isOlatAdmin) {
+		bcNode.updateModuleConfigDefaults(false);
+		// add folder not to merge source. Use name and node id to have unique name
+		VFSContainer rootFolder = null;
+		String subpath = bcNode.getModuleConfiguration().getStringValue(BCCourseNodeEditController.CONFIG_SUBPATH);
+		if(StringHelper.containsNonWhitespace(subpath)){
+			if(bcNode.isSharedFolder()){
+				// grab any shared folder that is configured
+				OlatRootFolderImpl sharedFolder = null;
+				String sfSoftkey = course.getCourseConfig().getSharedFolderSoftkey();
+				if (StringHelper.containsNonWhitespace(sfSoftkey) && !CourseConfig.VALUE_EMPTY_SHAREDFOLDER_SOFTKEY.equals(sfSoftkey)) {
+					RepositoryManager rm = RepositoryManager.getInstance();
+					RepositoryEntry re = rm.lookupRepositoryEntryBySoftkey(sfSoftkey, false);
+					if (re != null) {
+						sharedFolder = SharedFolderManager.getInstance().getSharedFolder(re.getOlatResource());
+						VFSContainer courseBase = sharedFolder;
+						subpath = subpath.replaceFirst("/_sharedfolder", "");
+						rootFolder = (VFSContainer) courseBase.resolve(subpath);
+						if(rootFolder != null) {
+							if(course.getCourseConfig().isSharedFolderReadOnlyMount() || courseReadOnly) {
+								rootFolder.setLocalSecurityCallback(new ReadOnlyCallback());
+							} else if(rootFolder.getLocalSecurityCallback() != null) {
+								SubscriptionContext subContext = CourseModule.createSubscriptionContext(course.getCourseEnvironment(), bcNode);
+								rootFolder.setLocalSecurityCallback(new OverrideSubscriptionSecurityCallback(rootFolder.getLocalSecurityCallback(), subContext));
+							}
+						}
+					}
+				}
+			} else {
+				VFSContainer courseBase = course.getCourseBaseContainer();
+				rootFolder = (VFSContainer) courseBase.resolve("/coursefolder" + subpath);
+				if(rootFolder != null && rootFolder.getLocalSecurityCallback() != null) {
+					SubscriptionContext subContext = CourseModule.createSubscriptionContext(course.getCourseEnvironment(), bcNode);
+					rootFolder.setLocalSecurityCallback(new OverrideSubscriptionSecurityCallback(rootFolder.getLocalSecurityCallback(), subContext));
+				}
+			}
+		}
+		
+		if(bcNode.getModuleConfiguration().getBooleanSafe(BCCourseNodeEditController.CONFIG_AUTO_FOLDER)){
+			String path = BCCourseNode.getFoldernodePathRelToFolderBase(course.getCourseEnvironment(), bcNode);
+			rootFolder = new OlatRootFolderImpl(path, null);
+			if(nodeEval != null) {
+				SubscriptionContext subContext = CourseModule.createSubscriptionContext(course.getCourseEnvironment(), bcNode);
+				rootFolder.setLocalSecurityCallback(new FolderNodeCallback(path, nodeEval, isOlatAdmin, false, subContext));
+			} else {
+				VFSSecurityCallback secCallback = VFSManager.findInheritedSecurityCallback(this);
+				if(secCallback != null) {
+					SubscriptionContext subContext = CourseModule.createSubscriptionContext(course.getCourseEnvironment(), bcNode);
+					rootFolder.setLocalSecurityCallback(new OverrideQuotaSecurityCallback(path, secCallback, subContext));
+				}
+			}
+		}
+		return rootFolder;
+	}
+
+	private Object readResolve() {
+		try {
+			init();
+			return this;
+		} catch (Exception e) {
+			log.error("Cannot init the merged container of a course after deserialization", e);
+			return null;
+		}
+	}
+	
+	private static class OverrideQuotaSecurityCallback implements VFSSecurityCallback {
+		
+		private final String relPath;
+		private Quota overridenQuota;
+		private final SubscriptionContext subContext;
+		private final VFSSecurityCallback secCallback;
+		
+		public OverrideQuotaSecurityCallback(String relPath, VFSSecurityCallback secCallback, SubscriptionContext subContext) {
+			this.relPath = relPath;
+			this.subContext = subContext;
+			this.secCallback = secCallback;
+		}
+
+		@Override
+		public boolean canRead() {
+			return secCallback.canRead();
+		}
+
+		@Override
+		public boolean canWrite() {
+			return secCallback.canWrite();
+		}
+
+		@Override
+		public boolean canCreateFolder() {
+			return secCallback.canCreateFolder();
+		}
+
+		@Override
+		public boolean canDelete() {
+			return secCallback.canDelete();
+		}
+
+		@Override
+		public boolean canList() {
+			return secCallback.canList();
+		}
+
+		@Override
+		public boolean canCopy() {
+			return secCallback.canCopy();
+		}
+
+		@Override
+		public boolean canDeleteRevisionsPermanently() {
+			return secCallback.canDeleteRevisionsPermanently();
+		}
+
+		@Override
+		public Quota getQuota() {
+			if(overridenQuota == null) {
+				QuotaManager qm = CoreSpringFactory.getImpl(QuotaManager.class);
+				overridenQuota = qm.getCustomQuota(relPath);
+				if (overridenQuota == null) {
+					Quota defQuota = qm.getDefaultQuota(QuotaConstants.IDENTIFIER_DEFAULT_NODES);
+					overridenQuota = qm.createQuota(relPath, defQuota.getQuotaKB(), defQuota.getUlLimitKB());
+				}
+			}
+			return overridenQuota;
+		}
+
+		@Override
+		public void setQuota(Quota quota) {
+			//
+		}
+
+		@Override
+		public SubscriptionContext getSubscriptionContext() {
+			return subContext == null ? secCallback.getSubscriptionContext() : subContext;
+		}	
+	}
+	
+
+	private static class OverrideSubscriptionSecurityCallback implements VFSSecurityCallback {
+
+		private final SubscriptionContext subContext;
+		private final VFSSecurityCallback secCallback;
+		
+		public OverrideSubscriptionSecurityCallback(VFSSecurityCallback secCallback, SubscriptionContext subContext) {
+			this.subContext = subContext;
+			this.secCallback = secCallback;
+		}
+
+		@Override
+		public boolean canRead() {
+			return secCallback.canRead();
+		}
+
+		@Override
+		public boolean canWrite() {
+			return secCallback.canWrite();
+		}
+
+		@Override
+		public boolean canCreateFolder() {
+			return secCallback.canCreateFolder();
+		}
+
+		@Override
+		public boolean canDelete() {
+			return secCallback.canDelete();
+		}
+
+		@Override
+		public boolean canList() {
+			return secCallback.canList();
+		}
+
+		@Override
+		public boolean canCopy() {
+			return secCallback.canCopy();
+		}
+
+		@Override
+		public boolean canDeleteRevisionsPermanently() {
+			return secCallback.canDeleteRevisionsPermanently();
+		}
+
+		@Override
+		public Quota getQuota() {
+			return secCallback.getQuota();
+		}
+
+		@Override
+		public void setQuota(Quota quota) {
+			//
+		}
+
+		@Override
+		public SubscriptionContext getSubscriptionContext() {
+			return subContext == null ? secCallback.getSubscriptionContext() : subContext;
+		}
+	}
+}
diff --git a/src/main/java/org/olat/course/folder/MergedPFCourseNodeContainer.java b/src/main/java/org/olat/course/folder/MergedPFCourseNodeContainer.java
new file mode 100644
index 0000000000000000000000000000000000000000..a7a746eb1e2971e10cc010fec5e1b973da1caf22
--- /dev/null
+++ b/src/main/java/org/olat/course/folder/MergedPFCourseNodeContainer.java
@@ -0,0 +1,104 @@
+/**
+ * <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.folder;
+
+import java.util.List;
+
+import org.olat.core.CoreSpringFactory;
+import org.olat.core.id.IdentityEnvironment;
+import org.olat.core.util.vfs.MergeSource;
+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.filters.VFSItemFilter;
+import org.olat.course.CourseFactory;
+import org.olat.course.ICourse;
+import org.olat.course.nodes.PFCourseNode;
+import org.olat.course.nodes.pf.manager.PFManager;
+import org.olat.course.run.userview.UserCourseEnvironment;
+import org.olat.course.run.userview.UserCourseEnvironmentImpl;
+
+/**
+ * 
+ * This container lazy load the participant folders.
+ * 
+ * Initial date: 29 juin 2018<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class MergedPFCourseNodeContainer extends MergeSource {
+
+	private boolean initialized = false;
+	
+	private final Long courseId;
+	private final PFCourseNode pfNode;
+	private final boolean admin;
+	private final boolean courseReadOnly;
+	private final IdentityEnvironment identityEnv;
+	
+	public MergedPFCourseNodeContainer(VFSContainer parentContainer, String folderName,
+			Long courseId, PFCourseNode pfNode,
+			IdentityEnvironment identityEnv, boolean courseReadOnly, boolean admin) {
+		super(parentContainer, folderName);
+		this.courseId = courseId;
+		this.pfNode = pfNode;
+		this.admin = admin;
+		this.courseReadOnly = courseReadOnly;
+		this.identityEnv = identityEnv;
+	}
+
+	@Override
+	protected void init() {
+		if(initialized) return;
+
+		PFManager pfManager = CoreSpringFactory.getImpl(PFManager.class);
+		ICourse course = CourseFactory.loadCourse(courseId);
+		if(admin) {
+			VFSContainer rootFolder = pfManager.provideAdminContainer(pfNode, course.getCourseEnvironment());
+			VFSContainer nodeContentContainer = new NamedContainerImpl(getName(), rootFolder);
+			addContainersChildren(nodeContentContainer, true);
+		} else {
+			UserCourseEnvironment userCourseEnv = new UserCourseEnvironmentImpl(identityEnv, course.getCourseEnvironment());
+			VFSContainer rootFolder = pfManager.provideCoachOrParticipantContainer(pfNode, userCourseEnv,
+					identityEnv.getIdentity(), courseReadOnly);
+			VFSContainer nodeContentContainer = new NamedContainerImpl(getName(), rootFolder);
+			addContainersChildren(nodeContentContainer, true);
+		}
+		
+		super.init();
+	}
+
+	@Override
+	public List<VFSItem> getItems(VFSItemFilter filter) {
+		if(!initialized) {
+			init();
+			initialized = true;
+		}
+		return super.getItems(filter);
+	}
+
+	@Override
+	public VFSItem resolve(String path) {
+		if(!initialized) {
+			init();
+		}
+		return super.resolve(path);
+	}
+}
diff --git a/src/main/java/org/olat/course/nodes/bc/BCCourseNodeRunController.java b/src/main/java/org/olat/course/nodes/bc/BCCourseNodeRunController.java
index 4493b9406a504e3e2c09e1b77608a37c5043d93c..461c45dbab48c6e54588c92ca31c0781055fedfc 100644
--- a/src/main/java/org/olat/course/nodes/bc/BCCourseNodeRunController.java
+++ b/src/main/java/org/olat/course/nodes/bc/BCCourseNodeRunController.java
@@ -119,7 +119,8 @@ public class BCCourseNodeRunController extends DefaultController implements Acti
 		} else{
 			//create folder automatically if not found
 			String subPath = courseNode.getModuleConfiguration().getStringValue(BCCourseNodeEditController.CONFIG_SUBPATH);
-			VFSContainer item = VFSManager.resolveOrCreateContainerFromPath(courseEnv.getCourseFolderContainer(), subPath);
+			VFSContainer courseContainer = courseEnv.getCourseFolderContainer();
+			VFSContainer item = VFSManager.resolveOrCreateContainerFromPath(courseContainer, subPath);
 			
 			String relPath;
 			if(item == null) {
@@ -135,7 +136,7 @@ public class BCCourseNodeRunController extends DefaultController implements Acti
 						&& inheritingContainer.getLocalSecurityCallback() .getQuota() != null) {
 					relPath = inheritingContainer.getLocalSecurityCallback().getQuota().getPath();
 				} else {
-					relPath = VFSManager.getRelativeItemPath(target, courseEnv.getCourseFolderContainer(), null);
+					relPath = VFSManager.getRelativeItemPath(target, courseContainer, null);
 				}
 				scallback = new FolderNodeCallback(relPath, ne, isOlatAdmin, isGuestOnly, nodefolderSubContext);
 			}
diff --git a/src/main/java/org/olat/course/nodes/iq/QTI21AssessmentRunController.java b/src/main/java/org/olat/course/nodes/iq/QTI21AssessmentRunController.java
index 5607540a4963e40121ad3c7aa0778f2c9e395ef1..471a4f3e81e6715ccf38d370ef2ba9861687449a 100644
--- a/src/main/java/org/olat/course/nodes/iq/QTI21AssessmentRunController.java
+++ b/src/main/java/org/olat/course/nodes/iq/QTI21AssessmentRunController.java
@@ -193,9 +193,9 @@ public class QTI21AssessmentRunController extends BasicController implements Gen
 		// fetch disclaimer file
 		String sDisclaimer = config.getStringValue(IQEditController.CONFIG_KEY_DISCLAIMER);
 		if (sDisclaimer != null) {
-			VFSContainer baseContainer = userCourseEnv.getCourseEnvironment().getCourseFolderContainer();
 			int lastSlash = sDisclaimer.lastIndexOf('/');
 			if (lastSlash != -1) {
+				VFSContainer baseContainer = userCourseEnv.getCourseEnvironment().getCourseFolderContainer();
 				baseContainer = (VFSContainer)baseContainer.resolve(sDisclaimer.substring(0, lastSlash));
 				sDisclaimer = sDisclaimer.substring(lastSlash);
 				// first check if disclaimer exists on filesystem
diff --git a/src/main/java/org/olat/ims/qti/qpool/QTIImportProcessor.java b/src/main/java/org/olat/ims/qti/qpool/QTIImportProcessor.java
index 7ac97cce65bfd7b7ed4cf6a634c1b7b6caa3de15..117f6508bb282d3a4cd32e3f2534940ad366f0ff 100644
--- a/src/main/java/org/olat/ims/qti/qpool/QTIImportProcessor.java
+++ b/src/main/java/org/olat/ims/qti/qpool/QTIImportProcessor.java
@@ -245,9 +245,11 @@ class QTIImportProcessor {
 		}
 		if(metadata != null) {
 			processItemMetadata(poolItem, metadata);
-			createLicense(poolItem, metadata);
 		}
 		questionItemDao.persist(owner, poolItem);
+		if(metadata != null) {
+			createLicense(poolItem, metadata);
+		}
 		return poolItem;
 	}