Package org.olat.course.editor

Source Code of org.olat.course.editor.EditorMainController

/**
* 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.
* <p>
*/

package org.olat.course.editor;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;

import org.olat.core.commons.fullWebApp.LayoutMain3ColsController;
import org.olat.core.commons.modules.bc.FolderRunController;
import org.olat.core.dispatcher.mapper.Mapper;
import org.olat.core.dispatcher.mapper.MapperRegistry;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.components.Component;
import org.olat.core.gui.components.htmlheader.HtmlHeaderComponent;
import org.olat.core.gui.components.link.Link;
import org.olat.core.gui.components.link.LinkFactory;
import org.olat.core.gui.components.tabbedpane.TabbedPane;
import org.olat.core.gui.components.tree.MenuTree;
import org.olat.core.gui.components.tree.SelectionTree;
import org.olat.core.gui.components.tree.TreeEvent;
import org.olat.core.gui.components.tree.TreeNode;
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.MainLayoutBasicController;
import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController;
import org.olat.core.gui.control.generic.modal.DialogBoxController;
import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory;
import org.olat.core.gui.control.generic.tabbable.ActivateableTabbableDefaultController;
import org.olat.core.gui.control.generic.tabbable.TabbableController;
import org.olat.core.gui.control.generic.tool.ToolController;
import org.olat.core.gui.control.generic.tool.ToolFactory;
import org.olat.core.gui.control.generic.wizard.Step;
import org.olat.core.gui.control.generic.wizard.StepRunnerCallback;
import org.olat.core.gui.control.generic.wizard.StepsMainRunController;
import org.olat.core.gui.control.generic.wizard.StepsRunContext;
import org.olat.core.gui.media.MediaResource;
import org.olat.core.gui.media.NotFoundMediaResource;
import org.olat.core.id.Identity;
import org.olat.core.id.OLATResourceable;
import org.olat.core.logging.AssertException;
import org.olat.core.logging.OLog;
import org.olat.core.logging.Tracing;
import org.olat.core.logging.activity.ActionType;
import org.olat.core.logging.activity.CourseLoggingAction;
import org.olat.core.logging.activity.ThreadLocalUserActivityLogger;
import org.olat.core.util.Formatter;
import org.olat.core.util.Util;
import org.olat.core.util.coordinate.CoordinatorManager;
import org.olat.core.util.coordinate.LockResult;
import org.olat.core.util.event.GenericEventListener;
import org.olat.core.util.nodes.INode;
import org.olat.core.util.resource.OLATResourceableJustBeforeDeletedEvent;
import org.olat.core.util.tree.TreeVisitor;
import org.olat.core.util.tree.Visitor;
import org.olat.core.util.vfs.NamedContainerImpl;
import org.olat.core.util.vfs.VFSContainer;
import org.olat.core.util.vfs.VFSItem;
import org.olat.core.util.vfs.VFSLeaf;
import org.olat.core.util.vfs.VFSMediaResource;
import org.olat.course.CourseFactory;
import org.olat.course.ICourse;
import org.olat.course.config.CourseConfig;
import org.olat.course.groupsandrights.CourseGroupManager;
import org.olat.course.nodes.CourseNode;
import org.olat.course.nodes.CourseNodeConfiguration;
import org.olat.course.nodes.CourseNodeFactory;
import org.olat.course.run.preview.PreviewConfigController;
import org.olat.course.tree.CourseEditorTreeModel;
import org.olat.course.tree.CourseEditorTreeNode;
import org.olat.testutils.codepoints.server.Codepoint;
import org.olat.util.logging.activity.LoggingResourceable;

/**
* Description:<br>
* The editor controller generates a view which is used to manipulate a course.
* The changes are all applied on the course editor model and not on the course
* runtime structure. Changes must be published explicitely. This mechanism is
* also part of the editor.<br>
* The editor uses the full window (menu - content - tool) and has a close link
* in the toolbox.
* <P>
*
* @author Felix Jost
* @author BPS (<a href="http://www.bps-system.de/">BPS Bildungsportal Sachsen GmbH</a>)
*/
public class EditorMainController extends MainLayoutBasicController implements GenericEventListener {
  private static final String VELOCITY_ROOT = Util.getPackageVelocityRoot(EditorMainController.class);
 
  private static final String TB_ACTION = "o_tb_do_";

  private static final String CMD_COPYNODE = "copyn";
  private static final String CMD_MOVENODE = "moven";
  private static final String CMD_DELNODE = "deln";
  private static final String CMD_CLOSEEDITOR = "cmd.close";
  private static final String CMD_PUBLISH = "pbl";
  private static final String CMD_COURSEFOLDER = "cfd";
  private static final String CMD_COURSEPREVIEW = "cprev";
  private static final String CMD_KEEPCLOSED_ERROR = "keep.closed.error";
  private static final String CMD_KEEPOPEN_ERROR = "keep.open.error";
  private static final String CMD_KEEPCLOSED_WARNING = "keep.closed.warning";
  private static final String CMD_KEEPOPEN_WARNING = "keep.open.warning";

  // NLS support
 
