From 8de06c2dd6b6f511f34684eaf6b2e8cd5bb73894 Mon Sep 17 00:00:00 2001
From: uhensler <urs.hensler@frentix.com>
Date: Fri, 21 Feb 2020 16:44:03 +0100
Subject: [PATCH] OO-4533: Configurations for create and manage notifications

---
 .../org/olat/course/nodes/InfoCourseNode.java | 105 +++++++++++++-----
 ...figForm.java => InfoConfigController.java} |  99 ++++++++++++++---
 .../info/InfoCourseNodeEditController.java    |  71 ++++++------
 .../info/InfoCourseSecurityCallback.java      |  56 ++++++++++
 .../course/nodes/info/InfoRunController.java  |  90 ++++++++-------
 .../olat/course/nodes/info/_content/edit.html |   1 -
 .../info/_i18n/LocalStrings_de.properties     |   5 +
 .../info/_i18n/LocalStrings_en.properties     |   5 +
 .../course/run/CourseRuntimeController.java   |   5 +-
 9 files changed, 310 insertions(+), 127 deletions(-)
 rename src/main/java/org/olat/course/nodes/info/{InfoConfigForm.java => InfoConfigController.java} (55%)
 create mode 100644 src/main/java/org/olat/course/nodes/info/InfoCourseSecurityCallback.java
 delete mode 100644 src/main/java/org/olat/course/nodes/info/_content/edit.html

diff --git a/src/main/java/org/olat/course/nodes/InfoCourseNode.java b/src/main/java/org/olat/course/nodes/InfoCourseNode.java
index 395be586f44..e3f1cf61ebb 100644
--- a/src/main/java/org/olat/course/nodes/InfoCourseNode.java
+++ b/src/main/java/org/olat/course/nodes/InfoCourseNode.java
@@ -41,6 +41,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;
@@ -66,10 +67,20 @@ import org.olat.repository.RepositoryEntry;
  * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
  */
 public class InfoCourseNode extends AbstractAccessableCourseNode {
+	
+	@SuppressWarnings("deprecation")
+	private static final String TRANSLATOR_PACKAGE = Util.getPackageName(InfoCourseNodeEditController.class);
 
 	public static final String TYPE = "info";
 	public static final String EDIT_CONDITION_ID = "editinfos";
 	public static final String ADMIN_CONDITION_ID = "admininfos";
+	
+	// Configs
+	private static final int CURRENT_VERSION = 2;
+	public static final String CONFIG_KEY_ADMIN_BY_COACH = "admin.by.coach";
+	public static final String CONFIG_KEY_EDIT_BY_COACH = "edit.by.coach";
+	public static final String CONFIG_KEY_EDIT_BY_PARTICIPANT = "edit.by.participant";
+	
 	private Condition preConditionEdit;
 	private Condition preConditionAdmin;
 	
@@ -90,6 +101,40 @@ public class InfoCourseNode extends AbstractAccessableCourseNode {
 			config.set(InfoCourseNodeConfiguration.CONFIG_DURATION, "90");
 			config.set(InfoCourseNodeConfiguration.CONFIG_LENGTH, "10");
 		}
+		
+		int version = config.getConfigurationVersion();
+		if (version < 2) {
+			config.setBooleanEntry(CONFIG_KEY_ADMIN_BY_COACH, true);
+			config.setBooleanEntry(CONFIG_KEY_EDIT_BY_COACH, true);
+			config.setBooleanEntry(CONFIG_KEY_EDIT_BY_PARTICIPANT, false);
+			removeDefaultPreconditions();
+		}
+		config.setConfigurationVersion(CURRENT_VERSION);
+	}
+	
+	private void removeDefaultPreconditions() {
+		if (hasCustomPreConditions()) {
+			boolean defaultPreconditions =
+					!preConditionAdmin.isExpertMode()
+				&& preConditionAdmin.isEasyModeCoachesAndAdmins()
+				&& !preConditionAdmin.isEasyModeAlwaysAllowCoachesAndAdmins()
+				&& !preConditionAdmin.isAssessmentMode()
+				&& !preConditionAdmin.isAssessmentModeViewResults()
+				&& !preConditionEdit.isExpertMode()
+				&& preConditionEdit.isEasyModeCoachesAndAdmins()
+				&& !preConditionEdit.isEasyModeAlwaysAllowCoachesAndAdmins()
+				&& !preConditionEdit.isAssessmentMode()
+				&& !preConditionEdit.isAssessmentModeViewResults();
+			if (defaultPreconditions) {
+				removeCustomPreconditions();
+			}
+		}
+	}
+	
+	public void removeCustomPreconditions() {
+		preConditionAdmin = null;
+		preConditionEdit = null;
+		setPreConditionAccess(null);
 	}
 	
 	@Override
