Moodle  2.2.1
http://www.collinsharper.com
C:/xampp/htdocs/moodle/user/selector/lib.php
Go to the documentation of this file.
00001 <?php
00002 
00003 // This file is part of Moodle - http://moodle.org/
00004 //
00005 // Moodle is free software: you can redistribute it and/or modify
00006 // it under the terms of the GNU General Public License as published by
00007 // the Free Software Foundation, either version 3 of the License, or
00008 // (at your option) any later version.
00009 //
00010 // Moodle is distributed in the hope that it will be useful,
00011 // but WITHOUT ANY WARRANTY; without even the implied warranty of
00012 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00013 // GNU General Public License for more details.
00014 //
00015 // You should have received a copy of the GNU General Public License
00016 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
00017 
00029 define('USER_SELECTOR_DEFAULT_ROWS', 20);
00030 
00038 abstract class user_selector_base {
00040     protected $name;
00042     protected $extrafields;
00045     protected $accesscontext;
00047     protected $multiselect = true;
00049     protected $rows = USER_SELECTOR_DEFAULT_ROWS;
00051     protected $exclude = array();
00053     protected $selected = null;
00056     protected $preserveselected = false;
00058     protected $autoselectunique = false;
00061     protected $searchanywhere = false;
00063     protected $validatinguserids = null;
00064 
00067     private static $searchoptionsoutput = false;
00068 
00070     protected static $jsmodule = array(
00071                 'name' => 'user_selector',
00072                 'fullpath' => '/user/selector/module.js',
00073                 'requires'  => array('node', 'event-custom', 'datasource', 'json'),
00074                 'strings' => array(
00075                     array('previouslyselectedusers', 'moodle', '%%SEARCHTERM%%'),
00076                     array('nomatchingusers', 'moodle', '%%SEARCHTERM%%'),
00077                     array('none', 'moodle')
00078                 ));
00079 
00080 
00081     // Public API ==============================================================
00082 
00090     public function __construct($name, $options = array()) {
00091         global $CFG, $PAGE;
00092 
00093         // Initialise member variables from constructor arguments.
00094         $this->name = $name;
00095 
00096         // Use specified context for permission checks, system context if not
00097         // specified
00098         if (isset($options['accesscontext'])) {
00099             $this->accesscontext = $options['accesscontext'];
00100         } else {
00101             $this->accesscontext = get_context_instance(CONTEXT_SYSTEM);
00102         }
00103 
00104         if (isset($options['extrafields'])) {
00105             $this->extrafields = $options['extrafields'];
00106         } else if (!empty($CFG->showuseridentity) &&
00107                 has_capability('moodle/site:viewuseridentity', $this->accesscontext)) {
00108             $this->extrafields = explode(',', $CFG->showuseridentity);
00109         } else {
00110             $this->extrafields = array();
00111         }
00112         if (isset($options['exclude']) && is_array($options['exclude'])) {
00113             $this->exclude = $options['exclude'];
00114         }
00115         if (isset($options['multiselect'])) {
00116             $this->multiselect = $options['multiselect'];
00117         }
00118 
00119         // Read the user prefs / optional_params that we use.
00120         $this->preserveselected = $this->initialise_option('userselector_preserveselected', $this->preserveselected);
00121         $this->autoselectunique = $this->initialise_option('userselector_autoselectunique', $this->autoselectunique);
00122         $this->searchanywhere = $this->initialise_option('userselector_searchanywhere', $this->searchanywhere);
00123     }
00124 
00132     public function exclude($arrayofuserids) {
00133         $this->exclude = array_unique(array_merge($this->exclude, $arrayofuserids));
00134     }
00135 
00139     public function clear_exclusions() {
00140         $exclude = array();
00141     }
00142 
00146     public function get_exclusions() {
00147         return clone($this->exclude);
00148     }
00149 
00155     public function get_selected_users() {
00156         // Do a lazy load.
00157         if (is_null($this->selected)) {
00158             $this->selected = $this->load_selected_users();
00159         }
00160         return $this->selected;
00161     }
00162 
00167     public function get_selected_user() {
00168         if ($this->multiselect) {
00169             throw new moodle_exception('cannotcallusgetselecteduser');
00170         }
00171         $users = $this->get_selected_users();
00172         if (count($users) == 1) {
00173             return reset($users);
00174         } else if (count($users) == 0) {
00175             return null;
00176         } else {
00177             throw new moodle_exception('userselectortoomany');
00178         }
00179     }
00180 
00187     public function invalidate_selected_users() {
00188         $this->selected = null;
00189     }
00190 
00196     public function display($return = false) {
00197         global $PAGE;
00198 
00199         // Get the list of requested users.
00200         $search = optional_param($this->name . '_searchtext', '', PARAM_RAW);
00201         if (optional_param($this->name . '_clearbutton', false, PARAM_BOOL)) {
00202             $search = '';
00203         }
00204         $groupedusers = $this->find_users($search);
00205 
00206         // Output the select.
00207         $name = $this->name;
00208         $multiselect = '';
00209         if ($this->multiselect) {
00210             $name .= '[]';
00211             $multiselect = 'multiple="multiple" ';
00212         }
00213         $output = '<div class="userselector" id="' . $this->name . '_wrapper">' . "\n" .
00214                 '<select name="' . $name . '" id="' . $this->name . '" ' .
00215                 $multiselect . 'size="' . $this->rows . '">' . "\n";
00216 
00217         // Populate the select.
00218         $output .= $this->output_options($groupedusers, $search);
00219 
00220         // Output the search controls.
00221         $output .= "</select>\n<div>\n";
00222         $output .= '<input type="text" name="' . $this->name . '_searchtext" id="' .
00223                 $this->name . '_searchtext" size="15" value="' . s($search) . '" />';
00224         $output .= '<input type="submit" name="' . $this->name . '_searchbutton" id="' .
00225                 $this->name . '_searchbutton" value="' . $this->search_button_caption() . '" />';
00226         $output .= '<input type="submit" name="' . $this->name . '_clearbutton" id="' .
00227                 $this->name . '_clearbutton" value="' . get_string('clear') . '" />';
00228 
00229         // And the search options.
00230         $optionsoutput = false;
00231         if (!user_selector_base::$searchoptionsoutput) {
00232             $output .= print_collapsible_region_start('', 'userselector_options',
00233                     get_string('searchoptions'), 'userselector_optionscollapsed', true, true);
00234             $output .= $this->option_checkbox('preserveselected', $this->preserveselected, get_string('userselectorpreserveselected'));
00235             $output .= $this->option_checkbox('autoselectunique', $this->autoselectunique, get_string('userselectorautoselectunique'));
00236             $output .= $this->option_checkbox('searchanywhere', $this->searchanywhere, get_string('userselectorsearchanywhere'));
00237             $output .= print_collapsible_region_end(true);
00238 
00239             $PAGE->requires->js_init_call('M.core_user.init_user_selector_options_tracker', array(), false, self::$jsmodule);
00240             user_selector_base::$searchoptionsoutput = true;
00241         }
00242         $output .= "</div>\n</div>\n\n";
00243 
00244         // Initialise the ajax functionality.
00245         $output .= $this->initialise_javascript($search);
00246 
00247         // Return or output it.
00248         if ($return) {
00249             return $output;
00250         } else {
00251             echo $output;
00252         }
00253     }
00254 
00260     public function set_rows($numrows) {
00261         $this->rows = $numrows;
00262     }
00263 
00267     public function get_rows() {
00268         return $this->rows;
00269     }
00270 
00276     public function set_multiselect($multiselect) {
00277         $this->multiselect = $multiselect;
00278     }
00279 
00283     public function is_multiselect() {
00284         return $this->multiselect;
00285     }
00286 
00290     public function get_name() {
00291         return $this->name;
00292     }
00293 
00300     public function set_extra_fields($fields) {
00301         $this->extrafields = $fields;
00302     }
00303 
00304     // API for sublasses =======================================================
00305 
00333     public abstract function find_users($search);
00334 
00341     protected function get_options() {
00342         return array(
00343             'class' => get_class($this),
00344             'name' => $this->name,
00345             'exclude' => $this->exclude,
00346             'extrafields' => $this->extrafields,
00347             'multiselect' => $this->multiselect,
00348             'accesscontext' => $this->accesscontext,
00349         );
00350     }
00351 
00352     // Inner workings ==========================================================
00353 
00358     protected function is_validating() {
00359         return !is_null($this->validatinguserids);
00360     }
00361 
00368     protected function load_selected_users() {
00369         // See if we got anything.
00370         if ($this->multiselect) {
00371             $userids = optional_param_array($this->name, array(), PARAM_INTEGER);
00372         } else if ($userid = optional_param($this->name, 0, PARAM_INTEGER)) {
00373             $userids = array($userid);
00374         }
00375         // If there are no users there is nobody to load
00376         if (empty($userids)) {
00377             return array();
00378         }
00379 
00380         // If we did, use the find_users method to validate the ids.
00381         $this->validatinguserids = $userids;
00382         $groupedusers = $this->find_users('');
00383         $this->validatinguserids = null;
00384 
00385         // Aggregate the resulting list back into a single one.
00386         $users = array();
00387         foreach ($groupedusers as $group) {
00388             foreach ($group as $user) {
00389                 if (!isset($users[$user->id]) && empty($user->disabled) && in_array($user->id, $userids)) {
00390                     $users[$user->id] = $user;
00391                 }
00392             }
00393         }
00394 
00395         // If we are only supposed to be selecting a single user, make sure we do.
00396         if (!$this->multiselect && count($users) > 1) {
00397             $users = array_slice($users, 0, 1);
00398         }
00399 
00400         return $users;
00401     }
00402 
00408     protected function required_fields_sql($u) {
00409         // Raw list of fields.
00410         $fields = array('id', 'firstname', 'lastname');
00411         $fields = array_merge($fields, $this->extrafields);
00412 
00413         // Prepend the table alias.
00414         if ($u) {
00415             foreach ($fields as &$field) {
00416                 $field = $u . '.' . $field;
00417             }
00418         }
00419         return implode(',', $fields);
00420     }
00421 
00430     protected function search_sql($search, $u) {
00431         global $DB, $CFG;
00432         $params = array();
00433         $tests = array();
00434 
00435         if ($u) {
00436             $u .= '.';
00437         }
00438 
00439         // If we have a $search string, put a field LIKE '$search%' condition on each field.
00440         if ($search) {
00441             $conditions = array(
00442                 $DB->sql_fullname($u . 'firstname', $u . 'lastname'),
00443                 $conditions[] = $u . 'lastname'
00444             );
00445             foreach ($this->extrafields as $field) {
00446                 $conditions[] = $u . $field;
00447             }
00448             if ($this->searchanywhere) {
00449                 $searchparam = '%' . $search . '%';
00450             } else {
00451                 $searchparam = $search . '%';
00452             }
00453             $i = 0;
00454             foreach ($conditions as $key=>$condition) {
00455                 $conditions[$key] = $DB->sql_like($condition, ":con{$i}00", false, false);
00456                 $params["con{$i}00"] = $searchparam;
00457                 $i++;
00458             }
00459             $tests[] = '(' . implode(' OR ', $conditions) . ')';
00460         }
00461 
00462         // Add some additional sensible conditions
00463         $tests[] = $u . "id <> :guestid";
00464         $params['guestid'] = $CFG->siteguest;
00465         $tests[] = $u . 'deleted = 0';
00466         $tests[] = $u . 'confirmed = 1';
00467 
00468         // If we are being asked to exclude any users, do that.
00469         if (!empty($this->exclude)) {
00470             list($usertest, $userparams) = $DB->get_in_or_equal($this->exclude, SQL_PARAMS_NAMED, 'ex', false);
00471             $tests[] = $u . 'id ' . $usertest;
00472             $params = array_merge($params, $userparams);
00473         }
00474 
00475         // If we are validating a set list of userids, add an id IN (...) test.
00476         if (!empty($this->validatinguserids)) {
00477             list($usertest, $userparams) = $DB->get_in_or_equal($this->validatinguserids, SQL_PARAMS_NAMED, 'val');
00478             $tests[] = $u . 'id ' . $usertest;
00479             $params = array_merge($params, $userparams);
00480         }
00481 
00482         if (empty($tests)) {
00483             $tests[] = '1 = 1';
00484         }
00485 
00486         // Combing the conditions and return.
00487         return array(implode(' AND ', $tests), $params);
00488     }
00489 
00499     protected function too_many_results($search, $count) {
00500         if ($search) {
00501             $a = new stdClass;
00502             $a->count = $count;
00503             $a->search = $search;
00504             return array(get_string('toomanyusersmatchsearch', '', $a) => array(),
00505                     get_string('pleasesearchmore') => array());
00506         } else {
00507             return array(get_string('toomanyuserstoshow', '', $count) => array(),
00508                     get_string('pleaseusesearch') => array());
00509         }
00510     }
00511 
00520     protected function output_options($groupedusers, $search) {
00521         $output = '';
00522 
00523         // Ensure that the list of previously selected users is up to date.
00524         $this->get_selected_users();
00525 
00526         // If $groupedusers is empty, make a 'no matching users' group. If there is
00527         // only one selected user, set a flag to select them if that option is turned on.
00528         $select = false;
00529         if (empty($groupedusers)) {
00530             if (!empty($search)) {
00531                 $groupedusers = array(get_string('nomatchingusers', '', $search) => array());
00532             } else {
00533                 $groupedusers = array(get_string('none') => array());
00534             }
00535         } else if ($this->autoselectunique && count($groupedusers) == 1 &&
00536                 count(reset($groupedusers)) == 1) {
00537             $select = true;
00538             if (!$this->multiselect) {
00539                 $this->selected = array();
00540             }
00541         }
00542 
00543         // Output each optgroup.
00544         foreach ($groupedusers as $groupname => $users) {
00545             $output .= $this->output_optgroup($groupname, $users, $select);
00546         }
00547 
00548         // If there were previously selected users who do not match the search, show them too.
00549         if ($this->preserveselected && !empty($this->selected)) {
00550             $output .= $this->output_optgroup(get_string('previouslyselectedusers', '', $search), $this->selected, true);
00551         }
00552 
00553         // This method trashes $this->selected, so clear the cache so it is
00554         // rebuilt before anyone tried to use it again.
00555         $this->selected = null;
00556 
00557         return $output;
00558     }
00559 
00568     protected function output_optgroup($groupname, $users, $select) {
00569         if (!empty($users)) {
00570             $output = '  <optgroup label="' . htmlspecialchars($groupname) . ' (' . count($users) . ')">' . "\n";
00571             foreach ($users as $user) {
00572                 $attributes = '';
00573                 if (!empty($user->disabled)) {
00574                     $attributes .= ' disabled="disabled"';
00575                 } else if ($select || isset($this->selected[$user->id])) {
00576                     $attributes .= ' selected="selected"';
00577                 }
00578                 unset($this->selected[$user->id]);
00579                 $output .= '    <option' . $attributes . ' value="' . $user->id . '">' .
00580                         $this->output_user($user) . "</option>\n";
00581             }
00582         } else {
00583             $output = '  <optgroup label="' . htmlspecialchars($groupname) . '">' . "\n";
00584             $output .= '    <option disabled="disabled">&nbsp;</option>' . "\n";
00585         }
00586         $output .= "  </optgroup>\n";
00587         return $output;
00588     }
00589 
00596     public function output_user($user) {
00597         $out = fullname($user);
00598         if ($this->extrafields) {
00599             $displayfields = array();
00600             foreach ($this->extrafields as $field) {
00601                 $displayfields[] = $user->{$field};
00602             }
00603             $out .= ' (' . implode(', ', $displayfields) . ')';
00604         }
00605         return $out;
00606     }
00607 
00611     protected function search_button_caption() {
00612         return get_string('search');
00613     }
00614 
00615     // Initialise one of the option checkboxes, either from
00616     // the request, or failing that from the user_preferences table, or
00617     // finally from the given default.
00618     private function initialise_option($name, $default) {
00619         $param = optional_param($name, null, PARAM_BOOL);
00620         if (is_null($param)) {
00621             return get_user_preferences($name, $default);
00622         } else {
00623             set_user_preference($name, $param);
00624             return $param;
00625         }
00626     }
00627 
00628     // Output one of the options checkboxes.
00629     private function option_checkbox($name, $on, $label) {
00630         if ($on) {
00631             $checked = ' checked="checked"';
00632         } else {
00633             $checked = '';
00634         }
00635         $name = 'userselector_' . $name;
00636         $output = '<p><input type="hidden" name="' . $name . '" value="0" />' .
00637                 // For the benefit of brain-dead IE, the id must be different from the name of the hidden form field above.
00638                 // It seems that document.getElementById('frog') in IE will return and element with name="frog".
00639                 '<input type="checkbox" id="' . $name . 'id" name="' . $name . '" value="1"' . $checked . ' /> ' .
00640                 '<label for="' . $name . 'id">' . $label . "</label></p>\n";
00641         user_preference_allow_ajax_update($name, PARAM_BOOL);
00642         return $output;
00643     }
00644 
00649     protected function initialise_javascript($search) {
00650         global $USER, $PAGE, $OUTPUT;
00651         $output = '';
00652 
00653         // Put the options into the session, to allow search.php to respond to the ajax requests.
00654         $options = $this->get_options();
00655         $hash = md5(serialize($options));
00656         $USER->userselectors[$hash] = $options;
00657 
00658         // Initialise the selector.
00659         $PAGE->requires->js_init_call('M.core_user.init_user_selector', array($this->name, $hash, $this->extrafields, $search), false, self::$jsmodule);
00660         return $output;
00661     }
00662 }
00663 
00664 // User selectors for managing group members ==================================
00665 
00669 abstract class groups_user_selector_base extends user_selector_base {
00670     protected $groupid;
00671     protected $courseid;
00672 
00677     public function __construct($name, $options) {
00678         global $CFG;
00679         $options['accesscontext'] = get_context_instance(CONTEXT_COURSE, $options['courseid']);
00680         parent::__construct($name, $options);
00681         $this->groupid = $options['groupid'];
00682         $this->courseid = $options['courseid'];
00683         require_once($CFG->dirroot . '/group/lib.php');
00684     }
00685 
00686     protected function get_options() {
00687         $options = parent::get_options();
00688         $options['groupid'] = $this->groupid;
00689         $options['courseid'] = $this->courseid;
00690         return $options;
00691     }
00692 
00697     protected function convert_array_format($roles, $search) {
00698         if (empty($roles)) {
00699             $roles = array();
00700         }
00701         $groupedusers = array();
00702         foreach ($roles as $role) {
00703             if ($search) {
00704                 $a = new stdClass;
00705                 $a->role = $role->name;
00706                 $a->search = $search;
00707                 $groupname = get_string('matchingsearchandrole', '', $a);
00708             } else {
00709                 $groupname = $role->name;
00710             }
00711             $groupedusers[$groupname] = $role->users;
00712             foreach ($groupedusers[$groupname] as &$user) {
00713                 unset($user->roles);
00714                 $user->fullname = fullname($user);
00715             }
00716         }
00717         return $groupedusers;
00718     }
00719 }
00720 
00725 class group_members_selector extends groups_user_selector_base {
00726     public function find_users($search) {
00727         list($wherecondition, $params) = $this->search_sql($search, 'u');
00728         $roles = groups_get_members_by_role($this->groupid, $this->courseid,
00729                 $this->required_fields_sql('u'), 'u.lastname, u.firstname',
00730                 $wherecondition, $params);
00731         return $this->convert_array_format($roles, $search);
00732     }
00733 }
00734 
00739 class group_non_members_selector extends groups_user_selector_base {
00740     const MAX_USERS_PER_PAGE = 100;
00741 
00745     private $potentialmembersids = array();
00746 
00747     public function output_user($user) {
00748         return parent::output_user($user) . ' (' . $user->numgroups . ')';
00749     }
00750 
00755     public function get_js_module() {
00756         return self::$jsmodule;
00757     }
00758 
00769     public function print_user_summaries($courseid) {
00770         global $DB, $PAGE;
00771 
00772         $usersummaries = array();
00773 
00774         // Get other groups user already belongs to
00775         $usergroups = array();
00776         $potentialmembersids = $this->potentialmembersids;
00777         if( empty($potentialmembersids)==false ) {
00778             list($membersidsclause, $params) = $DB->get_in_or_equal($potentialmembersids, SQL_PARAMS_NAMED, 'pm');
00779             $sql = "SELECT u.id AS userid, g.*
00780                     FROM {user} u
00781                     JOIN {groups_members} gm ON u.id = gm.userid
00782                     JOIN {groups} g ON gm.groupid = g.id
00783                     WHERE u.id $membersidsclause AND g.courseid = :courseid ";
00784             $params['courseid'] = $courseid;
00785             $rs = $DB->get_recordset_sql($sql, $params);
00786             foreach ($rs as $usergroup) {
00787                 $usergroups[$usergroup->userid][$usergroup->id] = $usergroup;
00788             }
00789             $rs->close();
00790 
00791             foreach ($potentialmembersids as $userid) {
00792                 if (isset($usergroups[$userid])) {
00793                     $usergrouplist = html_writer::start_tag('ul');
00794                     foreach ($usergroups[$userid] as $groupitem) {
00795                         $usergrouplist .= html_writer::tag('li', format_string($groupitem->name));
00796                     }
00797                     $usergrouplist .= html_writer::end_tag('ul');
00798                 } else {
00799                     $usergrouplist = '';
00800                 }
00801                 $usersummaries[] = $usergrouplist;
00802             }
00803         }
00804 
00805         $PAGE->requires->data_for_js('userSummaries', $usersummaries);
00806     }
00807 
00808     public function find_users($search) {
00809         global $DB;
00810 
00811         // Get list of allowed roles.
00812         $context = get_context_instance(CONTEXT_COURSE, $this->courseid);
00813         if ($validroleids = groups_get_possible_roles($context)) {
00814             list($roleids, $roleparams) = $DB->get_in_or_equal($validroleids, SQL_PARAMS_NAMED, 'r');
00815         } else {
00816             $roleids = " = -1";
00817             $roleparams = array();
00818         }
00819 
00820         // Get the search condition.
00821         list($searchcondition, $searchparams) = $this->search_sql($search, 'u');
00822 
00823         // Build the SQL
00824         list($enrolsql, $enrolparams) = get_enrolled_sql($context);
00825         $fields = "SELECT r.id AS roleid, r.shortname AS roleshortname, r.name AS rolename, u.id AS userid,
00826                           " . $this->required_fields_sql('u') . ",
00827                           (SELECT count(igm.groupid)
00828                              FROM {groups_members} igm
00829                              JOIN {groups} ig ON igm.groupid = ig.id
00830                             WHERE igm.userid = u.id AND ig.courseid = :courseid) AS numgroups";
00831         $sql = "   FROM {user} u
00832                    JOIN ($enrolsql) e ON e.id = u.id
00833               LEFT JOIN {role_assignments} ra ON (ra.userid = u.id AND ra.contextid " . get_related_contexts_string($context) . " AND ra.roleid $roleids)
00834               LEFT JOIN {role} r ON r.id = ra.roleid
00835                   WHERE u.deleted = 0
00836                         AND u.id NOT IN (SELECT userid
00837                                           FROM {groups_members}
00838                                          WHERE groupid = :groupid)
00839                         AND $searchcondition";
00840         $orderby = "ORDER BY u.lastname, u.firstname";
00841 
00842         $params = array_merge($searchparams, $roleparams, $enrolparams);
00843         $params['courseid'] = $this->courseid;
00844         $params['groupid']  = $this->groupid;
00845 
00846         if (!$this->is_validating()) {
00847             $potentialmemberscount = $DB->count_records_sql("SELECT COUNT(DISTINCT u.id) $sql", $params);
00848             if ($potentialmemberscount > group_non_members_selector::MAX_USERS_PER_PAGE) {
00849                 return $this->too_many_results($search, $potentialmemberscount);
00850             }
00851         }
00852 
00853         $rs = $DB->get_recordset_sql("$fields $sql $orderby", $params);
00854         $roles =  groups_calculate_role_people($rs, $context);
00855 
00856         //don't hold onto user IDs if we're doing validation
00857         if (empty($this->validatinguserids) ) {
00858             if($roles) {
00859                 foreach($roles as $k=>$v) {
00860                     if($v) {
00861                         foreach($v->users as $uid=>$userobject) {
00862                             $this->potentialmembersids[] = $uid;
00863                         }
00864                     }
00865                 }
00866             }
00867         }
00868 
00869         return $this->convert_array_format($roles, $search);
00870     }
00871 }
 All Data Structures Namespaces Files Functions Variables Enumerations