  private static final String NLS_PUBLISHED_NEVER_YET = "published.never.yet";
  private static final String NLS_PUBLISHED_LATEST = "published.latest";
  private static final String NLS_HEADER_TOOLS = "header.tools";
  private static final String NLS_COMMAND_COURSEFOLDER = "command.coursefolder";
  private static final String NLS_COMMAND_COURSEPREVIEW = "command.coursepreview";
  private static final String NLS_COMMAND_PUBLISH = "command.publish";
  private static final String NLS_COMMAND_CLOSEEDITOR = "command.closeeditor";
  private static final String NLS_HEADER_INSERTNODES = "header.insertnodes";
  private static final String NLS_COMMAND_DELETENODE_HEADER = "command.deletenode.header";
  private static final String NLS_COMMAND_DELETENODE = "command.deletenode";
  private static final String NLS_COMMAND_MOVENODE = "command.movenode";
  private static final String NLS_COMMAND_COPYNODE = "command.copynode";
  private static final String NLS_DELETENODE_SUCCESS = "deletenode.success";
  private static final String NLS_START_HELP_WIZARD = "start.help.wizard";
  private static final String NLS_INSERTNODE_TITLE = "insertnode.title";
  private static final String NLS_DELETENODE_ERROR_SELECTFIRST = "deletenode.error.selectfirst";
  private static final String NLS_DELETENODE_ERROR_ROOTNODE = "deletenode.error.rootnode";
  private static final String NLS_MOVECOPYNODE_ERROR_SELECTFIRST = "movecopynode.error.selectfirst";
  private static final String NLS_MOVECOPYNODE_ERROR_ROOTNODE = "movecopynode.error.rootnode";
  private static final String NLS_COURSEFOLDER_NAME = "coursefolder.name";
  private static final String NLS_COURSEFOLDER_CLOSE = "coursefolder.close";
 
  private Boolean errorIsOpen = Boolean.TRUE;
  private Boolean warningIsOpen = Boolean.FALSE;

  private MenuTree menuTree;
  private VelocityContainer main;

  private TabbedPane tabbedNodeConfig;
  private SelectionTree selTree;

  CourseEditorTreeModel cetm;
  private TabbableController nodeEditCntrllr;
  private StepsMainRunController publishStepsController;
  private PreviewConfigController previewController;
  private ToolController toolC;
  private MoveCopySubtreeController moveCopyController;
  private InsertNodeController insertNodeController;
  private DialogBoxController deleteDialogController;   
  private LayoutMain3ColsController columnLayoutCtr;
 
  private Identity identity;
  private LockResult lockEntry;
  private String cssFileRef;
  private Mapper cssUriMapper;
  private MapperRegistry mapreg;
 
  private HtmlHeaderComponent hc;
  EditorUserCourseEnvironmentImpl euce;
 
  private Link undelButton;
  private Link keepClosedErrorButton;
  private Link keepOpenErrorButton;
  private Link keepClosedWarningButton;
  private Link keepOpenWarningButton;
  private CloseableModalController cmc;

  private OLATResourceable ores;
 
