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;