Skip to content
Snippets Groups Projects
RunMainController.java 33 KiB
Newer Older
Alan Moran's avatar
Alan Moran committed
/**
* 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.
srosse's avatar
srosse committed
* <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.
*/
Alan Moran's avatar
Alan Moran committed

package org.olat.course.run;

import java.util.ArrayList;
Alan Moran's avatar
Alan Moran committed
import java.util.List;
import org.olat.core.CoreSpringFactory;
Alan Moran's avatar
Alan Moran committed
import org.olat.core.commons.fullWebApp.LayoutMain3ColsController;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.components.Component;
import org.olat.core.gui.components.htmlsite.OlatCmdEvent;
import org.olat.core.gui.components.link.Link;
import org.olat.core.gui.components.link.LinkFactory;
import org.olat.core.gui.components.panel.Panel;
import org.olat.core.gui.components.stack.TooledStackedPanel;
import org.olat.core.gui.components.stack.TooledStackedPanel.Align;
Alan Moran's avatar
Alan Moran committed
import org.olat.core.gui.components.tree.GenericTreeModel;
import org.olat.core.gui.components.tree.MenuTree;
import org.olat.core.gui.components.tree.TreeEvent;
import org.olat.core.gui.components.tree.TreeModel;
import org.olat.core.gui.components.tree.TreeNode;
import org.olat.core.gui.components.velocity.VelocityContainer;
import org.olat.core.gui.control.ConfigurationChangedListener;
Alan Moran's avatar
Alan Moran committed
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.dtabs.Activateable2;
Alan Moran's avatar
Alan Moran committed
import org.olat.core.gui.control.generic.messages.MessageController;
import org.olat.core.gui.control.generic.messages.MessageUIFactory;
import org.olat.core.gui.control.generic.textmarker.GlossaryMarkupItemController;
import org.olat.core.gui.control.generic.title.TitledWrapperController;
Alan Moran's avatar
Alan Moran committed
import org.olat.core.id.Identity;
import org.olat.core.id.OLATResourceable;
import org.olat.core.id.context.ContextEntry;
import org.olat.core.id.context.StateEntry;
Alan Moran's avatar
Alan Moran committed
import org.olat.core.logging.activity.CourseLoggingAction;
import org.olat.core.logging.activity.ThreadLocalUserActivityLogger;
srosse's avatar
srosse committed
import org.olat.core.util.Util;
Alan Moran's avatar
Alan Moran committed
import org.olat.core.util.coordinate.CoordinatorManager;
import org.olat.core.util.event.GenericEventListener;
import org.olat.core.util.prefs.Preferences;
import org.olat.core.util.resource.OLATResourceableJustBeforeDeletedEvent;
import org.olat.core.util.resource.OresHelper;
import org.olat.core.util.tree.TreeHelper;
Alan Moran's avatar
Alan Moran committed
import org.olat.course.CourseFactory;
import org.olat.course.CourseModule;
import org.olat.course.DisposedCourseRestartController;
Alan Moran's avatar
Alan Moran committed
import org.olat.course.ICourse;
import org.olat.course.assessment.AssessmentChangedEvent;
import org.olat.course.assessment.AssessmentMode;
import org.olat.course.assessment.manager.UserCourseInformationsManager;
Alan Moran's avatar
Alan Moran committed
import org.olat.course.config.CourseConfig;
import org.olat.course.editor.PublishEvent;
import org.olat.course.groupsandrights.CourseGroupManager;
import org.olat.course.nodes.CourseNode;
import org.olat.course.run.glossary.CourseGlossaryFactory;
import org.olat.course.run.glossary.CourseGlossaryToolLinkController;
import org.olat.course.run.navigation.NavigationHandler;
import org.olat.course.run.navigation.NodeClickedRef;
import org.olat.course.run.userview.AssessmentModeTreeFilter;
import org.olat.course.run.userview.TreeFilter;
Alan Moran's avatar
Alan Moran committed
import org.olat.course.run.userview.UserCourseEnvironmentImpl;
srosse's avatar
srosse committed
import org.olat.course.run.userview.VisibleTreeFilter;
Alan Moran's avatar
Alan Moran committed
import org.olat.group.BusinessGroup;
import org.olat.group.ui.edit.BusinessGroupModifiedEvent;
import org.olat.modules.cp.TreeNodeEvent;
import org.olat.repository.RepositoryEntry;
import org.olat.repository.RepositoryManager;
import org.olat.repository.RepositoryService;
import org.olat.repository.model.RepositoryEntrySecurity;
Alan Moran's avatar
Alan Moran committed
import org.olat.util.logging.activity.LoggingResourceable;
import org.springframework.beans.factory.annotation.Autowired;
Alan Moran's avatar
Alan Moran committed