  private OLog log = Tracing.createLoggerFor(this.getClass());
  private final static String RELEASE_LOCK_AT_CATCH_EXCEPTION = "Must release course lock since an exception occured in " + EditorMainController.class;

 
  /**
   * Constructor for the course editor controller
   *
   * @param ureq The user request
   * @param wControl The window controller
   * @param course The course
   */
  public EditorMainController(UserRequest ureq, WindowControl wControl, OLATResourceable ores) {
    super(ureq,wControl);
    this.ores = ores;
    this.identity = ureq.getIdentity();

    // OLAT-4955: setting the stickyActionType here passes it on to any controller defined in the scope of the editor,
    //            basically forcing any logging action called within the course editor to be of type 'admin'
    getUserActivityLogger().setStickyActionType(ActionType.admin);
    addLoggingResourceable(LoggingResourceable.wrap(CourseFactory.loadCourse(ores)));
   
    // try to acquire edit lock for this course.     
    lockEntry = CoordinatorManager.getCoordinator().getLocker().acquireLock(ores, ureq.getIdentity(), CourseFactory.COURSE_EDITOR_LOCK);

    try {     
    ThreadLocalUserActivityLogger.log(CourseLoggingAction.COURSE_EDITOR_OPEN, getClass());

    if (lockEntry.isSuccess()) {     
      ICourse course = CourseFactory.openCourseEditSession(ores.getResourceableId());
      main = createVelocityContainer("index");
     
      undelButton = LinkFactory.createButton("undeletenode.button", main, this);
      keepClosedErrorButton = LinkFactory.createCustomLink("keepClosedErrorButton", CMD_KEEPCLOSED_ERROR, "keep.closed", Link.BUTTON_SMALL, main, this);
      keepOpenErrorButton = LinkFactory.createCustomLink("keepOpenErrorButton", CMD_KEEPOPEN_ERROR, "keep.open", Link.BUTTON_SMALL, main, this);
      keepClosedWarningButton = LinkFactory.createCustomLink("keepClosedWarningButton", CMD_KEEPCLOSED_WARNING, "keep.closed", Link.BUTTON_SMALL, main, this);
      keepOpenWarningButton = LinkFactory.createCustomLink("keepOpenWarningButton", CMD_KEEPOPEN_WARNING, "keep.open", Link.BUTTON_SMALL, main, this);
     
      // set the custom course css
      enableCustomCss(ureq);

      menuTree = new MenuTree("luTree");
           

      /*
       * create editor user course environment for enhanced syntax/semantic
       * checks. Initialize it with the current course node id, which is not set
       * yet. Furthermore the course is refreshed, e.g. as it get's loaded by
       * XSTREAM constructors are not called, but transient data must be
       * caculated and initialized
       */
      cetm = CourseFactory.getCourseEditSession(ores.getResourceableId()).getEditorTreeModel();
      CourseEditorEnv cev = new CourseEditorEnvImpl(cetm, course.getCourseEnvironment().getCourseGroupManager(), ureq.getLocale());
      euce = new EditorUserCourseEnvironmentImpl(cev);
      euce.getCourseEditorEnv().setCurrentCourseNodeId(null);
      /*
       * validate course and update course status
       */
      euce.getCourseEditorEnv().validateCourse();
      StatusDescription[] courseStatus = euce.getCourseEditorEnv().getCourseStatus();
      updateCourseStatusMessages(ureq.getLocale(), courseStatus);

      long lpTimeStamp = cetm.getLatestPublishTimestamp();
      if (lpTimeStamp == -1) {       
        showInfo(NLS_PUBLISHED_NEVER_YET);
      } else { // course has been published before
        Date d = new Date(lpTimeStamp);
        getWindowControl().setInfo(translate(NLS_PUBLISHED_LATEST, Formatter.getInstance(ureq.getLocale()).formatDateAndTime(d)));
      }
      menuTree.setTreeModel(cetm);
      menuTree.addListener(this);

      selTree = new SelectionTree("selection", getTranslator());
      selTree.setTreeModel(cetm);
      selTree.setActionCommand("processpublish");
      selTree.setFormButtonKey("publizieren");
      selTree.addListener(this);

      tabbedNodeConfig = new TabbedPane("tabbedNodeConfig", ureq.getLocale());
      main.put(tabbedNodeConfig.getComponentName(), tabbedNodeConfig);

      toolC = ToolFactory.createToolController(getWindowControl());
      listenTo(toolC);
      toolC.addHeader(translate(NLS_HEADER_TOOLS));
      toolC.addLink(CMD_COURSEFOLDER, translate(NLS_COMMAND_COURSEFOLDER), CMD_COURSEFOLDER, "o_toolbox_coursefolder");
      toolC.addLink(CMD_COURSEPREVIEW, translate(NLS_COMMAND_COURSEPREVIEW), CMD_COURSEPREVIEW, "b_toolbox_preview" );
      toolC.addLink(CMD_PUBLISH, translate(NLS_COMMAND_PUBLISH), CMD_PUBLISH,"b_toolbox_publish" );
      toolC.addLink(CMD_CLOSEEDITOR, translate(NLS_COMMAND_CLOSEEDITOR), null, "b_toolbox_close");

      toolC.addHeader(translate(NLS_HEADER_INSERTNODES));
      CourseNodeFactory cnf = CourseNodeFactory.getInstance();
      for (Iterator<String> iter = cnf.getRegisteredCourseNodeAliases().iterator(); iter.hasNext();) {
        String courseNodeAlias = iter.next();
        CourseNodeConfiguration cnConfig = cnf.getCourseNodeConfiguration(courseNodeAlias);
        toolC.addLink(TB_ACTION + courseNodeAlias, cnConfig.getLinkText(ureq.getLocale()), courseNodeAlias, cnConfig.getIconCSSClass());
      }

      toolC.addHeader(translate(NLS_COMMAND_DELETENODE_HEADER));
      toolC.addLink(CMD_DELNODE, translate(NLS_COMMAND_DELETENODE), CMD_DELNODE, "b_toolbox_delete");
      toolC.addLink(CMD_MOVENODE, translate(NLS_COMMAND_MOVENODE), CMD_MOVENODE, "b_toolbox_move");
      toolC.addLink(CMD_COPYNODE, translate(NLS_COMMAND_COPYNODE), CMD_COPYNODE, "b_toolbox_copy");
     
      columnLayoutCtr = new LayoutMain3ColsController(ureq, getWindowControl(), menuTree, toolC.getInitialComponent(), main, "course" + course.getResourceableId());     
      columnLayoutCtr.addCssClassToMain("o_editor");
      listenTo(columnLayoutCtr);
      putInitialPanel(columnLayoutCtr.getInitialComponent());

      // add as listener to course so we are being notified about course events:
      // - deleted events
      CoordinatorManager.getCoordinator().getEventBus().registerFor(this, ureq.getIdentity(), course);
      // activate course root node
      String rootNodeIdent = cetm.getRootNode().getIdent();
      menuTree.setSelectedNodeId(rootNodeIdent);
      updateViewForSelectedNodeId(ureq, rootNodeIdent);
    }
    } catch (RuntimeException e) {
      log.warn(RELEASE_LOCK_AT_CATCH_EXCEPTION+" [in <init>]", e);     
      this.dispose();
      throw e;
    }
  }

