Merge branch 'MDL-67974-310' of git://github.com/sarjona/moodle into MOODLE_310_STABLE
[moodle.git] / user / selector / lib.php
blob7d79d579553126c22e25f50fb8c1c20f33ec9e63
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17 /**
18 * Code for ajax user selectors.
20 * @package core_user
21 * @copyright 1999 onwards Martin Dougiamas http://dougiamas.com
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25 /**
26 * The default size of a user selector.
28 define('USER_SELECTOR_DEFAULT_ROWS', 20);
30 /**
31 * Base class for user selectors.
33 * In your theme, you must give each user-selector a defined width. If the
34 * user selector has name="myid", then the div myid_wrapper must have a width
35 * specified.
37 * @copyright 1999 onwards Martin Dougiamas http://dougiamas.com
38 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
40 abstract class user_selector_base {
41 /** @var string The control name (and id) in the HTML. */
42 protected $name;
43 /** @var array Extra fields to search on and return in addition to firstname and lastname. */
44 protected $extrafields;
45 /** @var object Context used for capability checks regarding this selector (does
46 * not necessarily restrict user list) */
47 protected $accesscontext;
48 /** @var boolean Whether the conrol should allow selection of many users, or just one. */
49 protected $multiselect = true;
50 /** @var int The height this control should have, in rows. */
51 protected $rows = USER_SELECTOR_DEFAULT_ROWS;
52 /** @var array A list of userids that should not be returned by this control. */
53 protected $exclude = array();
54 /** @var array|null A list of the users who are selected. */
55 protected $selected = null;
56 /** @var boolean When the search changes, do we keep previously selected options that do
57 * not match the new search term? */
58 protected $preserveselected = false;
59 /** @var boolean If only one user matches the search, should we select them automatically. */
60 protected $autoselectunique = false;
61 /** @var boolean When searching, do we only match the starts of fields (better performance)
62 * or do we match occurrences anywhere? */
63 protected $searchanywhere = false;
64 /** @var mixed This is used by get selected users */
65 protected $validatinguserids = null;
67 /** @var boolean Used to ensure we only output the search options for one user selector on
68 * each page. */
69 private static $searchoptionsoutput = false;
71 /** @var array JavaScript YUI3 Module definition */
72 protected static $jsmodule = array(
73 'name' => 'user_selector',
74 'fullpath' => '/user/selector/module.js',
75 'requires' => array('node', 'event-custom', 'datasource', 'json', 'moodle-core-notification'),
76 'strings' => array(
77 array('previouslyselectedusers', 'moodle', '%%SEARCHTERM%%'),
78 array('nomatchingusers', 'moodle', '%%SEARCHTERM%%'),
79 array('none', 'moodle')
80 ));
82 /** @var int this is used to define maximum number of users visible in list */
83 public $maxusersperpage = 100;
85 /** @var boolean Whether to override fullname() */
86 public $viewfullnames = false;
88 /**
89 * Constructor. Each subclass must have a constructor with this signature.
91 * @param string $name the control name/id for use in the HTML.
92 * @param array $options other options needed to construct this selector.
93 * You must be able to clone a userselector by doing new get_class($us)($us->get_name(), $us->get_options());
95 public function __construct($name, $options = array()) {
96 global $CFG, $PAGE;
98 // Initialise member variables from constructor arguments.
99 $this->name = $name;
101 // Use specified context for permission checks, system context if not specified.
102 if (isset($options['accesscontext'])) {
103 $this->accesscontext = $options['accesscontext'];
104 } else {
105 $this->accesscontext = context_system::instance();
108 // Check if some legacy code tries to override $CFG->showuseridentity.
109 if (isset($options['extrafields'])) {
110 debugging('The user_selector classes do not support custom list of extra identity fields any more. '.
111 'Instead, the user identity fields defined by the site administrator will be used to respect '.
112 'the configured privacy setting.', DEBUG_DEVELOPER);
113 unset($options['extrafields']);
116 // Populate the list of additional user identifiers to display.
117 $this->extrafields = get_extra_user_fields($this->accesscontext);
119 if (isset($options['exclude']) && is_array($options['exclude'])) {
120 $this->exclude = $options['exclude'];
122 if (isset($options['multiselect'])) {
123 $this->multiselect = $options['multiselect'];
126 // Read the user prefs / optional_params that we use.
127 $this->preserveselected = $this->initialise_option('userselector_preserveselected', $this->preserveselected);
128 $this->autoselectunique = $this->initialise_option('userselector_autoselectunique', $this->autoselectunique);
129 $this->searchanywhere = $this->initialise_option('userselector_searchanywhere', $this->searchanywhere);
131 if (!empty($CFG->maxusersperpage)) {
132 $this->maxusersperpage = $CFG->maxusersperpage;
137 * All to the list of user ids that this control will not select.
139 * For example, on the role assign page, we do not list the users who already have the role in question.
141 * @param array $arrayofuserids the user ids to exclude.
143 public function exclude($arrayofuserids) {
144 $this->exclude = array_unique(array_merge($this->exclude, $arrayofuserids));
148 * Clear the list of excluded user ids.
150 public function clear_exclusions() {
151 $this->exclude = array();
155 * Returns the list of user ids that this control will not select.
157 * @return array the list of user ids that this control will not select.
159 public function get_exclusions() {
160 return clone($this->exclude);
164 * The users that were selected.
166 * This is a more sophisticated version of optional_param($this->name, array(), PARAM_INT) that validates the
167 * returned list of ids against the rules for this user selector.
169 * @return array of user objects.
171 public function get_selected_users() {
172 // Do a lazy load.
173 if (is_null($this->selected)) {
174 $this->selected = $this->load_selected_users();
176 return $this->selected;
180 * Convenience method for when multiselect is false (throws an exception if not).
182 * @throws moodle_exception
183 * @return object the selected user object, or null if none.
185 public function get_selected_user() {
186 if ($this->multiselect) {
187 throw new moodle_exception('cannotcallusgetselecteduser');
189 $users = $this->get_selected_users();
190 if (count($users) == 1) {
191 return reset($users);
192 } else if (count($users) == 0) {
193 return null;
194 } else {
195 throw new moodle_exception('userselectortoomany');
200 * Invalidates the list of selected users.
202 * If you update the database in such a way that it is likely to change the
203 * list of users that this component is allowed to select from, then you
204 * must call this method. For example, on the role assign page, after you have
205 * assigned some roles to some users, you should call this.
207 public function invalidate_selected_users() {
208 $this->selected = null;
212 * Output this user_selector as HTML.
214 * @param boolean $return if true, return the HTML as a string instead of outputting it.
215 * @return mixed if $return is true, returns the HTML as a string, otherwise returns nothing.
217 public function display($return = false) {
218 global $PAGE;
220 // Get the list of requested users.
221 $search = optional_param($this->name . '_searchtext', '', PARAM_RAW);
222 if (optional_param($this->name . '_clearbutton', false, PARAM_BOOL)) {
223 $search = '';
225 $groupedusers = $this->find_users($search);
227 // Output the select.
228 $name = $this->name;
229 $multiselect = '';
230 if ($this->multiselect) {
231 $name .= '[]';
232 $multiselect = 'multiple="multiple" ';
234 $output = '<div class="userselector" id="' . $this->name . '_wrapper">' . "\n" .
235 '<select name="' . $name . '" id="' . $this->name . '" ' .
236 $multiselect . 'size="' . $this->rows . '" class="form-control no-overflow">' . "\n";
238 // Populate the select.
239 $output .= $this->output_options($groupedusers, $search);
241 // Output the search controls.
242 $output .= "</select>\n<div class=\"form-inline\">\n";
243 $output .= '<input type="text" name="' . $this->name . '_searchtext" id="' .
244 $this->name . '_searchtext" size="15" value="' . s($search) . '" class="form-control"/>';
245 $output .= '<input type="submit" name="' . $this->name . '_searchbutton" id="' .
246 $this->name . '_searchbutton" value="' . $this->search_button_caption() . '" class="btn btn-secondary"/>';
247 $output .= '<input type="submit" name="' . $this->name . '_clearbutton" id="' .
248 $this->name . '_clearbutton" value="' . get_string('clear') . '" class="btn btn-secondary"/>';
250 // And the search options.
251 $optionsoutput = false;
252 if (!user_selector_base::$searchoptionsoutput) {
253 $output .= print_collapsible_region_start('', 'userselector_options',
254 get_string('searchoptions'), 'userselector_optionscollapsed', true, true);
255 $output .= $this->option_checkbox('preserveselected', $this->preserveselected,
256 get_string('userselectorpreserveselected'));
257 $output .= $this->option_checkbox('autoselectunique', $this->autoselectunique,
258 get_string('userselectorautoselectunique'));
259 $output .= $this->option_checkbox('searchanywhere', $this->searchanywhere,
260 get_string('userselectorsearchanywhere'));
261 $output .= print_collapsible_region_end(true);
263 $PAGE->requires->js_init_call('M.core_user.init_user_selector_options_tracker', array(), false, self::$jsmodule);
264 user_selector_base::$searchoptionsoutput = true;
266 $output .= "</div>\n</div>\n\n";
268 // Initialise the ajax functionality.
269 $output .= $this->initialise_javascript($search);
271 // Return or output it.
272 if ($return) {
273 return $output;
274 } else {
275 echo $output;
280 * The height this control will be displayed, in rows.
282 * @param integer $numrows the desired height.
284 public function set_rows($numrows) {
285 $this->rows = $numrows;
289 * Returns the number of rows to display in this control.
291 * @return integer the height this control will be displayed, in rows.
293 public function get_rows() {
294 return $this->rows;
298 * Whether this control will allow selection of many, or just one user.
300 * @param boolean $multiselect true = allow multiple selection.
302 public function set_multiselect($multiselect) {
303 $this->multiselect = $multiselect;
307 * Returns true is multiselect should be allowed.
309 * @return boolean whether this control will allow selection of more than one user.
311 public function is_multiselect() {
312 return $this->multiselect;
316 * Returns the id/name of this control.
318 * @return string the id/name that this control will have in the HTML.
320 public function get_name() {
321 return $this->name;
325 * Set the user fields that are displayed in the selector in addition to the user's name.
327 * @param array $fields a list of field names that exist in the user table.
329 public function set_extra_fields($fields) {
330 debugging('The user_selector classes do not support custom list of extra identity fields any more. '.
331 'Instead, the user identity fields defined by the site administrator will be used to respect '.
332 'the configured privacy setting.', DEBUG_DEVELOPER);
336 * Search the database for users matching the $search string, and any other
337 * conditions that apply. The SQL for testing whether a user matches the
338 * search string should be obtained by calling the search_sql method.
340 * This method is used both when getting the list of choices to display to
341 * the user, and also when validating a list of users that was selected.
343 * When preparing a list of users to choose from ($this->is_validating()
344 * return false) you should probably have an maximum number of users you will
345 * return, and if more users than this match your search, you should instead
346 * return a message generated by the too_many_results() method. However, you
347 * should not do this when validating.
349 * If you are writing a new user_selector subclass, I strongly recommend you
350 * look at some of the subclasses later in this file and in admin/roles/lib.php.
351 * They should help you see exactly what you have to do.
353 * @param string $search the search string.
354 * @return array An array of arrays of users. The array keys of the outer
355 * array should be the string names of optgroups. The keys of the inner
356 * arrays should be userids, and the values should be user objects
357 * containing at least the list of fields returned by the method
358 * required_fields_sql(). If a user object has a ->disabled property
359 * that is true, then that option will be displayed greyed out, and
360 * will not be returned by get_selected_users.
362 public abstract function find_users($search);
366 * Note: this function must be implemented if you use the search ajax field
367 * (e.g. set $options['file'] = '/admin/filecontainingyourclass.php';)
368 * @return array the options needed to recreate this user_selector.
370 protected function get_options() {
371 return array(
372 'class' => get_class($this),
373 'name' => $this->name,
374 'exclude' => $this->exclude,
375 'multiselect' => $this->multiselect,
376 'accesscontext' => $this->accesscontext,
381 * Returns true if this control is validating a list of users.
383 * @return boolean if true, we are validating a list of selected users,
384 * rather than preparing a list of uesrs to choose from.
386 protected function is_validating() {
387 return !is_null($this->validatinguserids);
391 * Get the list of users that were selected by doing optional_param then validating the result.
393 * @return array of user objects.
395 protected function load_selected_users() {
396 // See if we got anything.
397 if ($this->multiselect) {
398 $userids = optional_param_array($this->name, array(), PARAM_INT);
399 } else if ($userid = optional_param($this->name, 0, PARAM_INT)) {
400 $userids = array($userid);
402 // If there are no users there is nobody to load.
403 if (empty($userids)) {
404 return array();
407 // If we did, use the find_users method to validate the ids.
408 $this->validatinguserids = $userids;
409 $groupedusers = $this->find_users('');
410 $this->validatinguserids = null;
412 // Aggregate the resulting list back into a single one.
413 $users = array();
414 foreach ($groupedusers as $group) {
415 foreach ($group as $user) {
416 if (!isset($users[$user->id]) && empty($user->disabled) && in_array($user->id, $userids)) {
417 $users[$user->id] = $user;
422 // If we are only supposed to be selecting a single user, make sure we do.
423 if (!$this->multiselect && count($users) > 1) {
424 $users = array_slice($users, 0, 1);
427 return $users;
431 * Returns SQL to select required fields.
433 * @param string $u the table alias for the user table in the query being
434 * built. May be ''.
435 * @return string fragment of SQL to go in the select list of the query.
437 protected function required_fields_sql($u) {
438 // Raw list of fields.
439 $fields = array('id');
440 // Add additional name fields.
441 $fields = array_merge($fields, get_all_user_name_fields(), $this->extrafields);
443 // Prepend the table alias.
444 if ($u) {
445 foreach ($fields as &$field) {
446 $field = $u . '.' . $field;
449 return implode(',', $fields);
453 * Returns an array with SQL to perform a search and the params that go into it.
455 * @param string $search the text to search for.
456 * @param string $u the table alias for the user table in the query being
457 * built. May be ''.
458 * @return array an array with two elements, a fragment of SQL to go in the
459 * where clause the query, and an array containing any required parameters.
460 * this uses ? style placeholders.
462 protected function search_sql($search, $u) {
463 return users_search_sql($search, $u, $this->searchanywhere, $this->extrafields,
464 $this->exclude, $this->validatinguserids);
468 * Used to generate a nice message when there are too many users to show.
470 * The message includes the number of users that currently match, and the
471 * text of the message depends on whether the search term is non-blank.
473 * @param string $search the search term, as passed in to the find users method.
474 * @param int $count the number of users that currently match.
475 * @return array in the right format to return from the find_users method.
477 protected function too_many_results($search, $count) {
478 if ($search) {
479 $a = new stdClass;
480 $a->count = $count;
481 $a->search = $search;
482 return array(get_string('toomanyusersmatchsearch', '', $a) => array(),
483 get_string('pleasesearchmore') => array());
484 } else {
485 return array(get_string('toomanyuserstoshow', '', $count) => array(),
486 get_string('pleaseusesearch') => array());
491 * Output the list of <optgroup>s and <options>s that go inside the select.
493 * This method should do the same as the JavaScript method
494 * user_selector.prototype.handle_response.
496 * @param array $groupedusers an array, as returned by find_users.
497 * @param string $search
498 * @return string HTML code.
500 protected function output_options($groupedusers, $search) {
501 $output = '';
503 // Ensure that the list of previously selected users is up to date.
504 $this->get_selected_users();
506 // If $groupedusers is empty, make a 'no matching users' group. If there is
507 // only one selected user, set a flag to select them if that option is turned on.
508 $select = false;
509 if (empty($groupedusers)) {
510 if (!empty($search)) {
511 $groupedusers = array(get_string('nomatchingusers', '', $search) => array());
512 } else {
513 $groupedusers = array(get_string('none') => array());
515 } else if ($this->autoselectunique && count($groupedusers) == 1 &&
516 count(reset($groupedusers)) == 1) {
517 $select = true;
518 if (!$this->multiselect) {
519 $this->selected = array();
523 // Output each optgroup.
524 foreach ($groupedusers as $groupname => $users) {
525 $output .= $this->output_optgroup($groupname, $users, $select);
528 // If there were previously selected users who do not match the search, show them too.
529 if ($this->preserveselected && !empty($this->selected)) {
530 $output .= $this->output_optgroup(get_string('previouslyselectedusers', '', $search), $this->selected, true);
533 // This method trashes $this->selected, so clear the cache so it is rebuilt before anyone tried to use it again.
534 $this->selected = null;
536 return $output;
540 * Output one particular optgroup. Used by the preceding function output_options.
542 * @param string $groupname the label for this optgroup.
543 * @param array $users the users to put in this optgroup.
544 * @param boolean $select if true, select the users in this group.
545 * @return string HTML code.
547 protected function output_optgroup($groupname, $users, $select) {
548 if (!empty($users)) {
549 $output = ' <optgroup label="' . htmlspecialchars($groupname) . ' (' . count($users) . ')">' . "\n";
550 foreach ($users as $user) {
551 $attributes = '';
552 if (!empty($user->disabled)) {
553 $attributes .= ' disabled="disabled"';
554 } else if ($select || isset($this->selected[$user->id])) {
555 $attributes .= ' selected="selected"';
557 unset($this->selected[$user->id]);
558 $output .= ' <option' . $attributes . ' value="' . $user->id . '">' .
559 $this->output_user($user) . "</option>\n";
560 if (!empty($user->infobelow)) {
561 // Poor man's indent here is because CSS styles do not work in select options, except in Firefox.
562 $output .= ' <option disabled="disabled" class="userselector-infobelow">' .
563 '&nbsp;&nbsp;&nbsp;&nbsp;' . s($user->infobelow) . '</option>';
566 } else {
567 $output = ' <optgroup label="' . htmlspecialchars($groupname) . '">' . "\n";
568 $output .= ' <option disabled="disabled">&nbsp;</option>' . "\n";
570 $output .= " </optgroup>\n";
571 return $output;
575 * Convert a user object to a string suitable for displaying as an option in the list box.
577 * @param object $user the user to display.
578 * @return string a string representation of the user.
580 public function output_user($user) {
581 $out = fullname($user, $this->viewfullnames);
582 if ($this->extrafields) {
583 $displayfields = array();
584 foreach ($this->extrafields as $field) {
585 $displayfields[] = s($user->{$field});
587 $out .= ' (' . implode(', ', $displayfields) . ')';
589 return $out;
593 * Returns the string to use for the search button caption.
595 * @return string the caption for the search button.
597 protected function search_button_caption() {
598 return get_string('search');
602 * Initialise one of the option checkboxes, either from the request, or failing that from the
603 * user_preferences table, or finally from the given default.
605 * @param string $name
606 * @param mixed $default
607 * @return mixed|null|string
609 private function initialise_option($name, $default) {
610 $param = optional_param($name, null, PARAM_BOOL);
611 if (is_null($param)) {
612 return get_user_preferences($name, $default);
613 } else {
614 set_user_preference($name, $param);
615 return $param;
620 * Output one of the options checkboxes.
622 * @param string $name
623 * @param string $on
624 * @param string $label
625 * @return string
627 private function option_checkbox($name, $on, $label) {
628 if ($on) {
629 $checked = ' checked="checked"';
630 } else {
631 $checked = '';
633 $name = 'userselector_' . $name;
634 // For the benefit of brain-dead IE, the id must be different from the name of the hidden form field above.
635 // It seems that document.getElementById('frog') in IE will return and element with name="frog".
636 $output = '<div class="form-check"><input type="hidden" name="' . $name . '" value="0" />' .
637 '<label class="form-check-label" for="' . $name . 'id">' .
638 '<input class="form-check-input" type="checkbox" id="' . $name . 'id" name="' . $name .
639 '" value="1"' . $checked . ' /> ' . $label .
640 "</label>
641 </div>\n";
642 user_preference_allow_ajax_update($name, PARAM_BOOL);
643 return $output;
647 * Initialises JS for this control.
649 * @param string $search
650 * @return string any HTML needed here.
652 protected function initialise_javascript($search) {
653 global $USER, $PAGE, $OUTPUT;
654 $output = '';
656 // Put the options into the session, to allow search.php to respond to the ajax requests.
657 $options = $this->get_options();
658 $hash = md5(serialize($options));
659 $USER->userselectors[$hash] = $options;
661 // Initialise the selector.
662 $PAGE->requires->js_init_call(
663 'M.core_user.init_user_selector',
664 array($this->name, $hash, $this->extrafields, $search),
665 false,
666 self::$jsmodule
668 return $output;
673 * Base class to avoid duplicating code.
675 * @copyright 1999 onwards Martin Dougiamas http://dougiamas.com
676 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
678 abstract class groups_user_selector_base extends user_selector_base {
679 /** @var int */
680 protected $groupid;
681 /** @var int */
682 protected $courseid;
685 * Constructor.
687 * @param string $name control name
688 * @param array $options should have two elements with keys groupid and courseid.
690 public function __construct($name, $options) {
691 global $CFG;
692 $options['accesscontext'] = context_course::instance($options['courseid']);
693 parent::__construct($name, $options);
694 $this->groupid = $options['groupid'];
695 $this->courseid = $options['courseid'];
696 require_once($CFG->dirroot . '/group/lib.php');
700 * Returns options for this selector.
701 * @return array
703 protected function get_options() {
704 $options = parent::get_options();
705 $options['groupid'] = $this->groupid;
706 $options['courseid'] = $this->courseid;
707 return $options;
711 * Creates an organised array from given data.
713 * @param array $roles array in the format returned by groups_calculate_role_people.
714 * @param string $search
715 * @return array array in the format find_users is supposed to return.
717 protected function convert_array_format($roles, $search) {
718 if (empty($roles)) {
719 $roles = array();
721 $groupedusers = array();
722 foreach ($roles as $role) {
723 if ($search) {
724 $a = new stdClass;
725 $a->role = $role->name;
726 $a->search = $search;
727 $groupname = get_string('matchingsearchandrole', '', $a);
728 } else {
729 $groupname = $role->name;
731 $groupedusers[$groupname] = $role->users;
732 foreach ($groupedusers[$groupname] as &$user) {
733 unset($user->roles);
734 $user->fullname = fullname($user);
735 if (!empty($user->component)) {
736 $user->infobelow = get_string('addedby', 'group',
737 get_string('pluginname', $user->component));
741 return $groupedusers;
746 * User selector subclass for the list of users who are in a certain group.
748 * Used on the add group memebers page.
750 * @copyright 1999 onwards Martin Dougiamas http://dougiamas.com
751 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
753 class group_members_selector extends groups_user_selector_base {
756 * Finds users to display in this control.
757 * @param string $search
758 * @return array
760 public function find_users($search) {
761 list($wherecondition, $params) = $this->search_sql($search, 'u');
763 list($sort, $sortparams) = users_order_by_sql('u', $search, $this->accesscontext);
765 $roles = groups_get_members_by_role($this->groupid, $this->courseid,
766 $this->required_fields_sql('u') . ', gm.component',
767 $sort, $wherecondition, array_merge($params, $sortparams));
769 return $this->convert_array_format($roles, $search);
774 * User selector subclass for the list of users who are not in a certain group.
776 * Used on the add group members page.
778 * @copyright 1999 onwards Martin Dougiamas http://dougiamas.com
779 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
781 class group_non_members_selector extends groups_user_selector_base {
783 * An array of user ids populated by find_users() used in print_user_summaries()
784 * @var array
786 private $potentialmembersids = array();
789 * Output user.
791 * @param stdClass $user
792 * @return string
794 public function output_user($user) {
795 return parent::output_user($user) . ' (' . $user->numgroups . ')';
799 * Returns the user selector JavaScript module
800 * @return array
802 public function get_js_module() {
803 return self::$jsmodule;
807 * Creates a global JS variable (userSummaries) that is used by the group selector
808 * to print related information when the user clicks on a user in the groups UI.
810 * Used by /group/clientlib.js
812 * @global moodle_page $PAGE
813 * @param int $courseid
815 public function print_user_summaries($courseid) {
816 global $PAGE;
817 $usersummaries = $this->get_user_summaries($courseid);
818 $PAGE->requires->data_for_js('userSummaries', $usersummaries);
822 * Construct HTML lists of group-memberships of the current set of users.
824 * Used in user/selector/search.php to repopulate the userSummaries JS global
825 * that is created in self::print_user_summaries() above.
827 * @param int $courseid The course
828 * @return string[] Array of HTML lists of groups.
830 public function get_user_summaries($courseid) {
831 global $DB;
833 $usersummaries = array();
835 // Get other groups user already belongs to.
836 $usergroups = array();
837 $potentialmembersids = $this->potentialmembersids;
838 if (empty($potentialmembersids) == false) {
839 list($membersidsclause, $params) = $DB->get_in_or_equal($potentialmembersids, SQL_PARAMS_NAMED, 'pm');
840 $sql = "SELECT u.id AS userid, g.*
841 FROM {user} u
842 JOIN {groups_members} gm ON u.id = gm.userid
843 JOIN {groups} g ON gm.groupid = g.id
844 WHERE u.id $membersidsclause AND g.courseid = :courseid ";
845 $params['courseid'] = $courseid;
846 $rs = $DB->get_recordset_sql($sql, $params);
847 foreach ($rs as $usergroup) {
848 $usergroups[$usergroup->userid][$usergroup->id] = $usergroup;
850 $rs->close();
852 foreach ($potentialmembersids as $userid) {
853 if (isset($usergroups[$userid])) {
854 $usergrouplist = html_writer::start_tag('ul');
855 foreach ($usergroups[$userid] as $groupitem) {
856 $usergrouplist .= html_writer::tag('li', format_string($groupitem->name));
858 $usergrouplist .= html_writer::end_tag('ul');
859 } else {
860 $usergrouplist = '';
862 $usersummaries[] = $usergrouplist;
865 return $usersummaries;
869 * Finds users to display in this control.
871 * @param string $search
872 * @return array
874 public function find_users($search) {
875 global $DB;
877 // Get list of allowed roles.
878 $context = context_course::instance($this->courseid);
879 if ($validroleids = groups_get_possible_roles($context)) {
880 list($roleids, $roleparams) = $DB->get_in_or_equal($validroleids, SQL_PARAMS_NAMED, 'r');
881 } else {
882 $roleids = " = -1";
883 $roleparams = array();
886 // We want to query both the current context and parent contexts.
887 list($relatedctxsql, $relatedctxparams) =
888 $DB->get_in_or_equal($context->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'relatedctx');
890 // Get the search condition.
891 list($searchcondition, $searchparams) = $this->search_sql($search, 'u');
893 // Build the SQL.
894 $enrolledjoin = get_enrolled_join($context, 'u.id');
896 $wheres = [];
897 $wheres[] = $enrolledjoin->wheres;
898 $wheres[] = 'u.deleted = 0';
899 $wheres[] = 'gm.id IS NULL';
900 $wheres = implode(' AND ', $wheres);
901 $wheres .= ' AND ' . $searchcondition;
903 $fields = "SELECT r.id AS roleid, u.id AS userid,
904 " . $this->required_fields_sql('u') . ",
905 (SELECT count(igm.groupid)
906 FROM {groups_members} igm
907 JOIN {groups} ig ON igm.groupid = ig.id
908 WHERE igm.userid = u.id AND ig.courseid = :courseid) AS numgroups";
909 $sql = " FROM {user} u
910 $enrolledjoin->joins
911 LEFT JOIN {role_assignments} ra ON (ra.userid = u.id AND ra.contextid $relatedctxsql AND ra.roleid $roleids)
912 LEFT JOIN {role} r ON r.id = ra.roleid
913 LEFT JOIN {groups_members} gm ON (gm.userid = u.id AND gm.groupid = :groupid)
914 WHERE $wheres";
916 list($sort, $sortparams) = users_order_by_sql('u', $search, $this->accesscontext);
917 $orderby = ' ORDER BY ' . $sort;
919 $params = array_merge($searchparams, $roleparams, $relatedctxparams, $enrolledjoin->params);
920 $params['courseid'] = $this->courseid;
921 $params['groupid'] = $this->groupid;
923 if (!$this->is_validating()) {
924 $potentialmemberscount = $DB->count_records_sql("SELECT COUNT(DISTINCT u.id) $sql", $params);
925 if ($potentialmemberscount > $this->maxusersperpage) {
926 return $this->too_many_results($search, $potentialmemberscount);
930 $rs = $DB->get_recordset_sql("$fields $sql $orderby", array_merge($params, $sortparams));
931 $roles = groups_calculate_role_people($rs, $context);
933 // Don't hold onto user IDs if we're doing validation.
934 if (empty($this->validatinguserids) ) {
935 if ($roles) {
936 foreach ($roles as $k => $v) {
937 if ($v) {
938 foreach ($v->users as $uid => $userobject) {
939 $this->potentialmembersids[] = $uid;
946 return $this->convert_array_format($roles, $search);