Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
GTACourseNode.java 32.09 KiB
/**
 * <a href="http://www.openolat.org">
 * OpenOLAT - Online Learning and Training</a><br>
 * <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 the
 * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
 * <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>
 * Initial code contributed and copyrighted by<br>
 * frentix GmbH, http://www.frentix.com
 * <p>
 */
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.apache.commons.io.IOUtils;
import org.olat.basesecurity.IdentityRef;
import org.olat.core.CoreSpringFactory;
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.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.vfs.VFSContainer;
import org.olat.core.util.vfs.VFSItem;
import org.olat.core.util.vfs.restapi.SystemItemFilter;
import org.olat.course.CourseFactory;
import org.olat.course.ICourse;
import org.olat.course.archiver.ScoreAccountingHelper;
import org.olat.course.assessment.AssessmentManager;
import org.olat.course.auditing.UserNodeAuditManager;
import org.olat.course.editor.CourseEditorEnv;
import org.olat.course.editor.NodeEditController;
import org.olat.course.editor.PublishEvents;
import org.olat.course.editor.StatusDescription;
import org.olat.course.nodes.gta.GTAManager;
import org.olat.course.nodes.gta.GTAType;
import org.olat.course.nodes.gta.Task;
import org.olat.course.nodes.gta.TaskHelper;
import org.olat.course.nodes.gta.TaskList;
import org.olat.course.nodes.gta.ui.GTAAssessmentDetailsController;
import org.olat.course.nodes.gta.ui.GTAEditController;
import org.olat.course.nodes.gta.ui.GTAGroupAssessmentToolController;
import org.olat.course.nodes.gta.ui.GTARunController;
import org.olat.course.run.environment.CourseEnvironment;
import org.olat.course.run.navigation.NodeRunConstructionResult;
import org.olat.course.run.scoring.ScoreEvaluation;
import org.olat.course.run.userview.NodeEvaluation;
import org.olat.course.run.userview.UserCourseEnvironment;
import org.olat.group.BusinessGroup;
import org.olat.modules.ModuleConfiguration;
import org.olat.repository.RepositoryEntry;
import org.olat.resource.OLATResource;
import org.olat.user.UserManager;

/**
 * 
 * Initial date: 23.02.2015<br>
 * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
 *
 */
public class GTACourseNode extends AbstractAccessableCourseNode implements AssessableCourseNode {
	
	private final static OLog log = Tracing.createLoggerFor(GTACourseNode.class);
	private final static String PACKAGE_GTA = Util.getPackageName(GTAEditController.class);

	private static final long serialVersionUID = 1L;
	
	/**
	 * Setting for group or individual task
	 */
	public static final String GTASK_TYPE = "grouptask.type";
	public static final String GTASK_GROUPS = "grouptask.groups";
	public static final String GTASK_AREAS = "grouptask.areas";
	public static final String GTASK_ASSIGNMENT = "grouptask.assignement";
	public static final String GTASK_ASSIGNMENT_DEADLINE = "grouptask.assignment.deadline";
	public static final String GTASK_SUBMIT = "grouptask.submit";
	public static final String GTASK_SUBMIT_DEADLINE = "grouptask.submit.deadline";
	public static final String GTASK_REVIEW_AND_CORRECTION = "grouptask.review.and.correction";
	public static final String GTASK_REVISION_PERIOD = "grouptask.revision.period";
	public static final String GTASK_SAMPLE_SOLUTION = "grouptask.solution";
	public static final String GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER = "grouptask.solution.visible.after";
	public static final String GTASK_GRADING = "grouptask.grading";
	
	public static final String GTASK_TASKS = "grouptask.tasks";
	
	public static final String GTASK_ASSIGNEMENT_TYPE = "grouptask.assignement.type";
	public static final String GTASK_ASSIGNEMENT_TYPE_AUTO = "auto";
	public static final String GTASK_ASSIGNEMENT_TYPE_MANUAL = "manual";

	public static final String GTASK_USERS_TEXT = "grouptask.users.text";
	public static final String GTASK_PREVIEW = "grouptask.preview";
	
	public static final String GTASK_SAMPLING = "grouptask.sampling";
	public static final String GTASK_SAMPLING_REUSE = "reuse";
	public static final String GTASK_SAMPLING_UNIQUE = "unique";

	public static final String GTASK_EXTERNAL_EDITOR = "grouptask.external.editor";
	public static final String GTASK_EMBBEDED_EDITOR = "grouptask.embbeded.editor";
	public static final String GTASK_MAX_SUBMITTED_DOCS = "grouptask.max.submitted.docs";
	
