/**
* <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.gta.ui;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import org.olat.basesecurity.GroupRoles;
import org.olat.core.commons.services.notifications.PublisherData;
import org.olat.core.commons.services.notifications.SubscriptionContext;
import org.olat.core.commons.services.notifications.ui.ContextualSubscriptionController;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.components.Component;
import org.olat.core.gui.components.velocity.VelocityContainer;
import org.olat.core.gui.control.Event;
import org.olat.core.gui.control.WindowControl;
import org.olat.core.gui.control.controller.BasicController;
import org.olat.core.id.Identity;
import org.olat.core.id.OLATResourceable;
import org.olat.core.util.Formatter;
import org.olat.core.util.StringHelper;
import org.olat.core.util.coordinate.CoordinatorManager;
import org.olat.core.util.event.GenericEventListener;
import org.olat.core.util.resource.OresHelper;
import org.olat.course.CourseFactory;
import org.olat.course.CourseModule;
import org.olat.course.ICourse;
import org.olat.course.assessment.AssessmentHelper;
import org.olat.course.assessment.manager.UserCourseInformationsManager;
import org.olat.course.nodes.GTACourseNode;
import org.olat.course.nodes.gta.GTAManager;
import org.olat.course.nodes.gta.GTARelativeToDates;
import org.olat.course.nodes.gta.GTAType;
import org.olat.course.nodes.gta.Task;
import org.olat.course.nodes.gta.TaskList;
import org.olat.course.nodes.gta.TaskProcess;
import org.olat.course.nodes.gta.ui.events.TaskMultiUserEvent;
import org.olat.course.run.environment.CourseEnvironment;
import org.olat.course.run.userview.UserCourseEnvironment;
import org.olat.group.BusinessGroup;
import org.olat.group.BusinessGroupService;
import org.olat.modules.ModuleConfiguration;
import org.olat.repository.RepositoryEntry;
import org.olat.repository.RepositoryService;
import org.olat.repository.model.RepositoryEntryLifecycle;
import org.springframework.beans.factory.annotation.Autowired;
/**
*
* Initial date: 20.03.2015<br>
* @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
*
*/
public abstract class GTAAbstractController extends BasicController implements GenericEventListener {
protected VelocityContainer mainVC;
protected Identity assessedIdentity;
protected BusinessGroup assessedGroup;
protected CourseEnvironment courseEnv;
protected UserCourseEnvironment userCourseEnv;
protected final TaskList taskList;
protected final GTACourseNode gtaNode;
protected final ModuleConfiguration config;
protected final RepositoryEntry courseEntry;
protected final boolean withSubscription;
protected final PublisherData publisherData;
protected final SubscriptionContext subsContext;
protected final boolean withTitle;
protected final boolean withGrading;
protected final boolean businessGroupTask;
protected final OLATResourceable taskListEventResource;
protected GTAStepPreferences stepPreferences;
private ContextualSubscriptionController contextualSubscriptionCtr;
private DueDate assignmentDueDate;
private DueDate submissionDueDate;
private DueDate solutionDueDate;
@Autowired
protected GTAManager gtaManager;
@Autowired
protected RepositoryService repositoryService;
@Autowired
protected BusinessGroupService businessGroupService;
@Autowired
protected UserCourseInformationsManager userCourseInformationsManager;
@Autowired
protected CourseModule courseModule;
public GTAAbstractController(UserRequest ureq, WindowControl wControl,
GTACourseNode gtaNode, CourseEnvironment courseEnv, boolean withTitle, boolean withGrading, boolean withSubscription) {
this(ureq, wControl, gtaNode, courseEnv, null, null, null, withTitle, withGrading, withSubscription);
}
public GTAAbstractController(UserRequest ureq, WindowControl wControl,
GTACourseNode gtaNode, CourseEnvironment courseEnv, UserCourseEnvironment userCourseEnv, boolean withTitle, boolean withGrading, boolean withSubscription) {
this(ureq, wControl, gtaNode, courseEnv, userCourseEnv, null, null, withTitle, withGrading, withSubscription);
}
public GTAAbstractController(UserRequest ureq, WindowControl wControl,
GTACourseNode gtaNode, CourseEnvironment courseEnv, UserCourseEnvironment userCourseEnv,
BusinessGroup assessedGroup, Identity assessedIdentity, boolean withTitle, boolean withGrading, boolean withSubscription) {
super(ureq, wControl);
this.withTitle = withTitle;
this.withGrading = withGrading;
this.withSubscription = withSubscription;
this.gtaNode = gtaNode;
this.config = gtaNode.getModuleConfiguration();
this.userCourseEnv = userCourseEnv;
this.courseEnv = courseEnv;
this.courseEntry = courseEnv.getCourseGroupManager().getCourseEntry();
this.assessedIdentity = assessedIdentity;
this.assessedGroup = assessedGroup;
businessGroupTask = GTAType.group.name().equals(config.getStringValue(GTACourseNode.GTASK_TYPE));
taskList = gtaManager.createIfNotExists(courseEntry, gtaNode);
publisherData = gtaManager.getPublisherData(courseEnv, gtaNode);
subsContext = gtaManager.getSubscriptionContext(courseEnv, gtaNode);
stepPreferences = (GTAStepPreferences)ureq.getUserSession()
.getGuiPreferences()
.get(GTAStepPreferences.class, taskList.getKey().toString());
if(stepPreferences == null) {
stepPreferences = new GTAStepPreferences();
}
taskListEventResource = OresHelper.createOLATResourceableInstance("GTaskList", taskList.getKey());
CoordinatorManager.getInstance().getCoordinator()
.getEventBus().registerFor(this, getIdentity(), taskListEventResource);
}
@Override
protected void doDispose() {
CoordinatorManager.getInstance().getCoordinator()
.getEventBus().deregisterFor(this, taskListEventResource);
}
@Override
public void event(Event event) {
if(event instanceof TaskMultiUserEvent) {
TaskMultiUserEvent ste = (TaskMultiUserEvent)event;
if(!getIdentity().getKey().equals(ste.getEmitterKey())
&& ((assessedGroup != null && assessedGroup.getKey().equals(ste.getForGroupKey()))
|| (assessedIdentity != null && assessedIdentity.getKey().equals(ste.getForIdentityKey())))) {
processEvent(ste);
}
}
}
protected abstract void processEvent(TaskMultiUserEvent event);
protected abstract void initContainer(UserRequest ureq);
protected final void process(UserRequest ureq) {
Task task;
if(businessGroupTask) {
task = gtaManager.getTask(assessedGroup, taskList);
} else {
task = gtaManager.getTask(assessedIdentity, taskList);
}
if (withSubscription && subsContext != null) {
contextualSubscriptionCtr = new ContextualSubscriptionController(ureq, getWindowControl(), subsContext, publisherData);
listenTo(contextualSubscriptionCtr);
mainVC.put("contextualSubscription", contextualSubscriptionCtr.getInitialComponent());
}
boolean assignment = config.getBooleanSafe(GTACourseNode.GTASK_ASSIGNMENT);
mainVC.contextPut("assignmentEnabled", assignment);
if(assignment) {
task = stepAssignment(ureq, task);
} else if(task == null) {
TaskProcess firstStep = gtaManager.firstStep(gtaNode);
task = gtaManager.createTask(null, taskList, firstStep, assessedGroup, assessedIdentity, gtaNode);
}
boolean submit = config.getBooleanSafe(GTACourseNode.GTASK_SUBMIT);
mainVC.contextPut("submitEnabled", submit);
if(submit) {
task = stepSubmit(ureq, task);
} else if(task != null && task.getTaskStatus() == TaskProcess.submit) {
task = gtaManager.nextStep(task, gtaNode);
}
boolean reviewAndCorrection = config.getBooleanSafe(GTACourseNode.GTASK_REVIEW_AND_CORRECTION);
mainVC.contextPut("reviewAndCorrectionEnabled", reviewAndCorrection);
if(reviewAndCorrection) {
task = stepReviewAndCorrection(ureq, task);
} else if(task != null && task.getTaskStatus() == TaskProcess.review) {
task = gtaManager.nextStep(task, gtaNode);
}
boolean revision = config.getBooleanSafe(GTACourseNode.GTASK_REVISION_PERIOD);
mainVC.contextPut("revisionEnabled", reviewAndCorrection && revision);
if(reviewAndCorrection && revision) {
task = stepRevision(ureq, task);
} else if(task != null && (task.getTaskStatus() == TaskProcess.revision || task.getTaskStatus() == TaskProcess.correction)) {
task = gtaManager.nextStep(task, gtaNode);
}
boolean solution = config.getBooleanSafe(GTACourseNode.GTASK_SAMPLE_SOLUTION);
mainVC.contextPut("solutionEnabled", solution);
if(solution) {
stepSolution(ureq, task);
} else if(task != null && task.getTaskStatus() == TaskProcess.solution) {
task = gtaManager.nextStep(task, gtaNode);
}
boolean grading = config.getBooleanSafe(GTACourseNode.GTASK_GRADING);
mainVC.contextPut("gradingEnabled", grading);
if(grading) {
stepGrading(ureq, task);
} else if(task != null && task.getTaskStatus() == TaskProcess.grading) {
task = gtaManager.nextStep(task, gtaNode);
}
mainVC.contextPut("changelogconfig", courseModule.isDisplayChangeLog());
nodeLog();
collapsedContents(task);
}
protected final void collapsedContents(Task currentTask) {
TaskProcess status = null;
TaskProcess previousStatus = null;
if(currentTask != null) {
status = currentTask.getTaskStatus();
previousStatus = gtaManager.previousStep(status, gtaNode);
}
boolean assignment = Boolean.TRUE.equals(stepPreferences.getAssignement())
|| TaskProcess.assignment.equals(status) || TaskProcess.assignment.equals(previousStatus);
mainVC.contextPut("collapse_assignement", new Boolean(assignment));
boolean submit = Boolean.TRUE.equals(stepPreferences.getSubmit())
|| TaskProcess.submit.equals(status) || TaskProcess.submit.equals(previousStatus);
mainVC.contextPut("collapse_submit", new Boolean(submit));
boolean reviewAndCorrection = Boolean.TRUE.equals(stepPreferences.getReviewAndCorrection())
|| TaskProcess.review.equals(status) || TaskProcess.review.equals(previousStatus);
mainVC.contextPut("collapse_reviewAndCorrection", new Boolean(reviewAndCorrection));
boolean revision = Boolean.TRUE.equals(stepPreferences.getRevision())
|| TaskProcess.revision.equals(status) || TaskProcess.revision.equals(previousStatus)
|| TaskProcess.correction.equals(status) || TaskProcess.correction.equals(previousStatus);
mainVC.contextPut("collapse_revision", new Boolean(revision));
boolean solution = Boolean.TRUE.equals(stepPreferences.getSolution())
|| TaskProcess.solution.equals(status) || TaskProcess.solution.equals(previousStatus);
mainVC.contextPut("collapse_solution", new Boolean(solution));
boolean grading = Boolean.TRUE.equals(stepPreferences.getGrading())
|| TaskProcess.grading.equals(status) || TaskProcess.grading.equals(previousStatus);
mainVC.contextPut("collapse_grading", new Boolean(grading));
}
protected Task stepAssignment(@SuppressWarnings("unused") UserRequest ureq, Task assignedTask) {
DueDate dueDate = getAssignementDueDate(assignedTask);
if(dueDate != null) {
if(dueDate.getDueDate() != null) {
Date date = dueDate.getDueDate();
String dateAsString = formatDueDate(dueDate, true);
mainVC.contextPut("assignmentDueDate", dateAsString);
mainVC.contextRemove("assignmentDueDateMsg");
if(assignedTask != null && assignedTask.getTaskStatus() == TaskProcess.assignment
&& date.compareTo(new Date()) < 0) {
//push to the next step
assignedTask = gtaManager.nextStep(assignedTask, gtaNode);
}
} else if(dueDate.getMessage() != null) {
mainVC.contextPut("assignmentDueDateMsg", dueDate.getMessage());
mainVC.contextRemove("assignmentDueDate");
}
}
return assignedTask;
}
/**
* User friendly format, 2015-06-20 00:00 will be rendered as 2015-06-20
* if @param userDeadLine is false (for solution,e.g) and 2015-06-20
* if @param userDeadLine is true (meaning the user have the whole day
* to do the job until the deadline at midnight).
* @param dueDate
* @param user deadline
* @return
*/
protected String formatDueDate(DueDate dueDate, boolean userDeadLine) {
Date date = dueDate.getDueDate();
Calendar cal = Calendar.getInstance();
cal.setTime(date);
String formattedDate;
if(cal.get(Calendar.HOUR_OF_DAY) == 0 && cal.get(Calendar.MINUTE) == 0) {
if(userDeadLine) {
cal.add(Calendar.DATE, -1);
}
formattedDate = Formatter.getInstance(getLocale()).formatDate(cal.getTime());
} else {
formattedDate = Formatter.getInstance(getLocale()).formatDateAndTime(date);
}
return formattedDate;
}
protected void resetDueDates() {
assignmentDueDate = null;
submissionDueDate = null;
solutionDueDate = null;
}
protected DueDate getAssignementDueDate(Task task) {
if(assignmentDueDate == null) {
Date dueDate = gtaNode.getModuleConfiguration().getDateValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE);
boolean relativeDate = gtaNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_RELATIVE_DATES);
if(relativeDate) {
int numOfDays = gtaNode.getModuleConfiguration().getIntegerSafe(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE, -1);
String relativeTo = gtaNode.getModuleConfiguration().getStringValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE_TO);
if(numOfDays >= 0 && StringHelper.containsNonWhitespace(relativeTo)) {
assignmentDueDate = getReferenceDate(numOfDays, relativeTo, task);
}
} else if(dueDate != null) {
assignmentDueDate = new DueDate(dueDate);
}
}
return assignmentDueDate;
}
protected DueDate getReferenceDate(int numOfDays, String relativeTo, Task assignedTask) {
DueDate dueDate = null;
if(numOfDays >= 0 && StringHelper.containsNonWhitespace(relativeTo)) {
GTARelativeToDates rel = GTARelativeToDates.valueOf(relativeTo);
Date referenceDate = null;
String message = null;
switch(rel) {
case courseStart: {
RepositoryEntryLifecycle lifecycle = courseEntry.getLifecycle();
if(lifecycle != null && lifecycle.getValidFrom() != null) {
referenceDate = lifecycle.getValidFrom();
}
break;
}
case courseLaunch: {
referenceDate = userCourseInformationsManager
.getInitialLaunchDate(courseEnv.getCourseGroupManager().getCourseResource(), assessedIdentity);
break;
}
case enrollment: {
referenceDate = repositoryService
.getEnrollmentDate(courseEntry, assessedIdentity);
break;
}
case assignment: {
if(assignedTask != null) {
referenceDate = assignedTask.getAssignmentDate();
} else {
message = translate("relative.to.assignment.message", Integer.toString(numOfDays));
}
break;
}
}
if(referenceDate != null) {
Calendar cal = Calendar.getInstance();
cal.setTime(referenceDate);
cal.add(Calendar.DATE, numOfDays);
dueDate = new DueDate(cal.getTime());
} else if(message != null) {
dueDate = new DueDate(message);
}
}
return dueDate;
}
protected Task stepSubmit(@SuppressWarnings("unused")UserRequest ureq, Task assignedTask) {
DueDate dueDate = getSubmissionDueDate(assignedTask);
if(dueDate != null) {
if(dueDate.getDueDate() != null) {
Date date = dueDate.getDueDate();
String dateAsString = formatDueDate(dueDate, true);
mainVC.contextPut("submitDueDate", dateAsString);
mainVC.contextRemove("submitDueDateMsg");
if(assignedTask != null && assignedTask.getTaskStatus() == TaskProcess.submit
&& date.compareTo(new Date()) < 0) {
//push to the next step
assignedTask = gtaManager.nextStep(assignedTask, gtaNode);
doUpdateAttempts();
}
} else if(dueDate.getMessage() != null) {
mainVC.contextPut("submitDueDateMsg", dueDate.getMessage());
mainVC.contextRemove("submitDueDate");
}
}
return assignedTask;
}
protected DueDate getSubmissionDueDate(Task assignedTask) {
if(submissionDueDate == null) {
Date dueDate = gtaNode.getModuleConfiguration().getDateValue(GTACourseNode.GTASK_SUBMIT_DEADLINE);
boolean relativeDate = gtaNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_RELATIVE_DATES);
if(relativeDate) {
int numOfDays = gtaNode.getModuleConfiguration().getIntegerSafe(GTACourseNode.GTASK_SUBMIT_DEADLINE_RELATIVE, -1);
String relativeTo = gtaNode.getModuleConfiguration().getStringValue(GTACourseNode.GTASK_SUBMIT_DEADLINE_RELATIVE_TO);
if(numOfDays >= 0 && StringHelper.containsNonWhitespace(relativeTo)) {
submissionDueDate = getReferenceDate(numOfDays, relativeTo, assignedTask);
}
} else if(dueDate != null) {
submissionDueDate = new DueDate(dueDate);
}
}
return submissionDueDate;
}
protected Task stepReviewAndCorrection(@SuppressWarnings("unused")UserRequest ureq, Task assignedTask) {
return assignedTask;
}
protected Task stepRevision(@SuppressWarnings("unused")UserRequest ureq, Task assignedTask) {
return assignedTask;
}
protected Task stepSolution(@SuppressWarnings("unused")UserRequest ureq, Task assignedTask) {
DueDate availableDate = getSolutionDueDate(assignedTask);
if(availableDate != null) {
if(availableDate.getDueDate() != null) {
String date = formatDueDate(availableDate, false);
mainVC.contextPut("solutionAvailableDate", date);
mainVC.contextRemove("solutionAvailableDateMsg");
} else if(availableDate.getMessage() != null) {
mainVC.contextPut("solutionAvailableDateMsg", availableDate.getMessage());
mainVC.contextRemove("solutionAvailableDate");
}
}
return assignedTask;
}
protected DueDate getSolutionDueDate(Task assignedTask) {
if(solutionDueDate == null) {
boolean relativeDate = gtaNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_RELATIVE_DATES);
Date dueDate = gtaNode.getModuleConfiguration().getDateValue(GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER);
if(relativeDate) {
int numOfDays = gtaNode.getModuleConfiguration().getIntegerSafe(GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER_RELATIVE, -1);
String relativeTo = gtaNode.getModuleConfiguration().getStringValue(GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER_RELATIVE_TO);
if(numOfDays >= 0 && StringHelper.containsNonWhitespace(relativeTo)) {
solutionDueDate = getReferenceDate(numOfDays, relativeTo, assignedTask);
}
} else if(dueDate != null) {
solutionDueDate = new DueDate(dueDate);
}
}
return solutionDueDate;
}
protected Task stepGrading(@SuppressWarnings("unused") UserRequest ureq, Task assignedTask) {
return assignedTask;
}
protected void nodeLog() {
if(businessGroupTask) {
String groupLog = courseEnv.getAuditManager().getUserNodeLog(gtaNode, assessedGroup);
if(StringHelper.containsNonWhitespace(groupLog)) {
mainVC.contextPut("groupLog", groupLog);
} else {
mainVC.contextRemove("groupLog");
}
} else {
String userLog = courseEnv.getAuditManager().getUserNodeLog(gtaNode, assessedIdentity);
if(StringHelper.containsNonWhitespace(userLog)) {
mainVC.contextPut("userLog", userLog);
} else {
mainVC.contextRemove("userLog");
}
}
}
protected void doUpdateAttempts() {
if(businessGroupTask) {
List<Identity> identities = businessGroupService.getMembers(assessedGroup, GroupRoles.participant.name());
ICourse course = CourseFactory.loadCourse(courseEnv.getCourseGroupManager().getCourseEntry());
for(Identity identity:identities) {
UserCourseEnvironment uce = AssessmentHelper.createAndInitUserCourseEnvironment(identity, course);
gtaNode.incrementUserAttempts(uce);
}
} else {
UserCourseEnvironment assessedUserCourseEnv = getAssessedUserCourseEnvironment();
gtaNode.incrementUserAttempts(assessedUserCourseEnv);
}
}
protected UserCourseEnvironment getAssessedUserCourseEnvironment() {
if(userCourseEnv == null) {
ICourse course = CourseFactory.loadCourse(courseEnv.getCourseResourceableId());
userCourseEnv = AssessmentHelper.createAndInitUserCourseEnvironment(assessedIdentity, course);
}
return userCourseEnv;
}
@Override
protected void event(UserRequest ureq, Component source, Event event) {
if("show".equals(event.getCommand())) {
doShow(ureq);
} else if("hide".equals(event.getCommand())) {
doHide(ureq);
}
}
private void doShow(UserRequest ureq) {
String step = ureq.getParameter("step");
doSaveStepPreferences(ureq, step, Boolean.TRUE);
}
private void doHide(UserRequest ureq) {
String step = ureq.getParameter("step");
doSaveStepPreferences(ureq, step, Boolean.FALSE);
}
private void doSaveStepPreferences(UserRequest ureq, String step, Boolean showHide) {
if(step == null) return;
switch(step) {
case "assignment": stepPreferences.setAssignement(showHide); break;
case "submit": stepPreferences.setSubmit(showHide); break;
case "reviewAndCorrection": stepPreferences.setReviewAndCorrection(showHide); break;
case "revision": stepPreferences.setRevision(showHide); break;
case "solution": stepPreferences.setSolution(showHide); break;
case "grading": stepPreferences.setGrading(showHide); break;
default: {};
}
ureq.getUserSession().getGuiPreferences()
.putAndSave(GTAStepPreferences.class, taskList.getKey().toString(), stepPreferences);
}
public static class DueDate {
private final Date dueDate;
private final String message;
public DueDate(String message) {
this(null, message);
}
public DueDate(Date dueDate) {
this(dueDate, null);
}
public DueDate(Date dueDate, String message) {
this.dueDate = dueDate;
this.message = message;
}
public Date getDueDate() {
return dueDate;
}
public String getMessage() {
return message;
}
}
}