/** * <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.assessment.bulk.BulkAssessmentToolController; 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.export.CourseEnvironmentMapper; 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.model.TaskDefinition; import org.olat.course.nodes.gta.model.TaskDefinitionList; import org.olat.course.nodes.gta.ui.BulkDownloadToolController; 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_ASSIGNMENT_DEADLINE_RELATIVE = "grouptask.assignment.deadline.relative"; public static final String GTASK_ASSIGNMENT_DEADLINE_RELATIVE_TO = "grouptask.assignment.deadline.relative.to"; public static final String GTASK_SUBMIT = "grouptask.submit"; public static final String GTASK_SUBMIT_DEADLINE = "grouptask.submit.deadline"; public static final String GTASK_SUBMIT_DEADLINE_RELATIVE = "grouptask.submit.deadline.relative"; public static final String GTASK_SUBMIT_DEADLINE_RELATIVE_TO = "grouptask.submit.deadline.relative.to"; 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_SAMPLE_SOLUTION_VISIBLE_AFTER_RELATIVE = "grouptask.solution.visible.after.relative"; public static final String GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER_RELATIVE_TO = "grouptask.solution.visible.after.relative.to"; public static final String GTASK_GRADING = "grouptask.grading"; public static final String GTASK_TASKS = "grouptask.tasks"; public static final String GTASK_RELATIVE_DATES = "grouptask.rel.dates"; 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"; public static final String TYPE_GROUP = "gta"; public static final String TYPE_INDIVIDUAL = "ita"; public GTACourseNode() { super(TYPE_GROUP); updateModuleConfigDefaults(true); } public GTACourseNode(String type) { 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 if(getType().equals(TYPE_INDIVIDUAL)) { config.setStringValue(GTASK_TYPE, GTAType.individual.name()); } else { 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); config.set(MSCourseNode.CONFIG_KEY_HAS_COMMENT_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_PASSED_FIELD) && !config.getBooleanSafe(MSCourseNode.CONFIG_KEY_HAS_COMMENT_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); } else { TaskDefinitionList taskList = (TaskDefinitionList)config.get(GTACourseNode.GTASK_TASKS); if(taskList == null || taskList.getTasks() == null || taskList.getTasks().isEmpty()) { addStatusErrorDescription("error.missing.tasks", GTAEditController.PANE_TAB_ASSIGNMENT, sdList); } else { String[] filenames = taskDirectory.list(); for(TaskDefinition taskDef: taskList.getTasks()) { boolean found = false; for(String filename:filenames) { if(filename.equals(taskDef.getFilename())) { found = true; break; } } if(!found) { addStatusWarningDescription("error.missing.file", 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); } private void addStatusWarningDescription(String key, String pane, List<StatusDescription> status) { String[] params = new String[] { getShortTitle() }; StatusDescription sd = new StatusDescription(StatusDescription.WARNING, 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"); RepositoryEntry entry = course.getCourseEnvironment().getCourseGroupManager().getCourseEntry(); gtaManager.createIfNotExists(entry, this); } @Override public void postCopy(CourseEnvironmentMapper envMapper, Processing processType, ICourse course, ICourse sourceCourse) { super.postCopy(envMapper, processType, course, sourceCourse); GTAManager gtaManager = CoreSpringFactory.getImpl(GTAManager.class); //copy tasks File sourceTaskDirectory = gtaManager.getTasksDirectory(sourceCourse.getCourseEnvironment(), this); File copyTaskDirectory = gtaManager.getTasksDirectory(course.getCourseEnvironment(), this); FileUtils.copyDirContentsToDir(sourceTaskDirectory, copyTaskDirectory, false, "copy task course node"); //copy solutions File sourceSolutionsDirectory = gtaManager.getSolutionsDirectory(sourceCourse.getCourseEnvironment(), this); File copySolutionsDirectory = gtaManager.getSolutionsDirectory(course.getCourseEnvironment(), this); FileUtils.copyDirContentsToDir(sourceSolutionsDirectory, copySolutionsDirectory, false, "copy task course node solutions"); RepositoryEntry entry = course.getCourseEnvironment().getCourseGroupManager().getCourseEntry(); gtaManager.createIfNotExists(entry, this); } @Override public CourseNode createInstanceForCopy(boolean isNewTitle, ICourse course, Identity author) { GTACourseNode cNode = (GTACourseNode)super.createInstanceForCopy(isNewTitle, course, author); 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) { final GTAManager gtaManager = CoreSpringFactory.getImpl(GTAManager.class); final ModuleConfiguration config = getModuleConfiguration(); String prefix; if(GTAType.group.name().equals(config.getStringValue(GTACourseNode.GTASK_TYPE))) { prefix = "grouptask_"; } else { prefix = "ita_"; } String dirName = prefix + 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; 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 != null && options.getGroup() != null) { selectedGroups = Collections.singletonList(options.getGroup()); } else { selectedGroups = gtaManager.getBusinessGroups(this); } for(BusinessGroup businessGroup:selectedGroups) { archiveNodeData(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(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(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(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); //solutions File solutionsDirectory = gtaManager.getSolutionsDirectory(course.getCourseEnvironment(), this); FileUtils.deleteDirsAndFiles(solutionsDirectory, true, true); //clean up database RepositoryEntry entry = course.getCourseEnvironment().getCourseGroupManager().getCourseEntry(); gtaManager.deleteTaskList(entry, this); } @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() { if(getModuleConfiguration().getBooleanSafe(GTASK_ASSIGNMENT)) { return "table.header.details.gta"; } return null; } @Override public String getDetailsListView(UserCourseEnvironment userCourseEnvironment) { String details; if(getModuleConfiguration().getBooleanSafe(GTASK_ASSIGNMENT)) { 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); 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; } String taskName = task.getTaskName(); if(StringHelper.containsNonWhitespace(taskName)) { sb.append(StringHelper.escapeHtml(taskName)); } } details = sb.length() == 0 ? null : sb.toString(); } } else { details = null; } 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<>(2); if(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))) { if(options.getGroup() != null) { tools.add(new GTAGroupAssessmentToolController(ureq, wControl, courseEnv, options.getGroup(), this)); } tools.add(new BulkDownloadToolController(ureq, wControl, courseEnv, options, this)); } else if(GTAType.individual.name().equals(config.getStringValue(GTACourseNode.GTASK_TYPE))) { if(config.getBooleanSafe(GTASK_REVIEW_AND_CORRECTION) || config.getBooleanSafe(GTASK_GRADING)){ tools.add(new BulkAssessmentToolController(ureq, wControl, courseEnv, this)); } if(config.getBooleanSafe(GTASK_ASSIGNMENT) || config.getBooleanSafe(GTASK_SUBMIT) || config.getBooleanSafe(GTASK_REVIEW_AND_CORRECTION) || config.getBooleanSafe(GTASK_REVISION_PERIOD)) { tools.add(new BulkDownloadToolController(ureq, wControl, courseEnv, options, this)); } } 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); } } }