/**
 * Description: <br>
 * 
 * @author Felix Jost
 */
public class RunMainController extends MainLayoutBasicController implements GenericEventListener, Activateable2 {
	public static final String REBUILD = "rebuild";
Alan Moran's avatar
Alan Moran committed
	public static final String ORES_TYPE_COURSE_RUN = OresHelper.calculateTypeName(RunMainController.class, CourseModule.ORES_TYPE_COURSE);
	private final OLATResourceable courseRunOres; //course run ores for course run channel 

	private ICourse course;//o_clusterOK: this controller listen to course change events
	private RepositoryEntry courseRepositoryEntry;

	private Panel contentP;
	private MenuTree luTree;
	private VelocityContainer coursemain;
Alan Moran's avatar
Alan Moran committed
	private NavigationHandler navHandler;
	private UserCourseEnvironmentImpl uce;
	private TooledStackedPanel toolbarPanel;
Alan Moran's avatar
Alan Moran committed
	private LayoutMain3ColsController columnLayoutCtr;

	private Controller currentNodeController; // the currently open node config

	private boolean isInEditor = false;

	private CourseNode currentCourseNode;
	private TreeModel treeModel;
	private boolean needsRebuildAfter = false;
	private boolean needsRebuildAfterPublish = false;
Alan Moran's avatar
Alan Moran committed
	private boolean needsRebuildAfterRunDone = false;
	private boolean assessmentChangedEventReceived = false;
Alan Moran's avatar
Alan Moran committed
	
	private String courseTitle;
	private Link nextLink, previousLink;
Alan Moran's avatar
Alan Moran committed
	private GlossaryMarkupItemController glossaryMarkerCtr;
	