  /**
   * @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest,
   *      org.olat.core.gui.components.Component, org.olat.core.gui.control.Event)
   */
  public void event(UserRequest ureq, Component source, Event event) {
    try {
    ICourse course = CourseFactory.getCourseEditSession(ores.getResourceableId());
   
    if (source == menuTree) {
      if (event.getCommand().equals(MenuTree.COMMAND_TREENODE_CLICKED)) {
        // goto node in edit mode
        TreeEvent te = (TreeEvent) event;
        String nodeId = te.getNodeId();
        updateViewForSelectedNodeId(ureq, nodeId);       
      }
    } else if (source == main) {
      if (event.getCommand().startsWith(NLS_START_HELP_WIZARD)) {
        String findThis = event.getCommand().substring(NLS_START_HELP_WIZARD.length());
        StatusDescription[] courseStatus = euce.getCourseEditorEnv().getCourseStatus();
        for (int i = 0; i < courseStatus.length; i++) {
          String key = courseStatus[i].getDescriptionForUnit() + "." + courseStatus[i].getShortDescriptionKey();
          if (key.equals(findThis)) {
            menuTree.setSelectedNodeId(courseStatus[i].getDescriptionForUnit());
            euce.getCourseEditorEnv().setCurrentCourseNodeId(courseStatus[i].getDescriptionForUnit());
            jumpToNodeEditor(courseStatus[i].getActivateableViewIdentifier(), ureq, cetm.getCourseNode(courseStatus[i]
                .getDescriptionForUnit()), course.getCourseEnvironment().getCourseGroupManager());
            break;
          }
        }
        euce.getCourseEditorEnv().validateCourse();
        courseStatus = euce.getCourseEditorEnv().getCourseStatus();
        updateCourseStatusMessages(ureq.getLocale(), courseStatus);
      }
    } else if (source == keepClosedErrorButton){
      errorIsOpen = Boolean.FALSE;
      main.contextPut("errorIsOpen", errorIsOpen);
    } else if (source == keepOpenErrorButton){
      errorIsOpen = Boolean.TRUE;
      main.contextPut("errorIsOpen", errorIsOpen);
    } else if (source == keepClosedWarningButton){
      warningIsOpen = Boolean.FALSE;
      main.contextPut("warningIsOpen", warningIsOpen);
    } else if (source == keepOpenWarningButton){
      warningIsOpen = Boolean.TRUE;
      main.contextPut("warningIsOpen", warningIsOpen);
    } else if (source == undelButton){
      String ident = menuTree.getSelectedNode().getIdent();
      CourseEditorTreeNode activeNode = (CourseEditorTreeNode) cetm.getNodeById(ident);
      euce.getCourseEditorEnv().setCurrentCourseNodeId(activeNode.getIdent());
     
      CourseFactory.saveCourseEditorTreeModel(course.getResourceableId());
      cetm.markUnDeleted(activeNode);
      menuTree.setDirty(true);
      // show edit panels again
      initNodeEditor(ureq, activeNode.getCourseNode());
      tabbedNodeConfig.setVisible(true);
      toolC.setEnabled(CMD_DELNODE, true);
      toolC.setEnabled(CMD_MOVENODE, true);
      toolC.setEnabled(CMD_COPYNODE, true);
      main.setPage(VELOCITY_ROOT + "/index.html");
      /*
       * validate course and update course status
       */
      euce.getCourseEditorEnv().validateCourse();
      StatusDescription[] courseStatus = euce.getCourseEditorEnv().getCourseStatus();
      updateCourseStatusMessages(ureq.getLocale(), courseStatus);
    }
    } catch (RuntimeException e) {
      log.warn(RELEASE_LOCK_AT_CATCH_EXCEPTION+" [in event(UserRequest,Component,Event)]", e);     
      this.dispose();
      throw e;
    }
  }

  /**
   * helper to update menu tree, content area, tools to a selected tree node
   * @param ureq
   * @param nodeId
   */
  private void updateViewForSelectedNodeId(UserRequest ureq, String nodeId) {
   
    CourseEditorTreeNode cetn = (CourseEditorTreeNode) cetm.getNodeById(nodeId);
    // udpate the current node in the course editor environment
    euce.getCourseEditorEnv().setCurrentCourseNodeId(nodeId);
    // Start necessary controller for selected node

    if (cetn.isDeleted()) {
      tabbedNodeConfig.setVisible(false);
      toolC.setEnabled(CMD_DELNODE, false);
      toolC.setEnabled(CMD_MOVENODE, false);
      toolC.setEnabled(CMD_COPYNODE, false);
      if (((CourseEditorTreeNode) cetn.getParent()).isDeleted()) main.setPage(VELOCITY_ROOT + "/deletednode.html");
      else main.setPage(VELOCITY_ROOT + "/undeletenode.html");
    } else {
      tabbedNodeConfig.setVisible(true);     
      toolC.setEnabled(CMD_DELNODE, true);
      toolC.setEnabled(CMD_MOVENODE, true);
      toolC.setEnabled(CMD_COPYNODE, true);
      initNodeEditor(ureq, cetn.getCourseNode());
      main.setPage(VELOCITY_ROOT + "/index.html");         
    }
  }
 
  /**
   * Initializes the node edit tabbed pane and its controller for this
   * particular node
   *
   * @param ureq
   * @param chosenNode
   * @param groupMgr
   */
  private void initNodeEditor(UserRequest ureq, CourseNode chosenNode) {
    ICourse course = CourseFactory.getCourseEditSession(ores.getResourceableId());
    tabbedNodeConfig.removeAll();
    // dispose old one, if there was one
    removeAsListenerAndDispose(nodeEditCntrllr);
    nodeEditCntrllr = chosenNode.createEditController(ureq, getWindowControl(), course, euce);
    listenTo(nodeEditCntrllr);
    nodeEditCntrllr.addTabs(tabbedNodeConfig);

    main.contextPut("courseNode", chosenNode);
    main.contextPut("courseNodeCss", CourseNodeFactory.getInstance().getCourseNodeConfiguration(chosenNode.getType()).getIconCSSClass());

  }

