diff --git a/src/main/java/org/olat/admin/user/course/CourseOverviewController.java b/src/main/java/org/olat/admin/user/course/CourseOverviewController.java index 417b41071bf6aaf625d972157a530c317404eb0b..d734808b9b690190de09e3baa45f8f0c79f38f6e 100644 --- a/src/main/java/org/olat/admin/user/course/CourseOverviewController.java +++ b/src/main/java/org/olat/admin/user/course/CourseOverviewController.java @@ -202,10 +202,22 @@ public class CourseOverviewController extends BasicController { } memberView.setFirstTime(membership.getCreationDate()); - memberView.setLastTime(membership.getLastModified()); - memberView.getMembership().setRepoOwner(membership.isOwner()); - memberView.getMembership().setRepoTutor(membership.isCoach()); - memberView.getMembership().setRepoParticipant(membership.isParticipant()); + if(memberView.getLastTime() == null || + (memberView.getLastTime() != null && membership.getLastModified() != null + && membership.getLastModified().after(memberView.getLastTime()))) { + memberView.setLastTime(membership.getLastModified()); + } + + //add the roles + if(!memberView.getMembership().isRepoOwner()) { + memberView.getMembership().setRepoOwner(membership.isOwner()); + } + if(!memberView.getMembership().isRepoTutor()) { + memberView.getMembership().setRepoTutor(membership.isCoach()); + } + if(!memberView.getMembership().isRepoParticipant()) { + memberView.getMembership().setRepoParticipant(membership.isParticipant()); + } } List<BusinessGroupShort> groups = businessGroupService.loadShortBusinessGroups(groupKeys); @@ -236,7 +248,11 @@ public class CourseOverviewController extends BasicController { } memberView.addGroup(group); memberView.setFirstTime(membership.getCreationDate()); - memberView.setLastTime(membership.getLastModified()); + if(memberView.getLastTime() == null || ( + memberView.getLastTime() != null && membership.getLastModified() != null + && membership.getLastModified().after(memberView.getLastTime()))) { + memberView.setLastTime(membership.getLastModified()); + } switch(membership.getMembership()) { case owner: memberView.getMembership().setGroupTutor(true); break; case participant: memberView.getMembership().setGroupParticipant(true); break; diff --git a/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/table/AbstractFlexiTableRenderer.java b/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/table/AbstractFlexiTableRenderer.java index 23fb984fc82cda7451dc715ea865862fca8bf5c0..7ce770e7885dd518a73fba03d3e5424ca00136f5 100644 --- a/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/table/AbstractFlexiTableRenderer.java +++ b/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/table/AbstractFlexiTableRenderer.java @@ -315,16 +315,16 @@ public abstract class AbstractFlexiTableRenderer extends DefaultComponentRendere sb.append("<div class='o_table_footer'><div class='o_table_checkall input-sm'>"); - sb.append("<label class='checkbox-inline'><a id=\"") - .append(dispatchId).append("\" href=\"javascript:o_table_toggleCheck('").append(formName).append("', true);") + sb.append("<label class='checkbox-inline'><a id='") + .append(dispatchId).append("_sa' href=\"javascript:o_table_toggleCheck('").append(formName).append("', true);") .append(FormJSHelper.getXHRFnCallFor(ftE.getRootForm(), dispatchId, 1, new NameValuePair("select", "checkall"))) - .append("\"><input type='checkbox' checked='checked' disabled='disabled' /><span>").append(translator.translate("form.checkall")) + .append("\"><i class='o_icon o_icon-lg o_icon_check_on'> </i> <span>").append(translator.translate("form.checkall")) .append("</span></a></label>"); - sb.append("<label class='checkbox-inline'><a id=\"") - .append(dispatchId).append("\" href=\"javascript:o_table_toggleCheck('").append(formName).append("', false);") + sb.append("<label class='checkbox-inline'><a id='") + .append(dispatchId).append("_dsa' href=\"javascript:o_table_toggleCheck('").append(formName).append("', false);") .append(FormJSHelper.getXHRFnCallFor(ftE.getRootForm(), dispatchId, 1, new NameValuePair("select", "uncheckall"))) - .append("\"><input type='checkbox' disabled='disabled' /><span>").append(translator.translate("form.uncheckall")) + .append("\"><i class='o_icon o_icon-lg o_icon_check_off'> </i> <span>").append(translator.translate("form.uncheckall")) .append("</span></a></label>"); sb.append("</div></div>"); diff --git a/src/main/java/org/olat/core/gui/components/table/TableRenderer.java b/src/main/java/org/olat/core/gui/components/table/TableRenderer.java index 89b681d823708d37cc2ca203e3ad245d7632594a..5a5e659c3b31949310974b50f621ed0ef1aeaef5 100644 --- a/src/main/java/org/olat/core/gui/components/table/TableRenderer.java +++ b/src/main/java/org/olat/core/gui/components/table/TableRenderer.java @@ -220,17 +220,17 @@ public class TableRenderer extends DefaultComponentRenderer { private void appendSelectDeselectAllButtons(final StringOutput target, final Translator translator, Table table, String formName, int rows, int resultsPerPage) { if (table.isMultiSelect()) { - target.append("<div class='o_table_checkall input-sm'>"); - target.append("<label class='checkbox-inline'>"); - target.append("<a href='#' onclick=\"javascript:o_table_toggleCheck('").append(formName).append("', true)\">"); - target.append("<input type='checkbox' checked='checked' disabled='disabled' />"); - target.append(translator.translate("checkall")); - target.append("</a></label>"); - target.append("<label class='checkbox-inline'><a href=\"#\" onclick=\"javascript:o_table_toggleCheck('").append(formName).append("', false)\">"); - target.append("<input type='checkbox' disabled='disabled' />"); - target.append(translator.translate("uncheckall")); - target.append("</a></label>"); - target.append("</div>"); + target.append("<div class='o_table_checkall input-sm'>") + .append("<label class='checkbox-inline'>") + .append("<a href='#' onclick=\"javascript:o_table_toggleCheck('").append(formName).append("', true)\">") + .append("<i class='o_icon o_icon-lg o_icon_check_on'> </i> ") + .append(translator.translate("checkall")) + .append("</a></label>"); + target.append("<label class='checkbox-inline'><a href=\"#\" onclick=\"javascript:o_table_toggleCheck('").append(formName).append("', false)\">") + .append("<i class='o_icon o_icon-lg o_icon_check_off'> </i> ") + .append(translator.translate("uncheckall")) + .append("</a></label>") + .append("</div>"); } if (table.isShowAllSelected() && (rows > resultsPerPage)) { diff --git a/src/main/java/org/olat/core/id/context/HistoryManager.java b/src/main/java/org/olat/core/id/context/HistoryManager.java index 0e40b5c05f5160aff6651b9e6dcd7af8fc026df7..2c7b0f9ede48225a98cd12bb94796b6da9e04e4c 100644 --- a/src/main/java/org/olat/core/id/context/HistoryManager.java +++ b/src/main/java/org/olat/core/id/context/HistoryManager.java @@ -35,6 +35,7 @@ import org.olat.group.BusinessGroupImpl; import org.olat.repository.RepositoryEntry; import com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.converters.ConversionException; /** * @@ -105,6 +106,9 @@ public class HistoryManager extends BasicManager { String pathHomePage = FolderConfig.getCanonicalRoot() + FolderConfig.getUserHomePage(identity.getName()); File resumeXml = new File(pathHomePage, "resume.xml"); return readHistory(resumeXml); + } catch(ConversionException e) { + logWarn("Cannot read resume file: ", e); + return null; } catch (Exception e) { logError("Cannot read resume file: ", e); return null; diff --git a/src/main/java/org/olat/course/assessment/bulk/BulkAssessmentOverviewController.java b/src/main/java/org/olat/course/assessment/bulk/BulkAssessmentOverviewController.java index 6c3adc1c73e31256da2b1079ca8d3a1388f85897..f63fcd4d776d41734b42f0774eb40706d06ba644 100644 --- a/src/main/java/org/olat/course/assessment/bulk/BulkAssessmentOverviewController.java +++ b/src/main/java/org/olat/course/assessment/bulk/BulkAssessmentOverviewController.java @@ -146,7 +146,7 @@ public class BulkAssessmentOverviewController extends FormBasicController { } taskModel.setObjects(taskDatas); taskListEl.reset(); - this.flc.contextPut("hasScheduledTasks", Boolean.valueOf(taskDatas.size()>0)); + flc.contextPut("hasScheduledTasks", Boolean.valueOf(taskDatas.size()>0)); } @Override diff --git a/src/main/java/org/olat/course/assessment/manager/BulkAssessmentTask.java b/src/main/java/org/olat/course/assessment/manager/BulkAssessmentTask.java index 2f03c1571ae689910e8e88b2fb4ea390f7d4bd89..8279b5a9194bf2f61880a49e0456b2d189b666d8 100644 --- a/src/main/java/org/olat/course/assessment/manager/BulkAssessmentTask.java +++ b/src/main/java/org/olat/course/assessment/manager/BulkAssessmentTask.java @@ -81,13 +81,19 @@ import org.olat.course.assessment.model.BulkAssessmentRow; import org.olat.course.assessment.model.BulkAssessmentSettings; import org.olat.course.nodes.AssessableCourseNode; import org.olat.course.nodes.CourseNode; +import org.olat.course.nodes.GTACourseNode; import org.olat.course.nodes.MSCourseNode; import org.olat.course.nodes.ProjectBrokerCourseNode; import org.olat.course.nodes.TACourseNode; +import org.olat.course.nodes.gta.GTAManager; +import org.olat.course.nodes.gta.TaskList; +import org.olat.course.nodes.gta.TaskProcess; import org.olat.course.nodes.ta.ReturnboxController; +import org.olat.course.run.environment.CourseEnvironment; import org.olat.course.run.scoring.ScoreEvaluation; import org.olat.course.run.userview.UserCourseEnvironment; import org.olat.course.run.userview.UserCourseEnvironmentImpl; +import org.olat.repository.RepositoryEntry; import org.olat.user.UserManager; import org.olat.util.logging.activity.LoggingResourceable; @@ -310,7 +316,7 @@ public class BulkAssessmentTask implements LongRunnable, TaskAwareRunnable, Sequ final boolean hasScore = courseNode.hasScoreConfigured(); final boolean hasPassed = courseNode.hasPassedConfigured(); final boolean hasReturnFiles = (StringHelper.containsNonWhitespace(datas.getReturnFiles()) - && courseNode instanceof TACourseNode); + && (courseNode instanceof TACourseNode || courseNode instanceof GTACourseNode)); if(hasReturnFiles) { try { @@ -396,49 +402,108 @@ public class BulkAssessmentTask implements LongRunnable, TaskAwareRunnable, Sequ uce.getScoreAccounting().scoreInfoChanged(courseNode, se); } + boolean identityHasReturnFile = false; if(hasReturnFiles && row.getReturnFiles() != null && row.getReturnFiles().size() > 0) { String assessedId = row.getAssessedId(); File assessedFolder = new File(unzipped, assessedId); - if(assessedFolder.exists()) { - VFSContainer returnBox = getReturnBox(uce, courseNode, identity); - if(returnBox != null) { - for(String returnFilename:row.getReturnFiles()) { - File returnFile = new File(assessedFolder, returnFilename); - VFSItem currentReturnLeaf = returnBox.resolve(returnFilename); - if(currentReturnLeaf != null) { - //remove the current file (delete make a version is enabled) - currentReturnLeaf.delete(); - } - - VFSLeaf returnLeaf = returnBox.createChildLeaf(returnFilename); - if(returnFile.exists()) { - try { - InputStream inStream = new FileInputStream(returnFile); - VFSManager.copyContent(inStream, returnLeaf); - } catch (FileNotFoundException e) { - log.error("Cannot copy return file " + returnFilename + " from " + assessedId, e); - } - } - } - } + identityHasReturnFile = assessedFolder.exists(); + if(identityHasReturnFile) { + processReturnFile(courseNode, row, uce, assessedFolder); + } + } + + if(courseNode instanceof GTACourseNode) { + //push the state further + GTACourseNode gtaNode = (GTACourseNode)courseNode; + if((hasScore && score != null) || (hasPassed && passed != null)) { + //pushed to graded + updateTasksState(gtaNode, uce, TaskProcess.grading); + } else if(hasReturnFiles) { + //push to revised + updateTasksState(gtaNode, uce, TaskProcess.correction); } } if(count++ % 5 == 0) { dbInstance.commitAndCloseSession(); + } else { + dbInstance.commit(); } } } + private void updateTasksState(GTACourseNode courseNode, UserCourseEnvironment uce, TaskProcess status) { + final GTAManager gtaManager = CoreSpringFactory.getImpl(GTAManager.class); + Identity identity = uce.getIdentityEnvironment().getIdentity(); + RepositoryEntry entry = uce.getCourseEnvironment().getCourseGroupManager().getCourseEntry(); + + org.olat.course.nodes.gta.Task task; + TaskList taskList = gtaManager.getTaskList(entry, courseNode); + if(taskList == null) { + taskList = gtaManager.createIfNotExists(entry, courseNode); + task = gtaManager.createTask(null, taskList, status, null, identity, courseNode); + } else { + task = gtaManager.getTask(identity, taskList); + if(task == null) { + gtaManager.createTask(null, taskList, status, null, identity, courseNode); + } + } + + gtaManager.nextStep(status, courseNode); + } + + + private void processReturnFile(AssessableCourseNode courseNode, BulkAssessmentRow row, UserCourseEnvironment uce, File assessedFolder) { + String assessedId = row.getAssessedId(); + Identity identity = uce.getIdentityEnvironment().getIdentity(); + VFSContainer returnBox = getReturnBox(uce, courseNode, identity); + if(returnBox != null) { + for(String returnFilename:row.getReturnFiles()) { + File returnFile = new File(assessedFolder, returnFilename); + VFSItem currentReturnLeaf = returnBox.resolve(returnFilename); + if(currentReturnLeaf != null) { + //remove the current file (delete make a version if it is enabled) + currentReturnLeaf.delete(); + } + + VFSLeaf returnLeaf = returnBox.createChildLeaf(returnFilename); + if(returnFile.exists()) { + try { + InputStream inStream = new FileInputStream(returnFile); + VFSManager.copyContent(inStream, returnLeaf); + } catch (FileNotFoundException e) { + log.error("Cannot copy return file " + returnFilename + " from " + assessedId, e); + } + } + } + } + } + + /** + * Return the target folder of the assessed identity. This is a factory method which take care + * of the type of the course node. + * + * @param uce + * @param courseNode + * @param identity + * @return + */ private VFSContainer getReturnBox(UserCourseEnvironment uce, CourseNode courseNode, Identity identity) { - String returnPath = ReturnboxController.getReturnboxPathRelToFolderRoot(uce.getCourseEnvironment(), courseNode); - OlatRootFolderImpl rootFolder = new OlatRootFolderImpl(returnPath, null); - VFSItem assessedItem = rootFolder.resolve(identity.getName()); - if(assessedItem == null) { - return rootFolder.createChildContainer(identity.getName()); - } else if(assessedItem instanceof VFSContainer) { - return (VFSContainer)assessedItem; + VFSContainer returnContainer = null; + if(courseNode instanceof GTACourseNode) { + final GTAManager gtaManager = CoreSpringFactory.getImpl(GTAManager.class); + CourseEnvironment courseEnv = uce.getCourseEnvironment(); + returnContainer = gtaManager.getCorrectionContainer(courseEnv, (GTACourseNode)courseNode, identity); + } else { + String returnPath = ReturnboxController.getReturnboxPathRelToFolderRoot(uce.getCourseEnvironment(), courseNode); + OlatRootFolderImpl rootFolder = new OlatRootFolderImpl(returnPath, null); + VFSItem assessedItem = rootFolder.resolve(identity.getName()); + if(assessedItem == null) { + returnContainer = rootFolder.createChildContainer(identity.getName()); + } else if(assessedItem instanceof VFSContainer) { + returnContainer = (VFSContainer)assessedItem; + } } - return null; + return returnContainer; } } diff --git a/src/main/java/org/olat/course/assessment/manager/UserCourseInformationsManager.java b/src/main/java/org/olat/course/assessment/manager/UserCourseInformationsManager.java index e45eccaf8a4a2ba1f16f30845373114b55f5c3f4..d66a926ffeea30b9d68dd0a430544f75ea25baab 100644 --- a/src/main/java/org/olat/course/assessment/manager/UserCourseInformationsManager.java +++ b/src/main/java/org/olat/course/assessment/manager/UserCourseInformationsManager.java @@ -51,8 +51,22 @@ public interface UserCourseInformationsManager { public Map<Long,Date> getRecentLaunchDates(Long courseResourceId, List<Identity> identities); + /** + * Return the initial launch dates of a list of users. + * @param courseResourceId + * @param identities + * @return + */ public Map<Long,Date> getInitialLaunchDates(Long courseResourceId, List<Identity> identities); + /** + * Return all initial launch dates of a course. + * + * @param courseResourceId + * @return + */ + public Map<Long,Date> getInitialLaunchDates(Long courseResourceId); + public int deleteUserCourseInformations(RepositoryEntry entry); } diff --git a/src/main/java/org/olat/course/assessment/manager/UserCourseInformationsManagerImpl.java b/src/main/java/org/olat/course/assessment/manager/UserCourseInformationsManagerImpl.java index 55252b5f5edc29e24d9343aaa5112fddce53d6f1..0cdb16e5cbbcdab18e7c95ada7d3a352a1af4836 100644 --- a/src/main/java/org/olat/course/assessment/manager/UserCourseInformationsManagerImpl.java +++ b/src/main/java/org/olat/course/assessment/manager/UserCourseInformationsManagerImpl.java @@ -338,7 +338,40 @@ public class UserCourseInformationsManagerImpl implements UserCourseInformations return Collections.emptyMap(); } } + + /** + * Return a map of identity keys to initial launch date. + * + * @param courseResourceId The course resourceable id + * @return + */ + @Override + public Map<Long,Date> getInitialLaunchDates(Long courseResourceId) { + try { + + StringBuilder sb = new StringBuilder(); + sb.append("select infos.identity.key, infos.initialLaunch from ").append(UserCourseInfosImpl.class.getName()).append(" as infos ") + .append(" inner join infos.resource as resource") + .append(" where resource.resId=:resId and resource.resName='CourseModule'"); + + TypedQuery<Object[]> query = dbInstance.getCurrentEntityManager().createQuery(sb.toString(), Object[].class) + .setParameter("resId", courseResourceId); + List<Object[]> infoList = query.getResultList(); + Map<Long,Date> dateMap = new HashMap<Long,Date>(); + for(Object[] infos:infoList) { + Long identityKey = (Long)infos[0]; + Date initialLaunch = (Date)infos[1]; + if(identityKey != null && initialLaunch != null) { + dateMap.put(identityKey, initialLaunch); + } + } + return dateMap; + } catch (Exception e) { + log.error("Cannot retrieve course informations for: " + courseResourceId, e); + return Collections.emptyMap(); + } + } /** * Return a map of identity keys to initial launch date. diff --git a/src/main/java/org/olat/course/assessment/model/BulkAssessmentSettings.java b/src/main/java/org/olat/course/assessment/model/BulkAssessmentSettings.java index b0267ca5590f67f91b4036baa4518273aa037508..fae0d16ade05d801df6a89c455c010f6e1a6d33b 100644 --- a/src/main/java/org/olat/course/assessment/model/BulkAssessmentSettings.java +++ b/src/main/java/org/olat/course/assessment/model/BulkAssessmentSettings.java @@ -22,8 +22,11 @@ package org.olat.course.assessment.model; import java.io.Serializable; import org.olat.course.nodes.AssessableCourseNode; +import org.olat.course.nodes.GTACourseNode; import org.olat.course.nodes.ProjectBrokerCourseNode; import org.olat.course.nodes.TACourseNode; +import org.olat.course.nodes.gta.GTAType; +import org.olat.modules.ModuleConfiguration; /** * @@ -47,11 +50,15 @@ public class BulkAssessmentSettings implements Serializable { hasScore = courseNode.hasScoreConfigured(); hasPassed = courseNode.hasPassedConfigured(); + ModuleConfiguration config = courseNode.getModuleConfiguration(); if (courseNode instanceof TACourseNode) { - Boolean hasReturnBox = (Boolean)courseNode.getModuleConfiguration().get(TACourseNode.CONF_RETURNBOX_ENABLED); + Boolean hasReturnBox = (Boolean)config.get(TACourseNode.CONF_RETURNBOX_ENABLED); hasReturnFiles = hasReturnBox == null ? false : hasReturnBox.booleanValue(); + } else if (courseNode instanceof GTACourseNode) { + hasReturnFiles = GTAType.individual.name().equals(config.getStringValue(GTACourseNode.GTASK_TYPE)) + && config.getBooleanSafe(GTACourseNode.GTASK_REVIEW_AND_CORRECTION); } else if (courseNode instanceof ProjectBrokerCourseNode) { - Boolean hasReturnBox = (Boolean)courseNode.getModuleConfiguration().get(ProjectBrokerCourseNode.CONF_RETURNBOX_ENABLED); + Boolean hasReturnBox = (Boolean)config.get(ProjectBrokerCourseNode.CONF_RETURNBOX_ENABLED); hasReturnFiles = hasReturnBox == null ? false : hasReturnBox.booleanValue(); } else { hasReturnFiles = false; diff --git a/src/main/java/org/olat/course/config/CourseConfig.java b/src/main/java/org/olat/course/config/CourseConfig.java index 3f901a1b7509e53b8f7212a67a5508925c7afd06..5e95df63c05f791604be4029aa2327b8e328e78f 100644 --- a/src/main/java/org/olat/course/config/CourseConfig.java +++ b/src/main/java/org/olat/course/config/CourseConfig.java @@ -386,7 +386,11 @@ public class CourseConfig implements Serializable, Cloneable { * @return true if the efficency statement is enabled */ public Long getCertificateTemplate() { - Long templateId = (Long)configuration.get(CERTIFICATE_TEMPLATE); + Object templateIdObj = configuration.get(CERTIFICATE_TEMPLATE); + Long templateId = null; + if(templateIdObj instanceof Long) { + templateId = (Long)templateIdObj; + } return templateId; } diff --git a/src/main/java/org/olat/course/editor/EditorStatusController.java b/src/main/java/org/olat/course/editor/EditorStatusController.java index 0b15a0570a83cfa88573275f469d411ad8e8d9c4..9710b08654839bda214f3a1229f219c9cecd3fdf 100644 --- a/src/main/java/org/olat/course/editor/EditorStatusController.java +++ b/src/main/java/org/olat/course/editor/EditorStatusController.java @@ -78,7 +78,7 @@ public class EditorStatusController extends BasicController { long lpTimeStamp = cetm.getLatestPublishTimestamp(); if (lpTimeStamp == -1) { - main.contextPut("publishInfos", "published.never.yet"); + main.contextPut("publishInfos", translate("published.never.yet")); } else { // course has been published before Date d = new Date(lpTimeStamp); main.contextPut("publishInfos", translate("published.latest", Formatter.getInstance(getLocale()).formatDateAndTime(d))); diff --git a/src/main/java/org/olat/course/nodes/GTACourseNode.java b/src/main/java/org/olat/course/nodes/GTACourseNode.java index 4486129d868eae7bb8d46e4d0f7d7a4635ce6d0f..ecd475adf1c86915fa7cade2d7b194c9047a8eab 100644 --- a/src/main/java/org/olat/course/nodes/GTACourseNode.java +++ b/src/main/java/org/olat/course/nodes/GTACourseNode.java @@ -60,6 +60,7 @@ import org.olat.course.CourseFactory; import org.olat.course.ICourse; import org.olat.course.archiver.ScoreAccountingHelper; import org.olat.course.assessment.AssessmentManager; +import org.olat.course.assessment.bulk.BulkAssessmentToolController; import org.olat.course.auditing.UserNodeAuditManager; import org.olat.course.editor.CourseEditorEnv; import org.olat.course.editor.NodeEditController; @@ -70,6 +71,7 @@ import org.olat.course.nodes.gta.GTAType; import org.olat.course.nodes.gta.Task; import org.olat.course.nodes.gta.TaskHelper; import org.olat.course.nodes.gta.TaskList; +import org.olat.course.nodes.gta.ui.BulkDownloadToolController; import org.olat.course.nodes.gta.ui.GTAAssessmentDetailsController; import org.olat.course.nodes.gta.ui.GTAEditController; import org.olat.course.nodes.gta.ui.GTAGroupAssessmentToolController; @@ -106,16 +108,24 @@ public class GTACourseNode extends AbstractAccessableCourseNode implements Asses public static final String GTASK_AREAS = "grouptask.areas"; public static final String GTASK_ASSIGNMENT = "grouptask.assignement"; public static final String GTASK_ASSIGNMENT_DEADLINE = "grouptask.assignment.deadline"; + public static final String GTASK_ASSIGNMENT_DEADLINE_RELATIVE = "grouptask.assignment.deadline.relative"; + public static final String GTASK_ASSIGNMENT_DEADLINE_RELATIVE_TO = "grouptask.assignment.deadline.relative.to"; public static final String GTASK_SUBMIT = "grouptask.submit"; public static final String GTASK_SUBMIT_DEADLINE = "grouptask.submit.deadline"; + public static final String GTASK_SUBMIT_DEADLINE_RELATIVE = "grouptask.submit.deadline.relative"; + public static final String GTASK_SUBMIT_DEADLINE_RELATIVE_TO = "grouptask.submit.deadline.relative.to"; public static final String GTASK_REVIEW_AND_CORRECTION = "grouptask.review.and.correction"; public static final String GTASK_REVISION_PERIOD = "grouptask.revision.period"; public static final String GTASK_SAMPLE_SOLUTION = "grouptask.solution"; public static final String GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER = "grouptask.solution.visible.after"; + public static final String GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER_RELATIVE = "grouptask.solution.visible.after.relative"; + public static final String GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER_RELATIVE_TO = "grouptask.solution.visible.after.relative.to"; public static final String GTASK_GRADING = "grouptask.grading"; public static final String GTASK_TASKS = "grouptask.tasks"; + public static final String GTASK_RELATIVE_DATES = "grouptask.rel.dates"; + public static final String GTASK_ASSIGNEMENT_TYPE = "grouptask.assignement.type"; public static final String GTASK_ASSIGNEMENT_TYPE_AUTO = "auto"; public static final String GTASK_ASSIGNEMENT_TYPE_MANUAL = "manual"; @@ -137,10 +147,16 @@ public class GTACourseNode extends AbstractAccessableCourseNode implements Asses public static final String GTASK_SOLUTIONS = "grouptask.solutions"; - private static final String TYPE = "gta"; + public static final String TYPE_GROUP = "gta"; + public static final String TYPE_INDIVIDUAL = "ita"; public GTACourseNode() { - super(TYPE); + super(TYPE_GROUP); + updateModuleConfigDefaults(true); + } + + public GTACourseNode(String type) { + super(type); updateModuleConfigDefaults(true); } @@ -160,7 +176,12 @@ public class GTACourseNode extends AbstractAccessableCourseNode implements Asses //setup default configuration ModuleConfiguration config = getModuleConfiguration(); //group task - config.setStringValue(GTASK_TYPE, GTAType.group.name()); + if(getType().equals(TYPE_INDIVIDUAL)) { + config.setStringValue(GTASK_TYPE, GTAType.individual.name()); + } else { + config.setStringValue(GTASK_TYPE, GTAType.group.name()); + } + //manual choice config.setStringValue(GTASK_ASSIGNEMENT_TYPE, GTASK_ASSIGNEMENT_TYPE_MANUAL); //all steps @@ -219,7 +240,7 @@ public class GTACourseNode extends AbstractAccessableCourseNode implements Asses boolean hasScoring = config.getBooleanSafe(GTASK_GRADING); if (hasScoring) { if(!config.getBooleanSafe(MSCourseNode.CONFIG_KEY_HAS_SCORE_FIELD) - && !config.getBooleanSafe(MSCourseNode.CONFIG_KEY_HAS_SCORE_FIELD)) { + && !config.getBooleanSafe(MSCourseNode.CONFIG_KEY_HAS_PASSED_FIELD)) { addStatusErrorDescription("error.missing.score.config", GTAEditController.PANE_TAB_GRADING, sdList); } @@ -380,14 +401,14 @@ public class GTACourseNode extends AbstractAccessableCourseNode implements Asses if(taskList != null) { if(GTAType.group.name().equals(config.getStringValue(GTACourseNode.GTASK_TYPE))) { List<BusinessGroup> selectedGroups; - if(options.getGroup() != null) { + if(options != null && options.getGroup() != null) { selectedGroups = Collections.singletonList(options.getGroup()); } else { selectedGroups = gtaManager.getBusinessGroups(this); } for(BusinessGroup businessGroup:selectedGroups) { - archiveNodeData(locale, course, businessGroup, taskList, dirName, exportStream); + archiveNodeData(course, businessGroup, taskList, dirName, exportStream); } } else { if(users == null) { @@ -396,7 +417,7 @@ public class GTACourseNode extends AbstractAccessableCourseNode implements Asses Set<Identity> uniqueUsers = new HashSet<>(users); for(Identity user: uniqueUsers) { - archiveNodeData(locale, course, user, taskList, dirName, exportStream); + archiveNodeData(course, user, taskList, dirName, exportStream); } } } @@ -415,7 +436,7 @@ public class GTACourseNode extends AbstractAccessableCourseNode implements Asses return true; } - private void archiveNodeData(Locale locale, ICourse course, Identity assessedIdentity, TaskList taskList, String dirName, ZipOutputStream exportStream) { + private void archiveNodeData(ICourse course, Identity assessedIdentity, TaskList taskList, String dirName, ZipOutputStream exportStream) { ModuleConfiguration config = getModuleConfiguration(); GTAManager gtaManager = CoreSpringFactory.getImpl(GTAManager.class); @@ -460,7 +481,7 @@ public class GTACourseNode extends AbstractAccessableCourseNode implements Asses } } - private void archiveNodeData(Locale locale, ICourse course, BusinessGroup businessGroup, TaskList taskList, String dirName, ZipOutputStream exportStream) { + private void archiveNodeData(ICourse course, BusinessGroup businessGroup, TaskList taskList, String dirName, ZipOutputStream exportStream) { ModuleConfiguration config = getModuleConfiguration(); GTAManager gtaManager = CoreSpringFactory.getImpl(GTAManager.class); @@ -511,11 +532,14 @@ public class GTACourseNode extends AbstractAccessableCourseNode implements Asses //tasks File taskDirectory = gtaManager.getTasksDirectory(course.getCourseEnvironment(), this); FileUtils.deleteDirsAndFiles(taskDirectory, true, true); - //TODO the rest //solutions File solutionsDirectory = gtaManager.getSolutionsDirectory(course.getCourseEnvironment(), this); FileUtils.deleteDirsAndFiles(solutionsDirectory, true, true); + + //clean up database + RepositoryEntry entry = course.getCourseEnvironment().getCourseGroupManager().getCourseEntry(); + gtaManager.deleteTaskList(entry, this); } @Override @@ -676,15 +700,22 @@ public class GTACourseNode extends AbstractAccessableCourseNode implements Asses TooledStackedPanel stackPanel, CourseEnvironment courseEnv, AssessmentToolOptions options) { ModuleConfiguration config = getModuleConfiguration(); - List<Controller> tools = new ArrayList<>(1); - if(options.getGroup() != null && GTAType.group.name().equals(config.getStringValue(GTACourseNode.GTASK_TYPE)) + List<Controller> tools = new ArrayList<>(2); + if(GTAType.group.name().equals(config.getStringValue(GTACourseNode.GTASK_TYPE)) && (config.getBooleanSafe(GTASK_ASSIGNMENT) || config.getBooleanSafe(GTASK_SUBMIT) || config.getBooleanSafe(GTASK_REVIEW_AND_CORRECTION) || config.getBooleanSafe(GTASK_REVISION_PERIOD))) { - Controller tool = new GTAGroupAssessmentToolController(ureq, wControl, courseEnv, options.getGroup(), this); - tools.add(tool); + if(options.getGroup() != null) { + tools.add(new GTAGroupAssessmentToolController(ureq, wControl, courseEnv, options.getGroup(), this)); + } + tools.add(new BulkDownloadToolController(ureq, wControl, courseEnv, options, this)); + } else if(GTAType.individual.name().equals(config.getStringValue(GTACourseNode.GTASK_TYPE)) + && (config.getBooleanSafe(GTASK_REVIEW_AND_CORRECTION) + || config.getBooleanSafe(GTASK_GRADING))) { + tools.add(new BulkAssessmentToolController(ureq, wControl, courseEnv, this)); + tools.add(new BulkDownloadToolController(ureq, wControl, courseEnv, options, this)); } return tools; } diff --git a/src/main/java/org/olat/course/nodes/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/nodes/_i18n/LocalStrings_de.properties index c19dba0d67c3fc88e35f45661101c480932374a0..c020cc1c29988bf615d20907f491259ba51f4d24 100644 --- a/src/main/java/org/olat/course/nodes/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/course/nodes/_i18n/LocalStrings_de.properties @@ -15,6 +15,7 @@ title_dialog=Dateidiskussion title_en=Einschreibung title_fo=Forum title_gta=Gruppenaufgabe +title_ita=Aufgabe title_iqself=Selbsttest title_iqsurv=Fragebogen title_iqtest=Test @@ -22,7 +23,7 @@ title_ms=Bewertung title_scorm=SCORM-Lerninhalt title_sp=Einzelne Seite title_st=Struktur -title_ta=Aufgabe +title_ta=<s>Aufgabe (deprecated)</s> title_tu=Externe Seite title_wiki=Wiki title_ll=Linkliste diff --git a/src/main/java/org/olat/course/nodes/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/nodes/_i18n/LocalStrings_en.properties index 081542872708fc930fda19ca078a879d8ccb86af..50c0dde4c6090394f8a7e3b61f2454cb4ebf4a45 100644 --- a/src/main/java/org/olat/course/nodes/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/course/nodes/_i18n/LocalStrings_en.properties @@ -16,6 +16,7 @@ title_dialog=File dialog title_en=Enrolment title_fo=Forum title_gta=Grouptask +title_ita=Task title_iqself=Self-test title_iqsurv=Questionnaire title_iqtest=Test @@ -26,7 +27,7 @@ title_projectbroker=Topic assignment title_scorm=SCORM learning content title_sp=Single page title_st=Structure -title_ta=Task +title_ta=<s>Task (deprecated)</s> title_tu=External page title_wiki=Wiki personal.title=Performance summary \ No newline at end of file diff --git a/src/main/java/org/olat/course/nodes/_i18n/LocalStrings_fr.properties b/src/main/java/org/olat/course/nodes/_i18n/LocalStrings_fr.properties index 95d0b68017eac9df504f762f8dfbed4b1b461c13..d613022ed5a9e57a3ea30d4bc7c390844fcace38 100644 --- a/src/main/java/org/olat/course/nodes/_i18n/LocalStrings_fr.properties +++ b/src/main/java/org/olat/course/nodes/_i18n/LocalStrings_fr.properties @@ -16,6 +16,8 @@ title_den=Attribution de rendez-vous title_dialog=Fichier discut\u00E9 title_en=Inscription title_fo=Forum +title_gta=Devoir de groupe +title_ita=Devoir title_iqself=Auto-test title_iqsurv=Questionnaire title_iqtest=Test @@ -26,6 +28,6 @@ title_projectbroker=Affectation sujets title_scorm=Contenu did. SCORM title_sp=Page simple title_st=Structure -title_ta=Devoir +title_ta=<s>Devoir</s> title_tu=Page externe title_wiki=Page Wiki diff --git a/src/main/java/org/olat/course/nodes/_spring/buildingblockContext.xml b/src/main/java/org/olat/course/nodes/_spring/buildingblockContext.xml index 0ec7c18fc0666c2779ac3a5f3b28f5247c17983c..39c1a2ccf35dd38cb09f793ab0272f756627998f 100644 --- a/src/main/java/org/olat/course/nodes/_spring/buildingblockContext.xml +++ b/src/main/java/org/olat/course/nodes/_spring/buildingblockContext.xml @@ -79,14 +79,20 @@ <property name="order" value="130" /> <property name="alternativeCourseNodes"> <list> - <value>gta</value> + <value>igta</value> </list> </property> </bean> - <bean id="gta" class="org.olat.course.nodes.gta.GTACourseNodeConfiguration" scope="prototype"> + <bean id="ita" class="org.olat.course.nodes.gta.GTACourseNodeConfiguration" scope="prototype"> + <constructor-arg index="0" value="true" /> <property name="order" value="131" /> </bean> + + <bean id="gta" class="org.olat.course.nodes.gta.GTACourseNodeConfiguration" scope="prototype"> + <constructor-arg index="0" value="false" /> + <property name="order" value="132" /> + </bean> <bean id="projectbroker" class="org.olat.course.nodes.projectbroker.ProjectBrokerNodeConfiguration" scope="prototype"> <property name="order" value="181" /> diff --git a/src/main/java/org/olat/course/nodes/gta/GTACourseNodeConfiguration.java b/src/main/java/org/olat/course/nodes/gta/GTACourseNodeConfiguration.java index 67217c099781f06c428a607dc0394e5939fed4e5..c2db876c3673043511dacc17e17dd7db490a7b5a 100644 --- a/src/main/java/org/olat/course/nodes/gta/GTACourseNodeConfiguration.java +++ b/src/main/java/org/olat/course/nodes/gta/GTACourseNodeConfiguration.java @@ -37,20 +37,23 @@ import org.olat.course.nodes.GTACourseNode; */ public class GTACourseNodeConfiguration extends AbstractCourseNodeConfiguration { - private GTACourseNodeConfiguration() { + private final boolean individual; + + private GTACourseNodeConfiguration(boolean individual) { super(); + this.individual = individual; } @Override public CourseNode getInstance() { - return new GTACourseNode(); + return new GTACourseNode(getAlias()); } @Override public String getLinkText(Locale locale) { Translator fallback = Util.createPackageTranslator(CourseNodeConfiguration.class, locale); Translator translator = Util.createPackageTranslator(this.getClass(), locale, fallback); - return translator.translate("title_gta"); + return individual ? translator.translate("title_ita") : translator.translate("title_gta"); } @Override @@ -60,7 +63,7 @@ public class GTACourseNodeConfiguration extends AbstractCourseNodeConfiguration @Override public String getAlias() { - return "gta"; + return individual ? "ita" : "gta"; } @Override 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 5859a22a63e38fdaa63a9aacedf327f6e78cf7a7..474811c833a7edfa3812b4eed25749ddbcd2550c 100644 --- a/src/main/java/org/olat/course/nodes/gta/GTAManager.java +++ b/src/main/java/org/olat/course/nodes/gta/GTAManager.java @@ -145,6 +145,8 @@ public interface GTAManager { public TaskList getTaskList(RepositoryEntryRef entry, GTACourseNode cNode); + public int deleteTaskList(RepositoryEntryRef entry, GTACourseNode cNode); + public Membership getMembership(IdentityRef identity, RepositoryEntryRef entry, GTACourseNode cNode); @@ -181,7 +183,10 @@ public interface GTAManager { public AssignmentResponse assignTaskAutomatically(TaskList taskList, BusinessGroup assessedGroup, CourseEnvironment courseEnv, GTACourseNode cNode); public AssignmentResponse assignTaskAutomatically(TaskList taskList, Identity assessedIdentity, CourseEnvironment courseEnv, GTACourseNode cNode); - + + public TaskProcess firstStep(GTACourseNode cNode); + + public TaskProcess previousStep(TaskProcess currentStep, GTACourseNode cNode); public TaskProcess nextStep(TaskProcess currentStep, GTACourseNode cNode); diff --git a/src/main/java/org/olat/course/nodes/gta/GTARelativeToDates.java b/src/main/java/org/olat/course/nodes/gta/GTARelativeToDates.java new file mode 100644 index 0000000000000000000000000000000000000000..4faf419755f4686d191b0976c8f9b98439438345 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/gta/GTARelativeToDates.java @@ -0,0 +1,34 @@ +/** + * <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; + +/** + * + * Initial date: 08.05.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public enum GTARelativeToDates { + + courseStart,// relative to course start defined by a life-cycle + courseLaunch,// relative to the course launch by a user + enrollment//relative to the enrollment date + +} 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 746a050be9df66ffb81a24b701fa8e77e3fc47c1..595b0d213756b8218f4e5cdf1796a842c3965cd8 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 @@ -442,6 +442,24 @@ public class GTAManagerImpl implements GTAManager { return tasks.isEmpty() ? null : tasks.get(0); } + + @Override + public int deleteTaskList(RepositoryEntryRef entry, GTACourseNode cNode) { + TaskList taskList = getTaskList(entry, cNode); + + int numOfDeletedObjects; + if(taskList != null) { + String deleteTasks = "delete from gtatask as task where task.taskList.key=:taskListKey"; + int numOfTasks = dbInstance.getCurrentEntityManager().createQuery(deleteTasks) + .setParameter("taskListKey", taskList.getKey()) + .executeUpdate(); + dbInstance.getCurrentEntityManager().remove(taskList); + numOfDeletedObjects = numOfTasks + 1; + } else { + numOfDeletedObjects = 0; + } + return numOfDeletedObjects; + } @Override public List<Task> getTasks(TaskList taskList) { @@ -692,6 +710,83 @@ public class GTAManagerImpl implements GTAManager { dbInstance.commit();//make the thing definitiv return mergedtask; } + + + + @Override + public TaskProcess firstStep(GTACourseNode cNode) { + TaskProcess firstStep = null; + + if(cNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_ASSIGNMENT)) { + firstStep = TaskProcess.assignment; + } else if(cNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_SUBMIT)) { + firstStep = TaskProcess.submit; + } else if(cNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_REVIEW_AND_CORRECTION)) { + firstStep = TaskProcess.review; + } else if(cNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_REVISION_PERIOD)) { + firstStep = TaskProcess.revision; + } else if(cNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_REVISION_PERIOD)) { + firstStep = TaskProcess.correction; + } else if(cNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_SAMPLE_SOLUTION)) { + firstStep = TaskProcess.solution; + } else if(cNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_GRADING)) { + firstStep = TaskProcess.grading; + } + + return firstStep; + } + + @Override + public TaskProcess previousStep(TaskProcess currentStep, GTACourseNode cNode) { + TaskProcess previousStep = null; + switch(currentStep) { + case graded: + case grading: { + if(currentStep != TaskProcess.grading && cNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_GRADING)) { + previousStep = TaskProcess.grading; + break; + } + } + case solution: { + if(currentStep != TaskProcess.solution && cNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_SAMPLE_SOLUTION)) { + previousStep = TaskProcess.solution; + break; + } + } + case correction: { + if(currentStep != TaskProcess.correction && cNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_REVISION_PERIOD)) { + previousStep = TaskProcess.correction; + break; + } + } + case revision: { + if(currentStep != TaskProcess.revision && cNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_REVISION_PERIOD)) { + previousStep = TaskProcess.revision; + break; + } + } + case review: { + if(currentStep != TaskProcess.review && cNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_REVIEW_AND_CORRECTION)) { + previousStep = TaskProcess.review; + break; + } + } + case submit: { + if(currentStep != TaskProcess.submit && cNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_SUBMIT)) { + previousStep = TaskProcess.submit; + break; + } + } + case assignment: { + if(currentStep != TaskProcess.assignment && cNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_ASSIGNMENT)) { + previousStep = TaskProcess.assignment; + break; + } + } + } + + return previousStep; + } @Override public TaskProcess nextStep(TaskProcess currentStep, GTACourseNode cNode) { 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 71fbaccc78ca30863d85c03c3c04a85bacd4b376..ab761b65000cdaf1542ebcf172266d1d6eef938d 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 @@ -83,7 +83,7 @@ public class TaskImpl implements Task, CreateInfo, Persistable, ModifiedInfo { @Column(name="g_rev_loop", nullable=false, insertable=true, updatable=true) private int revisionLoop; - @Column(name="g_taskname", nullable=false, insertable=true, updatable=true) + @Column(name="g_taskname", nullable=true, insertable=true, updatable=true) private String taskName; @ManyToOne(targetEntity=TaskListImpl.class,fetch=FetchType.LAZY,optional=false) diff --git a/src/main/java/org/olat/course/nodes/gta/rule/AbstractDueDateTaskRuleSPI.java b/src/main/java/org/olat/course/nodes/gta/rule/AbstractDueDateTaskRuleSPI.java index 868517dbdc2759013df742280930d318b870a16b..364de565118c75f16b2b508ca3eae448f7cca5e4 100644 --- a/src/main/java/org/olat/course/nodes/gta/rule/AbstractDueDateTaskRuleSPI.java +++ b/src/main/java/org/olat/course/nodes/gta/rule/AbstractDueDateTaskRuleSPI.java @@ -19,20 +19,28 @@ */ package org.olat.course.nodes.gta.rule; +import java.util.ArrayList; +import java.util.Calendar; import java.util.Collections; import java.util.Date; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Set; import org.olat.basesecurity.GroupRoles; +import org.olat.core.CoreSpringFactory; import org.olat.core.id.Identity; +import org.olat.core.util.StringHelper; import org.olat.course.CourseFactory; import org.olat.course.ICourse; +import org.olat.course.assessment.manager.UserCourseInformationsManager; 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.GTARelativeToDates; import org.olat.course.nodes.gta.GTAType; import org.olat.course.nodes.gta.Task; import org.olat.course.nodes.gta.TaskList; @@ -45,7 +53,9 @@ import org.olat.modules.reminder.model.ReminderRuleImpl; import org.olat.modules.reminder.rule.LaunchUnit; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryEntryRelationType; +import org.olat.repository.RepositoryService; import org.olat.repository.manager.RepositoryEntryRelationDAO; +import org.olat.repository.model.RepositoryEntryLifecycle; import org.springframework.beans.factory.annotation.Autowired; /** @@ -79,20 +89,21 @@ public abstract class AbstractDueDateTaskRuleSPI implements IdentitiesProviderRu return identities == null ? Collections.<Identity>emptyList() : identities; } - protected List<Identity> evaluateRule(RepositoryEntry entry, GTACourseNode gtaNode, ReminderRuleImpl r) { + protected List<Identity> evaluateRule(RepositoryEntry entry, GTACourseNode gtaNode, ReminderRuleImpl rule) { List<Identity> identities = null; - Date dueDate = getDueDate(gtaNode); - if(dueDate != null) { - int value = Integer.parseInt(r.getRightOperand()); - String unit = r.getRightUnit(); - Date now = new Date(); - if(near(dueDate, now, value, LaunchUnit.valueOf(unit))) { + if(gtaNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_RELATIVE_DATES)) { + identities = evaluateRelativeDateRule(entry, gtaNode, rule); + } else { + Date dueDate = getDueDate(gtaNode); + if(dueDate != null && isNear(dueDate, now(), rule)) { identities = getPeopleToRemind(entry, gtaNode); } } return identities == null ? Collections.<Identity>emptyList() : identities; } + protected abstract List<Identity> evaluateRelativeDateRule(RepositoryEntry entry, GTACourseNode gtaNode, ReminderRuleImpl r); + protected abstract Date getDueDate(GTACourseNode gtaNode); protected List<Identity> getPeopleToRemind(RepositoryEntry entry, GTACourseNode gtaNode) { @@ -105,6 +116,95 @@ public abstract class AbstractDueDateTaskRuleSPI implements IdentitiesProviderRu } } + protected List<Identity> getPeopleToRemindRelativeTo(RepositoryEntry entry, GTACourseNode gtaNode, + int numOfDays, String relativeTo, ReminderRuleImpl rule) { + List<Identity> identities = null; + if(numOfDays >= 0 && StringHelper.containsNonWhitespace(relativeTo)) { + GTARelativeToDates rel = GTARelativeToDates.valueOf(relativeTo); + switch(rel) { + case courseStart: { + RepositoryEntryLifecycle lifecycle = entry.getLifecycle(); + if(lifecycle != null && lifecycle.getValidFrom() != null) { + Date referenceDate = getDate(lifecycle.getValidFrom(), numOfDays); + if(isNear(referenceDate, now(), rule)) { + identities = getPeopleToRemind(entry, gtaNode); + } + } + break; + } + + case courseLaunch: { + UserCourseInformationsManager userCourseInformationsManager = CoreSpringFactory.getImpl(UserCourseInformationsManager.class); + Map<Long,Date> initialLaunchDates = userCourseInformationsManager.getInitialLaunchDates(entry.getOlatResource().getResourceableId()); + Map<Long,Date> dueDates = getDueDates(initialLaunchDates, numOfDays); + identities = getPeopleToRemindRelativeTo(entry, gtaNode, dueDates, rule); + break; + } + case enrollment: { + RepositoryService repositoryService = CoreSpringFactory.getImpl(RepositoryService.class); + Map<Long,Date> enrollmentDates = repositoryService.getEnrollmentDates(entry); + Map<Long,Date> dueDates = getDueDates(enrollmentDates, numOfDays); + identities = getPeopleToRemindRelativeTo(entry, gtaNode, dueDates, rule); + break; + } + } + } + return identities; + } + + protected List<Identity> getPeopleToRemindRelativeTo(RepositoryEntry entry, GTACourseNode gtaNode, + Map<Long,Date> dates, ReminderRuleImpl rule) { + + Date now = now(); + Set<Long> potentialidentityKeys = new HashSet<>(); + for(Map.Entry<Long, Date> entryDate:dates.entrySet()) { + Long identityKey = entryDate.getKey(); + Date date = entryDate.getValue(); + if(isNear(date, now, rule)) { + potentialidentityKeys.add(identityKey); + } + } + + List<Identity> identities = null; + if(potentialidentityKeys.size() > 0) { + List<Identity> allIdentities = getPeopleToRemind(entry, gtaNode); + identities = new ArrayList<>(); + for(Identity identity:allIdentities) { + if(potentialidentityKeys.contains(identity.getKey())) { + identities.add(identity); + } + } + } + return identities; + } + + private Map<Long,Date> getDueDates(Map<Long,Date> referenceDates, int numOfDays) { + Map<Long, Date> dueDates = new HashMap<>(); + if(referenceDates != null && referenceDates.size() > 0) { + Calendar cal = Calendar.getInstance(); + for(Map.Entry<Long, Date> referenceEntry:referenceDates.entrySet()) { + Long identityKey = referenceEntry.getKey(); + cal.setTime(referenceEntry.getValue()); + cal.add(Calendar.DATE, numOfDays); + dueDates.put(identityKey, cal.getTime()); + } + } + return dueDates; + } + + private Date getDate(Date referenceDate, int numOfDays) { + Date date = null; + if(referenceDate != null) { + Calendar cal = Calendar.getInstance(); + cal.setTime(referenceDate); + cal.add(Calendar.DATE, numOfDays); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + date = cal.getTime(); + } + return date; + } + protected List<Identity> getGroupsToRemind(TaskList taskList, GTACourseNode gtaNode) { List<Task> tasks = gtaManager.getTasks(taskList); Set<BusinessGroup> doneTasks = new HashSet<BusinessGroup>(); @@ -143,6 +243,18 @@ public abstract class AbstractDueDateTaskRuleSPI implements IdentitiesProviderRu return identities; } + protected Date now() { + Calendar cal = Calendar.getInstance(); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + return cal.getTime(); + } + + protected boolean isNear(Date dueDate, Date now, ReminderRuleImpl r) { + int value = Integer.parseInt(r.getRightOperand()); + String unit = r.getRightUnit(); + return near(dueDate, now, value, LaunchUnit.valueOf(unit)); + } private boolean near(Date date, Date now, int distance, LaunchUnit unit) { double between = -1; @@ -160,7 +272,8 @@ public abstract class AbstractDueDateTaskRuleSPI implements IdentitiesProviderRu between = yearsBetween(now, date); break; } - return between <= distance || between < 0.0; + // 0.1 to let +- 2 hours to match + return between <= distance || between - 0.1 <= distance || between < 0.0; } private double daysBetween(Date d1, Date d2) { diff --git a/src/main/java/org/olat/course/nodes/gta/rule/AssignTaskRuleSPI.java b/src/main/java/org/olat/course/nodes/gta/rule/AssignTaskRuleSPI.java index d5ac5d5595776589fb81c92b3840b0433e8f85df..6d8feebbe2720f62d3ef3a14ab120dba887b9d6b 100644 --- a/src/main/java/org/olat/course/nodes/gta/rule/AssignTaskRuleSPI.java +++ b/src/main/java/org/olat/course/nodes/gta/rule/AssignTaskRuleSPI.java @@ -20,12 +20,16 @@ package org.olat.course.nodes.gta.rule; import java.util.Date; +import java.util.List; +import org.olat.core.id.Identity; +import org.olat.core.util.StringHelper; import org.olat.course.nodes.GTACourseNode; import org.olat.course.nodes.gta.ui.BeforeDateTaskRuleEditor; import org.olat.modules.ModuleConfiguration; import org.olat.modules.reminder.ReminderRule; import org.olat.modules.reminder.RuleEditorFragment; +import org.olat.modules.reminder.model.ReminderRuleImpl; import org.olat.repository.RepositoryEntry; import org.springframework.stereotype.Service; @@ -63,4 +67,15 @@ public class AssignTaskRuleSPI extends AbstractDueDateTaskRuleSPI { } return dueDate; } + + @Override + protected List<Identity> evaluateRelativeDateRule(RepositoryEntry entry, GTACourseNode gtaNode, ReminderRuleImpl rule) { + List<Identity> identities = null; + int numOfDays = gtaNode.getModuleConfiguration().getIntegerSafe(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE, -1); + String relativeTo = gtaNode.getModuleConfiguration().getStringValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE_TO); + if(numOfDays >= 0 && StringHelper.containsNonWhitespace(relativeTo)) { + identities = getPeopleToRemindRelativeTo(entry, gtaNode, numOfDays, relativeTo, rule); + } + return identities; + } } diff --git a/src/main/java/org/olat/course/nodes/gta/rule/SubmissionTaskRuleSPI.java b/src/main/java/org/olat/course/nodes/gta/rule/SubmissionTaskRuleSPI.java index a22e6eae58692835b78043362bc2a98d3b1de155..326c05d16b11bf6e5ab381fc550396ab354dcaa5 100644 --- a/src/main/java/org/olat/course/nodes/gta/rule/SubmissionTaskRuleSPI.java +++ b/src/main/java/org/olat/course/nodes/gta/rule/SubmissionTaskRuleSPI.java @@ -20,7 +20,10 @@ package org.olat.course.nodes.gta.rule; import java.util.Date; +import java.util.List; +import org.olat.core.id.Identity; +import org.olat.core.util.StringHelper; import org.olat.course.nodes.GTACourseNode; import org.olat.course.nodes.gta.GTAManager; import org.olat.course.nodes.gta.ui.BeforeDateTaskRuleEditor; @@ -28,6 +31,7 @@ import org.olat.group.BusinessGroupService; import org.olat.modules.ModuleConfiguration; import org.olat.modules.reminder.ReminderRule; import org.olat.modules.reminder.RuleEditorFragment; +import org.olat.modules.reminder.model.ReminderRuleImpl; import org.olat.repository.RepositoryEntry; import org.olat.repository.manager.RepositoryEntryRelationDAO; import org.springframework.beans.factory.annotation.Autowired; @@ -74,4 +78,15 @@ public class SubmissionTaskRuleSPI extends AbstractDueDateTaskRuleSPI { } return dueDate; } + + @Override + protected List<Identity> evaluateRelativeDateRule(RepositoryEntry entry, GTACourseNode gtaNode, ReminderRuleImpl rule) { + List<Identity> identities = null; + int numOfDays = gtaNode.getModuleConfiguration().getIntegerSafe(GTACourseNode.GTASK_SUBMIT_DEADLINE_RELATIVE, -1); + String relativeTo = gtaNode.getModuleConfiguration().getStringValue(GTACourseNode.GTASK_SUBMIT_DEADLINE_RELATIVE_TO); + if(numOfDays >= 0 && StringHelper.containsNonWhitespace(relativeTo)) { + identities = getPeopleToRemindRelativeTo(entry, gtaNode, numOfDays, relativeTo, rule); + } + return identities; + } } diff --git a/src/main/java/org/olat/course/nodes/gta/ui/BulkDownloadToolController.java b/src/main/java/org/olat/course/nodes/gta/ui/BulkDownloadToolController.java new file mode 100644 index 0000000000000000000000000000000000000000..9aec7c6a22be0339b83c3c3a03150f64ac81f0fb --- /dev/null +++ b/src/main/java/org/olat/course/nodes/gta/ui/BulkDownloadToolController.java @@ -0,0 +1,81 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.course.nodes.gta.ui; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.link.Link; +import org.olat.core.gui.components.link.LinkFactory; +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.course.archiver.ArchiveResource; +import org.olat.course.nodes.ArchiveOptions; +import org.olat.course.nodes.AssessmentToolOptions; +import org.olat.course.nodes.GTACourseNode; +import org.olat.course.run.environment.CourseEnvironment; +import org.olat.resource.OLATResource; + +/** + * + * Initial date: 07.05.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class BulkDownloadToolController extends BasicController { + + private final Link downloadButton; + + private final ArchiveOptions options; + private final OLATResource courseOres; + private final GTACourseNode courseNode; + + public BulkDownloadToolController(UserRequest ureq, WindowControl wControl, CourseEnvironment courseEnv, + AssessmentToolOptions asOptions, GTACourseNode courseNode) { + super(ureq, wControl); + this.options = new ArchiveOptions(); + this.options.setGroup(asOptions.getGroup()); + this.options.setIdentities(asOptions.getIdentities()); + this.courseNode = courseNode; + courseOres = courseEnv.getCourseGroupManager().getCourseResource(); + + downloadButton = LinkFactory.createButton("bulk.download.title", null, this); + downloadButton.setTranslator(getTranslator()); + putInitialPanel(downloadButton); + getInitialComponent().setSpanAsDomReplaceable(true); // override to wrap panel as span to not break link layout + } + + @Override + protected void doDispose() { + // + } + + @Override + protected void event(UserRequest ureq, Component source, Event event) { + if(downloadButton == source) { + doDownload(ureq); + } + } + + private void doDownload(UserRequest ureq) { + ArchiveResource resource = new ArchiveResource(courseNode, courseOres, options, getLocale()); + ureq.getDispatchResult().setResultingMediaResource(resource); + } +} diff --git a/src/main/java/org/olat/course/nodes/gta/ui/DescriptionWithTooltipCellRenderer.java b/src/main/java/org/olat/course/nodes/gta/ui/DescriptionWithTooltipCellRenderer.java new file mode 100644 index 0000000000000000000000000000000000000000..449b82bae4cfd68aa7bb10eebd91d230a0713f35 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/gta/ui/DescriptionWithTooltipCellRenderer.java @@ -0,0 +1,81 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.course.nodes.gta.ui; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiCellRenderer; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableComponent; +import org.olat.core.gui.render.Renderer; +import org.olat.core.gui.render.StringOutput; +import org.olat.core.gui.render.URLBuilder; +import org.olat.core.gui.translator.Translator; +import org.olat.core.util.Formatter; +import org.olat.core.util.filter.FilterFactory; + +/** + * + * Initial date: 06.05.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class DescriptionWithTooltipCellRenderer implements FlexiCellRenderer { + + private final AtomicInteger positionCounter = new AtomicInteger(1); + + @Override + public void render(Renderer renderer, StringOutput sb, Object cellValue, + int row, FlexiTableComponent source, URLBuilder ubu, Translator translator) { + if(cellValue instanceof String) { + String desc = (String)cellValue; + if(desc.length() > 50) { + String truncatedDesc = FilterFactory.getHtmlTagsFilter().filter(desc); + truncatedDesc = Formatter.truncate(truncatedDesc, 50, ""); + + String pos = Integer.toString(positionCounter.incrementAndGet()); + sb.append("<span id='o_sel_desc_").append(pos).append("' href='javascript:void(0); return false;'>") + .append(truncatedDesc) + .append("\u2026</span>"); + + sb.append("<div id='o_sel_desc_tooltip_").append(pos).append("' style='display:none;'>") + .append(desc) + .append("</div>"); + + sb.append("<script type='text/javascript'>") + .append("/* <![CDATA[ */") + .append("jQuery(function() {\n") + .append(" jQuery('#o_sel_desc_").append(pos).append("').tooltip({\n") + .append(" html: true,\n") + .append(" container: 'body',\n") + .append(" placement: 'bottom',\n") + .append(" title: function(){ return jQuery('#o_sel_desc_tooltip_").append(pos).append("').html(); }\n") + .append(" });\n") + .append(" jQuery('#o_sel_desc_").append(pos).append("').on('click', function(){\n") + .append(" jQuery('#o_sel_desc_").append(pos).append("').tooltip('hide');\n") + .append(" });\n") + .append("});") + .append("/* ]]> */") + .append("</script>"); + } else { + sb.append(desc); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/org/olat/course/nodes/gta/ui/GTAAbstractController.java b/src/main/java/org/olat/course/nodes/gta/ui/GTAAbstractController.java index 23b75d63a569173d8b8272ad23a56bd89dc62e15..5e4350d42fd5a568c6e383f951a9dfd68b52e5b9 100644 --- a/src/main/java/org/olat/course/nodes/gta/ui/GTAAbstractController.java +++ b/src/main/java/org/olat/course/nodes/gta/ui/GTAAbstractController.java @@ -19,20 +19,25 @@ */ package org.olat.course.nodes.gta.ui; +import java.util.Calendar; import java.util.Date; import org.olat.core.commons.services.notifications.PublisherData; import org.olat.core.commons.services.notifications.SubscriptionContext; import org.olat.core.commons.services.notifications.ui.ContextualSubscriptionController; import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.Component; import org.olat.core.gui.components.velocity.VelocityContainer; +import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.controller.BasicController; import org.olat.core.id.Identity; import org.olat.core.util.Formatter; import org.olat.core.util.StringHelper; +import org.olat.course.assessment.manager.UserCourseInformationsManager; import org.olat.course.nodes.GTACourseNode; import org.olat.course.nodes.gta.GTAManager; +import org.olat.course.nodes.gta.GTARelativeToDates; import org.olat.course.nodes.gta.GTAType; import org.olat.course.nodes.gta.Task; import org.olat.course.nodes.gta.TaskList; @@ -42,6 +47,8 @@ import org.olat.course.run.userview.UserCourseEnvironment; import org.olat.group.BusinessGroup; import org.olat.modules.ModuleConfiguration; import org.olat.repository.RepositoryEntry; +import org.olat.repository.RepositoryService; +import org.olat.repository.model.RepositoryEntryLifecycle; import org.springframework.beans.factory.annotation.Autowired; /** @@ -72,10 +79,20 @@ public abstract class GTAAbstractController extends BasicController { protected final boolean businessGroupTask; + protected GTAStepPreferences stepPreferences; + private ContextualSubscriptionController contextualSubscriptionCtr; + private Date assignmentDueDate; + private Date submissionDueDate; + private Date solutionDueDate; + @Autowired protected GTAManager gtaManager; + @Autowired + protected RepositoryService repositoryService; + @Autowired + protected UserCourseInformationsManager userCourseInformationsManager; public GTAAbstractController(UserRequest ureq, WindowControl wControl, GTACourseNode gtaNode, CourseEnvironment courseEnv, boolean withTitle, boolean withGrading) { @@ -111,6 +128,13 @@ public abstract class GTAAbstractController extends BasicController { publisherData = gtaManager.getPublisherData(courseEnv, gtaNode); subsContext = gtaManager.getSubscriptionContext(courseEnv, gtaNode); + stepPreferences = (GTAStepPreferences)ureq.getUserSession() + .getGuiPreferences() + .get(GTAStepPreferences.class, taskList.getKey().toString()); + if(stepPreferences == null) { + stepPreferences = new GTAStepPreferences(); + } + initContainer(ureq); process(ureq); } @@ -140,6 +164,9 @@ public abstract class GTAAbstractController extends BasicController { mainVC.contextPut("assignmentEnabled", assignment); if(assignment) { task = stepAssignment(ureq, task); + } else if(task == null) { + TaskProcess firstStep = gtaManager.firstStep(gtaNode); + task = gtaManager.createTask(null, taskList, firstStep, assessedGroup, assessedIdentity, gtaNode); } boolean submit = config.getBooleanSafe(GTACourseNode.GTASK_SUBMIT); @@ -171,10 +198,46 @@ public abstract class GTAAbstractController extends BasicController { if(grading) { stepGrading(ureq, task); } + + collapsedContents(task); + } + + protected final void collapsedContents(Task currentTask) { + TaskProcess status = null; + TaskProcess previousStatus = null; + if(currentTask != null) { + status = currentTask.getTaskStatus(); + previousStatus = gtaManager.previousStep(status, gtaNode); + } + + boolean assignment = Boolean.TRUE.equals(stepPreferences.getAssignement()) + || TaskProcess.assignment.equals(status) || TaskProcess.assignment.equals(previousStatus); + mainVC.contextPut("collapse_assignement", new Boolean(assignment)); + + boolean submit = Boolean.TRUE.equals(stepPreferences.getSubmit()) + || TaskProcess.submit.equals(status) || TaskProcess.submit.equals(previousStatus); + mainVC.contextPut("collapse_submit", new Boolean(submit)); + + boolean reviewAndCorrection = Boolean.TRUE.equals(stepPreferences.getReviewAndCorrection()) + || TaskProcess.review.equals(status) || TaskProcess.review.equals(previousStatus); + mainVC.contextPut("collapse_reviewAndCorrection", new Boolean(reviewAndCorrection)); + + boolean revision = Boolean.TRUE.equals(stepPreferences.getRevision()) + || TaskProcess.revision.equals(status) || TaskProcess.revision.equals(previousStatus) + || TaskProcess.correction.equals(status) || TaskProcess.correction.equals(previousStatus); + mainVC.contextPut("collapse_revision", new Boolean(revision)); + + boolean solution = Boolean.TRUE.equals(stepPreferences.getSolution()) + || TaskProcess.solution.equals(status) || TaskProcess.solution.equals(previousStatus); + mainVC.contextPut("collapse_solution", new Boolean(solution)); + + boolean grading = Boolean.TRUE.equals(stepPreferences.getGrading()) + || TaskProcess.grading.equals(status) || TaskProcess.grading.equals(previousStatus); + mainVC.contextPut("collapse_grading", new Boolean(grading)); } protected Task stepAssignment(@SuppressWarnings("unused") UserRequest ureq, Task assignedTask) { - Date dueDate = gtaNode.getModuleConfiguration().getDateValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE); + Date dueDate = getAssignementDueDate(); if(dueDate != null) { String date = Formatter.getInstance(getLocale()).formatDateAndTime(dueDate); mainVC.contextPut("assignmentDueDate", date); @@ -188,8 +251,66 @@ public abstract class GTAAbstractController extends BasicController { return assignedTask; } + protected void resetDueDates() { + assignmentDueDate = null; + submissionDueDate = null; + solutionDueDate = null; + } + + protected Date getAssignementDueDate() { + if(assignmentDueDate == null) { + Date dueDate = gtaNode.getModuleConfiguration().getDateValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE); + boolean relativeDate = gtaNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_RELATIVE_DATES); + if(relativeDate) { + int numOfDays = gtaNode.getModuleConfiguration().getIntegerSafe(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE, -1); + String relativeTo = gtaNode.getModuleConfiguration().getStringValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE_TO); + if(numOfDays >= 0 && StringHelper.containsNonWhitespace(relativeTo)) { + assignmentDueDate = getReferenceDate(numOfDays, relativeTo); + } + } else if(dueDate != null) { + assignmentDueDate = dueDate; + } + } + return assignmentDueDate; + } + + protected Date getReferenceDate(int numOfDays, String relativeTo) { + Date dueDate = null; + if(numOfDays >= 0 && StringHelper.containsNonWhitespace(relativeTo)) { + GTARelativeToDates rel = GTARelativeToDates.valueOf(relativeTo); + Date referenceDate = null; + switch(rel) { + case courseStart: { + RepositoryEntryLifecycle lifecycle = courseEntry.getLifecycle(); + if(lifecycle != null && lifecycle.getValidFrom() != null) { + referenceDate = lifecycle.getValidFrom(); + } + break; + } + case courseLaunch: { + referenceDate = userCourseInformationsManager + .getInitialLaunchDate(courseEnv.getCourseResourceableId(), assessedIdentity); + break; + } + case enrollment: { + referenceDate = repositoryService + .getEnrollmentDate(courseEntry, assessedIdentity); + break; + } + } + + if(referenceDate != null) { + Calendar cal = Calendar.getInstance(); + cal.setTime(referenceDate); + cal.add(Calendar.DATE, numOfDays); + dueDate = cal.getTime(); + } + } + return dueDate; + } + protected Task stepSubmit(@SuppressWarnings("unused")UserRequest ureq, Task assignedTask) { - Date dueDate = gtaNode.getModuleConfiguration().getDateValue(GTACourseNode.GTASK_SUBMIT_DEADLINE); + Date dueDate = getSubmissionDueDate(); if(dueDate != null) { String date = Formatter.getInstance(getLocale()).formatDateAndTime(dueDate); mainVC.contextPut("submitDueDate", date); @@ -204,6 +325,23 @@ public abstract class GTAAbstractController extends BasicController { return assignedTask; } + protected Date getSubmissionDueDate() { + if(submissionDueDate == null) { + Date dueDate = gtaNode.getModuleConfiguration().getDateValue(GTACourseNode.GTASK_SUBMIT_DEADLINE); + boolean relativeDate = gtaNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_RELATIVE_DATES); + if(relativeDate) { + int numOfDays = gtaNode.getModuleConfiguration().getIntegerSafe(GTACourseNode.GTASK_SUBMIT_DEADLINE_RELATIVE, -1); + String relativeTo = gtaNode.getModuleConfiguration().getStringValue(GTACourseNode.GTASK_SUBMIT_DEADLINE_RELATIVE_TO); + if(numOfDays >= 0 && StringHelper.containsNonWhitespace(relativeTo)) { + submissionDueDate = getReferenceDate(numOfDays, relativeTo); + } + } else if(dueDate != null) { + submissionDueDate = dueDate; + } + } + return submissionDueDate; + } + protected Task stepReviewAndCorrection(@SuppressWarnings("unused")UserRequest ureq, Task assignedTask) { return assignedTask; } @@ -213,7 +351,7 @@ public abstract class GTAAbstractController extends BasicController { } protected Task stepSolution(@SuppressWarnings("unused")UserRequest ureq, Task assignedTask) { - Date availableDate = gtaNode.getModuleConfiguration().getDateValue(GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER); + Date availableDate = getSolutionDueDate(); if(availableDate != null) { String date = Formatter.getInstance(getLocale()).formatDateAndTime(availableDate); mainVC.contextPut("solutionAvailableDate", date); @@ -221,6 +359,23 @@ public abstract class GTAAbstractController extends BasicController { return assignedTask; } + protected Date getSolutionDueDate() { + if(solutionDueDate == null) { + boolean relativeDate = gtaNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_RELATIVE_DATES); + Date dueDate = gtaNode.getModuleConfiguration().getDateValue(GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER); + if(relativeDate) { + int numOfDays = gtaNode.getModuleConfiguration().getIntegerSafe(GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER_RELATIVE, -1); + String relativeTo = gtaNode.getModuleConfiguration().getStringValue(GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER_RELATIVE_TO); + if(numOfDays >= 0 && StringHelper.containsNonWhitespace(relativeTo)) { + solutionDueDate = getReferenceDate(numOfDays, relativeTo); + } + } else if(dueDate != null) { + solutionDueDate = dueDate; + } + } + return solutionDueDate; + } + protected Task stepGrading(@SuppressWarnings("unused") UserRequest ureq, Task assignedTask) { if(businessGroupTask) { String groupLog = courseEnv.getAuditManager().getUserNodeLog(gtaNode, assessedGroup); @@ -239,4 +394,39 @@ public abstract class GTAAbstractController extends BasicController { } return assignedTask; } + + @Override + protected void event(UserRequest ureq, Component source, Event event) { + if("show".equals(event.getCommand())) { + doShow(ureq); + } else if("hide".equals(event.getCommand())) { + doHide(ureq); + } + } + + private void doShow(UserRequest ureq) { + String step = ureq.getParameter("step"); + doSaveStepPreferences(ureq, step, Boolean.TRUE); + } + + private void doHide(UserRequest ureq) { + String step = ureq.getParameter("step"); + doSaveStepPreferences(ureq, step, Boolean.FALSE); + } + + private void doSaveStepPreferences(UserRequest ureq, String step, Boolean showHide) { + if(step == null) return; + switch(step) { + case "assignment": stepPreferences.setAssignement(showHide); break; + case "submit": stepPreferences.setSubmit(showHide); break; + case "reviewAndCorrection": stepPreferences.setReviewAndCorrection(showHide); break; + case "revision": stepPreferences.setRevision(showHide); break; + case "solution": stepPreferences.setSolution(showHide); break; + case "grading": stepPreferences.setGrading(showHide); break; + default: {}; + } + + ureq.getUserSession().getGuiPreferences() + .putAndSave(GTAStepPreferences.class, taskList.getKey().toString(), stepPreferences); + } } \ No newline at end of file diff --git a/src/main/java/org/olat/course/nodes/gta/ui/GTAAvailableTaskController.java b/src/main/java/org/olat/course/nodes/gta/ui/GTAAvailableTaskController.java index 7e8c49f697c2a8f8fec1f5dbc74198c43d73b214..4b881ff69e882b5c4df0ec44c68aedc13072b69c 100644 --- a/src/main/java/org/olat/course/nodes/gta/ui/GTAAvailableTaskController.java +++ b/src/main/java/org/olat/course/nodes/gta/ui/GTAAvailableTaskController.java @@ -106,9 +106,9 @@ public class GTAAvailableTaskController extends FormBasicController { this.gtaNode = gtaNode; this.taskDefs = taskDefs; this.taskList = taskList; - this.courseEnv = courseEnv; this.assessedGroup = assessedGroup; this.assessedIdentity = assessedIdentity; + this.courseEnv = courseEnv; businessGroupTask = GTAType.group.name().equals(gtaNode.getModuleConfiguration().getStringValue(GTACourseNode.GTASK_TYPE)); initForm(ureq); } @@ -117,7 +117,8 @@ public class GTAAvailableTaskController extends FormBasicController { protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { FlexiTableColumnModel columnsModel = FlexiTableDataModelFactory.createFlexiTableColumnModel(); columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(ATDCols.title.i18nKey(), ATDCols.title.ordinal())); - columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(ATDCols.description.i18nKey(), ATDCols.description.ordinal())); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(ATDCols.description.i18nKey(), ATDCols.description.ordinal(), + new DescriptionWithTooltipCellRenderer())); boolean preview = gtaNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_PREVIEW); if(preview) { @@ -158,7 +159,6 @@ public class GTAAvailableTaskController extends FormBasicController { descriptionLink.setIconLeftCSS("o_icon o_icon_description"); } - File taskFile = new File(taskFolder, filename); DownloadLink download = uifactory.addDownloadLink("prev-" + CodeHelper.getRAMUniqueID(), filename, null, taskFile, tableEl); @@ -321,7 +321,7 @@ public class GTAAvailableTaskController extends FormBasicController { AvailableTask task = getObject(row); switch(ATDCols.values()[col]) { case title: return task.getTaskDef().getTitle(); - case description: return task.getDescriptionLink(); + case description: return task.getTaskDef().getDescription(); case preview: return task.getDownloadLink(); default: return "ERROR"; } 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 8bddf3b2c83b66c0d3e6aec9fc2d46880a7bf3e5..6f9a142ddeba307c76af651b401f6544048437db 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 @@ -348,7 +348,8 @@ public class GTACoachController extends GTAAbstractController { } else if(needRevisionsButton == source) { Task assignedTask = submitCorrectionsCtrl.getAssignedTask(); doRevisions(ureq, assignedTask); - } + } + super.event(ureq, source, event); } @Override diff --git a/src/main/java/org/olat/course/nodes/gta/ui/GTAEditController.java b/src/main/java/org/olat/course/nodes/gta/ui/GTAEditController.java index b40569b303771a3fbd7e30be178c5e78c7d9c295..a73c6007bfeb15e9d98140bfc50ce82ced185ea6 100644 --- a/src/main/java/org/olat/course/nodes/gta/ui/GTAEditController.java +++ b/src/main/java/org/olat/course/nodes/gta/ui/GTAEditController.java @@ -89,7 +89,7 @@ public class GTAEditController extends ActivateableTabbableDefaultController { accessCondition, AssessmentHelper.getAssessableNodes(editorModel, gtaNode), euce); listenTo(accessibilityCondCtrl); //workflow - workflowCtrl = new GTAWorkflowEditController(ureq, getWindowControl(), config, euce.getCourseEditorEnv()); + workflowCtrl = new GTAWorkflowEditController(ureq, getWindowControl(), gtaNode, euce.getCourseEditorEnv()); listenTo(workflowCtrl); //assignment assignmentCtrl = new GTAAssignmentEditController(ureq, getWindowControl(), config, tasksDir); 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 3ae3c7cb9a493b8e2c1ff1dd20bd8473734b4828..42d13b40627b5ad4d3676c69e9d092bbefbcf676 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 @@ -36,6 +36,8 @@ 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.modal.DialogBoxController; +import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory; import org.olat.core.id.Identity; import org.olat.core.util.StringHelper; import org.olat.core.util.mail.MailBundle; @@ -45,7 +47,12 @@ import org.olat.core.util.mail.MailManager; import org.olat.core.util.mail.MailTemplate; import org.olat.core.util.mail.MailerResult; import org.olat.core.util.vfs.VFSContainer; +import org.olat.course.CourseFactory; +import org.olat.course.ICourse; +import org.olat.course.assessment.AssessmentHelper; +import org.olat.course.assessment.AssessmentManager; import org.olat.course.nodes.GTACourseNode; +import org.olat.course.nodes.MSCourseNode; import org.olat.course.nodes.gta.AssignmentResponse; import org.olat.course.nodes.gta.GTAType; import org.olat.course.nodes.gta.Task; @@ -72,6 +79,7 @@ public class GTAParticipantController extends GTAAbstractController { private MSCourseNodeRunController gradingCtrl; private SubmitDocumentsController submitDocCtrl; + private DialogBoxController confirmSubmitDialog; private GTAAssignedTaskController assignedTaskCtrl; private GTAAvailableTaskController availableTaskCtrl; private CloseableCalloutWindowController chooserCalloutCtrl; @@ -97,6 +105,7 @@ public class GTAParticipantController extends GTAAbstractController { protected void initContainer(UserRequest ureq) { mainVC = createVelocityContainer("run"); putInitialPanel(mainVC); + initFlow() ; } @@ -135,7 +144,7 @@ public class GTAParticipantController extends GTAAbstractController { mainVC.contextPut("assignmentCssClass", "o_active"); //assignment open? - Date dueDate = gtaNode.getModuleConfiguration().getDateValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE); + Date dueDate = getAssignementDueDate(); if(dueDate != null && dueDate.compareTo(new Date()) < 0) { //assignment is closed mainVC.contextPut("assignmentClosed", Boolean.TRUE); @@ -240,7 +249,7 @@ public class GTAParticipantController extends GTAAbstractController { submitButton = LinkFactory.createCustomLink("run.submit.button", "submit", "run.submit.button", Link.BUTTON, mainVC, this); submitButton.setCustomEnabledLinkCSS("btn btn-primary"); - submitButton.setIconLeftCSS("o_icon o_icon o_icon_submit"); + submitButton.setIconLeftCSS("o_icon o_icon_submit"); } private void setSubmittedDocumentsController(UserRequest ureq) { @@ -256,6 +265,18 @@ public class GTAParticipantController extends GTAAbstractController { mainVC.put("submittedDocs", submittedDocCtrl.getInitialComponent()); } + private void doConfirmSubmit(UserRequest ureq, Task task) { + String title = translate("run.submit.button"); + String text; + if(GTAType.group.name().equals(config.getStringValue(GTACourseNode.GTASK_TYPE))) { + text = translate("run.submit.confirm.group", new String[]{ assessedGroup.getName() }); + } else { + text = translate("run.submit.confirm"); + } + confirmSubmitDialog = activateOkCancelDialog(ureq, title, text, confirmSubmitDialog); + confirmSubmitDialog.setUserObject(task); + } + private void doSubmitDocuments(UserRequest ureq, Task task) { task = gtaManager.updateTask(task, TaskProcess.review); showInfo("run.documents.successfully.submitted"); @@ -264,6 +285,7 @@ public class GTAParticipantController extends GTAAbstractController { cleanUpProcess(); process(ureq); + doUpdateAttempts(); //do send e-mail if(config.getBooleanSafe(GTACourseNode.GTASK_SUBMISSION_MAIL_CONFIRMATION)) { @@ -271,6 +293,22 @@ public class GTAParticipantController extends GTAAbstractController { } } + private void doUpdateAttempts() { + if(businessGroupTask) { + List<Identity> identities = businessGroupService.getMembers(assessedGroup, GroupRoles.participant.name()); + AssessmentManager assessmentManager = courseEnv.getAssessmentManager(); + assessmentManager.preloadCache(identities); + ICourse course = CourseFactory.loadCourse(courseEnv.getCourseResourceableId()); + + for(Identity identity:identities) { + UserCourseEnvironment userCourseEnv = AssessmentHelper.createAndInitUserCourseEnvironment(identity, course); + gtaNode.incrementUserAttempts(userCourseEnv); + } + } else { + gtaNode.incrementUserAttempts(userCourseEnv); + } + } + private void doSubmissionEmail() { String body = config.getStringValue(GTACourseNode.GTASK_SUBMISSION_TEXT); if(StringHelper.containsNonWhitespace(body)) { @@ -373,7 +411,8 @@ public class GTAParticipantController extends GTAAbstractController { private void setRevisionsAndCorrections(UserRequest ureq, Task task) { if(task.getRevisionLoop() > 0) { - revisionDocumentsCtrl = new GTAParticipantRevisionAndCorrectionsController(ureq, getWindowControl(), courseEnv, task, gtaNode, assessedGroup); + revisionDocumentsCtrl = new GTAParticipantRevisionAndCorrectionsController(ureq, getWindowControl(), + userCourseEnv, task, gtaNode, assessedGroup); listenTo(revisionDocumentsCtrl); mainVC.put("revisionDocs", revisionDocumentsCtrl.getInitialComponent()); } @@ -411,7 +450,7 @@ public class GTAParticipantController extends GTAAbstractController { } private void setSolutions(UserRequest ureq) { - Date availableDate = gtaNode.getModuleConfiguration().getDateValue(GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER); + Date availableDate = getSolutionDueDate(); boolean visible = availableDate == null || availableDate.compareTo(new Date()) <= 0; if(visible) { File documentsDir = gtaManager.getSolutionsDirectory(courseEnv, gtaNode); @@ -436,6 +475,11 @@ public class GTAParticipantController extends GTAAbstractController { } } + String infoTextUser = config.getStringValue(MSCourseNode.CONFIG_KEY_INFOTEXT_USER); + if(StringHelper.containsNonWhitespace(infoTextUser)) { + mainVC.contextPut("gradingInfoTextUser", StringHelper.xssScan(infoTextUser)); + } + if(config.getBooleanSafe(GTACourseNode.GTASK_ASSIGNMENT) || config.getBooleanSafe(GTACourseNode.GTASK_SUBMIT) || config.getBooleanSafe(GTACourseNode.GTASK_REVIEW_AND_CORRECTION) @@ -486,6 +530,7 @@ public class GTAParticipantController extends GTAAbstractController { private void setGroupHeaders(BusinessGroup group) { mainVC.contextPut("groupName", group.getName()); openGroupButton = LinkFactory.createButton("open.group", mainVC, this); + openGroupButton.setIconLeftCSS("o_icon o_icon_group"); } private void setMultiGroupsSelection() { @@ -505,8 +550,9 @@ public class GTAParticipantController extends GTAAbstractController { doChangeBusinessGroup(ureq); } else if(submitButton == source) { Task assignedTask = submitDocCtrl.getAssignedTask(); - doSubmitDocuments(ureq, assignedTask); + doConfirmSubmit(ureq, assignedTask); } + super.event(ureq, source, event); } @Override @@ -524,6 +570,7 @@ public class GTAParticipantController extends GTAAbstractController { } else if(businessGroupChooserCtrl == source) { if(event == Event.DONE_EVENT && businessGroupChooserCtrl.getSelectGroup() != null) { cleanUpProcess(); + resetDueDates(); assessedGroup = businessGroupChooserCtrl.getSelectGroup(); process(ureq); } @@ -531,10 +578,17 @@ public class GTAParticipantController extends GTAAbstractController { cleanUpPopups(); } else if(chooserCalloutCtrl == source) { cleanUpPopups(); + } else if(confirmSubmitDialog == source) { + if(DialogBoxUIFactory.isOkEvent(event) || DialogBoxUIFactory.isYesEvent(event)) { + Task task = (Task)confirmSubmitDialog.getUserObject(); + doSubmitDocuments(ureq, task); + } + cleanUpPopups(); } else if(submitDocCtrl == source) { if(event instanceof SubmitEvent) { Task assignedTask = submitDocCtrl.getAssignedTask(); gtaManager.log("Submit", (SubmitEvent)event, assignedTask, getIdentity(), assessedIdentity, assessedGroup, courseEnv, gtaNode); + doUpdateAttempts(); } } super.event(ureq, source, event); @@ -578,8 +632,10 @@ public class GTAParticipantController extends GTAAbstractController { private void cleanUpPopups() { removeAsListenerAndDispose(businessGroupChooserCtrl); + removeAsListenerAndDispose(confirmSubmitDialog); removeAsListenerAndDispose(chooserCalloutCtrl); businessGroupChooserCtrl = null; + confirmSubmitDialog = null; chooserCalloutCtrl = null; } @@ -587,8 +643,7 @@ public class GTAParticipantController extends GTAAbstractController { String businessPath = "[BusinessGroup:" + assessedGroup.getKey() + "]"; NewControllerFactory.getInstance().launch(businessPath, ureq, getWindowControl()); } - - + private void doChangeBusinessGroup(UserRequest ureq) { removeAsListenerAndDispose(businessGroupChooserCtrl); removeAsListenerAndDispose(chooserCalloutCtrl); 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 e32dbc7dea424c54cf72262e28a39b62839b2586..0b523023e3df91440b4c3358d1f18b585e787b53 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 @@ -23,6 +23,7 @@ import java.io.File; import java.util.ArrayList; import java.util.List; +import org.olat.basesecurity.GroupRoles; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; import org.olat.core.gui.components.link.Link; @@ -32,7 +33,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.controller.BasicController; +import org.olat.core.id.Identity; import org.olat.core.util.vfs.VFSContainer; +import org.olat.course.CourseFactory; +import org.olat.course.ICourse; +import org.olat.course.assessment.AssessmentHelper; +import org.olat.course.assessment.AssessmentManager; import org.olat.course.nodes.GTACourseNode; import org.olat.course.nodes.gta.GTAManager; import org.olat.course.nodes.gta.GTAType; @@ -40,7 +46,9 @@ import org.olat.course.nodes.gta.Task; import org.olat.course.nodes.gta.TaskHelper; import org.olat.course.nodes.gta.TaskProcess; import org.olat.course.run.environment.CourseEnvironment; +import org.olat.course.run.userview.UserCourseEnvironment; import org.olat.group.BusinessGroup; +import org.olat.group.BusinessGroupService; import org.olat.modules.ModuleConfiguration; import org.springframework.beans.factory.annotation.Autowired; @@ -63,15 +71,20 @@ public class GTAParticipantRevisionAndCorrectionsController extends BasicControl private final GTACourseNode gtaNode; private final BusinessGroup assessedGroup; private final CourseEnvironment courseEnv; + private final UserCourseEnvironment assessedUserCourseEnv; @Autowired private GTAManager gtaManager; + @Autowired + private BusinessGroupService businessGroupService; - public GTAParticipantRevisionAndCorrectionsController(UserRequest ureq, WindowControl wControl, CourseEnvironment courseEnv, - Task assignedTask, GTACourseNode gtaNode, BusinessGroup assessedGroup) { + public GTAParticipantRevisionAndCorrectionsController(UserRequest ureq, WindowControl wControl, + UserCourseEnvironment assessedUserCourseEnv,Task assignedTask, + GTACourseNode gtaNode, BusinessGroup assessedGroup) { super(ureq, wControl); this.gtaNode = gtaNode; - this.courseEnv = courseEnv; + courseEnv = assessedUserCourseEnv.getCourseEnvironment(); + this.assessedUserCourseEnv = assessedUserCourseEnv; this.assignedTask = assignedTask; this.assessedGroup = assessedGroup; this.businessGroupTask = GTAType.group.name().equals(gtaNode.getModuleConfiguration().getStringValue(GTACourseNode.GTASK_TYPE)); @@ -204,6 +217,19 @@ public class GTAParticipantRevisionAndCorrectionsController extends BasicControl private void doSubmitRevisions() { assignedTask = gtaManager.updateTask(assignedTask, TaskProcess.correction); gtaManager.log("Revision", "revision submitted", assignedTask, getIdentity(), getIdentity(), assessedGroup, courseEnv, gtaNode); - + + if(businessGroupTask) { + List<Identity> identities = businessGroupService.getMembers(assessedGroup, GroupRoles.participant.name()); + AssessmentManager assessmentManager = courseEnv.getAssessmentManager(); + assessmentManager.preloadCache(identities); + ICourse course = CourseFactory.loadCourse(courseEnv.getCourseResourceableId()); + + for(Identity identity:identities) { + UserCourseEnvironment userCourseEnv = AssessmentHelper.createAndInitUserCourseEnvironment(identity, course); + gtaNode.incrementUserAttempts(userCourseEnv); + } + } else { + gtaNode.incrementUserAttempts(assessedUserCourseEnv); + } } } diff --git a/src/main/java/org/olat/course/nodes/gta/ui/GTAStepPreferences.java b/src/main/java/org/olat/course/nodes/gta/ui/GTAStepPreferences.java new file mode 100644 index 0000000000000000000000000000000000000000..27385796a1a2a947d80c1caaba956103bbffc81a --- /dev/null +++ b/src/main/java/org/olat/course/nodes/gta/ui/GTAStepPreferences.java @@ -0,0 +1,84 @@ +/** + * <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; + +/** + * + * Initial date: 07.05.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class GTAStepPreferences { + + private Boolean assignement; + private Boolean submit; + private Boolean reviewAndCorrection; + private Boolean revision; + private Boolean solution; + private Boolean grading; + + public Boolean getAssignement() { + return assignement; + } + + public void setAssignement(Boolean assignement) { + this.assignement = assignement; + } + + public Boolean getSubmit() { + return submit; + } + + public void setSubmit(Boolean submit) { + this.submit = submit; + } + + public Boolean getReviewAndCorrection() { + return reviewAndCorrection; + } + + public void setReviewAndCorrection(Boolean reviewAndCorrection) { + this.reviewAndCorrection = reviewAndCorrection; + } + + public Boolean getRevision() { + return revision; + } + + public void setRevision(Boolean revision) { + this.revision = revision; + } + + public Boolean getSolution() { + return solution; + } + + public void setSolution(Boolean solution) { + this.solution = solution; + } + + public Boolean getGrading() { + return grading; + } + + public void setGrading(Boolean grading) { + this.grading = grading; + } +} diff --git a/src/main/java/org/olat/course/nodes/gta/ui/GTAWorkflowEditController.java b/src/main/java/org/olat/course/nodes/gta/ui/GTAWorkflowEditController.java index 32bd56624e0b1922ee3365f44ebbbdab85591067..c0c79a17687270728a1855db663c4018381aa9b3 100644 --- a/src/main/java/org/olat/course/nodes/gta/ui/GTAWorkflowEditController.java +++ b/src/main/java/org/olat/course/nodes/gta/ui/GTAWorkflowEditController.java @@ -31,6 +31,7 @@ import org.olat.core.gui.components.form.flexible.elements.FormLink; import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement; import org.olat.core.gui.components.form.flexible.elements.SingleSelection; import org.olat.core.gui.components.form.flexible.elements.StaticTextElement; +import org.olat.core.gui.components.form.flexible.elements.TextElement; import org.olat.core.gui.components.form.flexible.impl.FormBasicController; import org.olat.core.gui.components.form.flexible.impl.FormEvent; import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; @@ -44,6 +45,7 @@ import org.olat.course.condition.AreaSelectionController; import org.olat.course.condition.GroupSelectionController; import org.olat.course.editor.CourseEditorEnv; import org.olat.course.nodes.GTACourseNode; +import org.olat.course.nodes.gta.GTARelativeToDates; import org.olat.course.nodes.gta.GTAType; import org.olat.group.BusinessGroupService; import org.olat.group.BusinessGroupShort; @@ -62,6 +64,11 @@ public class GTAWorkflowEditController extends FormBasicController { private static final String[] keys = new String[]{ "on" }; private static final String[] executionKeys = new String[]{ GTAType.group.name(), GTAType.individual.name() }; + private static final String[] relativeDatesKeys = new String[] { + GTARelativeToDates.courseStart.name(), GTARelativeToDates.courseLaunch.name(), + GTARelativeToDates.enrollment.name() + }; + private final String[] relativeDatesValues; private CloseableModalController cmc; private AreaSelectionController areaSelectionCtrl; @@ -71,9 +78,12 @@ public class GTAWorkflowEditController extends FormBasicController { private FormLink chooseGroupButton, chooseAreaButton; private StaticTextElement groupListEl, areaListEl; private DateChooser assignmentDeadlineEl, submissionDeadlineEl, solutionVisibleAfterEl; - private MultipleSelectionElement taskAssignmentEl, submissionEl, reviewEl, revisionEl, sampleEl, gradingEl; - private FormLayoutContainer stepsCont; + private MultipleSelectionElement relativeDatesEl, taskAssignmentEl, submissionEl, reviewEl, revisionEl, sampleEl, gradingEl; + private FormLayoutContainer stepsCont, assignmentRelDeadlineCont, submissionRelDeadlineCont, solutionVisibleRelCont; + private TextElement assignementDeadlineDaysEl, submissionDeadlineDaysEl, solutionVisibleRelDaysEl; + private SingleSelection assignementDeadlineRelToEl, submissionDeadlineRelToEl, solutionVisibleRelToEl; + private final GTACourseNode gtaNode; private final ModuleConfiguration config; private final CourseEditorEnv courseEditorEnv; private List<Long> areaKeys; @@ -84,16 +94,25 @@ public class GTAWorkflowEditController extends FormBasicController { @Autowired private BusinessGroupService businessGroupService; - public GTAWorkflowEditController(UserRequest ureq, WindowControl wControl, ModuleConfiguration config, CourseEditorEnv courseEditorEnv) { + public GTAWorkflowEditController(UserRequest ureq, WindowControl wControl, GTACourseNode gtaNode, CourseEditorEnv courseEditorEnv) { super(ureq, wControl, LAYOUT_BAREBONE); - this.config = config; + this.gtaNode = gtaNode; + this.config = gtaNode.getModuleConfiguration(); this.courseEditorEnv = courseEditorEnv; + relativeDatesValues = new String[] { + translate("relative.to.course.start"), + translate("relative.to.course.launch"), + translate("relative.to.enrollment") + }; + initForm(ureq); } @Override protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + String type = config.getStringValue(GTACourseNode.GTASK_TYPE); + FormLayoutContainer typeCont = FormLayoutContainer.createDefaultFormLayout("type", getTranslator()); typeCont.setFormTitle(translate("task.type.title")); typeCont.setFormDescription(translate("task.type.description")); @@ -104,9 +123,10 @@ public class GTAWorkflowEditController extends FormBasicController { translate("task.execution.group"), translate("task.execution.individual") }; + typeEl = uifactory.addDropdownSingleselect("execution", "task.execution", typeCont, executionKeys, executionValues, null); typeEl.addActionListener(FormEvent.ONCHANGE); - String type = config.getStringValue(GTACourseNode.GTASK_TYPE); + typeEl.setEnabled(false); if(StringHelper.containsNonWhitespace(type)) { for(String executionKey:executionKeys) { if(executionKey.equals(type)) { @@ -145,14 +165,35 @@ public class GTAWorkflowEditController extends FormBasicController { areaListEl = uifactory.addStaticTextElement("areas.list", null, areaList, typeCont); areaListEl.setElementCssClass("text-muted"); areaListEl.setLabel(null, null); - updateTaskType(); + boolean mismatch = ((GTAType.group.name().equals(type) && !gtaNode.getType().equals(GTACourseNode.TYPE_GROUP)) + || (GTAType.individual.name().equals(type) && !gtaNode.getType().equals(GTACourseNode.TYPE_INDIVIDUAL))); + + if(GTAType.group.name().equals(type)) { + typeEl.setVisible(mismatch); + } else if(GTAType.individual.name().equals(type)) { + if(mismatch) { + typeEl.setVisible(true); + chooseGroupButton.setVisible(false); + groupListEl.setVisible(false); + chooseAreaButton.setVisible(false); + areaListEl.setVisible(false); + } else { + typeCont.setVisible(false); + } + } + //Steps stepsCont = FormLayoutContainer.createDefaultFormLayout("steps", getTranslator()); stepsCont.setFormTitle(translate("task.steps.title")); stepsCont.setFormDescription(translate("task.steps.description")); stepsCont.setRootForm(mainForm); formLayout.add(stepsCont); + + relativeDatesEl = uifactory.addCheckboxesHorizontal("relative.dates", "relative.dates", stepsCont, keys, new String[]{ "" }); + relativeDatesEl.addActionListener(FormEvent.ONCHANGE); + boolean useRelativeDates = config.getBooleanSafe(GTACourseNode.GTASK_RELATIVE_DATES); + relativeDatesEl.select(keys[0], useRelativeDates); //assignment String[] assignmentValues = new String[] { translate("enabled") }; @@ -164,7 +205,37 @@ public class GTAWorkflowEditController extends FormBasicController { Date assignmentDeadline = config.getDateValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE); assignmentDeadlineEl = uifactory.addDateChooser("assignementdeadline", "assignment.deadline", assignmentDeadline, stepsCont); assignmentDeadlineEl.setDateChooserTimeEnabled(true); - assignmentDeadlineEl.setVisible(assignement); + assignmentDeadlineEl.setVisible(assignement && !useRelativeDates); + + String relativeDatePage = velocity_root + "/assignment_relative_date.html"; + assignmentRelDeadlineCont = FormLayoutContainer.createCustomFormLayout("assignmentRelativeDeadline", getTranslator(), relativeDatePage); + assignmentRelDeadlineCont.setRootForm(mainForm); + assignmentRelDeadlineCont.setLabel("assignment.deadline", null); + assignmentRelDeadlineCont.setVisible(assignement && useRelativeDates); + stepsCont.add(assignmentRelDeadlineCont); + + int numOfDays = config.getIntegerSafe(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE, -1); + String assignmentNumOfDays = numOfDays >= 0 ? Integer.toString(numOfDays) : ""; + String assignmentRelativeTo = config.getStringValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE_TO); + assignementDeadlineDaysEl = uifactory.addTextElement("assignment.numOfDays", null, 4, assignmentNumOfDays, assignmentRelDeadlineCont); + assignementDeadlineDaysEl.setDisplaySize(4); + assignementDeadlineDaysEl.setDomReplacementWrapperRequired(false); + assignementDeadlineRelToEl = uifactory + .addDropdownSingleselect("assignmentrelativeto", "assignment.relative.to", null, assignmentRelDeadlineCont, relativeDatesKeys, relativeDatesValues, null); + assignementDeadlineRelToEl.setDomReplacementWrapperRequired(false); + + boolean found = false; + if(StringHelper.containsNonWhitespace(assignmentRelativeTo)) { + for(String relativeDatesKey:relativeDatesKeys) { + if(relativeDatesKey.equals(assignmentRelativeTo)) { + assignementDeadlineRelToEl.select(relativeDatesKey, true); + found = true; + } + } + } + if(!found) { + assignementDeadlineRelToEl.select(relativeDatesKeys[0], true); + } //turning in String[] submissionValues = new String[] { translate("submission.enabled") }; @@ -176,7 +247,37 @@ public class GTAWorkflowEditController extends FormBasicController { Date submissionDeadline = config.getDateValue(GTACourseNode.GTASK_SUBMIT_DEADLINE); submissionDeadlineEl = uifactory.addDateChooser("submitdeadline", "submit.deadline", submissionDeadline, stepsCont); submissionDeadlineEl.setDateChooserTimeEnabled(true); - submissionDeadlineEl.setVisible(submit); + submissionDeadlineEl.setVisible(submit && !useRelativeDates); + + //relative deadline + String submitPage = velocity_root + "/submit_relative_date.html"; + submissionRelDeadlineCont = FormLayoutContainer.createCustomFormLayout("submitRelativeDeadline", getTranslator(), submitPage); + submissionRelDeadlineCont.setRootForm(mainForm); + submissionRelDeadlineCont.setLabel("submit.deadline", null); + submissionRelDeadlineCont.setVisible(submit && useRelativeDates); + stepsCont.add(submissionRelDeadlineCont); + + numOfDays = config.getIntegerSafe(GTACourseNode.GTASK_SUBMIT_DEADLINE_RELATIVE, -1); + String submitRelDays = numOfDays >= 0 ? Integer.toString(numOfDays) : ""; + String submitRelTo = config.getStringValue(GTACourseNode.GTASK_SUBMIT_DEADLINE_RELATIVE_TO); + submissionDeadlineDaysEl = uifactory.addTextElement("submit.numOfDays", null, 4, submitRelDays, submissionRelDeadlineCont); + submissionDeadlineDaysEl.setDomReplacementWrapperRequired(false); + submissionDeadlineDaysEl.setDisplaySize(4); + submissionDeadlineRelToEl = uifactory + .addDropdownSingleselect("submitrelativeto", "submit.relative.to", null, submissionRelDeadlineCont, relativeDatesKeys, relativeDatesValues, null); + submissionDeadlineRelToEl.setDomReplacementWrapperRequired(false); + found = false; + if(StringHelper.containsNonWhitespace(submitRelTo)) { + for(String relativeDatesKey:relativeDatesKeys) { + if(relativeDatesKey.equals(submitRelTo)) { + submissionDeadlineRelToEl.select(relativeDatesKey, true); + found = true; + } + } + } + if(!found) { + submissionDeadlineRelToEl.select(relativeDatesKeys[0], true); + } //review and correction String[] reviewValues = new String[] { translate("review.enabled") }; @@ -203,6 +304,36 @@ public class GTAWorkflowEditController extends FormBasicController { solutionVisibleAfterEl = uifactory.addDateChooser("visibleafter", "sample.solution.visible.after", solutionVisibleAfter, stepsCont); solutionVisibleAfterEl.setDateChooserTimeEnabled(true); solutionVisibleAfterEl.setVisible(sample); + + //relative deadline + String solutionPage = velocity_root + "/solution_relative_date.html"; + solutionVisibleRelCont = FormLayoutContainer.createCustomFormLayout("solutionRelativeDeadline", getTranslator(), solutionPage); + solutionVisibleRelCont.setRootForm(mainForm); + solutionVisibleRelCont.setLabel("sample.solution.visible.after", null); + solutionVisibleRelCont.setVisible(submit && useRelativeDates); + stepsCont.add(solutionVisibleRelCont); + + numOfDays = config.getIntegerSafe(GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER_RELATIVE, -1); + String solutionRelDays = numOfDays >= 0 ? Integer.toString(numOfDays) : ""; + String solutionRelTo = config.getStringValue(GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER_RELATIVE_TO); + solutionVisibleRelDaysEl = uifactory.addTextElement("solution.numOfDays", null, 4, solutionRelDays, solutionVisibleRelCont); + solutionVisibleRelDaysEl.setDisplaySize(4); + solutionVisibleRelDaysEl.setDomReplacementWrapperRequired(false); + solutionVisibleRelToEl = uifactory + .addDropdownSingleselect("solutionrelativeto", "solution.relative.to", null, solutionVisibleRelCont, relativeDatesKeys, relativeDatesValues, null); + solutionVisibleRelToEl.setDomReplacementWrapperRequired(false); + found = false; + if(StringHelper.containsNonWhitespace(solutionRelTo)) { + for(String relativeDatesKey:relativeDatesKeys) { + if(relativeDatesKey.equals(solutionRelTo)) { + solutionVisibleRelToEl.select(relativeDatesKey, true); + found = true; + } + } + } + if(!found) { + solutionVisibleRelToEl.select(relativeDatesKeys[0], true); + } //grading String[] gradingValues = new String[] { translate("enabled") }; @@ -218,14 +349,6 @@ public class GTAWorkflowEditController extends FormBasicController { uifactory.addFormSubmitButton("save", "save", buttonCont); } - private void updateTaskType() { - boolean groupOption = typeEl.isSelected(0); - chooseGroupButton.setVisible(groupOption); - groupListEl.setVisible(groupOption); - chooseAreaButton.setVisible(groupOption); - areaListEl.setVisible(groupOption); - } - @Override protected void doDispose() { // @@ -249,6 +372,22 @@ public class GTAWorkflowEditController extends FormBasicController { } } + boolean relativeDates = relativeDatesEl.isAtLeastSelected(1); + assignementDeadlineDaysEl.clearError(); + if(relativeDates && taskAssignmentEl.isAtLeastSelected(1)) { + allOk &= validateIntegerOrEmpty(assignementDeadlineDaysEl); + } + + submissionDeadlineDaysEl.clearError(); + if(relativeDates && submissionEl.isAtLeastSelected(1)) { + allOk &= validateIntegerOrEmpty(submissionDeadlineDaysEl); + } + + solutionVisibleRelDaysEl.clearError(); + if(relativeDates && sampleEl.isAtLeastSelected(1)) { + allOk &= validateIntegerOrEmpty(solutionVisibleRelDaysEl); + } + taskAssignmentEl.clearError(); if(!taskAssignmentEl.isAtLeastSelected(1) && !submissionEl.isAtLeastSelected(1) && !reviewEl.isAtLeastSelected(1) && !revisionEl.isAtLeastSelected(1) @@ -260,6 +399,21 @@ public class GTAWorkflowEditController extends FormBasicController { return allOk & super.validateFormLogic(ureq); } + + private boolean validateIntegerOrEmpty(TextElement textEl) { + boolean allOk = true; + textEl.clearError(); + String val = textEl.getValue(); + if(StringHelper.containsNonWhitespace(val)) { + if(StringHelper.isLong(val)) { + + } else { + textEl.setErrorKey("integer.element.int.error", null); + allOk &= false; + } + } + return allOk; + } @Override protected void formOK(UserRequest ureq) { @@ -273,18 +427,31 @@ public class GTAWorkflowEditController extends FormBasicController { config.setList(GTACourseNode.GTASK_GROUPS, new ArrayList<Long>(0)); } + boolean relativeDates = relativeDatesEl.isAtLeastSelected(1); + config.setBooleanEntry(GTACourseNode.GTASK_RELATIVE_DATES, relativeDates); + boolean assignment = taskAssignmentEl.isAtLeastSelected(1); config.setBooleanEntry(GTACourseNode.GTASK_ASSIGNMENT, assignment); - if(assignment && assignmentDeadlineEl.getDate() != null) { - config.setDateValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE, assignmentDeadlineEl.getDate()); + if(assignment) { + if(relativeDates) { + setRelativeDates(assignementDeadlineDaysEl, GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE, + assignementDeadlineRelToEl, GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE_TO); + } else if(assignmentDeadlineEl.getDate() != null) { + config.setDateValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE, assignmentDeadlineEl.getDate()); + } } else { config.remove(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE); } boolean turningIn = submissionEl.isAtLeastSelected(1); config.setBooleanEntry(GTACourseNode.GTASK_SUBMIT, turningIn); - if(turningIn && submissionDeadlineEl.getDate() != null) { - config.setDateValue(GTACourseNode.GTASK_SUBMIT_DEADLINE, submissionDeadlineEl.getDate()); + if(turningIn) { + if(relativeDates) { + setRelativeDates(submissionDeadlineDaysEl, GTACourseNode.GTASK_SUBMIT_DEADLINE_RELATIVE, + submissionDeadlineRelToEl, GTACourseNode.GTASK_SUBMIT_DEADLINE_RELATIVE_TO); + } else { + config.setDateValue(GTACourseNode.GTASK_SUBMIT_DEADLINE, submissionDeadlineEl.getDate()); + } } else { config.remove(GTACourseNode.GTASK_SUBMIT_DEADLINE); } @@ -294,27 +461,47 @@ public class GTAWorkflowEditController extends FormBasicController { boolean sample = sampleEl.isAtLeastSelected(1); config.setBooleanEntry(GTACourseNode.GTASK_SAMPLE_SOLUTION, sample); - if(sample && solutionVisibleAfterEl.getDate() != null) { - config.setDateValue(GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER, solutionVisibleAfterEl.getDate()); + if(sample) { + if(relativeDates) { + setRelativeDates(solutionVisibleRelDaysEl, GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER_RELATIVE, + solutionVisibleRelToEl, GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER_RELATIVE_TO); + } else if(solutionVisibleAfterEl.getDate() != null) { + config.setDateValue(GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER, solutionVisibleAfterEl.getDate()); + } } else { config.remove(GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER); } config.setBooleanEntry(GTACourseNode.GTASK_GRADING, gradingEl.isAtLeastSelected(1)); - fireEvent(ureq, Event.DONE_EVENT); } + + private void setRelativeDates(TextElement daysEl, String daysKey, SingleSelection relativeToEl, String relativeToKey) { + String val = daysEl.getValue(); + if(StringHelper.isLong(val)) { + try { + config.setIntValue(daysKey, Integer.parseInt(val)); + } catch (NumberFormatException e) { + logWarn("", e); + } + + String relativeTo = relativeToEl.getSelectedKey(); + config.setStringValue(relativeToKey, relativeTo); + } + } @Override protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { - if(typeEl == source) { - updateTaskType(); - } else if(submissionEl == source) { - submissionDeadlineEl.setVisible(submissionEl.isAtLeastSelected(1)); + if(submissionEl == source) { + updateSubmissionDeadline(); } else if(taskAssignmentEl == source) { - assignmentDeadlineEl.setVisible(taskAssignmentEl.isAtLeastSelected(1)); + updateAssignmentDeadline(); } else if(sampleEl == source) { - solutionVisibleAfterEl.setVisible(sampleEl.isAtLeastSelected(1)); + updateSolutionDeadline(); + } else if(relativeDatesEl == source) { + updateAssignmentDeadline(); + updateSubmissionDeadline(); + updateSolutionDeadline(); } else if(chooseGroupButton == source) { doChooseGroup(ureq); } else if(chooseAreaButton == source) { @@ -324,10 +511,31 @@ public class GTAWorkflowEditController extends FormBasicController { super.formInnerEvent(ureq, source, event); } + private void updateAssignmentDeadline() { + boolean userRelativeDate = relativeDatesEl.isAtLeastSelected(1); + boolean assignment = taskAssignmentEl.isAtLeastSelected(1); + assignmentDeadlineEl.setVisible(assignment && !userRelativeDate); + assignmentRelDeadlineCont.setVisible(assignment && userRelativeDate); + } + + private void updateSubmissionDeadline() { + boolean userRelativeDate = relativeDatesEl.isAtLeastSelected(1); + boolean submit = submissionEl.isAtLeastSelected(1); + submissionDeadlineEl.setVisible(submit && !userRelativeDate); + submissionRelDeadlineCont.setVisible(submit && userRelativeDate); + } + + private void updateSolutionDeadline() { + boolean userRelativeDate = relativeDatesEl.isAtLeastSelected(1); + boolean solution = sampleEl.isAtLeastSelected(1); + solutionVisibleAfterEl.setVisible(solution && !userRelativeDate); + solutionVisibleRelCont.setVisible(solution && userRelativeDate); + } + @Override protected void event(UserRequest ureq, Controller source, Event event) { if(groupSelectionCtrl == source) { - if (event == Event.DONE_EVENT) { + if (event == Event.DONE_EVENT || event == Event.CHANGED_EVENT) { groupKeys = groupSelectionCtrl.getSelectedKeys(); groupListEl.setValue(getGroupNames(groupKeys)); if(courseEditorEnv.getCourseGroupManager().hasBusinessGroups()) { @@ -339,7 +547,7 @@ public class GTAWorkflowEditController extends FormBasicController { cmc.deactivate(); cleanUp(); } else if(areaSelectionCtrl == source) { - if (event == Event.DONE_EVENT) { + if (event == Event.DONE_EVENT || event == Event.CHANGED_EVENT) { areaKeys = areaSelectionCtrl.getSelectedKeys(); areaListEl.setValue(getAreaNames(areaKeys)); if(courseEditorEnv.getCourseGroupManager().hasAreas()) { @@ -358,9 +566,10 @@ public class GTAWorkflowEditController extends FormBasicController { private void cleanUp() { removeAsListenerAndDispose(groupSelectionCtrl); + removeAsListenerAndDispose(areaSelectionCtrl); removeAsListenerAndDispose(cmc); - groupSelectionCtrl = null; + areaSelectionCtrl = null; cmc = null; } diff --git a/src/main/java/org/olat/course/nodes/gta/ui/SubmitDocumentsController.java b/src/main/java/org/olat/course/nodes/gta/ui/SubmitDocumentsController.java index 6acf87cee8689f1cbde2d0061c7ba9cb2722b69d..626e6e0bad6ecd6905b5a492b1f7c3567fc6b412 100644 --- a/src/main/java/org/olat/course/nodes/gta/ui/SubmitDocumentsController.java +++ b/src/main/java/org/olat/course/nodes/gta/ui/SubmitDocumentsController.java @@ -120,7 +120,7 @@ class SubmitDocumentsController extends FormBasicController { columnsModel.addFlexiColumnModel(new StaticFlexiColumnModel("edit", DocCols.edit.ordinal(), "edit", new BooleanCellRenderer( new StaticFlexiCellRenderer(translate("edit"), "edit"), - new StaticFlexiCellRenderer(translate("replace"), "replace")))); + new StaticFlexiCellRenderer(translate("replace"), "edit")))); columnsModel.addFlexiColumnModel(new StaticFlexiColumnModel("delete", translate("delete"), "delete")); model = new DocumentTableModel(columnsModel); @@ -139,7 +139,7 @@ class SubmitDocumentsController extends FormBasicController { model.setObjects(docList); tableEl.reset(); - if(maxDocs >0 && docList.size() >= maxDocs) { + if(maxDocs > 0 && docList.size() >= maxDocs) { if(uploadDocButton != null) { uploadDocButton.setEnabled(false); } @@ -205,9 +205,11 @@ class SubmitDocumentsController extends FormBasicController { } private void cleanUp() { + removeAsListenerAndDispose(newDocumentEditorCtrl); removeAsListenerAndDispose(confirmDeleteCtrl); removeAsListenerAndDispose(uploadCtrl); removeAsListenerAndDispose(cmc); + newDocumentEditorCtrl = null; confirmDeleteCtrl = null; uploadCtrl = null; cmc = null; @@ -230,10 +232,8 @@ class SubmitDocumentsController extends FormBasicController { SubmittedSolution row = model.getObject(se.getIndex()); if("delete".equals(se.getCommand())) { doConfirmDelete(ureq, row); - } else if("replace".equals(se.getCommand())) { - doReplaceDocument(ureq, row); } else if("edit".equals(se.getCommand())) { - doEditDocumentEditor(ureq, row); + doEdit(ureq, row); } } } @@ -255,8 +255,16 @@ class SubmitDocumentsController extends FormBasicController { updateModel(); } + private void doEdit(UserRequest ureq, SubmittedSolution row) { + if(row.getFile().getName().endsWith(".html")) { + doEditDocumentEditor(ureq, row); + } else { + doReplaceDocument(ureq, row); + } + } + private void doReplaceDocument(UserRequest ureq, SubmittedSolution row) { - replaceCtrl = new DocumentUploadController(ureq, getWindowControl()); + replaceCtrl = new DocumentUploadController(ureq, getWindowControl(), row, row.getFile()); listenTo(replaceCtrl); String title = translate("replace.document"); diff --git a/src/main/java/org/olat/course/nodes/gta/ui/_content/assignment_relative_date.html b/src/main/java/org/olat/course/nodes/gta/ui/_content/assignment_relative_date.html new file mode 100644 index 0000000000000000000000000000000000000000..f1b4c29ad6f7ab910720bcb188a19b4619154d1c --- /dev/null +++ b/src/main/java/org/olat/course/nodes/gta/ui/_content/assignment_relative_date.html @@ -0,0 +1,6 @@ +<div class="form-inline">$r.render("assignment.numOfDays") <span class="form-control-static">$r.translate("days.after")</span> $r.render("assignment.relative.to")</div> +#if($f.hasError("assignment.numOfDays")) + <div class="form-inline"> + $r.render("assignment.numOfDays_ERROR") + </div> +#end \ No newline at end of file diff --git a/src/main/java/org/olat/course/nodes/gta/ui/_content/coach.html b/src/main/java/org/olat/course/nodes/gta/ui/_content/coach.html index d6b772afede557bbfb4d30c1916ccd22b9b04202..2ac7eee5de15f955dd6f106130b5fe470a54ccaa 100644 --- a/src/main/java/org/olat/course/nodes/gta/ui/_content/coach.html +++ b/src/main/java/org/olat/course/nodes/gta/ui/_content/coach.html @@ -10,11 +10,11 @@ $r.render("contextualSubscription") #if($assignmentEnabled) <div class="o_step $assignmentCssClass"> <div class="o_bar"></div> - <h4 class="o_title">$r.translate("run.assignment.title")</h4> + <h4 class="o_title"> <a href="#o_step_assignement_content" data-toggle="collapse" aria-expanded="$collapse_assignement">$r.translate("run.assignment.title")</a></h4> #if($assignmentDueDate) <div class="o_meta">$r.translate("run.assignment.due.date", $assignmentDueDate)</div> #end - <div class="o_content"> + <div id="o_step_assignement_content" class="o_content collapse #if($collapse_assignement) in #end" aria-expanded="$collapse_assignement"> #if($r.available("assignedTask")) $r.render("assignedTask") #else @@ -22,16 +22,23 @@ $r.render("contextualSubscription") #end </div> </div> + <script type='text/javascript'>/* <![CDATA[ */ + jQuery('#o_step_assignement_content').on('hide.bs.collapse', function () { + o_ffXHRNFEvent('$r.commandURIbg("hide")?step=assignment'); + }).on('show.bs.collapse', function () { + o_ffXHRNFEvent('$r.commandURIbg("show")?step=assignment'); + }) + /* ]]> */</script> #end #if($submitEnabled) <div class="o_step $submitCssClass"> <div class="o_bar"></div> - <h4 class="o_title">$r.translate("run.submit")</h4> + <h4 class="o_title"> <a href="#o_step_submit_content" data-toggle="collapse" aria-expanded="$collapse_submit">$r.translate("run.submit")</a></h4> #if($submitDueDate) <div class="o_meta">$r.translate("run.submit.due.date", $submitDueDate)</div> #end - <div class="o_content"> + <div id="o_step_submit_content" class="o_content collapse #if($collapse_submit) in #end" aria-expanded="$collapse_submit"> #if($r.available("submittedDocs")) $r.render("submittedDocs") #else @@ -39,13 +46,20 @@ $r.render("contextualSubscription") #end </div> </div> + <script type='text/javascript'>/* <![CDATA[ */ + jQuery('#o_step_submit_content').on('hide.bs.collapse', function () { + o_ffXHRNFEvent('$r.commandURIbg("hide")?step=submit'); + }).on('show.bs.collapse', function () { + o_ffXHRNFEvent('$r.commandURIbg("show")?step=submit'); + }) + /* ]]> */</script> #end #if($reviewAndCorrectionEnabled) <div class="o_step $reviewCssClass"> <div class="o_bar"></div> - <h4 class="o_title">$r.translate("run.review")</h4> - <div class="o_content"> + <h4 class="o_title"> <a href="#o_step_review_content" data-toggle="collapse" aria-expanded="$collapse_reviewAndCorrection">$r.translate("run.review")</a></h4> + <div id="o_step_review_content" class="o_content collapse #if($collapse_reviewAndCorrection) in #end" aria-expanded="$collapse_reviewAndCorrection"> #if($r.available("corrections")) $r.render("corrections") #if($r.available("coach.reviewed.button") || $r.available("coach.need.revision.button")) @@ -61,43 +75,65 @@ $r.render("contextualSubscription") #end </div> </div> + <script type='text/javascript'>/* <![CDATA[ */ + jQuery('#o_step_review_content').on('hide.bs.collapse', function () { + o_ffXHRNFEvent('$r.commandURIbg("hide")?step=reviewAndCorrection'); + }).on('show.bs.collapse', function () { + o_ffXHRNFEvent('$r.commandURIbg("show")?step=reviewAndCorrection'); + }) + /* ]]> */</script> #end #if($revisionEnabled) <div class="o_step $revisionCssClass"> <div class="o_bar"></div> - <h4 class="o_title">$r.translate("run.revision")</h4> - <div class="o_content"> + <h4 class="o_title"> <a href="#o_step_revision_content" data-toggle="collapse" aria-expanded="$collapse_revision">$r.translate("run.revision")</a></h4> + <div id="o_step_revision_content" class="o_content collapse #if($collapse_revision) in #end" aria-expanded="$collapse_revision"> #if($r.available("revisionDocs")) $r.render("revisionDocs") #end </div> </div> + <script type='text/javascript'>/* <![CDATA[ */ + jQuery('#o_step_revision_content').on('hide.bs.collapse', function () { + o_ffXHRNFEvent('$r.commandURIbg("hide")?step=revision'); + }).on('show.bs.collapse', function () { + o_ffXHRNFEvent('$r.commandURIbg("show")?step=revision'); + }) + /* ]]> */</script> #end #if($solutionEnabled) <div class="o_step $solutionCssClass"> <div class="o_bar"></div> - <h4 class="o_title">$r.translate("run.solution")</h4> + <h4 class="o_title"> <a href="#o_step_solution_content" data-toggle="collapse" aria-expanded="$collapse_solution">$r.translate("run.solution")</a></h4> #if($solutionAvailableDate) <div class="o_meta">$r.translate("run.solution.available.date", $solutionAvailableDate)</div> #end - <div class="o_content"> + <div id="o_step_solution_content" class="o_content collapse #if($collapse_solution) in #end" aria-expanded="$collapse_solution"> #if($r.available("solutions")) $r.render("solutions") #end </div> </div> + <script type='text/javascript'>/* <![CDATA[ */ + jQuery('#o_step_solution_content').on('hide.bs.collapse', function () { + o_ffXHRNFEvent('$r.commandURIbg("hide")?step=solution'); + }).on('show.bs.collapse', function () { + o_ffXHRNFEvent('$r.commandURIbg("show")?step=solution'); + }) + /* ]]> */</script> #end #if($gradingEnabled) <div class="o_step $gradingCssClass"> <div class="o_bar"></div> - <h4 class="o_title">$r.translate("run.grading")</h4> - <div class="o_content"> + <h4 class="o_title"> <a href="#o_step_grading_content" data-toggle="collapse" aria-expanded="$collapse_grading">$r.translate("run.grading")</a></h4> + <div id="o_step_grading_content" class="o_content collpase #if($collapse_grading) in #end" aria-expanded="$collapse_grading"> #if($r.available("grading")) $r.render("grading") #end + </div> #if($userLog || $groupLog) <div class="o_box"> #o_togglebox_start("o_course_run_log" $r.translate("log.title")) @@ -110,8 +146,14 @@ $r.render("contextualSubscription") #o_togglebox_end() </div> #end - </div> </div> + <script type='text/javascript'>/* <![CDATA[ */ + jQuery('#o_step_grading_content').on('hide.bs.collapse', function () { + o_ffXHRNFEvent('$r.commandURIbg("hide")?step=grading'); + }).on('show.bs.collapse', function () { + o_ffXHRNFEvent('$r.commandURIbg("show")?step=grading'); + }) + /* ]]> */</script> #end </div> \ No newline at end of file diff --git a/src/main/java/org/olat/course/nodes/gta/ui/_content/run.html b/src/main/java/org/olat/course/nodes/gta/ui/_content/run.html index c1b4893114003d231782f78f7744e34184349da6..886bc73dd8ef0dd6146ed86ce21a92e7776efefb 100644 --- a/src/main/java/org/olat/course/nodes/gta/ui/_content/run.html +++ b/src/main/java/org/olat/course/nodes/gta/ui/_content/run.html @@ -1,5 +1,6 @@ -$r.render("contextualSubscription") -<div class="o_block">[ugly]</div> +<div class="clearfix"> + $r.render("contextualSubscription") +</div> #if($noGroupError) <div class="o_error" role="alert">$r.translate("error.no.group")</div> @@ -18,16 +19,19 @@ $r.render("contextualSubscription") #end </div> #end +<div class="o_block"> +$collapse_assignement $collapse_submit $collapse_reviewAndCorrection $collapse_revision $collapse_solution $collapse_grading +</div> <div class="o_process"> #if($assignmentEnabled) - <div class="o_step $assignmentCssClass"> + <div id="o_step_assignement" class="o_step $assignmentCssClass"> <div class="o_bar"></div> - <h4 class="o_title">$r.translate("run.assignment.title")</h4> + <h4 class="o_title" > <a href="#o_step_assignement_content" data-toggle="collapse" aria-expanded="$collapse_assignement">$r.translate("run.assignment.title")</a></h4> #if($assignmentDueDate) <div class="o_meta">$r.translate("run.assignment.due.date", $assignmentDueDate)</div> #end - <div class="o_content"> + <div id="o_step_assignement_content" class="o_content collapse #if($collapse_assignement) in #end" aria-expanded="$collapse_assignement"> #if($assignmentClosed) <div class="o_error">$r.translate("error.assignment.closed")</div> #end @@ -41,16 +45,23 @@ $r.render("contextualSubscription") #end </div> </div> + <script type='text/javascript'>/* <![CDATA[ */ + jQuery('#o_step_assignement_content').on('hide.bs.collapse', function () { + o_ffXHRNFEvent('$r.commandURIbg("hide")?step=assignment'); + }).on('show.bs.collapse', function () { + o_ffXHRNFEvent('$r.commandURIbg("show")?step=assignment'); + }) + /* ]]> */</script> #end #if($submitEnabled) <div class="o_step $submitCssClass"> <div class="o_bar"></div> - <h4 class="o_title">$r.translate("run.submit")</h4> + <h4 class="o_title"> <a href="#o_step_submit_content" data-toggle="collapse" aria-expanded="$collapse_submit">$r.translate("run.submit")</a></h4> #if($submitDueDate) <div class="o_meta">$r.translate("run.submit.due.date", $submitDueDate)</div> #end - <div class="o_content"> + <div id="o_step_submit_content" class="o_content collapse #if($collapse_submit) in #end" aria-expanded="$collapse_submit"> #if($r.available("submitDocs")) <p>$r.translate("run.submit.individual.description.${sumbitWay}")</p> #if($groupWarning) @@ -66,13 +77,20 @@ $r.render("contextualSubscription") #end </div> </div> + <script type='text/javascript'>/* <![CDATA[ */ + jQuery('#o_step_submit_content').on('hide.bs.collapse', function () { + o_ffXHRNFEvent('$r.commandURIbg("hide")?step=submit'); + }).on('show.bs.collapse', function () { + o_ffXHRNFEvent('$r.commandURIbg("show")?step=submit'); + }) + /* ]]> */</script> #end #if($reviewAndCorrectionEnabled) <div class="o_step $reviewCssClass"> <div class="o_bar"></div> - <h4 class="o_title">$r.translate("run.review")</h4> - <div class="o_content"> + <h4 class="o_title"> <a href="#o_step_review_content" data-toggle="collapse" aria-expanded="$collapse_reviewAndCorrection">$r.translate("run.review")</a></h4> + <div id="o_step_review_content" class="o_content collapse #if($collapse_reviewAndCorrection) in #end" aria-expanded="$collapse_reviewAndCorrection"> #if($reviewMessage && !$reviewMessage.isEmpty()) <p>$r.escapeHtml($reviewMessage)</p> #end @@ -81,45 +99,88 @@ $r.render("contextualSubscription") #end </div> </div> + <script type='text/javascript'>/* <![CDATA[ */ + jQuery('#o_step_review_content').on('hide.bs.collapse', function () { + o_ffXHRNFEvent('$r.commandURIbg("hide")?step=reviewAndCorrection'); + }).on('show.bs.collapse', function () { + o_ffXHRNFEvent('$r.commandURIbg("show")?step=reviewAndCorrection'); + }) + /* ]]> */</script> #end #if($revisionEnabled) <div class="o_step $revisionCssClass"> <div class="o_bar"></div> - <h4 class="o_title">$r.translate("run.revision")</h4> - <div class="o_content"> + <h4 class="o_title"> <a href="#o_step_revision_content" data-toggle="collapse" aria-expanded="$collapse_revision">$r.translate("run.revision")</a></h4> + <div id="o_step_revision_content" class="o_content collapse #if($collapse_revision) in #end" aria-expanded="$collapse_revision"> #if($r.available("revisionDocs")) $r.render("revisionDocs") #end </div> </div> + <script type='text/javascript'>/* <![CDATA[ */ + jQuery('#o_step_revision_content').on('hide.bs.collapse', function () { + o_ffXHRNFEvent('$r.commandURIbg("hide")?step=revision'); + }).on('show.bs.collapse', function () { + o_ffXHRNFEvent('$r.commandURIbg("show")?step=revision'); + }) + /* ]]> */</script> #end #if($solutionEnabled) <div class="o_step $solutionCssClass"> <div class="o_bar"></div> - <h4 class="o_title">$r.translate("run.solution")</h4> + <h4 class="o_title"> <a href="#o_step_solution_content" data-toggle="collapse" aria-expanded="$collapse_solution">$r.translate("run.solution")</a></h4> #if($solutionAvailableDate) <div class="o_meta">$r.translate("run.solution.available.date", $solutionAvailableDate)</div> #end - <div class="o_content"> + <div id="o_step_solution_content" class="o_content collapse #if($collapse_solution) in #end" aria-expanded="$collapse_solution"> #if($r.available("solutions")) $r.render("solutions") #end </div> </div> + <script type='text/javascript'>/* <![CDATA[ */ + jQuery('#o_step_solution_content').on('hide.bs.collapse', function () { + o_ffXHRNFEvent('$r.commandURIbg("hide")?step=solution'); + }).on('show.bs.collapse', function () { + o_ffXHRNFEvent('$r.commandURIbg("show")?step=solution'); + }) + /* ]]> */</script> #end #if($gradingEnabled) <div class="o_step $gradingCssClass"> <div class="o_bar"></div> - <h4 class="o_title">$r.translate("run.grading")</h4> - <div class="o_content"> + <h4 class="o_title"> <a href="#o_step_grading_content" data-toggle="collapse" aria-expanded="$collapse_grading">$r.translate("run.grading")</a></h4> + <div id="o_step_grading_content" class="o_content collpase #if($collapse_grading) in #end" aria-expanded="$collapse_grading"> + #if($gradingInfoTextUser && !$gradingInfoTextUser.isEmpty()) + <div class="panel panel-default o_disclaimer"> + <div class="panel-heading" data-toggle="collapse" data-target="#collapseDisclaimer"> + <h4 class="panel-title"> + <i id="collapseDisclaimerToggler" class="o_icon o_icon-fw o_icon_close_togglebox"> </i> + $r.translate("info.title") + </h4> + </div> + <div id="collapseDisclaimer" class="panel-collapse collapse in"><div class="panel-body"> + $r.formatLatexFormulas($gradingInfoTextUser) + </div></div> + </div> + <script type="text/javascript"> + /* <![CDATA[ */ + jQuery('#collapseDisclaimer').on('hide.bs.collapse', function () { + jQuery('#collapseDisclaimerToggler').removeClass('o_icon_close_togglebox').addClass('o_icon_open_togglebox'); + }) + jQuery('#collapseDisclaimer').on('show.bs.collapse', function () { + jQuery('#collapseDisclaimerToggler').removeClass('o_icon_open_togglebox').addClass('o_icon_close_togglebox'); + }) + /* ]]> */ + </script> + #end #if($r.available("grading")) $r.render("grading") #end </div> - #if($userLog || $groupLog) <div class="o_box"> #o_togglebox_start("o_course_run_log" $r.translate("log.title")) @@ -133,5 +194,12 @@ $r.render("contextualSubscription") </div> #end </div> + <script type='text/javascript'>/* <![CDATA[ */ + jQuery('#o_step_grading_content').on('hide.bs.collapse', function () { + o_ffXHRNFEvent('$r.commandURIbg("hide")?step=grading'); + }).on('show.bs.collapse', function () { + o_ffXHRNFEvent('$r.commandURIbg("show")?step=grading'); + }) + /* ]]> */</script> #end </div> \ No newline at end of file diff --git a/src/main/java/org/olat/course/nodes/gta/ui/_content/solution_relative_date.html b/src/main/java/org/olat/course/nodes/gta/ui/_content/solution_relative_date.html new file mode 100644 index 0000000000000000000000000000000000000000..0e6f0ac5a33db349489cc823976f1827e10d1d3a --- /dev/null +++ b/src/main/java/org/olat/course/nodes/gta/ui/_content/solution_relative_date.html @@ -0,0 +1,6 @@ +<div class="form-inline">$r.render("solution.numOfDays") <span class="form-control-static">$r.translate("days.after")</span> $r.render("solution.relative.to")</div> +#if($f.hasError("solution.numOfDays")) + <div class="form-inline"> + $r.render("solution.numOfDays_ERROR") + </div> +#end \ No newline at end of file diff --git a/src/main/java/org/olat/course/nodes/gta/ui/_content/submit_relative_date.html b/src/main/java/org/olat/course/nodes/gta/ui/_content/submit_relative_date.html new file mode 100644 index 0000000000000000000000000000000000000000..e190568d69f4ef1a172ff311f52e92cdf04213c5 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/gta/ui/_content/submit_relative_date.html @@ -0,0 +1,6 @@ +<div class="form-inline">$r.render("submit.numOfDays") <span class="form-control-static">$r.translate("days.after")</span> $r.render("submit.relative.to")</div> +#if($f.hasError("submit.numOfDays")) + <div class="form-inline"> + $r.render("submit.numOfDays_ERROR") + </div> +#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 b4020e714f626fbefa6a050e2af43bbc9f96f6e8..baf1a7fb5d8d634f327274ee315673d50c6b2ba3 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 @@ -1,10 +1,11 @@ #Tue Mar 31 14:20:51 CEST 2015 -add.solution=Musterl\u00F6sungen hochladen +add.solution=Musterl\u00F6sung hochladen add.task=Aufgabe hinzuf\u00FCgen assessment.group.tool=Gruppe bewerten assignment.config.title=Aufgabe zuweisen assignment.deadline=Zuweisungstermin before=Vor +bulk.download.title=Alle abgegebene Dokumenten herunterladen bulk.review=Beurteilung herunterladen bulk.solutions=Musterl\u00F6sungen herunterladen bulk.submitted.documents=Abgegebene Dokumente @@ -18,7 +19,7 @@ coach.assessment=Bewerten coach.close.revision.button=Revisionsprozess schliessen coach.corrections.description=Sie haben folgende Korrekturen zur\u00FCckgegeben. coach.documents.successfully.reviewed=Begutachtet\! -coach.need.revision.button=Ben\u00F6tigt \u00FCberarbeitung +coach.need.revision.button=Ben\u00F6tigt \u00DCberarbeitung coach.reviewed.button=Begutachtet coach.revisions.description=Sie haben die folgenden Dokumente \u00FCberarbeitet. coach.submit.corrections.to.revision.button=Korrekturen absenden @@ -32,6 +33,7 @@ confirm.delete.solution.title=L\u00F6sung l\u00F6schen confirmation.title=Abgabe Best\u00E4tigung create.areas=Lernbereich erstellen create.groups=Gruppe erstellen +days.after=Tage nach document=Abgegebene Dokumente document.date=Datum download.task=Aufgabe herunterladen @@ -42,11 +44,11 @@ embedded.editor=Abgabe mit externem Editor (PDF) enabled=eingeschaltet error.assignment.closed=Zuweisung ist geschlossen. error.duplicate.coaching=Der Teilnehmer ist Teilnehmer von mehreren Gruppen f\u00FCr diese Aufgabe. -error.duplicate.memberships=Die folgende Teilnehmer sind Mitglieder von mehereren Gruppen\: {1} +error.duplicate.memberships=Die folgenden Teilnehmer sind Mitglieder von mehreren Gruppen\: {1} error.editor.atLeastOne=Sie m\u00FCssen mindestens einen Editortyp w\u00E4hlen. error.max.documents=Sie d\u00FCrfen nicht mehr als <b>{0}</b> Dokument(e) abgeben. Aber sie k\u00F6nnen noch ein Dokument austauschen oder editieren. error.missing.group=Sie haben noch keine Gruppe gew\u00E4hlt -error.missing.score.config=Fehlende Punkte konfiguration +error.missing.score.config=Fehlende Bewertungskonfiguration error.missing.solutions=Sie haben noch keine L\u00F6sungen hochgeladen error.missing.tasks=Sie haben noch keine Aufgabe erstellt error.no.group=Sie sind in keiner Gruppe @@ -61,6 +63,7 @@ group.apply.toall=F\u00FCr die ganze Gruppe group.passed=Gruppe bestanden/nicht bestanden group.score=Gruppe Punkte group.title=Leistungs\u00FCbersicht +info.title=$org.olat.course.nodes.ms\:info.title log.title=\u00C4nderungsverlauf mail.confirm.assignment.body=Zuweisung war erfolgreich mail.confirm.assignment.subject=Zuweisung @@ -83,6 +86,10 @@ pane.tab.workflow=Workflow preview=$org.olat.course.nodes.ta\:form.task.preview preview.disabled=$org.olat.course.nodes.ta\:form.task.without.preview preview.enabled=$org.olat.course.nodes.ta\:form.task.with.preview +relative.dates=Relative Datum +relative.to.course.start=Kurs Beginn +relative.to.course.launch=Kurs erstes Besuch +relative.to.enrollment=Enrollment replace=Austauschen replace.document=Dokument austauschen review.and.correction=\u00DCberarbeitung und Korrektur @@ -95,26 +102,28 @@ run.coach=Korrigieren run.corrections.description=Ihr Betreuer hat folgende Dokumente f\u00FCr Sie hinzugef\u00FCgt\: run.documents.successfully.submitted=Ihr(e) Dokument(e) wurden erfolgreich eingereicht. run.grading=Bewertung -run.pick.task.description=Bitte, w\u00E4hlen Sie eine Aufgabe aus. +run.pick.task.description=Bitte w\u00E4hlen Sie eine Aufgabe aus. run.review=Begutachtung und Korrektur run.review.closed.without.documents=Ihr Betreuer hat die eingereichten Dokumente begutachtet. run.review.description=Ihr Betreuer wird nun die eingereichten Dokumente begutachten. Sie werden benachrichtigt sobald eine begutachtete oder korrigierte Version verf\u00FCgbar ist. run.review.waiting=Ihr Betreuer begutachtet nun die eingereichten Dokumente. Sie werden benachrichtigt sobald eine begutachtete oder korrigierte Version verf\u00FCgbar ist. run.revised.description=Die folgenden \u00FCberarbeiteten Dokumente wurden von Ihnen eingereicht\: run.revision=\u00DCberarbeitungsperiode -run.revision.period.description=Your coach added a revision period. Create or upload a revised document. +run.revision.period.description=Ihr Betreuer hat eine Revisionsperiode hinzugef\u00FCgt. Erstellen Sie ein \u00FCberarbeitetes Dokument oder laden Sie eines hoch. run.run=Aufgabe run.solution=L\u00F6sungen run.solution.available.date={0} -run.solutions.description=Hier finden Sie Musterl\u00F6sungen, die Ihr Betreuer Ihnen zur Verf\u00FCgung stellt. +run.solutions.description=Hier finden Sie die von Ihrem Betreuer zur Verf\u00FCgung gestellten Musterl\u00F6sungen. run.submit=Abgabe -run.submit.button=Abgeben +run.submit.button=Endg\u00FCltige Abgabe +run.submit.confirm=Das Abgabe von dem Aufgabe ist endg\u00FCltig. +run.submit.confirm.group=$\:run.submit.confirm<br/>Dies ist eine Gruppenaufgabe\! Die hier getroffene Auswahl ist f\u00FCr alle Mitglieder der Gruppe "{0}" g\u00FCltig\! run.submit.due.date=Abgabe Termin\: {0} -run.submit.individual.description.all=Turn in your solution by either uploading a document you created on your computer or use the editor to write your solution right here. -run.submit.individual.description.editor=Turn in your solution by using the editor to write your solution right here. -run.submit.individual.description.upload=Turn in your solution by uploading a document you created on your computer. -run.submit.revision.button=Revision abgeben -run.submitted.description=The following solutions have been turned in by you\: +run.submit.individual.description.all=Geben Sie Ihre L\u00F6sung ab indem Sie entweder ein Dokument hochladen oder den hier verf\u00FCgbaren Editor benutzen. +run.submit.individual.description.editor=Schreiben Sie Ihre L\u00F6sung direkt im Editor. +run.submit.individual.description.upload=Laden Sie Ihre L\u00F6sung als fertiges Dokument hoch. +run.submit.revision.button=\u00DCberarbeitung abgeben +run.submitted.description=Die folgenden L\u00F6sungen wurden von Ihnen abgegeben\: sample.solution=Musterl\u00F6sungen sample.solution.enabled=eingeschaltet sample.solution.visible.after=Sichtbar nach @@ -123,24 +132,24 @@ sampling.reuse=Aufgabe wird mehreren Benutzern / Gruppe zugewiesen sampling.unique=Aufgabe wird genau einem Benutzer / Gruppe zugewiesen selected.group=Die Gruppe f\u00FCr diese Aufgabe ist\: <i class\="o_icon o_icon_group"> </i> "{0}" solution.file=Datei -solution.list.description=Select "Add solution" to add a solution or "Edit" to modify an existing solution. Note that solutions are not assigned to a particular task. +solution.list.description=W\u00E4hlen Sie "Musterl\u00F6sung hochladen" um eine Musterl\u00F6sung hinzuzuf\u00FCgen, oder "Editieren" um eine bestehende Musterl\u00F6sung zu bearbeiten. Bitte beachten Sie dass Musterl\u00F6sungen keiner spezifischen Aufgabe zugewiesen werden. solution.list.title=Musterl\u00F6sungen hochladen solution.title=Titel submission=Abgabe submission.confirmation=Hiermit wird best\u00E4tigt, dass $first $last ($email) die Datei "$filename" am $date um $time hochgeladen hat. -submission.email.confirmation=Send text additionally as email +submission.email.confirmation=Diesen Text zus\u00E4tzlich als E-Mail versenden. submission.enabled=eingeschaltet submission.mail.subject=OpenOLAT-Best\u00E4tigungs-E-Mail -submission.text=Text after handling in +submission.text=Text nach erfolgter Abgabe submit.deadline=Abgabetermin table.header.details.gta=$org.olat.course.nodes.ta\:table.header.details.ta table.header.group.name=Gruppe -table.header.passed=Passed +table.header.passed=Bestanden table.header.score=Punkte task.alreadyChosen=$org.olat.course.nodes.ta\:task.chosen -task.assigned.description=The following task has been assigned to you\: +task.assigned.description=Die folgende Aufgabe wurde Ihnen zugewiesen\: task.assignment=Zuweisung -task.assignment.error=Ein unerwartete Fehler ist aufgetreten\! +task.assignment.error=Ein unerwarteter Fehler ist aufgetreten\! task.assignment.type=$org.olat.course.nodes.ta\:form.task.type task.assignment.type.auto=$org.olat.course.nodes.ta\:form.task.type.auto task.assignment.type.manual=$org.olat.course.nodes.ta\:form.task.type.manual @@ -151,14 +160,14 @@ task.execution.individual=Individuell task.file=Datei task.list.description=Ausw\u00E4hlen... task.list.title=Aufgaben -task.steps.description=W\u00E4hlen Sie welche Elemente in der Aufgabe Workflow aktiviert sind und optional F\u00E4lligkeitsdatum f\u00FCr die Workflow-Management eingestellt sind. -task.steps.title=Workflow Schritte +task.steps.description=W\u00E4hlen Sie welche Elemente f\u00FCr den Workflow aktiviert werden sollen und geben Sie optional die dazugeh\u00F6rigen F\u00E4lligkeitsdaten f\u00FCr das Workflow-Management ein. +task.steps.title=Abschnitte task.successfully.assigned=Aufgabe erfolgreich zugwiesen. task.text=$org.olat.course.nodes.ta\:form.task.text task.title=Titel -task.type.description=Wenn als Gruppenaufgabe aktiviert ist, werden alle Schritte des Workflows als Gruppe und nicht als Einzelperson ausgef\u00FChrt. -task.type.title=Aufgabetyp +task.type.description=Wenn die Gruppenaufgabe aktiviert ist, werden alle Abschnitte des Workflows als Gruppe und nicht als Einzelperson ausgef\u00FChrt. +task.type.title=Aufgabentyp upload.document=Dokument hochladen -warning.group.pick.task=This is a group task\! The selection made here is valid for all members of the group "{0}"\! Make sur you discussed this selection within the group prior to select a task\! Only one member of the group can select the task for the group. -warning.group.submit=This is a group task\! The submitted document is valid for all members of the group "{0}"\! Make sure you discussed this solution document prior to upload it here\! Only one member of the group can submit a solution on behalf of all group members. -warning.group.task=This is a group task\! The task assignment, submit of documents and grading is performed as a group. Contact your group peers how to proceed to decide for a task and to collaboratively solve the task. +warning.group.pick.task=Dies ist eine Gruppenaufgabe\! Die hier getroffene Auswahl ist f\u00FCr alle Mitglieder der Gruppe "{0}" g\u00FCltig\! Stellen Sie sicher dass diese Auswahl zuvor innerhalb Ihrer Gruppe diskutiert wurde\! Nur ein Gruppenmitglied kann die Gruppenaufgabe ausw\u00E4len. +warning.group.submit=Dies ist eine Gruppenaufgabe\! Das abgegebene Dokument ist f\u00FCr alle Mitglieder der Gruppe "{0}" g\u00FCltig\! Stellen Sie sicher dass diese L\u00F6sung zuvor innerhalb Ihrer Gruppe diskutiert wurde\! Nur ein Gruppenmitglied kann eine L\u00F6sung im Namen aller Gruppenmitglieder abgeben. +warning.group.task=Dies ist eine Gruppenaufgabe\! Aufgbabenzuweisung, Abgabe sowie die Bewertung werden f\u00FCr die Gruppe vorgenommen. Besprechen Sie mit den anderen Teilnehmern ihrer Gruppe wie sie gemeinsam die Aufgabe ausw\u00E4hlen und bearbeiten wollen. 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 a5cc5c5c225e45bc72056336c7507afb0797be15..ecb811532129fb2027dc0cc7893d6d00d679001f 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 @@ -1,10 +1,11 @@ #Tue Mar 31 14:19:30 CEST 2015 add.solution=Add solution add.task=Add task -assessment.group.tool=Grade Group +assessment.group.tool=Grade group assignment.config.title=Task assignment configuration assignment.deadline=Assignment deadline before=Before +bulk.download.title=Download all submitted files bulk.review=Download review bulk.solutions=Download solutions bulk.submitted.documents=Download submitted documents @@ -16,12 +17,12 @@ choosed.areas=Areas choosed.groups=Groups coach.assessment=Grade coach.close.revision.button=Close revision process -coach.corrections.description=You have returned following corrections +coach.corrections.description=You have returned the following revisions coach.documents.successfully.reviewed=Reviewed\! coach.need.revision.button=Needs revision coach.reviewed.button=Reviewed coach.revisions.description=You have reviewed the following documents -coach.submit.corrections.to.revision.button=Send corrections +coach.submit.corrections.to.revision.button=Send revisions coach.submitted.documents.description=These documents have been submitted coach.task.assigned.description=The following task has been assigned coach.waiting.assignment=Waiting for assignment @@ -30,8 +31,9 @@ condition.accessibility.title=Access confirm.delete.solution.description=Do you want to delete the solution "{0}"? confirm.delete.solution.title=Delete submitted solution confirmation.title=Submit confirmation -create.areas=Create area +create.areas=Create learning area create.groups=Create group +days.after=Days after document=Submitted documents document.date=Date download.task=Download task @@ -41,10 +43,10 @@ editor.title=Submission configuration embedded.editor=Submit files created with external editor (e.g. PDF) enabled=enabled error.assignment.closed=Assignment is closed -error.duplicate.coaching=This user is member of multiple groups in this task. -error.duplicate.memberships=The following users are member of multiple groups\: {1} +error.duplicate.coaching=This user is a member of multiple groups in this task. +error.duplicate.memberships=The following users are members of multiple groups\: {1} error.editor.atLeastOne=You must choose at least one kind of editor. -error.max.documents=You are not allowd to submit more than <b>{0}</b> Documents. You are able to edit or change a document. +error.max.documents=You are not allowed to submit more than <b>{0}</b> Documents. You may edit or change a document. error.missing.group=You have not selected a group yet error.missing.score.config=Missing score configuration error.missing.solutions=You have not submitted any solutions yet @@ -61,17 +63,18 @@ group.apply.toall=For the whole group group.passed=Groups passed/failed group.score=Group score group.title=Score summary +info.title=$org.olat.course.nodes.ms\:info.title log.title=Change log -mail.confirm.assignment.body=Assignment was succesful +mail.confirm.assignment.body=Assignment was successful mail.confirm.assignment.subject=Assignment max.documents=Max. number of documents notifications.correction=New correction "{0}" of "{1}" -notifications.header=Grouptask in course "{0}" +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=Neue submitted documents "{0}" of "{1}" +notifications.submission.individual=New submitted documents "{0}" of "{1}" open.editor=Open solution editor open.group=Open group pane.tab.accessibility=Access @@ -83,6 +86,10 @@ pane.tab.workflow=Workflow preview=$org.olat.course.nodes.ta\:form.task.preview preview.disabled=$org.olat.course.nodes.ta\:form.task.without.preview preview.enabled=$org.olat.course.nodes.ta\:form.task.with.preview +relative.dates=Relative dates +relative.to.course.start=Course begin +relative.to.course.launch=Course first launch +relative.to.enrollment=Enrollment replace=Replace replace.document=Replace document review.and.correction=Review and correction @@ -99,7 +106,7 @@ run.pick.task.description=Please select a task from the list below. run.review=Review and correction run.review.closed.without.documents=Your coach has reviewed the submitted documents. run.review.description=Your coach is now reviewing your submitted documents. You will get notified when a reviewed or corrected version is available. -run.review.waiting=Your coach is now reviewing the turned in documents. You will get notified when a reviewed or corrected version is available. +run.review.waiting=Your coach is now reviewing the submitted documents. You will get notified when a reviewed or corrected version is available. run.revised.description=The following revised documents have been submitted by you\: run.revision=Revision period run.revision.period.description=Your coach added a revision period. Create or upload a revised document. @@ -108,13 +115,15 @@ run.solution=Sample solution run.solution.available.date={0} run.solutions.description=The following sample solutions are available for download\: run.submit=Submit -run.submit.button=Submit +run.submit.button=Final task submission +run.submit.confirm=The submission of the task is final. +run.submit.confirm.group=$\:run.submit.confirm<br/>This is a group task! The submitted document is valid for all members of the group "{0}"\! run.submit.due.date=Due date\: {0} -run.submit.individual.description.all=Turn in your solution by either uploading a document you created on your computer or use the editor to write your solution right here. -run.submit.individual.description.editor=Turn in your solution by using the editor to write your solution right here. -run.submit.individual.description.upload=Turn in your solution by uploading a document you created on your computer. -run.submit.revision.button=Submit revisions -run.submitted.description=The following solutions have been turned in by you\: +run.submit.individual.description.all=Submit your solution by either uploading a document you created on your computer or use the editor to write your solution right here. +run.submit.individual.description.editor=Submit your solution by using the editor to write your solution right here. +run.submit.individual.description.upload=Submit your solution by uploading a document you created on your computer. +run.submit.revision.button=Submit revision +run.submitted.description=The following solutions have been submitted by you\: sample.solution=Sample solution sample.solution.enabled=enabled sample.solution.visible.after=Visible after @@ -123,7 +132,7 @@ sampling.reuse=Your task will be assigned to more than one user / group sampling.unique=Your task will be assigned to only one single user / group selected.group=The group for this task is\: <i class\="o_icon o_icon_group"> </i> "{0}" solution.file=File -solution.list.description=Select "Add solution" to add a solution or "Edit" to modify an existing solution. Note that solutions are not assigned to a particular task. +solution.list.description=Select "Add solution" to add a solution or "Edit" to modify an existing solution. Please note that solutions are not assigned to a particular task. solution.list.title=Upload sample solutions solution.title=Title submission=Submission @@ -131,7 +140,7 @@ submission.confirmation=The submission of the file "$filename" for $first $last submission.email.confirmation=Send text additionally as email submission.enabled=drop box enabled submission.mail.subject=OpenOLAT-confirmation-E-Mail -submission.text=Text after handling in +submission.text=Text after handing in submit.deadline=Submission deadline table.header.details.gta=$org.olat.course.nodes.ta\:table.header.details.ta table.header.group.name=Group @@ -151,14 +160,14 @@ task.execution.individual=individual task.file=File task.list.description=Select "Add task" to add a new task or "Edit" to modify an existing task. If all users work on the same task, create a single task. task.list.title=Tasks -task.steps.description=Select which elements in the task workflow are enabled and set optional due date for the workflow management. +task.steps.description=Select which elements in the task workflow are enabled and set optional due dates for the workflow management. task.steps.title=Workflow steps -task.successfully.assigned=The task is successfully assigned to you. +task.successfully.assigned=The task was successfully assigned to you. task.text=$org.olat.course.nodes.ta\:form.task.text task.title=Title task.type.description=When group task is enabled, all steps of the workflow are executed as a group and not as an individual. task.type.title=Task type upload.document=Upload document -warning.group.pick.task=This is a group task\! The selection made here is valid for all members of the group "{0}"\! Make sure you discussed this selection within the group prior to select a task\! Only one member of the group can select the task for the group. -warning.group.submit=This is a group task\! The submitted document is valid for all members of the group "{0}"\! Make sure you discussed this solution document prior to upload it here\! Only one member of the group can submit a solution on behalf of all group members. -warning.group.task=This is a group task\! The task assignment, submit of documents and grading is performed as a group. Contact your group peers how to proceed to decide for a task and to collaboratively solve the task. +warning.group.pick.task=This is a group task\! The selection made here is valid for all members of the group "{0}"\! Make sure you discussed this selection within the group prior to selecting a task\! Only one member of the group can select the task for the group. +warning.group.submit=This is a group task\! The submitted document is valid for all members of the group "{0}"\! Make sure you discussed this solution document prior to uploading it here\! Only one member of the group can submit a solution on behalf of all group members. +warning.group.task=This is a group task\! The task assignment, submission of documents and grading are performed as a group. Contact your group peers on how to proceed on deciding for a task and to collaboratively solve the task. diff --git a/src/main/java/org/olat/course/nodes/ta/BulkDownloadToolController.java b/src/main/java/org/olat/course/nodes/ta/BulkDownloadToolController.java index f5b6c7678838c6a09364add7f3a47dbfd6d3ac50..44aad7023de334df682f25838fa60a316df2d5c4 100644 --- a/src/main/java/org/olat/course/nodes/ta/BulkDownloadToolController.java +++ b/src/main/java/org/olat/course/nodes/ta/BulkDownloadToolController.java @@ -28,8 +28,8 @@ import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.controller.BasicController; import org.olat.course.archiver.ArchiveResource; import org.olat.course.nodes.ArchiveOptions; -import org.olat.course.nodes.AssessableCourseNode; import org.olat.course.nodes.AssessmentToolOptions; +import org.olat.course.nodes.TACourseNode; import org.olat.course.run.environment.CourseEnvironment; import org.olat.resource.OLATResource; @@ -44,10 +44,10 @@ public class BulkDownloadToolController extends BasicController { private final ArchiveOptions options; private final OLATResource courseOres; - private final AssessableCourseNode courseNode; + private final TACourseNode courseNode; public BulkDownloadToolController(UserRequest ureq, WindowControl wControl, CourseEnvironment courseEnv, - AssessmentToolOptions asOptions, AssessableCourseNode courseNode) { + AssessmentToolOptions asOptions, TACourseNode courseNode) { super(ureq, wControl); this.options = new ArchiveOptions(); this.options.setGroup(asOptions.getGroup()); diff --git a/src/main/java/org/olat/course/reminder/ui/CourseReminderEditController.java b/src/main/java/org/olat/course/reminder/ui/CourseReminderEditController.java index 8d2195a118ecfdbff2cd3d53e4ce6f8a1fafa8e7..044a9736112fde16757dab38e39eb907c21807ad 100644 --- a/src/main/java/org/olat/course/reminder/ui/CourseReminderEditController.java +++ b/src/main/java/org/olat/course/reminder/ui/CourseReminderEditController.java @@ -101,7 +101,7 @@ public class CourseReminderEditController extends FormBasicController { @Override protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { - FormLayoutContainer generalCont = FormLayoutContainer.createDefaultFormLayout("general", getTranslator()); + FormLayoutContainer generalCont = FormLayoutContainer.createVerticalFormLayout("general", getTranslator()); generalCont.setRootForm(mainForm); formLayout.add(generalCont); @@ -111,6 +111,9 @@ public class CourseReminderEditController extends FormBasicController { String desc = reminder.getDescription(); descriptionEl = uifactory.addTextElement("reminder.description", "reminder.description", 128, desc, generalCont); + String sendTime = getSendTimeDescription(); + uifactory.addStaticTextElement("send.time.description.label", sendTime, generalCont); + //rules String rulePage = velocity_root + "/edit_rules.html"; rulesCont = FormLayoutContainer.createCustomFormLayout("rules", getTranslator(), rulePage); @@ -137,20 +140,28 @@ public class CourseReminderEditController extends FormBasicController { } //email content - FormLayoutContainer contentCont = FormLayoutContainer.createDefaultFormLayout("contents", getTranslator()); + FormLayoutContainer contentCont = FormLayoutContainer.createVerticalFormLayout("contents", getTranslator()); contentCont.setRootForm(mainForm); formLayout.add(contentCont); String emailContent = reminder == null ? "" : reminder.getEmailBody(); emailEl = uifactory.addRichTextElementForStringDataMinimalistic("email.content", "email.content", emailContent, 10, 60, contentCont, getWindowControl()); - FormLayoutContainer buttonLayout = FormLayoutContainer.createButtonLayout("buttons", getTranslator()); + String buttonPage = velocity_root + "/edit_rules_buttons.html"; + FormLayoutContainer buttonLayout = FormLayoutContainer.createCustomFormLayout("buttons", getTranslator(), buttonPage); buttonLayout.setRootForm(mainForm); - contentCont.add(buttonLayout); + formLayout.add(buttonLayout); uifactory.addFormSubmitButton("save", buttonLayout); uifactory.addFormCancelButton("cancel", buttonLayout, ureq, getWindowControl()); } + protected String getSendTimeDescription() { + String interval = reminderModule.getInterval(); + String desc = translate("interval." + interval); + String time = reminderModule.getDefaultSendTime(); + return translate("send.time.description", new String[] { desc, time} ); + } + protected RuleElement initRuleForm(UserRequest ureq, RuleSPI ruleSpi, ReminderRule rule) { String id = Integer.toString(counter++); String type = ruleSpi.getClass().getSimpleName(); diff --git a/src/main/java/org/olat/course/reminder/ui/_content/edit_rules_buttons.html b/src/main/java/org/olat/course/reminder/ui/_content/edit_rules_buttons.html new file mode 100644 index 0000000000000000000000000000000000000000..8c7c295ee00c713da68de7fc7a2cf4a38bce9dab --- /dev/null +++ b/src/main/java/org/olat/course/reminder/ui/_content/edit_rules_buttons.html @@ -0,0 +1,4 @@ +<div class="o_block o_button_group"> + $r.render("save") + $r.render("cancel") +</div> \ No newline at end of file diff --git a/src/main/java/org/olat/course/reminder/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/reminder/ui/_i18n/LocalStrings_de.properties index 62ec93e6b9ed03ee2e994166640375d60231aeca..43be3091d38820d6da0e12a67983da1811fcc85d 100644 --- a/src/main/java/org/olat/course/reminder/ui/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/course/reminder/ui/_i18n/LocalStrings_de.properties @@ -34,6 +34,8 @@ rule.submission.task=Grupenaufgabe Dokumente abgeben rules.description=Wenn folgende Bedingungen erf\u00FCllt sind send=Erinnerung jetzt schicken send.reminder=Geschickte Erinnerungen +send.time.description={0} um {1} +send.time.description.label=Intervall show.sent=Geschickte Erinnerungen zeigen table.header.actions=<i class\='o_icon o_icon_actions o_icon-lg'> </i> table.header.creationDate=Erstellt am diff --git a/src/main/java/org/olat/course/reminder/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/reminder/ui/_i18n/LocalStrings_en.properties index 1cc150f0a03ffbf1c02d2ce6834a2df767179d1c..b7f032d2fe2054898a26d459ae289849b6eeaaf4 100644 --- a/src/main/java/org/olat/course/reminder/ui/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/course/reminder/ui/_i18n/LocalStrings_en.properties @@ -34,6 +34,8 @@ rule.submission.task=Group task documents submission rules.description=When matching all of the following conditions send=Send reminders now send.reminder=Send reminders +send.time.description={0} at {1} +send.time.description.label=Interval show.sent=Show sent reminders table.header.actions=<i class\='o_icon o_icon_actions o_icon-lg'> </i> table.header.creationDate=Creation date diff --git a/src/main/java/org/olat/group/ui/main/BusinessGroupSearchController.java b/src/main/java/org/olat/group/ui/main/BusinessGroupSearchController.java index cd2b1a919d73e21af3ab1e5fe0af34c1ba0a7486..6a6ad015db95c13ef37fcb2f8e455253bea94716 100644 --- a/src/main/java/org/olat/group/ui/main/BusinessGroupSearchController.java +++ b/src/main/java/org/olat/group/ui/main/BusinessGroupSearchController.java @@ -26,6 +26,7 @@ import org.olat.core.CoreSpringFactory; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.form.flexible.FormItem; import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.elements.FormLink; import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement; import org.olat.core.gui.components.form.flexible.elements.SingleSelection; import org.olat.core.gui.components.form.flexible.elements.TextElement; @@ -33,8 +34,8 @@ import org.olat.core.gui.components.form.flexible.impl.Form; import org.olat.core.gui.components.form.flexible.impl.FormBasicController; import org.olat.core.gui.components.form.flexible.impl.FormEvent; import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; -import org.olat.core.gui.components.form.flexible.impl.elements.FormSubmit; import org.olat.core.gui.components.form.flexible.impl.elements.table.ExtendedFlexiTableSearchController; +import org.olat.core.gui.components.link.Link; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; @@ -60,7 +61,7 @@ public class BusinessGroupSearchController extends FormBasicController implement private TextElement owner; private TextElement description; private TextElement courseTitle; - private FormSubmit searchButton; + private FormLink searchButton; private SingleSelection rolesEl; private SingleSelection publicEl; private SingleSelection resourceEl; @@ -174,7 +175,10 @@ public class BusinessGroupSearchController extends FormBasicController implement FormLayoutContainer buttonLayout = FormLayoutContainer.createButtonLayout("button_layout", getTranslator()); formLayout.add(buttonLayout); buttonLayout.setElementCssClass("o_sel_group_search_groups_buttons"); - searchButton = uifactory.addFormSubmitButton("search", "search", buttonLayout); + searchButton = uifactory.addFormLink("search", buttonLayout, Link.BUTTON); + searchButton.setElementCssClass("o_sel_group_search_button"); + searchButton.setCustomEnabledLinkCSS("btn btn-primary"); + uifactory.addFormCancelButton("cancel", buttonLayout, ureq, getWindowControl()); } diff --git a/src/main/java/org/olat/modules/reminder/ReminderInterval.java b/src/main/java/org/olat/modules/reminder/ReminderInterval.java new file mode 100644 index 0000000000000000000000000000000000000000..41fdf81b275f32ec400b2848f1fa46909cd74183 --- /dev/null +++ b/src/main/java/org/olat/modules/reminder/ReminderInterval.java @@ -0,0 +1,74 @@ +/** + * <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.modules.reminder; + +import org.olat.core.util.StringHelper; + +/** + * + * Initial date: 11.05.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public enum ReminderInterval { + + every24(24, "interval.24"), + every12(12, "interval.12"), + every8(8, "interval.8"), + every6(6, "interval.6"), + every4(4, "interval.4"), + every2(2, "interval.2"), + every1(1, "interval.1"); + + private final int interval; + private final String key; + private final String i18nKey; + + private ReminderInterval(int interval, String i18nKey) { + this.interval = interval; + this.key = Integer.toString(interval); + this.i18nKey = i18nKey; + } + + public String key() { + return key; + } + + public int interval() { + return interval; + } + + public String i18nKey() { + return i18nKey; + } + + public static final ReminderInterval byKey(String key) { + ReminderInterval interval = null; + if(StringHelper.containsNonWhitespace(key)) { + for(ReminderInterval value:values()) { + if(key.equals(value.key())) { + interval = value; + break; + } + } + } + return interval; + } +} diff --git a/src/main/java/org/olat/modules/reminder/ReminderModule.java b/src/main/java/org/olat/modules/reminder/ReminderModule.java index 7d9212acd5f1cb44141ce9997ee06c2754d7a4e2..8897b11119733e9c3aae1bd7544d94bb38f01355 100644 --- a/src/main/java/org/olat/modules/reminder/ReminderModule.java +++ b/src/main/java/org/olat/modules/reminder/ReminderModule.java @@ -53,6 +53,7 @@ public class ReminderModule extends AbstractSpringModule { private static final String SMS_ENABLED = "sms.enabled"; private static final String SEND_TIME = "default.send.time"; private static final String SEND_TIMEZONE = "default.send.timezone"; + private static final String INTERVAL = "send.interval"; @Value("${reminders.enabled:true}") private boolean enabled; @@ -63,6 +64,9 @@ public class ReminderModule extends AbstractSpringModule { private String defaultSendTime; @Value("${reminders.default.send.timezone:server}") private String defaultSendTimeZone; + @Value("${reminders.interval:24}") + private String interval; + @Autowired private List<RuleSPI> ruleSpies; @@ -91,6 +95,11 @@ public class ReminderModule extends AbstractSpringModule { defaultSendTime = sendTimeObj; } + String intervalObj = getStringPropertyValue(INTERVAL, true); + if(StringHelper.containsNonWhitespace(intervalObj)) { + interval = intervalObj; + } + String sendTimezoneObj = getStringPropertyValue(SEND_TIMEZONE, true); if(StringHelper.containsNonWhitespace(sendTimezoneObj)) { defaultSendTimeZone = sendTimezoneObj; @@ -121,7 +130,7 @@ public class ReminderModule extends AbstractSpringModule { return selectedSpi; } /** - * Default 0 0 9 * * ? + * Default 0 0 9/1 * * ? * */ private void configureQuartzJob() { @@ -132,6 +141,7 @@ public class ReminderModule extends AbstractSpringModule { String currentCronExpression = cronTrigger.getCronExpression(); String cronExpression = getCronExpression(); if(!cronExpression.equals(currentCronExpression)) { + log.info("Start reminder with this cron expression: " + cronExpression); cronTrigger.setCronExpression(cronExpression); scheduler.rescheduleJob("reminderTrigger", Scheduler.DEFAULT_GROUP, (Trigger)cronTrigger.clone()); } @@ -141,7 +151,7 @@ public class ReminderModule extends AbstractSpringModule { } } - private String getCronExpression() { + protected String getCronExpression() { StringBuilder sb = new StringBuilder(); int hour = 9; int minute = 0; @@ -152,7 +162,21 @@ public class ReminderModule extends AbstractSpringModule { minute = parsedTime.getMinute(); } - sb.append("0 ").append(minute).append(" ").append(hour).append(" * * ?"); + ReminderInterval intervalVal = ReminderInterval.byKey(getInterval()); + String cronInterval; + if(intervalVal != null && !ReminderInterval.every24.equals(intervalVal)) { + int i = intervalVal.interval(); + if(i < hour) { + //correct the first time the cron job starts + int rest = hour % i; + hour = rest; + } + cronInterval = "/" + intervalVal.interval(); + } else { + cronInterval = "";//or 24 hours + } + + sb.append("0 ").append(minute).append(" ").append(hour).append(cronInterval).append(" * * ?"); return sb.toString(); } @@ -174,6 +198,15 @@ public class ReminderModule extends AbstractSpringModule { setStringProperty(SMS_ENABLED, Boolean.toString(smsEnabled), true); } + public String getInterval() { + return interval; + } + + public void setInterval(String interval) { + this.interval = interval; + setStringProperty(INTERVAL, interval, true); + } + public String getDefaultSendTime() { return defaultSendTime; } @@ -199,4 +232,11 @@ public class ReminderModule extends AbstractSpringModule { this.defaultSendTimeZone = timeZone.getID(); setStringProperty(SEND_TIMEZONE, defaultSendTimeZone, true); } + + public void setScheduler(String interval, String defaultSendTime) { + this.interval = interval; + this.defaultSendTime = defaultSendTime; + setStringProperty(INTERVAL, interval, true); + setStringProperty(SEND_TIME, defaultSendTime, true); + } } diff --git a/src/main/java/org/olat/modules/reminder/ui/ReminderAdminController.java b/src/main/java/org/olat/modules/reminder/ui/ReminderAdminController.java index 7ecd9cf6cad9147fc471411be0ac71e199e64154..038f39c427e03ce7853fff412f7293a8a1252048 100644 --- a/src/main/java/org/olat/modules/reminder/ui/ReminderAdminController.java +++ b/src/main/java/org/olat/modules/reminder/ui/ReminderAdminController.java @@ -33,6 +33,8 @@ import org.olat.core.gui.components.form.flexible.impl.FormEvent; import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.WindowControl; +import org.olat.core.util.StringHelper; +import org.olat.modules.reminder.ReminderInterval; import org.olat.modules.reminder.ReminderModule; import org.olat.modules.reminder.model.SendTime; import org.springframework.beans.factory.annotation.Autowired; @@ -47,17 +49,40 @@ public class ReminderAdminController extends FormBasicController { private static final String[] enableKeys = new String[]{ "on" }; + private static final String[] intervalKeys = new String[]{ + ReminderInterval.every24.key(), + ReminderInterval.every12.key(), + ReminderInterval.every8.key(), + ReminderInterval.every6.key(), + ReminderInterval.every4.key(), + ReminderInterval.every2.key(), + ReminderInterval.every1.key() + }; + private MultipleSelectionElement enableEl; private IntegerElement hoursEl, minutesEl; private SingleSelection timezoneEl; + private SingleSelection intervalEl; private FormLayoutContainer timeLayout; + private String[] intervalValues; + @Autowired private ReminderModule reminderModule; public ReminderAdminController(UserRequest ureq, WindowControl wControl) { super(ureq, wControl); + intervalValues = new String[]{ + translate(ReminderInterval.every24.i18nKey()), + translate(ReminderInterval.every12.i18nKey()), + translate(ReminderInterval.every8.i18nKey()), + translate(ReminderInterval.every6.i18nKey()), + translate(ReminderInterval.every4.i18nKey()), + translate(ReminderInterval.every2.i18nKey()), + translate(ReminderInterval.every1.i18nKey()) + }; + initForm(ureq); } @@ -76,6 +101,21 @@ public class ReminderAdminController extends FormBasicController { enableEl.addActionListener(FormEvent.ONCHANGE); enableEl.select(enableKeys[0], reminderModule.isEnabled()); + String interval = reminderModule.getInterval(); + intervalEl = uifactory.addDropdownSingleselect("interval", formLayout, intervalKeys, intervalValues, null); + boolean found = false; + if(StringHelper.containsNonWhitespace(interval)) { + for(String intervalKey:intervalKeys) { + if(intervalKey.equals(interval)) { + intervalEl.select(intervalKey, true); + found = true; + } + } + } + if(!found) { + intervalEl.select(intervalKeys[0], true); + } + int hour = 9; int minute = 0; @@ -130,6 +170,7 @@ public class ReminderAdminController extends FormBasicController { if(enableEl == source) { boolean enabled = enableEl.isAtLeastSelected(1); timeLayout.setVisible(enabled); + intervalEl.setVisible(enabled); //enableSmsEl.setVisible(enabled); } @@ -142,10 +183,12 @@ public class ReminderAdminController extends FormBasicController { reminderModule.setEnabled(enabled); if(enabled) { + String interval = intervalEl.getSelectedKey(); + int hours = hoursEl.getIntValue(); int minutes = minutesEl.getIntValue(); String sendTime = hours + ":" + minutes; - reminderModule.setDefaultSendTime(sendTime); + reminderModule.setScheduler(interval, sendTime); if(timezoneEl.isOneSelected()) { String timeZoneID = timezoneEl.getSelectedKey(); diff --git a/src/main/java/org/olat/modules/reminder/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/modules/reminder/ui/_i18n/LocalStrings_de.properties index 7d98b715cc95ebe37f6fa2204dcdac3b628a3c21..5c51a614e73a506bf8a953736a542a9bd699230a 100644 --- a/src/main/java/org/olat/modules/reminder/ui/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/modules/reminder/ui/_i18n/LocalStrings_de.properties @@ -6,6 +6,13 @@ default.send.time=Default time to send reminders enable.reminders=Activate course reminders enable.sms.reminders=SMS remidners error.group.not.found=Gruppe existiert nicht in diesem Kurs +interval.24=1 Mal pro Tag +interval.12=2 Mal pro Tag +interval.8=3 Mal pro Tag +interval.6=4 Mal pro Tag +interval.4=Jede vier Stunde +interval.2=Jede zwei Stunde +interval.1=Jede Stunde reminder.admin.title=Course reminders rule.after.date=Nach dem Datum rule.course.enrollment.date=Enrollment date @@ -14,3 +21,4 @@ rule.group.member=Gruppen Teilnehmer rule.initial.course.launch.date=Initial course launch date rule.recent.course.launch.date=Recent course launch date rule.user.property=User property + diff --git a/src/main/java/org/olat/modules/reminder/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/modules/reminder/ui/_i18n/LocalStrings_en.properties index 73c6889bd777ae38d7e6310c45b44ae8b774bba6..86a11b81a50d5783b0b76e3a81688b943fbbc5ab 100644 --- a/src/main/java/org/olat/modules/reminder/ui/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/modules/reminder/ui/_i18n/LocalStrings_en.properties @@ -6,6 +6,14 @@ default.send.time=Default time to send reminders enable.reminders=Activate course reminders enable.sms.reminders=SMS remidners error.group.not.found=Group doesn't exist within this course +interval=Interval +interval.24=1 time per day +interval.12=2 time per day +interval.8=3 time per day +interval.6=4 time per day +interval.4=Every 4 hours +interval.2=Every 2 hours +interval.1=Every hour reminder.admin.title=Course reminders rule.after.date=After date rule.course.enrollment.date=Enrollment date diff --git a/src/main/java/org/olat/repository/RepositoryService.java b/src/main/java/org/olat/repository/RepositoryService.java index a6705bf391d5f48d6c232db2cd303ef756140489..5bf667a94ae41a116a464c5cfc31068416dab289 100644 --- a/src/main/java/org/olat/repository/RepositoryService.java +++ b/src/main/java/org/olat/repository/RepositoryService.java @@ -20,8 +20,10 @@ package org.olat.repository; import java.util.Collection; +import java.util.Date; import java.util.List; import java.util.Locale; +import java.util.Map; import org.olat.basesecurity.Group; import org.olat.basesecurity.IdentityRef; @@ -110,6 +112,24 @@ public interface RepositoryService { */ public int countMembers(List<? extends RepositoryEntryRef> res); + /** + * Return the smallest enrollment date. + * + * @param re + * @param identity + * @return + */ + public Date getEnrollmentDate(RepositoryEntryRef re, IdentityRef identity, String... roles); + + /** + * Return the smallest enrollment date. + * + * @param re + * @param identity + * @return + */ + public Map<Long,Date> getEnrollmentDates(RepositoryEntryRef re, String... roles); + /** * @param re The repository entry * @return True if the configuration allowed user to leave the entry right now diff --git a/src/main/java/org/olat/repository/manager/RepositoryEntryRelationDAO.java b/src/main/java/org/olat/repository/manager/RepositoryEntryRelationDAO.java index 62757174ac1311c3d5c2f56cafb793d7d53b2a3e..ece81e4c76fc0d8e6d91adf54ee57ab4986e8bff 100644 --- a/src/main/java/org/olat/repository/manager/RepositoryEntryRelationDAO.java +++ b/src/main/java/org/olat/repository/manager/RepositoryEntryRelationDAO.java @@ -22,9 +22,11 @@ package org.olat.repository.manager; import java.util.ArrayList; import java.util.Collections; import java.util.Date; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Set; import javax.persistence.EntityManager; @@ -273,6 +275,81 @@ public class RepositoryEntryRelationDAO { return count == null ? 0 : count.intValue(); } + public Date getEnrollmentDate(RepositoryEntryRef re, IdentityRef identity, String... roles) { + if(re == null || identity == null) return null; + + List<String> roleList = null; + if(roles != null && roles.length > 0 && roles[0] != null) { + roleList = new ArrayList<>(roles.length); + for(String role:roles) { + roleList.add(role); + } + } + + StringBuilder sb = new StringBuilder(); + sb.append("select min(members.creationDate) from ").append(RepositoryEntry.class.getName()).append(" as v") + .append(" inner join v.groups as relGroup") + .append(" inner join relGroup.group as baseGroup") + .append(" inner join baseGroup.members as members") + .append(" where v.key=:repoKey and members.identity.key=:identityKey"); + if(roleList != null && roleList.size() > 0) { + sb.append(" and members.role in (:roles)"); + } + + TypedQuery<Date> datesQuery = dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), Date.class) + .setParameter("repoKey", re.getKey()) + .setParameter("identityKey", identity.getKey()); + if(roleList != null && roleList.size() > 0) { + datesQuery.setParameter("roles", roleList); + } + + List<Date> dates = datesQuery.getResultList(); + return dates.isEmpty() ? null : dates.get(0); + } + + public Map<Long,Date> getEnrollmentDates(RepositoryEntryRef re, String... roles) { + if(re == null) return null; + + List<String> roleList = null; + if(roles != null && roles.length > 0 && roles[0] != null) { + roleList = new ArrayList<>(roles.length); + for(String role:roles) { + roleList.add(role); + } + } + + StringBuilder sb = new StringBuilder(); + sb.append("select members.identity.key, min(members.creationDate) from ").append(RepositoryEntry.class.getName()).append(" as v") + .append(" inner join v.groups as relGroup") + .append(" inner join relGroup.group as baseGroup") + .append(" inner join baseGroup.members as members") + .append(" where v.key=:repoKey"); + if(roleList != null && roleList.size() > 0) { + sb.append(" and members.role in (:roles)"); + } + sb.append(" group by members.identity.key"); + + TypedQuery<Object[]> datesQuery = dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), Object[].class) + .setParameter("repoKey", re.getKey()); + if(roleList != null && roleList.size() > 0) { + datesQuery.setParameter("roles", roleList); + } + + List<Object[]> dateList = datesQuery.getResultList(); + Map<Long,Date> dateMap = new HashMap<>((dateList.size() * 2) + 1); + for(Object[] dateArr:dateList) { + Long key = (Long)dateArr[0]; + Date date = (Date)dateArr[1]; + if(key != null && date != null) { + dateMap.put(key, date); + } + } + + return dateMap; + } + public List<Long> getAuthorKeys(RepositoryEntryRef re) { diff --git a/src/main/java/org/olat/repository/manager/RepositoryServiceImpl.java b/src/main/java/org/olat/repository/manager/RepositoryServiceImpl.java index 8aaa4288bb276117df47a5926fa6bb90bba0122f..3978977bff08bfb96f009d2e0be31a2000fe6f75 100644 --- a/src/main/java/org/olat/repository/manager/RepositoryServiceImpl.java +++ b/src/main/java/org/olat/repository/manager/RepositoryServiceImpl.java @@ -26,6 +26,7 @@ import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Set; import org.olat.basesecurity.BaseSecurity; @@ -440,6 +441,16 @@ public class RepositoryServiceImpl implements RepositoryService { public int countMembers(List<? extends RepositoryEntryRef> res) { return reToGroupDao.countMembers(res); } + + @Override + public Date getEnrollmentDate(RepositoryEntryRef re, IdentityRef identity, String... roles) { + return reToGroupDao.getEnrollmentDate(re, identity, roles); + } + + @Override + public Map<Long, Date> getEnrollmentDates(RepositoryEntryRef re, String... roles) { + return reToGroupDao.getEnrollmentDates(re, roles); + } @Override public List<Long> getAuthors(RepositoryEntryRef re) { diff --git a/src/main/java/org/olat/repository/ui/RepositoryEntryRuntimeController.java b/src/main/java/org/olat/repository/ui/RepositoryEntryRuntimeController.java index 1ea2a850189879ac5e7f366ad4c3897b487520ef..9e4406023df1cd8ede3269a5d5c5344c3c6db41c 100644 --- a/src/main/java/org/olat/repository/ui/RepositoryEntryRuntimeController.java +++ b/src/main/java/org/olat/repository/ui/RepositoryEntryRuntimeController.java @@ -635,14 +635,17 @@ public class RepositoryEntryRuntimeController extends MainLayoutBasicController protected final void doClose(UserRequest ureq) { // Now try to go back to place that is attacked to (optional) root back business path - if (launchedFromPoint != null && StringHelper.containsNonWhitespace(launchedFromPoint.getBusinessPath())) { + if (launchedFromPoint != null && StringHelper.containsNonWhitespace(launchedFromPoint.getBusinessPath()) + && launchedFromPoint.getEntries() != null && launchedFromPoint.getEntries().size() > 0) { BusinessControl bc = BusinessControlFactory.getInstance().createFromPoint(launchedFromPoint); - WindowControl bwControl = BusinessControlFactory.getInstance().createBusinessWindowControl(bc, getWindowControl()); - try { - //make the resume secure. If something fail, don't generate a red screen - NewControllerFactory.getInstance().launch(ureq, bwControl); - } catch (Exception e) { - logError("Error while resuming with root leve back business path::" + launchedFromPoint.getBusinessPath(), e); + if(bc.hasContextEntry()) { + WindowControl bwControl = BusinessControlFactory.getInstance().createBusinessWindowControl(bc, getWindowControl()); + try { + //make the resume secure. If something fail, don't generate a red screen + NewControllerFactory.getInstance().launch(ureq, bwControl); + } catch (Exception e) { + logError("Error while resuming with root level back business path::" + launchedFromPoint.getBusinessPath(), e); + } } } diff --git a/src/main/webapp/static/js/functions.js b/src/main/webapp/static/js/functions.js index 8d49e8e9bf2549e1b9d068e19232923389f507cd..b5ee2a76d3eb9ad4acbe8710b1fbe5d346d5c7b9 100644 --- a/src/main/webapp/static/js/functions.js +++ b/src/main/webapp/static/js/functions.js @@ -1192,6 +1192,22 @@ function o_ffXHREvent(formNam, dispIdField, dispId, eventIdField, eventInt) { }) } +function o_ffXHRNFEvent(targetUrl) { + var data = new Object(); + jQuery.ajax(targetUrl,{ + type:'GET', + data: data, + cache: false, + dataType: 'json', + success: function(data, textStatus, jqXHR) { + console.log('Hourra'); + }, + error: function(jqXHR, textStatus, errorThrown) { + if(window.console) console.log('Error status', textStatus); + } + }) +} + // // param formId a String with flexi form id function setFlexiFormDirtyByListener(e){ diff --git a/src/main/webapp/static/js/js.plugins.min.js b/src/main/webapp/static/js/js.plugins.min.js index 83a059c472439599a227179a4108a2ac72cfb6f1..8d6fcf1b229eb084c706c5265093fe9f176d6daf 100644 --- a/src/main/webapp/static/js/js.plugins.min.js +++ b/src/main/webapp/static/js/js.plugins.min.js @@ -5,7 +5,7 @@ * Dual licensed under the MIT or GPL Version 2 licenses. * */ -jQuery.periodic=function(l,h){if(jQuery.isFunction(l)){h=l;l={}}var c=jQuery.extend({},jQuery.periodic.defaults,{ajax_complete:j,increment:g,reset:f,cancel:i},l);c.cur_period=c.period;c.tid=false;var e="";b();return c;function b(){i();c.tid=setTimeout(function(){h.call(c);g();if(c.tid){b()}},c.cur_period)}function j(n,m){if(m==="success"&&e!==n.responseText){e=n.responseText;f()}}function g(){c.cur_period*=c.decay;if(c.cur_period<c.period){f()}else{if(c.cur_period>c.max_period){c.cur_period=c.max_period;if(c.on_max!==undefined){c.on_max.call(c)}}}}function f(){c.cur_period=c.period;b()}function i(){clearTimeout(c.tid);c.tid=null}function k(){}function a(){}function d(){}};jQuery.periodic.defaults={period:4000,max_period:1800000,decay:1.5,on_max:undefined};var Hashtable=(function(){var p="function";var n=(typeof Array.prototype.splice==p)?function(s,r){s.splice(r,1)}:function(u,t){var s,v,r;if(t===u.length-1){u.length=t}else{s=u.slice(t+1);u.length=t;for(v=0,r=s.length;v<r;++v){u[t+v]=s[v]}}};function a(t){var r;if(typeof t=="string"){return t}else{if(typeof t.hashCode==p){r=t.hashCode();return(typeof r=="string")?r:a(r)}else{if(typeof t.toString==p){return t.toString()}else{try{return String(t)}catch(s){return Object.prototype.toString.call(t)}}}}}function g(r,s){return r.equals(s)}function e(r,s){return(typeof s.equals==p)?s.equals(r):(r===s)}function c(r){return function(s){if(s===null){throw new Error("null is not a valid "+r)}else{if(typeof s=="undefined"){throw new Error(r+" must not be undefined")}}}}var q=c("key"),l=c("value");function d(u,s,t,r){this[0]=u;this.entries=[];this.addEntry(s,t);if(r!==null){this.getEqualityFunction=function(){return r}}}var h=0,j=1,f=2;function o(r){return function(t){var s=this.entries.length,v,u=this.getEqualityFunction(t);while(s--){v=this.entries[s];if(u(t,v[0])){switch(r){case h:return true;case j:return v;case f:return[s,v[1]]}}}return false}}function k(r){return function(u){var v=u.length;for(var t=0,s=this.entries.length;t<s;++t){u[v+t]=this.entries[t][r]}}}d.prototype={getEqualityFunction:function(r){return(typeof r.equals==p)?g:e},getEntryForKey:o(j),getEntryAndIndexForKey:o(f),removeEntryForKey:function(s){var r=this.getEntryAndIndexForKey(s);if(r){n(this.entries,r[0]);return r[1]}return null},addEntry:function(r,s){this.entries[this.entries.length]=[r,s]},keys:k(0),values:k(1),getEntries:function(s){var u=s.length;for(var t=0,r=this.entries.length;t<r;++t){s[u+t]=this.entries[t].slice(0)}},containsKey:o(h),containsValue:function(s){var r=this.entries.length;while(r--){if(s===this.entries[r][1]){return true}}return false}};function m(s,t){var r=s.length,u;while(r--){u=s[r];if(t===u[0]){return r}}return null}function i(r,s){var t=r[s];return(t&&(t instanceof d))?t:null}function b(t,r){var w=this;var v=[];var u={};var x=(typeof t==p)?t:a;var s=(typeof r==p)?r:null;this.put=function(B,C){q(B);l(C);var D=x(B),E,A,z=null;E=i(u,D);if(E){A=E.getEntryForKey(B);if(A){z=A[1];A[1]=C}else{E.addEntry(B,C)}}else{E=new d(D,B,C,s);v[v.length]=E;u[D]=E}return z};this.get=function(A){q(A);var B=x(A);var C=i(u,B);if(C){var z=C.getEntryForKey(A);if(z){return z[1]}}return null};this.containsKey=function(A){q(A);var z=x(A);var B=i(u,z);return B?B.containsKey(A):false};this.containsValue=function(A){l(A);var z=v.length;while(z--){if(v[z].containsValue(A)){return true}}return false};this.clear=function(){v.length=0;u={}};this.isEmpty=function(){return !v.length};var y=function(z){return function(){var A=[],B=v.length;while(B--){v[B][z](A)}return A}};this.keys=y("keys");this.values=y("values");this.entries=y("getEntries");this.remove=function(B){q(B);var C=x(B),z,A=null;var D=i(u,C);if(D){A=D.removeEntryForKey(B);if(A!==null){if(!D.entries.length){z=m(v,C);n(v,z);delete u[C]}}}return A};this.size=function(){var A=0,z=v.length;while(z--){A+=v[z].entries.length}return A};this.each=function(C){var z=w.entries(),A=z.length,B;while(A--){B=z[A];C(B[0],B[1])}};this.putAll=function(H,C){var B=H.entries();var E,F,D,z,A=B.length;var G=(typeof C==p);while(A--){E=B[A];F=E[0];D=E[1];if(G&&(z=w.get(F))){D=C(F,z,D)}w.put(F,D)}};this.clone=function(){var z=new b(t,r);z.putAll(w);return z}}return b})();(function(b){b.fn.ooLog=function(f,d,e){var c=null;b(this).each(function(){c=b(this).data("_ooLog");if(c==undefined){c=new a();b(this).data("_ooLog",c)}});if(f==undefined){return c}else{if(typeof f==="string"){if(c){c.log(f,d,e)}}}};function a(){return this}a.prototype={isDebugEnabled:function(){return o_info.JSTracingLogDebugEnabled},log:function(e,c,d){if(!this.isDebugEnabled()){return}jQuery.post(o_info.JSTracingUri,{level:e,logMsg:c,jsFile:d})}}})(jQuery);(function(b){b.fn.ooTranslator=function(){var d=null;b(document).each(function(){d=b(document).data("_ooTranslator");if(d==undefined){d=new a();b(document).data("_ooTranslator",d)}});return d};function a(){return this}a.prototype={mapperUrl:null,translators:null,initialize:function(d){this.mapperUrl=d;this.translators=new Object()},getTranslator:function(d,f){if(this.translators[d]==null){this.translators[d]=new Object()}if(this.translators[d][f]==null){var e=this.mapperUrl+"/"+d+"/"+f+"/translations.js";jQuery.ajax(e,{async:false,dataType:"json",success:function(g,i,h){jQuery(document).ooTranslator()._createTranslator(g,d,f)}})}return this.translators[d][f]},_createTranslator:function(e,d,f){this.translators[d][f]=new c().initialize(e,d,f)}};function c(){return this}c.prototype={localizationData:null,bundle:null,locale:null,initialize:function(f,d,e){this.bundle=e;this.locale=d;this.localizationData=f;return this},translate:function(d){if(this.localizationData[d]){return this.localizationData[d]}else{return this.bundle+":"+d}}}})(jQuery);+function(b){var a=function(){this.addExtraElements();this.state={busy:false,brandW:0,sitesW:0,sitesDirty:false,sites:{collapsed:this.isSitesCollapsed(),extended:this.isSitesExtended},tabsW:0,tabsDirty:false,tabs:{collapsed:this.isTabsCollapsed(),extended:this.isTabsExtended()},toolsW:0,toolsDirty:false,tools:{collapsed:this.isToolsCollapsed(),extended:this.isToolsExtended()},offCanvasWidth:0,moreW:0};var c=b("#o_offcanvas_right").css("width");if(c){this.state.offCanvasWidth=parseInt(c.replace(/[^\d.]/g,""));this.initListners();this.calculateWidth();this.optimize()}};a.prototype.initListners=function(){b(window).resize(b.proxy(this.onResizeCallback,this));b(document).on("oo.nav.sites.modified",b.proxy(function(){this.state.sitesDirty=true},this));b(document).on("oo.nav.tabs.modified",b.proxy(function(){this.state.tabsDirty=true},this));b(document).on("oo.nav.tools.modified",b.proxy(function(){this.state.toolsDirty=true},this));b(document).on("oo.dom.replacement.after",b.proxy(this.onDOMreplacementCallback,this));b(window).on("orientationchange",b.proxy(this.hideRight,this));b("#o_navbar_right-toggle").on("click",b.proxy(this.toggleRight,this));b("#o_offcanvas_right .o_offcanvas_close").on("click",b.proxy(this.hideRight,this));b("#o_navbar_more").on("shown.bs.dropdown",this.onDropdownShown);b("#o_navbar_more").on("hidden.bs.dropdown",this.onDropdownHidden)};a.prototype.onResizeCallback=function(){if(!this.state.busy){this.state.busy=true;this.calculateWidth();this.optimize();this.state.busy=false}};a.prototype.onDOMreplacementCallback=function(){if(!this.state.busy&&(this.state.sitesDirty||this.state.tabsDirty||this.state.toolsDirty)){this.state.busy=true;this.cleanupMoreDropdown();this.calculateWidth();this.optimize();this.state.sitesDirty=false;this.state.tabsDirty=false;this.state.toolsDirty=false;this.state.busy=false}};a.prototype.onDropdownShown=function(c){var f=b("#o_navbar_more .dropdown-menu");if(f.length){var d=f.offset().left;if(d<0){f.removeClass("dropdown-menu-right")}}};a.prototype.onDropdownHidden=function(c){var d=b("#o_navbar_more .dropdown-menu");d.addClass("dropdown-menu-right")};a.prototype.calculateWidth=function(){var c=b("#o_navbar_container .o_navbar-collapse");this.state.navbarW=c.innerWidth();this.state.brandW=b(".o_navbar-brand").outerWidth(true);this.state.sitesW=this.getSites().outerWidth(true);this.state.tabsW=this.getTabs().outerWidth(true);this.state.toolsW=this.getTools().outerWidth(false);this.state.moreW=b("#o_navbar_more:visible").outerWidth(true)};a.prototype.getOverflow=function(c){var d=this.state.navbarW;d-=this.state.sitesW;d-=this.state.tabsW;d-=this.state.toolsW;d-=this.state.brandW;d-=this.state.moreW;d-=25;return -d};a.prototype.optimize=function(h){var c=this.getOverflow();var k=this.getSites();var l=this.getTabs();var g=this.getTools();var n=this.getMoreDropdown();var f=this.getOffcanvasRight();this.updateState();while(c>0&&(!this.state.tabs.collapsed||!this.state.sites.collapsed||!this.state.tools.collapsed)){if(!this.state.tabs.collapsed){this.collapse(l,n,"li","o_dropdown_tab")}else{if(!this.state.sites.collapsed){this.collapse(k,n,"li","o_dropdown_site")}else{if(!this.state.tools.collapsed){this.collapse(g,f,".o_navbar_tool:not(#o_navbar_imclient, #o_navbar_search_opener, #o_navbar_my_menu)","o_tool_right")}}}this.calculateWidth();c=this.getOverflow();this.updateState()}while(c<0&&(!this.state.tabs.extended||!this.state.sites.extended||!this.state.tools.extended)){if(!this.state.tools.extended){var m=this.extend(f,g.children("#o_navbar_imclient, #o_navbar_search_opener, #o_navbar_my_menu").first(),".o_tool_right","o_tool_right",true);if(!m){break}}if(!this.state.sites.extended){var j=this.extend(n,k,"li","o_dropdown_site");if(!j){break}}else{if(!this.state.tabs.extended){var d=this.extend(n,l,"li","o_dropdown_tab");if(!d){break}}}this.calculateWidth();c=this.getOverflow();this.updateState()}if(this.state.sites.extended&&this.state.tabs.extended){var i=b("#o_navbar_more");i.css("display","none")}this.checkToolsOrder()};a.prototype.updateState=function(){this.state.sites.collapsed=this.isSitesCollapsed();this.state.sites.extended=this.isSitesExtended();this.state.tabs.collapsed=this.isTabsCollapsed();this.state.tabs.extended=this.isTabsExtended();this.state.tools.collapsed=this.isToolsCollapsed();this.state.tools.extended=this.isToolsExtended()};a.prototype.collapse=function(g,d,c,f){var e=g.find(c);if(e.length){e=e.last()}if(e.length){f&&e.addClass(f);if(d){e.prependTo(d)}}this.updateDropdownToggle(g)};a.prototype.extend=function(g,d,c,i,f){var e=g.find(c);if(e.length){e=e.first()}var j=false;if(e.length){if(d){if(f){d.before(e)}else{e.appendTo(d)}this.updateDropdownToggle(g);this.calculateWidth();var h=this.getOverflow();if(h>0){e.prependTo(g)}else{i&&e.removeClass(i);j=true}}}this.updateDropdownToggle(g);return j};a.prototype.updateDropdownToggle=function(c){var d=c.parents(".o_dropdown_toggle");if(!d.length){return}if(c.children().length){d.css("display","block")}else{d.css("display","none")}};a.prototype.addExtraElements=function(){var d=b("#o_navbar_container .o_navbar-collapse");var c=b("#o_navbar_more");if(c.length==0){c=b('<ul id="o_navbar_more" class="nav o_navbar-nav o_dropdown_toggle"><li><a class="dropdown-toggle" data-toggle="dropdown" href="#"">'+o_info.i18n_topnav_more+' <b class="caret"></b></a><ul class="dropdown-menu dropdown-menu-right"></ul></li></ul>');c.appendTo(d)}this.getSites().append('<li class="divider o_dropdown_site"></li>');b("#o_navbar_help .o_icon, #o_navbar_print .o_icon").addClass("o_icon-fw")};a.prototype.cleanupMoreDropdown=function(){if(!this.state.sitesDirty){var f=this.getSites();var d=this.getMoreDropdown().children(".o_dropdown_site");d.appendTo(f)}else{this.getSites().append('<li class="divider o_dropdown_site"></li>')}if(!this.state.tabsDirty){var e=this.getTabs();var c=this.getMoreDropdown().children(".o_dropdown_tab");c.prependTo(e)}this.getMoreDropdown().empty()};a.prototype.checkToolsOrder=function(){var f=this.getTools();var e=f.find("#o_navbar_help");var d=f.find("#o_navbar_print");var c=f.find("#o_navbar_imclient");if(c&&d){c.after(d)}if(c&&e){c.after(e)}};a.prototype.showRight=function(){if(!this.isOffcanvasVisible()&&!this.offcanvasTransitioning){this.offcanvasTransitioning=true;var d=this;var c=b("#o_offcanvas_right");c.show().transition({x:-d.state.offCanvasWidth},function(){d.offcanvasTransitioning=false;b("body").addClass("o_offcanvas_right_visible");var e=b.proxy(d.hideRightOnClick,d);setTimeout(function(){b("html").on("click",e)},10)})}};a.prototype.hideRightOnClick=function(c){if("INPUT"!=c.target.nodeName){this.hideRight()}};a.prototype.hideRight=function(){if(this.isOffcanvasVisible()&&!this.offcanvasTransitioning){this.offcanvasTransitioning=true;b("html").off("click",b.proxy(this.hideRight,this));var d=this;var c=b("#o_offcanvas_right");c.transition({x:d.state.offCanvasWidth},function(){d.offcanvasTransitioning=false;c.hide();b("body").removeClass("o_offcanvas_right_visible")})}};a.prototype.toggleRight=function(){if(this.isOffcanvasVisible()){this.hideRight()}else{this.showRight()}};a.prototype.isOffcanvasVisible=function(){return b("#o_offcanvas_right:visible").length};a.prototype.getSites=function(){return b("#o_navbar_container .o_navbar_sites")};a.prototype.getTabs=function(){return b("#o_navbar_container .o_navbar_tabs")};a.prototype.getTools=function(){return b("#o_navbar_container #o_navbar_tools_permanent")};a.prototype.getMoreDropdown=function(){return b("#o_navbar_more .dropdown-menu")};a.prototype.getOffcanvasRight=function(){return b("#o_offcanvas_right_container .o_navbar-right")};a.prototype.isSitesCollapsed=function(){return !this.getSites().children("li").not(".divider").length};a.prototype.isSitesExtended=function(){return !this.getMoreDropdown().children(".o_dropdown_site").not(".divider").length};a.prototype.isTabsCollapsed=function(){return !this.getTabs().children("li").length};a.prototype.isTabsExtended=function(){return !this.getMoreDropdown().children(".o_dropdown_tab").length};a.prototype.isToolsCollapsed=function(){return !this.getTools().children(".o_navbar_tool").not("#o_navbar_imclient, #o_navbar_search_opener, #o_navbar_my_menu").length};a.prototype.isToolsExtended=function(){return !this.getOffcanvasRight().children(".o_tool_right").length};b(document).ready(function(){var d=b("#o_navbar_wrapper");if(d){var c=new a();window.OPOL.navbar=c}})}(jQuery);+function(b){b.fn.ooBgCarrousel=function(){return new a()};var a=function(){};a.prototype.initCarrousel=function(g){this.settings=b.extend({query:null,images:[],shuffle:false,shuffleFirst:false,durationshow:5000,durationout:500,durationin:500,easeout:"ease",easein:"ease"},g);this.pos=null;if(this.settings.query==null||this.settings.images.length==0){return}this.initialImage=this.settings.images[0];if(this.settings.shuffle){var f=this.settings.images;for(var d,c,e=f.length;e;d=parseInt(Math.random()*e),c=f[--e],f[e]=f[d],f[d]=c){}}if(this.settings.shuffleFirst){this._replaceImage()}this.rotate()};a.prototype.rotate=function(){setTimeout(b.proxy(this._hideCurrent,this),this.settings.durationshow)};a.prototype._hideCurrent=function(){var c=b(this.settings.query);if(c&&c.size()>0){c.transition({opacity:0,duration:this.settings.durationout,easing:this.settings.easeout},b.proxy(this._showNext,this))}};a.prototype._replaceImage=function(d){if(!d){d=b(this.settings.query)}if(d&&d.size()>0){this.newImg="";this.oldImg="";if(this.pos==null){this.pos=1;this.oldImg=this.initialImage}else{this.oldImg=this.settings.images[this.pos];this.pos++;if(this.settings.images.length==this.pos){this.pos=0}}this.newImg=this.settings.images[this.pos];var c=d.css("background-image");if(c.indexOf(this.oldImg)==-1){d.transition({opacity:1,duration:0});return}var e=c.replace(this.oldImg,this.newImg);d.css("background-image",e)}};a.prototype._showNext=function(){var c=b(this.settings.query);this._replaceImage(c);c.transition({opacity:1,duration:this.settings.durationin,easing:this.settings.easein},b.proxy(this.rotate,this))}}(jQuery);!function(a){function b(){function b(a){"remove"===a&&this.each(function(a,b){var c=e(b);c&&c.remove()}),this.find("span.mceEditor,div.mceEditor").each(function(a,b){var c=tinymce.get(b.id.replace(/_parent$/,""));c&&c.remove()})}function d(a){var c,d=this;if(null!=a)b.call(d),d.each(function(b,c){var d;(d=tinymce.get(c.id))&&d.setContent(a)});else if(d.length>0&&(c=tinymce.get(d[0].id)))return c.getContent()}function e(a){var b=null;return a&&a.id&&g.tinymce&&(b=tinymce.get(a.id)),b}function f(a){return!!(a&&a.length&&g.tinymce&&a.is(":tinymce"))}var h={};a.each(["text","html","val"],function(b,g){var i=h[g]=a.fn[g],j="text"===g;a.fn[g]=function(b){var g=this;if(!f(g))return i.apply(g,arguments);if(b!==c)return d.call(g.filter(":tinymce"),b),i.apply(g.not(":tinymce"),arguments),g;var h="",k=arguments;return(j?g:g.eq(0)).each(function(b,c){var d=e(c);h+=d?j?d.getContent().replace(/<(?:"[^"]*"|'[^']*'|[^'">])*>/g,""):d.getContent({save:!0}):i.apply(a(c),k)}),h}}),a.each(["append","prepend"],function(b,d){var g=h[d]=a.fn[d],i="prepend"===d;a.fn[d]=function(a){var b=this;return f(b)?a!==c?("string"==typeof a&&b.filter(":tinymce").each(function(b,c){var d=e(c);d&&d.setContent(i?a+d.getContent():d.getContent()+a)}),g.apply(b.not(":tinymce"),arguments),b):void 0:g.apply(b,arguments)}}),a.each(["remove","replaceWith","replaceAll","empty"],function(c,d){var e=h[d]=a.fn[d];a.fn[d]=function(){return b.call(this,d),e.apply(this,arguments)}}),h.attr=a.fn.attr,a.fn.attr=function(b,g){var i=this,j=arguments;if(!b||"value"!==b||!f(i))return g!==c?h.attr.apply(i,j):h.attr.apply(i,j);if(g!==c)return d.call(i.filter(":tinymce"),g),h.attr.apply(i.not(":tinymce"),j),i;var k=i[0],l=e(k);return l?l.getContent({save:!0}):h.attr.apply(a(k),j)}}var c,d,e,f=[],g=window;a.fn.tinymce=function(c){function h(){var d=[],f=0;e||(b(),e=!0),l.each(function(a,b){var e,g=b.id,h=c.oninit;g||(b.id=g=tinymce.DOM.uniqueId()),tinymce.get(g)||(e=new tinymce.Editor(g,c,tinymce.EditorManager),d.push(e),e.on("init",function(){var a,b=h;l.css("visibility",""),h&&++f==d.length&&("string"==typeof b&&(a=-1===b.indexOf(".")?null:tinymce.resolve(b.replace(/\.\w+$/,"")),b=tinymce.resolve(b)),b.apply(a||tinymce,d))}))}),a.each(d,function(a,b){b.render()})}var i,j,k,l=this,m="";if(!l.length)return l;if(!c)return window.tinymce?tinymce.get(l[0].id):null;if(l.css("visibility","hidden"),g.tinymce||d||!(i=c.script_url))1===d?f.push(h):h();else{d=1,j=i.substring(0,i.lastIndexOf("/")),-1!=i.indexOf(".min")&&(m=".min"),g.tinymce=g.tinyMCEPreInit||{base:j,suffix:m},-1!=i.indexOf("gzip")&&(k=c.language||"en",i=i+(/\?/.test(i)?"&":"?")+"js=true&core=true&suffix="+escape(m)+"&themes="+escape(c.theme||"modern")+"&plugins="+escape(c.plugins||"")+"&languages="+(k||""),g.tinyMCE_GZ||(g.tinyMCE_GZ={start:function(){function b(a){tinymce.ScriptLoader.markDone(tinymce.baseURI.toAbsolute(a))}b("langs/"+k+".js"),b("themes/"+c.theme+"/theme"+m+".js"),b("themes/"+c.theme+"/langs/"+k+".js"),a.each(c.plugins.split(","),function(a,c){c&&(b("plugins/"+c+"/plugin"+m+".js"),b("plugins/"+c+"/langs/"+k+".js"))})},end:function(){}}));var n=document.createElement("script");n.type="text/javascript",n.onload=n.onreadystatechange=function(b){b=b||window.event,2===d||"load"!=b.type&&!/complete|loaded/.test(n.readyState)||(tinymce.dom.Event.domLoaded=1,d=2,c.script_loaded&&c.script_loaded(),h(),a.each(f,function(a,b){b()}))},n.src=i,document.body.appendChild(n)}return l},a.extend(a.expr[":"],{tinymce:function(a){var b;return a.id&&"tinymce"in window&&(b=tinymce.get(a.id),b&&b.editorManager===tinymce)?!0:!1}})}(jQuery);OPOL={};var o2c=0;var o3c=new Array();o_info.guibusy=false;o_info.linkbusy=false;o_info.debug=true;var BLoader={_ajaxLoadedJS:new Array(),_isAlreadyLoadedJS:function(b){var a=true;jQuery("head script[src]").each(function(d,c){if(jQuery(c).attr("src").indexOf(b)!=-1){a=false}});if(jQuery.inArray(b,this._ajaxLoadedJS)!=-1){a=false}return !a},loadJS:function(b,c,a){if(!this._isAlreadyLoadedJS(b)){if(o_info.debug){o_log("BLoader::loadJS: loading ajax::"+a+" url::"+b)}if(a){jQuery.ajax(b,{async:false,dataType:"script",cache:true,success:function(d,f,e){}});this._ajaxLoadedJS.push(b)}else{jQuery.getScript(b)}if(o_info.debug){o_log("BLoader::loadJS: loading DONE url::"+b)}}else{if(o_info.debug){o_log("BLoader::loadJS: already loaded url::"+b)}}},executeGlobalJS:function(jsString,contextDesc){try{if(window.execScript){window.execScript(jsString)}else{window.eval(jsString)}}catch(e){if(window.console){console.log(contextDesc,"cannot execute js",jsString)}if(o_info.debug){o_logerr("BLoader::executeGlobalJS: Error when executing JS code in contextDesc::"+contextDesc+' error::"'+showerror(e)+" for: "+escape(jsString))}if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","BLoader::executeGlobalJS: Error when executing JS code in contextDesc::"+contextDesc+' error::"'+showerror(e)+" for: "+escape(jsString),"functions.js::BLoader::executeGlobalJS::"+contextDesc)}if(window.location.href.indexOf("o_winrndo")!=-1){window.location.reload()}else{window.location.href=window.location.href+(window.location.href.indexOf("?")!=-1?"&":"?")+"o_winrndo=1"}}},loadCSS:function(b,o,q){var r=window.document;try{if(r.createStyleSheet){var j=r.styleSheets;var d=0;var p=0;for(i=0;i<j.length;i++){var m=j[i];var g=m.href;if(g==b){d++;if(m.disabled){m.disabled=false;return}else{if(o_info.debug){o_logwarn("BLoader::loadCSS: style: "+b+" already in document and not disabled! (duplicate add)")}return}}if(m.id=="o_theme_css"){p=i}}if(d>1&&o_info.debug){o_logwarn("BLoader::loadCSS: apply styles: num of stylesheets found was not 0 or 1:"+d)}if(q){p=j.length}var f=r.createStyleSheet(b,p)}else{var c=jQuery("#"+o);if(c&&c.size()>0){if(o_info.debug){o_logwarn("BLoader::loadCSS: stylesheet already found in doc when trying to add:"+b+", with id "+o)}return}else{var a=jQuery('<link id="'+o+'" rel="stylesheet" type="text/css" href="'+b+'">');if(q){a.insertBefore(jQuery("#o_fontSize_css"))}else{a.insertBefore(jQuery("#o_theme_css"))}}}}catch(n){if(window.console){console.log(n)}if(o_info.debug){o_logerr("BLoader::loadCSS: Error when loading CSS from URL::"+b)}if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","BLoader::loadCSS: Error when loading CSS from URL::"+b,"functions.js::BLoader::loadCSS")}}},unLoadCSS:function(a,m){var n=window.document;try{if(n.createStyleSheet){var f=n.styleSheets;var d=0;var o=a;var b=window.location.href.substring(0,window.location.href.indexOf("/",8));if(a.indexOf(b)==0){o=a.substring(b.length)}for(i=0;i<f.length;i++){var g=f[i].href;if(g==a||g==o){d++;if(!f[i].disabled){f[i].disabled=true}else{if(o_info.debug){o_logwarn("stylesheet: when removing: matching url, but already disabled! url:"+g)}}}}if(d!=1&&o_info.debug){o_logwarn("stylesheet: when removeing: num of stylesheets found was not 1:"+d)}}else{var c=jQuery("#"+m);if(c){c.href="";c.remove();c=null;return}else{if(o_info.debug){o_logwarn("no link with id found to remove, id:"+m+", url "+a)}}}}catch(j){if(o_info.debug){o_logerr("BLoader::unLoadCSS: Error when unloading CSS from URL::"+a)}if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","BLoader::unLoadCSS: Error when unloading CSS from URL::"+a,"functions.js::BLoader::loadCSS")}}}};var BFormatter={formatLatexFormulas:function(b){try{if(window.jsMath){if(jsMath.loaded&&jsMath.tex2math&&jsMath.tex2math.loaded){jsMath.Process()}else{jsMath.Autoload.LoadJsMath();setTimeout(function(){BFormatter.formatLatexFormulas(b)},100)}}}catch(a){if(window.console){console.log("error in BFormatter.formatLatexFormulas: ",a)}}}};function o_init(){try{o_getMainWin().o_afterserver()}catch(a){if(o_info.debug){o_log("error in o_init: "+showerror(a))}}}function o_initEmPxFactor(){o_info.emPxFactor=jQuery("#o_width_1em").width();if(o_info.emPxFactor==0||o_info.emPxFactor=="undefined"){o_info.emPxFactor=12;if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","Could not read with of element b_width_1em, set o_info.emPxFactor to 12","functions.js")}}}function o_getMainWin(){try{if(window.OPOL){return window}else{if(window.opener&&window.opener.OPOL){return window.opener}}}catch(a){if(o_info.debug){o_logerr('Exception while getting main window. rror::"'+showerror(a))}if(window.console){console.log('Exception while getting main window. rror::"'+showerror(a),"functions.js");console.log(a)}}throw"Can not find main OpenOLAT window"}function o_beforeserver(){o_info.linkbusy=true;showAjaxBusy();if(window.suppressOlatOnUnloadOnce){window.suppressOlatOnUnloadOnce=false}else{if(window.olatonunload){olatonunload()}}}function o_afterserver(){o2c=0;o_info.linkbusy=false;removeAjaxBusy()}function o2cl(){if(o_info.linkbusy){return false}else{var a=(o2c==0||confirm(o_info.dirty_form));if(a){o_beforeserver()}return a}}function o2cl_noDirtyCheck(){if(o_info.linkbusy){return false}else{o_beforeserver();return true}}function o2cl_secure(){try{if(o2cl()){return true}else{return false}}catch(a){return false}}function o3cl(d){if(o_info.linkbusy){return false}else{var b=o3c1.indexOf(d)>-1;var a=(b&&o3c1.length>1)||o3c1.length>0;var c=(!a||confirm(o_info.dirty_form));if(c){o_beforeserver()}return c}}function o_onc(a){var b=a.responseText;BLoader.executeGlobalJS("o_info.last_o_onc="+b+";","o_onc");o_ainvoke(o_info.last_o_onc,false)}function o_allowNextClick(){o_info.linkbusy=false;removeAjaxBusy()}function removeBusyAfterDownload(c,b,a){o2c=0;o_afterserver()}Array.prototype.search=function(c,d){var a=this.length;for(var b=0;b<a;b++){if(this[b].constructor==Array){if(this[b].search(c,d)){return true;break}}else{if(d){if(this[b].indexOf(c)!=-1){return true;break}}else{if(this[b]==c){return true;break}}}}return false};if(!Function.prototype.curry){Function.prototype.curry=function(){if(arguments.length<1){return this}var a=this;var b=Array.prototype.slice.call(arguments);return function(){return a.apply(this,b.concat(Array.prototype.slice.call(arguments)))}}}if(!Array.prototype.indexOf){Array.prototype.indexOf=function(c){if(this==null){throw new TypeError()}var d=Object(this);var a=d.length>>>0;if(a===0){return -1}var e=0;if(arguments.length>1){e=Number(arguments[1]);if(e!=e){e=0}else{if(e!=0&&e!=Infinity&&e!=-Infinity){e=(e>0||-1)*Math.floor(Math.abs(e))}}}if(e>=a){return -1}var b=e>=0?e:Math.max(a-Math.abs(e),0);for(;b<a;b++){if(b in d&&d[b]===c){return b}}return -1}}var b_onDomReplacementFinished_callbacks=new Array();function b_AddOnDomReplacementFinishedCallback(a){var b=jQuery(document).ooLog().isDebugEnabled();if(b){jQuery(document).ooLog("debug","callback stack size: "+b_onDomReplacementFinished_callbacks.length,"functions.js ADD")}if(b&&b_onDomReplacementFinished_callbacks.toSource){jQuery(document).ooLog("debug","stack content"+b_onDomReplacementFinished_callbacks.toSource(),"functions.js ADD")}b_onDomReplacementFinished_callbacks.push(a);if(b){jQuery(document).ooLog("debug","push to callback stack, func: "+a,"functions.js ADD")}}var b_changedDomEl=new Array();function b_AddOnDomReplacementFinishedUniqueCallback(a){if(a.constructor==Array){if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","add: its an ARRAY! ","functions.js ADD")}if(b_onDomReplacementFinished_callbacks.search(a[0])){if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","push to callback stack, already there!!: "+a[0],"functions.js ADD")}return}}b_AddOnDomReplacementFinishedCallback(a)}var o_debug_trid=0;function o_ainvoke(N){if(N==undefined){return}o_info.inainvoke=true;var I=N.cmdcnt;if(I>0){jQuery(document).trigger("oo.dom.replacement.before");b_changedDomEl=new Array();if(o_info.debug){o_debug_trid++}var y=N.cmds;for(var T=0;T<I;T++){var J=y[T];var A=J.cmd;var R=J.cda;var U=J.w;var c=this.window;var K;if(c){switch(A){case 1:var M=R.e;BLoader.executeGlobalJS(M,"o_ainvoker::jsexec");if(o_info.debug){o_log("c1: execute jscode: "+M)}case 2:var u=R.cc;var F=R.cps;for(var Q=0;Q<u;Q++){var m=F[Q];var h=m.cid;var P=m.cidvis;var H=m.cw;var x=m.hfrag;var O=m.jsol;var g=m.hdr;if(o_info.debug){o_log("c2: redraw: "+m.cname+" ("+h+") "+m.hfragsize+" bytes, listener(s): "+m.clisteners)}var W=g+"\n\n"+x;var C="";var S=false;var E="o_c"+h;var B=jQuery("#"+E);if(B==null||B.length==0){E="o_fi"+h;B=jQuery("#"+E);S=true}if(B!=null){var w=jQuery("div.o_richtext_mce textarea",B);for(var L=0;L<w.length;L++){try{var a=jQuery(w.get(L)).attr("id");if(typeof top.tinymce!=undefined){top.tinymce.remove("#"+a)}}catch(Z){if(window.console){console.log(Z)}}}if(P){B.css("display","")}else{B.css("display","none")}if(S||!H){B.replaceWith(W)}else{try{B.empty().html(W);if(W.length>0&&B.get(0).innerHTML==""){B.get(0).innerHTML=W}}catch(Z){if(window.console){console.log(Z)}if(window.console){console.log("Fragment",W)}}b_changedDomEl.push(E)}B=null;if(C!=""){C.each(function(e){BLoader.executeGlobalJS(e,"o_ainvoker::inscripts")})}if(O!=""){BLoader.executeGlobalJS(O,"o_ainvoker::jsol")}}}break;case 3:c.o2c=0;var X=R.rurl;c.o_afterserver();c.document.location.replace(X);break;case 5:c.o2c=0;var X=R.rurl;c.o_afterserver();c.document.location.replace(X);break;case 6:c.o2c=0;c.o_afterserver();break;case 7:var o=c.document.location;var z=o.protocol+"//"+o.hostname;if(o.port!=""){z+=":"+o.port}var v=R.cssrm;for(Q=0;Q<v.length;Q++){var D=v[Q];var G=D.id;var f=z+D.url;BLoader.unLoadCSS(f,G);if(o_info.debug){o_log("c7: rm css: id:"+G+" ,url:'"+f+"'")}}var V=R.cssadd;for(k=0;k<V.length;k++){var D=V[k];var G=D.id;var f=z+D.url;var n=D.pt;BLoader.loadCSS(f,G,n);if(o_info.debug){o_log("c7: add css: id:"+G+" ,url:'"+f+"'")}}var p=R.jsadd;for(l=0;l<p.length;l++){var D=p[l];var Y=D.before;if(jQuery.type(Y)==="string"){BLoader.executeGlobalJS(Y,"o_ainvoker::preJsAdd")}var f=D.url;var q=D.enc;if(jQuery.type(f)==="string"){BLoader.loadJS(f,q,true)}if(o_info.debug){o_log("c7: add js: "+f)}}break;default:if(o_info.debug){o_log("?: unknown command "+A)}if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","Error in o_ainvoke(), ?: unknown command "+A,"functions.js")}break}}else{if(o_info.debug){o_log("could not find window??")}if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","Error in o_ainvoke(), could not find window??","functions.js")}}}var b=b_onDomReplacementFinished_callbacks.length;if(b_onDomReplacementFinished_callbacks.toSource&&jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","stack content"+b_onDomReplacementFinished_callbacks.toSource(),"functions.js")}for(mycounter=0;b>mycounter;mycounter++){if(mycounter>50){if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","Stopped executing DOM replacement callback functions - to many functions::"+b_onDomReplacementFinished_callbacks.length,"functions.js")}break}if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","Stacksize before shift: "+b_onDomReplacementFinished_callbacks.length,"functions.js")}var s=b_onDomReplacementFinished_callbacks.shift();if(typeof s.length==="number"){if(s[0]=="glosshighlighter"){var d=s[1];if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","arr fct: "+d,"functions.js")}s=d}}if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","Executing DOM replacement callback function #"+mycounter+" with timeout funct::"+s,"functions.js")}s();if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","Stacksize after timeout: "+b_onDomReplacementFinished_callbacks.length,"functions.js")}}jQuery(document).trigger("oo.dom.replacement.after")}o_info.inainvoke=false}function clearAfterAjaxIframeCall(){if(o_info.linkbusy){o_afterserver();showMessageBox("info",o_info.i18n_noresponse_title,o_info.i18n_noresponse,undefined)}}function showAjaxBusy(){setTimeout(function(){if(o_info.linkbusy){try{if(jQuery("#o_ajax_busy_backdrop").length==0){jQuery("#o_body").addClass("o_ajax_busy");jQuery("#o_ajax_busy").modal({show:true,backdrop:"static",keyboard:"false"});jQuery("#o_ajax_busy").after('<div id="o_ajax_busy_backdrop" class="modal-backdrop in"></div>');jQuery("#o_ajax_busy>.modal-backdrop").remove();jQuery("#o_ajax_busy_backdrop").css({"z-index":1200})}}catch(a){if(window.console){console.log(a)}}}},700)}function removeAjaxBusy(){try{jQuery("#o_body").removeClass("o_ajax_busy");jQuery("#o_ajax_busy_backdrop").remove();jQuery("#o_ajax_busy").modal("hide")}catch(a){if(window.console){console.log(a)}}}function setFormDirty(c){o2c=1;var a=document.getElementById(c);if(a!=null){var b=a.olat_fosm_0;if(b==null){b=a.olat_fosm}if(b){b.className="btn o_button_dirty"}}else{if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","Error in setFormDirty, myForm was null for formId="+c,"functions.js")}}}function contextHelpWindow(a){helpWindow=window.open(a,"HelpWindow","height=760, width=940, left=0, top=0, location=no, menubar=no, resizable=yes, scrollbars=yes, toolbar=no");helpWindow.focus()}function o_openPopUp(b,d,c,a,e){attributes="height="+a+", width="+c+", resizable=yes, scrollbars=yes, left=100, top=100, ";if(e){attributes+="location=yes, menubar=yes, status=yes, toolbar=yes"}else{attributes+="location=no, menubar=no, status=no, toolbar=no"}var f=window.open(b,d,attributes);f.focus();if(o_info.linkbusy){o_afterserver()}}function b_handleFileUploadFormChange(e,b,d){var f=e.value;slashPos=f.lastIndexOf("/");if(slashPos!=-1){f=f.substring(slashPos+1)}slashPos=f.lastIndexOf("\\");if(slashPos!=-1){f=f.substring(slashPos+1)}b.value=f;if(d){d.className="o_button_dirty"}var c=e.form.elements;for(i=0;i<c.length;i++){var a=c[i];if(a.name==b.name&&i+1<c.length){c[i+1].focus()}}}function gotonode(a){try{if(typeof o_activateCourseNode!="undefined"){o_activateCourseNode(a)}else{if(opener&&typeof opener.o_activateCourseNode!="undefined"){opener.o_activateCourseNode(a)}else{if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","Error in gotonode(), could not find main window","functions.js")}}}}catch(b){alert("Goto node error:"+b);if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","Error in gotonode()::"+b.message,"functions.js")}}}function o_openUriInMainWindow(b){try{var a=o_getMainWin();a.focus();a.location.replace(b)}catch(c){showMessageBox("error","Error","Can not find main OpenOLAT window to open URL.")}}function o_viewportHeight(){var a=jQuery(document).height();if(a>0){return a}else{return 600}}OPOL.getMainColumnsMaxHeight=function(){var j=0,f=0,a=0,c=0,h=0,b,g=jQuery("#o_main_left_content"),e=jQuery("#o_main_right_content"),d=jQuery("#o_main_center_content");if(g!="undefined"&&g!=null){j=g.outerHeight(true)}if(e!="undefined"&&e!=null){f=e.outerHeight(true)}if(d!="undefined"&&d!=null){a=d.outerHeight(true)}c=(j>f?j:f);c=(c>a?c:a);if(c>0){return c}b=jQuery("#o_main");if(b!="undefined"&&b!=null){h=b.height()}if(b>0){return b}return o_viewportHeight()};OPOL.adjustHeight=function(){try{var a=0;col1=jQuery("#o_main_left_content").outerHeight(true);col2=jQuery("#o_main_right_content").outerHeight(true);col3=jQuery("#o_main_center_content").outerHeight(true);a=Math.max(col1,col2,col3);if(col1!=null){jQuery("#o_main_left").css({"min-height":a+"px"})}if(col2!=null){jQuery("#o_main_right").css({"min-height":a+"px"})}if(col3!=null){jQuery("#o_main_center").css({"min-height":a+"px"})}}catch(b){if(window.console){console.log(b)}}};jQuery(window).resize(function(){clearTimeout(o_info.resizeId);o_info.resizeId=setTimeout(function(){jQuery(document).trigger("oo.window.resize.after")},500)});jQuery(document).on("oo.window.resize.after",OPOL.adjustHeight);jQuery(document).on("oo.dom.replacement.after",OPOL.adjustHeight);jQuery().ready(OPOL.adjustHeight);function o_scrollToElement(a){try{jQuery("html, body").animate({scrollTop:jQuery(a).offset().top},333)}catch(b){}}function o_popover(c,b,a){if(typeof(a)==="undefined"){a="bottom"}jQuery("#"+c).popover({placement:a,html:true,trigger:"click",container:"body",content:function(){return jQuery("#"+b).clone().html()}}).on("shown.bs.popover",function(){var d=function(f){jQuery("#"+c).popover("hide");jQuery("body").unbind("click",d)};setTimeout(function(){jQuery("body").on("click",d)},5)})}function o_popoverWithTitle(d,c,b,a){if(typeof(a)==="undefined"){a="bottom"}return jQuery("#"+d).popover({placement:a,html:true,title:b,trigger:"click",container:"body",content:function(){return jQuery("#"+c).clone().html()}}).on("shown.bs.popover",function(){var e=function(f){jQuery("#"+d).popover("destroy");jQuery("body").unbind("click",e)};setTimeout(function(){jQuery("body").on("click",e)},5)})}function o_shareLinkPopup(d,c,b){if(typeof(b)==="undefined"){b="top"}var a=jQuery("#"+d);a.popover({placement:b,html:true,trigger:"click",container:"body",content:c}).on("shown.bs.popover",function(){var e=function(f){if(jQuery(f.target).data("toggle")!=="popover"&&jQuery(f.target).parents(".popover.in").length===0){jQuery("#"+d).popover("hide");jQuery("body").unbind("click",e)}};setTimeout(function(){jQuery("body").on("click",e)},5)});a.attr("title",a.attr("data-original-title"))}function o_QRCodePopup(d,c,b){if(typeof(b)==="undefined"){b="top"}var a=jQuery("#"+d);a.popover({placement:b,html:true,trigger:"click",container:"body",content:'<div id="'+d+'_pop" class="o_qrcode"></div>'}).on("shown.bs.popover",function(){o_info.qr=o_QRCode(d+"_pop",(jQuery.isFunction(c)?c():c));var e=function(f){if(jQuery(f.target).data("toggle")!=="popover"&&jQuery(f.target).parents(".popover.in").length===0){jQuery("#"+d).popover("hide");jQuery("body").unbind("click",e)}};setTimeout(function(){jQuery("body").on("click",e)},5)}).on("hidden.bs.popover",function(){try{o_info.qr.clear();delete o_info.qr}catch(f){}});a.attr("title",a.attr("data-original-title"))}function o_QRCode(c,b){try{BLoader.loadJS(o_info.o_baseURI+"/js/jquery/qrcodejs/qrcode.min.js","utf8",true);return new QRCode(document.getElementById(c),b)}catch(a){return null}}function b_resizeIframeToMainMaxHeight(f){var d=jQuery("#"+f);if(d!="undefined"&&d!=null){var c=OPOL.getMainColumnsMaxHeight()-110;var b=o_viewportHeight()-100;b=b-d.offset().top;var e=jQuery("#b_footer");if(e!="undefined"&&e!=null){b=b-e.outerHeight(true)}var a=(b>c?b:c);d.height(a)}}var o_debu_oldcn,o_debu_oldtt;function o_debu_show(b,a){if(o_debu_oldcn){o_debu_hide(o_debu_oldcn,o_debu_oldtt)}jQuery(b).addClass("o_dev_m");jQuery(a).show();o_debu_oldtt=a;o_debu_oldcn=b}function o_debu_hide(b,a){jQuery(a).hide();jQuery(b).removeClass("o_dev_m")}function o_dbg_mark(a){var b=jQuery("#"+a);if(b){b.css("background-color","#FCFCB8");b.css("border","3px solid #00F")}}function o_dbg_unmark(a){var b=jQuery("#"+a);if(b){b.css("border","");b.css("background-color","")}}function o_clearConsole(){o_log_all="";o_log(null)}var o_log_all="";function o_log(b){if(b){o_log_all="\n"+o_debug_trid+"> "+b+o_log_all;o_log_all=o_log_all.substr(0,4000)}var a=jQuery("#o_debug_cons");if(a){if(o_log_all.length==4000){o_log_all=o_log_all+"\n... (stripped: to long)... "}a.value=o_log_all}if(!jQuery.type(window.console)==="undefined"){window.console.log(b)}}function o_logerr(a){o_log("ERROR:"+a)}function o_logwarn(a){o_log("WARN:"+a)}function showerror(c){var a="";for(var b in c){a+=b+": "+c[b]+"\n"}return"error detail:\n"+a}function o_ffEvent(e,d,c,h,j){var f,g,b,a;f=document.getElementById(d);g=f.value;f.value=c;b=document.getElementById(h);a=b.value;b.value=j;if(document.forms[e].onsubmit()){document.forms[e].submit()}f.value=g;b.value=a}function o_ffXHREvent(f,e,a,h,j){var c=new Object();c.dispatchuri=a;c.dispatchevent=j;if(arguments.length>5){var g=arguments.length;for(var d=5;d<g;d=d+2){if(g>d+1){c[arguments[d]]=arguments[d+1]}}}var b=jQuery("#"+f).attr("action");jQuery.ajax(b,{type:"GET",data:c,cache:false,dataType:"json",success:function(n,o,m){o_ainvoke(n)},error:function(m,o,n){if(window.console){console.log("Error status",o)}}})}function setFlexiFormDirtyByListener(a){setFlexiFormDirty(a.data.formId)}function setFlexiFormDirty(b){var a=o3c.indexOf(b)>-1;if(!a){o3c.push(b)}jQuery("#"+b).each(function(){var c=jQuery(this).data("FlexiSubmit");if(c!=null){jQuery("#"+c).addClass("btn o_button_dirty");o2c=1}})}function o_ffRegisterSubmit(b,a){jQuery("#"+b).data("FlexiSubmit",a)}function showInfoBox(g,d){var c=Math.floor(Math.random()*65536).toString(16);var f='<div id="'+c+'" class="o_alert_info "><div class="alert alert-info clearfix o_sel_info_message"><i class="o_icon o_icon_close"></i><h3><i class="o_icon o_icon_info"></i> '+g+"</h3><p>"+d+"</p></div></div>";var a=jQuery("#o_messages").prepend(f);var e=(d.length>150)?8000:((d.length>70)?6000:4000);var b=function(){jQuery("#"+c).transition({top:"-100%"},333,function(){jQuery("#"+c).remove()})};jQuery("#"+c).show().transition({top:0},333);jQuery("#"+c).click(function(h){b()});o_scrollToElement("#o_top");g=null;d=null;a=null;e=null;setTimeout(function(){try{b()}catch(h){}},8000)}function showMessageBox(b,f,d,a){if(b=="info"){showInfoBox(f,d);return null}else{var c='<div id="myFunctionalModal" class="modal fade" role="dialog"><div class="modal-dialog"><div class="modal-content">';c+='<div class="modal-header"><button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>';c+='<h4 class="modal-title">'+f+"</h4></div>";c+='<div class="modal-body alert ';if("warn"==b){c+="alert-warning"}else{if("error"==b){c+="alert-danger"}else{c+="alert-info"}}c+='"><p>'+d+"</p></div></div></div></div>";jQuery("#myFunctionalModal").remove();jQuery("body").append(c);var e=jQuery("#myFunctionalModal").modal("show").on("hidden.bs.modal",function(g){jQuery("#myFunctionalModal").remove()});o_scrollToElement("#o_top");return e}}function tableFormInjectCommandAndSubmit(a,b,c){document.forms[a].elements.cmd.value=b;document.forms[a].elements.param.value=c;document.forms[a].submit()}function o_table_toggleCheck(d,c){var a=document.forms[d].elements.tb_ms;len=a.length;if(typeof(len)=="undefined"){a.checked=c}else{var b;for(b=0;b<len;b++){a[b].checked=c}}}function onTreeStartDrag(a,b){jQuery(a.target).addClass("o_dnd_proxy")}function onTreeStopDrag(a,b){jQuery(a.target).removeClass("o_dnd_proxy")}function onTreeDrop(g,h){var a=jQuery(h.draggable[0]);var f=jQuery(this);f.css({position:"",width:""});var c=f.droppable("option","endUrl");if(c.lastIndexOf("/")==(c.length-1)){c=c.substring(0,c.length-1)}var e=a.attr("id");var b=e.substring(2,e.length);c+="%3Atnidle%3A"+b;var d=f.attr("id");if(d.indexOf("ds")==0){c+="%3Asne%3Ayes"}else{if(d.indexOf("dt")==0){c+="%3Asne%3Aend"}}jQuery(".ui-droppable").each(function(j,m){jQuery(m).droppable("disable")});frames.oaa0.location.href=c+"/"}function treeAcceptDrop(a){return true}function treeAcceptDrop_notWithChildren(a){var c=false;var b=jQuery(a);var e=b.attr("id");if(e!=undefined&&(e.indexOf("dd")==0||e.indexOf("ds")==0||e.indexOf("dt")==0||e.indexOf("da")==0||e.indexOf("row")==0)){var g=jQuery(this);var j=g.attr("id");var d=e.substring(2,e.length);var f=j.substring(2,j.length);if(d!=f){var h=jQuery("#dd"+d).parents("li");if(h.length>0&&jQuery(h.get(0)).find("#dd"+f).length==0){c=true}}}return c}function treeAcceptDrop_portfolio(b){var d=false;var c=jQuery(b);var f=c.attr("id");if(treeNode_isDragNode(f)){var h=jQuery(this);var n=h.attr("id");var e=f.substring(2,f.length);var g=n.substring(2,n.length);var m=f.indexOf("ds")==0||f.indexOf("dt")==0;if(e!=g){var j=treeNode_portfolioType(c);var a=treeNode_portfolioType(h);if(j=="artefact"){if(a=="page"||a=="struct"||a=="artefact"){d=true}}else{if(j=="struct"){if(a=="page"||a=="struct"){d=true}}else{if(j=="page"){if(a=="map"||a=="page"){d=true}}}}}}return d}function treeNode_portfolioType(e){var c=jQuery(e.get(0));var d=treeNode_portfolioTypes(c);if(d==null){var a=c.parent("a");if(a.length>0){d=treeNode_portfolioTypes(jQuery(a.get(0)))}else{if(c.attr("id").indexOf("ds")==0){var b=c.prev("div");if(b.length>0){d=treeNode_portfolioTypes(b)}}else{if(c.attr("id").indexOf("dt")==0){var b=c.next("div");if(b.length>0){d=treeNode_portfolioTypes(b)}}}}}return d}function treeNode_portfolioTypes(a){if(a.find===undefined){return null}else{if(a.find(".o_ep_icon_struct").length>0||a.hasClass("o_ep_icon_struct")){return"struct"}else{if(a.find(".o_ep_icon_page").length>0||a.hasClass("o_ep_icon_page")){return"page"}else{if(a.find(".o_ep_icon_map").length>0||a.hasClass("o_ep_icon_map")){return"map"}else{if(a.find(".o_ep_artefact").length>0||a.hasClass("o_ep_artefact")){return"artefact"}}}}}return null}function treeNode_isDragNode(a){if(a!=undefined&&(a.indexOf("dd")==0||a.indexOf("ds")==0||a.indexOf("dt")==0||a.indexOf("da")==0||a.indexOf("row")==0)){return true}return false}function o_choice_toggleCheck(c,b){var d=document.forms[c].elements;len=d.length;if(typeof(len)=="undefined"){d.checked=b}else{var a;for(a=0;a<len;a++){if(d[a].type=="checkbox"&&d[a].getAttribute("class")=="o_checkbox"){d[a].checked=b}}}}function b_briefcase_isChecked(c,e){var b;var a=document.getElementById(c);var d=0;for(b=0;a.elements[b];b++){if(a.elements[b].type=="checkbox"&&a.elements[b].name=="paths"&&a.elements[b].checked){d++}}if(d<1){alert(e);return false}return true}function b_briefcase_toggleCheck(d,c){var a=document.getElementById(d);len=a.elements.length;var b;for(b=0;b<len;b++){if(a.elements[b].name=="paths"){a.elements[b].checked=c}}}function o_doPrint(){var d=jQuery("div.o_iframedisplay iframe");if(d.length>0){try{var a=d[0];frames[a.name].focus();frames[a.name].print();return}catch(c){for(i=0;frames.length>i;i++){a=frames[i];if(a.name=="oaa0"){continue}var b=document.getElementsByName(a.name)[0];if(b&&b.getAttribute("class")=="ext-shim"){continue}if(a.name!=""){try{frames[a.name].focus();frames[a.name].print()}catch(c){window.print()}return}}window.print()}}else{window.print()}}function b_attach_i18n_inline_editing(){jQuery("span.o_translation_i18nitem").hover(function(){jQuery(this.firstChild).show();if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","Entered i18nitem::"+this.firstChild,"functions.js:b_attach_i18n_inline_editing()")}},function(){jQuery("a.o_translation_i18nitem_launcher").hide();if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","Leaving i18nitem::"+this,"functions.js:b_attach_i18n_inline_editing()")}});jQuery("a.o_translation_i18nitem_launcher").hover(function(){var a=jQuery(this).parent("span.o_translation_i18nitem");a.effect("highlight")});b_AddOnDomReplacementFinishedCallback(b_attach_i18n_inline_editing)}function b_hideExtMessageBox(){}var BDebugger={_lastDOMCount:0,_lastObjCount:0,_knownGlobalOLATObjects:["o_afterserver","o_onc","o_getMainWin","o_ainvoke","o_info","o_beforeserver","o_ffEvent","o_openPopUp","o_debu_show","o_logwarn","o_dbg_unmark","o_ffRegisterSubmit","o_clearConsole","o_init","o_log","o_allowNextClick","o_dbg_mark","o_debu_hide","o_logerr","o_debu_oldcn","o_debu_oldtt","o_openUriInMainWindow","o_debug_trid","o_log_all"],_countDOMElements:function(){return document.getElementsByTagName("*").length},_countGlobalObjects:function(){var a=0;for(prop in window){a++}return a},logDOMCount:function(){var b=BDebugger;var a=b._countDOMElements();var c=a-b._lastDOMCount;console.log((c>0?"+":"")+c+" \t"+a+" \tDOM element count after DOM replacement");b._lastDOMCount=a;a=null},logGlobalObjCount:function(){var b=BDebugger;var a=b._countGlobalObjects();var c=a-b._lastObjCount;console.log((c>0?"+":"")+c+" \t"+a+" \tGlobal object count after DOM replacement");b._lastObjCount=a;a=null},logGlobalOLATObjects:function(){var b=BDebugger;var a=new Array();for(prop in window){if(prop.indexOf("o_")==0&&b._knownGlobalOLATObjects.indexOf(prop)==-1){a.push(prop)}}if(a.length>0){console.log(a.length+" global OLAT objects found:");a.each(function(c){console.log("\t"+typeof window[c]+" \t"+c)})}},logManagedOLATObjects:function(){var a=BDebugger;if(o_info.objectMap.length>0){console.log(o_info.objectMap.length+" managed OLAT objects found:");o_info.objectMap.eachKey(function(b){var c=o_info.objectMap.get(b);console.log("\t"+typeof c+" \t"+b);return true})}}};/*! +jQuery.periodic=function(l,h){if(jQuery.isFunction(l)){h=l;l={}}var c=jQuery.extend({},jQuery.periodic.defaults,{ajax_complete:j,increment:g,reset:f,cancel:i},l);c.cur_period=c.period;c.tid=false;var e="";b();return c;function b(){i();c.tid=setTimeout(function(){h.call(c);g();if(c.tid){b()}},c.cur_period)}function j(n,m){if(m==="success"&&e!==n.responseText){e=n.responseText;f()}}function g(){c.cur_period*=c.decay;if(c.cur_period<c.period){f()}else{if(c.cur_period>c.max_period){c.cur_period=c.max_period;if(c.on_max!==undefined){c.on_max.call(c)}}}}function f(){c.cur_period=c.period;b()}function i(){clearTimeout(c.tid);c.tid=null}function k(){}function a(){}function d(){}};jQuery.periodic.defaults={period:4000,max_period:1800000,decay:1.5,on_max:undefined};var Hashtable=(function(){var p="function";var n=(typeof Array.prototype.splice==p)?function(s,r){s.splice(r,1)}:function(u,t){var s,v,r;if(t===u.length-1){u.length=t}else{s=u.slice(t+1);u.length=t;for(v=0,r=s.length;v<r;++v){u[t+v]=s[v]}}};function a(t){var r;if(typeof t=="string"){return t}else{if(typeof t.hashCode==p){r=t.hashCode();return(typeof r=="string")?r:a(r)}else{if(typeof t.toString==p){return t.toString()}else{try{return String(t)}catch(s){return Object.prototype.toString.call(t)}}}}}function g(r,s){return r.equals(s)}function e(r,s){return(typeof s.equals==p)?s.equals(r):(r===s)}function c(r){return function(s){if(s===null){throw new Error("null is not a valid "+r)}else{if(typeof s=="undefined"){throw new Error(r+" must not be undefined")}}}}var q=c("key"),l=c("value");function d(u,s,t,r){this[0]=u;this.entries=[];this.addEntry(s,t);if(r!==null){this.getEqualityFunction=function(){return r}}}var h=0,j=1,f=2;function o(r){return function(t){var s=this.entries.length,v,u=this.getEqualityFunction(t);while(s--){v=this.entries[s];if(u(t,v[0])){switch(r){case h:return true;case j:return v;case f:return[s,v[1]]}}}return false}}function k(r){return function(u){var v=u.length;for(var t=0,s=this.entries.length;t<s;++t){u[v+t]=this.entries[t][r]}}}d.prototype={getEqualityFunction:function(r){return(typeof r.equals==p)?g:e},getEntryForKey:o(j),getEntryAndIndexForKey:o(f),removeEntryForKey:function(s){var r=this.getEntryAndIndexForKey(s);if(r){n(this.entries,r[0]);return r[1]}return null},addEntry:function(r,s){this.entries[this.entries.length]=[r,s]},keys:k(0),values:k(1),getEntries:function(s){var u=s.length;for(var t=0,r=this.entries.length;t<r;++t){s[u+t]=this.entries[t].slice(0)}},containsKey:o(h),containsValue:function(s){var r=this.entries.length;while(r--){if(s===this.entries[r][1]){return true}}return false}};function m(s,t){var r=s.length,u;while(r--){u=s[r];if(t===u[0]){return r}}return null}function i(r,s){var t=r[s];return(t&&(t instanceof d))?t:null}function b(t,r){var w=this;var v=[];var u={};var x=(typeof t==p)?t:a;var s=(typeof r==p)?r:null;this.put=function(B,C){q(B);l(C);var D=x(B),E,A,z=null;E=i(u,D);if(E){A=E.getEntryForKey(B);if(A){z=A[1];A[1]=C}else{E.addEntry(B,C)}}else{E=new d(D,B,C,s);v[v.length]=E;u[D]=E}return z};this.get=function(A){q(A);var B=x(A);var C=i(u,B);if(C){var z=C.getEntryForKey(A);if(z){return z[1]}}return null};this.containsKey=function(A){q(A);var z=x(A);var B=i(u,z);return B?B.containsKey(A):false};this.containsValue=function(A){l(A);var z=v.length;while(z--){if(v[z].containsValue(A)){return true}}return false};this.clear=function(){v.length=0;u={}};this.isEmpty=function(){return !v.length};var y=function(z){return function(){var A=[],B=v.length;while(B--){v[B][z](A)}return A}};this.keys=y("keys");this.values=y("values");this.entries=y("getEntries");this.remove=function(B){q(B);var C=x(B),z,A=null;var D=i(u,C);if(D){A=D.removeEntryForKey(B);if(A!==null){if(!D.entries.length){z=m(v,C);n(v,z);delete u[C]}}}return A};this.size=function(){var A=0,z=v.length;while(z--){A+=v[z].entries.length}return A};this.each=function(C){var z=w.entries(),A=z.length,B;while(A--){B=z[A];C(B[0],B[1])}};this.putAll=function(H,C){var B=H.entries();var E,F,D,z,A=B.length;var G=(typeof C==p);while(A--){E=B[A];F=E[0];D=E[1];if(G&&(z=w.get(F))){D=C(F,z,D)}w.put(F,D)}};this.clone=function(){var z=new b(t,r);z.putAll(w);return z}}return b})();(function(b){b.fn.ooLog=function(f,d,e){var c=null;b(this).each(function(){c=b(this).data("_ooLog");if(c==undefined){c=new a();b(this).data("_ooLog",c)}});if(f==undefined){return c}else{if(typeof f==="string"){if(c){c.log(f,d,e)}}}};function a(){return this}a.prototype={isDebugEnabled:function(){return o_info.JSTracingLogDebugEnabled},log:function(e,c,d){if(!this.isDebugEnabled()){return}jQuery.post(o_info.JSTracingUri,{level:e,logMsg:c,jsFile:d})}}})(jQuery);(function(b){b.fn.ooTranslator=function(){var d=null;b(document).each(function(){d=b(document).data("_ooTranslator");if(d==undefined){d=new a();b(document).data("_ooTranslator",d)}});return d};function a(){return this}a.prototype={mapperUrl:null,translators:null,initialize:function(d){this.mapperUrl=d;this.translators=new Object()},getTranslator:function(d,f){if(this.translators[d]==null){this.translators[d]=new Object()}if(this.translators[d][f]==null){var e=this.mapperUrl+"/"+d+"/"+f+"/translations.js";jQuery.ajax(e,{async:false,dataType:"json",success:function(g,i,h){jQuery(document).ooTranslator()._createTranslator(g,d,f)}})}return this.translators[d][f]},_createTranslator:function(e,d,f){this.translators[d][f]=new c().initialize(e,d,f)}};function c(){return this}c.prototype={localizationData:null,bundle:null,locale:null,initialize:function(f,d,e){this.bundle=e;this.locale=d;this.localizationData=f;return this},translate:function(d){if(this.localizationData[d]){return this.localizationData[d]}else{return this.bundle+":"+d}}}})(jQuery);+function(b){var a=function(){this.addExtraElements();this.state={busy:false,brandW:0,sitesW:0,sitesDirty:false,sites:{collapsed:this.isSitesCollapsed(),extended:this.isSitesExtended},tabsW:0,tabsDirty:false,tabs:{collapsed:this.isTabsCollapsed(),extended:this.isTabsExtended()},toolsW:0,toolsDirty:false,tools:{collapsed:this.isToolsCollapsed(),extended:this.isToolsExtended()},offCanvasWidth:0,moreW:0};var c=b("#o_offcanvas_right").css("width");if(c){this.state.offCanvasWidth=parseInt(c.replace(/[^\d.]/g,""));this.initListners();this.calculateWidth();this.optimize()}};a.prototype.initListners=function(){b(window).resize(b.proxy(this.onResizeCallback,this));b(document).on("oo.nav.sites.modified",b.proxy(function(){this.state.sitesDirty=true},this));b(document).on("oo.nav.tabs.modified",b.proxy(function(){this.state.tabsDirty=true},this));b(document).on("oo.nav.tools.modified",b.proxy(function(){this.state.toolsDirty=true},this));b(document).on("oo.dom.replacement.after",b.proxy(this.onDOMreplacementCallback,this));b(window).on("orientationchange",b.proxy(this.hideRight,this));b("#o_navbar_right-toggle").on("click",b.proxy(this.toggleRight,this));b("#o_offcanvas_right .o_offcanvas_close").on("click",b.proxy(this.hideRight,this));b("#o_navbar_more").on("shown.bs.dropdown",this.onDropdownShown);b("#o_navbar_more").on("hidden.bs.dropdown",this.onDropdownHidden)};a.prototype.onResizeCallback=function(){if(!this.state.busy){this.state.busy=true;this.calculateWidth();this.optimize();this.state.busy=false}};a.prototype.onDOMreplacementCallback=function(){if(!this.state.busy&&(this.state.sitesDirty||this.state.tabsDirty||this.state.toolsDirty)){this.state.busy=true;this.cleanupMoreDropdown();this.calculateWidth();this.optimize();this.state.sitesDirty=false;this.state.tabsDirty=false;this.state.toolsDirty=false;this.state.busy=false}};a.prototype.onDropdownShown=function(c){var f=b("#o_navbar_more .dropdown-menu");if(f.length){var d=f.offset().left;if(d<0){f.removeClass("dropdown-menu-right")}}};a.prototype.onDropdownHidden=function(c){var d=b("#o_navbar_more .dropdown-menu");d.addClass("dropdown-menu-right")};a.prototype.calculateWidth=function(){var c=b("#o_navbar_container .o_navbar-collapse");this.state.navbarW=c.innerWidth();this.state.brandW=b(".o_navbar-brand").outerWidth(true);this.state.sitesW=this.getSites().outerWidth(true);this.state.tabsW=this.getTabs().outerWidth(true);this.state.toolsW=this.getTools().outerWidth(false);this.state.moreW=b("#o_navbar_more:visible").outerWidth(true)};a.prototype.getOverflow=function(c){var d=this.state.navbarW;d-=this.state.sitesW;d-=this.state.tabsW;d-=this.state.toolsW;d-=this.state.brandW;d-=this.state.moreW;d-=25;return -d};a.prototype.optimize=function(h){var c=this.getOverflow();var k=this.getSites();var l=this.getTabs();var g=this.getTools();var n=this.getMoreDropdown();var f=this.getOffcanvasRight();this.updateState();while(c>0&&(!this.state.tabs.collapsed||!this.state.sites.collapsed||!this.state.tools.collapsed)){if(!this.state.tabs.collapsed){this.collapse(l,n,"li","o_dropdown_tab")}else{if(!this.state.sites.collapsed){this.collapse(k,n,"li","o_dropdown_site")}else{if(!this.state.tools.collapsed){this.collapse(g,f,".o_navbar_tool:not(#o_navbar_imclient, #o_navbar_search_opener, #o_navbar_my_menu)","o_tool_right")}}}this.calculateWidth();c=this.getOverflow();this.updateState()}while(c<0&&(!this.state.tabs.extended||!this.state.sites.extended||!this.state.tools.extended)){if(!this.state.tools.extended){var m=this.extend(f,g.children("#o_navbar_imclient, #o_navbar_search_opener, #o_navbar_my_menu").first(),".o_tool_right","o_tool_right",true);if(!m){break}}if(!this.state.sites.extended){var j=this.extend(n,k,"li","o_dropdown_site");if(!j){break}}else{if(!this.state.tabs.extended){var d=this.extend(n,l,"li","o_dropdown_tab");if(!d){break}}}this.calculateWidth();c=this.getOverflow();this.updateState()}if(this.state.sites.extended&&this.state.tabs.extended){var i=b("#o_navbar_more");i.css("display","none")}this.checkToolsOrder()};a.prototype.updateState=function(){this.state.sites.collapsed=this.isSitesCollapsed();this.state.sites.extended=this.isSitesExtended();this.state.tabs.collapsed=this.isTabsCollapsed();this.state.tabs.extended=this.isTabsExtended();this.state.tools.collapsed=this.isToolsCollapsed();this.state.tools.extended=this.isToolsExtended()};a.prototype.collapse=function(g,d,c,f){var e=g.find(c);if(e.length){e=e.last()}if(e.length){f&&e.addClass(f);if(d){e.prependTo(d)}}this.updateDropdownToggle(g)};a.prototype.extend=function(g,d,c,i,f){var e=g.find(c);if(e.length){e=e.first()}var j=false;if(e.length){if(d){if(f){d.before(e)}else{e.appendTo(d)}this.updateDropdownToggle(g);this.calculateWidth();var h=this.getOverflow();if(h>0){e.prependTo(g)}else{i&&e.removeClass(i);j=true}}}this.updateDropdownToggle(g);return j};a.prototype.updateDropdownToggle=function(c){var d=c.parents(".o_dropdown_toggle");if(!d.length){return}if(c.children().length){d.css("display","block")}else{d.css("display","none")}};a.prototype.addExtraElements=function(){var d=b("#o_navbar_container .o_navbar-collapse");var c=b("#o_navbar_more");if(c.length==0){c=b('<ul id="o_navbar_more" class="nav o_navbar-nav o_dropdown_toggle"><li><a class="dropdown-toggle" data-toggle="dropdown" href="#"">'+o_info.i18n_topnav_more+' <b class="caret"></b></a><ul class="dropdown-menu dropdown-menu-right"></ul></li></ul>');c.appendTo(d)}this.getSites().append('<li class="divider o_dropdown_site"></li>');b("#o_navbar_help .o_icon, #o_navbar_print .o_icon").addClass("o_icon-fw")};a.prototype.cleanupMoreDropdown=function(){if(!this.state.sitesDirty){var f=this.getSites();var d=this.getMoreDropdown().children(".o_dropdown_site");d.appendTo(f)}else{this.getSites().append('<li class="divider o_dropdown_site"></li>')}if(!this.state.tabsDirty){var e=this.getTabs();var c=this.getMoreDropdown().children(".o_dropdown_tab");c.prependTo(e)}this.getMoreDropdown().empty()};a.prototype.checkToolsOrder=function(){var f=this.getTools();var e=f.find("#o_navbar_help");var d=f.find("#o_navbar_print");var c=f.find("#o_navbar_imclient");if(c&&d){c.after(d)}if(c&&e){c.after(e)}};a.prototype.showRight=function(){if(!this.isOffcanvasVisible()&&!this.offcanvasTransitioning){this.offcanvasTransitioning=true;var d=this;var c=b("#o_offcanvas_right");c.show().transition({x:-d.state.offCanvasWidth},function(){d.offcanvasTransitioning=false;b("body").addClass("o_offcanvas_right_visible");var e=b.proxy(d.hideRightOnClick,d);setTimeout(function(){b("html").on("click",e)},10)})}};a.prototype.hideRightOnClick=function(c){if("INPUT"!=c.target.nodeName){this.hideRight()}};a.prototype.hideRight=function(){if(this.isOffcanvasVisible()&&!this.offcanvasTransitioning){this.offcanvasTransitioning=true;b("html").off("click",b.proxy(this.hideRight,this));var d=this;var c=b("#o_offcanvas_right");c.transition({x:d.state.offCanvasWidth},function(){d.offcanvasTransitioning=false;c.hide();b("body").removeClass("o_offcanvas_right_visible")})}};a.prototype.toggleRight=function(){if(this.isOffcanvasVisible()){this.hideRight()}else{this.showRight()}};a.prototype.isOffcanvasVisible=function(){return b("#o_offcanvas_right:visible").length};a.prototype.getSites=function(){return b("#o_navbar_container .o_navbar_sites")};a.prototype.getTabs=function(){return b("#o_navbar_container .o_navbar_tabs")};a.prototype.getTools=function(){return b("#o_navbar_container #o_navbar_tools_permanent")};a.prototype.getMoreDropdown=function(){return b("#o_navbar_more .dropdown-menu")};a.prototype.getOffcanvasRight=function(){return b("#o_offcanvas_right_container .o_navbar-right")};a.prototype.isSitesCollapsed=function(){return !this.getSites().children("li").not(".divider").length};a.prototype.isSitesExtended=function(){return !this.getMoreDropdown().children(".o_dropdown_site").not(".divider").length};a.prototype.isTabsCollapsed=function(){return !this.getTabs().children("li").length};a.prototype.isTabsExtended=function(){return !this.getMoreDropdown().children(".o_dropdown_tab").length};a.prototype.isToolsCollapsed=function(){return !this.getTools().children(".o_navbar_tool").not("#o_navbar_imclient, #o_navbar_search_opener, #o_navbar_my_menu").length};a.prototype.isToolsExtended=function(){return !this.getOffcanvasRight().children(".o_tool_right").length};b(document).ready(function(){var d=b("#o_navbar_wrapper");if(d){var c=new a();window.OPOL.navbar=c}})}(jQuery);+function(b){b.fn.ooBgCarrousel=function(){return new a()};var a=function(){};a.prototype.initCarrousel=function(g){this.settings=b.extend({query:null,images:[],shuffle:false,shuffleFirst:false,durationshow:5000,durationout:500,durationin:500,easeout:"ease",easein:"ease"},g);this.pos=null;if(this.settings.query==null||this.settings.images.length==0){return}this.initialImage=this.settings.images[0];if(this.settings.shuffle){var f=this.settings.images;for(var d,c,e=f.length;e;d=parseInt(Math.random()*e),c=f[--e],f[e]=f[d],f[d]=c){}}if(this.settings.shuffleFirst){this._replaceImage()}this.rotate()};a.prototype.rotate=function(){setTimeout(b.proxy(this._hideCurrent,this),this.settings.durationshow)};a.prototype._hideCurrent=function(){var c=b(this.settings.query);if(c&&c.size()>0){c.transition({opacity:0,duration:this.settings.durationout,easing:this.settings.easeout},b.proxy(this._showNext,this))}};a.prototype._replaceImage=function(d){if(!d){d=b(this.settings.query)}if(d&&d.size()>0){this.newImg="";this.oldImg="";if(this.pos==null){this.pos=1;this.oldImg=this.initialImage}else{this.oldImg=this.settings.images[this.pos];this.pos++;if(this.settings.images.length==this.pos){this.pos=0}}this.newImg=this.settings.images[this.pos];var c=d.css("background-image");if(c.indexOf(this.oldImg)==-1){d.transition({opacity:1,duration:0});return}var e=c.replace(this.oldImg,this.newImg);d.css("background-image",e)}};a.prototype._showNext=function(){var c=b(this.settings.query);this._replaceImage(c);c.transition({opacity:1,duration:this.settings.durationin,easing:this.settings.easein},b.proxy(this.rotate,this))}}(jQuery);!function(a){function b(){function b(a){"remove"===a&&this.each(function(a,b){var c=e(b);c&&c.remove()}),this.find("span.mceEditor,div.mceEditor").each(function(a,b){var c=tinymce.get(b.id.replace(/_parent$/,""));c&&c.remove()})}function d(a){var c,d=this;if(null!=a)b.call(d),d.each(function(b,c){var d;(d=tinymce.get(c.id))&&d.setContent(a)});else if(d.length>0&&(c=tinymce.get(d[0].id)))return c.getContent()}function e(a){var b=null;return a&&a.id&&g.tinymce&&(b=tinymce.get(a.id)),b}function f(a){return!!(a&&a.length&&g.tinymce&&a.is(":tinymce"))}var h={};a.each(["text","html","val"],function(b,g){var i=h[g]=a.fn[g],j="text"===g;a.fn[g]=function(b){var g=this;if(!f(g))return i.apply(g,arguments);if(b!==c)return d.call(g.filter(":tinymce"),b),i.apply(g.not(":tinymce"),arguments),g;var h="",k=arguments;return(j?g:g.eq(0)).each(function(b,c){var d=e(c);h+=d?j?d.getContent().replace(/<(?:"[^"]*"|'[^']*'|[^'">])*>/g,""):d.getContent({save:!0}):i.apply(a(c),k)}),h}}),a.each(["append","prepend"],function(b,d){var g=h[d]=a.fn[d],i="prepend"===d;a.fn[d]=function(a){var b=this;return f(b)?a!==c?("string"==typeof a&&b.filter(":tinymce").each(function(b,c){var d=e(c);d&&d.setContent(i?a+d.getContent():d.getContent()+a)}),g.apply(b.not(":tinymce"),arguments),b):void 0:g.apply(b,arguments)}}),a.each(["remove","replaceWith","replaceAll","empty"],function(c,d){var e=h[d]=a.fn[d];a.fn[d]=function(){return b.call(this,d),e.apply(this,arguments)}}),h.attr=a.fn.attr,a.fn.attr=function(b,g){var i=this,j=arguments;if(!b||"value"!==b||!f(i))return g!==c?h.attr.apply(i,j):h.attr.apply(i,j);if(g!==c)return d.call(i.filter(":tinymce"),g),h.attr.apply(i.not(":tinymce"),j),i;var k=i[0],l=e(k);return l?l.getContent({save:!0}):h.attr.apply(a(k),j)}}var c,d,e,f=[],g=window;a.fn.tinymce=function(c){function h(){var d=[],f=0;e||(b(),e=!0),l.each(function(a,b){var e,g=b.id,h=c.oninit;g||(b.id=g=tinymce.DOM.uniqueId()),tinymce.get(g)||(e=new tinymce.Editor(g,c,tinymce.EditorManager),d.push(e),e.on("init",function(){var a,b=h;l.css("visibility",""),h&&++f==d.length&&("string"==typeof b&&(a=-1===b.indexOf(".")?null:tinymce.resolve(b.replace(/\.\w+$/,"")),b=tinymce.resolve(b)),b.apply(a||tinymce,d))}))}),a.each(d,function(a,b){b.render()})}var i,j,k,l=this,m="";if(!l.length)return l;if(!c)return window.tinymce?tinymce.get(l[0].id):null;if(l.css("visibility","hidden"),g.tinymce||d||!(i=c.script_url))1===d?f.push(h):h();else{d=1,j=i.substring(0,i.lastIndexOf("/")),-1!=i.indexOf(".min")&&(m=".min"),g.tinymce=g.tinyMCEPreInit||{base:j,suffix:m},-1!=i.indexOf("gzip")&&(k=c.language||"en",i=i+(/\?/.test(i)?"&":"?")+"js=true&core=true&suffix="+escape(m)+"&themes="+escape(c.theme||"modern")+"&plugins="+escape(c.plugins||"")+"&languages="+(k||""),g.tinyMCE_GZ||(g.tinyMCE_GZ={start:function(){function b(a){tinymce.ScriptLoader.markDone(tinymce.baseURI.toAbsolute(a))}b("langs/"+k+".js"),b("themes/"+c.theme+"/theme"+m+".js"),b("themes/"+c.theme+"/langs/"+k+".js"),a.each(c.plugins.split(","),function(a,c){c&&(b("plugins/"+c+"/plugin"+m+".js"),b("plugins/"+c+"/langs/"+k+".js"))})},end:function(){}}));var n=document.createElement("script");n.type="text/javascript",n.onload=n.onreadystatechange=function(b){b=b||window.event,2===d||"load"!=b.type&&!/complete|loaded/.test(n.readyState)||(tinymce.dom.Event.domLoaded=1,d=2,c.script_loaded&&c.script_loaded(),h(),a.each(f,function(a,b){b()}))},n.src=i,document.body.appendChild(n)}return l},a.extend(a.expr[":"],{tinymce:function(a){var b;return a.id&&"tinymce"in window&&(b=tinymce.get(a.id),b&&b.editorManager===tinymce)?!0:!1}})}(jQuery);OPOL={};var o2c=0;var o3c=new Array();o_info.guibusy=false;o_info.linkbusy=false;o_info.debug=true;var BLoader={_ajaxLoadedJS:new Array(),_isAlreadyLoadedJS:function(b){var a=true;jQuery("head script[src]").each(function(d,c){if(jQuery(c).attr("src").indexOf(b)!=-1){a=false}});if(jQuery.inArray(b,this._ajaxLoadedJS)!=-1){a=false}return !a},loadJS:function(b,c,a){if(!this._isAlreadyLoadedJS(b)){if(o_info.debug){o_log("BLoader::loadJS: loading ajax::"+a+" url::"+b)}if(a){jQuery.ajax(b,{async:false,dataType:"script",cache:true,success:function(d,f,e){}});this._ajaxLoadedJS.push(b)}else{jQuery.getScript(b)}if(o_info.debug){o_log("BLoader::loadJS: loading DONE url::"+b)}}else{if(o_info.debug){o_log("BLoader::loadJS: already loaded url::"+b)}}},executeGlobalJS:function(jsString,contextDesc){try{if(window.execScript){window.execScript(jsString)}else{window.eval(jsString)}}catch(e){if(window.console){console.log(contextDesc,"cannot execute js",jsString)}if(o_info.debug){o_logerr("BLoader::executeGlobalJS: Error when executing JS code in contextDesc::"+contextDesc+' error::"'+showerror(e)+" for: "+escape(jsString))}if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","BLoader::executeGlobalJS: Error when executing JS code in contextDesc::"+contextDesc+' error::"'+showerror(e)+" for: "+escape(jsString),"functions.js::BLoader::executeGlobalJS::"+contextDesc)}if(window.location.href.indexOf("o_winrndo")!=-1){window.location.reload()}else{window.location.href=window.location.href+(window.location.href.indexOf("?")!=-1?"&":"?")+"o_winrndo=1"}}},loadCSS:function(b,o,q){var r=window.document;try{if(r.createStyleSheet){var j=r.styleSheets;var d=0;var p=0;for(i=0;i<j.length;i++){var m=j[i];var g=m.href;if(g==b){d++;if(m.disabled){m.disabled=false;return}else{if(o_info.debug){o_logwarn("BLoader::loadCSS: style: "+b+" already in document and not disabled! (duplicate add)")}return}}if(m.id=="o_theme_css"){p=i}}if(d>1&&o_info.debug){o_logwarn("BLoader::loadCSS: apply styles: num of stylesheets found was not 0 or 1:"+d)}if(q){p=j.length}var f=r.createStyleSheet(b,p)}else{var c=jQuery("#"+o);if(c&&c.size()>0){if(o_info.debug){o_logwarn("BLoader::loadCSS: stylesheet already found in doc when trying to add:"+b+", with id "+o)}return}else{var a=jQuery('<link id="'+o+'" rel="stylesheet" type="text/css" href="'+b+'">');if(q){a.insertBefore(jQuery("#o_fontSize_css"))}else{a.insertBefore(jQuery("#o_theme_css"))}}}}catch(n){if(window.console){console.log(n)}if(o_info.debug){o_logerr("BLoader::loadCSS: Error when loading CSS from URL::"+b)}if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","BLoader::loadCSS: Error when loading CSS from URL::"+b,"functions.js::BLoader::loadCSS")}}},unLoadCSS:function(a,m){var n=window.document;try{if(n.createStyleSheet){var f=n.styleSheets;var d=0;var o=a;var b=window.location.href.substring(0,window.location.href.indexOf("/",8));if(a.indexOf(b)==0){o=a.substring(b.length)}for(i=0;i<f.length;i++){var g=f[i].href;if(g==a||g==o){d++;if(!f[i].disabled){f[i].disabled=true}else{if(o_info.debug){o_logwarn("stylesheet: when removing: matching url, but already disabled! url:"+g)}}}}if(d!=1&&o_info.debug){o_logwarn("stylesheet: when removeing: num of stylesheets found was not 1:"+d)}}else{var c=jQuery("#"+m);if(c){c.href="";c.remove();c=null;return}else{if(o_info.debug){o_logwarn("no link with id found to remove, id:"+m+", url "+a)}}}}catch(j){if(o_info.debug){o_logerr("BLoader::unLoadCSS: Error when unloading CSS from URL::"+a)}if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","BLoader::unLoadCSS: Error when unloading CSS from URL::"+a,"functions.js::BLoader::loadCSS")}}}};var BFormatter={formatLatexFormulas:function(b){try{if(window.jsMath){if(jsMath.loaded&&jsMath.tex2math&&jsMath.tex2math.loaded){jsMath.Process()}else{jsMath.Autoload.LoadJsMath();setTimeout(function(){BFormatter.formatLatexFormulas(b)},100)}}}catch(a){if(window.console){console.log("error in BFormatter.formatLatexFormulas: ",a)}}}};function o_init(){try{o_getMainWin().o_afterserver()}catch(a){if(o_info.debug){o_log("error in o_init: "+showerror(a))}}}function o_initEmPxFactor(){o_info.emPxFactor=jQuery("#o_width_1em").width();if(o_info.emPxFactor==0||o_info.emPxFactor=="undefined"){o_info.emPxFactor=12;if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","Could not read with of element b_width_1em, set o_info.emPxFactor to 12","functions.js")}}}function o_getMainWin(){try{if(window.OPOL){return window}else{if(window.opener&&window.opener.OPOL){return window.opener}}}catch(a){if(o_info.debug){o_logerr('Exception while getting main window. rror::"'+showerror(a))}if(window.console){console.log('Exception while getting main window. rror::"'+showerror(a),"functions.js");console.log(a)}}throw"Can not find main OpenOLAT window"}function o_beforeserver(){o_info.linkbusy=true;showAjaxBusy();if(window.suppressOlatOnUnloadOnce){window.suppressOlatOnUnloadOnce=false}else{if(window.olatonunload){olatonunload()}}}function o_afterserver(){o2c=0;o_info.linkbusy=false;removeAjaxBusy()}function o2cl(){if(o_info.linkbusy){return false}else{var a=(o2c==0||confirm(o_info.dirty_form));if(a){o_beforeserver()}return a}}function o2cl_noDirtyCheck(){if(o_info.linkbusy){return false}else{o_beforeserver();return true}}function o2cl_secure(){try{if(o2cl()){return true}else{return false}}catch(a){return false}}function o3cl(d){if(o_info.linkbusy){return false}else{var b=o3c1.indexOf(d)>-1;var a=(b&&o3c1.length>1)||o3c1.length>0;var c=(!a||confirm(o_info.dirty_form));if(c){o_beforeserver()}return c}}function o_onc(a){var b=a.responseText;BLoader.executeGlobalJS("o_info.last_o_onc="+b+";","o_onc");o_ainvoke(o_info.last_o_onc,false)}function o_allowNextClick(){o_info.linkbusy=false;removeAjaxBusy()}function removeBusyAfterDownload(c,b,a){o2c=0;o_afterserver()}Array.prototype.search=function(c,d){var a=this.length;for(var b=0;b<a;b++){if(this[b].constructor==Array){if(this[b].search(c,d)){return true;break}}else{if(d){if(this[b].indexOf(c)!=-1){return true;break}}else{if(this[b]==c){return true;break}}}}return false};if(!Function.prototype.curry){Function.prototype.curry=function(){if(arguments.length<1){return this}var a=this;var b=Array.prototype.slice.call(arguments);return function(){return a.apply(this,b.concat(Array.prototype.slice.call(arguments)))}}}if(!Array.prototype.indexOf){Array.prototype.indexOf=function(c){if(this==null){throw new TypeError()}var d=Object(this);var a=d.length>>>0;if(a===0){return -1}var e=0;if(arguments.length>1){e=Number(arguments[1]);if(e!=e){e=0}else{if(e!=0&&e!=Infinity&&e!=-Infinity){e=(e>0||-1)*Math.floor(Math.abs(e))}}}if(e>=a){return -1}var b=e>=0?e:Math.max(a-Math.abs(e),0);for(;b<a;b++){if(b in d&&d[b]===c){return b}}return -1}}var b_onDomReplacementFinished_callbacks=new Array();function b_AddOnDomReplacementFinishedCallback(a){var b=jQuery(document).ooLog().isDebugEnabled();if(b){jQuery(document).ooLog("debug","callback stack size: "+b_onDomReplacementFinished_callbacks.length,"functions.js ADD")}if(b&&b_onDomReplacementFinished_callbacks.toSource){jQuery(document).ooLog("debug","stack content"+b_onDomReplacementFinished_callbacks.toSource(),"functions.js ADD")}b_onDomReplacementFinished_callbacks.push(a);if(b){jQuery(document).ooLog("debug","push to callback stack, func: "+a,"functions.js ADD")}}var b_changedDomEl=new Array();function b_AddOnDomReplacementFinishedUniqueCallback(a){if(a.constructor==Array){if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","add: its an ARRAY! ","functions.js ADD")}if(b_onDomReplacementFinished_callbacks.search(a[0])){if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","push to callback stack, already there!!: "+a[0],"functions.js ADD")}return}}b_AddOnDomReplacementFinishedCallback(a)}var o_debug_trid=0;function o_ainvoke(N){if(N==undefined){return}o_info.inainvoke=true;var I=N.cmdcnt;if(I>0){jQuery(document).trigger("oo.dom.replacement.before");b_changedDomEl=new Array();if(o_info.debug){o_debug_trid++}var y=N.cmds;for(var T=0;T<I;T++){var J=y[T];var A=J.cmd;var R=J.cda;var U=J.w;var c=this.window;var K;if(c){switch(A){case 1:var M=R.e;BLoader.executeGlobalJS(M,"o_ainvoker::jsexec");if(o_info.debug){o_log("c1: execute jscode: "+M)}case 2:var u=R.cc;var F=R.cps;for(var Q=0;Q<u;Q++){var m=F[Q];var h=m.cid;var P=m.cidvis;var H=m.cw;var x=m.hfrag;var O=m.jsol;var g=m.hdr;if(o_info.debug){o_log("c2: redraw: "+m.cname+" ("+h+") "+m.hfragsize+" bytes, listener(s): "+m.clisteners)}var W=g+"\n\n"+x;var C="";var S=false;var E="o_c"+h;var B=jQuery("#"+E);if(B==null||B.length==0){E="o_fi"+h;B=jQuery("#"+E);S=true}if(B!=null){var w=jQuery("div.o_richtext_mce textarea",B);for(var L=0;L<w.length;L++){try{var a=jQuery(w.get(L)).attr("id");if(typeof top.tinymce!=undefined){top.tinymce.remove("#"+a)}}catch(Z){if(window.console){console.log(Z)}}}if(P){B.css("display","")}else{B.css("display","none")}if(S||!H){B.replaceWith(W)}else{try{B.empty().html(W);if(W.length>0&&B.get(0).innerHTML==""){B.get(0).innerHTML=W}}catch(Z){if(window.console){console.log(Z)}if(window.console){console.log("Fragment",W)}}b_changedDomEl.push(E)}B=null;if(C!=""){C.each(function(e){BLoader.executeGlobalJS(e,"o_ainvoker::inscripts")})}if(O!=""){BLoader.executeGlobalJS(O,"o_ainvoker::jsol")}}}break;case 3:c.o2c=0;var X=R.rurl;c.o_afterserver();c.document.location.replace(X);break;case 5:c.o2c=0;var X=R.rurl;c.o_afterserver();c.document.location.replace(X);break;case 6:c.o2c=0;c.o_afterserver();break;case 7:var o=c.document.location;var z=o.protocol+"//"+o.hostname;if(o.port!=""){z+=":"+o.port}var v=R.cssrm;for(Q=0;Q<v.length;Q++){var D=v[Q];var G=D.id;var f=z+D.url;BLoader.unLoadCSS(f,G);if(o_info.debug){o_log("c7: rm css: id:"+G+" ,url:'"+f+"'")}}var V=R.cssadd;for(k=0;k<V.length;k++){var D=V[k];var G=D.id;var f=z+D.url;var n=D.pt;BLoader.loadCSS(f,G,n);if(o_info.debug){o_log("c7: add css: id:"+G+" ,url:'"+f+"'")}}var p=R.jsadd;for(l=0;l<p.length;l++){var D=p[l];var Y=D.before;if(jQuery.type(Y)==="string"){BLoader.executeGlobalJS(Y,"o_ainvoker::preJsAdd")}var f=D.url;var q=D.enc;if(jQuery.type(f)==="string"){BLoader.loadJS(f,q,true)}if(o_info.debug){o_log("c7: add js: "+f)}}break;default:if(o_info.debug){o_log("?: unknown command "+A)}if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","Error in o_ainvoke(), ?: unknown command "+A,"functions.js")}break}}else{if(o_info.debug){o_log("could not find window??")}if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","Error in o_ainvoke(), could not find window??","functions.js")}}}var b=b_onDomReplacementFinished_callbacks.length;if(b_onDomReplacementFinished_callbacks.toSource&&jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","stack content"+b_onDomReplacementFinished_callbacks.toSource(),"functions.js")}for(mycounter=0;b>mycounter;mycounter++){if(mycounter>50){if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","Stopped executing DOM replacement callback functions - to many functions::"+b_onDomReplacementFinished_callbacks.length,"functions.js")}break}if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","Stacksize before shift: "+b_onDomReplacementFinished_callbacks.length,"functions.js")}var s=b_onDomReplacementFinished_callbacks.shift();if(typeof s.length==="number"){if(s[0]=="glosshighlighter"){var d=s[1];if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","arr fct: "+d,"functions.js")}s=d}}if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","Executing DOM replacement callback function #"+mycounter+" with timeout funct::"+s,"functions.js")}s();if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","Stacksize after timeout: "+b_onDomReplacementFinished_callbacks.length,"functions.js")}}jQuery(document).trigger("oo.dom.replacement.after")}o_info.inainvoke=false}function clearAfterAjaxIframeCall(){if(o_info.linkbusy){o_afterserver();showMessageBox("info",o_info.i18n_noresponse_title,o_info.i18n_noresponse,undefined)}}function showAjaxBusy(){setTimeout(function(){if(o_info.linkbusy){try{if(jQuery("#o_ajax_busy_backdrop").length==0){jQuery("#o_body").addClass("o_ajax_busy");jQuery("#o_ajax_busy").modal({show:true,backdrop:"static",keyboard:"false"});jQuery("#o_ajax_busy").after('<div id="o_ajax_busy_backdrop" class="modal-backdrop in"></div>');jQuery("#o_ajax_busy>.modal-backdrop").remove();jQuery("#o_ajax_busy_backdrop").css({"z-index":1200})}}catch(a){if(window.console){console.log(a)}}}},700)}function removeAjaxBusy(){try{jQuery("#o_body").removeClass("o_ajax_busy");jQuery("#o_ajax_busy_backdrop").remove();jQuery("#o_ajax_busy").modal("hide")}catch(a){if(window.console){console.log(a)}}}function setFormDirty(c){o2c=1;var a=document.getElementById(c);if(a!=null){var b=a.olat_fosm_0;if(b==null){b=a.olat_fosm}if(b){b.className="btn o_button_dirty"}}else{if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","Error in setFormDirty, myForm was null for formId="+c,"functions.js")}}}function contextHelpWindow(a){helpWindow=window.open(a,"HelpWindow","height=760, width=940, left=0, top=0, location=no, menubar=no, resizable=yes, scrollbars=yes, toolbar=no");helpWindow.focus()}function o_openPopUp(b,d,c,a,e){attributes="height="+a+", width="+c+", resizable=yes, scrollbars=yes, left=100, top=100, ";if(e){attributes+="location=yes, menubar=yes, status=yes, toolbar=yes"}else{attributes+="location=no, menubar=no, status=no, toolbar=no"}var f=window.open(b,d,attributes);f.focus();if(o_info.linkbusy){o_afterserver()}}function b_handleFileUploadFormChange(e,b,d){var f=e.value;slashPos=f.lastIndexOf("/");if(slashPos!=-1){f=f.substring(slashPos+1)}slashPos=f.lastIndexOf("\\");if(slashPos!=-1){f=f.substring(slashPos+1)}b.value=f;if(d){d.className="o_button_dirty"}var c=e.form.elements;for(i=0;i<c.length;i++){var a=c[i];if(a.name==b.name&&i+1<c.length){c[i+1].focus()}}}function gotonode(a){try{if(typeof o_activateCourseNode!="undefined"){o_activateCourseNode(a)}else{if(opener&&typeof opener.o_activateCourseNode!="undefined"){opener.o_activateCourseNode(a)}else{if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","Error in gotonode(), could not find main window","functions.js")}}}}catch(b){alert("Goto node error:"+b);if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","Error in gotonode()::"+b.message,"functions.js")}}}function o_openUriInMainWindow(b){try{var a=o_getMainWin();a.focus();a.location.replace(b)}catch(c){showMessageBox("error","Error","Can not find main OpenOLAT window to open URL.")}}function o_viewportHeight(){var a=jQuery(document).height();if(a>0){return a}else{return 600}}OPOL.getMainColumnsMaxHeight=function(){var j=0,f=0,a=0,c=0,h=0,b,g=jQuery("#o_main_left_content"),e=jQuery("#o_main_right_content"),d=jQuery("#o_main_center_content");if(g!="undefined"&&g!=null){j=g.outerHeight(true)}if(e!="undefined"&&e!=null){f=e.outerHeight(true)}if(d!="undefined"&&d!=null){a=d.outerHeight(true)}c=(j>f?j:f);c=(c>a?c:a);if(c>0){return c}b=jQuery("#o_main");if(b!="undefined"&&b!=null){h=b.height()}if(b>0){return b}return o_viewportHeight()};OPOL.adjustHeight=function(){try{var a=0;col1=jQuery("#o_main_left_content").outerHeight(true);col2=jQuery("#o_main_right_content").outerHeight(true);col3=jQuery("#o_main_center_content").outerHeight(true);a=Math.max(col1,col2,col3);if(col1!=null){jQuery("#o_main_left").css({"min-height":a+"px"})}if(col2!=null){jQuery("#o_main_right").css({"min-height":a+"px"})}if(col3!=null){jQuery("#o_main_center").css({"min-height":a+"px"})}}catch(b){if(window.console){console.log(b)}}};jQuery(window).resize(function(){clearTimeout(o_info.resizeId);o_info.resizeId=setTimeout(function(){jQuery(document).trigger("oo.window.resize.after")},500)});jQuery(document).on("oo.window.resize.after",OPOL.adjustHeight);jQuery(document).on("oo.dom.replacement.after",OPOL.adjustHeight);jQuery().ready(OPOL.adjustHeight);function o_scrollToElement(a){try{jQuery("html, body").animate({scrollTop:jQuery(a).offset().top},333)}catch(b){}}function o_popover(c,b,a){if(typeof(a)==="undefined"){a="bottom"}jQuery("#"+c).popover({placement:a,html:true,trigger:"click",container:"body",content:function(){return jQuery("#"+b).clone().html()}}).on("shown.bs.popover",function(){var d=function(f){jQuery("#"+c).popover("hide");jQuery("body").unbind("click",d)};setTimeout(function(){jQuery("body").on("click",d)},5)})}function o_popoverWithTitle(d,c,b,a){if(typeof(a)==="undefined"){a="bottom"}return jQuery("#"+d).popover({placement:a,html:true,title:b,trigger:"click",container:"body",content:function(){return jQuery("#"+c).clone().html()}}).on("shown.bs.popover",function(){var e=function(f){jQuery("#"+d).popover("destroy");jQuery("body").unbind("click",e)};setTimeout(function(){jQuery("body").on("click",e)},5)})}function o_shareLinkPopup(d,c,b){if(typeof(b)==="undefined"){b="top"}var a=jQuery("#"+d);a.popover({placement:b,html:true,trigger:"click",container:"body",content:c}).on("shown.bs.popover",function(){var e=function(f){if(jQuery(f.target).data("toggle")!=="popover"&&jQuery(f.target).parents(".popover.in").length===0){jQuery("#"+d).popover("hide");jQuery("body").unbind("click",e)}};setTimeout(function(){jQuery("body").on("click",e)},5)});a.attr("title",a.attr("data-original-title"))}function o_QRCodePopup(d,c,b){if(typeof(b)==="undefined"){b="top"}var a=jQuery("#"+d);a.popover({placement:b,html:true,trigger:"click",container:"body",content:'<div id="'+d+'_pop" class="o_qrcode"></div>'}).on("shown.bs.popover",function(){o_info.qr=o_QRCode(d+"_pop",(jQuery.isFunction(c)?c():c));var e=function(f){if(jQuery(f.target).data("toggle")!=="popover"&&jQuery(f.target).parents(".popover.in").length===0){jQuery("#"+d).popover("hide");jQuery("body").unbind("click",e)}};setTimeout(function(){jQuery("body").on("click",e)},5)}).on("hidden.bs.popover",function(){try{o_info.qr.clear();delete o_info.qr}catch(f){}});a.attr("title",a.attr("data-original-title"))}function o_QRCode(c,b){try{BLoader.loadJS(o_info.o_baseURI+"/js/jquery/qrcodejs/qrcode.min.js","utf8",true);return new QRCode(document.getElementById(c),b)}catch(a){return null}}function b_resizeIframeToMainMaxHeight(f){var d=jQuery("#"+f);if(d!="undefined"&&d!=null){var c=OPOL.getMainColumnsMaxHeight()-110;var b=o_viewportHeight()-100;b=b-d.offset().top;var e=jQuery("#b_footer");if(e!="undefined"&&e!=null){b=b-e.outerHeight(true)}var a=(b>c?b:c);d.height(a)}}var o_debu_oldcn,o_debu_oldtt;function o_debu_show(b,a){if(o_debu_oldcn){o_debu_hide(o_debu_oldcn,o_debu_oldtt)}jQuery(b).addClass("o_dev_m");jQuery(a).show();o_debu_oldtt=a;o_debu_oldcn=b}function o_debu_hide(b,a){jQuery(a).hide();jQuery(b).removeClass("o_dev_m")}function o_dbg_mark(a){var b=jQuery("#"+a);if(b){b.css("background-color","#FCFCB8");b.css("border","3px solid #00F")}}function o_dbg_unmark(a){var b=jQuery("#"+a);if(b){b.css("border","");b.css("background-color","")}}function o_clearConsole(){o_log_all="";o_log(null)}var o_log_all="";function o_log(b){if(b){o_log_all="\n"+o_debug_trid+"> "+b+o_log_all;o_log_all=o_log_all.substr(0,4000)}var a=jQuery("#o_debug_cons");if(a){if(o_log_all.length==4000){o_log_all=o_log_all+"\n... (stripped: to long)... "}a.value=o_log_all}if(!jQuery.type(window.console)==="undefined"){window.console.log(b)}}function o_logerr(a){o_log("ERROR:"+a)}function o_logwarn(a){o_log("WARN:"+a)}function showerror(c){var a="";for(var b in c){a+=b+": "+c[b]+"\n"}return"error detail:\n"+a}function o_ffEvent(e,d,c,h,j){var f,g,b,a;f=document.getElementById(d);g=f.value;f.value=c;b=document.getElementById(h);a=b.value;b.value=j;if(document.forms[e].onsubmit()){document.forms[e].submit()}f.value=g;b.value=a}function o_ffXHREvent(f,e,a,h,j){var c=new Object();c.dispatchuri=a;c.dispatchevent=j;if(arguments.length>5){var g=arguments.length;for(var d=5;d<g;d=d+2){if(g>d+1){c[arguments[d]]=arguments[d+1]}}}var b=jQuery("#"+f).attr("action");jQuery.ajax(b,{type:"GET",data:c,cache:false,dataType:"json",success:function(n,o,m){o_ainvoke(n)},error:function(m,o,n){if(window.console){console.log("Error status",o)}}})}function o_ffXHRNFEvent(b){var a=new Object();jQuery.ajax(b,{type:"GET",data:a,cache:false,dataType:"json",success:function(d,e,c){console.log("Hourra")},error:function(c,e,d){if(window.console){console.log("Error status",e)}}})}function setFlexiFormDirtyByListener(a){setFlexiFormDirty(a.data.formId)}function setFlexiFormDirty(b){var a=o3c.indexOf(b)>-1;if(!a){o3c.push(b)}jQuery("#"+b).each(function(){var c=jQuery(this).data("FlexiSubmit");if(c!=null){jQuery("#"+c).addClass("btn o_button_dirty");o2c=1}})}function o_ffRegisterSubmit(b,a){jQuery("#"+b).data("FlexiSubmit",a)}function showInfoBox(g,d){var c=Math.floor(Math.random()*65536).toString(16);var f='<div id="'+c+'" class="o_alert_info "><div class="alert alert-info clearfix o_sel_info_message"><i class="o_icon o_icon_close"></i><h3><i class="o_icon o_icon_info"></i> '+g+"</h3><p>"+d+"</p></div></div>";var a=jQuery("#o_messages").prepend(f);var e=(d.length>150)?8000:((d.length>70)?6000:4000);var b=function(){jQuery("#"+c).transition({top:"-100%"},333,function(){jQuery("#"+c).remove()})};jQuery("#"+c).show().transition({top:0},333);jQuery("#"+c).click(function(h){b()});o_scrollToElement("#o_top");g=null;d=null;a=null;e=null;setTimeout(function(){try{b()}catch(h){}},8000)}function showMessageBox(b,f,d,a){if(b=="info"){showInfoBox(f,d);return null}else{var c='<div id="myFunctionalModal" class="modal fade" role="dialog"><div class="modal-dialog"><div class="modal-content">';c+='<div class="modal-header"><button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>';c+='<h4 class="modal-title">'+f+"</h4></div>";c+='<div class="modal-body alert ';if("warn"==b){c+="alert-warning"}else{if("error"==b){c+="alert-danger"}else{c+="alert-info"}}c+='"><p>'+d+"</p></div></div></div></div>";jQuery("#myFunctionalModal").remove();jQuery("body").append(c);var e=jQuery("#myFunctionalModal").modal("show").on("hidden.bs.modal",function(g){jQuery("#myFunctionalModal").remove()});o_scrollToElement("#o_top");return e}}function tableFormInjectCommandAndSubmit(a,b,c){document.forms[a].elements.cmd.value=b;document.forms[a].elements.param.value=c;document.forms[a].submit()}function o_table_toggleCheck(d,c){var a=document.forms[d].elements.tb_ms;len=a.length;if(typeof(len)=="undefined"){a.checked=c}else{var b;for(b=0;b<len;b++){a[b].checked=c}}}function onTreeStartDrag(a,b){jQuery(a.target).addClass("o_dnd_proxy")}function onTreeStopDrag(a,b){jQuery(a.target).removeClass("o_dnd_proxy")}function onTreeDrop(g,h){var a=jQuery(h.draggable[0]);var f=jQuery(this);f.css({position:"",width:""});var c=f.droppable("option","endUrl");if(c.lastIndexOf("/")==(c.length-1)){c=c.substring(0,c.length-1)}var e=a.attr("id");var b=e.substring(2,e.length);c+="%3Atnidle%3A"+b;var d=f.attr("id");if(d.indexOf("ds")==0){c+="%3Asne%3Ayes"}else{if(d.indexOf("dt")==0){c+="%3Asne%3Aend"}}jQuery(".ui-droppable").each(function(j,m){jQuery(m).droppable("disable")});frames.oaa0.location.href=c+"/"}function treeAcceptDrop(a){return true}function treeAcceptDrop_notWithChildren(a){var c=false;var b=jQuery(a);var e=b.attr("id");if(e!=undefined&&(e.indexOf("dd")==0||e.indexOf("ds")==0||e.indexOf("dt")==0||e.indexOf("da")==0||e.indexOf("row")==0)){var g=jQuery(this);var j=g.attr("id");var d=e.substring(2,e.length);var f=j.substring(2,j.length);if(d!=f){var h=jQuery("#dd"+d).parents("li");if(h.length>0&&jQuery(h.get(0)).find("#dd"+f).length==0){c=true}}}return c}function treeAcceptDrop_portfolio(b){var d=false;var c=jQuery(b);var f=c.attr("id");if(treeNode_isDragNode(f)){var h=jQuery(this);var n=h.attr("id");var e=f.substring(2,f.length);var g=n.substring(2,n.length);var m=f.indexOf("ds")==0||f.indexOf("dt")==0;if(e!=g){var j=treeNode_portfolioType(c);var a=treeNode_portfolioType(h);if(j=="artefact"){if(a=="page"||a=="struct"||a=="artefact"){d=true}}else{if(j=="struct"){if(a=="page"||a=="struct"){d=true}}else{if(j=="page"){if(a=="map"||a=="page"){d=true}}}}}}return d}function treeNode_portfolioType(e){var c=jQuery(e.get(0));var d=treeNode_portfolioTypes(c);if(d==null){var a=c.parent("a");if(a.length>0){d=treeNode_portfolioTypes(jQuery(a.get(0)))}else{if(c.attr("id").indexOf("ds")==0){var b=c.prev("div");if(b.length>0){d=treeNode_portfolioTypes(b)}}else{if(c.attr("id").indexOf("dt")==0){var b=c.next("div");if(b.length>0){d=treeNode_portfolioTypes(b)}}}}}return d}function treeNode_portfolioTypes(a){if(a.find===undefined){return null}else{if(a.find(".o_ep_icon_struct").length>0||a.hasClass("o_ep_icon_struct")){return"struct"}else{if(a.find(".o_ep_icon_page").length>0||a.hasClass("o_ep_icon_page")){return"page"}else{if(a.find(".o_ep_icon_map").length>0||a.hasClass("o_ep_icon_map")){return"map"}else{if(a.find(".o_ep_artefact").length>0||a.hasClass("o_ep_artefact")){return"artefact"}}}}}return null}function treeNode_isDragNode(a){if(a!=undefined&&(a.indexOf("dd")==0||a.indexOf("ds")==0||a.indexOf("dt")==0||a.indexOf("da")==0||a.indexOf("row")==0)){return true}return false}function o_choice_toggleCheck(c,b){var d=document.forms[c].elements;len=d.length;if(typeof(len)=="undefined"){d.checked=b}else{var a;for(a=0;a<len;a++){if(d[a].type=="checkbox"&&d[a].getAttribute("class")=="o_checkbox"){d[a].checked=b}}}}function b_briefcase_isChecked(c,e){var b;var a=document.getElementById(c);var d=0;for(b=0;a.elements[b];b++){if(a.elements[b].type=="checkbox"&&a.elements[b].name=="paths"&&a.elements[b].checked){d++}}if(d<1){alert(e);return false}return true}function b_briefcase_toggleCheck(d,c){var a=document.getElementById(d);len=a.elements.length;var b;for(b=0;b<len;b++){if(a.elements[b].name=="paths"){a.elements[b].checked=c}}}function o_doPrint(){var d=jQuery("div.o_iframedisplay iframe");if(d.length>0){try{var a=d[0];frames[a.name].focus();frames[a.name].print();return}catch(c){for(i=0;frames.length>i;i++){a=frames[i];if(a.name=="oaa0"){continue}var b=document.getElementsByName(a.name)[0];if(b&&b.getAttribute("class")=="ext-shim"){continue}if(a.name!=""){try{frames[a.name].focus();frames[a.name].print()}catch(c){window.print()}return}}window.print()}}else{window.print()}}function b_attach_i18n_inline_editing(){jQuery("span.o_translation_i18nitem").hover(function(){jQuery(this.firstChild).show();if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","Entered i18nitem::"+this.firstChild,"functions.js:b_attach_i18n_inline_editing()")}},function(){jQuery("a.o_translation_i18nitem_launcher").hide();if(jQuery(document).ooLog().isDebugEnabled()){jQuery(document).ooLog("debug","Leaving i18nitem::"+this,"functions.js:b_attach_i18n_inline_editing()")}});jQuery("a.o_translation_i18nitem_launcher").hover(function(){var a=jQuery(this).parent("span.o_translation_i18nitem");a.effect("highlight")});b_AddOnDomReplacementFinishedCallback(b_attach_i18n_inline_editing)}function b_hideExtMessageBox(){}var BDebugger={_lastDOMCount:0,_lastObjCount:0,_knownGlobalOLATObjects:["o_afterserver","o_onc","o_getMainWin","o_ainvoke","o_info","o_beforeserver","o_ffEvent","o_openPopUp","o_debu_show","o_logwarn","o_dbg_unmark","o_ffRegisterSubmit","o_clearConsole","o_init","o_log","o_allowNextClick","o_dbg_mark","o_debu_hide","o_logerr","o_debu_oldcn","o_debu_oldtt","o_openUriInMainWindow","o_debug_trid","o_log_all"],_countDOMElements:function(){return document.getElementsByTagName("*").length},_countGlobalObjects:function(){var a=0;for(prop in window){a++}return a},logDOMCount:function(){var b=BDebugger;var a=b._countDOMElements();var c=a-b._lastDOMCount;console.log((c>0?"+":"")+c+" \t"+a+" \tDOM element count after DOM replacement");b._lastDOMCount=a;a=null},logGlobalObjCount:function(){var b=BDebugger;var a=b._countGlobalObjects();var c=a-b._lastObjCount;console.log((c>0?"+":"")+c+" \t"+a+" \tGlobal object count after DOM replacement");b._lastObjCount=a;a=null},logGlobalOLATObjects:function(){var b=BDebugger;var a=new Array();for(prop in window){if(prop.indexOf("o_")==0&&b._knownGlobalOLATObjects.indexOf(prop)==-1){a.push(prop)}}if(a.length>0){console.log(a.length+" global OLAT objects found:");a.each(function(c){console.log("\t"+typeof window[c]+" \t"+c)})}},logManagedOLATObjects:function(){var a=BDebugger;if(o_info.objectMap.length>0){console.log(o_info.objectMap.length+" managed OLAT objects found:");o_info.objectMap.eachKey(function(b){var c=o_info.objectMap.get(b);console.log("\t"+typeof c+" \t"+b);return true})}}};/*! * jQuery Transit - CSS3 transitions and transformations * (c) 2011-2014 Rico Sta. Cruz * MIT Licensed. diff --git a/src/test/java/org/olat/course/assessment/manager/UserCourseInformationsManagerTest.java b/src/test/java/org/olat/course/assessment/manager/UserCourseInformationsManagerTest.java index 8f0f97db9a2daf4467f334ea034f7e831d061999..0505275d38538fe477ea8873e532ec7d3f0f9ac8 100644 --- a/src/test/java/org/olat/course/assessment/manager/UserCourseInformationsManagerTest.java +++ b/src/test/java/org/olat/course/assessment/manager/UserCourseInformationsManagerTest.java @@ -145,6 +145,32 @@ public class UserCourseInformationsManagerTest extends OlatTestCase { Assert.assertNotNull(launchDates.get(user2.getKey())); } + @Test + public void getInitialLaunchDates_noIdentites() { + Identity user1 = JunitTestHelper.createAndPersistIdentityAsRndUser("user-launch-7-"); + Identity user2 = JunitTestHelper.createAndPersistIdentityAsRndUser("user-launch-8-"); + Identity user3 = JunitTestHelper.createAndPersistIdentityAsRndUser("user-launch-9-"); + ICourse course = CoursesWebService.createEmptyCourse(user1, "course-launch-dates", "course long name", null); + dbInstance.commitAndCloseSession(); + + userCourseInformationsManager.updateUserCourseInformations(course.getResourceableId(), user1, true); + userCourseInformationsManager.updateUserCourseInformations(course.getResourceableId(), user2, true); + userCourseInformationsManager.updateUserCourseInformations(course.getResourceableId(), user3, true); + dbInstance.commitAndCloseSession(); + + //get all launch dates + Map<Long,Date> launchDates = userCourseInformationsManager.getInitialLaunchDates(course.getResourceableId()); + Assert.assertNotNull(launchDates); + Assert.assertEquals(3, launchDates.size()); + Assert.assertTrue(launchDates.containsKey(user1.getKey())); + Assert.assertNotNull(launchDates.get(user1.getKey())); + Assert.assertTrue(launchDates.containsKey(user2.getKey())); + Assert.assertNotNull(launchDates.get(user2.getKey())); + Assert.assertTrue(launchDates.containsKey(user3.getKey())); + Assert.assertNotNull(launchDates.get(user3.getKey())); + } + + /** * This test is to analyze a red screen 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 534029e7f6ab10bd6184763a4b33bc40c3931f51..349c75300a9bf2703b6c06c7da1e90a54cc2f2ce 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 @@ -224,6 +224,106 @@ public class GTAManagerTest extends OlatTestCase { Assert.assertTrue(assigned.contains("work_2.txt")); } + @Test + public void deleteTaskList() { + Identity coach = JunitTestHelper.createAndPersistIdentityAsRndUser("gta-user-9"); + Identity participant = JunitTestHelper.createAndPersistIdentityAsRndUser("gta-user-10"); + BusinessGroup businessGroup = businessGroupDao.createAndPersist(coach, "gdao", "gdao-desc", -1, -1, false, false, false, false, false); + businessGroupRelationDao.addRole(participant, businessGroup, GroupRole.participant.name()); + dbInstance.commit(); + RepositoryEntry re = JunitTestHelper.createAndPersistRepositoryEntry("", false); + GTACourseNode node = new GTACourseNode(); + node.getModuleConfiguration().setStringValue(GTACourseNode.GTASK_TYPE, GTAType.group.name()); + TaskList tasks = gtaManager.createIfNotExists(re, node); + File taskFile = new File("bg.txt"); + Assert.assertNotNull(tasks); + dbInstance.commit(); + + //select + AssignmentResponse response = gtaManager.selectTask(businessGroup, tasks, node, taskFile); + dbInstance.commitAndCloseSession(); + Assert.assertNotNull(response); + + //check that there is tasks + List<Task> assignedTasks = gtaManager.getTasks(participant, re, node); + Assert.assertNotNull(assignedTasks); + Assert.assertEquals(1, assignedTasks.size()); + + //delete + int numOfDeletedObjects = gtaManager.deleteTaskList(re, node); + Assert.assertEquals(2, numOfDeletedObjects); + dbInstance.commitAndCloseSession(); + + //check that there isn't any tasks + List<Task> deletedAssignedTasks = gtaManager.getTasks(participant, re, node); + Assert.assertNotNull(deletedAssignedTasks); + Assert.assertEquals(0, deletedAssignedTasks.size()); + } + + /** + * Create 2 pseudo nodes in a course, and delete the task of the first node + * and check that the task of second are always there. + * + */ + @Test + public void deleteTaskList_parano() { + Identity coach = JunitTestHelper.createAndPersistIdentityAsRndUser("gta-user-9"); + Identity participant = JunitTestHelper.createAndPersistIdentityAsRndUser("gta-user-10"); + BusinessGroup businessGroup = businessGroupDao.createAndPersist(coach, "gdao", "gdao-desc", -1, -1, false, false, false, false, false); + businessGroupRelationDao.addRole(participant, businessGroup, GroupRole.participant.name()); + dbInstance.commit(); + RepositoryEntry re = JunitTestHelper.createAndPersistRepositoryEntry("", false); + + //node 1 + GTACourseNode node1 = new GTACourseNode(); + node1.getModuleConfiguration().setStringValue(GTACourseNode.GTASK_TYPE, GTAType.group.name()); + TaskList tasks1 = gtaManager.createIfNotExists(re, node1); + File taskFile = new File("bg.txt"); + Assert.assertNotNull(tasks1); + dbInstance.commit(); + + //node 2 + GTACourseNode node2 = new GTACourseNode(); + node2.getModuleConfiguration().setStringValue(GTACourseNode.GTASK_TYPE, GTAType.group.name()); + TaskList tasks2 = gtaManager.createIfNotExists(re, node2); + Assert.assertNotNull(tasks2); + dbInstance.commit(); + + //select node 1 + AssignmentResponse response1 = gtaManager.selectTask(businessGroup, tasks1, node1, taskFile); + dbInstance.commitAndCloseSession(); + Assert.assertNotNull(response1); + + //select node 2 + AssignmentResponse response2 = gtaManager.selectTask(businessGroup, tasks2, node2, taskFile); + dbInstance.commitAndCloseSession(); + Assert.assertNotNull(response2); + + //check that there is tasks + List<Task> assignedTasks1 = gtaManager.getTasks(participant, re, node1); + Assert.assertNotNull(assignedTasks1); + Assert.assertEquals(1, assignedTasks1.size()); + + List<Task> assignedTasks2 = gtaManager.getTasks(participant, re, node2); + Assert.assertNotNull(assignedTasks2); + Assert.assertEquals(1, assignedTasks2.size()); + + //delete + int numOfDeletedObjects = gtaManager.deleteTaskList(re, node1); + Assert.assertEquals(2, numOfDeletedObjects); + dbInstance.commitAndCloseSession(); + + //check that there isn't any tasks in node 1 + List<Task> deletedAssignedTasks = gtaManager.getTasks(participant, re, node1); + Assert.assertNotNull(deletedAssignedTasks); + Assert.assertEquals(0, deletedAssignedTasks.size()); + + //but always in node 2 + List<Task> notDeletedAssignedTasks2 = gtaManager.getTasks(participant, re, node2); + Assert.assertNotNull(notDeletedAssignedTasks2); + Assert.assertEquals(1, notDeletedAssignedTasks2.size()); + } + @Test public void roundRobin() { String[] slots = new String[]{ "A", "B", "C" }; diff --git a/src/test/java/org/olat/course/nodes/gta/rule/GTAReminderRuleTest.java b/src/test/java/org/olat/course/nodes/gta/rule/GTAReminderRuleTest.java index 27de702b6b9fef27b7b3ffe6d32ae5af22075549..f482bfe454ad6afdd713ed23b7d0140c391855c9 100644 --- a/src/test/java/org/olat/course/nodes/gta/rule/GTAReminderRuleTest.java +++ b/src/test/java/org/olat/course/nodes/gta/rule/GTAReminderRuleTest.java @@ -22,15 +22,22 @@ package org.olat.course.nodes.gta.rule; import java.io.File; import java.util.ArrayList; import java.util.Calendar; +import java.util.Date; import java.util.List; +import java.util.UUID; import org.junit.Assert; import org.junit.Test; import org.olat.basesecurity.GroupRoles; +import org.olat.basesecurity.model.GroupMembershipImpl; import org.olat.core.commons.persistence.DB; import org.olat.core.id.Identity; +import org.olat.course.ICourse; +import org.olat.course.assessment.manager.UserCourseInformationsManager; +import org.olat.course.assessment.model.UserCourseInfosImpl; import org.olat.course.nodes.GTACourseNode; import org.olat.course.nodes.gta.AssignmentResponse; +import org.olat.course.nodes.gta.GTARelativeToDates; import org.olat.course.nodes.gta.GTAType; import org.olat.course.nodes.gta.TaskList; import org.olat.course.nodes.gta.manager.GTAManagerImpl; @@ -41,7 +48,11 @@ import org.olat.modules.reminder.model.ReminderRuleImpl; import org.olat.modules.reminder.rule.LaunchUnit; import org.olat.modules.vitero.model.GroupRole; import org.olat.repository.RepositoryEntry; +import org.olat.repository.manager.RepositoryEntryLifecycleDAO; import org.olat.repository.manager.RepositoryEntryRelationDAO; +import org.olat.repository.model.RepositoryEntryLifecycle; +import org.olat.repository.model.RepositoryEntryToGroupRelation; +import org.olat.restapi.repository.course.CoursesWebService; import org.olat.test.JunitTestHelper; import org.olat.test.OlatTestCase; import org.springframework.beans.factory.annotation.Autowired; @@ -61,9 +72,13 @@ public class GTAReminderRuleTest extends OlatTestCase { @Autowired private BusinessGroupDAO businessGroupDao; @Autowired + private RepositoryEntryLifecycleDAO reLifeCycleDao; + @Autowired private BusinessGroupRelationDAO businessGroupRelationDao; @Autowired private RepositoryEntryRelationDAO repositoryEntryRelationDao; + @Autowired + private UserCourseInformationsManager userCourseInformationsManager; @Autowired @@ -211,6 +226,179 @@ public class GTAReminderRuleTest extends OlatTestCase { Assert.assertTrue(toRemind.contains(participant4)); } + @Test + public void assignTask_relativeToDateEnrollment() { + //prepare a course with a volatile task + Identity participant1 = JunitTestHelper.createAndPersistIdentityAsRndUser("gta-user-1"); + Identity participant2 = JunitTestHelper.createAndPersistIdentityAsRndUser("gta-user-2"); + RepositoryEntry re = JunitTestHelper.createAndPersistRepositoryEntry("", false); + addEnrollmentDate(re, participant1, GroupRoles.participant, -12, Calendar.DATE); + addEnrollmentDate(re, participant2, GroupRoles.participant, -5, Calendar.DATE); + dbInstance.commit(); + + // create a fake node + GTACourseNode node = new GTACourseNode(); + node.getModuleConfiguration().setStringValue(GTACourseNode.GTASK_TYPE, GTAType.individual.name()); + node.getModuleConfiguration().setBooleanEntry(GTACourseNode.GTASK_RELATIVE_DATES, true); + node.getModuleConfiguration().setIntValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE, 15); + node.getModuleConfiguration().setStringValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE_TO, GTARelativeToDates.enrollment.name()); + + // need the task list + TaskList tasks = gtaManager.createIfNotExists(re, node); + Assert.assertNotNull(tasks); + dbInstance.commit(); + + // participant 1 has still 3 days to choose a task + // participant 2 has still 10 days to choose a task + + { // check before 1 day + ReminderRuleImpl rule = getAssignedTaskRules(1, LaunchUnit.day); + List<Identity> all = assignTaskRuleSPI.evaluateRule(re, node, rule); + + Assert.assertEquals(0, all.size()); + } + + { // check before 5 days + ReminderRuleImpl rule = getAssignedTaskRules(5, LaunchUnit.day); + List<Identity> all = assignTaskRuleSPI.evaluateRule(re, node, rule); + + Assert.assertEquals(1, all.size()); + Assert.assertTrue(all.contains(participant1)); + } + + { // check before 1 week + ReminderRuleImpl rule = getAssignedTaskRules(1, LaunchUnit.week); + List<Identity> all = assignTaskRuleSPI.evaluateRule(re, node, rule); + + Assert.assertEquals(1, all.size()); + Assert.assertTrue(all.contains(participant1)); + } + + { // check before 1 month + ReminderRuleImpl rule = getAssignedTaskRules(1, LaunchUnit.month); + List<Identity> all = assignTaskRuleSPI.evaluateRule(re, node, rule); + + Assert.assertEquals(2, all.size()); + Assert.assertTrue(all.contains(participant1)); + Assert.assertTrue(all.contains(participant2)); + } + } + + private void addEnrollmentDate(RepositoryEntry entry, Identity id, GroupRoles role, int amount, int field) { + RepositoryEntryToGroupRelation rel = entry.getGroups().iterator().next(); + rel.getGroup(); + + Calendar cal = Calendar.getInstance(); + cal.setTime(new Date()); + cal.add(field, amount); + + GroupMembershipImpl membership = new GroupMembershipImpl(); + membership.setCreationDate(cal.getTime()); + membership.setLastModified(cal.getTime()); + membership.setGroup(rel.getGroup()); + membership.setIdentity(id); + membership.setRole(role.name()); + dbInstance.getCurrentEntityManager().persist(membership); + dbInstance.commit(); + } + + @Test + public void assignTask_relativeToInitialLaunchDate() { + //create a course with 3 members + Identity id1 = JunitTestHelper.createAndPersistIdentityAsRndUser("initial-launch-1"); + Identity id2 = JunitTestHelper.createAndPersistIdentityAsRndUser("initial-launch-2"); + Identity id3 = JunitTestHelper.createAndPersistIdentityAsRndUser("initial-launch-3"); + + ICourse course = CoursesWebService.createEmptyCourse(null, "initial-launch-dates", "course long name", null); + RepositoryEntry re = course.getCourseEnvironment().getCourseGroupManager().getCourseEntry(); + repositoryEntryRelationDao.addRole(id1, re, GroupRoles.participant.name()); + repositoryEntryRelationDao.addRole(id2, re, GroupRoles.participant.name()); + repositoryEntryRelationDao.addRole(id3, re, GroupRoles.participant.name()); + dbInstance.commit(); + + //create user course infos + Long courseResId = course.getCourseEnvironment().getCourseResourceableId(); + userCourseInformationsManager.updateUserCourseInformations(courseResId, id1, true); + userCourseInformationsManager.updateUserCourseInformations(courseResId, id2, true); + userCourseInformationsManager.updateUserCourseInformations(courseResId, id3, true); + dbInstance.commit(); + + //fake the date + updateInitialLaunchDate(courseResId, id1, -5, Calendar.DATE); + updateInitialLaunchDate(courseResId, id2, -35, Calendar.DATE); + updateInitialLaunchDate(courseResId, id3, -75, Calendar.DATE); + dbInstance.commitAndCloseSession(); + + // create a fake node + GTACourseNode node = new GTACourseNode(); + node.getModuleConfiguration().setStringValue(GTACourseNode.GTASK_TYPE, GTAType.individual.name()); + node.getModuleConfiguration().setBooleanEntry(GTACourseNode.GTASK_RELATIVE_DATES, true); + node.getModuleConfiguration().setIntValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE, 40); + node.getModuleConfiguration().setStringValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE_TO, GTARelativeToDates.courseLaunch.name()); + + // need the task list + TaskList tasks = gtaManager.createIfNotExists(re, node); + Assert.assertNotNull(tasks); + dbInstance.commit(); + + { // check 3 days + ReminderRuleImpl rule = getAssignedTaskRules(3, LaunchUnit.day); + List<Identity> all = assignTaskRuleSPI.evaluateRule(re, node, rule); + + Assert.assertEquals(1, all.size()); + Assert.assertTrue(all.contains(id3)); + } + + { // check 5 days + ReminderRuleImpl rule = getAssignedTaskRules(5, LaunchUnit.day); + List<Identity> all = assignTaskRuleSPI.evaluateRule(re, node, rule); + + Assert.assertEquals(2, all.size()); + Assert.assertTrue(all.contains(id2)); + Assert.assertTrue(all.contains(id3)); + } + + { // check 1 week + ReminderRuleImpl rule = getAssignedTaskRules(1, LaunchUnit.week); + List<Identity> all = assignTaskRuleSPI.evaluateRule(re, node, rule); + + Assert.assertEquals(2, all.size()); + Assert.assertTrue(all.contains(id2)); + Assert.assertTrue(all.contains(id3)); + } + + { // check 1 month + ReminderRuleImpl rule = getAssignedTaskRules(1, LaunchUnit.month); + List<Identity> all = assignTaskRuleSPI.evaluateRule(re, node, rule); + + Assert.assertEquals(2, all.size()); + Assert.assertTrue(all.contains(id2)); + Assert.assertTrue(all.contains(id3)); + } + + { // check 2 month + ReminderRuleImpl rule = getAssignedTaskRules(2, LaunchUnit.month); + List<Identity> all = assignTaskRuleSPI.evaluateRule(re, node, rule); + + Assert.assertEquals(3, all.size()); + Assert.assertTrue(all.contains(id1)); + Assert.assertTrue(all.contains(id2)); + Assert.assertTrue(all.contains(id3)); + } + + } + + private void updateInitialLaunchDate(Long courseResId, Identity id, int amount, int field) { + UserCourseInfosImpl userCourseInfos = (UserCourseInfosImpl)userCourseInformationsManager.getUserCourseInformations(courseResId, id); + Date initialLaunch = userCourseInfos.getInitialLaunch(); + Calendar cal = Calendar.getInstance(); + cal.setTime(initialLaunch); + cal.add(field, amount); + userCourseInfos.setInitialLaunch(cal.getTime()); + dbInstance.getCurrentEntityManager().merge(userCourseInfos); + dbInstance.commit(); + } + @Test public void submitTask_individual() { //prepare a course with a volatile task @@ -297,6 +485,112 @@ public class GTAReminderRuleTest extends OlatTestCase { } } + @Test + public void submitTask_relativeLifecycle() { + //prepare a course with a volatile task + Identity participant1 = JunitTestHelper.createAndPersistIdentityAsRndUser("gta-user-1"); + Identity participant2 = JunitTestHelper.createAndPersistIdentityAsRndUser("gta-user-2"); + RepositoryEntry re = JunitTestHelper.createAndPersistRepositoryEntry("", false); + repositoryEntryRelationDao.addRole(participant1, re, GroupRoles.participant.name()); + repositoryEntryRelationDao.addRole(participant2, re, GroupRoles.participant.name()); + dbInstance.commit(); + + String label = "Life cycle for relative date"; + String softKey = UUID.randomUUID().toString(); + Calendar cal = Calendar.getInstance(); + cal.setTime(new Date()); + cal.add(Calendar.DATE, -5); + Date from = cal.getTime(); + cal.add(Calendar.DATE, 20); + Date to = cal.getTime(); + RepositoryEntryLifecycle lifecycle = reLifeCycleDao.create(label, softKey, true, from, to); + re.setLifecycle(lifecycle); + re = dbInstance.getCurrentEntityManager().merge(re); + dbInstance.commit(); + + //create a fake node with a relative submit deadline 15 days after the start of the course + GTACourseNode node = new GTACourseNode(); + node.getModuleConfiguration().setStringValue(GTACourseNode.GTASK_TYPE, GTAType.individual.name()); + node.getModuleConfiguration().setBooleanEntry(GTACourseNode.GTASK_RELATIVE_DATES, true); + node.getModuleConfiguration().setIntValue(GTACourseNode.GTASK_SUBMIT_DEADLINE_RELATIVE, 15); + node.getModuleConfiguration().setStringValue(GTACourseNode.GTASK_SUBMIT_DEADLINE_RELATIVE_TO, GTARelativeToDates.courseStart.name()); + + TaskList tasks = gtaManager.createIfNotExists(re, node); + Assert.assertNotNull(tasks); + dbInstance.commitAndCloseSession(); + + //the course has start 5 days before, deadline is 15 days after it + //conclusion the deadline is 10 days from now + + { // check before 5 days + ReminderRuleImpl rule = getSubmitTaskRules(5, LaunchUnit.day); + List<Identity> all = submissionTaskRuleSPI.evaluateRule(re, node, rule); + + Assert.assertEquals(0, all.size()); + } + + { // check before 1 week + ReminderRuleImpl rule = getSubmitTaskRules(1, LaunchUnit.week); + List<Identity> all = submissionTaskRuleSPI.evaluateRule(re, node, rule); + + Assert.assertEquals(0, all.size()); + } + + { // check before 10 days + ReminderRuleImpl rule = getSubmitTaskRules(10, LaunchUnit.day); + List<Identity> all = submissionTaskRuleSPI.evaluateRule(re, node, rule); + + Assert.assertEquals(2, all.size()); + Assert.assertTrue(all.contains(participant1)); + Assert.assertTrue(all.contains(participant2)); + } + + { // check before 2 days + ReminderRuleImpl rule = getSubmitTaskRules(10, LaunchUnit.week); + List<Identity> all = submissionTaskRuleSPI.evaluateRule(re, node, rule); + + Assert.assertEquals(2, all.size()); + Assert.assertTrue(all.contains(participant1)); + Assert.assertTrue(all.contains(participant2)); + } + + { // check before 30 days + ReminderRuleImpl rule = getSubmitTaskRules(30, LaunchUnit.day); + List<Identity> all = submissionTaskRuleSPI.evaluateRule(re, node, rule); + + Assert.assertEquals(2, all.size()); + Assert.assertTrue(all.contains(participant1)); + Assert.assertTrue(all.contains(participant2)); + } + + { // check before 1 months + ReminderRuleImpl rule = getSubmitTaskRules(1, LaunchUnit.month); + List<Identity> all = submissionTaskRuleSPI.evaluateRule(re, node, rule); + + Assert.assertEquals(2, all.size()); + Assert.assertTrue(all.contains(participant1)); + Assert.assertTrue(all.contains(participant2)); + } + + { // check before 5 months + ReminderRuleImpl rule = getSubmitTaskRules(5, LaunchUnit.month); + List<Identity> all = submissionTaskRuleSPI.evaluateRule(re, node, rule); + + Assert.assertEquals(2, all.size()); + Assert.assertTrue(all.contains(participant1)); + Assert.assertTrue(all.contains(participant2)); + } + + { // check before 1 year + ReminderRuleImpl rule = getSubmitTaskRules(1, LaunchUnit.year); + List<Identity> all = submissionTaskRuleSPI.evaluateRule(re, node, rule); + + Assert.assertEquals(2, all.size()); + Assert.assertTrue(all.contains(participant1)); + Assert.assertTrue(all.contains(participant2)); + } + } + private ReminderRuleImpl getSubmitTaskRules(int amount, LaunchUnit unit) { ReminderRuleImpl rule = new ReminderRuleImpl(); rule.setType(SubmissionTaskRuleSPI.class.getSimpleName()); diff --git a/src/test/java/org/olat/modules/reminder/ReminderModuleTest.java b/src/test/java/org/olat/modules/reminder/ReminderModuleTest.java new file mode 100644 index 0000000000000000000000000000000000000000..6a0b4372ff24604a9505ddd96cedb35ae01014b9 --- /dev/null +++ b/src/test/java/org/olat/modules/reminder/ReminderModuleTest.java @@ -0,0 +1,89 @@ +/** + * <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.modules.reminder; + +import java.text.ParseException; +import java.util.Calendar; +import java.util.Date; + +import org.junit.Assert; +import org.junit.Test; +import org.olat.test.OlatTestCase; +import org.quartz.CronExpression; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 11.05.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class ReminderModuleTest extends OlatTestCase { + + @Autowired + private ReminderModule reminderModule; + + + @Test + public void testCronJob_everyTwoHours() throws ParseException { + reminderModule.setScheduler("2", "9:30"); + String cron = reminderModule.getCronExpression(); + + Calendar cal = Calendar.getInstance(); + cal.set(Calendar.HOUR_OF_DAY, 0); + cal.set(Calendar.MINUTE, 5); + + CronExpression cronExpression = new CronExpression(cron); + Date d1 = cronExpression.getNextValidTimeAfter(cal.getTime()); + + Calendar cal1 = Calendar.getInstance(); + cal1.setTime(d1); + Assert.assertEquals(1, cal1.get(Calendar.HOUR_OF_DAY)); + Assert.assertEquals(30, cal1.get(Calendar.MINUTE)); + } + + @Test + public void testCronJob_everyHeightHours() throws ParseException { + reminderModule.setScheduler("8", "6:30"); + String cron = reminderModule.getCronExpression(); + + Calendar cal = Calendar.getInstance(); + cal.set(Calendar.HOUR_OF_DAY, 0); + cal.set(Calendar.MINUTE, 5); + + CronExpression cronExpression = new CronExpression(cron); + Date d1 = cronExpression.getNextValidTimeAfter(cal.getTime()); + + Calendar triggerCal = Calendar.getInstance(); + triggerCal.setTime(d1); + Assert.assertEquals(6, triggerCal.get(Calendar.HOUR_OF_DAY)); + Assert.assertEquals(30, triggerCal.get(Calendar.MINUTE)); + + Date d2 = cronExpression.getNextValidTimeAfter(d1); + triggerCal.setTime(d2); + Assert.assertEquals(14, triggerCal.get(Calendar.HOUR_OF_DAY)); + Assert.assertEquals(30, triggerCal.get(Calendar.MINUTE)); + + Date d3 = cronExpression.getNextValidTimeAfter(d2); + triggerCal.setTime(d3); + Assert.assertEquals(22, triggerCal.get(Calendar.HOUR_OF_DAY)); + Assert.assertEquals(30, triggerCal.get(Calendar.MINUTE)); + } +} \ No newline at end of file diff --git a/src/test/java/org/olat/repository/manager/RepositoryEntryRelationDAOTest.java b/src/test/java/org/olat/repository/manager/RepositoryEntryRelationDAOTest.java index a37d6dcaa89455edda31e358b39143c490e10bcd..c4b576c8f6c9998cffae266d0f322cfc0e984eb7 100644 --- a/src/test/java/org/olat/repository/manager/RepositoryEntryRelationDAOTest.java +++ b/src/test/java/org/olat/repository/manager/RepositoryEntryRelationDAOTest.java @@ -21,7 +21,9 @@ package org.olat.repository.manager; import java.util.ArrayList; import java.util.Collections; +import java.util.Date; import java.util.List; +import java.util.Map; import java.util.UUID; import org.junit.Assert; @@ -181,6 +183,65 @@ public class RepositoryEntryRelationDAOTest extends OlatTestCase { Assert.assertEquals(4, numOfMembers); } + @Test + public void getEnrollmentDate() { + Identity id = JunitTestHelper.createAndPersistIdentityAsRndUser("enroll-date-1-"); + Identity wid = JunitTestHelper.createAndPersistIdentityAsRndUser("not-enroll-date-1-"); + RepositoryEntry re = repositoryService.create("Rei Ayanami", "rel", "rel", null, null); + dbInstance.commit(); + repositoryEntryRelationDao.addRole(id, re, GroupRoles.owner.name()); + repositoryEntryRelationDao.addRole(id, re, GroupRoles.participant.name()); + dbInstance.commit(); + + //enrollment date + Date enrollmentDate = repositoryEntryRelationDao.getEnrollmentDate(re, id); + Assert.assertNotNull(enrollmentDate); + + //this user isn't enrolled + Date withoutEnrollmentDate = repositoryEntryRelationDao.getEnrollmentDate(re, wid); + Assert.assertNull(withoutEnrollmentDate); + + //as participant + Date participantEnrollmentDate = repositoryEntryRelationDao.getEnrollmentDate(re, id, GroupRoles.participant.name()); + Assert.assertNotNull(participantEnrollmentDate); + //as owner + Date ownerEnrollmentDate = repositoryEntryRelationDao.getEnrollmentDate(re, id, GroupRoles.owner.name()); + Assert.assertNotNull(ownerEnrollmentDate); + //is not enrolled as coached + Date coachEnrollmentDate = repositoryEntryRelationDao.getEnrollmentDate(re, id, GroupRoles.coach.name()); + Assert.assertNull(coachEnrollmentDate); + } + + @Test + public void getEnrollmentDates() { + Identity id1 = JunitTestHelper.createAndPersistIdentityAsRndUser("enroll-date-2-"); + Identity id2 = JunitTestHelper.createAndPersistIdentityAsRndUser("enroll-date-3-"); + Identity wid = JunitTestHelper.createAndPersistIdentityAsRndUser("not-enroll-date-2-"); + RepositoryEntry re = repositoryService.create("Rei Ayanami", "rel", "rel", null, null); + dbInstance.commit(); + repositoryEntryRelationDao.addRole(id1, re, GroupRoles.owner.name()); + repositoryEntryRelationDao.addRole(id2, re, GroupRoles.participant.name()); + dbInstance.commit(); + + //enrollment date + Map<Long,Date> enrollmentDates = repositoryEntryRelationDao.getEnrollmentDates(re); + Assert.assertNotNull(enrollmentDates); + Assert.assertEquals(2, enrollmentDates.size()); + Assert.assertTrue(enrollmentDates.containsKey(id1.getKey())); + Assert.assertTrue(enrollmentDates.containsKey(id2.getKey())); + Assert.assertFalse(enrollmentDates.containsKey(wid.getKey())); + } + + @Test + public void getEnrollmentDates_emptyCourse() { + //enrollment of an empty course + RepositoryEntry notEnrolledRe = repositoryService.create("Rei Ayanami", "rel", "rel", null, null); + dbInstance.commit(); + Map<Long,Date> notEnrollmentDates = repositoryEntryRelationDao.getEnrollmentDates(notEnrolledRe); + Assert.assertNotNull(notEnrollmentDates); + Assert.assertEquals(0, notEnrollmentDates.size()); + } + @Test public void getAuthorKeys() { Identity id1 = JunitTestHelper.createAndPersistIdentityAsRndUser("auth-1-"); diff --git a/src/test/java/org/olat/selenium/UserTest.java b/src/test/java/org/olat/selenium/UserTest.java index 079a8455b5a7f520bb4117daa48ed4ffff7c384a..5403933e349e503e87b71c24cae12b3569f639fd 100644 --- a/src/test/java/org/olat/selenium/UserTest.java +++ b/src/test/java/org/olat/selenium/UserTest.java @@ -35,12 +35,12 @@ import org.jboss.arquillian.test.api.ArquillianResource; import org.jboss.shrinkwrap.api.spec.WebArchive; import org.junit.Assert; import org.junit.Assume; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.olat.restapi.support.vo.CourseVO; import org.olat.selenium.page.LoginPage; import org.olat.selenium.page.NavigationPage; +import org.olat.selenium.page.Student; import org.olat.selenium.page.User; import org.olat.selenium.page.course.CoursePageFragment; import org.olat.selenium.page.graphene.OOGraphene; @@ -498,7 +498,7 @@ public class UserTest { * @throws IOException * @throws URISyntaxException */ - @Test @Ignore + @Test @RunAsClient public void importUsers(@InitialPage LoginPage loginPage, @Drone @User WebDriver userBrowser) @@ -540,4 +540,70 @@ public class UserTest { .resume() .assertLoggedIn(user1); } + + /** + * Import 1 new user and 1 existing, change the password and the last name + * of the existing user. + * + * @param loginPage + * @param existingUserBrowser + * @throws IOException + * @throws URISyntaxException + */ + @Test + @RunAsClient + public void importExistingUsers(@InitialPage LoginPage loginPage, + @Drone @User WebDriver existingUserBrowser, + @Drone @Student WebDriver newUserBrowser) + throws IOException, URISyntaxException { + + UserVO user1 = new UserRestClient(deploymentUrl) + .createRandomUser("tsukune"); + + //login + loginPage + .assertOnLoginPage() + .loginAs("administrator", "openolat") + .resume(); + + UserAdminPage userAdminPage = navBar + .openUserManagement() + .openImportUsers(); + //start import wizard + ImportUserPage importWizard = userAdminPage.startImport(); + + String uuid = UUID.randomUUID().toString(); + String username1 = "moka-" + uuid; + + StringBuilder csv = new StringBuilder(); + UserVO newUser = importWizard.append(username1, "rosario02", "Moka", "Akashiya", csv); + user1 = importWizard.append(user1, "Aono", "openolat2", csv); + importWizard + .fill(csv.toString()) + .next() // -> preview + .assertGreen(1) + .assertWarn(1) + .changePassword() + .next() // -> groups + .next() // -> emails + .finish(); + + OOGraphene.waitAndCloseBlueMessageWindow(browser); + + //existing user log in with its new password and check if its name was updated + LoginPage userLoginPage = LoginPage.getLoginPage(existingUserBrowser, deploymentUrl); + //tools + userLoginPage + .loginAs(user1.getLogin(), "openolat2") + .resume() + .assertLoggedInByLastName("Aono"); + + //new user log in + LoginPage newLoginPage = LoginPage.getLoginPage(newUserBrowser, deploymentUrl); + //tools + newLoginPage + .loginAs(newUser.getLogin(), "rosario02") + .resume() + .assertLoggedInByLastName("Akashiya"); + } } diff --git a/src/test/java/org/olat/selenium/page/LoginPage.java b/src/test/java/org/olat/selenium/page/LoginPage.java index c82430671da9255fe8d1f681531a7dde3ed3ac0f..fa61508fba1364f6eb093c01c0a129953ad87851 100644 --- a/src/test/java/org/olat/selenium/page/LoginPage.java +++ b/src/test/java/org/olat/selenium/page/LoginPage.java @@ -79,6 +79,14 @@ public class LoginPage { Assert.assertTrue(name.contains(user.getLastName())); } + public void assertLoggedInByLastName(String lastName) { + WebElement username = browser.findElement(usernameFooterBy); + Assert.assertNotNull(username); + Assert.assertTrue(username.isDisplayed()); + String name = username.getText(); + Assert.assertTrue(name.contains(lastName)); + } + /** * Login and accept the disclaimer if there is one. * diff --git a/src/test/java/org/olat/selenium/page/graphene/OOGraphene.java b/src/test/java/org/olat/selenium/page/graphene/OOGraphene.java index 6ead43e3c6d18d57ab3778d48d6056efcd406136..b44d2040e8ff1fa2eacead2b3a3e715bcddffcdf 100644 --- a/src/test/java/org/olat/selenium/page/graphene/OOGraphene.java +++ b/src/test/java/org/olat/selenium/page/graphene/OOGraphene.java @@ -69,6 +69,11 @@ public class OOGraphene { ((JavascriptExecutor)browser).executeScript("top.tinymce.activeEditor.setContent('" + content + "')"); } + public static final void textarea(WebElement textareaEl, String content, WebDriver browser) { + String id = textareaEl.getAttribute("id"); + ((JavascriptExecutor)browser).executeScript("document.getElementById('" + id + "').value = '" + content + "'"); + } + public static final void date(Date date, String seleniumCssClass, WebDriver browser) { Locale locale = getLocale(browser); String dateText = DateFormat.getDateInstance(DateFormat.SHORT, locale).format(date); diff --git a/src/test/java/org/olat/selenium/page/user/ImportUserPage.java b/src/test/java/org/olat/selenium/page/user/ImportUserPage.java index acac1fc0ae260bb738bf1203b1be53f3aef178c3..e4b99bfa2bca9081798ebaf531b8c9c5c67eebf1 100644 --- a/src/test/java/org/olat/selenium/page/user/ImportUserPage.java +++ b/src/test/java/org/olat/selenium/page/user/ImportUserPage.java @@ -19,16 +19,12 @@ */ package org.olat.selenium.page.user; -import java.awt.Toolkit; -import java.awt.datatransfer.StringSelection; import java.util.List; import org.junit.Assert; import org.olat.selenium.page.graphene.OOGraphene; import org.olat.user.restapi.UserVO; import org.openqa.selenium.By; -import org.openqa.selenium.Keys; -import org.openqa.selenium.Platform; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; @@ -54,12 +50,16 @@ public class ImportUserPage { public ImportUserPage fill(String csv) { By importTextareaBy = By.cssSelector("div.o_wizard_steps_current_content textarea"); WebElement importTextareaEl = browser.findElement(importTextareaBy); - Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(csv), null); - if(Platform.MAC.equals(Platform.getCurrent())) { - importTextareaEl.sendKeys(Keys.COMMAND + "v"); - } else { - importTextareaEl.sendKeys(Keys.CONTROL + "v"); - } + //focus + importTextareaEl.sendKeys(""); + OOGraphene.textarea(importTextareaEl, csv, browser); + return this; + } + + public ImportUserPage changePassword() { + By updatePassword = By.cssSelector("input[name='update.password'][type='checkbox']"); + browser.findElement(updatePassword).click(); + OOGraphene.waitBusy(browser); return this; } @@ -77,7 +77,10 @@ public class ImportUserPage { * @param sb */ public UserVO append(String username, String password, String firstName, String lastName, StringBuilder sb) { - + if(sb.length() > 0) { + sb.append("\\n"); + } + String email = username.replace("-", "") + "@frentix.com"; String institution = "frentix GmbH"; String institutionNumber = "034-" + System.currentTimeMillis(); @@ -91,7 +94,7 @@ public class ImportUserPage { .append(email).append(" ") .append(institution).append(" ") .append(institutionNumber).append(" ") - .append(institutionEmail).append('\n'); + .append(institutionEmail); UserVO userVo = new UserVO(); userVo.setLogin(username); @@ -101,6 +104,27 @@ public class ImportUserPage { return userVo; } + public UserVO append(UserVO userVo, String newLastName, String password, StringBuilder sb) { + if(sb.length() > 0) { + sb.append("\\n"); + } + sb.append(userVo.getLogin()).append(" ") + .append(password).append(" ") + .append("de").append(" ") + .append(userVo.getFirstName()).append(" "); + if(newLastName != null) { + sb.append(newLastName).append(" "); + userVo.setLastName(newLastName); + } else { + sb.append(userVo.getLastName()).append(" "); + } + sb.append(userVo.getEmail()).append(" ") + .append("").append(" ") + .append("").append(" ") + .append(""); + return userVo; + } + public ImportUserPage assertGreen(int numOfGreen) { By greenBy = By.cssSelector(".o_dnd_label i.o_icon_new"); List<WebElement> greenEls = browser.findElements(greenBy); @@ -108,6 +132,14 @@ public class ImportUserPage { return this; } + public ImportUserPage assertWarn(int numOfWarns) { + By warnBy = By.cssSelector(".o_dnd_label i.o_icon_warn"); + List<WebElement> warnEls = browser.findElements(warnBy); + Assert.assertEquals(numOfWarns, warnEls.size()); + return this; + + } + /** * Next * @return this diff --git a/src/test/java/org/olat/test/AllTestsJunit4.java b/src/test/java/org/olat/test/AllTestsJunit4.java index 22ba3c1efe2b803bddf8251d919ffe2caae20f74..5cf77c00e057cd5e9b8fdeaf5f9923b48507ffa8 100644 --- a/src/test/java/org/olat/test/AllTestsJunit4.java +++ b/src/test/java/org/olat/test/AllTestsJunit4.java @@ -141,6 +141,7 @@ import org.junit.runners.Suite; org.olat.modules.wiki.gui.components.wikiToHtml.FilterUtilTest.class, org.olat.modules.coach.manager.CoachingDAOTest.class, org.olat.modules.coach.CoachingLargeTest.class, + org.olat.modules.reminder.ReminderModuleTest.class, org.olat.modules.reminder.manager.ReminderDAOTest.class, org.olat.modules.reminder.manager.ReminderRuleEngineTest.class, org.olat.properties.PropertyTest.class,