	public static final String GTASK_SUBMISSION_TEXT = "grouptask.submission.text";
	public static final String GTASK_SUBMISSION_MAIL_CONFIRMATION = "grouptask.submission.mail.confirmation";
	
	public static final String GTASK_SOLUTIONS = "grouptask.solutions";
	

	private static final String TYPE = "gta";

	public GTACourseNode() {
		super(TYPE);
        updateModuleConfigDefaults(true);
	}

	@Override
	public boolean needsReferenceToARepositoryEntry() {
		return false;
	}
	
	@Override
	public RepositoryEntry getReferencedRepositoryEntry() {
		return null;
	}
	
	@Override
	public void updateModuleConfigDefaults(boolean isNewNode) {
		if(isNewNode) {
			//setup default configuration
			ModuleConfiguration config = getModuleConfiguration();
			//group task
			config.setStringValue(GTASK_TYPE, GTAType.group.name());
			//manual choice
			config.setStringValue(GTASK_ASSIGNEMENT_TYPE, GTASK_ASSIGNEMENT_TYPE_MANUAL);
			//all steps
			config.setBooleanEntry(GTASK_ASSIGNMENT, true);
			config.setBooleanEntry(GTASK_SUBMIT, true);
			config.setBooleanEntry(GTASK_REVIEW_AND_CORRECTION, true);
			config.setBooleanEntry(GTASK_REVISION_PERIOD, true);
			config.setBooleanEntry(GTASK_SAMPLE_SOLUTION, true);
			config.setBooleanEntry(GTASK_GRADING, true);
			//editors
			config.setBooleanEntry(GTACourseNode.GTASK_EXTERNAL_EDITOR, true);
			config.setBooleanEntry(GTACourseNode.GTASK_EMBBEDED_EDITOR, true);
			//reuse tasks
			config.setStringValue(GTACourseNode.GTASK_SAMPLING, GTACourseNode.GTASK_SAMPLING_REUSE);
			//configure grading
			config.set(MSCourseNode.CONFIG_KEY_HAS_SCORE_FIELD, Boolean.FALSE);
			config.set(MSCourseNode.CONFIG_KEY_SCORE_MIN, new Float(0));
			config.set(MSCourseNode.CONFIG_KEY_SCORE_MAX, new Float(0));
			config.set(MSCourseNode.CONFIG_KEY_HAS_PASSED_FIELD, Boolean.TRUE);
		}
	}

	@Override
	public StatusDescription isConfigValid() {
		if (oneClickStatusCache != null && oneClickStatusCache.length > 0) {
			return oneClickStatusCache[0];
		}
		
		List<StatusDescription> statusDescs = validateInternalConfiguration(null);
		if(statusDescs.isEmpty()) {
			statusDescs.add(StatusDescription.NOERROR);
		}
		oneClickStatusCache = StatusDescriptionHelper.sort(statusDescs);
		return oneClickStatusCache[0];
	}
	
	@Override
	public StatusDescription[] isConfigValid(CourseEditorEnv cev) {
		oneClickStatusCache = null;//delete the cache
		
		List<StatusDescription> sds = isConfigValidWithTranslator(cev, PACKAGE_GTA, getConditionExpressions());
		if(oneClickStatusCache != null && oneClickStatusCache.length > 0) {
			//isConfigValidWithTranslator add first
			sds.remove(oneClickStatusCache[0]);
		}
		sds.addAll(validateInternalConfiguration(cev));
		oneClickStatusCache = StatusDescriptionHelper.sort(sds);
		return oneClickStatusCache;
	}
	
