From 0f96e6a216710b390840762d97f9735cc0b1d343 Mon Sep 17 00:00:00 2001
From: uhensler <urs.hensler@frentix.com>
Date: Wed, 31 Jul 2019 09:36:39 +0200
Subject: [PATCH] OO-4168: Course tool "information for participants"

---
 .../info/ui/InfoDisplayController.java        | 23 +-----
 .../LearningResourceLoggingAction.java        |  5 ++
 .../org/olat/course/config/CourseConfig.java  | 20 ++++-
 .../olat/course/config/CourseConfigEvent.java |  1 +
 .../config/ui/CourseToolbarController.java    | 55 +++++++++----
 .../ui/_i18n/LocalStrings_de.properties       |  1 +
 .../ui/_i18n/LocalStrings_en.properties       |  1 +
 .../course/nodes/info/InfoRunController.java  | 79 +++++++++++++------
 .../course/run/CourseRuntimeController.java   | 65 +++++++++++++--
 .../run/_i18n/LocalStrings_de.properties      |  3 +-
 .../run/_i18n/LocalStrings_en.properties      |  1 +
 .../RepositoryEntryManagedFlag.java           |  3 +-
 .../ui/RepositoryEntryRuntimeController.java  |  8 +-
 13 files changed, 196 insertions(+), 69 deletions(-)

diff --git a/src/main/java/org/olat/commons/info/ui/InfoDisplayController.java b/src/main/java/org/olat/commons/info/ui/InfoDisplayController.java
index 101f9c47e1c..cdf14d6a1aa 100644
--- a/src/main/java/org/olat/commons/info/ui/InfoDisplayController.java
+++ b/src/main/java/org/olat/commons/info/ui/InfoDisplayController.java
@@ -75,9 +75,7 @@ import org.olat.core.util.resource.OresHelper;
 import org.olat.core.util.vfs.VFSConstants;
 import org.olat.core.util.vfs.VFSLeaf;
 import org.olat.core.util.vfs.VFSMediaResource;
-import org.olat.course.nodes.info.InfoCourseNodeConfiguration;
 import org.olat.group.BusinessGroup;
