Newer
Older
/**
* <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.List;
import java.util.Map;
import org.olat.basesecurity.BaseSecurityManager;

srosse
committed
import org.olat.basesecurity.BaseSecurityModule;
import org.olat.basesecurity.events.SingleIdentityChosenEvent;

srosse
committed
import org.olat.core.CoreSpringFactory;
import org.olat.core.gui.UserRequest;

srosse
committed
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.MultipleSelectionElement;
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.link.Link;
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.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;
/**
* 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, selectAll, deselectAll;
private TextElement loginEl;
private List<UserPropertyHandler> userPropertyHandlers;
private Map <String,FormItem>propFormItems;
private FlexiTableElement tableEl;
private VelocityContainer tableVC;
private UserSearchFlexiTableModel userTableModel;
private FormLayoutContainer autoCompleterContainer;
private FormLayoutContainer searchFormContainer;
private boolean isAdministrativeUser;

srosse
committed
private final BaseSecurityModule securityModule;
private final UserManager userManager;
/**
* @param ureq
* @param wControl
* @param cancelbutton
* @param userMultiSelect
* @param statusEnabled
*/
public UserSearchFlexiController(UserRequest ureq, WindowControl wControl, Form rootForm) {

srosse
committed
super(ureq, wControl, LAYOUT_CUSTOM, "usersearchext", rootForm);

srosse
committed
securityModule = CoreSpringFactory.getImpl(BaseSecurityModule.class);
userManager = UserManager.getInstance();
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;

srosse
committed
Roles roles = ureq.getUserSession().getRoles();
isAdministrativeUser = securityModule.isUserAllowedAdminProps(roles);
// insert a autocompleter search

srosse
committed
boolean autoCompleteAllowed = securityModule.isUserAllowedAutoComplete(roles);
boolean ajax = Windows.getWindows(ureq).getWindowManager().isAjaxEnabled();
if (ajax && autoCompleteAllowed) {
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
//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);
//searchform = new UserSearchForm(ureq, getWindowControl(), isAdministrativeUser, false, mainForm);
searchFormContainer = FormLayoutContainer.createDefaultFormLayout("usersearchPanel", getTranslator());
searchFormContainer.setRootForm(mainForm);
layoutCont.add(searchFormContainer);
layoutCont.add("usersearchPanel", searchFormContainer);
loginEl = uifactory.addTextElement("login", "search.form.login", 128, "", searchFormContainer);
loginEl.setVisible(isAdministrativeUser);
UserManager um = UserManager.getInstance();
Translator tr = Util.createPackageTranslator(UserPropertyHandler.class, getLocale(), getTranslator());
userPropertyHandlers = um.getUserPropertyHandlersFor(UserSearchForm.class.getCanonicalName(), isAdministrativeUser);
propFormItems = new HashMap<String,FormItem>();
for (UserPropertyHandler userPropertyHandler : userPropertyHandlers) {
if (userPropertyHandler == null) continue;
FormItem fi = userPropertyHandler.addFormItem(getLocale(), null, UserSearchForm.class.getCanonicalName(), false, searchFormContainer);
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);

srosse
committed
}
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");
tableVC = createVelocityContainer("userflexisearch");
layoutCont.put("userTable", tableVC);
//add the table
FlexiTableColumnModel tableColumnModel = FlexiTableDataModelFactory.createFlexiTableColumnModel();

srosse
committed
int colPos = 0;
tableColumnModel.addFlexiColumnModel(new DefaultFlexiColumnModel("table.user.select", colPos++));
if(isAdministrativeUser) {

srosse
committed
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);

srosse
committed
tableColumnModel.addFlexiColumnModel(new DefaultFlexiColumnModel(userPropertyHandler.i18nColumnDescriptorLabelKey(), colPos++));
}
}

srosse
committed
tableColumnModel.addFlexiColumnModel(new DefaultFlexiColumnModel("select", colPos++));
Translator myTrans = userManager.getPropertyHandlerTranslator(getTranslator());

srosse
committed
userTableModel = new UserSearchFlexiTableModel(Collections.<UserResultWrapper>emptyList(), resultingPropertyHandlers, isAdministrativeUser, getLocale(), tableColumnModel);
tableEl = uifactory.addTableElement(ureq, getWindowControl(), "users", userTableModel, myTrans, formLayout);
selectAll = uifactory.addFormLink("selectall", formLayout);
deselectAll = uifactory.addFormLink("deselectall", formLayout);
tableVC.put("table", tableEl.getComponent());
tableVC.put("selectAll", selectAll.getComponent());
tableVC.put("deselectAll", deselectAll.getComponent());
}
protected String getSearchValue(UserRequest ureq) {

srosse
committed
String searchValue = ureq.getParameter(autoCompleterContainer.getId(JSNAME_INPUTFIELD));
return searchValue;
}
@Override
public void event(UserRequest ureq, Component source, Event event) {
if (source == backLink) {
flc.contextPut("showButton","false");

srosse
committed
} else if(autoCompleterContainer != null && source == autoCompleterContainer.getComponent()) {
if (event.getCommand().equals(COMMAND_SELECT)) {
doSelect(ureq);
}
} else {
super.event(ureq, source, event);
}
}
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
@Override
protected void doFireSelection(UserRequest ureq, List<String> res) {
// 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"));
}
}
@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 : 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 formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
if(source == backLink) {
flc.contextPut("noList","false");
flc.contextPut("showButton","false");
} else if(selectAll == source) {
checkAll(true);
} else if(deselectAll == source) {
checkAll(false);
} else if(searchButton == source) {
if(validateForm(ureq)) {
doSearch();
}
} else if (source instanceof FormLink && source.getName().startsWith("sel_lin")) {
Identity chosenIdent = (Identity)source.getUserObject();
fireEvent(ureq, new SingleIdentityChosenEvent(chosenIdent));
} else {
super.formInnerEvent(ureq, source, event);
}
}

srosse
committed
@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)) {

srosse
committed
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(wrapIdentities(res));
}
}
} else {
if(validateForm(ureq)) {
doSearch();
}
}
}
private void checkAll(boolean select) {
for(UserResultWrapper wrapper : userTableModel.getObjects()) {
wrapper.getSelectEl().select("on", select);
}
tableVC.setDirty(true);
}
public List<Identity> getSelectedIdentities() {
List<Identity> identities = new ArrayList<Identity>();
for(UserResultWrapper wrapper : userTableModel.getObjects()) {
if(wrapper.getSelectEl().isSelected(0)) {
identities.add(wrapper.getIdentity());
}
}
return identities;
}
private void doSearch() {
String login = loginEl.getValue();
// build user fields search map
Map<String, String> userPropertiesSearch = new HashMap<String, String>();
for (UserPropertyHandler userPropertyHandler : userPropertyHandlers) {
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(wrapIdentities(users));
flc.contextPut("showButton","true");
} else {
getWindowControl().setInfo(translate("error.no.user.found"));
}
}
private List<UserResultWrapper> wrapIdentities(List<Identity> identities) {
List<UserResultWrapper> wrappers = new ArrayList<UserResultWrapper>(identities.size());
for(Identity identity:identities) {
MultipleSelectionElement selectEl = uifactory.addCheckboxesHorizontal("sel_" + identity.getKey(), null, flc, new String[]{"on"}, new String[]{""}, null);
FormLink selectLink = uifactory.addFormLink("sel_lin_" + identity.getKey(), "select", null, flc, Link.LINK);
selectLink.setUserObject(identity);
wrappers.add(new UserResultWrapper(identity, selectLink, selectEl));
}
return wrappers;
/**
* @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
*/
private 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);
}