From 6c1f489106d413a3e48065cd9d28fc4f2f769a0b Mon Sep 17 00:00:00 2001
From: uhensler <urs.hensler@frentix.com>
Date: Wed, 28 Aug 2019 08:53:38 +0200
Subject: [PATCH] OO-4206: GUI for the configuration of the learning path
 options

---
 .../core/gui/components/util/KeyValues.java   |  10 +-
 .../course/assessment/AssessmentAction.java   |  33 +++
 .../ConditionNodeAccessProvider.java          |   9 +
 .../course/editor/NodeEditController.java     |  45 +++--
 .../LearningPathNodeAccessProvider.java       |  15 ++
 .../ui/LeaningPathNodeConfigController.java   | 188 +++++++++++++++++-
 ...bbableLeaningPathNodeConfigController.java |  94 +++++++++
 .../learningpath/ui/_content/config.html      |   1 +
 .../ui/_i18n/LocalStrings_de.properties       |  11 +-
 .../ui/_i18n/LocalStrings_en.properties       |  11 +-
 .../course/nodeaccess/NodeAccessProvider.java |   7 +
 .../course/nodeaccess/NodeAccessService.java  |  17 ++
 .../manager/NodeAccessServiceImpl.java        |  24 +++
 13 files changed, 449 insertions(+), 16 deletions(-)
 create mode 100644 src/main/java/org/olat/course/assessment/AssessmentAction.java
 create mode 100644 src/main/java/org/olat/course/learningpath/ui/TabbableLeaningPathNodeConfigController.java
 create mode 100644 src/main/java/org/olat/course/learningpath/ui/_content/config.html

diff --git a/src/main/java/org/olat/core/gui/components/util/KeyValues.java b/src/main/java/org/olat/core/gui/components/util/KeyValues.java
index c09e8cc432a..6c9e52a5200 100644
--- a/src/main/java/org/olat/core/gui/components/util/KeyValues.java
+++ b/src/main/java/org/olat/core/gui/components/util/KeyValues.java
@@ -50,6 +50,14 @@ public class KeyValues {
 		remove(keyValue.getKey());
 		keyValues.add(keyValue);
 	}
+
+	public void addAll(KeyValues additionalKeyValues) {
+		if (additionalKeyValues == null) return;
+		
+		for (KeyValue additionalKeyValue : additionalKeyValues.keyValues) {
+			add(additionalKeyValue);
+		}
+	}
 	
 	/**
 	 * If a key / value pair with the key exists, the existing pair is replaced. If
@@ -177,4 +185,4 @@ public class KeyValues {
 		}
 	}
 
-}
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/assessment/AssessmentAction.java b/src/main/java/org/olat/course/assessment/AssessmentAction.java
new file mode 100644
index 00000000000..4305bc40c2e
--- /dev/null
+++ b/src/main/java/org/olat/course/assessment/AssessmentAction.java
@@ -0,0 +1,33 @@
+/**
+ * <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.assessment;
+
+/**
+ * 
+ * Initial date: 27 Aug 2019<br>
+ * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
+ *
+ */
+public enum AssessmentAction {
+
+	nodeClicked,
+	confirmed
+	
+}
diff --git a/src/main/java/org/olat/course/condition/ConditionNodeAccessProvider.java b/src/main/java/org/olat/course/condition/ConditionNodeAccessProvider.java
index 0fe0c2acfc9..8539e3cb2e0 100644
--- a/src/main/java/org/olat/course/condition/ConditionNodeAccessProvider.java
+++ b/src/main/java/org/olat/course/condition/ConditionNodeAccessProvider.java
@@ -21,9 +21,13 @@ package org.olat.course.condition;
 
 import java.util.Locale;
 
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.core.gui.control.generic.tabbable.TabbableController;
 import org.olat.core.gui.translator.Translator;
 import org.olat.core.util.Util;
 import org.olat.course.nodeaccess.NodeAccessProvider;
