From 5ff5f2f026839f2d128f25d1ce48d1639c31351f Mon Sep 17 00:00:00 2001
From: uhensler <urs.hensler@frentix.com>
Date: Tue, 6 Aug 2019 15:43:29 +0200
Subject: [PATCH] OO-4168: Course tool "E-mail"

---
 .../LearningResourceLoggingAction.java        |   4 +
 .../org/olat/course/config/CourseConfig.java  | 169 ++++--------------
 .../olat/course/config/CourseConfigEvent.java |   5 +-
 .../config/ui/CourseToolbarController.java    |  28 ++-
 .../ui/_i18n/LocalStrings_de.properties       |   1 +
 .../ui/_i18n/LocalStrings_en.properties       |   1 +
 .../course/nodes/co/COEditController.java     |  21 +--
 .../olat/course/nodes/co/CORunController.java |  11 +-
 .../course/nodes/co/COToolController.java     | 147 +++++++++++++++
 .../nodes/co/COToolRecipientsController.java  | 124 +++++++++++++
 .../olat/course/nodes/co/_content/tool.html   |   2 +
 .../nodes/co/_i18n/LocalStrings_de.properties |  19 +-
 .../nodes/co/_i18n/LocalStrings_en.properties |  21 +--
 .../ui/group/_i18n/LocalStrings_en.properties |   2 +-
 .../course/run/CourseRuntimeController.java   |  45 ++++-
 .../run/_i18n/LocalStrings_de.properties      |   1 +
 .../run/_i18n/LocalStrings_en.properties      |   1 +
 .../java/org/olat/modules/co/ContactForm.java |  23 ++-
 .../modules/co/ContactFormController.java     |   6 +
 .../RepositoryEntryManagedFlag.java           |   1 +
 20 files changed, 433 insertions(+), 199 deletions(-)
 create mode 100644 src/main/java/org/olat/course/nodes/co/COToolController.java
 create mode 100644 src/main/java/org/olat/course/nodes/co/COToolRecipientsController.java
 create mode 100644 src/main/java/org/olat/course/nodes/co/_content/tool.html

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 361b06045bf..ba5d8cbb3f3 100644
--- a/src/main/java/org/olat/core/logging/activity/LearningResourceLoggingAction.java
+++ b/src/main/java/org/olat/core/logging/activity/LearningResourceLoggingAction.java
@@ -103,6 +103,10 @@ public class LearningResourceLoggingAction extends BaseLoggingAction {
 			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_EMAIL_ENABLED = 
+			new LearningResourceLoggingAction(ActionType.admin, CrudAction.update, ActionVerb.add, ActionObject.mail).setTypeList(LEARNING_RESOURCE_OPEN_CLOSE_LIST);
+	public static final ILoggingAction REPOSITORY_ENTRY_PROPERTIES_EMAIL_DISABLED = 
+			new LearningResourceLoggingAction(ActionType.admin, CrudAction.update, ActionVerb.remove, ActionObject.mail).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 = 
diff --git a/src/main/java/org/olat/course/config/CourseConfig.java b/src/main/java/org/olat/course/config/CourseConfig.java
index b77f29092ce..9e059823e5a 100644
--- a/src/main/java/org/olat/course/config/CourseConfig.java
+++ b/src/main/java/org/olat/course/config/CourseConfig.java
@@ -73,25 +73,11 @@ public class CourseConfig implements Serializable, Cloneable {
 	 * current config file version
 	 */
 	private static final transient int CURRENTVERSION = 15;
-	/**
-	 * Log levels
-	 */
+	
 	public static final transient String KEY_LOGLEVEL_ADMIN = "LOGLEVELADMIN";
-	/**
-	 * Log levels
-	 */
 	public static final transient String KEY_LOGLEVEL_USER = "LOGLEVELUSER";
-	/**
-	 * Log levels
-	 */
 	public static final transient String KEY_LOGLEVEL_STATISTIC = "LOGLEVELSTAT";
-	/**
-	 * Chat enabled boolean
-	 */
-	public static final transient String KEY_CHAT_ENABLED = "COURSE_CHAT_ENABLED";
-	/**
-	 * efficency statement
-	 */
+
 	public static final transient String KEY_EFFICENCY_ENABLED = "KEY_EFFICENCY_ENABLED";
 	public static final transient String CERTIFICATE_AUTO_ENABLED = "CERTIFICATE_AUTO";
 	public static final transient String CERTIFICATE_MANUAL_ENABLED = "CERTIFICATE_MANUAL";
@@ -103,49 +89,24 @@ public class CourseConfig implements Serializable, Cloneable {
 	public static final transient String RECERTIFICATION_TIMELAPSE = "RECERTIFICATION_TIMELAPSE";
 	public static final transient String RECERTIFICATION_TIMELAPSE_UNIT = "RECERTIFICATION_TIMELAPSE_UNIT";
 	
-	/**
-	 * The menu is enabled by default
-	 */
 	public static final transient String MENU_ENABLED = "MENU_ENABLED";
-	/**
-	 * The toolbar is enabled by default
-	 */
 	public static final transient String TOOLBAR_ENABLED = "TOOLBAR_ENABLED";
-	/**
-	 * The bread crumb is enabled by default
-	 */
 	public static final transient String BREADCRUMB_ENABLED = "BREADCRUMB_ENABLED";
-	/**
-	 * The course search is enabled by default
-	 */
+	
 	public static final transient String COURSESEARCH_ENABLED = "COURSESEARCH_ENABLED";
+	public static final transient String KEY_CHAT_ENABLED = "COURSE_CHAT_ENABLED";
 	public static final transient String PARTICIPANT_LIST_ENABLED = "PARTICIPANT_LIST_ENABLED";
 	public static final transient String PARTICIPANT_INFO_ENABLED = "PARTICIPANT_INFO_ENABLED";
-	/**
-	 * course calendar
-	 */
+	public static final transient String EMAIL_ENABLED = "EMAIL_ENABLED";
 	public static final transient String KEY_CALENDAR_ENABLED = "KEY_CALENDAR_ENABLED";
 	
-	/**
-	 * course glossary in toolbar enabled
-	 */
 	public static final transient String KEY_GLOSSARY_ENABLED = "KEY_GLOSSARY_ENABLED";
-	/**
-	 * course glossary
-	 */
 	public static final transient String KEY_GLOSSARY_SOFTKEY = "KEY_GLOSSARY_SOFTKEY";
-	/**
-	 * 
-	 */
 	public static final transient String KEY_CSS_FILEREF = "CSS_FILEREF";
-	/**
-	 * 
-	 */
+
 	public static final transient String KEY_SHAREDFOLDER_SOFTKEY = "SHAREDFOLDER_SOFTKEY";
-	/**
-	 * 
-	 */
 	public static final transient String KEY_SHAREDFOLDER_READONLY = "SHAREDFOLDER_RO";
+
 	/**
 	 * current key set
 	 */
@@ -158,18 +119,22 @@ public class CourseConfig implements Serializable, Cloneable {
 	 * holds the configuration
 	 */
 	private Map<String,Object> configuration = new Hashtable<>();
-
+	
 	public CourseConfig() {
-	// empty, for XSTream
+		// empty, for XSTream
 	}
-
+	
 	/**
 	 * @return version of this loaded/created instance
 	 */
 	public int getVersion() {
 		return version;
 	}
-
+	
+	public void setVersion(int version) {
+		this.version = version;
+	}
+	
 	/**
 	 * initialize with default values
 	 */
@@ -192,13 +157,13 @@ public class CourseConfig implements Serializable, Cloneable {
 		configuration.remove(KEY_LOGLEVEL_USER);
 		configuration.remove(KEY_LOGLEVEL_STATISTIC);
 		
-
 		configuration.put(MENU_ENABLED, Boolean.TRUE);
 		configuration.put(TOOLBAR_ENABLED, Boolean.TRUE);
 		
 		configuration.put(COURSESEARCH_ENABLED, Boolean.TRUE);
 		configuration.put(PARTICIPANT_LIST_ENABLED, Boolean.FALSE);
 		configuration.put(PARTICIPANT_INFO_ENABLED, Boolean.FALSE);
+		configuration.put(EMAIL_ENABLED, Boolean.FALSE);
 
 		this.version = CURRENTVERSION;
 	}
@@ -217,47 +182,41 @@ public class CourseConfig implements Serializable, Cloneable {
 	public boolean resolveVersionIssues() {
 		boolean versionChanged = false;
 		if (version < CURRENTVERSION) {
-			// from version 1 -> 2
 			if (version == 1) {
 				this.version = 2;
 			}
-			// from version 2 -> 3
+			
 			if (version == 2) {
-				// add the new key
 				if (!configuration.containsKey(KEY_CHAT_ENABLED)) configuration.put(KEY_CHAT_ENABLED, Boolean.TRUE);
 				this.version = 3;
 			}
-			// from version 3 -> 4
+			
 			if (version == 3) {
-				// add the new key
 				if (!configuration.containsKey(KEY_CSS_FILEREF)) configuration.put(KEY_CSS_FILEREF, VALUE_EMPTY_CSS_FILEREF);
 				this.version = 4;
 			}
-			// from version 4 -> 5
+			
 			if (version == 4) {
-				// add the new key
 				if (!configuration.containsKey(KEY_SHAREDFOLDER_SOFTKEY)) configuration.put(KEY_SHAREDFOLDER_SOFTKEY,
 						VALUE_EMPTY_SHAREDFOLDER_SOFTKEY);
 				this.version = 5;
 			}
-			// from version 5 -> 6
+			
 			if (version == 5) {
-				// add the new key
 				if (!configuration.containsKey(KEY_EFFICENCY_ENABLED)) configuration.put(KEY_EFFICENCY_ENABLED, Boolean.FALSE);
 				this.version = 6;
 			}
-			// from version 6 -> 7
+			
 			if (version == 6) {
-				// add the new key
 				if (!configuration.containsKey(KEY_CALENDAR_ENABLED)) configuration.put(KEY_CALENDAR_ENABLED, Boolean.TRUE);
 				this.version = 7;
 			}
-			// from version 7 -> 8
+			
 			if (version == 7) {
 				// glossary configuration has been added. no default values needed
 				this.version = 8;
 			}
-			// from version 8 -> 9
+
 			if (version == 8) {
 				if (configuration.containsKey(KEY_LOGLEVEL_ADMIN)) configuration.remove(KEY_LOGLEVEL_ADMIN);
 				if (configuration.containsKey(KEY_LOGLEVEL_USER)) configuration.remove(KEY_LOGLEVEL_USER);
@@ -284,7 +243,7 @@ public class CourseConfig implements Serializable, Cloneable {
 				if (!configuration.containsKey(COURSESEARCH_ENABLED)) configuration.put(COURSESEARCH_ENABLED, Boolean.FALSE);
 				this.version = 12;
 			}
-
+			
 			if (version == 12) {
 				if (!configuration.containsKey(BREADCRUMB_ENABLED)) configuration.put(BREADCRUMB_ENABLED, Boolean.TRUE);
 				this.version = 13;
@@ -301,6 +260,7 @@ public class CourseConfig implements Serializable, Cloneable {
 			if (version == 14) {
 				if (!configuration.containsKey(PARTICIPANT_LIST_ENABLED)) configuration.put(PARTICIPANT_LIST_ENABLED, Boolean.FALSE);
 				if (!configuration.containsKey(PARTICIPANT_INFO_ENABLED)) configuration.put(PARTICIPANT_INFO_ENABLED, Boolean.FALSE);
+				if (!configuration.containsKey(EMAIL_ENABLED)) configuration.put(EMAIL_ENABLED, Boolean.FALSE);
 				
 				this.version = 15;
 			}
@@ -335,37 +295,24 @@ public class CourseConfig implements Serializable, Cloneable {
 		return versionChanged;
 	}
 
-	/**
-	 * @return if chat is enabled
-	 */
 	public boolean isChatEnabled() {
 		Boolean bool = (Boolean) configuration.get(KEY_CHAT_ENABLED);
 		return bool != null && bool.booleanValue();
 	}
 
-	/**
-	 * @param b
-	 */
 	public void setChatIsEnabled(boolean b) {
 		configuration.put(KEY_CHAT_ENABLED, Boolean.valueOf(b));
 	}
 	
-	/**
-	 * @return if chat is enabled
-	 */
 	public boolean isGlossaryEnabled() {
 		Boolean bool = (Boolean) configuration.get(KEY_GLOSSARY_ENABLED);
 		return bool != null && bool.booleanValue();
 	}
 
-	/**
-	 * @param b
-	 */
 	public void setGlossaryIsEnabled(boolean b) {
 		configuration.put(KEY_GLOSSARY_ENABLED, Boolean.valueOf(b));
 	}
 	
-
 	/**
 	 * set the course layout by adding a reference to a css file, or disabling
 	 * custom layout by adding the empty css fileref
@@ -407,23 +354,14 @@ public class CourseConfig implements Serializable, Cloneable {
 		return (String) configuration.get(KEY_GLOSSARY_SOFTKEY);
 	}
 
-	/**
-	 * @return boolean
-	 */
 	public boolean hasGlossary() {
 		return (getGlossarySoftKey() != null);
 	}
 
-	/**
-	 * @return boolean
-	 */
 	public boolean hasCustomCourseCSS() {
 		return !(VALUE_EMPTY_CSS_FILEREF.equals(getCssLayoutRef()));
 	}
 
-	/**
-	 * @param softkey
-	 */
 	public void setSharedFolderSoftkey(String softkey) {
 		if(softkey == null) {
 			configuration.put(KEY_SHAREDFOLDER_SOFTKEY, VALUE_EMPTY_SHAREDFOLDER_SOFTKEY);
@@ -432,16 +370,10 @@ public class CourseConfig implements Serializable, Cloneable {
 		}
 	}
 
-	/**
-	 * @return softkey
-	 */
 	public String getSharedFolderSoftkey() {
 		return (String) configuration.get(KEY_SHAREDFOLDER_SOFTKEY);
 	}
 
-	/**
-	 * @return boolean
-	 */
 	public boolean hasCustomSharedFolder() {
 		return !(VALUE_EMPTY_SHAREDFOLDER_SOFTKEY.equals(getSharedFolderSoftkey()));
 	}
@@ -455,25 +387,15 @@ public class CourseConfig implements Serializable, Cloneable {
 		configuration.put(KEY_SHAREDFOLDER_READONLY, Boolean.valueOf(mount));
 	}
 
-	/**
-	 * @param b
-	 */
 	public void setEfficencyStatementIsEnabled(boolean b) {
 		configuration.put(KEY_EFFICENCY_ENABLED, Boolean.valueOf(b));
 	}
 
-	/**
-	 * @return true if the efficency statement is enabled
-	 */
 	public boolean isEfficencyStatementEnabled() {
 		Boolean bool = (Boolean) configuration.get(KEY_EFFICENCY_ENABLED);
 		return bool.booleanValue();
 	}
 	
-	
-	/**
-	 * @return true if the efficency statement is enabled
-	 */
 	public Long getCertificateTemplate() {
 		Object templateIdObj = configuration.get(CERTIFICATE_TEMPLATE);
 		Long templateId = null;
@@ -519,9 +441,6 @@ public class CourseConfig implements Serializable, Cloneable {
 		}
 	}
 	
-	/**
-	 * @param b
-	 */
 	public void setCertificateTemplate(Long templateId ) {
 		if(templateId != null) {
 			configuration.put(CERTIFICATE_TEMPLATE, templateId);
@@ -552,17 +471,11 @@ public class CourseConfig implements Serializable, Cloneable {
 		configuration.put(CERTIFICATE_MANUAL_ENABLED, Boolean.valueOf(enabled));
 	}
 
-	/**
-	 * @return true if the efficency statement is enabled
-	 */
 	public boolean isRecertificationEnabled() {
 		Boolean bool = (Boolean) configuration.get(RECERTIFICATION_ENABLED);
 		return bool != null && bool.booleanValue();
 	}
 	
-	/**
-	 * @param b
-	 */
 	public void setRecertificationEnabled(boolean b) {
 		configuration.put(RECERTIFICATION_ENABLED, Boolean.valueOf(b));
 	}
@@ -596,17 +509,11 @@ public class CourseConfig implements Serializable, Cloneable {
 		}
 	}
 
-	/**
-	 * @return true if calendar is enabled
-	 */
 	public boolean isCalendarEnabled() {
 		Boolean bool = (Boolean) configuration.get(KEY_CALENDAR_ENABLED);
 		return bool != null && bool.booleanValue();
 	}
 
-	/**
-	 * @param b
-	 */
 	public void setCalendarEnabled(boolean b) {
 		configuration.put(KEY_CALENDAR_ENABLED, Boolean.valueOf(b));
 	}
@@ -647,6 +554,15 @@ public class CourseConfig implements Serializable, Cloneable {
 		configuration.put(PARTICIPANT_INFO_ENABLED, Boolean.valueOf(b));
 	}
 	
+	public boolean isEmailEnabled() {
+		Boolean bool = (Boolean) configuration.get(EMAIL_ENABLED);
+		return bool.booleanValue();
+	}
+	
+	public void setEmailEnabled(boolean b) {
+		configuration.put(EMAIL_ENABLED, Boolean.valueOf(b));
+	}
+	
 	public boolean isToolbarEnabled() {
 		Boolean bool = (Boolean) configuration.get(TOOLBAR_ENABLED);
 		return bool.booleanValue();
@@ -665,11 +581,6 @@ public class CourseConfig implements Serializable, Cloneable {
 		configuration.put(BREADCRUMB_ENABLED, Boolean.valueOf(b));
 	}
 
-	/**
-	 * Creates a deep clone for the current object.
-	 * 
-	 * @see java.lang.Object#clone()
-	 */
 	@Override
 	public CourseConfig clone() {
 		CourseConfig clone = new CourseConfig();
@@ -696,13 +607,10 @@ public class CourseConfig implements Serializable, Cloneable {
 		clone.setCourseSearchEnabled(isCourseSearchEnabled());
 		clone.setParticipantListEnabled(isParticipantListEnabled());
 		clone.setParticipantInfoEnabled(isParticipantInfoEnabled());
+		clone.setEmailEnabled(isEmailEnabled());
 		return clone;
 	}
 
-	/**
-	 * 
-	 * @see java.lang.Object#equals(java.lang.Object)
-	 */
 	@Override
 	public boolean equals(Object obj) {
 		if(obj == this) {
@@ -735,11 +643,4 @@ public class CourseConfig implements Serializable, Cloneable {
 		return false;
 	}
 
-	/**
-	 * @param version The version to set.
-	 */
-	public void setVersion(int version) {
-		this.version = version;
-	}
-
 }
diff --git a/src/main/java/org/olat/course/config/CourseConfigEvent.java b/src/main/java/org/olat/course/config/CourseConfigEvent.java
index ee23318ddf4..9265caad496 100644
--- a/src/main/java/org/olat/course/config/CourseConfigEvent.java
+++ b/src/main/java/org/olat/course/config/CourseConfigEvent.java
@@ -58,10 +58,11 @@ public class CourseConfigEvent extends MultiUserEvent {
 	
 	public enum CourseConfigType {
 		efficiencyStatement,
+		search,
+		calendar,
 		participantList,
 		participantInfo,
-		calendar,
-		search,
+		email,
 		chat,
 		glossary,
 		layout
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 c431586758f..2e7b52cc514 100644
--- a/src/main/java/org/olat/course/config/ui/CourseToolbarController.java
+++ b/src/main/java/org/olat/course/config/ui/CourseToolbarController.java
@@ -72,6 +72,7 @@ public class CourseToolbarController extends FormBasicController {
 	private SelectionElement calendarEl;
 	private SelectionElement participantListEl;
 	private SelectionElement participantInfoEl;
+	private SelectionElement emailEl;
 	private SelectionElement chatEl;
 	private SelectionElement glossaryEl;
 	
@@ -170,6 +171,15 @@ public class CourseToolbarController extends FormBasicController {
 			canHideToolbar &= false;
 		}
 
+		boolean emailEnabled = courseConfig.isEmailEnabled();
+		boolean managedEmail = RepositoryEntryManagedFlag.isManaged(entry, RepositoryEntryManagedFlag.email);
+		emailEl = uifactory.addCheckboxesHorizontal("emailIsOn", "chkbx.email.onoff", formLayout, onKeys, onValues);
+		emailEl.select(onKeys[0], emailEnabled);
+		emailEl.setEnabled(editable && !managedEmail);
+		if(managedEmail && emailEnabled) {
+			canHideToolbar &= false;
+		}
+		
 		boolean chatEnabled = courseConfig.isChatEnabled();
 		boolean managedChat = RepositoryEntryManagedFlag.isManaged(entry, RepositoryEntryManagedFlag.chat);
 		chatEl = uifactory.addCheckboxesHorizontal("chatIsOn", "chkbx.chat.onoff", formLayout, onKeys, onValues);
@@ -216,6 +226,7 @@ public class CourseToolbarController extends FormBasicController {
 				|| (calendarEl != null && calendarEl.isSelected(0))
 				|| participantListEl.isSelected(0)
 				|| participantInfoEl.isSelected(0)
+				|| emailEl.isSelected(0)
 				|| chatEl.isSelected(0)
 				|| glossaryEl.isSelected(0);
 	}
@@ -229,6 +240,7 @@ public class CourseToolbarController extends FormBasicController {
 		}
 		participantListEl.setVisible(enabled);
 		participantInfoEl.setVisible(enabled);
+		emailEl.setVisible(enabled);
 		chatEl.setVisible(enabled);
 		glossaryEl.setVisible(enabled);
 	}
@@ -258,6 +270,10 @@ public class CourseToolbarController extends FormBasicController {
 		boolean updateParticipantInfo = courseConfig.isParticipantInfoEnabled() != enableParticipantInfo;
 		courseConfig.setParticipantInfoEnabled(enableParticipantInfo && toolbarEnabled);
 		
+		boolean enableEmail = emailEl.isSelected(0);
+		boolean updateEmail = courseConfig.isEmailEnabled() != enableEmail;
+		courseConfig.setEmailEnabled(enableEmail && toolbarEnabled);
+		
 		boolean enableChat = chatEl.isSelected(0);
 		boolean updateChat = courseConfig.isChatEnabled() != enableChat;
 		courseConfig.setChatIsEnabled(enableChat && toolbarEnabled);
@@ -300,7 +316,7 @@ public class CourseToolbarController extends FormBasicController {
 			CoordinatorManager.getInstance().getCoordinator().getEventBus()
 				.fireEventToListenersOf(new CourseConfigEvent(CourseConfigType.participantList, course.getResourceableId()), course);
 		}
-		
+
 		if(updateParticipantInfo) {
 			ILoggingAction loggingAction = enableParticipantInfo ?
 					LearningResourceLoggingAction.REPOSITORY_ENTRY_PROPERTIES_PARTICIPANTINFO_ENABLED:
@@ -311,6 +327,16 @@ public class CourseToolbarController extends FormBasicController {
 				.fireEventToListenersOf(new CourseConfigEvent(CourseConfigType.participantInfo, course.getResourceableId()), course);
 		}
 		
+		if(updateEmail) {
+			ILoggingAction loggingAction = enableEmail ?
+					LearningResourceLoggingAction.REPOSITORY_ENTRY_PROPERTIES_EMAIL_ENABLED:
+					LearningResourceLoggingAction.REPOSITORY_ENTRY_PROPERTIES_EMAIL_DISABLED;
+			ThreadLocalUserActivityLogger.log(loggingAction, getClass());
+			
+			CoordinatorManager.getInstance().getCoordinator().getEventBus()
+				.fireEventToListenersOf(new CourseConfigEvent(CourseConfigType.email, course.getResourceableId()), course);
+		}
+		
 		if(updateChat) {
 			ILoggingAction loggingAction =enableChat ?
 					LearningResourceLoggingAction.REPOSITORY_ENTRY_PROPERTIES_IM_ENABLED:
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 b5b1594ea8f..c69e1b4036d 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
@@ -2,6 +2,7 @@
 chkbx.calendar.onoff=Kurskalender
 chkbx.chat.onoff=Kurs-Chat
 chkbx.efficency.onoff=Leistungsnachweis verwenden
+chkbx.email.onoff=E-Mail
 chkbx.glossary.explain=Das Glossar muss unter "Optionen" konfiguriert werden.
 chkbx.glossary.inverse.explain=Glossar Menu in Toolbar muss unter "Toolbar" konfiguriert werden.
 chkbx.glossary.onoff=Glossar
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 64e7109a265..6007cec9805 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
@@ -2,6 +2,7 @@
 chkbx.calendar.onoff=Course calendar
 chkbx.chat.onoff=Course chat
 chkbx.search.onoff=Course search
+chkbx.email.onoff=E-mail
 chkbx.efficency.onoff=Use evidence of achievement
 chkbx.glossary.explain=The glossary need to be configured under "Options".
 chkbx.glossary.inverse.explain=Glossary menu in toolbar is configured under "Toolbar".
diff --git a/src/main/java/org/olat/course/nodes/co/COEditController.java b/src/main/java/org/olat/course/nodes/co/COEditController.java
index 4dd90c7845b..21fbd8146a2 100755
--- a/src/main/java/org/olat/course/nodes/co/COEditController.java
+++ b/src/main/java/org/olat/course/nodes/co/COEditController.java
@@ -128,18 +128,11 @@ public class COEditController extends ActivateableTabbableDefaultController impl
 		listenTo(accessibilityCondContr);
 	}
 
-	/**
-	 * @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest,
-	 *      org.olat.core.gui.components.Component, org.olat.core.gui.control.Event)
-	 */
+	@Override
 	public void event(UserRequest ureq, Component source, Event event) {
 		//
 	}
 
-	/**
-	 * @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest,
-	 *      org.olat.core.gui.control.Controller, org.olat.core.gui.control.Event)
-	 */
 	@Override
 	public void event(UserRequest ureq, Controller source, Event event) {
 		if (source == accessibilityCondContr) {
@@ -156,9 +149,7 @@ public class COEditController extends ActivateableTabbableDefaultController impl
 		}
 	}
 
-	/**
-	 * @see org.olat.core.gui.control.generic.tabbable.TabbableDefaultController#addTabs(org.olat.core.gui.components.TabbedPane)
-	 */
+	@Override
 	public void addTabs(TabbedPane tabbedPane) {
 		myTabbedPane = tabbedPane;
 		
@@ -166,17 +157,17 @@ public class COEditController extends ActivateableTabbableDefaultController impl
 		tabbedPane.addTab(translate(PANE_TAB_COCONFIG), myContent);
 	}
 
-	/**
-	 * @see org.olat.core.gui.control.DefaultController#doDispose(boolean)
-	 */
+	@Override
 	protected void doDispose() {
-    //nothing to do
+		//
 	}
 
+	@Override
 	public String[] getPaneKeys() {
 		return paneKeys;
 	}
 
+	@Override
 	public TabbedPane getTabbedPane() {
 		return myTabbedPane;
 	}
diff --git a/src/main/java/org/olat/course/nodes/co/CORunController.java b/src/main/java/org/olat/course/nodes/co/CORunController.java
index 08555c64a13..ea211ba4b69 100755
--- a/src/main/java/org/olat/course/nodes/co/CORunController.java
+++ b/src/main/java/org/olat/course/nodes/co/CORunController.java
@@ -300,17 +300,12 @@ public class CORunController extends BasicController {
 		return cl;
 	}
 
-	/**
-	 * @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest,
-	 * @see org.olat.core.gui.components.Component, @see org.olat.core.gui.control.Event)
-	 */
+	@Override
 	public void event(UserRequest ureq, Component source, Event event) {
-	// no components to listen to
+		//
 	}
 
-	/**
-	 * @see org.olat.core.gui.control.DefaultController#doDispose(boolean)
-	 */
+	@Override
 	protected void doDispose() {
 		//
 	}
diff --git a/src/main/java/org/olat/course/nodes/co/COToolController.java b/src/main/java/org/olat/course/nodes/co/COToolController.java
new file mode 100644
index 00000000000..60784427c9e
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/co/COToolController.java
@@ -0,0 +1,147 @@
+/**
+ * <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.co;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.Component;
+import org.olat.core.gui.components.form.flexible.impl.FormEvent;
+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.controller.BasicController;
+import org.olat.core.id.Identity;
+import org.olat.core.util.mail.ContactList;
+import org.olat.core.util.mail.ContactMessage;
+import org.olat.course.groupsandrights.CourseGroupManager;
+import org.olat.course.nodes.co.COToolRecipientsController.Recipients;
+import org.olat.course.nodes.members.MembersHelpers;
+import org.olat.course.run.userview.UserCourseEnvironment;
+import org.olat.modules.co.ContactFormController;
+import org.olat.repository.RepositoryEntry;
+import org.olat.repository.RepositoryService;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 6 Aug 2019<br>
+ * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
+ *
+ */
+public class COToolController extends BasicController {
+
+	private final ContactFormController emailCtrl;
+	private final COToolRecipientsController recipientCtrl;
+	
+	private final RepositoryEntry courseRepositoryEntry;
+	private final CourseGroupManager courseGroupManager;
+
+	@Autowired
+	private RepositoryService repositoryService;
+
+	public COToolController(UserRequest ureq, WindowControl wControl, UserCourseEnvironment userCourseEnv) {
+		super(ureq, wControl);
+		courseGroupManager = userCourseEnv.getCourseEnvironment().getCourseGroupManager();
+		this.courseRepositoryEntry = userCourseEnv.getCourseEnvironment().getCourseGroupManager().getCourseEntry();
+		
+		VelocityContainer mainVC = createVelocityContainer("tool");
+		
+		recipientCtrl = new COToolRecipientsController(ureq, wControl);
+		listenTo(recipientCtrl);
+		mainVC.put("recipients", recipientCtrl.getInitialComponent());
+		
+		ContactMessage cmsg = new ContactMessage(getIdentity());
+		Set<Recipients> recipients = recipientCtrl.getSelectedRecipients();
+		for (ContactList recipientList : getRecipientsLists(recipients)) {
+			cmsg.addEmailTo(recipientList);
+		}
+		emailCtrl = new ContactFormController(ureq, getWindowControl(), false, false, false, cmsg, null);
+		emailCtrl.setContactFormTitle(null);
+		listenTo(emailCtrl);
+		mainVC.put("email", emailCtrl.getInitialComponent());
+		
+		putInitialPanel(mainVC);
+	}
+
+	@Override
+	protected void event(UserRequest ureq, Controller source, Event event) {
+		if (source == recipientCtrl && event == FormEvent.CHANGED_EVENT) {
+			Set<Recipients> recipients = recipientCtrl.getSelectedRecipients();
+			doSetReciepients(recipients);
+		}
+		super.event(ureq, source, event);
+	}
+
+	private void doSetReciepients(Set<Recipients> recipients) {
+		List<ContactList> contactLists = getRecipientsLists(recipients);
+		emailCtrl.setRecipientsLists(contactLists);
+	}
+
+	private List<ContactList> getRecipientsLists(Set<Recipients> recipients) {
+		List<ContactList> contactLists = new ArrayList<>();
+		if (recipients.contains(Recipients.participants)) {
+			contactLists.add(getParticipantsContactList());
+		}
+		if (recipients.contains(Recipients.coaches)) {
+			contactLists.add(getCoachesContactList());
+		}
+		if (recipients.contains(Recipients.owners)) {
+			contactLists.add(getOwnersContactList());
+		}
+		return contactLists;
+	}
+	
+	private ContactList getOwnersContactList() {
+		ContactList cl = new ContactList(translate("form.message.chckbx.owners"));
+		List<Identity> identities = MembersHelpers.getOwners(repositoryService, courseRepositoryEntry);
+		cl.addAllIdentites(identities);
+		return cl;
+	}
+	
+	private ContactList getCoachesContactList() {
+		ContactList cl = new ContactList(translate("form.message.chckbx.coaches"));
+		Collection<Identity> identities = courseGroupManager.getCoaches();
+		cl.addAllIdentites(identities);
+		return cl;
+	}
+	
+	private ContactList getParticipantsContactList() {
+		ContactList cl = new ContactList(translate("form.message.chckbx.partips"));
+		Collection<Identity> identities = courseGroupManager.getParticipants();
+		cl.addAllIdentites(identities);
+		return cl;
+	}
+
+	@Override
+	protected void event(UserRequest ureq, Component source, Event event) {
+		//
+	}
+
+	@Override
+	protected void doDispose() {
+		//
+	}
+
+}
diff --git a/src/main/java/org/olat/course/nodes/co/COToolRecipientsController.java b/src/main/java/org/olat/course/nodes/co/COToolRecipientsController.java
new file mode 100644
index 00000000000..469f1be5299
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/co/COToolRecipientsController.java
@@ -0,0 +1,124 @@
+/**
+ * <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.co;
+
+import static org.olat.core.gui.components.util.KeyValues.entry;
+
+import java.util.HashSet;
+import java.util.Set;
+
+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.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.WindowControl;
+
+/**
+ * 
+ * Initial date: 6 Aug 2019<br>
+ * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
+ *
+ */
+public class COToolRecipientsController extends FormBasicController {
+	
+	public enum Recipients {
+		owners("tool.recipients.owners"),
+		coaches("tool.recipients.coaches"),
+		participants("tool.recipients.participants");
+		
+		private final String i18nKey;
+
+		private Recipients(String i18nKey) {
+			this.i18nKey = i18nKey;
+			
+		}
+
+		public String getI18nKey() {
+			return i18nKey;
+		}
+	}
+	
+	private MultipleSelectionElement recipientsEl;
+	
+	public COToolRecipientsController(UserRequest ureq, WindowControl wControl) {
+		super(ureq, wControl);
+		initForm(ureq);
+	}
+	
+	public Set<Recipients> getSelectedRecipients() {
+		Set<Recipients> recipients = new HashSet<>();
+		for (String key : recipientsEl.getSelectedKeys()) {
+			recipients.add(Recipients.valueOf(key));
+		}
+		return recipients;
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		setFormTitle("tool.title");
+		
+		KeyValues recipientKV = new KeyValues();
+		for (Recipients recipient : Recipients.values()) {
+			recipientKV.add(entry(recipient.name(), translate(recipient.getI18nKey())));
+		}
+		recipientsEl = uifactory.addCheckboxesHorizontal("tool.recipients", formLayout, recipientKV.keys(), recipientKV.values());
+		recipientsEl.select(Recipients.owners.name(), true);
+		recipientsEl.addActionListener(FormEvent.ONCHANGE);
+	}
+
+	@Override
+	protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
+		if (source == recipientsEl) {
+			boolean valid = validateFormLogic(ureq);
+			if (valid) {
+				fireEvent(ureq, FormEvent.CHANGED_EVENT);
+			}
+		}
+		super.formInnerEvent(ureq, source, event);
+	}
+
+	@Override
+	protected boolean validateFormLogic(UserRequest ureq) {
+		boolean allOk = true;
+		
+		recipientsEl.clearError();
+		if (!recipientsEl.isAtLeastSelected(1)) {
+			recipientsEl.setErrorKey("tool.recipients.mandatory", null);
+			allOk = false;
+		}
+		
+		return allOk & super.validateFormLogic(ureq);
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		//
+	}
+
+	@Override
+	protected void doDispose() {
+		//
+	}
+
+}
diff --git a/src/main/java/org/olat/course/nodes/co/_content/tool.html b/src/main/java/org/olat/course/nodes/co/_content/tool.html
new file mode 100644
index 00000000000..1d2b31ec095
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/co/_content/tool.html
@@ -0,0 +1,2 @@
+$r.render("recipients")
+$r.render("email")
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/nodes/co/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/nodes/co/_i18n/LocalStrings_de.properties
index 0146dbdfad2..c19bc69bff1 100755
--- a/src/main/java/org/olat/course/nodes/co/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/course/nodes/co/_i18n/LocalStrings_de.properties
@@ -1,14 +1,4 @@
 #Mon Mar 02 09:54:04 CET 2009
-
-
-
-
-
-
-
-
-
-
 groupCoachesChoose=Gruppe ausw\u00E4hlen
 groupParticipantsChoose=Gruppe ausw\u00E4hlen
 groupCreate=Gruppe erstellen
@@ -22,7 +12,7 @@ error.norecipients.long=Es sind keine Empf\u00E4nger f\u00FCr {0} definiert. Die
 error.norecipients.short=Empf\u00E4nger f\u00FCr {0} fehlen.
 error.notfound.name=Der angegebene Name ({1}) wurde im Gruppenmanagement dieses Kurses nicht gefunden.
 error.notfound.names=Die angegebenen Namen ({1}) wurden im Gruppenmanagement dieses Kurses nicht gefunden.
-error.no.choice.specified=W\u00e4hlen Sie mindestens
+error.no.choice.specified=W\u00E4hlen Sie mindestens
 error.no.group.specified=W\u00E4hlen Sie eine Gruppe/Lernbereich
 form.areanames.wrong=Geben Sie Namen von Lernbereichen getrennt mit Kommas ein oder lassen Sie dieses Feld leer.
 form.choose.coachesandpartips=Sie m\u00FCssen Teilnehmer oder Betreuer ausw\u00E4hlen
@@ -42,7 +32,6 @@ form.message.participants.group=Nur Teilnehmer aus den Gruppen
 form.message.participants.course=Nur Teilnehmer aus dem Kurs
 form.noGroupsOrAreas=Es muss mindestens eine Lerngruppe oder ein Lernbereich angegeben werden
 header=Empf\u00E4nger
-
 message.body=Nachricht (Vorlage)
 message.emailtoadresses=E-Mailadressen
 message.subject=Betreff (Vorlage)
@@ -57,3 +46,9 @@ pane.tab.coconfig=Empf\u00E4nger
 popupchooseareas=Lernbereiche aus Gruppenmanagement w\u00E4hlen
 popupchoosegroups=Gruppen aus Gruppenmanagement w\u00E4hlen
 recipients=Externe Empf\u00E4nger
+tool.recipients=Empf\u00E4nger
+tool.recipients.coaches=Betreuer
+tool.recipients.participants=Kursbesitzer
+tool.recipients.mandatory=Sie m\u00FCssen mindestens eine Option ausw\u00E4hlen.
+tool.recipients.owners=Teilnehmer
+tool.title=E-Mail
diff --git a/src/main/java/org/olat/course/nodes/co/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/nodes/co/_i18n/LocalStrings_en.properties
index 5d1937ef10f..e1f012deee4 100755
--- a/src/main/java/org/olat/course/nodes/co/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/course/nodes/co/_i18n/LocalStrings_en.properties
@@ -1,14 +1,4 @@
 #Sun Aug 15 18:44:08 CEST 2010
-
-
-
-
-
-
-
-
-
-
 groupCoachesChoose=Select group
 groupParticipantsChoose=Select group
 groupCreate=Create group
@@ -28,7 +18,7 @@ form.areanames.wrong=Please indicate titles of learning areas separated by comma
 form.choose.coachesandpartips=You have to select some participants or tutors
 form.groupnames.wrong=Please indicate titles of learning groups separated by commas or leave this box blank.
 form.message.area=Selected areas
-form.message.chckbx.coaches=Tutors of selected learning groups
+form.message.chckbx.coaches=Coaches of selected learning groups
 form.message.chckbx.partips=Participants of selected learning groups
 form.message.chckbx.owners=Course owners
 form.message.example.area=(Example\: Gr_1, Gr_2)
@@ -42,7 +32,6 @@ form.message.participants.group=Only group participants
 form.message.participants.course=Only course participants
 form.noGroupsOrAreas=At least one learning group or learning area has to be indicated
 header=Recipient
-
 message.body=Message (template)
 message.emailtoadresses=E-mail addresses
 message.subject=Subject (template)
@@ -56,4 +45,10 @@ pane.tab.accessibility=Access
 pane.tab.coconfig=Recipient
 popupchooseareas=Select learning areas from group management
 popupchoosegroups=Select groups from group management
-recipients=External recipients
\ No newline at end of file
+recipients=External recipients
+tool.recipients=Recipients
+tool.recipients.coaches=Coaches
+tool.recipients.participants=Participants
+tool.recipients.mandatory=You have to select at least one option.
+tool.recipients.owners=Owners
+tool.title=E-mail
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/nodes/members/ui/group/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/nodes/members/ui/group/_i18n/LocalStrings_en.properties
index bd8c02edb9b..7b09e5babe7 100755
--- a/src/main/java/org/olat/course/nodes/members/ui/group/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/course/nodes/members/ui/group/_i18n/LocalStrings_en.properties
@@ -31,7 +31,7 @@ form.areanames.wrong=Please indicate titles of learning areas separated by comma
 form.choose.coachesandpartips=You have to select some participants or tutors
 form.groupnames.wrong=Please indicate titles of learning groups separated by commas or leave this box blank.
 form.message.area=Selected areas
-form.message.chckbx.coaches=Tutors of selected learning groups
+form.message.chckbx.coaches=Coaches of selected learning groups
 form.message.chckbx.partips=Participants of selected learning groups
 form.message.chckbx.owners=Course owners
 form.message.curriculum.element=Curriculum
diff --git a/src/main/java/org/olat/course/run/CourseRuntimeController.java b/src/main/java/org/olat/course/run/CourseRuntimeController.java
index 78cf9ca6e08..f54966a1a0a 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.co.COToolController;
 import org.olat.course.nodes.info.InfoRunController;
 import org.olat.course.nodes.members.MembersToolRunController;
 import org.olat.course.reminder.ui.CourseRemindersController;
@@ -174,25 +175,26 @@ public class CourseRuntimeController extends RepositoryEntryRuntimeController im
 		assessmentModeLink, lifeCycleChangeLink,
 		//my course
 		efficiencyStatementsLink, calendarLink, noteLink, chatLink, leaveLink, searchLink,
-		participantListLink, participantInfoLink,
+		participantListLink, participantInfoLink, emailLink,
 		//glossary
 		openGlossaryLink, enableGlossaryLink, lecturesLink;
 	private Link currentUserCountLink;
 	private Dropdown myCourse, glossary;
 
 	private CloseableModalController cmc;
+	private COToolController emailCtrl;
 	private CourseAreasController areasCtrl;
 	private ConfirmLeaveController leaveDialogBox;
 	private ArchiverMainController archiverCtrl;
 	private CustomDBMainController databasesCtrl;
 	private FolderRunController courseFolderCtrl;
-	private MembersToolRunController participatListCtrl;
 	private InfoRunController participatInfoCtrl;
 	private SearchInputController searchController;
 	private StatisticMainController statisticsCtrl;
 	private CourseRemindersController remindersCtrl;
 	private TeacherOverviewController lecturesCtrl;
 	private AssessmentToolController assessmentToolCtr;
+	private MembersToolRunController participatListCtrl;
 	private MembersManagementMainController membersCtrl;
 	private StatisticCourseNodesController statsToolCtr;
 	private AssessmentModeListController assessmentModeCtrl;
@@ -807,13 +809,19 @@ public class CourseRuntimeController extends RepositoryEntryRuntimeController im
 			participantListLink.setVisible(cc.isParticipantListEnabled());
 			toolbarPanel.addTool(participantListLink);
 		}
-
+		
 		if(!assessmentLock) {
 			participantInfoLink = LinkFactory.createToolLink("participantinfo", translate("command.participant.info"), this, "o_infomsg_icon");
 			participantInfoLink.setVisible(cc.isParticipantInfoEnabled());
 			toolbarPanel.addTool(participantInfoLink);
 		}
 		
+		if(!assessmentLock) {
+			emailLink = LinkFactory.createToolLink("email", translate("command.email"), this, "o_co_icon");
+			emailLink.setVisible(cc.isEmailEnabled());
+			toolbarPanel.addTool(emailLink);
+		}
+		
 		if(!assessmentLock) {
 			glossary = new Dropdown("glossary", "command.glossary", false, getTranslator());
 			glossary.setIconCSS("o_icon o_FileResource-GLOSSARY_icon");
@@ -944,6 +952,8 @@ public class CourseRuntimeController extends RepositoryEntryRuntimeController im
 			doParticipantList(ureq);
 		} else if(participantInfoLink == source) {
 			doParticipantInfo(ureq);
+		} else if(emailLink == source) {
+			doEmail(ureq);
 		} else if(calendarLink == source) {
 			launchCalendar(ureq);
 		} else if(chatLink == source) {
@@ -1047,6 +1057,7 @@ public class CourseRuntimeController extends RepositoryEntryRuntimeController im
 						case pop: popToRoot(ureq); cleanUp(); break;
 						case participantList: doParticipantList(ureq); break;
 						case participantInfo: doParticipantInfo(ureq); break;
+						case email: doEmail(ureq); break;
 					}
 					delayedClose = null;
 				} else {
@@ -1692,6 +1703,22 @@ public class CourseRuntimeController extends RepositoryEntryRuntimeController im
 		};
 	}
 	
+	private void doEmail(UserRequest ureq) {
+		if(delayedClose == Delayed.email || requestForClose(ureq)) {
+			removeCustomCSS();
+			
+			OLATResourceable ores = OresHelper.createOLATResourceableType("email");
+			WindowControl swControl = addToHistory(ureq, ores, null);
+			emailCtrl = new COToolController(ureq, swControl, getUserCourseEnvironment());
+
+			pushController(ureq, translate("command.email"), emailCtrl);
+			setActiveTool(emailLink);
+			currentToolCtr = emailCtrl;
+		} else {
+			delayedClose = Delayed.email;
+		};
+	}
+	
 	private void launchCalendar(UserRequest ureq) {
 		ControllerCreator ctrlCreator = (lureq, lwControl) -> {
 			ICourse course = CourseFactory.loadCourse(getRepositoryEntry());
@@ -1835,6 +1862,15 @@ public class CourseRuntimeController extends RepositoryEntryRuntimeController im
 				}
 				break;
 			}
+			case email: {
+				if(emailLink != null) {
+					ICourse course = CourseFactory.loadCourse(getRepositoryEntry());
+					CourseConfig cc = course.getCourseEnvironment().getCourseConfig();
+					emailLink.setVisible(cc.isEmailEnabled());
+					toolbarPanel.setDirty(true);
+				}
+				break;
+			}
 			case chat: {
 				if(chatLink != null) {
 					ICourse course = CourseFactory.loadCourse(getRepositoryEntry());
@@ -1973,6 +2009,7 @@ public class CourseRuntimeController extends RepositoryEntryRuntimeController im
 		close,
 		pop,
 		participantList,
-		participantInfo
+		participantInfo,
+		email
 	}
 }
\ 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 22e96227320..fa932ad6d66 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
@@ -13,6 +13,7 @@ command.courseconfig=Kursinfo
 command.coursefolder=Ablageordner
 command.coursesearch=Kurssuche
 command.efficiencystatement=Leistungsnachweis
+command.email=E-Mail
 command.glossary=Glossar
 command.glossary.open=Glossar in separatem Fenster \u00F6ffnen
 command.glossary.off=aus
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 d062cae4275..bbb9249bc7e 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
@@ -13,6 +13,7 @@ command.courseconfig=Course info
 command.coursefolder=Storage folder
 command.coursesearch=Course search
 command.efficiencystatement=Evidence of achievement
+command.email=E-Mail
 command.glossary=Glossary
 command.glossary.off=off
 command.glossary.off.alt=Hide glossary terms of learning content
diff --git a/src/main/java/org/olat/modules/co/ContactForm.java b/src/main/java/org/olat/modules/co/ContactForm.java
index 0a3305e72e9..1bbd87a17d9 100644
--- a/src/main/java/org/olat/modules/co/ContactForm.java
+++ b/src/main/java/org/olat/modules/co/ContactForm.java
@@ -100,7 +100,7 @@ public class ContactForm extends FormBasicController {
 	private FileElement attachmentEl;
 	private List<FormLink> attachmentLinks = new ArrayList<>();
 	private FormLayoutContainer uploadCont;
-	private boolean recipientsAreEditable = false;
+	private final boolean recipientsAreEditable;
 	private static final int emailCols = 60;
 	private boolean readOnly=false;
 	private boolean hasMsgCancel=false;
@@ -146,6 +146,15 @@ public class ContactForm extends FormBasicController {
 		tsubject.setMandatory(tsubject.isEnabled());
 	}
 
+	public void setRecipientsLists(List<ContactList> recipientsLists) {
+		contactLists.clear();
+		tto.setValue("");
+		ttoBig.setValue("");
+		for (ContactList contactList : recipientsLists) {
+			addEmailTo(contactList);
+		}
+	}
+
 	/**
 	 * add a ContactList as EmailTo:
 	 * 
@@ -171,9 +180,6 @@ public class ContactForm extends FormBasicController {
 		defaultEmailTo += tto.getValue();
 		tto.setValue(defaultEmailTo);
 		ttoBig.setValue(defaultEmailTo);
-		
-		tto.setVisible(!recipientsAreEditable);
-		ttoBig.setVisible(recipientsAreEditable);
 	}
 
 	public void setBody(String defaultBody) {
@@ -371,18 +377,17 @@ public class ContactForm extends FormBasicController {
 			fullName = "[" + fullName + "]";
 		}
 		tfrom = uifactory.addTextElement("ttfrom", NLS_CONTACT_FROM, 255, fullName, formLayout);
-		tfrom.setElementCssClass("o_sel_contact_to");
+		tfrom.setElementCssClass("o_sel_contact_from");
 		// When no identity is set, let user enter a valid email address
 		tfrom.setEnabled((this.emailFrom == null));
 		
 		tto = uifactory.addTextElement("tto", NLS_CONTACT_TO, 255, "", formLayout);
 		tto.setElementCssClass("o_sel_contact_to");
 		tto.setEnabled(false);
-		tto.setVisible(false);
+		tto.setVisible(!recipientsAreEditable);
 	
 		ttoBig = uifactory.addTextAreaElement("ttoBig", NLS_CONTACT_TO, -1, 2, emailCols, true, false, "", formLayout);
-		ttoBig.setEnabled(false);
-		ttoBig.setVisible(false);
+		ttoBig.setVisible(recipientsAreEditable);
 		
 		tsubject = uifactory.addTextElement("tsubject", NLS_CONTACT_SUBJECT, 255, "", formLayout);
 		tsubject.setElementCssClass("o_sel_contact_subject");
@@ -422,5 +427,5 @@ public class ContactForm extends FormBasicController {
 	protected void doDispose() {
 		cleanUpAttachments();
 	}
- 	
+
 }
\ No newline at end of file
diff --git a/src/main/java/org/olat/modules/co/ContactFormController.java b/src/main/java/org/olat/modules/co/ContactFormController.java
index 142c326ed73..afc90808683 100644
--- a/src/main/java/org/olat/modules/co/ContactFormController.java
+++ b/src/main/java/org/olat/modules/co/ContactFormController.java
@@ -188,6 +188,12 @@ public class ContactFormController extends BasicController {
 		}
 		return null;
 	}
+	
+	public void setRecipientsLists(List<ContactList> recipientsLists) {
+		if (cntctForm != null) {
+			cntctForm.setRecipientsLists(recipientsLists);
+		}	
+	}
 
 	private void init(UserRequest ureq, boolean hasAtLeastOneAddress, List<Identity> disabledIdentities) {
 		if (hasAtLeastOneAddress) {
diff --git a/src/main/java/org/olat/repository/RepositoryEntryManagedFlag.java b/src/main/java/org/olat/repository/RepositoryEntryManagedFlag.java
index 74f407ed780..591545ab9fe 100644
--- a/src/main/java/org/olat/repository/RepositoryEntryManagedFlag.java
+++ b/src/main/java/org/olat/repository/RepositoryEntryManagedFlag.java
@@ -49,6 +49,7 @@ public enum RepositoryEntryManagedFlag {
       search(settings, all),
       participantList(settings, all),
       participantInfo(settings, all),
+      email(settings, all),
       chat(settings,all),
       layout(settings,all),
       resourcefolder(settings,all),
-- 
GitLab