  /**
   * Initializes the node edit tabbed pane and its controller for this
   * particular node
   *
   * @param ureq
   * @param chosenNode
   * @param groupMgr
   */
  private void jumpToNodeEditor(String activatorIdent, UserRequest ureq, CourseNode chosenNode, CourseGroupManager groupMgr) {
    initNodeEditor(ureq, chosenNode);
    if (nodeEditCntrllr instanceof ActivateableTabbableDefaultController) {
      ((ActivateableTabbableDefaultController) nodeEditCntrllr).activate(ureq, activatorIdent);
    }
  }

  /**
   * @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest,
   *      org.olat.core.gui.control.Controller, org.olat.core.gui.control.Event)
   */
  public void event(UserRequest ureq, Controller source, Event event) {
    try {
    ICourse course = CourseFactory.getCourseEditSession(ores.getResourceableId());
   
    if (source == toolC) {
      if (event.getCommand().startsWith(TB_ACTION)) {
        String cnAlias = event.getCommand().substring(TB_ACTION.length());
        if (cnAlias == null) throw new AssertException("Received event from ButtonController which is not registered with the toolbox.");
       
        Codepoint.codepoint(EditorMainController.class, "startInsertNode");
        insertNodeController = new InsertNodeController(ureq, getWindowControl(), course, cnAlias);       
        listenTo(insertNodeController);
        cmc = new CloseableModalController(getWindowControl(), translate("close"), insertNodeController.getInitialComponent(), true, translate(NLS_INSERTNODE_TITLE));
        cmc.activate();
      } else if (event.getCommand().equals(CMD_DELNODE)) {
        TreeNode tn = menuTree.getSelectedNode();
        if (tn == null) {
          showError(NLS_DELETENODE_ERROR_SELECTFIRST);
          return;
        }
        if (tn.getParent() == null) {
          showError(NLS_DELETENODE_ERROR_ROOTNODE);
          return;
        }
        // deletion is possible, start asking if really to delete.
        tabbedNodeConfig.setVisible(false);
        deleteDialogController = activateYesNoDialog(ureq, translate("deletenode.header", tn.getTitle()), translate("deletenode.confirm"), deleteDialogController);
               
      } else if (event.getCommand().equals(CMD_MOVENODE) || event.getCommand().equals(CMD_COPYNODE)) {
        TreeNode tn = menuTree.getSelectedNode();
        if (tn == null) {
          showError(NLS_MOVECOPYNODE_ERROR_SELECTFIRST);
          return;
        }
        if (tn.getParent() == null) {
          showError(NLS_MOVECOPYNODE_ERROR_ROOTNODE);
          return;
        }
       
        CourseEditorTreeNode cetn = cetm.getCourseEditorNodeById(tn.getIdent());
        moveCopyController = new MoveCopySubtreeController(ureq, getWindowControl(), course, cetn, event.getCommand().equals(CMD_COPYNODE));       
        this.listenTo(moveCopyController);
        cmc = new CloseableModalController(getWindowControl(), translate("close"), moveCopyController.getInitialComponent(), true, translate(NLS_INSERTNODE_TITLE));
        cmc.activate();
      }
     
      else if (event.getCommand().equals(CMD_CLOSEEDITOR)) {
        doReleaseEditLock();
        fireEvent(ureq, Event.DONE_EVENT);
      } else if (event.getCommand().equals(CMD_PUBLISH)) {
        /*
         * start follwoing steps -> cancel wizardf does not touch data
         * (M) Mandatory (O) Optional
         * - (M)Step 00  -> show selection tree to choose changed nodes to be published
         * ...........-> calculate errors & warnings
         * ...........(next|finish) available if no errors or nothing to publish
         * - (O)Step 00A -> review errors & warnings
         * ...........(previous|next|finish) available
         * - (O)Step 00B -> review publish changes that will happen
         * ...........(previous|next|finish) available
         * - (O)Step 01  -> change general access to course
         * ...........(previous|finish) available
         * - FinishCallback -> apply course nodes change set
         * .................-> apply general access changes.
         */
       
        Step start  = new PublishStep00(ureq, cetm, course);
       
        /*
         * callback executed in case wizard is finished.
         */
        StepRunnerCallback finish = new StepRunnerCallback(){
          @SuppressWarnings("unchecked")
          public Step execute(UserRequest ureq1, WindowControl wControl1, StepsRunContext runContext) {
            /*
             * all information to do now is within the runContext saved
             */
            boolean hasChanges = false;

            if (runContext.containsKey("validPublish") && ((Boolean) runContext.get("validPublish")).booleanValue()) {

              Set<String> selectedNodeIds = (Set<String>) runContext.get("publishSetCreatedFor");
              hasChanges = (selectedNodeIds != null) && (selectedNodeIds.size() > 0);
              if (hasChanges) {
                PublishProcess publishManager = (PublishProcess) runContext.get("publishProcess");
                publishManager.applyPublishSet(ureq1.getIdentity(), ureq1.getLocale());
              }
            }
            if (runContext.containsKey("changedaccess")) {
              // there were changes made to the general course access
              String newAccessStr = (String) runContext.get("changedaccess");
              int newAccess = Integer.valueOf(newAccessStr);
              PublishProcess publishManager = (PublishProcess) runContext.get("publishProcess");
              // fires an EntryChangedEvent for repository entry notifying
              // about modification.
              publishManager.changeGeneralAccess(ureq1, newAccess);
              hasChanges = true;
            }

            // signal correct completion and tell if changes were made or not.
            return hasChanges ? StepsMainRunController.DONE_MODIFIED : StepsMainRunController.DONE_UNCHANGED;
          }
        };

        publishStepsController = new StepsMainRunController(ureq, getWindowControl(), start, finish, null, translate("publish.wizard.title") );
        listenTo(publishStepsController);
        getWindowControl().pushAsModalDialog(publishStepsController.getInitialComponent());
         
      } else if (event.getCommand().equals(CMD_COURSEPREVIEW)) {
        previewController = new PreviewConfigController(ureq, getWindowControl(), course);
        listenTo(previewController);   
        previewController.activate();
       
      } else if (event.getCommand().equals(CMD_COURSEFOLDER)) {
        // folder for course
        VFSContainer namedCourseFolder = new NamedContainerImpl(translate(NLS_COURSEFOLDER_NAME), course.getCourseFolderContainer());
        FolderRunController bcrun = new FolderRunController(namedCourseFolder, true, ureq, getWindowControl());
        bcrun.addLoggingResourceable(LoggingResourceable.wrap(course));
        Component folderComponent = bcrun.getInitialComponent();
        CloseableModalController clc = new CloseableModalController(getWindowControl(), translate(NLS_COURSEFOLDER_CLOSE),
            folderComponent);
        clc.activate();
       
      }
    } else if (source == nodeEditCntrllr) {
      // event from the tabbed pane (any tab)
      if (event == NodeEditController.NODECONFIG_CHANGED_EVENT) {
        // if the user changed the name of the node, we need to update the tree also.
        // the event is too generic to find out what happened -> update tree in all cases (applies to ajax mode only)
        menuTree.setDirty(true);
       
        cetm.nodeConfigChanged(menuTree.getSelectedNode());       
        CourseFactory.saveCourseEditorTreeModel(course.getResourceableId());
        euce.getCourseEditorEnv().validateCourse();
        StatusDescription[] courseStatus = euce.getCourseEditorEnv().getCourseStatus();
        updateCourseStatusMessages(ureq.getLocale(), courseStatus);
      }
    } else if (source == publishStepsController) {
      //if (event == Event.DONE_EVENT) {
        getWindowControl().pop();
        removeAsListenerAndDispose(publishStepsController);
        publishStepsController = null;
        // reset to root node... may have published a deleted node -> this
        // resets the view
        cetm = course.getEditorTreeModel();
        menuTree.setTreeModel(cetm);
        String rootNodeIdent = menuTree.getTreeModel().getRootNode().getIdent();
        menuTree.setSelectedNodeId(rootNodeIdent);
        updateViewForSelectedNodeId(ureq, rootNodeIdent);
        if(event == Event.CHANGED_EVENT){         
          showInfo("pbl.success", null);
          // do logging
          ThreadLocalUserActivityLogger.log(CourseLoggingAction.COURSE_EDITOR_PUBLISHED, getClass());
        }//else Event.DONE -> nothing changed / else Event.CANCELLED -> cancelled wizard
       
    } else if (source == previewController) {
      if (event == Event.DONE_EVENT) {
        // no need to deactivate preview controller, already done internally
        removeAsListenerAndDispose(previewController);
        previewController = null;
      }
    } else if (source == moveCopyController) { 
      cmc.deactivate();
      if (event == Event.DONE_EVENT) {         
        menuTree.setDirty(true); // setDirty when moving
        // Repositioning to move/copy course node
        String nodeId = moveCopyController.getCopyNodeId();       
        if (nodeId != null) {
          menuTree.setSelectedNodeId(nodeId);
          euce.getCourseEditorEnv().setCurrentCourseNodeId(nodeId);         
          CourseNode copyNode = cetm.getCourseNode(nodeId);
          initNodeEditor(ureq, copyNode);
        }                       
        euce.getCourseEditorEnv().validateCourse();
        StatusDescription[] courseStatus = euce.getCourseEditorEnv().getCourseStatus();
        updateCourseStatusMessages(ureq.getLocale(), courseStatus);
       
      } else if (event == Event.FAILED_EVENT) {       
        getWindowControl().setError("Error in copy of subtree.");       
      } else if (event == Event.CANCELLED_EVENT) {
        // user canceled           
      }
     
    } else if (source == insertNodeController) {          
      cmc.deactivate();
      if (event == Event.DONE_EVENT) {
        // Activate new node in menu and create necessary edit controllers
        // necessary if previous action was a delete node action       
        tabbedNodeConfig.setVisible(true);
        main.setPage(VELOCITY_ROOT + "/index.html");       
        CourseNode newNode = insertNodeController.getInsertedNode();       
        menuTree.setSelectedNodeId(newNode.getIdent());
        // update the current node in the editor course environment
        euce.getCourseEditorEnv().setCurrentCourseNodeId(newNode.getIdent());
        euce.getCourseEditorEnv().validateCourse();
        StatusDescription[] courseStatus = euce.getCourseEditorEnv().getCourseStatus();
        updateCourseStatusMessages(ureq.getLocale(), courseStatus);         
        initNodeEditor(ureq, newNode);
      }
      // in all cases:
      removeAsListenerAndDispose(insertNodeController);
      insertNodeController = null;
    } else if (source == deleteDialogController){
      removeAsListenerAndDispose(deleteDialogController);
      deleteDialogController = null;
      if (DialogBoxUIFactory.isYesEvent(event)){
        // delete confirmed
        String ident = menuTree.getSelectedNode().getIdent();
        // udpate the current node in the course editor environment
        euce.getCourseEditorEnv().setCurrentCourseNodeId(ident);
        CourseNode activeNode = cetm.getCourseNode(ident);

        cetm.markDeleted(activeNode);
        menuTree.setDirty(true);
     
        CourseFactory.saveCourseEditorTreeModel(course.getResourceableId());
        tabbedNodeConfig.removeAll();
        tabbedNodeConfig.setVisible(false);
        toolC.setEnabled(CMD_DELNODE, false);
        toolC.setEnabled(CMD_MOVENODE, false);
        toolC.setEnabled(CMD_COPYNODE, false);
        main.setPage(VELOCITY_ROOT + "/undeletenode.html"); // offer undelete
        showInfo(NLS_DELETENODE_SUCCESS);
        /*
         * validate course and update course status
         */
        euce.getCourseEditorEnv().validateCourse();
        StatusDescription[] courseStatus = euce.getCourseEditorEnv().getCourseStatus();
        updateCourseStatusMessages(ureq.getLocale(), courseStatus);

        ThreadLocalUserActivityLogger.log(CourseLoggingAction.COURSE_EDITOR_NODE_DELETED, getClass(),
            LoggingResourceable.wrap(activeNode));
     
      } else {
        tabbedNodeConfig.setVisible(true);
      }
    }
    } catch (RuntimeException e) {
      log.warn(RELEASE_LOCK_AT_CATCH_EXCEPTION+" [in event(UserRequest,Controller,Event)]", e);     
      this.dispose();
      throw e;
    }
  }