	private List<StatusDescription> validateInternalConfiguration(CourseEditorEnv cev) {
		List<StatusDescription> sdList = new ArrayList<>(5);

		ModuleConfiguration config = getModuleConfiguration();
		
		boolean hasScoring = config.getBooleanSafe(GTASK_GRADING);
		if (hasScoring) {
			if(!config.getBooleanSafe(MSCourseNode.CONFIG_KEY_HAS_SCORE_FIELD)
					&& !config.getBooleanSafe(MSCourseNode.CONFIG_KEY_HAS_SCORE_FIELD)) {

				addStatusErrorDescription("error.missing.score.config", GTAEditController.PANE_TAB_GRADING, sdList);
			}
		}
		
		if(GTAType.group.name().equals(config.getStringValue(GTACourseNode.GTASK_TYPE))) {
			List<Long> groupKeys = config.getList(GTACourseNode.GTASK_GROUPS, Long.class);
			List<Long> areaKeys = config.getList(GTACourseNode.GTASK_AREAS, Long.class);
			if(groupKeys.isEmpty() && areaKeys.isEmpty()) {
				addStatusErrorDescription("error.missing.group", GTAEditController.PANE_TAB_GRADING, sdList);
			}
		}
		
		//at least one step
		if(!config.getBooleanSafe(GTACourseNode.GTASK_ASSIGNMENT) && !config.getBooleanSafe(GTACourseNode.GTASK_SUBMIT)
				&& !config.getBooleanSafe(GTACourseNode.GTASK_REVIEW_AND_CORRECTION) && !config.getBooleanSafe(GTACourseNode.GTASK_REVISION_PERIOD)
				&& !config.getBooleanSafe(GTACourseNode.GTASK_SAMPLE_SOLUTION) && !config.getBooleanSafe(GTACourseNode.GTASK_GRADING)) {
			addStatusErrorDescription("error.select.atleastonestep", GTAEditController.PANE_TAB_WORKLOW, sdList);
		}
		
		if(cev != null) {
			//check assignment
			GTAManager gtaManager = CoreSpringFactory.getImpl(GTAManager.class);
			OLATResource courseOres = cev.getCourseGroupManager().getCourseResource();
			ICourse course = CourseFactory.loadCourse(courseOres);
			if(config.getBooleanSafe(GTACourseNode.GTASK_ASSIGNMENT)) {
				File taskDirectory = gtaManager.getTasksDirectory(course.getCourseEnvironment(), this);
				if(!TaskHelper.hasDocuments(taskDirectory)) {
					addStatusErrorDescription("error.missing.tasks", GTAEditController.PANE_TAB_ASSIGNMENT, sdList);
				}
			}
			
			//check solutions
			if(config.getBooleanSafe(GTACourseNode.GTASK_SAMPLE_SOLUTION)) {
				File solutionDirectory = gtaManager.getSolutionsDirectory(course.getCourseEnvironment(), this);
				if(!TaskHelper.hasDocuments(solutionDirectory)) {
					addStatusErrorDescription("error.missing.solutions", GTAEditController.PANE_TAB_SOLUTIONS, sdList);
				}
			}
			
			List<IdentityRef> participants = gtaManager.getDuplicatedMemberships(this);
			if(participants.size() > 0) {
				UserManager um = CoreSpringFactory.getImpl(UserManager.class);
				StringBuilder sb = new StringBuilder();
				for(IdentityRef participant:participants) {
					String fullname = um.getUserDisplayName(participant.getKey());
					if(sb.length() > 0) sb.append(", ");
					sb.append(fullname);
				}

				String[] params = new String[] { getShortTitle(), sb.toString()  };
				StatusDescription sd = new StatusDescription(StatusDescription.WARNING, "error.duplicate.memberships", "error.duplicate.memberships", params, PACKAGE_GTA);
				sd.setDescriptionForUnit(getIdent());
				sd.setActivateableViewIdentifier(GTAEditController.PANE_TAB_WORKLOW);
				sdList.add(sd);
			}
		}
		return sdList;
	}
	
	private void addStatusErrorDescription(String key, String pane, List<StatusDescription> status) {
		String[] params = new String[] { getShortTitle() };
		StatusDescription sd = new StatusDescription(StatusDescription.ERROR, key, key, params, PACKAGE_GTA);
		sd.setDescriptionForUnit(getIdent());
		sd.setActivateableViewIdentifier(pane);
		status.add(sd);
	}
	
	/**
	 * 
	 * The files are exported in export/{node ident}/tasks and export/{node ident}/solutions
	 * 
	 */
	@Override
	public void exportNode(File fExportDirectory, ICourse course) {
		File fNodeExportDir = new File(fExportDirectory, getIdent());
		GTAManager gtaManager = CoreSpringFactory.getImpl(GTAManager.class);
		
		// export the tasks
		File tasksExportDir = new File(fNodeExportDir, "tasks");
		File taskDirectory = gtaManager.getTasksDirectory(course.getCourseEnvironment(), this);
		fNodeExportDir.mkdirs();
		FileUtils.copyDirContentsToDir(taskDirectory, tasksExportDir, false, "export task course node");
		
		//export the solutions
		File fSolExportDir = new File(fNodeExportDir, "solutions");
		File solutionsDirectory = gtaManager.getSolutionsDirectory(course.getCourseEnvironment(), this);
		fSolExportDir.mkdirs();
		FileUtils.copyDirContentsToDir(solutionsDirectory, fSolExportDir, false, "export task course node solutions");
	}
	
