Skip to content
Snippets Groups Projects
STCourseNode.java 31 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.nodes;

Alan Moran's avatar
Alan Moran committed
import java.util.ArrayList;
import java.util.List;

import org.olat.core.commons.controllers.linkchooser.CustomLinkTreeModel;
import org.olat.core.commons.fullWebApp.LayoutMain3ColsController;
import org.olat.core.commons.fullWebApp.popup.BaseFullWebappPopupLayoutFactory;
import org.olat.core.commons.modules.singlepage.SinglePageController;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.components.stack.BreadcrumbPanel;
Alan Moran's avatar
Alan Moran committed
import org.olat.core.gui.control.Controller;
import org.olat.core.gui.control.WindowControl;
import org.olat.core.gui.control.creator.ControllerCreator;
import org.olat.core.gui.control.generic.clone.CloneController;
import org.olat.core.gui.control.generic.clone.CloneLayoutControllerCreatorCallback;
import org.olat.core.gui.control.generic.clone.CloneableController;
import org.olat.core.gui.control.generic.iframe.DeliveryOptions;
Alan Moran's avatar
Alan Moran committed
import org.olat.core.gui.control.generic.tabbable.TabbableController;
import org.olat.core.id.Identity;
import org.olat.core.id.OLATResourceable;
import org.olat.core.logging.AssertException;
import org.olat.core.logging.OLATRuntimeException;
import org.olat.core.logging.OLog;
import org.olat.core.logging.Tracing;
import org.olat.core.util.StringHelper;
import org.olat.core.util.Util;
import org.olat.core.util.resource.OresHelper;
import org.olat.course.CourseFactory;
import org.olat.course.CourseModule;
import org.olat.course.ICourse;
import org.olat.course.condition.Condition;
import org.olat.course.condition.interpreter.ConditionExpression;
import org.olat.course.condition.interpreter.ConditionInterpreter;
import org.olat.course.editor.CourseEditorEnv;
import org.olat.course.editor.NodeEditController;
import org.olat.course.editor.StatusDescription;
import org.olat.course.export.CourseEnvironmentMapper;
Alan Moran's avatar
Alan Moran committed
import org.olat.course.groupsandrights.CourseGroupManager;
import org.olat.course.groupsandrights.CourseRights;
import org.olat.course.nodes.sp.SPEditController;
Alan Moran's avatar
Alan Moran committed
import org.olat.course.nodes.sp.SPPeekviewController;
import org.olat.course.nodes.st.STCourseNodeEditController;
import org.olat.course.nodes.st.STCourseNodeRunController;
import org.olat.course.nodes.st.STPeekViewController;
import org.olat.course.run.navigation.NodeRunConstructionResult;
import org.olat.course.run.scoring.ScoreCalculator;
import org.olat.course.run.scoring.ScoreEvaluation;
import org.olat.course.run.userview.NodeEvaluation;
import org.olat.course.run.userview.UserCourseEnvironment;
import org.olat.course.tree.CourseInternalLinkTreeModel;
import org.olat.modules.ModuleConfiguration;
import org.olat.repository.RepositoryEntry;
import org.olat.util.logging.activity.LoggingResourceable;

/**
 * Description:<br>
 * The structure node (ST) is used to build structures in the course hierarchy.
 * In addition it is also used to calculate score and passed values, to syndicate
 * these values e.g. from children nodes. Example: a lesson with two tests is 
 * passed when both tests are passed. This would be designed as an ST node with
 * two IMSTEST nodes as children and a scoring rule on the ST node that syndicates
 * the testresults. In the assessment tool the ST node results can be seen but not 
 * changed since these are calculated values and not saved values from properties.
 * 
 * <P>
 * Initial Date: Feb 9, 2004<br>
 * @author Mike Stock
 * @author BPS (<a href="http://www.bps-system.de/">BPS Bildungsportal Sachsen GmbH</a>)
 */
public class STCourseNode extends AbstractAccessableCourseNode implements AssessableCourseNode {

	private static final long serialVersionUID = -7460670977531082040L;
Alan Moran's avatar
Alan Moran committed
	private static final String TYPE = "st";
	private static final String ICON_CSS_CLASS = "o_st_icon";

	private ScoreCalculator scoreCalculator;

	transient private Condition scoreExpression;

	transient private Condition passedExpression;