  /*
   * FIXME:pb:b never used...
   */
  private Map<String, List<String>> checkReferencesFor(TreeNode tn) {
    final CourseEditorTreeModel cetm = CourseFactory.getCourseEditSession(ores.getResourceableId()).getEditorTreeModel();
     //create a list of all nodes in the selected subtree
    final Set<String> allSubTreeids = new HashSet<String>();
    TreeVisitor tv = new TreeVisitor(new Visitor() {
      public void visit(INode node) {
        allSubTreeids.add(node.getIdent());
      }
    }, tn, true);
    tv.visitAll();
     //find all references pointing from outside the subtree into the subtree or
     //on the subtree root node.
    final Map<String, List<String>> allRefs = new HashMap<String, List<String>>();
    tv = new TreeVisitor(new Visitor() {
      public void visit(INode node) {
        List referencingNodes = euce.getCourseEditorEnv().getReferencingNodeIdsFor(node.getIdent());
        // subtract the inner nodes. This allows to delete a whole subtree if
        // only references residing completly inside the subtree are active.
        referencingNodes.removeAll(allSubTreeids);
        if (referencingNodes.size() > 0) {
          List<String> nodeNames = new ArrayList<String>();
          for (Iterator iter = referencingNodes.iterator(); iter.hasNext();) {
            String nodeId = (String) iter.next();
            CourseNode cn = cetm.getCourseNode(nodeId);
            nodeNames.add(cn.getShortTitle());
          }
          allRefs.put(node.getIdent(), nodeNames);
        }
      }
    }, tn, true);
    // traverse all nodes from the deletion startpoint
    tv.visitAll();
    // allRefs contains now all references, or zero if ready for delete.
    return allRefs;
  }