+import org.olat.course.nodes.CourseNode;
 import org.springframework.stereotype.Service;
 
 /**
@@ -48,4 +52,9 @@ public class ConditionNodeAccessProvider implements NodeAccessProvider {
 		return translator.translate("access.provider.name");
 	}
 
+	@Override
+	public TabbableController createEditController(UserRequest ureq, WindowControl wControl, CourseNode courseNode) {
+		return null;
+	}
+
 }
diff --git a/src/main/java/org/olat/course/editor/NodeEditController.java b/src/main/java/org/olat/course/editor/NodeEditController.java
index ff816d6f0d4..07fca36fe1c 100644
--- a/src/main/java/org/olat/course/editor/NodeEditController.java
+++ b/src/main/java/org/olat/course/editor/NodeEditController.java
@@ -42,15 +42,16 @@ 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.condition.ConditionNodeAccessProvider;
+import org.olat.course.nodeaccess.NodeAccessService;
 import org.olat.course.nodes.CourseNode;
 import org.olat.course.run.userview.UserCourseEnvironment;
 import org.olat.course.tree.CourseEditorTreeModel;
 import org.olat.repository.RepositoryManager;
 import org.olat.util.logging.activity.LoggingResourceable;
+import org.springframework.beans.factory.annotation.Autowired;
 
 /**
- * Description:<br>
- * is the controller for
  * 
  * @author Felix Jost
  */
@@ -82,11 +83,15 @@ public class NodeEditController extends ActivateableTabbableDefaultController im
 	private ConditionEditController visibilityCondContr;
 	private NoAccessExplEditController noAccessContr;
 	private TabbedPane myTabbedPane;
+	private TabbableController nodeAccessCtrl;
 	private TabbableController childTabsCntrllr;
 
 	/** Event that signals that the node configuration has been changed * */
 	public static final Event NODECONFIG_CHANGED_EVENT = new Event("nodeconfigchanged");
 	private static final String[] paneKeys = { PANE_TAB_VISIBILITY, PANE_TAB_GENERAL };
