/**
* 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.nodes;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.olat.core.CoreSpringFactory;
import org.olat.core.commons.modules.bc.FolderConfig;
import org.olat.core.commons.modules.bc.vfs.OlatNamedContainerImpl;
import org.olat.core.commons.modules.bc.vfs.OlatRootFolderImpl;
import org.olat.core.commons.services.taskexecutor.TaskExecutorManager;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.components.stack.BreadcrumbPanel;
import org.olat.core.gui.components.stack.TooledStackedPanel;
import org.olat.core.gui.control.Controller;
import org.olat.core.gui.control.WindowControl;
import org.olat.core.gui.control.generic.messages.MessageUIFactory;
import org.olat.core.gui.control.generic.tabbable.TabbableController;
import org.olat.core.gui.translator.PackageTranslator;
import org.olat.core.gui.translator.Translator;
import org.olat.core.id.Identity;
import org.olat.core.id.Roles;
import org.olat.core.logging.OLATRuntimeException;
import org.olat.core.logging.OLog;
import org.olat.core.logging.Tracing;
import org.olat.core.util.ExportUtil;
import org.olat.core.util.FileUtils;
import org.olat.core.util.Formatter;
import org.olat.core.util.StringHelper;
import org.olat.core.util.Util;
import org.olat.core.util.ZipUtil;
import org.olat.core.util.io.ShieldOutputStream;
import org.olat.core.util.vfs.VFSItem;
import org.olat.core.util.vfs.VFSManager;
import org.olat.course.ICourse;
import org.olat.course.archiver.ScoreAccountingHelper;
import org.olat.course.assessment.AssessmentManager;
import org.olat.course.assessment.bulk.BulkAssessmentToolController;
import org.olat.course.auditing.UserNodeAuditManager;
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;
import org.olat.course.nodes.ms.MSEditFormController;
import org.olat.course.nodes.ta.BulkDownloadToolController;
import org.olat.course.nodes.ta.ConvertToGTACourseNode;
import org.olat.course.nodes.ta.DropboxController;
import org.olat.course.nodes.ta.DropboxScoringViewController;
import org.olat.course.nodes.ta.ReturnboxController;
import org.olat.course.nodes.ta.TACourseNodeEditController;
import org.olat.course.nodes.ta.TACourseNodeRunController;
import org.olat.course.nodes.ta.TaskController;
import org.olat.course.properties.CoursePropertyManager;
import org.olat.course.properties.PersistingCoursePropertyManager;
import org.olat.course.run.environment.CourseEnvironment;
import org.olat.course.run.navigation.NodeRunConstructionResult;
import org.olat.course.run.scoring.AssessmentEvaluation;
import org.olat.course.run.scoring.ScoreEvaluation;
import org.olat.course.run.userview.NodeEvaluation;
import org.olat.course.run.userview.UserCourseEnvironment;
import org.olat.modules.ModuleConfiguration;
import org.olat.modules.assessment.AssessmentEntry;
import org.olat.modules.assessment.AssessmentToolOptions;
import org.olat.properties.Property;
import org.olat.repository.RepositoryEntry;
import org.olat.resource.OLATResource;
/**
* Initial Date: 30.08.2004
*
* @author Mike Stock
* @author BPS (<a href="http://www.bps-system.de/">BPS Bildungsportal Sachsen GmbH</a>)
*/
public class TACourseNode extends GenericCourseNode implements PersistentAssessableCourseNode {
private static final long serialVersionUID = -7266553843441305310L;
private static final String PACKAGE_TA = Util.getPackageName(TACourseNodeRunController.class);
private static final String PACKAGE = Util.getPackageName(TACourseNode.class);
private static final String TYPE = "ta";
// NLS support:
private static final String NLS_GUESTNOACCESS_TITLE = "guestnoaccess.title";
private static final String NLS_GUESTNOACCESS_MESSAGE = "guestnoaccess.message";
private static final String NLS_ERROR_MISSINGSCORECONFIG_SHORT = "error.missingscoreconfig.short";
private static final String NLS_WARN_NODEDELETE = "warn.nodedelete";
private static final int CURRENT_CONFIG_VERSION = 2;
/** CONF_TASK_ENABLED configuration parameter key. */
public static final String CONF_TASK_ENABLED = "task_enabled";
/** CONF_TASK_TYPE configuration parameter key. */
public static final String CONF_TASK_TYPE = "task_type";
/** CONF_TASK_TEXT configuration parameter key. */
public static final String CONF_TASK_TEXT = "task_text";
/** CONF_TASK_SAMPLING_WITH_REPLACEMENT configuration parameter key. */
public static final String CONF_TASK_SAMPLING_WITH_REPLACEMENT = "task_sampling";
/** CONF_TASK_FOLDER_REL_PATH configuration parameter key. */
public static final String CONF_TASK_FOLDER_REL_PATH = "task_folder_rel";
/** CONF_DROPBOX_ENABLED configuration parameter key. */
public static final String CONF_DROPBOX_ENABLED = "dropbox_enabled";
/** CONF_DROPBOX_ENABLEMAIL configuration parameter key. */
public static final String CONF_DROPBOX_ENABLEMAIL = "dropbox_enablemail";
/** CONF_DROPBOX_CONFIRMATION configuration parameter key. */
public static final String CONF_DROPBOX_CONFIRMATION = "dropbox_confirmation";
/** CONF_RETURNBOX_ENABLED configuration parameter key. */
public static final String CONF_RETURNBOX_ENABLED = "returnbox_enabled";
/** CONF_SCORING_ENABLED configuration parameter key. */
public static final String CONF_SCORING_ENABLED = "scoring_enabled";
/** ACCESS_SCORING configuration parameter key. */
public static final String ACCESS_SCORING = "scoring";
/** ACCESS_DROPBOX configuration parameter key. */
public static final String ACCESS_DROPBOX = "dropbox";
/** ACCESS_RETURNBOX configuration parameter key. */
public static final String ACCESS_RETURNBOX = "returnbox";
/** ACCESS_TASK configuration parameter key. */
public static final String ACCESS_TASK = "task";
/** ACCESS_SOLUTION configuration parameter key. */
public static final String ACCESS_SOLUTION = "solution";
/** CONF_SOLUTION_ENABLED configuration parameter key. */
public static final String CONF_SOLUTION_ENABLED = "solution_enabled";
/** Solution folder-name in the file-system. */
public static final String SOLUTION_FOLDER_NAME = "solutions";
/** CONF_TASK_PREVIEW configuration parameter key used for task-form. */
public static final String CONF_TASK_PREVIEW = "task_preview";
/** CONF_TASK_DESELECT configuration parameter key used for task-form. */
public static final String CONF_TASK_DESELECT = "task_deselect";
private Condition conditionTask, conditionDrop, conditionReturnbox, conditionScoring, conditionSolution;
private static final OLog log = Tracing.createLoggerFor(TACourseNode.class);
/**
* Default constructor.
*/
public TACourseNode() {
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)
*/
@Override
public TabbableController createEditController(UserRequest ureq, WindowControl wControl, BreadcrumbPanel stackPanel, ICourse course, UserCourseEnvironment euce) {
updateModuleConfigDefaults(false);
TACourseNodeEditController childTabCntrllr = new TACourseNodeEditController(ureq, wControl, course, this, euce);
CourseNode chosenNode = course.getEditorTreeModel().getCourseNode(euce.getCourseEditorEnv().getCurrentCourseNodeId());
return new NodeEditController(ureq, wControl, course.getEditorTreeModel(), course, chosenNode, euce, childTabCntrllr);
}
/**
* @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)
*/
@Override
public NodeRunConstructionResult createNodeRunConstructionResult(UserRequest ureq, WindowControl wControl,
UserCourseEnvironment userCourseEnv, NodeEvaluation ne, String nodecmd) {
updateModuleConfigDefaults(false);
Controller controller;
// Do not allow guests to access tasks
Roles roles = ureq.getUserSession().getRoles();
if (roles.isGuestOnly()) {
Translator trans = new PackageTranslator(PACKAGE, ureq.getLocale());
String title = trans.translate(NLS_GUESTNOACCESS_TITLE);
String message = trans.translate(NLS_GUESTNOACCESS_MESSAGE);
controller = MessageUIFactory.createInfoMessage(ureq, wControl, title, message);
} else {
controller = new TACourseNodeRunController(ureq, wControl, userCourseEnv, this, ne, false);
}
Controller ctrl = TitledWrapperHelper.getWrapper(ureq, wControl, controller, this, "o_ta_icon");
return new NodeRunConstructionResult(ctrl);
}
/**
* @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)
*/
@Override
public Controller createPreviewController(UserRequest ureq, WindowControl wControl, UserCourseEnvironment userCourseEnv, NodeEvaluation ne) {
return new TACourseNodeRunController(ureq, wControl, userCourseEnv, this, ne, true);
}
/**
* @see org.olat.course.nodes.CourseNode#getReferencedRepositoryEntry()
*/
@Override
public RepositoryEntry getReferencedRepositoryEntry() {
return null;
}
/**
* @see org.olat.course.nodes.CourseNode#needsReferenceToARepositoryEntry()
*/
@Override
public boolean needsReferenceToARepositoryEntry() {
return false;
}
/**
* @see org.olat.course.nodes.CourseNode#isConfigValid()
*/
@Override
public StatusDescription isConfigValid() {
/*
* first check the one click cache
*/
if (oneClickStatusCache != null) { return oneClickStatusCache[0]; }
boolean isValid = true;
Boolean hasScoring = (Boolean) getModuleConfiguration().get(CONF_SCORING_ENABLED);
if (hasScoring.booleanValue()) {
if (!MSEditFormController.isConfigValid(getModuleConfiguration())) isValid = false;
}
StatusDescription sd = StatusDescription.NOERROR;
if (!isValid) {
// FIXME: refine statusdescriptions by moving the statusdescription
String shortKey = NLS_ERROR_MISSINGSCORECONFIG_SHORT;
String longKey = NLS_ERROR_MISSINGSCORECONFIG_SHORT;
String[] params = new String[] { this.getShortTitle() };
String translPackage = Util.getPackageName(MSEditFormController.class);
sd = new StatusDescription(StatusDescription.ERROR, shortKey, longKey, params, translPackage);
sd.setDescriptionForUnit(getIdent());
// set which pane is affected by error
sd.setActivateableViewIdentifier(TACourseNodeEditController.PANE_TAB_CONF_SCORING);
}
// Check if any group exist make sense only with dropbox, scoring or solution
Boolean hasDropbox = (Boolean) getModuleConfiguration().get(CONF_DROPBOX_ENABLED);
if (hasDropbox == null) {
hasDropbox = new Boolean(false);
}
Boolean hasReturnbox = (Boolean) getModuleConfiguration().get(CONF_RETURNBOX_ENABLED);
if(hasReturnbox == null) {
hasReturnbox = hasDropbox;
}
Boolean hasSolution = (Boolean) getModuleConfiguration().get(CONF_SOLUTION_ENABLED);
if (hasSolution == null) {
hasSolution = new Boolean(false);
}
//remove the error handling for missing groups as you can use the course members
return sd;
}
/**
* @see org.olat.course.nodes.CourseNode#isConfigValid(org.olat.course.run.userview.UserCourseEnvironment)
*/
@Override
public StatusDescription[] isConfigValid(CourseEditorEnv cev) {
oneClickStatusCache = null;
// only here we know which translator to take for translating condition
// error messages
String translatorStr = Util.getPackageName(TACourseNodeEditController.class);
// check if group-manager is already initialized
List<StatusDescription> sds = isConfigValidWithTranslator(cev, translatorStr, getConditionExpressions());
oneClickStatusCache = StatusDescriptionHelper.sort(sds);
return oneClickStatusCache;
}
@Override
protected void calcAccessAndVisibility(ConditionInterpreter ci, NodeEvaluation nodeEval) {
if (ci == null) throw new OLATRuntimeException("no condition interpreter <" + getIdent() + " " + getShortName() + ">",
new IllegalArgumentException());
if (nodeEval == null) throw new OLATRuntimeException("node Evaluationt is null!! for <" + getIdent() + " " + getShortName() + ">",
new IllegalArgumentException());
// evaluate the preconditions
boolean task = (getConditionTask().getConditionExpression() == null ? true : ci.evaluateCondition(conditionTask));
nodeEval.putAccessStatus(ACCESS_TASK, task);
boolean dropbox = (getConditionDrop().getConditionExpression() == null ? true : ci.evaluateCondition(conditionDrop));
nodeEval.putAccessStatus(ACCESS_DROPBOX, dropbox);
boolean returnbox = (getConditionReturnbox().getConditionExpression() == null ? true : ci.evaluateCondition(conditionReturnbox));
nodeEval.putAccessStatus(ACCESS_RETURNBOX, returnbox);
boolean scoring = (getConditionScoring().getConditionExpression() == null ? true : ci.evaluateCondition(conditionScoring));
nodeEval.putAccessStatus(ACCESS_SCORING, scoring);
boolean solution = (getConditionSolution().getConditionExpression() == null ? true : ci.evaluateCondition(conditionSolution));
nodeEval.putAccessStatus(ACCESS_SOLUTION, solution);
boolean visible = (getPreConditionVisibility().getConditionExpression() == null ? true : ci
.evaluateCondition(getPreConditionVisibility()));
nodeEval.setVisible(visible);
}
/**
* @see org.olat.course.nodes.CourseNode#informOnDelete(org.olat.core.gui.UserRequest,
* org.olat.course.ICourse)
*/
@Override
public String informOnDelete(Locale locale, ICourse course) {
Translator trans = new PackageTranslator(PACKAGE_TA, locale);
CoursePropertyManager cpm = PersistingCoursePropertyManager.getInstance(course);
List<Property> list = cpm.listCourseNodeProperties(this, null, null, null);
if (list.size() != 0) return trans.translate("warn.nodedelete"); // properties exist
File fTaskFolder = new File(FolderConfig.getCanonicalRoot() + TACourseNode.getTaskFolderPathRelToFolderRoot(course, this));
if (fTaskFolder.exists() && fTaskFolder.list().length > 0) return trans.translate(NLS_WARN_NODEDELETE); // task folder contains files
return null; // no data yet.
}
/**
* @see org.olat.course.nodes.CourseNode#cleanupOnDelete(
* org.olat.course.ICourse)
*/
@Override
public void cleanupOnDelete(ICourse course) {
CoursePropertyManager pm = course.getCourseEnvironment().getCoursePropertyManager();
// Delete all properties...
pm.deleteNodeProperties(this, null);
File fTaskFolder = new File(FolderConfig.getCanonicalRoot() + TACourseNode.getTaskFolderPathRelToFolderRoot(course, this));
if (fTaskFolder.exists()) {
FileUtils.deleteDirsAndFiles(fTaskFolder, true, true);
}
File fDropBox = new File(FolderConfig.getCanonicalRoot() + DropboxController.getDropboxPathRelToFolderRoot(course.getCourseEnvironment(), this));
if (fDropBox.exists()) {
FileUtils.deleteDirsAndFiles(fDropBox, true, true);
}
OLATResource resource = course.getCourseEnvironment().getCourseGroupManager().getCourseResource();
CoreSpringFactory.getImpl(TaskExecutorManager.class).delete(resource, getIdent());
}
/**
* @return dropbox condition
*/
public Condition getConditionDrop() {
if (conditionDrop == null) {
conditionDrop = new Condition();
}
conditionDrop.setConditionId("drop");
return conditionDrop;
}
/**
*
* @return Returnbox condition
*/
public Condition getConditionReturnbox() {
if (conditionReturnbox == null) {
conditionReturnbox = new Condition();
}
conditionReturnbox.setConditionId(ACCESS_RETURNBOX);
return conditionReturnbox;
}
/**
* @return scoring condition
*/
public Condition getConditionScoring() {
if (conditionScoring == null) {
conditionScoring = new Condition();
}
conditionScoring.setConditionId("scoring");
return conditionScoring;
}
/**
* @return task condition
*/
public Condition getConditionTask() {
if (conditionTask == null) {
conditionTask = new Condition();
}
conditionTask.setConditionId("task");
return conditionTask;
}
/**
* @return scoring condition
*/
public Condition getConditionSolution() {
if (conditionSolution == null) {
conditionSolution = new Condition();
}
conditionSolution.setConditionId("solution");
return conditionSolution;
}
/**
* @param conditionDrop
*/
public void setConditionDrop(Condition conditionDrop) {
if (conditionDrop == null) {
conditionDrop = getConditionDrop();
}
conditionDrop.setConditionId("drop");
this.conditionDrop = conditionDrop;
}
/**
*
* @param condition
*/
public void setConditionReturnbox(Condition condition) {
if (condition == null) {
condition = getConditionReturnbox();
}
condition.setConditionId(ACCESS_RETURNBOX);
this.conditionReturnbox = condition;
}
/**
* @param conditionScoring
*/
public void setConditionScoring(Condition conditionScoring) {
if (conditionScoring == null) {
conditionScoring = getConditionScoring();
}
conditionScoring.setConditionId("scoring");
this.conditionScoring = conditionScoring;
}
/**
* @param conditionTask
*/
public void setConditionTask(Condition conditionTask) {
if (conditionTask == null) {
conditionTask = getConditionTask();
}
conditionTask.setConditionId("task");
this.conditionTask = conditionTask;
}
/**
* @param conditionScoring
*/
public void setConditionSolution(Condition conditionSolution) {
if (conditionSolution == null) {
conditionSolution = getConditionSolution();
}
conditionSolution.setConditionId("solution");
this.conditionSolution = conditionSolution;
}
/**
* @see org.olat.course.nodes.AssessableCourseNode#getUserScoreEvaluation(org.olat.course.run.userview.UserCourseEnvironment)
*/
@Override
public AssessmentEvaluation getUserScoreEvaluation(UserCourseEnvironment userCourseEnv) {
if(hasPassedConfigured() || hasScoreConfigured()) {
return getUserScoreEvaluation(getUserAssessmentEntry(userCourseEnv));
}
return AssessmentEvaluation.EMPTY_EVAL;
}
@Override
public AssessmentEvaluation getUserScoreEvaluation(AssessmentEntry entry) {
return AssessmentEvaluation.toAssessmentEvalutation(entry, this);
}
@Override
public AssessmentEntry getUserAssessmentEntry(UserCourseEnvironment userCourseEnv) {
AssessmentManager am = userCourseEnv.getCourseEnvironment().getAssessmentManager();
Identity mySelf = userCourseEnv.getIdentityEnvironment().getIdentity();
return am.getAssessmentEntry(this, mySelf);
}
/**
* @see org.olat.course.nodes.AssessableCourseNode#hasCommentConfigured()
*/
@Override
public boolean hasCommentConfigured() {
Boolean hasScoring = (Boolean) getModuleConfiguration().get(CONF_SCORING_ENABLED);
if (hasScoring) {
ModuleConfiguration config = getModuleConfiguration();
Boolean comment = (Boolean) config.get(MSCourseNode.CONFIG_KEY_HAS_COMMENT_FIELD);
if (comment != null) {
return comment.booleanValue();
}
}
return false;
}
/**
* @see org.olat.course.nodes.AssessableCourseNode#hasPassedConfigured()
*/
@Override
public boolean hasPassedConfigured() {
Boolean hasScoring = (Boolean) getModuleConfiguration().get(CONF_SCORING_ENABLED);
if (hasScoring) {
ModuleConfiguration config = getModuleConfiguration();
Boolean passed = (Boolean) config.get(MSCourseNode.CONFIG_KEY_HAS_PASSED_FIELD);
if (passed != null) {
return passed.booleanValue();
}
}
return false;
}
/**
* @see org.olat.course.nodes.AssessableCourseNode#hasScoreConfigured()
*/
@Override
public boolean hasScoreConfigured() {
Boolean hasScoring = (Boolean) getModuleConfiguration().get(CONF_SCORING_ENABLED);
if (hasScoring) {
ModuleConfiguration config = getModuleConfiguration();
Boolean score = (Boolean) config.get(MSCourseNode.CONFIG_KEY_HAS_SCORE_FIELD);
if (score != null) {
return score.booleanValue();
}
}
return false;
}
@Override
public boolean isAssessedBusinessGroups() {
return false;
}
/**
* @see org.olat.course.nodes.AssessableCourseNode#hasStatusConfigured()
*/
@Override
public boolean hasStatusConfigured() {
return true; // Task Course node has always a status-field
}
/**
* @see org.olat.course.nodes.AssessableCourseNode#getMaxScoreConfiguration()
*/
@Override
public Float getMaxScoreConfiguration() {
if (!hasScoreConfigured()) { throw new OLATRuntimeException(TACourseNode.class, "getMaxScore not defined when hasScore set to false", null); }
ModuleConfiguration config = getModuleConfiguration();
Float max = (Float) config.get(MSCourseNode.CONFIG_KEY_SCORE_MAX);
return max;
}
/**
* @see org.olat.course.nodes.AssessableCourseNode#getMinScoreConfiguration()
*/
@Override
public Float getMinScoreConfiguration() {
if (!hasScoreConfigured()) { throw new OLATRuntimeException(TACourseNode.class, "getMinScore not defined when hasScore set to false", null); }
ModuleConfiguration config = getModuleConfiguration();
Float min = (Float) config.get(MSCourseNode.CONFIG_KEY_SCORE_MIN);
return min;
}
/**
* @see org.olat.course.nodes.AssessableCourseNode#getCutValueConfiguration()
*/
@Override
public Float getCutValueConfiguration() {
if (!hasPassedConfigured()) { throw new OLATRuntimeException(TACourseNode.class, "getCutValue not defined when hasPassed set to false", null); }
ModuleConfiguration config = getModuleConfiguration();
Float cut = (Float) config.get(MSCourseNode.CONFIG_KEY_PASSED_CUT_VALUE);
return cut;
}
/**
* @see org.olat.course.nodes.AssessableCourseNode#getUserCoachComment(org.olat.course.run.userview.UserCourseEnvironment)
*/
@Override
public String getUserCoachComment(UserCourseEnvironment userCourseEnvironment) {
AssessmentManager am = userCourseEnvironment.getCourseEnvironment().getAssessmentManager();
String coachCommentValue = am.getNodeCoachComment(this, userCourseEnvironment.getIdentityEnvironment().getIdentity());
return coachCommentValue;
}
/**
* @see org.olat.course.nodes.AssessableCourseNode#getUserUserComment(org.olat.course.run.userview.UserCourseEnvironment)
*/
@Override
public String getUserUserComment(UserCourseEnvironment userCourseEnvironment) {
AssessmentManager am = userCourseEnvironment.getCourseEnvironment().getAssessmentManager();
String userCommentValue = am.getNodeComment(this, userCourseEnvironment.getIdentityEnvironment().getIdentity());
return userCommentValue;
}
/**
* @see org.olat.course.nodes.AssessableCourseNode#getUserLog(org.olat.course.run.userview.UserCourseEnvironment)
*/
@Override
public String getUserLog(UserCourseEnvironment userCourseEnvironment) {
UserNodeAuditManager am = userCourseEnvironment.getCourseEnvironment().getAuditManager();
String logValue = am.getUserNodeLog(this, userCourseEnvironment.getIdentityEnvironment().getIdentity());
return logValue;
}
/**
* @see org.olat.course.nodes.AssessableCourseNode#isEditableConfigured()
*/
@Override
public boolean isEditableConfigured() {
// always true
return true;
}
/**
* @see org.olat.course.nodes.AssessableCourseNode#updateUserCoachComment(java.lang.String,
* org.olat.course.run.userview.UserCourseEnvironment)
*/
@Override
public void updateUserCoachComment(String coachComment, UserCourseEnvironment userCourseEnvironment) {
AssessmentManager am = userCourseEnvironment.getCourseEnvironment().getAssessmentManager();
Identity mySelf = userCourseEnvironment.getIdentityEnvironment().getIdentity();
if (coachComment != null) {
am.saveNodeCoachComment(this, mySelf, coachComment);
}
}
/**
* @see org.olat.course.nodes.AssessableCourseNode#updateUserScoreEvaluation(org.olat.course.run.scoring.ScoreEvaluation,
* org.olat.course.run.userview.UserCourseEnvironment,
* org.olat.core.id.Identity)
*/
@Override
public void updateUserScoreEvaluation(ScoreEvaluation scoreEval, UserCourseEnvironment userCourseEnvironment,
Identity coachingIdentity, boolean incrementAttempts) {
AssessmentManager am = userCourseEnvironment.getCourseEnvironment().getAssessmentManager();
Identity mySelf = userCourseEnvironment.getIdentityEnvironment().getIdentity();
ScoreEvaluation newScoreEval = new ScoreEvaluation(scoreEval.getScore(), scoreEval.getPassed(), scoreEval.getAssessmentStatus(), scoreEval.getUserVisible(), null, null);
am.saveScoreEvaluation(this, coachingIdentity, mySelf, newScoreEval, userCourseEnvironment, incrementAttempts);
}
/**
* @see org.olat.course.nodes.AssessableCourseNode#updateUserUserComment(java.lang.String,
* org.olat.course.run.userview.UserCourseEnvironment,
* org.olat.core.id.Identity)
*/
@Override
public void updateUserUserComment(String userComment, UserCourseEnvironment userCourseEnvironment, Identity coachingIdentity) {
AssessmentManager am = userCourseEnvironment.getCourseEnvironment().getAssessmentManager();
Identity mySelf = userCourseEnvironment.getIdentityEnvironment().getIdentity();
if (userComment != null) {
am.saveNodeComment(this, coachingIdentity, mySelf, userComment);
}
}
/**
* @see org.olat.course.nodes.AssessableCourseNode#getUserAttempts(org.olat.course.run.userview.UserCourseEnvironment)
*/
@Override
public Integer getUserAttempts(UserCourseEnvironment userCourseEnvironment) {
AssessmentManager am = userCourseEnvironment.getCourseEnvironment().getAssessmentManager();
Identity mySelf = userCourseEnvironment.getIdentityEnvironment().getIdentity();
Integer userAttemptsValue = am.getNodeAttempts(this, mySelf);
return userAttemptsValue;
}
/**
* @see org.olat.course.nodes.AssessableCourseNode#hasAttemptsConfigured()
*/
@Override
public boolean hasAttemptsConfigured() {
return true;
}
/**
* @see org.olat.course.nodes.AssessableCourseNode#updateUserAttempts(java.lang.Integer,
* org.olat.course.run.userview.UserCourseEnvironment,
* org.olat.core.id.Identity)
*/
@Override
public void updateUserAttempts(Integer userAttempts, UserCourseEnvironment userCourseEnvironment, Identity coachingIdentity) {
if (userAttempts != null) {
AssessmentManager am = userCourseEnvironment.getCourseEnvironment().getAssessmentManager();
Identity mySelf = userCourseEnvironment.getIdentityEnvironment().getIdentity();
am.saveNodeAttempts(this, coachingIdentity, mySelf, userAttempts);
}
}
/**
* @see org.olat.course.nodes.AssessableCourseNode#incrementUserAttempts(org.olat.course.run.userview.UserCourseEnvironment)
*/
@Override
public void incrementUserAttempts(UserCourseEnvironment userCourseEnvironment) {
AssessmentManager am = userCourseEnvironment.getCourseEnvironment().getAssessmentManager();
Identity mySelf = userCourseEnvironment.getIdentityEnvironment().getIdentity();
am.incrementNodeAttempts(this, mySelf, userCourseEnvironment);
}
/**
* @see org.olat.course.nodes.AssessableCourseNode#getDetailsEditController(org.olat.core.gui.UserRequest,
* org.olat.core.gui.control.WindowControl,
* org.olat.course.run.userview.UserCourseEnvironment)
*/
@Override
public Controller getDetailsEditController(UserRequest ureq, WindowControl wControl, BreadcrumbPanel stackPanel,
UserCourseEnvironment coachCourseEnv, UserCourseEnvironment assessedUserCourseEnv) {
// prepare file component
return new DropboxScoringViewController(ureq, wControl, this, assessedUserCourseEnv);
}
/**
* Factory method to launch course element assessment tools. limitToGroup is optional to skip he the group choose step
*/
@Override
public List<Controller> createAssessmentTools(UserRequest ureq, WindowControl wControl, TooledStackedPanel stackPanel,
UserCourseEnvironment coachCourseEnv, AssessmentToolOptions options) {
List<Controller> tools = new ArrayList<Controller>(1);
CourseEnvironment courseEnv = coachCourseEnv.getCourseEnvironment();
if(!coachCourseEnv.isCourseReadOnly()) {
tools.add(new BulkAssessmentToolController(ureq, wControl, courseEnv, this));
}
tools.add(new BulkDownloadToolController(ureq, wControl, courseEnv, options, this));
return tools;
}
/**
* @see org.olat.course.nodes.AssessableCourseNode#getDetailsListView(org.olat.course.run.userview.UserCourseEnvironment)
*/
@Override
public String getDetailsListView(UserCourseEnvironment userCourseEnvironment) {
Identity identity = userCourseEnvironment.getIdentityEnvironment().getIdentity();
CoursePropertyManager propMgr = userCourseEnvironment.getCourseEnvironment().getCoursePropertyManager();
List<Property> samples = propMgr.findCourseNodeProperties(this, identity, null, TaskController.PROP_ASSIGNED);
if (samples.size() == 0) return null; // no sample assigned yet
return samples.get(0).getStringValue();
}
/**
* @see org.olat.course.nodes.AssessableCourseNode#getDetailsListViewHeaderKey()
*/
@Override
public String getDetailsListViewHeaderKey() {
return "table.header.details.ta";
}
/**
* @see org.olat.course.nodes.AssessableCourseNode#hasDetails()
*/
@Override
public boolean hasDetails() {
ModuleConfiguration modConfig = getModuleConfiguration();
Boolean hasTask = (Boolean) modConfig.get(TACourseNode.CONF_TASK_ENABLED);
if (hasTask == null) hasTask = Boolean.FALSE;
Boolean hasDropbox = (Boolean) modConfig.get(TACourseNode.CONF_DROPBOX_ENABLED);
if (hasDropbox == null) hasDropbox = Boolean.FALSE;
Boolean hasReturnbox = (Boolean) modConfig.get(TACourseNode.CONF_RETURNBOX_ENABLED);
if (hasReturnbox == null) hasReturnbox = hasDropbox;
return (hasTask.booleanValue() || hasDropbox.booleanValue() || hasReturnbox.booleanValue());
}
@Override
public void copyConfigurationTo(CourseNode courseNode, ICourse course) {
if(courseNode instanceof GTACourseNode) {
ConvertToGTACourseNode convert = new ConvertToGTACourseNode();
convert.convert(this, (GTACourseNode)courseNode, course);
}
}
/**
* @see org.olat.course.nodes.CourseNode#exportNode(java.io.File,
* org.olat.course.ICourse)
*/
@Override
public void exportNode(File fExportDirectory, ICourse course) {
// export the tasks
File fTaskFolder = new File(FolderConfig.getCanonicalRoot(), TACourseNode.getTaskFolderPathRelToFolderRoot(course, this));
File fNodeExportDir = new File(fExportDirectory, getIdent());
fNodeExportDir.mkdirs();
FileUtils.copyDirContentsToDir(fTaskFolder, fNodeExportDir, false, "export task course node");
//export thes solutions
File fSolutionDir = new File(FolderConfig.getCanonicalRoot(), TACourseNode.getFoldernodesPathRelToFolderBase(course.getCourseEnvironment()) + "/" + getIdent());
File fSolExportDir = new File(new File(fExportDirectory, "solutions"), getIdent());
fSolExportDir.mkdirs();
FileUtils.copyDirContentsToDir(fSolutionDir, fSolExportDir, false, "export task course node solutions");
}
/**
* @see org.olat.course.nodes.GenericCourseNode#importNode(java.io.File,
* org.olat.course.ICourse, org.olat.core.gui.UserRequest,
* org.olat.core.gui.control.WindowControl, boolean)
*/
@Override
public void importNode(File importDirectory, ICourse course, Identity owner, Locale locale, boolean withReferences) {
//import tasks
File fNodeImportDir = new File(importDirectory, getIdent());
File fTaskfolderDir = new File(FolderConfig.getCanonicalRoot() + getTaskFolderPathRelToFolderRoot(course, this));
FileUtils.copyDirContentsToDir(fNodeImportDir, fTaskfolderDir, false, "import task course node");
File fSolutionDir = new File(FolderConfig.getCanonicalRoot(), getFoldernodesPathRelToFolderBase(course.getCourseEnvironment()) + "/" + getIdent());
fSolutionDir.mkdirs();
File fSolImportDir = new File(new File(importDirectory, "solutions"), getIdent());
FileUtils.copyDirContentsToDir(fSolImportDir, fSolutionDir, false, "import task course node solutions");
}
@Override
public boolean archiveNodeData(Locale locale, ICourse course, ArchiveOptions options, ZipOutputStream exportStream, String charset) {
boolean dataFound = false;
String dropboxPath = DropboxController.getDropboxPathRelToFolderRoot(course.getCourseEnvironment(), this);
OlatRootFolderImpl dropboxDir = new OlatRootFolderImpl(dropboxPath, null);
String solutionsPath = TACourseNode.getFoldernodesPathRelToFolderBase(course.getCourseEnvironment()) + "/" + this.getIdent();
OlatRootFolderImpl solutionDir = new OlatRootFolderImpl(solutionsPath, null);
String returnboxPath = ReturnboxController.getReturnboxPathRelToFolderRoot(course.getCourseEnvironment(), this);
OlatRootFolderImpl returnboxDir = new OlatRootFolderImpl(returnboxPath, null);
Boolean hasTask = (Boolean) getModuleConfiguration().get(TACourseNode.CONF_TASK_ENABLED);
String dirName = "task_"
+ StringHelper.transformDisplayNameToFileSystemName(getShortName())
+ "_" + Formatter.formatDatetimeFilesystemSave(new Date(System.currentTimeMillis()));
if (dropboxDir.exists() || solutionDir.exists() || returnboxDir.exists() || hasTask.booleanValue()){
// prepare writing course results overview table
List<Identity> users = ScoreAccountingHelper.loadUsers(course.getCourseEnvironment(), options);
Set<String> dropboxNames = null;
if(options != null && (options.getGroup() != null || options.getIdentities() != null)) {
dropboxNames = new HashSet<String>();
for(Identity user:users) {
dropboxNames.add(user.getName());
}
}
String courseTitle = course.getCourseTitle();
String fileName = ExportUtil.createFileNameWithTimeStamp(courseTitle, "xlsx");
List<AssessableCourseNode> nodes = Collections.<AssessableCourseNode>singletonList(this);
// write course results overview table to filesystem
try {
exportStream.putNextEntry(new ZipEntry(dirName + "/" + fileName));
ScoreAccountingHelper.createCourseResultsOverviewXMLTable(users, nodes, course, locale,
new ShieldOutputStream(exportStream));
exportStream.closeEntry();
} catch (IOException e) {
log.error("", e);
}
// copy solutions to tmp dir
if (solutionDir.exists()) {
for(VFSItem child:solutionDir.getItems()) {
dataFound = true;
ZipUtil.addToZip(child, dirName + "/solutions", exportStream);
}
}
// copy dropboxes to tmp dir
if (dropboxDir.exists()) {
//OLAT-6362 archive only dropboxes of users that handed in at least one file -> prevent empty folders in archive
List<VFSItem> dropBoxContent = dropboxDir.getItems();
for (VFSItem file:dropBoxContent) {
if((dropboxNames == null || dropboxNames.contains(file.getName())) && VFSManager.isDirectoryAndNotEmpty(file)){
dataFound = true;
ZipUtil.addToZip(file, dirName + "/dropboxes", exportStream);
}
}
}
// copy only the choosen task to user taskfolder, loop over all users
String taskfolderPath = TACourseNode.getTaskFolderPathRelToFolderRoot(course.getCourseEnvironment(),this);
OlatRootFolderImpl taskfolderDir = new OlatRootFolderImpl(taskfolderPath, null);
for(Identity identity:users) {
// check if user already chose a task
String assignedTask = TaskController.getAssignedTask(identity, course.getCourseEnvironment(), this);
if (assignedTask != null) {
VFSItem item = taskfolderDir.resolve(assignedTask);
if(item != null) {
// copy choosen task to user folder
ZipUtil.addToZip(item, dirName + "/taskfolders/" + identity.getName(), exportStream);
dataFound = true;
}
}
}
// copy returnboxes
if (returnboxDir.exists()) {
//OLAT-6362 archive only existing returnboxes -> prevent empty folders in archive
List<VFSItem> returnBoxContent = returnboxDir.getItems();
for (VFSItem file : returnBoxContent) {
if((dropboxNames == null || dropboxNames.contains(file.getName())) && VFSManager.isDirectoryAndNotEmpty(file)){
dataFound = true;
ZipUtil.addToZip(file, dirName + "/returnboxes", exportStream);
}
}
}
}
return dataFound;
}
/**
* Get the the place where all task folders are stored. Path relative to the
* folder root.
*
* @param courseEnv
* @return the task folders path relative to the folder root.
*/
public static String getTaskFoldersPathRelToFolderRoot(CourseEnvironment courseEnv) {
return courseEnv.getCourseBaseContainer().getRelPath() + "/taskfolders";
}
/**
* Get the task folder path relative to the folder root for a specific node.
*
* @param courseEnv
* @param cNode
* @return the task folder path relative to the folder root.
*/
public static String getTaskFolderPathRelToFolderRoot(CourseEnvironment courseEnv, CourseNode cNode) {
return getTaskFoldersPathRelToFolderRoot(courseEnv) + "/" + cNode.getIdent();
}
/**
* Get the task folder path relative to the folder root for a specific node.
*
* @param course
* @param cNode
* @return the task folder path relative to the folder root.
*/
public static String getTaskFolderPathRelToFolderRoot(ICourse course, CourseNode cNode) {
return getTaskFolderPathRelToFolderRoot(course.getCourseEnvironment(), cNode);
}
/**
* Get the the place where all dropboxes are stored. Path relative to the
* folder root.
*
* @param courseEnv
* @return the dropboxes path relative to the folder root.
*/
public static String getDropBoxesPathRelToFolderRoot(CourseEnvironment courseEnv) {
return courseEnv.getCourseBaseContainer().getRelPath() + "/dropboxes";
}
/**
* Get the dropbox path relative to the folder root for a specific node.
*
* @param courseEnv
* @param cNode
* @return the dropbox path relative to the folder root.
*/
public static String getDropBoxPathRelToFolderRoot(CourseEnvironment courseEnv, CourseNode cNode) {
return getDropBoxesPathRelToFolderRoot(courseEnv) + "/" + cNode.getIdent();
}
/**
* Get the dropbox path relative to the folder root for a specific node.
*
* @param course
* @param cNode
* @return the dropbox path relative to the folder root.
*/
public static String getDropBoxPathRelToFolderRoot(ICourse course, CourseNode cNode) {
return getDropBoxPathRelToFolderRoot(course.getCourseEnvironment(), cNode);
}
/**
* @see org.olat.course.nodes.GenericCourseNode#getConditionExpressions()
*/
@Override
public List<ConditionExpression> getConditionExpressions() {
List<ConditionExpression> retVal;
List<ConditionExpression> parentsConditions = super.getConditionExpressions();
if (parentsConditions.size() > 0) {
retVal = new ArrayList<ConditionExpression>(parentsConditions);
} else {
retVal = new ArrayList<ConditionExpression>();
}
//
String coS = getConditionDrop().getConditionExpression();
if (coS != null && !coS.equals("")) {
// an active condition is defined
ConditionExpression ce = new ConditionExpression(getConditionDrop().getConditionId());
ce.setExpressionString(getConditionDrop().getConditionExpression());
retVal.add(ce);
}
coS = getConditionReturnbox().getConditionExpression();
if (coS != null && !coS.equals("")) {
// an active condition is defined
ConditionExpression ce = new ConditionExpression(getConditionReturnbox().getConditionId());
ce.setExpressionString(getConditionReturnbox().getConditionExpression());
retVal.add(ce);
} else if(coS == null && getConditionDrop().getConditionExpression()!=null && !getConditionDrop().getConditionExpression().equals("")) {
//old courses that had dropbox but no returnbox: use for returnbox the conditionExpression from dropbox
ConditionExpression ce = new ConditionExpression(getConditionReturnbox().getConditionId());
ce.setExpressionString(getConditionDrop().getConditionExpression());
retVal.add(ce);
}
coS = getConditionScoring().getConditionExpression();
if (coS != null && !coS.equals("")) {
// an active condition is defined
ConditionExpression ce = new ConditionExpression(getConditionScoring().getConditionId());
ce.setExpressionString(getConditionScoring().getConditionExpression());
retVal.add(ce);
}
coS = getConditionTask().getConditionExpression();
if (coS != null && !coS.equals("")) {
// an active condition is defined
ConditionExpression ce = new ConditionExpression(getConditionTask().getConditionId());
ce.setExpressionString(getConditionTask().getConditionExpression());
retVal.add(ce);
}
//
return retVal;
}
/**
* @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)
*/
public static OlatNamedContainerImpl getNodeFolderContainer(TACourseNode node, CourseEnvironment courseEnvironment) {
String path = getFoldernodePathRelToFolderBase(courseEnvironment, node);
OlatRootFolderImpl rootFolder = new OlatRootFolderImpl(path, null);
OlatNamedContainerImpl namedFolder = new OlatNamedContainerImpl(TACourseNode.SOLUTION_FOLDER_NAME, rootFolder);
return namedFolder;
}
/**
* @param courseEnv
* @param node
* @return the relative folder base path for this folder node
*/
private static String getFoldernodePathRelToFolderBase(CourseEnvironment courseEnvironment, TACourseNode node) {
return getFoldernodesPathRelToFolderBase(courseEnvironment) + "/" + node.getIdent();
}
/**
* @param courseEnv
* @return the relative folder base path for folder nodes
*/
public static String getFoldernodesPathRelToFolderBase(CourseEnvironment courseEnv) {
return courseEnv.getCourseBaseContainer().getRelPath() + "/" + TACourseNode.SOLUTION_FOLDER_NAME;
}
/**
* Init config parameter with default values for a new course node.
*/
@Override
public void updateModuleConfigDefaults(boolean isNewNode) {
ModuleConfiguration config = getModuleConfiguration();
if (isNewNode) {
// use defaults for new course building blocks
// task defaults
config.set(CONF_TASK_ENABLED, Boolean.TRUE);
config.set(CONF_TASK_TYPE, TaskController.TYPE_MANUAL);
config.set(CONF_TASK_TEXT, "");
config.set(CONF_TASK_SAMPLING_WITH_REPLACEMENT, Boolean.TRUE);
// dropbox defaults
config.set(CONF_DROPBOX_ENABLED, Boolean.TRUE);
config.set(CONF_RETURNBOX_ENABLED, Boolean.TRUE);
config.set(CONF_DROPBOX_ENABLEMAIL, Boolean.FALSE);
config.set(CONF_DROPBOX_CONFIRMATION, "");
// scoring defaults
config.set(CONF_SCORING_ENABLED, Boolean.TRUE);
// New config parameter version 2
config.setBooleanEntry(CONF_TASK_PREVIEW, false);
// solution defaults
config.set(CONF_SOLUTION_ENABLED, Boolean.TRUE);
MSCourseNode.initDefaultConfig(config);
config.setConfigurationVersion(CURRENT_CONFIG_VERSION);
} else {
int version = config.getConfigurationVersion();
if (version < CURRENT_CONFIG_VERSION) {
// Loaded config is older than current config version => migrate
if (version == 1) {
// migrate V1 => V2
config.setBooleanEntry(CONF_TASK_PREVIEW, false);
// solution defaults
config.set(CONF_SOLUTION_ENABLED, Boolean.FALSE);
MSCourseNode.initDefaultConfig(config);
version = 2;
}
config.setConfigurationVersion(CURRENT_CONFIG_VERSION);
}
}
}
@Override
protected void postImportCopyConditions(CourseEnvironmentMapper envMapper) {
super.postImportCopyConditions(envMapper);
postImportCondition(conditionTask, envMapper);
postImportCondition(conditionDrop, envMapper);
postImportCondition(conditionReturnbox, envMapper);
postImportCondition(conditionScoring, envMapper);
postImportCondition(conditionSolution, envMapper);
}
@Override
public void postExport(CourseEnvironmentMapper envMapper, boolean backwardsCompatible) {
super.postExport(envMapper, backwardsCompatible);
postExportCondition(conditionTask, envMapper, backwardsCompatible);
postExportCondition(conditionDrop, envMapper, backwardsCompatible);
postExportCondition(conditionReturnbox, envMapper, backwardsCompatible);
postExportCondition(conditionScoring, envMapper, backwardsCompatible);
postExportCondition(conditionSolution, envMapper, backwardsCompatible);
}
}