	@Override
	public void importNode(File importDirectory, ICourse course, Identity owner, Locale locale, boolean withReferences) {
		File fNodeImportDir = new File(importDirectory, getIdent());
		GTAManager gtaManager = CoreSpringFactory.getImpl(GTAManager.class);
		
		//import tasks
		File tasksImportDir = new File(fNodeImportDir, "tasks");
		File taskDirectory = gtaManager.getTasksDirectory(course.getCourseEnvironment(), this);
		FileUtils.copyDirContentsToDir(tasksImportDir, taskDirectory, false, "import task course node");
	
		//import solutions
		File fSolImportDir = new File(fNodeImportDir, "solutions");
		File solutionsDirectory = gtaManager.getSolutionsDirectory(course.getCourseEnvironment(), this);
		FileUtils.copyDirContentsToDir(fSolImportDir, solutionsDirectory, false, "import task course node solutions");
	}

	@Override
	public CourseNode createInstanceForCopy(boolean isNewTitle, ICourse course) {
		GTACourseNode cNode = (GTACourseNode)super.createInstanceForCopy(isNewTitle, course);
		GTAManager gtaManager = CoreSpringFactory.getImpl(GTAManager.class);
		
		//copy tasks
		File taskDirectory = gtaManager.getTasksDirectory(course.getCourseEnvironment(), this);
		File copyTaskDirectory = gtaManager.getTasksDirectory(course.getCourseEnvironment(), cNode);
		FileUtils.copyDirContentsToDir(taskDirectory, copyTaskDirectory, false, "copy task course node");
		
		//copy solutions
		File solutionsDirectory = gtaManager.getSolutionsDirectory(course.getCourseEnvironment(), this);
		File copySolutionsDirectory = gtaManager.getSolutionsDirectory(course.getCourseEnvironment(), cNode);
		FileUtils.copyDirContentsToDir(solutionsDirectory, copySolutionsDirectory, false, "copy task course node solutions");

		return cNode;
	}

	@Override
	public boolean archiveNodeData(Locale locale, ICourse course, ArchiveOptions options, ZipOutputStream exportStream, String charset) {
		GTAManager gtaManager = CoreSpringFactory.getImpl(GTAManager.class);
	
		String dirName = "grouptask_"
				+ StringHelper.transformDisplayNameToFileSystemName(getShortName())
				+ "_" + Formatter.formatDatetimeFilesystemSave(new Date(System.currentTimeMillis()));
		
		TaskList taskList = gtaManager.getTaskList(course.getCourseEnvironment().getCourseGroupManager().getCourseEntry(), this);

		//save assessment datas
		List<Identity> users = null;
		ModuleConfiguration config = getModuleConfiguration();
		if(config.getBooleanSafe(GTASK_GRADING)) {
			users = ScoreAccountingHelper.loadUsers(course.getCourseEnvironment(), options);
			
			String courseTitle = course.getCourseTitle();
			String fileName = ExportUtil.createFileNameWithTimeStamp(courseTitle, "xls");
			List<AssessableCourseNode> nodes = Collections.<AssessableCourseNode>singletonList(this);
			String s = ScoreAccountingHelper.createCourseResultsOverviewTable(users, nodes, course, locale);
			// write course results overview table to filesystem
			try {
				exportStream.putNextEntry(new ZipEntry(dirName + "/" + fileName));
				IOUtils.write(s, exportStream);
				exportStream.closeEntry();
			} catch (IOException e) {
				log.error("", e);
			}
		}
		
		//copy tasks
		if(taskList != null) {
			if(GTAType.group.name().equals(config.getStringValue(GTACourseNode.GTASK_TYPE))) {
				List<BusinessGroup> selectedGroups;
				if(options.getGroup() != null) {
					selectedGroups = Collections.singletonList(options.getGroup());
				} else {
					selectedGroups = gtaManager.getBusinessGroups(this);
				}
				
				for(BusinessGroup businessGroup:selectedGroups) {
					archiveNodeData(locale, course, businessGroup, taskList, dirName, exportStream);
				}
			} else {
				if(users == null) {
					users = ScoreAccountingHelper.loadUsers(course.getCourseEnvironment(), options);
				}
				
				Set<Identity> uniqueUsers = new HashSet<>(users);
				for(Identity user: uniqueUsers) {
					archiveNodeData(locale, course, user, taskList, dirName, exportStream);
				}
			}
		}

		//copy solutions
		if(config.getBooleanSafe(GTACourseNode.GTASK_SAMPLE_SOLUTION)) {
			VFSContainer solutions = gtaManager.getSolutionsContainer(course.getCourseEnvironment(), this);
			if (solutions.exists()) {
				String solutionDirName = dirName + "/solutions";
				for(VFSItem solution:solutions.getItems(new SystemItemFilter())) {
					ZipUtil.addToZip(solution, solutionDirName, exportStream);
				}
			}
		}
		
		return true;
	}
	
