|
Moodle
2.2.1
http://www.collinsharper.com
|
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"> </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 }