  /**
   * @param ureq
   * @param courseStatus
   */
  private void updateCourseStatusMessages(Locale locale, StatusDescription[] courseStatus) {

    /*
     * clean up velocity context
     */
    main.contextRemove("hasWarnings");
    main.contextRemove("warningIsForNode");
    main.contextRemove("warningMessage");
    main.contextRemove("warningHelpWizardLink");
    main.contextRemove("warningsCount");
    main.contextRemove("warningIsOpen");
    main.contextRemove("hasErrors");
    main.contextRemove("errorIsForNode");
    main.contextRemove("errorMessage");
    main.contextRemove("errorHelpWizardLink");
    main.contextRemove("errorsCount");
    main.contextRemove("errorIsOpen");
    if (courseStatus == null || courseStatus.length == 0) {
      main.contextPut("hasCourseStatus", Boolean.FALSE);
      main.contextPut("errorIsOpen", Boolean.FALSE);
      return;
    }
    //
    List<String> errorIsForNode = new ArrayList<String>();
    List<String> errorMessage = new ArrayList<String>();
    List<String> errorHelpWizardLink = new ArrayList<String>();
    List<String> warningIsForNode = new ArrayList<String>();
    List<String> warningMessage = new ArrayList<String>();
    List<String> warningHelpWizardLink = new ArrayList<String>();
    //
    int errCnt = 0;
    int warCnt = 0;
    String helpWizardCmd;
    for (int i = 0; i < courseStatus.length; i++) {
      StatusDescription description = courseStatus[i];
      String nodeId = courseStatus[i].getDescriptionForUnit();
      String nodeName = cetm.getCourseNode(nodeId).getShortName();
      // prepare wizard link
      helpWizardCmd = courseStatus[i].getActivateableViewIdentifier();
      if (helpWizardCmd != null) {
        helpWizardCmd = "start.help.wizard" + courseStatus[i].getDescriptionForUnit() + "." + courseStatus[i].getShortDescriptionKey();
      } else {
        helpWizardCmd = "NONE";
      }
      if (description.isError()) {
        errCnt++;
        errorIsForNode.add(nodeName);
        errorMessage.add(description.getShortDescription(locale));
        errorHelpWizardLink.add(helpWizardCmd);
      } else if (description.isWarning()) {
        warCnt++;
        warningIsForNode.add(nodeName);
        warningMessage.add(description.getShortDescription(locale));
        warningHelpWizardLink.add(helpWizardCmd);
      }
    }
    /*
     *
     */
    if (errCnt > 0 || warCnt > 0) {
      if (warCnt > 0) {
        main.contextPut("hasWarnings", Boolean.TRUE);
        main.contextPut("warningIsForNode", warningIsForNode);
        main.contextPut("warningMessage", warningMessage);
        main.contextPut("warningHelpWizardLink", warningHelpWizardLink);
        main.contextPut("warningsCount", new String[] { Integer.toString(warCnt) });
        main.contextPut("warningIsOpen", warningIsOpen);
      }
      if (errCnt > 0) {
        main.contextPut("hasErrors", Boolean.TRUE);
        main.contextPut("errorIsForNode", errorIsForNode);
        main.contextPut("errorMessage", errorMessage);
        main.contextPut("errorHelpWizardLink", errorHelpWizardLink);
        main.contextPut("errorsCount", new String[] { Integer.toString(errCnt) });
        main.contextPut("errorIsOpen", errorIsOpen);
      }
    } else {
      main.contextPut("hasWarnings", Boolean.FALSE);
      main.contextPut("hasErrors", Boolean.FALSE);
    }
  }