	private void archiveNodeData(Locale locale, ICourse course, Identity assessedIdentity, TaskList taskList, String dirName, ZipOutputStream exportStream) {
		ModuleConfiguration config = getModuleConfiguration();
		GTAManager gtaManager = CoreSpringFactory.getImpl(GTAManager.class);
		
		int flow = 0;//for beautiful ordering
		String userDirName = dirName + "/"
				+ StringHelper.transformDisplayNameToFileSystemName(assessedIdentity.getName())
				+ "_" + assessedIdentity.getKey();
		
		Task task = gtaManager.getTask(assessedIdentity, taskList);
		if(task != null && config.getBooleanSafe(GTASK_ASSIGNMENT)) {
			File taskDirectory = gtaManager.getTasksDirectory(course.getCourseEnvironment(), this);
			File taskFile = new File(taskDirectory, task.getTaskName());
			if(taskFile.exists()) {
				String path = userDirName + "/"  + (++flow) + "_task/" + taskFile.getName(); 
				ZipUtil.addFileToZip(path, taskFile, exportStream);
			}
		}
		
		if(config.getBooleanSafe(GTASK_SUBMIT)) {
			File submitDirectory = gtaManager.getSubmitDirectory(course.getCourseEnvironment(), this, assessedIdentity);
			String submissionDirName = userDirName + "/" + (++flow) + "_submissions";
			ZipUtil.addDirectoryToZip(submitDirectory.toPath(), submissionDirName, exportStream);
		}

		if(config.getBooleanSafe(GTACourseNode.GTASK_REVIEW_AND_CORRECTION)) {
			File correctionsDir = gtaManager.getCorrectionDirectory(course.getCourseEnvironment(), this, assessedIdentity);
			String correctionDirName = userDirName + "/" + (++flow) + "_corrections";
			ZipUtil.addDirectoryToZip(correctionsDir.toPath(), correctionDirName, exportStream);
		}
		
		if(task != null && config.getBooleanSafe(GTACourseNode.GTASK_REVISION_PERIOD)) {
			int numOfIteration = task.getRevisionLoop();
			for(int i=1; i<=numOfIteration; i++) {
				File revisionDirectory = gtaManager.getRevisedDocumentsDirectory(course.getCourseEnvironment(), this, i, assessedIdentity);
				String revisionDirName = userDirName + "/" + (++flow) + "_revisions_" + i;
				ZipUtil.addDirectoryToZip(revisionDirectory.toPath(), revisionDirName, exportStream);
				
				File correctionDirectory = gtaManager.getRevisedDocumentsCorrectionsDirectory(course.getCourseEnvironment(), this, i, assessedIdentity);
				String correctionDirName = userDirName + "/" + (++flow) + "_corrections_" + i;
				ZipUtil.addDirectoryToZip(correctionDirectory.toPath(), correctionDirName, exportStream);
			}
		}
	}
	
