From e0032e2e9c53192aba0ad3e78855fe10344e81ad Mon Sep 17 00:00:00 2001 From: srosse <none@none> Date: Wed, 9 Aug 2017 12:22:44 +0200 Subject: [PATCH] OO-2911, OO-2912: enhance the notifications for the task/group task to check the different dates, wording, subscriptions date chooser show the date user for the list notifications and not today --- .../ui/DateChooserController.java | 3 +- .../ui/NotificationNewsController.java | 7 +- .../DisplayOrDownloadComponentRenderer.java | 39 +- .../org/olat/course/nodes/gta/GTAManager.java | 10 + .../java/org/olat/course/nodes/gta/Task.java | 6 + .../course/nodes/gta/TaskRevisionDate.java | 42 ++ .../nodes/gta/manager/GTAManagerImpl.java | 76 ++ .../nodes/gta/manager/GTANotifications.java | 710 +++++++++++++++--- .../gta/manager/GTANotificationsHandler.java | 11 +- .../nodes/gta/model/TaskDueDateImpl.java | 37 + .../olat/course/nodes/gta/model/TaskImpl.java | 37 + .../nodes/gta/model/TaskRevisionDateImpl.java | 173 +++++ .../nodes/gta/ui/DirectoryController.java | 30 +- .../nodes/gta/ui/GTACoachController.java | 26 +- ...CoachRevisionAndCorrectionsController.java | 32 +- .../gta/ui/GTACoachSelectionController.java | 58 +- .../gta/ui/GTACoachedGroupListController.java | 9 + .../GTACoachedParticipantListController.java | 11 + .../gta/ui/GTAParticipantController.java | 36 +- ...ipantRevisionAndCorrectionsController.java | 28 +- .../course/nodes/gta/ui/GTARunController.java | 76 +- .../gta/ui/_content/coach_selection.html | 5 +- .../gta/ui/_content/documents_readonly.html | 5 +- .../gta/ui/_i18n/LocalStrings_de.properties | 31 +- .../gta/ui/_i18n/LocalStrings_en.properties | 31 +- .../gta/ui/_i18n/LocalStrings_fr.properties | 7 - .../gta/ui/_i18n/LocalStrings_it.properties | 7 - .../gta/ui/_i18n/LocalStrings_pl.properties | 7 - .../ui/_i18n/LocalStrings_pt_BR.properties | 7 - .../ui/component/DownloadDocumentMapper.java | 56 ++ .../nodes/ms/MSCourseNodeRunController.java | 43 +- .../olat/course/nodes/ms/_content/run.html | 3 + src/main/resources/META-INF/persistence.xml | 1 + .../database/mysql/alter_11_5_x_to_12_0_0.sql | 21 + .../database/mysql/setupDatabase.sql | 16 + .../oracle/alter_11_5_x_to_12_0_0.sql | 17 + .../database/oracle/setupDatabase.sql | 16 + .../postgresql/alter_11_5_x_to_12_0_0.sql | 15 + .../database/postgresql/setupDatabase.sql | 16 + .../nodes/gta/manager/GTAManagerTest.java | 58 ++ 40 files changed, 1583 insertions(+), 236 deletions(-) create mode 100644 src/main/java/org/olat/course/nodes/gta/TaskRevisionDate.java create mode 100644 src/main/java/org/olat/course/nodes/gta/model/TaskRevisionDateImpl.java create mode 100644 src/main/java/org/olat/course/nodes/gta/ui/component/DownloadDocumentMapper.java diff --git a/src/main/java/org/olat/core/commons/services/notifications/ui/DateChooserController.java b/src/main/java/org/olat/core/commons/services/notifications/ui/DateChooserController.java index c96d5595edc..97888b317a3 100644 --- a/src/main/java/org/olat/core/commons/services/notifications/ui/DateChooserController.java +++ b/src/main/java/org/olat/core/commons/services/notifications/ui/DateChooserController.java @@ -80,8 +80,7 @@ public class DateChooserController extends FormBasicController { @Override protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { dateChooser = uifactory.addDateChooser("news.since", null, formLayout); - //FIXME: Can't use time format for now, only date format due to bug OLAT-4736 - // dateChooser.setDateChooserTimeEnabled(true); + dateChooser.setDate(initDate); dateChooser.addActionListener(FormEvent.ONCHANGE); diff --git a/src/main/java/org/olat/core/commons/services/notifications/ui/NotificationNewsController.java b/src/main/java/org/olat/core/commons/services/notifications/ui/NotificationNewsController.java index 05a8e28b1ae..ec661492981 100644 --- a/src/main/java/org/olat/core/commons/services/notifications/ui/NotificationNewsController.java +++ b/src/main/java/org/olat/core/commons/services/notifications/ui/NotificationNewsController.java @@ -28,6 +28,7 @@ import java.util.Date; import java.util.List; import java.util.Map; +import org.olat.commons.calendar.CalendarUtils; import org.olat.core.commons.services.notifications.NotificationHelper; import org.olat.core.commons.services.notifications.NotificationsManager; import org.olat.core.commons.services.notifications.Subscriber; @@ -92,13 +93,14 @@ public class NotificationNewsController extends BasicController implements } else { compareDate = newsSinceDate; } + compareDate = CalendarUtils.removeTime(compareDate); + // Main view is a velocity container newsVC = createVelocityContainer("notificationsNews"); // Fetch data from DB and update datamodel and reuse subscribers List<Subscriber> subs = updateNewsDataModel(); // Add date and type chooser - dateChooserCtr = new DateChooserController(ureq, getWindowControl(), - new Date()); + dateChooserCtr = new DateChooserController(ureq, getWindowControl(), compareDate); dateChooserCtr.setSubscribers(subs); listenTo(dateChooserCtr); newsVC.put("dateChooserCtr", dateChooserCtr.getInitialComponent()); @@ -136,6 +138,7 @@ public class NotificationNewsController extends BasicController implements * @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest, * org.olat.core.gui.control.Controller, org.olat.core.gui.control.Event) */ + @Override protected void event(UserRequest ureq, Controller source, Event event) { if (source == dateChooserCtr) { if (event == Event.CHANGED_EVENT) { diff --git a/src/main/java/org/olat/core/gui/components/download/DisplayOrDownloadComponentRenderer.java b/src/main/java/org/olat/core/gui/components/download/DisplayOrDownloadComponentRenderer.java index 99a4a732809..5680ada6138 100644 --- a/src/main/java/org/olat/core/gui/components/download/DisplayOrDownloadComponentRenderer.java +++ b/src/main/java/org/olat/core/gui/components/download/DisplayOrDownloadComponentRenderer.java @@ -20,10 +20,9 @@ package org.olat.core.gui.components.download; import org.olat.core.gui.components.Component; -import org.olat.core.gui.components.ComponentRenderer; +import org.olat.core.gui.components.DefaultComponentRenderer; import org.olat.core.gui.render.RenderResult; import org.olat.core.gui.render.Renderer; -import org.olat.core.gui.render.RenderingState; import org.olat.core.gui.render.StringOutput; import org.olat.core.gui.render.URLBuilder; import org.olat.core.gui.translator.Translator; @@ -38,16 +37,8 @@ import org.olat.core.gui.translator.Translator; * * @author gnaegi */ -class DisplayOrDownloadComponentRenderer implements ComponentRenderer { +class DisplayOrDownloadComponentRenderer extends DefaultComponentRenderer { - /** - * @see org.olat.core.gui.components.ComponentRenderer#render(org.olat.core.gui.render.Renderer, - * org.olat.core.gui.render.StringOutput, - * org.olat.core.gui.components.Component, - * org.olat.core.gui.render.URLBuilder, - * org.olat.core.gui.translator.Translator, - * org.olat.core.gui.render.RenderResult, java.lang.String[]) - */ @Override public void render(Renderer renderer, StringOutput sb, Component source, URLBuilder ubu, Translator translator, RenderResult renderResult, String[] args) { @@ -63,30 +54,4 @@ class DisplayOrDownloadComponentRenderer implements ComponentRenderer { sb.append("</script>"); } } - - /** - * @see org.olat.core.gui.components.ComponentRenderer#renderBodyOnLoadJSFunctionCall(org.olat.core.gui.render.Renderer, - * org.olat.core.gui.render.StringOutput, - * org.olat.core.gui.components.Component, - * org.olat.core.gui.render.RenderingState) - */ - @Override - public void renderBodyOnLoadJSFunctionCall(Renderer renderer, StringOutput sb, Component source, RenderingState rstate) { - // no body on-load methods (must also work in non-ajax mode) - } - - /** - * @see org.olat.core.gui.components.ComponentRenderer#renderHeaderIncludes(org.olat.core.gui.render.Renderer, - * org.olat.core.gui.render.StringOutput, - * org.olat.core.gui.components.Component, - * org.olat.core.gui.render.URLBuilder, - * org.olat.core.gui.translator.Translator, - * org.olat.core.gui.render.RenderingState) - */ - @Override - public void renderHeaderIncludes(Renderer renderer, StringOutput sb, Component source, URLBuilder ubu, Translator translator, - RenderingState rstate) { - // no header includes - } - } diff --git a/src/main/java/org/olat/course/nodes/gta/GTAManager.java b/src/main/java/org/olat/course/nodes/gta/GTAManager.java index 49f2e071b03..e584aa1e4b4 100644 --- a/src/main/java/org/olat/course/nodes/gta/GTAManager.java +++ b/src/main/java/org/olat/course/nodes/gta/GTAManager.java @@ -272,6 +272,8 @@ public interface GTAManager { public List<Task> getTasks(TaskList taskList, GTACourseNode gtaNode); public List<TaskLight> getTasksLight(RepositoryEntryRef entry, GTACourseNode gtaNode); + + public List<TaskRevisionDate> getTaskRevisions(Task task); /** @@ -327,6 +329,14 @@ public interface GTAManager { public Task collectTask(Task task, GTACourseNode cNode); + /** + * Task is reviewed and accepted. + * @param task + * @param cNode + * @return + */ + public Task reviewedTask(Task task, GTACourseNode cNode); + public Task updateTask(Task task, TaskProcess newStatus, GTACourseNode cNode); public TaskDueDate updateTaskDueDate(TaskDueDate taskDueDate); diff --git a/src/main/java/org/olat/course/nodes/gta/Task.java b/src/main/java/org/olat/course/nodes/gta/Task.java index 9eadea2c561..74abe1bb54e 100644 --- a/src/main/java/org/olat/course/nodes/gta/Task.java +++ b/src/main/java/org/olat/course/nodes/gta/Task.java @@ -38,6 +38,12 @@ public interface Task extends TaskRef { public Date getCollectionDate(); + public Date getAcceptationDate(); + + public Date getSolutionDate(); + + public Date getGraduationDate(); + public TaskList getTaskList(); public Identity getIdentity(); diff --git a/src/main/java/org/olat/course/nodes/gta/TaskRevisionDate.java b/src/main/java/org/olat/course/nodes/gta/TaskRevisionDate.java new file mode 100644 index 00000000000..ce6f0a40648 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/gta/TaskRevisionDate.java @@ -0,0 +1,42 @@ +/** + * <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; + +import java.util.Date; + +/** + * + * Initial date: 7 août 2017<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public interface TaskRevisionDate { + + public Long getKey(); + + public TaskProcess getTaskStatus(); + + public int getRevisionLoop(); + + public Date getDate(); + + public Task getTask(); + +} diff --git a/src/main/java/org/olat/course/nodes/gta/manager/GTAManagerImpl.java b/src/main/java/org/olat/course/nodes/gta/manager/GTAManagerImpl.java index a68fcd1b484..2251948e194 100644 --- a/src/main/java/org/olat/course/nodes/gta/manager/GTAManagerImpl.java +++ b/src/main/java/org/olat/course/nodes/gta/manager/GTAManagerImpl.java @@ -42,6 +42,7 @@ import org.olat.basesecurity.IdentityRef; import org.olat.core.commons.modules.bc.FolderConfig; import org.olat.core.commons.modules.bc.vfs.OlatRootFolderImpl; import org.olat.core.commons.persistence.DB; +import org.olat.core.commons.services.notifications.NotificationsManager; import org.olat.core.commons.services.notifications.PublisherData; import org.olat.core.commons.services.notifications.SubscriptionContext; import org.olat.core.id.Identity; @@ -66,6 +67,7 @@ import org.olat.course.nodes.gta.TaskLight; import org.olat.course.nodes.gta.TaskList; import org.olat.course.nodes.gta.TaskProcess; import org.olat.course.nodes.gta.TaskRef; +import org.olat.course.nodes.gta.TaskRevisionDate; import org.olat.course.nodes.gta.model.DueDate; import org.olat.course.nodes.gta.model.Membership; import org.olat.course.nodes.gta.model.Solution; @@ -75,6 +77,7 @@ import org.olat.course.nodes.gta.model.TaskDefinitionList; import org.olat.course.nodes.gta.model.TaskDueDateImpl; import org.olat.course.nodes.gta.model.TaskImpl; import org.olat.course.nodes.gta.model.TaskListImpl; +import org.olat.course.nodes.gta.model.TaskRevisionDateImpl; import org.olat.course.nodes.gta.ui.events.SubmitEvent; import org.olat.course.run.environment.CourseEnvironment; import org.olat.group.BusinessGroup; @@ -122,6 +125,8 @@ public class GTAManagerImpl implements GTAManager, DeletableGroupData { @Autowired private BusinessGroupService businessGroupService; @Autowired + private NotificationsManager notificationsManager; + @Autowired private BusinessGroupRelationDAO businessGroupRelationDao; @Autowired private RepositoryEntryRelationDAO repositoryEntryRelationDao; @@ -862,6 +867,17 @@ public class GTAManagerImpl implements GTAManager, DeletableGroupData { return tasks.isEmpty() ? null : tasks.get(0); } + + @Override + public List<TaskRevisionDate> getTaskRevisions(Task task) { + if(task == null || task.getKey() == null) return Collections.emptyList(); + + String q = "select taskrev from gtataskrevisiondate taskrev where taskrev.task.key=:taskKey"; + return dbInstance.getCurrentEntityManager() + .createQuery(q, TaskRevisionDate.class) + .setParameter("taskKey", task.getKey()) + .getResultList(); + } @Override public AssignmentResponse assignTaskAutomatically(TaskList taskList, BusinessGroup assessedGroup, CourseEnvironment courseEnv, GTACourseNode cNode) { @@ -1071,6 +1087,10 @@ public class GTAManagerImpl implements GTAManager, DeletableGroupData { task.setTaskStatus(status);//assignment is ok -> go to submit step task.setRevisionLoop(0); + if(status == TaskProcess.graded) { + task.setGraduationDate(new Date()); + } + if(GTAType.group.name().equals(cNode.getModuleConfiguration().getStringValue(GTACourseNode.GTASK_TYPE))) { task.setBusinessGroup(assessedGroup); } else { @@ -1078,6 +1098,17 @@ public class GTAManagerImpl implements GTAManager, DeletableGroupData { } return task; } + + public TaskRevisionDate createAndPersistTaskRevisionDate(Task task, int revisionLoop, TaskProcess status) { + TaskRevisionDateImpl rev = new TaskRevisionDateImpl(); + rev.setCreationDate(new Date()); + rev.setDate(rev.getCreationDate()); + rev.setRevisionLoop(revisionLoop); + rev.setStatus(status.name()); + rev.setTask(task); + dbInstance.getCurrentEntityManager().persist(rev); + return rev; + } @Override public boolean isDueDateEnabled(GTACourseNode cNode) { @@ -1407,18 +1438,61 @@ public class GTAManagerImpl implements GTAManager, DeletableGroupData { public Task submitRevisions(Task task, GTACourseNode cNode) { TaskImpl taskImpl = (TaskImpl)task; taskImpl.setSubmissionRevisionsDate(new Date()); + //log the date + createAndPersistTaskRevisionDate(taskImpl, taskImpl.getRevisionLoop(), TaskProcess.correction); return updateTask(taskImpl, TaskProcess.correction, cNode); } + + @Override + public Task reviewedTask(Task task, GTACourseNode cNode) { + TaskProcess solution = nextStep(TaskProcess.correction, cNode); + TaskImpl taskImpl = (TaskImpl)task; + taskImpl.setAcceptationDate(new Date()); + return updateTask(taskImpl, solution, cNode); + } @Override public Task updateTask(Task task, TaskProcess newStatus, GTACourseNode cNode) { TaskImpl taskImpl = (TaskImpl)task; taskImpl.setTaskStatus(newStatus); + syncDates(taskImpl, newStatus); taskImpl = dbInstance.getCurrentEntityManager().merge(taskImpl); syncAssessmentEntry(taskImpl, cNode); + + //update + OLATResource resource = taskImpl.getTaskList().getEntry().getOlatResource(); + notificationsManager.markPublisherNews(getSubscriptionContext(resource, cNode), null, false); + return taskImpl; } + private void syncDates(TaskImpl taskImpl, TaskProcess newStatus) { + //solution date + if(newStatus == TaskProcess.solution || newStatus == TaskProcess.grading || newStatus == TaskProcess.graded) { + if(taskImpl.getSolutionDate() == null) { + taskImpl.setSolutionDate(new Date()); + } + } else { + taskImpl.setSolutionDate(null); + } + + //graduation date + if(newStatus == TaskProcess.graded) { + if(taskImpl.getGraduationDate() == null) { + taskImpl.setGraduationDate(new Date()); + } + } else { + taskImpl.setGraduationDate(null); + } + + //check submission date because of repon + if(newStatus == TaskProcess.assignment || newStatus == TaskProcess.submit) { + if(taskImpl.getSubmissionDate() != null) { + taskImpl.setSubmissionDate(null); + } + } + } + @Override public TaskDueDate updateTaskDueDate(TaskDueDate taskDueDate) { ((TaskDueDateImpl)taskDueDate).setLastModified(new Date()); @@ -1431,6 +1505,8 @@ public class GTAManagerImpl implements GTAManager, DeletableGroupData { taskImpl.setTaskStatus(newStatus); taskImpl.setRevisionLoop(iteration); taskImpl = dbInstance.getCurrentEntityManager().merge(taskImpl); + //log date + createAndPersistTaskRevisionDate(taskImpl, iteration, newStatus); syncAssessmentEntry(taskImpl, cNode); return taskImpl; } diff --git a/src/main/java/org/olat/course/nodes/gta/manager/GTANotifications.java b/src/main/java/org/olat/course/nodes/gta/manager/GTANotifications.java index ec1faaead33..132d662ed73 100644 --- a/src/main/java/org/olat/course/nodes/gta/manager/GTANotifications.java +++ b/src/main/java/org/olat/course/nodes/gta/manager/GTANotifications.java @@ -38,24 +38,33 @@ import org.olat.core.commons.services.notifications.model.SubscriptionListItem; import org.olat.core.gui.translator.Translator; import org.olat.core.id.Identity; import org.olat.core.id.context.BusinessControlFactory; +import org.olat.core.util.Formatter; +import org.olat.core.util.StringHelper; import org.olat.core.util.Util; import org.olat.core.util.io.SystemFileFilter; import org.olat.core.util.vfs.VFSContainer; import org.olat.core.util.vfs.VFSItem; import org.olat.course.CourseFactory; import org.olat.course.ICourse; +import org.olat.course.assessment.AssessmentHelper; import org.olat.course.nodes.CourseNode; import org.olat.course.nodes.GTACourseNode; 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.TaskList; +import org.olat.course.nodes.gta.TaskProcess; +import org.olat.course.nodes.gta.TaskRevisionDate; +import org.olat.course.nodes.gta.model.DueDate; import org.olat.course.nodes.gta.model.Membership; import org.olat.course.nodes.gta.ui.GTARunController; 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.group.model.SearchBusinessGroupParams; +import org.olat.modules.assessment.AssessmentEntry; +import org.olat.modules.assessment.manager.AssessmentEntryDAO; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryService; import org.olat.user.UserManager; @@ -73,32 +82,50 @@ class GTANotifications { private final List<SubscriptionListItem> items = new ArrayList<>(); private TaskList taskList; + private String header; private String displayName; + private GTACourseNode gtaNode; + private CourseEnvironment courseEnv; private Calendar cal = Calendar.getInstance(); private final Translator translator; + private final Formatter formatter; private final GTAManager gtaManager; private final UserManager userManager; private final RepositoryService repositoryService; private final BusinessGroupService businessGroupService; + private final AssessmentEntryDAO courseNodeAssessmentDao; public GTANotifications(Subscriber subscriber, Locale locale, Date compareDate, RepositoryService repositoryService, GTAManager gtaManager, - BusinessGroupService businessGroupService, UserManager userManager) { + BusinessGroupService businessGroupService, UserManager userManager, + AssessmentEntryDAO courseNodeAssessmentDao) { this.gtaManager = gtaManager; this.userManager = userManager; this.repositoryService = repositoryService; this.businessGroupService = businessGroupService; + this.courseNodeAssessmentDao = courseNodeAssessmentDao; this.subscriber = subscriber; this.compareDate = compareDate; + formatter = Formatter.getInstance(locale); translator = Util.createPackageTranslator(GTARunController.class, locale); } public String getDisplayName() { return displayName; } + + public String getNotifificationHeader() { + if(StringHelper.containsNonWhitespace(header)) { + return header; + } + if(GTAType.group.name().equals(gtaNode.getModuleConfiguration().getStringValue(GTACourseNode.GTASK_TYPE))) { + return translator.translate("notifications.group.header", new String[]{ displayName }); + } + return translator.translate("notifications.individual.header", new String[]{ displayName }); + } public List<SubscriptionListItem> getItems() { Publisher p = subscriber.getPublisher(); @@ -110,39 +137,28 @@ class GTANotifications { } if(entry != null && node instanceof GTACourseNode) { + gtaNode = (GTACourseNode)node; displayName = entry.getDisplayname(); - - GTACourseNode gtaNode = (GTACourseNode)node; + courseEnv = course.getCourseEnvironment(); taskList = gtaManager.getTaskList(entry, gtaNode); if(GTAType.group.name().equals(gtaNode.getModuleConfiguration().getStringValue(GTACourseNode.GTASK_TYPE))) { - createBusinessGroupsSubscriptionInfo(subscriber.getIdentity(), course.getCourseEnvironment(), gtaNode); + createBusinessGroupsSubscriptionInfo(subscriber.getIdentity()); } else { - createIndividualSubscriptionInfo(subscriber.getIdentity(), course.getCourseEnvironment(), gtaNode); - } - - //copy solutions - if(gtaNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_SAMPLE_SOLUTION)) { - File solutionDirectory = gtaManager.getSolutionsDirectory(course.getCourseEnvironment(), gtaNode); - VFSContainer solutionContainer = gtaManager.getSolutionsContainer(course.getCourseEnvironment(), gtaNode); - File[] solutions = solutionDirectory.listFiles(SystemFileFilter.FILES_ONLY); - for(File solution:solutions) { - String author = getAuthor(solution, solutionContainer); - appendSubscriptionItem("notifications.solution", new String[] { solution.getName(), author }, solution); - } + createIndividualSubscriptionInfo(subscriber.getIdentity()); } } return items; } - private void createIndividualSubscriptionInfo(Identity subscriberIdentity, CourseEnvironment ce, GTACourseNode gtaNode) { - RepositoryEntry entry = ce.getCourseGroupManager().getCourseEntry(); + private void createIndividualSubscriptionInfo(Identity subscriberIdentity) { + RepositoryEntry entry = courseEnv.getCourseGroupManager().getCourseEntry(); List<String> roles = repositoryService.getRoles(subscriberIdentity, entry); boolean owner = roles.contains(GroupRoles.owner.name()); boolean coach = roles.contains(GroupRoles.coach.name()); - if(owner|| coach) { + if(owner || coach) { Set<Identity> duplicateKiller = new HashSet<>(); List<Identity> assessableIdentities = new ArrayList<>(); @@ -175,132 +191,294 @@ class GTANotifications { } for(Identity assessedIdentity: assessableIdentities) { - createIndividualSubscriptionInfo(assessedIdentity, ce, gtaNode, true); + createIndividualSubscriptionInfo(assessedIdentity, true); } - } - - if(roles.contains(GroupRoles.participant.name())) { - createIndividualSubscriptionInfo(subscriberIdentity, ce, gtaNode, false); - } - } - private void createIndividualSubscriptionInfo(Identity assessedIdentity, CourseEnvironment ce, GTACourseNode gtaNode, boolean coach) { - String fullName = userManager.getUserDisplayName(assessedIdentity); - if(coach && gtaNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_SUBMIT)) { - File submitDirectory = gtaManager.getSubmitDirectory(ce, gtaNode, assessedIdentity); - File[] solutions = submitDirectory.listFiles(SystemFileFilter.FILES_ONLY); - for(File solution:solutions) { - appendSubscriptionItem("notifications.submission.individual", new String[] { solution.getName(), fullName }, solution); + createCoachSolutionItems(); + } else { + Task task = gtaManager.getTask(subscriberIdentity, taskList); + if(task != null) { + header = translator.translate("notifications.individual.header.task", new String[]{ task.getTaskName(), displayName }); } } - if(!coach && gtaNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_REVIEW_AND_CORRECTION)) { - File correctionDirectory = gtaManager.getCorrectionDirectory(ce, gtaNode, assessedIdentity); - VFSContainer correctionContainer = gtaManager.getCorrectionContainer(ce, gtaNode, assessedIdentity); - File[] corrections = correctionDirectory.listFiles(SystemFileFilter.FILES_ONLY); - for(File correction:corrections) { - String author = getAuthor(correction, correctionContainer); - appendSubscriptionItem("notifications.correction", new String[] { correction.getName(), author }, correction); + if(roles.contains(GroupRoles.participant.name())) { + createIndividualSubscriptionInfo(subscriberIdentity, false); + Task task = gtaManager.getTask(subscriberIdentity, taskList); + if(isSolutionVisible(subscriberIdentity, null, task)) { + createParticipantSolutionItems(task, subscriberIdentity, null); + } + if(task != null && task.getTaskStatus() == TaskProcess.graded) { + createAssessmentItem(task, subscriberIdentity, false); } } + } + + private void createIndividualSubscriptionInfo(Identity assessedIdentity, boolean coach) { + Task task = gtaManager.getTask(assessedIdentity, taskList); - if(gtaNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_REVISION_PERIOD)) { - Task task = gtaManager.getTask(assessedIdentity, taskList); - if(task != null) { - int currentIteration = task.getRevisionLoop(); - for(int i=1; i<=currentIteration; i++) { - //revision - - if(coach) { - //revision - File revisionDirectory = gtaManager.getRevisedDocumentsDirectory(ce, gtaNode, i, assessedIdentity); - File[] revisions = revisionDirectory.listFiles(SystemFileFilter.FILES_ONLY); - for(File revision:revisions) { - appendSubscriptionItem("notifications.revision.individual", new String[] { revision.getName(), fullName }, revision); - } - } else { - //corrections - File correctionDirectory = gtaManager.getRevisedDocumentsCorrectionsDirectory(ce, gtaNode, i, assessedIdentity); - VFSContainer correctionContainer = gtaManager.getRevisedDocumentsCorrectionsContainer(ce, gtaNode, i, assessedIdentity); - File[] corrections = correctionDirectory.listFiles(SystemFileFilter.FILES_ONLY); - for(File correction:corrections) { - String author = getAuthor(correction, correctionContainer); - appendSubscriptionItem("notifications.correction", new String[] { correction.getName(), author }, correction); - } + if(coach && gtaNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_SUBMIT)) { + task = checkSubmitStep(assessedIdentity, null, task); + //show after the step submit, if submission date after compare date + if(task != null && notInStep(task, TaskProcess.assignment, TaskProcess.submit) + && task.getSubmissionDate() != null && task.getSubmissionDate().after(compareDate)) { + + Date submissionDate = task.getSubmissionDate(); + String fullName = userManager.getUserDisplayName(assessedIdentity); + File submitDirectory = gtaManager.getSubmitDirectory(courseEnv, gtaNode, assessedIdentity); + File[] submissions = submitDirectory.listFiles(SystemFileFilter.FILES_ONLY); + if(submissions.length == 0) { + String[] params = new String[] { + task.getTaskName(), // {0} + displayName, // {1} + fullName // {2} + }; + appendSubscriptionItem("notifications.submission.individual", params, assessedIdentity, submissionDate, coach); + } else { + for(File submission:submissions) { + String[] params = new String[] { + task.getTaskName(), // {0} + displayName, // {1} + submission.getName(), // {2} + fullName // {3} + }; + appendSubscriptionItemForFile("notifications.submission.individual.doc", params, assessedIdentity, + "[submit:0]", submission, submissionDate, coach); } } } } + + createReviewAndRevisionsItems(task, assessedIdentity, null, coach); + createAcceptedItem(task, assessedIdentity, null, coach); } - private void createBusinessGroupsSubscriptionInfo(Identity subscriberIdentity, CourseEnvironment ce, GTACourseNode gtaNode) { - RepositoryEntry entry = ce.getCourseGroupManager().getCourseEntry(); + private void createBusinessGroupsSubscriptionInfo(Identity subscriberIdentity) { + RepositoryEntry entry = courseEnv.getCourseGroupManager().getCourseEntry(); Membership membership = gtaManager.getMembership(subscriberIdentity, entry, gtaNode); boolean owner = repositoryService.hasRole(subscriberIdentity, entry, GroupRoles.owner.name()); if(owner) { List<BusinessGroup> groups = gtaManager.getBusinessGroups(gtaNode); for(BusinessGroup group:groups) { - createBusinessGroupsSubscriptionItems(group, ce, gtaNode, true); + createBusinessGroupsSubscriptionItems(group, true); } - } else if(membership.isCoach() ) { + createCoachSolutionItems(); + } else if(membership.isCoach()) { List<BusinessGroup> groups = gtaManager.getCoachedBusinessGroups(subscriberIdentity, gtaNode); for(BusinessGroup group:groups) { - createBusinessGroupsSubscriptionItems(group, ce, gtaNode, true); + createBusinessGroupsSubscriptionItems(group, true); } + createCoachSolutionItems(); } if(membership.isParticipant()) { List<BusinessGroup> groups = gtaManager.getParticipatingBusinessGroups(subscriberIdentity, gtaNode); + if(groups.size() == 1 && !owner && !membership.isCoach()) { + Task task = gtaManager.getTask(groups.get(0), taskList); + if(task != null) { + header = translator.translate("notifications.group.header.task", new String[]{ task.getTaskName(), displayName }); + } + } + for(BusinessGroup group:groups) { - createBusinessGroupsSubscriptionItems(group, ce, gtaNode, false); + createBusinessGroupsSubscriptionItems(group, false); + Task task = gtaManager.getTask(group, taskList); + if(createParticipantSolutionItems(task, null, group)) { + break; + } } } } - private void createBusinessGroupsSubscriptionItems(BusinessGroup group, CourseEnvironment ce, GTACourseNode gtaNode, boolean coach) { + private void createBusinessGroupsSubscriptionItems(BusinessGroup group, boolean coach) { + Task task = gtaManager.getTask(group, taskList); + if(gtaNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_SUBMIT)) { - File submitDirectory = gtaManager.getSubmitDirectory(ce, gtaNode, group); - VFSContainer submitContainer = gtaManager.getSubmitContainer(ce, gtaNode, group); - File[] documents = submitDirectory.listFiles(SystemFileFilter.FILES_ONLY); - for(File document:documents) { - String author = getAuthor(document, submitContainer); - appendSubscriptionItem("notifications.submission.group", new String[] { document.getName(), group.getName(), author }, document); + task = checkSubmitStep(null, group, task); + if(task != null && notInStep(task, TaskProcess.assignment, TaskProcess.submit) + && (task.getSubmissionDate() == null || task.getSubmissionDate().after(compareDate))) { + File submitDirectory = gtaManager.getSubmitDirectory(courseEnv, gtaNode, group); + VFSContainer submitContainer = gtaManager.getSubmitContainer(courseEnv, gtaNode, group); + + Date submissionDate = task.getSubmissionDate(); + File[] submisssions = submitDirectory.listFiles(SystemFileFilter.FILES_ONLY); + if(submisssions.length == 0) { + String[] params = new String[] { + task.getTaskName(), + displayName, + group.getName() + }; + appendSubscriptionItem("notifications.submission.group", params, group, submissionDate, coach); + } else { + + for(File submission:submisssions) { + String author = getAuthor(submission, submitContainer); + String[] params = new String[] { + task.getTaskName(), // {0} + displayName, // {1} + submission.getName(), // {2} + author, // {3} + group.getName() // {3} + }; + appendSubscriptionItemForFile("notifications.submission.group.doc", params, group, + "[submit:0]", submission, submissionDate, coach); + } + } } } + + createReviewAndRevisionsItems(task, null, group, coach); + createAcceptedItem(task, null, group, coach); + } + + private void createReviewAndRevisionsItems(Task task, Identity assessedIdentity, BusinessGroup group, boolean coach) { + if(task == null) return; + String name; + if(group != null) { + name = group.getName(); + } else { + name = userManager.getUserDisplayName(assessedIdentity); + } + + List<TaskRevisionDate> taskRevisions = gtaManager.getTaskRevisions(task); if(!coach && gtaNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_REVIEW_AND_CORRECTION)) { - File correctionDirectory = gtaManager.getCorrectionDirectory(ce, gtaNode, group); - VFSContainer correctionContainer = gtaManager.getCorrectionContainer(ce, gtaNode, group); - File[] corrections = correctionDirectory.listFiles(SystemFileFilter.FILES_ONLY); - for(File correction:corrections) { - String author = getAuthor(correction, correctionContainer); - appendSubscriptionItem("notifications.correction", new String[] { correction.getName(), author }, correction); + //check task revision 1 + if(task != null && notInStep(task, TaskProcess.assignment, TaskProcess.submit, TaskProcess.review) + && checkRevisionLoop(TaskProcess.revision, 1, taskRevisions)) { + File correctionDirectory; + VFSContainer correctionContainer; + if(group != null) { + correctionDirectory = gtaManager.getCorrectionDirectory(courseEnv, gtaNode, group); + correctionContainer = gtaManager.getCorrectionContainer(courseEnv, gtaNode, group); + } else { + correctionDirectory = gtaManager.getCorrectionDirectory(courseEnv, gtaNode, assessedIdentity); + correctionContainer = gtaManager.getCorrectionContainer(courseEnv, gtaNode, assessedIdentity); + } + + Date correctionDate = getRevisionLoopDate(TaskProcess.revision, 1, taskRevisions); + if(task.getRevisionsDueDate() != null) { + String[] params = new String[] { + task.getTaskName(), + displayName, + formatter.formatDateAndTime(task.getRevisionsDueDate()) + }; + appendSubscriptionItem("notifications.correction.duedate", params, assessedIdentity, correctionDate, coach); + } else { + String[] params = new String[] { + task.getTaskName(), + displayName + }; + appendSubscriptionItem("notifications.correction", params, assessedIdentity, correctionDate, coach); + } + + File[] corrections = correctionDirectory.listFiles(SystemFileFilter.FILES_ONLY); + for(File correction:corrections) { + String author = getAuthor(correction, correctionContainer); + String[] params = new String[] { + task.getTaskName(), + displayName, + correction.getName(), + author + }; + appendSubscriptionItemForFile("notifications.correction.doc", params, assessedIdentity, + "[correction:0]", correction, correctionDate, coach); + } } } if(gtaNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_REVISION_PERIOD)) { - Task task = gtaManager.getTask(group, taskList); - if(task != null) { + task = checkRevisionStep(assessedIdentity, null, task); + if(task != null && notInStep(task, TaskProcess.assignment, TaskProcess.submit, TaskProcess.review)) { int currentIteration = task.getRevisionLoop(); for(int i=1; i<=currentIteration; i++) { - //revision - File revisionDirectory = gtaManager.getRevisedDocumentsDirectory(ce, gtaNode, i, group); - VFSContainer revisionContainer = gtaManager.getRevisedDocumentsContainer(ce, gtaNode, i, group); + + if(coach) { + // revision of the students + if(checkRevisionLoop(TaskProcess.correction, i, taskRevisions)) { + File revisionDirectory; + VFSContainer revisionContainer; + if(group != null) { + revisionDirectory = gtaManager.getRevisedDocumentsDirectory(courseEnv, gtaNode, i, group); + revisionContainer = gtaManager.getRevisedDocumentsContainer(courseEnv, gtaNode, i, group); + } else { + revisionDirectory = gtaManager.getRevisedDocumentsDirectory(courseEnv, gtaNode, i, assessedIdentity); + revisionContainer = gtaManager.getRevisedDocumentsContainer(courseEnv, gtaNode, i, assessedIdentity); + } - File[] revisions = revisionDirectory.listFiles(SystemFileFilter.FILES_ONLY); - for(File revision:revisions) { - String author = getAuthor(revision, revisionContainer); - appendSubscriptionItem("notifications.revision.group", new String[] { revision.getName(), group.getName(), author }, revision); - } - //corrections - if(!coach) { - File correctionDirectory = gtaManager.getRevisedDocumentsCorrectionsDirectory(ce, gtaNode, i, group); - VFSContainer correctionContainer = gtaManager.getRevisedDocumentsCorrectionsContainer(ce, gtaNode, i, group); + Date revisionDate = getRevisionLoopDate(TaskProcess.correction, i, taskRevisions); + File[] revisions = revisionDirectory.listFiles(SystemFileFilter.FILES_ONLY); + if(revisions.length == 0) { + String[] params = new String[] { + task.getTaskName(), + displayName, + name + }; + if(group != null) { + appendSubscriptionItem("notifications.revision.group", params, group, revisionDate, coach); + } else { + appendSubscriptionItem("notifications.revision.individual", params, assessedIdentity, revisionDate, coach); + } + } else { + for(File revision:revisions) { + String author = getAuthor(revision, revisionContainer); + String[] params = new String[] { + task.getTaskName(), + displayName, + revision.getName(), + name, + author + }; + if(group != null) { + appendSubscriptionItemForFile("notifications.revision.group.doc", params, group, + "[revision:" + i + "]", revision, revisionDate, coach); + } else { + appendSubscriptionItemForFile("notifications.revision.individual.doc", params, assessedIdentity, + "[revision:" + i + "]", revision, revisionDate, coach); + } + } + } + } + } else if(checkRevisionLoop(TaskProcess.revision, i, taskRevisions)) { + // corrections of the coach + File correctionDirectory; + VFSContainer correctionContainer; + if(group != null) { + correctionDirectory = gtaManager.getRevisedDocumentsCorrectionsDirectory(courseEnv, gtaNode, i, group); + correctionContainer = gtaManager.getRevisedDocumentsCorrectionsContainer(courseEnv, gtaNode, i, group); + } else { + correctionDirectory = gtaManager.getRevisedDocumentsCorrectionsDirectory(courseEnv, gtaNode, i, assessedIdentity); + correctionContainer = gtaManager.getRevisedDocumentsCorrectionsContainer(courseEnv, gtaNode, i, assessedIdentity); + } + + Date correctionDate = getRevisionLoopDate(TaskProcess.revision, i, taskRevisions); + if(task.getRevisionsDueDate() != null) { + String[] params = new String[] { + task.getTaskName(), + displayName, + formatter.formatDateAndTime(task.getRevisionsDueDate()) + }; + appendSubscriptionItem("notifications.correction.duedate", params, assessedIdentity, correctionDate, coach); + } else { + String[] params = new String[] { + task.getTaskName(), + displayName + }; + appendSubscriptionItem("notifications.correction", params, assessedIdentity, correctionDate, coach); + } + File[] corrections = correctionDirectory.listFiles(SystemFileFilter.FILES_ONLY); for(File correction:corrections) { String author = getAuthor(correction, correctionContainer); - appendSubscriptionItem("notifications.correction", new String[] { correction.getName(), author }, correction); + String[] params = new String[] { + task.getTaskName(), + displayName, + correction.getName(), + author + }; + appendSubscriptionItemForFile("notifications.correction.doc", params, assessedIdentity, + "[correction:" + i + "]", correction, correctionDate, coach); } } } @@ -308,6 +486,259 @@ class GTANotifications { } } + private void createAcceptedItem(Task task, Identity assessedIdentity, BusinessGroup assessedGroup, boolean coach) { + if(task == null || task.getAcceptationDate() == null || coach) return; + + if(task.getAcceptationDate().after(compareDate)) { + RepositoryEntry courseEntry = courseEnv.getCourseGroupManager().getCourseEntry(); + String[] params = new String[] { + task.getTaskName(), + courseEntry.getDisplayname() + }; + if(assessedGroup != null) { + appendSubscriptionItem("notifications.accepted", params, assessedGroup, task.getAcceptationDate(), coach); + } else { + appendSubscriptionItem("notifications.accepted", params, assessedIdentity, task.getAcceptationDate(), coach); + } + } + } + + private void createAssessmentItem(Task task, Identity assessedIdentity, boolean coach) { + if(task == null || task.getGraduationDate() == null) return; + + if(task.getGraduationDate().after(compareDate)) { + RepositoryEntry courseEntry = courseEnv.getCourseGroupManager().getCourseEntry(); + AssessmentEntry assessment = courseNodeAssessmentDao.loadAssessmentEntry(assessedIdentity, courseEntry, gtaNode.getIdent()); + boolean resultsVisible = assessment != null + && (assessment.getUserVisibility() == null || assessment.getUserVisibility().booleanValue()); + if(resultsVisible) { + String score = null; + String status = null; + if(gtaNode.hasScoreConfigured() && assessment.getScore() != null) { + score = AssessmentHelper.getRoundedScore(assessment.getScore()); + } + if(gtaNode.hasPassedConfigured() && assessment.getPassed() != null) { + status = assessment.getPassed().booleanValue() + ? translator.translate("notifications.assessment.passed.true") : translator.translate("notifications.assessment.passed.false"); + } + + Date graduationDate = task.getGraduationDate(); + String[] params = new String[] { + task.getTaskName(), + courseEntry.getDisplayname(), + score, + status + }; + + if(score != null && status != null) { + appendSubscriptionItem("notifications.assessment.score.passed", params, assessedIdentity, graduationDate, coach); + } else if(score != null) { + appendSubscriptionItem("notifications.assessment.score", params, assessedIdentity, graduationDate, coach); + } else if(status != null) { + appendSubscriptionItem("notifications.assessment.passed", params, assessedIdentity, graduationDate, coach); + } + + ICourse course = CourseFactory.loadCourse(courseEnv.getCourseGroupManager().getCourseEntry()); + UserCourseEnvironment assessedUserCourseEnv = AssessmentHelper + .createAndInitUserCourseEnvironment(assessedIdentity, course); + List<File> docs = gtaNode.getIndividualAssessmentDocuments(assessedUserCourseEnv); + for(File doc:docs) { + String[] docParams = new String[] { + task.getTaskName(), + courseEntry.getDisplayname(), + doc.getName() + }; + appendSubscriptionItemForFile("notifications.assessment.doc", docParams, assessedIdentity, + "[assessment:0]", doc, graduationDate, coach); + } + } + } + } + + private void createCoachSolutionItems() { + //copy solutions + if(gtaNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_SAMPLE_SOLUTION)) { + File solutionDirectory = gtaManager.getSolutionsDirectory(courseEnv, gtaNode); + VFSContainer solutionContainer = gtaManager.getSolutionsContainer(courseEnv, gtaNode); + File[] solutions = solutionDirectory.listFiles(SystemFileFilter.FILES_ONLY); + for(File solution:solutions) { + String author = getAuthor(solution, solutionContainer); + String[] params = new String[] { + displayName, + solution.getName(), + author + }; + appendSubscriptionItemForFile("notifications.solution", params, "", + "[solution:0]" , solution, null, true); + } + } + } + + private boolean createParticipantSolutionItems(Task task, Identity assessedIdentity, BusinessGroup group) { + //copy solutions + if(gtaNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_SAMPLE_SOLUTION)) { + RepositoryEntry re = courseEnv.getCourseGroupManager().getCourseEntry(); + DueDate dueDate = gtaManager.getSolutionDueDate(task, assessedIdentity, group, gtaNode, re, true); + + Date solutionDate = null; + if(dueDate == null) { + if(inStep(task, TaskProcess.solution, TaskProcess.grading, TaskProcess.graded)) { + solutionDate = task.getSolutionDate(); + } else { + return false; + } + } else if(dueDate != null && dueDate.getDueDate() != null) { + if(task == null) { + if(inStep(task, TaskProcess.assignment, TaskProcess.submit, TaskProcess.review, TaskProcess.correction, TaskProcess.revision) + && !dueDate.isRelative() && gtaNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_ALL, false)) { + solutionDate = dueDate.getDueDate(); + } else { + return false; + } + } else { + solutionDate = dueDate.getDueDate(); + } + } else if(task != null) { + solutionDate = task.getSolutionDate(); + } + + if(solutionDate != null) { + if(task.getSolutionDate() != null && task.getSolutionDate().after(solutionDate)) { + solutionDate = task.getSolutionDate(); + } + + File solutionDirectory = gtaManager.getSolutionsDirectory(courseEnv, gtaNode); + VFSContainer solutionContainer = gtaManager.getSolutionsContainer(courseEnv, gtaNode); + File[] solutions = solutionDirectory.listFiles(SystemFileFilter.FILES_ONLY); + for(File solution:solutions) { + Date date = solutionDate; + + //check if there are new solutions too + cal.setTimeInMillis(solution.lastModified()); + Date modificationDate = cal.getTime(); + if(date.before(modificationDate)) { + date = modificationDate; + } + + if(date.after(compareDate)) { + String author = getAuthor(solution, solutionContainer); + if(task != null) { + String[] params = new String[] { + task.getTaskName(), + displayName, + solution.getName(), + author + }; + appendSubscriptionItemForFile("notifications.solution.task", params, assessedIdentity, + "[solution:0]" , solution, date, false); + } else { + String[] params = new String[] { + displayName, + solution.getName(), + author + }; + appendSubscriptionItemForFile("notifications.solution", params, assessedIdentity, + "[solution:0]" , solution, date, false); + } + } + } + return true; + } + } + return false; + } + + private Task checkSubmitStep(Identity assessedIdentity, BusinessGroup assessedGroup, Task task) { + if(task != null && task.getTaskStatus() == TaskProcess.submit) { + RepositoryEntry re = courseEnv.getCourseGroupManager().getCourseEntry(); + DueDate dueDate = gtaManager.getSubmissionDueDate(task, assessedIdentity, assessedGroup, gtaNode, re, true); + if(dueDate != null && dueDate.getDueDate() != null && dueDate.getDueDate().before(new Date())) { + task = gtaManager.nextStep(task, gtaNode); + doUpdateAttempts(assessedIdentity, assessedGroup); + } + } + return task; + } + + private boolean checkRevisionLoop(TaskProcess status, int revisionLoop, List<TaskRevisionDate> taskRevisions) { + for(TaskRevisionDate taskRevision:taskRevisions) { + if(taskRevision.getTaskStatus() == status && taskRevision.getRevisionLoop() == revisionLoop) { + Date date = taskRevision.getDate(); + if(date.after(compareDate)) { + return true; + } + } + } + return false; + } + + private Date getRevisionLoopDate(TaskProcess status, int revisionLoop, List<TaskRevisionDate> taskRevisions) { + for(TaskRevisionDate taskRevision:taskRevisions) { + if(taskRevision.getTaskStatus() == status && taskRevision.getRevisionLoop() == revisionLoop) { + return taskRevision.getDate(); + } + } + return null; + } + + /** + * This checks the revision due date. + * + * @param assessedIdentity The identity which is assessed + * @param assessedGroup The group which is assessed + * @param task The task + * @return An updated task if the status as automatically changed + */ + private Task checkRevisionStep(Identity assessedIdentity, BusinessGroup assessedGroup, Task task) { + if(task != null) { + if(task != null && task.getTaskStatus() == TaskProcess.revision + && task.getRevisionsDueDate() != null + && task.getRevisionsDueDate().compareTo(new Date()) < 0) { + //push to the next step + task = gtaManager.nextStep(task, gtaNode); + doUpdateAttempts(assessedIdentity, assessedGroup); + } + } + return task; + } + + private boolean isSolutionVisible(Identity assessedIdentity, BusinessGroup assessedGroup, Task task) { + if(task != null) { + RepositoryEntry re = courseEnv.getCourseGroupManager().getCourseEntry(); + DueDate availableDate = gtaManager.getSolutionDueDate(task, assessedIdentity, assessedGroup, gtaNode, re, true); + boolean visible = availableDate == null || + (availableDate.getDueDate() != null && availableDate.getDueDate().compareTo(new Date()) <= 0); + if(visible) { + if(task.getTaskStatus() == TaskProcess.solution || task.getTaskStatus() == TaskProcess.grading || task.getTaskStatus() == TaskProcess.graded) { + // step solution or beyond + return true; + } else if((task == null || task.getTaskStatus() == TaskProcess.assignment || task.getTaskStatus() == TaskProcess.submit + || task.getTaskStatus() == TaskProcess.review || task.getTaskStatus() == TaskProcess.correction + || task.getTaskStatus() == TaskProcess.revision) + && gtaNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_ALL, false)) { + // steps before solution only if configured to + return true; + } + } + } + return false; + } + + private void doUpdateAttempts(Identity assessedIdentity, BusinessGroup assessedGroup) { + ICourse course = CourseFactory.loadCourse(courseEnv.getCourseGroupManager().getCourseEntry()); + if(GTAType.group.name().equals(gtaNode.getModuleConfiguration().getStringValue(GTACourseNode.GTASK_TYPE))) { + List<Identity> identities = businessGroupService.getMembers(assessedGroup, GroupRoles.participant.name()); + for(Identity identity:identities) { + UserCourseEnvironment uce = AssessmentHelper.createAndInitUserCourseEnvironment(identity, course); + gtaNode.incrementUserAttempts(uce); + } + } else { + UserCourseEnvironment assessedUserCourseEnv = AssessmentHelper + .createAndInitUserCourseEnvironment(assessedIdentity, course); + gtaNode.incrementUserAttempts(assessedUserCourseEnv); + } + } + private String getAuthor(File file, VFSContainer container) { String author = null; VFSItem item = container.resolve(file.getName()); @@ -323,16 +754,91 @@ class GTANotifications { return author == null ? "" : author; } - private void appendSubscriptionItem(String notificationKey, String[] params, File file) { - cal.setTimeInMillis(file.lastModified()); - Date modDate = cal.getTime(); - if(modDate.compareTo(compareDate) >= 0) { + + private void appendSubscriptionItemForFile(String notificationKey, String[] params, Identity identity, + String fileCategory, File file, Date modificationDate, boolean coach) { + String idPath = "[Identity:" + identity.getKey() + "]"; + appendSubscriptionItemForFile(notificationKey, params, idPath, fileCategory, file, modificationDate, coach); + } + + private void appendSubscriptionItemForFile(String notificationKey, String[] params, BusinessGroup group, + String fileCategory, File file, Date modificationDate, boolean coach) { + String idPath = "[BusinessGroup:" + group.getKey() + "]"; + appendSubscriptionItemForFile(notificationKey, params, idPath, fileCategory, file, modificationDate, coach); + } + + private void appendSubscriptionItemForFile(String notificationKey, String[] params, String idPath, + String fileCategory, File file, Date modificationDate, boolean coach) { + if(modificationDate == null) { + cal.setTimeInMillis(file.lastModified()); + modificationDate = cal.getTime(); + } + if(modificationDate.compareTo(compareDate) >= 0) { String desc = translator.translate(notificationKey, params); String businessPath = subscriber.getPublisher().getBusinessPath(); + if(coach) { + businessPath += "[Coach:0]"; + } + if(StringHelper.containsNonWhitespace(idPath) ) { + businessPath += idPath; + } + if(StringHelper.containsNonWhitespace(fileCategory)) { + businessPath += fileCategory; + } + if(file != null) { + businessPath += "[path=" + file.getName() + ":0]"; + } String urlToSend = BusinessControlFactory.getInstance().getURLFromBusinessPathString(businessPath); String iconCssClass = GTANotificationsHandler.CSS_CLASS_ICON; - SubscriptionListItem item = new SubscriptionListItem(desc, urlToSend, businessPath, modDate, iconCssClass); + SubscriptionListItem item = new SubscriptionListItem(desc, urlToSend, businessPath, modificationDate, iconCssClass); items.add(item); } } + + private void appendSubscriptionItem(String notificationKey, String[] params, Identity identity, Date modificationDate, boolean coach) { + String path = "[Identity:" + identity.getKey() + "]"; + appendSubscriptionItem(notificationKey, params, path, modificationDate, coach); + } + + private void appendSubscriptionItem(String notificationKey, String[] params, BusinessGroup group, Date modificationDate, boolean coach) { + String path = "[BusinessGroup:" + group.getKey() + "]"; + appendSubscriptionItem(notificationKey, params, path, modificationDate, coach); + } + + private void appendSubscriptionItem(String notificationKey, String[] params, String path, Date modificationDate, boolean coach) { + String desc = translator.translate(notificationKey, params); + String businessPath = subscriber.getPublisher().getBusinessPath(); + if(coach) { + businessPath += "[Coach:0]"; + } + if(StringHelper.containsNonWhitespace(path)) { + businessPath += path; + } + String urlToSend = BusinessControlFactory.getInstance().getURLFromBusinessPathString(businessPath); + String iconCssClass = GTANotificationsHandler.CSS_CLASS_ICON; + SubscriptionListItem item = new SubscriptionListItem(desc, urlToSend, businessPath, modificationDate, iconCssClass); + items.add(item); + } + + private boolean notInStep(Task task, TaskProcess... status) { + boolean ok = true; + if(task == null) { + ok &= false; + } else { + for(TaskProcess state:status) { + ok &= task.getTaskStatus() != state; + } + } + return ok; + } + + private boolean inStep(Task task, TaskProcess... status) { + boolean ok = false; + if(task != null) { + for(TaskProcess state:status) { + ok |= task.getTaskStatus() == state; + } + } + return ok; + } } diff --git a/src/main/java/org/olat/course/nodes/gta/manager/GTANotificationsHandler.java b/src/main/java/org/olat/course/nodes/gta/manager/GTANotificationsHandler.java index 5b4bae76aaf..1ffba243223 100644 --- a/src/main/java/org/olat/course/nodes/gta/manager/GTANotificationsHandler.java +++ b/src/main/java/org/olat/course/nodes/gta/manager/GTANotificationsHandler.java @@ -37,6 +37,7 @@ import org.olat.core.util.Util; import org.olat.course.nodes.gta.GTAManager; import org.olat.course.nodes.gta.ui.GTARunController; import org.olat.group.BusinessGroupService; +import org.olat.modules.assessment.manager.AssessmentEntryDAO; import org.olat.repository.RepositoryManager; import org.olat.repository.RepositoryService; import org.olat.user.UserManager; @@ -63,6 +64,8 @@ public class GTANotificationsHandler implements NotificationsHandler { private RepositoryService repositoryService; @Autowired private BusinessGroupService businessGroupService; + @Autowired + private AssessmentEntryDAO courseNodeAssessmentDao; @Override public SubscriptionInfo createSubscriptionInfo(Subscriber subscriber, Locale locale, Date compareDate) { @@ -70,20 +73,16 @@ public class GTANotificationsHandler implements NotificationsHandler { Date latestNews = p.getLatestNewsDate(); SubscriptionInfo si; - // there could be news for me, investigate deeper try { if (NotificationsManager.getInstance().isPublisherValid(p) && compareDate.before(latestNews)) { - Translator translator = Util.createPackageTranslator(GTARunController.class, locale); - GTANotifications notifications = new GTANotifications(subscriber, locale, compareDate, - repositoryService, gtaManager, businessGroupService, userManager); + repositoryService, gtaManager, businessGroupService, userManager, courseNodeAssessmentDao); List<SubscriptionListItem> items = notifications.getItems(); if(items.isEmpty()) { si = NotificationsManager.getInstance().getNoSubscriptionInfo(); } else { - String displayName = notifications.getDisplayName(); - String title = translator.translate("notifications.header", new String[]{ displayName }); + String title = notifications.getNotifificationHeader(); TitleItem titleItem = new TitleItem(title, CSS_CLASS_ICON); si = new SubscriptionInfo(subscriber.getKey(), p.getType(), titleItem, items); } diff --git a/src/main/java/org/olat/course/nodes/gta/model/TaskDueDateImpl.java b/src/main/java/org/olat/course/nodes/gta/model/TaskDueDateImpl.java index 3b181508103..f4fde0fdaab 100644 --- a/src/main/java/org/olat/course/nodes/gta/model/TaskDueDateImpl.java +++ b/src/main/java/org/olat/course/nodes/gta/model/TaskDueDateImpl.java @@ -98,6 +98,16 @@ public class TaskDueDateImpl implements TaskDueDate, CreateInfo, Persistable, Mo @Column(name="g_collection_date", nullable=true, insertable=true, updatable=false) private Date collectionDate; + @Temporal(TemporalType.TIMESTAMP) + @Column(name="g_acceptation_date", nullable=true, insertable=true, updatable=false) + private Date acceptationDate; + @Temporal(TemporalType.TIMESTAMP) + @Column(name="g_solution_date", nullable=true, insertable=true, updatable=false) + private Date solutionDate; + @Temporal(TemporalType.TIMESTAMP) + @Column(name="g_graduation_date", nullable=true, insertable=true, updatable=false) + private Date graduationDate; + @Temporal(TemporalType.TIMESTAMP) @Column(name="g_assignment_due_date", nullable=true, insertable=true, updatable=true) private Date assignmentDueDate; @@ -227,6 +237,33 @@ public class TaskDueDateImpl implements TaskDueDate, CreateInfo, Persistable, Mo this.collectionDate = collectionDate; } + @Override + public Date getAcceptationDate() { + return acceptationDate; + } + + public void setAcceptationDate(Date acceptationDate) { + this.acceptationDate = acceptationDate; + } + + @Override + public Date getSolutionDate() { + return solutionDate; + } + + public void setSolutionDate(Date solutionDate) { + this.solutionDate = solutionDate; + } + + @Override + public Date getGraduationDate() { + return graduationDate; + } + + public void setGraduationDate(Date graduationDate) { + this.graduationDate = graduationDate; + } + @Override public Date getAssignmentDueDate() { return assignmentDueDate; diff --git a/src/main/java/org/olat/course/nodes/gta/model/TaskImpl.java b/src/main/java/org/olat/course/nodes/gta/model/TaskImpl.java index 7c0a0c742d5..844e1b02b8c 100644 --- a/src/main/java/org/olat/course/nodes/gta/model/TaskImpl.java +++ b/src/main/java/org/olat/course/nodes/gta/model/TaskImpl.java @@ -100,6 +100,16 @@ public class TaskImpl implements Task, CreateInfo, Persistable, ModifiedInfo { @Column(name="g_collection_date", nullable=true, insertable=true, updatable=true) private Date collectionDate; + @Temporal(TemporalType.TIMESTAMP) + @Column(name="g_acceptation_date", nullable=true, insertable=true, updatable=true) + private Date acceptationDate; + @Temporal(TemporalType.TIMESTAMP) + @Column(name="g_solution_date", nullable=true, insertable=true, updatable=true) + private Date solutionDate; + @Temporal(TemporalType.TIMESTAMP) + @Column(name="g_graduation_date", nullable=true, insertable=true, updatable=true) + private Date graduationDate; + @Temporal(TemporalType.TIMESTAMP) @Column(name="g_assignment_due_date", nullable=true, insertable=true, updatable=false) private Date assignmentDueDate; @@ -229,6 +239,33 @@ public class TaskImpl implements Task, CreateInfo, Persistable, ModifiedInfo { this.collectionDate = collectionDate; } + @Override + public Date getAcceptationDate() { + return acceptationDate; + } + + public void setAcceptationDate(Date acceptationDate) { + this.acceptationDate = acceptationDate; + } + + @Override + public Date getSolutionDate() { + return solutionDate; + } + + public void setSolutionDate(Date solutionDate) { + this.solutionDate = solutionDate; + } + + @Override + public Date getGraduationDate() { + return graduationDate; + } + + public void setGraduationDate(Date graduationDate) { + this.graduationDate = graduationDate; + } + @Override public Date getAssignmentDueDate() { return assignmentDueDate; diff --git a/src/main/java/org/olat/course/nodes/gta/model/TaskRevisionDateImpl.java b/src/main/java/org/olat/course/nodes/gta/model/TaskRevisionDateImpl.java new file mode 100644 index 00000000000..c2d1dcd3688 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/gta/model/TaskRevisionDateImpl.java @@ -0,0 +1,173 @@ +/** + * <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.model; + +import java.util.Date; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import javax.persistence.Transient; + +import org.hibernate.annotations.GenericGenerator; +import org.hibernate.annotations.Parameter; +import org.olat.core.id.CreateInfo; +import org.olat.core.id.Persistable; +import org.olat.core.util.StringHelper; +import org.olat.course.nodes.gta.Task; +import org.olat.course.nodes.gta.TaskProcess; +import org.olat.course.nodes.gta.TaskRevisionDate; + +/** + * + * Initial date: 7 août 2017<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +@Entity(name="gtataskrevisiondate") +@Table(name="o_gta_task_revision_date") +public class TaskRevisionDateImpl implements CreateInfo, Persistable, TaskRevisionDate { + + private static final long serialVersionUID = -1476103422622666128L; + + @Id + @GeneratedValue(generator = "system-uuid") + @GenericGenerator(name = "system-uuid", strategy = "enhanced-sequence", parameters={ + @Parameter(name="sequence_name", value="hibernate_unique_key"), + @Parameter(name="force_table_use", value="true"), + @Parameter(name="optimizer", value="legacy-hilo"), + @Parameter(name="value_column", value="next_hi"), + @Parameter(name="increment_size", value="32767"), + @Parameter(name="initial_value", value="32767") + }) + @Column(name="id", nullable=false, unique=true, insertable=true, updatable=false) + private Long key; + + @Temporal(TemporalType.TIMESTAMP) + @Column(name="creationdate", nullable=false, insertable=true, updatable=false) + private Date creationDate; + + @Column(name="g_status", nullable=false, insertable=true, updatable=true) + private String status; + @Column(name="g_rev_loop", nullable=false, insertable=true, updatable=true) + private int revisionLoop; + @Temporal(TemporalType.TIMESTAMP) + @Column(name="g_date", nullable=true, insertable=true, updatable=false) + private Date date; + + @ManyToOne(targetEntity=TaskImpl.class,fetch=FetchType.LAZY,optional=false) + @JoinColumn(name="fk_task", nullable=false, insertable=true, updatable=false) + private Task task; + + @Override + public Long getKey() { + return key; + } + + @Override + public Date getCreationDate() { + return creationDate; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + @Override + @Transient + public TaskProcess getTaskStatus() { + if(StringHelper.containsNonWhitespace(status)) { + return TaskProcess.valueOf(status); + } + return null; + } + + public void setTaskStatus(TaskProcess taskStatus) { + if(taskStatus == null) { + status = null; + } else { + status = taskStatus.name(); + } + } + + @Override + public int getRevisionLoop() { + return revisionLoop; + } + + public void setRevisionLoop(int revisionLoop) { + this.revisionLoop = revisionLoop; + } + + @Override + public Date getDate() { + return date; + } + + public void setDate(Date date) { + this.date = date; + } + + @Override + public Task getTask() { + return task; + } + + public void setTask(Task task) { + this.task = task; + } + + public void setCreationDate(Date creationDate) { + this.creationDate = creationDate; + } + + @Override + public int hashCode() { + return key == null ? 848502 : key.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if(obj == this) { + return true; + } + if(obj instanceof TaskRevisionDateImpl) { + TaskRevisionDateImpl rev = (TaskRevisionDateImpl)obj; + return key != null && key.equals(rev.getKey()); + } + return super.equals(obj); + } + + @Override + public boolean equalsByPersistableKey(Persistable persistable) { + return equals(persistable); + } +} diff --git a/src/main/java/org/olat/course/nodes/gta/ui/DirectoryController.java b/src/main/java/org/olat/course/nodes/gta/ui/DirectoryController.java index ab4cf8d3214..6d997abfe85 100644 --- a/src/main/java/org/olat/course/nodes/gta/ui/DirectoryController.java +++ b/src/main/java/org/olat/course/nodes/gta/ui/DirectoryController.java @@ -29,6 +29,7 @@ import org.olat.core.commons.modules.bc.meta.tagged.MetaTagged; import org.olat.core.commons.modules.singlepage.SinglePageController; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.download.DisplayOrDownloadComponent; import org.olat.core.gui.components.link.Link; import org.olat.core.gui.components.link.LinkFactory; import org.olat.core.gui.components.velocity.VelocityContainer; @@ -37,15 +38,20 @@ 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.gui.control.generic.closablewrapper.CloseableModalController; +import org.olat.core.gui.control.generic.dtabs.Activateable2; import org.olat.core.gui.media.FileMediaResource; import org.olat.core.gui.media.MediaResource; import org.olat.core.gui.util.CSSHelper; +import org.olat.core.id.context.BusinessControlFactory; +import org.olat.core.id.context.ContextEntry; +import org.olat.core.id.context.StateEntry; import org.olat.core.util.CodeHelper; import org.olat.core.util.Formatter; import org.olat.core.util.StringHelper; import org.olat.core.util.io.SystemFileFilter; import org.olat.core.util.vfs.VFSContainer; import org.olat.core.util.vfs.VFSItem; +import org.olat.course.nodes.gta.ui.component.DownloadDocumentMapper; import org.olat.fileresource.ZippedDirectoryMediaResource; import org.olat.user.UserManager; import org.springframework.beans.factory.annotation.Autowired; @@ -56,11 +62,15 @@ import org.springframework.beans.factory.annotation.Autowired; * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ -public class DirectoryController extends BasicController { +public class DirectoryController extends BasicController implements Activateable2 { private Link bulkReviewLink; + private final VelocityContainer mainVC; + private final DisplayOrDownloadComponent download; + private final String zipName; + private final String mapperUri; private final File documentsDir; private final VFSContainer documentsContainer; @@ -87,9 +97,13 @@ public class DirectoryController extends BasicController { format = Formatter.getInstance(ureq.getLocale()); - VelocityContainer mainVC = createVelocityContainer("documents_readonly"); + mainVC = createVelocityContainer("documents_readonly"); mainVC.contextPut("description", translate(i18nDescription)); + mapperUri = registerMapper(ureq, new DownloadDocumentMapper(documentsDir)); + download = new DisplayOrDownloadComponent("download", null); + mainVC.put("download", download); + if(StringHelper.containsNonWhitespace(i18nBulkDownload)) { bulkReviewLink = LinkFactory.createCustomLink("bulk", "bulk", null, Link.BUTTON + Link.NONTRANSLATED, mainVC, this); bulkReviewLink.setIconLeftCSS("o_icon o_icon_download"); @@ -135,6 +149,18 @@ public class DirectoryController extends BasicController { // } + @Override + public void activate(UserRequest ureq, List<ContextEntry> entries, StateEntry state) { + if(entries == null || entries.isEmpty()) return; + + String path = BusinessControlFactory.getInstance().getPath(entries.get(0)); + File document = new File(documentsDir, path); + if(document.exists()) { + String url = mapperUri + "/" + document.getName(); + download.triggerFileDownload(url); + } + } + @Override protected void event(UserRequest ureq, Component source, Event event) { if(bulkReviewLink == source) { diff --git a/src/main/java/org/olat/course/nodes/gta/ui/GTACoachController.java b/src/main/java/org/olat/course/nodes/gta/ui/GTACoachController.java index 2d909b7ce66..e9e2dfae3a5 100644 --- a/src/main/java/org/olat/course/nodes/gta/ui/GTACoachController.java +++ b/src/main/java/org/olat/course/nodes/gta/ui/GTACoachController.java @@ -34,9 +34,12 @@ import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController; +import org.olat.core.gui.control.generic.dtabs.Activateable2; import org.olat.core.gui.control.generic.modal.DialogBoxController; import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory; import org.olat.core.id.Identity; +import org.olat.core.id.context.ContextEntry; +import org.olat.core.id.context.StateEntry; import org.olat.core.util.coordinate.CoordinatorManager; import org.olat.core.util.io.SystemFilenameFilter; import org.olat.core.util.mail.ContactList; @@ -68,8 +71,7 @@ import org.springframework.beans.factory.annotation.Autowired; * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ -public class GTACoachController extends GTAAbstractController implements AssessmentFormCallback { - +public class GTACoachController extends GTAAbstractController implements AssessmentFormCallback, Activateable2 { private DirectoryController solutionsCtrl; private DirectoryController correctionsCtrl; @@ -479,6 +481,23 @@ public class GTACoachController extends GTAAbstractController implements Assessm process(ureq); } } + + @Override + public void activate(UserRequest ureq, List<ContextEntry> entries, StateEntry state) { + if(entries == null || entries.size() <= 1) return; + + String type = entries.get(0).getOLATResourceable().getResourceableTypeName(); + if("Submit".equalsIgnoreCase(type)) { + if(submittedDocCtrl != null) { + List<ContextEntry> subEntries = entries.subList(1, entries.size()); + submittedDocCtrl.activate(ureq, subEntries, null); + } + } else if("Revision".equalsIgnoreCase(type)) { + if(revisionDocumentsCtrl != null) { + revisionDocumentsCtrl.activate(ureq, entries, null); + } + } + } @Override protected void event(UserRequest ureq, Component source, Event event) { @@ -602,8 +621,7 @@ public class GTACoachController extends GTAAbstractController implements Assessm private void doReviewedDocument(UserRequest ureq, Task task) { //go to solution, grading or graded - TaskProcess nextStep = gtaManager.nextStep(TaskProcess.correction, gtaNode); - gtaManager.updateTask(task, nextStep, gtaNode); + gtaManager.reviewedTask(task, gtaNode); showInfo("coach.documents.successfully.reviewed"); gtaManager.log("Review", "documents reviewed", task, getIdentity(), assessedIdentity, assessedGroup, courseEnv, gtaNode); diff --git a/src/main/java/org/olat/course/nodes/gta/ui/GTACoachRevisionAndCorrectionsController.java b/src/main/java/org/olat/course/nodes/gta/ui/GTACoachRevisionAndCorrectionsController.java index 2c0e8a8138a..7b18c1776b2 100644 --- a/src/main/java/org/olat/course/nodes/gta/ui/GTACoachRevisionAndCorrectionsController.java +++ b/src/main/java/org/olat/course/nodes/gta/ui/GTACoachRevisionAndCorrectionsController.java @@ -21,7 +21,9 @@ package org.olat.course.nodes.gta.ui; import java.io.File; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.olat.basesecurity.GroupRoles; import org.olat.core.gui.UserRequest; @@ -35,10 +37,13 @@ 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.gui.control.generic.closablewrapper.CloseableModalController; +import org.olat.core.gui.control.generic.dtabs.Activateable2; import org.olat.core.gui.control.generic.modal.DialogBoxController; import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory; import org.olat.core.id.Identity; import org.olat.core.id.OLATResourceable; +import org.olat.core.id.context.ContextEntry; +import org.olat.core.id.context.StateEntry; import org.olat.core.util.coordinate.CoordinatorManager; import org.olat.core.util.io.SystemFilenameFilter; import org.olat.core.util.vfs.VFSContainer; @@ -67,13 +72,14 @@ import org.springframework.beans.factory.annotation.Autowired; * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ -public class GTACoachRevisionAndCorrectionsController extends BasicController { +public class GTACoachRevisionAndCorrectionsController extends BasicController implements Activateable2 { private final VelocityContainer mainVC; private Link returnToRevisionsButton, closeRevisionsButton, collectButton; private CloseableModalController cmc; - private DirectoryController revisionsCtrl, correctionsCtrl; + private Map<Integer,DirectoryController> loopToRevisionCtrl = new HashMap<>(); + //private DirectoryController revisionsCtrl, correctionsCtrl; private SubmitDocumentsController uploadCorrectionsCtrl; private ConfirmRevisionsController confirmReturnToRevisionsCtrl; private DialogBoxController confirmCloseRevisionProcessCtrl, confirmCollectCtrl; @@ -185,10 +191,11 @@ public class GTACoachRevisionAndCorrectionsController extends BasicController { boolean hasDocuments = TaskHelper.hasDocuments(documentsDir); if(hasDocuments) { - revisionsCtrl = new DirectoryController(ureq, getWindowControl(), documentsDir, documentsContainer, + DirectoryController revisionsCtrl = new DirectoryController(ureq, getWindowControl(), documentsDir, documentsContainer, "coach.revisions.description", "bulk.revisions", "revisions.zip"); listenTo(revisionsCtrl); mainVC.put(cmpName, revisionsCtrl.getInitialComponent()); + loopToRevisionCtrl.put(iteration, revisionsCtrl); } else if (assignedTask.getTaskStatus() == TaskProcess.revision) { String msg = "<i class='o_icon o_icon_error'> </i> " + translate("coach.corrections.rejected"); TextFactory.createTextComponentFromString(cmpName, msg, null, true, mainVC); @@ -211,7 +218,7 @@ public class GTACoachRevisionAndCorrectionsController extends BasicController { boolean hasDocuments = TaskHelper.hasDocuments(documentsDir); if(hasDocuments) { - correctionsCtrl = new DirectoryController(ureq, getWindowControl(), documentsDir, documentsContainer, + DirectoryController correctionsCtrl = new DirectoryController(ureq, getWindowControl(), documentsDir, documentsContainer, "run.coach.corrections.description", "bulk.review", "review"); listenTo(correctionsCtrl); mainVC.put(cmpName, correctionsCtrl.getInitialComponent()); @@ -260,6 +267,20 @@ public class GTACoachRevisionAndCorrectionsController extends BasicController { closeRevisionsButton.setVisible(!coachCourseEnv.isCourseReadOnly()); } + @Override + public void activate(UserRequest ureq, List<ContextEntry> entries, StateEntry state) { + if(entries == null || entries.size() <= 1) return; + + String type = entries.get(0).getOLATResourceable().getResourceableTypeName(); + if("Revision".equalsIgnoreCase(type)) { + int revisionLoop = entries.get(0).getOLATResourceable().getResourceableId().intValue(); + if(loopToRevisionCtrl.containsKey(revisionLoop)) { + List<ContextEntry> subEntriess = entries.subList(1, entries.size()); + loopToRevisionCtrl.get(revisionLoop).activate(ureq, subEntriess, null); + } + } + } + @Override protected void event(UserRequest ureq, Controller source, Event event) { if(uploadCorrectionsCtrl == source) { @@ -386,8 +407,7 @@ public class GTACoachRevisionAndCorrectionsController extends BasicController { } private void doCloseRevisionProcess() { - TaskProcess nextStep = gtaManager.nextStep(TaskProcess.correction, gtaNode); - assignedTask = gtaManager.updateTask(assignedTask, nextStep, gtaNode); + assignedTask = gtaManager.reviewedTask(assignedTask, gtaNode); gtaManager.log("Revision", "close revision", assignedTask, getIdentity(), assessedIdentity, assessedGroup, courseEnv, gtaNode); } } diff --git a/src/main/java/org/olat/course/nodes/gta/ui/GTACoachSelectionController.java b/src/main/java/org/olat/course/nodes/gta/ui/GTACoachSelectionController.java index 1b1e7ad24b7..1db0a9b946c 100644 --- a/src/main/java/org/olat/course/nodes/gta/ui/GTACoachSelectionController.java +++ b/src/main/java/org/olat/course/nodes/gta/ui/GTACoachSelectionController.java @@ -20,6 +20,7 @@ */ package org.olat.course.nodes.gta.ui; +import java.io.File; import java.util.Collections; import java.util.List; @@ -29,6 +30,7 @@ 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.download.DisplayOrDownloadComponent; import org.olat.core.gui.components.link.Link; import org.olat.core.gui.components.link.LinkFactory; import org.olat.core.gui.components.velocity.VelocityContainer; @@ -36,13 +38,19 @@ import org.olat.core.gui.control.Controller; 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.gui.control.generic.dtabs.Activateable2; import org.olat.core.id.Identity; +import org.olat.core.id.context.BusinessControlFactory; +import org.olat.core.id.context.ContextEntry; +import org.olat.core.id.context.StateEntry; +import org.olat.core.util.resource.OresHelper; import org.olat.course.archiver.ArchiveResource; import org.olat.course.groupsandrights.CourseGroupManager; import org.olat.course.nodes.ArchiveOptions; import org.olat.course.nodes.GTACourseNode; import org.olat.course.nodes.gta.GTAManager; import org.olat.course.nodes.gta.GTAType; +import org.olat.course.nodes.gta.ui.component.DownloadDocumentMapper; import org.olat.course.nodes.gta.ui.events.SelectBusinessGroupEvent; import org.olat.course.nodes.gta.ui.events.SelectIdentityEvent; import org.olat.course.run.environment.CourseEnvironment; @@ -59,7 +67,7 @@ import org.springframework.beans.factory.annotation.Autowired; * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ -public class GTACoachSelectionController extends BasicController { +public class GTACoachSelectionController extends BasicController implements Activateable2 { private GTACoachController coachingCtrl; private GTACoachedGroupListController groupListCtrl; @@ -67,6 +75,9 @@ public class GTACoachSelectionController extends BasicController { private final Link backLink, downloadButton; private final VelocityContainer mainVC; + + private final String solutionMapperUri; + private final DisplayOrDownloadComponent solutionDownloadCmp; private final GTACourseNode gtaNode; private final CourseEnvironment courseEnv; @@ -90,6 +101,11 @@ public class GTACoachSelectionController extends BasicController { mainVC = createVelocityContainer("coach_selection"); backLink = LinkFactory.createLinkBack(mainVC, this); + File solutionsDir = gtaManager.getSolutionsDirectory(courseEnv, gtaNode); + solutionMapperUri = registerMapper(ureq, new DownloadDocumentMapper(solutionsDir)); + solutionDownloadCmp = new DisplayOrDownloadComponent("download", null); + mainVC.put("solutionDownload", solutionDownloadCmp); + downloadButton = LinkFactory.createButton("bulk.download.title", mainVC, this); downloadButton.setTranslator(getTranslator()); @@ -136,6 +152,33 @@ public class GTACoachSelectionController extends BasicController { // } + @Override + public void activate(UserRequest ureq, List<ContextEntry> entries, StateEntry state) { + if(entries == null || entries.isEmpty()) return; + + String type = entries.get(0).getOLATResourceable().getResourceableTypeName(); + Long key = entries.get(0).getOLATResourceable().getResourceableId(); + if("Identity".equalsIgnoreCase(type)) { + if(participantListCtrl != null && participantListCtrl.hasIdentityKey(key)) { + Identity selectedIdentity = securityManager.loadIdentityByKey(key); + List<ContextEntry> subEntries = entries.subList(1, entries.size()); + doSelectParticipant(ureq, selectedIdentity).activate(ureq, subEntries, entries.get(0).getTransientState()); + } + } else if("BusinessGroup".equalsIgnoreCase(type)) { + if(groupListCtrl != null) { + BusinessGroup group = groupListCtrl.getBusinessGroup(key); + if(group != null) { + List<ContextEntry> subEntries = entries.subList(1, entries.size()); + doSelectBusinessGroup(ureq, group).activate(ureq, subEntries, entries.get(0).getTransientState()); + } + } + } else if("Solution".equals(type) && entries.size() > 1) { + String path = BusinessControlFactory.getInstance().getPath(entries.get(1)); + String url = solutionMapperUri + "/" + path; + solutionDownloadCmp.triggerFileDownload(url); + } + } + @Override protected void event(UserRequest ureq, Controller source, Event event) { if(groupListCtrl == source) { @@ -195,17 +238,22 @@ public class GTACoachSelectionController extends BasicController { } } - private void doSelectBusinessGroup(UserRequest ureq, BusinessGroup group) { + private Activateable2 doSelectBusinessGroup(UserRequest ureq, BusinessGroup group) { removeAsListenerAndDispose(coachingCtrl); - coachingCtrl = new GTACoachController(ureq, getWindowControl(), courseEnv, gtaNode, coachCourseEnv, group, true, true, false); + + WindowControl swControl = addToHistory(ureq, OresHelper.clone(group), null); + coachingCtrl = new GTACoachController(ureq, swControl, courseEnv, gtaNode, coachCourseEnv, group, true, true, false); listenTo(coachingCtrl); mainVC.put("selection", coachingCtrl.getInitialComponent()); + return coachingCtrl; } - private void doSelectParticipant(UserRequest ureq, Identity identity) { + private Activateable2 doSelectParticipant(UserRequest ureq, Identity identity) { removeAsListenerAndDispose(coachingCtrl); - coachingCtrl = new GTACoachController(ureq, getWindowControl(), courseEnv, gtaNode, coachCourseEnv, identity, true, true, false); + WindowControl swControl = addToHistory(ureq, OresHelper.createOLATResourceableInstance("Identity", identity.getKey()), null); + coachingCtrl = new GTACoachController(ureq, swControl, courseEnv, gtaNode, coachCourseEnv, identity, true, true, false); listenTo(coachingCtrl); mainVC.put("selection", coachingCtrl.getInitialComponent()); + return coachingCtrl; } } diff --git a/src/main/java/org/olat/course/nodes/gta/ui/GTACoachedGroupListController.java b/src/main/java/org/olat/course/nodes/gta/ui/GTACoachedGroupListController.java index d42f1a5d9c1..885473c6de7 100644 --- a/src/main/java/org/olat/course/nodes/gta/ui/GTACoachedGroupListController.java +++ b/src/main/java/org/olat/course/nodes/gta/ui/GTACoachedGroupListController.java @@ -87,6 +87,15 @@ public class GTACoachedGroupListController extends GTACoachedListController { initForm(ureq); updateModel(); } + + public BusinessGroup getBusinessGroup(Long key) { + for(BusinessGroup group:coachedGroups) { + if(group.getKey().equals(key)) { + return group; + } + } + return null; + } @Override protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { diff --git a/src/main/java/org/olat/course/nodes/gta/ui/GTACoachedParticipantListController.java b/src/main/java/org/olat/course/nodes/gta/ui/GTACoachedParticipantListController.java index 1d39dc2d3ac..38e46c283d5 100644 --- a/src/main/java/org/olat/course/nodes/gta/ui/GTACoachedParticipantListController.java +++ b/src/main/java/org/olat/course/nodes/gta/ui/GTACoachedParticipantListController.java @@ -131,6 +131,17 @@ public class GTACoachedParticipantListController extends GTACoachedListControlle updateModel(); } + public boolean hasIdentityKey(Long identityKey) { + if(assessableIdentities != null) { + for(UserPropertiesRow row:assessableIdentities) { + if(row.getIdentityKey().equals(identityKey)) { + return true; + } + } + } + return false; + } + public List<Identity> getAssessableIdentities() { List<Identity> identities = new ArrayList<>(); collectIdentities(new Consumer<Identity>() { diff --git a/src/main/java/org/olat/course/nodes/gta/ui/GTAParticipantController.java b/src/main/java/org/olat/course/nodes/gta/ui/GTAParticipantController.java index 567e0b9ec60..7e4e6cda5ae 100644 --- a/src/main/java/org/olat/course/nodes/gta/ui/GTAParticipantController.java +++ b/src/main/java/org/olat/course/nodes/gta/ui/GTAParticipantController.java @@ -39,9 +39,12 @@ import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.generic.closablewrapper.CloseableCalloutWindowController; +import org.olat.core.gui.control.generic.dtabs.Activateable2; import org.olat.core.gui.control.generic.modal.DialogBoxController; import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory; import org.olat.core.id.Identity; +import org.olat.core.id.context.ContextEntry; +import org.olat.core.id.context.StateEntry; import org.olat.core.util.StringHelper; import org.olat.core.util.coordinate.CoordinatorManager; import org.olat.core.util.io.SystemFilenameFilter; @@ -75,7 +78,7 @@ import org.springframework.beans.factory.annotation.Autowired; * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ -public class GTAParticipantController extends GTAAbstractController { +public class GTAParticipantController extends GTAAbstractController implements Activateable2 { private Link submitButton, openGroupButton, changeGroupLink; @@ -105,7 +108,6 @@ public class GTAParticipantController extends GTAAbstractController { protected void initContainer(UserRequest ureq) { mainVC = createVelocityContainer("run"); putInitialPanel(mainVC); - initFlow() ; } @@ -411,7 +413,7 @@ public class GTAParticipantController extends GTAAbstractController { documentsContainer = gtaManager.getCorrectionContainer(courseEnv, gtaNode, getIdentity()); } - if(TaskHelper.hasDocuments(documentsDir)) { + if(!waiting && TaskHelper.hasDocuments(documentsDir)) { correctionsCtrl = new DirectoryController(ureq, getWindowControl(), documentsDir, documentsContainer, "run.corrections.description", "bulk.review", "review"); listenTo(correctionsCtrl); @@ -630,6 +632,34 @@ public class GTAParticipantController extends GTAAbstractController { // } + @Override + public void activate(UserRequest ureq, List<ContextEntry> entries, StateEntry state) { + if(entries == null || entries.isEmpty()) return; + + String type = entries.get(0).getOLATResourceable().getResourceableTypeName(); + if("Correction".equalsIgnoreCase(type)) { + int revisionLoop = entries.get(0).getOLATResourceable().getResourceableId().intValue(); + if(revisionLoop == 0) { + if(correctionsCtrl != null) { + List<ContextEntry> subEntries = entries.subList(1, entries.size()); + correctionsCtrl.activate(ureq, subEntries, null); + } + } else if(revisionDocumentsCtrl != null) { + revisionDocumentsCtrl.activate(ureq, entries, null); + } + } else if("Solution".equalsIgnoreCase(type)) { + if(solutionsCtrl != null) { + List<ContextEntry> subEntries = entries.subList(1, entries.size()); + solutionsCtrl.activate(ureq, subEntries, null); + } + } else if("Assessment".equalsIgnoreCase(type)) { + if(gradingCtrl != null) { + List<ContextEntry> subEntries = entries.subList(1, entries.size()); + gradingCtrl.activate(ureq, subEntries, null); + } + } + } + @Override protected void processEvent(TaskMultiUserEvent event) { if(TaskMultiUserEvent.SUMBIT_TASK.equals(event.getCommand())) { diff --git a/src/main/java/org/olat/course/nodes/gta/ui/GTAParticipantRevisionAndCorrectionsController.java b/src/main/java/org/olat/course/nodes/gta/ui/GTAParticipantRevisionAndCorrectionsController.java index cf5f6506f04..e191342351d 100644 --- a/src/main/java/org/olat/course/nodes/gta/ui/GTAParticipantRevisionAndCorrectionsController.java +++ b/src/main/java/org/olat/course/nodes/gta/ui/GTAParticipantRevisionAndCorrectionsController.java @@ -22,7 +22,9 @@ package org.olat.course.nodes.gta.ui; import java.io.File; import java.util.ArrayList; import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.olat.basesecurity.GroupRoles; import org.olat.core.gui.UserRequest; @@ -34,10 +36,13 @@ import org.olat.core.gui.control.Controller; 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.gui.control.generic.dtabs.Activateable2; import org.olat.core.gui.control.generic.modal.DialogBoxController; import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory; import org.olat.core.id.Identity; import org.olat.core.id.OLATResourceable; +import org.olat.core.id.context.ContextEntry; +import org.olat.core.id.context.StateEntry; import org.olat.core.util.StringHelper; import org.olat.core.util.coordinate.CoordinatorManager; import org.olat.core.util.io.SystemFilenameFilter; @@ -66,14 +71,14 @@ import org.springframework.beans.factory.annotation.Autowired; * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ -public class GTAParticipantRevisionAndCorrectionsController extends BasicController { +public class GTAParticipantRevisionAndCorrectionsController extends BasicController implements Activateable2 { private Link submitRevisionButton; private final VelocityContainer mainVC; private DialogBoxController confirmSubmitDialog; private SubmitDocumentsController uploadRevisionsCtrl; - private DirectoryController correctionsCtrl, revisionsCtrl; + private final Map<Integer,DirectoryController> revisionLoopToCorrectionsCtrl = new HashMap<>(); private Task assignedTask; private final boolean businessGroupTask; @@ -196,7 +201,7 @@ public class GTAParticipantRevisionAndCorrectionsController extends BasicControl boolean hasDocument = TaskHelper.hasDocuments(documentsDir); if(hasDocument) { - revisionsCtrl = new DirectoryController(ureq, getWindowControl(), documentsDir, documentsContainer, + DirectoryController revisionsCtrl = new DirectoryController(ureq, getWindowControl(), documentsDir, documentsContainer, "run.revised.description", "bulk.submitted.revisions", "revisions.zip"); listenTo(revisionsCtrl); mainVC.put(cmpName, revisionsCtrl.getInitialComponent()); @@ -217,14 +222,29 @@ public class GTAParticipantRevisionAndCorrectionsController extends BasicControl boolean hasDocument = TaskHelper.hasDocuments(documentsDir); if(hasDocument) { - correctionsCtrl = new DirectoryController(ureq, getWindowControl(), documentsDir, documentsContainer, + DirectoryController correctionsCtrl = new DirectoryController(ureq, getWindowControl(), documentsDir, documentsContainer, "run.corrections.description", "bulk.review", "review"); listenTo(correctionsCtrl); mainVC.put(cmpName, correctionsCtrl.getInitialComponent()); + revisionLoopToCorrectionsCtrl.put(iteration, correctionsCtrl); } return hasDocument; } + @Override + public void activate(UserRequest ureq, List<ContextEntry> entries, StateEntry state) { + if(entries == null || entries.size() <= 1) return; + + String type = entries.get(0).getOLATResourceable().getResourceableTypeName(); + int revisionLoop = entries.get(0).getOLATResourceable().getResourceableId().intValue(); + if("Correction".equalsIgnoreCase(type)) { + if(revisionLoopToCorrectionsCtrl.containsKey(revisionLoop)) { + List<ContextEntry> subEntries = entries.subList(1, entries.size()); + revisionLoopToCorrectionsCtrl.get(revisionLoop).activate(ureq, subEntries, null); + } + } + } + @Override protected void event(UserRequest ureq, Controller source, Event event) { if(uploadRevisionsCtrl == source) { diff --git a/src/main/java/org/olat/course/nodes/gta/ui/GTARunController.java b/src/main/java/org/olat/course/nodes/gta/ui/GTARunController.java index 242da92eccb..9fb75bed33d 100644 --- a/src/main/java/org/olat/course/nodes/gta/ui/GTARunController.java +++ b/src/main/java/org/olat/course/nodes/gta/ui/GTARunController.java @@ -19,6 +19,8 @@ */ package org.olat.course.nodes.gta.ui; +import java.util.List; + import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; import org.olat.core.gui.components.link.Link; @@ -31,7 +33,11 @@ import org.olat.core.gui.control.Controller; 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.gui.control.generic.dtabs.Activateable2; import org.olat.core.gui.control.generic.messages.MessageUIFactory; +import org.olat.core.id.context.ContextEntry; +import org.olat.core.id.context.StateEntry; +import org.olat.core.util.resource.OresHelper; import org.olat.course.nodes.GTACourseNode; import org.olat.course.nodes.gta.GTAManager; import org.olat.course.nodes.gta.model.Membership; @@ -46,7 +52,7 @@ import org.springframework.beans.factory.annotation.Autowired; * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ -public class GTARunController extends BasicController { +public class GTARunController extends BasicController implements Activateable2 { private GTAParticipantController runCtrl; private GTACoachSelectionController coachCtrl; @@ -77,14 +83,14 @@ public class GTARunController extends BasicController { segmentView = SegmentViewFactory.createSegmentView("segments", mainVC, this); runLink = LinkFactory.createLink("run.run", mainVC, this); - segmentView.addSegment(runLink, true); + segmentView.addSegment(runLink, false); coachLink = LinkFactory.createLink("run.coach", mainVC, this); - segmentView.addSegment(coachLink, false); + segmentView.addSegment(coachLink, true); if(isManagementTabAvalaible(config)) { manageLink = LinkFactory.createLink("run.manage.coach", mainVC, this); segmentView.addSegment(manageLink, false); } - doOpenRun(ureq); + doOpenCoach(ureq); mainVC.put("segments", segmentView); putInitialPanel(mainVC); } else if(isManagementTabAvalaible(config)) { @@ -120,6 +126,37 @@ public class GTARunController extends BasicController { && (config.getBooleanSafe(GTACourseNode.GTASK_ASSIGNMENT) || config.getBooleanSafe(GTACourseNode.GTASK_SAMPLE_SOLUTION)); } + @Override + public void activate(UserRequest ureq, List<ContextEntry> entries, StateEntry state) { + if(entries == null || entries.isEmpty()) return; + + String type = entries.get(0).getOLATResourceable().getResourceableTypeName(); + if("coach".equalsIgnoreCase(type)) { + if(coachLink != null) { + List<ContextEntry> subEntries = entries.subList(1, entries.size()); + doOpenCoach(ureq).activate(ureq, subEntries, entries.get(0).getTransientState()); + if(segmentView != null) { + segmentView.select(coachLink); + } + } + } else if("management".equalsIgnoreCase(type)) { + if(manageLink != null) { + doManage(ureq); + if(segmentView != null) { + segmentView.select(manageLink); + } + } + } else if("identity".equalsIgnoreCase(type)) { + if(getIdentity().getKey().equals(entries.get(0).getOLATResourceable().getResourceableId())) { + List<ContextEntry> subEntries = entries.subList(1, entries.size()); + doOpenRun(ureq).activate(ureq, subEntries, entries.get(0).getTransientState()); + if(segmentView != null) { + segmentView.select(runLink); + } + } + } + } + @Override protected void event(UserRequest ureq, Component source, Event event) { if(source == segmentView) { @@ -143,25 +180,39 @@ public class GTARunController extends BasicController { // } - private void doOpenRun(UserRequest ureq) { + private Activateable2 doOpenRun(UserRequest ureq) { if(runCtrl == null) { createRun(ureq); + } else { + addToHistory(ureq, runCtrl); } - mainVC.put("segmentCmp", runCtrl.getInitialComponent()); + if(mainVC != null) { + mainVC.put("segmentCmp", runCtrl.getInitialComponent()); + } + return runCtrl; } - private void doOpenCoach(UserRequest ureq) { + private Activateable2 doOpenCoach(UserRequest ureq) { if(coachCtrl == null) { createCoach(ureq); + } else { + addToHistory(ureq, coachCtrl); } - mainVC.put("segmentCmp", coachCtrl.getInitialComponent()); + if(mainVC != null) { + mainVC.put("segmentCmp", coachCtrl.getInitialComponent()); + } + return coachCtrl; } private void doManage(UserRequest ureq) { if(manageCtrl == null) { createManage(ureq); + } else { + addToHistory(ureq, manageCtrl); + } + if(mainVC != null) { + mainVC.put("segmentCmp", manageCtrl.getInitialComponent()); } - mainVC.put("segmentCmp", manageCtrl.getInitialComponent()); } private GTAParticipantController createRun(UserRequest ureq) { @@ -175,14 +226,17 @@ public class GTARunController extends BasicController { private GTACoachSelectionController createCoach(UserRequest ureq) { removeAsListenerAndDispose(coachCtrl); - coachCtrl = new GTACoachSelectionController(ureq, getWindowControl(), userCourseEnv, gtaNode); + WindowControl swControl = addToHistory(ureq, OresHelper.createOLATResourceableType("coach"), null); + coachCtrl = new GTACoachSelectionController(ureq, swControl, userCourseEnv, gtaNode); listenTo(coachCtrl); return coachCtrl; } private GTACoachManagementController createManage(UserRequest ureq) { removeAsListenerAndDispose(manageCtrl); - manageCtrl = new GTACoachManagementController(ureq, getWindowControl(), userCourseEnv, gtaNode); + + WindowControl swControl = addToHistory(ureq, OresHelper.createOLATResourceableType("management"), null); + manageCtrl = new GTACoachManagementController(ureq, swControl, userCourseEnv, gtaNode); listenTo(manageCtrl); return manageCtrl; } diff --git a/src/main/java/org/olat/course/nodes/gta/ui/_content/coach_selection.html b/src/main/java/org/olat/course/nodes/gta/ui/_content/coach_selection.html index ca363c74ba8..391b4852ad5 100644 --- a/src/main/java/org/olat/course/nodes/gta/ui/_content/coach_selection.html +++ b/src/main/java/org/olat/course/nodes/gta/ui/_content/coach_selection.html @@ -27,4 +27,7 @@ #end $r.render("list") #end -</div> \ No newline at end of file +</div> +#if($r.available("solutionDownload")) + $r.render("solutionDownload") +#end \ No newline at end of file diff --git a/src/main/java/org/olat/course/nodes/gta/ui/_content/documents_readonly.html b/src/main/java/org/olat/course/nodes/gta/ui/_content/documents_readonly.html index 7dc61ff6a75..72d5a1ee0a6 100644 --- a/src/main/java/org/olat/course/nodes/gta/ui/_content/documents_readonly.html +++ b/src/main/java/org/olat/course/nodes/gta/ui/_content/documents_readonly.html @@ -9,4 +9,7 @@ #foreach($docInfo in $linkNames) <li>$r.render($docInfo.linkName) #if($docInfo.uploadedBy)<small>$r.translate("uploaded.by",$docInfo.uploadedBy)</small>#end #if($r.isNotNull($docInfo.lastModified))<small>$r.translate("lastmodified",$docInfo.lastModified)</small>#end</li> #end -</ul> \ No newline at end of file +</ul> +#if($r.available("download")) + $r.render("download") +#end \ No newline at end of file diff --git a/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_de.properties index d5ffcdf00a1..4bbde7169b5 100644 --- a/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_de.properties @@ -123,13 +123,30 @@ mailto.group=E-Mail an Gruppe mailto.user=E-Mail an Benutzer max.documents=Max. Anzahl von Dokumenten no.submission=Nicht abgegeben -notifications.correction=Neue Korrektur "{0}" von "{1}" -notifications.header=Gruppenaufgabe in Kurs "{0}" -notifications.revision.group=Neue \u00DCberarbeitungen "{0}" von "{2}" f\u00FCr die Gruppe "{1}" hochgeladen -notifications.revision.individual=Neue \u00DCberarbeitungen "{0}" von "{1}" -notifications.solution=Neue Musterl\u00F6sung "{0}" -notifications.submission.group=Neue abgegebene Dokumente "{0}" von "{2}" f\u00FCr Gruppe "{1}" hochgeladen -notifications.submission.individual=Neue abgegebene Dokumente "{0}" von "{1}" +notifications.accepted=Ihre Aufgabe "{0}" im Kurs "{1}" wurde akzeptiert. +notifications.assessment.score.passed=Für die Aufgabe "{0}" im Kurs "{1}" haben Sie eine Bewertung erhalten. Ihre Punktzahl beträgt {2} Punkte und Sie haben {3}. +notifications.assessment.score=Für die Aufgabe "{0}" im Kurs "{1}" haben Sie eine Bewertung erhalten. Ihre Punktzahl beträgt {2} Punkte. +notifications.assessment.passed=Für die Aufgabe "{0}" im Kurs "{1}" haben Sie eine Bewertung erhalten. Sie haben {3}. +notifications.assessment.doc=Für die Aufgabe "{0}" im Kurs "{1}" stehen bereit Bewertungsdokumente zum Download: "{2}". +notifications.assessment.passed.true=bestanden +notifications.assessment.passed.false=nicht bestanden +notifications.group.header=Gruppenaufgabe in Kurs "{0}" +notifications.group.header.task=Gruppenaufgabe "{0}" in Kurs "{1}" +notifications.individual.header=Aufgabe in Kurs "{0}" +notifications.individual.header.task=Aufgabe "{0}" in Kurs "{1}" +notifications.solution=Im Kurs "{0}" steht die folgende Musterlösung zum Download bereit: "{1}". +notifications.solution.task=Für die Aufgabe "{0}" im Kurs "{1}" steht die folgende Musterlösung zum Download bereit: "{2}". +notifications.correction=Für Ihre Aufgabe "{0}" im Kurs "{1}" wurde eine Überarbeitung angefordert. +notifications.correction.duedate=$\:notifications.correction Die Überarbeitungsfrist wurde auf den {3} gesetzt. +notifications.correction.doc=Sie haben ein Feedback für die Aufgabe "{0}" im Kurs "{1}" von {3} erhalten: "{2}". +notifications.submission.individual=Die Aufgabe "{0}" im Kurs "{1}" wurde von "{2}" abgegeben. +notifications.submission.group=Die Aufgabe "{0}" im Kurs "{1}" wurde f\u00FCr Gruppe "{2}" abgegeben. +notifications.submission.group.doc==Für die Aufgabe "{0}" im Kurs "{1}" wurde eine neue Dokument "{2}" von "{3}" f\u00FCr Gruppe "{4}" hochgeladen. +notifications.submission.individual.doc=Für die Aufgabe "{0}" im Kurs "{1}" wurde eine neue Dokument "{2}" von "{3}" hochgeladen. +notifications.revision.group=Für die Aufgabe "{0}" im Kurs "{1}" wurde eine neue \u00DCberarbeitungen f\u00FCr die Gruppe "{2}" abgegeben. +notifications.revision.individual=Für die Aufgabe "{0}" im Kurs "{1}" wurde eine neue \u00DCberarbeitungen von "{2}" abgegeben. +notifications.revision.group.doc=Für die Aufgabe "{0}" im Kurs "{1}" wurde eine neue \u00DCberarbeitungen "{2}" f\u00FCr die Gruppe "{3}" hochgeladen. +notifications.revision.individual.doc=Für die Aufgabe "{0}" im Kurs "{1}" wurde eine neue \u00DCberarbeitungen "{2}" von "{3}" hochgeladen. open.group=Gruppe \u00F6ffnen pane.tab.accessibility=Zugang pane.tab.assignment=Aufgabenstellung diff --git a/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_en.properties index 50ec2a04c38..7ae1dcdf12e 100644 --- a/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_en.properties @@ -119,13 +119,30 @@ mailto.group=Mail to group mailto.user=Mail to user max.documents=Max. number of documents no.submission=No submission -notifications.correction=New correction "{0}" of "{1}" -notifications.header=Group task in course "{0}" -notifications.revision.group=New revisions "{0}" of "{2}" uploaded for group "{1}" -notifications.revision.individual=New revisions "{0}" of "{1}" -notifications.solution=New sample solutions "{0}" -notifications.submission.group=New documents "{0}" von "{2}" submitted for group "{1}" -notifications.submission.individual=New submitted documents "{0}" of "{1}" +notifications.accepted=Your task "{0}" in the course "{1}" has been accepted. +notifications.assessment.score.passed=You have got a grading for the task "{0}" in the course "{1}". Your score ist {2} and you have {3}. +notifications.assessment.score=You have got a grading for the task "{0}" in the course "{1}". Your score ist {2}. +notifications.assessment.passed=You have got a grading for the task "{0}" in the course "{1}". You have {3}. +notifications.assessment.doc=You have got a grading document for the task "{0}" in the course "{1}": "{2}". +notifications.solution=In the course "{0}" the following sample solution is available for download: "{1}". +notifications.solution.task=For the task "{0}" in the course "{1}" the following sample solution is available for download: "{2}". +notifications.assessment.passed.true=passed +notifications.assessment.passed.false=failed +notifications.group.header=Group task in course "{0}" +notifications.group.header.task=Group task "{0}" in course "{1}" +notifications.individual.header=Task in course "{0}" +notifications.individual.header.task=Task "{0}" in course "{1}" +notifications.correction=You have received a feedback for the task "{0}" in the course "{1}". +notifications.correction.duedate=$\:notifications.correction The deadline for the revision is set to the {2}. +notifications.correction.doc=You have received a feedback for the task "{0}" in the course "{1}" by {3}: "{2}". +notifications.submission.individual=The task "{0}" in course "{1}" was submitted by "{2}". +notifications.submission.group=The task "{0}" in course "{1}" was submitted for the group "{2}". +notifications.submission.group.doc==For the task "{0}" in course "{1}" was a new document "{2}" submitted by "{3}" for the group "{4}". +notifications.submission.individual.doc=For the task "{0}" in course "{1}" was a new document "{2}" submitted by "{3}". +notifications.revision.group=For the task "{0}" in course "{1}" was a new revision submitted for the group "{2}". +notifications.revision.individual=For the task "{0}" in course "{1}" was a new revision submitted by "{2}". +notifications.revision.group.doc=For the task "{0}" in course "{1}" was a new revision "{2}" submitted for the group "{3}". +notifications.revision.individual.doc=For the task "{0}" in course "{1}" was a new revision "{2}" submitted by "{3}". open.group=Open group pane.tab.accessibility=Access pane.tab.assignment=Assignment diff --git a/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_fr.properties b/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_fr.properties index 2746cb83bf0..4b613d07336 100644 --- a/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_fr.properties +++ b/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_fr.properties @@ -118,13 +118,6 @@ mail.confirm.assignment.subject=Affectation mailto.group=Courriel au groupe mailto.user=Courriel au utilisateur max.documents=Nombre maximum de documents -notifications.correction=Nouvelles corrections "{0}" de "{1}" -notifications.header=Devoirs de groupe du cours "{0}" -notifications.revision.group=De nouveaux remaniements "{0}" de "{2}" pour le groupe "{1}" ont \u00E9t\u00E9 t\u00E9l\u00E9charg\u00E9s. -notifications.revision.individual=De nouveaux remaniements "{0}" de "{1}" -notifications.solution=Nouvelle solution "{0}" -notifications.submission.group=De nouveaux documents "{0}" ont \u00E9t\u00E9 d\u00E9pos\u00E9s par "{1}" pour le groupe "{1}". -notifications.submission.individual=De nouveaux documents "{0}" d\u00E9pos\u00E9s par "{1}" open.group=Ouvrir le groupe pane.tab.accessibility=Acc\u00E8s pane.tab.assignment=Affectation diff --git a/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_it.properties b/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_it.properties index 9fa475155bb..fb6f1a00a44 100644 --- a/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_it.properties +++ b/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_it.properties @@ -118,13 +118,6 @@ mail.confirm.assignment.subject=Assegnazione mailto.group=E-mail al gruppo mailto.user=E-mail all'utente max.documents=Numero massimo di documenti -notifications.correction=Nuova correzione "{0}" di "{1}" -notifications.header=Compito di gruppo nel corso "{0}" -notifications.revision.group=Nuove revisioni "{0}" di "{2}" caricate per il gruppo "{1}" -notifications.revision.individual=Nuove revisioni "{0}" di "{1}" -notifications.solution=Nuova soluzione "{0}" -notifications.submission.group=Nuovi documenti "{0}" di "{2}" consegnati per il gruppo "{1}" -notifications.submission.individual=Nuovi documenti consegnati "{0}" di "{1}" open.group=Aprire il gruppo pane.tab.accessibility=Accesso pane.tab.assignment=Assegnazione diff --git a/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_pl.properties b/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_pl.properties index 6b3776df2b3..3be48f06b75 100644 --- a/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_pl.properties +++ b/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_pl.properties @@ -114,13 +114,6 @@ mail.confirm.assignment.subject=Zadanie mailto.group=E-mail do grupy mailto.user=E-mail do uczestnika max.documents=Max. liczba dokument\u00F3w -notifications.correction=New correction "{0}" of "{1}" -notifications.header=Zadanie grupowe w kursie "{0}" -notifications.revision.group=New revisions "{0}" of "{2}" uploaded for group "{1}" -notifications.revision.individual=New revisions "{0}" of "{1}" -notifications.solution=Nowe przyk\u0142adowe rozwi\u0105zanie dla "{0}" -notifications.submission.group=New documents "{0}" out of "{2}" submitted for group "{1}" -notifications.submission.individual=New submitted documents "{0}" of "{1}" open.group=Open group pane.tab.accessibility=Dost\u0119p pane.tab.assignment=Assignment diff --git a/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_pt_BR.properties b/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_pt_BR.properties index ce1170b59d7..9961972fc82 100644 --- a/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_pt_BR.properties +++ b/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_pt_BR.properties @@ -118,13 +118,6 @@ mail.confirm.assignment.subject=Atribui\u00E7\u00E3o mailto.group=Email para o grupo mailto.user=Email para o usu\u00E1rio max.documents=N\u00FAmero m\u00E1ximo de documentos -notifications.correction=Nova corre\u00E7\u00E3o "{0}" of "{1}" -notifications.header=Tarefa de Grupo no curso "{0}" -notifications.revision.group=Novas revis\u00F5es "{0}" de "{2}" enviadas para o grupo "{1}" -notifications.revision.individual=Novas revis\u00F5es "{0}" de "{1}" -notifications.solution=Nova solu\u00E7\u00F5es de amostra "{0}" -notifications.submission.group=Novos documentos "{0}" de "{2}" apresentados para o grupo "{1}" -notifications.submission.individual=Novos documentos enviados "{0}" of "{1}" open.group=Abrir grupo pane.tab.accessibility=Acesso pane.tab.assignment=Atribui\u00E7\u00E3o diff --git a/src/main/java/org/olat/course/nodes/gta/ui/component/DownloadDocumentMapper.java b/src/main/java/org/olat/course/nodes/gta/ui/component/DownloadDocumentMapper.java new file mode 100644 index 00000000000..40a472e86c3 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/gta/ui/component/DownloadDocumentMapper.java @@ -0,0 +1,56 @@ +/** + * <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.component; + +import java.io.File; + +import javax.servlet.http.HttpServletRequest; + +import org.olat.core.dispatcher.mapper.Mapper; +import org.olat.core.gui.media.FileMediaResource; +import org.olat.core.gui.media.MediaResource; +import org.olat.core.gui.media.NotFoundMediaResource; + +/** + * + * Initial date: 9 août 2017<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class DownloadDocumentMapper implements Mapper { + + private final File documentDir; + + public DownloadDocumentMapper(File documentDir) { + this.documentDir = documentDir; + } + + @Override + public MediaResource handle(String relPath, HttpServletRequest request) { + if(relPath != null && relPath.startsWith("/")) { + relPath = relPath.substring(1, relPath.length()); + } + File document = new File(documentDir, relPath); + if(document.exists() && document.getParentFile().equals(documentDir)) { + return new FileMediaResource(document, true); + } + return new NotFoundMediaResource(relPath); + } +} diff --git a/src/main/java/org/olat/course/nodes/ms/MSCourseNodeRunController.java b/src/main/java/org/olat/course/nodes/ms/MSCourseNodeRunController.java index 58037ef4cd0..f383cb3e4c3 100644 --- a/src/main/java/org/olat/course/nodes/ms/MSCourseNodeRunController.java +++ b/src/main/java/org/olat/course/nodes/ms/MSCourseNodeRunController.java @@ -30,10 +30,15 @@ import java.util.List; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.download.DisplayOrDownloadComponent; 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.gui.control.generic.dtabs.Activateable2; +import org.olat.core.id.context.BusinessControlFactory; +import org.olat.core.id.context.ContextEntry; +import org.olat.core.id.context.StateEntry; import org.olat.core.util.Formatter; import org.olat.core.util.StringHelper; import org.olat.core.util.Util; @@ -55,9 +60,12 @@ import org.springframework.beans.factory.annotation.Autowired; * Initial Date: Jun 16, 2004 * @author gnaegi */ -public class MSCourseNodeRunController extends BasicController { +public class MSCourseNodeRunController extends BasicController implements Activateable2 { private final VelocityContainer myContent; + private DisplayOrDownloadComponent download; + + private String mapperUri; private final boolean showLog; private boolean hasScore, hasPassed, hasComment; private final UserCourseEnvironment userCourseEnv; @@ -132,8 +140,8 @@ public class MSCourseNodeRunController extends BasicController { myContent.contextPut("changelogconfig", courseModule.isDisplayChangeLog()); // Push variables to velocity page - exposeConfigToVC(ureq, config); - exposeUserDataToVC(ureq, userCourseEnv, courseNode); + exposeConfigToVC(ureq); + exposeUserDataToVC(ureq); putInitialPanel(myContent); } @@ -158,6 +166,20 @@ public class MSCourseNodeRunController extends BasicController { return hasComment; } + @Override + public void activate(UserRequest ureq, List<ContextEntry> entries, StateEntry state) { + if(entries == null || entries.isEmpty()) return; + + String type = entries.get(0).getOLATResourceable().getResourceableTypeName(); + if(type.startsWith("path")) { + if(download != null) { + String path = BusinessControlFactory.getInstance().getPath(entries.get(0)); + String url = mapperUri + "/" + path; + download.triggerFileDownload(url); + } + } + } + /** * @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest, org.olat.core.gui.components.Component, org.olat.core.gui.control.Event) */ @@ -170,21 +192,22 @@ public class MSCourseNodeRunController extends BasicController { } } - private void exposeConfigToVC(UserRequest ureq, ModuleConfiguration config) { + private void exposeConfigToVC(UserRequest ureq) { + ModuleConfiguration config = courseNode.getModuleConfiguration(); myContent.contextPut(MSCourseNode.CONFIG_KEY_HAS_SCORE_FIELD, config.get(MSCourseNode.CONFIG_KEY_HAS_SCORE_FIELD)); myContent.contextPut(MSCourseNode.CONFIG_KEY_HAS_PASSED_FIELD, config.get(MSCourseNode.CONFIG_KEY_HAS_PASSED_FIELD)); myContent.contextPut(MSCourseNode.CONFIG_KEY_HAS_COMMENT_FIELD, config.get(MSCourseNode.CONFIG_KEY_HAS_COMMENT_FIELD)); String infoTextUser = (String) config.get(MSCourseNode.CONFIG_KEY_INFOTEXT_USER); if(StringHelper.containsNonWhitespace(infoTextUser)) { - myContent.contextPut(MSCourseNode.CONFIG_KEY_INFOTEXT_USER, infoTextUser); - myContent.contextPut("in-disclaimer", isPanelOpen(ureq, "disclaimer", true)); + myContent.contextPut(MSCourseNode.CONFIG_KEY_INFOTEXT_USER, infoTextUser); + myContent.contextPut("in-disclaimer", isPanelOpen(ureq, "disclaimer", true)); } myContent.contextPut(MSCourseNode.CONFIG_KEY_PASSED_CUT_VALUE, AssessmentHelper.getRoundedScore((Float)config.get(MSCourseNode.CONFIG_KEY_PASSED_CUT_VALUE))); myContent.contextPut(MSCourseNode.CONFIG_KEY_SCORE_MIN, AssessmentHelper.getRoundedScore((Float)config.get(MSCourseNode.CONFIG_KEY_SCORE_MIN))); myContent.contextPut(MSCourseNode.CONFIG_KEY_SCORE_MAX, AssessmentHelper.getRoundedScore((Float)config.get(MSCourseNode.CONFIG_KEY_SCORE_MAX))); } - private void exposeUserDataToVC(UserRequest ureq, UserCourseEnvironment userCourseEnv, PersistentAssessableCourseNode courseNode) { + private void exposeUserDataToVC(UserRequest ureq) { AssessmentEntry assessmentEntry = courseNode.getUserAssessmentEntry(userCourseEnv); if(assessmentEntry == null) { myContent.contextPut("hasPassedValue", Boolean.FALSE); @@ -213,10 +236,14 @@ public class MSCourseNodeRunController extends BasicController { if(courseNode.hasIndividualAsssessmentDocuments()) { List<File> docs = courseNode.getIndividualAssessmentDocuments(userCourseEnv); - String mapperUri = registerCacheableMapper(ureq, null, new DocumentsMapper(docs)); + mapperUri = registerCacheableMapper(ureq, null, new DocumentsMapper(docs)); myContent.contextPut("docsMapperUri", mapperUri); myContent.contextPut("docs", docs); myContent.contextPut("in-assessmentDocuments", isPanelOpen(ureq, "assessmentDocuments", true)); + if(download == null) { + download = new DisplayOrDownloadComponent("", null); + myContent.put("download", download); + } } } } diff --git a/src/main/java/org/olat/course/nodes/ms/_content/run.html b/src/main/java/org/olat/course/nodes/ms/_content/run.html index c0a109a7d83..4104256a4ab 100644 --- a/src/main/java/org/olat/course/nodes/ms/_content/run.html +++ b/src/main/java/org/olat/course/nodes/ms/_content/run.html @@ -156,4 +156,7 @@ #o_togglebox_end() </div> #end +#end +#if($r.available("download")) + $r.render("download") #end \ No newline at end of file diff --git a/src/main/resources/META-INF/persistence.xml b/src/main/resources/META-INF/persistence.xml index 4b8a40a57ca..9e839a67c67 100644 --- a/src/main/resources/META-INF/persistence.xml +++ b/src/main/resources/META-INF/persistence.xml @@ -95,6 +95,7 @@ <class>org.olat.course.nodes.gta.model.TaskLightImpl</class> <class>org.olat.course.nodes.gta.model.TaskDueDateImpl</class> <class>org.olat.course.nodes.gta.model.TaskListImpl</class> + <class>org.olat.course.nodes.gta.model.TaskRevisionDateImpl</class> <class>org.olat.course.certificate.model.CertificateImpl</class> <class>org.olat.course.certificate.model.CertificateStandalone</class> <class>org.olat.course.certificate.model.CertificateLightImpl</class> diff --git a/src/main/resources/database/mysql/alter_11_5_x_to_12_0_0.sql b/src/main/resources/database/mysql/alter_11_5_x_to_12_0_0.sql index f4b34b37891..86c43c0d8c0 100644 --- a/src/main/resources/database/mysql/alter_11_5_x_to_12_0_0.sql +++ b/src/main/resources/database/mysql/alter_11_5_x_to_12_0_0.sql @@ -225,3 +225,24 @@ alter table o_gta_task add column g_submission_due_date datetime default null; alter table o_gta_task add column g_revisions_due_date datetime default null; alter table o_gta_task add column g_solution_due_date datetime default null; +alter table o_gta_task add column g_acceptation_date datetime default null; +alter table o_gta_task add column g_solution_date datetime default null; +alter table o_gta_task add column g_graduation_date datetime default null; + +create table o_gta_task_revision_date ( + id bigint not null auto_increment, + creationdate datetime not null, + g_status varchar(36) not null, + g_rev_loop bigint not null, + g_date datetime not null, + fk_task bigint not null, + primary key (id) +); +alter table o_gta_task_revision_date ENGINE = InnoDB; + +alter table o_gta_task_revision_date add constraint gtaskrev_to_task_idx foreign key (fk_task) references o_gta_task (id); + + + + + diff --git a/src/main/resources/database/mysql/setupDatabase.sql b/src/main/resources/database/mysql/setupDatabase.sql index d86c282c9cf..538c65bf213 100644 --- a/src/main/resources/database/mysql/setupDatabase.sql +++ b/src/main/resources/database/mysql/setupDatabase.sql @@ -1858,6 +1858,9 @@ create table o_gta_task ( g_submission_date datetime, g_submission_revisions_date datetime, g_collection_date datetime, + g_acceptation_date datetime, + g_solution_date datetime, + g_graduation_date datetime, g_assignment_due_date datetime, g_submission_due_date datetime, g_revisions_due_date datetime, @@ -1869,6 +1872,16 @@ create table o_gta_task ( primary key (id) ); +create table o_gta_task_revision_date ( + id bigint not null auto_increment, + creationdate datetime not null, + g_status varchar(36) not null, + g_rev_loop bigint not null, + g_date datetime not null, + fk_task bigint not null, + primary key (id) +); + create table o_rem_reminder ( id bigint not null, creationdate datetime not null, @@ -2400,6 +2413,7 @@ alter table o_cl_checkbox ENGINE = InnoDB; alter table o_cl_check ENGINE = InnoDB; alter table o_gta_task_list ENGINE = InnoDB; alter table o_gta_task ENGINE = InnoDB; +alter table o_gta_task_revision_date ENGINE = InnoDB; alter table o_cer_template ENGINE = InnoDB; alter table o_cer_certificate ENGINE = InnoDB; alter table o_rem_reminder ENGINE = InnoDB; @@ -2622,6 +2636,8 @@ alter table o_gta_task add constraint gtask_to_bgroup_idx foreign key (fk_busine alter table o_gta_task_list add constraint gta_list_to_repo_entry_idx foreign key (fk_entry) references o_repositoryentry (repositoryentry_id); +alter table o_gta_task_revision_date add constraint gtaskrev_to_task_idx foreign key (fk_task) references o_gta_task (id); + -- reminders alter table o_rem_reminder add constraint rem_reminder_to_repo_entry_idx foreign key (fk_entry) references o_repositoryentry (repositoryentry_id); alter table o_rem_reminder add constraint rem_reminder_to_creator_idx foreign key (fk_creator) references o_bs_identity (id); diff --git a/src/main/resources/database/oracle/alter_11_5_x_to_12_0_0.sql b/src/main/resources/database/oracle/alter_11_5_x_to_12_0_0.sql index be4eb0da87a..435c868534c 100644 --- a/src/main/resources/database/oracle/alter_11_5_x_to_12_0_0.sql +++ b/src/main/resources/database/oracle/alter_11_5_x_to_12_0_0.sql @@ -226,3 +226,20 @@ alter table o_gta_task add g_submission_due_date date default null; alter table o_gta_task add g_revisions_due_date date default null; alter table o_gta_task add g_solution_due_date date default null; +alter table o_gta_task add g_acceptation_date date default null; +alter table o_gta_task add g_solution_date date default null; +alter table o_gta_task add g_graduation_date date default null; + +create table o_gta_task_revision_date ( + id number(20) generated always as identity, + creationdate date not null, + g_status varchar2(36 char) not null, + g_rev_loop number(20) not null, + g_date date not null, + fk_task number(20) not null, + primary key (id) +); + +alter table o_gta_task_revision_date add constraint gtaskrev_to_task_idx foreign key (fk_task) references o_gta_task (id); +create index idx_gtaskrev_to_task_idx on o_gta_task_revision_date (fk_task); + diff --git a/src/main/resources/database/oracle/setupDatabase.sql b/src/main/resources/database/oracle/setupDatabase.sql index fb1a38997b7..e18daca7471 100644 --- a/src/main/resources/database/oracle/setupDatabase.sql +++ b/src/main/resources/database/oracle/setupDatabase.sql @@ -1882,6 +1882,9 @@ create table o_gta_task ( g_submission_date date, g_submission_revisions_date date, g_collection_date date, + g_acceptation_date date, + g_solution_date date, + g_graduation_date date, g_assignment_due_date date, g_submission_due_date date, g_revisions_due_date date, @@ -1892,6 +1895,16 @@ create table o_gta_task ( primary key (id) ); +create table o_gta_task_revision_date ( + id number(20) generated always as identity, + creationdate date not null, + g_status varchar2(36 char) not null, + g_rev_loop number(20) not null, + g_date date not null, + fk_task number(20) not null, + primary key (id) +); + create table o_rem_reminder ( id number(20) not null, creationdate date not null, @@ -2693,6 +2706,9 @@ create index idx_gtask_to_bgroup_idx on o_gta_task (fk_businessgroup); alter table o_gta_task_list add constraint gta_list_to_repo_entry_idx foreign key (fk_entry) references o_repositoryentry (repositoryentry_id); create index idx_gta_list_to_repo_entry_idx on o_gta_task_list (fk_entry); +alter table o_gta_task_revision_date add constraint gtaskrev_to_task_idx foreign key (fk_task) references o_gta_task (id); +create index idx_gtaskrev_to_task_idx on o_gta_task_revision_date (fk_task); + -- reminders alter table o_rem_reminder add constraint rem_reminder_to_repo_entry_idx foreign key (fk_entry) references o_repositoryentry (repositoryentry_id); create index idx_reminder_to_repo_entry_idx on o_rem_reminder (fk_entry); diff --git a/src/main/resources/database/postgresql/alter_11_5_x_to_12_0_0.sql b/src/main/resources/database/postgresql/alter_11_5_x_to_12_0_0.sql index e3fafcc00df..7380438f3ec 100644 --- a/src/main/resources/database/postgresql/alter_11_5_x_to_12_0_0.sql +++ b/src/main/resources/database/postgresql/alter_11_5_x_to_12_0_0.sql @@ -226,7 +226,22 @@ alter table o_gta_task add column g_submission_due_date timestamp default null; alter table o_gta_task add column g_revisions_due_date timestamp default null; alter table o_gta_task add column g_solution_due_date timestamp default null; +alter table o_gta_task add column g_acceptation_date timestamp default null; +alter table o_gta_task add column g_solution_date timestamp default null; +alter table o_gta_task add column g_graduation_date timestamp default null; +create table o_gta_task_revision_date ( + id bigserial not null, + creationdate timestamp not null, + g_status varchar(36) not null, + g_rev_loop int8 not null, + g_date timestamp not null, + fk_task int8 not null, + primary key (id) +); + +alter table o_gta_task_revision_date add constraint gtaskrev_to_task_idx foreign key (fk_task) references o_gta_task (id); +create index idx_gtaskrev_to_task_idx on o_gta_task_revision_date (fk_task); diff --git a/src/main/resources/database/postgresql/setupDatabase.sql b/src/main/resources/database/postgresql/setupDatabase.sql index 14dcca0325f..d34929ad20e 100644 --- a/src/main/resources/database/postgresql/setupDatabase.sql +++ b/src/main/resources/database/postgresql/setupDatabase.sql @@ -1855,6 +1855,9 @@ create table o_gta_task ( g_submission_date timestamp, g_submission_revisions_date timestamp, g_collection_date timestamp, + g_acceptation_date timestamp, + g_solution_date timestamp, + g_graduation_date timestamp, g_assignment_due_date timestamp, g_submission_due_date timestamp, g_revisions_due_date timestamp, @@ -1866,6 +1869,16 @@ create table o_gta_task ( primary key (id) ); +create table o_gta_task_revision_date ( + id bigserial not null, + creationdate timestamp not null, + g_status varchar(36) not null, + g_rev_loop int8 not null, + g_date timestamp not null, + fk_task int8 not null, + primary key (id) +); + create table o_rem_reminder ( id int8 not null, creationdate timestamp not null, @@ -2542,6 +2555,9 @@ create index idx_gtask_to_bgroup_idx on o_gta_task (fk_businessgroup); alter table o_gta_task_list add constraint gta_list_to_repo_entry_idx foreign key (fk_entry) references o_repositoryentry (repositoryentry_id); create index idx_gta_list_to_repo_entry_idx on o_gta_task_list (fk_entry); +alter table o_gta_task_revision_date add constraint gtaskrev_to_task_idx foreign key (fk_task) references o_gta_task (id); +create index idx_gtaskrev_to_task_idx on o_gta_task_revision_date (fk_task); + -- reminders alter table o_rem_reminder add constraint rem_reminder_to_repo_entry_idx foreign key (fk_entry) references o_repositoryentry (repositoryentry_id); create index idx_reminder_to_repo_entry_idx on o_rem_reminder (fk_entry); diff --git a/src/test/java/org/olat/course/nodes/gta/manager/GTAManagerTest.java b/src/test/java/org/olat/course/nodes/gta/manager/GTAManagerTest.java index 42328a37563..0b8aa388381 100644 --- a/src/test/java/org/olat/course/nodes/gta/manager/GTAManagerTest.java +++ b/src/test/java/org/olat/course/nodes/gta/manager/GTAManagerTest.java @@ -35,6 +35,8 @@ import org.olat.course.nodes.gta.AssignmentResponse.Status; 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.TaskRevisionDate; import org.olat.course.nodes.gta.model.TaskListImpl; import org.olat.group.BusinessGroup; import org.olat.group.manager.BusinessGroupDAO; @@ -609,6 +611,62 @@ public class GTAManagerTest extends OlatTestCase { Assert.assertEquals(1, notDeletedAssignedTasks2_2.size()); } + @Test + public void createTaskRevisionDate() { + //prepare + Identity participant = JunitTestHelper.createAndPersistIdentityAsRndUser("gta-user-20"); + RepositoryEntry re = JunitTestHelper.createAndPersistRepositoryEntry("", false); + GTACourseNode node = new GTACourseNode(); + node.getModuleConfiguration().setStringValue(GTACourseNode.GTASK_TYPE, GTAType.individual.name()); + TaskList tasks = gtaManager.createIfNotExists(re, node); + dbInstance.commit(); + + //create task + Task task = gtaManager.createAndPersistTask(null, tasks, TaskProcess.assignment, null, participant, node); + dbInstance.commitAndCloseSession(); + + //create the revision log + TaskRevisionDate taskRevision = gtaManager.createAndPersistTaskRevisionDate(task, 2, TaskProcess.correction); + Assert.assertNotNull(taskRevision); + dbInstance.commitAndCloseSession(); + Assert.assertNotNull(taskRevision.getKey()); + Assert.assertNotNull(taskRevision.getDate()); + Assert.assertEquals(task, taskRevision.getTask()); + Assert.assertEquals(2, taskRevision.getRevisionLoop()); + Assert.assertEquals(TaskProcess.correction, taskRevision.getTaskStatus()); + } + + @Test + public void getTaskRevisions() { + //prepare + Identity participant = JunitTestHelper.createAndPersistIdentityAsRndUser("gta-user-21"); + RepositoryEntry re = JunitTestHelper.createAndPersistRepositoryEntry("", false); + GTACourseNode node = new GTACourseNode(); + node.getModuleConfiguration().setStringValue(GTACourseNode.GTASK_TYPE, GTAType.individual.name()); + TaskList tasks = gtaManager.createIfNotExists(re, node); + dbInstance.commit(); + + //create a task + Task task = gtaManager.createAndPersistTask(null, tasks, TaskProcess.assignment, null, participant, node); + dbInstance.commitAndCloseSession(); + + //add the revision log + TaskRevisionDate taskRevision = gtaManager.createAndPersistTaskRevisionDate(task, 2, TaskProcess.correction); + Assert.assertNotNull(taskRevision); + dbInstance.commitAndCloseSession(); + + //load the revisions + List<TaskRevisionDate> taskRevisions = gtaManager.getTaskRevisions(task); + Assert.assertNotNull(taskRevisions); + Assert.assertEquals(1, taskRevisions.size()); + TaskRevisionDate loadedTaskRevision = taskRevisions.get(0); + Assert.assertNotNull(loadedTaskRevision.getKey()); + Assert.assertNotNull(loadedTaskRevision.getDate()); + Assert.assertEquals(task, loadedTaskRevision.getTask()); + Assert.assertEquals(2, loadedTaskRevision.getRevisionLoop()); + Assert.assertEquals(TaskProcess.correction, loadedTaskRevision.getTaskStatus()); + } + @Test public void roundRobin() { String[] slots = new String[]{ "A", "B", "C" }; -- GitLab