	@Autowired
	private RepositoryService repositoryService;
Alan Moran's avatar
Alan Moran committed
	/**
	 * Constructor for the run main controller
	 * 
	 * @param ureq The user request
	 * @param wControl The current window controller
	 * @param course The current course
	 * @param initialViewIdentifier if null the default view will be started,
	 *          otherwise a controllerfactory type dependant view will be
	 *          activated (subscription subtype) number: node with nodenumber will
	 *          be activated "assessmentTool": assessment tool will be activated
	 * @param offerBookmark - whether to offer bookmarks or not
	 * @param showCourseConfigLink  Flag to enable/disable link to detail-page in tool menu. 
	 */
	public RunMainController(UserRequest ureq, WindowControl wControl, TooledStackedPanel toolbarPanel,
			ICourse course, RepositoryEntry re, RepositoryEntrySecurity reSecurity, AssessmentMode assessmentMode) {
		// Use repository package as fallback translator
		super(ureq, wControl, Util.createPackageTranslator(RepositoryEntry.class, ureq.getLocale()));
Alan Moran's avatar
Alan Moran committed
		this.course = course;
		this.toolbarPanel = toolbarPanel;
Alan Moran's avatar
Alan Moran committed
		addLoggingResourceable(LoggingResourceable.wrap(course));
		this.courseTitle = course.getCourseTitle();
Alan Moran's avatar
Alan Moran committed
		this.courseRunOres = OresHelper.createOLATResourceableInstance(ORES_TYPE_COURSE_RUN, course.getResourceableId());
		
		Identity identity = ureq.getIdentity();
		// course and system logging
		ThreadLocalUserActivityLogger.log(CourseLoggingAction.COURSE_ENTERING, getClass());
		
		// log shows who entered which course, this can then be further used to jump
		// to the courselog
		logAudit("Entering course: [[["+courseTitle+"]]]", course.getResourceableId().toString());
		luTree = new MenuTree(null, "luTreeRun", this);
srosse's avatar
srosse committed
		luTree.setExpandSelectedNode(false);
		luTree.setElementCssClass("o_course_menu");
Alan Moran's avatar
Alan Moran committed
		contentP = new Panel("building_block_content");

		// preload user assessment data in assessment properties cache to speed up
Alan Moran's avatar
Alan Moran committed
		// course loading
		course.getCourseEnvironment().getAssessmentManager().preloadCache(identity);

		// build up the running structure for this user;
		// get all group memberships for this course
		uce = loadUserCourseEnvironment(ureq, reSecurity);
Alan Moran's avatar
Alan Moran committed
		// build score now
		uce.getScoreAccounting().evaluateAll();
		
		if(assessmentMode != null && assessmentMode.isRestrictAccessElements()) {
			treeFilter = new AssessmentModeTreeFilter(assessmentMode, uce.getCourseEnvironment().getRunStructure());
		} else {
			treeFilter = new VisibleTreeFilter();
		}
		navHandler = new NavigationHandler(uce, treeFilter, false);
		updateTreeAndContent(ureq, currentCourseNode, null);
Alan Moran's avatar
Alan Moran committed
		
		//set the launch date after the evaluation
Alan Moran's avatar
Alan Moran committed

		if (courseRepositoryEntry != null && RepositoryManager.getInstance().createRepositoryEntryStatus(courseRepositoryEntry.getStatusCode()).isClosed()) {
			wControl.setWarning(translate("course.closed"));
		}

		// add text marker wrapper controller to implement course glossary
		// textMarkerCtr must be created before the toolC!
		CourseConfig cc = uce.getCourseEnvironment().getCourseConfig();
		glossaryMarkerCtr = CourseGlossaryFactory.createGlossaryMarkupWrapper(ureq, wControl, contentP, cc);	
Alan Moran's avatar
Alan Moran committed
		if (glossaryMarkerCtr != null) {
			listenTo(glossaryMarkerCtr);
			// enable / disable glossary highlighting according to user prefs
			Preferences prefs = ureq.getUserSession().getGuiPreferences();
			Boolean state = (Boolean) prefs.get(CourseGlossaryToolLinkController.class, CourseGlossaryFactory.createGuiPrefsKey(course));

			//Glossary always on for guests. OLAT-4241
			if(ureq.getUserSession().getRoles().isGuestOnly()){
				state = Boolean.TRUE;
			}
			
			if (state == null) {
				glossaryMarkerCtr.setTextMarkingEnabled(false);
			} else {
				glossaryMarkerCtr.setTextMarkingEnabled(state.booleanValue());
			}
			columnLayoutCtr = new LayoutMain3ColsController(ureq, getWindowControl(), luTree, glossaryMarkerCtr.getInitialComponent(), "course" + course.getResourceableId());				
Alan Moran's avatar
Alan Moran committed
		} else {
			columnLayoutCtr = new LayoutMain3ColsController(ureq, getWindowControl(), luTree, contentP, "courseRun" + course.getResourceableId());							
Alan Moran's avatar
Alan Moran committed
		}
		listenTo(columnLayoutCtr);

		// activate the custom course css if any
		setCustomCSS(CourseFactory.getCustomCourseCss(ureq.getUserSession(), uce.getCourseEnvironment()));
		coursemain = createVelocityContainer("index");
		coursemain.setDomReplaceable(false);
		// see function gotonode in functions.js to see why we need the repositoryentry-key here:
		// it is to correctly apply external links using course-internal links via javascript
		coursemain.contextPut("courserepokey", courseRepositoryEntry.getKey());
		coursemain.put("coursemain", columnLayoutCtr.getInitialComponent());
srosse's avatar
srosse committed
		putInitialPanel(coursemain);
		// disposed message controller must be created beforehand
		Controller courseCloser = new DisposedCourseRestartController(ureq, getWindowControl(), courseRepositoryEntry);
		Controller disposedRestartController = new LayoutMain3ColsController(ureq, wControl, courseCloser);
		setDisposedMsgController(disposedRestartController);
Alan Moran's avatar
Alan Moran committed
		// add as listener to course so we are being notified about course events:
		// - publish changes
		// - assessment events
		// - listen for CourseConfig events
		CoordinatorManager.getInstance().getCoordinator().getEventBus().registerFor(this, identity, course);
		// - group modification events
		CoordinatorManager.getInstance().getCoordinator().getEventBus().registerFor(this, identity, courseRepositoryEntry);
	}
	
	protected void setTextMarkingEnabled(boolean enabled) {
		if (glossaryMarkerCtr != null) {
			glossaryMarkerCtr.setTextMarkingEnabled(enabled);
		}
	}
	
