diff --git a/src/main/java/org/olat/admin/user/UserSearchController.java b/src/main/java/org/olat/admin/user/UserSearchController.java
index 5866d305ec591eebbf9b953b00e9d3a869546c4b..e7d722cd3b68d8266b601fce4677a1fceeba2e0a 100644
--- a/src/main/java/org/olat/admin/user/UserSearchController.java
+++ b/src/main/java/org/olat/admin/user/UserSearchController.java
@@ -22,9 +22,7 @@ package org.olat.admin.user;
 
 import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.Iterator;
 import java.util.List;
-import java.util.Locale;
 import java.util.Map;
 
 import org.olat.basesecurity.BaseSecurityManager;
@@ -34,12 +32,6 @@ import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.Windows;
 import org.olat.core.gui.components.Component;
 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.TextElement;
-import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
-import org.olat.core.gui.components.form.flexible.impl.FormEvent;
-import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
 import org.olat.core.gui.components.link.Link;
 import org.olat.core.gui.components.link.LinkFactory;
 import org.olat.core.gui.components.panel.Panel;
@@ -57,18 +49,13 @@ import org.olat.core.gui.control.controller.BasicController;
 import org.olat.core.gui.control.generic.ajax.autocompletion.AutoCompleterController;
 import org.olat.core.gui.control.generic.ajax.autocompletion.EntriesChosenEvent;
 import org.olat.core.gui.control.generic.ajax.autocompletion.ListProvider;
-import org.olat.core.gui.control.generic.ajax.autocompletion.ListReceiver;
 import org.olat.core.gui.translator.PackageTranslator;
 import org.olat.core.gui.translator.Translator;
-import org.olat.core.gui.util.CSSHelper;
 import org.olat.core.id.Identity;
 import org.olat.core.id.Roles;
-import org.olat.core.id.User;
-import org.olat.core.id.UserConstants;
 import org.olat.core.util.StringHelper;
 import org.olat.core.util.Util;
 import org.olat.user.UserManager;
-import org.olat.user.propertyhandlers.EmailProperty;
 import org.olat.user.propertyhandlers.UserPropertyHandler;
 
 /**
@@ -198,41 +185,9 @@ public class UserSearchController extends BasicController {
 		myContent.contextPut("showButton","false");
 		
 		boolean ajax = Windows.getWindows(ureq).getWindowManager().isAjaxEnabled();
-		final Locale loc = ureq.getLocale();
 		if (ajax) {
 			// insert a autocompleter search
-			ListProvider provider = new ListProvider() {
-				/**
-				 * @see org.olat.core.gui.control.generic.ajax.autocompletion.ListProvider#getResult(java.lang.String,
-				 *      org.olat.core.gui.control.generic.ajax.autocompletion.ListReceiver)
-				 */
-				public void getResult(String searchValue, ListReceiver receiver) {
-					Map<String, String> userProperties = new HashMap<String, String>();
-					// We can only search in mandatory User-Properties due to problems
-					// with hibernate query with join and not existing rows
-					userProperties.put(UserConstants.FIRSTNAME, searchValue);
-					userProperties.put(UserConstants.LASTNAME, searchValue);
-					userProperties.put(UserConstants.EMAIL, searchValue);
-					// Search in all fileds -> non intersection search
-					List<Identity> res = searchUsers(searchValue,	userProperties, false);
-					int maxEntries = 15;
-					boolean hasMore = false;
-					for (Iterator<Identity> it_res = res.iterator(); (hasMore=it_res.hasNext()) && maxEntries > 0;) {
-						maxEntries--;
-						Identity ident = it_res.next();
-						User u = ident.getUser();
-						String key = ident.getKey().toString();
-						String displayKey = ident.getName();
-						String first = u.getProperty(UserConstants.FIRSTNAME, loc);
-						String last = u.getProperty(UserConstants.LASTNAME, loc);
-						String displayText = last + " " + first;
-						receiver.addEntry(key, displayKey, displayText, CSSHelper.CSS_CLASS_USER);
-					}					
-					if(hasMore){
-						receiver.addEntry(".....",".....");
-					}
-				}
-			};
+			ListProvider provider = new UserSearchListProvider();
 			autocompleterC = new AutoCompleterController(ureq, getWindowControl(), provider, null, isAdmin, 60, 3, null);
 			listenTo(autocompleterC);
 			myContent.put("autocompletionsearch", autocompleterC.getInitialComponent());
@@ -380,168 +335,4 @@ public class UserSearchController extends BasicController {
 			userPropertiesSearch, userPropertiesAsIntersectionSearch,	// in normal search fields are intersected
 			null, null, null, null, null);
 	}
-}
-
-
-/**
- * <pre>
- *
- * Initial Date:  Jul 29, 2003
- *
- * @author gnaegi
- * 
- * Comment:  
- * The user search form
- * </pre>
- */
-class UserSearchForm extends FormBasicController {
-	
-	private final boolean isAdmin, cancelButton;
-	private FormLink searchButton;
-	
-	protected TextElement login;
-	protected List<UserPropertyHandler> userPropertyHandlers;
-	protected Map <String,FormItem>propFormItems;
-	
-	/**
-	 * @param name
-	 * @param cancelbutton
-	 * @param isAdmin if true, no field must be filled in at all, otherwise
-	 *          validation takes place
-	 */
-	public UserSearchForm(UserRequest ureq, WindowControl wControl, boolean isAdmin, boolean cancelButton) {
-		super(ureq, wControl);
-		
-		this.isAdmin = isAdmin;
-		this.cancelButton = cancelButton;
-	
-		initForm(ureq);
-	}
-	
-	@Override
-	@SuppressWarnings("unused")
-	public boolean validateFormLogic (UserRequest ureq) {
-		// override for admins
-		if (isAdmin) return true;
-		
-		boolean filled = !login.isEmpty();
-		StringBuffer  full = new StringBuffer(login.getValue().trim());  
-		FormItem lastFormElement = login;
-		
-		// DO NOT validate each user field => see OLAT-3324
-		// this are custom fields in a Search Form
-		// the same validation logic can not be applied
-		// i.e. email must be searchable and not about getting an error like
-		// "this e-mail exists already"
-		for (UserPropertyHandler userPropertyHandler : userPropertyHandlers) {
-			FormItem ui = propFormItems.get(userPropertyHandler.getName());
-			String uiValue = userPropertyHandler.getStringValue(ui);
-			// add value for later non-empty search check
-			if (StringHelper.containsNonWhitespace(uiValue)) {
-				full.append(uiValue.trim());
-				filled = true;
-			}else{
-				//its an empty field
-				filled = filled || false;
-			}
-
-			lastFormElement = ui;
-		}
-
-		// Don't allow searches with * or %  or @ chars only (wild cards). We don't want
-		// users to get a complete list of all OLAT users this easily.
-		String fullString = full.toString();
-		boolean onlyStar= fullString.matches("^[\\*\\s@\\%]*$");
-
-		if (!filled || onlyStar) {
-			// set the error message
-			lastFormElement.setErrorKey("error.search.form.notempty", null);
-			return false;
-		}
-		if ( fullString.contains("**") ) {
-			lastFormElement.setErrorKey("error.search.form.no.wildcard.dublicates", null);
-			return false;
-		}		
-		int MIN_LENGTH = 4;
-		if ( fullString.length() < MIN_LENGTH ) {
-			lastFormElement.setErrorKey("error.search.form.to.short", null);
-			return false;
-		}
-		
-		return true;
-	}
-
-	@Override
-	@SuppressWarnings("unused")
-	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
-		login = uifactory.addTextElement("login", "search.form.login", 128, "", formLayout);
-
-		UserManager um = UserManager.getInstance();
-		Translator tr = Util.createPackageTranslator(
-				UserPropertyHandler.class,
-				getLocale(), 
-				getTranslator()
-		);
-		
-		userPropertyHandlers = um.getUserPropertyHandlersFor(
-				getClass().getCanonicalName(), isAdmin
-		);
-		
-		propFormItems = new HashMap<String,FormItem>();
-		for (UserPropertyHandler userPropertyHandler : userPropertyHandlers) {
-			if (userPropertyHandler == null) continue;
-			
-			FormItem fi = userPropertyHandler.addFormItem(
-					getLocale(), null, getClass().getCanonicalName(), false, formLayout
-			);
-			fi.setTranslator(tr);
-			
-			// DO NOT validate email field => see OLAT-3324, OO-155, OO-222
-			if (userPropertyHandler instanceof EmailProperty && fi instanceof TextElement) {
-				TextElement textElement = (TextElement)fi;
-				textElement.setItemValidatorProvider(null);
-			}
-
-			propFormItems.put(userPropertyHandler.getName(), fi);
-		}
-		
-		FormLayoutContainer buttonGroupLayout = FormLayoutContainer.createButtonLayout("buttonGroupLayout", getTranslator());
-		formLayout.add(buttonGroupLayout);
-
-		// Don't use submit button, form should not be marked as dirty since this is
-		// not a configuration form but only a search form (OLAT-5626)
-		searchButton = uifactory.addFormLink("submit.search", buttonGroupLayout, Link.BUTTON);
-		searchButton.addActionListener(this, FormEvent.ONCLICK);
-		if (cancelButton) {
-			uifactory.addFormCancelButton("cancel", buttonGroupLayout, ureq, getWindowControl());
-		}
-	}
-
-	/**
-	 * @see org.olat.core.gui.components.form.flexible.impl.FormBasicController#formInnerEvent(org.olat.core.gui.UserRequest,
-	 *      org.olat.core.gui.components.form.flexible.FormItem,
-	 *      org.olat.core.gui.components.form.flexible.impl.FormEvent)
-	 */
-	@Override
-	protected void formInnerEvent(UserRequest ureq, FormItem source, @SuppressWarnings("unused") FormEvent event) {
-		if (source == searchButton) {
-			source.getRootForm().submit(ureq);			
-		}
-	}
-	
-	@Override
-	protected void formOK(UserRequest ureq) {
-		fireEvent (ureq, Event.DONE_EVENT);
-	}
-	
-	@Override
-	protected void formCancelled(UserRequest ureq) {
-		fireEvent (ureq, Event.CANCELLED_EVENT);
-	}
-	
-	@Override
-	protected void doDispose() {
-		//
-	}
-
 }
