/**
* <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.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.olat.basesecurity.BaseSecurity;
import org.olat.basesecurity.BaseSecurityModule;
import org.olat.basesecurity.events.SingleIdentityChosenEvent;
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.FlexiTableElement;
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.FormEvent;
import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiColumnModel;
import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel;
import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableDataModelFactory;
import org.olat.core.gui.components.form.flexible.impl.elements.table.SelectionEvent;
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.control.generic.ajax.autocompletion.FlexiAutoCompleterController;
import org.olat.core.gui.control.generic.ajax.autocompletion.ListProvider;
import org.olat.core.gui.translator.Translator;
import org.olat.core.id.Identity;
import org.olat.core.id.Roles;
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;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Initial Date: Jul 29, 2003
*
* @author Felix Jost, Florian Gnaegi
* @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
*
* <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 FlexiAutoCompleterController {
private static final String usageIdentifyer = UserTableDataModel.class.getCanonicalName();
private FormLink backLink, searchButton;
private TextElement loginEl;
private Map <String,FormItem>propFormItems;
private FlexiTableElement tableEl;
private UserSearchFlexiTableModel userTableModel;
private FormLayoutContainer autoCompleterContainer;
private FormLayoutContainer searchFormContainer;
private final boolean isAdministrativeUser;
private final List<UserPropertyHandler> userSearchFormPropertyHandlers;
@Autowired
private UserManager userManager;
@Autowired
private BaseSecurity securityManager;
@Autowired
private BaseSecurityModule securityModule;
/**
* @param ureq
* @param wControl
* @param cancelbutton
* @param userMultiSelect
* @param statusEnabled
*/
public UserSearchFlexiController(UserRequest ureq, WindowControl wControl, Form rootForm) {
super(ureq, wControl, LAYOUT_CUSTOM, "usersearchext", rootForm);
setTranslator(Util.createPackageTranslator(UserPropertyHandler.class, getLocale(), getTranslator()));
setTranslator(Util.createPackageTranslator(UserSearchFlexiController.class, getLocale(), getTranslator()));
Roles roles = ureq.getUserSession().getRoles();
isAdministrativeUser = securityModule.isUserAllowedAdminProps(roles);
userSearchFormPropertyHandlers = userManager.getUserPropertyHandlersFor(UserSearchForm.class.getCanonicalName(), isAdministrativeUser);
ListProvider provider = new UserSearchListProvider();
setListProvider(provider);
setAllowNewValues(false);
initForm(ureq);
}
@Override
protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
if(formLayout instanceof FormLayoutContainer) {
FormLayoutContainer layoutCont = (FormLayoutContainer)formLayout;
// insert a autocompleter search
Roles roles = ureq.getUserSession().getRoles();
boolean autoCompleteAllowed = securityModule.isUserAllowedAutoComplete(roles);
boolean ajax = Windows.getWindows(ureq).getWindowManager().isAjaxEnabled();
if (ajax && autoCompleteAllowed) {
//auto complete
String velocityAutoCRoot = Util.getPackageVelocityRoot(FlexiAutoCompleterController.class);
String autoCPage = velocityAutoCRoot + "/autocomplete.html";
autoCompleterContainer = FormLayoutContainer.createCustomFormLayout("autocompletionsearch", getTranslator(), autoCPage);
autoCompleterContainer.setRootForm(mainForm);
layoutCont.add(autoCompleterContainer);
layoutCont.add("autocompletionsearch", autoCompleterContainer);
setupAutoCompleter(ureq, autoCompleterContainer, null, isAdministrativeUser, 60, 3, null);
}
// user search form
backLink = uifactory.addFormLink("btn.back", formLayout);
backLink.setIconLeftCSS("o_icon o_icon_back");
searchFormContainer = FormLayoutContainer.createDefaultFormLayout("usersearchPanel", getTranslator());
searchFormContainer.setRootForm(mainForm);
searchFormContainer.setElementCssClass("o_sel_usersearch_searchform");
searchFormContainer.setFormTitle(translate("header.normal"));
layoutCont.add(searchFormContainer);
layoutCont.add("usersearchPanel", searchFormContainer);
loginEl = uifactory.addTextElement("login", "search.form.login", 128, "", searchFormContainer);
loginEl.setVisible(isAdministrativeUser);
propFormItems = new HashMap<>();
for (UserPropertyHandler userPropertyHandler : userSearchFormPropertyHandlers) {
if (userPropertyHandler == null) continue;
FormItem fi = userPropertyHandler.addFormItem(getLocale(), null, UserSearchForm.class.getCanonicalName(), false, searchFormContainer);
// 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());
buttonGroupLayout.setRootForm(mainForm);
searchFormContainer.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);
layoutCont.contextPut("noList","false");
layoutCont.contextPut("showButton","false");
//add the table
FlexiTableColumnModel tableColumnModel = FlexiTableDataModelFactory.createFlexiTableColumnModel();
int colPos = 0;
if(isAdministrativeUser) {
tableColumnModel.addFlexiColumnModel(new DefaultFlexiColumnModel("table.user.login", colPos++));
}
List<UserPropertyHandler> userPropertyHandlers = userManager.getUserPropertyHandlersFor(usageIdentifyer, isAdministrativeUser);
List<UserPropertyHandler> resultingPropertyHandlers = new ArrayList<UserPropertyHandler>();
// followed by the users fields
for (int i = 0; i < userPropertyHandlers.size(); i++) {
UserPropertyHandler userPropertyHandler = userPropertyHandlers.get(i);
boolean visible = UserManager.getInstance().isMandatoryUserProperty(usageIdentifyer , userPropertyHandler);
if(visible) {
resultingPropertyHandlers.add(userPropertyHandler);
tableColumnModel.addFlexiColumnModel(new DefaultFlexiColumnModel(userPropertyHandler.i18nColumnDescriptorLabelKey(), colPos++));
}
}
tableColumnModel.addFlexiColumnModel(new DefaultFlexiColumnModel("select", translate("select"), "select"));
Translator myTrans = userManager.getPropertyHandlerTranslator(getTranslator());
userTableModel = new UserSearchFlexiTableModel(Collections.<Identity>emptyList(), resultingPropertyHandlers, isAdministrativeUser, getLocale(), tableColumnModel);
tableEl = uifactory.addTableElement(getWindowControl(), "users", userTableModel, 250, false, myTrans, formLayout);
tableEl.setCustomizeColumns(false);
tableEl.setMultiSelect(true);
tableEl.setSelectAllEnable(true);
layoutCont.put("userTable", tableEl.getComponent());
}
}
@Override
protected String getSearchValue(UserRequest ureq) {
if(autoCompleterContainer != null) {
return ureq.getParameter(autoCompleterContainer.getId(JSNAME_INPUTFIELD));
}
return null;
}
@Override
public void event(UserRequest ureq, Component source, Event event) {
if (source == backLink) {
flc.contextPut("showButton","false");
} else if(autoCompleterContainer != null && source == autoCompleterContainer.getComponent()) {
if (event.getCommand().equals(COMMAND_SELECT)) {
doSelect(ureq);
}
} else {
super.event(ureq, source, event);
}
}
@Override
protected void doFireSelection(UserRequest ureq, List<String> res) {
String mySel = res.isEmpty() ? null : res.get(0);
if(StringHelper.containsNonWhitespace(mySel) && StringHelper.isLong(mySel)) {
try {
Long key = Long.valueOf(mySel);
if (key > 0) {
Identity chosenIdent = securityManager.loadIdentityByKey(key);
if(chosenIdent != null) {
fireEvent(ureq, new SingleIdentityChosenEvent(chosenIdent));
List<Identity> selectedIdentities = Collections.singletonList(chosenIdent);
userTableModel.setObjects(selectedIdentities);
Set<Integer> selectedIndex = new HashSet<>();
selectedIndex.add(new Integer(0));
tableEl.setMultiSelectedIndex(selectedIndex);
}
}
} catch (NumberFormatException e) {
getWindowControl().setWarning(translate("error.no.user.found"));
}
} else {
getWindowControl().setWarning(translate("error.search.form.notempty"));
}
}
@Override
protected boolean validateFormLogic(UserRequest ureq) {
return true;
}
private boolean validateForm(UserRequest ureq) {
// override for sys admins
if (ureq.getUserSession() != null && ureq.getUserSession().getRoles() != null
&& ureq.getUserSession().getRoles().isOLATAdmin()) {
return true;
}
boolean filled = !loginEl.isEmpty();
StringBuilder full = new StringBuilder(loginEl.getValue().trim());
FormItem lastFormElement = loginEl;
// 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 : userSearchFormPropertyHandlers) {
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 formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
if(source == backLink) {
flc.contextPut("noList","false");
flc.contextPut("showButton","false");
if(userTableModel != null) {
userTableModel.setObjects(new ArrayList<>());
tableEl.reset();
}
} else if(searchButton == source) {
if(validateForm(ureq)) {
doSearch();
}
} else if (tableEl == source) {
if(event instanceof SelectionEvent) {
SelectionEvent se = (SelectionEvent)event;
Identity chosenIdent = userTableModel.getObject(se.getIndex());
fireEvent(ureq, new SingleIdentityChosenEvent(chosenIdent));
}
} else {
super.formInnerEvent(ureq, source, event);
}
}
@Override
protected void formNext(UserRequest ureq) {
//
}
@Override
protected void formFinish(UserRequest ureq) {
//
}
@Override
protected void formOK(UserRequest ureq) {
String searchValue = getSearchValue(ureq);
if(StringHelper.containsNonWhitespace(searchValue)) {
if(StringHelper.isLong(searchValue)) {
doFireSelection(ureq, Collections.singletonList(searchValue));
} else if(searchValue.length() >= 3){
Map<String, String> userProperties = new HashMap<String, String>();
userProperties.put(UserConstants.FIRSTNAME, searchValue);
userProperties.put(UserConstants.LASTNAME, searchValue);
userProperties.put(UserConstants.EMAIL, searchValue);
List<Identity> res = searchUsers(searchValue, userProperties, false);
if(res.size() == 1) {
//do select
Identity chosenIdent = res.get(0);
fireEvent(ureq, new SingleIdentityChosenEvent(chosenIdent));
} else if (res.size() > 1){
tableEl.reset();
userTableModel.setObjects(res);
}
}
} else {
if(validateForm(ureq)) {
doSearch();
}
}
}
public List<Identity> getSelectedIdentities() {
Set<Integer> index = tableEl.getMultiSelectedIndex();
List<Identity> selectedIdentities = new ArrayList<>(index.size());
for(Integer i : index) {
Identity selectedIdentity = userTableModel.getObject(i.intValue());
selectedIdentities.add(selectedIdentity);
}
return selectedIdentities;
}
public void doSearch() {
String login = loginEl.getValue();
// build user fields search map
Map<String, String> userPropertiesSearch = new HashMap<>();
for (UserPropertyHandler userPropertyHandler : userSearchFormPropertyHandlers) {
if (userPropertyHandler == null) continue;
FormItem ui = 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;
}
tableEl.reset();
List<Identity> users = searchUsers(login, userPropertiesSearch, true);
if (!users.isEmpty()) {
userTableModel.setObjects(users);
flc.contextPut("showButton","true");
} else {
getWindowControl().setInfo(translate("error.no.user.found"));
}
}
/**
* @see org.olat.core.gui.control.DefaultController#doDispose(boolean)
*/
@Override
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
*/
private 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);
}
}