	private void archiveNodeData(Locale locale, ICourse course, BusinessGroup businessGroup, TaskList taskList, String dirName, ZipOutputStream exportStream) {
		ModuleConfiguration config = getModuleConfiguration();
		GTAManager gtaManager = CoreSpringFactory.getImpl(GTAManager.class);
		
		int flow = 0;//for beautiful ordering
		String groupDirName = dirName + "/"
				+ StringHelper.transformDisplayNameToFileSystemName(businessGroup.getName())
				+ "_" + businessGroup.getKey();
		
		Task task = gtaManager.getTask(businessGroup, taskList);
		if(task != null && config.getBooleanSafe(GTASK_ASSIGNMENT)) {
			File taskDirectory = gtaManager.getTasksDirectory(course.getCourseEnvironment(), this);
			File taskFile = new File(taskDirectory, task.getTaskName());
			if(taskFile.exists()) {
				String path = groupDirName + "/"  + (++flow) + "_task/" + taskFile.getName(); 
				ZipUtil.addFileToZip(path, taskFile, exportStream);
			}
		}
		
		if(config.getBooleanSafe(GTASK_SUBMIT)) {
			File submitDirectory = gtaManager.getSubmitDirectory(course.getCourseEnvironment(), this, businessGroup);
			String submissionDirName = groupDirName + "/" + (++flow) + "_submissions";
			ZipUtil.addDirectoryToZip(submitDirectory.toPath(), submissionDirName, exportStream);
		}

		if(config.getBooleanSafe(GTACourseNode.GTASK_REVIEW_AND_CORRECTION)) {
			File correctionsDir = gtaManager.getCorrectionDirectory(course.getCourseEnvironment(), this, businessGroup);
			String correctionDirName = groupDirName + "/" + (++flow) + "_corrections";
			ZipUtil.addDirectoryToZip(correctionsDir.toPath(), correctionDirName, exportStream);
		}
		
		if(task != null && config.getBooleanSafe(GTACourseNode.GTASK_REVISION_PERIOD)) {
			int numOfIteration = task.getRevisionLoop();
			for(int i=1; i<=numOfIteration; i++) {
				File revisionDirectory = gtaManager.getRevisedDocumentsDirectory(course.getCourseEnvironment(), this, i, businessGroup);
				String revisionDirName = groupDirName + "/" + (++flow) + "_revisions_" + i;
				ZipUtil.addDirectoryToZip(revisionDirectory.toPath(), revisionDirName, exportStream);
				
				File correctionDirectory = gtaManager.getRevisedDocumentsCorrectionsDirectory(course.getCourseEnvironment(), this, i, businessGroup);
				String correctionDirName = groupDirName + "/" + (++flow) + "_corrections_" + i;
				ZipUtil.addDirectoryToZip(correctionDirectory.toPath(), correctionDirName, exportStream);
			}
		}
	}
	
	@Override
	public void cleanupOnDelete(ICourse course) {
		GTAManager gtaManager = CoreSpringFactory.getImpl(GTAManager.class);
		//tasks
		File taskDirectory = gtaManager.getTasksDirectory(course.getCourseEnvironment(), this);
		FileUtils.deleteDirsAndFiles(taskDirectory, true, true);
		//TODO the rest
		
		//solutions
		File solutionsDirectory = gtaManager.getSolutionsDirectory(course.getCourseEnvironment(), this);
		FileUtils.deleteDirsAndFiles(solutionsDirectory, true, true);
	}
	
	@Override
	public boolean hasStatusConfigured() {
		return true; // Task Course node has always a status-field
	}

	@Override
	public Float getMaxScoreConfiguration() {
		if (!hasScoreConfigured()) {
			throw new OLATRuntimeException(TACourseNode.class, "getMaxScore not defined when hasScore set to false", null);
		}
		return getModuleConfiguration().getFloatEntry(MSCourseNode.CONFIG_KEY_SCORE_MAX);
	}

	@Override
	public Float getMinScoreConfiguration() {
		if (!hasScoreConfigured()) {
			throw new OLATRuntimeException(TACourseNode.class, "getMinScore not defined when hasScore set to false", null);
		}
		return getModuleConfiguration().getFloatEntry(MSCourseNode.CONFIG_KEY_SCORE_MIN);
	}

	@Override
	public Float getCutValueConfiguration() {
		if (!hasPassedConfigured()) {
			throw new OLATRuntimeException(TACourseNode.class, "getCutValue not defined when hasPassed set to false", null);
		}
		return getModuleConfiguration().getFloatEntry(MSCourseNode.CONFIG_KEY_PASSED_CUT_VALUE);
	}

	@Override
	public boolean hasScoreConfigured() {
		boolean hasGrading = getModuleConfiguration().getBooleanSafe(GTASK_GRADING);
		if (hasGrading) {
			Boolean score = (Boolean) getModuleConfiguration().get(MSCourseNode.CONFIG_KEY_HAS_SCORE_FIELD);
			if (score != null) {
				return score.booleanValue();
			}
		}
		return false;
	}

	@Override
	public boolean hasPassedConfigured() {
		boolean hasGrading = getModuleConfiguration().getBooleanSafe(GTASK_GRADING);
		if (hasGrading) {
			Boolean passed = (Boolean)getModuleConfiguration().get(MSCourseNode.CONFIG_KEY_HAS_PASSED_FIELD);
			if (passed != null) {
				return passed.booleanValue();
			}
		}
		return false;
	}

	@Override
	public boolean hasCommentConfigured() {
		boolean hasGrading = getModuleConfiguration().getBooleanSafe(GTASK_GRADING);
		if (hasGrading) {
			Boolean comment = (Boolean) getModuleConfiguration().get(MSCourseNode.CONFIG_KEY_HAS_COMMENT_FIELD);
			if (comment != null) {
				return comment.booleanValue();
			}
		}
		return false;
	}

