From 46ff406a01bcc721323eef6bdb41b66b7fd30818 Mon Sep 17 00:00:00 2001
From: Dirk Furrer <none@none>
Date: Thu, 19 Mar 2015 12:26:44 +0100
Subject: [PATCH] OO-1472: extended EnrollmentNode configuration. changed
 EnrollmentManager, RunController and BusinessGroupTableModel to handle the
 new configurationState

---
 .../_content/group_or_area_selection.html     |   2 +-
 .../org/olat/course/nodes/ENCourseNode.java   |  12 +-
 .../en/ENEditGroupAreaFormController.java     | 118 ++++++++++--------
 .../olat/course/nodes/en/ENRunController.java |  86 +++++++------
 .../course/nodes/en/EnrollmentManager.java    |  53 +++-----
 .../nodes/en/_content/enrollmultiple.html     |  46 ++++---
 .../nodes/en/_i18n/LocalStrings_de.properties |  10 +-
 .../nodes/en/_i18n/LocalStrings_en.properties |  10 +-
 .../BusinessGroupTableModelWithMaxSize.java   |  24 ++--
 9 files changed, 210 insertions(+), 151 deletions(-)

diff --git a/src/main/java/org/olat/course/condition/_content/group_or_area_selection.html b/src/main/java/org/olat/course/condition/_content/group_or_area_selection.html
index d1941a3a631..8d2dd1ffd50 100644
--- a/src/main/java/org/olat/course/condition/_content/group_or_area_selection.html
+++ b/src/main/java/org/olat/course/condition/_content/group_or_area_selection.html
@@ -4,6 +4,6 @@
 </div>
 #end
 $r.render("entries")