	/**
	 * Constructor for a course building block of the type structure
	 */
	public STCourseNode() {
		super(TYPE);
		updateModuleConfigDefaults(true);
	}

	/**
	 * @see org.olat.course.nodes.CourseNode#createEditController(org.olat.core.gui.UserRequest,
	 *      org.olat.core.gui.control.WindowControl, org.olat.course.ICourse)
	 */
	public TabbableController createEditController(UserRequest ureq, WindowControl wControl, BreadcrumbPanel stackPanel, ICourse course, UserCourseEnvironment euce) {
Alan Moran's avatar
Alan Moran committed
		updateModuleConfigDefaults(false);
		// only the precondition "access" can be configured till now
		STCourseNodeEditController childTabCntrllr = new STCourseNodeEditController(ureq, wControl, this, course.getCourseFolderContainer(),
				course.getEditorTreeModel(), euce);
Alan Moran's avatar
Alan Moran committed
		CourseNode chosenNode = course.getEditorTreeModel().getCourseNode(euce.getCourseEditorEnv().getCurrentCourseNodeId());
		NodeEditController nodeEditController = new NodeEditController(ureq, wControl, course.getEditorTreeModel(), course, chosenNode, euce, childTabCntrllr);
		// special case: listen to st edit controller, must be informed when the short title is being modified
		nodeEditController.addControllerListener(childTabCntrllr); 
		return nodeEditController;

Alan Moran's avatar
Alan Moran committed
	}

	/**
	 * @see org.olat.course.nodes.CourseNode#createNodeRunConstructionResult(org.olat.core.gui.UserRequest,
	 *      org.olat.core.gui.control.WindowControl,
	 *      org.olat.course.run.userview.UserCourseEnvironment,
	 *      org.olat.course.run.userview.NodeEvaluation)
	 */
Alan Moran's avatar
Alan Moran committed
	public NodeRunConstructionResult createNodeRunConstructionResult(UserRequest ureq, WindowControl wControl,
			final UserCourseEnvironment userCourseEnv, NodeEvaluation ne, String nodecmd) {
		updateModuleConfigDefaults(false);
		Controller cont;
		
		String displayType = getModuleConfiguration().getStringValue(STCourseNodeEditController.CONFIG_KEY_DISPLAY_TYPE);
		String relPath = STCourseNodeEditController.getFileName(getModuleConfiguration());
		
		if (relPath != null && displayType.equals(STCourseNodeEditController.CONFIG_VALUE_DISPLAY_FILE)) {
			// we want a user chosen overview, so display the chosen file from the
			// material folder, otherwise display the normal overview
			// reuse the Run controller from the "Single Page" building block, since
			// we need to do exactly the same task
			Boolean allowRelativeLinks = getModuleConfiguration().getBooleanEntry(STCourseNodeEditController.CONFIG_KEY_ALLOW_RELATIVE_LINKS);
srosse's avatar
srosse committed
			if(allowRelativeLinks == null) {
				allowRelativeLinks = Boolean.FALSE;
			}
			DeliveryOptions deliveryOptions = (DeliveryOptions)getModuleConfiguration().get(SPEditController.CONFIG_KEY_DELIVERYOPTIONS);
Alan Moran's avatar
Alan Moran committed
			OLATResourceable ores = OresHelper.createOLATResourceableInstance(CourseModule.class, userCourseEnv.getCourseEnvironment().getCourseResourceableId());
			SinglePageController spCtr = new SinglePageController(ureq, wControl, userCourseEnv.getCourseEnvironment().getCourseFolderContainer(),
					relPath, allowRelativeLinks.booleanValue(), ores, deliveryOptions);
Alan Moran's avatar
Alan Moran committed
			// check if user is allowed to edit the page in the run view
			CourseGroupManager cgm = userCourseEnv.getCourseEnvironment().getCourseGroupManager();
			boolean hasEditRights = (cgm.isIdentityCourseAdministrator(ureq.getIdentity()) 
					|| cgm.hasRight(ureq.getIdentity(),CourseRights.RIGHT_COURSEEDITOR))
					|| (getModuleConfiguration().getBooleanSafe(SPEditController.CONFIG_KEY_ALLOW_COACH_EDIT, false) && cgm.isIdentityCourseCoach(ureq.getIdentity()));
			
Alan Moran's avatar
Alan Moran committed
			if (hasEditRights) {
				spCtr.allowPageEditing();
				// set the link tree model to internal for the HTML editor
				CustomLinkTreeModel linkTreeModel = new CourseInternalLinkTreeModel(userCourseEnv.getCourseEnvironment().getRunStructure().getRootNode());
				spCtr.setInternalLinkTreeModel(linkTreeModel);
			}
			spCtr.addLoggingResourceable(LoggingResourceable.wrap(this));
			// create clone wrapper layout, allow popping into second window
			CloneLayoutControllerCreatorCallback clccc = new CloneLayoutControllerCreatorCallback() {
				@Override
				public ControllerCreator createLayoutControllerCreator(final UserRequest uureq, final ControllerCreator contentControllerCreator) {
					return BaseFullWebappPopupLayoutFactory.createAuthMinimalPopupLayout(uureq, new ControllerCreator() {
						@Override
Alan Moran's avatar
Alan Moran committed
						public Controller createController(UserRequest lureq, WindowControl lwControl) {
							// wrap in column layout, popup window needs a layout controller
Alan Moran's avatar
Alan Moran committed
							Controller ctr = contentControllerCreator.createController(lureq, lwControl);
							LayoutMain3ColsController layoutCtr = new LayoutMain3ColsController(lureq, lwControl, ctr);
Alan Moran's avatar
Alan Moran committed
							layoutCtr.setCustomCSS(CourseFactory.getCustomCourseCss(lureq.getUserSession(), userCourseEnv.getCourseEnvironment()));
							
							Controller wrappedCtrl = TitledWrapperHelper.getWrapper(lureq, lwControl, ctr, STCourseNode.this, ICON_CSS_CLASS);
							layoutCtr.addDisposableChildController(wrappedCtrl);
							return layoutCtr;
						}
					});
				}
			};
			Controller wrappedCtrl = TitledWrapperHelper.getWrapper(ureq, wControl, spCtr, this, ICON_CSS_CLASS);
			if(wrappedCtrl instanceof CloneableController) {
				cont = new CloneController(ureq, wControl, (CloneableController)wrappedCtrl, clccc);
Alan Moran's avatar
Alan Moran committed
			} else {
				throw new AssertException("Need to be a cloneable");
			}
		} else {
			// evaluate the score accounting for this node. this uses the score accountings local
			// cache hash map to reduce unnecessary calculations
			ScoreEvaluation se = userCourseEnv.getScoreAccounting().evalCourseNode(this);
			cont = TitledWrapperHelper.getWrapper(ureq, wControl, new STCourseNodeRunController(ureq, wControl, userCourseEnv, this, se, ne), this, ICON_CSS_CLASS);
		}

		// access the current calculated score, if there is one, so that it can be
		// displayed in the ST-Runcontroller
		return new NodeRunConstructionResult(cont);
	}
srosse's avatar
srosse committed
	