@@ -124,19 +169,24 @@ public class InfoCourseNode extends AbstractAccessableCourseNode {
 	@Override
 	public StatusDescription[] isConfigValid(CourseEditorEnv cev) {
 		oneClickStatusCache = null;
-		String translatorStr = Util.getPackageName(InfoCourseNodeEditController.class);
-		List<StatusDescription> statusDescs =isConfigValidWithTranslator(cev, translatorStr, getConditionExpressions());
+		List<StatusDescription> statusDescs = isConfigValidWithTranslator(cev, TRANSLATOR_PACKAGE, getConditionExpressions());
 		oneClickStatusCache = StatusDescriptionHelper.sort(statusDescs);
 		return oneClickStatusCache;
 	}
 
-
 	@Override
 	public TabbableController createEditController(UserRequest ureq, WindowControl wControl, BreadcrumbPanel stackPanel, ICourse course, UserCourseEnvironment euce) {
-		InfoCourseNodeEditController childTabCntrllr = new InfoCourseNodeEditController(ureq, wControl, getModuleConfiguration(), this, course, euce);
+		InfoCourseNodeEditController childTabCntrllr = new InfoCourseNodeEditController(ureq, wControl, 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 Controller createPeekViewRunController(UserRequest ureq, WindowControl wControl, UserCourseEnvironment userCourseEnv,
@@ -144,9 +194,8 @@ public class InfoCourseNode extends AbstractAccessableCourseNode {
 		if (nodeSecCallback.isAccessible()) {
 			InfoPeekViewController ctrl = new InfoPeekViewController(ureq, wControl, userCourseEnv, this);
 			return ctrl;
-		} else {
-			return super.createPeekViewRunController(ureq, wControl, userCourseEnv, nodeSecCallback);
 		}
+		return super.createPeekViewRunController(ureq, wControl, userCourseEnv, nodeSecCallback);
 	}
 
 	@Override
@@ -165,21 +214,27 @@ public class InfoCourseNode extends AbstractAccessableCourseNode {
 			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);
-		}
-		Condition adminCondition = getPreConditionAdmin();
-		if(adminCondition != null && StringHelper.containsNonWhitespace(adminCondition.getConditionExpression())) {
-			ConditionExpression ce = new ConditionExpression(adminCondition.getConditionId());
-			ce.setExpressionString(adminCondition.getConditionExpression());
-			conditions.add(ce);
+		if (hasCustomPreConditions()) {
+			Condition editCondition = getPreConditionEdit();
+			if(editCondition != null && StringHelper.containsNonWhitespace(editCondition.getConditionExpression())) {
+				ConditionExpression ce = new ConditionExpression(editCondition.getConditionId());
+				ce.setExpressionString(editCondition.getConditionExpression());
+				conditions.add(ce);
+			}
+			Condition adminCondition = getPreConditionAdmin();
+			if(adminCondition != null && StringHelper.containsNonWhitespace(adminCondition.getConditionExpression())) {
+				ConditionExpression ce = new ConditionExpression(adminCondition.getConditionId());
+				ce.setExpressionString(adminCondition.getConditionExpression());
+				conditions.add(ce);
+			}
 		}
 		return conditions;
 	}
 	
+	public boolean hasCustomPreConditions() {
+		return preConditionAdmin != null || preConditionEdit != null;
+	}
+	
 	/**
 	 * Default set the write privileges to coaches and admin only
 	 * @return
@@ -236,21 +291,21 @@ public class InfoCourseNode extends AbstractAccessableCourseNode {
 
 	@Override
 	public void calcAccessAndVisibility(ConditionInterpreter ci, NodeEvaluation nodeEval) {
-		//nodeEval.setVisible(true);
 		super.calcAccessAndVisibility(ci, nodeEval);
 		
-		// evaluate the preconditions
-		boolean editor = (getPreConditionEdit().getConditionExpression() == null ? true : ci.evaluateCondition(getPreConditionEdit()));
-		nodeEval.putAccessStatus(EDIT_CONDITION_ID, editor);
-		
-		boolean admin = (getPreConditionAdmin().getConditionExpression() == null ? true : ci.evaluateCondition(getPreConditionAdmin()));
-		nodeEval.putAccessStatus(ADMIN_CONDITION_ID, admin);
+		if (hasCustomPreConditions()) {
+			boolean editor = (getPreConditionEdit().getConditionExpression() == null ? true : ci.evaluateCondition(getPreConditionEdit()));
+			nodeEval.putAccessStatus(EDIT_CONDITION_ID, editor);
+			
+			boolean admin = (getPreConditionAdmin().getConditionExpression() == null ? true : ci.evaluateCondition(getPreConditionAdmin()));
+			nodeEval.putAccessStatus(ADMIN_CONDITION_ID, admin);
+		}
 	}
 	
-		@Override
 	/**
 	 * is called when deleting this node, clean up info-messages and subscriptions!
 	 */
+	@Override
 	public void cleanupOnDelete(ICourse course) {
 		super.cleanupOnDelete(course);
 		// delete infoMessages and subscriptions (OLAT-6171)
diff --git a/src/main/java/org/olat/course/nodes/info/InfoConfigForm.java b/src/main/java/org/olat/course/nodes/info/InfoConfigController.java
similarity index 55%
rename from src/main/java/org/olat/course/nodes/info/InfoConfigForm.java
rename to src/main/java/org/olat/course/nodes/info/InfoConfigController.java
index 10f2f49dac6..f41f0eba836 100644
--- a/src/main/java/org/olat/course/nodes/info/InfoConfigForm.java
+++ b/src/main/java/org/olat/course/nodes/info/InfoConfigController.java
@@ -20,16 +20,23 @@
 
 package org.olat.course.nodes.info;
 
+import static org.olat.core.gui.translator.TranslatorHelper.translateAll;
+
+import java.util.Collection;
+
 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.MultipleSelectionElement;
 import org.olat.core.gui.components.form.flexible.elements.SingleSelection;
 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.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.editor.NodeEditController;
+import org.olat.course.nodes.InfoCourseNode;
 import org.olat.modules.ModuleConfiguration;
 
 /**
@@ -41,7 +48,7 @@ import org.olat.modules.ModuleConfiguration;
  * Initial Date:  3 aug. 2010 <br>
  * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
  */
-public class InfoConfigForm extends FormBasicController {
+public class InfoConfigController extends FormBasicController {
 
 	
 	private static final String[] maxDurationValues = new String[] {
@@ -60,34 +67,50 @@ public class InfoConfigForm extends FormBasicController {
 		null
 	};
 	
+	private static final String ROLE_COACH = "config.role.coach";
+	private static final String ROLE_PARTICIPANT = "config.role.participant";
+	private static final String[] ADMIN_KEYS = new String[] {
+			ROLE_COACH
+	};
+	private static final String[] EDIT_KEYS = new String[] {
+			ROLE_COACH,
+			ROLE_PARTICIPANT
+	};
+	
+	private final InfoCourseNode courseNode;
 	private final ModuleConfiguration config;
 	
 	private SingleSelection durationSelection;
 	private SingleSelection lengthSelection;
 	private MultipleSelectionElement autoSubscribeSelection;
-	
-	public InfoConfigForm(UserRequest ureq, WindowControl wControl, ModuleConfiguration config) {
-		super(ureq, wControl);
-		
-		this.config = config;
+	private MultipleSelectionElement adminRolesEl;
+	private MultipleSelectionElement editRolesEl;
+
+	public InfoConfigController(UserRequest ureq, WindowControl wControl, InfoCourseNode courseNode) {
+		super(ureq, wControl, LAYOUT_BAREBONE);
+		this.courseNode = courseNode;
+		this.config = courseNode.getModuleConfiguration();
 		autoSubscribeValues[0] = translate("pane.tab.infos_config.auto_subscribe.on");
 		initForm(ureq);
 	}
 	
 	@Override
 	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
-		formLayout.setElementCssClass("o_sel_course_info_form");
-		setFormTitle("pane.tab.infos_config.title");
-		setFormContextHelp("Administration and Organisation#_mitteilung_zugang");
-
+		FormLayoutContainer generalCont = FormLayoutContainer.createDefaultFormLayout("general", getTranslator());
+		generalCont.setFormTitle(translate("pane.tab.infos_config.title"));
+		generalCont.setFormContextHelp("Administration and Organisation#_mitteilung_zugang");
+		generalCont.setElementCssClass("o_sel_course_info_form");
+		formLayout.add(generalCont);
+		
 		String page = velocity_root + "/editShow.html";
 		final FormLayoutContainer showLayout = FormLayoutContainer.createCustomFormLayout("pane.tab.infos_config.shown", getTranslator(), page);
 		showLayout.setLabel("pane.tab.infos_config.shown", null);
-		formLayout.add(showLayout);
+		generalCont.add(showLayout);
 
 		durationSelection = uifactory.addDropdownSingleselect("pane.tab.infos_config.max_duration", showLayout, maxDurationValues, maxDurationValues, null);
 		durationSelection.setLabel("pane.tab.infos_config.max", null);
 		durationSelection.setElementCssClass("o_sel_course_info_duration");
+		durationSelection.addActionListener(FormEvent.ONCHANGE);
 		String durationStr = (String)config.get(InfoCourseNodeConfiguration.CONFIG_DURATION);
 		if(StringHelper.containsNonWhitespace(durationStr)) {
 			durationSelection.select(durationStr, true);
@@ -98,6 +121,7 @@ public class InfoConfigForm extends FormBasicController {
 		lengthSelection = uifactory.addDropdownSingleselect("pane.tab.infos_config.max_shown", null, showLayout, maxLengthValues, maxLengthValues, null);
 		lengthSelection.setElementCssClass("o_sel_course_info_length");
 		lengthSelection.setLabel("pane.tab.infos_config.max", null);
+		lengthSelection.addActionListener(FormEvent.ONCHANGE);
 		String lengthStr = (String)config.get(InfoCourseNodeConfiguration.CONFIG_LENGTH);
 		if(StringHelper.containsNonWhitespace(lengthStr)) {
 			lengthSelection.select(lengthStr, true);
@@ -105,16 +129,49 @@ public class InfoConfigForm extends FormBasicController {
 			lengthSelection.select("5", true);
 		}
 		
-		autoSubscribeSelection = uifactory.addCheckboxesHorizontal("auto_subscribe", formLayout, autoSubscribeKeys, autoSubscribeValues);
+		autoSubscribeSelection = uifactory.addCheckboxesHorizontal("auto_subscribe", generalCont, autoSubscribeKeys, autoSubscribeValues);
+		autoSubscribeSelection.addActionListener(FormEvent.ONCHANGE);
 		String autoSubscribeStr = (String)config.get(InfoCourseNodeConfiguration.CONFIG_AUTOSUBSCRIBE);
 		if("on".equals(autoSubscribeStr) || !StringHelper.containsNonWhitespace(autoSubscribeStr)) {
 			autoSubscribeSelection.select("on", true);
 		}
 		
-		uifactory.addFormSubmitButton("save", formLayout);
+		if (!courseNode.hasCustomPreConditions()) {
+			FormLayoutContainer rightsCont = FormLayoutContainer.createDefaultFormLayout("rights", getTranslator());
+			formLayout.add(rightsCont);
+			rightsCont.setFormTitle(translate("config.rights"));
+			
+			adminRolesEl = uifactory.addCheckboxesVertical("config.admin", rightsCont, ADMIN_KEYS,
+					translateAll(getTranslator(), ADMIN_KEYS), 1);
+			adminRolesEl.select(ROLE_COACH, config.getBooleanSafe(InfoCourseNode.CONFIG_KEY_ADMIN_BY_COACH));
+			adminRolesEl.addActionListener(FormEvent.ONCHANGE);
+			
+			editRolesEl = uifactory.addCheckboxesVertical("config.edit", rightsCont, EDIT_KEYS,
+					translateAll(getTranslator(), EDIT_KEYS), 1);
+			editRolesEl.select(ROLE_COACH, config.getBooleanSafe(InfoCourseNode.CONFIG_KEY_EDIT_BY_COACH));
+			editRolesEl.select(ROLE_PARTICIPANT,
+					config.getBooleanSafe(InfoCourseNode.CONFIG_KEY_EDIT_BY_PARTICIPANT));
+			editRolesEl.addActionListener(FormEvent.ONCHANGE);
+		}
 	}
 	
-	protected ModuleConfiguration getUpdatedConfig() {
+	@Override
+	protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
+		if (source == durationSelection) {
+			doUpdatedConfig(ureq);
+		} else if (source == lengthSelection) {
+			doUpdatedConfig(ureq);
+		} else if (source == autoSubscribeSelection) {
+			doUpdatedConfig(ureq);
+		} else if (source == adminRolesEl) {
+			doUpdatedConfig(ureq);
+		} else if (source == editRolesEl) {
+			doUpdatedConfig(ureq);
+		}
+		super.formInnerEvent(ureq, source, event);
+	}
+
+	private void doUpdatedConfig(UserRequest ureq) {
 		String durationStr = durationSelection.getSelectedKey();
 		config.set(InfoCourseNodeConfiguration.CONFIG_DURATION, durationStr);
 		
@@ -123,7 +180,15 @@ public class InfoConfigForm extends FormBasicController {
 		
 		String autoSubscribeStr = autoSubscribeSelection.isSelected(0) ? "on" : "off";
 		config.set(InfoCourseNodeConfiguration.CONFIG_AUTOSUBSCRIBE, autoSubscribeStr);
-		return config;
+		
+		Collection<String> selectedAdminKeys = adminRolesEl.getSelectedKeys();
+		config.setBooleanEntry(InfoCourseNode.CONFIG_KEY_ADMIN_BY_COACH, selectedAdminKeys.contains(ROLE_COACH));
+		
+		Collection<String> selectedEditKeys = editRolesEl.getSelectedKeys();
+		config.setBooleanEntry(InfoCourseNode.CONFIG_KEY_EDIT_BY_COACH, selectedEditKeys.contains(ROLE_COACH));
+		config.setBooleanEntry(InfoCourseNode.CONFIG_KEY_EDIT_BY_PARTICIPANT, selectedEditKeys.contains(ROLE_PARTICIPANT));
+		
+		fireEvent(ureq, NodeEditController.NODECONFIG_CHANGED_EVENT);
 	}
 	
 	@Override
@@ -133,6 +198,6 @@ public class InfoConfigForm extends FormBasicController {
 
 	@Override
 	protected void formOK(UserRequest ureq) {
-		fireEvent (ureq, Event.DONE_EVENT);
+		//
 	}
 }
diff --git a/src/main/java/org/olat/course/nodes/info/InfoCourseNodeEditController.java b/src/main/java/org/olat/course/nodes/info/InfoCourseNodeEditController.java
index ed95ee3b9b4..c5ddfc0e0cd 100644
--- a/src/main/java/org/olat/course/nodes/info/InfoCourseNodeEditController.java
+++ b/src/main/java/org/olat/course/nodes/info/InfoCourseNodeEditController.java
@@ -57,47 +57,45 @@ public class InfoCourseNodeEditController extends ActivateableTabbableDefaultCon
 	private final InfoCourseNode courseNode;
 
 	private TabbedPane myTabbedPane;
-	private VelocityContainer configContent;
-	private InfoConfigForm infoConfigForm;
+	private InfoConfigController configCtrl;
 	private VelocityContainer editAccessVc;
 	private ConditionEditController accessCondContr;
 	private ConditionEditController editCondContr;
 	private ConditionEditController adminCondContr;
 	
-	public InfoCourseNodeEditController(UserRequest ureq, WindowControl wControl, ModuleConfiguration config, InfoCourseNode courseNode, ICourse course,
-			UserCourseEnvironment euce) {
+	public InfoCourseNodeEditController(UserRequest ureq, WindowControl wControl, InfoCourseNode courseNode,
+			ICourse course, UserCourseEnvironment euce) {
 		super(ureq,wControl);
 		
 		this.courseNode = courseNode;
 		
-		infoConfigForm = new InfoConfigForm(ureq, wControl, config);
-		listenTo(infoConfigForm);
+		configCtrl = new InfoConfigController(ureq, wControl, courseNode);
+		listenTo(configCtrl);
 		
-		editAccessVc = createVelocityContainer("edit_access");
-		CourseEditorTreeModel editorModel = course.getEditorTreeModel();
-		// Accessibility precondition
-		Condition accessCondition = courseNode.getPreConditionAccess();
-		accessCondContr = new ConditionEditController(ureq, getWindowControl(), euce, accessCondition,
-				AssessmentHelper.getAssessableNodes(editorModel, courseNode));
-		listenTo(accessCondContr);
-		editAccessVc.put("readerCondition", accessCondContr.getInitialComponent());
+		if (courseNode.hasCustomPreConditions()) {
+			editAccessVc = createVelocityContainer("edit_access");
+			CourseEditorTreeModel editorModel = course.getEditorTreeModel();
+			// Accessibility precondition
+			Condition accessCondition = courseNode.getPreConditionAccess();
+			accessCondContr = new ConditionEditController(ureq, getWindowControl(), euce, accessCondition,
+					AssessmentHelper.getAssessableNodes(editorModel, courseNode));
+			listenTo(accessCondContr);
+			editAccessVc.put("readerCondition", accessCondContr.getInitialComponent());
 
-		// read / write preconditions
-		Condition editCondition = courseNode.getPreConditionEdit();
-		editCondContr = new ConditionEditController(ureq, getWindowControl(), euce, editCondition, AssessmentHelper
-				.getAssessableNodes(editorModel, courseNode));
-		listenTo(editCondContr);
-		editAccessVc.put("editCondition", editCondContr.getInitialComponent());
-		
-		// administration preconditions
-		Condition adminCondition = courseNode.getPreConditionAdmin();
-		adminCondContr = new ConditionEditController(ureq, getWindowControl(), euce, adminCondition, AssessmentHelper
-				.getAssessableNodes(editorModel, courseNode));
-		listenTo(adminCondContr);
-		editAccessVc.put("adminCondition", adminCondContr.getInitialComponent());
-		
-		configContent = createVelocityContainer("edit");
-		configContent.put("infoConfigForm", infoConfigForm.getInitialComponent());
+			// read / write preconditions
+			Condition editCondition = courseNode.getPreConditionEdit();
+			editCondContr = new ConditionEditController(ureq, getWindowControl(), euce, editCondition, AssessmentHelper
+					.getAssessableNodes(editorModel, courseNode));
+			listenTo(editCondContr);
+			editAccessVc.put("editCondition", editCondContr.getInitialComponent());
+			
+			// administration preconditions
+			Condition adminCondition = courseNode.getPreConditionAdmin();
+			adminCondContr = new ConditionEditController(ureq, getWindowControl(), euce, adminCondition, AssessmentHelper
+					.getAssessableNodes(editorModel, courseNode));
+			listenTo(adminCondContr);
+			editAccessVc.put("adminCondition", adminCondContr.getInitialComponent());
+		}
 	}
 	
 	@Override
@@ -118,8 +116,10 @@ public class InfoCourseNodeEditController extends ActivateableTabbableDefaultCon
 	@Override
 	public void addTabs(TabbedPane tabbedPane) {
 		myTabbedPane = tabbedPane;
-		tabbedPane.addTab(translate(PANE_TAB_ACCESSIBILITY), editAccessVc);
-		tabbedPane.addTab(translate(PANE_TAB_CONFIG), configContent);
+		if (editAccessVc != null) {
+			tabbedPane.addTab(translate(PANE_TAB_ACCESSIBILITY), editAccessVc);
+		}
+		tabbedPane.addTab(translate(PANE_TAB_CONFIG), configCtrl.getInitialComponent());
 	}
 
 
@@ -130,11 +130,8 @@ public class InfoCourseNodeEditController extends ActivateableTabbableDefaultCon
 
 	@Override
 	public void event(UserRequest ureq, Controller source, Event event) {
-		if (source == infoConfigForm) {
-			if (event == Event.DONE_EVENT) {
-				infoConfigForm.getUpdatedConfig();
-				fireEvent(ureq, NodeEditController.NODECONFIG_CHANGED_EVENT);
-			}
+		if (source == configCtrl) {
+			fireEvent(ureq, event);
 		} else if (source == accessCondContr) {
 			if (event == Event.CHANGED_EVENT) {
 				Condition cond = accessCondContr.getCondition();
diff --git a/src/main/java/org/olat/course/nodes/info/InfoCourseSecurityCallback.java b/src/main/java/org/olat/course/nodes/info/InfoCourseSecurityCallback.java
new file mode 100644
index 00000000000..524326e43e4
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/info/InfoCourseSecurityCallback.java
@@ -0,0 +1,56 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.course.nodes.info;
+
+import org.olat.commons.info.InfoMessage;
+import org.olat.commons.info.ui.InfoSecurityCallback;
+import org.olat.core.id.Identity;
+
+public class InfoCourseSecurityCallback implements InfoSecurityCallback {
+	private final boolean canAdd;
+	private final boolean canAdmin;
+	private final Identity identity;
+	
+	public InfoCourseSecurityCallback(Identity identity, boolean canAdd, boolean canAdmin) {
+		this.canAdd = canAdd;
+		this.canAdmin = canAdmin;
+		this.identity = identity;
+	}
+	
+	@Override
+	public boolean canRead() {
+		return true;
+	}
+
+	@Override
+	public boolean canAdd() {
+		return canAdd;
+	}
+
+	@Override
+	public boolean canEdit(InfoMessage infoMessage) {
+		return identity.equals(infoMessage.getAuthor()) || canAdmin;
+	}
+
+	@Override
+	public boolean canDelete() {
+		return canAdmin;
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/nodes/info/InfoRunController.java b/src/main/java/org/olat/course/nodes/info/InfoRunController.java
index f8fa4c9b3bf..60483ad4b30 100644
--- a/src/main/java/org/olat/course/nodes/info/InfoRunController.java
+++ b/src/main/java/org/olat/course/nodes/info/InfoRunController.java
@@ -25,7 +25,6 @@ import java.util.List;
 import java.util.StringTokenizer;
 
 import org.olat.basesecurity.GroupRoles;
-import org.olat.commons.info.InfoMessage;
 import org.olat.commons.info.InfoSubscriptionManager;
 import org.olat.commons.info.manager.MailFormatter;
 import org.olat.commons.info.ui.InfoDisplayController;
@@ -41,7 +40,6 @@ import org.olat.core.gui.components.velocity.VelocityContainer;
 import org.olat.core.gui.control.Event;
 import org.olat.core.gui.control.WindowControl;
 import org.olat.core.gui.control.controller.BasicController;
-import org.olat.core.id.Identity;
 import org.olat.core.id.OLATResourceable;
 import org.olat.core.util.StringHelper;
 import org.olat.core.util.UserSession;
@@ -89,26 +87,61 @@ public class InfoRunController extends BasicController {
 			canAdd = false;
 			canAdmin = false;
 		} else {
-			boolean isAdmin = userCourseEnv.isAdmin();
-			canAdd = isAdmin || ne.isCapabilityAccessible(InfoCourseNode.EDIT_CONDITION_ID);
-			canAdmin = isAdmin || ne.isCapabilityAccessible(InfoCourseNode.ADMIN_CONDITION_ID);
+			canAdd = canEdit(courseNode, userCourseEnv, ne);
+			canAdmin = canAdmin(courseNode, userCourseEnv, ne);
 		}
+		InfoSecurityCallback secCallback = new InfoCourseSecurityCallback(getIdentity(), canAdd, canAdmin);
 
 		boolean autoSubscribe = InfoCourseNodeEditController.getAutoSubscribe(config);
 		int maxResults = getConfigValue(config, InfoCourseNodeConfiguration.CONFIG_LENGTH, 10);
 		int duration = getConfigValue(config, InfoCourseNodeConfiguration.CONFIG_DURATION, 90);
-
-		initVC(ureq, userCourseEnv, resSubPath, canAdd, canAdmin, autoSubscribe, maxResults, duration);
+		
+		initVC(ureq, userCourseEnv, resSubPath, secCallback, autoSubscribe, maxResults, duration);
+	}
+	
+	private boolean canEdit(InfoCourseNode courseNode, UserCourseEnvironment userCourseEnv, NodeEvaluation ne) {
+		if (userCourseEnv.isAdmin()) {
+			return true;
+		}
+		
+		if (courseNode.hasCustomPreConditions()) {
+			return ne.isCapabilityAccessible(InfoCourseNode.EDIT_CONDITION_ID);
+		}
+		
+		ModuleConfiguration moduleConfig = courseNode.getModuleConfiguration();
+		if ((moduleConfig.getBooleanSafe(InfoCourseNode.CONFIG_KEY_EDIT_BY_COACH) && userCourseEnv.isCoach())
+				|| (moduleConfig.getBooleanSafe(InfoCourseNode.CONFIG_KEY_EDIT_BY_PARTICIPANT) && userCourseEnv.isParticipant())) {
+			return true;
+		}
+		
+		return false;
+	}
+	
+	private boolean canAdmin(InfoCourseNode courseNode, UserCourseEnvironment userCourseEnv, NodeEvaluation ne) {
+		if (userCourseEnv.isAdmin()) {
+			return true;
+		}
+		
+		if (courseNode.hasCustomPreConditions()) {
+			return ne.isCapabilityAccessible(InfoCourseNode.ADMIN_CONDITION_ID);
+		}
+		
+		ModuleConfiguration moduleConfig = courseNode.getModuleConfiguration();
+		if (moduleConfig.getBooleanSafe(InfoCourseNode.CONFIG_KEY_ADMIN_BY_COACH) && userCourseEnv.isCoach()) {
+			return true;
+		}
+		
+		return false;
 	}
 	
 	public InfoRunController(UserRequest ureq, WindowControl wControl, UserCourseEnvironment userCourseEnv,
-			String resSubPath, boolean canAdd, boolean canAdmin, boolean autoSubscribe) {
+			String resSubPath, InfoSecurityCallback secCallback, boolean autoSubscribe) {
 		super(ureq, wControl);
-		initVC(ureq, userCourseEnv, resSubPath, canAdd, canAdmin, autoSubscribe, -1, -1);
+		initVC(ureq, userCourseEnv, resSubPath, secCallback, autoSubscribe, -1, -1);
 	}
 
 	private void initVC(UserRequest ureq, UserCourseEnvironment userCourseEnv,
-			String resSubPath, boolean canAdd, boolean canAdmin, boolean autoSubscribe, int maxResults, int duration) {
+			String resSubPath, InfoSecurityCallback secCallback, boolean autoSubscribe, int maxResults, int duration) {
 		Long resId = userCourseEnv.getCourseEnvironment().getCourseResourceableId();
 		OLATResourceable infoResourceable = new InfoOLATResourceable(resId);
 		businessPath = normalizeBusinessPath(getWindowControl().getBusinessControl().getAsString());
@@ -125,10 +158,7 @@ public class InfoRunController extends BasicController {
 			PublisherData pdata = subscriptionManager.getInfoPublisherData(infoResourceable, businessPath);
 			subscriptionController = new ContextualSubscriptionController(ureq, getWindowControl(), subContext, pdata, autoSubscribe);
 			listenTo(subscriptionController);
-		}
-
-		InfoSecurityCallback secCallback = new InfoCourseSecurityCallback(getIdentity(), canAdd, canAdmin);
-		
+		}	
 		
 		infoDisplayController = new InfoDisplayController(ureq, getWindowControl(), maxResults, duration, secCallback, infoResourceable, resSubPath, businessPath);
 		infoDisplayController.addSendMailOptions(new SendSubscriberMailOption(infoResourceable, resSubPath, getLocale()));
@@ -201,38 +231,6 @@ public class InfoRunController extends BasicController {
 		//
 	}
 
-	private class InfoCourseSecurityCallback implements InfoSecurityCallback {
-		private final boolean canAdd;
-		private final boolean canAdmin;
-		private final Identity identity;
-		
-		public InfoCourseSecurityCallback(Identity identity, boolean canAdd, boolean canAdmin) {
-			this.canAdd = canAdd;
-			this.canAdmin = canAdmin;
-			this.identity = identity;
-		}
-		
-		@Override
-		public boolean canRead() {
-			return true;
-		}
-
-		@Override
-		public boolean canAdd() {
-			return canAdd;
-		}
-
-		@Override
-		public boolean canEdit(InfoMessage infoMessage) {
-			return identity.equals(infoMessage.getAuthor()) || canAdmin;
-		}
-
-		@Override
-		public boolean canDelete() {
-			return canAdmin;
-		}
-	}
-	
 	private class InfoOLATResourceable implements OLATResourceable {
 		private final Long resId;
 		
diff --git a/src/main/java/org/olat/course/nodes/info/_content/edit.html b/src/main/java/org/olat/course/nodes/info/_content/edit.html
deleted file mode 100644
index d83973964af..00000000000
--- a/src/main/java/org/olat/course/nodes/info/_content/edit.html
+++ /dev/null
@@ -1 +0,0 @@
-$r.render("infoConfigForm")
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/nodes/info/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/nodes/info/_i18n/LocalStrings_de.properties
index 43042c7409c..a2eec4abdf1 100644
--- a/src/main/java/org/olat/course/nodes/info/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/course/nodes/info/_i18n/LocalStrings_de.properties
@@ -3,6 +3,11 @@ title_info=Mitteilungen
 condition.accessibility.title=Mitteilungen lesen
 condition.admin.title=Mitteilungen verwalten
 condition.editable.title=Mitteilung verfassen
+config.admin=$\:condition.admin.title
+config.edit=$\:condition.editable.title
+config.rights=Benutzerberechtigungen
+config.role.coach=Betreuer
+config.role.participant=Teilnehmer
 mail.body.title=Mitteilung aus Kurs {0}
 mail.body.from=Verfasst von {0} am {1}
 mail.body.more=Weitere Mitteilungen
diff --git a/src/main/java/org/olat/course/nodes/info/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/nodes/info/_i18n/LocalStrings_en.properties
index 617278a2e9a..5bf4c527f65 100644
--- a/src/main/java/org/olat/course/nodes/info/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/course/nodes/info/_i18n/LocalStrings_en.properties
@@ -3,6 +3,11 @@ auto_subscribe=Subscribe automatically
 condition.accessibility.title=Read notifications
 condition.admin.title=Manage notifications
 condition.editable.title=Create notifications
+config.admin=$\:condition.admin.title
+config.edit=$\:condition.editable.title
+config.rights=User rights
+config.role.coach=Coach
+config.role.participant=Participant
 mail.body.title=Notification regarding course {0}
 mail.body.from=Written by {0} on {1}
 mail.body.more=Further information
diff --git a/src/main/java/org/olat/course/run/CourseRuntimeController.java b/src/main/java/org/olat/course/run/CourseRuntimeController.java
index 03ba19173b5..e5f21f4d07a 100644
--- a/src/main/java/org/olat/course/run/CourseRuntimeController.java
+++ b/src/main/java/org/olat/course/run/CourseRuntimeController.java
@@ -27,6 +27,7 @@ import java.util.concurrent.atomic.AtomicInteger;
 
 import org.olat.NewControllerFactory;
 import org.olat.commons.calendar.CalendarModule;
+import org.olat.commons.info.ui.InfoSecurityCallback;
 import org.olat.core.CoreSpringFactory;
 import org.olat.core.commons.controllers.linkchooser.CustomLinkTreeModel;
 import org.olat.core.commons.fullWebApp.LayoutMain3ColsController;
@@ -109,6 +110,7 @@ import org.olat.course.nodes.bc.CourseDocumentsController;
 import org.olat.course.nodes.co.COToolController;
 import org.olat.course.nodes.feed.blog.BlogToolController;
 import org.olat.course.nodes.fo.FOToolController;
+import org.olat.course.nodes.info.InfoCourseSecurityCallback;
 import org.olat.course.nodes.info.InfoRunController;
 import org.olat.course.nodes.members.MembersToolRunController;
 import org.olat.course.reminder.ui.CourseRemindersController;
@@ -1812,11 +1814,12 @@ public class CourseRuntimeController extends RepositoryEntryRuntimeController im
 				canAdd = isAdmin;
 				canAdmin = isAdmin;
 			}
+			InfoSecurityCallback secCallback = new InfoCourseSecurityCallback(getIdentity(), canAdd, canAdmin);
 			
 			OLATResourceable ores = OresHelper.createOLATResourceableType("ParticipantInfos");
 			WindowControl swControl = addToHistory(ureq, ores, null);
 			participatInfoCtrl = new InfoRunController(ureq, swControl, getUserCourseEnvironment(), "ParticipantInfos",
-					canAdd, canAdmin, autoSubscribe);
+					secCallback, autoSubscribe);
 
 			pushController(ureq, translate("command.participant.info"), participatInfoCtrl);
 			setActiveTool(participantInfoLink);
-- 
GitLab