	protected void initToolbar() {
		if(toolbarPanel != null) {
			// new toolbox 'general'
			previousLink = LinkFactory.createToolLink("previouselement","", this, "o_icon_previous_toolbar");
			previousLink.setTitle(translate("command.previous"));
			toolbarPanel.addTool(previousLink, Align.rightEdge, false, "o_tool_previous");
			nextLink = LinkFactory.createToolLink("nextelement","", this, "o_icon_next_toolbar");
			nextLink.setTitle(translate("command.next"));
			toolbarPanel.addTool(nextLink, Align.rightEdge, false, "o_tool_next");
			updateNextPrevious();
	private UserCourseEnvironmentImpl loadUserCourseEnvironment(UserRequest ureq, RepositoryEntrySecurity reSecurity) {
		CourseGroupManager cgm = course.getCourseEnvironment().getCourseGroupManager();
		List<BusinessGroup> coachedGroups;
		if(reSecurity.isGroupCoach()) {
			coachedGroups = cgm.getOwnedBusinessGroups(ureq.getIdentity());
		} else {
			coachedGroups = Collections.emptyList();
		}
		List<BusinessGroup> participatedGroups;
		if(reSecurity.isGroupParticipant()) {
			participatedGroups = cgm.getParticipatingBusinessGroups(ureq.getIdentity());
		} else {
			participatedGroups = Collections.emptyList();
		}
		List<BusinessGroup> waitingLists;
		if(reSecurity.isGroupWaiting()) {
			waitingLists = cgm.getWaitingListGroups(ureq.getIdentity());
		} else {
			waitingLists = Collections.emptyList();
		}
		return new UserCourseEnvironmentImpl(ureq.getUserSession().getIdentityEnvironment(), course.getCourseEnvironment(),
				coachedGroups, participatedGroups, waitingLists,
				reSecurity.isCourseCoach() || reSecurity.isGroupCoach(),
				reSecurity.isEntryAdmin(),
				reSecurity.isCourseParticipant() || reSecurity.isGroupParticipant());
	 * Initialize the users group memberships for groups used within this course
	 * 
	 * @param identity
Alan Moran's avatar
Alan Moran committed
	 */
	protected void reloadGroupMemberships(RepositoryEntrySecurity reSecurity) {
		CourseGroupManager cgm = course.getCourseEnvironment().getCourseGroupManager();
		List<BusinessGroup> coachedGroups;
		if(reSecurity.isGroupCoach()) {
			coachedGroups = cgm.getOwnedBusinessGroups(getIdentity());
		} else {
			coachedGroups = Collections.emptyList();
		}
		List<BusinessGroup> participatedGroups;
		
		if(reSecurity.isGroupParticipant()) {
			participatedGroups = cgm.getParticipatingBusinessGroups(getIdentity());
		} else {
			participatedGroups = Collections.emptyList();
		}
		List<BusinessGroup> waitingLists;
		if(reSecurity.isGroupWaiting()) {
			waitingLists = cgm.getWaitingListGroups(getIdentity());
		} else {
			waitingLists = Collections.emptyList();
		}
		uce.setGroupMemberships(coachedGroups, participatedGroups, waitingLists);
		needsRebuildAfterRunDone = true;
	}
	
	private void setLaunchDates() {
		UserCourseInformationsManager userCourseInfoMgr = CoreSpringFactory.getImpl(UserCourseInformationsManager.class);
		userCourseInfoMgr.updateUserCourseInformations(uce.getCourseEnvironment().getCourseGroupManager().getCourseResource(), getIdentity());
Alan Moran's avatar
Alan Moran committed
	}
	private CourseNode updateAfterChanges(CourseNode courseNode) {
		if(currentCourseNode == null) return null;
		CourseNode newCurrentCourseNode;
		NodeClickedRef nclr = navHandler.reloadTreeAfterChanges(courseNode);
		if(nclr == null) {
			doDisposeAfterEvent();
			newCurrentCourseNode = null;
		} else {
			treeModel = nclr.getTreeModel();
			luTree.setTreeModel(treeModel);
			String selNodeId = nclr.getSelectedNodeId();
			luTree.setSelectedNodeId(selNodeId);
			luTree.setOpenNodeIds(nclr.getOpenNodeIds());
			newCurrentCourseNode = nclr.getCalledCourseNode();
		}
		return newCurrentCourseNode;
	protected void updateNextPrevious() {
		if(nextLink == null || previousLink == null || luTree == null) {
			return;
		}
		
		boolean hasPrevious;
		boolean hasNext;
		if(luTree.getSelectedNode() == null) {
			hasPrevious = true;
			hasNext = true;
		} else {
			List<TreeNode> flatTree = new ArrayList<>();
			TreeHelper.makeTreeFlat(luTree.getTreeModel().getRootNode(), flatTree);
			int index = flatTree.indexOf(luTree.getSelectedNode());
			hasPrevious = index > 0;
			hasNext = index  >= 0 && index+1 < flatTree.size();
		}
		previousLink.setEnabled(hasPrevious);
		nextLink.setEnabled(hasNext);
	}
Alan Moran's avatar
Alan Moran committed

	/**
	 * side-effecty to content and luTree
	 * 
	 * @param ureq
	 * @param calledCourseNode the node to jump to, if null = jump to root node
	 * @param nodecmd An optional command used to activate the run view or NULL if not available
	 * @return true if the node jumped to is visible
	 */
	private boolean updateTreeAndContent(UserRequest ureq, CourseNode calledCourseNode, String nodecmd) {
		return updateTreeAndContent(ureq, calledCourseNode, nodecmd, null, null);
	}
	
	private boolean updateTreeAndContent(UserRequest ureq, CourseNode calledCourseNode, String nodecmd, List<ContextEntry> entries, StateEntry state) {
Alan Moran's avatar
Alan Moran committed
		// build menu (treemodel)
		// dispose old node controller before creating the NodeClickedRef which creates 
		// the new node controller. It is important that the old node controller is 
		// disposed before the new one to not get conflicts with cacheable mappers that
		// might be used in both controllers with the same ID (e.g. the course folder)
		if (currentNodeController != null && !currentNodeController.isDisposed() && !navHandler.isListening(currentNodeController)) {
Alan Moran's avatar
Alan Moran committed
			currentNodeController.dispose();
		}
		NodeClickedRef nclr = navHandler.evaluateJumpToCourseNode(ureq, getWindowControl(), calledCourseNode, this, nodecmd);
		if (!nclr.isVisible()) {
			// if not root -> fallback to root. e.g. when a direct node jump fails
			if (calledCourseNode != null) {
				nclr = navHandler.evaluateJumpToCourseNode(ureq, getWindowControl(), null, this, null);
Alan Moran's avatar
Alan Moran committed
			}
			if (!nclr.isVisible()) {
				MessageController msgController = MessageUIFactory.createInfoMessage(ureq, getWindowControl(),	translate("course.noaccess.title"), translate("course.noaccess.text"));
Alan Moran's avatar
Alan Moran committed
				contentP.setContent(msgController.getInitialComponent());					
				luTree.setTreeModel(new GenericTreeModel());
				return false;
			}
		}

		treeModel = nclr.getTreeModel();
		luTree.setTreeModel(treeModel);
		String selNodeId = nclr.getSelectedNodeId();
		luTree.setSelectedNodeId(selNodeId);
srosse's avatar
srosse committed
		luTree.setOpenNodeIds(nclr.getOpenNodeIds());
Alan Moran's avatar
Alan Moran committed

		// get new run controller.
		currentNodeController = nclr.getRunController();
		addToHistory(ureq, currentNodeController);
		
		if (currentNodeController instanceof TitledWrapperController) {
			Controller contentcontroller = ((TitledWrapperController)currentNodeController).getContentController();
			addToHistory(ureq, contentcontroller);
			if(contentcontroller instanceof Activateable2) {
				((Activateable2)contentcontroller).activate(ureq, entries, state);
			}
		} else if(currentNodeController instanceof Activateable2) {
			((Activateable2)currentNodeController).activate(ureq, entries, state);
		} else if(currentNodeController != null) {
			contentP.setContent(currentNodeController.getInitialComponent());
		} else {
			MessageController msgCtrl = MessageUIFactory
					.createWarnMessage(ureq, getWindowControl(), null, translate("msg.nodenotavailableanymore"));
			listenTo(msgCtrl);
			contentP.setContent(msgCtrl.getInitialComponent());
		updateNextPrevious();
		updateLastUsage(nclr.getCalledCourseNode());
Alan Moran's avatar
Alan Moran committed
		return true;
	}
	
	private void updateLastUsage(CourseNode calledCourseNode) {
		if(calledCourseNode != null && calledCourseNode.needsReferenceToARepositoryEntry()) {
			RepositoryEntry referencedRe = calledCourseNode.getReferencedRepositoryEntry();
			if(referencedRe != null) {
				repositoryService.setLastUsageNowFor(referencedRe);
			}
		}
	}
Alan Moran's avatar
Alan Moran committed

	/**
	 * @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)
	 */
	@Override
	public void event(UserRequest ureq, Component source, Event event) {
			currentCourseNode = updateAfterChanges(currentCourseNode);
		if(nextLink == source) {
			doNext(ureq);
		} else if(previousLink == source) {
			doPrevious(ureq);
Alan Moran's avatar
Alan Moran committed
			if (event.getCommand().equals(MenuTree.COMMAND_TREENODE_CLICKED)) {
				TreeEvent tev = (TreeEvent) event;
Alan Moran's avatar
Alan Moran committed
			}
		} else if (source == coursemain) {
			if (event.getCommand().equals("activateCourseNode")) {
				// Events from the JS function o_activateCourseNode() - activate the given node id
				String nodeid = ureq.getParameter("nodeid");
Alan Moran's avatar
Alan Moran committed
				if (nodeid != null) {
					CourseNode identNode = course.getRunStructure().getNode(nodeid);
					boolean success = updateTreeAndContent(ureq, identNode, null);
					if (success) {
						currentCourseNode = identNode;
					} else {
						getWindowControl().setWarning(translate("msg.nodenotavailableanymore"));
					}
	protected void toolCtrDone(UserRequest ureq) {
		if (isInEditor) {
			isInEditor = false; // for clarity
			if (needsRebuildAfterPublish) {
				needsRebuildAfterPublish = false;
				
			  // rebuild up the running structure for this user, after publish;
				course = CourseFactory.loadCourse(course.getResourceableId());
				uce = new UserCourseEnvironmentImpl(ureq.getUserSession().getIdentityEnvironment(), course.getCourseEnvironment(),
						uce.getCoachedGroups(), uce.getParticipatingGroups(), uce.getWaitingLists(),
						null, null, null);
				// build score now
				uce.getScoreAccounting().evaluateAll();
				navHandler = new NavigationHandler(uce, treeFilter, false);
				
				// rebuild and jump to root node
				updateTreeAndContent(ureq, null, null);
			}
		}
	}

	protected boolean isInEditor() {
		return isInEditor;
	}

	protected void setInEditor(boolean isInEditor) {
		this.isInEditor = isInEditor;
	}

Alan Moran's avatar
Alan Moran committed
	/**
	 * @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)
	 */
Alan Moran's avatar
Alan Moran committed
	public void event(UserRequest ureq, Controller source, Event event) {
			currentCourseNode = updateAfterChanges(currentCourseNode);
		// event from the current tool (editor, groupmanagement, archiver)	
		
		if (source == currentNodeController) {
Alan Moran's avatar
Alan Moran committed
			if (event instanceof OlatCmdEvent) {
				OlatCmdEvent oe = (OlatCmdEvent) event;
				String cmd = oe.getCommand();
				if (cmd.equals(OlatCmdEvent.GOTONODE_CMD)) {
					String subcmd = oe.getSubcommand(); // "69680861018558/node-specific-data";
					CourseNode identNode;
					String nodecmd = null;
					int slPos = subcmd.indexOf('/');
					if (slPos != -1) {
						nodecmd = subcmd.substring(slPos + 1);
						identNode = course.getRunStructure().getNode(subcmd.substring(0, slPos));
					} else {
						identNode = course.getRunStructure().getNode(subcmd);
					}
					addLoggingResourceable(LoggingResourceable.wrap(identNode));
Alan Moran's avatar
Alan Moran committed
					currentCourseNode = identNode;
					updateTreeAndContent(ureq, identNode, nodecmd);
					oe.accept();
				}
			} else if (event == Event.DONE_EVENT) {
				// the controller is done.
				// we have a chance here to test if we need to refresh the evalution.
				// this is the case when a test was submitted and scoring has changed ->
				// preconditions may have changed
				if (needsRebuildAfterRunDone) {
					needsRebuildAfterRunDone = false;
					updateTreeAndContent(ureq, currentCourseNode, null);
				}
			} else if (REBUILD.equals(event.getCommand())) {
				needsRebuildAfterRunDone = false;
				updateTreeAndContent(ureq, currentCourseNode, null);
Alan Moran's avatar
Alan Moran committed
			} else if (event instanceof TreeNodeEvent) {
				TreeNodeEvent tne = (TreeNodeEvent) event;
				TreeNode newCpTreeNode = tne.getChosenTreeNode();
				luTree.setSelectedNodeId(newCpTreeNode.getIdent());
			} else if (event == Event.CHANGED_EVENT) {
				updateTreeAndContent(ureq, currentCourseNode, null);
			} else if (event instanceof BusinessGroupModifiedEvent) {
				fireEvent(ureq, event);
				updateTreeAndContent(ureq, currentCourseNode, null);
	private void doNext(UserRequest ureq) {
		List<TreeNode> flatList = new ArrayList<>();
		TreeNode currentNode = luTree.getSelectedNode();
		TreeHelper.makeTreeFlat(luTree.getTreeModel().getRootNode(), flatList);
		int index = flatList.indexOf(currentNode);
		if(index >= 0 && index+1 <flatList.size()) {
			TreeNode nextNode = flatList.get(index + 1);
			TreeEvent tev = new TreeEvent(MenuTree.COMMAND_TREENODE_CLICKED, nextNode.getIdent());
			doNodeClick(ureq, tev);
		}
	}
	
	private void doPrevious(UserRequest ureq) {
		List<TreeNode> flatList = new ArrayList<>();
		TreeNode currentNode = luTree.getSelectedNode();
		TreeHelper.makeTreeFlat(luTree.getTreeModel().getRootNode(), flatList);
		int index = flatList.indexOf(currentNode);
		if(index-1 >= 0 && index-1 < flatList.size()) {
			TreeNode previousNode = flatList.get(index - 1);
			TreeEvent tev = new TreeEvent(MenuTree.COMMAND_TREENODE_CLICKED, previousNode.getIdent());
			doNodeClick(ureq, tev);
		}
	}
	
	private void doNodeClick(UserRequest ureq, TreeEvent tev) {
		if(assessmentChangedEventReceived) {
			uce.getScoreAccounting().evaluateAll();
			assessmentChangedEventReceived = false;
		}
		
		// goto node:
		// after a click in the tree, evaluate the model anew, and set the
		// selection of the tree again
		NodeClickedRef nclr = navHandler.evaluateJumpToTreeNode(ureq, getWindowControl(), treeModel, tev, this, null, currentNodeController);
		if (!nclr.isVisible()) {
			getWindowControl().setWarning(translate("msg.nodenotavailableanymore"));
			// go to root since the current node is no more visible 
			updateTreeAndContent(ureq, null, null);
			updateNextPrevious();					
			return;
		}
		// a click to a subtree's node
		if (nclr.isHandledBySubTreeModelListener() || nclr.getSelectedNodeId() == null) {
			if(nclr.getRunController() != null) {
				//there is an update to the currentNodeController, apply it
				if (currentNodeController != null && !currentNodeController.isDisposed() && !navHandler.isListening(currentNodeController)) {
					currentNodeController.dispose();
				}
				currentNodeController = nclr.getRunController();
				Component nodeComp = currentNodeController.getInitialComponent();
				contentP.setContent(nodeComp);
				updateLastUsage(nclr.getCalledCourseNode());
			
			if(nclr.getSelectedNodeId() != null && nclr.getOpenNodeIds() != null) {
				luTree.setSelectedNodeId(nclr.getSelectedNodeId());
				luTree.setOpenNodeIds(nclr.getOpenNodeIds());
			}
			updateNextPrevious();
			return;
		}

		// set the new treemodel
		treeModel = nclr.getTreeModel();
		luTree.setTreeModel(treeModel);

		// set the new tree selection
		String nodeId = nclr.getSelectedNodeId();
		luTree.setSelectedNodeId(nodeId);
		luTree.setOpenNodeIds(nclr.getOpenNodeIds());
		currentCourseNode = nclr.getCalledCourseNode();

		// get new run controller. Dispose only if not already disposed in navHandler.evaluateJumpToTreeNode()
		if (currentNodeController != null && !currentNodeController.isDisposed() && !navHandler.isListening(currentNodeController)) {
			currentNodeController.dispose();
		}
		currentNodeController = nclr.getRunController();
		updateLastUsage(nclr.getCalledCourseNode());
		Component nodeComp = currentNodeController.getInitialComponent();
		contentP.setContent(nodeComp);
		addToHistory(ureq, currentNodeController);
		
		// set glossary wrapper dirty after menu click to make it reload the glossary
		// stuff properly when in AJAX mode
		if (glossaryMarkerCtr != null && glossaryMarkerCtr.isTextMarkingEnabled()) {
			glossaryMarkerCtr.getInitialComponent().setDirty(true);
		}
		
		updateNextPrevious();
Alan Moran's avatar
Alan Moran committed


	/**
	 * implementation of listener which listens to publish events
	 * 
	 * @see org.olat.core.util.event.GenericEventListener#event(org.olat.core.gui.control.Event)
	 */
Alan Moran's avatar
Alan Moran committed
	public void event(Event event) {	
		if (event instanceof PublishEvent) {
			PublishEvent pe = (PublishEvent)event;
			if(course.getResourceableId().equals(pe.getPublishedCourseResId())) {
				processPublishEvent(pe);
Alan Moran's avatar
Alan Moran committed
			}
		} else 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(course, true)) {
				doDisposeAfterEvent();
Alan Moran's avatar
Alan Moran committed
			}
		} else if (event instanceof AssessmentChangedEvent) {
			AssessmentChangedEvent ace = (AssessmentChangedEvent) event;
			Identity identity = uce.getIdentityEnvironment().getIdentity();
			// reevaluate the changed node if the event changed the current user
			if (ace.getIdentityKey().equals(identity.getKey())) {
				String assessmentChangeType = ace.getCommand();
				// do not re-evaluate things if only comment has been changed
				if (assessmentChangeType.equals(AssessmentChangedEvent.TYPE_SCORE_EVAL_CHANGED)
						|| assessmentChangeType.equals(AssessmentChangedEvent.TYPE_ATTEMPTS_CHANGED)) {
					//LD: do not recalculate the score now, but at the next click, since the event comes before DB commit
					//uce.getScoreAccounting().evaluateAll(); 
Alan Moran's avatar
Alan Moran committed
					assessmentChangedEventReceived = true;										
				}
				// raise a flag to indicate refresh
				needsRebuildAfterRunDone = true;
			}
	private void processPublishEvent(PublishEvent pe) {
		if (pe.getState() == PublishEvent.PRE_PUBLISH) {
			// so far not interested in PRE PUBLISH event, but one could add user
			// and the currently active BB information. This in turn could be used 
			// by the publish event issuer to decide whether or not to publish...
		} else if (pe.getState() == PublishEvent.PUBLISH) {
			// disable this controller and issue a information
			if (isInEditor) {
				needsRebuildAfterPublish = true;
				// author went in editor and published the course -> raise a flag to
				// later prepare the new
				// course to present him/her a nice view when
				// he/she closes the editor to return to the run main (this controller)
			} else if(getIdentity().getKey().equals(pe.getAuthorKey())) {
				//do nothing
			} else {
				if(currentCourseNode == null) {
					needsRebuildAfter = true;
				} else {
					try {
						String currentNodeIdent = currentCourseNode.getIdent();
						Set<String> deletedNodeIds = pe.getDeletedCourseNodeIds();
						Set<String> modifiedNodeIds = pe.getModifiedCourseNodeIds();
						
						if(deletedNodeIds != null && deletedNodeIds.contains(currentNodeIdent)) {
							doDisposeAfterEvent();
						} else if(modifiedNodeIds != null && modifiedNodeIds.contains(currentNodeIdent)) {
							doDisposeAfterEvent();
						}
					} catch (Exception e) {
						logError("", e);
						//beyond update, be paranoid
						doDisposeAfterEvent();
					}
				}
			}
		}
	}
	
	protected void doDisposeAfterEvent() {
		if(currentNodeController instanceof ConfigurationChangedListener) {
			//give to opportunity to close popups ...
			((ConfigurationChangedListener)currentNodeController).configurationChanged();
		}
	UserCourseEnvironmentImpl getUce() {
		return uce;
	CourseNode getCurrentCourseNode() {
		return currentCourseNode;
Alan Moran's avatar
Alan Moran committed
	}

	/**
	 * @see org.olat.core.gui.control.DefaultController#doDispose(boolean)
	 */
	protected void doDispose() {
		// remove as listener from this course events:
		// - group modification events
		CoordinatorManager.getInstance().getCoordinator().getEventBus().deregisterFor(this, courseRepositoryEntry);
		// - publish changes
		// - assessment events
		CoordinatorManager.getInstance().getCoordinator().getEventBus().deregisterFor(this, course);
		//remove as listener from course run eventAgency
		CoordinatorManager.getInstance().getCoordinator().getEventBus().deregisterFor(this, courseRunOres);
				
		// currentNodeController must be disposed manually does not work with
		// general BasicController methods
		if (currentNodeController != null && !currentNodeController.isDisposed()) {
Alan Moran's avatar
Alan Moran committed
			currentNodeController.dispose();
		}
		currentNodeController = null;
		navHandler.dispose();
Alan Moran's avatar
Alan Moran committed

		// log to Statistical and User log
		ThreadLocalUserActivityLogger.log(CourseLoggingAction.COURSE_LEAVING, getClass());
		
		// log the fact that the user is leaving the course in the log file
		logAudit("Leaving course: [[["+courseTitle+"]]]", course.getResourceableId().toString());
	}

	@Override
	public void activate(UserRequest ureq, List<ContextEntry> entries, StateEntry state) {
		if(entries == null || entries.isEmpty()) return;
		
		ContextEntry firstEntry = entries.get(0);
		String type = firstEntry.getOLATResourceable().getResourceableTypeName();
		if("CourseNode".equalsIgnoreCase(type)) {
			CourseNode cn = course.getRunStructure().getNode(firstEntry.getOLATResourceable().getResourceableId().toString());
			if(currentCourseNode == null || !currentCourseNode.equals(cn)) {
				getWindowControl().makeFlat();
				// add logging information for case course gets started via jump-in
				// link/search
				addLoggingResourceable(LoggingResourceable.wrap(course));
				if (cn != null) {
					addLoggingResourceable(LoggingResourceable.wrap(cn));
				}
				
				if(entries.size() > 1) {
					entries = entries.subList(1, entries.size());
				}
				updateTreeAndContent(ureq, cn, null, entries, firstEntry.getTransientState());
			}
		}
	}

	public void disableToolController(boolean disable) {
		toolbarPanel.setToolbarEnabled(!disable);
	public void disableCourseClose(boolean disable) {
		toolbarPanel.setShowCloseLink(true, !disable);
	}

	@Override
	public void setDisposedMsgController(Controller disposeMsgController) {
		super.setDisposedMsgController(disposeMsgController);
	}
Alan Moran's avatar
Alan Moran committed
}