MDL-23927 do not use = 'guest' because we have CFG->siteguest AND it matches any...
[moodle.git] / user / selector / lib.php
1 <?php
3 // This file is part of Moodle - http://moodle.org/
4 //
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
18 /**
19  * Code for ajax user selectors.
20  *
21  * @package   user
22  * @copyright 1999 onwards Martin Dougiamas  http://dougiamas.com
23  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  */
26 /**
27  * The default size of a user selector.
28  */
29 define('USER_SELECTOR_DEFAULT_ROWS', 20);
31 /**
32  * Base class for user selectors.
33  *
34  * In your theme, you must give each user-selector a defined width. If the
35  * user selector has name="myid", then the div myid_wrapper must have a width
36  * specified.
37  */
38 abstract class user_selector_base {
39     /** @var string The control name (and id) in the HTML. */
40     protected $name;
41     /** @var array Extra fields to search on and return in addition to firstname and lastname. */
42     protected $extrafields;
43     /** @var boolean Whether the conrol should allow selection of many users, or just one. */
44     protected $multiselect = true;
45     /** @var int The height this control should have, in rows. */
46     protected $rows = USER_SELECTOR_DEFAULT_ROWS;
47     /** @var array A list of userids that should not be returned by this control. */
48     protected $exclude = array();
49     /** @var array|null A list of the users who are selected. */
50     protected $selected = null;
51     /** @var boolean When the search changes, do we keep previously selected options that do
52      * not match the new search term? */
53     protected $preserveselected = false;
54     /** @var boolean If only one user matches the search, should we select them automatically. */
55     protected $autoselectunique = false;
56     /** @var boolean When searching, do we only match the starts of fields (better performance)
57      * or do we match occurrences anywhere? */
58     protected $searchanywhere = false;
59     /** @var mixed This is used by get selected users */
60     protected $validatinguserids = null;
62     /**  @var boolean Used to ensure we only output the search options for one user selector on
63      * each page. */
64     private static $searchoptionsoutput = false;
66     /** @var array JavaScript YUI3 Module definition */
67     protected static $jsmodule = array(
68                 'name' => 'user_selector',
69                 'fullpath' => '/user/selector/module.js',
70                 'requires'  => array('node', 'event-custom', 'datasource', 'json'),
71                 'strings' => array(
72                     array('previouslyselectedusers', 'moodle', '%%SEARCHTERM%%'),
73                     array('nomatchingusers', 'moodle', '%%SEARCHTERM%%'),
74                     array('none', 'moodle')
75                 ));
78     // Public API ==============================================================
80     /**
81      * Constructor. Each subclass must have a constructor with this signature.
82      *
83      * @param string $name the control name/id for use in the HTML.
84      * @param array $options other options needed to construct this selector.
85      * You must be able to clone a userselector by doing new get_class($us)($us->get_name(), $us->get_options());
86      */
87     public function __construct($name, $options = array()) {
88         global $CFG, $PAGE;
90         // Initialise member variables from constructor arguments.
91         $this->name = $name;
92         if (isset($options['extrafields'])) {
93             $this->extrafields = $options['extrafields'];
94         } else if (!empty($CFG->extrauserselectorfields)) {
95             $this->extrafields = explode(',', $CFG->extrauserselectorfields);
96         } else {
97             $this->extrafields = array();
98         }
99         if (isset($options['exclude']) && is_array($options['exclude'])) {
100             $this->exclude = $options['exclude'];
101         }
102         if (isset($options['multiselect'])) {
103             $this->multiselect = $options['multiselect'];
104         }
106         // Read the user prefs / optional_params that we use.
107         $this->preserveselected = $this->initialise_option('userselector_preserveselected', $this->preserveselected);
108         $this->autoselectunique = $this->initialise_option('userselector_autoselectunique', $this->autoselectunique);
109         $this->searchanywhere = $this->initialise_option('userselector_searchanywhere', $this->searchanywhere);
110     }
112     /**
113      * All to the list of user ids that this control will not select. For example,
114      * on the role assign page, we do not list the users who already have the role
115      * in question.
116      *
117      * @param array $arrayofuserids the user ids to exclude.
118      */
119     public function exclude($arrayofuserids) {
120         $this->exclude = array_unique(array_merge($this->exclude, $arrayofuserids));
121     }
123     /**
124      * Clear the list of excluded user ids.
125      */
126     public function clear_exclusions() {
127         $exclude = array();
128     }
130     /**
131      * @return array the list of user ids that this control will not select.
132      */
133     public function get_exclusions() {
134         return clone($this->exclude);
135     }
137     /**
138      * @return array of user objects. The users that were selected. This is a more sophisticated version
139      * of optional_param($this->name, array(), PARAM_INTEGER) that validates the
140      * returned list of ids against the rules for this user selector.
141      */
142     public function get_selected_users() {
143         // Do a lazy load.
144         if (is_null($this->selected)) {
145             $this->selected = $this->load_selected_users();
146         }
147         return $this->selected;
148     }
150     /**
151      * Convenience method for when multiselect is false (throws an exception if not).
152      * @return object the selected user object, or null if none.
153      */
154     public function get_selected_user() {
155         if ($this->multiselect) {
156             throw new moodle_exception('cannotcallusgetselecteduser');
157         }
158         $users = $this->get_selected_users();
159         if (count($users) == 1) {
160             return reset($users);
161         } else if (count($users) == 0) {
162             return null;
163         } else {
164             throw new moodle_exception('userselectortoomany');
165         }
166     }
168     /**
169      * If you update the database in such a way that it is likely to change the
170      * list of users that this component is allowed to select from, then you
171      * must call this method. For example, on the role assign page, after you have
172      * assigned some roles to some users, you should call this.
173      */
174     public function invalidate_selected_users() {
175         $this->selected = null;
176     }
178     /**
179      * Output this user_selector as HTML.
180      * @param boolean $return if true, return the HTML as a string instead of outputting it.
181      * @return mixed if $return is true, returns the HTML as a string, otherwise returns nothing.
182      */
183     public function display($return = false) {
184         global $PAGE;
186         // Get the list of requested users.
187         $search = optional_param($this->name . '_searchtext', '', PARAM_RAW);
188         if (optional_param($this->name . '_clearbutton', false, PARAM_BOOL)) {
189             $search = '';
190         }
191         $groupedusers = $this->find_users($search);
193         // Output the select.
194         $name = $this->name;
195         $multiselect = '';
196         if ($this->multiselect) {
197             $name .= '[]';
198             $multiselect = 'multiple="multiple" ';
199         }
200         $output = '<div class="userselector" id="' . $this->name . '_wrapper">' . "\n" .
201                 '<select name="' . $name . '" id="' . $this->name . '" ' .
202                 $multiselect . 'size="' . $this->rows . '">' . "\n";
204         // Populate the select.
205         $output .= $this->output_options($groupedusers, $search);
207         // Output the search controls.
208         $output .= "</select>\n<div>\n";
209         $output .= '<input type="text" name="' . $this->name . '_searchtext" id="' .
210                 $this->name . '_searchtext" size="15" value="' . s($search) . '" />';
211         $output .= '<input type="submit" name="' . $this->name . '_searchbutton" id="' .
212                 $this->name . '_searchbutton" value="' . $this->search_button_caption() . '" />';
213         $output .= '<input type="submit" name="' . $this->name . '_clearbutton" id="' .
214                 $this->name . '_clearbutton" value="' . get_string('clear') . '" />';
216         // And the search options.
217         $optionsoutput = false;
218         if (!user_selector_base::$searchoptionsoutput) {
219             $output .= print_collapsible_region_start('', 'userselector_options',
220                     get_string('searchoptions'), 'userselector_optionscollapsed', true, true);
221             $output .= $this->option_checkbox('preserveselected', $this->preserveselected, get_string('userselectorpreserveselected'));
222             $output .= $this->option_checkbox('autoselectunique', $this->autoselectunique, get_string('userselectorautoselectunique'));
223             $output .= $this->option_checkbox('searchanywhere', $this->searchanywhere, get_string('userselectorsearchanywhere'));
224             $output .= print_collapsible_region_end(true);
226             $PAGE->requires->js_init_call('M.core_user.init_user_selector_options_tracker', array(), false, self::$jsmodule);
227             user_selector_base::$searchoptionsoutput = true;
228         }
229         $output .= "</div>\n</div>\n\n";
231         // Initialise the ajax functionality.
232         $output .= $this->initialise_javascript($search);
234         // Return or output it.
235         if ($return) {
236             return $output;
237         } else {
238             echo $output;
239         }
240     }
242     /**
243      * The height this control will be displayed, in rows.
244      *
245      * @param integer $numrows the desired height.
246      */
247     public function set_rows($numrows) {
248         $this->rows = $numrows;
249     }
251     /**
252      * @return integer the height this control will be displayed, in rows.
253      */
254     public function get_rows() {
255         return $this->rows;
256     }
258     /**
259      * Whether this control will allow selection of many, or just one user.
260      *
261      * @param boolean $multiselect true = allow multiple selection.
262      */
263     public function set_multiselect($multiselect) {
264         $this->multiselect = $multiselect;
265     }
267     /**
268      * @return boolean whether this control will allow selection of more than one user.
269      */
270     public function is_multiselect() {
271         return $this->multiselect;
272     }
274     /**
275      * @return string the id/name that this control will have in the HTML.
276      */
277     public function get_name() {
278         return $this->name;
279     }
281     /**
282      * Set the user fields that are displayed in the selector in addition to the
283      * user's name.
284      *
285      * @param array $fields a list of field names that exist in the user table.
286      */
287     public function set_extra_fields($fields) {
288         $this->extrafields = $fields;
289     }
291     // API for sublasses =======================================================
293     /**
294      * Search the database for users matching the $search string, and any other
295      * conditions that apply. The SQL for testing whether a user matches the
296      * search string should be obtained by calling the search_sql method.
297      *
298      * This method is used both when getting the list of choices to display to
299      * the user, and also when validating a list of users that was selected.
300      *
301      * When preparing a list of users to choose from ($this->is_validating()
302      * return false) you should probably have an maximum number of users you will
303      * return, and if more users than this match your search, you should instead
304      * return a message generated by the too_many_results() method. However, you
305      * should not do this when validating.
306      *
307      * If you are writing a new user_selector subclass, I strongly recommend you
308      * look at some of the subclasses later in this file and in admin/roles/lib.php.
309      * They should help you see exactly what you have to do.
310      *
311      * @param string $search the search string.
312      * @return array An array of arrays of users. The array keys of the outer
313      *      array should be the string names of optgroups. The keys of the inner
314      *      arrays should be userids, and the values should be user objects
315      *      containing at least the list of fields returned by the method
316      *      required_fields_sql(). If a user object has a ->disabled property
317      *      that is true, then that option will be displayed greyed out, and
318      *      will not be returned by get_selected_users.
319      */
320     public abstract function find_users($search);
322     /**
323      *
324      * Note: this function must be implemented if you use the search ajax field
325      *       (e.g. set $options['file'] = '/admin/filecontainingyourclass.php';)
326      * @return array the options needed to recreate this user_selector.
327      */
328     protected function get_options() {
329         return array(
330             'class' => get_class($this),
331             'name' => $this->name,
332             'exclude' => $this->exclude,
333             'extrafields' => $this->extrafields,
334             'multiselect' => $this->multiselect
335         );
336     }
338     // Inner workings ==========================================================
340     /**
341      * @return boolean if true, we are validating a list of selected users,
342      *      rather than preparing a list of uesrs to choose from.
343      */
344     protected function is_validating() {
345         return !is_null($this->validatinguserids);
346     }
348     /**
349      * Get the list of users that were selected by doing optional_param then
350      * validating the result.
351      *
352      * @return array of user objects.
353      */
354     protected function load_selected_users() {
355         // See if we got anything.
356         $userids = optional_param($this->name, array(), PARAM_INTEGER);
357         if (empty($userids)) {
358             return array();
359         }
360         if (!$this->multiselect) {
361             $userids = array($userids);
362         }
364         // If we did, use the find_users method to validate the ids.
365         $this->validatinguserids = $userids;
366         $groupedusers = $this->find_users('');
367         $this->validatinguserids = null;
369         // Aggregate the resulting list back into a single one.
370         $users = array();
371         foreach ($groupedusers as $group) {
372             foreach ($group as $user) {
373                 if (!isset($users[$user->id]) && empty($user->disabled) && in_array($user->id, $userids)) {
374                     $users[$user->id] = $user;
375                 }
376             }
377         }
379         // If we are only supposed to be selecting a single user, make sure we do.
380         if (!$this->multiselect && count($users) > 1) {
381             $users = array_slice($users, 0, 1);
382         }
384         return $users;
385     }
387     /**
388      * @param string $u the table alias for the user table in the query being
389      *      built. May be ''.
390      * @return string fragment of SQL to go in the select list of the query.
391      */
392     protected function required_fields_sql($u) {
393         // Raw list of fields.
394         $fields = array('id', 'firstname', 'lastname');
395         $fields = array_merge($fields, $this->extrafields);
397         // Prepend the table alias.
398         if ($u) {
399             foreach ($fields as &$field) {
400                 $field = $u . '.' . $field;
401             }
402         }
403         return implode(',', $fields);
404     }
406     /**
407      * @param string $search the text to search for.
408      * @param string $u the table alias for the user table in the query being
409      *      built. May be ''.
410      * @return array an array with two elements, a fragment of SQL to go in the
411      *      where clause the query, and an array containing any required parameters.
412      *      this uses ? style placeholders.
413      */
414     protected function search_sql($search, $u) {
415         global $DB, $CFG;
416         $params = array();
417         $tests = array();
419         if ($u) {
420             $u .= '.';
421         }
423         // If we have a $search string, put a field LIKE '$search%' condition on each field.
424         if ($search) {
425             $conditions = array(
426                 $DB->sql_fullname($u . 'firstname', $u . 'lastname'),
427                 $conditions[] = $u . 'lastname'
428             );
429             foreach ($this->extrafields as $field) {
430                 $conditions[] = $u . $field;
431             }
432             $ilike = ' ' . $DB->sql_ilike();
433             if ($this->searchanywhere) {
434                 $searchparam = '%' . $search . '%';
435             } else {
436                 $searchparam = $search . '%';
437             }
438             $i = 0;
439             foreach ($conditions as &$condition) {
440                 $condition .= "$ilike :con{$i}00";
441                 $params["con{$i}00"] = $searchparam;
442                 $i++;
443             }
444             $tests[] = '(' . implode(' OR ', $conditions) . ')';
445         }
447         // Add some additional sensible conditions
448         $tests[] = $u . "id <> :guestid";
449         $params['guestid'] = $CFG->siteguest;
450         $tests[] = $u . 'deleted = 0';
451         $tests[] = $u . 'confirmed = 1';
453         // If we are being asked to exclude any users, do that.
454         if (!empty($this->exclude)) {
455             list($usertest, $userparams) = $DB->get_in_or_equal($this->exclude, SQL_PARAMS_NAMED, 'ex000', false);
456             $tests[] = $u . 'id ' . $usertest;
457             $params = array_merge($params, $userparams);
458         }
460         // If we are validating a set list of userids, add an id IN (...) test.
461         if (!empty($this->validatinguserids)) {
462             list($usertest, $userparams) = $DB->get_in_or_equal($this->validatinguserids, SQL_PARAMS_NAMED, 'val000');
463             $tests[] = $u . 'id ' . $usertest;
464             $params = array_merge($params, $userparams);
465         }
467         if (empty($tests)) {
468             $tests[] = '1 = 1';
469         }
471         // Combing the conditions and return.
472         return array(implode(' AND ', $tests), $params);
473     }
475     /**
476      * Used to generate a nice message when there are too many users to show.
477      * The message includes the number of users that currently match, and the
478      * text of the message depends on whether the search term is non-blank.
479      *
480      * @param string $search the search term, as passed in to the find users method.
481      * @param unknown_type $count the number of users that currently match.
482      * @return array in the right format to return from the find_users method.
483      */
484     protected function too_many_results($search, $count) {
485         if ($search) {
486             $a = new stdClass;
487             $a->count = $count;
488             $a->search = $search;
489             return array(get_string('toomanyusersmatchsearch', '', $a) => array(),
490                     get_string('pleasesearchmore') => array());
491         } else {
492             return array(get_string('toomanyuserstoshow', '', $count) => array(),
493                     get_string('pleaseusesearch') => array());
494         }
495     }
497     /**
498      * Output the list of <optgroup>s and <options>s that go inside the select.
499      * This method should do the same as the JavaScript method
500      * user_selector.prototype.handle_response.
501      *
502      * @param array $groupedusers an array, as returned by find_users.
503      * @return string HTML code.
504      */
505     protected function output_options($groupedusers, $search) {
506         $output = '';
508         // Ensure that the list of previously selected users is up to date.
509         $this->get_selected_users();
511         // If $groupedusers is empty, make a 'no matching users' group. If there is
512         // only one selected user, set a flag to select them if that option is turned on.
513         $select = false;
514         if (empty($groupedusers)) {
515             if (!empty($search)) {
516                 $groupedusers = array(get_string('nomatchingusers', '', $search) => array());
517             } else {
518                 $groupedusers = array(get_string('none') => array());
519             }
520         } else if ($this->autoselectunique && count($groupedusers) == 1 &&
521                 count(reset($groupedusers)) == 1) {
522             $select = true;
523             if (!$this->multiselect) {
524                 $this->selected = array();
525             }
526         }
528         // Output each optgroup.
529         foreach ($groupedusers as $groupname => $users) {
530             $output .= $this->output_optgroup($groupname, $users, $select);
531         }
533         // If there were previously selected users who do not match the search, show them too.
534         if ($this->preserveselected && !empty($this->selected)) {
535             $output .= $this->output_optgroup(get_string('previouslyselectedusers', '', $search), $this->selected, true);
536         }
538         // This method trashes $this->selected, so clear the cache so it is
539         // rebuilt before anyone tried to use it again.
540         $this->selected = null;
542         return $output;
543     }
545     /**
546      * Output one particular optgroup. Used by the preceding function output_options.
547      *
548      * @param string $groupname the label for this optgroup.
549      * @param array $users the users to put in this optgroup.
550      * @param boolean $select if true, select the users in this group.
551      * @return string HTML code.
552      */
553     protected function output_optgroup($groupname, $users, $select) {
554         if (!empty($users)) {
555             $output = '  <optgroup label="' . htmlspecialchars($groupname) . ' (' . count($users) . ')">' . "\n";
556             foreach ($users as $user) {
557                 $attributes = '';
558                 if (!empty($user->disabled)) {
559                     $attributes .= ' disabled="disabled"';
560                 } else if ($select || isset($this->selected[$user->id])) {
561                     $attributes .= ' selected="selected"';
562                 }
563                 unset($this->selected[$user->id]);
564                 $output .= '    <option' . $attributes . ' value="' . $user->id . '">' .
565                         $this->output_user($user) . "</option>\n";
566             }
567         } else {
568             $output = '  <optgroup label="' . htmlspecialchars($groupname) . '">' . "\n";
569             $output .= '    <option disabled="disabled">&nbsp;</option>' . "\n";
570         }
571         $output .= "  </optgroup>\n";
572         return $output;
573     }
575     /**
576      * Convert a user object to a string suitable for displaying as an option in the list box.
577      *
578      * @param object $user the user to display.
579      * @return string a string representation of the user.
580      */
581     public function output_user($user) {
582         $bits = array(
583             fullname($user)
584         );
585         foreach ($this->extrafields as $field) {
586             $bits[] = $user->$field;
587         }
588         return implode(', ', $bits);
589     }
591     /**
592      * @return string the caption for the search button.
593      */
594     protected function search_button_caption() {
595         return get_string('search');
596     }
598     // Initialise one of the option checkboxes, either from
599     // the request, or failing that from the user_preferences table, or
600     // finally from the given default.
601     private function initialise_option($name, $default) {
602         $param = optional_param($name, null, PARAM_BOOL);
603         if (is_null($param)) {
604             return get_user_preferences($name, $default);
605         } else {
606             set_user_preference($name, $param);
607             return $param;
608         }
609     }
611     // Output one of the options checkboxes.
612     private function option_checkbox($name, $on, $label) {
613         if ($on) {
614             $checked = ' checked="checked"';
615         } else {
616             $checked = '';
617         }
618         $name = 'userselector_' . $name;
619         $output = '<p><input type="hidden" name="' . $name . '" value="0" />' .
620                 // For the benefit of brain-dead IE, the id must be different from the name of the hidden form field above.
621                 // It seems that document.getElementById('frog') in IE will return and element with name="frog".
622                 '<input type="checkbox" id="' . $name . 'id" name="' . $name . '" value="1"' . $checked . ' /> ' .
623                 '<label for="' . $name . 'id">' . $label . "</label></p>\n";
624         user_preference_allow_ajax_update($name, PARAM_BOOL);
625         return $output;
626     }
628     /**
629      * @param boolean $optiontracker if true, initialise JavaScript for updating the user prefs.
630      * @return any HTML needed here.
631      */
632     protected function initialise_javascript($search) {
633         global $USER, $PAGE, $OUTPUT;
634         $output = '';
636         // Put the options into the session, to allow search.php to respond to the ajax requests.
637         $options = $this->get_options();
638         $hash = md5(serialize($options));
639         $USER->userselectors[$hash] = $options;
641         // Initialise the selector.
642         $PAGE->requires->js_init_call('M.core_user.init_user_selector', array($this->name, $hash, $this->extrafields, $search), false, self::$jsmodule);
643         return $output;
644     }
647 // User selectors for managing group members ==================================
649 /**
650  * Base class to avoid duplicating code.
651  */
652 abstract class groups_user_selector_base extends user_selector_base {
653     protected $groupid;
654     protected $courseid;
656     /**
657      * @param string $name control name
658      * @param array $options should have two elements with keys groupid and courseid.
659      */
660     public function __construct($name, $options) {
661         global $CFG;
662         parent::__construct($name, $options);
663         $this->groupid = $options['groupid'];
664         $this->courseid = $options['courseid'];
665         require_once($CFG->dirroot . '/group/lib.php');
666     }
668     protected function get_options() {
669         $options = parent::get_options();
670         $options['groupid'] = $this->groupid;
671         $options['courseid'] = $this->courseid;
672         return $options;
673     }
675     /**
676      * @param array $roles array in the format returned by groups_calculate_role_people.
677      * @return array array in the format find_users is supposed to return.
678      */
679     protected function convert_array_format($roles, $search) {
680         if (empty($roles)) {
681             $roles = array();
682         }
683         $groupedusers = array();
684         foreach ($roles as $role) {
685             if ($search) {
686                 $a = new stdClass;
687                 $a->role = $role->name;
688                 $a->search = $search;
689                 $groupname = get_string('matchingsearchandrole', '', $a);
690             } else {
691                 $groupname = $role->name;
692             }
693             $groupedusers[$groupname] = $role->users;
694             foreach ($groupedusers[$groupname] as &$user) {
695                 unset($user->roles);
696                 $user->fullname = fullname($user);
697             }
698         }
699         return $groupedusers;
700     }
703 /**
704  * User selector subclass for the list of users who are in a certain group.
705  * Used on the add group memebers page.
706  */
707 class group_members_selector extends groups_user_selector_base {
708     public function find_users($search) {
709         list($wherecondition, $params) = $this->search_sql($search, 'u');
710         $roles = groups_get_members_by_role($this->groupid, $this->courseid,
711                 $this->required_fields_sql('u'), 'u.lastname, u.firstname',
712                 $wherecondition, $params);
713         return $this->convert_array_format($roles, $search);
714     }
717 /**
718  * User selector subclass for the list of users who are not in a certain group.
719  * Used on the add group members page.
720  */
721 class group_non_members_selector extends groups_user_selector_base {
722     const MAX_USERS_PER_PAGE = 100;
724     /**
725      * An array of user ids populated by find_users() used in print_user_summaries()
726      */
727     private $potentialmembersids = array();
729     public function output_user($user) {
730         return parent::output_user($user) . ' (' . $user->numgroups . ')';
731     }
733     /**
734      * Returns the user selector JavaScript module
735      * @return array
736      */
737     public function get_js_module() {
738         return self::$jsmodule;
739     }
741     /**
742      * Outputs a Javascript array containing the other groups non-members are in.
743      * Used on the add group members page.
744      */
745     public function print_user_summaries($courseid) {
746         global $DB;
748         echo <<<END
749         <script type="text/javascript">
750 //<![CDATA[
751 var userSummaries = Array(
752 END;
753         // Get other groups user already belongs to
754         $usergroups = array();
755         $potentialmembersids = $this->potentialmembersids;
756         if( empty($potentialmembersids)==false ) {
757             list($membersidsclause, $params) = $DB->get_in_or_equal($potentialmembersids, SQL_PARAMS_NAMED, 'pm0');
758             $sql = "SELECT u.id AS userid, g.*
759                    FROM {user} u
760                    JOIN {groups_members} gm ON u.id = gm.userid
761                    JOIN {groups} g ON gm.groupid = g.id
762                   WHERE u.id $membersidsclause AND g.courseid = :courseid ";
763             $params['courseid'] = $courseid;
764             if ($rs = $DB->get_recordset_sql($sql, $params)) {
765                 foreach ($rs as $usergroup) {
766                     $usergroups[$usergroup->userid][$usergroup->id] = $usergroup;
767                 }
768                 $rs->close();
769             }
771             $membercnt = count($potentialmembersids);
772             $i=1;
773             foreach ($potentialmembersids as $userid) {
774              if (isset($usergroups[$userid])) {
775                  $usergrouplist = '<ul>';
777                  foreach ($usergroups[$userid] as $groupitem) {
778                      $usergrouplist .= '<li>'.addslashes_js(format_string($groupitem->name)).'</li>';
779                  }
780                  $usergrouplist .= '</ul>';
781              }
782              else {
783                  $usergrouplist = '';
784              }
785              echo "'$usergrouplist'";
786              if ($i < $membercnt) {
787                  echo ', ';
788              }
789              $i++;
790             }
791         }
792 echo <<<END
793 );
794 //]]>
795 </script>
796 END;
797     }
799     public function find_users($search) {
800         global $DB;
802         // Get list of allowed roles.
803         $context = get_context_instance(CONTEXT_COURSE, $this->courseid);
804         if (!$validroleids = groups_get_possible_roles($context)) {
805             return array();
806         }
807         list($roleids, $roleparams) = $DB->get_in_or_equal($validroleids, SQL_PARAMS_NAMED, 'r00');
809         // Get the search condition.
810         list($searchcondition, $searchparams) = $this->search_sql($search, 'u');
812         // Build the SQL
813         list($enrolsql, $enrolparams) = get_enrolled_sql($context);
814         $fields = "SELECT r.id AS roleid, r.shortname AS roleshortname, r.name AS rolename, u.id AS userid,
815                           " . $this->required_fields_sql('u') . ",
816                           (SELECT count(igm.groupid)
817                              FROM {groups_members} igm
818                              JOIN {groups} ig ON igm.groupid = ig.id
819                             WHERE igm.userid = u.id AND ig.courseid = :courseid) AS numgroups";
820         $sql = "   FROM {user} u
821                    JOIN ($enrolsql) e ON e.id = u.id
822                    JOIN {role_assignments} ra ON ra.userid = u.id
823                    JOIN {role} r ON r.id = ra.roleid
824                   WHERE ra.contextid " . get_related_contexts_string($context) . "
825                         AND u.deleted = 0
826                         AND ra.roleid $roleids
827                         AND u.id NOT IN (SELECT userid
828                                           FROM {groups_members}
829                                          WHERE groupid = :groupid)
830                         AND $searchcondition";
831         $orderby = "ORDER BY u.lastname, u.firstname";
833         $params = array_merge($searchparams, $roleparams, $enrolparams);
834         $params['courseid'] = $this->courseid;
835         $params['groupid']  = $this->groupid;
837         if (!$this->is_validating()) {
838             $potentialmemberscount = $DB->count_records_sql("SELECT COUNT(DISTINCT u.id) $sql", $params);
839             if ($potentialmemberscount > group_non_members_selector::MAX_USERS_PER_PAGE) {
840                 return $this->too_many_results($search, $potentialmemberscount);
841             }
842         }
844         $rs = $DB->get_recordset_sql("$fields $sql $orderby", $params);
845         $roles =  groups_calculate_role_people($rs, $context);
847         //don't hold onto user IDs if we're doing validation
848         if (empty($this->validatinguserids) ) {
849             if($roles) {
850                 foreach($roles as $k=>$v) {
851                     if($v) {
852                         foreach($v->users as $uid=>$userobject) {
853                             $this->potentialmembersids[] = $uid;
854                         }
855                     }
856                 }
857             }
858         }
860         return $this->convert_array_format($roles, $search);
861     }