-import org.olat.modules.ModuleConfiguration;
 import org.olat.user.UserManager;
 import org.olat.util.logging.activity.LoggingResourceable;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -115,7 +113,6 @@ public class InfoDisplayController extends FormBasicController {
 	
 	private int maxResults = 0;
 	private int maxResultsConfig = 0;
-	private int duration = -1;
 	private Date after = null;
 	private Date afterConfig = null;
 	
@@ -148,16 +145,16 @@ public class InfoDisplayController extends FormBasicController {
 		loadMessages();		
 	}
 	
-	public InfoDisplayController(UserRequest ureq, WindowControl wControl, ModuleConfiguration config,
+	public InfoDisplayController(UserRequest ureq, WindowControl wControl, int maxResults, int duration,
 			InfoSecurityCallback secCallback, OLATResourceable ores, String resSubPath, String businessPath) {
 		super(ureq, wControl, "display");
 		this.secCallback = secCallback;
 		this.ores = ores;
 		this.resSubPath = resSubPath;
 		this.businessPath = businessPath;
+		this.maxResults = maxResults;
+		this.maxResultsConfig = maxResults;
 		
-		maxResults = maxResultsConfig = getConfigValue(config, InfoCourseNodeConfiguration.CONFIG_LENGTH, 10);
-		duration = getConfigValue(config, InfoCourseNodeConfiguration.CONFIG_DURATION, 90);
 		thumbnailMapper = registerCacheableMapper(ureq, "InfoMessagesThumbnail", new ThumbnailMapper());
 		attachmentMapper = registerCacheableMapper(ureq, "InfoMessages", new AttachmentMapper());
 		
@@ -189,19 +186,7 @@ public class InfoDisplayController extends FormBasicController {
 		// now load with configuration
 		loadMessages();
 	}
-	
-	private int getConfigValue(ModuleConfiguration config, String key, int def) {
-		String durationStr = (String)config.get(key);
-		if("\u221E".equals(durationStr)) {
-			return -1;
-		} else if(StringHelper.containsNonWhitespace(durationStr)) {
-			try {
-				return Integer.parseInt(durationStr);
-			} catch(NumberFormatException e) { /* fallback to default */ }
-		}
-		return def;
-	}
-	
+
 	public List<SendMailOption> getSendMailOptions() {
 		return sendMailOptions;
 	}
diff --git a/src/main/java/org/olat/core/logging/activity/LearningResourceLoggingAction.java b/src/main/java/org/olat/core/logging/activity/LearningResourceLoggingAction.java
index 07b8d608dda..4bd406986c9 100644
--- a/src/main/java/org/olat/core/logging/activity/LearningResourceLoggingAction.java
+++ b/src/main/java/org/olat/core/logging/activity/LearningResourceLoggingAction.java
@@ -95,6 +95,10 @@ public class LearningResourceLoggingAction extends BaseLoggingAction {
 			new LearningResourceLoggingAction(ActionType.admin, CrudAction.update, ActionVerb.add, ActionObject.search).setTypeList(LEARNING_RESOURCE_OPEN_CLOSE_LIST);
 	public static final ILoggingAction REPOSITORY_ENTRY_PROPERTIES_COURSESEARCH_DISABLED = 
 			new LearningResourceLoggingAction(ActionType.admin, CrudAction.update, ActionVerb.remove, ActionObject.search).setTypeList(LEARNING_RESOURCE_OPEN_CLOSE_LIST);
+	public static final ILoggingAction REPOSITORY_ENTRY_PROPERTIES_PARTICIPANTINFO_ENABLED = 
+			new LearningResourceLoggingAction(ActionType.admin, CrudAction.update, ActionVerb.add, ActionObject.infomessage).setTypeList(LEARNING_RESOURCE_OPEN_CLOSE_LIST);
+	public static final ILoggingAction REPOSITORY_ENTRY_PROPERTIES_PARTICIPANTINFO_DISABLED = 
+			new LearningResourceLoggingAction(ActionType.admin, CrudAction.update, ActionVerb.remove, ActionObject.infomessage).setTypeList(LEARNING_RESOURCE_OPEN_CLOSE_LIST);
 	public static final ILoggingAction REPOSITORY_ENTRY_PROPERTIES_GLOSSARY_ENABLED = 
 		new LearningResourceLoggingAction(ActionType.admin, CrudAction.update, ActionVerb.add, ActionObject.glossar).setTypeList(LEARNING_RESOURCE_OPEN_CLOSE_LIST);
 	public static final ILoggingAction REPOSITORY_ENTRY_PROPERTIES_GLOSSARY_DISABLED = 
@@ -169,6 +173,7 @@ public class LearningResourceLoggingAction extends BaseLoggingAction {
 	 * @see BaseLoggingAction#BaseLoggingAction(boolean, CrudAction, ActionVerb, String)
 	 * @deprecated
 	 */
+	@Deprecated
 	LearningResourceLoggingAction(ActionType resourceActionType, CrudAction action, ActionVerb actionVerb, String actionObject) {
 		super(resourceActionType, action, actionVerb, actionObject);
 	}
diff --git a/src/main/java/org/olat/course/config/CourseConfig.java b/src/main/java/org/olat/course/config/CourseConfig.java
index bb14583ccad..4010d011b6a 100644
--- a/src/main/java/org/olat/course/config/CourseConfig.java
+++ b/src/main/java/org/olat/course/config/CourseConfig.java
@@ -72,7 +72,7 @@ public class CourseConfig implements Serializable, Cloneable {
 	/**
 	 * current config file version
 	 */
-	private static final transient int CURRENTVERSION = 14;
+	private static final transient int CURRENTVERSION = 15;
 	/**
 	 * Log levels
 	 */
@@ -119,6 +119,7 @@ public class CourseConfig implements Serializable, Cloneable {
 	 * The course search is enabled by default
 	 */
 	public static final transient String COURSESEARCH_ENABLED = "COURSESEARCH_ENABLED";
+	public static final transient String PARTICIPANT_INFO_ENABLED = "PARTICIPANT_INFO_ENABLED";
 	/**
 	 * course calendar
 	 */
@@ -195,6 +196,7 @@ public class CourseConfig implements Serializable, Cloneable {
 		configuration.put(TOOLBAR_ENABLED, Boolean.TRUE);
 		
 		configuration.put(COURSESEARCH_ENABLED, Boolean.TRUE);
+		configuration.put(PARTICIPANT_INFO_ENABLED, Boolean.FALSE);
 
 		this.version = CURRENTVERSION;
 	}
@@ -293,6 +295,12 @@ public class CourseConfig implements Serializable, Cloneable {
 				
 				this.version = 14;
 			}
+			
+			if (version == 14) {
+				if (!configuration.containsKey(PARTICIPANT_INFO_ENABLED)) configuration.put(PARTICIPANT_INFO_ENABLED, Boolean.FALSE);
+				
+				this.version = 15;
+			}
 
 			
 			/*
@@ -618,6 +626,15 @@ public class CourseConfig implements Serializable, Cloneable {
 		configuration.put(COURSESEARCH_ENABLED, Boolean.valueOf(b));
 	}
 	
+	public boolean isParticipantInfoEnabled() {
+		Boolean bool = (Boolean) configuration.get(PARTICIPANT_INFO_ENABLED);
+		return bool.booleanValue();
+	}
+	
+	public void setParticipantInfoEnabled(boolean b) {
+		configuration.put(PARTICIPANT_INFO_ENABLED, Boolean.valueOf(b));
+	}
+	
 	public boolean isToolbarEnabled() {
 		Boolean bool = (Boolean) configuration.get(TOOLBAR_ENABLED);
 		return bool.booleanValue();
@@ -665,6 +682,7 @@ public class CourseConfig implements Serializable, Cloneable {
 		clone.setToolbarEnabled(isToolbarEnabled());
 		clone.setBreadCrumbEnabled(isBreadCrumbEnabled());
 		clone.setCourseSearchEnabled(isCourseSearchEnabled());
+		clone.setParticipantInfoEnabled(isParticipantInfoEnabled());
 		return clone;
 	}
 
diff --git a/src/main/java/org/olat/course/config/CourseConfigEvent.java b/src/main/java/org/olat/course/config/CourseConfigEvent.java
index f6622b66ba9..7b303753c49 100644
--- a/src/main/java/org/olat/course/config/CourseConfigEvent.java
+++ b/src/main/java/org/olat/course/config/CourseConfigEvent.java
@@ -58,6 +58,7 @@ public class CourseConfigEvent extends MultiUserEvent {
 	
 	public enum CourseConfigType {
 		efficiencyStatement,
+		participantInfo,
 		calendar,
 		search,
 		chat,
diff --git a/src/main/java/org/olat/course/config/ui/CourseToolbarController.java b/src/main/java/org/olat/course/config/ui/CourseToolbarController.java
index f5b47189220..30a571f8565 100644
--- a/src/main/java/org/olat/course/config/ui/CourseToolbarController.java
+++ b/src/main/java/org/olat/course/config/ui/CourseToolbarController.java
@@ -68,8 +68,9 @@ public class CourseToolbarController extends FormBasicController {
 	
 	private SelectionElement toolbarEl;
 	private StaticTextElement explainEl;
-	private SelectionElement calendarEl;
 	private SelectionElement searchEl;
+	private SelectionElement participantInfoEl;
+	private SelectionElement calendarEl;
 	private SelectionElement chatEl;
 	private SelectionElement glossaryEl;
 	
@@ -127,6 +128,25 @@ public class CourseToolbarController extends FormBasicController {
 		explainEl = uifactory.addStaticTextElement("chkbx.toolbar.explain", "", formLayout);
 
 		boolean canHideToolbar = true;
+
+		boolean searchEnabled = courseConfig.isCourseSearchEnabled();
+		boolean managedSearch = RepositoryEntryManagedFlag.isManaged(entry, RepositoryEntryManagedFlag.search);
+		searchEl = uifactory.addCheckboxesHorizontal("searchIsOn", "chkbx.search.onoff", formLayout, onKeys, onValues);
+		searchEl.select(onKeys[0], searchEnabled);
+		searchEl.setEnabled(editable && !managedSearch);
+		if(managedSearch && searchEnabled) {
+			canHideToolbar &= false;
+		}
+		
+		boolean participantInfoEnabled = courseConfig.isParticipantInfoEnabled();
+		boolean managedInfo = RepositoryEntryManagedFlag.isManaged(entry, RepositoryEntryManagedFlag.participantInfo);
+		participantInfoEl = uifactory.addCheckboxesHorizontal("infoIsOn", "chkbx.participantinfo.onoff", formLayout, onKeys, onValues);
+		participantInfoEl.select(onKeys[0], participantInfoEnabled);
+		participantInfoEl.setEnabled(editable && !managedInfo);
+		if(managedInfo && participantInfoEnabled) {
+			canHideToolbar &= false;
+		}
+		
 		if(calendarModule.isEnabled() && calendarModule.isEnableCourseToolCalendar()) {
 			boolean calendarEnabled = courseConfig.isCalendarEnabled();
 			boolean managedCal = RepositoryEntryManagedFlag.isManaged(entry, RepositoryEntryManagedFlag.calendar);
@@ -140,15 +160,6 @@ public class CourseToolbarController extends FormBasicController {
 			}
 		}
 
-		boolean searchEnabled = courseConfig.isCourseSearchEnabled();
-		boolean managedSearch = RepositoryEntryManagedFlag.isManaged(entry, RepositoryEntryManagedFlag.search);
-		searchEl = uifactory.addCheckboxesHorizontal("searchIsOn", "chkbx.search.onoff", formLayout, onKeys, onValues);
-		searchEl.select(onKeys[0], searchEnabled);
-		searchEl.setEnabled(editable && !managedSearch);
-		if(managedSearch && searchEnabled) {
-			canHideToolbar &= false;
-		}
-
 		boolean chatEnabled = courseConfig.isChatEnabled();
 		boolean managedChat = RepositoryEntryManagedFlag.isManaged(entry, RepositoryEntryManagedFlag.chat);
 		chatEl = uifactory.addCheckboxesHorizontal("chatIsOn", "chkbx.chat.onoff", formLayout, onKeys, onValues);
@@ -191,20 +202,22 @@ public class CourseToolbarController extends FormBasicController {
 	}
 	
 	private boolean isAnyToolSelected() {
-		return (calendarEl != null && calendarEl.isSelected(0))
+		return searchEl.isSelected(0)
+				|| participantInfoEl.isSelected(0)
+				|| (calendarEl != null && calendarEl.isSelected(0))
 				|| chatEl.isSelected(0)
-				|| searchEl.isSelected(0)
 				|| glossaryEl.isSelected(0);
 	}
 
 	private void updateToolbar() {
 		boolean enabled = toolbarEl.isSelected(0);
 		explainEl.setVisible(enabled);
+		searchEl.setVisible(enabled);
+		participantInfoEl.setVisible(enabled);
 		if(calendarEl != null) {
 			calendarEl.setVisible(enabled);
 		}
 		chatEl.setVisible(enabled);
-		searchEl.setVisible(enabled);
 		glossaryEl.setVisible(enabled);
 	}
 
@@ -221,6 +234,10 @@ public class CourseToolbarController extends FormBasicController {
 		boolean updateSearch = courseConfig.isCourseSearchEnabled() != enableSearch;
 		courseConfig.setCourseSearchEnabled(enableSearch && toolbarEnabled);
 		
+		boolean enableParticipantInfo = searchEl.isSelected(0);
+		boolean updateParticipantInfo = courseConfig.isParticipantInfoEnabled() != enableParticipantInfo;
+		courseConfig.setParticipantInfoEnabled(enableParticipantInfo && toolbarEnabled);
+		
 		boolean enableChat = chatEl.isSelected(0);
 		boolean updateChat = courseConfig.isChatEnabled() != enableChat;
 		courseConfig.setChatIsEnabled(enableChat && toolbarEnabled);
@@ -237,7 +254,7 @@ public class CourseToolbarController extends FormBasicController {
 		CourseFactory.closeCourseEditSession(course.getResourceableId(), true);
 		
 		if(updateSearch) {
-			ILoggingAction loggingAction =enableSearch ?
+			ILoggingAction loggingAction = enableSearch ?
 					LearningResourceLoggingAction.REPOSITORY_ENTRY_PROPERTIES_COURSESEARCH_ENABLED:
 					LearningResourceLoggingAction.REPOSITORY_ENTRY_PROPERTIES_COURSESEARCH_DISABLED;
 			ThreadLocalUserActivityLogger.log(loggingAction, getClass());
@@ -245,6 +262,16 @@ public class CourseToolbarController extends FormBasicController {
 			CoordinatorManager.getInstance().getCoordinator().getEventBus()
 				.fireEventToListenersOf(new CourseConfigEvent(CourseConfigType.search, course.getResourceableId()), course);
 		}
+		
+		if(updateParticipantInfo) {
+			ILoggingAction loggingAction = enableParticipantInfo ?
+					LearningResourceLoggingAction.REPOSITORY_ENTRY_PROPERTIES_PARTICIPANTINFO_ENABLED:
+					LearningResourceLoggingAction.REPOSITORY_ENTRY_PROPERTIES_PARTICIPANTINFO_DISABLED;
+			ThreadLocalUserActivityLogger.log(loggingAction, getClass());
+			
+			CoordinatorManager.getInstance().getCoordinator().getEventBus()
+				.fireEventToListenersOf(new CourseConfigEvent(CourseConfigType.participantInfo, course.getResourceableId()), course);
+		}
 
 		if(updateChat) {
 			ILoggingAction loggingAction =enableChat ?
diff --git a/src/main/java/org/olat/course/config/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/config/ui/_i18n/LocalStrings_de.properties
index 361685f2e48..fb2df7b6054 100644
--- a/src/main/java/org/olat/course/config/ui/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/course/config/ui/_i18n/LocalStrings_de.properties
@@ -7,6 +7,7 @@ chkbx.glossary.inverse.explain=Glossar Menu in Toolbar muss unter "Toolbar" konf
 chkbx.glossary.onoff=Glossar
 chkbx.search.onoff=Kurssuche
 chkbx.menu.onoff=Menu sichtbar f\u00FCr Teilnehmer und Betreuer
+chkbx.participantinfo.onoff=Teilnehmer Infos
 chkbx.toolbar.explain=Werkzeuge in Toolbar aktivieren:
 chkbx.toolbar.onoff=Toolbar sichtbar f\u00FCr Teilnehmer
 chkbx.toolbar.off.warning=Wenn sie die Toolbar abschalten, stehen die einzelnen Werkzeuge ebenfalls nicht mehr zur Verf\u00FCgung.
diff --git a/src/main/java/org/olat/course/config/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/config/ui/_i18n/LocalStrings_en.properties
index f21a4a7831c..73df9d805f7 100644
--- a/src/main/java/org/olat/course/config/ui/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/course/config/ui/_i18n/LocalStrings_en.properties
@@ -7,6 +7,7 @@ chkbx.glossary.explain=The glossary need to be configured under "Options".
 chkbx.glossary.inverse.explain=Glossary menu in toolbar is configured under "Toolbar".
 chkbx.glossary.onoff=Glossary
 chkbx.menu.onoff=Menu visible for participants and coaches
+chkbx.participantinfo.onoff=Participant infos
 chkbx.toolbar.explain=Activate tools in toolbar:
 chkbx.toolbar.onoff=Toolbar visible for participants
 chkbx.toolbar.off.warning=If you disable the toolbar, the individual tools are also no longer available.
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 0357267bb55..a14fdcdf600 100644
--- a/src/main/java/org/olat/course/nodes/info/InfoRunController.java
+++ b/src/main/java/org/olat/course/nodes/info/InfoRunController.java
@@ -45,6 +45,7 @@ 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;
 import org.olat.core.util.resource.OresHelper;
 import org.olat.course.CourseFactory;
@@ -55,6 +56,7 @@ import org.olat.course.nodes.InfoCourseNode;
 import org.olat.course.run.userview.NodeEvaluation;
 import org.olat.course.run.userview.UserCourseEnvironment;
 import org.olat.modules.ModuleConfiguration;
+import org.olat.repository.RepositoryEntry;
 import org.springframework.beans.factory.annotation.Autowired;
 
 /**
@@ -68,11 +70,11 @@ import org.springframework.beans.factory.annotation.Autowired;
  */
 public class InfoRunController extends BasicController {
 	
-	private final VelocityContainer runVc;
-	private final InfoDisplayController infoDisplayController;
+	private VelocityContainer runVc;
+	private InfoDisplayController infoDisplayController;
 	private ContextualSubscriptionController subscriptionController;
 	
-	private final String businessPath;
+	private String businessPath;
 	
 	@Autowired
 	private InfoSubscriptionManager subscriptionManager;
@@ -81,21 +83,49 @@ public class InfoRunController extends BasicController {
 			NodeEvaluation ne, InfoCourseNode courseNode) {
 		super(ureq, wControl);
 		ModuleConfiguration config = courseNode.getModuleConfiguration();
-		
-		Long resId = userCourseEnv.getCourseEnvironment().getCourseResourceableId();
-		
 		String resSubPath = courseNode.getIdent();
+
+		boolean canAdd;
+		boolean canAdmin;
+		if(userCourseEnv.isCourseReadOnly()) {
+			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);
+		}
+
+		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);
+	}
+	
+	public InfoRunController(UserRequest ureq, WindowControl wControl, UserCourseEnvironment userCourseEnv,
+			String resSubPath, boolean canAdd, boolean canAdmin, boolean autoSubscribe) {
+		super(ureq, wControl);
+		initVC(ureq, userCourseEnv, resSubPath, canAdd, canAdmin, autoSubscribe, -1, -1);
+	}
+
+	private void initVC(UserRequest ureq, UserCourseEnvironment userCourseEnv,
+			String resSubPath, boolean canAdd, boolean canAdmin, boolean autoSubscribe, int maxResults, int duration) {
+		Long resId = userCourseEnv.getCourseEnvironment().getCourseResourceableId();
 		OLATResourceable infoResourceable = new InfoOLATResourceable(resId);
-		businessPath = normalizeBusinessPath(wControl.getBusinessControl().getAsString());
+		businessPath = normalizeBusinessPath(getWindowControl().getBusinessControl().getAsString());
 		ICourse course = CourseFactory.loadCourse(resId);
+		
 		CourseGroupManager cgm = userCourseEnv.getCourseEnvironment().getCourseGroupManager();
+		RepositoryEntry courseEntry = cgm.getCourseEntry();
+		String infoMailTitle = course.getCourseTitle();
 		
 		//manage opt-out subscription
 		UserSession usess = ureq.getUserSession();
 		if(!usess.getRoles().isGuestOnly()) {
 			SubscriptionContext subContext = subscriptionManager.getInfoSubscriptionContext(infoResourceable, resSubPath);
 			PublisherData pdata = subscriptionManager.getInfoPublisherData(infoResourceable, businessPath);
-			if(InfoCourseNodeEditController.getAutoSubscribe(config)) {
+			if(autoSubscribe) {
 				InfoSubscription infoSubscription = subscriptionManager.getInfoSubscription(usess.getGuiPreferences());
 				if(infoSubscription.subscribed(businessPath, false)) {
 					subscriptionManager.subscribe(infoResourceable, resSubPath, businessPath, getIdentity());
@@ -104,26 +134,17 @@ public class InfoRunController extends BasicController {
 			subscriptionController = new ContextualSubscriptionController(ureq, getWindowControl(), subContext, pdata);
 			listenTo(subscriptionController);
 		}
-		boolean canAdd;
-		boolean canAdmin;
-		if(userCourseEnv.isCourseReadOnly()) {
-			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);
-		}
 
 		InfoSecurityCallback secCallback = new InfoCourseSecurityCallback(getIdentity(), canAdd, canAdmin);
 		
-		infoDisplayController = new InfoDisplayController(ureq, wControl, config, secCallback, infoResourceable, resSubPath, businessPath);
+		
+		infoDisplayController = new InfoDisplayController(ureq, getWindowControl(), maxResults, duration, secCallback, infoResourceable, resSubPath, businessPath);
 		infoDisplayController.addSendMailOptions(new SendSubscriberMailOption(infoResourceable, resSubPath, getLocale()));
-		infoDisplayController.addSendMailOptions(new SendMembersMailOption(cgm.getCourseEntry(), GroupRoles.owner, translate("wizard.step1.send_option.owner")));
-		infoDisplayController.addSendMailOptions(new SendMembersMailOption(cgm.getCourseEntry(), GroupRoles.coach, translate("wizard.step1.send_option.coach")));
-		infoDisplayController.addSendMailOptions(new SendMembersMailOption(cgm.getCourseEntry(), GroupRoles.participant, translate("wizard.step1.send_option.participant")));
+		infoDisplayController.addSendMailOptions(new SendMembersMailOption(courseEntry, GroupRoles.owner, translate("wizard.step1.send_option.owner")));
+		infoDisplayController.addSendMailOptions(new SendMembersMailOption(courseEntry, GroupRoles.coach, translate("wizard.step1.send_option.coach")));
+		infoDisplayController.addSendMailOptions(new SendMembersMailOption(courseEntry, GroupRoles.participant, translate("wizard.step1.send_option.participant")));
 
-		MailFormatter mailFormatter = new SendInfoMailFormatter(course.getCourseTitle(), businessPath, getTranslator());
+		MailFormatter mailFormatter = new SendInfoMailFormatter(infoMailTitle, businessPath, getTranslator());
 		infoDisplayController.setSendMailFormatter(mailFormatter);
 		listenTo(infoDisplayController);
 
@@ -136,6 +157,18 @@ public class InfoRunController extends BasicController {
 		putInitialPanel(runVc);
 	}
 	
+	private int getConfigValue(ModuleConfiguration config, String key, int def) {
+		String durationStr = (String)config.get(key);
+		if("\u221E".equals(durationStr)) {
+			return -1;
+		} else if(StringHelper.containsNonWhitespace(durationStr)) {
+			try {
+				return Integer.parseInt(durationStr);
+			} catch(NumberFormatException e) { /* fallback to default */ }
+		}
+		return def;
+	}
+	
 	/**
 	 * Remove ROOT, remove identity context entry or duplicate, 
 	 * @param url
diff --git a/src/main/java/org/olat/course/run/CourseRuntimeController.java b/src/main/java/org/olat/course/run/CourseRuntimeController.java
index a9cc8f1ceae..31303d86ca3 100644
--- a/src/main/java/org/olat/course/run/CourseRuntimeController.java
+++ b/src/main/java/org/olat/course/run/CourseRuntimeController.java
@@ -103,6 +103,7 @@ import org.olat.course.groupsandrights.CourseRights;
 import org.olat.course.member.MembersManagementMainController;
 import org.olat.course.nodes.CourseNode;
 import org.olat.course.nodes.ENCourseNode;
+import org.olat.course.nodes.info.InfoRunController;
 import org.olat.course.reminder.ui.CourseRemindersController;
 import org.olat.course.run.calendar.CourseCalendarController;
 import org.olat.course.run.glossary.CourseGlossaryFactory;
@@ -172,6 +173,7 @@ public class CourseRuntimeController extends RepositoryEntryRuntimeController im
 		assessmentModeLink, lifeCycleChangeLink,
 		//my course
 		efficiencyStatementsLink, calendarLink, noteLink, chatLink, leaveLink, searchLink,
+		participantInfoLink,
 		//glossary
 		openGlossaryLink, enableGlossaryLink, lecturesLink;
 	private Link currentUserCountLink;
@@ -183,6 +185,7 @@ public class CourseRuntimeController extends RepositoryEntryRuntimeController im
 	private ArchiverMainController archiverCtrl;
 	private CustomDBMainController databasesCtrl;
 	private FolderRunController courseFolderCtrl;
+	private InfoRunController participatInfoCtrl;
 	private SearchInputController searchController;
 	private StatisticMainController statisticsCtrl;
 	private CourseRemindersController remindersCtrl;
@@ -778,11 +781,17 @@ public class CourseRuntimeController extends RepositoryEntryRuntimeController im
 		UserCourseEnvironment userCourseEnv = getUserCourseEnvironment();
 
 		CourseConfig cc = course.getCourseConfig();
-		if (!assessmentLock && showInfos) {
+		if (!assessmentLock && showDetails) {
 			detailsLink = LinkFactory.createToolLink("courseconfig",translate("command.courseconfig"), this, "o_icon_details");
 			toolbarPanel.addTool(detailsLink);
 		}
 		
+		if(!assessmentLock) {
+			participantInfoLink = LinkFactory.createToolLink("participantinfo", translate("command.participant.info"), this, "o_infomsg_icon");
+			participantInfoLink.setVisible(cc.isParticipantInfoEnabled());
+			toolbarPanel.addTool(participantInfoLink);
+		}
+		
 		boolean calendarIsEnabled =  !assessmentLock && !isGuestOnly && calendarModule.isEnabled()
 				&& calendarModule.isEnableCourseToolCalendar() && reSecurity.canLaunch();
 		if (calendarIsEnabled && userCourseEnv != null) {
@@ -923,6 +932,8 @@ public class CourseRuntimeController extends RepositoryEntryRuntimeController im
 			doAssessmentSurveyStatistics(ureq);
 		} else if(assessmentLink == source) {
 			doAssessmentTool(ureq);
+		} else if(participantInfoLink == source) {
+			doParticipantInfo(ureq);
 		} else if(calendarLink == source) {
 			launchCalendar(ureq);
 		} else if(chatLink == source) {
@@ -1024,6 +1035,7 @@ public class CourseRuntimeController extends RepositoryEntryRuntimeController im
 						case orders: doOrders(ureq); break;
 						case close: doClose(ureq); break;
 						case pop: popToRoot(ureq); cleanUp(); break;
+						case participantInfo: doParticipantInfo(ureq); break;
 					}
 					delayedClose = null;
 				} else {
@@ -1090,6 +1102,8 @@ public class CourseRuntimeController extends RepositoryEntryRuntimeController im
 			} else if("Settings".equalsIgnoreCase(type) || "EditDescription".equalsIgnoreCase(type)) {
 				List<ContextEntry> subEntries = entries.subList(1, entries.size());
 				doSettings(ureq, subEntries);
+			} else if("ParticipantInfos".equalsIgnoreCase(type)) {
+				doParticipantInfo(ureq);
 			} else if("Certification".equalsIgnoreCase(type)) {
 				doEfficiencyStatements(ureq);
 			} else if("Reminders".equalsIgnoreCase(type) || "RemindersLogs".equalsIgnoreCase(type)) {
@@ -1622,12 +1636,41 @@ public class CourseRuntimeController extends RepositoryEntryRuntimeController im
 		listenTo(courseSearchCalloutCtr);
 	}
 	
+	private void doParticipantInfo(UserRequest ureq) {
+		if(delayedClose == Delayed.participantInfo || requestForClose(ureq)) {
+			removeCustomCSS();
+			
+			boolean autoSubscribe = false;
+			boolean canAdd;
+			boolean canAdmin;
+			if(getUserCourseEnvironment().isCourseReadOnly()) {
+				canAdd = false;
+				canAdmin = false;
+			} else {
+				boolean isAdmin = getUserCourseEnvironment().isAdmin();
+				canAdd = isAdmin;
+				canAdmin = isAdmin;
+			}
+			
+			OLATResourceable ores = OresHelper.createOLATResourceableType("participantInfos");
+			WindowControl swControl = addToHistory(ureq, ores, null);
+			participatInfoCtrl = new InfoRunController(ureq, swControl, getUserCourseEnvironment(), "participantInfos",
+					canAdd, canAdmin, autoSubscribe);
+
+			pushController(ureq, translate("command.participant.info"), participatInfoCtrl);
+			setActiveTool(participantInfoLink);
+			currentToolCtr = participatInfoCtrl;
+		} else {
+			delayedClose = Delayed.participantInfo;
+		};
+	}
+	
 	private void launchCalendar(UserRequest ureq) {
 		ControllerCreator ctrlCreator = (lureq, lwControl) -> {
 			ICourse course = CourseFactory.loadCourse(getRepositoryEntry());
 			ContextEntry ce = BusinessControlFactory.getInstance().createContextEntry(getRepositoryEntry());
 			WindowControl llwControl = BusinessControlFactory.getInstance().createBusinessWindowControl(ce, lwControl);
-			CourseCalendarController calendarController = new CourseCalendarController(lureq, llwControl, getUserCourseEnvironment());					
+			CourseCalendarController calendarController = new CourseCalendarController(lureq, llwControl, getUserCourseEnvironment());
 			// use a one-column main layout
 			LayoutMain3ColsController layoutCtr = new LayoutMain3ColsController(lureq, llwControl, calendarController);
 			layoutCtr.setCustomCSS(CourseFactory.getCustomCourseCss(lureq.getUserSession(), course.getCourseEnvironment()));
@@ -1729,6 +1772,15 @@ public class CourseRuntimeController extends RepositoryEntryRuntimeController im
 
 	private void processCourseConfigEvent(CourseConfigEvent event) {
 		switch(event.getType()) {
+			case search: {
+				if(searchLink != null) {
+					ICourse course = CourseFactory.loadCourse(getRepositoryEntry());
+					CourseConfig cc = course.getCourseEnvironment().getCourseConfig();
+					searchLink.setVisible(cc.isCourseSearchEnabled());
+					toolbarPanel.setDirty(true);
+				}
+				break;
+			}
 			case calendar: {
 				if(calendarLink != null) {
 					ICourse course = CourseFactory.loadCourse(getRepositoryEntry());
@@ -1738,11 +1790,11 @@ public class CourseRuntimeController extends RepositoryEntryRuntimeController im
 				}
 				break;
 			}
-			case search: {
-				if(searchLink != null) {
+			case participantInfo: {
+				if(participantInfoLink != null) {
 					ICourse course = CourseFactory.loadCourse(getRepositoryEntry());
 					CourseConfig cc = course.getCourseEnvironment().getCourseConfig();
-					searchLink.setVisible(cc.isCourseSearchEnabled());
+					calendarLink.setVisible(cc.isCalendarEnabled() && calendarModule.isEnabled() && calendarModule.isEnableCourseToolCalendar());
 					toolbarPanel.setDirty(true);
 				}
 				break;
@@ -1883,6 +1935,7 @@ public class CourseRuntimeController extends RepositoryEntryRuntimeController im
 		members,
 		orders,
 		close,
-		pop
+		pop,
+		participantInfo
 	}
 }
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/run/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/run/_i18n/LocalStrings_de.properties
index f2a30ca4e58..d328a1db150 100644
--- a/src/main/java/org/olat/course/run/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/course/run/_i18n/LocalStrings_de.properties
@@ -14,7 +14,7 @@ command.coursefolder=Ablageordner
 command.coursesearch=Kurssuche
 command.efficiencystatement=Leistungsnachweis
 command.glossary=Glossar
-command.glossary.open=Glossar in separatem Fenster \u00f6ffnen
+command.glossary.open=Glossar in separatem Fenster \u00F6ffnen
 command.glossary.off=aus
 command.glossary.off.alt=Glossarbegriffe von Lerninhalt ausblenden
 command.glossary.on=ein
@@ -36,6 +36,7 @@ command.options=Optionen
 command.options.certificates=Leistungnachweis
 command.options.lectures.admin=Lektionen und Absenzen
 command.options.reminders=Erinnerung
+command.participant.info=Teilnehmerinfos
 command.personalnote=Notizen
 command.previous=Zur\u00FCck zur letzten Seite
 command.reminders=Erinnerung
diff --git a/src/main/java/org/olat/course/run/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/run/_i18n/LocalStrings_en.properties
index 7869161e707..368375a782c 100644
--- a/src/main/java/org/olat/course/run/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/course/run/_i18n/LocalStrings_en.properties
@@ -36,6 +36,7 @@ command.options=Options
 command.options.certificates=Evidence of achievement
 command.options.lectures.admin=Lectures and absences
 command.options.reminders=Reminders
+command.participant.info=Participant infos
 command.personalnote=Notes
 command.previous=Go to previous page
 command.reminders=Reminders
diff --git a/src/main/java/org/olat/repository/RepositoryEntryManagedFlag.java b/src/main/java/org/olat/repository/RepositoryEntryManagedFlag.java
index 8335710926b..325d5bdbd6d 100644
--- a/src/main/java/org/olat/repository/RepositoryEntryManagedFlag.java
+++ b/src/main/java/org/olat/repository/RepositoryEntryManagedFlag.java
@@ -21,8 +21,8 @@ package org.olat.repository;
 
 import java.util.Arrays;
 
-import org.olat.core.CoreSpringFactory;
 import org.apache.logging.log4j.Logger;
+import org.olat.core.CoreSpringFactory;
 import org.olat.core.logging.Tracing;
 import org.olat.core.util.StringHelper;
 
@@ -47,6 +47,7 @@ public enum RepositoryEntryManagedFlag {
     settings(all),//max num of participants...
       access(settings,all),
       search(settings, all),
+      participantInfo(settings, all),
       chat(settings,all),
       layout(settings,all),
       resourcefolder(settings,all),
diff --git a/src/main/java/org/olat/repository/ui/RepositoryEntryRuntimeController.java b/src/main/java/org/olat/repository/ui/RepositoryEntryRuntimeController.java
index 640b2d205fa..e13aad62a35 100644
--- a/src/main/java/org/olat/repository/ui/RepositoryEntryRuntimeController.java
+++ b/src/main/java/org/olat/repository/ui/RepositoryEntryRuntimeController.java
@@ -148,7 +148,7 @@ public class RepositoryEntryRuntimeController extends MainLayoutBasicController
 	protected RepositoryEntrySecurity reSecurity;
 	protected final Roles roles;
 
-	protected final boolean showInfos;
+	protected final boolean showDetails;
 	protected final boolean allowBookmark;
 	
 	protected boolean corrupted;
@@ -190,7 +190,7 @@ public class RepositoryEntryRuntimeController extends MainLayoutBasicController
 	}
 
 	public RepositoryEntryRuntimeController(UserRequest ureq, WindowControl wControl, RepositoryEntry re,
-			RepositoryEntrySecurity reSecurity, RuntimeControllerCreator runtimeControllerCreator, boolean allowBookmark, boolean showInfos) {
+			RepositoryEntrySecurity reSecurity, RuntimeControllerCreator runtimeControllerCreator, boolean allowBookmark, boolean showDetails) {
 		super(ureq, wControl);
 		setTranslator(Util.createPackageTranslator(RepositoryService.class, getLocale(), getTranslator()));
 		
@@ -208,7 +208,7 @@ public class RepositoryEntryRuntimeController extends MainLayoutBasicController
 		}
 		
 		this.re = re;
-		this.showInfos = showInfos;
+		this.showDetails = showDetails;
 		this.allowBookmark = allowBookmark;
 		this.runtimeControllerCreator = runtimeControllerCreator;
 		organisations = repositoryService.getOrganisationReferences(re);
@@ -372,7 +372,7 @@ public class RepositoryEntryRuntimeController extends MainLayoutBasicController
 		detailsLink = LinkFactory.createToolLink("details", translate("details.header"), this, "o_sel_repo_details");
 		detailsLink.setIconLeftCSS("o_icon o_icon-fw o_icon_details");
 		detailsLink.setElementCssClass("o_sel_author_details");
-		detailsLink.setVisible(showInfos);
+		detailsLink.setVisible(showDetails);
 		toolbarPanel.addTool(detailsLink);
 		
 		boolean marked = markManager.isMarked(re, getIdentity(), null);
-- 
GitLab