Something went wrong on our end
-
srosse authored
OO-2043: add message under the toolbar is a course is freezed, better handler changes of status, reload course element by status changes...
srosse authoredOO-2043: add message under the toolbar is a course is freezed, better handler changes of status, reload course element by status changes...
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
RunMainController.java 35.21 KiB
/**
* 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.
*/
package org.olat.course.run;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
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;
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;
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;
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;
import org.olat.core.gui.control.winmgr.JSCommand;
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;
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.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;
import org.olat.course.CourseFactory;
import org.olat.course.CourseModule;
import org.olat.course.DisposedCourseRestartController;
import org.olat.course.ICourse;
import org.olat.course.assessment.AssessmentChangedEvent;
import org.olat.course.assessment.AssessmentMode;
import org.olat.course.assessment.AssessmentMode.Status;
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.InvisibleTreeFilter;
import org.olat.course.run.userview.TreeFilter;
import org.olat.course.run.userview.UserCourseEnvironmentImpl;
import org.olat.course.run.userview.VisibleTreeFilter;
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;
import org.olat.util.logging.activity.LoggingResourceable;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Description: <br>
*
* @author Felix Jost
*/
public class RunMainController extends MainLayoutBasicController implements GenericEventListener, Activateable2 {
public static final String REBUILD = "rebuild";
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;
private NavigationHandler navHandler;
private UserCourseEnvironmentImpl uce;
private TooledStackedPanel toolbarPanel;
private LayoutMain3ColsController columnLayoutCtr;
private Controller currentNodeController; // the currently open node config
private boolean isInEditor = false;
private CourseNode currentCourseNode;
private TreeModel treeModel;
private TreeFilter treeFilter;
private boolean needsRebuildAfter = false;
private boolean needsRebuildAfterPublish = false;
private boolean needsRebuildAfterRunDone = false;
private boolean assessmentChangedEventReceived = false;
private String courseTitle;
private Link nextLink, previousLink;
private GlossaryMarkupItemController glossaryMarkerCtr;
@Autowired
private RepositoryManager repositoryManager;
@Autowired
private RepositoryService repositoryService;
/**
* 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()));
this.course = course;
this.toolbarPanel = toolbarPanel;
addLoggingResourceable(LoggingResourceable.wrap(course));
this.courseTitle = course.getCourseTitle();
this.courseRepositoryEntry = re;
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);
luTree.setScrollTopOnClick(true);
luTree.setExpandSelectedNode(false);
luTree.setElementCssClass("o_course_menu");
contentP = new Panel("building_block_content");
// build up the running structure for this user;
// get all group memberships for this course
uce = loadUserCourseEnvironment(ureq, reSecurity);
// build score now
uce.getScoreAccounting().evaluateAll();
if(assessmentMode != null && assessmentMode.isRestrictAccessElements()) {
Status assessmentStatus = assessmentMode.getStatus();
if(assessmentStatus == Status.assessment) {
treeFilter = new AssessmentModeTreeFilter(assessmentMode, uce.getCourseEnvironment().getRunStructure());
} else if(assessmentStatus == Status.leadtime || assessmentStatus == Status.followup) {
treeFilter = new InvisibleTreeFilter();
} else {
treeFilter = new VisibleTreeFilter();
}
} else {
treeFilter = new VisibleTreeFilter();
}
navHandler = new NavigationHandler(uce, treeFilter, false);
updateTreeAndContent(ureq, currentCourseNode, null);
if (courseRepositoryEntry != null && repositoryManager.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);
MenuTree layoutTree = luTree;
if(!cc.isMenuEnabled() && !uce.isAdmin()) {
layoutTree = null;
}
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(), layoutTree, glossaryMarkerCtr.getInitialComponent(), "course" + course.getResourceableId());
} else {
columnLayoutCtr = new LayoutMain3ColsController(ureq, getWindowControl(), layoutTree, contentP, "courseRun" + course.getResourceableId());
}
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());
// on initial call we have to set the data-nodeid manually. later it
// will be updated by updateCourseDataAttributes() automatically, but
// only when course visible to users (menu tree not null)
if (treeModel != null) {
String initNodeId = currentCourseNode != null ? currentCourseNode.getIdent() : null;
if (initNodeId == null) {
initNodeId = treeModel.getRootNode().getIdent();
}
coursemain.contextPut("initNodeId", initNodeId);
}
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);
// 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(), getWindowControl(),
coachedGroups, participatedGroups, waitingLists,
reSecurity.isCourseCoach() || reSecurity.isGroupCoach(),
reSecurity.isEntryAdmin(),
reSecurity.isCourseParticipant() || reSecurity.isGroupParticipant(),
reSecurity.isReadOnly());
}
/**
* Initialize the users group memberships for groups used within this course
*
* @param identity
*/
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.setCourseReadOnly(reSecurity.isReadOnly());
uce.setGroupMemberships(coachedGroups, participatedGroups, waitingLists);
needsRebuildAfterRunDone = true;
}
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);
}
protected boolean updateCurrentCourseNode(UserRequest ureq) {
return updateTreeAndContent(ureq, getCurrentCourseNode(), "", null, null);
}
/**
* 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) {
// 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)) {
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);
}
if (!nclr.isVisible()) {
MessageController msgController = MessageUIFactory.createInfoMessage(ureq, getWindowControl(), translate("course.noaccess.title"), translate("course.noaccess.text"));
contentP.setContent(msgController.getInitialComponent());
luTree.setTreeModel(new GenericTreeModel());
return false;
}
}
treeModel = nclr.getTreeModel();
luTree.setTreeModel(treeModel);
String selNodeId = nclr.getSelectedNodeId();
luTree.setSelectedNodeId(selNodeId);
luTree.setOpenNodeIds(nclr.getOpenNodeIds());
// 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);
}
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();
updateCourseDataAttributes(nclr.getCalledCourseNode());
updateLastUsage(nclr.getCalledCourseNode());
return true;
}
private void updateLastUsage(CourseNode calledCourseNode) {
if(calledCourseNode != null && calledCourseNode.needsReferenceToARepositoryEntry()) {
RepositoryEntry referencedRe = calledCourseNode.getReferencedRepositoryEntry();
if(referencedRe != null) {
repositoryService.setLastUsageNowFor(referencedRe);
}
}
}
private void updateCourseDataAttributes(CourseNode calledCourseNode) {
StringBuilder sb = new StringBuilder();
sb.append("try {var oocourse = jQuery('.o_course_run');");
if (calledCourseNode == null) {
sb.append("oocourse.removeAttr('data-nodeid');");
} else {
sb.append("oocourse.attr('data-nodeid','");
sb.append(Formatter.escapeDoubleQuotes(calledCourseNode.getIdent()));
sb.append("');");
}
sb.append("oocourse=null;}catch(e){}");
JSCommand jsc = new JSCommand(sb.toString());
WindowControl wControl = getWindowControl();
if (wControl != null && wControl.getWindowBackOffice() != null) {
wControl.getWindowBackOffice().sendCommandTo(jsc);
}
}
/**
* @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) {
if(needsRebuildAfter) {
currentCourseNode = updateAfterChanges(currentCourseNode);
needsRebuildAfter = false;
}
// Links in editTools dropdown
if(nextLink == source) {
doNext(ureq);
} else if(previousLink == source) {
doPrevious(ureq);
} else if (source == luTree) {
if (event.getCommand().equals(MenuTree.COMMAND_TREENODE_CLICKED) || event.getCommand().equals(MenuTree.COMMAND_TREENODE_EXPANDED)) {
TreeEvent tev = (TreeEvent) event;
doNodeClick(ureq, tev);
}
} else if (source == coursemain) {
if ("activateCourseNode".equals(event.getCommand())) {
// Events from the JS function o_activateCourseNode() - activate the given node id
String nodeid = ureq.getParameter("nodeid");
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, RepositoryEntrySecurity reSecurity) {
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 = loadUserCourseEnvironment(ureq, reSecurity);
// 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;
}
/**
* @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)
*/
@Override
public void event(UserRequest ureq, Controller source, Event event) {
if(needsRebuildAfter) {
currentCourseNode = updateAfterChanges(currentCourseNode);
needsRebuildAfter = false;
}
// event from the current tool (editor, groupmanagement, archiver)
if (source == currentNodeController) {
if (event instanceof OlatCmdEvent) {
OlatCmdEvent oe = (OlatCmdEvent) event;
String cmd = oe.getCommand();
if (OlatCmdEvent.GOTONODE_CMD.equals(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);
}
if(identNode == null) {
showWarning("msg.nodenotavailableanymore");
} else {
addLoggingResourceable(LoggingResourceable.wrap(identNode));
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);
} 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();
updateCourseDataAttributes(nclr.getCalledCourseNode());
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();
updateCourseDataAttributes(nclr.getCalledCourseNode());
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();
updateCourseDataAttributes(nclr.getCalledCourseNode());
}
/**
* implementation of listener which listens to publish events
*
* @see org.olat.core.util.event.GenericEventListener#event(org.olat.core.gui.control.Event)
*/
@Override
public void event(Event event) {
if (event instanceof PublishEvent) {
PublishEvent pe = (PublishEvent)event;
if(course.getResourceableId().equals(pe.getPublishedCourseResId())) {
processPublishEvent(pe);
}
} 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();
}
} 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();
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();
//needsRebuildAfter = true;
} else {
needsRebuildAfter = true;
}
} 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();
}
dispose();
}
UserCourseEnvironmentImpl getUce() {
return uce;
}
CourseNode getCurrentCourseNode() {
return currentCourseNode;
}
/**
* @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()) {
currentNodeController.dispose();
}
currentNodeController = null;
navHandler.dispose();
// 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()) {
if(currentNodeController != null) {
addToHistory(ureq, currentNodeController);
} else {
addToHistory(ureq, this);
}
return;
}
ContextEntry firstEntry = entries.get(0);
String type = firstEntry.getOLATResourceable().getResourceableTypeName();
if("CourseNode".equalsIgnoreCase(type) || "Part".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));
}
// consume our entry
if(entries.size() > 1) {
entries = entries.subList(1, entries.size());
}
updateTreeAndContent(ureq, cn, null, entries, firstEntry.getTransientState());
} else if (currentCourseNode.equals(cn)) {
// consume our entry
if(entries.size() > 1) {
entries = entries.subList(1, entries.size());
}
// the node to be activated is the one that is already on the screen
if (currentNodeController instanceof Activateable2) {
Activateable2 activateable = (Activateable2) currentNodeController;
activateable.activate(ureq, entries, state);
}
}
}
}
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);
}
}