  /**
   * @return true if lock on this course has been acquired, flase otherwhise
   */
  public LockResult getLockEntry() {
    return lockEntry;
  }

  protected void doDispose() {
    ICourse course = CourseFactory.loadCourse(ores.getResourceableId());
    CoordinatorManager.getCoordinator().getEventBus().deregisterFor(this, course);
    // those controllers are disposed by BasicController:
    nodeEditCntrllr = null;
    publishStepsController = null;
    deleteDialogController = null;
    cmc = null;
    moveCopyController = null;
    insertNodeController = null;
    previewController = null;
    toolC = null;
    columnLayoutCtr = null;
    insertNodeController = null;
    moveCopyController = null;
   
    doReleaseEditLock();
    ThreadLocalUserActivityLogger.log(CourseLoggingAction.COURSE_EDITOR_CLOSE, getClass());
    if (mapreg !=null && cssUriMapper != null) {
      mapreg.deregister(cssUriMapper);
    }
  }
 
  private void doReleaseEditLock() {
    if (lockEntry!=null && lockEntry.isSuccess()) {
      CoordinatorManager.getCoordinator().getLocker().releaseLock(lockEntry);
      CourseFactory.fireModifyCourseEvent(ores.getResourceableId());
      lockEntry = null;
    }
  }

  /**
   * @see org.olat.core.util.event.GenericEventListener#event(org.olat.core.gui.control.Event)
   */
  public void event(Event event) {
    try {
    if (event instanceof OLATResourceableJustBeforeDeletedEvent) {
      OLATResourceableJustBeforeDeletedEvent ojde = (OLATResourceableJustBeforeDeletedEvent) event;
      // make sure it is our course (actually not needed till now, since we
      // registered only to one event, but good style.
      if (ojde.targetEquals(ores, true)) {
        // true = throw an exception if the target does not match ores
        dispose();
      }
    }
    } catch (RuntimeException e) {
      log.warn(RELEASE_LOCK_AT_CATCH_EXCEPTION+" [in event(Event)]", e);     
      this.dispose();
      throw e;
    }
  }

  /**
   * @param ureq
   * @param course
   */
  private void enableCustomCss(UserRequest ureq) {
    /*
     * add also the choosen courselayout css if any
     */
    final ICourse course = CourseFactory.getCourseEditSession(ores.getResourceableId());
    CourseConfig cc = course.getCourseEnvironment().getCourseConfig();
    if (cc.hasCustomCourseCSS()) {
      cssFileRef = cc.getCssLayoutRef();
      mapreg = MapperRegistry.getInstanceFor(ureq.getUserSession());
      if (cssUriMapper != null) {
        // deregister old mapper
        mapreg.deregister(cssUriMapper);
      }
      cssUriMapper = new Mapper() {
        final VFSContainer courseFolder = course.getCourseFolderContainer();

        @SuppressWarnings("unused")
        public MediaResource handle(String relPath, HttpServletRequest request) {
          VFSItem vfsItem = courseFolder.resolve(relPath);
          MediaResource mr;
          if (vfsItem == null || !(vfsItem instanceof VFSLeaf)) mr = new NotFoundMediaResource(relPath);
          else mr = new VFSMediaResource((VFSLeaf) vfsItem);
          return mr;
        }
      };
      String uri = mapreg.register(cssUriMapper);
      final String fulluri = uri + cssFileRef; // the stylesheet's relative
      // path
      hc = new HtmlHeaderComponent("custom-css", null, "<link rel=\"StyleSheet\" href=\"" + fulluri
          + "\" type=\"text/css\" media=\"screen\"/>");
      main.put("css-inset2", hc);
    }
  }

}
TOP

Related Classes of org.olat.course.editor.EditorMainController

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.