/** * OLAT - Online Learning and Training<br> * http://www.olat.org * <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 * <p> * http://www.apache.org/licenses/LICENSE-2.0 * <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> * Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br> * University of Zurich, Switzerland. * <hr> * <a href="http://www.openolat.org"> * OpenOLAT - Online Learning and Training</a><br> * This file has been modified by the OpenOLAT community. Changes are licensed * under the Apache 2.0 license as the original file. * <p> */ package org.olat.core.commons.modules.bc; import java.io.File; import java.util.Enumeration; import java.util.Iterator; import java.util.List; import org.olat.core.CoreSpringFactory; import org.olat.core.commons.controllers.linkchooser.CustomLinkTreeModel; import org.olat.core.commons.modules.bc.commands.CmdCreateFile; import org.olat.core.commons.modules.bc.commands.CmdCreateFolder; import org.olat.core.commons.modules.bc.commands.CmdDelete; import org.olat.core.commons.modules.bc.commands.CmdEditContent; import org.olat.core.commons.modules.bc.commands.CmdEditQuota; import org.olat.core.commons.modules.bc.commands.CmdMoveCopy; import org.olat.core.commons.modules.bc.commands.FolderCommand; import org.olat.core.commons.modules.bc.commands.FolderCommandFactory; import org.olat.core.commons.modules.bc.commands.FolderCommandStatus; import org.olat.core.commons.modules.bc.components.FolderComponent; import org.olat.core.commons.services.notifications.PublisherData; import org.olat.core.commons.services.notifications.SubscriptionContext; import org.olat.core.commons.services.notifications.ui.ContextualSubscriptionController; import org.olat.core.commons.services.webdav.WebDAVModule; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; import org.olat.core.gui.components.download.DisplayOrDownloadComponent; import org.olat.core.gui.components.link.Link; import org.olat.core.gui.components.link.LinkFactory; import org.olat.core.gui.components.velocity.VelocityContainer; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.controller.BasicController; import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController; import org.olat.core.gui.control.generic.dtabs.Activateable2; import org.olat.core.id.OLATResourceable; import org.olat.core.id.context.BusinessControl; import org.olat.core.id.context.BusinessControlFactory; import org.olat.core.id.context.ContextEntry; import org.olat.core.id.context.StateEntry; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; import org.olat.core.logging.activity.CoreLoggingResourceable; import org.olat.core.logging.activity.ILoggingAction; import org.olat.core.logging.activity.ThreadLocalUserActivityLogger; import org.olat.core.util.StringHelper; import org.olat.core.util.resource.OresHelper; import org.olat.core.util.vfs.OlatRelPathImpl; import org.olat.core.util.vfs.Quota; import org.olat.core.util.vfs.VFSContainer; import org.olat.core.util.vfs.VFSContainerMapper; import org.olat.core.util.vfs.VFSItem; import org.olat.core.util.vfs.VFSLeaf; import org.olat.core.util.vfs.VFSManager; import org.olat.core.util.vfs.callbacks.VFSSecurityCallback; import org.olat.core.util.vfs.filters.VFSItemFilter; import org.olat.search.SearchServiceUIFactory; import org.olat.search.SearchServiceUIFactory.DisplayOption; import org.olat.search.ui.SearchInputController; import org.olat.user.UserManager; /** * Description:<br> * The FolderRunController offers a full-fledged folder component that can be * used to navigate and manage a VFS based file/folder structure. There are some * options to configure the webDAV link visibility, file filters and a custom * link tree model that is used in the HTML editor when editing a file. * * @author Felix Jost, Florian Gnägi */ public class FolderRunController extends BasicController implements Activateable2 { private OLog log = Tracing.createLoggerFor(this.getClass()); public static final String ACTION_PRE = ".action"; public static final String FORM_ACTION = "action"; private VelocityContainer folderContainer; private SubscriptionContext subsContext; private ContextualSubscriptionController csController; private SearchInputController searchC; private FolderComponent folderComponent; private Controller folderCommandController; private FolderCommand folderCommand; private CloseableModalController cmc; private Link editQuotaButton; /** * Default Constructor, results in showing users personal folder, used by Spring * * @param ureq * @param wControl */ public FolderRunController(UserRequest ureq, WindowControl wControl) { this(new BriefcaseWebDAVMergeSource(ureq.getIdentity(), UserManager.getInstance().getUserDisplayName(ureq.getIdentity())), true, true, true, ureq, wControl); //set the resource URL to match the indexer ones setResourceURL("[Identity:" + ureq.getIdentity().getKey() + "][userfolder:0]"); } /** * Constructor for a folder controller without filter and custom link model for editor * @param rootContainer * @param displayWebDAVLink * true: show the webDAV link; false: hide the webDAV link * @param ureq * @param wControl */ public FolderRunController(VFSContainer rootContainer, boolean displayWebDAVLink, UserRequest ureq, WindowControl wControl) { this(rootContainer, displayWebDAVLink, false, false, ureq, wControl, null, null); } /** * Constructor for a folder controller without filter and custom link model for editor. * @param rootContainer * @param displayWebDAVLink * true: show the webDAV link; false: hide the webDAV link * @param displaySearch * true: display the search field; false: omit the search field. * Note: for guest users the search is always omitted. * @param canMail * true: allow sending document / link to document via email to other users * false: don't use mail feature * @param ureq * @param wControl */ public FolderRunController(VFSContainer rootContainer, boolean displayWebDAVLink, boolean displaySearch, boolean canMail, UserRequest ureq, WindowControl wControl) { this(rootContainer, displayWebDAVLink, displaySearch, canMail, ureq, wControl, null, null); } /** * Constructor for a folder controller with an optional file filter and an * optional custom link model for editor. Use this one if you don't wan't to * display all files in the file browser or if you want to use a custom link * tree model in the editor. * * @param rootContainer * The folder base. User can not navigate out of this container. * @param displayWebDAVLink * true: show the webDAV link; false: hide the webDAV link * @param displaySearch * true: display the search field; false: omit the search field. * Note: for guest users the search is always omitted. * @param canMail * true: allow sending document / link to document via email to other users * false: don't use mail feature * @param ureq * The user request object * @param wControl * The window control object * @param filter * A file filter or NULL to not use a filter * @param customLinkTreeModel * A custom link tree model used in the HTML editor or NULL to * not use this feature. */ public FolderRunController(VFSContainer rootContainer, boolean displayWebDAVLink, boolean displaySearch, boolean canMail, UserRequest ureq, WindowControl wControl, VFSItemFilter filter, CustomLinkTreeModel customLinkTreeModel) { this(rootContainer, displayWebDAVLink, displaySearch, canMail, ureq, wControl, filter, customLinkTreeModel, null); } /** * Constructor for a folder controller with an optional file filter and an * optional custom link model for editor. Use this one if you don't wan't to * display all files in the file browser or if you want to use a custom link * tree model in the editor. * * @param rootContainer * The folder base. User can not navigate out of this container. * @param displayWebDAVLink * true: show the webDAV link; false: hide the webDAV link * @param displaySearch * true: display the search field; false: omit the search field. * Note: for guest users the search is always omitted. * @param canMail * true: allow sending document / link to document via email to other users * false: don't use mail feature * @param ureq * The user request object * @param wControl * The window control object * @param filter * A file filter or NULL to not use a filter * @param customLinkTreeModel * A custom link tree model used in the HTML editor or NULL to * not use this feature. * @param externContainerForCopy * A container to copy files from */ public FolderRunController(VFSContainer rootContainer, boolean displayWebDAVLink, boolean displaySearch, boolean canMail, UserRequest ureq, WindowControl wControl, VFSItemFilter filter, CustomLinkTreeModel customLinkTreeModel, VFSContainer externContainerForCopy) { this(rootContainer, displayWebDAVLink, displaySearch, canMail, false, ureq, wControl, filter, customLinkTreeModel, externContainerForCopy); } /** * Constructor for a folder controller with an optional file filter and an * optional custom link model for editor. Use this one if you don't wan't to * display all files in the file browser or if you want to use a custom link * tree model in the editor. * * @param rootContainer * The folder base. User can not navigate out of this container. * @param displayWebDAVLink * true: show the webDAV link; false: hide the webDAV link * @param displaySearch * true: display the search field; false: omit the search field. * Note: for guest users the search is always omitted. * @param canMail * true: allow sending document / link to document via email to other users * false: don't use mail feature * @param isCourseFolder * true: the FolderRunController is used for display a courseFolder and show contextHelp * false: the FolderRunController is used for other occasions * @param ureq * The user request object * @param wControl * The window control object * @param filter * A file filter or NULL to not use a filter * @param customLinkTreeModel * A custom link tree model used in the HTML editor or NULL to * not use this feature. * @param externContainerForCopy * A container to copy files from */ public FolderRunController(VFSContainer rootContainer, boolean displayWebDAVLink, boolean displaySearch, boolean canMail, boolean isCourseFolder, UserRequest ureq, WindowControl wControl, VFSItemFilter filter, CustomLinkTreeModel customLinkTreeModel, VFSContainer externContainerForCopy) { super(ureq, wControl); folderContainer = createVelocityContainer("run"); editQuotaButton = LinkFactory.createButtonSmall("editQuota", folderContainer, this); folderContainer.contextPut("showCourseFolderHelp", isCourseFolder); BusinessControl bc = getWindowControl().getBusinessControl(); // --- subscription --- VFSSecurityCallback secCallback = VFSManager.findInheritedSecurityCallback(rootContainer); if (secCallback != null) { subsContext = secCallback.getSubscriptionContext(); // if null, then no subscription is desired if (subsContext != null && (rootContainer instanceof OlatRelPathImpl)) { String businessPath = wControl.getBusinessControl().getAsString(); String data = ((OlatRelPathImpl)rootContainer).getRelPath(); PublisherData pdata = new PublisherData(OresHelper.calculateTypeName(FolderModule.class), data, businessPath); csController = new ContextualSubscriptionController(ureq, getWindowControl(), subsContext, pdata); folderContainer.put("subscription", csController.getInitialComponent()); } } if(!ureq.getUserSession().getRoles().isGuestOnly() && displaySearch) { SearchServiceUIFactory searchUIFactory = (SearchServiceUIFactory)CoreSpringFactory.getBean(SearchServiceUIFactory.class); searchC = searchUIFactory.createInputController(ureq, wControl, DisplayOption.STANDARD, null); listenTo(searchC); // register for auto-dispose folderContainer.put("searchcomp", searchC.getInitialComponent()); } boolean isGuest = ureq.getUserSession().getRoles().isGuestOnly(); folderComponent = new FolderComponent(ureq, "foldercomp", rootContainer, filter, customLinkTreeModel, externContainerForCopy); folderComponent.setCanMail(isGuest ? false : canMail); // guests can never send mail folderComponent.addListener(this); folderContainer.put("foldercomp", folderComponent); if (displayWebDAVLink && !isGuest) { WebDAVModule webDAVModule = CoreSpringFactory.getImpl(WebDAVModule.class); if (webDAVModule.isEnabled() && webDAVModule.isLinkEnabled() && displayWebDAVLink) { folderContainer.contextPut("webdavhttp", FolderManager.getWebDAVHttp()); folderContainer.contextPut("webdavhttps", FolderManager.getWebDAVHttps()); } } // jump to either the forum or the folder if the business-launch-path says so. ContextEntry ce = bc.popLauncherContextEntry(); if ( ce != null ) { // a context path is left for me if (log.isDebug()) log.debug("businesscontrol (for further jumps) would be:"+bc); OLATResourceable ores = ce.getOLATResourceable(); if (log.isDebug()) log.debug("OLATResourceable=" + ores); String typeName = ores.getResourceableTypeName(); // typeName format: 'path=/test1/test2/readme.txt' // First remove prefix 'path=' String path = typeName.substring("path=".length()); if(path.endsWith(":0")) { path = path.substring(0, path.length() - 2); } activatePath(ureq, path); } enableDisableQuota(ureq); putInitialPanel(folderContainer); } /** * Remove the subscription panel but let the subscription context active */ public void disableSubscriptionController() { if(csController != null) { folderContainer.remove(csController.getInitialComponent()); } } public void setResourceURL(String resourceUrl) { if(searchC != null) { searchC.setResourceUrl(resourceUrl); } } @Override public void event(UserRequest ureq, Controller source, Event event) { if (source == folderCommandController) { if (event == FolderCommand.FOLDERCOMMAND_FINISHED) { if (!folderCommand.runsModal() && cmc != null) { cmc.deactivate(); } folderComponent.updateChildren(); // do logging if (source instanceof CmdCreateFile) { ThreadLocalUserActivityLogger .log( FolderLoggingAction.FILE_CREATE, getClass(), CoreLoggingResourceable .wrapBCFile(folderComponent .getCurrentContainerPath() + ((folderComponent.getCurrentContainerPath().length() > 1) ? File.separator:"") + ((CmdCreateFile) source).getFileName())); } else if (source instanceof CmdCreateFolder) { ThreadLocalUserActivityLogger .log( FolderLoggingAction.FOLDER_CREATE, getClass(), CoreLoggingResourceable .wrapBCFile(folderComponent .getCurrentContainerPath() + ((folderComponent.getCurrentContainerPath().length() > 1) ? File.separator:"") + ((CmdCreateFolder) source).getFolderName())); } else if (source instanceof CmdEditContent) { ThreadLocalUserActivityLogger .log( FolderLoggingAction.FILE_EDIT, getClass(), CoreLoggingResourceable .wrapBCFile(folderComponent .getCurrentContainerPath() + ((folderComponent.getCurrentContainerPath().length() > 1) ? File.separator:"") + ((CmdEditContent) source).getFileName())); } else if (source instanceof CmdDelete) { Iterator<String> it = ((CmdDelete) source).getFileSelection().getFiles().iterator(); while(it.hasNext()) { String aFileName = it.next(); ThreadLocalUserActivityLogger .log( FolderLoggingAction.FILE_DELETE, getClass(), CoreLoggingResourceable .wrapBCFile(folderComponent .getCurrentContainerPath() + ((folderComponent.getCurrentContainerPath().length() > 1) ? File.separator: "") + aFileName)); } } else if (source instanceof CmdEditQuota) { ThreadLocalUserActivityLogger.log(FolderLoggingAction.EDIT_QUOTA, getClass()); } else if (source instanceof CmdMoveCopy) { ILoggingAction loggingAction = ((CmdMoveCopy)source).isMoved()?FolderLoggingAction.FILE_MOVED:FolderLoggingAction.FILE_COPIED; String target = ((CmdMoveCopy)source).getTarget(); Iterator<String> it = ((CmdMoveCopy) source).getFileSelection().getFiles().iterator(); while(it.hasNext()) { String aFileName = it.next(); ThreadLocalUserActivityLogger .log( loggingAction, getClass(), CoreLoggingResourceable .wrapBCFile(folderComponent .getCurrentContainerPath() + ((folderComponent.getCurrentContainerPath().length() > 1) ? File.separator: "") + aFileName), CoreLoggingResourceable .wrapBCFile(target)); } } removeAsListenerAndDispose(folderCommandController); folderCommandController = null; removeAsListenerAndDispose(cmc); cmc = null; fireEvent(ureq, event); } else if (event instanceof FolderEvent) { enableDisableQuota(ureq); fireEvent(ureq, event); } } else if (source == cmc) { // close event from modal dialog, cleanup upload controller removeAsListenerAndDispose(folderCommandController); folderCommandController = null; removeAsListenerAndDispose(cmc); cmc = null; } } /** * @seec org.olat removeAsListenerAndDispose(folderCommandController); * folderCommandController = null; removeAsListenerAndDispose(cmc); cmc = * null; .UserRequest, org.olat.core.gui.components.Component, * org.olat.core.gui.control.Event) */ @Override public void event(UserRequest ureq, Component source, Event event) { if (source == folderComponent || source == folderContainer || source == editQuotaButton) { // we catch events from both folderComponent and folderContainer // and process them through the generic folder command implementations String cmd = event.getCommand(); if (cmd.equals(FORM_ACTION)) { cmd = getFormAction(ureq); } folderCommand = FolderCommandFactory.getInstance().getCommand(cmd, ureq, getWindowControl()); if (folderCommand != null) { Controller commandController = folderCommand.execute(folderComponent, ureq, getWindowControl(), getTranslator()); if (commandController != null) { folderCommandController = commandController; // activate command's controller listenTo(folderCommandController); if (!folderCommand.runsModal()) { String title = folderCommand.getModalTitle(); cmc = new CloseableModalController(getWindowControl(), translate("close"), folderCommandController.getInitialComponent(), true, title); cmc.activate(); listenTo(cmc); } } else { // update view after unzip if (cmd.equals(FolderCommandFactory.COMMAND_UNZIP)) { if(folderCommand.getStatus()==FolderCommandStatus.STATUS_INVALID_NAME) { showError("zip.name.notvalid"); } // update view, but not when serving a resource, then nothing has to // be updated here (and specially nothing has to be marked as dirty) else if ( ! cmd.equals(FolderCommandFactory.COMMAND_SERV)) { folderComponent.updateChildren(); } } } if(FolderCommandStatus.STATUS_FAILED == folderCommand.getStatus()) { //failed, reload the children to see if a file has disappeared folderComponent.updateChildren(); } } if(FolderCommandFactory.COMMAND_BROWSE.equals(cmd)) { updatePathResource(ureq); } enableDisableQuota(ureq); } } private void updatePathResource(UserRequest ureq) { final String path = "path=" + folderComponent.getCurrentContainerPath(); OLATResourceable ores = OresHelper.createOLATResourceableTypeWithoutCheck(path); addToHistory(ureq, ores, null); } private void enableDisableQuota(UserRequest ureq) { //prevent a timing condition if the user logout while a thumbnail is generated if (ureq.getUserSession() == null || ureq.getUserSession().getRoles() == null) { return; } Boolean newEditQuota = Boolean.FALSE; if (ureq.getUserSession().getRoles().isOLATAdmin() || ureq.getUserSession().getRoles().isInstitutionalResourceManager()) { // Only sys admins or institutonal resource managers can have the quota button Quota q = VFSManager.isTopLevelQuotaContainer(folderComponent.getCurrentContainer()); newEditQuota = (q == null)? Boolean.FALSE : Boolean.TRUE; } Boolean currentEditQuota = (Boolean) folderContainer.contextGet("editQuota"); // Update the container only if a new value is available or no value is set to // not make the component dirty after asynchronous thumbnail loading if (currentEditQuota == null || !currentEditQuota.equals(newEditQuota)) { folderContainer.contextPut("editQuota", newEditQuota); } } /** * Special treatment of forms with multiple submit actions. * @param ureq * @return The action triggered by the user. */ private String getFormAction(UserRequest ureq) { Enumeration<String> params = ureq.getHttpReq().getParameterNames(); while (params.hasMoreElements()) { String key = params.nextElement(); if (key.startsWith(ACTION_PRE)) { return key.substring(ACTION_PRE.length()); } else if("multi_action_identifier".equals(key)) { String actionKey = ureq.getParameter("multi_action_identifier"); if (actionKey.startsWith(ACTION_PRE)) { return actionKey.substring(ACTION_PRE.length()); } } } return null; } /** * * @see org.olat.core.gui.control.DefaultController#doDispose(boolean) */ @Override protected void doDispose() { // } @Override public void activate(UserRequest ureq, List<ContextEntry> entries, StateEntry state) { if(entries == null || entries.isEmpty()) return; String path = BusinessControlFactory.getInstance().getPath(entries.get(0)); VFSItem vfsItem = folderComponent.getRootContainer().resolve(path); if (vfsItem instanceof VFSContainer) { folderComponent.setCurrentContainerPath(path); updatePathResource(ureq); } else { activatePath(ureq, path); } } public void activatePath(UserRequest ureq, String path) { if (path != null && path.length() > 0) { VFSItem vfsItem = folderComponent.getRootContainer().resolve(path.endsWith("/") ? path.substring(0, path.length()-1) : path); if (vfsItem instanceof VFSLeaf) { // could be a file - create the mapper - otherwise don't create one if it's a directory // Create a mapper to deliver the auto-download of the file. We have to // create a dedicated mapper here // and can not reuse the standard briefcase way of file delivering, some // very old fancy code // Mapper is cleaned up automatically by basic controller String baseUrl = registerMapper(ureq, new VFSContainerMapper(folderComponent.getRootContainer())); // Trigger auto-download DisplayOrDownloadComponent dordc = new DisplayOrDownloadComponent("downloadcomp", baseUrl + path); folderContainer.put("autoDownloadComp", dordc); if (path.lastIndexOf("/") > 0) { String dirPath = path.substring(0, path.lastIndexOf("/")); if (StringHelper.containsNonWhitespace(dirPath)) { folderComponent.setCurrentContainerPath(dirPath); } } } else if(vfsItem instanceof VFSContainer) { if (StringHelper.containsNonWhitespace(path)) { folderComponent.setCurrentContainerPath(path); } } updatePathResource(ureq); } } }