-<div class="o_button_group">
+<div class="o_button_group o_sel_group_selection_groups">
 	$r.render("subm") $r.render("cancel")
 </div>
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/nodes/ENCourseNode.java b/src/main/java/org/olat/course/nodes/ENCourseNode.java
index 3a77a0c3c9f..233bf4b0c06 100644
--- a/src/main/java/org/olat/course/nodes/ENCourseNode.java
+++ b/src/main/java/org/olat/course/nodes/ENCourseNode.java
@@ -99,12 +99,13 @@ public class ENCourseNode extends AbstractAccessableCourseNode {
 	/** CONFIG_AREANAME configuration parameter key. */
 	public static final String CONFIG_AREA_IDS = "areakeys";
 	
-	
+	/** CONFIG_ALLOW_MULTIPLE_ENTROLL_COUNT configuration parameter */
+	public static final String CONFIG_ALLOW_MULTIPLE_ENROLL_COUNT = "allow_multiple_enroll_count";
 	
 	/** CONF_CANCEL_ENROLL_ENABLED configuration parameter key. */
 	public static final String CONF_CANCEL_ENROLL_ENABLED = "cancel_enroll_enabled";
 
-	private static final int CURRENT_CONFIG_VERSION = 2;
+	private static final int CURRENT_CONFIG_VERSION = 3;
 
 	/**
 	 * Constructor for enrollment buildig block
@@ -258,7 +259,8 @@ public class ENCourseNode extends AbstractAccessableCourseNode {
 		ModuleConfiguration config = getModuleConfiguration();
 		// defaults
 		config.set(CONF_CANCEL_ENROLL_ENABLED, Boolean.TRUE);
-    config.setConfigurationVersion(CURRENT_CONFIG_VERSION);
+		config.set(CONFIG_ALLOW_MULTIPLE_ENROLL_COUNT,1);
+		config.setConfigurationVersion(CURRENT_CONFIG_VERSION);
 	}
 	
 	@Override
@@ -324,6 +326,10 @@ public class ENCourseNode extends AbstractAccessableCourseNode {
 				// migrate V1 => V2
 				config.set(CONF_CANCEL_ENROLL_ENABLED, Boolean.TRUE);
 				version = 2;
+			}else if(version <= 2){
+				// migrate V2 -> V3
+				config.set(CONFIG_ALLOW_MULTIPLE_ENROLL_COUNT, 1);
+				version = 3;
 			}
 			config.setConfigurationVersion(CURRENT_CONFIG_VERSION);
 		}
diff --git a/src/main/java/org/olat/course/nodes/en/ENEditGroupAreaFormController.java b/src/main/java/org/olat/course/nodes/en/ENEditGroupAreaFormController.java
index 06ff7c572bf..c9482b557d1 100644
--- a/src/main/java/org/olat/course/nodes/en/ENEditGroupAreaFormController.java
+++ b/src/main/java/org/olat/course/nodes/en/ENEditGroupAreaFormController.java
@@ -36,6 +36,7 @@ import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.form.flexible.FormItem;
 import org.olat.core.gui.components.form.flexible.FormItemContainer;
 import org.olat.core.gui.components.form.flexible.elements.FormLink;
+import org.olat.core.gui.components.form.flexible.elements.IntegerElement;
 import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement;
 import org.olat.core.gui.components.form.flexible.elements.StaticTextElement;
 import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
@@ -43,7 +44,6 @@ import org.olat.core.gui.components.form.flexible.impl.FormEvent;
 import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
 import org.olat.core.gui.components.form.flexible.impl.elements.FormLinkImpl;
 import org.olat.core.gui.components.form.flexible.impl.elements.FormSubmit;
-import org.olat.core.gui.components.link.Link;
 import org.olat.core.gui.control.Controller;
 import org.olat.core.gui.control.Event;
 import org.olat.core.gui.control.WindowControl;
@@ -87,14 +87,14 @@ class ENEditGroupAreaFormController extends FormBasicController implements Gener
 	private ModuleConfiguration moduleConfig;
 	private CourseEditorEnv cev;
 	private MultipleSelectionElement enableCancelEnroll;
+	private MultipleSelectionElement allowMultipleEnroll;
+	private IntegerElement multipleEnrollCount;
 	
 	private StaticTextElement easyGroupList;
 	private FormLink chooseGroupsLink;
-	private FormLink createGroupsLink;
 	
 	private StaticTextElement easyAreaList;
 	private FormLink chooseAreasLink;
-	private FormLink createAreasLink;
 	
 	private boolean hasAreas;
 	private boolean hasGroups;
@@ -178,6 +178,10 @@ class ENEditGroupAreaFormController extends FormBasicController implements Gener
 		moduleConfig.set(ENCourseNode.CONF_CANCEL_ENROLL_ENABLED, cancelEnrollEnabled);
 		hasAreas = areaManager.countBGAreasInContext(cev.getCourseGroupManager().getCourseResource()) > 0;
 		hasGroups = businessGroupService.countBusinessGroups(null, cev.getCourseGroupManager().getCourseEntry()) > 0;
+		//4. multiple groups flag
+		int enrollCount = multipleEnrollCount.getIntValue();
+		if(!allowMultipleEnroll.isSelected(0)) enrollCount=1; 
+		moduleConfig.set(ENCourseNode.CONFIG_ALLOW_MULTIPLE_ENROLL_COUNT, enrollCount);
 		// Inform all listeners about the changed condition
 		fireEvent(ureq, NodeEditController.NODECONFIG_CHANGED_EVENT);
 	}
@@ -202,14 +206,20 @@ class ENEditGroupAreaFormController extends FormBasicController implements Gener
 		}
 		groupInitVal = getGroupNames(groupKeys);
 		
-		easyGroupList = uifactory.addStaticTextElement("group", "form.groupnames", groupInitVal == null ? "" : groupInitVal, formLayout);
+		chooseGroupsLink = uifactory.addFormLink("chooseGroup", formLayout, "btn btn-default o_xsmall o_form_groupchooser");
+		chooseGroupsLink.setLabel("form.groupnames", null);
+		chooseGroupsLink.setIconLeftCSS("o_icon o_icon-fw o_icon_group");
+		
+		easyGroupList = uifactory.addStaticTextElement("group", null, groupInitVal == null ? "" : groupInitVal, formLayout);
 		easyGroupList.setUserObject(groupKeys);
-
-		chooseGroupsLink = uifactory.addFormLink("chooseGroup", "choose", null, formLayout, Link.LINK);	
-		chooseGroupsLink.setElementCssClass("o_sel_course_en_choose_group");
-		createGroupsLink = uifactory.addFormLink("createGroup", "create", null, formLayout, Link.LINK);	
-		createGroupsLink.setElementCssClass("o_sel_course_en_create_group");
+		easyGroupList.setElementCssClass("text-muted");
+		
 		hasGroups = businessGroupService.countBusinessGroups(null, cev.getCourseGroupManager().getCourseEntry()) > 0;
+		if(hasGroups){
+			chooseGroupsLink.setI18nKey("choose");
+		}else{
+			chooseGroupsLink.setI18nKey("create");
+		}
 		
 		// areas
 		String areaInitVal;
@@ -220,12 +230,32 @@ class ENEditGroupAreaFormController extends FormBasicController implements Gener
 			areaKeys = areaManager.toAreaKeys(areaInitVal, cev.getCourseGroupManager().getCourseResource());
 		}
 		areaInitVal = getAreaNames(areaKeys);
-		easyAreaList = uifactory.addStaticTextElement("area", "form.areanames", areaInitVal == null ? "" : areaInitVal, formLayout);
+		
+		chooseAreasLink = uifactory.addFormLink("chooseArea", formLayout, "btn btn-default o_xsmall o_form_areachooser");
+		chooseAreasLink.setLabel("form.areanames", null);
+		chooseAreasLink.setIconLeftCSS("o_icon o_icon-fw o_icon_courseareas");
+		
+		easyAreaList = uifactory.addStaticTextElement("area", null, areaInitVal == null ? "" : areaInitVal, formLayout);
 		easyAreaList.setUserObject(areaKeys);
+		easyAreaList.setElementCssClass("text-muted");
+		
+		if(areaInitVal.isEmpty()){
+			chooseAreasLink.setI18nKey("create");
+		} else {
+			chooseAreasLink.setI18nKey("choose");
+		}
+		
+		//multiple group selection
+		int enrollCountConfig = moduleConfig.getIntegerSafe(ENCourseNode.CONFIG_ALLOW_MULTIPLE_ENROLL_COUNT,1);
+		Boolean multipleEnroll = (enrollCountConfig > 1);
+		allowMultipleEnroll = uifactory.addCheckboxesHorizontal("allowMultipleEnroll", "form.allowMultiEnroll", formLayout, new String[] { "multiEnroll" }, new String[] { "" });
+		allowMultipleEnroll.select("multiEnroll", multipleEnroll);
+		allowMultipleEnroll.addActionListener(FormEvent.ONCLICK);
+		
+		multipleEnrollCount = uifactory.addIntegerElement("form.multipleEnrollCount", enrollCountConfig, formLayout);
+		multipleEnrollCount.setMinValueCheck(1, "error.multipleEnroll");
+		multipleEnrollCount.setVisible(allowMultipleEnroll.isSelected(0));
 		
-		chooseAreasLink = uifactory.addFormLink("chooseArea", "choose", null, formLayout, Link.LINK);
-		createAreasLink = uifactory.addFormLink("createArea", "create", null, formLayout, Link.LINK);
-
 		// enrolment
 		Boolean initialCancelEnrollEnabled  = (Boolean) moduleConfig.get(ENCourseNode.CONF_CANCEL_ENROLL_ENABLED);
 		enableCancelEnroll = uifactory.addCheckboxesHorizontal("enableCancelEnroll", "form.enableCancelEnroll", formLayout, new String[] { "ison" }, new String[] { "" });
@@ -373,7 +403,13 @@ class ENEditGroupAreaFormController extends FormBasicController implements Gener
 	
 	@Override
 	protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
-		if (source == chooseGroupsLink) {
+		if(source == allowMultipleEnroll){
+			if(allowMultipleEnroll.isSelected(0)){
+				multipleEnrollCount.setVisible(true);
+			}else{
+				multipleEnrollCount.setVisible(false);
+			}
+		}else if (source == chooseGroupsLink) {
 			removeAsListenerAndDispose(groupChooseC);
 			groupChooseC = new GroupSelectionController(ureq, getWindowControl(), true,
 					cev.getCourseGroupManager(), getKeys(easyGroupList));
@@ -386,20 +422,6 @@ class ENEditGroupAreaFormController extends FormBasicController implements Gener
 			cmc.activate();
 			subm.setEnabled(false);
 
-		} else if (source == createGroupsLink) {
-			removeAsListenerAndDispose(cmc);
-			removeAsListenerAndDispose(groupCreateCntrllr);
-			// no groups in group management -> directly show group create dialog
-
-			OLATResource courseResource = cev.getCourseGroupManager().getCourseResource();
-			RepositoryEntry courseRe = RepositoryManager.getInstance().lookupRepositoryEntry(courseResource, false);
-			groupCreateCntrllr = new NewBGController(ureq, getWindowControl(), courseRe, true, null);
-			listenTo(groupCreateCntrllr);
-
-			cmc = new CloseableModalController(getWindowControl(), "close", groupCreateCntrllr.getInitialComponent());
-			listenTo(cmc);
-			cmc.activate();
-			subm.setEnabled(false);
 		} else if (source == chooseAreasLink) {
 			removeAsListenerAndDispose(cmc);
 			removeAsListenerAndDispose(areaChooseC);
@@ -413,18 +435,6 @@ class ENEditGroupAreaFormController extends FormBasicController implements Gener
 			listenTo(cmc);
 			cmc.activate();
 			subm.setEnabled(false);
-		} else if (source == createAreasLink) {
-			removeAsListenerAndDispose(cmc);
-			removeAsListenerAndDispose(areaCreateCntrllr);
-			// no areas -> directly show creation dialog
-			OLATResource courseResource = cev.getCourseGroupManager().getCourseResource();
-			areaCreateCntrllr = new NewAreaController(ureq, getWindowControl(), courseResource, true, null);
-			listenTo(areaCreateCntrllr);
-			
-			cmc = new CloseableModalController(getWindowControl(), "close", areaCreateCntrllr.getInitialComponent());
-			listenTo(cmc);
-			cmc.activate();
-			subm.setEnabled(false);
 		} else if (source == fixGroupError) {
 			/*
 			 * user wants to fix problem with fixing group error link e.g. create one
@@ -490,6 +500,7 @@ class ENEditGroupAreaFormController extends FormBasicController implements Gener
 				easyGroupList.setValue(list == null ? "" : list);
 				easyGroupList.setUserObject(groupChooseC.getSelectedKeys());
 				easyGroupList.getRootForm().submit(ureq);
+				chooseGroupsLink.setI18nKey("choose");
 			} else if (Event.CANCELLED_EVENT == event) {
 				cmc.deactivate();
 			}
@@ -505,6 +516,7 @@ class ENEditGroupAreaFormController extends FormBasicController implements Gener
 				easyAreaList.setValue(list == null ? "" : list);
 				easyAreaList.setUserObject(areaChooseC.getSelectedKeys());
 				easyAreaList.getRootForm().submit(ureq);
+				chooseAreasLink.setI18nKey("choose");
 			} else if (event == Event.CANCELLED_EVENT) {
 				cmc.deactivate();
 			}
@@ -524,8 +536,7 @@ class ENEditGroupAreaFormController extends FormBasicController implements Gener
 				easyGroupList.setUserObject(c);
 				
 				if (groupCreateCntrllr.getCreatedGroupNames().size() > 0 && !hasGroups) {
-					chooseGroupsLink.setVisible(true);
-					createGroupsLink.setVisible(false);
+					chooseGroupsLink.setLinkTitle("select");
 					singleUserEventCenter.fireEventToListenersOf(new MultiUserEvent("changed"), groupConfigChangeEventOres);
 				}
 				
@@ -546,8 +557,7 @@ class ENEditGroupAreaFormController extends FormBasicController implements Gener
 				easyAreaList.setUserObject(c);
 				
 				if (areaCreateCntrllr.getCreatedAreaNames().size() > 0 && !hasAreas) {
-					chooseAreasLink.setVisible(true);
-					createAreasLink.setVisible(false);
+					chooseAreasLink.setLinkTitle("select");
 					singleUserEventCenter.fireEventToListenersOf(new MultiUserEvent("changed"), groupConfigChangeEventOres);
 				}
 				easyAreaList.getRootForm().submit(ureq);
@@ -569,12 +579,18 @@ class ENEditGroupAreaFormController extends FormBasicController implements Gener
 	
 	private void updateGroupsAndAreasCheck() {
 		hasGroups = businessGroupService.countBusinessGroups(null, cev.getCourseGroupManager().getCourseEntry()) > 0;
-		chooseGroupsLink.setVisible(hasGroups);
-		createGroupsLink.setVisible(!hasGroups && !managedGroups);
+		if(!hasGroups && !managedGroups){
+			chooseGroupsLink.setLinkTitle("create");
+		}else{
+			chooseGroupsLink.setLinkTitle("choose");
+		}
 		
 		hasAreas = areaManager.countBGAreasInContext(cev.getCourseGroupManager().getCourseResource()) > 0;
-		chooseAreasLink.setVisible(hasAreas);
-		createAreasLink.setVisible(!hasAreas);	
+		if(hasAreas){
+			chooseAreasLink.setLinkTitle("choose");
+		}else{
+			chooseAreasLink.setLinkTitle("create");
+		}
 	}
 	
 	private boolean isEmpty(StaticTextElement element) {
@@ -608,7 +624,8 @@ class ENEditGroupAreaFormController extends FormBasicController implements Gener
 		StringBuilder sb = new StringBuilder();
 		List<BusinessGroupShort> groups = businessGroupService.loadShortBusinessGroups(keys);
 		for(BusinessGroupShort group:groups) {
-			if(sb.length() > 0) sb.append(", ");
+			if(sb.length() > 0) sb.append("&nbsp;&nbsp;");
+			sb.append("<i class='o_icon o_icon-fw o_icon_group'>&nbsp;</i> ");
 			sb.append(StringHelper.escapeHtml(group.getName()));
 		}
 		return sb.toString();
@@ -618,7 +635,8 @@ class ENEditGroupAreaFormController extends FormBasicController implements Gener
 		StringBuilder sb = new StringBuilder();
 		List<BGArea> areas = areaManager.loadAreas(keys);
 		for(BGArea area:areas) {
-			if(sb.length() > 0) sb.append(", ");
+			if(sb.length() > 0) sb.append("&nbsp;&nbsp;");
+			sb.append("<i class='o_icon o_icon-fw o_icon_courseareas'>&nbsp;</i> ");
 			sb.append(StringHelper.escapeHtml(area.getName()));
 		}
 		return sb.toString();
diff --git a/src/main/java/org/olat/course/nodes/en/ENRunController.java b/src/main/java/org/olat/course/nodes/en/ENRunController.java
index 03652c6cc9a..7f3d4f7acf2 100644
--- a/src/main/java/org/olat/course/nodes/en/ENRunController.java
+++ b/src/main/java/org/olat/course/nodes/en/ENRunController.java
@@ -25,6 +25,7 @@
 
 package org.olat.course.nodes.en;
 
+import java.util.ArrayList;
 import java.util.List;
 
 import org.olat.NewControllerFactory;
@@ -100,10 +101,11 @@ public class ENRunController extends BasicController implements GenericEventList
 	private final CoursePropertyManager coursePropertyManager;
 
 	// workflow variables
-	private BusinessGroup enrolledGroup;
-	private BusinessGroup waitingListGroup;
+	private List<BusinessGroup> enrolledGroups;
+	private List<BusinessGroup> waitingListGroups;
 	
 	private boolean cancelEnrollEnabled;
+	private int maxEnrollCount;
 	
 	/**
 	 * @param moduleConfiguration
@@ -143,15 +145,15 @@ public class ENRunController extends BasicController implements GenericEventList
 		cancelEnrollEnabled = ((Boolean) moduleConfig.get(ENCourseNode.CONF_CANCEL_ENROLL_ENABLED)).booleanValue();
 
 		Identity identity = userCourseEnv.getIdentityEnvironment().getIdentity();
-		enrolledGroup = enrollmentManager.getBusinessGroupWhereEnrolled(identity, enrollableGroupKeys, enrollableAreaKeys, courseGroupManager.getCourseEntry());
-		waitingListGroup = enrollmentManager.getBusinessGroupWhereInWaitingList(identity, enrollableGroupKeys, enrollableAreaKeys);
+		enrolledGroups = enrollmentManager.getBusinessGroupsWhereEnrolled(identity, enrollableGroupKeys, enrollableAreaKeys, courseGroupManager.getCourseEntry());
+		waitingListGroups = enrollmentManager.getBusinessGroupsWhereInWaitingList(identity, enrollableGroupKeys, enrollableAreaKeys);
 		registerGroupChangedEvents(enrollableGroupKeys, enrollableAreaKeys, ureq.getIdentity());
 		// Set correct view
 		enrollVC = createVelocityContainer("enrollmultiple");
 		List<BusinessGroup> groups = enrollmentManager.loadGroupsFromNames(enrollableGroupKeys, enrollableAreaKeys);
 		
 		tableCtr = createTableController(ureq, enrollmentManager.hasAnyWaitingList(groups));
-		
+		maxEnrollCount = moduleConfiguration.getIntegerSafe(ENCourseNode.CONFIG_ALLOW_MULTIPLE_ENROLL_COUNT, 1);
 		doEnrollView(ureq);
 
 		// push title and learning objectives, only visible on intro page
@@ -195,37 +197,35 @@ public class ENRunController extends BasicController implements GenericEventList
 					EnrollStatus enrollStatus = enrollmentManager.doEnroll(ureq.getIdentity(), ureq.getUserSession().getRoles(), choosenGroup, enNode, coursePropertyManager, getWindowControl(), getTranslator(),
 							                                                   enrollableGroupKeys, enrollableAreaKeys, courseGroupManager);
 					if (enrollStatus.isEnrolled() ) {
-						enrolledGroup = choosenGroup;
+						enrolledGroups.add(choosenGroup);
 					} else if (enrollStatus.isInWaitingList() ) {
-						waitingListGroup = choosenGroup;
+						waitingListGroups.add(choosenGroup);
 					} else {
 						getWindowControl().setError(enrollStatus.getErrorMessage());
 					}
 					// events are already fired BusinessGroupManager level :: BusinessGroupModifiedEvent.fireModifiedGroupEvents(BusinessGroupModifiedEvent.IDENTITY_ADDED_EVENT, choosenGroup,  ureq.getIdentity());
 					// but async
-					doEnrollView(ureq);
 					// fire event to indicate runmaincontroller that the menuview is to update
-					
+					doEnrollView(ureq);
 					if (enrollStatus.isEnrolled() ) {
-						fireEvent(ureq, new BusinessGroupModifiedEvent(BusinessGroupModifiedEvent.IDENTITY_ADDED_EVENT, enrolledGroup, getIdentity()));
+						fireEvent(ureq, new BusinessGroupModifiedEvent(BusinessGroupModifiedEvent.IDENTITY_ADDED_EVENT, choosenGroup, getIdentity()));
 					} else {
 						fireEvent(ureq, Event.DONE_EVENT);
 					}
+
 				} else if (actionid.equals(CMD_ENROLLED_CANCEL)) {
-					if (waitingListGroup != null) {
+ 					if (enrollmentManager.getBusinessGroupsWhereInWaitingList(getIdentity(), enrollableGroupKeys, enrollableAreaKeys).contains(choosenGroup)) {
 						enrollmentManager.doCancelEnrollmentInWaitingList(ureq.getIdentity(), choosenGroup, enNode, coursePropertyManager, getWindowControl(), getTranslator());
-						waitingListGroup = null;
-					} else {
+						waitingListGroups.remove(choosenGroup);
+					} else if(enrollmentManager.getBusinessGroupsWhereEnrolled(getIdentity(), enrollableGroupKeys, enrollableAreaKeys, courseGroupManager.getCourseEntry()).contains(choosenGroup)) {
 						enrollmentManager.doCancelEnrollment(ureq.getIdentity(), choosenGroup, enNode, coursePropertyManager, getWindowControl(), getTranslator());
-						enrolledGroup = null;
-					}
-					doEnrollView(ureq);
-					if (enrolledGroup == null) {
-						// fire event to indicate runmaincontroller that the menuview is to update
-						fireEvent(ureq, new BusinessGroupModifiedEvent(BusinessGroupModifiedEvent.IDENTITY_REMOVED_EVENT, choosenGroup, getIdentity()));
+						enrolledGroups.remove(choosenGroup);
 					}
+					// fire event to indicate runmaincontroller that the menuview is to update
+					fireEvent(ureq, new BusinessGroupModifiedEvent(BusinessGroupModifiedEvent.IDENTITY_REMOVED_EVENT, choosenGroup, getIdentity()));
 					// events are already fired BusinessGroupManager level :: BusinessGroupModifiedEvent.fireModifiedGroupEvents(BusinessGroupModifiedEvent.IDENTITY_REMOVED_EVENT, group,  ureq.getIdentity());
 					// but async
+					doEnrollView(ureq);
 				} else if(CMD_VISIT_CARD.equals(actionid)) {
 					String businessPath;
 					if(businessGroupService.isIdentityInBusinessGroup(getIdentity(), choosenGroup)) {
@@ -246,32 +246,42 @@ public class ENRunController extends BasicController implements GenericEventList
 	}	
 
 	private void doEnrollView(UserRequest ureq) {
-		//TODO read from config: 1) user can choose or 2) round robin
-		// for now only case 1
-		if (enrolledGroup != null) {
-			enrollVC.contextPut("isEnrolledView", Boolean.TRUE);
-			enrollVC.contextPut("isWaitingList", Boolean.FALSE);
-			enrollVC.contextPut("groupName", StringHelper.escapeHtml(enrolledGroup.getName()));
-			String desc = StringHelper.xssScan(enrolledGroup.getDescription());
-			enrollVC.contextPut("groupDesc", (desc == null) ? "" : desc);    	
-		} else if (waitingListGroup != null){
+		enrollVC.contextPut("multiEnroll", (maxEnrollCount > 1 && enrolledGroups.size()+waitingListGroups.size()<maxEnrollCount));
+		if(!enrolledGroups.isEmpty()||!waitingListGroups.isEmpty()){
+			String[] hintNumbers = new String[2];
+			hintNumbers[0]=String.valueOf(enrolledGroups.size()+waitingListGroups.size());
+			hintNumbers[1]=String.valueOf(maxEnrollCount-enrolledGroups.size()-waitingListGroups.size());
+			enrollVC.contextPut("multipleHint", translate("multiple.select.hint.outstanding", hintNumbers));
+		}else{
+			enrollVC.contextPut("multipleHint", translate("multiple.select.hint", String.valueOf(maxEnrollCount)));
+		}
+		
+		if (!enrolledGroups.isEmpty()) {
 			enrollVC.contextPut("isEnrolledView", Boolean.TRUE);
-			enrollVC.contextPut("isWaitingList", Boolean.TRUE);
-			enrollVC.contextPut("groupName", StringHelper.escapeHtml(waitingListGroup.getName()));
-			String desc = StringHelper.xssScan(waitingListGroup.getDescription());
-			enrollVC.contextPut("groupDesc", (desc == null) ? "" : desc);    	
-		} else {
+			ArrayList<String> groupnames = new ArrayList<String>();
+			for(BusinessGroup group:enrolledGroups){
+				groupnames.add(StringHelper.escapeHtml(group.getName()));
+			}
+			enrollVC.contextPut("groupNames", groupnames);	
+		}else{
 			enrollVC.contextPut("isEnrolledView", Boolean.FALSE);
+		} 
+		
+		if (!waitingListGroups.isEmpty()){
+			enrollVC.contextPut("isInWaitingList", Boolean.TRUE);
+			ArrayList<String> waitingListNames = new ArrayList<String>();
+			for(BusinessGroup waitingGroup:waitingListGroups){
+				waitingListNames.add(StringHelper.escapeHtml(waitingGroup.getName()));
+			}
+			enrollVC.contextPut("waitingListNames", waitingListNames);    	
+		}else{
+			enrollVC.contextPut("isInWaitingList", Boolean.FALSE);
 		}
-		doEnrollMultipleView(ureq);
-	}
-
-	private void doEnrollMultipleView(UserRequest ureq) {
 		// 1. Fetch groups from database
 		List<BusinessGroup> groups = enrollmentManager.loadGroupsFromNames(enrollableGroupKeys, enrollableAreaKeys);
 		List<Integer> members = courseGroupManager.getNumberOfMembersFromGroups(groups);
 		// 2. Build group list
-		groupListModel = new BusinessGroupTableModelWithMaxSize(groups, members, getTranslator(), ureq.getIdentity(), cancelEnrollEnabled);
+		groupListModel = new BusinessGroupTableModelWithMaxSize(groups, members, getTranslator(), ureq.getIdentity(), cancelEnrollEnabled, maxEnrollCount);
 		tableCtr.setTableDataModel(groupListModel);
 		tableCtr.modelChanged();
 		// 3. Add group list to view
diff --git a/src/main/java/org/olat/course/nodes/en/EnrollmentManager.java b/src/main/java/org/olat/course/nodes/en/EnrollmentManager.java
index 06da38432ac..a3a71b2d590 100644
--- a/src/main/java/org/olat/course/nodes/en/EnrollmentManager.java
+++ b/src/main/java/org/olat/course/nodes/en/EnrollmentManager.java
@@ -87,9 +87,11 @@ public class EnrollmentManager extends BasicManager {
 		
 		final EnrollStatus enrollStatus = new EnrollStatus();
 		if (isLogDebugEnabled()) logDebug("doEnroll");
-		// check if the user is already enrolled (user can be enrooled only in one group)
-		if ( ( getBusinessGroupWhereEnrolled( identity, groupKeys, areaKeys, cgm.getCourseEntry()) == null)
-			  && ( getBusinessGroupWhereInWaitingList(identity, groupKeys, areaKeys) == null) ) {
+		// check if the user is able to be enrolled
+		int groupsEnrolledCount = getBusinessGroupsWhereEnrolled(identity, groupKeys, areaKeys, cgm.getCourseEntry()).size();
+		int waitingListCount = getBusinessGroupsWhereInWaitingList(identity, groupKeys, areaKeys).size();
+		int enrollCountConfig = enNode.getModuleConfiguration().getIntegerSafe(ENCourseNode.CONFIG_ALLOW_MULTIPLE_ENROLL_COUNT, 1);
+		if ( (groupsEnrolledCount + waitingListCount) < enrollCountConfig ) {
 			if (isLogDebugEnabled()) logDebug("Identity is not enrolled identity=" + identity.getName() + "  group=" + group.getName());
 			// 1. Check if group has max size defined. If so check if group is full
 			// o_clusterREVIEW cg please review it - also where does the group.getMaxParticipants().equals("") come from??
@@ -106,6 +108,7 @@ public class EnrollmentManager extends BasicManager {
 					enrollStatus.setIsEnrolled(true);
 				} else if(state.getEnrolled() == BGMembership.waiting) {
 					addUserToWaitingList(identity, group, enNode, coursePropertyManager, wControl, trans);
+					enrollStatus.setIsInWaitingList(true);
 				}
 			}
 		} else {
@@ -173,42 +176,25 @@ public class EnrollmentManager extends BasicManager {
 	// ////////////////
 	/**
 	 * @param identity
-	 * @param groupNames
-	 * @return BusinessGroup in which the identity is enrolled, null if identity
-	 *         is nowhere enrolled.
+	 * @param List<Long> groupKeys which are in the list
+	 * @param List<Long> areaKeys which are in the list
+	 * @return List<BusinessGroup> in which the identity is enrolled
 	 */
-	protected BusinessGroup getBusinessGroupWhereEnrolled(Identity identity, List<Long> groupKeys, List<Long> areaKeys, RepositoryEntry courseResource) {
-		// 1. check in groups
+	protected List<BusinessGroup> getBusinessGroupsWhereEnrolled(Identity identity, List<Long> groupKeys, List<Long> areaKeys, RepositoryEntry courseResource) {
+		List<BusinessGroup> groups = new ArrayList<BusinessGroup>();
+		//search in the enrollable bg keys for the groups where identity is attendee
 		if(groupKeys != null && !groupKeys.isEmpty()) {
 			SearchBusinessGroupParams params = new SearchBusinessGroupParams();
 			params.setAttendee(true);
 			params.setIdentity(identity);
 			params.setGroupKeys(groupKeys);
-			List<BusinessGroup> groups = businessGroupService.findBusinessGroups(params, courseResource, 0, 1);
-			if (groups.size() > 0) {
-					// Usually it is only possible to be in one group. However,
-					// theoretically the
-					// admin can put the user in a second enrollment group or the user could
-					// theoretically be in a second group context. For now, we only look for
-					// the first
-					// group. All groups found after the first one are discarded.
-				return groups.get(0);
-			}
+			groups.addAll(businessGroupService.findBusinessGroups(params, courseResource, 0, 0));
 		}
-		// 2. check in areas
+		//search in the enrollable area keys for the groups where identity is attendee
 		if(areaKeys != null && !areaKeys.isEmpty()) {
-			List<BusinessGroup> groups = areaManager.findBusinessGroupsOfAreaAttendedBy(identity, areaKeys, courseResource.getOlatResource());
-			if (groups.size() > 0) {
-				// Usually it is only possible to be in one group. However,
-				// theoretically the
-				// admin can put the user in a second enrollment group or the user could
-				// theoretically be in a second group context. For now, we only look for
-				// the first
-				// group. All groups found after the first one are discarded.
-				return groups.get(0);
-			}
+			groups.addAll(areaManager.findBusinessGroupsOfAreaAttendedBy(identity, areaKeys, courseResource.getOlatResource()));
 		}
-		return null; 
+		return groups; 
 	}
 
 	/**
@@ -217,15 +203,16 @@ public class EnrollmentManager extends BasicManager {
 	 * @return true if this identity is any waiting-list group in this course that
 	 *         has a name that is in the group names list
 	 */
-	protected BusinessGroup getBusinessGroupWhereInWaitingList(Identity identity, List<Long> groupKeys, List<Long> areaKeys) {
+	protected List<BusinessGroup> getBusinessGroupsWhereInWaitingList(Identity identity, List<Long> groupKeys, List<Long> areaKeys) {
 		List<BusinessGroup> groups = loadGroupsFromNames(groupKeys, areaKeys);
+		List<BusinessGroup> waitingInTheseGroups = new ArrayList<BusinessGroup> ();
 		// loop over all business-groups
 		for (BusinessGroup businessGroup:groups) {
 			if (businessGroupService.hasRoles(identity, businessGroup, GroupRoles.waiting.name())) { 
-				return businessGroup;
+				waitingInTheseGroups.add(businessGroup);
 			}
 		}
-		return null;
+		return waitingInTheseGroups;
 	}
 
 	/**
diff --git a/src/main/java/org/olat/course/nodes/en/_content/enrollmultiple.html b/src/main/java/org/olat/course/nodes/en/_content/enrollmultiple.html
index 2fb6c434698..fad413fed2c 100644
--- a/src/main/java/org/olat/course/nodes/en/_content/enrollmultiple.html
+++ b/src/main/java/org/olat/course/nodes/en/_content/enrollmultiple.html
@@ -1,22 +1,40 @@
-	#if ($isEnrolledView)
+  	#if($isEnrolledView || $isInWaitingList)
 	<div class="o_course_run_groupinfo">	
-		<p>
-			<strong>$r.translate("enrolled.group.name"):</strong> 
-			<br />
-			$groupName
-		</p>		
-		#if ($groupDesc != "")
-		<p>
-			<strong>$r.translate("enrolled.group.desc"): </strong>
-			<br />
-			$r.formatLatexFormulas($groupDesc)
-		</p>
-		#end
+			#if($isEnrolledView)
+			<div class="o_block">
+				<h5>$r.translate("enrolled.group.name"):</h5>
+				<ul class="list-inline">
+				#foreach( $group in $groupNames )
+		  			  <li>
+		  			  	<i class='o_icon o_icon-fw o_icon_group'>&nbsp;</i>  $group
+		  			  </li>
+				#end
+				</ul>
+			</div>
+			#end
+			#if( $isInWaitingList )
+			<div class="o_block">
+				<h5>$r.translate("enrolled.waitinglist.name"):</h5>
+				<ul class="list-inline">
+				#foreach( $wlGroup in $waitingListNames )
+					<li>
+	  			  		<i class='o_icon o_icon-fw o_icon_group'>&nbsp;</i>  $wlGroup
+	  			  	</li>
+				#end
+				</ul>
+			</div>
+			#end
+	</div>
+	#end
+
+	#if($multiEnroll )
+	<div class="o_note">
+		$multipleHint
 	</div>
 	#end
 	
 	<div class="o_course_run_statusinfo">	
-	#if ($isEnrolledView)
+	#if($isEnrolledView)
 		<p>
 		#if($isWaitingList)
 		  $r.translate("waitinglist.explain")
diff --git a/src/main/java/org/olat/course/nodes/en/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/nodes/en/_i18n/LocalStrings_de.properties
index 092088da2ce..6e1e32502c5 100644
--- a/src/main/java/org/olat/course/nodes/en/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/course/nodes/en/_i18n/LocalStrings_de.properties
@@ -20,17 +20,21 @@ cmd.enrolled.cancel=Austragen
 condition.accessibility.title=Zugang
 config.header1=Einschreibung in Lerngruppen und Lernbereiche
 enroll.explain=W\u00E4hlen Sie eine der untenstehenden Lerngruppen aus, um sich einzuschreiben.
-enrolled.explain=Sie sind in untenstehender Lerngruppe eingeschrieben. W\u00E4hlen Sie - sofern vorhanden - den Link Austragen, um sich aus der Gruppe auszutragen. <b>Achtung\:</b> Diese Einschreibung betrifft einzig die gew\u00E4hlte Gruppe im entsprechenden OLAT-Kurs.
+enrolled.explain=Sie sind in die untenstehenden Lerngruppen eingeschrieben. W\u00E4hlen Sie - sofern vorhanden - den Link Austragen, um sich aus der entsprechenden Gruppe auszutragen. <b>Achtung\:</b> Diese Einschreibung betrifft einzig die gew\u00E4hlten Gruppen im entsprechenden OLAT-Kurs.
 enrolled.group.desc=Beschreibung
-enrolled.group.name=Name der Lerngruppe
+enrolled.group.name=Namen der Lerngruppen
+enrolled.waitinglist.name=Namen der Wartelisten
 error.group.already.enrolled=Sie sind bereits in der Gruppe eingetragen.
 error.group.full=In der Zwischenzeit ist diese Gruppe vollst\u00E4ndig belegt. Bitte w\u00E4hlen Sie eine andere Gruppe.
 error.nogroupdefined.long=Es muss mindestens eine Lerngruppe oder ein Lernbereich f\u00FCr "{0}" aus dem Gruppenmanagement ausgew\u00E4hlt werden.
 error.nogroupdefined.short=F\u00FCr "{0}" fehlt eine Lerngruppe oder ein Lernbereich.
+error.multipleEnroll=Der Wert muss 1 oder mehr betragen
 form.areanames=Lernbereiche
 form.areanames.example=(Beispiel\: Exkursionen)
 form.areanames.wrong=Geben Sie Namen von Lernbereichen getrennt mit Kommas ein oder lassen Sie dieses Feld leer.
 form.enableCancelEnroll=Austragen erlaubt
+form.allowMultiEnroll= Mehrere Eintragungen erlauben
+form.multipleEnrollCount=Anzahl der Gruppen
 form.groupnames=Lerngruppen
 form.groupnames.example=(Beispiel\: Rot,Gr\u00FCn,Blau)
 form.groupnames.wrong=Geben Sie Namen von Lerngruppen getrennt mit Kommas ein oder lassen Sie dieses Feld leer.
@@ -70,3 +74,5 @@ pane.tab.enconfig=Konfiguration
 popupchooseareas=Lernbereich aus Gruppenmanagement w\u00E4hlen
 popupchoosegroups=Gruppe aus Gruppenmanagement w\u00E4hlen
 waitinglist.explain=Sie sind in untenstehender Lerngruppe auf der Warteliste eingeschrieben. W\u00E4hlen Sie den Link Austragen, um sich aus der Warteliste auszutragen. 
+multiple.select.hint=Sie k\u00F6nnen sich in eine der unten aufgeführen Lerngruppen einschreiben. Sie k\u00F6nnen sich in maximal <b>{0}</b> Gruppen einschreiben.
+multiple.select.hint.outstanding=Sie haben sich in <b>{0}</b> Gruppen eingeschrieben. Sie k\u00F6nnen sich noch in <b>{1}</b> Gruppen einschreiben.
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/nodes/en/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/nodes/en/_i18n/LocalStrings_en.properties
index 21001db76f9..e3afcc02009 100644
--- a/src/main/java/org/olat/course/nodes/en/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/course/nodes/en/_i18n/LocalStrings_en.properties
@@ -19,17 +19,21 @@ cmd.enrolled.cancel=Cancel
 condition.accessibility.title=Access
 config.header1=Enrolment in learning groups and learning areas
 enroll.explain=Choose one of the learning groups below to enrol.
-enrolled.explain=You have already enroled for the learning group mentioned below. To cancel your enrolment please click on the button below (if available). <b>Attention\:</b> Your enrolment concerns only the group selected in the corresponding OLAT course.
+enrolled.explain=You have already enrolled for the learning groups mentioned below. To cancel your enrollment please click on the button below (if available). <b>Attention\:</b> Your enrolment concerns only the groups selected in the corresponding OLAT course.
 enrolled.group.desc=Description
-enrolled.group.name=Name of learning group
+enrolled.group.name=Names of learning groups
+enrolled.waitinglist.name=Names of waiting lists
 error.group.already.enrolled=You are already a registered member of this group.
 error.group.full=In the meantime this group is complete. Please select another one.
 error.nogroupdefined.long=There must be at least one learning group or one learning area selected for "{0}" from the section group management.
 error.nogroupdefined.short=Learning group or learning area missing for "{0}".
+error.multipleEnroll=The Value must be 1 or higher
 form.areanames=Learning areas
 form.areanames.example=(Example\: Study trips)
 form.areanames.wrong=Enter names of learning areas separated by commas or leave this field empty.
 form.enableCancelEnroll=Delisting permitted
+form.allowMultiEnroll=Allow multiple enrollments
+form.multipleEnrollCount=Number of groups
 form.groupnames=Learning groups
 form.groupnames.example=(Example\: Green,Red,Blue)
 form.groupnames.wrong=Enter names of learning groups separated by commas or leave this field empty.
@@ -70,3 +74,5 @@ popupchooseareas=Select learning areas from group management
 popupchoosegroups=Select groups from group management
 title_en=Enrolment
 waitinglist.explain=You are on the waiting list of the learning group mentioned below. Click 'Cancel enrolment' in order to delist.
+multiple.select.hint=Choose from the learning groups below to enroll. You are allowed to enroll to a total of <b>{0}</b> groups.
+multiple.select.hint.outstanding=You have enrolled to <b>{0}</b> Group. You can enroll to <b>{1}</b> more groups.
\ No newline at end of file
diff --git a/src/main/java/org/olat/group/ui/BusinessGroupTableModelWithMaxSize.java b/src/main/java/org/olat/group/ui/BusinessGroupTableModelWithMaxSize.java
index 1e35c34fa73..1d0962c8b72 100644
--- a/src/main/java/org/olat/group/ui/BusinessGroupTableModelWithMaxSize.java
+++ b/src/main/java/org/olat/group/ui/BusinessGroupTableModelWithMaxSize.java
@@ -37,6 +37,7 @@ import org.olat.core.id.Identity;
 import org.olat.core.logging.OLog;
 import org.olat.core.logging.Tracing;
 import org.olat.core.util.Formatter;
+import org.olat.core.util.StringHelper;
 import org.olat.core.util.filter.FilterFactory;
 import org.olat.group.BusinessGroup;
 import org.olat.group.BusinessGroupService;
@@ -59,6 +60,7 @@ public class BusinessGroupTableModelWithMaxSize extends DefaultTableDataModel<Bu
 	private Identity identity;
 	private boolean cancelEnrollEnabled;
 	private BusinessGroupService businessGroupService;
+	private int maxEnrolCount;
 
 	/**
 	 * @param groups List of business groups
@@ -66,13 +68,14 @@ public class BusinessGroupTableModelWithMaxSize extends DefaultTableDataModel<Bu
 	 *          The index of the list corresponds with the index of the group list
 	 * @param trans
 	 */
-	public BusinessGroupTableModelWithMaxSize(List<BusinessGroup> groups, List<Integer> members, Translator trans, Identity identity, boolean cancelEnrollEnabled) {
+	public BusinessGroupTableModelWithMaxSize(List<BusinessGroup> groups, List<Integer> members, Translator trans, Identity identity, boolean cancelEnrollEnabled, int maxEnrolCount) {
 		super(groups);
 		this.members = members;
 		this.trans = trans;
 		this.identity = identity;
 		businessGroupService = CoreSpringFactory.getImpl(BusinessGroupService.class);
 		this.cancelEnrollEnabled = cancelEnrollEnabled;
+		this.maxEnrolCount = maxEnrolCount;
 	}
 
 	/**
@@ -136,8 +139,8 @@ public class BusinessGroupTableModelWithMaxSize extends DefaultTableDataModel<Bu
 				return trans.translate("grouplist.table.state.notEnrolled");
 			case 5:
 				// Action enroll
-				if (isEnrolledInAnyGroup(identity)) {
-					// Allready enrolled => does not show action-link 'enroll'
+				if (getEnrolCount(identity) >= maxEnrolCount || isEnrolledIn(businessGroup, identity)) {
+					// Already too much enrollments or already enrolled in the bg of the row => does not show action-link 'enroll'
 					return Boolean.FALSE;
 				}
 				if (max != null && !businessGroup.getWaitingListEnabled().booleanValue() && (numbParts.intValue() >= max.intValue()) ) {
@@ -166,7 +169,7 @@ public class BusinessGroupTableModelWithMaxSize extends DefaultTableDataModel<Bu
 
 	@Override
 	public Object createCopyWithEmptyList() {
-		return new BusinessGroupTableModelWithMaxSize(new ArrayList<BusinessGroup>(), members, trans, identity, cancelEnrollEnabled);
+		return new BusinessGroupTableModelWithMaxSize(new ArrayList<BusinessGroup>(), members, trans, identity, cancelEnrollEnabled, maxEnrolCount);
 	}
 
 	/**
@@ -201,16 +204,21 @@ public class BusinessGroupTableModelWithMaxSize extends DefaultTableDataModel<Bu
 	/**
 	 * Check if an identity is in any security-group.
 	 * @param ident
-	 * @return true: Found identity in any security-group of this table model.
+	 * @return amount of business groups the identity is enrolled in
 	 */		
-	private boolean isEnrolledInAnyGroup(Identity ident) {
+	private int getEnrolCount(Identity ident) {
+		int enrolCount=0;
 		// loop over all business-groups
 		for (BusinessGroup businessGroup:objects) {
 			if (isEnrolledIn(businessGroup, ident) ) {
-				return true;
+				enrolCount++;
+				// optimize, enough is enough
+				if (maxEnrolCount == enrolCount) {
+					return enrolCount;
+				}
 			}
 		}
-		return false;
+		return enrolCount;
 	}
 
 	@Override
-- 
GitLab