	/**
	 * Checks if the given CourseNode is of type "Structure Node" and if it is set
	 * to delegate to it's first visible child
	 * 
	 * @param nodeToCheck
	 * @return returns true if the given coursenNode is a STCourseNode and is configured to delegate
	 */
	public static boolean isDelegatingSTCourseNode(CourseNode nodeToCheck) {
		if (!(nodeToCheck instanceof STCourseNode)) return false;
		
		STCourseNode node = (STCourseNode) nodeToCheck;
		String displayMode = node.getModuleConfiguration().getStringValue(STCourseNodeEditController.CONFIG_KEY_DISPLAY_TYPE,
				STCourseNodeEditController.CONFIG_VALUE_DISPLAY_TOC);
		return (STCourseNodeEditController.CONFIG_VALUE_DISPLAY_DELEGATE.equals(displayMode));
	}
	
Alan Moran's avatar
Alan Moran committed
	/**
	 * @see org.olat.course.nodes.GenericCourseNode#createPreviewController(org.olat.core.gui.UserRequest,
	 *      org.olat.core.gui.control.WindowControl,
	 *      org.olat.course.run.userview.UserCourseEnvironment,
	 *      org.olat.course.run.userview.NodeEvaluation)
	 */
	public Controller createPreviewController(UserRequest ureq, WindowControl wControl, UserCourseEnvironment userCourseEnv, NodeEvaluation ne) {
		return createNodeRunConstructionResult(ureq, wControl, userCourseEnv, ne, null).getRunController();
	}
	
	
	/**
	 * @see org.olat.course.nodes.GenericCourseNode#createPeekViewRunController(org.olat.core.gui.UserRequest,
	 *      org.olat.core.gui.control.WindowControl,
	 *      org.olat.course.run.userview.UserCourseEnvironment,
	 *      org.olat.course.run.userview.NodeEvaluation)
	 */
	public Controller createPeekViewRunController(UserRequest ureq, WindowControl wControl, UserCourseEnvironment userCourseEnv,
			NodeEvaluation ne) {
		if (ne.isAtLeastOneAccessible()) {
			ModuleConfiguration config = getModuleConfiguration();
			if (STCourseNodeEditController.CONFIG_VALUE_DISPLAY_FILE.equals(config.getStringValue(STCourseNodeEditController.CONFIG_KEY_DISPLAY_TYPE))) {
				// use single page preview if a file is configured
				OLATResourceable ores = OresHelper.createOLATResourceableInstance(CourseModule.class, userCourseEnv.getCourseEnvironment().getCourseResourceableId());
				return new SPPeekviewController(ureq, wControl, userCourseEnv, config, ores);				
			} else {
				// a peekview controller that displays the listing of the next ST level
				return new STPeekViewController(ureq, wControl, ne);				
			}
		} else {
			// use standard peekview without content
			return super.createPeekViewRunController(ureq, wControl, userCourseEnv, ne);
		}
	}

