/**
* 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.
* <p>
*/
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.ManagerFactory;
import org.olat.basesecurity.events.MultiIdentityChosenEvent;
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.Form;
import org.olat.core.gui.components.link.Link;
import org.olat.core.gui.components.link.LinkFactory;
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.components.velocity.VelocityContainer;
import org.olat.core.gui.control.Controller;
import org.olat.core.gui.control.ControllerEventListener;
import org.olat.core.gui.control.DefaultController;
import org.olat.core.gui.control.Event;
import org.olat.core.gui.control.WindowControl;
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.control.state.ControllerState;
import org.olat.core.gui.formelements.AbstractFormElement;
import org.olat.core.gui.formelements.FormElement;
import org.olat.core.gui.formelements.StaticSingleSelectionElement;
import org.olat.core.gui.formelements.TextElement;
import org.olat.core.gui.translator.PackageTranslator;
import org.olat.core.gui.translator.Translator;
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.logging.Tracing;
import org.olat.core.util.StringHelper;
import org.olat.core.util.Util;
import org.olat.user.UserManager;
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 UserSearchController extends DefaultController implements ControllerEventListener {
private static final String PACKAGE = UserSearchController.class.getPackage().getName();
private static final String VELOCITY_ROOT = Util.getPackageVelocityRoot(PACKAGE);
private static final String ACTION_SINGLESELECT_CHOOSE = "ssc";
private static final String ACTION_MULTISELECT_CHOOSE = "msc";
private VelocityContainer myContent;
private Panel searchPanel;
private UserSearchForm searchform;
private TableController tableCtr;
private UserTableDataModel tdm;
private List<Identity> foundIdentities = new ArrayList<Identity>();
private Translator pT;
private Link backLink;
private boolean useMultiSelect = false;
private AutoCompleterController autocompleterC;
private String actionKeyChoose;
private static final String STATE_SEARCHFORM = "searchform";
private static final String STATE_RESULTS = "results";
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
*/
public UserSearchController(UserRequest ureq, WindowControl wControl, boolean cancelbutton) {
this(ureq, wControl, cancelbutton, false, false);
}
/**
* @param ureq
* @param windowControl
* @param cancelbutton
* @param userMultiSelect
* @param statusEnabled
* @param actionKeyChooseFinish
*/
public UserSearchController(UserRequest ureq, WindowControl windowControl, boolean cancelbutton, boolean userMultiSelect, boolean statusEnabled, String actionKeyChooseFinish) {
this(ureq, windowControl, cancelbutton, userMultiSelect, statusEnabled);
this.actionKeyChoose = actionKeyChooseFinish;
}
/**
* @param ureq
* @param wControl
* @param cancelbutton
* @param userMultiSelect
* @param statusEnabled
*/
public UserSearchController(UserRequest ureq, WindowControl wControl, boolean cancelbutton, boolean userMultiSelect, boolean statusEnabled) {
super(wControl);
this.useMultiSelect = userMultiSelect;
this.actionKeyChoose = ACTION_KEY_CHOOSE;
pT = new PackageTranslator(PACKAGE, ureq.getLocale());
pT = UserManager.getInstance().getPropertyHandlerTranslator(pT);
myContent = new VelocityContainer("olatusersearch", VELOCITY_ROOT + "/usersearch.html", pT, this);
backLink = LinkFactory.createButton("btn.back", myContent, this);
searchPanel = new Panel("usersearchPanel");
searchPanel.addListener(this);
myContent.put("usersearchPanel", searchPanel);
if (ureq.getUserSession()==null) {
Tracing.logError("UserSearchController<init>: session is null!", UserSearchController.class);
} else if (ureq.getUserSession().getRoles()==null) {
Tracing.logError("UserSearchController<init>: roles is null!", UserSearchController.class);
}
boolean isAdmin = ureq.getUserSession().getRoles().isOLATAdmin();
searchform = new UserSearchForm("usersearchform", pT, isAdmin, cancelbutton, statusEnabled);
searchform.addListener(this);
searchPanel.setContent(searchform);
myContent.contextPut("noList","false");
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() {
public void getResult(String searchValue, ListReceiver receiver) {
Map<String, String> userProperties = new HashMap<String, String>();
userProperties.put(UserConstants.FIRSTNAME, searchValue);
userProperties.put(UserConstants.LASTNAME, searchValue);
userProperties.put(UserConstants.EMAIL, searchValue);
// FIXME:fg:cg: 13.03.08: User-Properties problem with undefine properties;
// userProperties.put(UserConstants.INSTITUTIONALEMAIL, searchValue);
// userProperties.put(UserConstants.INSTITUTIONALUSERIDENTIFIER, searchValue);
// search in all fileds -> non intersection search
List res = searchUsers(searchValue, userProperties, false);
int maxEntries = 15;
boolean hasMore = false;
for (Iterator it_res = res.iterator(); (hasMore=it_res.hasNext()) && maxEntries > 0;) {
maxEntries--;
Identity ident = (Identity) it_res.next();
User u = ident.getUser();
String login = ident.getName();
String first = u.getProperty(UserConstants.FIRSTNAME, loc);
String last = u.getProperty(UserConstants.LASTNAME, loc);
//String instId = u.getProperty(UserConstants.INSTITUTIONALUSERIDENTIFIER, loc);
//receiver.addEntry(login, last + " " + first + (StringHelper.containsNonWhitespace(instId) ? " (" + instId + ")": ""));
receiver.addEntry(login, last + " " + first);
}
if(hasMore){
receiver.addEntry(".....",".....");
}
}
};
autocompleterC = new AutoCompleterController(ureq, getWindowControl(), provider, "-", false);
autocompleterC.addControllerListener(this);
myContent.put("autocompletionsearch", autocompleterC.getInitialComponent());
}
setInitialComponent(myContent);
setState(STATE_SEARCHFORM);
}
/**
* @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 == backLink) {
myContent.contextPut("noList","false");
myContent.contextPut("showButton","false");
searchPanel.popContent();
setState(STATE_SEARCHFORM);
} else if (source == searchform) {
if (event == Form.EVNT_VALIDATION_OK) {
// form validation was ok
String login = searchform.getTextElement("login").getValue();
// build user fields search map
Map<String, String> userPropertiesSearch = new HashMap<String, String>();
for (UserPropertyHandler userPropertyHandler : searchform.userPropertyHandlers) {
if (userPropertyHandler == null) continue;
FormElement ui = searchform.getFormElement(userPropertyHandler.getName());
String uiValue = userPropertyHandler.getStringValue(ui);
if (StringHelper.containsNonWhitespace(uiValue)) {
userPropertiesSearch.put(userPropertyHandler.getName(), uiValue);
}
}
if (userPropertiesSearch.isEmpty()) userPropertiesSearch = null;
myContent.contextPut("showButton","true");
TableGuiConfiguration tableConfig = new TableGuiConfiguration();
tableConfig.setTableEmptyMessage(pT.translate("error.no.user.found"));
tableConfig.setDownloadOffered(false);// no download because user should not download user-list
if (tableCtr != null) tableCtr.dispose();
tableCtr = new TableController(tableConfig, ureq, getWindowControl(), pT, this);
Roles roles = ureq.getUserSession().getRoles();
boolean isAdministrativeUser = (roles.isAuthor() || roles.isGroupManager() || roles.isUserManager() || roles.isOLATAdmin());
tdm = new UserTableDataModel(searchUsers(login, userPropertiesSearch, true), ureq.getLocale(), isAdministrativeUser);
// add the data column descriptors
tdm.addColumnDescriptors(tableCtr, null);
// add the action columns
if (useMultiSelect) {
// add multiselect action
tableCtr.addMultiSelectAction(this.actionKeyChoose, ACTION_MULTISELECT_CHOOSE);
} else {
// add single column selec action
tableCtr.addColumnDescriptor(new StaticColumnDescriptor(ACTION_SINGLESELECT_CHOOSE, "table.header.action", myContent
.getTranslator().translate("action.choose")));
}
tableCtr.setTableDataModel(tdm);
tableCtr.setMultiSelect(useMultiSelect);
searchPanel.pushContent(tableCtr.getInitialComponent());
setState(STATE_RESULTS);
} else if (event == Form.EVNT_FORM_CANCELLED) {
fireEvent(ureq, Event.CANCELLED_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 res = ece.getEntries();
// if we get the event, we have a result
String mySel = res.isEmpty() ? null : (String) res.get(0);
if (( mySel == null) || mySel.trim().equals("")) {
getWindowControl().setWarning(pT.translate("error.search.form.notempty"));
return;
}
if(!mySel.startsWith(".....")){
List identities = searchUsers(mySel, null, false);
if (identities.size() == 0) {
getWindowControl().setWarning(pT.translate("error.no.user.found"));
return;
}
Identity chosenIdent = (Identity) identities.get(0);
// tell that an identity has been chosen
fireEvent(ureq, new SingleIdentityChosenEvent(chosenIdent));
}
}
}
protected void adjustState(ControllerState cstate, UserRequest ureq) {
String state = cstate.getSerializedState();
if (state.equals(STATE_SEARCHFORM)) {
// we should and can adjust to the searchform
searchPanel.popContent();
setState(STATE_SEARCHFORM);
}
}
/**
* @see org.olat.core.gui.control.DefaultController#doDispose(boolean)
*/
protected void doDispose() {
if (tableCtr != null) {
tableCtr.dispose();
tableCtr = null;
}
if (autocompleterC != null) {
autocompleterC.dispose();
autocompleterC = null;
}
}
/**
* Can be overwritten by subclassen to search other users or filter users.
* @param login
* @param userPropertiesSearch
* @return
*/
protected List searchUsers(String login, Map<String, String> userPropertiesSearch, boolean userPropertiesAsIntersectionSearch) {
return ManagerFactory.getManager().getVisibleIdentitiesByPowerSearch(
(login.equals("") ? null : login),
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 Form {
private static final String formIdentifyer = UserSearchForm.class.getCanonicalName();
private final boolean isAdmin;
List<UserPropertyHandler> userPropertyHandlers;
/**
* @param name
* @param cancelbutton
* @param isAdmin if true, no field must be filled in at all, otherwise
* validation takes place
*/
@SuppressWarnings("unused")
public UserSearchForm(String name, Translator translator, boolean isAdmin, boolean cancelbutton, boolean statusEnabled) {
super(name, translator);
this.isAdmin = isAdmin;
addFormElement("login", new TextElement("search.form.login", 128));
UserManager um = UserManager.getInstance();
userPropertyHandlers = um.getUserPropertyHandlersFor(formIdentifyer, isAdmin);
// Add all available user fields to this form
for (UserPropertyHandler userPropertyHandler : userPropertyHandlers) {
if (userPropertyHandler == null) continue;
FormElement ui = userPropertyHandler.getFormElement(getLocale(), null, formIdentifyer, false);
addFormElement(userPropertyHandler.getName(), ui);
}
addSubmitKey("submit.search", "submit");
if (cancelbutton) setCancelKey("submit.cancel");
}
private StaticSingleSelectionElement createStatusSelectionElement() {
String[] statusKeys = { Integer.toString(Identity.STATUS_ACTIV), Integer.toString(Identity.STATUS_PERMANENT), Integer.toString(Identity.STATUS_LOGIN_DENIED) };
String[] statusValues = {translate("rightsForm.status.activ"), translate("rightsForm.status.permanent"), translate("rightsForm.status.login_denied")};
return new StaticSingleSelectionElement("rightsForm.status", statusKeys, statusValues);
}
/**
* @see org.olat.core.gui.components.Form#validate(org.olat.core.gui.UserRequest)
*/
public boolean validate() {
// override for admins
if (isAdmin) return true;
boolean filled = !getTextElement("login").isEmpty();
StringBuffer full = new StringBuffer(getTextElement("login").getValue().trim());
FormElement lastFormElement = getFormElement("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) {
FormElement ui = getFormElement(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("^[\\* @\\%]*$");
if (!filled || onlyStar) {
// set the error message
((AbstractFormElement) lastFormElement).setErrorKey("error.search.form.notempty");
return false;
}
if ( fullString.contains("**") ) {
((AbstractFormElement)lastFormElement).setErrorKey("error.search.form.no.wildcard.dublicates");
return false;
}
int MIN_LENGTH = 4;
if ( fullString.length() < MIN_LENGTH ) {
((AbstractFormElement)lastFormElement).setErrorKey("error.search.form.to.short");
return false;
}
return true;
}
}