	@Override
	public boolean hasAttemptsConfigured(){
		return true;
	}

	@Override
	public boolean hasDetails() {
		ModuleConfiguration config =  getModuleConfiguration();
		return config.getBooleanSafe(GTASK_ASSIGNMENT)
				|| config.getBooleanSafe(GTASK_SUBMIT)
				|| config.getBooleanSafe(GTASK_REVIEW_AND_CORRECTION)
				|| config.getBooleanSafe(GTASK_REVISION_PERIOD);
	}

	@Override
	public boolean isEditableConfigured() {
		return true;
	}
	
	@Override
	public TabbableController createEditController(UserRequest ureq, WindowControl wControl, BreadcrumbPanel stackPanel,
			ICourse course, UserCourseEnvironment euce) {
		GTAEditController editCtrl = new GTAEditController(ureq, wControl, this, course, euce);
		CourseNode chosenNode = course.getEditorTreeModel().getCourseNode(euce.getCourseEditorEnv().getCurrentCourseNodeId());
		return new NodeEditController(ureq, wControl, course.getEditorTreeModel(), course, chosenNode, euce, editCtrl);
	}

	@Override
	public NodeRunConstructionResult createNodeRunConstructionResult(UserRequest ureq, WindowControl wControl,
			UserCourseEnvironment userCourseEnv, NodeEvaluation ne, String nodecmd) {
		
		Controller controller;
		Roles roles = ureq.getUserSession().getRoles();
		if (roles.isGuestOnly()) {
			Translator trans = Util.createPackageTranslator(GTACourseNode.class, ureq.getLocale());
			String title = trans.translate("guestnoaccess.title");
			String message = trans.translate("guestnoaccess.message");
			controller = MessageUIFactory.createInfoMessage(ureq, wControl, title, message);
		} else {
			controller = new GTARunController(ureq, wControl, this, userCourseEnv);
		}
		 
		Controller ctrl = TitledWrapperHelper.getWrapper(ureq, wControl, controller, this, "o_gta_icon");
		return new NodeRunConstructionResult(ctrl);
	}

	@Override
	public void updateOnPublish(Locale locale, ICourse course, Identity publisher, PublishEvents publishEvents) {
		RepositoryEntry re = course.getCourseEnvironment().getCourseGroupManager().getCourseEntry();
		CoreSpringFactory.getImpl(GTAManager.class).createIfNotExists(re, this);
		super.updateOnPublish(locale, course, publisher, publishEvents);
	}

	@Override
	public String getDetailsListViewHeaderKey() {
		return "table.header.details.gta";
	}
	
	@Override
	public String getDetailsListView(UserCourseEnvironment userCourseEnvironment) {
		GTAManager gtaManager = CoreSpringFactory.getImpl(GTAManager.class);
		Identity assessedIdentity = userCourseEnvironment.getIdentityEnvironment().getIdentity();
		RepositoryEntry entry = userCourseEnvironment.getCourseEnvironment().getCourseGroupManager().getCourseEntry();
		List<Task> tasks = gtaManager.getTasks(assessedIdentity, entry, this);
		String details;
		if(tasks == null || tasks.isEmpty()) {
			details = null;
		} else {
			StringBuilder sb = new StringBuilder();
			for(Task task:tasks) {
				if(sb.length() > 0) sb.append(", ");
				if(sb.length() > 64) {
					sb.append("...");
					break;
				}
				sb.append(StringHelper.escapeHtml(task.getTaskName()));
			}
			details = sb.toString();
		}
		return details;
	}

	@Override
	public Controller getDetailsEditController(UserRequest ureq, WindowControl wControl,
			BreadcrumbPanel stackPanel, UserCourseEnvironment userCourseEnvironment) {
		return new GTAAssessmentDetailsController(ureq, wControl, userCourseEnvironment, this);
	}

	@Override
	public List<Controller> createAssessmentTools(UserRequest ureq, WindowControl wControl,
			TooledStackedPanel stackPanel, CourseEnvironment courseEnv, AssessmentToolOptions options) {
		
		ModuleConfiguration config =  getModuleConfiguration();
		List<Controller> tools = new ArrayList<>(1);
		if(options.getGroup() != null && GTAType.group.name().equals(config.getStringValue(GTACourseNode.GTASK_TYPE))
			&& (config.getBooleanSafe(GTASK_ASSIGNMENT)
				|| config.getBooleanSafe(GTASK_SUBMIT)
				|| config.getBooleanSafe(GTASK_REVIEW_AND_CORRECTION)
				|| config.getBooleanSafe(GTASK_REVISION_PERIOD))) {
			
			Controller tool = new GTAGroupAssessmentToolController(ureq, wControl, courseEnv, options.getGroup(), this);
			tools.add(tool);		
		}
		return tools;
	}

