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; }