\ No newline at end of file
diff --git a/src/main/java/org/olat/admin/user/UserSearchFlexiController.java b/src/main/java/org/olat/admin/user/UserSearchFlexiController.java
new file mode 100644
index 0000000000000000000000000000000000000000..87a8437059e8463ed4adf5bd334998e53bc96761
--- /dev/null
+++ b/src/main/java/org/olat/admin/user/UserSearchFlexiController.java
@@ -0,0 +1,308 @@
+/**
+ * <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.admin.user;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.olat.basesecurity.BaseSecurityManager;
+import org.olat.basesecurity.events.MultiIdentityChosenEvent;
+import org.olat.basesecurity.events.SingleIdentityChosenEvent;
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.Component;
+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.impl.Form;
+import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
+import org.olat.core.gui.components.form.flexible.impl.FormEvent;
+import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
+import org.olat.core.gui.components.panel.Panel;
+import org.olat.core.gui.components.table.StaticColumnDescriptor;
+import org.olat.core.gui.components.table.Table;
+import org.olat.core.gui.components.table.TableController;
+import org.olat.core.gui.components.table.TableEvent;
+import org.olat.core.gui.components.table.TableGuiConfiguration;
+import org.olat.core.gui.components.table.TableMultiSelectEvent;
+import org.olat.core.gui.control.Controller;
+import org.olat.core.gui.control.Event;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.core.gui.control.generic.ajax.autocompletion.EntriesChosenEvent;
+import org.olat.core.gui.control.generic.ajax.autocompletion.FlexiAutoCompleterController;
+import org.olat.core.gui.control.generic.ajax.autocompletion.ListProvider;
+import org.olat.core.id.Identity;
+import org.olat.core.id.Roles;
+import org.olat.core.util.StringHelper;
+import org.olat.user.propertyhandlers.UserPropertyHandler;
+
+/**
+ * Initial Date:  Jul 29, 2003
+ *
+ * @author Felix Jost, Florian Gnaegi
+ * 
+ * <pre>
+ * Comment:  
+ * Subworkflow that allows the user to search for a user and choose the user from 
+ * the list of users that match the search criteria. Users can be searched by
+ * <ul>
+ * <li />
+ * Username
+ * <li />
+ * First name
+ * <li />
+ * Last name
+ * <li />
+ * Email address
+ * </ul>
+ * 
+ * </pre>
+ * 
+ * Events:<br>
+ *         Fires a SingleIdentityChoosenEvent when an identity has been chosen
+ *         which contains the choosen identity<br>
+ *         Fires a MultiIdentityChoosenEvent when multiples identities have been
+ *         chosen which contains the choosen identities<br>
+ *         <p>
+ *         Optionally set the useMultiSelect boolean to true which allows to
+ *         select multiple identities from within the search results.
+ */
+public class UserSearchFlexiController extends FormBasicController {
+
+	private static final String ACTION_SINGLESELECT_CHOOSE = "ssc";
+	private static final String ACTION_MULTISELECT_CHOOSE = "msc";
+
+	private Panel searchPanel;
+	private UserSearchForm searchform;
+	private TableController tableCtr;
+	private TableGuiConfiguration tableConfig;
+	private UserTableDataModel tdm;
+	private List<Identity> foundIdentities = new ArrayList<Identity>();
+	private boolean useMultiSelect = false;
+	private Object userObject;
+	
+	private FlexiAutoCompleterController autocompleterC;
+	private String actionKeyChoose;
+	private boolean isAdministrativeUser;
+	private FormLink backLink;
+
+	public static final String ACTION_KEY_CHOOSE = "action.choose";
+	public static final String ACTION_KEY_CHOOSE_FINISH = "action.choose.finish";
+
+	/**
+	 * @param ureq
+	 * @param wControl
+	 * @param cancelbutton
+	 * @param userMultiSelect
+	 * @param statusEnabled
+	 */
+	public UserSearchFlexiController(UserRequest ureq, WindowControl wControl, boolean userMultiSelect,
+			boolean statusEnabled, String actionKeyChooseFinish, Form rootForm) {
+		super(ureq, wControl, LAYOUT_CUSTOM, "usersearch", rootForm);
+		this.useMultiSelect = userMultiSelect;
+		this.actionKeyChoose = ACTION_KEY_CHOOSE;
+
+		initForm(ureq);
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		if(formLayout instanceof FormLayoutContainer) {
+			FormLayoutContainer layoutCont = (FormLayoutContainer)formLayout;
+			backLink = uifactory.addFormLink("btn.back", formLayout);
+
+			searchPanel = new Panel("usersearchPanel");
+			layoutCont.put("usersearchPanel", searchPanel);
+	
+			boolean isAdmin = ureq.getUserSession().getRoles().isOLATAdmin();
+			searchform = new UserSearchForm(ureq, getWindowControl(), isAdmin, false, formLayout.getRootForm());
+			listenTo(searchform);
+			
+			searchPanel.setContent(searchform.getInitialComponent());
+			layoutCont.contextPut("noList","false");			
+			layoutCont.contextPut("showButton","false");
+
+			// insert a autocompleter search
+			ListProvider provider = new UserSearchListProvider();
+			autocompleterC = new FlexiAutoCompleterController(ureq, getWindowControl(), provider, null, isAdmin, 60, 3, null);
+			listenTo(autocompleterC);
+			layoutCont.put("autocompletionsearch", autocompleterC.getInitialComponent());
+
+			//add the table
+			tableConfig = new TableGuiConfiguration();
+			tableConfig.setTableEmptyMessage(translate("error.no.user.found"));
+			tableConfig.setDownloadOffered(false);// no download because user should not download user-list
+			tableCtr = new TableController(tableConfig, ureq, getWindowControl(), getTranslator());
+			listenTo(tableCtr);
+			
+			Roles roles = ureq.getUserSession().getRoles();
+			isAdministrativeUser = (roles.isAuthor() || roles.isGroupManager() || roles.isUserManager() || roles.isOLATAdmin());
+		}
+	}
+
+	public Object getUserObject() {
+		return userObject;
+	}
+
+	public void setUserObject(Object userObject) {
+		this.userObject = userObject;
+	}
+
+	@Override
+	public void event(UserRequest ureq, Component source, Event event) {
+		if (source == backLink) {		
+			flc.contextPut("noList","false");			
+			flc.contextPut("showButton","false");
+			searchPanel.popContent();
+		} else {
+			super.event(ureq, source, event);
+		}
+	}
+
+	@Override
+	protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
+		if(source == backLink) {
+			flc.contextPut("noList","false");			
+			flc.contextPut("showButton","false");
+			searchPanel.popContent();
+		} else {
+			super.formInnerEvent(ureq, source, 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)
+	 */
+	public void event(UserRequest ureq, Controller source, Event event) {
+		if (source == tableCtr) {
+			if (event.getCommand().equals(Table.COMMANDLINK_ROWACTION_CLICKED)) {
+				TableEvent te = (TableEvent) event;
+				if (te.getActionId().equals(ACTION_SINGLESELECT_CHOOSE)) {
+					int rowid = te.getRowId();
+					Identity foundIdentity = (Identity)tdm.getObject(rowid);
+					foundIdentities.add(foundIdentity);
+					// Tell parentController that a subject has been found
+					fireEvent(ureq, new SingleIdentityChosenEvent(foundIdentity));
+				}
+			} else if (event.getCommand().equals(Table.COMMAND_MULTISELECT)) {
+				TableMultiSelectEvent tmse = (TableMultiSelectEvent) event;
+				if (tmse.getAction().equals(ACTION_MULTISELECT_CHOOSE)) {
+					foundIdentities = tdm.getObjects(tmse.getSelection());
+					fireEvent(ureq, new MultiIdentityChosenEvent(foundIdentities));
+				}
+			}
+		} else if (source == autocompleterC) {
+			EntriesChosenEvent ece = (EntriesChosenEvent)event;
+			List<String> res = ece.getEntries();
+			// if we get the event, we have a result or an incorrect selection see OLAT-5114 -> check for empty
+			String mySel = res.isEmpty() ? null : res.get(0);
+			if (( mySel == null) || mySel.trim().equals("")) {
+				getWindowControl().setWarning(translate("error.search.form.notempty"));
+				return;
+			}
+			Long key = -1l; // default not found
+			try {
+				key = Long.valueOf(mySel);				
+				if (key > 0) {
+					Identity chosenIdent = BaseSecurityManager.getInstance().loadIdentityByKey(key);
+					// No need to check for null, exception is thrown when identity does not exist which really 
+					// should not happen at all. 
+					// Tell that an identity has been chosen
+					fireEvent(ureq, new SingleIdentityChosenEvent(chosenIdent));
+				}
+			} catch (NumberFormatException e) {
+				getWindowControl().setWarning(translate("error.no.user.found"));
+				return;									
+			}
+		} else if (source == searchform) {
+			if (event == Event.DONE_EVENT) {
+				// form validation was ok
+
+				String login = searchform.login.getValue();
+				// build user fields search map
+				Map<String, String> userPropertiesSearch = new HashMap<String, String>();				
+				for (UserPropertyHandler userPropertyHandler : searchform.userPropertyHandlers) {
+					if (userPropertyHandler == null) continue;
+					FormItem ui = searchform.propFormItems.get(userPropertyHandler.getName());
+					String uiValue = userPropertyHandler.getStringValue(ui);
+					if (StringHelper.containsNonWhitespace(uiValue)) {
+						userPropertiesSearch.put(userPropertyHandler.getName(), uiValue);
+						getLogger().info("Search property:" + userPropertyHandler.getName() + "=" + uiValue);
+					}
+				}
+				if (userPropertiesSearch.isEmpty()) userPropertiesSearch = null;
+				
+				tableCtr = new TableController(tableConfig, ureq, getWindowControl(), getTranslator());
+				listenTo(tableCtr);
+				
+				List<Identity> users = searchUsers(login,	userPropertiesSearch, true);
+				if (!users.isEmpty()) {
+					tdm = new UserTableDataModel(users, ureq.getLocale(), isAdministrativeUser);
+					// add the data column descriptors
+					tdm.addColumnDescriptors(tableCtr, null);
+					// add the action columns
+					if (useMultiSelect) {
+						// add multiselect action
+						tableCtr.addMultiSelectAction(actionKeyChoose, ACTION_MULTISELECT_CHOOSE);
+					} else {
+						// add single column selec action
+						tableCtr.addColumnDescriptor(new StaticColumnDescriptor(ACTION_SINGLESELECT_CHOOSE, "table.header.action", translate("action.choose")));
+					}
+					tableCtr.setTableDataModel(tdm);
+					tableCtr.setMultiSelect(useMultiSelect);
+					searchPanel.pushContent(tableCtr.getInitialComponent());
+					flc.contextPut("showButton","true");
+				} else {
+					getWindowControl().setInfo(translate("error.no.user.found"));
+				}
+			} else if (event == Event.CANCELLED_EVENT) {
+				fireEvent(ureq, Event.CANCELLED_EVENT);
+			}
+		}
+		
+	}
+	
+	@Override
+	protected void formOK(UserRequest ureq) {
+		//
+	}
+	
+	/**
+	 * @see org.olat.core.gui.control.DefaultController#doDispose(boolean)
+	 */
+	protected void doDispose() {
+		// Child controllers auto-disposed by basic controller
+	}
+
+	/**
+	 * Can be overwritten by subclassen to search other users or filter users.
+	 * @param login
+	 * @param userPropertiesSearch
+	 * @return
+	 */
+	protected List<Identity> searchUsers(String login, Map<String, String> userPropertiesSearch, boolean userPropertiesAsIntersectionSearch) {
+	  return BaseSecurityManager.getInstance().getVisibleIdentitiesByPowerSearch(
+			(login.equals("") ? null : login),
+			userPropertiesSearch, userPropertiesAsIntersectionSearch,	// in normal search fields are intersected
+			null, null, null, null, null);
+	}
+}
diff --git a/src/main/java/org/olat/admin/user/UserSearchForm.java b/src/main/java/org/olat/admin/user/UserSearchForm.java
new file mode 100644
index 0000000000000000000000000000000000000000..7bad5488246dc6bb69ef072b5d22cdcc47cfd947
--- /dev/null
+++ b/src/main/java/org/olat/admin/user/UserSearchForm.java
@@ -0,0 +1,214 @@
+/**
+ * <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.admin.user;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+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.TextElement;
+import org.olat.core.gui.components.form.flexible.impl.Form;
+import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
+import org.olat.core.gui.components.form.flexible.impl.FormEvent;
+import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
+import org.olat.core.gui.components.link.Link;
+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.translator.Translator;
+import org.olat.core.util.StringHelper;
+import org.olat.core.util.Util;
+import org.olat.user.UserManager;
+import org.olat.user.propertyhandlers.EmailProperty;
+import org.olat.user.propertyhandlers.UserPropertyHandler;
+
+
+/**
+ * <pre>
+ *
+ * Initial Date:  Jul 29, 2003
+ *
+ * @author gnaegi
+ * 
+ * Comment:  
+ * The user search form
+ * </pre>
+ */
+public class UserSearchForm extends FormBasicController {
+	
+	private final boolean isAdmin, cancelButton;
+	private FormLink searchButton;
+	
+	protected TextElement login;
+	protected List<UserPropertyHandler> userPropertyHandlers;
+	protected Map <String,FormItem>propFormItems;
+	
+	/**
+	 * @param name
+	 * @param cancelbutton
+	 * @param isAdmin if true, no field must be filled in at all, otherwise
+	 *          validation takes place
+	 */
+	public UserSearchForm(UserRequest ureq, WindowControl wControl, boolean isAdmin, boolean cancelButton) {
+		super(ureq, wControl);
+		
+		this.isAdmin = isAdmin;
+		this.cancelButton = cancelButton;
+	
+		initForm(ureq);
+	}
+	
+	public UserSearchForm(UserRequest ureq, WindowControl wControl, boolean isAdmin, boolean cancelButton, Form rootForm) {
+		super(ureq, wControl, LAYOUT_DEFAULT, null, rootForm);
+		
+		this.isAdmin = isAdmin;
+		this.cancelButton = cancelButton;
+	
+		initForm(ureq);
+	}
+	
+	@Override
+	public boolean validateFormLogic (UserRequest ureq) {
+		// override for admins
+		if (isAdmin) return true;
+		
+		boolean filled = !login.isEmpty();
+		StringBuffer  full = new StringBuffer(login.getValue().trim());  
+		FormItem lastFormElement = login;
+		
+		// DO NOT validate each user field => see OLAT-3324
+		// this are custom fields in a Search Form
+		// the same validation logic can not be applied
+		// i.e. email must be searchable and not about getting an error like
+		// "this e-mail exists already"
+		for (UserPropertyHandler userPropertyHandler : userPropertyHandlers) {
+			FormItem ui = propFormItems.get(userPropertyHandler.getName());
+			String uiValue = userPropertyHandler.getStringValue(ui);
+			// add value for later non-empty search check
+			if (StringHelper.containsNonWhitespace(uiValue)) {
+				full.append(uiValue.trim());
+				filled = true;
+			}else{
+				//its an empty field
+				filled = filled || false;
+			}
+
+			lastFormElement = ui;
+		}
+
+		// Don't allow searches with * or %  or @ chars only (wild cards). We don't want
+		// users to get a complete list of all OLAT users this easily.
+		String fullString = full.toString();
+		boolean onlyStar= fullString.matches("^[\\*\\s@\\%]*$");
+
+		if (!filled || onlyStar) {
+			// set the error message
+			lastFormElement.setErrorKey("error.search.form.notempty", null);
+			return false;
+		}
+		if ( fullString.contains("**") ) {
+			lastFormElement.setErrorKey("error.search.form.no.wildcard.dublicates", null);
+			return false;
+		}		
+		int MIN_LENGTH = 4;
+		if ( fullString.length() < MIN_LENGTH ) {
+			lastFormElement.setErrorKey("error.search.form.to.short", null);
+			return false;
+		}
+		
+		return true;
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		login = uifactory.addTextElement("login", "search.form.login", 128, "", formLayout);
+
+		UserManager um = UserManager.getInstance();
+		Translator tr = Util.createPackageTranslator(
+				UserPropertyHandler.class,
+				getLocale(), 
+				getTranslator()
+		);
+		
+		userPropertyHandlers = um.getUserPropertyHandlersFor(
+				getClass().getCanonicalName(), isAdmin
+		);
+		
+		propFormItems = new HashMap<String,FormItem>();
+		for (UserPropertyHandler userPropertyHandler : userPropertyHandlers) {
+			if (userPropertyHandler == null) continue;
+			
+			FormItem fi = userPropertyHandler.addFormItem(
+					getLocale(), null, getClass().getCanonicalName(), false, formLayout
+			);
+			fi.setTranslator(tr);
+			
+			// DO NOT validate email field => see OLAT-3324, OO-155, OO-222
+			if (userPropertyHandler instanceof EmailProperty && fi instanceof TextElement) {
+				TextElement textElement = (TextElement)fi;
+				textElement.setItemValidatorProvider(null);
+			}
+
+			propFormItems.put(userPropertyHandler.getName(), fi);
+		}
+		
+		FormLayoutContainer buttonGroupLayout = FormLayoutContainer.createButtonLayout("buttonGroupLayout", getTranslator());
+		formLayout.add(buttonGroupLayout);
+
+		// Don't use submit button, form should not be marked as dirty since this is
+		// not a configuration form but only a search form (OLAT-5626)
+		searchButton = uifactory.addFormLink("submit.search", buttonGroupLayout, Link.BUTTON);
+		searchButton.addActionListener(this, FormEvent.ONCLICK);
+		if (cancelButton) {
+			uifactory.addFormCancelButton("cancel", buttonGroupLayout, ureq, getWindowControl());
+		}
+	}
+
+	/**
+	 * @see org.olat.core.gui.components.form.flexible.impl.FormBasicController#formInnerEvent(org.olat.core.gui.UserRequest,
+	 *      org.olat.core.gui.components.form.flexible.FormItem,
+	 *      org.olat.core.gui.components.form.flexible.impl.FormEvent)
+	 */
+	@Override
+	protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
+		if (source == searchButton) {
+			source.getRootForm().submit(ureq);			
+		}
+	}
+	
+	@Override
+	protected void formOK(UserRequest ureq) {
+		fireEvent (ureq, Event.DONE_EVENT);
+	}
+	
+	@Override
+	protected void formCancelled(UserRequest ureq) {
+		fireEvent (ureq, Event.CANCELLED_EVENT);
+	}
+	
+	@Override
+	protected void doDispose() {
+		//
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/admin/user/UserSearchListProvider.java b/src/main/java/org/olat/admin/user/UserSearchListProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..8d79f1a62db13c30e6ad12d68c44aa53d4112351
--- /dev/null
+++ b/src/main/java/org/olat/admin/user/UserSearchListProvider.java
@@ -0,0 +1,84 @@
+/**
+ * <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.admin.user;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.olat.basesecurity.BaseSecurity;
+import org.olat.core.CoreSpringFactory;
+import org.olat.core.gui.control.generic.ajax.autocompletion.ListProvider;
+import org.olat.core.gui.control.generic.ajax.autocompletion.ListReceiver;
+import org.olat.core.gui.util.CSSHelper;
+import org.olat.core.id.Identity;
+import org.olat.core.id.User;
+import org.olat.core.id.UserConstants;
+import org.olat.user.UserManager;
+
+/**
+ * 
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ */
+public class UserSearchListProvider implements ListProvider {
+	
+	private final BaseSecurity securityManager;
+	private final UserManager userManager;
+	
+	public UserSearchListProvider() {
+		securityManager = CoreSpringFactory.getImpl(BaseSecurity.class);
+		userManager = CoreSpringFactory.getImpl(UserManager.class);
+	}
+	
+	
+	@Override
+	public void getResult(String searchValue, ListReceiver receiver) {
+		Map<String, String> userProperties = new HashMap<String, String>();
+		// We can only search in mandatory User-Properties due to problems
+		// with hibernate query with join and not existing rows
+		userProperties.put(UserConstants.FIRSTNAME, searchValue);
+		userProperties.put(UserConstants.LASTNAME, searchValue);
+		userProperties.put(UserConstants.EMAIL, searchValue);
+		// Search in all fileds -> non intersection search
+		List<Identity> res = searchUsers(searchValue,	userProperties, false);
+		int maxEntries = 15;
+		boolean hasMore = false;
+		for (Iterator<Identity> it_res = res.iterator(); (hasMore=it_res.hasNext()) && maxEntries > 0;) {
+			maxEntries--;
+			Identity ident = it_res.next();
+			User u = ident.getUser();
+			String key = ident.getKey().toString();
+			String displayKey = ident.getName();
+			String displayText = userManager.getUserDisplayName(u);
+			receiver.addEntry(key, displayKey, displayText, CSSHelper.CSS_CLASS_USER);
+		}					
+		if(hasMore){
+			receiver.addEntry(".....",".....");
+		}
+	}
+	
+	protected List<Identity> searchUsers(String login, Map<String, String> userPropertiesSearch, boolean userPropertiesAsIntersectionSearch) {
+	  return securityManager.getVisibleIdentitiesByPowerSearch(
+			(login.equals("") ? null : login),
+			userPropertiesSearch, userPropertiesAsIntersectionSearch,	// in normal search fields are intersected
+			null, null, null, null, null);
+	}
+}
diff --git a/src/main/java/org/olat/admin/user/_content/usersearch.html b/src/main/java/org/olat/admin/user/_content/usersearch.html
index 75c2cdf92920c8bec42f926ebe8488d7243491c9..39bf4ed8d2dfe3f2fe37cc18f5d3280fe1c164d5 100644
--- a/src/main/java/org/olat/admin/user/_content/usersearch.html
+++ b/src/main/java/org/olat/admin/user/_content/usersearch.html
@@ -1,4 +1,4 @@
-#if ($showButton == "true")
+#if ($showButton == "true" && $r.available("btn.back"))
 $r.render("btn.back")
 <p>
 #end
diff --git a/src/main/java/org/olat/core/gui/components/table/DefaultTableDataModel.java b/src/main/java/org/olat/core/gui/components/table/DefaultTableDataModel.java
index ce39583e12c565cc3c85b2de10fe4e2cb19928af..921dc5b33615e34f2c87a61e73602acfb5bd6ef6 100644
--- a/src/main/java/org/olat/core/gui/components/table/DefaultTableDataModel.java
+++ b/src/main/java/org/olat/core/gui/components/table/DefaultTableDataModel.java
@@ -58,7 +58,7 @@ public abstract class DefaultTableDataModel<U> implements TableDataModel<U> {
 	 * @see org.olat.core.gui.components.table.TableDataModel#getRowCount()
 	 */
 	public int getRowCount() {
-		return objects.size();
+		return objects == null ? 0 : objects.size();
 	}
 
 	/**
diff --git a/src/main/java/org/olat/core/gui/control/generic/ajax/autocompletion/AutoCompleterController.java b/src/main/java/org/olat/core/gui/control/generic/ajax/autocompletion/AutoCompleterController.java
index 9a4956114d61cbb68d445f7cebfc77e250d38895..44e84f4b00c2a4a87c89d0d95688829e662b55aa 100644
--- a/src/main/java/org/olat/core/gui/control/generic/ajax/autocompletion/AutoCompleterController.java
+++ b/src/main/java/org/olat/core/gui/control/generic/ajax/autocompletion/AutoCompleterController.java
@@ -28,8 +28,6 @@ package org.olat.core.gui.control.generic.ajax.autocompletion;
 import java.util.ArrayList;
 import java.util.List;
 
-import javax.servlet.http.HttpServletRequest;
-
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
@@ -42,12 +40,7 @@ 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.gui.control.winmgr.JSCommand;
-import org.olat.core.gui.media.MediaResource;
-import org.olat.core.gui.media.StringMediaResource;
 import org.olat.core.gui.render.velocity.VelocityRenderDecorator;
-import org.olat.core.gui.util.CSSHelper;
-import org.olat.core.logging.AssertException;
-import org.olat.core.logging.LogDelegator;
 
 /**
  * 
@@ -67,13 +60,7 @@ import org.olat.core.logging.LogDelegator;
  * @author Felix Jost, FLorian Gnägi
  */
 public class AutoCompleterController extends BasicController {
-	private static final String CONTENT_TYPE_APPLICATION_X_JSON = "application/x-json";
-	private static final String CONTENT_TYPE_TEXT_JAVASCRIPT = "text/javascript";
-	private static final String RESPONSE_ENCODING = "utf-8";
 	private static final String COMMAND_SELECT = "select";
-	private static final String PARAM_CALLBACK = "callback";
-	private static final String PARAM_QUERY = "query";
-	private static final String PARAM_KEY = "key";
 	private static final String JSNAME_INPUTFIELD = "b_autocomplete_input";
 	private static final String JSNAME_DATASTORE = "autocompleterDatastore";
 	private static final String JSNAME_COMBOBOX = "autocompleterCombobox";
@@ -130,57 +117,13 @@ public class AutoCompleterController extends BasicController {
 		datastoreName = "o_s" + JSNAME_DATASTORE + myContent.getDispatchID();
 		comboboxName = "o_s" + JSNAME_COMBOBOX + myContent.getDispatchID();
 
-		
 		// Create a mapper for the server responses for a given input
-		mapper = new Mapper() {
-			@Override
-			@SuppressWarnings({ "synthetic-access" })			
-			public MediaResource handle(String relPath, HttpServletRequest request) {
-				// Prepare resulting media resource
-				StringBuffer response = new StringBuffer();
-				StringMediaResource smr = new StringMediaResource();
-				smr.setEncoding(RESPONSE_ENCODING);
-				// Prepare result for ExtJS ScriptTagProxy call-back
-				boolean scriptTag = false;
-				String cb = request.getParameter(PARAM_CALLBACK);
-				if (cb != null) {
-				    scriptTag = true;
-				    smr.setContentType(CONTENT_TYPE_TEXT_JAVASCRIPT);
-				} else {
-					smr.setContentType(CONTENT_TYPE_APPLICATION_X_JSON);
-				}
-				if (scriptTag) {
-				    response.append(cb + "(");
-				}
-				// Read query and generate JSON result
-				String lastN = request.getParameter(PARAM_QUERY);
-				AutoCompleterListReceiver receiver = new AutoCompleterListReceiver(noResults, showDisplayKey);
-				gprovider.getResult(lastN, receiver);
-				JSONObject json = new JSONObject();
-				try {
-					JSONArray result = receiver.getResult(); 
-					json.put("rows", result);
-					json.put("results", result.length());
-					response.append(json.toString());
-				} catch (JSONException e) {
-					// Ups, just log error and proceed with empty string
-					logError("Could not put rows and results to JSONArray", e);
-					response.append("");
-				}
-				// Close call-back call
-				if (scriptTag) {
-				    response.append(");");
-				}
-				// Add result to media resource and deliver
-				smr.setData(response.toString());
-				return smr;
-			}
-		};
+		mapper = new AutoCompleterMapper(noResults, showDisplayKey, gprovider);
+			
 		// Add mapper URL to JS data store in velocity
 		String fetchUri = registerMapper(mapper);
 		final String fulluri = fetchUri; // + "/" + fileName;
 		myContent.contextPut("mapuri", fulluri+"/autocomplete.json");
-		//
 		putInitialPanel(myContent);
 	}
 
@@ -194,7 +137,7 @@ public class AutoCompleterController extends BasicController {
 		if (source == myContent) {
 			if (event.getCommand().equals(COMMAND_SELECT)) {
 				List<String> selectedEntries = new ArrayList<String>(); // init empty result list
-				String key = ureq.getParameter(PARAM_KEY);
+				String key = ureq.getParameter(AutoCompleterMapper.PARAM_KEY);
 				if (key == null) {
 					// Fallback to submitted input field: the input field does not contain
 					// the key but the search value itself
@@ -217,7 +160,7 @@ public class AutoCompleterController extends BasicController {
 					if (result.length() > 0) {
 						try {
 							JSONObject object = result.getJSONObject(0);
-							key = object.getString(PARAM_KEY);
+							key = object.getString(AutoCompleterMapper.PARAM_KEY);
 						} catch (JSONException e) {
 							logError("Error while getting json object from list receiver", e);						
 							key = "";
@@ -274,95 +217,4 @@ public class AutoCompleterController extends BasicController {
 		
 		// Mapper autodisposed by basic controller
 	}
-	
-}
-
-/**
- * 
- * Description:<br>
- * The AutoCompleterListReceiver implementes a list receiver that generates JSON
- * output. The class is only used in the AutoCompleterController
- * 
- * <P>
- * Initial Date: 25.11.2010 <br>
- * 
- * @author gnaegi
- */
-class AutoCompleterListReceiver extends LogDelegator implements ListReceiver {
-	private static final String VALUE = "value";
-	private static final String CSS_CLASS = "cssClass";
-	private static final String CSS_CLASS_EMPTY = "";
-	private static final String CSS_CLASS_WITH_ICON = "b_with_small_icon_left ";
-	private static final String DISPLAY_KEY = "displayKey";
-	private static final String DISPLAY_KEY_NO_RESULTS = "-";
-	
-	private final JSONArray list = new JSONArray();
-	private final String noresults;
-	private final boolean showDisplayKey;
-
-	/**
-	 * Constructor
-	 * 
-	 * @param noResults Text to use when no results are found
-	 * @param showDisplayKey true: add displayKey in result; false: don't add
-	 *          displayKey in results (e.g. to protect privacy)
-	 */
-	AutoCompleterListReceiver(String noresults, boolean showDisplayKey) {
-		this.noresults = noresults;
-		this.showDisplayKey = showDisplayKey;
-	}
-	
-	@Override
-	public void addEntry(String key, String displayText) {
-		addEntry(key, key, displayText, null);
-	}
-
-	/**
-	 * @return the result as a JSONArray object
-	 */
-	public JSONArray getResult() {
-		if (list.length() == 0) {
-			addEntry(AutoCompleterController.AUTOCOMPLETER_NO_RESULT, DISPLAY_KEY_NO_RESULTS, noresults, CSSHelper.CSS_CLASS_ERROR);
-		}
-		return list;
-	}
-
-	@Override	
-	public void addEntry(String key, String displayKey, String displayText, String iconCssClass) {
-		if (key == null) {
-			throw new AssertException("Can not add entry with displayText::" + displayText + " with a NULL key!");
-		}
-		if (isLogDebugEnabled()) {
-			logDebug("Add entry with key::" + key+ ", displayKey::" + displayKey + ", displayText::" + displayText + ", iconCssClass::" + iconCssClass);
-		}
-		try {
-			JSONObject object = new JSONObject();
-			// add key
-			object.put("key", key);
-			// add displayable key, use key as fallback 
-			if (showDisplayKey) {				
-				if (displayKey == null) {
-					object.put(DISPLAY_KEY, key);
-				} else {
-					object.put(DISPLAY_KEY, displayKey);
-				}
-			}
-			// add value to be displayed
-			object.put(VALUE, displayText);
-			// add optional css class
-			if (iconCssClass == null) {
-				object.put(CSS_CLASS, CSS_CLASS_EMPTY);								
-			} else {
-				object.put(CSS_CLASS, CSS_CLASS_WITH_ICON + iconCssClass);				
-			}
-			// JSCON object finished
-			list.put(object);
-
-		} catch (JSONException e) {
-			// do nothing, only log error to logfile
-			logError("Could not add entry with key::" + key+ ", displayKey::" + displayKey + ", displayText::" + displayText + ", iconCssClass::" + iconCssClass, e);
-		}
-
-	}
-
-}
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/core/gui/control/generic/ajax/autocompletion/AutoCompleterListReceiver.java b/src/main/java/org/olat/core/gui/control/generic/ajax/autocompletion/AutoCompleterListReceiver.java
new file mode 100644
index 0000000000000000000000000000000000000000..d6f75c8e4349c673dad90202cb41e81f22cc2c87
--- /dev/null
+++ b/src/main/java/org/olat/core/gui/control/generic/ajax/autocompletion/AutoCompleterListReceiver.java
@@ -0,0 +1,116 @@
+/**
+ * <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.core.gui.control.generic.ajax.autocompletion;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.olat.core.gui.util.CSSHelper;
+import org.olat.core.logging.AssertException;
+import org.olat.core.logging.LogDelegator;
+
+/**
+ * 
+ * Description:<br>
+ * The AutoCompleterListReceiver implementes a list receiver that generates JSON
+ * output. The class is only used in the AutoCompleterController
+ * 
+ * <P>
+ * Initial Date: 25.11.2010 <br>
+ * 
+ * @author gnaegi
+ */
+public class AutoCompleterListReceiver extends LogDelegator implements ListReceiver {
+	private static final String VALUE = "value";
+	private static final String CSS_CLASS = "cssClass";
+	private static final String CSS_CLASS_EMPTY = "";
+	private static final String CSS_CLASS_WITH_ICON = "b_with_small_icon_left ";
+	private static final String DISPLAY_KEY = "displayKey";
+	private static final String DISPLAY_KEY_NO_RESULTS = "-";
+	
+	private final JSONArray list = new JSONArray();
+	private final String noresults;
+	private final boolean showDisplayKey;
+
+	/**
+	 * Constructor
+	 * 
+	 * @param noResults Text to use when no results are found
+	 * @param showDisplayKey true: add displayKey in result; false: don't add
+	 *          displayKey in results (e.g. to protect privacy)
+	 */
+	AutoCompleterListReceiver(String noresults, boolean showDisplayKey) {
+		this.noresults = noresults;
+		this.showDisplayKey = showDisplayKey;
+	}
+	
+	@Override
+	public void addEntry(String key, String displayText) {
+		addEntry(key, key, displayText, null);
+	}
+
+	/**
+	 * @return the result as a JSONArray object
+	 */
+	public JSONArray getResult() {
+		if (list.length() == 0) {
+			addEntry(AutoCompleterController.AUTOCOMPLETER_NO_RESULT, DISPLAY_KEY_NO_RESULTS, noresults, CSSHelper.CSS_CLASS_ERROR);
+		}
+		return list;
+	}
+
+	@Override	
+	public void addEntry(String key, String displayKey, String displayText, String iconCssClass) {
+		if (key == null) {
+			throw new AssertException("Can not add entry with displayText::" + displayText + " with a NULL key!");
+		}
+		if (isLogDebugEnabled()) {
+			logDebug("Add entry with key::" + key+ ", displayKey::" + displayKey + ", displayText::" + displayText + ", iconCssClass::" + iconCssClass);
+		}
+		try {
+			JSONObject object = new JSONObject();
+			// add key
+			object.put("key", key);
+			// add displayable key, use key as fallback 
+			if (showDisplayKey) {				
+				if (displayKey == null) {
+					object.put(DISPLAY_KEY, key);
+				} else {
+					object.put(DISPLAY_KEY, displayKey);
+				}
+			}
+			// add value to be displayed
+			object.put(VALUE, displayText);
+			// add optional css class
+			if (iconCssClass == null) {
+				object.put(CSS_CLASS, CSS_CLASS_EMPTY);								
+			} else {
+				object.put(CSS_CLASS, CSS_CLASS_WITH_ICON + iconCssClass);				
+			}
+			// JSCON object finished
+			list.put(object);
+
+		} catch (JSONException e) {
+			// do nothing, only log error to logfile
+			logError("Could not add entry with key::" + key+ ", displayKey::" + displayKey + ", displayText::" + displayText + ", iconCssClass::" + iconCssClass, e);
+		}
+
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/core/gui/control/generic/ajax/autocompletion/AutoCompleterMapper.java b/src/main/java/org/olat/core/gui/control/generic/ajax/autocompletion/AutoCompleterMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..e75c82beb40e97c75236c50ba68759590009affc
--- /dev/null
+++ b/src/main/java/org/olat/core/gui/control/generic/ajax/autocompletion/AutoCompleterMapper.java
@@ -0,0 +1,99 @@
+/**
+ * <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.core.gui.control.generic.ajax.autocompletion;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.olat.core.dispatcher.mapper.Mapper;
+import org.olat.core.gui.media.MediaResource;
+import org.olat.core.gui.media.StringMediaResource;
+import org.olat.core.logging.OLog;
+import org.olat.core.logging.Tracing;
+
+/**
+ * 
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ */
+public class AutoCompleterMapper implements Mapper {
+	private final static OLog log = Tracing.createLoggerFor(AutoCompleterMapper.class);
+	
+	private static final String CONTENT_TYPE_APPLICATION_X_JSON = "application/x-json";
+	private static final String CONTENT_TYPE_TEXT_JAVASCRIPT = "text/javascript";
+	private static final String RESPONSE_ENCODING = "utf-8";
+	private static final String PARAM_CALLBACK = "callback";
+	private static final String PARAM_QUERY = "query";
+	protected static final String PARAM_KEY = "key";
+	
+	private final String noResults;
+	private final boolean showDisplayKey;
+	private final ListProvider gprovider;
+	
+	public AutoCompleterMapper(String noResults, boolean showDisplayKey, ListProvider gprovider) {
+		this.noResults = noResults;
+		this.showDisplayKey = showDisplayKey;
+		this.gprovider = gprovider;
+	}
+
+	@Override
+	@SuppressWarnings({ "synthetic-access" })			
+	public MediaResource handle(String relPath, HttpServletRequest request) {
+		// Prepare resulting media resource
+		StringBuffer response = new StringBuffer();
+		StringMediaResource smr = new StringMediaResource();
+		smr.setEncoding(RESPONSE_ENCODING);
+		// Prepare result for ExtJS ScriptTagProxy call-back
+		boolean scriptTag = false;
+		String cb = request.getParameter(PARAM_CALLBACK);
+		if (cb != null) {
+		    scriptTag = true;
+		    smr.setContentType(CONTENT_TYPE_TEXT_JAVASCRIPT);
+		} else {
+			smr.setContentType(CONTENT_TYPE_APPLICATION_X_JSON);
+		}
+		if (scriptTag) {
+		    response.append(cb + "(");
+		}
+		// Read query and generate JSON result
+		String lastN = request.getParameter(PARAM_QUERY);
+		AutoCompleterListReceiver receiver = new AutoCompleterListReceiver(noResults, showDisplayKey);
+		gprovider.getResult(lastN, receiver);
+		JSONObject json = new JSONObject();
+		try {
+			JSONArray result = receiver.getResult(); 
+			json.put("rows", result);
+			json.put("results", result.length());
+			response.append(json.toString());
+		} catch (JSONException e) {
+			// Ups, just log error and proceed with empty string
+			log.error("Could not put rows and results to JSONArray", e);
+			response.append("");
+		}
+		// Close call-back call
+		if (scriptTag) {
+		    response.append(");");
+		}
+		// Add result to media resource and deliver
+		smr.setData(response.toString());
+		return smr;
+	}
+}
diff --git a/src/main/java/org/olat/core/gui/control/generic/ajax/autocompletion/EntriesChosenEvent.java b/src/main/java/org/olat/core/gui/control/generic/ajax/autocompletion/EntriesChosenEvent.java
index 31751a8212bcbfe7b17b9e66a1bb137d1ea7e50c..6ca6c12d1b992c58e2482e68d48b37f362b83ea2 100644
--- a/src/main/java/org/olat/core/gui/control/generic/ajax/autocompletion/EntriesChosenEvent.java
+++ b/src/main/java/org/olat/core/gui/control/generic/ajax/autocompletion/EntriesChosenEvent.java
@@ -42,12 +42,13 @@ import org.olat.core.gui.control.Event;
  */
 public class EntriesChosenEvent extends Event {
 
-	private final List entries;
+	private static final long serialVersionUID = 641650982845790452L;
+	private final List<String> entries;
 
 	/**
 	 * @param command
 	 */
-	EntriesChosenEvent(List entries) {
+	EntriesChosenEvent(List<String> entries) {
 		super("chosenentries");
 		this.entries = entries;
 	}
@@ -55,7 +56,7 @@ public class EntriesChosenEvent extends Event {
 	/**
 	 * @return Returns the entries. never null, but may be an empty list. an item in the list contains a key, which is a string.
 	 */
-	public List getEntries() {
+	public List<String> getEntries() {
 		return entries;
 	}
 
diff --git a/src/main/java/org/olat/core/gui/control/generic/ajax/autocompletion/FlexiAutoCompleterController.java b/src/main/java/org/olat/core/gui/control/generic/ajax/autocompletion/FlexiAutoCompleterController.java
new file mode 100644
index 0000000000000000000000000000000000000000..2f6b5fc6c635773ed0253de2770f2cb8254630ab
--- /dev/null
+++ b/src/main/java/org/olat/core/gui/control/generic/ajax/autocompletion/FlexiAutoCompleterController.java
@@ -0,0 +1,221 @@
+/**
+* OLAT - Online Learning and Training<br>
+* http://www.olat.org
+* <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
+* <p>
+* http://www.apache.org/licenses/LICENSE-2.0
+* <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>
+* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br>
+* University of Zurich, Switzerland.
+* <hr>
+* <a href="http://www.openolat.org">
+* OpenOLAT - Online Learning and Training</a><br>
+* This file has been modified by the OpenOLAT community. Changes are licensed
+* under the Apache 2.0 license as the original file.
+*/
+
+package org.olat.core.gui.control.generic.ajax.autocompletion;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.olat.core.dispatcher.mapper.Mapper;
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.Component;
+import org.olat.core.gui.components.form.flexible.FormItemContainer;
+import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
+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.winmgr.JSCommand;
+import org.olat.core.gui.render.velocity.VelocityRenderDecorator;
+
+/**
+ * 
+ * Description:<br>
+ * The AutoCompleterController provides an input field with a live-AJAX feed
+ * from the database. While typing, after entering a configurable amount of
+ * characters, the system performs a server side search and shows a list of
+ * search results the user can choose from.
+ * <p>
+ * This controller uses ExtJS javascript library to implement the feature
+ * <p>
+ * Fires: an EntriesChosenEvent which contain the chosen entry/entries as
+ * strings
+ * <P>
+ * Initial Date: 06.10.2006 <br>
+ * 
+ * @author Felix Jost, FLorian Gnägi
+ */
+public class FlexiAutoCompleterController extends FormBasicController {
+
+	private static final String COMMAND_SELECT = "select";
+	private static final String JSNAME_INPUTFIELD = "b_autocomplete_input";
+	private static final String JSNAME_DATASTORE = "autocompleterDatastore";
+	private static final String JSNAME_COMBOBOX = "autocompleterCombobox";
+
+	static final String AUTOCOMPLETER_NO_RESULT = "AUTOCOMPLETER_NO_RESULT";
+
+	private Mapper mapper;
+	private final ListProvider gprovider;
+	
+	private String datastoreName;
+	private String comboboxName;
+
+	/**
+	 * Constructor to create an auto completer controller
+	 * 
+	 * @param ureq
+	 *            The user request object
+	 * @param wControl
+	 *            The window control object
+	 * @param provider
+	 *            The provider that can be called to return the search-results
+	 *            for a given search query
+	 * @param noResults
+	 *            The translated value to display when no results are found,
+	 *            e.g. "no matches found" or "-no users found-". When a NULL
+	 *            value is provided, the controller will use a generic message.
+	 * @param showDisplayKey
+	 *            true: show the key for each record; false: don't show the key,
+	 *            only the value
+	 * @param inputWidth
+	 *            The input field width in characters
+	 * @param minChars
+	 *            The minimum number of characters the user has to enter to
+	 *            perform a search
+	 * @param label
+	 */
+	public FlexiAutoCompleterController(UserRequest ureq, WindowControl wControl, ListProvider provider, String noresults,
+			final boolean showDisplayKey, int inputWidth, int minChars, String label) {
+		super(ureq, wControl, "autocomplete");
+		this.gprovider = provider;
+		String noResults = (noresults == null ? translate("autocomplete.noresults") : noresults);
+
+		// Configure displaying parameters
+		if (label != null) {
+			flc.contextPut("autocompleter_label", label);
+		}
+		flc.contextPut("showDisplayKey", Boolean.valueOf(showDisplayKey));
+		flc.contextPut("inputWidth", Integer.valueOf(inputWidth));
+		flc.contextPut("minChars", Integer.valueOf(minChars));
+		flc.contextPut("flexi", Boolean.TRUE);
+		// Create name for addressing the javascript components
+		datastoreName = "o_s" + JSNAME_DATASTORE + flc.getComponent().getDispatchID();
+		comboboxName = "o_s" + JSNAME_COMBOBOX + flc.getComponent().getDispatchID();
+		
+		flc.getComponent().addListener(this);
+
+		// Create a mapper for the server responses for a given input
+		mapper = new AutoCompleterMapper(noResults, showDisplayKey, gprovider);
+		
+		// Add mapper URL to JS data store in velocity
+		String fetchUri = registerMapper(mapper);
+		final String fulluri = fetchUri; // + "/" + fileName;
+		flc.contextPut("mapuri", fulluri+"/autocomplete.json");
+	}
+	
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		//
+	}
+
+	/**
+	 * This dispatches component events...
+	 * 
+	 * @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)
+	 */
+	public void event(UserRequest ureq, Component source, Event event) {
+		if (source == flc.getComponent()) {
+			if (event.getCommand().equals(COMMAND_SELECT)) {
+				List<String> selectedEntries = new ArrayList<String>(); // init empty result list
+				String key = ureq.getParameter(AutoCompleterMapper.PARAM_KEY);
+				if (key == null) {
+					// Fallback to submitted input field: the input field does not contain
+					// the key but the search value itself
+					VelocityRenderDecorator r = (VelocityRenderDecorator) ((VelocityContainer)flc.getComponent()).getContext().get("r");
+					String searchValue = ureq.getParameter(r.getId(JSNAME_INPUTFIELD).toString());
+					if (searchValue == null) {
+						// log error because something went wrong in the code and send empty list as result
+						logError("Auto complete JS code must always send 'key' or the autocomplete parameter!", null);						
+						getWindowControl().setError(translate("autocomplete.error"));
+						return;
+					} else if (searchValue.equals("") || searchValue.length() < 3) {
+						getWindowControl().setWarning(translate("autocomplete.not.enough.chars"));
+						return;
+					}
+					// Create temporary receiver and perform search for given value. 
+					AutoCompleterListReceiver receiver = new AutoCompleterListReceiver("-", false);
+					gprovider.getResult(searchValue, receiver);
+					JSONArray result = receiver.getResult();
+					// Use key from first result
+					if (result.length() > 0) {
+						try {
+							JSONObject object = result.getJSONObject(0);
+							key = object.getString(AutoCompleterMapper.PARAM_KEY);
+						} catch (JSONException e) {
+							logError("Error while getting json object from list receiver", e);						
+							key = "";
+						}
+					} else {
+						key = "";
+					}
+				}
+				// Proceed with a key, empty or valid key
+				key = key.trim();
+				if (!key.equals("") && !key.equals(AUTOCOMPLETER_NO_RESULT)) {
+					// Normal case, add entry
+					selectedEntries.add(key);
+				} else if (key.equals(AUTOCOMPLETER_NO_RESULT)) {
+					return;
+				}
+				fireEvent(ureq, new EntriesChosenEvent(selectedEntries));					
+			}
+		}
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		//
+	}
+
+	/**
+	 * @see org.olat.core.gui.control.DefaultController#doDispose()
+	 */
+	protected void doDispose() {
+		// Cleanup javascript objects on browser side by triggering dispose
+		// function
+		StringBuffer sb = new StringBuffer();
+		// first datastore
+		sb.append("if (o_info.objectMap.containsKey('")
+				.append(datastoreName)
+				.append("')) {var oldStore = o_info.objectMap.removeKey('")
+				.append(datastoreName)
+				.append("');if (oldStore) {oldStore.destroy();} oldStore = null;}");
+		// second combobox
+		sb.append("if (o_info.objectMap.containsKey('")
+				.append(comboboxName)
+				.append("')) { var oldCombo = o_info.objectMap.removeKey('")
+				.append(comboboxName)
+				.append("'); if (oldCombo) {	oldCombo.destroy(); } oldCombo = null;}");
+		//
+		JSCommand jsCommand = new JSCommand(sb.toString());
+		getWindowControl().getWindowBackOffice().sendCommandTo(jsCommand);
+		
+		// Mapper autodisposed by basic controller
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/core/gui/control/generic/ajax/autocompletion/_content/autocomplete.html b/src/main/java/org/olat/core/gui/control/generic/ajax/autocompletion/_content/autocomplete.html
index 1c299ddf8ed8af8cbbb9a2241b692cafe18ce749..5bd0dde4f724c383ea9714bf9952003b5ad28682 100644
--- a/src/main/java/org/olat/core/gui/control/generic/ajax/autocompletion/_content/autocomplete.html
+++ b/src/main/java/org/olat/core/gui/control/generic/ajax/autocompletion/_content/autocomplete.html
@@ -1,12 +1,20 @@
 <div class="b_form_auto_completer">
-	<form id="$r.getId("aj_ac_f")" action="$r.formURIbg('select')" method="post" $r.bgTarget()>
-	    #if ($autocompleter_label)
-			$autocompleter_label 
-		#end
-		<div class="b_form_element">
-		    <input type="text" size="$inputWidth" name="$r.getId("b_autocomplete_input")" id="$r.getId("b_autocomplete_input")" />
-		</div>	
-	</form>
+	#if($flexi)
+		<div id="$r.getId("aj_ac_f")">
+	#else
+		<form id="$r.getId("aj_ac_f")" action="$r.formURIbg('select')" method="post" $r.bgTarget()>
+	#end
+    #if ($autocompleter_label)
+		$autocompleter_label 
+	#end
+	<div class="b_form_element">
+	    <input type="text" size="$inputWidth" name="$r.getId("b_autocomplete_input")" id="$r.getId("b_autocomplete_input")" />
+	</div>	
+	#if($flexi)
+		</div>
+	#else
+		</form>
+	#end
 </div>
 
 <script type="text/javascript">
@@ -29,6 +37,8 @@ Ext.onReady(function(){
 		oldCombo = null;
 	}
 	
+	console.log('Hello');
+	
 	## Data store gets key value pairs from server
     var dataStore = new Ext.data.Store({
         proxy: new Ext.data.ScriptTagProxy({
@@ -79,7 +89,9 @@ Ext.onReady(function(){
     ## by the form submit and the combo box result list is showed delayed. In this case
     ## don't expand
     comboBox.on('expand', function(combo) {
+    		console.log('Expand');
 	    	var elem=document.getElementById('${r.getId("aj_ac_f")}'); 
+    		console.log('Expand', elem);
 	    	if (!elem) combo.collapse();
 	    	elem = null;
     	}
diff --git a/src/main/java/org/olat/core/util/mail/MailTemplateForm.java b/src/main/java/org/olat/core/util/mail/MailTemplateForm.java
index 63e6ad99ddfe8952c7373fa938f5076c6a7ec509..21997bb0504a60a101f97d6f0754c0cb91412302 100644
--- a/src/main/java/org/olat/core/util/mail/MailTemplateForm.java
+++ b/src/main/java/org/olat/core/util/mail/MailTemplateForm.java
@@ -30,6 +30,7 @@ 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.SelectionElement;
 import org.olat.core.gui.components.form.flexible.elements.TextElement;
+import org.olat.core.gui.components.form.flexible.impl.Form;
 import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
 import org.olat.core.gui.components.form.flexible.impl.FormEvent;
 import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
@@ -54,7 +55,8 @@ public class MailTemplateForm extends FormBasicController {
 	private SelectionElement ccSender;
 	private final static String NLS_CONTACT_SEND_CP_FROM = "contact.cp.from";
 	
-	private boolean useCancel;
+	private final boolean useCancel;
+	private final boolean useSubmit;
 	private MailTemplate template;
 	/**
 	 * Constructor for the mail notification form
@@ -67,6 +69,14 @@ public class MailTemplateForm extends FormBasicController {
 		super(ureq, wControl);
 		this.template = template;
 		this.useCancel = useCancel;
+		this.useSubmit = true;
+		initForm (ureq);
+	}
+	
+	public MailTemplateForm(UserRequest ureq, WindowControl wControl, MailTemplate template, Form rootForm) {
+		super(ureq, wControl, LAYOUT_DEFAULT, null, rootForm);
+		this.template = template;
+		useCancel = useSubmit = false;
 		initForm (ureq);
 	}
 
@@ -91,7 +101,7 @@ public class MailTemplateForm extends FormBasicController {
 	}
 	
 	@Override
-	protected boolean validateFormLogic (UserRequest ureq) {
+	public boolean validateFormLogic (UserRequest ureq) {
 		// validate only when sendMail is enabled
 		if (sendMail.isSelected(0)) {
 			if (subjectElem.getValue().trim().length() == 0) {
@@ -135,7 +145,9 @@ public class MailTemplateForm extends FormBasicController {
 		FormLayoutContainer buttonGroupLayout = FormLayoutContainer.createButtonLayout("buttonGroupLayout", getTranslator());
 		formLayout.add(buttonGroupLayout);
 		
-		uifactory.addFormSubmitButton("continue", "mailtemplateform.continue", buttonGroupLayout);
+		if(useSubmit) {
+			uifactory.addFormSubmitButton("continue", "mailtemplateform.continue", buttonGroupLayout);
+		}
 		if (useCancel) {
 			uifactory.addFormCancelButton("cancel", buttonGroupLayout, ureq, getWindowControl());
 		}
diff --git a/src/main/java/org/olat/course/member/AbstractMemberListController.java b/src/main/java/org/olat/course/member/AbstractMemberListController.java
index e9d6db42e925b946d6784b872f9957a6e8358ac3..300ddc0117cb90dc5feed171e580cf4adcccdec5 100644
--- a/src/main/java/org/olat/course/member/AbstractMemberListController.java
+++ b/src/main/java/org/olat/course/member/AbstractMemberListController.java
@@ -28,7 +28,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
-import org.olat.admin.securitygroup.gui.IdentitiesAddEvent;
 import org.olat.basesecurity.BaseSecurity;
 import org.olat.basesecurity.IdentityShort;
 import org.olat.core.CoreSpringFactory;
@@ -63,6 +62,7 @@ import org.olat.group.model.BusinessGroupMembershipChange;
 import org.olat.repository.RepositoryEntry;
 import org.olat.repository.RepositoryManager;
 import org.olat.repository.model.RepositoryEntryMembership;
+import org.olat.repository.model.RepositoryEntryPermissionChangeEvent;
 import org.olat.user.UserManager;
 
 /**
@@ -235,31 +235,10 @@ public abstract class AbstractMemberListController extends BasicController {
 		reloadModel();
 	}
 	
-	private void doChangeRepoPermission(MemberPermissionChangeEvent e) {
+	private void doChangeRepoPermission(RepositoryEntryPermissionChangeEvent e) {
 		//first change repository permission
-		if(e.getRepoOwner() != null) {
-			if(e.getRepoOwner().booleanValue()) {
-				repositoryManager.addOwners(getIdentity(), new IdentitiesAddEvent(e.getMember()), repoEntry);
-			} else {
-				repositoryManager.removeOwners(getIdentity(), Collections.singletonList(e.getMember()), repoEntry);
-			}
-		}
-		
-		if(e.getRepoTutor() != null) {
-			if(e.getRepoTutor().booleanValue()) {
-				repositoryManager.addTutors(getIdentity(), new IdentitiesAddEvent(e.getMember()), repoEntry);
-			} else {
-				repositoryManager.removeTutors(getIdentity(), Collections.singletonList(e.getMember()), repoEntry);
-			}
-		}
-		
-		if(e.getRepoParticipant() != null) {
-			if(e.getRepoParticipant().booleanValue()) {
-				repositoryManager.addParticipants(getIdentity(), new IdentitiesAddEvent(e.getMember()), repoEntry);
-			} else {
-				repositoryManager.removeParticipants(getIdentity(), Collections.singletonList(e.getMember()), repoEntry);
-			}
-		}
+		List<RepositoryEntryPermissionChangeEvent> changes = Collections.singletonList(e);
+		repositoryManager.updateRepositoryEntryMembership(getIdentity(), repoEntry, changes);
 	}
 	
 	private void doChangeGroupPermissions(List<BusinessGroupMembershipChange> changes) {
diff --git a/src/main/java/org/olat/course/member/EditMembershipController.java b/src/main/java/org/olat/course/member/EditMembershipController.java
index e24a300ca522f64c778662a6832d32516cdda6a7..fa9eb96e8e17b64261799096eb094dc3ba814507 100644
--- a/src/main/java/org/olat/course/member/EditMembershipController.java
+++ b/src/main/java/org/olat/course/member/EditMembershipController.java
@@ -62,6 +62,7 @@ public class EditMembershipController extends FormBasicController {
 	private EditMemberTableDataModel tableDataModel;
 	private MultipleSelectionElement repoRightsEl;
 	private MemberInfoController infoController;
+	private boolean withButtons;
 	
 	private static final String[] repoRightsKeys = {"owner", "tutor", "participant"};
 	
@@ -81,6 +82,7 @@ public class EditMembershipController extends FormBasicController {
 		super(ureq, wControl, "edit_member");
 		this.member = member;
 		this.repoEntry = repoEntry;
+		this.withButtons = true;
 		repositoryManager = CoreSpringFactory.getImpl(RepositoryManager.class);
 		businessGroupService = CoreSpringFactory.getImpl(BusinessGroupService.class);
 		
@@ -97,6 +99,7 @@ public class EditMembershipController extends FormBasicController {
 		
 		this.member = null;
 		this.repoEntry = repoEntry;
+		this.withButtons = false;
 		repositoryManager = CoreSpringFactory.getImpl(RepositoryManager.class);
 		businessGroupService = CoreSpringFactory.getImpl(BusinessGroupService.class);
 		
@@ -172,11 +175,13 @@ public class EditMembershipController extends FormBasicController {
 		tableDataModel = new EditMemberTableDataModel(Collections.<MemberOption>emptyList(), tableColumnModel);
 		uifactory.addTableElement("groupList", tableDataModel, formLayout);
 		
-		FormLayoutContainer buttonLayout = FormLayoutContainer.createButtonLayout("buttonLayout", getTranslator());
-		formLayout.add(buttonLayout);
-		buttonLayout.setRootForm(mainForm);
-		uifactory.addFormSubmitButton("ok", buttonLayout);
-		uifactory.addFormCancelButton("cancel", buttonLayout, ureq, getWindowControl());
+		if(withButtons) {
+			FormLayoutContainer buttonLayout = FormLayoutContainer.createButtonLayout("buttonLayout", getTranslator());
+			formLayout.add(buttonLayout);
+			buttonLayout.setRootForm(mainForm);
+			uifactory.addFormSubmitButton("ok", buttonLayout);
+			uifactory.addFormCancelButton("cancel", buttonLayout, ureq, getWindowControl());
+		}
 	}
 
 	@Override
@@ -197,7 +202,7 @@ public class EditMembershipController extends FormBasicController {
 		fireEvent(ureq, Event.CANCELLED_EVENT);
 	}
 	
-	protected void collectRepoChanges(MemberPermissionChangeEvent e) {
+	public void collectRepoChanges(MemberPermissionChangeEvent e) {
 		RepoPermission repoPermission = PermissionHelper.getPermission(repoEntry, member, memberships);
 
 		Set<String>	selectRepoRights = repoRightsEl.getSelectedKeys();
@@ -209,7 +214,7 @@ public class EditMembershipController extends FormBasicController {
 		e.setRepoParticipant(repoParticipant == repoPermission.isParticipant() ? null : new Boolean(repoParticipant));
 	}
 	
-	protected void collectGroupChanges(MemberPermissionChangeEvent e) {
+	public void collectGroupChanges(MemberPermissionChangeEvent e) {
 		List<BusinessGroupMembershipChange> changes = new ArrayList<BusinessGroupMembershipChange>();
 		
 		for(MemberOption option:tableDataModel.getObjects()) {
diff --git a/src/main/java/org/olat/course/member/MemberPermissionChangeEvent.java b/src/main/java/org/olat/course/member/MemberPermissionChangeEvent.java
index 23c146c87c9f4d0437ae0f0e122f84181dd77cce..8e4cf916f50eefd3796a936239e438a23381ac54 100644
--- a/src/main/java/org/olat/course/member/MemberPermissionChangeEvent.java
+++ b/src/main/java/org/olat/course/member/MemberPermissionChangeEvent.java
@@ -21,57 +21,23 @@ package org.olat.course.member;
 
 import java.util.List;
 
-import org.olat.core.gui.control.Event;
 import org.olat.core.id.Identity;
 import org.olat.group.model.BusinessGroupMembershipChange;
+import org.olat.repository.model.RepositoryEntryPermissionChangeEvent;
 
 /**
  * 
  * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
  */
-public class MemberPermissionChangeEvent extends Event {
+public class MemberPermissionChangeEvent extends RepositoryEntryPermissionChangeEvent {
 	private static final long serialVersionUID = 8499004967313689825L;
 
-	private final Identity member;
-	
-	private Boolean repoOwner;
-	private Boolean repoTutor;
-	private Boolean repoParticipant;
-	
 	private List<BusinessGroupMembershipChange> groupChanges;
 	
 	public MemberPermissionChangeEvent(Identity member) {
-		super("id-perm-changed");
-		this.member = member;
+		super(member);
 	}
 	
-	public Identity getMember() {
-		return member;
-	}
-
-	public Boolean getRepoOwner() {
-		return repoOwner;
-	}
-
-	public void setRepoOwner(Boolean repoOwner) {
-		this.repoOwner = repoOwner;
-	}
-
-	public Boolean getRepoTutor() {
-		return repoTutor;
-	}
-
-	public void setRepoTutor(Boolean repoTutor) {
-		this.repoTutor = repoTutor;
-	}
-
-	public Boolean getRepoParticipant() {
-		return repoParticipant;
-	}
-
-	public void setRepoParticipant(Boolean repoParticipant) {
-		this.repoParticipant = repoParticipant;
-	}
 
 	public List<BusinessGroupMembershipChange> getGroupChanges() {
 		return groupChanges;
diff --git a/src/main/java/org/olat/course/member/MemberView.java b/src/main/java/org/olat/course/member/MemberView.java
index 98e925a89d5a90759819bfb7b55d5a188bd3b2a9..2669974bbe2cc7f810f9901f88ab39860004e4b3 100644
--- a/src/main/java/org/olat/course/member/MemberView.java
+++ b/src/main/java/org/olat/course/member/MemberView.java
@@ -123,4 +123,12 @@ public class MemberView {
 		}
 		return false;
 	}
+	
+	@Override
+	public String toString() {
+		StringBuilder sb = new StringBuilder();
+		sb.append("memberView[name=").append(firstName == null ? "" : firstName)
+			.append(" ").append(lastName == null ? "NULL" : lastName).append("]");
+		return sb.toString();
+	}
 }
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/member/MembersOverviewController.java b/src/main/java/org/olat/course/member/MembersOverviewController.java
index f840cee4339f11ceeac689f1e6c44d99a40bec18..6a92999346c6dfdb98554f92a8c587cd6df36d53 100644
--- a/src/main/java/org/olat/course/member/MembersOverviewController.java
+++ b/src/main/java/org/olat/course/member/MembersOverviewController.java
@@ -19,10 +19,10 @@
  */
 package org.olat.course.member;
 
+import java.util.ArrayList;
 import java.util.List;
 
-import org.olat.collaboration.CollaborationTools;
-import org.olat.collaboration.CollaborationToolsFactory;
+import org.olat.core.CoreSpringFactory;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.Component;
 import org.olat.core.gui.components.link.Link;
@@ -41,16 +41,21 @@ import org.olat.core.gui.control.generic.wizard.Step;
 import org.olat.core.gui.control.generic.wizard.StepRunnerCallback;
 import org.olat.core.gui.control.generic.wizard.StepsMainRunController;
 import org.olat.core.gui.control.generic.wizard.StepsRunContext;
+import org.olat.core.id.Identity;
 import org.olat.core.id.OLATResourceable;
 import org.olat.core.id.context.BusinessControlFactory;
 import org.olat.core.id.context.ContextEntry;
 import org.olat.core.id.context.StateEntry;
 import org.olat.core.logging.activity.ThreadLocalUserActivityLogger;
+import org.olat.core.util.mail.MailTemplate;
 import org.olat.core.util.resource.OresHelper;
-import org.olat.course.member.wizard.ImportMember_1_ChooseMemberStep;
-import org.olat.group.BusinessGroup;
-import org.olat.group.ui.wizard.BGConfigBusinessGroup;
+import org.olat.course.member.wizard.ImportMember_1a_LoginListStep;
+import org.olat.course.member.wizard.ImportMember_1b_ChooseMemberStep;
+import org.olat.group.BusinessGroupService;
+import org.olat.group.model.BusinessGroupMembershipChange;
 import org.olat.repository.RepositoryEntry;
+import org.olat.repository.RepositoryManager;
+import org.olat.repository.model.RepositoryEntryPermissionChangeEvent;
 import org.olat.util.logging.activity.LoggingResourceable;
 
 /**
@@ -70,15 +75,20 @@ public class MembersOverviewController extends BasicController implements Activa
 	private MemberListController tutorsCtrl;
 	private MemberListController participantsCtrl;
 	private MemberListController waitingCtrl;
+	private MemberListController selectedCtrl;
 	private final Link importMemberLink, addMemberLink;
 	
 	private StepsMainRunController importMembersWizard;
 	
 	private final RepositoryEntry repoEntry;
+	private final RepositoryManager repositoryManager;
+	private final BusinessGroupService businessGroupService;
 	
 	public MembersOverviewController(UserRequest ureq, WindowControl wControl, RepositoryEntry repoEntry) {
 		super(ureq, wControl);
 		this.repoEntry = repoEntry;
+		repositoryManager = CoreSpringFactory.getImpl(RepositoryManager.class);
+		businessGroupService = CoreSpringFactory.getImpl(BusinessGroupService.class);
 
 		mainVC = createVelocityContainer("members_overview");
 		segmentView = SegmentViewFactory.createSegmentView("segments", mainVC, this);
@@ -124,19 +134,21 @@ public class MembersOverviewController extends BasicController implements Activa
 			String segmentCName = sve.getComponentName();
 			Component clickedLink = mainVC.getComponent(segmentCName);
 			if (clickedLink == allMembersLink) {
-				updateAllMembers(ureq);
+				selectedCtrl =updateAllMembers(ureq);
 			} else if (clickedLink == ownersLink){
-				updateOwners(ureq);
+				selectedCtrl = updateOwners(ureq);
 			} else if (clickedLink == tutorsLink){
-				updateTutors(ureq);
+				selectedCtrl = updateTutors(ureq);
 			} else if (clickedLink == participantsLink) {
-				updateParticipants(ureq);
+				selectedCtrl = updateParticipants(ureq);
 			} else if (clickedLink == waitingListLink) {
-				updateWaitingList(ureq);
+				selectedCtrl = updateWaitingList(ureq);
 			} else if (clickedLink == searchLink) {
 				updateSearch(ureq);
+				selectedCtrl = null;
 			}
 		} else if (source == addMemberLink) {
+			doChooseMembers(ureq);
 			
 		} else if (source == importMemberLink) {
 			doImportMembers(ureq);
@@ -146,20 +158,45 @@ public class MembersOverviewController extends BasicController implements Activa
 	@Override
 	protected void event(UserRequest ureq, Controller source, Event event) {
 		if(source == importMembersWizard) {
-			System.out.println("Import!!!");
+			if(event == Event.CANCELLED_EVENT || event == Event.DONE_EVENT || event == Event.CHANGED_EVENT) {
+				getWindowControl().pop();
+				removeAsListenerAndDispose(importMembersWizard);
+				importMembersWizard = null;
+				if(event == Event.DONE_EVENT || event == Event.CHANGED_EVENT) {
+					if(selectedCtrl != null) {
+						selectedCtrl.reloadModel();
+					}
+				}
+			}
 		}
 		super.event(ureq, source, event);
 	}
 
+	private void doChooseMembers(UserRequest ureq) {
+		removeAsListenerAndDispose(importMembersWizard);
+
+		Step start = new ImportMember_1b_ChooseMemberStep(ureq, repoEntry);
+		StepRunnerCallback finish = new StepRunnerCallback() {
+			@Override
+			public Step execute(UserRequest ureq, WindowControl wControl, StepsRunContext runContext) {
+				addMembers(runContext);
+				return StepsMainRunController.DONE_MODIFIED;
+			}
+		};
+		
+		importMembersWizard = new StepsMainRunController(ureq, getWindowControl(), start, finish, null, translate("import.member"));
+		listenTo(importMembersWizard);
+		getWindowControl().pushAsModalDialog(importMembersWizard.getInitialComponent());
+	}
+	
 	private void doImportMembers(UserRequest ureq) {
 		removeAsListenerAndDispose(importMembersWizard);
 
-		Step start = new ImportMember_1_ChooseMemberStep(ureq, repoEntry);
+		Step start = new ImportMember_1a_LoginListStep(ureq, repoEntry);
 		StepRunnerCallback finish = new StepRunnerCallback() {
 			@Override
 			public Step execute(UserRequest ureq, WindowControl wControl, StepsRunContext runContext) {
-				//configuration
-				System.out.println("Import!!!");
+				addMembers(runContext);
 				return StepsMainRunController.DONE_MODIFIED;
 			}
 		};
@@ -169,6 +206,32 @@ public class MembersOverviewController extends BasicController implements Activa
 		getWindowControl().pushAsModalDialog(importMembersWizard.getInitialComponent());
 	}
 	
+	protected void addMembers(StepsRunContext runContext) {
+		@SuppressWarnings("unchecked")
+		List<Identity> members = (List<Identity>)runContext.get("members");
+		
+		MemberPermissionChangeEvent changes = (MemberPermissionChangeEvent)runContext.get("permissions");
+		//commit changes to the repository entry
+		List<RepositoryEntryPermissionChangeEvent> repoChanges = new ArrayList<RepositoryEntryPermissionChangeEvent>();
+		for(Identity member:members) {
+			repoChanges.add(new RepositoryEntryPermissionChangeEvent(member, changes));
+		}
+		repositoryManager.updateRepositoryEntryMembership(getIdentity(), repoEntry, repoChanges);
+
+		//commit all changes to the group memberships
+		List<BusinessGroupMembershipChange> groupChanges = changes.getGroupChanges();
+		List<BusinessGroupMembershipChange> allModifications = new ArrayList<BusinessGroupMembershipChange>();
+		for(BusinessGroupMembershipChange groupChange:groupChanges) {
+			for(Identity member:members) {
+				allModifications.add(new BusinessGroupMembershipChange(member, groupChange));
+			}
+		}
+		businessGroupService.updateMemberships(getIdentity(), allModifications);
+		
+		MailTemplate mailTemplate = (MailTemplate)runContext.get("mailTemplate");
+		System.out.println("Send mails: " + mailTemplate);
+	}
+	
 	private MemberListController updateAllMembers(UserRequest ureq) {
 		if(allMemberListCtrl == null) {
 			OLATResourceable ores = OresHelper.createOLATResourceableInstance("AllMembers", 0l);
diff --git a/src/main/java/org/olat/course/member/PermissionHelper.java b/src/main/java/org/olat/course/member/PermissionHelper.java
index e19a1e5cfecfea7badd7fb09b5f31c2595aedd14..a15863734192a8fc15f6ac61ad83711b1b372521 100644
--- a/src/main/java/org/olat/course/member/PermissionHelper.java
+++ b/src/main/java/org/olat/course/member/PermissionHelper.java
@@ -34,15 +34,17 @@ public class PermissionHelper {
 	
 	public static final RepoPermission getPermission(RepositoryEntry re, Identity id, List<RepositoryEntryMembership> memberships) {
 		RepoPermission p = new RepoPermission();
-		for(RepositoryEntryMembership membership:memberships) {
-			if(membership.getOwnerRepoKey() != null && re.getKey().equals(membership.getOwnerRepoKey())) {
-				p.setOwner(true);
-			}
-			if(membership.getTutorRepoKey() != null && re.getKey().equals(membership.getTutorRepoKey())) {
-				p.setTutor(true);
-			}
-			if(membership.getParticipantRepoKey() != null && re.getKey().equals(membership.getParticipantRepoKey())) {
-				p.setParticipant(true);
+		if(id != null && memberships != null && !memberships.isEmpty()) {
+			for(RepositoryEntryMembership membership:memberships) {
+				if(membership.getOwnerRepoKey() != null && re.getKey().equals(membership.getOwnerRepoKey())) {
+					p.setOwner(true);
+				}
+				if(membership.getTutorRepoKey() != null && re.getKey().equals(membership.getTutorRepoKey())) {
+					p.setTutor(true);
+				}
+				if(membership.getParticipantRepoKey() != null && re.getKey().equals(membership.getParticipantRepoKey())) {
+					p.setParticipant(true);
+				}
 			}
 		}
 		return p;
@@ -50,16 +52,18 @@ public class PermissionHelper {
 	
 	public static final BGPermission getPermission(Long groupkey, Identity id, List<BusinessGroupMembership> memberships) {
 		BGPermission p = new BGPermission();
-		for(BusinessGroupMembership membership:memberships) {
-			if(membership.getGroupKey().equals(groupkey)) {
-				if(membership.isOwner()) {
-					p.setTutor(true);
-				}
-				if(membership.isParticipant()) {
-					p.setParticipant(true);
-				}
-				if(membership.isWaiting()) {
-					p.setWaitingList(true);
+		if(id != null && memberships != null && !memberships.isEmpty()) {
+			for(BusinessGroupMembership membership:memberships) {
+				if(membership.getGroupKey().equals(groupkey)) {
+					if(membership.isOwner()) {
+						p.setTutor(true);
+					}
+					if(membership.isParticipant()) {
+						p.setParticipant(true);
+					}
+					if(membership.isWaiting()) {
+						p.setWaitingList(true);
+					}
 				}
 			}
 		}
diff --git a/src/main/java/org/olat/course/member/SearchMembersParams.java b/src/main/java/org/olat/course/member/SearchMembersParams.java
index b16d862dfdd82cb8477333a0bf6c3ae4136b3daa..348fbe4079f18f7bbdf5b14022a36bb04be67cf7 100644
--- a/src/main/java/org/olat/course/member/SearchMembersParams.java
+++ b/src/main/java/org/olat/course/member/SearchMembersParams.java
@@ -1,3 +1,22 @@
+/**
+ * <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.member;
 
 /**
diff --git a/src/main/java/org/olat/course/member/_content/edit_member.html b/src/main/java/org/olat/course/member/_content/edit_member.html
index 9e5adaf9af408682bedddd9ff3f3feb16af41aa1..af17764a8cd410fdb290134bb67fca8de40d9855 100644
--- a/src/main/java/org/olat/course/member/_content/edit_member.html
+++ b/src/main/java/org/olat/course/member/_content/edit_member.html
@@ -1,8 +1,14 @@
 #if($r.available("infos"))
 	$r.render("infos")
 #end
-<h4>$editTitle</h4>
-$r.render("repoRights")
-<h5>$r.translate("edit.member.groups")</h5>
-$r.render("groupList")
-$r.render("buttonLayout")
\ No newline at end of file
+#if($r.available("repoRights"))
+	<h4>$editTitle</h4>
+	$r.render("repoRights")
+#end
+#if($r.available("groupList"))
+	<h5>$r.translate("edit.member.groups")</h5>
+	$r.render("groupList")
+#end
+#if($r.available("buttonLayout"))
+	$r.render("buttonLayout")
+#end
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/member/wizard/ImportMemberBySearchController.java b/src/main/java/org/olat/course/member/wizard/ImportMemberBySearchController.java
new file mode 100644
index 0000000000000000000000000000000000000000..629c26482d1efb0b071f9de0da1320a088b01d68
--- /dev/null
+++ b/src/main/java/org/olat/course/member/wizard/ImportMemberBySearchController.java
@@ -0,0 +1,79 @@
+/**
+ * <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.member.wizard;
+
+import java.util.Collections;
+
+import org.olat.admin.user.UserSearchFlexiController;
+import org.olat.basesecurity.events.SingleIdentityChosenEvent;
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.FormItemContainer;
+import org.olat.core.gui.components.form.flexible.impl.Form;
+import org.olat.core.gui.control.Controller;
+import org.olat.core.gui.control.Event;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.core.gui.control.generic.wizard.StepFormBasicController;
+import org.olat.core.gui.control.generic.wizard.StepsEvent;
+import org.olat.core.gui.control.generic.wizard.StepsRunContext;
+
+
+/**
+ * 
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ */
+public class ImportMemberBySearchController extends StepFormBasicController {
+	private UserSearchFlexiController searchController;
+
+	public ImportMemberBySearchController(UserRequest ureq, WindowControl wControl, Form rootForm, StepsRunContext runContext) {
+		super(ureq, wControl, rootForm, runContext, LAYOUT_CUSTOM, "import_search");
+
+		searchController = new UserSearchFlexiController(ureq, wControl, false, false, null, rootForm);
+		listenTo(searchController);
+		
+		initForm (ureq);
+	}
+
+	@Override
+	protected void event(UserRequest ureq, Controller source, Event event) {
+		if(event instanceof SingleIdentityChosenEvent) {
+			SingleIdentityChosenEvent e = (SingleIdentityChosenEvent)event;
+			String key = e.getChosenIdentity().getKey().toString();
+			addToRunContext("keys", Collections.singletonList(key));
+			fireEvent(ureq, StepsEvent.ACTIVATE_NEXT);
+		} else {
+			super.event(ureq, source, event);
+		}
+	}
+
+	@Override
+	protected void formOK(UserRequest ureq) {
+		//do nothing, it's import as it receive event from the UserSearchFlexiController
+	}
+
+	@Override
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		formLayout.add("search", searchController.getInitialFormItem());
+	}
+
+	@Override
+	protected void doDispose() {
+		//
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/member/wizard/ImportMemberByUsernamesController.java b/src/main/java/org/olat/course/member/wizard/ImportMemberByUsernamesController.java
index 896338743a3f3b98df332d93f79df28cfe948a04..dd9ccb020138037ba394a8774ff5a0b48d361474 100644
--- a/src/main/java/org/olat/course/member/wizard/ImportMemberByUsernamesController.java
+++ b/src/main/java/org/olat/course/member/wizard/ImportMemberByUsernamesController.java
@@ -24,7 +24,6 @@ import org.olat.core.gui.components.form.flexible.FormItemContainer;
 import org.olat.core.gui.components.form.flexible.elements.TextElement;
 import org.olat.core.gui.components.form.flexible.impl.Form;
 import org.olat.core.gui.control.Controller;
-import org.olat.core.gui.control.Event;
 import org.olat.core.gui.control.WindowControl;
 import org.olat.core.gui.control.generic.wizard.StepFormBasicController;
 import org.olat.core.gui.control.generic.wizard.StepsEvent;
@@ -53,6 +52,8 @@ public class ImportMemberByUsernamesController extends StepFormBasicController {
 
 	@Override
 	protected void formOK(UserRequest ureq) {
+		String logins = idata.getValue();
+		addToRunContext("logins", logins);
 		fireEvent(ureq, StepsEvent.ACTIVATE_NEXT);
 	}
 
diff --git a/src/main/java/org/olat/course/member/wizard/ImportMemberMailController.java b/src/main/java/org/olat/course/member/wizard/ImportMemberMailController.java
index 970dd644a32cc17ab35d5981d5af04e1de8668af..7de746b7a0cc8ccc719982e276646b603490a58d 100644
--- a/src/main/java/org/olat/course/member/wizard/ImportMemberMailController.java
+++ b/src/main/java/org/olat/course/member/wizard/ImportMemberMailController.java
@@ -19,48 +19,73 @@
  */
 package org.olat.course.member.wizard;
 
+import org.apache.velocity.VelocityContext;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.form.flexible.FormItemContainer;
-import org.olat.core.gui.components.form.flexible.elements.TextElement;
 import org.olat.core.gui.components.form.flexible.impl.Form;
 import org.olat.core.gui.control.Controller;
-import org.olat.core.gui.control.Event;
 import org.olat.core.gui.control.WindowControl;
 import org.olat.core.gui.control.generic.wizard.StepFormBasicController;
+import org.olat.core.gui.control.generic.wizard.StepsEvent;
 import org.olat.core.gui.control.generic.wizard.StepsRunContext;
+import org.olat.core.id.Identity;
+import org.olat.core.util.mail.MailTemplate;
+import org.olat.core.util.mail.MailTemplateForm;
 
 /**
  * 
  * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
  */
 public class ImportMemberMailController extends StepFormBasicController {
-	private TextElement subject;
-	private TextElement content;
+	
+	private final MailTemplate mailTemplate;
+	private final MailTemplateForm mailTemplateForm;
 
 	public ImportMemberMailController(UserRequest ureq, WindowControl wControl, Form rootForm, StepsRunContext runContext) {
-		super(ureq, wControl, rootForm, runContext, LAYOUT_DEFAULT, null);
+		super(ureq, wControl, rootForm, runContext, LAYOUT_CUSTOM, "mail_template");
+		
+		mailTemplate = new TestMailTemplate();
+		mailTemplateForm = new MailTemplateForm(ureq, wControl, mailTemplate, rootForm);
 		
 		initForm (ureq);
 	}
-
-	public boolean validate() {
-		return true;
-	}
-
-
-
+	
 	@Override
-	protected void formOK(UserRequest ureq) {
-		fireEvent (ureq, Event.DONE_EVENT);
+	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+		formLayout.add("template", mailTemplateForm.getInitialFormItem());
 	}
-
+	
 	@Override
-	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
+	protected void doDispose() {
+		//
+	}
 	
+	@Override
+	protected boolean validateFormLogic(UserRequest ureq) {
+		boolean allOk =  mailTemplateForm.validateFormLogic(ureq);
+		return allOk && super.validateFormLogic(ureq);
 	}
 
 	@Override
-	protected void doDispose() {
-		//
+	protected void formOK(UserRequest ureq) {
+		if(mailTemplateForm.sendMailSwitchEnabled()) {
+			mailTemplateForm.updateTemplateFromForm(mailTemplate);
+			addToRunContext("mailTemplate", mailTemplate);
+		} else {
+			addToRunContext("mailTemplate", null);
+		}
+		fireEvent (ureq, StepsEvent.ACTIVATE_NEXT);
+	}
+
+	private static class TestMailTemplate extends MailTemplate {
+		public TestMailTemplate() {
+			super("", "", null);
+		}
+		
+		
+		@Override
+		public void putVariablesInMailContext(VelocityContext context, Identity recipient) {
+			//
+		}
 	}
 }
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/member/wizard/ImportMemberOverviewIdentitiesController.java b/src/main/java/org/olat/course/member/wizard/ImportMemberOverviewIdentitiesController.java
index f3e1a8f9934a2aca08aea5b89b05544a757e74f8..245b813c367f22623829862f3d994b221de26f79 100644
--- a/src/main/java/org/olat/course/member/wizard/ImportMemberOverviewIdentitiesController.java
+++ b/src/main/java/org/olat/course/member/wizard/ImportMemberOverviewIdentitiesController.java
@@ -20,16 +20,21 @@
 package org.olat.course.member.wizard;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 import org.olat.admin.securitygroup.gui.UserControllerFactory;
+import org.olat.basesecurity.BaseSecurity;
+import org.olat.basesecurity.Constants;
+import org.olat.basesecurity.SecurityGroup;
+import org.olat.core.CoreSpringFactory;
+import org.olat.core.commons.persistence.SyncHelper;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.form.flexible.FormItemContainer;
 import org.olat.core.gui.components.form.flexible.impl.Form;
 import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
 import org.olat.core.gui.components.table.TableController;
 import org.olat.core.gui.control.Controller;
-import org.olat.core.gui.control.Event;
 import org.olat.core.gui.control.WindowControl;
 import org.olat.core.gui.control.generic.wizard.StepFormBasicController;
 import org.olat.core.gui.control.generic.wizard.StepsEvent;
@@ -42,16 +47,101 @@ import org.olat.core.id.Identity;
  */
 public class ImportMemberOverviewIdentitiesController extends StepFormBasicController {
 	private TableController identityTableCtrl;
+	private List<Identity> oks;
+	private final BaseSecurity securityManager;
 
 	public ImportMemberOverviewIdentitiesController(UserRequest ureq, WindowControl wControl, Form rootForm, StepsRunContext runContext) {
 		super(ureq, wControl, rootForm, runContext, LAYOUT_CUSTOM, "confirm_identities");
+		securityManager = CoreSpringFactory.getImpl(BaseSecurity.class);
+
+		oks = null;
+		if(containsRunContextKey("logins")) {
+			String logins = (String)runContext.get("logins");
+			oks = loadModel(logins);
+		} else if(containsRunContextKey("keys")) {
+			@SuppressWarnings("unchecked")
+			List<String> keys = (List<String>)runContext.get("keys");
+			oks = loadModel(keys);
+		}
+		
 		
-		List<Identity> oks = new ArrayList<Identity>();
 		identityTableCtrl = UserControllerFactory.createTableControllerFor(null, oks, ureq, getWindowControl(), null);
 		listenTo(identityTableCtrl);
 
 		initForm (ureq);
 	}
+	
+	private List<Identity> loadModel(List<String> keys) {
+		List<Identity> existIdents = Collections.emptyList();//securityManager.getIdentitiesOfSecurityGroup(securityGroup);
+
+		List<Identity> oks = new ArrayList<Identity>();
+		List<String> isanonymous = new ArrayList<String>();
+		List<String> notfounds = new ArrayList<String>();
+		List<String> alreadyin = new ArrayList<String>();
+
+		SecurityGroup anonymousSecGroup = securityManager.findSecurityGroupByName(Constants.GROUP_ANONYMOUS);
+		for (String identityKey : keys) {
+			Identity ident = securityManager.loadIdentityByKey(Long.parseLong(identityKey));
+			if (ident == null) { // not found, add to not-found-list
+				notfounds.add(identityKey);
+			} else if (securityManager.isIdentityInSecurityGroup(ident, anonymousSecGroup)) {
+				isanonymous.add(identityKey);
+			} else {
+				// check if already in group
+				boolean inGroup = SyncHelper.containsPersistable(existIdents, ident);
+				if (inGroup) {
+					// added to warning: already in group
+					alreadyin.add(ident.getName());
+				} else {
+					// ok to add -> preview (but filter duplicate entries)
+					if (!SyncHelper.containsPersistable(oks, ident)) {
+						oks.add(ident);
+					}
+				}
+			}
+		}
+		
+		return oks;
+	}
+	
+	private List<Identity> loadModel(String inp) {
+		List<Identity> existIdents = Collections.emptyList();//securityManager.getIdentitiesOfSecurityGroup(securityGroup);
+
+		List<Identity> oks = new ArrayList<Identity>();
+		List<String> isanonymous = new ArrayList<String>();
+		List<String> notfounds = new ArrayList<String>();
+		List<String> alreadyin = new ArrayList<String>();
+
+		SecurityGroup anonymousSecGroup = securityManager.findSecurityGroupByName(Constants.GROUP_ANONYMOUS);
+
+		String[] lines = inp.split("\r?\n");
+		for (int i = 0; i < lines.length; i++) {
+			String username = lines[i].trim();
+			if (!username.equals("")) { // skip empty lines
+				Identity ident = securityManager.findIdentityByName(username);
+				if (ident == null) { // not found, add to not-found-list
+					notfounds.add(username);
+				} else if (securityManager.isIdentityInSecurityGroup(ident, anonymousSecGroup)) {
+					isanonymous.add(username);
+				} else {
+					// check if already in group
+					boolean inGroup = SyncHelper.containsPersistable(existIdents, ident);
+					if (inGroup) {
+						// added to warning: already in group
+						alreadyin.add(ident.getName());
+					} else {
+						// ok to add -> preview (but filter duplicate entries)
+						if (!SyncHelper.containsPersistable(oks, ident)) {
+							oks.add(ident);
+						}
+					}
+				}
+			}
+		}
+		
+		return oks;
+	}
+	
 
 	public boolean validate() {
 		return true;
@@ -59,6 +149,7 @@ public class ImportMemberOverviewIdentitiesController extends StepFormBasicContr
 
 	@Override
 	protected void formOK(UserRequest ureq) {
+		addToRunContext("members", oks);
 		fireEvent(ureq, StepsEvent.ACTIVATE_NEXT);
 	}
 
diff --git a/src/main/java/org/olat/course/member/wizard/ImportMemberPermissionChoiceController.java b/src/main/java/org/olat/course/member/wizard/ImportMemberPermissionChoiceController.java
index d7a7149571edc47f95038202dd29415151fd05be..91d78f85c2eff719de80df96d168c8cd7473ced9 100644
--- a/src/main/java/org/olat/course/member/wizard/ImportMemberPermissionChoiceController.java
+++ b/src/main/java/org/olat/course/member/wizard/ImportMemberPermissionChoiceController.java
@@ -28,6 +28,7 @@ import org.olat.core.gui.control.generic.wizard.StepFormBasicController;
 import org.olat.core.gui.control.generic.wizard.StepsEvent;
 import org.olat.core.gui.control.generic.wizard.StepsRunContext;
 import org.olat.course.member.EditMembershipController;
+import org.olat.course.member.MemberPermissionChangeEvent;
 import org.olat.repository.RepositoryEntry;
 
 /**
@@ -53,6 +54,10 @@ public class ImportMemberPermissionChoiceController extends StepFormBasicControl
 
 	@Override
 	protected void formOK(UserRequest ureq) {
+		MemberPermissionChangeEvent e = new MemberPermissionChangeEvent(null);
+		permissionCtrl.collectRepoChanges(e);
+		permissionCtrl.collectGroupChanges(e);
+		addToRunContext("permissions", e);
 		fireEvent (ureq, StepsEvent.ACTIVATE_NEXT);
 	}
 
diff --git a/src/main/java/org/olat/course/member/wizard/ImportMember_1_ChooseMemberStep.java b/src/main/java/org/olat/course/member/wizard/ImportMember_1a_LoginListStep.java
similarity index 92%
rename from src/main/java/org/olat/course/member/wizard/ImportMember_1_ChooseMemberStep.java
rename to src/main/java/org/olat/course/member/wizard/ImportMember_1a_LoginListStep.java
index e68e65ac68f5f5a1f209ff2aecb1638b147fda1f..b6e58b4995e52bdac5b8a44fde76a623aa6dda90 100644
--- a/src/main/java/org/olat/course/member/wizard/ImportMember_1_ChooseMemberStep.java
+++ b/src/main/java/org/olat/course/member/wizard/ImportMember_1a_LoginListStep.java
@@ -32,9 +32,9 @@ import org.olat.repository.RepositoryEntry;
  * 
  * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
  */
-public class ImportMember_1_ChooseMemberStep extends BasicStep {
+public class ImportMember_1a_LoginListStep extends BasicStep {
 	
-	public ImportMember_1_ChooseMemberStep(UserRequest ureq, RepositoryEntry repoEntry) {
+	public ImportMember_1a_LoginListStep(UserRequest ureq, RepositoryEntry repoEntry) {
 		super(ureq);
 		setNextStep(new ImportMember_2_ConfirmMemberChoiceStep(ureq, repoEntry));
 		setI18nTitleAndDescr("import.import.title", "import.import.title");
diff --git a/src/main/java/org/olat/course/member/wizard/ImportMember_1b_ChooseMemberStep.java b/src/main/java/org/olat/course/member/wizard/ImportMember_1b_ChooseMemberStep.java
new file mode 100644
index 0000000000000000000000000000000000000000..984b4ba16ef0be37dc3a08cbb367810fe09612c1
--- /dev/null
+++ b/src/main/java/org/olat/course/member/wizard/ImportMember_1b_ChooseMemberStep.java
@@ -0,0 +1,53 @@
+/**
+ * <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.member.wizard;
+
+import org.olat.core.gui.UserRequest;
+import org.olat.core.gui.components.form.flexible.impl.Form;
+import org.olat.core.gui.control.WindowControl;
+import org.olat.core.gui.control.generic.wizard.BasicStep;
+import org.olat.core.gui.control.generic.wizard.PrevNextFinishConfig;
+import org.olat.core.gui.control.generic.wizard.StepFormController;
+import org.olat.core.gui.control.generic.wizard.StepsRunContext;
+import org.olat.repository.RepositoryEntry;
+
+/**
+ * 
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ */
+public class ImportMember_1b_ChooseMemberStep extends BasicStep {
+	
+	public ImportMember_1b_ChooseMemberStep(UserRequest ureq, RepositoryEntry repoEntry) {
+		super(ureq);
+		setNextStep(new ImportMember_2_ConfirmMemberChoiceStep(ureq, repoEntry));
+		setI18nTitleAndDescr("import.import.title", "import.import.title");
+	}
+
+	@Override
+	public PrevNextFinishConfig getInitialPrevNextFinishConfig() {
+		return new PrevNextFinishConfig(false, true, false);
+	}
+
+	@Override
+	public StepFormController getStepController(UserRequest ureq, WindowControl wControl, StepsRunContext runContext, Form form) {
+		ImportMemberBySearchController controller = new ImportMemberBySearchController(ureq, wControl, form, runContext);
+		return controller;
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/member/wizard/_content/import_search.html b/src/main/java/org/olat/course/member/wizard/_content/import_search.html
new file mode 100644
index 0000000000000000000000000000000000000000..6ef3ecad8c260a65092ce68ffdca9b2e4adb6247
--- /dev/null
+++ b/src/main/java/org/olat/course/member/wizard/_content/import_search.html
@@ -0,0 +1 @@
+$r.render("search")
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/member/wizard/_content/mail_template.html b/src/main/java/org/olat/course/member/wizard/_content/mail_template.html
new file mode 100644
index 0000000000000000000000000000000000000000..684bc98824a40368f3cdcefec62303d6fa15cc57
--- /dev/null
+++ b/src/main/java/org/olat/course/member/wizard/_content/mail_template.html
@@ -0,0 +1 @@
+$r.render("template")
\ No newline at end of file
diff --git a/src/main/java/org/olat/group/manager/BusinessGroupServiceImpl.java b/src/main/java/org/olat/group/manager/BusinessGroupServiceImpl.java
index 563a7b85ae1f00d7d0c084b161bfa3add605c0b0..3472dff90a64c358e0ace84fece0143c97eb67a1 100644
--- a/src/main/java/org/olat/group/manager/BusinessGroupServiceImpl.java
+++ b/src/main/java/org/olat/group/manager/BusinessGroupServiceImpl.java
@@ -84,6 +84,7 @@ import org.olat.group.model.BusinessGroupMembershipImpl;
 import org.olat.group.model.BusinessGroupMembershipViewImpl;
 import org.olat.group.model.BusinessGroupMembershipsChanges;
 import org.olat.group.model.DisplayMembers;
+import org.olat.group.model.IdentityGroupKey;
 import org.olat.group.model.MembershipModification;
 import org.olat.group.model.SearchBusinessGroupParams;
 import org.olat.group.right.BGRightManager;
@@ -1439,34 +1440,37 @@ public class BusinessGroupServiceImpl implements BusinessGroupService, UserDataD
 		List<BusinessGroupMembershipViewImpl> views =
 				businessGroupDAO.getMembershipInfoInBusinessGroups(businessGroups, identity);
 
-		Map<Long, BusinessGroupMembershipImpl> memberships = new HashMap<Long, BusinessGroupMembershipImpl>();
+		Map<IdentityGroupKey, BusinessGroupMembershipImpl> memberships = new HashMap<IdentityGroupKey, BusinessGroupMembershipImpl>();
 		for(BusinessGroupMembershipViewImpl membership: views) {
 			if(membership.getOwnerGroupKey() != null) {
 				Long groupKey = membership.getOwnerGroupKey();
-				if(!memberships.containsKey(groupKey)) {
-					memberships.put(groupKey, new BusinessGroupMembershipImpl(membership.getIdentityKey(), groupKey));
+				IdentityGroupKey key = new IdentityGroupKey(membership.getIdentityKey(), groupKey);
+				if(!memberships.containsKey(key)) {
+					memberships.put(key, new BusinessGroupMembershipImpl(membership.getIdentityKey(), groupKey));
 				}
-				BusinessGroupMembershipImpl mb = memberships.get(groupKey);
+				BusinessGroupMembershipImpl mb = memberships.get(key);
 				mb.setOwner(true);
 				mb.setCreationDate(membership.getCreationDate());
 				mb.setLastModified(membership.getLastModified());
 			}
 			if(membership.getParticipantGroupKey() != null) {
 				Long groupKey = membership.getParticipantGroupKey();
-				if(!memberships.containsKey(groupKey)) {
-					memberships.put(groupKey, new BusinessGroupMembershipImpl(membership.getIdentityKey(), groupKey));
+				IdentityGroupKey key = new IdentityGroupKey(membership.getIdentityKey(), groupKey);
+				if(!memberships.containsKey(key)) {
+					memberships.put(key, new BusinessGroupMembershipImpl(membership.getIdentityKey(), groupKey));
 				}
-				BusinessGroupMembershipImpl mb = memberships.get(groupKey);
+				BusinessGroupMembershipImpl mb = memberships.get(key);
 				mb.setParticipant(true);
 				mb.setCreationDate(membership.getCreationDate());
 				mb.setLastModified(membership.getLastModified());
 			}
 			if(membership.getWaitingGroupKey() != null) {
 				Long groupKey = membership.getWaitingGroupKey();
-				if(!memberships.containsKey(groupKey)) {
-					memberships.put(groupKey, new BusinessGroupMembershipImpl(membership.getIdentityKey(), groupKey));
+				IdentityGroupKey key = new IdentityGroupKey(membership.getIdentityKey(), groupKey);
+				if(!memberships.containsKey(key)) {
+					memberships.put(key, new BusinessGroupMembershipImpl(membership.getIdentityKey(), groupKey));
 				}
-				BusinessGroupMembershipImpl mb = memberships.get(groupKey);
+				BusinessGroupMembershipImpl mb = memberships.get(key);
 				mb.setWaiting(true);
 				mb.setCreationDate(membership.getCreationDate());
 				mb.setLastModified(membership.getLastModified());
diff --git a/src/main/java/org/olat/group/model/BusinessGroupMembershipChange.java b/src/main/java/org/olat/group/model/BusinessGroupMembershipChange.java
index bb12c670cc67cbd7b35e838a679853b51919e1b3..1fb80410cdb4030f46eb9bcbf1072222a86725e0 100644
--- a/src/main/java/org/olat/group/model/BusinessGroupMembershipChange.java
+++ b/src/main/java/org/olat/group/model/BusinessGroupMembershipChange.java
@@ -42,6 +42,13 @@ public class BusinessGroupMembershipChange extends Event {
 		this.groupKey = groupKey;
 	}
 	
+	public BusinessGroupMembershipChange(Identity member, BusinessGroupMembershipChange origin) {
+		this(member, origin.getGroupKey());
+		tutor = origin.tutor;
+		participant = origin.participant;
+		waitingList = origin.waitingList;	
+	}
+	
 	public Identity getMember() {
 		return member;
 	}
diff --git a/src/main/java/org/olat/group/model/BusinessGroupMembershipsChanges.java b/src/main/java/org/olat/group/model/BusinessGroupMembershipsChanges.java
index 382f538dc4e650c195367b3dd37390e3fa2fa987..e7b8d6d851fc64a3e3cecabc1bb73c70b9ccef7b 100644
--- a/src/main/java/org/olat/group/model/BusinessGroupMembershipsChanges.java
+++ b/src/main/java/org/olat/group/model/BusinessGroupMembershipsChanges.java
@@ -1,3 +1,22 @@
+/**
+ * <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.group.model;
 
 import java.util.ArrayList;
diff --git a/src/main/java/org/olat/group/model/IdentityGroupKey.java b/src/main/java/org/olat/group/model/IdentityGroupKey.java
new file mode 100644
index 0000000000000000000000000000000000000000..39efc7ebefd2fb7bdff41cbdac229b4bd3e8f890
--- /dev/null
+++ b/src/main/java/org/olat/group/model/IdentityGroupKey.java
@@ -0,0 +1,62 @@
+/**
+ * <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.group.model;
+
+/**
+ * Useful as key for HashMap
+ * 
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ */
+public class IdentityGroupKey {
+	private final Long identityKey;
+	private final Long groupKey;
+	
+	public IdentityGroupKey(Long identityKey, Long groupKey) {
+		this.identityKey = identityKey;
+		this.groupKey = groupKey;
+	}
+
+	public Long getIdentityKey() {
+		return identityKey;
+	}
+
+	public Long getGroupKey() {
+		return groupKey;
+	}
+	
+	@Override
+	public int hashCode() {
+		return (identityKey == null ? 38469 : identityKey.hashCode())
+				+ (groupKey == null ? 910 : groupKey.hashCode());
+	}
+	
+	@Override
+	public boolean equals(Object obj) {
+		if(this == obj) {
+			return true;
+		}
+		if(obj instanceof IdentityGroupKey) {
+			IdentityGroupKey key = (IdentityGroupKey)obj;
+			return identityKey != null && identityKey.equals(key.identityKey)
+					&& groupKey != null && groupKey.equals(key.groupKey);
+		}
+		return false;
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/group/ui/main/SelectBusinessGroupController.java b/src/main/java/org/olat/group/ui/main/SelectBusinessGroupController.java
index 6e1683426940b1c885db022e6e3c1a8e6f32fbac..54c78f8ad46078f7f2ccc0c0f75d58c9d8e20b18 100644
--- a/src/main/java/org/olat/group/ui/main/SelectBusinessGroupController.java
+++ b/src/main/java/org/olat/group/ui/main/SelectBusinessGroupController.java
@@ -1,3 +1,22 @@
+/**
+ * <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.group.ui.main;
 
 import org.olat.core.gui.UserRequest;
diff --git a/src/main/java/org/olat/repository/RepositoryManager.java b/src/main/java/org/olat/repository/RepositoryManager.java
index e55f45b55577246e0e511cc8ee1f5e4e89edaa02..e827fe8c1f5df0ac511461b4a1d68cf9b695ef3d 100644
--- a/src/main/java/org/olat/repository/RepositoryManager.java
+++ b/src/main/java/org/olat/repository/RepositoryManager.java
@@ -83,6 +83,7 @@ import org.olat.repository.handlers.RepositoryHandler;
 import org.olat.repository.handlers.RepositoryHandlerFactory;
 import org.olat.repository.model.RepositoryEntryMember;
 import org.olat.repository.model.RepositoryEntryMembership;
+import org.olat.repository.model.RepositoryEntryPermissionChangeEvent;
 import org.olat.repository.model.RepositoryEntryStrictMember;
 import org.olat.repository.model.RepositoryEntryStrictParticipant;
 import org.olat.repository.model.RepositoryEntryStrictTutor;
@@ -1732,6 +1733,34 @@ public class RepositoryManager extends BasicManager {
 		return entries;
 	}
 	
+	public void updateRepositoryEntryMembership(Identity ureqIdentity, RepositoryEntry re, List<RepositoryEntryPermissionChangeEvent> changes) {
+		for(RepositoryEntryPermissionChangeEvent e:changes) {
+			if(e.getRepoOwner() != null) {
+				if(e.getRepoOwner().booleanValue()) {
+					addOwners(ureqIdentity, new IdentitiesAddEvent(e.getMember()), re);
+				} else {
+					removeOwners(ureqIdentity, Collections.singletonList(e.getMember()), re);
+				}
+			}
+			
+			if(e.getRepoTutor() != null) {
+				if(e.getRepoTutor().booleanValue()) {
+					addTutors(ureqIdentity, new IdentitiesAddEvent(e.getMember()), re);
+				} else {
+					removeTutors(ureqIdentity, Collections.singletonList(e.getMember()), re);
+				}
+			}
+			
+			if(e.getRepoParticipant() != null) {
+				if(e.getRepoParticipant().booleanValue()) {
+					addParticipants(ureqIdentity, new IdentitiesAddEvent(e.getMember()), re);
+				} else {
+					removeParticipants(ureqIdentity, Collections.singletonList(e.getMember()), re);
+				}
+			}
+		}
+	}
+	
 	private final boolean and(StringBuilder sb, boolean and) {
 		if(and) sb.append(" and ");
 		else sb.append(" where ");
diff --git a/src/main/java/org/olat/repository/model/RepositoryEntryPermissionChangeEvent.java b/src/main/java/org/olat/repository/model/RepositoryEntryPermissionChangeEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..d55409b0591f18499b517c4fd3ec39abfd73a9d1
--- /dev/null
+++ b/src/main/java/org/olat/repository/model/RepositoryEntryPermissionChangeEvent.java
@@ -0,0 +1,77 @@
+/**
+ * <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.repository.model;
+
+import org.olat.core.gui.control.Event;
+import org.olat.core.id.Identity;
+
+/**
+ * 
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ */
+public class RepositoryEntryPermissionChangeEvent extends Event {
+	private static final long serialVersionUID = 8499004967313689825L;
+
+	private final Identity member;
+	
+	private Boolean repoOwner;
+	private Boolean repoTutor;
+	private Boolean repoParticipant;
+	
+	public RepositoryEntryPermissionChangeEvent(Identity member) {
+		super("id-perm-changed");
+		this.member = member;
+	}
+	
+	public RepositoryEntryPermissionChangeEvent(Identity member, RepositoryEntryPermissionChangeEvent origin) {
+		this(member);
+		repoOwner = origin.repoOwner;
+		repoTutor = origin.repoTutor;
+		repoParticipant = origin.repoParticipant;
+	}
+	
+	public Identity getMember() {
+		return member;
+	}
+
+	public Boolean getRepoOwner() {
+		return repoOwner;
+	}
+
+	public void setRepoOwner(Boolean repoOwner) {
+		this.repoOwner = repoOwner;
+	}
+
+	public Boolean getRepoTutor() {
+		return repoTutor;
+	}
+
+	public void setRepoTutor(Boolean repoTutor) {
+		this.repoTutor = repoTutor;
+	}
+
+	public Boolean getRepoParticipant() {
+		return repoParticipant;
+	}
+
+	public void setRepoParticipant(Boolean repoParticipant) {
+		this.repoParticipant = repoParticipant;
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/restapi/system/MonitoringModule.java b/src/main/java/org/olat/restapi/system/MonitoringModule.java
index 29985ae6cc8bcc60813d1bdf1688c9e2e472651b..6a222bdccf9b67ffd404318115084dc2bd44db57 100644
--- a/src/main/java/org/olat/restapi/system/MonitoringModule.java
+++ b/src/main/java/org/olat/restapi/system/MonitoringModule.java
@@ -1,3 +1,22 @@
+/**
+ * <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.restapi.system;
 
 import org.olat.core.configuration.AbstractOLATModule;
diff --git a/src/test/java/org/olat/basesecurity/BaseSecurityManagerTest.java b/src/test/java/org/olat/basesecurity/BaseSecurityManagerTest.java
index 468eaccb4bdaf60572dd932f0a0f55e7abb05c4b..7ecaa1d8de4f649e99aa29c791cd34adb0540380 100644
--- a/src/test/java/org/olat/basesecurity/BaseSecurityManagerTest.java
+++ b/src/test/java/org/olat/basesecurity/BaseSecurityManagerTest.java
@@ -1,3 +1,22 @@
+/**
+ * <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.basesecurity;
 
 import java.util.ArrayList;