	@Override
	public ScoreEvaluation getUserScoreEvaluation(UserCourseEnvironment userCourseEnv) {
		AssessmentManager am = userCourseEnv.getCourseEnvironment().getAssessmentManager();
		Identity assessedIdentity = userCourseEnv.getIdentityEnvironment().getIdentity();
		Boolean passed = null;
		Float score = null;
		// only db lookup if configured, else return null
		if (hasPassedConfigured()) {
			passed = am.getNodePassed(this, assessedIdentity);
		}
		if (hasScoreConfigured()) {
			score = am.getNodeScore(this, assessedIdentity);
		}
		return new ScoreEvaluation(score, passed);
	}

	@Override
	public String getUserUserComment(UserCourseEnvironment userCourseEnv) {
		AssessmentManager am = userCourseEnv.getCourseEnvironment().getAssessmentManager();
		return am.getNodeComment(this, userCourseEnv.getIdentityEnvironment().getIdentity());
	}

	@Override
	public String getUserCoachComment(UserCourseEnvironment userCourseEnv) {
		AssessmentManager am = userCourseEnv.getCourseEnvironment().getAssessmentManager();
		return am.getNodeCoachComment(this, userCourseEnv.getIdentityEnvironment().getIdentity());
	}

	@Override
	public String getUserLog(UserCourseEnvironment userCourseEnv) {
		UserNodeAuditManager am = userCourseEnv.getCourseEnvironment().getAuditManager();
		return am.getUserNodeLog(this, userCourseEnv.getIdentityEnvironment().getIdentity());
	}

	@Override
	public Integer getUserAttempts(UserCourseEnvironment userCourseEnv) {
		AssessmentManager am = userCourseEnv.getCourseEnvironment().getAssessmentManager();
		Identity assessedIdentity = userCourseEnv.getIdentityEnvironment().getIdentity();
		return am.getNodeAttempts(this, assessedIdentity);
	}

	@Override
	public void updateUserScoreEvaluation(ScoreEvaluation scoreEvaluation, UserCourseEnvironment userCourseEnv,
			Identity coachingIdentity, boolean incrementAttempts) {
		AssessmentManager am = userCourseEnv.getCourseEnvironment().getAssessmentManager();
		Identity assessedIdentity = userCourseEnv.getIdentityEnvironment().getIdentity();
		am.saveScoreEvaluation(this, coachingIdentity, assessedIdentity, new ScoreEvaluation(scoreEvaluation.getScore(), scoreEvaluation.getPassed()), userCourseEnv, incrementAttempts);
	}

	@Override
	public void updateUserUserComment(String userComment, UserCourseEnvironment userCourseEnv, Identity coachingIdentity) {
		AssessmentManager am = userCourseEnv.getCourseEnvironment().getAssessmentManager();
		Identity assessedIdentity = userCourseEnv.getIdentityEnvironment().getIdentity();
		if (userComment != null) {
			am.saveNodeComment(this, coachingIdentity, assessedIdentity, userComment);
		}
	}

	@Override
	public void incrementUserAttempts(UserCourseEnvironment userCourseEnv) {
		AssessmentManager am = userCourseEnv.getCourseEnvironment().getAssessmentManager();
		Identity assessedIdentity = userCourseEnv.getIdentityEnvironment().getIdentity();
		am.incrementNodeAttempts(this, assessedIdentity, userCourseEnv);
	}

	@Override
	public void updateUserAttempts(Integer userAttempts, UserCourseEnvironment userCourseEnv, Identity coachingIdentity) {
		if (userAttempts != null) {
			AssessmentManager am = userCourseEnv.getCourseEnvironment().getAssessmentManager();
			Identity assessedIdentity = userCourseEnv.getIdentityEnvironment().getIdentity();
			am.saveNodeAttempts(this, coachingIdentity, assessedIdentity, userAttempts);
		}
	}

	@Override
	public void updateUserCoachComment(String coachComment, UserCourseEnvironment userCourseEnv) {
		AssessmentManager am = userCourseEnv.getCourseEnvironment().getAssessmentManager();
		Identity assessedIdentity = userCourseEnv.getIdentityEnvironment().getIdentity();
		if (coachComment != null) {
			am.saveNodeCoachComment(this, assessedIdentity, coachComment);
		}
	}
}