	/**
	 * the structure node does not have a score itself, but calculates the
	 * score/passed info by evaluating the configured expression in the the
	 * (condition)interpreter.
	 * 
	 * @see org.olat.course.nodes.AssessableCourseNode#getUserScoreEvaluation(org.olat.course.run.userview.UserCourseEnvironment)
	 */
	public ScoreEvaluation getUserScoreEvaluation(UserCourseEnvironment userCourseEnv) {
		Float score = null;
		Boolean passed = null;

		if (scoreCalculator == null) { 
			// this is a not-computable course node at the moment (no scoring/passing rules defined)
			return null; 
		}
		String scoreExpressionStr = scoreCalculator.getScoreExpression();
		String passedExpressionStr = scoreCalculator.getPassedExpression();

		ConditionInterpreter ci = userCourseEnv.getConditionInterpreter();
		userCourseEnv.getScoreAccounting().setEvaluatingCourseNode(this);
		if (scoreExpressionStr != null) {
			score = new Float(ci.evaluateCalculation(scoreExpressionStr));
		}
		if (passedExpressionStr != null) {
			passed = new Boolean(ci.evaluateCondition(passedExpressionStr));
		}
		ScoreEvaluation se = new ScoreEvaluation(score, passed);
		return se;
	}

	/**
	 * @see org.olat.course.nodes.CourseNode#isConfigValid()
	 */
	public StatusDescription isConfigValid() {
		/*
		 * first check the one click cache
		 */
		if (oneClickStatusCache != null) { return oneClickStatusCache[0]; }

		ModuleConfiguration config = getModuleConfiguration();
		StatusDescription sd = StatusDescription.NOERROR;
		if (STCourseNodeEditController.CONFIG_VALUE_DISPLAY_FILE.equals(config.getStringValue(STCourseNodeEditController.CONFIG_KEY_DISPLAY_TYPE))){
			String fileName = (String) config.get(STCourseNodeEditController.CONFIG_KEY_FILE);
			if (fileName == null || !StringHelper.containsNonWhitespace(fileName)){
				String shortKey = "error.missingfile.short";
				String longKey = "error.missingfile.long";
				String[] params = new String[] { this.getShortTitle() };
				String translPackage = Util.getPackageName(SPEditController.class);
				sd = new StatusDescription(StatusDescription.ERROR, shortKey, longKey, params, translPackage);
				sd.setDescriptionForUnit(getIdent());
				// set which pane is affected by error
				sd.setActivateableViewIdentifier(STCourseNodeEditController.PANE_TAB_ST_CONFIG);
			}
Alan Moran's avatar
Alan Moran committed
	}

	/**
	 * @see org.olat.course.nodes.CourseNode#isConfigValid(org.olat.course.run.userview.UserCourseEnvironment)
	 */
	public StatusDescription[] isConfigValid(CourseEditorEnv cev) {
		oneClickStatusCache = null;
		// only here we know which translator to take for translating condition
		// error messages
		String translatorStr = Util.getPackageName(STCourseNodeEditController.class);
		List<StatusDescription> sds = isConfigValidWithTranslator(cev, translatorStr, getConditionExpressions());
Alan Moran's avatar
Alan Moran committed
		oneClickStatusCache = StatusDescriptionHelper.sort(sds);
		return oneClickStatusCache;
	}

	/**
	 * @see org.olat.course.nodes.CourseNode#getReferencedRepositoryEntry()
	 */
	public RepositoryEntry getReferencedRepositoryEntry() {
		return null;
	}

	/**
	 * @see org.olat.course.nodes.CourseNode#needsReferenceToARepositoryEntry()
	 */
	public boolean needsReferenceToARepositoryEntry() {
		return false;
	}

	/**
	 * @return Returns the scoreCalculator.
	 */
	public ScoreCalculator getScoreCalculator() {
		if (scoreCalculator == null) {
			scoreCalculator = new ScoreCalculator(null, null);
		}
		passedExpression = new Condition();
		passedExpression.setConditionId("passed");
		if (scoreCalculator.getPassedExpression() != null) {
			passedExpression.setConditionExpression(scoreCalculator.getPassedExpression());
			passedExpression.setExpertMode(true);
		}
		scoreExpression = new Condition();
		scoreExpression.setConditionId("score");
		if (scoreCalculator.getScoreExpression() != null) {
			scoreExpression.setConditionExpression(scoreCalculator.getScoreExpression());
			scoreExpression.setExpertMode(true);
		}
		return scoreCalculator;
	}

	/**
	 * @param scoreCalculator The scoreCalculator to set.
	 */
	public void setScoreCalculator(ScoreCalculator scoreCalculatorP) {
		scoreCalculator = scoreCalculatorP;
		if (scoreCalculatorP == null) {
			scoreCalculator = getScoreCalculator();
		}
		String passed, score;
		passed = scoreCalculator.getPassedExpression();
		score = scoreCalculator.getScoreExpression();
		scoreExpression.setExpertMode(true);
		scoreExpression.setConditionExpression(score);
		scoreExpression.setConditionId("score");
		passedExpression.setExpertMode(true);
		passedExpression.setConditionExpression(passed);
		passedExpression.setConditionId("passed");
	}

	/**
	 * @see org.olat.course.nodes.AssessableCourseNode#getCutValueConfiguration()
	 */
	public Float getCutValueConfiguration() {
		throw new OLATRuntimeException(STCourseNode.class, "Cut value never defined for ST nodes", null);
	}

	/**
	 * @see org.olat.course.nodes.AssessableCourseNode#getMaxScoreConfiguration()
	 */
	public Float getMaxScoreConfiguration() {
		throw new OLATRuntimeException(STCourseNode.class, "Max score never defined for ST nodes", null);
	}

	/**
	 * @see org.olat.course.nodes.AssessableCourseNode#getMinScoreConfiguration()
	 */
	public Float getMinScoreConfiguration() {
		throw new OLATRuntimeException(STCourseNode.class, "Min score never defined for ST nodes", null);
	}

	/**
	 * @see org.olat.course.nodes.AssessableCourseNode#getUserCoachComment(org.olat.course.run.userview.UserCourseEnvironment)
	 */
	public String getUserCoachComment(UserCourseEnvironment userCourseEnvironment) {
		throw new OLATRuntimeException(STCourseNode.class, "No coach comments available in ST nodes", null);
	}

	/**
	 * @see org.olat.course.nodes.AssessableCourseNode#getUserLog(org.olat.course.run.userview.UserCourseEnvironment)
	 */
	public String getUserLog(UserCourseEnvironment userCourseEnvironment) {
		throw new OLATRuntimeException(STCourseNode.class, "No user logs available in ST nodes", null);
	}

	/**
	 * @see org.olat.course.nodes.AssessableCourseNode#getUserUserComment(org.olat.course.run.userview.UserCourseEnvironment)
	 */
	public String getUserUserComment(UserCourseEnvironment userCourseEnvironment) {
		throw new OLATRuntimeException(STCourseNode.class, "No comments available in ST nodes", null);
	}

	/**
	 * @see org.olat.course.nodes.AssessableCourseNode#hasCommentConfigured()
	 */
	public boolean hasCommentConfigured() {
		// never has comments
		return false;
	}

	/**
	 * @see org.olat.course.nodes.AssessableCourseNode#hasPassedConfigured()
	 */
	public boolean hasPassedConfigured() {
		if (scoreCalculator != null && StringHelper.containsNonWhitespace(scoreCalculator.getPassedExpression())) return true;
		return false;
	}

	/**
	 * @see org.olat.course.nodes.AssessableCourseNode#hasScoreConfigured()
	 */
	public boolean hasScoreConfigured() {
		if (scoreCalculator != null && StringHelper.containsNonWhitespace(scoreCalculator.getScoreExpression())) return true;
		return false;
	}

	/**
	 * @see org.olat.course.nodes.AssessableCourseNode#hasStatusConfigured()
	 */
	public boolean hasStatusConfigured() {
		return false;
	}

	/**
	 * @see org.olat.course.nodes.AssessableCourseNode#isEditableConfigured()
	 */
	public boolean isEditableConfigured() {
		// ST nodes never editable, data generated on the fly
		return false;
	}

	/**
	 * @see org.olat.course.nodes.AssessableCourseNode#updateUserCoachComment(java.lang.String,
	 *      org.olat.course.run.userview.UserCourseEnvironment)
	 */
	public void updateUserCoachComment(String coachComment, UserCourseEnvironment userCourseEnvironment) {
		throw new OLATRuntimeException(STCourseNode.class, "Coach comment variable can't be updated in ST nodes", null);
	}

	/**
	 * @see org.olat.course.nodes.AssessableCourseNode#updateUserScoreEvaluation(org.olat.course.run.scoring.ScoreEvaluation,
	 *      org.olat.course.run.userview.UserCourseEnvironment,
	 *      org.olat.core.id.Identity)
	 */
	public void updateUserScoreEvaluation(ScoreEvaluation scoreEvaluation, UserCourseEnvironment userCourseEnvironment,
			Identity coachingIdentity, boolean incrementAttempts) {
		throw new OLATRuntimeException(STCourseNode.class, "Score variable can't be updated in ST nodes", null);
	}

	/**
	 * @see org.olat.course.nodes.AssessableCourseNode#updateUserUserComment(java.lang.String,
	 *      org.olat.course.run.userview.UserCourseEnvironment,
	 *      org.olat.core.id.Identity)
	 */
	public void updateUserUserComment(String userComment, UserCourseEnvironment userCourseEnvironment, Identity coachingIdentity) {
		throw new OLATRuntimeException(STCourseNode.class, "Comment variable can't be updated in ST nodes", null);
	}

	/**
	 * @see org.olat.course.nodes.AssessableCourseNode#getUserAttempts(org.olat.course.run.userview.UserCourseEnvironment)
	 */
	public Integer getUserAttempts(UserCourseEnvironment userCourseEnvironment) {
		throw new OLATRuntimeException(STCourseNode.class, "No attempts available in ST nodes", null);

	}

	/**
	 * @see org.olat.course.nodes.AssessableCourseNode#hasAttemptsConfigured()
	 */
	public boolean hasAttemptsConfigured() {
		return false;
	}

	/**
	 * @see org.olat.course.nodes.AssessableCourseNode#updateUserAttempts(java.lang.Integer,
	 *      org.olat.course.run.userview.UserCourseEnvironment,
	 *      org.olat.core.id.Identity)
	 */
	public void updateUserAttempts(Integer userAttempts, UserCourseEnvironment userCourseEnvironment, Identity coachingIdentity) {
		throw new OLATRuntimeException(STCourseNode.class, "Attempts variable can't be updated in ST nodes", null);
	}

	/**
	 * @see org.olat.course.nodes.AssessableCourseNode#incrementUserAttempts(org.olat.course.run.userview.UserCourseEnvironment)
	 */
	public void incrementUserAttempts(UserCourseEnvironment userCourseEnvironment) {
		throw new OLATRuntimeException(STCourseNode.class, "Attempts variable can't be updated in ST nodes", null);
	}

	/**
	 * @see org.olat.course.nodes.AssessableCourseNode#getDetailsEditController(org.olat.core.gui.UserRequest,
	 *      org.olat.core.gui.control.WindowControl,
	 *      org.olat.course.run.userview.UserCourseEnvironment)
	 */
	public Controller getDetailsEditController(UserRequest ureq, WindowControl wControl, BreadcrumbPanel stackPanel, UserCourseEnvironment userCourseEnvironment) {
Alan Moran's avatar
Alan Moran committed
		throw new OLATRuntimeException(STCourseNode.class, "Details controler not available in ST nodes", null);
	}

	/**
	 * @see org.olat.course.nodes.AssessableCourseNode#getDetailsListView(org.olat.course.run.userview.UserCourseEnvironment)
	 */
	public String getDetailsListView(UserCourseEnvironment userCourseEnvironment) {
		throw new OLATRuntimeException(STCourseNode.class, "Details not available in ST nodes", null);
	}

	/**
	 * @see org.olat.course.nodes.AssessableCourseNode#getDetailsListViewHeaderKey()
	 */
	public String getDetailsListViewHeaderKey() {
		throw new OLATRuntimeException(STCourseNode.class, "Details not available in ST nodes", null);
	}

	/**
	 * @see org.olat.course.nodes.AssessableCourseNode#hasDetails()
	 */
	public boolean hasDetails() {
		return false;
	}

	/**
	 * Update the module configuration to have all mandatory configuration flags
	 * set to usefull default values
	 * 
	 * @param isNewNode true: an initial configuration is set; false: upgrading
	 *          from previous node configuration version, set default to maintain
	 *          previous behaviour
	 */
	public void updateModuleConfigDefaults(boolean isNewNode) {
		ModuleConfiguration config = getModuleConfiguration();
		if (isNewNode) {
			// use defaults for new course building blocks
			config.setBooleanEntry(STCourseNodeEditController.CONFIG_KEY_ALLOW_RELATIVE_LINKS, Boolean.FALSE.booleanValue());
			// set the default display to peekview in two columns
			config.setStringValue(STCourseNodeEditController.CONFIG_KEY_DISPLAY_TYPE, STCourseNodeEditController.CONFIG_VALUE_DISPLAY_PEEKVIEW);
			config.setIntValue(STCourseNodeEditController.CONFIG_KEY_COLUMNS, 2);

			DeliveryOptions defaultOptions = DeliveryOptions.defaultWithGlossary();
			config.set(SPEditController.CONFIG_KEY_DELIVERYOPTIONS, defaultOptions);
			
Alan Moran's avatar
Alan Moran committed
			config.setConfigurationVersion(3);
		} else {
			// update to version 2
			if (config.getConfigurationVersion() < 2) {
				// use values accoring to previous functionality
				config.setBooleanEntry(STCourseNodeEditController.CONFIG_KEY_ALLOW_RELATIVE_LINKS, Boolean.FALSE.booleanValue());
				// previous version of score st node didn't have easy mode on score
				// calculator, se to expert mode
				if (getScoreCalculator() != null) {
					getScoreCalculator().setExpertMode(true);
				}
				config.setConfigurationVersion(2);
			}
			// update to version 3
			if (config.getConfigurationVersion() < 3) {
				String fileName = (String) config.get(STCourseNodeEditController.CONFIG_KEY_FILE);
				if (fileName != null) {
					// set to custom file display config
					config.setStringValue(STCourseNodeEditController.CONFIG_KEY_DISPLAY_TYPE, STCourseNodeEditController.CONFIG_VALUE_DISPLAY_FILE);
				} else {
					// set the default display to plain vanilla TOC view in one column
					config.setStringValue(STCourseNodeEditController.CONFIG_KEY_DISPLAY_TYPE, STCourseNodeEditController.CONFIG_VALUE_DISPLAY_TOC);
					config.setIntValue(STCourseNodeEditController.CONFIG_KEY_COLUMNS, 1);
				}
				config.setConfigurationVersion(3);
			}

			if (config.getConfigurationVersion() < 4) {
				if(config.get(SPEditController.CONFIG_KEY_DELIVERYOPTIONS) == null) {
					DeliveryOptions defaultOptions = DeliveryOptions.defaultWithGlossary();
					config.set(SPEditController.CONFIG_KEY_DELIVERYOPTIONS, defaultOptions);
				}
				config.setConfigurationVersion(4);
			}
    public void postCopy(CourseEnvironmentMapper envMapper, Processing processType, ICourse course, ICourse sourceCrourse) {
        super.postCopy(envMapper, processType, course, sourceCrourse);
	public void postImport(File importDirectory, ICourse course, CourseEnvironmentMapper envMapper, Processing processType) {
		super.postImport(importDirectory, course, envMapper, processType);
	private void postImportCopy(CourseEnvironmentMapper envMapper) {
		ScoreCalculator calculator = getScoreCalculator();
		boolean changed = false;
		if(StringHelper.containsNonWhitespace(calculator.getScoreExpression())) {
			String score = calculator.getScoreExpression();
			String processedExpression = convertExpressionNameToKey(score, envMapper);
			processedExpression = convertExpressionKeyToKey(score, envMapper);
			if(!processedExpression.equals(score)) {
				calculator.setScoreExpression(processedExpression);
				changed = true;
			}	
		}
		
		if(StringHelper.containsNonWhitespace(calculator.getPassedExpression())) {
			String passed = calculator.getPassedExpression();
			String processedExpression = convertExpressionNameToKey(passed, envMapper);
			processedExpression = convertExpressionKeyToKey(passed, envMapper);
			if(!processedExpression.equals(passed)) {
				calculator.setScoreExpression(processedExpression);
				changed = true;
			}	
		}
		
		if(changed) {
			setScoreCalculator(calculator);
		}
	}

	@Override
	public void postExport(CourseEnvironmentMapper envMapper, boolean backwardsCompatible) {
		super.postExport(envMapper, backwardsCompatible);
		
		//if backwards compatible, convert expression to use names
		if(backwardsCompatible) {
			ScoreCalculator calculator = getScoreCalculator();
			boolean changed = false;
			if(StringHelper.containsNonWhitespace(calculator.getScoreExpression())) {
				String score = calculator.getScoreExpression();
				String processedExpression = convertExpressionKeyToName(score, envMapper);
				if(!processedExpression.equals(score)) {
					calculator.setScoreExpression(processedExpression);
					changed = true;
				}	
			}
			
			if(StringHelper.containsNonWhitespace(calculator.getPassedExpression())) {
				String passed = calculator.getPassedExpression();
				String processedExpression = convertExpressionKeyToName(passed, envMapper);
				if(!processedExpression.equals(passed)) {
					calculator.setScoreExpression(processedExpression);
					changed = true;
				}	
			}
			
			if(changed) {
				setScoreCalculator(calculator);
			}
		}
	}
Alan Moran's avatar
Alan Moran committed

	/**
	 * @see org.olat.course.nodes.AbstractAccessableCourseNode#getConditionExpressions()
	 */
	public List<ConditionExpression> getConditionExpressions() {
		List<ConditionExpression> retVal;
		List<ConditionExpression> parentsConditions = super.getConditionExpressions();
Alan Moran's avatar
Alan Moran committed
		if (parentsConditions.size() > 0) {
			retVal = new ArrayList<ConditionExpression>(parentsConditions);
Alan Moran's avatar
Alan Moran committed
		} else {
			retVal = new ArrayList<ConditionExpression>();
Alan Moran's avatar
Alan Moran committed
		}
		// init passedExpression and scoreExpression
		getScoreCalculator();
		//
		passedExpression.setExpertMode(true);
		String coS = passedExpression.getConditionExpression();
		if (coS != null && !coS.equals("")) {
			// an active condition is defined
			ConditionExpression ce = new ConditionExpression(passedExpression.getConditionId());
			ce.setExpressionString(passedExpression.getConditionExpression());
			retVal.add(ce);
		}
		scoreExpression.setExpertMode(true);
		coS = scoreExpression.getConditionExpression();
		if (coS != null && !coS.equals("")) {
			// an active condition is defined
			ConditionExpression ce = new ConditionExpression(scoreExpression.getConditionId());
			ce.setExpressionString(scoreExpression.getConditionExpression());
			retVal.add(ce);
		}
		//
		return retVal;
	}

	/**
	 * @see org.olat.course.nodes.GenericCourseNode#getDefaultTitleOption()
	 */
	@Override
	public String getDisplayOption() {
		// if nothing other defined, view content only, when a structure node
		// contains an html-file.
		OLog logger = Tracing.createLoggerFor(this.getClass());
		ModuleConfiguration config = getModuleConfiguration();
		String thisConf = super.getDisplayOption(false);
		if (thisConf == null
				&& config.get(STCourseNodeEditController.CONFIG_KEY_DISPLAY_TYPE).equals(STCourseNodeEditController.CONFIG_VALUE_DISPLAY_FILE)) {
			if (logger.isDebug()) {
				logger.debug("no displayOption set, use default (content)", thisConf);
			}
			return CourseNode.DISPLAY_OPTS_CONTENT;
		}
		if (logger.isDebug()) {
			logger.debug("there is a config set, use it: " + thisConf);
		}
		return super.getDisplayOption();
	}

	
}