+	
+	@Autowired
+	private NodeAccessService nodeAccessService;
 
 	public NodeEditController(UserRequest ureq, WindowControl wControl, CourseEditorTreeModel editorModel, ICourse course, CourseNode luNode,
 			UserCourseEnvironment euce, TabbableController childTabsController) {
@@ -127,13 +132,21 @@ public class NodeEditController extends ActivateableTabbableDefaultController im
 		// Visibility and no-access explanation component
 		visibilityVc = createVelocityContainer("visibilityedit");
 
-		// Visibility precondition
-		Condition visibCondition = luNode.getPreConditionVisibility();
-		visibilityCondContr = new ConditionEditController(ureq, getWindowControl(), euce, visibCondition, 
-				AssessmentHelper.getAssessableNodes(editorModel, luNode));
-		//set this useractivity logger for the visibility condition controller
-		listenTo(visibilityCondContr);
-		visibilityVc.put("visibilityCondition", visibilityCondContr.getInitialComponent());
+		String nodeAccessType = course.getCourseConfig().getNodeAccessType();
+		if (!ConditionNodeAccessProvider.TYPE.equals(nodeAccessType)) {
+			TabbableController nodeAccessCtrl = nodeAccessService.createEditController(ureq, getWindowControl(),
+					nodeAccessType, courseNode);
+			this.nodeAccessCtrl = nodeAccessCtrl;
+			listenTo(nodeAccessCtrl);
+		} else {
+			// Visibility precondition
+			Condition visibCondition = luNode.getPreConditionVisibility();
+			visibilityCondContr = new ConditionEditController(ureq, getWindowControl(), euce, visibCondition, 
+					AssessmentHelper.getAssessableNodes(editorModel, luNode));
+			//set this useractivity logger for the visibility condition controller
+			listenTo(visibilityCondContr);
+			visibilityVc.put("visibilityCondition", visibilityCondContr.getInitialComponent());
+		}
 
 		// No-Access-Explanation
 		String noAccessExplanation = luNode.getNoAccessExplanation();
@@ -149,7 +162,6 @@ public class NodeEditController extends ActivateableTabbableDefaultController im
 
 	@Override
 	public void event(UserRequest urequest, Controller source, Event event) {
-		
 		if (source == visibilityCondContr) {
 			if (event == Event.CHANGED_EVENT) {
 				Condition cond = visibilityCondContr.getCondition();
@@ -162,6 +174,10 @@ public class NodeEditController extends ActivateableTabbableDefaultController im
 				courseNode.setNoAccessExplanation(noAccessExplanation);
 				fireEvent(urequest, NodeEditController.NODECONFIG_CHANGED_EVENT);
 			}
+		} else if (source == nodeAccessCtrl) {
+			if (event == NodeEditController.NODECONFIG_CHANGED_EVENT) {
+				fireEvent(urequest, NodeEditController.NODECONFIG_CHANGED_EVENT);
+			}
 		} else if (source == childTabsCntrllr) {
 			if (event == NodeEditController.NODECONFIG_CHANGED_EVENT) {
 				//fire child controller request further
@@ -217,9 +233,14 @@ public class NodeEditController extends ActivateableTabbableDefaultController im
 
 	@Override
 	public void addTabs(TabbedPane tabbedPane) {
-		myTabbedPane = tabbedPane;		
+		myTabbedPane = tabbedPane;
 		tabbedPane.addTab(translate(PANE_TAB_GENERAL), descriptionVc);
-		tabbedPane.addTab(translate(PANE_TAB_VISIBILITY), visibilityVc);
+		if (nodeAccessCtrl!= null) {
+			nodeAccessCtrl.addTabs(tabbedPane);
+		}
+		if (visibilityCondContr !=null) {
+			tabbedPane.addTab(translate(PANE_TAB_VISIBILITY), visibilityVc);
+		}
 		if (childTabsCntrllr != null) {
 			childTabsCntrllr.addTabs(tabbedPane);
 		}
diff --git a/src/main/java/org/olat/course/learningpath/LearningPathNodeAccessProvider.java b/src/main/java/org/olat/course/learningpath/LearningPathNodeAccessProvider.java
index 4f3bae40b13..89978971940 100644
--- a/src/main/java/org/olat/course/learningpath/LearningPathNodeAccessProvider.java
+++ b/src/main/java/org/olat/course/learningpath/LearningPathNodeAccessProvider.java
@@ -21,10 +21,17 @@ package org.olat.course.learningpath;
 
 import java.util.Locale;
 
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.core.gui.control.generic.tabbable.TabbableController;
 import org.olat.core.gui.translator.Translator;
 import org.olat.core.util.Util;
+import org.olat.course.assessment.AssessmentAction;
 import org.olat.course.learningpath.ui.LeaningPathNodeConfigController;
+import org.olat.course.learningpath.ui.LeaningPathNodeConfigController.LearningPathControllerConfig;
+import org.olat.course.learningpath.ui.TabbableLeaningPathNodeConfigController;
 import org.olat.course.nodeaccess.NodeAccessProvider;
+import org.olat.course.nodes.CourseNode;
 import org.springframework.stereotype.Service;
 
 /**
@@ -47,4 +54,12 @@ public class LearningPathNodeAccessProvider implements NodeAccessProvider {
 		return translator.translate("access.provider.name");
 	}
 
+	@Override
+	public TabbableController createEditController(UserRequest ureq, WindowControl wControl, CourseNode courseNode) {
+		LearningPathControllerConfig ctrlConfig = LeaningPathNodeConfigController.builder()
+				.addAssessmentAction(AssessmentAction.nodeClicked)
+				.build();
+		return new TabbableLeaningPathNodeConfigController(ureq, wControl, courseNode.getModuleConfiguration(), ctrlConfig);
+	}
+
 }
diff --git a/src/main/java/org/olat/course/learningpath/ui/LeaningPathNodeConfigController.java b/src/main/java/org/olat/course/learningpath/ui/LeaningPathNodeConfigController.java
index 556b4f26a4e..a31ea79948e 100644
--- a/src/main/java/org/olat/course/learningpath/ui/LeaningPathNodeConfigController.java
+++ b/src/main/java/org/olat/course/learningpath/ui/LeaningPathNodeConfigController.java
@@ -19,12 +19,198 @@
  */
 package org.olat.course.learningpath.ui;
 
+import static org.olat.core.gui.components.util.KeyValues.entry;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.FormItemContainer;
+import org.olat.core.gui.components.form.flexible.elements.SingleSelection;
+import org.olat.core.gui.components.form.flexible.elements.TextElement;
+import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
+import org.olat.core.gui.components.form.flexible.impl.FormEvent;
+import org.olat.core.gui.components.util.KeyValues;
+import org.olat.core.gui.control.Controller;
+import org.olat.core.gui.control.Event;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.core.util.StringHelper;
+import org.olat.course.assessment.AssessmentAction;
+import org.olat.modules.ModuleConfiguration;
+
 /**
  * 
  * Initial date: 27 Aug 2019<br>
  * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
  *
  */
-public class LeaningPathNodeConfigController {
+public class LeaningPathNodeConfigController extends FormBasicController {
+	
+	public static final String CONFIG_KEY_ESTIMATED_DURATION = "learning.path.estimated.duration";
+	public static final String CONFIG_KEY_OBLIGATION = "learning.path.obligation";
+	public static final String CONFIG_VALUE_OBLIGATION_MANDATORY = "obligation.mandatory";
+	public static final String CONFIG_VALUE_OBLIGATION_OPTIONAL = "obligation.optional";
+	public static final String CONFIG_DEFAULT_OBLIGATION = CONFIG_VALUE_OBLIGATION_MANDATORY;
+	public static final String CONFIG_KEY_DONE_TRIGGER = "learning.path.done.trigger";
+	private static final String CONFIG_VALUE_DONE_TRIGGER_NONE = "done.trigger.none";
+	public static final String CONFIG_DEFAULT_DONE_TRIGGER = CONFIG_VALUE_DONE_TRIGGER_NONE;
+	
+	private TextElement estimatedDurationEl;
+	private SingleSelection obligationEl;
+	private SingleSelection doneTriggerEl;
+
+	private final ModuleConfiguration configs;
+	private final LearningPathControllerConfig ctrlConfig;
+
+	public LeaningPathNodeConfigController(UserRequest ureq, WindowControl wControl,
+			ModuleConfiguration configs, LearningPathControllerConfig ctrlConfig) {
+		super(ureq, wControl);
+		this.configs = configs;
+		this.ctrlConfig = ctrlConfig;
+		initForm(ureq);
+	}
 
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		String estimatedTime = configs.getStringValue(CONFIG_KEY_ESTIMATED_DURATION);
+		estimatedDurationEl = uifactory.addTextElement("config.estimated.duration", 128, estimatedTime , formLayout);
+		
+		KeyValues obligationKV = new KeyValues();
+		obligationKV.add(entry(CONFIG_VALUE_OBLIGATION_MANDATORY, translate("config.obligation.mandatory")));
+		obligationKV.add(entry(CONFIG_VALUE_OBLIGATION_OPTIONAL, translate("config.obligation.optional")));
+		obligationEl = uifactory.addRadiosHorizontal("config.obligation", formLayout, obligationKV.keys(), obligationKV.values());
+		String obligationKey = configs.getStringValue(CONFIG_KEY_OBLIGATION, CONFIG_DEFAULT_OBLIGATION);
+		if (Arrays.asList(obligationEl.getKeys()).contains(obligationKey)) {
+			obligationEl.select(obligationKey, true);
+		}
+		
+		KeyValues doneTriggerKV = getDoneTriggerKV();
+		doneTriggerEl = uifactory.addDropdownSingleselect("config.done.trigger", formLayout,
+				doneTriggerKV.keys(), doneTriggerKV.values());
+		doneTriggerEl.addActionListener(FormEvent.ONCHANGE);
+		String doneTriggerKey = configs.getStringValue(CONFIG_KEY_DONE_TRIGGER, CONFIG_DEFAULT_DONE_TRIGGER);
+		if (Arrays.asList(doneTriggerEl.getKeys()).contains(doneTriggerKey)) {
+			doneTriggerEl.select(doneTriggerKey, true);
+		}
+		
+		uifactory.addFormSubmitButton("save", formLayout);
+	}
+	
+	private KeyValues getDoneTriggerKV() {
+		KeyValues doneTriggerKV = new KeyValues();
+		if (ctrlConfig.getAssessmentActions().contains(AssessmentAction.nodeClicked)) {
+			doneTriggerKV.add(entry(AssessmentAction.nodeClicked.name(), translate("config.done.trigger.started")));
+		}
+		if (ctrlConfig.getAssessmentActions().contains(AssessmentAction.confirmed)) {
+			doneTriggerKV.add(entry(AssessmentAction.confirmed.name(), translate("config.done.trigger.confirmed")));
+		}
+		return doneTriggerKV;
+	}
+	
+	@Override
+	protected boolean validateFormLogic(UserRequest ureq) {
+		boolean allOk = true;
+		
+		allOk = validateInteger(estimatedDurationEl, 1, 10000, "error.positiv.int");
+		
+		return allOk & super.validateFormLogic(ureq);
+	}
+	
+	public static boolean validateInteger(TextElement el, int min, int max, String i18nKey) {
+		boolean allOk = true;
+		el.clearError();
+		if(el.isEnabled() && el.isVisible()) {
+			String val = el.getValue();
+			if(StringHelper.containsNonWhitespace(val)) {
+				
+				try {
+					int value = Integer.parseInt(val);
+					if(min > value) {
+						el.setErrorKey(i18nKey, null);
+						allOk = false;
+					} else if(max < value) {
+						el.setErrorKey(i18nKey, null);
+						allOk = false;
+					}
+				} catch (NumberFormatException e) {
+					el.setErrorKey(i18nKey, null);
+					allOk = false;
+				}
+			}
+		}
+		return allOk;
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		fireEvent(ureq, Event.DONE_EVENT);
+	}
+	
+	protected ModuleConfiguration getUpdatedConfig() {
+		String estimatedTime = estimatedDurationEl.getValue();
+		configs.setStringValue(CONFIG_KEY_ESTIMATED_DURATION, estimatedTime);
+		
+		String obligation = obligationEl.isOneSelected()
+				? obligationEl.getSelectedKey()
+				: CONFIG_DEFAULT_OBLIGATION;
+		configs.setStringValue(CONFIG_KEY_OBLIGATION, obligation);
+		
+		String doneTrigger = doneTriggerEl.isOneSelected()
+				? doneTriggerEl.getSelectedKey()
+				: CONFIG_DEFAULT_DONE_TRIGGER;
+		configs.setStringValue(CONFIG_KEY_DONE_TRIGGER, doneTrigger);
+		
+		return configs;
+	}
+
+	@Override
+	protected void doDispose() {
+		//
+	}
+	
+	public interface LearningPathControllerConfig {
+		
+		public Set<AssessmentAction> getAssessmentActions();
+		
+	}
+	
+	public static ControllerConfigBuilder builder() {
+		return new ControllerConfigBuilder();
+	}
+	
+	public static class ControllerConfigBuilder {
+		
+		private final Set<AssessmentAction> assessmentActions = new HashSet<>();
+		
+		private ControllerConfigBuilder() {
+		}
+		
+		public ControllerConfigBuilder addAssessmentAction(AssessmentAction action) {
+			assessmentActions.add(action);
+			return this;
+		}
+		
+		public LearningPathControllerConfig build() {
+			return new ControllerConfigImpl(this);
+		}
+		
+		private final static class ControllerConfigImpl implements LearningPathControllerConfig {
+			
+			private final Set<AssessmentAction> assessmentActions;
+
+			public ControllerConfigImpl(ControllerConfigBuilder builder) {
+				this.assessmentActions = new HashSet<>(builder.assessmentActions);
+			}
+			
+			@Override
+			public Set<AssessmentAction> getAssessmentActions() {
+				return assessmentActions;
+			}
+			
+		}
+		
+	}
+	
 }
+
diff --git a/src/main/java/org/olat/course/learningpath/ui/TabbableLeaningPathNodeConfigController.java b/src/main/java/org/olat/course/learningpath/ui/TabbableLeaningPathNodeConfigController.java
new file mode 100644
index 00000000000..2e6ff757ed1
--- /dev/null
+++ b/src/main/java/org/olat/course/learningpath/ui/TabbableLeaningPathNodeConfigController.java
@@ -0,0 +1,94 @@
+/**
+ * <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.learningpath.ui;
+
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.Component;
+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.Event;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.core.gui.control.generic.tabbable.ActivateableTabbableDefaultController;
+import org.olat.course.editor.NodeEditController;
+import org.olat.course.learningpath.ui.LeaningPathNodeConfigController.LearningPathControllerConfig;
+import org.olat.modules.ModuleConfiguration;
+
+/**
+ * 
+ * Initial date: 27 Aug 2019<br>
+ * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
+ *
+ */
+public class TabbableLeaningPathNodeConfigController extends ActivateableTabbableDefaultController {
+
+	private static final String PANE_TAB_LEARNIN_PATH = "pane.tab.learning.path";
+	private final static String[] paneKeys = { PANE_TAB_LEARNIN_PATH };
+	
+	private final VelocityContainer configVC;
+	private final LeaningPathNodeConfigController configController; 
+	private TabbedPane tabPane;
+	
+	public TabbableLeaningPathNodeConfigController(UserRequest ureq, WindowControl wControl, 
+			ModuleConfiguration configs, LearningPathControllerConfig ctrlConfig) {
+		super(ureq, wControl);
+
+		configController = new LeaningPathNodeConfigController(ureq, wControl, configs, ctrlConfig);
+		listenTo(configController);
+
+		configVC = createVelocityContainer("config");
+		configVC.put("config", configController.getInitialComponent());
+	}
+
+	@Override
+	public void addTabs(TabbedPane tabbedPane) {
+		tabPane = tabbedPane;
+		tabbedPane.addTab(translate(PANE_TAB_LEARNIN_PATH), configVC);
+	}
+
+	@Override
+	public String[] getPaneKeys() {
+		return paneKeys;
+	}
+
+	@Override
+	public TabbedPane getTabbedPane() {
+		return tabPane;
+	}
+
+	@Override
+	public void event(UserRequest ureq, Controller source, Event event) {
+		if (source == configController && event.equals(Event.DONE_EVENT)) {
+			configController.getUpdatedConfig();
+			fireEvent(ureq, NodeEditController.NODECONFIG_CHANGED_EVENT);
+		}
+	}
+	
+	@Override
+	protected void event(UserRequest ureq, Component source, Event event) {
+		//
+	}
+
+	@Override
+	protected void doDispose() {
+		//
+	}
+
+}
diff --git a/src/main/java/org/olat/course/learningpath/ui/_content/config.html b/src/main/java/org/olat/course/learningpath/ui/_content/config.html
new file mode 100644
index 00000000000..a6a4023478b
--- /dev/null
+++ b/src/main/java/org/olat/course/learningpath/ui/_content/config.html
@@ -0,0 +1 @@
+$r.render("config")
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/learningpath/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/learningpath/ui/_i18n/LocalStrings_de.properties
index 71f4b0ce27b..a215c355cba 100644
--- a/src/main/java/org/olat/course/learningpath/ui/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/course/learningpath/ui/_i18n/LocalStrings_de.properties
@@ -1 +1,10 @@
-access.provider.name=Lernpfad
\ No newline at end of file
+access.provider.name=Lernpfad
+error.positiv.int=Geben Sie eine positive Zahl ein.
+config.done.trigger=Lernfortschritt
+config.done.trigger.confirmed=Best\u00E4tigt
+config.done.trigger.started=Ge\u00F6ffnet
+config.estimated.duration=Zeitvorgabe (Minuten)
+congig.obligation=Pflicht
+config.obligation.mandatory=Obligatorisch
+config.obligation.optional=Freiwillig
+pane.tab.learning.path=Lernpfad
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/learningpath/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/learningpath/ui/_i18n/LocalStrings_en.properties
index 69e8fe2cc65..84f81bf0b93 100644
--- a/src/main/java/org/olat/course/learningpath/ui/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/course/learningpath/ui/_i18n/LocalStrings_en.properties
@@ -1 +1,10 @@
-access.provider.name=Learning path
\ No newline at end of file
+access.provider.name=Learning path
+error.positiv.int=Enter a positive number.
+config.done.trigger=Done trigger
+config.done.trigger.confirmed=Confirmed
+config.done.trigger.started=Opened
+config.estimated.duration=Estimated duration (minutes)
+config.obligation=Obligation
+config.obligation.mandatory=Mandatory
+config.obligation.optional=Optional
+pane.tab.learning.path=Learning path
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/nodeaccess/NodeAccessProvider.java b/src/main/java/org/olat/course/nodeaccess/NodeAccessProvider.java
index e93c1c0d945..8e5c1f96421 100644
--- a/src/main/java/org/olat/course/nodeaccess/NodeAccessProvider.java
+++ b/src/main/java/org/olat/course/nodeaccess/NodeAccessProvider.java
@@ -19,6 +19,11 @@
  */
 package org.olat.course.nodeaccess;
 
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.core.gui.control.generic.tabbable.TabbableController;
+import org.olat.course.nodes.CourseNode;
+
 /**
  * 
  * Initial date: 27 Aug 2019<br>
@@ -27,4 +32,6 @@ package org.olat.course.nodeaccess;
  */
 public interface NodeAccessProvider extends NodeAccessProviderIdentifier {
 
+	public TabbableController createEditController(UserRequest ureq, WindowControl wControl, CourseNode courseNode);
+
 }
diff --git a/src/main/java/org/olat/course/nodeaccess/NodeAccessService.java b/src/main/java/org/olat/course/nodeaccess/NodeAccessService.java
index 361c589a9de..2254cb7f873 100644
--- a/src/main/java/org/olat/course/nodeaccess/NodeAccessService.java
+++ b/src/main/java/org/olat/course/nodeaccess/NodeAccessService.java
@@ -21,6 +21,11 @@ package org.olat.course.nodeaccess;
 
 import java.util.List;
 
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.core.gui.control.generic.tabbable.TabbableController;
+import org.olat.course.nodes.CourseNode;
+
 /**
  * 
  * Initial date: 27 Aug 2019<br>
@@ -31,4 +36,16 @@ public interface NodeAccessService {
 	
 	public List<? extends NodeAccessProviderIdentifier> getNodeAccessProviderIdentifer();
 
+	/**
+	 * Creates the controller to edit the configurations of the node.
+	 * 
+	 * @param ureq 
+	 * @param windowControl 
+	 * @param nodeAccessType
+	 * @param courseNode
+	 * @return
+	 */
+	public TabbableController createEditController(UserRequest ureq, WindowControl wControl, String nodeAccessType,
+			CourseNode courseNode);
+
 }
diff --git a/src/main/java/org/olat/course/nodeaccess/manager/NodeAccessServiceImpl.java b/src/main/java/org/olat/course/nodeaccess/manager/NodeAccessServiceImpl.java
index 2392b705885..225b06007fa 100644
--- a/src/main/java/org/olat/course/nodeaccess/manager/NodeAccessServiceImpl.java
+++ b/src/main/java/org/olat/course/nodeaccess/manager/NodeAccessServiceImpl.java
@@ -21,9 +21,15 @@ package org.olat.course.nodeaccess.manager;
 
 import java.util.List;
 
+import org.apache.logging.log4j.Logger;
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.core.gui.control.generic.tabbable.TabbableController;
+import org.olat.core.logging.Tracing;
 import org.olat.course.nodeaccess.NodeAccessProvider;
 import org.olat.course.nodeaccess.NodeAccessProviderIdentifier;
 import org.olat.course.nodeaccess.NodeAccessService;
+import org.olat.course.nodes.CourseNode;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
@@ -36,12 +42,30 @@ import org.springframework.stereotype.Service;
 @Service
 public class NodeAccessServiceImpl implements NodeAccessService {
 
+	private static final Logger log = Tracing.createLoggerFor(NodeAccessServiceImpl.class);
+
 	@Autowired
 	private List<NodeAccessProvider> nodeAccessProviders;
 	
+	private NodeAccessProvider getNodeAccessProvider(String nodeAccessType) {
+		for (NodeAccessProvider provider : nodeAccessProviders) {
+			if (provider.getType().equals(nodeAccessType)) {
+				return provider;
+			}
+		}
+		log.error("No node access provider found for type '{}'!", nodeAccessType);
+		return null;
+	}
+	
 	@Override
 	public List<? extends NodeAccessProviderIdentifier> getNodeAccessProviderIdentifer() {
 		return nodeAccessProviders;
 	}
 
+	@Override
+	public TabbableController createEditController(UserRequest ureq, WindowControl wControl, String nodeAccessType,
+			CourseNode courseNode) {
+		return getNodeAccessProvider(nodeAccessType).createEditController(ureq, wControl, courseNode);
+	}
+
 }
-- 
GitLab