get_name(), $us->get_options()); */ public function __construct($name, $options = array()) { global $CFG; // Initialise member variables from constructor arguments. $this->name = $name; if (isset($options['extrafields'])) { $this->extrafields = $options['extrafields']; } else if (!empty($CFG->extrauserselectorfields)) { $this->extrafields = explode(',', $CFG->extrauserselectorfields); } else { $this->extrafields = array(); } if (isset($options['exclude']) && is_array($options['exclude'])) { $this->exclude = $options['exclude']; } if (isset($options['multiselect'])) { $this->multiselect = $options['multiselect']; } // Read the user prefs / optional_params that we use. $this->preserveselected = $this->initialise_option('userselector_preserveselected', $this->preserveselected); $this->autoselectunique = $this->initialise_option('userselector_autoselectunique', $this->autoselectunique); $this->searchanywhere = $this->initialise_option('userselector_searchanywhere', $this->searchanywhere); // Required JavaScript code. require_js(array('yui_yahoo', 'yui_event', 'yui_json', 'yui_connection', 'yui_datasource')); require_js('user/selector/script.js'); } /** * All to the list of user ids that this control will not select. For example, * on the role assign page, we do not list the users who already have the role * in question. * * @param array $arrayofuserids the user ids to exclude. */ public function exclude($arrayofuserids) { $this->exclude = array_unique(array_merge($this->exclude, $arrayofuserids)); } /** * Clear the list of excluded user ids. */ public function clear_exclusions() { $exclude = array(); } /** * @return array the list of user ids that this control will not select. */ public function get_exclusions() { return clone($this->exclude); } /** * @return array of user objects. The users that were selected. This is a more sophisticated version * of optional_param($this->name, array(), PARAM_INTEGER) that validates the * returned list of ids against the rules for this user selector. */ public function get_selected_users() { // Do a lazy load. if (is_null($this->selected)) { $this->selected = $this->load_selected_users(); } return $this->selected; } /** * Convenience method for when multiselect is false (throws an exception if not). * @return object the selected user object, or null if none. */ public function get_selected_user() { if ($this->multiselect) { throw new moodle_exception('cannotcallusgetselecteduser'); } $users = $this->get_selected_users(); if (count($users) == 1) { return reset($users); } else if (count($users) == 0) { return null; } else { throw new moodle_exception('userselectortoomany'); } } /** * If you update the database in such a way that it is likely to change the * list of users that this component is allowed to select from, then you * must call this method. For example, on the role assign page, after you have * assigned some roles to some users, you should call this. */ public function invalidate_selected_users() { $this->selected = null; } /** * Output this user_selector as HTML. * @param boolean $return if true, return the HTML as a string instead of outputting it. * @return mixed if $return is true, returns the HTML as a string, otherwise returns nothing. */ public function display($return = false) { // Get the list of requested users. $search = optional_param($this->name . '_searchtext', '', PARAM_RAW); if (optional_param($this->name . '_clearbutton', false, PARAM_BOOL)) { $search = ''; } $groupedusers = $this->find_users($search); // Output the select. $name = $this->name; $multiselect = ''; if ($this->multiselect) { $name .= '[]'; $multiselect = 'multiple="multiple" '; } $output = '
' . "\n" . '\n
\n"; $output .= ''; $output .= ''; $output .= ''; // And the search options. $optionsoutput = false; if (!user_selector_base::$searchoptionsoutput) { $output .= print_collapsible_region_start('', 'userselector_options', get_string('searchoptions'), 'userselector_optionscollapsed', true, true); $output .= $this->option_checkbox('preserveselected', $this->preserveselected, get_string('userselectorpreserveselected')); $output .= $this->option_checkbox('autoselectunique', $this->autoselectunique, get_string('userselectorautoselectunique')); $output .= $this->option_checkbox('searchanywhere', $this->searchanywhere, get_string('userselectorsearchanywhere')); $output .= print_collapsible_region_end(true); $output .= print_js_call('new user_selector_options_tracker', array(), true); user_selector_base::$searchoptionsoutput = true; } $output .= "
\n
\n\n"; // Initialise the ajax functionality. $output .= $this->initialise_javascript($search); // Return or output it. if ($return) { return $output; } else { echo $output; } } /** * The height this control will be displayed, in rows. * * @param integer $numrows the desired height. */ public function set_rows($numrows) { $this->rows = $numrows; } /** * @return integer the height this control will be displayed, in rows. */ public function get_rows() { return $this->rows; } /** * Whether this control will allow selection of many, or just one user. * * @param boolean $multiselect true = allow multiple selection. */ public function set_multiselect($multiselect) { $this->multiselect = $multiselect; } /** * @return boolean whether this control will allow selection of more than one user. */ public function is_multiselect() { return $this->multiselect; } /** * @return string the id/name that this control will have in the HTML. */ public function get_name() { return $this->name; } /** * Set the user fields that are displayed in the selector in addition to the * user's name. * * @param array $fields a list of field names that exist in the user table. */ public function set_extra_fields($fields) { $this->extrafields = $fields; } // API for sublasses ======================================================= /** * Search the database for users matching the $search string, and any other * conditions that apply. The SQL for testing whether a user matches the * search string should be obtained by calling the search_sql method. * * This method is used both when getting the list of choices to display to * the user, and also when validating a list of users that was selected. * * When preparing a list of users to choose from ($this->is_validating() * return false) you should probably have an maximum number of users you will * return, and if more users than this match your search, you should instead * return a message generated by the too_many_results() method. However, you * should not do this when validating. * * If you are writing a new user_selector subclass, I strongly recommend you * look at some of the subclasses later in this file and in admin/roles/lib.php. * They should help you see exactly what you have to do. * * @param string $search the search string. * @return array An array of arrays of users. The array keys of the outer * array should be the string names of optgroups. The keys of the inner * arrays should be userids, and the values should be user objects * containing at least the list of fields returned by the method * required_fields_sql(). If a user object has a ->disabled property * that is true, then that option will be displayed greyed out, and * will not be returned by get_selected_users. */ public abstract function find_users($search); /** * @return array the options needed to recreate this user_selector. */ protected function get_options() { return array( 'class' => get_class($this), 'name' => $this->name, 'exclude' => $this->exclude, 'extrafields' => $this->extrafields, 'multiselect' => $this->multiselect ); } // Inner workings ========================================================== /** * @return boolean if true, we are validating a list of selected users, * rather than preparing a list of uesrs to choose from. */ protected function is_validating() { return !is_null($this->validatinguserids); } /** * Get the list of users that were selected by doing optional_param then * validating the result. * * @return array of user objects. */ protected function load_selected_users() { // See if we got anything. $userids = optional_param($this->name, array(), PARAM_INTEGER); if (empty($userids)) { return array(); } if (!$this->multiselect) { $userids = array($userids); } // If we did, use the find_users method to validate the ids. $this->validatinguserids = $userids; $groupedusers = $this->find_users(''); $this->validatinguserids = null; // Aggregate the resulting list back into a single one. $users = array(); foreach ($groupedusers as $group) { foreach ($group as $user) { if (!isset($users[$user->id]) && empty($user->disabled)) { $users[$user->id] = $user; } } } // If we are only supposed to be selecting a single user, make sure we do. if (!$this->multiselect && count($users) > 1) { $users = array_slice($users, 0, 1); } return $users; } /** * @param string $u the table alias for the user table in the query being * built. May be ''. * @return string fragment of SQL to go in the select list of the query. */ protected function required_fields_sql($u) { // Raw list of fields. $fields = array('id', 'firstname', 'lastname'); $fields = array_merge($fields, $this->extrafields); // Prepend the table alias. if ($u) { foreach ($fields as &$field) { $field = $u . '.' . $field; } } return implode(',', $fields); } /** * @param string $search the text to search for. * @param string $u the table alias for the user table in the query being * built. May be ''. * @return array an array with two elements, a fragment of SQL to go in the * where clause the query, and an array containing any required parameters. * this uses ? style placeholders. */ protected function search_sql($search, $u) { global $DB; $params = array(); $tests = array(); if ($u) { $u .= '.'; } // If we have a $search string, put a field LIKE '$search%' condition on each field. if ($search) { $conditions = array( $DB->sql_fullname($u . 'firstname', $u . 'lastname'), $conditions[] = $u . 'lastname' ); foreach ($this->extrafields as $field) { $conditions[] = $u . $field; } $ilike = ' ' . $DB->sql_ilike() . ' ?'; if ($this->searchanywhere) { $searchparam = '%' . $search . '%'; } else { $searchparam = $search . '%'; } foreach ($conditions as &$condition) { $condition .= $ilike; $params[] = $searchparam; } $tests[] = '(' . implode(' OR ', $conditions) . ')'; } // Add some additional sensible conditions $tests[] = $u . "username <> 'guest'"; $tests[] = $u . 'deleted = 0'; $tests[] = $u . 'confirmed = 1'; // If we are being asked to exclude any users, do that. if (!empty($this->exclude)) { list($usertest, $userparams) = $DB->get_in_or_equal($this->exclude, SQL_PARAMS_QM, '', false); $tests[] = $u . 'id ' . $usertest; $params = array_merge($params, $userparams); } // If we are validating a set list of userids, add an id IN (...) test. if (!empty($this->validatinguserids)) { list($usertest, $userparams) = $DB->get_in_or_equal($this->validatinguserids); $tests[] = $u . 'id ' . $usertest; $params = array_merge($params, $userparams); } if (empty($tests)) { $tests[] = '1 = 1'; } // Combing the conditions and return. return array(implode(' AND ', $tests), $params); } /** * Used to generate a nice message when there are too many users to show. * The message includes the number of users that currently match, and the * text of the message depends on whether the search term is non-blank. * * @param string $search the search term, as passed in to the find users method. * @param unknown_type $count the number of users that currently match. * @return array in the right format to return from the find_users method. */ protected function too_many_results($search, $count) { if ($search) { $a = new stdClass; $a->count = $count; $a->search = $search; return array(get_string('toomanyusersmatchsearch', '', $a) => array(), get_string('pleasesearchmore') => array()); } else { return array(get_string('toomanyuserstoshow', '', $count) => array(), get_string('pleaseusesearch') => array()); } } /** * Output the list of s and s that go inside the select. * This method should do the same as the JavaScript method * user_selector.prototype.handle_response. * * @param array $groupedusers an array, as returned by find_users. * @return string HTML code. */ protected function output_options($groupedusers, $search) { $output = ''; // Ensure that the list of previously selected users is up to date. $this->get_selected_users(); // If $groupedusers is empty, make a 'no matching users' group. If there is // only one selected user, set a flag to select them if that option is turned on. $select = false; if (empty($groupedusers)) { if (!empty($search)) { $groupedusers = array(get_string('nomatchingusers', '', $search) => array()); } else { $groupedusers = array(get_string('none') => array()); } } else if ($this->autoselectunique && count($groupedusers) == 1 && count(reset($groupedusers)) == 1) { $select = true; if (!$this->multiselect) { $this->selected = array(); } } // Output each optgroup. foreach ($groupedusers as $groupname => $users) { $output .= $this->output_optgroup($groupname, $users, $select); } // If there were previously selected users who do not match the search, show them too. if ($this->preserveselected && !empty($this->selected)) { $output .= $this->output_optgroup(get_string('previouslyselectedusers', '', $search), $this->selected, true); } // This method trashes $this->selected, so clear the cache so it is // rebuilt before anyone tried to use it again. $this->selected = null; return $output; } /** * Output one particular optgroup. Used by the preceding function output_options. * * @param string $groupname the label for this optgroup. * @param array $users the users to put in this optgroup. * @param boolean $select if true, select the users in this group. * @return string HTML code. */ protected function output_optgroup($groupname, $users, $select) { if (!empty($users)) { $output = ' ' . "\n"; foreach ($users as $user) { $attributes = ''; if (!empty($user->disabled)) { $attributes .= ' disabled="disabled"'; } else if ($select || isset($this->selected[$user->id])) { $attributes .= ' selected="selected"'; } unset($this->selected[$user->id]); $output .= ' ' . $this->output_user($user) . "\n"; } } else { $output = ' ' . "\n"; $output .= ' ' . "\n"; } $output .= " \n"; return $output; } /** * Convert a user object to a string suitable for displaying as an option in the list box. * * @param object $user the user to display. * @return string a string representation of the user. */ public function output_user($user) { $bits = array( fullname($user) ); foreach ($this->extrafields as $field) { $bits[] = $user->$field; } return implode(', ', $bits); } /** * @return string the caption for the search button. */ protected function search_button_caption() { return get_string('search'); } // Initialise one of the option checkboxes, either from // the request, or failing that from the user_preferences table, or // finally from the given default. private function initialise_option($name, $default) { $param = optional_param($name, null, PARAM_BOOL); if (is_null($param)) { return get_user_preferences($name, $default); } else { set_user_preference($name, $param); return $param; } } // Output one of the options checkboxes. private function option_checkbox($name, $on, $label) { if ($on) { $checked = ' checked="checked"'; } else { $checked = ''; } $name = 'userselector_' . $name; $output = '

' . // For the benefit of brain-dead IE, the id must be different from the name of the hidden form field above. // It seems that document.getElementById('frog') in IE will return and element with name="frog". ' ' . '

\n"; user_preference_allow_ajax_update($name, PARAM_BOOL); return $output; } /** * @param boolean $optiontracker if true, initialise JavaScript for updating the user prefs. * @return any HTML needed here. */ protected function initialise_javascript($search) { global $USER; $output = ''; // Put the options into the session, to allow search.php to respond to the ajax requests. $options = $this->get_options(); $hash = md5(serialize($options)); $USER->userselectors[$hash] = $options; // Initialise the selector. $output .= print_js_call('new user_selector', array($this->name, $hash, $this->extrafields, $search, get_string('previouslyselectedusers', '', '%%SEARCHTERM%%'), get_string('nomatchingusers', '', '%%SEARCHTERM%%'), get_string('none')), true); return $output; } } // User selectors for managing group memebers ================================== /** * Base class to avoid duplicating code. */ abstract class groups_user_selector_base extends user_selector_base { protected $groupid; protected $courseid; /** * @param string $name control name * @param array $options should have two elements with keys groupid and courseid. */ public function __construct($name, $options) { global $CFG; parent::__construct($name, $options); $this->groupid = $options['groupid']; $this->courseid = $options['courseid']; require_once($CFG->dirroot . '/group/lib.php'); } protected function get_options() { $options = parent::get_options(); $options['groupid'] = $this->groupid; $options['courseid'] = $this->courseid; return $options; } /** * @param array $roles array in the format returned by groups_calculate_role_people. * @return array array in the format find_users is supposed to return. */ protected function convert_array_format($roles, $search) { if (empty($roles)) { $roles = array(); } $groupedusers = array(); foreach ($roles as $role) { if ($search) { $a = new stdClass; $a->role = $role->name; $a->search = $search; $groupname = get_string('matchingsearchandrole', '', $a); } else { $groupname = $role->name; } $groupedusers[$groupname] = $role->users; foreach ($groupedusers[$groupname] as &$user) { unset($user->roles); $user->fullname = fullname($user); } } return $groupedusers; } } /** * User selector subclass for the list of users who are in a certain group. * Used on the add group memebers page. */ class group_members_selector extends groups_user_selector_base { public function find_users($search) { list($wherecondition, $params) = $this->search_sql($search, 'u'); $roles = groups_get_members_by_role($this->groupid, $this->courseid, $this->required_fields_sql('u'), 'u.lastname, u.firstname', $wherecondition, $params); return $this->convert_array_format($roles, $search); } } /** * User selector subclass for the list of users who are not in a certain group. * Used on the add group memebers page. */ class group_non_members_selector extends groups_user_selector_base { const MAX_USERS_PER_PAGE = 100; public function output_user($user) { return parent::output_user($user) . ' (' . $user->numgroups . ')'; } public function find_users($search) { global $DB; // Get list of allowed roles. $context = get_context_instance(CONTEXT_COURSE, $this->courseid); if (!$validroleids = groups_get_possible_roles($context)) { return array(); } list($roleids, $roleparams) = $DB->get_in_or_equal($validroleids); // Get the search condition. list($searchcondition, $searchparams) = $this->search_sql($search, 'u'); // Build the SQL $fields = "SELECT r.id AS roleid, r.shortname AS roleshortname, r.name AS rolename, u.id AS userid, " . $this->required_fields_sql('u') . ', (SELECT count(igm.groupid) FROM {groups_members} igm JOIN {groups} ig ON igm.groupid = ig.id WHERE igm.userid = u.id AND ig.courseid = ?) AS numgroups '; $sql = " FROM {user} u JOIN {role_assignments} ra ON ra.userid = u.id JOIN {role} r ON r.id = ra.roleid WHERE ra.contextid " . get_related_contexts_string($context) . " AND u.deleted = 0 AND ra.roleid $roleids AND u.id NOT IN (SELECT userid FROM {groups_members} WHERE groupid = ?) AND $searchcondition"; $orderby = " ORDER BY u.lastname, u.firstname"; $params = array_merge($roleparams, array($this->groupid), $searchparams); if (!$this->is_validating()) { $potentialmemberscount = $DB->count_records_sql('SELECT count(DISTINCT u.id) ' . $sql, $params); if ($potentialmemberscount > group_non_members_selector::MAX_USERS_PER_PAGE) { return $this->too_many_results($search, $potentialmemberscount); } } array_unshift($params, $this->courseid); $rs = $DB->get_recordset_sql($fields . $sql . $orderby, $params); $roles = groups_calculate_role_people($rs, $context); return $this->convert_array_format($roles, $search); } } ?>