diff --git a/pom.xml b/pom.xml index 674db7d34c91ed151337087d87f77b0883135d1d..dae72ed9aac30835d72b6ea83f65b2cad094cff4 100644 --- a/pom.xml +++ b/pom.xml @@ -62,8 +62,8 @@ <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <targetJdk>1.8</targetJdk> - <org.springframework.version>5.2.7.RELEASE</org.springframework.version> - <org.hibernate.version>5.4.18.Final</org.hibernate.version> + <org.springframework.version>5.2.8.RELEASE</org.springframework.version> + <org.hibernate.version>5.4.19.Final</org.hibernate.version> <apache.cxf>3.3.7</apache.cxf> <apache.pdfbox>2.0.20</apache.pdfbox> <apache.poi>4.1.2</apache.poi> @@ -73,11 +73,11 @@ <jackson.version>2.11.1</jackson.version> <org.mysql.version>5.1.46</org.mysql.version> <org.postgresql.version>42.2.14</org.postgresql.version> - <org.infinispan.version>11.0.0.Final</org.infinispan.version> + <org.infinispan.version>11.0.1.Final</org.infinispan.version> <lucene.version>7.7.0</lucene.version> <version.selenium>3.141.59</version.selenium> <version.drone>2.5.1</version.drone> - <activemq.version>5.15.12</activemq.version> + <activemq.version>5.16.0</activemq.version> <qtiworks.version>1.0.19</qtiworks.version> <!-- properties for testing and Q&A --> @@ -99,9 +99,9 @@ <test.env.db.oracle.host>localhost</test.env.db.oracle.host> <test.env.db.oracle.host.port>1521</test.env.db.oracle.host.port> <test.env.instance.id>myolat</test.env.instance.id> - <test.env.jmx.rmi.port.0>3000</test.env.jmx.rmi.port.0> <test.env.webdriver.browser>chrome</test.env.webdriver.browser> - <test.env.webdriver.chrome.version></test.env.webdriver.chrome.version> + <test.env.webdriver.firefox.version>v0.27.0</test.env.webdriver.firefox.version> + <test.env.webdriver.chrome.version>84.0.4147.30</test.env.webdriver.chrome.version> <test.env.webdriver.chrome.arguments></test.env.webdriver.chrome.arguments> <skipTests>true</skipTests> <skipSeleniumTests>false</skipSeleniumTests> @@ -1400,9 +1400,9 @@ <test.env.db.postgresql.host.name>${test.env.db.postgresql.host.name}</test.env.db.postgresql.host.name> <test.env.db.postgresql.host.port>${test.env.db.postgresql.host.port}</test.env.db.postgresql.host.port> <test.env.instance.id>${test.env.instance.id}</test.env.instance.id> - <test.env.jmx.rmi.port.0>${test.env.jmx.rmi.port.0}</test.env.jmx.rmi.port.0> <arquillian.launch>tomcat-7-managed</arquillian.launch> <webdriver.browser>${test.env.webdriver.browser}</webdriver.browser> + <webdriver.firefox.version>${test.env.webdriver.firefox.version}</webdriver.firefox.version> <webdriver.chrome.version>${test.env.webdriver.chrome.version}</webdriver.chrome.version> <webdriver.chrome.arguments>${test.env.webdriver.chrome.arguments}</webdriver.chrome.arguments> </systemPropertyVariables> diff --git a/src/main/java/org/olat/admin/user/UsermanagerUserSearchController.java b/src/main/java/org/olat/admin/user/UsermanagerUserSearchController.java index 5f49300550629658ad85c20642b7d5e862fd6361..402ec0e58cd1d3f395d4a21af47ad0dfcf261c3b 100644 --- a/src/main/java/org/olat/admin/user/UsermanagerUserSearchController.java +++ b/src/main/java/org/olat/admin/user/UsermanagerUserSearchController.java @@ -76,6 +76,7 @@ public class UsermanagerUserSearchController extends BasicController implements private final boolean showDelete; private final boolean showEmailButton; + private final boolean showTableSearch; private final boolean showStatusFilters; private final boolean showOrganisationMove; private final boolean isAdministrativeUser; @@ -106,6 +107,7 @@ public class UsermanagerUserSearchController extends BasicController implements showEmailButton = true; showDelete = true; showOrganisationMove = false; + showTableSearch = true; isAdministrativeUser = securityModule.isUserAllowedAdminProps(ureq.getUserSession().getRoles()); @@ -131,12 +133,14 @@ public class UsermanagerUserSearchController extends BasicController implements * @param searchCreatedBefore */ public UsermanagerUserSearchController(UserRequest ureq, WindowControl wControl, TooledStackedPanel stackedPanel, - SearchIdentityParams predefinedQuery, boolean showEmailButton, boolean showOrganisationMove, boolean showDelete, boolean showStatusFilters) { + SearchIdentityParams predefinedQuery, boolean showEmailButton, boolean showOrganisationMove, boolean showDelete, + boolean showStatusFilters, boolean showTableSearch) { super(ureq, wControl); setTranslator(userManager.getPropertyHandlerTranslator(getTranslator())); this.showDelete = showDelete; this.stackedPanel = stackedPanel; this.showEmailButton = showEmailButton; + this.showTableSearch = showTableSearch; this.showStatusFilters = showStatusFilters; this.showOrganisationMove = showOrganisationMove; @@ -144,7 +148,7 @@ public class UsermanagerUserSearchController extends BasicController implements isAdministrativeUser = securityModule.isUserAllowedAdminProps(ureq.getUserSession().getRoles()); tableCtr = new UserSearchTableController(ureq, getWindowControl(), stackedPanel, - UserSearchTableSettings.withVCard(showEmailButton, showOrganisationMove, showDelete, showStatusFilters)); + UserSearchTableSettings.withVCard(showEmailButton, showOrganisationMove, showDelete, showStatusFilters, true)); listenTo(tableCtr); tableCtr.loadModel(identityQueryParams); putInitialPanel(tableCtr.getInitialComponent()); @@ -168,12 +172,13 @@ public class UsermanagerUserSearchController extends BasicController implements this.stackedPanel = stackedPanel; this.showEmailButton = showEmailButton; showStatusFilters = false; + showTableSearch = false; showOrganisationMove = false; isAdministrativeUser = securityModule.isUserAllowedAdminProps(ureq.getUserSession().getRoles()); tableCtr = new UserSearchTableController(ureq, getWindowControl(), stackedPanel, - UserSearchTableSettings.withVCard(showEmailButton, false, showDelete, true)); + UserSearchTableSettings.withVCard(showEmailButton, false, showDelete, true, true)); listenTo(tableCtr); tableCtr.loadModel(identitiesList); @@ -229,11 +234,11 @@ public class UsermanagerUserSearchController extends BasicController implements searchState.setDelegate(states); searchFormCtrl.setStateEntry(searchState); - doPushSearch(ureq); + doPushSearch(ureq, showTableSearch); tableCtr.activate(ureq, entries, null); } - private void doPushSearch(UserRequest ureq) { + private void doPushSearch(UserRequest ureq, boolean withTableSearch) { identityQueryParams = searchFormCtrl.getSearchIdentityParams(); if(identityQueryParams.getOrganisations() == null || identityQueryParams.getOrganisations().isEmpty()) { identityQueryParams.setOrganisations(manageableOrganisations); @@ -243,7 +248,7 @@ public class UsermanagerUserSearchController extends BasicController implements ThreadLocalUserActivityLogger.addLoggingResourceInfo(LoggingResourceable.wrapBusinessPath(ores)); WindowControl bwControl = addToHistory(ureq, ores, null); tableCtr = new UserSearchTableController(ureq, bwControl, stackedPanel, - UserSearchTableSettings.withVCard(showEmailButton, showOrganisationMove, showDelete, showStatusFilters)); + UserSearchTableSettings.withVCard(showEmailButton, showOrganisationMove, showDelete, showStatusFilters, withTableSearch)); listenTo(tableCtr); tableCtr.loadModel(identityQueryParams); stackedPanel.pushController("Results", tableCtr); @@ -261,7 +266,7 @@ public class UsermanagerUserSearchController extends BasicController implements public void event(UserRequest ureq, Controller source, Event event) { if (source == searchFormCtrl) { if (event == Event.DONE_EVENT) { - doPushSearch(ureq); + doPushSearch(ureq, false); } else if (event == Event.CANCELLED_EVENT) { fireEvent(ureq, Event.CANCELLED_EVENT); } diff --git a/src/main/java/org/olat/admin/user/UsermanagerUserSearchForm.java b/src/main/java/org/olat/admin/user/UsermanagerUserSearchForm.java index 5f0679adfa33a5f23e80dfb0650b82487d42700a..4fd7d81ca08dcc7e44306a859ad5a8b9a68fad39 100644 --- a/src/main/java/org/olat/admin/user/UsermanagerUserSearchForm.java +++ b/src/main/java/org/olat/admin/user/UsermanagerUserSearchForm.java @@ -141,8 +141,8 @@ public class UsermanagerUserSearchForm extends FormBasicController { statusValues = new String[] { translate("rightsForm.status.activ"), translate("rightsForm.status.permanent"), - translate("rightsForm.status.inactive"), translate("rightsForm.status.pending"), + translate("rightsForm.status.inactive"), translate("rightsForm.status.login_denied") }; diff --git a/src/main/java/org/olat/core/commons/services/jmx/_spring/jmxContext.xml b/src/main/java/org/olat/core/commons/services/jmx/_spring/jmxContext.xml index 603ad7a412a6428ba1e68c41fb1268c55f04d59c..f7be7d25a78f79f055340ccca0ae066a98f9bd37 100644 --- a/src/main/java/org/olat/core/commons/services/jmx/_spring/jmxContext.xml +++ b/src/main/java/org/olat/core/commons/services/jmx/_spring/jmxContext.xml @@ -12,9 +12,5 @@ <bean id="org.springframework.jmx.support.MBeanServerFactoryBean" class="org.springframework.jmx.support.MBeanServerFactoryBean"> <property name="locateExistingServerIfPossible" value="true" /> </bean> - - <bean id="registry" class="org.springframework.remoting.rmi.RmiRegistryFactoryBean" lazy-init="true"> - <property name="port" value="${jmx.rmi.port}"/> - </bean> </beans> diff --git a/src/main/java/org/olat/core/gui/components/link/LinkRenderer.java b/src/main/java/org/olat/core/gui/components/link/LinkRenderer.java index 6dafc3b23f2612069bc5d48c2e5f362dbd4c78d4..3a75af5f3e13367fa4db9f9cc1590ab565e7f4ce 100644 --- a/src/main/java/org/olat/core/gui/components/link/LinkRenderer.java +++ b/src/main/java/org/olat/core/gui/components/link/LinkRenderer.java @@ -189,11 +189,11 @@ public class LinkRenderer extends DefaultComponentRenderer { .append(";\" ") .append("onclick=\"return o2cl_dirtyCheckOnly();\" "); } else { - String href = StringHelper.containsNonWhitespace(link.getUrl()) ? link.getUrl() : "javascript:;"; + boolean hasUrl = StringHelper.containsNonWhitespace(link.getUrl()); + String href = hasUrl ? link.getUrl() : "javascript:;"; sb.append("href=\"").append(href).append("\" onclick=\"") .append(FormJSHelper.getJSFnCallFor(flexiLink.getRootForm(), elementId, 1)) - .append(";"); - sb.append("\" "); + .append("; ").append(" return false;", hasUrl).append("\" "); } } else if(link.isPopup()) { try(StringOutput href = new StringOutput()) { diff --git a/src/main/java/org/olat/course/assessment/ui/tool/IdentityListCourseNodeController.java b/src/main/java/org/olat/course/assessment/ui/tool/IdentityListCourseNodeController.java index 26dc189640769fb5e0dabcd85232f33b1ec39535..d4edfa5628eda8489b69abc864d69bc1210be717 100644 --- a/src/main/java/org/olat/course/assessment/ui/tool/IdentityListCourseNodeController.java +++ b/src/main/java/org/olat/course/assessment/ui/tool/IdentityListCourseNodeController.java @@ -160,11 +160,11 @@ public class IdentityListCourseNodeController extends FormBasicController private ContactFormController contactCtrl; @Autowired - private DB dbInstance; + protected DB dbInstance; @Autowired private UserManager userManager; @Autowired - private BaseSecurity securityManager; + protected BaseSecurity securityManager; @Autowired private GradingService gradingService; @Autowired @@ -987,14 +987,14 @@ public class IdentityListCourseNodeController extends FormBasicController ICourse course = CourseFactory.loadCourse(courseEntry); for(AssessedIdentityElementRow row:rows) { Identity assessedIdentity = securityManager.loadIdentityByKey(row.getIdentityKey()); - doSetDone(assessedIdentity, courseNode, course); + doSetStatus(assessedIdentity, AssessmentEntryStatus.done, courseNode, course); dbInstance.commitAndCloseSession(); } loadModel(ureq); } } - protected void doSetDone(Identity assessedIdentity, CourseNode courseNode, ICourse course) { + protected void doSetStatus(Identity assessedIdentity, AssessmentEntryStatus status, CourseNode courseNode, ICourse course) { Roles roles = securityManager.getRoles(assessedIdentity); IdentityEnvironment identityEnv = new IdentityEnvironment(assessedIdentity, roles); @@ -1003,11 +1003,10 @@ public class IdentityListCourseNodeController extends FormBasicController ScoreEvaluation scoreEval = courseAssessmentService.getAssessmentEvaluation(courseNode, assessedUserCourseEnv); ScoreEvaluation doneEval = new ScoreEvaluation(scoreEval.getScore(), scoreEval.getPassed(), - AssessmentEntryStatus.done, null, scoreEval.getCurrentRunCompletion(), + status, null, scoreEval.getCurrentRunCompletion(), scoreEval.getCurrentRunStatus(), scoreEval.getAssessmentID()); courseAssessmentService.updateScoreEvaluation(courseNode, doneEval, assessedUserCourseEnv, getIdentity(), false, Role.coach); - } private void doUpdateCompletion(Double completion, AssessmentRunStatus status, Long assessedIdentityKey) { diff --git a/src/main/java/org/olat/course/nodes/gta/ui/GTAIdentityListCourseNodeController.java b/src/main/java/org/olat/course/nodes/gta/ui/GTAIdentityListCourseNodeController.java index 45003a2fc389dfb666c4f834d4e718728ed5f8e1..096e1549a542e91d845325ef87d8766dbe4a81a1 100644 --- a/src/main/java/org/olat/course/nodes/gta/ui/GTAIdentityListCourseNodeController.java +++ b/src/main/java/org/olat/course/nodes/gta/ui/GTAIdentityListCourseNodeController.java @@ -56,6 +56,7 @@ import org.olat.group.BusinessGroup; import org.olat.modules.ModuleConfiguration; import org.olat.modules.assessment.AssessmentToolOptions; import org.olat.modules.assessment.Role; +import org.olat.modules.assessment.model.AssessmentEntryStatus; import org.olat.modules.assessment.ui.AssessedIdentityElementRow; import org.olat.modules.assessment.ui.AssessmentToolContainer; import org.olat.modules.assessment.ui.AssessmentToolSecurityCallback; @@ -218,12 +219,12 @@ public class GTAIdentityListCourseNodeController extends IdentityListCourseNodeC } @Override - protected void doSetDone(Identity assessedIdentity, CourseNode assessableCourseNode, ICourse course) { - super.doSetDone(assessedIdentity, assessableCourseNode, course); + protected void doSetStatus(Identity assessedIdentity, AssessmentEntryStatus status, CourseNode assessableCourseNode, ICourse course) { + super.doSetStatus(assessedIdentity, status, assessableCourseNode, course); TaskList taskList = gtaManager.getTaskList(getCourseRepositoryEntry(), (GTACourseNode)assessableCourseNode); Task task = gtaManager.getTask(assessedIdentity, taskList); - if(task != null) { + if(task != null && status == AssessmentEntryStatus.done) { gtaManager.updateTask(task, TaskProcess.graded, (GTACourseNode)assessableCourseNode, false, getIdentity(), Role.coach); } } diff --git a/src/main/java/org/olat/course/nodes/iq/ConfirmReopenAssessmentEntriesController.java b/src/main/java/org/olat/course/nodes/iq/ConfirmReopenAssessmentEntriesController.java new file mode 100644 index 0000000000000000000000000000000000000000..e55c39aae8490d16bf0e185b9e0ea44363af4e1e --- /dev/null +++ b/src/main/java/org/olat/course/nodes/iq/ConfirmReopenAssessmentEntriesController.java @@ -0,0 +1,91 @@ +package org.olat.course.nodes.iq; + +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.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.link.Link; +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.util.Util; +import org.olat.ims.qti21.ui.ConfirmReopenAssessmentEntryController; + +/** + * + * Initial date: 28 juil. 2020<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class ConfirmReopenAssessmentEntriesController extends FormBasicController { + + private FormLink readOnlyButton; + + private Object userObject; + private final long numOfAssessmentEntriesDone; + + /** + * Confirm reopen of the assessment entries . + * + * @param ureq The user request + * @param wControl The window control + */ + public ConfirmReopenAssessmentEntriesController(UserRequest ureq, WindowControl wControl, long numOfAssessmentEntriesDone) { + super(ureq, wControl, "confirm_reopen_assessments", Util + .createPackageTranslator(ConfirmReopenAssessmentEntryController.class, ureq.getLocale())); + this.numOfAssessmentEntriesDone = numOfAssessmentEntriesDone; + initForm(ureq); + } + + public Object getUserObject() { + return userObject; + } + + public void setUserObject(Object userObject) { + this.userObject = userObject; + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + if(formLayout instanceof FormLayoutContainer) { + FormLayoutContainer layoutCont = (FormLayoutContainer)formLayout; + String msg; + if(numOfAssessmentEntriesDone == 1) { + msg = translate("reopen.assessment.text"); + } else { + msg = translate("reopen.assessments.text", new String[] { Long.toString(numOfAssessmentEntriesDone) }); + } + layoutCont.contextPut("msg", msg); + } + + uifactory.addFormCancelButton("cancel", formLayout, ureq, getWindowControl()); + uifactory.addFormSubmitButton("reopen.assessment", formLayout); + readOnlyButton = uifactory.addFormLink("correction.readonly", formLayout, Link.BUTTON); + } + + @Override + protected void doDispose() { + // + } + + @Override + protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { + if(readOnlyButton == source) { + fireEvent(ureq, Event.DONE_EVENT); + } + super.formInnerEvent(ureq, source, event); + } + + @Override + protected void formOK(UserRequest ureq) { + fireEvent(ureq, Event.CHANGED_EVENT); + } + + @Override + protected void formCancelled(UserRequest ureq) { + fireEvent(ureq, Event.CANCELLED_EVENT); + } +} diff --git a/src/main/java/org/olat/course/nodes/iq/IQIdentityListCourseNodeController.java b/src/main/java/org/olat/course/nodes/iq/IQIdentityListCourseNodeController.java index a9ac1306653b2e38b3467dabb470e3a441443300..ee13d1c46a4b614976e46b6ee47d8993c0b300e2 100644 --- a/src/main/java/org/olat/course/nodes/iq/IQIdentityListCourseNodeController.java +++ b/src/main/java/org/olat/course/nodes/iq/IQIdentityListCourseNodeController.java @@ -28,6 +28,8 @@ import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; import org.olat.basesecurity.GroupRoles; import org.olat.basesecurity.IdentityRef; @@ -49,6 +51,8 @@ import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController; import org.olat.core.gui.media.MediaResource; import org.olat.core.id.Identity; +import org.olat.course.CourseFactory; +import org.olat.course.ICourse; import org.olat.course.archiver.ScoreAccountingHelper; import org.olat.course.assessment.AssessmentHelper; import org.olat.course.assessment.CourseAssessmentService; @@ -118,6 +122,7 @@ public class IQIdentityListCourseNodeController extends IdentityListCourseNodeCo private ConfirmExtraTimeController extraTimeCtrl; private ValidationXmlSignatureController validationCtrl; private CorrectionOverviewController correctionIdentitiesCtrl; + private ConfirmReopenAssessmentEntriesController reopenForCorrectionCtrl; private boolean modelDirty = false; @@ -340,6 +345,16 @@ public class IQIdentityListCourseNodeController extends IdentityListCourseNodeCo } else if(event == Event.DONE_EVENT) { loadModel(ureq); } + } else if(reopenForCorrectionCtrl == source) { + CorrectionOverviewController correctionCtrl = (CorrectionOverviewController)reopenForCorrectionCtrl.getUserObject(); + cmc.deactivate(); + cleanUp(); + if(event == Event.CHANGED_EVENT) { + doReopenAssessmentEntries(correctionCtrl); + doOpenCorrection(correctionCtrl); + } else if(event == Event.DONE_EVENT) { + doOpenCorrection(correctionCtrl); + } } super.event(ureq, source, event); } @@ -367,10 +382,12 @@ public class IQIdentityListCourseNodeController extends IdentityListCourseNodeCo @Override protected void cleanUp() { + removeAsListenerAndDispose(reopenForCorrectionCtrl); removeAsListenerAndDispose(retrieveConfirmationCtr); removeAsListenerAndDispose(validationCtrl); removeAsListenerAndDispose(extraTimeCtrl); removeAsListenerAndDispose(resetDataCtrl); + reopenForCorrectionCtrl = null; retrieveConfirmationCtr = null; validationCtrl = null; extraTimeCtrl = null; @@ -431,14 +448,51 @@ public class IQIdentityListCourseNodeController extends IdentityListCourseNodeCo private void doStartCorrection(UserRequest ureq) { AssessmentToolOptions asOptions = getOptions(); - correctionIdentitiesCtrl = new CorrectionOverviewController(ureq, getWindowControl(), stackPanel, + CorrectionOverviewController correctionCtrl = new CorrectionOverviewController(ureq, getWindowControl(), stackPanel, getCourseEnvironment(), asOptions, (IQTESTCourseNode)courseNode); - if(correctionIdentitiesCtrl.getNumberOfAssessedIdentities() == 0) { + long numOfAssessmentEntriesDone = usersTableModel.getObjects().stream() + .filter(row -> row.getAssessmentStatus() == AssessmentEntryStatus.done) + .count(); + if(correctionCtrl.getNumberOfAssessedIdentities() == 0) { showWarning("grade.nobody"); - correctionIdentitiesCtrl = null; + } else if(numOfAssessmentEntriesDone > 0) { + doReopenForCorrection(ureq, correctionCtrl, numOfAssessmentEntriesDone); } else { - listenTo(correctionIdentitiesCtrl); - stackPanel.pushController(translate("correction.test.title"), correctionIdentitiesCtrl); + doOpenCorrection(correctionCtrl); + } + } + + private void doOpenCorrection(CorrectionOverviewController correctionCtrl) { + correctionIdentitiesCtrl = correctionCtrl; + listenTo(correctionIdentitiesCtrl); + stackPanel.pushController(translate("correction.test.title"), correctionIdentitiesCtrl); + } + + private void doReopenForCorrection(UserRequest ureq, CorrectionOverviewController correctionCtrl, long numOfAssessmentEntriesDone) { + if(guardModalController(reopenForCorrectionCtrl)) return; + + reopenForCorrectionCtrl = new ConfirmReopenAssessmentEntriesController(ureq, getWindowControl(), numOfAssessmentEntriesDone); + reopenForCorrectionCtrl.setUserObject(correctionCtrl); + listenTo(reopenForCorrectionCtrl); + + cmc = new CloseableModalController(getWindowControl(), "close", reopenForCorrectionCtrl.getInitialComponent(), + true, translate("reopen.assessments.title")); + cmc.activate(); + listenTo(cmc); + } + + private void doReopenAssessmentEntries(CorrectionOverviewController correctionCtrl) { + List<Identity> assessedIdentities = correctionCtrl.getAssessedIdentities(); + Set<Long> assessedIdentitiesKeys = assessedIdentities.stream() + .map(Identity::getKey) + .collect(Collectors.toSet()); + List<AssessedIdentityElementRow> rows = usersTableModel.getObjects(); + ICourse course = CourseFactory.loadCourse(courseEntry); + for(AssessedIdentityElementRow row:rows) { + if(row.getAssessmentStatus() == AssessmentEntryStatus.done && assessedIdentitiesKeys.contains(row.getIdentityKey())) { + Identity assessedIdentity = securityManager.loadIdentityByKey(row.getIdentityKey()); + doSetStatus(assessedIdentity, AssessmentEntryStatus.inReview, courseNode, course); + dbInstance.commitAndCloseSession(); } } } diff --git a/src/main/java/org/olat/course/nodes/iq/QTI21IdentityListCourseNodeToolsController.java b/src/main/java/org/olat/course/nodes/iq/QTI21IdentityListCourseNodeToolsController.java index 3ab1ce192cc5359a9d8d7428f4afbca88aa21f6e..55e25c7eec57f30f7b27202fbefeea05564c700b 100644 --- a/src/main/java/org/olat/course/nodes/iq/QTI21IdentityListCourseNodeToolsController.java +++ b/src/main/java/org/olat/course/nodes/iq/QTI21IdentityListCourseNodeToolsController.java @@ -47,6 +47,7 @@ import org.olat.ims.qti21.model.jpa.AssessmentTestSessionStatistics; import org.olat.ims.qti21.model.xml.ManifestBuilder; import org.olat.ims.qti21.ui.AssessmentTestDisplayController; import org.olat.ims.qti21.ui.AssessmentTestSessionComparator; +import org.olat.ims.qti21.ui.ConfirmReopenAssessmentEntryController; import org.olat.ims.qti21.ui.QTI21ResetDataController; import org.olat.ims.qti21.ui.QTI21RetrieveTestsController; import org.olat.ims.qti21.ui.assessment.CorrectionIdentityAssessmentItemListController; @@ -82,6 +83,7 @@ public class QTI21IdentityListCourseNodeToolsController extends AbstractToolsCon private ConfirmExtraTimeController extraTimeCtrl; private QTI21RetrieveTestsController retrieveConfirmationCtr; private CorrectionIdentityAssessmentItemListController correctionCtrl; + private ConfirmReopenAssessmentEntryController reopenForCorrectionCtrl; private RepositoryEntry testEntry; private RepositoryEntry courseEntry; @@ -166,7 +168,7 @@ public class QTI21IdentityListCourseNodeToolsController extends AbstractToolsCon protected void event(UserRequest ureq, Component source, Event event) { if(correctionLink == source) { fireEvent(ureq, Event.CLOSE_EVENT); - doOpenCorrection(ureq); + doCorrection(ureq); } else if(pullTestLink == source) { fireEvent(ureq, Event.CLOSE_EVENT); doConfirmPullSession(ureq, lastSession); @@ -203,6 +205,10 @@ public class QTI21IdentityListCourseNodeToolsController extends AbstractToolsCon cmc.deactivate(); cleanUp(); fireAlteredEvent(ureq, event); + } else if(reopenForCorrectionCtrl == source) { + cmc.deactivate(); + cleanUp(); + doOpenCorrection(ureq); } else if(cmc == source) { cleanUp(); fireEvent(ureq, Event.CHANGED_EVENT); @@ -221,17 +227,42 @@ public class QTI21IdentityListCourseNodeToolsController extends AbstractToolsCon } private void cleanUp() { + removeAsListenerAndDispose(reopenForCorrectionCtrl); removeAsListenerAndDispose(correctionCtrl); removeAsListenerAndDispose(extraTimeCtrl); removeAsListenerAndDispose(resetDataCtrl); removeAsListenerAndDispose(cmc); + reopenForCorrectionCtrl = null; correctionCtrl = null; extraTimeCtrl = null; resetDataCtrl = null; cmc = null; } + private void doCorrection(UserRequest ureq) { + boolean assessmentEntryDone = isAssessementEntryDone(); + if(assessmentEntryDone) { + doReopenForCorrection(ureq); + } else { + doOpenCorrection(ureq); + } + } + + private void doReopenForCorrection(UserRequest ureq) { + if(guardModalController(reopenForCorrectionCtrl)) return; + + reopenForCorrectionCtrl = new ConfirmReopenAssessmentEntryController(ureq, getWindowControl(), + assessedUserCourseEnv, (IQTESTCourseNode)courseNode, null); + listenTo(reopenForCorrectionCtrl); + + cmc = new CloseableModalController(getWindowControl(), "close", reopenForCorrectionCtrl.getInitialComponent(), + true, translate("reopen.assessment.title")); + cmc.activate(); + listenTo(cmc); + } + private void doOpenCorrection(UserRequest ureq) { + boolean assessmentEntryDone = isAssessementEntryDone(); File unzippedDirRoot = FileResourceManager.getInstance().unzipFileResource(testEntry.getOlatResource()); ResolvedAssessmentTest resolvedAssessmentTest = qtiService.loadAndResolveAssessmentTest(unzippedDirRoot, false, false); ManifestBuilder manifestBuilder = ManifestBuilder.read(new File(unzippedDirRoot, "imsmanifest.xml")); @@ -242,8 +273,8 @@ public class QTI21IdentityListCourseNodeToolsController extends AbstractToolsCon testSessionStates.put(assessedIdentity, testSessionState); CorrectionOverviewModel model = new CorrectionOverviewModel(courseEntry, testCourseNode, testEntry, resolvedAssessmentTest, manifestBuilder, lastSessionMap, testSessionStates); - boolean readOnly = isAssessementEntryDone(); - correctionCtrl = new CorrectionIdentityAssessmentItemListController(ureq, getWindowControl(), stackPanel, model, assessedIdentity, readOnly); + + correctionCtrl = new CorrectionIdentityAssessmentItemListController(ureq, getWindowControl(), stackPanel, model, assessedIdentity, assessmentEntryDone); listenTo(correctionCtrl); stackPanel.pushController(translate("tool.correction"), correctionCtrl); } diff --git a/src/main/java/org/olat/course/nodes/iq/_content/confirm_reopen_assessments.html b/src/main/java/org/olat/course/nodes/iq/_content/confirm_reopen_assessments.html new file mode 100644 index 0000000000000000000000000000000000000000..0cd82652d95a867088a188d4a620f6ce59640c16 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/iq/_content/confirm_reopen_assessments.html @@ -0,0 +1,10 @@ +<p>$msg</p> +<div class="o_button_group"> + $r.render("cancel") + #if($r.available("reopen.assessment")) + $r.render("reopen.assessment") + #end + #if($r.available("correction.readonly")) + $r.render("correction.readonly") + #end +</div> \ No newline at end of file diff --git a/src/main/java/org/olat/course/nodes/iq/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/nodes/iq/_i18n/LocalStrings_de.properties index 527bd3b57337a6d2ea872f1649c49f1797e7b085..1d4cac49fff98e854bfc3c988570ed2b1bd5c6c7 100644 --- a/src/main/java/org/olat/course/nodes/iq/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/course/nodes/iq/_i18n/LocalStrings_de.properties @@ -165,6 +165,9 @@ qti.form.test.date.confirm=W\u00E4hrend dem Testzeitraum kann der Test gestartet qti.form.test.date.help=$\:qti.form.test.date.confirm qti.form.test.title=Test reopen=Erneut starten +reopen.assessments.title=Abgeschlossene Bewertungen wieder \u00F6ffnen +reopen.assessment.text=Es gibt bereits eine abgeschlossene Bewertung. Wollen Sie sie wieder\u00F6ffnen , um den Test erneut zu korrigieren? +reopen.assessments.text=Es gibt bereits {0} abgeschlossene Bewertungen. Wollen Sie diese wieder \u00F6ffnen, um die Tests erneut zu korrigieren? reopen.explanation=Dieser Test wurde beendet. Sie k\u00F6nnen den Test erneut starten. Der Testteilnehmende kann den Test dann dort weiterf\u00FChren, wo er aufgeh\u00F6rt hat. Falls der Test auf 1 Versuch beschr\u00E4nkt ist, m\u00FCssen au�erdem die Versuche zur\u00FCckgesetzt werden, damit der Testteilnehmende den Test weiterf\u00FChren kann. reopen.test=Beendeten Test wieder \u00F6ffnen replace.wizard.information.empty.results=<b>HINWEIS\: Dieser Test/Fragebogen wurde bereits von {0} Teilnehmer(n) begonnen\!</b> Die bisherigen Ergebnisse dieser Nutzer werden mit Klick auf "Fertigstellen" im n\u00E4chsten Schritt gel\u00F6scht.<br /><br /><b>Publizieren Sie schnellstm\u00F6glich Ihren Kurs\!</b> Erst nach dem Publizieren des Kurses k\u00F6nnen die Kursteilnehmer den neu zugeordneten Test/Fragebogen starten und entsprechende Ergebnisse gespeichert werden. diff --git a/src/main/java/org/olat/course/nodes/iq/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/nodes/iq/_i18n/LocalStrings_en.properties index dda05daef0a8f2530d558ef06b68e41c7091e845..b15c85fb0b0194f74f6c71a0d2342dcab5ddbc73 100644 --- a/src/main/java/org/olat/course/nodes/iq/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/course/nodes/iq/_i18n/LocalStrings_en.properties @@ -166,6 +166,9 @@ qti.form.test.date.confirm=During the test period the test can be started. As s qti.form.test.date.help=$\:qti.form.test.date.confirm qti.form.test.title=Test reopen=Start again +reopen.assessments.title=Reopen closed assessments +reopen.assessment.text=There are already a closed assessment. Do you want to reopen it to correct the test again? +reopen.assessments.text=There are already {0} closed assessments. Do you want to reopen them to correct the tests again? reopen.explanation=This test has been finished. You can restart the test. The test participant continues the test where he left it. If the test is limited to 1 attempt, the number of attempts must be reset so the test participants can continue the test. reopen.test=Reopen finished test replace.wizard.information.empty.results=<b>NOTE\: This test/questionnaire has already been launched by {0} participant(s)\!</b> All user results already existing will be deleted by clicking next on "Finish".<br></br><b>Please publish your course as soon as possible\!</b> Only after publishing will course participants be able to launch your newly assigned test/questionnaire; any results can then be saved accordingly. diff --git a/src/main/java/org/olat/ims/qti21/manager/QTI21StatisticsManagerImpl.java b/src/main/java/org/olat/ims/qti21/manager/QTI21StatisticsManagerImpl.java index 24701e45fb996f6b05b9c0a58b1e86931753cfb8..b4e8b89994989dce9396e80020ee9b7c00868968 100644 --- a/src/main/java/org/olat/ims/qti21/manager/QTI21StatisticsManagerImpl.java +++ b/src/main/java/org/olat/ims/qti21/manager/QTI21StatisticsManagerImpl.java @@ -112,7 +112,8 @@ public class QTI21StatisticsManagerImpl implements QTI21StatisticsManager { } sb.append(" and asession.lastModified = (select max(a2session.lastModified) from qtiassessmenttestsession a2session") - .append(" where asession.testEntry.key=a2session.testEntry.key and a2session.repositoryEntry.key=asession.repositoryEntry.key"); + .append(" where asession.testEntry.key=a2session.testEntry.key and a2session.repositoryEntry.key=asession.repositoryEntry.key") + .append(" and a2session.exploded=false and a2session.cancelled=false"); if(searchParams.getNodeIdent() != null ) { sb.append(" and a2session.subIdent=asession.subIdent"); } else { diff --git a/src/main/java/org/olat/ims/qti21/questionimport/CSVToAssessmentItemConverter.java b/src/main/java/org/olat/ims/qti21/questionimport/CSVToAssessmentItemConverter.java index 67c2a398c3d923c7ea306535610e798d02ba0373..7c713bae5f03383437c350772832505e6aa54d04 100644 --- a/src/main/java/org/olat/ims/qti21/questionimport/CSVToAssessmentItemConverter.java +++ b/src/main/java/org/olat/ims/qti21/questionimport/CSVToAssessmentItemConverter.java @@ -97,6 +97,7 @@ public class CSVToAssessmentItemConverter { public void parse(String input) { List<String[]> lines = getLines(input); for(String[] line:lines) { + currentLine++; processLine(line); } buildCurrentItem(); diff --git a/src/main/java/org/olat/ims/qti21/ui/ConfirmReopenAssessmentEntryController.java b/src/main/java/org/olat/ims/qti21/ui/ConfirmReopenAssessmentEntryController.java new file mode 100644 index 0000000000000000000000000000000000000000..c73f98c61812e2fc5e81911723ca7e4e3615c51f --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/ui/ConfirmReopenAssessmentEntryController.java @@ -0,0 +1,144 @@ +package org.olat.ims.qti21.ui; + +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.impl.FormBasicController; +import org.olat.core.gui.components.form.flexible.impl.FormEvent; +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; +import org.olat.course.assessment.CourseAssessmentService; +import org.olat.course.nodes.IQTESTCourseNode; +import org.olat.course.run.scoring.ScoreEvaluation; +import org.olat.course.run.userview.UserCourseEnvironment; +import org.olat.ims.qti21.AssessmentTestSession; +import org.olat.modules.assessment.AssessmentEntry; +import org.olat.modules.assessment.AssessmentService; +import org.olat.modules.assessment.Role; +import org.olat.modules.assessment.model.AssessmentEntryStatus; +import org.olat.repository.RepositoryEntry; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 28 juil. 2020<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class ConfirmReopenAssessmentEntryController extends FormBasicController { + + private FormLink readOnlyButton; + + private Object userObject; + private final IQTESTCourseNode courseNode; + private final AssessmentTestSession session; + private final UserCourseEnvironment assessedUserCourseEnv; + + @Autowired + private AssessmentService assessmentService; + @Autowired + private CourseAssessmentService courseAssessmentService; + + /** + * Confirm reopen of the assessment in a course element. + * + * @param ureq The user request + * @param wControl The window control + * @param assessedUserCourseEnv The user course environment of the assessed identity + * @param courseNode The course node + * @param session The assessment test session + */ + public ConfirmReopenAssessmentEntryController(UserRequest ureq, WindowControl wControl, + UserCourseEnvironment assessedUserCourseEnv, IQTESTCourseNode courseNode, + AssessmentTestSession session) { + super(ureq, wControl, "confirm_reopen_assessment"); + this.assessedUserCourseEnv = assessedUserCourseEnv; + this.courseNode = courseNode; + this.session = session; + initForm(ureq); + } + + /** + * Confirm reopen of the assessment of test done within a test + * repository entry. + * + * @param ureq The user request + * @param wControl The window control + * @param session The assessment test session + */ + public ConfirmReopenAssessmentEntryController(UserRequest ureq, WindowControl wControl, + AssessmentTestSession session) { + super(ureq, wControl, "confirm_reopen_assessment"); + assessedUserCourseEnv = null; + courseNode = null; + this.session = session; + initForm(ureq); + } + + public AssessmentTestSession getAssessmentTestSession() { + return session; + } + + public Object getUserObject() { + return userObject; + } + + public void setUserObject(Object userObject) { + this.userObject = userObject; + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + + uifactory.addFormCancelButton("cancel", formLayout, ureq, getWindowControl()); + uifactory.addFormSubmitButton("reopen.assessment", formLayout); + readOnlyButton = uifactory.addFormLink("correction.readonly", formLayout, Link.BUTTON); + } + + @Override + protected void doDispose() { + // + } + + @Override + protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { + if(readOnlyButton == source) { + fireEvent(ureq, Event.DONE_EVENT); + } + super.formInnerEvent(ureq, source, event); + } + + @Override + protected void formOK(UserRequest ureq) { + doReopen(); + fireEvent(ureq, Event.CHANGED_EVENT); + } + + @Override + protected void formCancelled(UserRequest ureq) { + fireEvent(ureq, Event.CANCELLED_EVENT); + } + + private void doReopen() { + if(courseNode != null) { + ScoreEvaluation scoreEval = assessedUserCourseEnv.getScoreAccounting().evalCourseNode(courseNode); + if (scoreEval != null) { + ScoreEvaluation reopenedEval = new ScoreEvaluation(scoreEval.getScore(), scoreEval.getPassed(), + AssessmentEntryStatus.inReview, scoreEval.getUserVisible(), scoreEval.getCurrentRunCompletion(), + scoreEval.getCurrentRunStatus(), scoreEval.getAssessmentID()); + courseAssessmentService.updateScoreEvaluation(courseNode, reopenedEval, assessedUserCourseEnv, + getIdentity(), false, Role.coach); + } + } else if(session != null) { + RepositoryEntry testEntry = session.getTestEntry(); + AssessmentEntry assessmentEntry = assessmentService.loadAssessmentEntry(session.getIdentity(), testEntry, null, testEntry); + if (assessmentEntry != null) { + assessmentEntry.setAssessmentStatus(AssessmentEntryStatus.inReview); + assessmentService.updateAssessmentEntry(assessmentEntry); + } + } + } +} diff --git a/src/main/java/org/olat/ims/qti21/ui/QTI21AssessmentDetailsController.java b/src/main/java/org/olat/ims/qti21/ui/QTI21AssessmentDetailsController.java index 9c7a4af6a4961cce0f7686bd2f16110170fcb885..13726e65851ed647ebaad90c387a1bcedbf40e44 100644 --- a/src/main/java/org/olat/ims/qti21/ui/QTI21AssessmentDetailsController.java +++ b/src/main/java/org/olat/ims/qti21/ui/QTI21AssessmentDetailsController.java @@ -143,6 +143,7 @@ public class QTI21AssessmentDetailsController extends FormBasicController { private DialogBoxController retrieveConfirmationCtr; private CloseableCalloutWindowController calloutCtrl; private CorrectionIdentityAssessmentItemListController correctionCtrl; + private ConfirmReopenAssessmentEntryController reopenForCorrectionCtrl; private ConfirmAssessmentTestSessionInvalidationController invalidateConfirmationCtr; private ConfirmAssessmentTestSessionRevalidationController revalidateConfirmationCtr; @@ -376,6 +377,15 @@ public class QTI21AssessmentDetailsController extends FormBasicController { } cmc.deactivate(); cleanUp(); + } else if(reopenForCorrectionCtrl == source) { + cmc.deactivate(); + AssessmentTestSession session = reopenForCorrectionCtrl.getAssessmentTestSession(); + cleanUp(); + if(event == Event.DONE_EVENT || event == Event.CHANGED_EVENT) { + fireEvent(ureq, Event.CHANGED_EVENT); + AssessmentTestSession testSession = qtiService.getAssessmentTestSession(session.getKey()); + doOpenCorrection(ureq, testSession); + } } else if(toolsCtrl == source) { if(event == Event.DONE_EVENT) { calloutCtrl.deactivate(); @@ -389,6 +399,7 @@ public class QTI21AssessmentDetailsController extends FormBasicController { removeAsListenerAndDispose(invalidateConfirmationCtr); removeAsListenerAndDispose(revalidateConfirmationCtr); removeAsListenerAndDispose(retrieveConfirmationCtr); + removeAsListenerAndDispose(reopenForCorrectionCtrl); removeAsListenerAndDispose(correctionCtrl); removeAsListenerAndDispose(resetToolCtrl); removeAsListenerAndDispose(calloutCtrl); @@ -398,6 +409,7 @@ public class QTI21AssessmentDetailsController extends FormBasicController { invalidateConfirmationCtr = null; revalidateConfirmationCtr = null; retrieveConfirmationCtr = null; + reopenForCorrectionCtrl = null; correctionCtrl = null; resetToolCtrl = null; calloutCtrl = null; @@ -443,6 +455,16 @@ public class QTI21AssessmentDetailsController extends FormBasicController { } private void doCorrection(UserRequest ureq, AssessmentTestSession session) { + boolean assessmentEntryDone = isAssessmentEntryDone(); + if(assessmentEntryDone && !readOnly) { + confirmReopenAssessment(ureq, session); + } else { + doOpenCorrection(ureq, session); + } + } + + private void doOpenCorrection(UserRequest ureq, AssessmentTestSession session) { + boolean assessmentEntryDone = isAssessmentEntryDone(); RepositoryEntry testEntry = session.getTestEntry(); File unzippedDirRoot = FileResourceManager.getInstance().unzipFileResource(testEntry.getOlatResource()); ResolvedAssessmentTest resolvedAssessmentTest = qtiService.loadAndResolveAssessmentTest(unzippedDirRoot, false, false); @@ -454,11 +476,11 @@ public class QTI21AssessmentDetailsController extends FormBasicController { lastSessions.put(assessedIdentity, session); Map<Identity, TestSessionState> testSessionStates = new HashMap<>(); testSessionStates.put(assessedIdentity, testSessionState); - boolean assessmentEntryDone = isCorrectionReadOnly(); + boolean correctionReadOnly = readOnly || assessmentEntryDone; CorrectionOverviewModel model = new CorrectionOverviewModel(entry, courseNode, testEntry, resolvedAssessmentTest, manifestBuilder, lastSessions, testSessionStates); correctionCtrl = new CorrectionIdentityAssessmentItemListController(ureq, getWindowControl(), stackPanel, - model, assessedIdentity, assessmentEntryDone); + model, assessedIdentity, correctionReadOnly); listenTo(correctionCtrl); stackPanel.pushController(translate("correction"), correctionCtrl); } catch(Exception e) { @@ -467,9 +489,20 @@ public class QTI21AssessmentDetailsController extends FormBasicController { } } - private boolean isCorrectionReadOnly() { - if(readOnly) return true; + private void confirmReopenAssessment(UserRequest ureq, AssessmentTestSession session) { + if(guardModalController(reopenForCorrectionCtrl)) return; + reopenForCorrectionCtrl = new ConfirmReopenAssessmentEntryController(ureq, getWindowControl(), + assessedUserCourseEnv, courseNode, session); + listenTo(reopenForCorrectionCtrl); + + cmc = new CloseableModalController(getWindowControl(), "close", reopenForCorrectionCtrl.getInitialComponent(), + true, translate("reopen.assessment.title")); + cmc.activate(); + listenTo(cmc); + } + + private boolean isAssessmentEntryDone() { if(assessedUserCourseEnv != null) { AssessmentEvaluation eval = assessedUserCourseEnv.getScoreAccounting().getScoreEvaluation(courseNode); return eval != null && eval.getAssessmentStatus() == AssessmentEntryStatus.done; diff --git a/src/main/java/org/olat/ims/qti21/ui/QTI21AssessmentTestSessionTableModel.java b/src/main/java/org/olat/ims/qti21/ui/QTI21AssessmentTestSessionTableModel.java index 957c7468deb6692010114d97854b803054ed65f2..58e84e16da80e5c2ff702f68ce1cf5b61c80a228 100644 --- a/src/main/java/org/olat/ims/qti21/ui/QTI21AssessmentTestSessionTableModel.java +++ b/src/main/java/org/olat/ims/qti21/ui/QTI21AssessmentTestSessionTableModel.java @@ -122,17 +122,19 @@ public class QTI21AssessmentTestSessionTableModel extends DefaultFlexiTableDataM } return ""; } - case open: { - Date finished = session.getTestSession().getFinishTime(); - return finished == null ? Boolean.FALSE : Boolean.TRUE; - } + case open: return Boolean.valueOf(!isTestSessionOpen(session)); case correction: return (lastSession != null && lastSession.equals(session.getTestSession())); - case invalidate: return !session.getTestSession().isCancelled() && !session.getTestSession().isExploded(); + case invalidate: return !isTestSessionOpen(session) && !session.getTestSession().isCancelled() && !session.getTestSession().isExploded(); case tools: return session.getToolsLink(); default: return "ERROR"; } } + private boolean isTestSessionOpen(QTI21AssessmentTestSessionDetails session) { + Date finished = session.getTestSession().getFinishTime(); + return finished == null; + } + private Date getTerminationTime(QTI21AssessmentTestSessionDetails session) { Date endTime = session.getTestSession().getTerminationTime(); if(endTime == null) { diff --git a/src/main/java/org/olat/ims/qti21/ui/_content/confirm_reopen_assessment.html b/src/main/java/org/olat/ims/qti21/ui/_content/confirm_reopen_assessment.html new file mode 100644 index 0000000000000000000000000000000000000000..13beba566959a8d9a4d4834b9276d1083f60a7ae --- /dev/null +++ b/src/main/java/org/olat/ims/qti21/ui/_content/confirm_reopen_assessment.html @@ -0,0 +1,10 @@ +<p>$r.translate("reopen.assessment.text")</p> +<div class="o_button_group"> + $r.render("cancel") + #if($r.available("reopen.assessment")) + $r.render("reopen.assessment") + #end + #if($r.available("correction.readonly")) + $r.render("correction.readonly") + #end +</div> \ No newline at end of file diff --git a/src/main/java/org/olat/ims/qti21/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/ims/qti21/ui/_i18n/LocalStrings_de.properties index 05ce41e31d1efeada125d023bd510f404f55dc96..1ed94bcc933a5d3469ddc5b42aead40a3542d85d 100644 --- a/src/main/java/org/olat/ims/qti21/ui/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/ims/qti21/ui/_i18n/LocalStrings_de.properties @@ -80,6 +80,7 @@ confirm.suspend.test=$org.olat.modules.iq\:confirmSuspend confirmation=Best\u00E4tigung correct.solution=Korrekte L\u00F6sung correction=Korrigieren +correction.readonly=Korrektur nur sehen correction.workflow=Korrekturworkflow correction.workflow.anonymous=Anonym countdown.running=Zeitlimit {1} Sekunden\: nur <strong>{0} Sekunden \u00FCbrigs</strong> @@ -143,7 +144,7 @@ interaction.order.target=Ziel invalidate=Annullieren invalidate.overwrite=Annullieren und Resultat \u00FCbertragen invalidate.test.confirm.title=Annullieren -invalidate.test.confirm.text=Wollen Sie den Test Session ung\u00FCltig machen? Die Daten werden nicht gel\u00F6scht. +invalidate.test.confirm.text=Wollen Sie diese Test-Session als ung\u00FCltig markieren? Die Daten werden nicht gel\u00F6scht. item.comment=Kommentar kprim.minus=Falsch kprim.plus=Richtig @@ -193,6 +194,9 @@ question.progress.noMaxScore=$org.olat.modules.iq\:noMaxScore question.progress.score=$org.olat.modules.iq\:actualPoints question.title=Frage {0} questions=Anzahl Fragen im Test +reopen.assessment=Bewertung wiederöffnen +reopen.assessment.title=Abgeschlossene Bewertung wieder \u00F6ffnen +reopen.assessment.text=Diese Bewertung ist bereits abgeschlossen. Wollen Sie sie wieder \u00F6ffnen, um den Test erneut zu korrigieren? reset.data=Daten zur\u00FCcksetzen reset.test.data.acknowledge=Ich verstehe, dass die Daten endg\u00FCltig gel\u00F6scht werden. reset.test.data.text=Wollen Sie wirklich alle Daten von dem Test zur\u00FCcksetzen? Die Resultate von <strong>{0} Benutzern</strong> werden definitiv gel\u00F6scht. @@ -290,8 +294,8 @@ upload.explanation=Datei auf lokalem Computer f\u00FCr \u00DCbertragung w\u00E4h validate.xml.signature=Testquittung validieren validate.xml.signature.file=XML Datei validate.xml.signature.ok=Testquittung und Datei konnte erfolgreich validiert werden. -warning.assignment.done=Korrektur von diesem Test ist abgeschlossen. Wenn dieser Test ung\u00FCltig gemachen wird, werden alle Korrekturen verloren. -warning.assignment.inProcess=Korrektur von diesem Test wurde angefangen. Wenn dieser Test ung\u00FCltig gemachen wird, werden alle Korrekturen verloren. +warning.assignment.done=Die Korrektur dieses Tests ist bereits abgeschlossen. Wird diese Test-Session als ung\u00FCltig markiert (annulliert) wird, so gehen alle bestehenden Korrekturen verloren. +warning.assignment.inProcess=Dieser Test befindet sich bereits in Korrektur. Wird diese Test-Session als ung\u00FCltig markiert (annulliert), so gehen alle Korrekturen verloren. warning.download.log=Es gibt leider kein Logdatei f\u00FCr diesen Test. warning.reset.assessmenttest.data=Die Test-Resultate wurden von einem Administrator oder Kursbesitzer zur\u00FCckgesetzt. Sie k\u00F6nnen den Test nicht fortsetzen und m\u00FCssen ihn erneut starten. warning.reset.test.data.nobody=Es gibt kein Teilnehmer zu zur\u00FCcksetzen diff --git a/src/main/java/org/olat/ims/qti21/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/ims/qti21/ui/_i18n/LocalStrings_en.properties index bae8ed6dd36fad24cbb3e0570165d4393570c17d..5be1eefe82d97c31922a55c2be17deab204ec26f 100644 --- a/src/main/java/org/olat/ims/qti21/ui/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/ims/qti21/ui/_i18n/LocalStrings_en.properties @@ -80,6 +80,7 @@ confirm.suspend.test=$org.olat.modules.iq\:confirmSuspend confirmation=Confirmation correct.solution=Correct solution correction=Grade +correction.readonly=See correction read only correction.workflow=Correction workflow correction.workflow.anonymous=Anonymous countdown.running=Time limit {1} seconds\: <strong>{0} seconds left</strong> @@ -143,7 +144,7 @@ interaction.order.target=Target invalidate=Invalidate invalidate.overwrite=Invalidate and transfer result invalidate.test.confirm.title=Invalidate -invalidate.test.confirm.text=Do you want to invalidate this test session? The date will not be deleted. +invalidate.test.confirm.text=Do you want to mark this test session as invalid? The data will not be deleted. item.comment=Comment kprim.minus=False kprim.plus=True @@ -193,6 +194,9 @@ question.progress.noMaxScore=$org.olat.modules.iq\:noMaxScore question.progress.score=$org.olat.modules.iq\:actualPoints question.title=Question {0} questions=Number of questions in test +reopen.assessment=Reopen assessment +reopen.assessment.title=Reopen closed assessment +reopen.assessment.text=This assessment is already closed. Do you want to reopen it to correct the test? reset.data=Reset data reset.test.data.acknowledge=I understand that the data will be definitely deleted. reset.test.data.text=Do you really want to reset the assessment data of test? The results of <strong>{0} users</strong> will be definitively deleted. @@ -290,8 +294,8 @@ upload.explanation=Select a file from your computer to upload it validate.xml.signature=Validate test receipt validate.xml.signature.file=XML file validate.xml.signature.ok=Test receipt and results was successfully validated. -warning.assignment.done=Grading of this test is done. If the test is invalidated, all correction will be lost. -warning.assignment.inProcess=Grading of this test was already started. If the test is invalidated, all correction will be lost. +warning.assignment.done=The grading of this test has already been completed. If this test session is marked as invalid, all existing corrections will be lost. +warning.assignment.inProcess=The grading of the test has already started. If this test session is marked as invalid, all existing corrections will be lost. warning.download.log=There is not a log file for this test. warning.reset.assessmenttest.data=The test results were reset by an administrator or course owner. You cannot continue the test and have to restart it. warning.suspended.ended.assessmenttest=You have already suspended or ended this test, probably in an other window. Please close this window. diff --git a/src/main/java/org/olat/ims/qti21/ui/assessment/CorrectionAssessmentItemListController.java b/src/main/java/org/olat/ims/qti21/ui/assessment/CorrectionAssessmentItemListController.java index 7d1ccbc8117f6d03a6f7456f9238e14fbc3fbcb9..9d137b4834af29b9f5d24606e9700a4e855a8fe5 100644 --- a/src/main/java/org/olat/ims/qti21/ui/assessment/CorrectionAssessmentItemListController.java +++ b/src/main/java/org/olat/ims/qti21/ui/assessment/CorrectionAssessmentItemListController.java @@ -469,10 +469,10 @@ public class CorrectionAssessmentItemListController extends FormBasicController itemRef, itemNode); itemCorrection.setItemSession(reloadItemSession); - boolean readOnly = model.isReadOnly(assessedIdentity); + boolean assessmentEntryDone = model.isAssessmentEntryDone(assessedIdentity); identityItemCtrl = new CorrectionIdentityAssessmentItemNavigationController(ureq, getWindowControl(), model.getTestEntry(), model.getResolvedAssessmentTest(), itemCorrection, listEntry, - selectedItemSessions, model, null, readOnly, true); + selectedItemSessions, model, null, assessmentEntryDone, true); listenTo(identityItemCtrl); updatePreviousNext(); diff --git a/src/main/java/org/olat/ims/qti21/ui/assessment/CorrectionIdentityListController.java b/src/main/java/org/olat/ims/qti21/ui/assessment/CorrectionIdentityListController.java index c23406eeb58e16c5fcb9e9c7971f1699a2e20e25..65bf5eedc1d085fc2b68892c7ff395864c7d7a4c 100644 --- a/src/main/java/org/olat/ims/qti21/ui/assessment/CorrectionIdentityListController.java +++ b/src/main/java/org/olat/ims/qti21/ui/assessment/CorrectionIdentityListController.java @@ -52,11 +52,14 @@ import org.olat.core.id.Identity; import org.olat.core.id.Roles; import org.olat.core.util.coordinate.CoordinatorManager; import org.olat.core.util.coordinate.LockResult; +import org.olat.course.assessment.AssessmentHelper; +import org.olat.course.run.userview.UserCourseEnvironment; import org.olat.ims.qti21.AssessmentItemSession; import org.olat.ims.qti21.AssessmentTestSession; import org.olat.ims.qti21.QTI21Module; import org.olat.ims.qti21.QTI21Module.CorrectionWorkflow; import org.olat.ims.qti21.QTI21Service; +import org.olat.ims.qti21.ui.ConfirmReopenAssessmentEntryController; import org.olat.ims.qti21.ui.assessment.CorrectionIdentityTableModel.IdentityCols; import org.olat.ims.qti21.ui.assessment.components.CorrectedFlexiCellRenderer; import org.olat.ims.qti21.ui.assessment.components.NotCorrectedFlexiCellRenderer; @@ -98,6 +101,7 @@ public class CorrectionIdentityListController extends FormBasicController { private CloseableModalController cmc; private ConfirmSaveTestsController confirmSaveTestCtrl; + private ConfirmReopenAssessmentEntryController reopenForCorrectionCtrl; private CorrectionIdentityAssessmentItemListController identityItemListCtrl; private final boolean isAdministrativeUser; @@ -320,6 +324,14 @@ public class CorrectionIdentityListController extends FormBasicController { } cmc.deactivate(); cleanUp(); + } else if(reopenForCorrectionCtrl == source) { + cmc.deactivate(); + CorrectionIdentityRow row = (CorrectionIdentityRow)reopenForCorrectionCtrl.getUserObject(); + cleanUp(); + if(event == Event.CHANGED_EVENT || event == Event.DONE_EVENT) { + model.discardAssessmentEntryDone(row.getIdentity()); + doOpenCorrection(ureq, row); + } } else if(cmc == source) { cleanUp(); } @@ -327,8 +339,10 @@ public class CorrectionIdentityListController extends FormBasicController { } private void cleanUp() { + removeAsListenerAndDispose(reopenForCorrectionCtrl); removeAsListenerAndDispose(confirmSaveTestCtrl); removeAsListenerAndDispose(cmc); + reopenForCorrectionCtrl = null; confirmSaveTestCtrl = null; cmc = null; } @@ -361,10 +375,36 @@ public class CorrectionIdentityListController extends FormBasicController { } Identity assessedIdentity = row.getIdentity(); - boolean readOnly = model.isReadOnly(assessedIdentity); + boolean assessmentEntryDone = model.isAssessmentEntryDone(assessedIdentity); + if(assessmentEntryDone) { + doReopenForCorrection(ureq, row); + } else { + doOpenCorrection(ureq, row); + } + } + + private void doReopenForCorrection(UserRequest ureq, CorrectionIdentityRow row) { + if(guardModalController(reopenForCorrectionCtrl)) return; + + UserCourseEnvironment assessedUserCourseEnv = AssessmentHelper + .createAndInitUserCourseEnvironment(row.getIdentity(), model.getCourseEnvironment()); + reopenForCorrectionCtrl = new ConfirmReopenAssessmentEntryController(ureq, getWindowControl(), + assessedUserCourseEnv, model.getCourseNode(), row.getCandidateSession()); + reopenForCorrectionCtrl.setUserObject(row); + listenTo(reopenForCorrectionCtrl); + + cmc = new CloseableModalController(getWindowControl(), "close", reopenForCorrectionCtrl.getInitialComponent(), + true, translate("reopen.assessment.title")); + cmc.activate(); + listenTo(cmc); + } + + private void doOpenCorrection(UserRequest ureq, CorrectionIdentityRow row) { + Identity assessedIdentity = row.getIdentity(); + boolean assessmentEntryDone = model.isAssessmentEntryDone(assessedIdentity); String title = anonymous ? row.getUser() : userManager.getUserDisplayName(row.getIdentity()); identityItemListCtrl = new CorrectionIdentityAssessmentItemListController(ureq, getWindowControl(), stackPanel, - model, assessedIdentity, title, readOnly); + model, assessedIdentity, title, assessmentEntryDone); listenTo(identityItemListCtrl); String crumb; diff --git a/src/main/java/org/olat/ims/qti21/ui/assessment/CorrectionOverviewController.java b/src/main/java/org/olat/ims/qti21/ui/assessment/CorrectionOverviewController.java index ccfa892765a0abce1add22de5a76d5def084d678..206a334d09253e645b7f5138ccf5d7891eec1b92 100644 --- a/src/main/java/org/olat/ims/qti21/ui/assessment/CorrectionOverviewController.java +++ b/src/main/java/org/olat/ims/qti21/ui/assessment/CorrectionOverviewController.java @@ -109,7 +109,7 @@ public class CorrectionOverviewController extends BasicController implements Too identifierToRefs.put(itemRef.getIdentifier(), itemRef); } - List<Identity> assessedIdentities = getAssessedIdentities(); + List<Identity> assessedIdentities = initializeAssessedIdentities(); model = new CorrectionOverviewModel(courseEntry, courseNode, testEntry, resolvedAssessmentTest, manifestBuilder, assessedIdentities); @@ -134,7 +134,11 @@ public class CorrectionOverviewController extends BasicController implements Too return model.getNumberOfAssessedIdentities(); } - private List<Identity> getAssessedIdentities() { + public List<Identity> getAssessedIdentities() { + return model.getAssessedIdentities(); + } + + private List<Identity> initializeAssessedIdentities() { Set<Identity> identitiesSet; if(asOptions.getGroup() != null) { List<Identity> identities = businessGroupService.getMembers(asOptions.getGroup(), GroupRoles.participant.name()); diff --git a/src/main/java/org/olat/ims/qti21/ui/assessment/CorrectionOverviewModel.java b/src/main/java/org/olat/ims/qti21/ui/assessment/CorrectionOverviewModel.java index 4e190d3a5b02182ad075de66f869beca67bfd910..17dedb9adb07dc61529e2f941f6b2905238b8479 100644 --- a/src/main/java/org/olat/ims/qti21/ui/assessment/CorrectionOverviewModel.java +++ b/src/main/java/org/olat/ims/qti21/ui/assessment/CorrectionOverviewModel.java @@ -262,7 +262,11 @@ public class CorrectionOverviewModel { return manifestBuilder.getResourceBuilderByHref(itemRef.getHref().toString()); } - public boolean isReadOnly(Identity assessedIdentity) { + public void discardAssessmentEntryDone(Identity assessedIdentity) { + assessedIdentitiesDone.remove(assessedIdentity); + } + + public boolean isAssessmentEntryDone(Identity assessedIdentity) { Boolean done = assessedIdentitiesDone.computeIfAbsent(assessedIdentity, identity -> { if(getCourseNode() != null) { UserCourseEnvironment assessedUserCourseEnv = AssessmentHelper diff --git a/src/main/java/org/olat/ims/qti21/ui/assessment/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/ims/qti21/ui/assessment/_i18n/LocalStrings_de.properties index 72fc0bb3dcb865a0dc495109ea44f0d23613f317..bcb52f79a9652e6d69a7d0c606bfdd3a71bf8375 100644 --- a/src/main/java/org/olat/ims/qti21/ui/assessment/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/ims/qti21/ui/assessment/_i18n/LocalStrings_de.properties @@ -26,6 +26,7 @@ not.responded=Die Frage wurde nicht beantwortet overview.tests=\u00DCbersicht und abschliessen previous.item=Vorherige Frage previous.user=Vorheriger Benutzer +reopen.assessment.title=$org.olat.ims.qti21.ui\:reopen.assessment.title save.back=Speichern und zur \u00DCbersicht save.next=Speichern und n\u00E4chste Frage save.next.identity=Speichern und n\u00E4chster Teilnehmer diff --git a/src/main/java/org/olat/ims/qti21/ui/assessment/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/ims/qti21/ui/assessment/_i18n/LocalStrings_en.properties index 524ddb4f1e4df2d52ab33ed3b2a44941eee606a3..f62c1d79e5a40162f3b470d43be2a4fa0812555d 100644 --- a/src/main/java/org/olat/ims/qti21/ui/assessment/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/ims/qti21/ui/assessment/_i18n/LocalStrings_en.properties @@ -26,6 +26,7 @@ override.score=Override score overview.tests=Overview and closing previous.item=Previous question previous.user=Previous user +reopen.assessment.title=$org.olat.ims.qti21.ui\:reopen.assessment.title save.back=Save and back to overview save.next=Save and next question save.next.identity=Save and next participant diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/ims/qti21/ui/editor/_i18n/LocalStrings_de.properties index a17019eecc96f189c32d213b8aaaa76f895267a3..8cba37429b84c9ae2cc6fc19e973bb4d4ce98d6b 100644 --- a/src/main/java/org/olat/ims/qti21/ui/editor/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/ims/qti21/ui/editor/_i18n/LocalStrings_de.properties @@ -90,7 +90,7 @@ fib.tolerance.mode.relative.example=Prozent, zum Beispiel 15 oder 99.0 fib.tolerance.mode.relative.help=Die Schranke stellt eine relative Zahl in Prozent dar.\u2028Beispiel\: L\u00F6sung 20, Untere Schranke 10, Obere Schranke 10 → Alle L\u00F6sungen zwischen 18 und 22 sind g\u00FCltig, denn die untere Schranke bedeutet minus 10% (20-2) und die obere Schranke plus 10% (20+2). fib.tolerance.up=Obere Schranke file=Datei -force.inherited.max.attempts=Setzt alle unter "L\u00F6sungsversuche" zu "Vererbt" +force.inherited.max.attempts=Anzahl L\u00F6sungsversuche an alle Unterelemente (Fragen, Sektionen) vererben form.choice=Auswahl form.drawing=Zeichnen form.essay=Freitext diff --git a/src/main/java/org/olat/ims/qti21/ui/editor/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/ims/qti21/ui/editor/_i18n/LocalStrings_en.properties index 53202636e26ce76327459d7196997aff2b0613e0..73c88a05be274d4384259ab222a51e0848b6fa97 100644 --- a/src/main/java/org/olat/ims/qti21/ui/editor/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/ims/qti21/ui/editor/_i18n/LocalStrings_en.properties @@ -90,7 +90,7 @@ fib.tolerance.mode.relative.example=Percentage, example 15 or 99.0 fib.tolerance.mode.relative.help=The solution is accepted until a lower and an upper bound. The bound is a relative number in percent. Example\: Solution 20, lower bound 10, upper bound 10 → all solutions between 18 and 22 are correct, as the lower bound means minus 10% (20-2) and the upper bound plus 10% (20+2). fib.tolerance.up=Upper bound file=File -force.inherited.max.attempts=Set all under "Number of attempts" to inherit +force.inherited.max.attempts=Inherit number of attempts to all sub-elements (sections, questions) form.choice=Choice form.drawing=Drawing form.essay=Essay diff --git a/src/main/java/org/olat/modules/grading/manager/GradingServiceImpl.java b/src/main/java/org/olat/modules/grading/manager/GradingServiceImpl.java index 9d9d0f22353f4d04513703c68194ce06137b4396..818da3c2b0ac30bc9fe102692de6af0db142b61c 100644 --- a/src/main/java/org/olat/modules/grading/manager/GradingServiceImpl.java +++ b/src/main/java/org/olat/modules/grading/manager/GradingServiceImpl.java @@ -36,6 +36,7 @@ import java.util.function.Function; import java.util.stream.Collectors; import org.apache.logging.log4j.Logger; +import org.olat.basesecurity.BaseSecurity; import org.olat.basesecurity.IdentityRef; import org.olat.commons.calendar.CalendarUtils; import org.olat.core.commons.persistence.DB; @@ -119,6 +120,8 @@ public class GradingServiceImpl implements GradingService, UserDataDeletable, Re @Autowired private MailManager mailManager; @Autowired + private BaseSecurity securityManager; + @Autowired private GradingModule gradingModule; @Autowired private TaxonomyModule taxonomyModule; @@ -306,7 +309,11 @@ public class GradingServiceImpl implements GradingService, UserDataDeletable, Re } for(IdentityTimeRecordStatistics record:records) { - GraderWithStatistics statistics = identityToStatistics.get(record.getKey()); + Long graderIdentityKey = record.getKey(); + GraderWithStatistics statistics = identityToStatistics.computeIfAbsent(graderIdentityKey, key -> { + Identity grader = securityManager.loadIdentityByKey(graderIdentityKey); + return new GraderWithStatistics(grader, GraderStatistics.empty(graderIdentityKey)); + }); statistics.addRecordedTimeInSeconds(record.getTime()); statistics.addRecordedMetadataTimeInSeconds(record.getMetadataTime()); } diff --git a/src/main/java/org/olat/user/ui/admin/UserAdminMainController.java b/src/main/java/org/olat/user/ui/admin/UserAdminMainController.java index 90ae5c7737bfdb7e9ca5e7ed4c3b9efe3239f9a9..716fd574fc6b55bdc321f3c8707fbf831c34f761 100644 --- a/src/main/java/org/olat/user/ui/admin/UserAdminMainController.java +++ b/src/main/java/org/olat/user/ui/admin/UserAdminMainController.java @@ -346,16 +346,16 @@ public class UserAdminMainController extends MainLayoutBasicController implement case "useradmin": return createUserSearchController(ureq, bwControl); // groups case "groupcoach": return createUserSearchController(ureq, bwControl, - SearchIdentityParams.resources(null, true, GroupRoles.coach, null, null, null, Identity.STATUS_VISIBLE_LIMIT), false, true, true); + SearchIdentityParams.resources(null, true, GroupRoles.coach, null, null, null, Identity.STATUS_VISIBLE_LIMIT), false, true, true, true); case "groupparticipant": return createUserSearchController(ureq, bwControl, - SearchIdentityParams.resources(null, true, GroupRoles.participant, null, null, null, Identity.STATUS_VISIBLE_LIMIT), false, true, true); + SearchIdentityParams.resources(null, true, GroupRoles.participant, null, null, null, Identity.STATUS_VISIBLE_LIMIT), false, true, true, true); // resources case "coauthors": return createUserSearchController(ureq, bwControl, - SearchIdentityParams.resources(GroupRoles.owner, true, null, null, null, null, Identity.STATUS_VISIBLE_LIMIT), false, true, true); + SearchIdentityParams.resources(GroupRoles.owner, true, null, null, null, null, Identity.STATUS_VISIBLE_LIMIT), false, true, true, true); case "courseparticipants": return createUserSearchController(ureq, bwControl, - SearchIdentityParams.resources(GroupRoles.participant, true, null, null, null, null, Identity.STATUS_VISIBLE_LIMIT), false, true, true); + SearchIdentityParams.resources(GroupRoles.participant, true, null, null, null, null, Identity.STATUS_VISIBLE_LIMIT), false, true, true, true); case "coursecoach": return createUserSearchController(ureq, bwControl, - SearchIdentityParams.resources(GroupRoles.coach, true, null, null, null, null, Identity.STATUS_VISIBLE_LIMIT), false, true, true); + SearchIdentityParams.resources(GroupRoles.coach, true, null, null, null, null, Identity.STATUS_VISIBLE_LIMIT), false, true, true, true); // status case "pendinggroup": return createUserSearchController(ureq, bwControl, Identity.STATUS_PENDING); case "inactivegroup": return createUserSearchController(ureq, bwControl, Identity.STATUS_INACTIVE); @@ -363,7 +363,7 @@ public class UserAdminMainController extends MainLayoutBasicController implement case "deletedusers": return createDeletedUserController(ureq, bwControl); // predefined queries case "userswithoutgroup": - return createUserSearchController(ureq, bwControl, SearchIdentityParams.withBusinesGroups(), false, true, true); + return createUserSearchController(ureq, bwControl, SearchIdentityParams.withBusinesGroups(), false, true, true, true); case "userswithoutemail": List<Identity> usersWithoutEmail = userManager.findVisibleIdentitiesWithoutEmail(); return new UsermanagerUserSearchController(ureq, bwControl, content, usersWithoutEmail, true, true, false); @@ -371,7 +371,7 @@ public class UserAdminMainController extends MainLayoutBasicController implement List<Identity> usersEmailDuplicates = userManager.findVisibleIdentitiesWithEmailDuplicates(); return new UsermanagerUserSearchController(ureq, bwControl, content, usersEmailDuplicates, true, true, false); case "noauthentication": return createUserSearchController(ureq, bwControl, - SearchIdentityParams.authenticationProviders(new String[]{ null }, Identity.STATUS_VISIBLE_LIMIT), false, true, true); + SearchIdentityParams.authenticationProviders(new String[]{ null }, Identity.STATUS_VISIBLE_LIMIT), false, true, true, true); // time based predefined queries case "created.lastweek": return createUserSearchControllerAfterDate(ureq, bwControl, Calendar.DAY_OF_MONTH, -7); case "created.lastmonth": return createUserSearchControllerAfterDate(ureq, bwControl, Calendar.MONTH, -1); @@ -383,7 +383,7 @@ public class UserAdminMainController extends MainLayoutBasicController implement private Controller getController(UserRequest ureq, Organisation organisation) { SearchIdentityParams predefinedQuery = SearchIdentityParams.organisation(organisation, Identity.STATUS_VISIBLE_LIMIT); - return createUserSearchController(ureq, getWindowControl(), predefinedQuery, true, true, true); + return createUserSearchController(ureq, getWindowControl(), predefinedQuery, true, true, true, true); } private Controller createInfoController(UserRequest ureq, WindowControl bwControl, Presentation template) { @@ -394,23 +394,23 @@ public class UserAdminMainController extends MainLayoutBasicController implement Calendar cal = Calendar.getInstance(); cal.add(unit, amount); SearchIdentityParams predefinedQuery = SearchIdentityParams.params(cal.getTime(), null, Identity.STATUS_VISIBLE_LIMIT); - return createUserSearchController(ureq, bwControl, predefinedQuery, false, true, true); + return createUserSearchController(ureq, bwControl, predefinedQuery, false, true, true, true); } private UsermanagerUserSearchController createUserSearchController(UserRequest ureq, WindowControl bwControl, OrganisationRoles role) { final OrganisationRoles[] roles = { role }; SearchIdentityParams predefinedQuery = SearchIdentityParams.params(roles, Identity.STATUS_VISIBLE_LIMIT); - return createUserSearchController(ureq, bwControl, predefinedQuery, false, true, true); + return createUserSearchController(ureq, bwControl, predefinedQuery, false, true, true, true); } private UsermanagerUserSearchController createUserSearchController(UserRequest ureq, WindowControl bwControl, CurriculumRoles role) { SearchIdentityParams predefinedQuery = SearchIdentityParams.resources(null, true, null, role, null, null, Identity.STATUS_VISIBLE_LIMIT); - return createUserSearchController(ureq, bwControl, predefinedQuery, false, true, true); + return createUserSearchController(ureq, bwControl, predefinedQuery, false, true, true, true); } private UsermanagerUserSearchController createUserSearchController(UserRequest ureq, WindowControl bwControl, Integer status) { SearchIdentityParams predefinedQuery = SearchIdentityParams.params(null, status); - return createUserSearchController(ureq, bwControl, predefinedQuery, false, true, false); + return createUserSearchController(ureq, bwControl, predefinedQuery, false, true, false, true); } private UsermanagerUserSearchController createUserSearchController(UserRequest ureq, WindowControl bwControl) { @@ -418,7 +418,7 @@ public class UserAdminMainController extends MainLayoutBasicController implement } private UsermanagerUserSearchController createUserSearchController(UserRequest ureq, WindowControl bwControl, - SearchIdentityParams predefinedQuery, boolean showOrganisationMove, boolean showDelete, boolean statusFilter) { + SearchIdentityParams predefinedQuery, boolean showOrganisationMove, boolean showDelete, boolean statusFilter, boolean tableSearch) { if(manageableOrganisations != null) { List<OrganisationRef> allowedOrganisations = new ArrayList<>(manageableOrganisations); if(predefinedQuery.getOrganisations() != null) { @@ -426,7 +426,8 @@ public class UserAdminMainController extends MainLayoutBasicController implement } predefinedQuery.setOrganisations(allowedOrganisations); } - return new UsermanagerUserSearchController(ureq, bwControl, content, predefinedQuery, true, showOrganisationMove, showDelete, statusFilter); + return new UsermanagerUserSearchController(ureq, bwControl, content, predefinedQuery, true, showOrganisationMove, showDelete, + statusFilter, tableSearch); } private UsermanagerUserSearchController createUserSearchController(UserRequest ureq, WindowControl bwControl, IdentityRelation relation) { diff --git a/src/main/java/org/olat/user/ui/admin/UserSearchTableController.java b/src/main/java/org/olat/user/ui/admin/UserSearchTableController.java index 96abdbf796aca5032e3da0c405addebfddabd1b2..785746aa4e282278e4762124ab092c24045b66cf 100644 --- a/src/main/java/org/olat/user/ui/admin/UserSearchTableController.java +++ b/src/main/java/org/olat/user/ui/admin/UserSearchTableController.java @@ -209,7 +209,7 @@ public class UserSearchTableController extends FormBasicController implements Ac tableEl.setExportEnabled(true); tableEl.setMultiSelect(true); tableEl.setSelectAllEnable(true); - tableEl.setSearchEnabled(true); + tableEl.setSearchEnabled(settings.isTableSearch()); tableEl.setAndLoadPersistedPreferences(ureq, "user_search_table"); initBulkActions(formLayout); diff --git a/src/main/java/org/olat/user/ui/admin/UserSearchTableSettings.java b/src/main/java/org/olat/user/ui/admin/UserSearchTableSettings.java index f8956ef30eb4679bea777bb22f43bef3616ff465..7d0833683ccc01f9e3e649943ef790fcd17decee 100644 --- a/src/main/java/org/olat/user/ui/admin/UserSearchTableSettings.java +++ b/src/main/java/org/olat/user/ui/admin/UserSearchTableSettings.java @@ -32,27 +32,36 @@ public class UserSearchTableSettings { private final boolean bulkDelete; private final boolean bulkOrganisationMove; private final boolean statusFilter; + private final boolean tableSearch; - private UserSearchTableSettings(boolean vCard, boolean bulkMail, boolean bulkOrganisationMove, boolean bulkDelete, boolean statusFilter) { + private UserSearchTableSettings(boolean vCard, boolean bulkMail, boolean bulkOrganisationMove, boolean bulkDelete, boolean statusFilter, boolean tableSearch) { this.vCard = vCard; this.bulkMail = bulkMail; this.bulkDelete = bulkDelete; this.bulkOrganisationMove = bulkOrganisationMove; this.statusFilter = statusFilter; + this.tableSearch = tableSearch; } - public static UserSearchTableSettings none() { - return new UserSearchTableSettings(false, false, false, false, false); + /** + * @return The table seetings with only the search enabled + */ + public static UserSearchTableSettings minimal() { + return new UserSearchTableSettings(false, false, false, false, false, true); } - public static UserSearchTableSettings withVCard(boolean bulkMail, boolean bulkOrganisationMove, boolean bulkDelete, boolean statusFilter) { - return new UserSearchTableSettings(false, bulkMail, bulkOrganisationMove, bulkDelete, statusFilter); + public static UserSearchTableSettings withVCard(boolean bulkMail, boolean bulkOrganisationMove, boolean bulkDelete, boolean statusFilter, boolean tableSearch) { + return new UserSearchTableSettings(false, bulkMail, bulkOrganisationMove, bulkDelete, statusFilter, tableSearch); } public boolean isVCard() { return vCard; } + public boolean isTableSearch() { + return tableSearch; + } + public boolean isBulkMail() { return bulkMail; } diff --git a/src/main/java/org/olat/user/ui/admin/lifecycle/ConfirmDeleteUserController.java b/src/main/java/org/olat/user/ui/admin/lifecycle/ConfirmDeleteUserController.java index 5e1cf362a684f082a48d76153b664ed37a7fed4f..2e9a4da5d49697e6762686a2ac9cc559c3719f5f 100644 --- a/src/main/java/org/olat/user/ui/admin/lifecycle/ConfirmDeleteUserController.java +++ b/src/main/java/org/olat/user/ui/admin/lifecycle/ConfirmDeleteUserController.java @@ -76,7 +76,13 @@ public class ConfirmDeleteUserController extends FormBasicController { if(formLayout instanceof FormLayoutContainer) { FormLayoutContainer layout = (FormLayoutContainer)formLayout; String names = buildUserNameList(toDelete); - layout.contextPut("names", names); + String message; + if(toDelete.size() == 1) { + message = translate("readyToDelete.delete.confirm.single", names); + } else { + message = translate("readyToDelete.delete.confirm", names); + } + layout.contextPut("msg", message); FormLayoutContainer layoutCont = FormLayoutContainer.createDefaultFormLayout("confirm", getTranslator()); formLayout.add("confirm", layoutCont); diff --git a/src/main/java/org/olat/user/ui/admin/lifecycle/UserLifecycleOverviewController.java b/src/main/java/org/olat/user/ui/admin/lifecycle/UserLifecycleOverviewController.java index d6edbb3d77c2d10addac9995e6958212dfeb63c0..562974c80571059cc723e3f76f6848607ec83abb 100644 --- a/src/main/java/org/olat/user/ui/admin/lifecycle/UserLifecycleOverviewController.java +++ b/src/main/java/org/olat/user/ui/admin/lifecycle/UserLifecycleOverviewController.java @@ -143,7 +143,7 @@ public class UserLifecycleOverviewController extends BasicController implements mainVC.put("tabPane", lifecycleTabbedPane); // ready to inactivate - readyToInactivateUserCtrl = new UserSearchTableController(ureq, getWindowControl(), stackPanel, UserSearchTableSettings.none()); + readyToInactivateUserCtrl = new UserSearchTableController(ureq, getWindowControl(), stackPanel, UserSearchTableSettings.minimal()); listenTo(readyToInactivateUserCtrl); lifecycleTabbedPane.addTab(translate("overview.ready.to.inactivate.user"), readyToInactivateUserCtrl.getInitialComponent()); @@ -151,7 +151,7 @@ public class UserLifecycleOverviewController extends BasicController implements readyToInactivateUserCtrl.loadModel(readyToInactivateSearchParams); // inactive - inactiveUserCtrl = new UserSearchTableController(ureq, getWindowControl(), stackPanel, UserSearchTableSettings.none()); + inactiveUserCtrl = new UserSearchTableController(ureq, getWindowControl(), stackPanel, UserSearchTableSettings.minimal()); listenTo(inactiveUserCtrl); lifecycleTabbedPane.addTab(translate("overview.inactive.user"), inactiveUserCtrl.getInitialComponent()); @@ -159,7 +159,7 @@ public class UserLifecycleOverviewController extends BasicController implements inactiveUserCtrl.loadModel(inactiveSearchParams); // ready to delete - readyToDeleteUserCtrl = new UserSearchTableController(ureq, getWindowControl(), stackPanel, UserSearchTableSettings.none()); + readyToDeleteUserCtrl = new UserSearchTableController(ureq, getWindowControl(), stackPanel, UserSearchTableSettings.minimal()); listenTo(readyToDeleteUserCtrl); lifecycleTabbedPane.addTab(translate("overview.ready.to.delete.user"), readyToDeleteUserCtrl.getInitialComponent()); diff --git a/src/main/java/org/olat/user/ui/admin/lifecycle/_content/confirm_delete.html b/src/main/java/org/olat/user/ui/admin/lifecycle/_content/confirm_delete.html index b6355ca0bfe6dbf15de6179171c45ba9db149500..71f7ac34b3f63a9d275849e181a032d4091cf419 100644 --- a/src/main/java/org/olat/user/ui/admin/lifecycle/_content/confirm_delete.html +++ b/src/main/java/org/olat/user/ui/admin/lifecycle/_content/confirm_delete.html @@ -1,5 +1,5 @@ <div class="o_error"> - <i class="o_icon o_icon-lg o_icon_important"> </i> $r.translate("readyToDelete.delete.confirm", $names) + <i class="o_icon o_icon-lg o_icon_important"> </i> $r.xssScan($msg) </div> #if($notAllDeleteable) <div class="o_warning"> diff --git a/src/main/java/org/olat/user/ui/admin/lifecycle/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/user/ui/admin/lifecycle/_i18n/LocalStrings_de.properties index cdb1fdbeee3f9ddd86a82fc13652e75d0814903a..e99014d6e61dc661ff7733e6bdd4ea479b926e54 100644 --- a/src/main/java/org/olat/user/ui/admin/lifecycle/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/user/ui/admin/lifecycle/_i18n/LocalStrings_de.properties @@ -37,6 +37,7 @@ overview.inactive.user=Deaktivierte Benutzer overview.ready.to.delete.user=Bereits zu l\u00F6schen overview.ready.to.inactivate.user=Benutzer ohne Aktivit\u00E4t readyToDelete.delete.confirm=Die ausgew\u00E4hlten Benutzerkonten "<strong>{0}</strong>" (inklusive pers\u00F6nliche Daten) werden gel\u00F6scht und k\u00F6nnen nicht wiederhergestellt werden. +readyToDelete.delete.confirm.single=Das ausgew\u00E4hlte Benutzerkonto "<strong>{0}</strong>" (inklusive pers\u00F6nliche Daten) wird gel\u00F6scht und kann nicht wiederhergestellt werden. readyToDelete.delete.confirm.check=Alle Dateien werden definitiv gel\u00F6scht und k\u00F6nnen nicht mehr wiederhergestellt werden. readyToDelete.delete.confirm.check.label=Best\u00E4tigung table.identity.deleted.name=Del_Benutzername diff --git a/src/main/java/org/olat/user/ui/admin/lifecycle/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/user/ui/admin/lifecycle/_i18n/LocalStrings_en.properties index 877c543b3dbb5418e4033fe91282c5dc1c8c7132..7f56d05f424f5392d13d7a47f1fafb8117ee2b14 100644 --- a/src/main/java/org/olat/user/ui/admin/lifecycle/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/user/ui/admin/lifecycle/_i18n/LocalStrings_en.properties @@ -37,6 +37,7 @@ overview.inactive.user=Deactivated users overview.ready.to.delete.user=Ready to delete overview.ready.to.inactivate.user=Users with no activity readyToDelete.delete.confirm=The selected user accounts "<strong>{0}</strong>" (along with personal data) will be deleted unrecoverably. +readyToDelete.delete.confirm.singlle=The selected user account "<strong>{0}</strong>" (along with personal data) will be deleted unrecoverably. readyToDelete.delete.confirm.check=All files will be permanently removed and cannot be recovered. readyToDelete.delete.confirm.check.label=Confirmation table.identity.deleted.name=Del_User name diff --git a/src/main/java/org/olat/user/ui/admin/lifecycle/_i18n/LocalStrings_fr.properties b/src/main/java/org/olat/user/ui/admin/lifecycle/_i18n/LocalStrings_fr.properties index 7a466a87092ca53b971000fbc4c980d0372c4d1c..012d00978b109b3cbf59d7b9ee67163b82453441 100644 --- a/src/main/java/org/olat/user/ui/admin/lifecycle/_i18n/LocalStrings_fr.properties +++ b/src/main/java/org/olat/user/ui/admin/lifecycle/_i18n/LocalStrings_fr.properties @@ -36,7 +36,8 @@ num.inactive.day.deletion=Nombre de jours d\u00E9sactiv\u00E9 avant effacement overview.inactive.user=Utilisateurs d\u00E9sactiv\u00E9s overview.ready.to.delete.user=Pr\u00EAt \u00E0 \u00EAtre effac\u00E9 overview.ready.to.inactivate.user=Utilisateurs sans activit\u00E9 -readyToDelete.delete.confirm=Les comptes d'utilisateur s\u00E9lectionn\u00E9s "<strong>{0}</strong>" (y compris les donn\u00E9es personnelles) seront supprim\u00E9s et ne pourront plus \u00EAtre r\u00E9tablis. +readyToDelete.delete.confirm=Les comptes des utilisateurs s\u00E9lectionn\u00E9s "<strong>{0}</strong>" (y compris les donn\u00E9es personnelles) seront supprim\u00E9s et ne pourront plus \u00EAtre r\u00E9tablis. +readyToDelete.delete.confirm.single=Le compte de l'utilisateur s\u00E9lectionn\u00E9 "<strong>{0}</strong>" (y compris les donn\u00E9es personnelles) sera supprim\u00E9 et ne pourra plus \u00EAtre r\u00E9tabli. readyToDelete.delete.confirm.check=Toutes les donn\u00E9es seront d\u00E9finitivement effac\u00E9es et ne pourront pas \u00EAtre r\u00E9cup\u00E9r\u00E9es. readyToDelete.delete.confirm.check.label=Confirmation table.identity.deleted.name=Del_Nom d'utilisateur diff --git a/src/main/resources/serviceconfig/olat.properties b/src/main/resources/serviceconfig/olat.properties index bfcc6cdd975a15f80347ec77935db715f9ca7530..b99a353f7a2de36ab1cf1e0b11c7249b253d495e 100644 --- a/src/main/resources/serviceconfig/olat.properties +++ b/src/main/resources/serviceconfig/olat.properties @@ -649,8 +649,6 @@ mobile.context=/mobile server.domainname=localhost # the port on which the container is listening server.port=8080 -# OLAT JMX server port (must be unique per node in a cluster) -jmx.rmi.port=3000 ######################################################################## # MathJAX CDN @@ -1114,10 +1112,10 @@ certificate.broker.url=vm://embedded?broker.persistent=false #if you use the jndi connection jms.broker.jndi=OpenOLATConnectionFactory -sysbus.broker.jndi=topic/sysbus -search.broker.jndi=queue/searchQueue -index.broker.jndi=queue/indexQueue -certificate.broker.jndi=queue/certificateQueue +sysbus.broker.jndi=topic/sysbus&broker.useJmx=false +search.broker.jndi=queue/searchQueue&broker.useJmx=false +index.broker.jndi=queue/indexQueue&broker.useJmx=false +certificate.broker.jndi=queue/certificateQueue&broker.useJmx=false ##### #query cache config for singlevm/cluster diff --git a/src/test/profile/mysql/olat.local.properties b/src/test/profile/mysql/olat.local.properties index b79526968cd496263d2d0d4662b9226cca4fb6dd..00b93048559e8478ca993fa069789ef04a9512a8 100644 --- a/src/test/profile/mysql/olat.local.properties +++ b/src/test/profile/mysql/olat.local.properties @@ -31,7 +31,6 @@ keepUserLoginAfterDeletion=true # do not run upgrades and scheduled jobs and such cluster.singleton.services = disabled -jmx.rmi.port=${test.env.jmx.rmi.port.0:1009} # SingleVM jms.broker.url jms.broker.url=vm://embedded?broker.persistent=false diff --git a/src/test/profile/oracle/olat.local.properties b/src/test/profile/oracle/olat.local.properties index 54304245ef03cb66adf38c68f848346210aadba6..078aaf062e37114a3ffd60c26386426891272371 100644 --- a/src/test/profile/oracle/olat.local.properties +++ b/src/test/profile/oracle/olat.local.properties @@ -31,7 +31,6 @@ keepUserLoginAfterDeletion=true # do not run upgrades and scheduled jobs and such cluster.singleton.services = disabled -jmx.rmi.port=${test.env.jmx.rmi.port.0:1009} # SingleVM jms.broker.url jms.broker.url=vm://embedded?broker.persistent=false diff --git a/src/test/profile/postgresql/olat.local.properties b/src/test/profile/postgresql/olat.local.properties index 1dda8938a7e5a5eba96e25519e09c541ebaaf74e..99fa6854fbcdf4df21a0d2d1ab046daa13649cab 100644 --- a/src/test/profile/postgresql/olat.local.properties +++ b/src/test/profile/postgresql/olat.local.properties @@ -31,7 +31,6 @@ keepUserLoginAfterDeletion=true # do not run upgrades and scheduled jobs and such cluster.singleton.services = disabled -jmx.rmi.port=${test.env.jmx.rmi.port.0:1009} # SingleVM jms.broker.url jms.broker.url=vm://embedded?broker.persistent=false diff --git a/src/test/resources/arquillian.xml b/src/test/resources/arquillian.xml index c2667baa98ff5397688bd864f7dab35de62fe77f..f656a58437d8e95cd593cd1915c1e90663339a3b 100644 --- a/src/test/resources/arquillian.xml +++ b/src/test/resources/arquillian.xml @@ -28,6 +28,7 @@ <property name="chromeDriverBinary">target/drone/675a673c111fdcc9678d11df0e69b334/chromedriver</property> <property name="firefoxDriverBinary">target/drone/ce03addb1fc8c24900011f90fc80f3c1/geckodriver</property> --> + <property name="firefoxDriverVersion">${webdriver.firefox.version:v0.27.0}</property> <property name="firefoxUserPreferences">src/test/profile/firefox/prefs.js</property> <property name="chromeDriverVersion">${webdriver.chrome.version:84.0.4147.30}</property> <property name="chromeArguments">${webdriver.chrome.arguments}</property>