diff --git a/src/main/java/org/olat/collaboration/CollaborationTools.java b/src/main/java/org/olat/collaboration/CollaborationTools.java index 7bebf36f6c8a94cf1a3665910c348cad89288774..d44dc60d21fad04260a1a8fe80194ab6f6349157 100644 --- a/src/main/java/org/olat/collaboration/CollaborationTools.java +++ b/src/main/java/org/olat/collaboration/CollaborationTools.java @@ -110,6 +110,7 @@ import org.olat.modules.portfolio.PortfolioService; import org.olat.modules.portfolio.PortfolioV2Module; import org.olat.modules.portfolio.manager.BinderUserInformationsDAO; import org.olat.modules.portfolio.ui.BinderController; +import org.olat.modules.wiki.DryRunAssessmentProvider; import org.olat.modules.wiki.WikiManager; import org.olat.modules.wiki.WikiSecurityCallback; import org.olat.modules.wiki.WikiSecurityCallbackImpl; @@ -521,7 +522,7 @@ public class CollaborationTools implements Serializable { initialPage = initialPage.substring(0, initialPage.length() - 2); } } - return WikiManager.getInstance().createWikiMainController(ureq, wControl, ores, callback, initialPage); + return WikiManager.getInstance().createWikiMainController(ureq, wControl, ores, callback, DryRunAssessmentProvider.create(), initialPage); } /** diff --git a/src/main/java/org/olat/course/learningpath/ui/LearningPathNodeConfigController.java b/src/main/java/org/olat/course/learningpath/ui/LearningPathNodeConfigController.java index 2d492632f2ead52b4b7d78c6330d0cd5f70fc7e0..25afddffee257e831a0c0b962e16d90941241e82 100644 --- a/src/main/java/org/olat/course/learningpath/ui/LearningPathNodeConfigController.java +++ b/src/main/java/org/olat/course/learningpath/ui/LearningPathNodeConfigController.java @@ -91,7 +91,7 @@ public class LearningPathNodeConfigController extends FormBasicController { @Override protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { setFormTitle("config.title"); - + setFormContextHelp("Learning path element"); KeyValues obligationKV = new KeyValues(); obligationKV.add(entry(AssessmentObligation.mandatory.name(), translate("config.obligation.mandatory"))); obligationKV.add(entry(AssessmentObligation.optional.name(), translate("config.obligation.optional"))); diff --git a/src/main/java/org/olat/course/learningpath/ui/_content/identity.html b/src/main/java/org/olat/course/learningpath/ui/_content/identity.html index 43833403c7e0d14e40bd943bdb022f46f9f8d914..017395035eaff4ab12e79626c209768fd34e6289 100644 --- a/src/main/java/org/olat/course/learningpath/ui/_content/identity.html +++ b/src/main/java/org/olat/course/learningpath/ui/_content/identity.html @@ -3,6 +3,7 @@ <i class="o_icon o_icon_learning_path"> </i> $r.translate("identity.list.title") <small>$r.translate("identity.list.course",$courseTitle)</small> + $r.contextHelpWithWrapper("Learning path profile") </h2> $r.render("user") $r.render("list") diff --git a/src/main/java/org/olat/course/nodeaccess/ui/NodeAccessSettingsController.java b/src/main/java/org/olat/course/nodeaccess/ui/NodeAccessSettingsController.java index 42ce0e9a6e24f248f119559ce2791bfbbedddb2c..871734d52e929e3c004132f41d7e1a63006547e7 100644 --- a/src/main/java/org/olat/course/nodeaccess/ui/NodeAccessSettingsController.java +++ b/src/main/java/org/olat/course/nodeaccess/ui/NodeAccessSettingsController.java @@ -103,7 +103,7 @@ public class NodeAccessSettingsController extends FormBasicController { @Override protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { setFormTitle("settings.title"); - + setFormContextHelp("Access course elements"); String nodeAccessTypeName = nodeAccessService.getNodeAccessTypeName(courseConfig.getNodeAccessType(), getLocale()); uifactory.addStaticTextElement("settings.type", nodeAccessTypeName, formLayout);; diff --git a/src/main/java/org/olat/course/nodes/CPCourseNode.java b/src/main/java/org/olat/course/nodes/CPCourseNode.java index 55abf30ca7bca2dd1af0f429f88adf8dcc9baa04..075cdeb22db2a8c28e7251161eb0fdc87f19277c 100644 --- a/src/main/java/org/olat/course/nodes/CPCourseNode.java +++ b/src/main/java/org/olat/course/nodes/CPCourseNode.java @@ -75,7 +75,7 @@ public class CPCourseNode extends AbstractAccessableCourseNode { private static final Logger log = Tracing.createLoggerFor(CPCourseNode.class); private static final long serialVersionUID = -4317662219173515498L; - private static final String TYPE = "cp"; + public static final String TYPE = "cp"; public CPCourseNode() { this(null); @@ -101,14 +101,14 @@ public class CPCourseNode extends AbstractAccessableCourseNode { public NodeRunConstructionResult createNodeRunConstructionResult(UserRequest ureq, WindowControl wControl, UserCourseEnvironment userCourseEnv, CourseNodeSecurityCallback nodeSecCallback, String nodecmd) { OLATResourceable ores = OresHelper.createOLATResourceableInstance(ICourse.class, userCourseEnv.getCourseEnvironment().getCourseResourceableId()); - CPRunController cprunC = new CPRunController(getModuleConfiguration(), ureq, wControl, this, nodecmd, ores, false); + CPRunController cprunC = new CPRunController(getModuleConfiguration(), ureq, wControl, this, nodecmd, ores, false, userCourseEnv); return cprunC.createNodeRunConstructionResult(ureq, null); } @Override public Controller createPreviewController(UserRequest ureq, WindowControl wControl, UserCourseEnvironment userCourseEnv, CourseNodeSecurityCallback nodeSecCallback) { OLATResourceable ores = OresHelper.createOLATResourceableInstance(ICourse.class, userCourseEnv.getCourseEnvironment().getCourseResourceableId()); - return new CPRunController(getModuleConfiguration(), ureq, wControl, this, null, ores, true); + return new CPRunController(getModuleConfiguration(), ureq, wControl, this, null, ores, true, userCourseEnv); } @Override diff --git a/src/main/java/org/olat/course/nodes/WikiCourseNode.java b/src/main/java/org/olat/course/nodes/WikiCourseNode.java index faf51a6fdd5067fb167417e4115b8e8d290adae8..5dabf59951d6e7b5d9d7b563d105f30cc75f5cc4 100644 --- a/src/main/java/org/olat/course/nodes/WikiCourseNode.java +++ b/src/main/java/org/olat/course/nodes/WikiCourseNode.java @@ -56,6 +56,7 @@ import org.olat.course.ICourse; import org.olat.course.condition.Condition; import org.olat.course.condition.interpreter.ConditionExpression; import org.olat.course.condition.interpreter.ConditionInterpreter; +import org.olat.course.editor.ConditionAccessEditConfig; import org.olat.course.editor.CourseEditorEnv; import org.olat.course.editor.NodeEditController; import org.olat.course.editor.StatusDescription; @@ -83,11 +84,19 @@ import org.olat.repository.handlers.RepositoryHandlerFactory; * @author Felix Jost */ public class WikiCourseNode extends AbstractAccessableCourseNode { + private static final long serialVersionUID = -5800975339569440113L; private static final Logger log = Tracing.createLoggerFor(WikiCourseNode.class); public static final String TYPE = "wiki"; + + private static final int CURRENT_VERSION = 2; + public static final String CONFIG_KEY_REPOSITORY_SOFTKEY = "reporef"; + public static final String CONFIG_KEY_EDIT_BY_COACH = "edit.by.coach"; + public static final String CONFIG_KEY_EDIT_BY_PARTICIPANT = "edit.by.participant"; + + public static final String EDIT_CONDITION = "editarticle"; private Condition preConditionEdit; public WikiCourseNode() { @@ -105,6 +114,31 @@ public class WikiCourseNode extends AbstractAccessableCourseNode { config.setBooleanEntry(NodeEditController.CONFIG_STARTPAGE, false); config.setConfigurationVersion(1); } + if (config.getConfigurationVersion() < 2) { + config.setBooleanEntry(CONFIG_KEY_EDIT_BY_COACH, Boolean.TRUE); + config.setBooleanEntry(CONFIG_KEY_EDIT_BY_PARTICIPANT, Boolean.TRUE); + removeDefaultPreconditions(); + } + + config.setConfigurationVersion(CURRENT_VERSION); + } + + private void removeDefaultPreconditions() { + if (hasCustomPreConditions()) { + boolean defaultPreconditions = + !preConditionEdit.isExpertMode() + && !preConditionEdit.isEasyModeCoachesAndAdmins() + && !preConditionEdit.isEasyModeAlwaysAllowCoachesAndAdmins() + && !preConditionEdit.isAssessmentMode() + && !preConditionEdit.isAssessmentModeViewResults(); + if (defaultPreconditions) { + removeCustomPreconditions(); + } + } + } + + public void removeCustomPreconditions() { + preConditionEdit = null; } @Override @@ -121,10 +155,17 @@ public class WikiCourseNode extends AbstractAccessableCourseNode { @Override public TabbableController createEditController(UserRequest ureq, WindowControl wControl, BreadcrumbPanel stackPanel, ICourse course,UserCourseEnvironment euce) { - WikiEditController childTabCntrllr = new WikiEditController(getModuleConfiguration(), ureq, wControl, this, course,euce); + WikiEditController childTabCntrllr = new WikiEditController(ureq, wControl, stackPanel, this, course,euce); CourseNode chosenNode = course.getEditorTreeModel().getCourseNode(euce.getCourseEditorEnv().getCurrentCourseNodeId()); return new NodeEditController(ureq, wControl, course, chosenNode, euce, childTabCntrllr); } + + @Override + public ConditionAccessEditConfig getAccessEditConfig() { + return hasCustomPreConditions() + ? ConditionAccessEditConfig.custom() + : ConditionAccessEditConfig.regular(false); + } @Override public NodeRunConstructionResult createNodeRunConstructionResult(UserRequest ureq, WindowControl wControl, @@ -139,9 +180,6 @@ public class WikiCourseNode extends AbstractAccessableCourseNode { @Override public StatusDescription isConfigValid() { - /* - * first check the one click cache - */ if(oneClickStatusCache!=null) { return oneClickStatusCache[0]; } @@ -242,37 +280,40 @@ public class WikiCourseNode extends AbstractAccessableCourseNode { @Override public List<ConditionExpression> getConditionExpressions() { - List<ConditionExpression> parentConditions = super.getConditionExpressions(); - List<ConditionExpression> conditions = new ArrayList<>(); - if(parentConditions != null && parentConditions.size() > 0) { - conditions.addAll(parentConditions); - } - Condition editCondition = getPreConditionEdit(); - if(editCondition != null && StringHelper.containsNonWhitespace(editCondition.getConditionExpression())) { - ConditionExpression ce = new ConditionExpression(editCondition.getConditionId()); - ce.setExpressionString(editCondition.getConditionExpression()); - conditions.add(ce); + if (hasCustomPreConditions()) { + List<ConditionExpression> parentConditions = super.getConditionExpressions(); + List<ConditionExpression> conditions = new ArrayList<>(); + if(parentConditions != null && parentConditions.size() > 0) { + conditions.addAll(parentConditions); + } + Condition editCondition = getPreConditionEdit(); + if(editCondition != null && StringHelper.containsNonWhitespace(editCondition.getConditionExpression())) { + ConditionExpression ce = new ConditionExpression(editCondition.getConditionId()); + ce.setExpressionString(editCondition.getConditionExpression()); + conditions.add(ce); + } + return conditions; } - return conditions; + return super.getConditionExpressions(); + } + + public boolean hasCustomPreConditions() { + return preConditionEdit != null; } public Condition getPreConditionEdit() { if (preConditionEdit == null) { preConditionEdit = new Condition(); } - preConditionEdit.setConditionId("editarticle"); + preConditionEdit.setConditionId(EDIT_CONDITION); return preConditionEdit; } - /** - * - * @param preConditionEdit - */ public void setPreConditionEdit(Condition preConditionEdit) { if (preConditionEdit == null) { preConditionEdit = getPreConditionEdit(); } - preConditionEdit.setConditionId("editarticle"); + preConditionEdit.setConditionId(EDIT_CONDITION); this.preConditionEdit = preConditionEdit; } @@ -285,10 +326,12 @@ public class WikiCourseNode extends AbstractAccessableCourseNode { */ @Override public void calcAccessAndVisibility(ConditionInterpreter ci, NodeEvaluation nodeEval) { - super.calcAccessAndVisibility(ci, nodeEval); - - boolean editor = (getPreConditionEdit().getConditionExpression() == null ? true : ci.evaluateCondition(getPreConditionEdit())); - nodeEval.putAccessStatus("editarticle", editor); + super.calcAccessAndVisibility(ci, nodeEval); + + if (hasCustomPreConditions()) { + boolean editor = (getPreConditionEdit().getConditionExpression() == null ? true : ci.evaluateCondition(getPreConditionEdit())); + nodeEval.putAccessStatus(EDIT_CONDITION, editor); + } } @Override diff --git a/src/main/java/org/olat/course/nodes/cp/CPAssessmentHandler.java b/src/main/java/org/olat/course/nodes/cp/CPAssessmentHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..198ca119e54ce43e93d651a8f16226d2a773121a --- /dev/null +++ b/src/main/java/org/olat/course/nodes/cp/CPAssessmentHandler.java @@ -0,0 +1,40 @@ +/** + * <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.cp; + +import org.olat.course.learningpath.LearningPathOnlyAssessmentHandler; +import org.olat.course.nodes.CPCourseNode; +import org.springframework.stereotype.Service; + +/** + * + * Initial date: 27.02.2020<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +@Service +public class CPAssessmentHandler extends LearningPathOnlyAssessmentHandler { + + @Override + public String acceptCourseNodeType() { + return CPCourseNode.TYPE; + } + +} diff --git a/src/main/java/org/olat/course/nodes/cp/CPLearningPathNodeHandler.java b/src/main/java/org/olat/course/nodes/cp/CPLearningPathNodeHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..13e6aeb891fa40fafc7ea2e3e3445382bfdb482a --- /dev/null +++ b/src/main/java/org/olat/course/nodes/cp/CPLearningPathNodeHandler.java @@ -0,0 +1,80 @@ +/** + * <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.cp; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.WindowControl; +import org.olat.course.learningpath.LearningPathConfigs; +import org.olat.course.learningpath.LearningPathEditConfigs; +import org.olat.course.learningpath.LearningPathNodeHandler; +import org.olat.course.learningpath.model.ModuleLearningPathConfigs; +import org.olat.course.learningpath.ui.LearningPathNodeConfigController; +import org.olat.course.nodes.CPCourseNode; +import org.olat.course.nodes.CourseNode; +import org.olat.repository.RepositoryEntry; +import org.springframework.stereotype.Service; + +/** + * + * Initial date: 27.02.2020<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +@Service +public class CPLearningPathNodeHandler implements LearningPathNodeHandler { + + private static final LearningPathEditConfigs EDIT_CONFIGS = LearningPathEditConfigs.builder() + .enableNodeVisited() + .enableConfirmed() + .build(); + + @Override + public String acceptCourseNodeType() { + return CPCourseNode.TYPE; + } + + @Override + public boolean isSupported() { + return true; + } + + @Override + public LearningPathConfigs getConfigs(CourseNode courseNode) { + return new ModuleLearningPathConfigs(courseNode.getModuleConfiguration(), true); + } + + @Override + public Controller createConfigEditController(UserRequest ureq, WindowControl wControl, RepositoryEntry courseEntry, + CourseNode courseNode) { + return new LearningPathNodeConfigController(ureq, wControl, courseEntry, courseNode, EDIT_CONFIGS); + } + + @Override + public LearningPathEditConfigs getEditConfigs() { + return EDIT_CONFIGS; + } + + @Override + public void onMigrated(CourseNode courseNode) { + // + } + +} diff --git a/src/main/java/org/olat/course/nodes/cp/CPRunController.java b/src/main/java/org/olat/course/nodes/cp/CPRunController.java index 9010b2d3a45d8677846dcd4f7079b4fe114e6e52..160ec30744f82ffba46198838b2448a841e76172 100644 --- a/src/main/java/org/olat/course/nodes/cp/CPRunController.java +++ b/src/main/java/org/olat/course/nodes/cp/CPRunController.java @@ -54,13 +54,17 @@ import org.olat.course.editor.NodeEditController; import org.olat.course.nodes.CPCourseNode; import org.olat.course.nodes.TitledWrapperHelper; import org.olat.course.run.navigation.NodeRunConstructionResult; +import org.olat.course.run.userview.UserCourseEnvironment; import org.olat.fileresource.FileResourceManager; import org.olat.ims.cp.CPManager; import org.olat.ims.cp.ui.CPPackageConfig; import org.olat.modules.ModuleConfiguration; +import org.olat.modules.cp.CPAssessmentProvider; import org.olat.modules.cp.CPDisplayController; import org.olat.modules.cp.CPManifestTreeModel; import org.olat.modules.cp.CPUIFactory; +import org.olat.modules.cp.DryRunAssessmentProvider; +import org.olat.modules.cp.PersistingAssessmentProvider; import org.olat.modules.cp.TreeNodeEvent; import org.olat.repository.RepositoryEntry; import org.olat.util.logging.activity.LoggingResourceable; @@ -91,10 +95,12 @@ public class CPRunController extends BasicController implements ControllerEventL private String selNodeId; private boolean preview; private OLATResourceable courseResource; - + private final UserCourseEnvironment userCourseEnv; + private CPAssessmentProvider cpAssessmentProvider; + @Autowired private CPManager cpManager; - + /** * Use this constructor to launch a CP via Repository reference key set in the * ModuleConfiguration. On the into page a title and the learning objectives @@ -105,9 +111,10 @@ public class CPRunController extends BasicController implements ControllerEventL * @param userCourseEnv * @param wControl * @param cpNode + * @param userCourseEnv */ public CPRunController(ModuleConfiguration config, UserRequest ureq, WindowControl wControl, CPCourseNode cpNode, String nodecmd, - OLATResourceable course, boolean preview) { + OLATResourceable course, boolean preview, UserCourseEnvironment userCourseEnv) { super(ureq, wControl); this.nodecmd = nodecmd; this.courseResource = OresHelper.clone(course); @@ -116,6 +123,7 @@ public class CPRunController extends BasicController implements ControllerEventL this.config = config; this.cpNode = cpNode; this.preview = preview; + this.userCourseEnv = userCourseEnv; addLoggingResourceable(LoggingResourceable.wrap(cpNode)); // jump to either the forum or the folder if the business-launch-path says so. @@ -140,10 +148,6 @@ public class CPRunController extends BasicController implements ControllerEventL putInitialPanel(main); } - /** - * @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest, - * org.olat.core.gui.components.Component, org.olat.core.gui.control.Event) - */ @Override public void event(UserRequest ureq, Component source, Event event) { if (source == showCPButton) { // those must be links @@ -152,10 +156,6 @@ public class CPRunController extends BasicController implements ControllerEventL } } - /** - * @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest, - * org.olat.core.gui.control.Controller, org.olat.core.gui.control.Event) - */ @Override public void event(UserRequest ureq, Controller source, Event event) { if (source == null) { // external source (from the course at this time being) @@ -202,6 +202,10 @@ public class CPRunController extends BasicController implements ControllerEventL deliveryOptions = packageConfig.getDeliveryOptions(); } } + + cpAssessmentProvider = userCourseEnv.getIdentityEnvironment().getRoles().isGuestOnly() + ? DryRunAssessmentProvider.create() + : PersistingAssessmentProvider.create(re, getIdentity()); } // else cpRoot is already set (save some db access if the user opens / // closes / reopens the cp from the same CPRuncontroller instance) @@ -210,8 +214,10 @@ public class CPRunController extends BasicController implements ControllerEventL activateFirstPage = false; } boolean showNavigation = !config.getBooleanSafe(NodeEditController.CONFIG_COMPONENT_MENU); - cpDispC = CPUIFactory.getInstance().createContentOnlyCPDisplayController(ureq, getWindowControl(), new LocalFolderImpl(cpRoot), - activateFirstPage, showNavigation, deliveryOptions, nodecmd, courseResource, cpNode.getIdent(), preview); + + cpDispC = CPUIFactory.getInstance().createContentOnlyCPDisplayController(ureq, getWindowControl(), + new LocalFolderImpl(cpRoot), activateFirstPage, showNavigation, deliveryOptions, nodecmd, + courseResource, cpNode.getIdent(), preview, cpAssessmentProvider); cpDispC.setContentEncoding(deliveryOptions.getContentEncoding()); cpDispC.setJSEncoding(deliveryOptions.getJavascriptEncoding()); cpDispC.addControllerListener(this); @@ -240,9 +246,6 @@ public class CPRunController extends BasicController implements ControllerEventL return (config.getBooleanEntry(NodeEditController.CONFIG_COMPONENT_MENU).booleanValue()); } - /** - * @see org.olat.core.gui.control.DefaultController#doDispose(boolean) - */ @Override protected void doDispose() { if (cpDispC != null) { diff --git a/src/main/java/org/olat/course/nodes/st/assessment/STLearningPathConfigController.java b/src/main/java/org/olat/course/nodes/st/assessment/STLearningPathConfigController.java index ca4ea02b7cdbdf636b874d2cc75046bce74cbc69..1e73e148882af1ccb910d2003f796d182c7f50c7 100644 --- a/src/main/java/org/olat/course/nodes/st/assessment/STLearningPathConfigController.java +++ b/src/main/java/org/olat/course/nodes/st/assessment/STLearningPathConfigController.java @@ -58,7 +58,7 @@ public class STLearningPathConfigController extends FormBasicController { @Override protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { setFormTitle("config.title"); - + setFormContextHelp("Learning Path"); KeyValues sequenceKV = new KeyValues(); sequenceKV.add(KeyValues.entry(STCourseNode.CONFIG_LP_SEQUENCE_VALUE_SEQUENTIAL, translate("config.sequence.sequential"))); sequenceKV.add(KeyValues.entry(STCourseNode.CONFIG_LP_SEQUENCE_VALUE_WITHOUT, translate("config.sequence.without"))); diff --git a/src/main/java/org/olat/course/nodes/wiki/WikiAssessmentHandler.java b/src/main/java/org/olat/course/nodes/wiki/WikiAssessmentHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..3750ed7599760272e67607c88a1767d62a2397a4 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/wiki/WikiAssessmentHandler.java @@ -0,0 +1,40 @@ +/** + * <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.wiki; + +import org.olat.course.learningpath.LearningPathOnlyAssessmentHandler; +import org.olat.course.nodes.WikiCourseNode; +import org.springframework.stereotype.Service; + +/** + * + * Initial date: 28.02.2020<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +@Service +public class WikiAssessmentHandler extends LearningPathOnlyAssessmentHandler { + + @Override + public String acceptCourseNodeType() { + return WikiCourseNode.TYPE; + } + +} diff --git a/src/main/java/org/olat/course/nodes/wiki/WikiConfigController.java b/src/main/java/org/olat/course/nodes/wiki/WikiConfigController.java new file mode 100644 index 0000000000000000000000000000000000000000..ece7c1f484d83e45483d8d9006373a1d8c1ac1f5 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/wiki/WikiConfigController.java @@ -0,0 +1,262 @@ +/** + * <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.wiki; + +import static org.olat.core.gui.translator.TranslatorHelper.translateAll; + +import java.util.Collection; + +import org.olat.basesecurity.GroupRoles; +import org.olat.basesecurity.OrganisationRoles; +import org.olat.core.commons.services.notifications.SubscriptionContext; +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.StaticTextElement; +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.components.stack.BreadcrumbPanel; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController; +import org.olat.core.id.Roles; +import org.olat.core.util.StringHelper; +import org.olat.course.ICourse; +import org.olat.course.editor.NodeEditController; +import org.olat.course.nodes.AbstractFeedCourseNode; +import org.olat.course.nodes.CourseNodeFactory; +import org.olat.course.nodes.WikiCourseNode; +import org.olat.course.run.environment.CourseEnvironment; +import org.olat.fileresource.types.WikiResource; +import org.olat.modules.ModuleConfiguration; +import org.olat.modules.wiki.DryRunAssessmentProvider; +import org.olat.modules.wiki.WikiManager; +import org.olat.modules.wiki.WikiSecurityCallback; +import org.olat.modules.wiki.WikiSecurityCallbackImpl; +import org.olat.repository.RepositoryEntry; +import org.olat.repository.RepositoryService; +import org.olat.repository.controllers.ReferencableEntriesSearchController; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 2 Mar 2020<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +public class WikiConfigController extends FormBasicController { + + private static final String ROLE_COACH = "config.role.coach"; + private static final String ROLE_PARTICIPANT = "config.role.participant"; + private static final String[] EDIT_KEYS = new String[] { + ROLE_COACH, + ROLE_PARTICIPANT + }; + + private StaticTextElement wikiNotChoosenEl; + private FormLink previewLink; + private FormLink chooseLink; + private FormLink replaceLink; + private FormLink editLink; + private MultipleSelectionElement editRolesEl; + + private CloseableModalController cmc; + private ReferencableEntriesSearchController repositorySearchCtrl; + private Controller wikiCtrl; + + private final BreadcrumbPanel stackPanel; + private final WikiCourseNode courseNode; + private final ModuleConfiguration config; + private final ICourse course; + private RepositoryEntry wikiEntry; + + @Autowired + private RepositoryService repositoryService; + + public WikiConfigController(UserRequest ureq, WindowControl wControl, BreadcrumbPanel stackPanel, + WikiCourseNode courseNode, ICourse course) { + super(ureq, wControl, LAYOUT_BAREBONE); + this.stackPanel = stackPanel; + this.courseNode = courseNode; + this.course = course; + this.config = courseNode.getModuleConfiguration(); + this.wikiEntry = courseNode.getReferencedRepositoryEntry(); + initForm(ureq); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + FormLayoutContainer generalCont = FormLayoutContainer.createDefaultFormLayout("general", getTranslator()); + formLayout.add(generalCont); + generalCont.setRootForm(mainForm); + generalCont.setFormTitle(translate("header")); + generalCont.setFormContextHelp("Communication and Collaboration#_bb_wiki"); + + wikiNotChoosenEl = uifactory.addStaticTextElement("chosenwiki", "chosenwiki", + translate("no.entry.chosen"), generalCont); + previewLink = uifactory.addFormLink("command.preview", "", translate("command.preview"), generalCont, + Link.NONTRANSLATED); + previewLink.setIconLeftCSS("o_icon o_icon-fw o_icon_preview"); + + FormLayoutContainer buttonsCont = FormLayoutContainer.createButtonLayout("buttons", getTranslator()); + buttonsCont.setRootForm(mainForm); + generalCont.add(buttonsCont); + chooseLink = uifactory.addFormLink("command.create", buttonsCont, "btn btn-default o_xsmall"); + chooseLink.setElementCssClass("o_sel_survey_choose_repofile"); + replaceLink = uifactory.addFormLink("command.change", buttonsCont, "btn btn-default o_xsmall"); + editLink = uifactory.addFormLink("edit", buttonsCont, "btn btn-default o_xsmall"); + + + if (!courseNode.hasCustomPreConditions()) { + FormLayoutContainer rightsCont = FormLayoutContainer.createDefaultFormLayout("rights", getTranslator()); + formLayout.add(rightsCont); + rightsCont.setFormTitle(translate("config.rights")); + + editRolesEl = uifactory.addCheckboxesVertical("config.edit", rightsCont, EDIT_KEYS, + translateAll(getTranslator(), EDIT_KEYS), 1); + editRolesEl.select(ROLE_COACH, + config.getBooleanSafe(WikiCourseNode.CONFIG_KEY_EDIT_BY_COACH)); + editRolesEl.select(ROLE_PARTICIPANT, + config.getBooleanSafe(WikiCourseNode.CONFIG_KEY_EDIT_BY_PARTICIPANT)); + editRolesEl.addActionListener(FormEvent.ONCHANGE); + } + + updateUI(); + } + + private void updateUI() { + boolean feedSelected = wikiEntry != null; + if (feedSelected) { + String displayname = StringHelper.escapeHtml(wikiEntry.getDisplayname()); + previewLink.setI18nKey(displayname); + flc.setDirty(true); + } + wikiNotChoosenEl.setVisible(!feedSelected); + chooseLink.setVisible(!feedSelected); + previewLink.setVisible(feedSelected); + replaceLink.setVisible(feedSelected); + editLink.setVisible(feedSelected); + } + + @Override + protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { + if (source == chooseLink || source == replaceLink) { + doSelectFeed(ureq); + } else if (source == previewLink) { + doPreviewFeed(ureq); + } else if (source == editLink) { + doEditFeed(ureq); + } else if (source == editRolesEl) { + doUpdatedEditRoles(ureq); + } + super.formInnerEvent(ureq, source, event); + } + + @Override + public void event(UserRequest urequest, Controller source, Event event) { + if (source == repositorySearchCtrl) { + if (event == ReferencableEntriesSearchController.EVENT_REPOSITORY_ENTRY_SELECTED) { + wikiEntry = repositorySearchCtrl.getSelectedEntry(); + if (wikiEntry != null) { + //TODO uh set copnfig + AbstractFeedCourseNode.setReference(courseNode.getModuleConfiguration(), wikiEntry); + WikiEditController.setWikiRepoReference(wikiEntry, config); + fireEvent(urequest, NodeEditController.NODECONFIG_CHANGED_EVENT); + updateUI(); + } + } + cmc.deactivate(); + cleanUp(); + } else if (source == cmc) { + cmc.deactivate(); + cleanUp(); + } + } + + private void cleanUp() { + removeAsListenerAndDispose(repositorySearchCtrl); + removeAsListenerAndDispose(wikiCtrl); + removeAsListenerAndDispose(cmc); + repositorySearchCtrl = null; + wikiCtrl = null; + cmc = null; + } + + private void doSelectFeed(UserRequest ureq) { + repositorySearchCtrl = new ReferencableEntriesSearchController(getWindowControl(), ureq, WikiResource.TYPE_NAME, + translate("command.choose")); + listenTo(repositorySearchCtrl); + cmc = new CloseableModalController(getWindowControl(), translate("close"), + repositorySearchCtrl.getInitialComponent(), true, translate("command.create")); + cmc.activate(); + } + + private void doPreviewFeed(UserRequest ureq) { + if (wikiEntry == null) { + showError("error.repoentrymissing"); + } else { + Roles roles = ureq.getUserSession().getRoles(); + boolean isAdministrator = (roles.isAdministrator() || roles.isLearnResourceManager()) + && repositoryService.hasRoleExpanded(getIdentity(), wikiEntry, + OrganisationRoles.administrator.name(), OrganisationRoles.learnresourcemanager.name()); + boolean isResourceOwner = repositoryService.hasRole(getIdentity(), wikiEntry, GroupRoles.owner.name()); + + CourseEnvironment cenv = course.getCourseEnvironment(); + SubscriptionContext subsContext = WikiManager.createTechnicalSubscriptionContextForCourse(cenv, courseNode); + WikiSecurityCallback callback = new WikiSecurityCallbackImpl(null, isAdministrator, false, false, isResourceOwner, subsContext); + wikiCtrl = WikiManager.getInstance().createWikiMainController(ureq, getWindowControl(), wikiEntry.getOlatResource(), + callback, DryRunAssessmentProvider.create(), null); + listenTo(wikiCtrl); + stackPanel.pushController(translate("preview"), wikiCtrl); + } + } + + private void doEditFeed(UserRequest ureq) { + if (wikiEntry == null) { + showError("error.repoentrymissing"); + } else { + CourseNodeFactory.getInstance().launchReferencedRepoEntryEditor(ureq, getWindowControl(), courseNode); + } + } + + private void doUpdatedEditRoles(UserRequest ureq) { + Collection<String> selectedEditKeys = editRolesEl.getSelectedKeys(); + config.setBooleanEntry(WikiCourseNode.CONFIG_KEY_EDIT_BY_COACH, selectedEditKeys.contains(ROLE_COACH)); + config.setBooleanEntry(WikiCourseNode.CONFIG_KEY_EDIT_BY_PARTICIPANT, selectedEditKeys.contains(ROLE_PARTICIPANT)); + + fireEvent(ureq, NodeEditController.NODECONFIG_CHANGED_EVENT); + } + + @Override + protected void formOK(UserRequest ureq) { + // + } + + @Override + protected void doDispose() { + // + } + +} diff --git a/src/main/java/org/olat/course/nodes/wiki/WikiEditController.java b/src/main/java/org/olat/course/nodes/wiki/WikiEditController.java index 3f2ac3d52a47fff8a4c9dac6f15eb587b664a983..2b33f445fad53e87945bf66d94730364c2027fde 100644 --- a/src/main/java/org/olat/course/nodes/wiki/WikiEditController.java +++ b/src/main/java/org/olat/course/nodes/wiki/WikiEditController.java @@ -25,46 +25,28 @@ package org.olat.course.nodes.wiki; -import org.olat.NewControllerFactory; -import org.olat.basesecurity.GroupRoles; -import org.olat.basesecurity.OrganisationRoles; -import org.olat.core.commons.services.notifications.SubscriptionContext; 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.components.panel.Panel; +import org.olat.core.gui.components.stack.BreadcrumbPanel; import org.olat.core.gui.components.tabbedpane.TabbedPane; import org.olat.core.gui.components.velocity.VelocityContainer; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.ControllerEventListener; import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; -import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController; import org.olat.core.gui.control.generic.tabbable.ActivateableTabbableDefaultController; -import org.olat.core.id.Roles; import org.olat.core.logging.AssertException; -import org.olat.core.util.StringHelper; import org.olat.course.ICourse; import org.olat.course.assessment.AssessmentHelper; import org.olat.course.condition.Condition; import org.olat.course.condition.ConditionEditController; import org.olat.course.editor.NodeEditController; import org.olat.course.nodes.WikiCourseNode; -import org.olat.course.run.environment.CourseEnvironment; import org.olat.course.run.userview.UserCourseEnvironment; import org.olat.course.tree.CourseEditorTreeModel; -import org.olat.fileresource.types.WikiResource; import org.olat.modules.ModuleConfiguration; -import org.olat.modules.wiki.WikiMainController; -import org.olat.modules.wiki.WikiManager; -import org.olat.modules.wiki.WikiSecurityCallback; -import org.olat.modules.wiki.WikiSecurityCallbackImpl; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryManager; -import org.olat.repository.RepositoryService; -import org.olat.repository.controllers.ReferencableEntriesSearchController; -import org.springframework.beans.factory.annotation.Autowired; /** * Description: <BR/>Edit controller for single page course nodes <P/> Initial @@ -75,204 +57,81 @@ import org.springframework.beans.factory.annotation.Autowired; public class WikiEditController extends ActivateableTabbableDefaultController implements ControllerEventListener { public static final String PANE_TAB_ACCESSIBILITY = "pane.tab.accessibility"; public static final String PANE_TAB_WIKICONFIG = "pane.tab.wikiconfig"; - public static final String PANE_TAB_WIKIDISPLAYCONFIG = "pane.tab.wikidisplayconfig"; private static final String[] paneKeys = { PANE_TAB_WIKICONFIG, PANE_TAB_ACCESSIBILITY }; - private static final String CHOSEN_ENTRY = "chosen_entry"; - private static final String CONFIG_KEY_REPOSITORY_SOFTKEY = "reporef"; - - private ModuleConfiguration moduleConfiguration; - private WikiCourseNode wikiCourseNode; - private ConditionEditController accessCondContr; private TabbedPane tabs; - private Panel main; - private VelocityContainer content; - private ReferencableEntriesSearchController searchController; - private WikiMainController wikiCtr; - private CloseableModalController cmcWikiCtr; - private CloseableModalController cmcSearchController; - private Link previewLink; - private Link chooseButton; - private Link changeButton; - private Link editLink; + private WikiConfigController configCtrl; private VelocityContainer editAccessVc; + private ConditionEditController accessCondCtrl; private ConditionEditController editCondContr; - private ICourse course; - - @Autowired - private RepositoryService repositoryService; - /** - * Constructor for wiki page editor controller - * - * @param config The node module configuration - * @param ureq The user request - * @param wikiCourseNode The current wiki page course node - * @param course - */ - public WikiEditController(ModuleConfiguration config, UserRequest ureq, WindowControl wControl, WikiCourseNode wikiCourseNode, - ICourse course, UserCourseEnvironment euce) { + private final WikiCourseNode wikiCourseNode; + + public WikiEditController(UserRequest ureq, WindowControl wControl, BreadcrumbPanel stackPanel, + WikiCourseNode wikiCourseNode, ICourse course, UserCourseEnvironment euce) { super(ureq, wControl); - this.moduleConfiguration = config; this.wikiCourseNode = wikiCourseNode; - //o_clusterOk by guido: save to hold reference to course inside editor - this.course = course; - main = new Panel("wikimain"); + configCtrl = new WikiConfigController(ureq, wControl, stackPanel, wikiCourseNode, course); + listenTo(configCtrl); - content = createVelocityContainer("edit"); - chooseButton = LinkFactory.createButtonSmall("command.create", content, this); - chooseButton.setElementCssClass("o_sel_wiki_choose_repofile"); - changeButton = LinkFactory.createButtonSmall("command.change", content, this); - changeButton.setElementCssClass("o_sel_wiki_choose_repofile"); - - editAccessVc = this.createVelocityContainer("edit_access"); - CourseEditorTreeModel editorModel = course.getEditorTreeModel(); - // Accessibility precondition - Condition accessCondition = wikiCourseNode.getPreConditionAccess(); - accessCondContr = new ConditionEditController(ureq, getWindowControl(), euce, accessCondition, - AssessmentHelper.getAssessableNodes(editorModel, wikiCourseNode)); - listenTo(accessCondContr); - editAccessVc.put("readerCondition", accessCondContr.getInitialComponent()); - - //wiki read / write preconditions - Condition editCondition = wikiCourseNode.getPreConditionEdit(); - editCondContr = new ConditionEditController(ureq, getWindowControl(), euce, editCondition, AssessmentHelper - .getAssessableNodes(editorModel, wikiCourseNode)); - listenTo(editCondContr); - editAccessVc.put("editCondition", editCondContr.getInitialComponent()); - - - if (config.get(CONFIG_KEY_REPOSITORY_SOFTKEY) != null) { - // fetch repository entry to display the repository entry title of the - // chosen wiki - RepositoryEntry re = getWikiRepoReference(config, false); - if (re == null) { // we cannot display the entrie's name, because the - // repository entry had been deleted between the time - // when it was chosen here, and now - this.showError("error.repoentrymissing"); - content.contextPut("showPreviewLink", Boolean.FALSE); - content.contextPut(CHOSEN_ENTRY, translate("no.entry.chosen")); - } else { - // no securitycheck on wiki, editable by everybody - editLink = LinkFactory.createButtonSmall("edit", content, this); - content.contextPut("showPreviewLink", Boolean.TRUE); - String displayname = StringHelper.escapeHtml(re.getDisplayname()); - previewLink = LinkFactory.createCustomLink("command.preview", "command.preview", displayname, Link.NONTRANSLATED, content, this); - previewLink.setIconLeftCSS("o_icon o_icon-fw o_icon_preview"); - previewLink.setCustomEnabledLinkCSS("o_preview"); - previewLink.setTitle(getTranslator().translate("command.preview")); - } - } else { - // no valid config yet - content.contextPut("showPreviewLink", Boolean.FALSE); - content.contextPut(CHOSEN_ENTRY, translate("no.entry.chosen")); + if (wikiCourseNode.hasCustomPreConditions()) { + editAccessVc = this.createVelocityContainer("edit_access"); + CourseEditorTreeModel editorModel = course.getEditorTreeModel(); + // Accessibility precondition + Condition accessCondition = wikiCourseNode.getPreConditionAccess(); + accessCondCtrl = new ConditionEditController(ureq, getWindowControl(), euce, accessCondition, + AssessmentHelper.getAssessableNodes(editorModel, wikiCourseNode)); + listenTo(accessCondCtrl); + editAccessVc.put("readerCondition", accessCondCtrl.getInitialComponent()); + + //wiki read / write preconditions + Condition editCondition = wikiCourseNode.getPreConditionEdit(); + editCondContr = new ConditionEditController(ureq, getWindowControl(), euce, editCondition, AssessmentHelper + .getAssessableNodes(editorModel, wikiCourseNode)); + listenTo(editCondContr); + editAccessVc.put("editCondition", editCondContr.getInitialComponent()); } - - main.setContent(content); } @Override public void event(UserRequest ureq, Component source, Event event) { - if (source == previewLink) { - doPreview(ureq); - } else if (source == chooseButton || source == changeButton) { - searchController = new ReferencableEntriesSearchController(getWindowControl(), ureq, WikiResource.TYPE_NAME, translate("command.choose")); - listenTo(searchController); - cmcSearchController = new CloseableModalController(getWindowControl(), translate("close"), searchController.getInitialComponent(), true, translate("command.create")); - cmcSearchController.activate(); - } else if (source == editLink) { - RepositoryEntry repositoryEntry = wikiCourseNode.getReferencedRepositoryEntry(); - if (repositoryEntry == null) { - // do nothing - return; - } - String bPath = "[RepositoryEntry:" + repositoryEntry.getKey() + "][Editor:0]"; - NewControllerFactory.getInstance().launch(bPath, ureq, getWindowControl()); - } + // } @Override - public void event(UserRequest urequest, Controller source, Event event) { - if (source == searchController) { - cmcSearchController.deactivate(); - // repository search controller done - if (event == ReferencableEntriesSearchController.EVENT_REPOSITORY_ENTRY_SELECTED) { - RepositoryEntry re = searchController.getSelectedEntry(); - if (re != null) { - setWikiRepoReference(re, moduleConfiguration); - content.contextPut("showPreviewLink", Boolean.TRUE); - String displayname = StringHelper.escapeHtml(re.getDisplayname()); - previewLink = LinkFactory.createCustomLink("command.preview", "command.preview", displayname, Link.NONTRANSLATED, content, this); - previewLink.setIconLeftCSS("o_icon o_icon-fw o_icon_preview"); - previewLink.setCustomEnabledLinkCSS("o_preview"); - previewLink.setTitle(getTranslator().translate("command.preview")); - // no securitycheck on wiki, editable by everybody - editLink = LinkFactory.createButtonSmall("edit", content, this); - // fire event so the updated config is saved by the - // editormaincontroller - fireEvent(urequest, NodeEditController.NODECONFIG_CHANGED_EVENT); - } - } // else cancelled repo search - } else if (source == accessCondContr) { + public void event(UserRequest ureq, Controller source, Event event) { + if (source == configCtrl) { + fireEvent(ureq, event); + } else if (source == accessCondCtrl) { if (event == Event.CHANGED_EVENT) { - Condition cond = accessCondContr.getCondition(); + Condition cond = accessCondCtrl.getCondition(); wikiCourseNode.setPreConditionAccess(cond); - fireEvent(urequest, NodeEditController.NODECONFIG_CHANGED_EVENT); + fireEvent(ureq, NodeEditController.NODECONFIG_CHANGED_EVENT); } } else if (source == editCondContr) { if (event == Event.CHANGED_EVENT) { Condition cond = editCondContr.getCondition(); wikiCourseNode.setPreConditionEdit(cond); - fireEvent(urequest, NodeEditController.NODECONFIG_CHANGED_EVENT); + fireEvent(ureq, NodeEditController.NODECONFIG_CHANGED_EVENT); } - } else if (source == cmcWikiCtr) { - if (event == CloseableModalController.CLOSE_MODAL_EVENT) { - cmcWikiCtr.dispose(); - wikiCtr.dispose(); - } - } - } - - private void doPreview(UserRequest ureq) { - // Preview as modal dialogue only if the config is valid - RepositoryEntry re = getWikiRepoReference(moduleConfiguration, false); - if (re == null) { // we cannot preview it, because the repository entry - // had been deleted between the time when it was - // chosen here, and now - showError("error.repoentrymissing"); - } else { - Roles roles = ureq.getUserSession().getRoles(); - boolean isAdministrator = (roles.isAdministrator() || roles.isLearnResourceManager()) - && repositoryService.hasRoleExpanded(getIdentity(), re, - OrganisationRoles.administrator.name(), OrganisationRoles.learnresourcemanager.name()); - boolean isResourceOwner = repositoryService.hasRole(getIdentity(), re, GroupRoles.owner.name()); - - CourseEnvironment cenv = course.getCourseEnvironment(); - SubscriptionContext subsContext = WikiManager.createTechnicalSubscriptionContextForCourse(cenv, wikiCourseNode); - WikiSecurityCallback callback = new WikiSecurityCallbackImpl(null, isAdministrator, false, false, isResourceOwner, subsContext); - wikiCtr = WikiManager.getInstance().createWikiMainController(ureq, getWindowControl(), re.getOlatResource(), callback, null); - cmcWikiCtr = new CloseableModalController(getWindowControl(), translate("command.close"), wikiCtr.getInitialComponent()); - listenTo(cmcWikiCtr); - cmcWikiCtr.activate(); } } @Override public void addTabs(TabbedPane tabbedPane) { tabs = tabbedPane; - tabbedPane.addTab(translate(PANE_TAB_ACCESSIBILITY), editAccessVc); - tabbedPane.addTab(translate(PANE_TAB_WIKICONFIG), main); + if (editAccessVc != null) { + tabbedPane.addTab(translate(PANE_TAB_ACCESSIBILITY), editAccessVc); + + } + tabbedPane.addTab(translate(PANE_TAB_WIKICONFIG), configCtrl.getInitialComponent()); } @Override protected void doDispose() { - //child controllers registered with listenTo() get disposed in BasicController - if (wikiCtr != null) { - wikiCtr.dispose(); - wikiCtr = null; - } + // } @Override @@ -295,7 +154,7 @@ public class WikiEditController extends ActivateableTabbableDefaultController im */ public static RepositoryEntry getWikiRepoReference(ModuleConfiguration config, boolean strict) { if (config == null) throw new AssertException("missing config in wiki course node"); - String repoSoftkey = (String) config.get(WikiEditController.CONFIG_KEY_REPOSITORY_SOFTKEY); + String repoSoftkey = (String) config.get(WikiCourseNode.CONFIG_KEY_REPOSITORY_SOFTKEY); if (repoSoftkey == null) throw new AssertException("invalid config when being asked for references"); RepositoryManager rm = RepositoryManager.getInstance(); return rm.lookupRepositoryEntryBySoftkey(repoSoftkey, strict); @@ -307,7 +166,7 @@ public class WikiEditController extends ActivateableTabbableDefaultController im * @param moduleConfiguration */ public static void setWikiRepoReference(RepositoryEntry re, ModuleConfiguration moduleConfiguration) { - moduleConfiguration.set(CONFIG_KEY_REPOSITORY_SOFTKEY, re.getSoftkey()); + moduleConfiguration.set(WikiCourseNode.CONFIG_KEY_REPOSITORY_SOFTKEY, re.getSoftkey()); } /** @@ -315,7 +174,7 @@ public class WikiEditController extends ActivateableTabbableDefaultController im * @return boolean */ public static boolean isModuleConfigValid(ModuleConfiguration moduleConfiguration) { - return (moduleConfiguration.get(CONFIG_KEY_REPOSITORY_SOFTKEY) != null); + return (moduleConfiguration.get(WikiCourseNode.CONFIG_KEY_REPOSITORY_SOFTKEY) != null); } @@ -332,7 +191,7 @@ public class WikiEditController extends ActivateableTabbableDefaultController im if (strict) throw new AssertException("missing config in Wiki"); else return null; } - String repoSoftkey = (String) config.get(WikiEditController.CONFIG_KEY_REPOSITORY_SOFTKEY); + String repoSoftkey = (String) config.get(WikiCourseNode.CONFIG_KEY_REPOSITORY_SOFTKEY); if (repoSoftkey == null) { if (strict) throw new AssertException("invalid config when being asked for references"); else return null; @@ -346,7 +205,7 @@ public class WikiEditController extends ActivateableTabbableDefaultController im * @param moduleConfig */ public static void removeWikiReference(ModuleConfiguration moduleConfig) { - moduleConfig.remove(WikiEditController.CONFIG_KEY_REPOSITORY_SOFTKEY); + moduleConfig.remove(WikiCourseNode.CONFIG_KEY_REPOSITORY_SOFTKEY); } } diff --git a/src/main/java/org/olat/course/nodes/wiki/WikiLearningPathNodeHandler.java b/src/main/java/org/olat/course/nodes/wiki/WikiLearningPathNodeHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..afc381a9319d6428c6bf70aa4d9a5919de1634c3 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/wiki/WikiLearningPathNodeHandler.java @@ -0,0 +1,82 @@ +/** + * <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.wiki; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.WindowControl; +import org.olat.course.learningpath.LearningPathConfigs; +import org.olat.course.learningpath.LearningPathEditConfigs; +import org.olat.course.learningpath.LearningPathNodeHandler; +import org.olat.course.learningpath.model.ModuleLearningPathConfigs; +import org.olat.course.learningpath.ui.LearningPathNodeConfigController; +import org.olat.course.nodes.CourseNode; +import org.olat.course.nodes.WikiCourseNode; +import org.olat.repository.RepositoryEntry; +import org.springframework.stereotype.Service; + +/** + * + * Initial date: 28.02.2020<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +@Service +public class WikiLearningPathNodeHandler implements LearningPathNodeHandler { + + private static final LearningPathEditConfigs EDIT_CONFIGS = LearningPathEditConfigs.builder() + .enableNodeVisited() + .enableConfirmed() + .build(); + + @Override + public String acceptCourseNodeType() { + return WikiCourseNode.TYPE; + } + + @Override + public boolean isSupported() { + return true; + } + + @Override + public LearningPathConfigs getConfigs(CourseNode courseNode) { + return new ModuleLearningPathConfigs(courseNode.getModuleConfiguration(), true); + } + + @Override + public Controller createConfigEditController(UserRequest ureq, WindowControl wControl, RepositoryEntry courseEntry, + CourseNode courseNode) { + return new LearningPathNodeConfigController(ureq, wControl, courseEntry, courseNode, EDIT_CONFIGS); + } + + @Override + public LearningPathEditConfigs getEditConfigs() { + return EDIT_CONFIGS; + } + + @Override + public void onMigrated(CourseNode courseNode) { + if (courseNode instanceof WikiCourseNode) { + ((WikiCourseNode)courseNode).removeCustomPreconditions(); + } + } + +} diff --git a/src/main/java/org/olat/course/nodes/wiki/WikiRunController.java b/src/main/java/org/olat/course/nodes/wiki/WikiRunController.java index 16e5eecb6afda3991554b937094b30ba03e45b9e..c34a609dee8f55a2a7c22aa8231eecc98efe3266 100644 --- a/src/main/java/org/olat/course/nodes/wiki/WikiRunController.java +++ b/src/main/java/org/olat/course/nodes/wiki/WikiRunController.java @@ -56,7 +56,10 @@ import org.olat.course.run.navigation.NodeRunConstructionResult; import org.olat.course.run.userview.NodeEvaluation; import org.olat.course.run.userview.UserCourseEnvironment; import org.olat.modules.ModuleConfiguration; +import org.olat.modules.wiki.DryRunAssessmentProvider; +import org.olat.modules.wiki.PersistingAssessmentProvider; import org.olat.modules.wiki.Wiki; +import org.olat.modules.wiki.WikiAssessmentProvider; import org.olat.modules.wiki.WikiMainController; import org.olat.modules.wiki.WikiManager; import org.olat.modules.wiki.WikiReadOnlySecurityCallback; @@ -91,13 +94,13 @@ public class WikiRunController extends BasicController implements Activateable2 addLoggingResourceable(LoggingResourceable.wrap(wikiCourseNode)); //get repository entry in "strict" mode - RepositoryEntry re = WikiEditController.getWikiRepoReference(config, true); + RepositoryEntry wikiEntry = WikiEditController.getWikiRepoReference(config, true); //check role UserSession usess = ureq.getUserSession(); boolean isAdmininstrator = userCourseEnv.isAdmin(); boolean isGuestOnly = usess.getRoles().isGuestOnly(); - boolean isResourceOwner = isAdmininstrator || repositoryService.hasRole(getIdentity(), re, GroupRoles.owner.name()); + boolean isResourceOwner = isAdmininstrator || repositoryService.hasRole(getIdentity(), wikiEntry, GroupRoles.owner.name()); // Check for jumping to certain wiki page BusinessControl bc = wControl.getBusinessControl(); @@ -105,12 +108,20 @@ public class WikiRunController extends BasicController implements Activateable2 SubscriptionContext subsContext = WikiManager.createTechnicalSubscriptionContextForCourse(courseEnv, wikiCourseNode); WikiSecurityCallback callback; + WikiAssessmentProvider assessmentProvider; if(userCourseEnv.isCourseReadOnly()) { callback = new WikiReadOnlySecurityCallback(isGuestOnly, (isAdmininstrator || isResourceOwner)); + assessmentProvider = DryRunAssessmentProvider.create(); } else { - callback = new WikiSecurityCallbackImpl(ne, isAdmininstrator, isGuestOnly, false, isResourceOwner, subsContext); + Boolean courseEditRight = Boolean.valueOf(hasEditRights(wikiCourseNode, userCourseEnv, ne)); + callback = new WikiSecurityCallbackImpl(courseEditRight, isAdmininstrator, isGuestOnly, false, + isResourceOwner, subsContext); + assessmentProvider = userCourseEnv.isParticipant() + ? PersistingAssessmentProvider.create(wikiEntry, getIdentity()) + : DryRunAssessmentProvider.create(); } + if ( ce != null ) { //jump to a certain context OLATResourceable ores = ce.getOLATResourceable(); String typeName = ores.getResourceableTypeName(); @@ -118,9 +129,9 @@ public class WikiRunController extends BasicController implements Activateable2 if(page.endsWith(":0")) { page = page.substring(0, page.length() - 2); } - wikiCtr = WikiManager.getInstance().createWikiMainController(ureq, wControl, re.getOlatResource(), callback, page); + wikiCtr = WikiManager.getInstance().createWikiMainController(ureq, wControl, wikiEntry.getOlatResource(), callback, assessmentProvider, page); } else { - wikiCtr = WikiManager.getInstance().createWikiMainController(ureq, wControl, re.getOlatResource(), callback, null); + wikiCtr = WikiManager.getInstance().createWikiMainController(ureq, wControl, wikiEntry.getOlatResource(), callback, assessmentProvider, null); } listenTo(wikiCtr); @@ -145,6 +156,20 @@ public class WikiRunController extends BasicController implements Activateable2 putInitialPanel(new Panel("uups.no.clone.controller")); } } + + private boolean hasEditRights(WikiCourseNode courseNode, UserCourseEnvironment userCourseEnv, NodeEvaluation ne) { + if (courseNode.hasCustomPreConditions()) { + return ne.isCapabilityAccessible(WikiCourseNode.EDIT_CONDITION); + } + + ModuleConfiguration moduleConfig = courseNode.getModuleConfiguration(); + if ((moduleConfig.getBooleanSafe(WikiCourseNode.CONFIG_KEY_EDIT_BY_COACH) && userCourseEnv.isCoach()) + || (moduleConfig.getBooleanSafe(WikiCourseNode.CONFIG_KEY_EDIT_BY_PARTICIPANT) && userCourseEnv.isParticipant())) { + return true; + } + + return false; + } @Override public void activate(UserRequest ureq, List<ContextEntry> entries, StateEntry state) { diff --git a/src/main/java/org/olat/course/nodes/wiki/_content/edit.html b/src/main/java/org/olat/course/nodes/wiki/_content/edit.html deleted file mode 100644 index e89831383cd0b16a45d9ecb7ddda545cde085b3f..0000000000000000000000000000000000000000 --- a/src/main/java/org/olat/course/nodes/wiki/_content/edit.html +++ /dev/null @@ -1,27 +0,0 @@ -<fieldset class="o_form form-horizontal clearfix"> - <legend>$r.contextHelpWithWrapper("Communication and Collaboration#_bb_wiki") - $r.translate("header")</legend> - - #if ($showPreviewLink) - <div class="form-group"> - <label class="control-label col-sm-3">$r.translate("chosenwiki")</label> - <div class="col-sm-9"><p class="form-control-static">$r.render("command.preview")</p></div> - </div> - <div class="form-group"> - <div class="col-sm-offset-3 col-sm-9"> - $r.render("command.change") - #if($r.available("edit")) - $r.render("edit") - #end - </div> - </div> - #else - <div class="form-group"> - <label class="control-label col-sm-3">$r.translate("chosenwiki")</label> - <div class="col-sm-9"><p class="form-control-static">$chosen_entry</p></div> - </div> - <div class="form-group"> - <div class="col-sm-offset-3 col-sm-9">$r.render("command.create")</div> - </div> - #end -</fieldset> \ No newline at end of file diff --git a/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_ar.properties b/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_ar.properties index ae1be6ecc8ea615df419d7eec1925106be4e40cd..9370bf7489ad38594ebb4eb6305654981c4ceb4e 100644 --- a/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_ar.properties +++ b/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_ar.properties @@ -56,7 +56,6 @@ chosenwiki=\u0627\u0644\u0648\u064A\u0643\u0649 \u0627\u0644\u0645\u062D\u062F\u062F command.change=\u0627\u0633\u062A\u0628\u062F\u0627\u0644 \u0627\u0644\u0648\u064A\u0643\u0649 command.choose=\u0627\u062E\u062A\u064A\u0627\u0631 \u0648\u064A\u0643\u0649 -command.close=\u0625\u063A\u0644\u0627\u0642 \u0627\u0644\u0639\u0631\u0636 command.create=\u0627\u062E\u062A\u064A\u0627\u0631 \u0623\u0648 \u0625\u0646\u0634\u0627\u0621 \u0623\u0648 \u0627\u0633\u062A\u064A\u0631\u0627\u062F \u0648\u064A\u0643\u0649 command.preview=\u0639\u0631\u0636 \u0627\u0644\u0645\u0639\u0627\u064A\u0646\u0629 command.show=\u0639\u0631\u0636 \u0627\u0644\u0648\u064A\u0643\u0649 @@ -77,4 +76,3 @@ header=\u0627\u062E\u062A\u064A\u0627\u0631 \u0648\u064A\u0643\u0649 no.entry.chosen=<i>\u0644\u0645 \u064A\u062A\u0645 \u0627\u062E\u062A\u064A\u0627\u0631 \u0623\u0649 \u0648\u064A\u0643\u0649</i> pane.tab.accessibility=\u0648\u0635\u0648\u0644 pane.tab.wikiconfig=\u0645\u062D\u062A\u0648\u0649 \u0627\u0644\u0648\u064A\u0643\u0649 \u0627\u0644\u062A\u0639\u0644\u064A\u0645\u0649 -pane.tab.wikidisplayconfig=\u0639\u0631\u0636 diff --git a/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_bg.properties b/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_bg.properties index ddb87c48176c791ad4b600842ace43c2c685df0b..41e72cf5f3e666c3b697e4ca12a77f2303ffefcf 100644 --- a/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_bg.properties +++ b/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_bg.properties @@ -56,7 +56,6 @@ chosenwiki=\u0418\u0437\u0431\u0440\u0430\u043D\u043E \u0423\u0438\u043A\u0438 command.change=\u0421\u043C\u0435\u043D\u0435\u0442\u0435 \u0423\u0438\u043A\u0438 command.choose=\u0418\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u0423\u0438\u043A\u0438 -command.close=\u0417\u0430\u0442\u0432\u043E\u0440\u0435\u0442\u0435 \u043F\u0440\u043E\u0437\u043E\u0440\u0435\u0446 command.preview=\u041F\u043E\u043A\u0430\u0436\u0435\u0442\u0435 \u043F\u0440\u0435\u0433\u043B\u0435\u0434 command.show=\u041F\u043E\u043A\u0430\u0436\u0435\u0442\u0435 \u0423\u0438\u043A\u0438 command.showpopup=\u041F\u043E\u043A\u0430\u0436\u0435\u0442\u0435 \u0423\u0438\u043A\u0438 \u0432 \u043D\u043E\u0432 \u043F\u0440\u043E\u0437\u043E\u0440\u0435\u0446 @@ -76,4 +75,3 @@ header=\u0418\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u0423\u0438\u043A\u0438 no.entry.chosen=<i>\u041D\u0435 \u0435 \u0438\u0437\u0431\u0440\u0430\u043D\u043E \u0423\u0438\u043A\u0438</i> pane.tab.accessibility=\u0414\u043E\u0441\u0442\u044A\u043F pane.tab.wikiconfig=\u0423\u0438\u043A\u0438 \u0443\u0447\u0435\u0431\u043D\u043E \u0441\u044A\u0434\u044A\u0440\u0436\u0430\u043D\u0438\u0435 -pane.tab.wikidisplayconfig=\u041F\u043E\u043A\u0430\u0436\u0435\u0442\u0435 diff --git a/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_cs.properties b/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_cs.properties index cbd2d7a3881624591e5fe4bed8320273b5ad43e3..f2e11dacc264cf2c350ebc5c0bea2e44e48babc7 100644 --- a/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_cs.properties +++ b/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_cs.properties @@ -46,7 +46,6 @@ chosenwiki=Vybran\u00FD Wiki studijn\u00ED materi\u00E1l command.change=P\u0159esunout studijn\u00ED materi\u00E1l Wiki command.choose=Vybrat studijn\u00ED materi\u00E1l Wiki -command.close=Zav\u0159\u00EDt n\u00E1hled command.preview=N\u00E1hled command.show=Zobrazit studijn\u00ED materi\u00E1l Wiki command.showpopup=Zobrazit studijn\u00ED materi\u00E1l Wiki v nov\u00E9m okn\u011B @@ -66,4 +65,3 @@ header=Vyberte studijn\u00ED materi\u00E1l Wiki no.entry.chosen=<i>\u017D\u00E1dn\u00FD Wiki studijn\u00ED materi\u00E1l nebyl vybr\u00E1n</i> pane.tab.accessibility=P\u0159\u00EDstup pane.tab.wikiconfig=Studijn\u00ED materi\u00E1l -pane.tab.wikidisplayconfig=Zobrazit diff --git a/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_da.properties b/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_da.properties index 72adff973d251245ffb619a3d292736bd2a53e2d..af07f5e41053e137f06fb39d7f5711ced5dc0fff 100644 --- a/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_da.properties +++ b/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_da.properties @@ -46,7 +46,6 @@ chosenwiki=Valgte Wiki command.change=Erstat Wiki command.choose=V\u00E6lg Wiki -command.close=Luk command.preview=Forh\u00E5ndsvisning command.show=Vis wiki command.showpopup=Vis Wiki i et nyt vindue @@ -64,4 +63,3 @@ header=V\u00E6lg Wiki no.entry.chosen=<i>Ingen Wiki er valgt</i> pane.tab.accessibility=Adgange pane.tab.wikiconfig=L\u00E6ringsindhold -pane.tab.wikidisplayconfig=Visning diff --git a/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_de.properties index 6234681ba7cb5972cc51a062dd6c1380c7546ccf..fe4ba6f3d2f3e68f82349552199932114f0f9099 100644 --- a/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_de.properties @@ -58,12 +58,15 @@ chosenwiki=Gew\u00E4hltes Wiki command.change=Wiki auswechseln command.create=Wiki w\u00E4hlen, erstellen oder importieren command.choose=Wiki w\u00E4hlen -command.close=Ansicht schliessen command.preview=Vorschau anzeigen command.show=Wiki anzeigen command.showpopup=Wiki in neuem Fenster anzeigen condition.accessibility.title=Zugang condition.editable.title=Artikel bearbeiten / erstellen +config.edit=Artikel bearbeiten +config.rights=Benutzerberechtigungen +config.role.coach=Betreuer +config.role.participant=Teilnehmer display.config=Wiki-Menu in Kursmenu? display.config.fieldsettitle=Konfiguration error.launch=Wiki konnte nicht gestartet werden. @@ -78,4 +81,4 @@ header=Wiki ausw\u00E4hlen no.entry.chosen=<i>Kein Wiki ausgew\u00E4hlt</i> pane.tab.accessibility=Zugang pane.tab.wikiconfig=Wiki-Lerninhalt -pane.tab.wikidisplayconfig=Anzeige +preview=Vorschau diff --git a/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_el.properties b/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_el.properties index 3c58e3a5346983d6a2bcbef2156b672402d116f0..29caf6bb8de81347876f9789615a04e9a5af2f36 100644 --- a/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_el.properties +++ b/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_el.properties @@ -44,7 +44,6 @@ chosenwiki=\u0395\u03C0\u03B9\u03BB\u03B5\u03B3\u03BC\u03AD\u03BD\u03BF Wiki command.change=\u0391\u03BD\u03C4\u03B9\u03BA\u03B1\u03C4\u03AC\u03C3\u03C4\u03B1\u03C3\u03B7 Wiki command.choose=\u0395\u03C0\u03B9\u03BB\u03BF\u03B3\u03AE Wiki -command.close=\u039A\u03BB\u03B5\u03AF\u03C3\u03B9\u03BC\u03BF \u03B5\u03BC\u03C6\u03AC\u03BD\u03B9\u03C3\u03B7\u03C2 command.create=\u0395\u03C0\u03B9\u03BB\u03BF\u03B3\u03AE, \u03B4\u03B7\u03BC\u03B9\u03BF\u03C5\u03C1\u03B3\u03AF\u03B1 \u03AE \u03B5\u03B9\u03C3\u03B1\u03B3\u03C9\u03B3\u03AE Wiki command.preview=\u0395\u03BC\u03C6\u03AC\u03BD\u03B9\u03C3\u03B7 \u03C0\u03C1\u03BF\u03B5\u03C0\u03B9\u03C3\u03BA\u03CC\u03C0\u03B7\u03C3\u03B7\u03C2 command.show=\u0395\u03BC\u03C6\u03AC\u03BD\u03B9\u03C3\u03B7 Wiki @@ -65,5 +64,4 @@ header=Select Wiki no.entry.chosen=<i>\u0394\u03B5\u03BD \u03B5\u03C0\u03B9\u03BB\u03AD\u03C7\u03C4\u03B7\u03BA\u03B5 Wiki</i> pane.tab.accessibility=\u03A0\u03C1\u03CC\u03C3\u03B2\u03B1\u03C3\u03B7 pane.tab.wikiconfig=\u03A0\u03B5\u03C1\u03B9\u03B5\u03C7\u03CC\u03BC\u03B5\u03BD\u03BF \u03BC\u03AC\u03B8\u03B7\u03C3\u03B7\u03C2 Wiki -pane.tab.wikidisplayconfig=\u0395\u03BC\u03C6\u03AC\u03BD\u03B9\u03C3\u03B7 title_wiki=Wiki diff --git a/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_en.properties index 4854ff5f1a3b73d003775bbd1f2fd6b27d7d5598..46efef79f42e60294d494193c3d9aa278c1a68f0 100644 --- a/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_en.properties @@ -56,13 +56,16 @@ chosenwiki=Selected Wiki command.change=Replace Wiki command.choose=Choose Wiki -command.close=Close view command.create=Choose, create or import Wiki command.preview=Show preview command.show=Show Wiki command.showpopup=Show Wiki in new window condition.accessibility.title=Access condition.editable.title=Create/edit material +config.edit=Edit Material +config.rights=User rights +config.role.coach=Coach +config.role.participant=Participant display.config=Wiki menu in course menu? display.config.fieldsettitle=Configuration error.launch=Unable to start Wiki. @@ -77,5 +80,5 @@ header=Select Wiki no.entry.chosen=<i>No Wiki selected</i> pane.tab.accessibility=Access pane.tab.wikiconfig=Wiki learning content -pane.tab.wikidisplayconfig=Display +preview=Preview title_wiki=Wiki diff --git a/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_es.properties b/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_es.properties index 94cf7cc6b867624c05704007eaeab3bff2f65699..682449ce885f3a34309aa527f9c558bb279705e5 100644 --- a/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_es.properties +++ b/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_es.properties @@ -56,7 +56,6 @@ chosenwiki=Contenido did\u00E1ctico Wiki elegido command.change=Substituye contenido did\u00E1ctico Wiki command.choose=Elige contenido did\u00E1ctico Wiki -command.close=Cerrar command.preview=Mostrar vista previa command.show=Muesta contenido did\u00E1ctico Wiki command.showpopup=Muesta contenido did\u00E1ctico Wiki en p\u00E1gina nueva @@ -76,4 +75,3 @@ header=Elegir contenido did\u00E1ctico Wiki no.entry.chosen=<i>Ningun Wiki fue elegido</i> pane.tab.accessibility=Acceso pane.tab.wikiconfig=Contenido did\u00E1ctico Wiki -pane.tab.wikidisplayconfig=Presentaci\u00F3n diff --git a/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_fr.properties b/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_fr.properties index 41050b51542c9109547946e437b2d6057bedac03..00a38486739dcca65c4fe5bf8c28c0c5bc67c040 100644 --- a/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_fr.properties +++ b/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_fr.properties @@ -56,7 +56,6 @@ chosenwiki=Wiki s\u00E9lectionn\u00E9 command.change=Remplacer wiki command.choose=Choisir wiki -command.close=Fermer aper\u00E7u command.create=S\u00E9lectionner, cr\u00E9er ou importer wiki command.preview=Afficher aper\u00E7u command.show=Afficher wiki @@ -77,5 +76,4 @@ header=Choisir wiki no.entry.chosen=<i>aucun wiki s\u00E9lectionn\u00E9</i> pane.tab.accessibility=Acc\u00E8s pane.tab.wikiconfig=Contenu didactique Wiki -pane.tab.wikidisplayconfig=Affichage title_wiki=Wiki diff --git a/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_it.properties b/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_it.properties index d2d460f8acb4ea02a11a8f263004efde31f07966..1ae98b73b6626bfa952c51dbf9f8210c27ffec72 100644 --- a/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_it.properties +++ b/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_it.properties @@ -56,7 +56,6 @@ chosenwiki=Wiki selezionato command.change=Sostituire wiki command.choose=Selezionare wiki -command.close=Chiudere command.create=Selezionare, creare o importare un wiki command.preview=Mostrare anteprima command.show=Mostrare wiki @@ -77,5 +76,4 @@ header=Selezione del wiki no.entry.chosen=<i>Nessun wiki selezionato</i> pane.tab.accessibility=Accesso pane.tab.wikiconfig=Contenuto didattico wiki -pane.tab.wikidisplayconfig=Visualizzazione title_wiki=Wiki diff --git a/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_jp.properties b/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_jp.properties index 621e7525db2cedccf216545da918680dcf341acc..5e2e82940af476dc9dc9b34f8bbc8610a343fb65 100644 --- a/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_jp.properties +++ b/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_jp.properties @@ -48,7 +48,6 @@ chosenwiki=\u9078\u629E\u6E08\u307FWiki command.change=Wiki\u3092\u7F6E\u63DB\u3059\u308B command.choose=Wiki\u3092\u9078\u629E\u3059\u308B -command.close=\u30D3\u30E5\u30FC\u3092\u9589\u3058\u308B command.create=Wik\u3092\u9078\u629E\u3001\u4F5C\u6210\u307E\u305F\u306F\u30A4\u30F3\u30DD\u30FC\u30C8\u3059\u308B command.preview=\u30D7\u30EC\u30D3\u30E5\u30FC\u3092\u8868\u793A\u3059\u308B command.show=Wiki\u3092\u8868\u793A\u3059\u308B @@ -68,4 +67,3 @@ header=Wiki\u3092\u9078\u629E\u3059\u308B no.entry.chosen=<i>Wiki\u304C\u9078\u629E\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002</i> pane.tab.accessibility=\u30A2\u30AF\u30BB\u30B9 pane.tab.wikiconfig=Wiki\u5B66\u7FD2\u30B3\u30F3\u30C6\u30F3\u30C4 -pane.tab.wikidisplayconfig=\u8868\u793A diff --git a/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_lt.properties b/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_lt.properties index 92e06495ff113f38fff6f8fa276d6da96f2c6855..f6ac75e26efca1f91d4decd2dedea4c3d94a9f6c 100644 --- a/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_lt.properties +++ b/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_lt.properties @@ -18,7 +18,6 @@ chosenwiki=Pasirinktas turinys command.change=Wiki mokymo turin\u012F pakeisti kitu command.choose=Pasirinkti Wiki mokymo turin\u012F -command.close=U\u017Edaryti command.preview=Per\u017Ei\u016Bra command.show=Rodyti Wiki mokymo turin\u012F command.showpopup=Wiki mokymo turin\u012F rodyti naujame lange @@ -35,4 +34,3 @@ header=I\u0161rinkite Wiki mokymo turin\u012F no.entry.chosen=<i>Turinys nepasirinktas</i> pane.tab.accessibility=Prieiga pane.tab.wikiconfig=Mokymo turinys -pane.tab.wikidisplayconfig=Pavaizduoti diff --git a/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_nl_NL.properties b/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_nl_NL.properties index bfcf630df581065fb562709e9d5abf23e38b33bd..da733aba2da086fb9d3016f8523f196d06ddd1f8 100644 --- a/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_nl_NL.properties +++ b/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_nl_NL.properties @@ -56,7 +56,6 @@ chosenwiki=Selecteer Wiki command.change=Vervang Wiki command.choose=Kies Wiki -command.close=Sluit weergave command.create=Eem Wiki kiezen, aanmaken of importeren command.preview=Toon voorvertoning command.show=Toon Wiki @@ -77,5 +76,4 @@ header=Selecteer Wiki no.entry.chosen=<i>Geen Wiki geselecteerd</i> pane.tab.accessibility=Toegang pane.tab.wikiconfig=Wiki leerinhoud -pane.tab.wikidisplayconfig=Weergave title_wiki=Wiki diff --git a/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_pl.properties b/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_pl.properties index 2cc891758b831b410d33bacd7a88fdc969e5d787..fd16d7f2eb7eddb3f46b9c5f21948c4ee93fb7b3 100644 --- a/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_pl.properties +++ b/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_pl.properties @@ -56,7 +56,6 @@ chosenwiki=Wybrany portal Wiki command.change=Zmie\u0144 portal Wiki command.choose=Wybierz portal Wiki -command.close=Zamknij widok command.create=Wybierz, utw\u00F3rz lub zaimportuj Wiki command.preview=Podgl\u0105d command.show=Poka\u017C portal Wiki @@ -77,5 +76,4 @@ header=Wybierz portal Wiki no.entry.chosen=<i>Portal Wiki nie zosta\u0142 wybrany</i> pane.tab.accessibility=Dost\u0119p pane.tab.wikiconfig=Konfiguracja Wiki -pane.tab.wikidisplayconfig=Wy\u015Bwietl title_wiki=Wiki diff --git a/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_pt_BR.properties b/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_pt_BR.properties index 5705a0c5b0516ee1730f8df45d71ba45e70a849a..7900e647dfe201f2615cbf5c3cc66a8287ffeebf 100644 --- a/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_pt_BR.properties +++ b/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_pt_BR.properties @@ -56,7 +56,6 @@ chosenwiki=Conte\u00FAdo did\u00E1tico Wiki selecionado command.change=Substituir conte\u00FAdo did\u00E1tico Wiki command.choose=Escolher conte\u00FAdo did\u00E1tico Wiki -command.close=Fechar visualiza\u00E7\u00E3o command.create=<b>Data de vencimento</b> Data de in\u00EDcio command.preview=Visualiza\u00E7\u00E3o command.show=Exibir conte\u00FAdo did\u00E1tico Wiki @@ -77,5 +76,4 @@ header=Escolher conte\u00FAdo did\u00E1tico Wiki no.entry.chosen=<i>Nenhum conte\u00FAdo did\u00E1tico Wiki selecionado</i> pane.tab.accessibility=Acesso pane.tab.wikiconfig=Conte\u00FAdo did\u00E1tico -pane.tab.wikidisplayconfig=Exibir title_wiki=Wiki diff --git a/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_pt_PT.properties b/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_pt_PT.properties index 1176f6fb07e70ba2dfad5ad26d086405c41e38d1..e425d8bbf0220889fde16b7d2f640a63916e8520 100644 --- a/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_pt_PT.properties +++ b/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_pt_PT.properties @@ -46,7 +46,6 @@ chosenwiki=Conte\u00FAdo did\u00E1tico Wiki selecionado command.change=Substituir conte\u00FAdo did\u00E1tico Wiki command.choose=Escolher conte\u00FAdo did\u00E1tico Wiki -command.close=Fechar visualiza\u00E7\u00E3o command.preview=Visualiza\u00E7\u00E3o command.show=Exibir conte\u00FAdo did\u00E1tico Wiki command.showpopup=Exibir conte\u00FAdo did\u00E1tico Wiki em nova janela @@ -64,4 +63,3 @@ header=Escolher conte\u00FAdo did\u00E1tico Wiki no.entry.chosen=<i>Nenhum conte\u00FAdo did\u00E1tico Wiki selecionado</i> pane.tab.accessibility=Acesso pane.tab.wikiconfig=Conte\u00FAdo did\u00E1tico -pane.tab.wikidisplayconfig=Exibir diff --git a/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_ru.properties b/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_ru.properties index b1bcc43af2fa79e6edf0469da4c3058635d13bfc..09de776218f02b2f638c7a757e71d7e10dd3dc0c 100644 --- a/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_ru.properties +++ b/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_ru.properties @@ -18,7 +18,6 @@ chosenwiki=\u0412\u044B\u0431\u0440\u0430\u043D\u043D\u0430\u044F \u0432\u0438\u043A\u0438 command.change=\u0417\u0430\u043C\u0435\u043D\u0438\u0442\u044C \u0432\u0438\u043A\u0438 command.choose=\u0412\u044B\u0431\u0440\u0430\u0442\u044C \u0432\u0438\u043A\u0438 -command.close=\u0417\u0430\u043A\u0440\u044B\u0442\u044C \u043F\u043E\u043A\u0430\u0437 command.create=\u0412\u044B\u0431\u0440\u0430\u0442\u044C, \u0441\u043E\u0437\u0434\u0430\u0442\u044C \u0438\u043B\u0438 \u0438\u043C\u043F\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0412\u0438\u043A\u0438 command.preview=\u041F\u0440\u0435\u0434\u0432\u0430\u0440\u0438\u0442\u0435\u043B\u044C\u043D\u044B\u0439 \u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440 command.show=\u041F\u043E\u043A\u0430\u0437\u0430\u0442\u044C \u0432\u0438\u043A\u0438 @@ -39,4 +38,3 @@ header=\u0412\u044B\u0431\u0440\u0430\u0442\u044C \u0432\u0438\u043A\u0438 no.entry.chosen=<i>\u041D\u0435 \u0431\u044B\u043B\u043E \u0432\u044B\u0431\u0440\u0430\u043D\u043E \u043D\u0438 \u043E\u0434\u043D\u043E\u0439 \u0432\u0438\u043A\u0438</i> pane.tab.accessibility=\u0414\u043E\u0441\u0442\u0443\u043F pane.tab.wikiconfig=\u0423\u0447\u0435\u0431\u043D\u043E\u0435 \u0441\u043E\u0434\u0435\u0440\u0436\u0430\u043D\u0438\u0435 \u0432\u0438\u043A\u0438 -pane.tab.wikidisplayconfig=\u041F\u043E\u043A\u0430\u0437 diff --git a/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_sq.properties b/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_sq.properties index c7420030152451821ed377e55a897562eb12f340..b28c1d5fdfc20c0704e55a93bca8a223edf90c3d 100644 --- a/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_sq.properties +++ b/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_sq.properties @@ -18,7 +18,6 @@ chosenwiki=Wiki t\u00EB p\u00EBrzgjedhur command.change=Z\u00EBvend\u00EBso Wiki command.choose=P\u00EBrzgjedh Wiki -command.close=Mbylle pamjen command.preview=Paraafisho command.show=Shfaq Wiki command.showpopup=Shfaq Wiki n\u00EB nj\u00EB dritare t\u00EB re @@ -36,4 +35,3 @@ header=P\u00EBrzgjedh Wiki no.entry.chosen=<i>Asjnj\u00EB Wiki t\u00EB p\u00EBrzgjedhur</i> pane.tab.accessibility=Qasshm\u00EBria pane.tab.wikiconfig=P\u00EBrmbajtje m\u00EBsimi -pane.tab.wikidisplayconfig=Shfaq diff --git a/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_zh_CN.properties b/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_zh_CN.properties index 530939bf702459b033cfe37d22408fa9d11e6040..f009bb4acf19fbb369818f1aebbcde870fc5737f 100644 --- a/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_zh_CN.properties +++ b/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_zh_CN.properties @@ -56,7 +56,6 @@ chosenwiki=\u9009\u4E2D\u7684Wiki\u5B66\u4E60\u5185\u5BB9 command.change=\u66FF\u6362Wiki\u5B66\u4E60\u5185\u5BB9 command.choose=\u9009\u62E9Wiki\u5B66\u4E60\u5185\u5BB9 -command.close=\u5173\u95ED\u89C6\u56FE command.create=\u9009\u62E9\uFF0C\u521B\u5EFA\u6216\u8005\u5BFC\u5165\u7EF4\u57FA command.preview=\u9884\u89C8 command.show=\u663E\u793AWiki\u5B66\u4E60\u5185\u5BB9 @@ -77,4 +76,3 @@ header=\u9009\u62E9Wiki\u5B66\u4E60\u5185\u5BB9 no.entry.chosen=<i>\u6CA1\u6709\u9009\u62E9\u4EFB\u4F55Wiki\u5B66\u4E60\u5185\u5BB9</i> pane.tab.accessibility=\u8BBF\u95EE pane.tab.wikiconfig=\u5B66\u4E60\u5185\u5BB9 -pane.tab.wikidisplayconfig=\u663E\u793A diff --git a/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_zh_TW.properties b/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_zh_TW.properties index bbbd4891c006ef3eb3961cf1723b401e05ffb4f2..c5c68efd3f3164b4a228c9f83b32674867cca210 100644 --- a/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_zh_TW.properties +++ b/src/main/java/org/olat/course/nodes/wiki/_i18n/LocalStrings_zh_TW.properties @@ -27,7 +27,6 @@ chosenwiki=\u9078\u53D6\u7684 Wiki command.change=\u53D6\u4EE3 Wiki command.choose=\u9078\u53D6 Wiki -command.close=\u95DC\u9589\u8996\u7A97 command.preview=\u986F\u793A\u9810\u89BD command.show=\u986F\u793A Wiki command.showpopup=\u5728\u65B0\u8996\u7A97\u986F\u793A Wiki @@ -47,5 +46,4 @@ header=\u9078\u64C7 Wiki no.entry.chosen=<i>\u6C92\u6709 Wiki \u88AB\u9078\u53D6</i> pane.tab.accessibility=\u5B58\u53D6 pane.tab.wikiconfig=Wiki \u5B78\u7FD2\u5167\u5BB9 -pane.tab.wikidisplayconfig=\u986F\u793A title_wiki=Wiki diff --git a/src/main/java/org/olat/course/run/RunMainController.java b/src/main/java/org/olat/course/run/RunMainController.java index 36cd0d817bb844c869ad41070353ac5f394b4d84..f19c6a4cdbb22d007d70f8abbf82168ba59c674b 100644 --- a/src/main/java/org/olat/course/run/RunMainController.java +++ b/src/main/java/org/olat/course/run/RunMainController.java @@ -371,11 +371,17 @@ public class RunMainController extends MainLayoutBasicController implements Gene needsRebuildAfterRunDone = true; } - private CourseNode updateAfterChanges(CourseNode courseNode) { + /** + * + * @param courseNode + * @param selectedNodeId my be the nodeId of a node of the subtree + * @return + */ + private CourseNode updateAfterChanges(CourseNode courseNode, String selectedNodeId) { if(currentCourseNode == null) return null; CourseNode newCurrentCourseNode; - NodeClickedRef nclr = navHandler.reloadTreeAfterChanges(courseNode); + NodeClickedRef nclr = navHandler.reloadTreeAfterChanges(courseNode, selectedNodeId); if(nclr == null) { doDisposeAfterEvent(); newCurrentCourseNode = null; @@ -528,10 +534,12 @@ public class RunMainController extends MainLayoutBasicController implements Gene boolean showDone = false; if (calledCourseNode != null) { TreeNode treeNode = treeModel.getNodeById(calledCourseNode.getIdent()); - boolean confirmationEnabled = nodeAccessService.isAssessmentConfirmationEnabled(calledCourseNode, getUce()); - AssessmentEvaluation assessmentEvaluation = getUce().getScoreAccounting().evalCourseNode(calledCourseNode); - confirmVisible = confirmationEnabled && treeNode.isAccessible(); - showDone = !Boolean.TRUE.equals(assessmentEvaluation.getFullyAssessed()); + if (treeNode != null) { + boolean confirmationEnabled = nodeAccessService.isAssessmentConfirmationEnabled(calledCourseNode, getUce()); + AssessmentEvaluation assessmentEvaluation = getUce().getScoreAccounting().evalCourseNode(calledCourseNode); + confirmVisible = confirmationEnabled && treeNode.isAccessible(); + showDone = !Boolean.TRUE.equals(assessmentEvaluation.getFullyAssessed()); + } } paginationCtrl.updateAssessmentConfirmUI(confirmVisible, showDone); updateProgressUI(); @@ -582,7 +590,7 @@ public class RunMainController extends MainLayoutBasicController implements Gene @Override public void event(UserRequest ureq, Component source, Event event) { if(needsRebuildAfter) { - currentCourseNode = updateAfterChanges(currentCourseNode); + currentCourseNode = updateAfterChanges(currentCourseNode, currentCourseNode.getIdent()); needsRebuildAfter = false; } @@ -643,7 +651,7 @@ public class RunMainController extends MainLayoutBasicController implements Gene @Override public void event(UserRequest ureq, Controller source, Event event) { if(needsRebuildAfter) { - currentCourseNode = updateAfterChanges(currentCourseNode); + currentCourseNode = updateAfterChanges(currentCourseNode, currentCourseNode.getIdent()); needsRebuildAfter = false; } @@ -734,7 +742,7 @@ public class RunMainController extends MainLayoutBasicController implements Gene private void doAssessmentConfirmation(boolean confirmed) { nodeAccessService.onAssessmentConfirmed(getCurrentCourseNode(), getUce(), confirmed); - updateAfterChanges(getCurrentCourseNode()); + updateAfterChanges(getCurrentCourseNode(), luTree.getSelectedNodeId()); updateAssessmentConfirmUI(getCurrentCourseNode()); } diff --git a/src/main/java/org/olat/course/run/navigation/NavigationHandler.java b/src/main/java/org/olat/course/run/navigation/NavigationHandler.java index 376a2307c823b8fb25f8b861faaef6076ba456b6..d5368bf3906c801df6a35c52162b92b9bf2b71cb 100644 --- a/src/main/java/org/olat/course/run/navigation/NavigationHandler.java +++ b/src/main/java/org/olat/course/run/navigation/NavigationHandler.java @@ -68,6 +68,7 @@ import org.olat.course.nodes.CourseNode; import org.olat.course.nodes.CourseNodeFactory; import org.olat.course.nodes.STCourseNode; import org.olat.course.nodes.cp.CPRunController; +import org.olat.course.nodes.wiki.WikiRunController; import org.olat.course.run.userview.CourseTreeNode; import org.olat.course.run.userview.UserCourseEnvironment; import org.olat.course.run.userview.VisibilityFilter; @@ -187,8 +188,6 @@ public class NavigationHandler implements Disposable { } } else { // Use the subtreemodelhandler - Object userObject = selTN.getUserObject(); - NodeRunConstructionResult nrcr = null; CourseNode internCourseNode = null; GenericTreeModel subTreeModel; @@ -229,6 +228,9 @@ public class NavigationHandler implements Disposable { if(subtreemodelListener != currentNodeController) { if(subtreemodelListener instanceof CPRunController) { nrcr = ((CPRunController)subtreemodelListener).createNodeRunConstructionResult(ureq, selTN.getIdent()); + } else + if(subtreemodelListener instanceof WikiRunController) { + nrcr = ((WikiRunController)subtreemodelListener).createNodeRunConstructionResult(); } else { nrcr = new NodeRunConstructionResult((Controller)subtreemodelListener); } @@ -239,45 +241,35 @@ public class NavigationHandler implements Disposable { log.debug("delegating to handler: treeNodeId = " + treeNodeId); } - // Update the node and event to match the new tree model - unless we - // are already on the correct node to prevent jumping to other - // chapters in CP's when the href (userObject) is not unique and - // used in multiple nodes. - if (!userObject.equals(selTN.getUserObject())) { - selTN = subTreeModel.findNodeByUserObject(userObject); - } treeEvent = new TreeEvent(treeEvent.getCommand(), treeEvent.getSubCommand(), selTN.getIdent()); boolean dispatch = true; String selectedNodeId = null; - if(userObject instanceof String) { - String sObject = (String)userObject; - if(MenuTree.COMMAND_TREENODE_CLICKED.equals(treeEvent.getCommand()) && treeEvent.getSubCommand() == null) { - openCourseNodeIds.add(sObject); - selectedNodeId = selTN.getIdent(); - } else if(TreeEvent.COMMAND_TREENODE_OPEN.equals(treeEvent.getSubCommand())) { - openCourseNodeIds.add(sObject); - selectedNodeId = selTN.getIdent(); - dispatch = false; - } else if(TreeEvent.COMMAND_TREENODE_CLOSE.equals(treeEvent.getSubCommand())) { - removeChildrenFromOpenNodes(selTN); - openCourseNodeIds.remove(sObject); - openCourseNodeIds.remove(selTN.getIdent()); - dispatch = false; - } + if(MenuTree.COMMAND_TREENODE_CLICKED.equals(treeEvent.getCommand()) && treeEvent.getSubCommand() == null) { + openCourseNodeIds.add(selTN.getIdent()); + selectedNodeId = selTN.getIdent(); + } else if(TreeEvent.COMMAND_TREENODE_OPEN.equals(treeEvent.getSubCommand())) { + openCourseNodeIds.add(selTN.getIdent()); + selectedNodeId = selTN.getIdent(); + dispatch = false; + } else if(TreeEvent.COMMAND_TREENODE_CLOSE.equals(treeEvent.getSubCommand())) { + removeChildrenFromOpenNodes(selTN); + openCourseNodeIds.remove(selTN.getIdent()); + dispatch = false; } if(dispatch) { // null as controller source since we are not a controller subtreemodelListener.dispatchEvent(ureq, null, treeEvent); // no node construction result indicates handled + reattachExternalTreeModels(treeModel); } ncr = new NodeClickedRef(treeModel, true, selectedNodeId, openCourseNodeIds, internCourseNode, nrcr, true); } return ncr; } - public NodeClickedRef reloadTreeAfterChanges(CourseNode courseNode) { + public NodeClickedRef reloadTreeAfterChanges(CourseNode courseNode, String selectedNodeId) { GenericTreeModel treeModel = createTreeModel(); TreeNode treeNode = treeModel.getNodeById(courseNode.getIdent()); NodeClickedRef nclr = null; @@ -290,12 +282,10 @@ public class NavigationHandler implements Disposable { reattachExternalTreeModels(treeModel); } - selectedCourseNodeId = courseTreeNode.getCourseNode().getIdent(); - if(subtreemodelListener == null) { - nclr = new NodeClickedRef(treeModel, true, selectedCourseNodeId, openCourseNodeIds, courseTreeNode.getCourseNode(), null, false); + nclr = new NodeClickedRef(treeModel, true, selectedNodeId, openCourseNodeIds, courseTreeNode.getCourseNode(), null, false); } else { - nclr = new NodeClickedRef(treeModel, true, selectedCourseNodeId, openCourseNodeIds, courseTreeNode.getCourseNode(), null, true); + nclr = new NodeClickedRef(treeModel, true, selectedNodeId, openCourseNodeIds, courseTreeNode.getCourseNode(), null, true); } } return nclr; @@ -416,8 +406,8 @@ public class NavigationHandler implements Disposable { if(!newSelectedNodeId.equals(ncr.getSelectedTreeNodeId())) { if(ncr.getSelectedTreeNodeId() != null) { TreeNode selectedNode = subTreeModel.getNodeById(ncr.getSelectedTreeNodeId()); - if(selectedNode != null && selectedNode.getUserObject() instanceof String) { - openCourseNodeIds.add((String)selectedNode.getUserObject()); + if(selectedNode != null) { + openCourseNodeIds.add(selectedNode.getIdent()); } } } @@ -458,7 +448,7 @@ public class NavigationHandler implements Disposable { } } if (evaluateTree) { - treeModel = createTreeModel();; + treeModel = createTreeModel(); } if((TreeEvent.COMMAND_TREENODE_OPEN.equals(nodeSubCmd) || TreeEvent.COMMAND_TREENODE_CLOSE.equals(nodeSubCmd)) && @@ -490,7 +480,7 @@ public class NavigationHandler implements Disposable { return nodeAccessService.getCourseTreeModelBuilder(userCourseEnv).withFilter(filter).build(); } - private void reattachExternalTreeModels(GenericTreeModel courseTreeModel) { + private void reattachExternalTreeModels(TreeModel courseTreeModel) { if(externalTreeModels == null || externalTreeModels.isEmpty()) return; for(Map.Entry<String, SubTree> entry:externalTreeModels.entrySet()) { @@ -517,7 +507,6 @@ public class NavigationHandler implements Disposable { private void removeChildrenFromOpenNodes(TreeNode treeNode) { openCourseNodeIds.remove(treeNode.getIdent()); - openCourseNodeIds.remove(treeNode.getUserObject()); for(int i=treeNode.getChildCount(); i-->0; ) { removeChildrenFromOpenNodes((TreeNode)treeNode.getChildAt(i)); } @@ -568,12 +557,10 @@ public class NavigationHandler implements Disposable { private void addSubTreeModel(TreeNode parent, TreeModel modelToAppend) { // ignore root and directly add children. // need to clone children so that are not detached from their original - // parent (which is the cp treemodel) - // parent.addChild(modelToAppend.getRootNode()); + parent.removeAllChildren(); TreeNode root = modelToAppend.getRootNode(); int chdCnt = root.getChildCount(); - // full cloning of ETH webclass energie takes about 4/100 of a second for (int i = chdCnt; i > 0; i--) { INode chd = root.getChildAt(i-1); INode chdc = (INode) XStreamHelper.xstreamClone(chd); @@ -583,20 +570,6 @@ public class NavigationHandler implements Disposable { // always insert before already existing course building block children parent.insert(chdc, 0); } - - copyIdent(parent, root); - } - - private void copyIdent(TreeNode guiNode, TreeNode originalNode) { - if(guiNode instanceof GenericTreeNode) { - ((GenericTreeNode)guiNode).setIdent(originalNode.getIdent()); - } - - for (int i=originalNode.getChildCount(); i-->0; ) { - INode originalChild = originalNode.getChildAt(i); - INode guiChild = guiNode.getChildAt(i); - copyIdent((TreeNode)guiChild, (TreeNode)originalChild); - } } private static class SubTree { diff --git a/src/main/java/org/olat/gui/control/OlatFooterController.java b/src/main/java/org/olat/gui/control/OlatFooterController.java index 057ccf6681a23863f0bbc2c2e9abfead60562e8d..29df761002fbeca04c4e6716ff4ed3e7e825c012 100644 --- a/src/main/java/org/olat/gui/control/OlatFooterController.java +++ b/src/main/java/org/olat/gui/control/OlatFooterController.java @@ -116,13 +116,11 @@ public class OlatFooterController extends BasicController implements LockableCon // Push information about user if (!isGuest && usess.isAuthenticated()) { olatFootervc.contextPut("loggedIn", Boolean.TRUE); - if(isInvitee) { - String fullName = CoreSpringFactory.getImpl(UserManager.class).getUserDisplayName(ureq.getIdentity()); - olatFootervc.contextPut("username", StringHelper.escapeHtml(fullName) + " " + translate("logged.in.invitee")); - } else { - String fullName = CoreSpringFactory.getImpl(UserManager.class).getUserDisplayName(ureq.getIdentity()); - olatFootervc.contextPut("username", StringHelper.escapeHtml(fullName)); + String fullName = StringHelper.escapeHtml(CoreSpringFactory.getImpl(UserManager.class).getUserDisplayName(ureq.getIdentity())); + if (isInvitee) { + fullName = fullName + " " + translate("logged.in.invitee"); } + olatFootervc.contextPut("username", fullName); } else { olatFootervc.contextPut("loggedIn", Boolean.FALSE); } diff --git a/src/main/java/org/olat/ims/cp/ui/CPEditMainController.java b/src/main/java/org/olat/ims/cp/ui/CPEditMainController.java index 0ef50abc34b4d3694554d2b26abcf009a2b6b69e..8b61dc064ec7caac71dbe20ec5f3dc8804ec620a 100644 --- a/src/main/java/org/olat/ims/cp/ui/CPEditMainController.java +++ b/src/main/java/org/olat/ims/cp/ui/CPEditMainController.java @@ -45,7 +45,10 @@ import org.olat.core.util.coordinate.LockResult; import org.olat.core.util.vfs.VFSContainer; import org.olat.ims.cp.CPManager; import org.olat.ims.cp.ContentPackage; +import org.olat.modules.cp.CPAssessmentProvider; import org.olat.modules.cp.CPUIFactory; +import org.olat.modules.cp.PersistingAssessmentProvider; +import org.olat.repository.RepositoryEntry; import org.olat.repository.ui.RepositoryEntryRuntimeController.ToolbarAware; import org.springframework.beans.factory.annotation.Autowired; @@ -65,9 +68,10 @@ public class CPEditMainController extends BasicController implements ToolbarAwar private CPManager cpManager; public CPEditMainController(UserRequest ureq, WindowControl wControl, TooledStackedPanel toolbar, - VFSContainer cpContainer, OLATResourceable ores) { + VFSContainer cpContainer, RepositoryEntry cpEntry) { super(ureq, wControl); this.stackPanel = toolbar; + OLATResourceable ores = cpEntry.getOlatResource(); // acquire lock for resource lock = CoordinatorManager.getInstance().getCoordinator().getLocker().acquireLock(ores, ureq.getIdentity(), null); @@ -103,8 +107,10 @@ public class CPEditMainController extends BasicController implements ToolbarAwar } } else { showInfo("contentcontroller.no.lock"); + + CPAssessmentProvider cpAssessmentProvider = PersistingAssessmentProvider.create(cpEntry, getIdentity()); Controller cpCtr = CPUIFactory.getInstance() - .createMainLayoutController(ureq, wControl, cpContainer, true, deliveryOptions); + .createMainLayoutController(ureq, wControl, cpContainer, true, deliveryOptions, cpAssessmentProvider); putInitialPanel(cpCtr.getInitialComponent()); } } else { diff --git a/src/main/java/org/olat/ims/qti21/pool/QTI21ExportProcessor.java b/src/main/java/org/olat/ims/qti21/pool/QTI21ExportProcessor.java index 08f5c5d6beb57105e9c0ee94aeb1834d57be30fe..fce4ef6f6ddfe2295188454f035875f77d2705a8 100644 --- a/src/main/java/org/olat/ims/qti21/pool/QTI21ExportProcessor.java +++ b/src/main/java/org/olat/ims/qti21/pool/QTI21ExportProcessor.java @@ -22,6 +22,7 @@ package org.olat.ims.qti21.pool; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.io.OutputStream; import java.net.URI; import java.net.URISyntaxException; import java.nio.file.FileVisitResult; @@ -29,8 +30,10 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; +import java.util.HashMap; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.concurrent.atomic.DoubleAdder; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @@ -60,6 +63,7 @@ import org.olat.modules.qpool.manager.QPoolFileStorage; import uk.ac.ed.ph.jqtiplus.node.item.AssessmentItem; import uk.ac.ed.ph.jqtiplus.node.test.AssessmentSection; import uk.ac.ed.ph.jqtiplus.node.test.AssessmentTest; +import uk.ac.ed.ph.jqtiplus.node.test.TestPart; import uk.ac.ed.ph.jqtiplus.resolution.ResolvedAssessmentItem; import uk.ac.ed.ph.jqtiplus.serialization.QtiSerializer; @@ -104,9 +108,9 @@ public class QTI21ExportProcessor { .loadAndResolveAssessmentItemForCopy(assessmentItemUri, rootDirectory); enrichWithMetadata(qitem, resolvedAssessmentItem, manifestBuilder); - try { + try(OutputStream out = new ShieldOutputStream(zout)) { zout.putNextEntry(new ZipEntry(rootDir + "/imsmanifest.xml")); - manifestBuilder.write(new ShieldOutputStream(zout)); + manifestBuilder.write(out); zout.closeEntry(); } catch (Exception e) { log.error("", e); @@ -180,7 +184,7 @@ public class QTI21ExportProcessor { metadataBuilder.appendMetadataFrom(qitem, resolvedAssessmentItem, locale); } - public void assembleTest(String title, List<QuestionItemFull> fullItems, File directory) { + public void assembleTest(String title, List<QuestionItemFull> fullItems, boolean groupByTaxonomyLevel, File directory) { try { QtiSerializer qtiSerializer = qtiService.qtiSerializer(); //imsmanifest @@ -200,7 +204,9 @@ public class QTI21ExportProcessor { manifest.appendAssessmentTest(assessmentTestFilename); //make a section - AssessmentSection section = assessmentTest.getTestParts().get(0).getAssessmentSections().get(0); + final TestPart testPart = assessmentTest.getTestParts().get(0); + AssessmentSection defaultSection = testPart.getAssessmentSections().get(0); + Map<String,AssessmentSection> sectionByTitles = new HashMap<>(); //assessment items for(QuestionItemFull qitem:fullItems) { @@ -217,6 +223,12 @@ public class QTI21ExportProcessor { File newItemFile = new File(containerDir, assessmentItem.getIdentifier() + ".xml"); String newItemFilename = container + "/" + newItemFile.getName(); qtiService.persistAssessmentObject(newItemFile, assessmentItem); + + AssessmentSection section = defaultSection; + if(groupByTaxonomyLevel && StringHelper.containsNonWhitespace(qitem.getTaxonomyLevelName())) { + section = sectionByTitles.computeIfAbsent(qitem.getTaxonomyLevelName(), level + -> AssessmentTestFactory.appendAssessmentSection(level, testPart)); + } AssessmentTestFactory.appendAssessmentItem(section, newItemFilename); manifest.appendAssessmentItem(newItemFilename); @@ -242,6 +254,10 @@ public class QTI21ExportProcessor { } } + if(defaultSection.getSectionParts().isEmpty()) { + testPart.getChildAbstractParts().remove(defaultSection); + } + AssessmentTestBuilder assessmentTestBuilder = new AssessmentTestBuilder(assessmentTest); double sumMaxScore = atomicMaxScore.sum(); if(sumMaxScore > 0.0d) { @@ -263,7 +279,6 @@ public class QTI21ExportProcessor { public void assembleTest(List<QuestionItemFull> fullItems, ZipOutputStream zout) { try { - QtiSerializer qtiSerializer = qtiService.qtiSerializer(); //imsmanifest ManifestBuilder manifest = ManifestBuilder.createAssessmentTestBuilder(); @@ -310,14 +325,31 @@ public class QTI21ExportProcessor { } zout.putNextEntry(new ZipEntry(assessmentTestFilename)); - qtiSerializer.serializeJqtiObject(assessmentTest, new ShieldOutputStream(zout)); + serializeAssessmentTest(assessmentTest, zout); zout.closeEntry(); zout.putNextEntry(new ZipEntry("imsmanifest.xml")); - manifest.write(new ShieldOutputStream(zout)); + writeManifest(manifest, zout); zout.closeEntry(); } catch (IOException | URISyntaxException e) { log.error("", e); } } + + private void writeManifest(ManifestBuilder manifest, ZipOutputStream zout) { + try(OutputStream out = new ShieldOutputStream(zout)) { + manifest.write(out); + } catch(IOException e) { + log.error("Cannot write manifest", e); + } + } + + private void serializeAssessmentTest(AssessmentTest assessmentTest, ZipOutputStream zout) { + try(OutputStream out = new ShieldOutputStream(zout)) { + QtiSerializer qtiSerializer = qtiService.qtiSerializer(); + qtiSerializer.serializeJqtiObject(assessmentTest, out); + } catch(IOException e) { + log.error("Cannot write manifest", e); + } + } } diff --git a/src/main/java/org/olat/ims/qti21/pool/QTI21QPoolServiceProvider.java b/src/main/java/org/olat/ims/qti21/pool/QTI21QPoolServiceProvider.java index eaab55e620207ee11d57794bcafc3b7fb24e8971..5cc21534b8e8acf535a06976940d2c800f809465 100644 --- a/src/main/java/org/olat/ims/qti21/pool/QTI21QPoolServiceProvider.java +++ b/src/main/java/org/olat/ims/qti21/pool/QTI21QPoolServiceProvider.java @@ -482,10 +482,10 @@ public class QTI21QPoolServiceProvider implements QPoolSPI { * @param items The list of questions to export * @param locale The language */ - public void exportToEditorPackage(String testTitle, File exportDir, List<QuestionItemShort> items, Locale locale) { + public void exportToEditorPackage(String testTitle, File exportDir, List<QuestionItemShort> items, boolean groupByTaxonomyLevel, Locale locale) { List<QuestionItemFull> fullItems = loadQuestionFullItems(items); QTI21ExportProcessor processor = new QTI21ExportProcessor(qtiService, qpoolFileStorage, locale); - processor.assembleTest(testTitle, fullItems, exportDir); + processor.assembleTest(testTitle, fullItems, groupByTaxonomyLevel, exportDir); } private List<QuestionItemFull> loadQuestionFullItems(List<QuestionItemShort> items) { diff --git a/src/main/java/org/olat/ims/qti21/repository/handlers/QTI21AssessmentTestHandler.java b/src/main/java/org/olat/ims/qti21/repository/handlers/QTI21AssessmentTestHandler.java index 5165706b610794e21b578cccc07c4cc21ef2664d..d5657989d2fe6b4ba9e8f479516ea242a6f90a04 100644 --- a/src/main/java/org/olat/ims/qti21/repository/handlers/QTI21AssessmentTestHandler.java +++ b/src/main/java/org/olat/ims/qti21/repository/handlers/QTI21AssessmentTestHandler.java @@ -167,7 +167,8 @@ public class QTI21AssessmentTestHandler extends FileHandler { } if(createObject instanceof QItemList) { QItemList itemToImport = (QItemList)createObject; - qpoolServiceProvider.exportToEditorPackage(displayname, repositoryDir, itemToImport.getItems(), locale); + qpoolServiceProvider.exportToEditorPackage(displayname, repositoryDir, + itemToImport.getItems(), itemToImport.isGroupByTaxonomyLevel(), locale); } else if(createObject instanceof QTIEditorPackage) { QTIEditorPackage testToConvert = (QTIEditorPackage)createObject; QTI21DeliveryOptions options = qtiService.getDeliveryOptions(re); diff --git a/src/main/java/org/olat/modules/cp/CPAssessmentProvider.java b/src/main/java/org/olat/modules/cp/CPAssessmentProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..c8305fb35eb4a969efde3e336204ae6875754bdc --- /dev/null +++ b/src/main/java/org/olat/modules/cp/CPAssessmentProvider.java @@ -0,0 +1,36 @@ +/** + * <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.cp; + +import org.olat.modules.assessment.model.AssessmentEntryStatus; + +/** + * + * Initial date: 26 Feb 2020<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +public interface CPAssessmentProvider { + + AssessmentEntryStatus onPageVisited(String itemIdentifier); + + AssessmentEntryStatus getStatus(String itemIdentifier); + +} diff --git a/src/main/java/org/olat/modules/cp/CPDisplayController.java b/src/main/java/org/olat/modules/cp/CPDisplayController.java index 584bb7800e1aa3a537a06dd7d76e4f9178687cda..2e1a8eb349060cdfa5b88894888f9a59761f5902 100644 --- a/src/main/java/org/olat/modules/cp/CPDisplayController.java +++ b/src/main/java/org/olat/modules/cp/CPDisplayController.java @@ -35,6 +35,7 @@ import org.olat.core.gui.components.htmlsite.HtmlStaticPageComponent; import org.olat.core.gui.components.htmlsite.NewInlineUriEvent; import org.olat.core.gui.components.link.Link; import org.olat.core.gui.components.link.LinkFactory; +import org.olat.core.gui.components.tree.GenericTreeNode; import org.olat.core.gui.components.tree.MenuTree; import org.olat.core.gui.components.tree.TreeEvent; import org.olat.core.gui.components.tree.TreeNode; @@ -68,6 +69,8 @@ import org.olat.core.util.vfs.VFSItem; import org.olat.core.util.vfs.VFSLeaf; import org.olat.core.util.vfs.VFSMediaResource; import org.olat.course.ICourse; +import org.olat.modules.assessment.model.AssessmentEntryStatus; +import org.olat.modules.cp.CPManifestTreeModel.UserObject; import org.olat.search.SearchModule; import org.olat.search.SearchServiceUIFactory; import org.olat.search.SearchServiceUIFactory.DisplayOption; @@ -101,24 +104,28 @@ public class CPDisplayController extends BasicController implements Activateable private CPSelectPrintPagesController printController; private CloseableModalController printPopup; + + private final CPAssessmentProvider cpAssessmentProvider; @Autowired private SearchModule searchModule; /** * @param ureq - * @param cpRoot * @param showMenu * @param showNavigation Show the next/previous link * @param activateFirstPage * @param identPrefix In a course, set a unique prefix per node, if someone set 2x the same CPs in the course, the node identifiers * of the CP elements must be different but predictable + * @param cpAssessmentProvider + * @param cpRoot */ public CPDisplayController(UserRequest ureq, WindowControl wControl, VFSContainer rootContainer, boolean showMenu, boolean showNavigation, boolean activateFirstPage, boolean showPrint, DeliveryOptions deliveryOptions, String initialUri, OLATResourceable ores, - String identPrefix, boolean randomizeMapper) { + String identPrefix, boolean randomizeMapper, CPAssessmentProvider cpAssessmentProvider) { super(ureq, wControl); this.rootContainer = rootContainer; + this.cpAssessmentProvider = cpAssessmentProvider; // wrapper velocity container for page content myContent = createVelocityContainer("cpcontent"); @@ -146,7 +153,7 @@ public class CPDisplayController extends BasicController implements Activateable } // initialize tree model in any case try { - ctm = new CPManifestTreeModel((VFSLeaf) mani, identPrefix); + ctm = new CPManifestTreeModel((VFSLeaf) mani, identPrefix, cpAssessmentProvider); } catch (IOException e) { showError("error.manifest.corrupted"); return; @@ -203,7 +210,8 @@ public class CPDisplayController extends BasicController implements Activateable } else node = null; } if (node != null) { // node.isAccessible - String nodeUri = (String) node.getUserObject(); + UserObject userObject = (UserObject)node.getUserObject(); + String nodeUri = userObject.getHref(); if (cpContentCtr != null) cpContentCtr.setCurrentURI(nodeUri); if (cpComponent != null) cpComponent.setCurrentURI(nodeUri); if (showMenu) cpTree.setSelectedNodeId(node.getIdent()); @@ -211,13 +219,11 @@ public class CPDisplayController extends BasicController implements Activateable // empty anyway and saves one user click) selNodeId = node.getIdent(); + onPageVisited(node); nodeInfo = LoggingResourceable.wrapCpNode(nodeUri); updateNextPreviousLink(node); - if(node.getUserObject() != null) { - String identifierRes = (String)node.getUserObject(); - OLATResourceable pOres = OresHelper.createOLATResourceableInstanceWithoutCheck("path=" + identifierRes, 0l); - addToHistory(ureq, pOres, null); - } + OLATResourceable pOres = OresHelper.createOLATResourceableInstanceWithoutCheck("path=" + nodeUri, 0l); + addToHistory(ureq, pOres, null); } } else if (initialUri != null) { // set page @@ -234,7 +240,7 @@ public class CPDisplayController extends BasicController implements Activateable } updateNextPreviousLink(newNode); if(newNode.getUserObject() != null) { - String identifierRes = (String)newNode.getUserObject(); + String identifierRes = ((UserObject)newNode.getUserObject()).getHref(); Long id = Long.parseLong(newNode.getIdent()); OLATResourceable pOres = OresHelper.createOLATResourceableInstanceWithoutCheck("path=" + identifierRes, id); addToHistory(ureq, pOres, null); @@ -281,11 +287,6 @@ public class CPDisplayController extends BasicController implements Activateable return cpTree; } - /** - * @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest, - * org.olat.core.gui.components.Component, - * org.olat.core.gui.control.Event) - */ @Override public void event(UserRequest ureq, Component source, Event event) { if (source == cpTree) { @@ -450,7 +451,8 @@ public class CPDisplayController extends BasicController implements Activateable } public void switchToPage(UserRequest ureq, TreeNode tn) { - String identifierRes = (String) tn.getUserObject(); + UserObject userObject = (UserObject)tn.getUserObject(); + String identifierRes = userObject.getHref(); OLATResourceable ores = OresHelper.createOLATResourceableInstanceWithoutCheck("path=" + identifierRes, 0l); addToHistory(ureq, ores, null); @@ -487,13 +489,19 @@ public class CPDisplayController extends BasicController implements Activateable } updateNextPreviousLink(tn); + onPageVisited(tn); + ThreadLocalUserActivityLogger.log(CourseLoggingAction.CP_GET_FILE, getClass(), LoggingResourceable.wrapCpNode(identifierRes)); } - /** - * - * @see org.olat.core.gui.control.DefaultController#doDispose(boolean) - */ + private void onPageVisited(TreeNode treeNode) { + UserObject userObject = (UserObject)treeNode.getUserObject(); + String identifier = userObject.getIdentifier(); + AssessmentEntryStatus status = cpAssessmentProvider.onPageVisited(identifier); + String cssClass = CPManifestTreeModel.getItemCssClass(status); + ((GenericTreeNode)treeNode).setCssClass(cssClass); + } + @Override protected void doDispose() { ThreadLocalUserActivityLogger.log(LearningResourceLoggingAction.LEARNING_RESOURCE_CLOSE, getClass()); diff --git a/src/main/java/org/olat/modules/cp/CPManifestTreeModel.java b/src/main/java/org/olat/modules/cp/CPManifestTreeModel.java index 8b1fbd8b6ae3f8bb78602039697fb2a66adfd9b9..e939daa7d058eb45e940931fa0094359072d3c1a 100644 --- a/src/main/java/org/olat/modules/cp/CPManifestTreeModel.java +++ b/src/main/java/org/olat/modules/cp/CPManifestTreeModel.java @@ -50,6 +50,7 @@ import org.olat.core.util.Encoder; import org.olat.core.util.vfs.VFSLeaf; import org.olat.core.util.xml.XMLParser; import org.olat.ims.resources.IMSEntityResolver; +import org.olat.modules.assessment.model.AssessmentEntryStatus; /** * Description:<br> @@ -65,19 +66,23 @@ public class CPManifestTreeModel extends GenericTreeModel { private final List<TreeNode> treeNodes = new ArrayList<>(); private final String identPrefix; private final Logger log = Tracing.createLoggerFor(this.getClass()); + private final CPAssessmentProvider cpAssessmentProvider; /** * Constructor of the content packaging tree model * @param manifest the imsmanifest.xml file + * @param cpAssessmentProvider */ - CPManifestTreeModel(VFSLeaf manifest, String identPrefix) throws IOException { + CPManifestTreeModel(VFSLeaf manifest, String identPrefix, CPAssessmentProvider cpAssessmentProvider) throws IOException { this.identPrefix = identPrefix; + this.cpAssessmentProvider = cpAssessmentProvider; Document doc = loadDocument(manifest); initDocument(doc); } CPManifestTreeModel(String manifest, String identPrefix) throws IOException { this.identPrefix = identPrefix; + this.cpAssessmentProvider = DryRunAssessmentProvider.create(); Document doc = loadDocument(manifest); initDocument(doc); } @@ -195,7 +200,7 @@ public class CPManifestTreeModel extends GenericTreeModel { XPath meta = rootElement.createXPath("//ns:resource[@identifier='" + identifierref + "']"); meta.setNamespaceURIs(nsuris); gtn.setAccessible(true); - gtn.setUserObject(href); + gtn.setUserObject(new UserObject(identifier, href)); if (hrefToTreeNode.containsKey(href)){ log.debug("Duplicate href::" + href + " for identifierref::" + identifierref + " and identifier::" + identifier + ", use first one"); } else { @@ -207,6 +212,7 @@ public class CPManifestTreeModel extends GenericTreeModel { } } else if (item.getName().equals("item")) { gtn.setIconCssClass("o_cp_item"); + gtn.setCssClass(getItemCssClass(identifier)); //set resolved file path directly String identifierref = item.attributeValue("identifierref"); if(identifierref != null) { @@ -216,7 +222,7 @@ public class CPManifestTreeModel extends GenericTreeModel { meta.setNamespaceURIs(nsuris); String href = resources.get(identifierref); if (href != null) { - gtn.setUserObject(href); + gtn.setUserObject(new UserObject(identifier, href)); // allow lookup of a treenode given a href so we can quickly adjust the menu if the user clicks on hyperlinks within the text if (hrefToTreeNode.containsKey(href)){ log.debug("Duplicate href::" + href + " for identifierref::" + identifierref + " and identifier::" + identifier + ", use first one"); @@ -257,6 +263,18 @@ public class CPManifestTreeModel extends GenericTreeModel { return gtn; } + private String getItemCssClass(String identifier) { + AssessmentEntryStatus status = cpAssessmentProvider.getStatus(identifier); + return getItemCssClass(status); + } + + public static String getItemCssClass(AssessmentEntryStatus status) { + if (AssessmentEntryStatus.done.equals(status)) { + return "o_lp_done o_lp_not_in_sequence o_lp_contains_no_sequence"; + } + return "o_lp_ready o_lp_not_in_sequence o_lp_contains_no_sequence"; + } + private Document loadDocument(VFSLeaf documentF) throws IOException { InputStream in = null; Document doc = null; @@ -294,4 +312,24 @@ public class CPManifestTreeModel extends GenericTreeModel { } return doc; } + + public static final class UserObject { + + private final String identifier; + private final String href; + + public UserObject(String identifier, String href) { + this.identifier = identifier; + this.href = href; + } + + public String getIdentifier() { + return identifier; + } + + public String getHref() { + return href; + } + + } } diff --git a/src/main/java/org/olat/modules/cp/CPOfflineReadableManager.java b/src/main/java/org/olat/modules/cp/CPOfflineReadableManager.java index 62be000b01ce0b7ec99e26d1fb2c8607f44ce786..ebf4d8c09cf9f38835d05f9cdb66396ebc8d12a2 100644 --- a/src/main/java/org/olat/modules/cp/CPOfflineReadableManager.java +++ b/src/main/java/org/olat/modules/cp/CPOfflineReadableManager.java @@ -54,6 +54,7 @@ import org.olat.core.util.WebappHelper; import org.olat.core.util.ZipUtil; import org.olat.core.util.vfs.LocalFileImpl; import org.olat.fileresource.FileResourceManager; +import org.olat.modules.cp.CPManifestTreeModel.UserObject; /** * Description: <br> @@ -189,7 +190,7 @@ public class CPOfflineReadableManager { /* first, we do the menu-tree */ File mani = new File(unzippedDir, FILENAME_IMSMANIFEST); LocalFileImpl vfsMani = new LocalFileImpl(mani); - CPManifestTreeModel ctm = new CPManifestTreeModel(vfsMani, ""); + CPManifestTreeModel ctm = new CPManifestTreeModel(vfsMani, "", DryRunAssessmentProvider.create()); TreeNode root = ctm.getRootNode(); // let's take the rootnode title as page title this.rootTitle = root.getTitle(); @@ -260,7 +261,7 @@ public class CPOfflineReadableManager { // available // render current node - String nodeUri = (String) node.getUserObject(); + String nodeUri = node.getUserObject() != null? ((UserObject)node.getUserObject()).getHref(): null; String title = node.getTitle(); String altText = node.getAltText(); diff --git a/src/main/java/org/olat/modules/cp/CPPrintMapper.java b/src/main/java/org/olat/modules/cp/CPPrintMapper.java index d629ab64945022993e6e0a27927836a92cf0df4f..b5894d01ad48e9ccfc4abf168d5a4d6104e27287 100644 --- a/src/main/java/org/olat/modules/cp/CPPrintMapper.java +++ b/src/main/java/org/olat/modules/cp/CPPrintMapper.java @@ -48,6 +48,7 @@ import org.olat.core.util.vfs.VFSContainer; import org.olat.core.util.vfs.VFSItem; import org.olat.core.util.vfs.VFSLeaf; import org.olat.core.util.vfs.VFSMediaResource; +import org.olat.modules.cp.CPManifestTreeModel.UserObject; import org.xml.sax.InputSource; import nu.validator.htmlparser.common.XmlViolationPolicy; @@ -180,7 +181,7 @@ public class CPPrintMapper implements Mapper { for(String nodeId:nodeIds) { HtmlPageHandler parsedPage = null; TreeNode treeNode = ctm.getNodeById(nodeId); - String identifierRes = (String)treeNode.getUserObject(); + String identifierRes = ((UserObject)treeNode.getUserObject()).getHref(); if(StringHelper.containsNonWhitespace(identifierRes)) { VFSItem currentItem = rootDir.resolve(identifierRes); if(currentItem instanceof VFSLeaf) { diff --git a/src/main/java/org/olat/modules/cp/CPUIFactory.java b/src/main/java/org/olat/modules/cp/CPUIFactory.java index 75be2d650447d0c61b3092648348fee60df2ef11..dd561f7d5d896b12149ec5eeea074124db547754 100644 --- a/src/main/java/org/olat/modules/cp/CPUIFactory.java +++ b/src/main/java/org/olat/modules/cp/CPUIFactory.java @@ -69,13 +69,14 @@ public class CPUIFactory { * @param activateFirstPage true to automatically activate the first node with * content * @param initialUri can be NULL, will use first page then + * @param cpAssessmentProvider * @return a CPDisplayController */ public CPDisplayController createContentOnlyCPDisplayController(UserRequest ureq, WindowControl wControl, VFSContainer rootContainer, boolean activateFirstPage, boolean showNavigation, DeliveryOptions deliveryOptions, - String initialUri, OLATResourceable ores, String identPrefix, boolean preview) { + String initialUri, OLATResourceable ores, String identPrefix, boolean preview, CPAssessmentProvider cpAssessmentProvider) { return new CPDisplayController(ureq, wControl, rootContainer, false, showNavigation, activateFirstPage, true, deliveryOptions, - initialUri, ores, identPrefix, preview); + initialUri, ores, identPrefix, preview, cpAssessmentProvider); } /** @@ -89,11 +90,13 @@ public class CPUIFactory { * @param wControl * @param rootContainer The VFS root container where the CP is found on disk * @param showMenu true to display the menu, false to hide the menu + * @param cpAssessmentProvider * @return A main layout controller */ public MainLayout3ColumnsController createMainLayoutController(UserRequest ureq, WindowControl wControl, VFSContainer rootContainer, - boolean showMenu, DeliveryOptions deliveryOptions) { - CPDisplayController cpCtr = new CPDisplayController(ureq, wControl, rootContainer, showMenu, true, true, true, deliveryOptions, null, null, "", false); + boolean showMenu, DeliveryOptions deliveryOptions, CPAssessmentProvider cpAssessmentProvider) { + CPDisplayController cpCtr = new CPDisplayController(ureq, wControl, rootContainer, showMenu, true, true, true, + deliveryOptions, null, null, "", false, cpAssessmentProvider); MainLayout3ColumnsController layoutCtr = new LayoutMain3ColsController(ureq, wControl, cpCtr.getMenuComponent(), cpCtr.getInitialComponent(), rootContainer.getName()); layoutCtr.addDisposableChildController(cpCtr); // cascade disposing requests return layoutCtr; @@ -114,7 +117,8 @@ public class CPUIFactory { */ public LayoutMain3ColsPreviewController createMainLayoutPreviewController(UserRequest ureq, WindowControl wControl, VFSContainer rootContainer, boolean showMenu, DeliveryOptions deliveryOptions) { - CPDisplayController cpCtr = new CPDisplayController(ureq, wControl, rootContainer, showMenu, true, true, true, deliveryOptions, null, null, "", false); + CPDisplayController cpCtr = new CPDisplayController(ureq, wControl, rootContainer, showMenu, true, true, true, + deliveryOptions, null, null, "", false, DryRunAssessmentProvider.create()); LayoutMain3ColsPreviewController layoutCtr = new LayoutMain3ColsPreviewController(ureq, wControl, cpCtr.getMenuComponent(), cpCtr.getInitialComponent(), rootContainer.getName()); layoutCtr.addDisposableChildController(cpCtr); // cascade disposing requests return layoutCtr; @@ -135,7 +139,8 @@ public class CPUIFactory { */ public LayoutMain3ColsController createMainLayoutPreviewController_v2(UserRequest ureq, WindowControl wControl, VFSContainer rootContainer, boolean showMenu, DeliveryOptions deliveryOptions) { - CPDisplayController cpCtr = new CPDisplayController(ureq, wControl, rootContainer, showMenu, true, true, true, deliveryOptions, null, null, "", false); + CPDisplayController cpCtr = new CPDisplayController(ureq, wControl, rootContainer, showMenu, true, true, true, + deliveryOptions, null, null, "", false, DryRunAssessmentProvider.create()); LayoutMain3ColsController layoutCtr = new LayoutMain3ColsController(ureq, wControl, cpCtr.getMenuComponent(), cpCtr.getInitialComponent(), rootContainer.getName()); layoutCtr.addDisposableChildController(cpCtr); // cascade disposing requests layoutCtr.addCssClassToMain("o_preview"); diff --git a/src/main/java/org/olat/modules/cp/DryRunAssessmentProvider.java b/src/main/java/org/olat/modules/cp/DryRunAssessmentProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..46eb53be33d31851aee827a2794f5855c7108a70 --- /dev/null +++ b/src/main/java/org/olat/modules/cp/DryRunAssessmentProvider.java @@ -0,0 +1,52 @@ +/** + * <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.cp; + +import org.olat.modules.assessment.model.AssessmentEntryStatus; + +/** + * + * Initial date: 26 Feb 2020<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +public class DryRunAssessmentProvider implements CPAssessmentProvider { + + private static final CPAssessmentProvider INSTANCE = new DryRunAssessmentProvider(); + + public static final CPAssessmentProvider create() { + return INSTANCE; + } + + private DryRunAssessmentProvider() { + // + } + + @Override + public AssessmentEntryStatus onPageVisited(String itemIdentifier) { + return null; + } + + @Override + public AssessmentEntryStatus getStatus(String itemIdentifier) { + return null; + } + +} diff --git a/src/main/java/org/olat/modules/cp/PersistingAssessmentProvider.java b/src/main/java/org/olat/modules/cp/PersistingAssessmentProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..af87aad28f7cea642fd84eac80484ea2ee8fe4e6 --- /dev/null +++ b/src/main/java/org/olat/modules/cp/PersistingAssessmentProvider.java @@ -0,0 +1,76 @@ +/** + * <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.cp; + +import java.util.Map; +import java.util.stream.Collectors; + +import org.olat.core.CoreSpringFactory; +import org.olat.core.id.Identity; +import org.olat.modules.assessment.AssessmentEntry; +import org.olat.modules.assessment.AssessmentService; +import org.olat.modules.assessment.model.AssessmentEntryStatus; +import org.olat.repository.RepositoryEntry; + +/** + * + * Initial date: 26 Feb 2020<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +public class PersistingAssessmentProvider implements CPAssessmentProvider { + + private final Identity identity; + private final RepositoryEntry cpEntry; + + private Map<String, AssessmentEntryStatus> identifierToStatus; + + private AssessmentService assessmentService; + + public static final CPAssessmentProvider create(RepositoryEntry cpEntry, Identity identity) { + return new PersistingAssessmentProvider(cpEntry, identity); + } + + private PersistingAssessmentProvider(RepositoryEntry cpEntry, Identity identity) { + this.identity = identity; + this.cpEntry = cpEntry; + this.assessmentService = CoreSpringFactory.getImpl(AssessmentService.class); + this.identifierToStatus = assessmentService.loadAssessmentEntriesByAssessedIdentity(identity, cpEntry).stream() + .filter(ae -> ae.getSubIdent() != null && ae.getAssessmentStatus() != null) + .collect(Collectors.toMap(AssessmentEntry::getSubIdent, AssessmentEntry::getAssessmentStatus)); + } + + @Override + public AssessmentEntryStatus onPageVisited(String itemIdentifier) { + AssessmentEntry assessmentEntry = assessmentService.getOrCreateAssessmentEntry(identity, null, cpEntry, itemIdentifier, false, null); + if (!AssessmentEntryStatus.done.equals(assessmentEntry.getAssessmentStatus())) { + assessmentEntry.setAssessmentStatus(AssessmentEntryStatus.done); + assessmentService.updateAssessmentEntry(assessmentEntry); + identifierToStatus.put(itemIdentifier, assessmentEntry.getAssessmentStatus()); + } + return assessmentEntry.getAssessmentStatus(); + } + + @Override + public AssessmentEntryStatus getStatus(String itemIdentifier) { + return identifierToStatus.get(itemIdentifier); + } + +} diff --git a/src/main/java/org/olat/modules/qpool/model/QItemList.java b/src/main/java/org/olat/modules/qpool/model/QItemList.java index 921a8d107ce10a8167298151876d477375577cba..0976f861508784b0c0d04ad624645c366431fb1a 100644 --- a/src/main/java/org/olat/modules/qpool/model/QItemList.java +++ b/src/main/java/org/olat/modules/qpool/model/QItemList.java @@ -31,22 +31,20 @@ import org.olat.modules.qpool.QuestionItemShort; */ public class QItemList { - private List<QuestionItemShort> items; + private final boolean groupByTaxonomyLevel; + private final List<QuestionItemShort> items; - public QItemList() { - // - } - - public QItemList(List<QuestionItemShort> items) { + public QItemList(List<QuestionItemShort> items, boolean groupByTaxonomyLevel) { this.items = items; + this.groupByTaxonomyLevel = groupByTaxonomyLevel; } - public List<QuestionItemShort> getItems() { - return items; + public boolean isGroupByTaxonomyLevel() { + return groupByTaxonomyLevel; } - public void setItems(List<QuestionItemShort> items) { - this.items = items; + public List<QuestionItemShort> getItems() { + return items; } } diff --git a/src/main/java/org/olat/modules/qpool/ui/CreateTestOverviewController.java b/src/main/java/org/olat/modules/qpool/ui/CreateTestOverviewController.java index 7144d4f8c1d0a368861ea85cd960d1e5f36a9bf6..e09a8e7776b3ee0c632f662b246f771ca4df8ca2 100644 --- a/src/main/java/org/olat/modules/qpool/ui/CreateTestOverviewController.java +++ b/src/main/java/org/olat/modules/qpool/ui/CreateTestOverviewController.java @@ -36,13 +36,14 @@ import org.olat.core.commons.services.license.ResourceLicense; import org.olat.core.commons.services.license.ui.LicenseUIFactory; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement; import org.olat.core.gui.components.form.flexible.impl.FormBasicController; import org.olat.core.gui.components.form.flexible.impl.elements.table.BooleanCellRenderer; import org.olat.core.gui.components.form.flexible.impl.elements.table.CSSIconFlexiCellRenderer; import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiColumnModel; import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiTableDataModel; -import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiColumnDef; import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiColumnModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiSortableColumnDef; import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel; import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableDataModelFactory; import org.olat.core.gui.control.Controller; @@ -52,8 +53,10 @@ import org.olat.core.util.Formatter; import org.olat.core.util.StringHelper; import org.olat.modules.qpool.ExportFormatOptions; import org.olat.modules.qpool.QPoolSPI; +import org.olat.modules.qpool.QPoolSecurityCallback; import org.olat.modules.qpool.QuestionItemShort; import org.olat.modules.qpool.QuestionPoolModule; +import org.olat.modules.qpool.QuestionStatus; import org.olat.modules.qpool.manager.QuestionPoolLicenseHandler; import org.springframework.beans.factory.annotation.Autowired; @@ -64,12 +67,15 @@ import org.springframework.beans.factory.annotation.Autowired; * */ public class CreateTestOverviewController extends FormBasicController { + + private static final String[] groupByKeys = new String[] { "on" }; private final boolean withLicenses; + private final boolean withTaxonomy; private final ExportFormatOptions format; - private QItemDataModel itemsModel; - + private QItemDataModel itemsModel; + private MultipleSelectionElement groupByEl; @Autowired private LicenseModule licenseModule; @@ -78,11 +84,11 @@ public class CreateTestOverviewController extends FormBasicController { @Autowired private QuestionPoolLicenseHandler licenseHandler; - public CreateTestOverviewController(UserRequest ureq, WindowControl wControl, List<QuestionItemShort> items, - ExportFormatOptions format) { + ExportFormatOptions format, QPoolSecurityCallback secCallback) { super(ureq, wControl, "create_test"); this.format = format; + withTaxonomy = secCallback.canUseTaxonomy(); withLicenses = licenseModule.isEnabled(licenseHandler); initForm(ureq); loadModel(items); @@ -98,15 +104,26 @@ public class CreateTestOverviewController extends FormBasicController { new CSSIconFlexiCellRenderer("o_icon_failed")) )); columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Cols.title)); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Cols.topic)); + if(withTaxonomy) { + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Cols.taxonomyLevel)); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(false, Cols.taxonomyPath)); + } + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Cols.type)); columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Cols.format)); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Cols.status, new QuestionStatusCellRenderer())); if(withLicenses) { columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(Cols.license)); } itemsModel = new QItemDataModel(columnsModel, format, getLocale()); uifactory.addTableElement(getWindowControl(), "shares", itemsModel, getTranslator(), formLayout); - uifactory.addFormSubmitButton("create.test", formLayout); + String[] groupByValues = new String[] { translate("group.by.taxonomy.level") }; + groupByEl = uifactory.addCheckboxesHorizontal("group.by", null, formLayout, groupByKeys, groupByValues); + groupByEl.setVisible(withTaxonomy); + uifactory.addFormCancelButton("cancel", formLayout, ureq, getWindowControl()); + uifactory.addFormSubmitButton("create.test", formLayout); } private void loadModel(List<QuestionItemShort> items) { @@ -156,6 +173,10 @@ public class CreateTestOverviewController extends FormBasicController { } return exportableItems; } + + public boolean isGroupByTaxonomyLevel() { + return groupByEl.isVisible() && groupByEl.isAtLeastSelected(1); + } @Override protected void formOK(UserRequest ureq) { @@ -218,6 +239,18 @@ public class CreateTestOverviewController extends FormBasicController { return question.getTitle(); } + public String getTopic() { + return question.getTopic(); + } + + public String getTaxonomyLevelName() { + return question.getTaxonomyLevelName(); + } + + public String getTaxonomyPath() { + return question.getTaxonomicPath(); + } + public String getFormat() { return question.getFormat(); } @@ -225,6 +258,18 @@ public class CreateTestOverviewController extends FormBasicController { public ResourceLicense getLicense() { return license; } + + public QuestionStatus getQuestionStatus() { + return question.getQuestionStatus(); + } + + public String getItemType() { + String type = question.getItemType(); + if(type == null) { + return ""; + } + return type; + } public QuestionItemShort getQuestion() { return question; @@ -261,7 +306,12 @@ public class CreateTestOverviewController extends FormBasicController { return Boolean.FALSE; } case title: return share.getTitle(); + case topic: return share.getTopic(); + case taxonomyLevel: return share.getTaxonomyLevelName(); + case taxonomyPath: return share.getTaxonomyPath(); case format: return share.getFormat(); + case type: return share.getItemType(); + case status: return share.getQuestionStatus(); case license: return shortenedLicense(share); default : return share; } @@ -286,10 +336,15 @@ public class CreateTestOverviewController extends FormBasicController { } } - private enum Cols implements FlexiColumnDef { + private enum Cols implements FlexiSortableColumnDef { accept("export.overview.accept"), title("general.title"), + topic("general.topic"), + taxonomyLevel("classification.taxonomy.level"), + taxonomyPath("classification.taxonomic.path"), format("technical.format"), + status("lifecycle.status"), + type("question.type"), license("rights.license"); private final String i18nKey; @@ -302,5 +357,15 @@ public class CreateTestOverviewController extends FormBasicController { public String i18nHeaderKey() { return i18nKey; } + + @Override + public boolean sortable() { + return false; + } + + @Override + public String sortKey() { + return name(); + } } } diff --git a/src/main/java/org/olat/modules/qpool/ui/QuestionListController.java b/src/main/java/org/olat/modules/qpool/ui/QuestionListController.java index 813f607bac57a6dd1aae31da97c8ef2520986ad3..4204cbd40b3e4fe898b68710044cb0b87f26c159 100644 --- a/src/main/java/org/olat/modules/qpool/ui/QuestionListController.java +++ b/src/main/java/org/olat/modules/qpool/ui/QuestionListController.java @@ -520,10 +520,11 @@ public class QuestionListController extends AbstractItemListController implement List<QuestionItemShort> items = createTestOverviewCtrl.getExportableQuestionItems(); String typeFormat = createTestOverviewCtrl.getResourceTypeFormat(); LicenseType licenseType = createTestOverviewCtrl.getLicenseType(); + boolean groupBy = createTestOverviewCtrl.isGroupByTaxonomyLevel(); cmc.deactivate(); cleanUp(); if (event == Event.DONE_EVENT) { - doOpenCreateRepositoryTest(ureq, items, typeFormat, licenseType); + doOpenCreateRepositoryTest(ureq, items, typeFormat, licenseType, groupBy); } } else if(source == exportWizard) { if(event == Event.CANCELLED_EVENT || event == Event.DONE_EVENT || event == Event.CHANGED_EVENT) { @@ -1042,7 +1043,7 @@ public class QuestionListController extends AbstractItemListController implement private void doShowCreateTestOverview(UserRequest ureq, List<QuestionItemShort> items, ExportFormatOptions format) { removeAsListenerAndDispose(createTestOverviewCtrl); - createTestOverviewCtrl = new CreateTestOverviewController(ureq, getWindowControl(), items, format); + createTestOverviewCtrl = new CreateTestOverviewController(ureq, getWindowControl(), items, format, getSecurityCallback()); listenTo(createTestOverviewCtrl); removeAsListenerAndDispose(cmc); @@ -1052,13 +1053,13 @@ public class QuestionListController extends AbstractItemListController implement listenTo(cmc); } - private void doOpenCreateRepositoryTest(UserRequest ureq, List<QuestionItemShort> items, String type, LicenseType licenseType) { + private void doOpenCreateRepositoryTest(UserRequest ureq, List<QuestionItemShort> items, String type, LicenseType licenseType, boolean groupBy) { removeAsListenerAndDispose(cmc); removeAsListenerAndDispose(addController); RepositoryHandler handler = repositoryHandlerFactory.getRepositoryHandler(type); addController = handler.createCreateRepositoryEntryController(ureq, getWindowControl()); - addController.setCreateObject(new QItemList(items)); + addController.setCreateObject(new QItemList(items, groupBy)); addController.setLicenseType(licenseType); listenTo(addController); cmc = new CloseableModalController(getWindowControl(), translate("close"), addController.getInitialComponent()); diff --git a/src/main/java/org/olat/modules/qpool/ui/_content/create_test.html b/src/main/java/org/olat/modules/qpool/ui/_content/create_test.html index 5e3ebd6d6bd7cd29c81011aef16e846180caae48..c0a5f52c911a0ec8317549026b105ba5334a58e5 100644 --- a/src/main/java/org/olat/modules/qpool/ui/_content/create_test.html +++ b/src/main/java/org/olat/modules/qpool/ui/_content/create_test.html @@ -3,6 +3,9 @@ $r.translate("warning.different.licenses")</div> #end $r.render("shares") +#if($r.visible("group.by")) + $r.render("group.by") +#end <div class="o_button_group"> $r.render("cancel") $r.render("create.test") diff --git a/src/main/java/org/olat/modules/qpool/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/modules/qpool/ui/_i18n/LocalStrings_de.properties index 0960dec93169ee4fa74824082e8df62252da6d82..4cba538728e884aac7e2c49dc7c1d7cb9e8e1dc9 100644 --- a/src/main/java/org/olat/modules/qpool/ui/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/modules/qpool/ui/_i18n/LocalStrings_de.properties @@ -115,6 +115,7 @@ general.taxonomy.level=Fachbereich general.taxonomy.path={0} general.title=Titel general.topic=Thema +group.by.taxonomy.level=Fragen nach Fachbereich gruppieren import.excellike.12=QTI 1.2 Excelimport \u00FCber Copy&Paste import.excellike.21=QTI 2.1 Excelimport \u00FCber Copy&Paste import.failed=Frage konnte nicht importiert werden. diff --git a/src/main/java/org/olat/modules/qpool/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/modules/qpool/ui/_i18n/LocalStrings_en.properties index 11f9173265eb379b361c526cd3a315557bc7754a..f514b7bec39f10095e546d8bb10222334f9ed640 100644 --- a/src/main/java/org/olat/modules/qpool/ui/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/modules/qpool/ui/_i18n/LocalStrings_en.properties @@ -115,6 +115,7 @@ general.taxonomy.level=Subject general.taxonomy.path={0} general.title=Title general.topic=Topic +group.by.taxonomy.level=Group items by subject import.excellike.12=QTI 1.2 Excel import via copy&paste import.excellike.21=QTI 2.1 Excel import via copy&paste import.failed=No questions were imported diff --git a/src/main/java/org/olat/modules/wiki/DryRunAssessmentProvider.java b/src/main/java/org/olat/modules/wiki/DryRunAssessmentProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..9f4c0b046319364dbb848c99a3e441b83f7f9876 --- /dev/null +++ b/src/main/java/org/olat/modules/wiki/DryRunAssessmentProvider.java @@ -0,0 +1,52 @@ +/** + * <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.wiki; + +import org.olat.modules.assessment.model.AssessmentEntryStatus; + +/** + * + * Initial date: 2 Mar 2020<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +public class DryRunAssessmentProvider implements WikiAssessmentProvider { + + private static final WikiAssessmentProvider INSTANCE = new DryRunAssessmentProvider(); + + public static final WikiAssessmentProvider create() { + return INSTANCE; + } + + private DryRunAssessmentProvider() { + // + } + + @Override + public void setStatusDone(String pageId) { + // + } + + @Override + public AssessmentEntryStatus getStatus(String pageId) { + return null; + } + +} diff --git a/src/main/java/org/olat/modules/wiki/PersistingAssessmentProvider.java b/src/main/java/org/olat/modules/wiki/PersistingAssessmentProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..8330bdef120a97690ac280fc7b1543da4f399f4f --- /dev/null +++ b/src/main/java/org/olat/modules/wiki/PersistingAssessmentProvider.java @@ -0,0 +1,75 @@ +/** + * <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.wiki; + +import java.util.Map; +import java.util.stream.Collectors; + +import org.olat.core.CoreSpringFactory; +import org.olat.core.id.Identity; +import org.olat.modules.assessment.AssessmentEntry; +import org.olat.modules.assessment.AssessmentService; +import org.olat.modules.assessment.model.AssessmentEntryStatus; +import org.olat.repository.RepositoryEntry; + +/** + * + * Initial date: 28 Feb 2020<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +public class PersistingAssessmentProvider implements WikiAssessmentProvider { + + private final Identity identity; + private final RepositoryEntry wikiEntry; + + private Map<String, AssessmentEntryStatus> pageIdToStatus; + + private AssessmentService assessmentService; + + public static final WikiAssessmentProvider create(RepositoryEntry wikiEntry, Identity identity) { + return new PersistingAssessmentProvider(wikiEntry, identity); + } + + private PersistingAssessmentProvider(RepositoryEntry wikiEntry, Identity identity) { + this.identity = identity; + this.wikiEntry = wikiEntry; + this.assessmentService = CoreSpringFactory.getImpl(AssessmentService.class); + this.pageIdToStatus = assessmentService.loadAssessmentEntriesByAssessedIdentity(identity, wikiEntry).stream() + .filter(ae -> ae.getSubIdent() != null && ae.getAssessmentStatus() != null) + .collect(Collectors.toMap(AssessmentEntry::getSubIdent, AssessmentEntry::getAssessmentStatus)); + } + + @Override + public void setStatusDone(String pageId) { + AssessmentEntry assessmentEntry = assessmentService.getOrCreateAssessmentEntry(identity, null, wikiEntry, pageId, false, null); + if (!AssessmentEntryStatus.done.equals(assessmentEntry.getAssessmentStatus())) { + assessmentEntry.setAssessmentStatus(AssessmentEntryStatus.done); + assessmentService.updateAssessmentEntry(assessmentEntry); + pageIdToStatus.put(pageId, assessmentEntry.getAssessmentStatus()); + } + } + + @Override + public AssessmentEntryStatus getStatus(String pageId) { + return pageIdToStatus.get(pageId); + } + +} diff --git a/src/main/java/org/olat/modules/wiki/Wiki.java b/src/main/java/org/olat/modules/wiki/Wiki.java index 684fb357496b3cac258fe969de1aea7e737df916..d35af5ae3a0087099abaedca474c7544e7efb34a 100644 --- a/src/main/java/org/olat/modules/wiki/Wiki.java +++ b/src/main/java/org/olat/modules/wiki/Wiki.java @@ -38,12 +38,13 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; +import java.util.function.Predicate; +import org.apache.logging.log4j.Logger; import org.jamwiki.utils.Utilities; import org.olat.core.CoreSpringFactory; import org.olat.core.logging.AssertException; import org.olat.core.logging.OLATRuntimeException; -import org.apache.logging.log4j.Logger; import org.olat.core.logging.Tracing; import org.olat.core.util.FileUtils; import org.olat.core.util.Formatter; @@ -378,74 +379,49 @@ public class Wiki implements WikiContainer, Serializable { return page; } - /** - * @see org.olat.core.commons.modules.wiki.WikiContainer#generatePageId(java.lang.String) - */ @Override public String generatePageId(String pageName) { if(log.isDebugEnabled()) log.debug("Generating page id from page name: "+pageName +" to id: "+WikiManager.generatePageId(pageName)); return WikiManager.generatePageId(pageName); } - /** - * @return a List of all pages in a wiki ordered by date - */ - protected List<WikiPage> getPagesByDate() { - ArrayList<WikiPage> pages = new ArrayList<>(wikiPages.values()); - Collections.sort(pages, WikiPageSort.MODTIME_ORDER); - return pages; + public List<WikiPage> getAllPages() { + return new ArrayList<>(wikiPages.values()); } - - /** - * @return a List containing all pages names of the wiki sorted alphabetically - */ - protected List<String> getListOfAllPageNames() { - ArrayList<WikiPage> pages = new ArrayList<>(wikiPages.values()); - ArrayList<String> pageNames = new ArrayList<>(pages.size()); - Collections.sort(pages, WikiPageSort.PAGENAME_ORDER); - for (Iterator<WikiPage> iter = pages.iterator(); iter.hasNext();) { - WikiPage page = iter.next(); - if (!page.getPageName().startsWith("O_")) { - pageNames.add(page.getPageName()); - } - } - return pageNames; + + public final static Predicate<WikiPage> REGULAR_PAGE_FILTER = + page -> !page.getPageName().startsWith("O_"); + + public List<WikiPage> getAllPagesWithContent() { + return getAllPagesWithContent(false); } - - /** - * - * @return a List of all pages in a wiki - */ - public List<WikiPage> getAllPagesWithContent() { - return getAllPagesWithContent(false); - } - - public List<WikiPage> getAllPagesWithContent(boolean includeSpecialPages) { - ArrayList<WikiPage> pages = new ArrayList<>(); - for (Iterator<String> keyes = wikiPages.keySet().iterator(); keyes.hasNext();) { - String pageId = keyes.next(); - WikiPage wikiPage = getPage(pageId); - // check if the page is a content page - if (includeSpecialPages) { + + public List<WikiPage> getAllPagesWithContent(boolean includeSpecialPages) { + ArrayList<WikiPage> pages = new ArrayList<>(); + for (Iterator<String> keyes = wikiPages.keySet().iterator(); keyes.hasNext();) { + String pageId = keyes.next(); + WikiPage wikiPage = getPage(pageId); + // check if the page is a content page + if (includeSpecialPages) { + if (wikiPage.getContent().equals("") ) { + // wikiPage has empty content => try to load content + if (!wikiPage.getPageName().startsWith("O_")) { + wikiPage = getPage(pageId, true); + } + } + pages.add(wikiPage); + } else { + if (!wikiPage.getPageName().startsWith("O_")) { if (wikiPage.getContent().equals("") ) { // wikiPage has empty content => try to load content - if (!wikiPage.getPageName().startsWith("O_")) { - wikiPage = getPage(pageId, true); - } - } - pages.add(wikiPage); - } else { - if (!wikiPage.getPageName().startsWith("O_")) { - if (wikiPage.getContent().equals("") ) { - // wikiPage has empty content => try to load content - wikiPage = getPage(pageId, true); - } - pages.add(wikiPage); + wikiPage = getPage(pageId, true); } + pages.add(wikiPage); } } - return pages; } + return pages; + } /** * FIXME:gs increase performance diff --git a/src/main/java/org/olat/modules/wiki/WikiAssessmentProvider.java b/src/main/java/org/olat/modules/wiki/WikiAssessmentProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..0457946b328eb9670217fde263c104dd05111cd0 --- /dev/null +++ b/src/main/java/org/olat/modules/wiki/WikiAssessmentProvider.java @@ -0,0 +1,36 @@ +/** + * <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.wiki; + +import org.olat.modules.assessment.model.AssessmentEntryStatus; + +/** + * + * Initial date: 28 Feb 2020<br> + * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com + * + */ +public interface WikiAssessmentProvider { + + void setStatusDone(String pageId); + + public AssessmentEntryStatus getStatus(String pageId); + +} diff --git a/src/main/java/org/olat/modules/wiki/WikiMainController.java b/src/main/java/org/olat/modules/wiki/WikiMainController.java index e1984250c98d79c50cb9dd43c52e51a195b5ac47..756fb037b51ae32d7d787d74e53e389ecace59a6 100644 --- a/src/main/java/org/olat/modules/wiki/WikiMainController.java +++ b/src/main/java/org/olat/modules/wiki/WikiMainController.java @@ -28,14 +28,15 @@ package org.olat.modules.wiki; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; +import java.util.stream.Collectors; +import org.apache.logging.log4j.Logger; import org.olat.core.CoreSpringFactory; import org.olat.core.commons.modules.bc.FolderEvent; import org.olat.core.commons.services.notifications.NotificationsManager; @@ -78,7 +79,6 @@ import org.olat.core.id.context.BusinessControlFactory; import org.olat.core.id.context.ContextEntry; import org.olat.core.id.context.StateEntry; import org.olat.core.logging.OLATRuntimeException; -import org.apache.logging.log4j.Logger; import org.olat.core.logging.Tracing; import org.olat.core.logging.activity.LearningResourceLoggingAction; import org.olat.core.logging.activity.OlatResourceableType; @@ -92,6 +92,7 @@ import org.olat.core.util.vfs.VFSContainer; import org.olat.core.util.vfs.VFSItem; import org.olat.core.util.vfs.VFSLeaf; import org.olat.core.util.vfs.VFSMediaResource; +import org.olat.modules.assessment.model.AssessmentEntryStatus; import org.olat.modules.fo.Forum; import org.olat.modules.fo.ForumCallback; import org.olat.modules.fo.manager.ForumManager; @@ -167,6 +168,7 @@ public class WikiMainController extends BasicController implements CloneableCont private Dropdown wikiMenuDropdown, navigationDropdown, breadcrumpDropdown; private GenericTreeNode navMainPageNode, navAZNode, navChangesNode, wikiMenuNode; + private WikiAssessmentProvider assessmentProvider; public static final String ACTION_COMPARE = "compare"; public static final String ACTION_SHOW = "view.version"; @@ -196,13 +198,14 @@ public class WikiMainController extends BasicController implements CloneableCont private NotificationsManager notificationsManager; public WikiMainController(UserRequest ureq, WindowControl wControl, OLATResourceable ores, - WikiSecurityCallback securityCallback, String initialPageName) { + WikiSecurityCallback securityCallback, WikiAssessmentProvider assessmentProvider, String initialPageName) { super(ureq, wControl); this.wikiContainer = WikiManager.getInstance().getWikiRootContainer(ores); this.ores = ores; this.securityCallback = securityCallback; this.subsContext = securityCallback.getSubscriptionContext(); + this.assessmentProvider = assessmentProvider; WikiPage page = null; Wiki wiki = getWiki(); @@ -376,24 +379,35 @@ public class WikiMainController extends BasicController implements CloneableCont List<VFSItem> mediaFiles = wiki.getMediaFileList(); Collections.sort(mediaFiles, new WikiFileComparator(getLocale())); editContent.contextPut("fileList", mediaFiles); - List<String> allPages = wiki.getListOfAllPageNames(); - Collections.sort(allPages, new WikiPageNameComparator(getLocale())); - editContent.contextPut("linkList", allPages); + List<String> linkList = wiki.getAllPages().stream() + .filter(Wiki.REGULAR_PAGE_FILTER) + .map(WikiPage::getPageName) + .sorted(new WikiPageNameComparator(getLocale())) + .collect(Collectors.toList()); + editContent.contextPut("linkList", linkList); } private void updateWikiMenu(Wiki wiki) { - Collection<String> links = wiki.getListOfAllPageNames(); + List<WikiPage> pages = wiki.getAllPages().stream() + .filter(Wiki.REGULAR_PAGE_FILTER) + .sorted(WikiPageSort.PAGENAME_ORDER) + .collect(Collectors.toList()); + if (wikiMenuNode != null) { wikiMenuNode.removeAllChildren(); - for (String link : links) { + for (WikiPage page : pages) { + String link = page.getPageName(); String ident = "w" + Encoder.md5hash(link); GenericTreeNode menuItemNode = new GenericTreeNode(ident, link, link); + String cssClass = getNodeCssClass(page.getPageId()); + menuItemNode.setCssClass(cssClass); wikiMenuNode.addChild(menuItemNode); } } - + wikiMenuDropdown.removeAllComponents(); - for (String link : links) { + for (WikiPage page : pages) { + String link = page.getPageName(); Link menuLink = LinkFactory.createToolLink(link, "select-page", link, this); wikiMenuDropdown.addComponent(menuLink); } @@ -415,20 +429,24 @@ public class WikiMainController extends BasicController implements CloneableCont // Index String navMainItem = "nav-main-item-" + resId; navMainPageNode = new GenericTreeNode(navMainItem, translate("navigation.mainpage"), navMainItem); + navMainPageNode.setCssClass(getNodeDoneCssClass()); rootNode.addChild(navMainPageNode); // Wiki-Menu String wikiMenuTitle = translate("navigation.menu"); String wikiMenuItem = "menu-item-" + resId; wikiMenuNode = new GenericTreeNode(wikiMenuItem, wikiMenuTitle, wikiMenuItem); + wikiMenuNode.setCssClass(getNodeDoneCssClass()); rootNode.addChild(wikiMenuNode); String navAZItem = "nav-az-item-" + resId; navAZNode = new GenericTreeNode(navAZItem, translate("navigation.a-z"), navAZItem); + navAZNode.setCssClass(getNodeDoneCssClass()); rootNode.addChild(navAZNode); String navChangesItem = "nav-changes-item-" + resId; navChangesNode = new GenericTreeNode(navChangesItem, translate("navigation.changes"), navChangesItem); + navChangesNode.setCssClass(getNodeDoneCssClass()); rootNode.addChild(navChangesNode); updateWikiMenu(wiki); @@ -439,6 +457,22 @@ public class WikiMainController extends BasicController implements CloneableCont navigationContent.contextPut("navigationEnabled", Boolean.FALSE); return wikiMenuModel; } + + private String getNodeCssClass(String pageId) { + AssessmentEntryStatus status = assessmentProvider.getStatus(pageId); + return getNodeCssClass(status); + } + + private String getNodeCssClass(AssessmentEntryStatus status) { + if (AssessmentEntryStatus.done.equals(status)) { + return getNodeDoneCssClass(); + } + return "o_lp_ready o_lp_not_in_sequence o_lp_contains_no_sequence"; + } + + private String getNodeDoneCssClass() { + return "o_lp_done o_lp_not_in_sequence o_lp_contains_no_sequence"; + } @Override public void activate(UserRequest ureq, List<ContextEntry> entries, StateEntry state) { @@ -1169,7 +1203,7 @@ public class WikiMainController extends BasicController implements CloneableCont @Override public Controller cloneController(UserRequest ureq, WindowControl wControl) { - return WikiManager.getInstance().createWikiMainController(ureq, wControl, ores, securityCallback, null); + return WikiManager.getInstance().createWikiMainController(ureq, wControl, ores, securityCallback, null, null); } private void doReleaseEditLock() { @@ -1243,8 +1277,11 @@ public class WikiMainController extends BasicController implements CloneableCont OLATResourceable pageRes = OresHelper.createOLATResourceableInstanceWithoutCheck("path=" + page.getPageName(), 0l); addToHistory(ureq, pageRes, null); + + assessmentProvider.setStatusDone(page.getPageId()); + updateWikiMenu(getWiki()); } - + private void clearPortfolioLink() { navigationContent.put("portfolio-link", new Panel("empty")); } diff --git a/src/main/java/org/olat/modules/wiki/WikiManager.java b/src/main/java/org/olat/modules/wiki/WikiManager.java index 0fd330270763b1da7651e8b30265bf7f067e2103..208a0129550c88e3adba155ee93b2d47bc044e97 100644 --- a/src/main/java/org/olat/modules/wiki/WikiManager.java +++ b/src/main/java/org/olat/modules/wiki/WikiManager.java @@ -42,13 +42,13 @@ import java.util.List; import java.util.Properties; import org.apache.commons.codec.binary.Base64; +import org.apache.logging.log4j.Logger; import org.olat.core.commons.services.notifications.SubscriptionContext; import org.olat.core.gui.UserRequest; import org.olat.core.gui.control.WindowControl; import org.olat.core.id.OLATResourceable; import org.olat.core.logging.AssertException; import org.olat.core.logging.OLATRuntimeException; -import org.apache.logging.log4j.Logger; import org.olat.core.logging.Tracing; import org.olat.core.logging.activity.LearningResourceLoggingAction; import org.olat.core.logging.activity.ThreadLocalUserActivityLogger; @@ -131,22 +131,11 @@ public class WikiManager { return instance; } - - // ---- begin controller factory ----- - /** @param ureq - * @param wControl - * @param ores either an OlatResourcable of an repository entry or of an BusinessGroup - * @param securityCallback a callback to evaluate the permissions - * @param initialPageName opens the wiki with an certain page, default is the index page if null is passed - * @param courseContext - a course context or null if used outside a course - * @param courseNodeContext - a courseNode context or null if used outside a course - */ - public WikiMainController createWikiMainController(UserRequest ureq, WindowControl wControl, OLATResourceable ores, WikiSecurityCallback securityCallback, String initialPageName) { - return new WikiMainController(ureq, wControl, ores, securityCallback, initialPageName); + public WikiMainController createWikiMainController(UserRequest ureq, WindowControl wControl, OLATResourceable ores, + WikiSecurityCallback securityCallback, WikiAssessmentProvider assessmentProvider, String initialPageName) { + return new WikiMainController(ureq, wControl, ores, securityCallback, assessmentProvider, initialPageName); } - // ---- end controller factory ----- - /** * @return the new created resource */ diff --git a/src/main/java/org/olat/modules/wiki/WikiPageChangeOrCreateNotificationHandler.java b/src/main/java/org/olat/modules/wiki/WikiPageChangeOrCreateNotificationHandler.java index 04c90c3cbcadab9f93fd306435541cb9f6fac910..6225d9288ce2a9f82f99009a8c64ecceabbd5965 100644 --- a/src/main/java/org/olat/modules/wiki/WikiPageChangeOrCreateNotificationHandler.java +++ b/src/main/java/org/olat/modules/wiki/WikiPageChangeOrCreateNotificationHandler.java @@ -29,6 +29,7 @@ import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Locale; +import java.util.stream.Collectors; import org.apache.logging.log4j.Logger; import org.olat.basesecurity.BaseSecurityManager; @@ -137,7 +138,9 @@ public class WikiPageChangeOrCreateNotificationHandler implements NotificationsH } Wiki wiki = WikiManager.getInstance().getOrLoadWiki(ores); - final List<WikiPage> pages = wiki.getPagesByDate(); + List<WikiPage> pages = wiki.getAllPages().stream() + .sorted(WikiPageSort.MODTIME_ORDER) + .collect(Collectors.toList()); Translator translator = Util.createPackageTranslator(WikiPageChangeOrCreateNotificationHandler.class, locale); Translator forumTranslator = Util.createPackageTranslator(ForumNotificationsHandler.class, locale); diff --git a/src/main/java/org/olat/modules/wiki/WikiSecurityCallbackImpl.java b/src/main/java/org/olat/modules/wiki/WikiSecurityCallbackImpl.java index 0b9205ff4f635d2b095ff2112472ff42eaab0436..423fa17c044ca4d0555dae564ac2cb8c073f200c 100644 --- a/src/main/java/org/olat/modules/wiki/WikiSecurityCallbackImpl.java +++ b/src/main/java/org/olat/modules/wiki/WikiSecurityCallbackImpl.java @@ -25,7 +25,6 @@ package org.olat.modules.wiki; import org.olat.core.commons.services.notifications.SubscriptionContext; -import org.olat.course.run.userview.NodeEvaluation; import org.olat.modules.fo.ForumCallback; /** @@ -34,24 +33,17 @@ import org.olat.modules.fo.ForumCallback; */ public class WikiSecurityCallbackImpl implements WikiSecurityCallback { - private NodeEvaluation ne; + private Boolean courseEditRight; private boolean isAdministator; private boolean isGuestOnly; private boolean isGroupWiki; private boolean isResourceOwner; private SubscriptionContext subscriptionContext; - /** - * - * @param ne - * @param isOlatAdmin - * @param isGuestOnly - * @param isGroupWiki - */ - public WikiSecurityCallbackImpl(NodeEvaluation ne, boolean isAdministator, + public WikiSecurityCallbackImpl(Boolean courseEditRight, boolean isAdministator, boolean isGuestOnly, boolean isGroupWiki, boolean isResourceOwner, SubscriptionContext subscriptionContext){ - this.ne = ne; + this.courseEditRight = courseEditRight; this.isAdministator = isAdministator; this.isGuestOnly = isGuestOnly; this.isGroupWiki = isGroupWiki; @@ -59,36 +51,25 @@ public class WikiSecurityCallbackImpl implements WikiSecurityCallback { this.subscriptionContext = subscriptionContext; } - /** - * - * @return true if administrator or allowed by preconditions - */ @Override public boolean mayEditAndCreateArticle() { if(isGuestOnly) return false; if(isGroupWiki || isAdministator) { return true; } - if(ne != null && ne.isCapabilityAccessible("access") && ne.isCapabilityAccessible("editarticle")) { + if(courseEditRight != null && courseEditRight.booleanValue()) { return true; } //wiki is started from repo, and it's visible to this user, so creating pages is allowed - return ne == null; + return courseEditRight == null; } - /** - * - * @return true if admin or resource owner or used in group context - */ @Override public boolean mayEditWikiMenu(){ if(isGuestOnly) return false; return isGroupWiki || isAdministator || isResourceOwner; } - /** - * @return the subscriptionContext. if null, then no subscription must be offered - */ @Override public SubscriptionContext getSubscriptionContext() { return (isGuestOnly ? null : subscriptionContext); diff --git a/src/main/java/org/olat/modules/wiki/WikiToCPExport.java b/src/main/java/org/olat/modules/wiki/WikiToCPExport.java index 368020e51607ff2d21025888eb3581b75c1e8727..eb0633017f8ade12825d97f47adc2b0d973f444b 100644 --- a/src/main/java/org/olat/modules/wiki/WikiToCPExport.java +++ b/src/main/java/org/olat/modules/wiki/WikiToCPExport.java @@ -31,6 +31,7 @@ import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Locale; +import java.util.stream.Collectors; import org.jamwiki.parser.ParserDocument; import org.jamwiki.parser.ParserInput; @@ -97,7 +98,9 @@ public class WikiToCPExport { protected String createJsMappingContent(Wiki wiki) { StringBuilder sb = new StringBuilder(); - List<WikiPage> pages = wiki.getPagesByDate(); + List<WikiPage> pages = wiki.getAllPages().stream() + .sorted(WikiPageSort.MODTIME_ORDER) + .collect(Collectors.toList()); // create javascript assoz. array sb.append("var mappings = new Array();\n"); @@ -273,8 +276,10 @@ public class WikiToCPExport { // href="einleitung.html"> // <file href="einleitung.html" /> // </resource> - List<WikiPage> pageNames = wiki.getPagesByDate(); - for (WikiPage page :pageNames) { + List<WikiPage> pages = wiki.getAllPages().stream() + .sorted(WikiPageSort.MODTIME_ORDER) + .collect(Collectors.toList()); + for (WikiPage page : pages) { sb.append("<resource identifier=\"res_").append(page.getPageId()).append("\" type=\"text/html\" ").append("href=\""); sb.append(page.getPageId()).append(".html\">"); sb.append("<file href=\"").append(page.getPageId()).append(".html\" />"); diff --git a/src/main/java/org/olat/repository/handlers/ImsCPHandler.java b/src/main/java/org/olat/repository/handlers/ImsCPHandler.java index d982cd8a0217a137915b8e72deaacdb1c8319be5..fbbc1d3abfad590f546e8a2a6c8691ff2e0e0300 100644 --- a/src/main/java/org/olat/repository/handlers/ImsCPHandler.java +++ b/src/main/java/org/olat/repository/handlers/ImsCPHandler.java @@ -66,8 +66,10 @@ import org.olat.ims.cp.ui.CPContentController; import org.olat.ims.cp.ui.CPEditMainController; import org.olat.ims.cp.ui.CPPackageConfig; import org.olat.ims.cp.ui.CPRuntimeController; +import org.olat.modules.cp.CPAssessmentProvider; import org.olat.modules.cp.CPDisplayController; import org.olat.modules.cp.CPOfflineReadableManager; +import org.olat.modules.cp.PersistingAssessmentProvider; import org.olat.repository.RepositoryEntry; import org.olat.repository.RepositoryEntrySecurity; import org.olat.repository.RepositoryEntryStatusEnum; @@ -221,8 +223,10 @@ public class ImsCPHandler extends FileHandler { OLATResource res = re.getOlatResource(); File cpRoot = FileResourceManager.getInstance().unzipFileResource(res); final LocalFolderImpl vfsWrapper = new LocalFolderImpl(cpRoot); - CPPackageConfig packageConfig = CoreSpringFactory.getImpl(CPManager.class).getCPPackageConfig(res); + CPManager cpManager = CoreSpringFactory.getImpl(CPManager.class); + CPPackageConfig packageConfig = cpManager.getCPPackageConfig(res); final DeliveryOptions deliveryOptions = (packageConfig == null ? null : packageConfig.getDeliveryOptions()); + return new CPRuntimeController(ureq, wControl, re, reSecurity, (uureq, wwControl, toolbarPanel, entry, security, assessmentMode) -> { boolean activateFirstPage = true; @@ -231,8 +235,9 @@ public class ImsCPHandler extends FileHandler { CoreSpringFactory.getImpl(UserCourseInformationsManager.class) .updateUserCourseInformations(entry.getOlatResource(), uureq.getIdentity()); + CPAssessmentProvider cpAssessmentProvider = PersistingAssessmentProvider.create(re, uureq.getIdentity()); CPDisplayController cpCtr = new CPDisplayController(uureq, wwControl, vfsWrapper, true, true, activateFirstPage, true, deliveryOptions, - initialUri, entry.getOlatResource(), "", false); + initialUri, entry.getOlatResource(), "", false, cpAssessmentProvider); LayoutMain3ColsController ctr = new LayoutMain3ColsController(uureq, wwControl, cpCtr.getMenuComponent(), cpCtr.getInitialComponent(), vfsWrapper.getName()); ctr.addDisposableChildController(cpCtr); ctr.addActivateableDelegate(cpCtr); @@ -254,7 +259,7 @@ public class ImsCPHandler extends FileHandler { VFSSecurityCallback secCallback = new FullAccessWithQuotaCallback(quota); cpRoot.setLocalSecurityCallback(secCallback); - return new CPEditMainController(ureq, wControl, toolbar, cpRoot, re.getOlatResource()); + return new CPEditMainController(ureq, wControl, toolbar, cpRoot, re); } @Override @@ -262,31 +267,23 @@ public class ImsCPHandler extends FileHandler { return null; } + @Override protected String getDeletedFilePrefix() { return "del_imscp_"; } - /** - * - * @see org.olat.repository.handlers.RepositoryHandler#acquireLock(org.olat.core.id.OLATResourceable, org.olat.core.id.Identity) - */ + @Override public LockResult acquireLock(OLATResourceable ores, Identity identity) { //nothing to do return null; } - /** - * - * @see org.olat.repository.handlers.RepositoryHandler#releaseLock(org.olat.core.util.coordinate.LockResult) - */ + @Override public void releaseLock(LockResult lockResult) { //nothing to do since nothing locked } - /** - * - * @see org.olat.repository.handlers.RepositoryHandler#isLocked(org.olat.core.id.OLATResourceable) - */ + @Override public boolean isLocked(OLATResourceable ores) { return false; } diff --git a/src/main/java/org/olat/repository/handlers/WikiHandler.java b/src/main/java/org/olat/repository/handlers/WikiHandler.java index cf305098e722ea27af3f806c0c8617ded02866e6..6a582080b0a99cc03d3a9222bc294a4181b87969 100644 --- a/src/main/java/org/olat/repository/handlers/WikiHandler.java +++ b/src/main/java/org/olat/repository/handlers/WikiHandler.java @@ -60,6 +60,8 @@ import org.olat.course.assessment.manager.UserCourseInformationsManager; import org.olat.fileresource.FileResourceManager; import org.olat.fileresource.types.ResourceEvaluation; import org.olat.fileresource.types.WikiResource; +import org.olat.modules.wiki.PersistingAssessmentProvider; +import org.olat.modules.wiki.WikiAssessmentProvider; import org.olat.modules.wiki.WikiMainController; import org.olat.modules.wiki.WikiManager; import org.olat.modules.wiki.WikiModule; @@ -221,6 +223,7 @@ public class WikiHandler implements RepositoryHandler { final ContextEntry ce = bc.popLauncherContextEntry(); SubscriptionContext subsContext = new SubscriptionContext(res, WikiManager.WIKI_RESOURCE_FOLDER_NAME); final WikiSecurityCallback callback = new WikiSecurityCallbackImpl(null, isOLatAdmin, isGuestOnly, false, isResourceOwner, subsContext); + WikiAssessmentProvider assessmentProvider = PersistingAssessmentProvider.create(re, ureq.getIdentity()); return new RepositoryEntryRuntimeController(ureq, wControl, re, reSecurity, new RuntimeControllerCreator() { @@ -234,9 +237,9 @@ public class WikiHandler implements RepositoryHandler { OLATResourceable ores = ce.getOLATResourceable(); String typeName = ores.getResourceableTypeName(); String page = typeName.substring("page=".length()); - controller = new WikiMainController(uureq, wwControl, entry.getOlatResource(), callback, page); + controller = new WikiMainController(uureq, wwControl, entry.getOlatResource(), callback, assessmentProvider, page); } else { - controller = new WikiMainController(uureq, wwControl, entry.getOlatResource(), callback, null); + controller = new WikiMainController(uureq, wwControl, entry.getOlatResource(), callback, assessmentProvider, null); } return new OLATResourceableListeningWrapperController(uureq, wwControl, entry.getOlatResource(), controller, null, uureq.getIdentity()); }