diff --git a/admin/roles/assign.html b/admin/roles/assign.html deleted file mode 100755 index 099bb2dad4d..00000000000 --- a/admin/roles/assign.html +++ /dev/null @@ -1,111 +0,0 @@ -
-
- - - - - - - - - - - - - - -
- -
-
-
- - -

-
- -

-
- -
- -
- - - - '."\n"; - } - ?> -
-
-
- - diff --git a/admin/roles/assign.php b/admin/roles/assign.php index 8c78c30a9c9..a072fbf1ad6 100755 --- a/admin/roles/assign.php +++ b/admin/roles/assign.php @@ -1,28 +1,25 @@ dirroot.'/mod/forum/lib.php'); + require_once(dirname(__FILE__) . '/../../config.php'); require_once($CFG->libdir.'/adminlib.php'); + require_once($CFG->dirroot.'/user/selector/lib.php'); + require_js(array('yui_yahoo', 'yui_dom', 'yui_event')); + require_js($CFG->admin . '/roles/roles.js'); - define("MAX_USERS_PER_PAGE", 5000); define("MAX_USERS_TO_LIST_PER_ROLE", 10); - $contextid = required_param('contextid',PARAM_INT); // context id - $roleid = optional_param('roleid', 0, PARAM_INT); // required role id - $add = optional_param('add', 0, PARAM_BOOL); - $remove = optional_param('remove', 0, PARAM_BOOL); - $showall = optional_param('showall', 0, PARAM_BOOL); - $searchtext = optional_param('searchtext', '', PARAM_RAW); // search string + $contextid = required_param('contextid',PARAM_INT); + $roleid = optional_param('roleid', 0, PARAM_INT); + $userid = optional_param('userid', 0, PARAM_INT); // needed for user tabs + $courseid = optional_param('courseid', 0, PARAM_INT); // needed for user tabs $hidden = optional_param('hidden', 0, PARAM_BOOL); // whether this assignment is hidden $extendperiod = optional_param('extendperiod', 0, PARAM_INT); $extendbase = optional_param('extendbase', 0, PARAM_INT); - $userid = optional_param('userid', 0, PARAM_INT); // needed for user tabs - $courseid = optional_param('courseid', 0, PARAM_INT); // needed for user tabs $errors = array(); - $baseurl = $CFG->wwwroot . '/' . $CFG->admin . '/role/assign.php?contextid=' . $contextid; + $baseurl = $CFG->wwwroot . '/' . $CFG->admin . '/roles/assign.php?contextid=' . $contextid; if (!empty($userid)) { $baseurl .= '&userid='.$userid; } @@ -55,17 +52,23 @@ $course = clone($SITE); } +/// Check login and permissions. require_login($course); - require_capability('moodle/role:assign', $context); -/// needed for tabs.php - +/// These are needed early because of tabs.php $overridableroles = get_overridable_roles($context, ROLENAME_BOTH); list($assignableroles, $assigncounts, $nameswithcounts) = get_assignable_roles($context, ROLENAME_BOTH, true); -/// Get some language strings +/// Make sure this user can assign this role + if ($roleid && !isset($assignableroles[$roleid])) { + $a = stdClass; + $a->role = $roleid; + $a->context = $contextname; + print_error('cannotassignrolehere', '', get_context_url($context), $a); + } +/// Get some language strings $strpotentialusers = get_string('potentialusers', 'role'); $strexistingusers = get_string('existingusers', 'role'); $straction = get_string('assignroles', 'role'); @@ -74,6 +77,7 @@ $strparticipants = get_string('participants'); $strsearchresults = get_string('searchresults'); +/// Build the list of options for the enrolment period dropdown. $unlimitedperiod = get_string('unlimited'); $defaultperiod = $course->enrolperiod; for ($i=1; $i<=365; $i++) { @@ -81,6 +85,7 @@ $periodmenu[$seconds] = get_string('numdays', '', $i); } +/// Build the list of options for the starting from dropdown. $timeformat = get_string('strftimedate'); $today = time(); $today = make_timestamp(date('Y', $today), date('m', $today), date('d', $today), 0, 0, 0); @@ -101,23 +106,11 @@ } } -/// Make sure this user can assign this role - - if ($roleid) { - if (!isset($assignableroles[$roleid])) { - error ('you can not override this role in this context'); - } - } - - if ($userid) { +/// Print the header and tabs + if ($context->contextlevel == CONTEXT_USER) { $user = $DB->get_record('user', array('id'=>$userid)); $fullname = fullname($user, has_capability('moodle/site:viewfullnames', $context)); - } - -/// Print the header and tabs - - if ($context->contextlevel == CONTEXT_USER) { /// course header $navlinks = array(); if ($courseid != SITEID) { @@ -141,243 +134,179 @@ $showroles = 1; $currenttab = 'assign'; include_once($CFG->dirroot.'/user/tabs.php'); + } else if ($context->contextlevel == CONTEXT_SYSTEM) { admin_externalpage_setup('assignroles'); admin_externalpage_print_header(); - } else if ($context->contextlevel==CONTEXT_COURSE and $context->instanceid == SITEID) { + + } else if ($context->contextlevel == CONTEXT_COURSE and $context->instanceid == SITEID) { admin_externalpage_setup('frontpageroles'); admin_externalpage_print_header(); $currenttab = 'assign'; include_once('tabs.php'); + } else { $currenttab = 'assign'; include_once('tabs.php'); } - -/// Process incoming role assignment - - if ($frm = data_submitted()) { - - if ($add and !empty($frm->addselect) and confirm_sesskey()) { - - foreach ($frm->addselect as $adduser) { - if (!$adduser = clean_param($adduser, PARAM_INT)) { - continue; - } - $allow = true; - if ($inmeta) { - if (has_capability('moodle/course:managemetacourse', $context, $adduser)) { - //ok - } else { - $managerroles = get_roles_with_capability('moodle/course:managemetacourse', CAP_ALLOW, $context); - if (!empty($managerroles) and !array_key_exists($roleid, $managerroles)) { - $erruser = $DB->get_record('user', array('id'=>$adduser), 'id, firstname, lastname'); - $errors[] = get_string('metaassignerror', 'role', fullname($erruser)); - $allow = false; - } - } - } - if ($allow) { - switch($extendbase) { - case 0: - $timestart = $course->startdate; - break; - case 3: - $timestart = $today; - break; - case 4: - $timestart = $course->enrolstartdate; - break; - case 5: - $timestart = $course->enrolenddate; - break; - } - - if($extendperiod > 0) { - $timeend = $timestart + $extendperiod; - } else { - $timeend = 0; - } - if (! role_assign($roleid, $adduser, 0, $context->id, $timestart, $timeend, $hidden)) { - $errors[] = "Could not add user with id $adduser to this role!"; - } - } - } - - $rolename = $DB->get_field('role', 'name', array('id'=>$roleid)); - add_to_log($course->id, 'role', 'assign', 'admin/roles/assign.php?contextid='.$context->id.'&roleid='.$roleid, $rolename, '', $USER->id); - } else if ($remove and !empty($frm->removeselect) and confirm_sesskey()) { - - $sitecontext = get_context_instance(CONTEXT_SYSTEM); - $topleveladmin = false; - - // we only worry about this if the role has doanything capability at site level - if ($context->id == $sitecontext->id && $adminroles = get_roles_with_capability('moodle/site:doanything', CAP_ALLOW, $sitecontext)) { - foreach ($adminroles as $adminrole) { - if ($adminrole->id == $roleid) { - $topleveladmin = true; - } - } - } - - foreach ($frm->removeselect as $removeuser) { - $removeuser = clean_param($removeuser, PARAM_INT); - - if ($topleveladmin && ($removeuser == $USER->id)) { // Prevent unassigning oneself from being admin - continue; - } - - if (! role_unassign($roleid, $removeuser, 0, $context->id)) { - $errors[] = "Could not remove user with id $removeuser from this role!"; - } else if ($inmeta) { - sync_metacourse($courseid); - $newroles = get_user_roles($context, $removeuser, false); - if (!empty($newroles) and !array_key_exists($roleid, $newroles)) { - $erruser = $DB->get_record('user', array('id'=>$removeuser), 'id, firstname, lastname'); - $errors[] = get_string('metaunassignerror', 'role', fullname($erruser)); - $allow = false; - } - } - } - - $rolename = $DB->get_field('role', 'name', array('id'=>$roleid)); - add_to_log($course->id, 'role', 'unassign', 'admin/roles/assign.php?contextid='.$context->id.'&roleid='.$roleid, $rolename, '', $USER->id); - } else if ($showall) { - $searchtext = ''; - } - } - +/// Print heading. if ($isfrontpage) { print_heading_with_help(get_string('frontpageroles', 'admin'), 'assignroles'); } else { print_heading_with_help(get_string('assignrolesin', 'role', $contextname), 'assignroles'); } - if ($context->contextlevel==CONTEXT_SYSTEM) { +/// Print a warning if we are assigning system roles. + if ($context->contextlevel == CONTEXT_SYSTEM) { print_box(get_string('globalroleswarning', 'role')); } - if ($roleid) { + if ($roleid) { /// UI for assigning a particular role. - /// Get all existing participants in this context. - // Why is this not done with get_users??? - - if (!$contextusers = get_role_users($roleid, $context, false, 'u.id, u.firstname, u.lastname, u.email, ra.hidden')) { - $contextusers = array(); + /// Create the user selector objects. + $options = array('context' => $context, 'roleid' => $roleid); + if ($context->contextlevel > CONTEXT_COURSE) { + $potentialuserselector = new potential_assignees_below_course('addselect', $options); + } else { + $potentialuserselector = new potential_assignees_course_and_above('addselect', $options); + } + if ($context->contextlevel == CONTEXT_SYSTEM && is_admin_role($roleid)) { + $currentuserselector = new existing_role_holders_site_admin('removeselect', $options); + } else { + $currentuserselector = new existing_role_holders('removeselect', $options); } - $select = "username <> 'guest' AND deleted = 0 AND confirmed = 1"; - $params = array(); + /// Process incoming role assignments + if (optional_param('add', false, PARAM_BOOL) && confirm_sesskey()) { + $userstoassign = $potentialuserselector->get_selected_users(); + if (!empty($userstoassign)) { - $usercount = $DB->count_records_select('user', $select, $params) - count($contextusers); - - $searchtext = trim($searchtext); - - if ($searchtext !== '') { // Search for a subset of remaining users - $LIKE = $DB->sql_ilike(); - $FULLNAME = $DB->sql_fullname(); - - $select .= " AND ($FULLNAME $LIKE :search1 OR email $LIKE :search2) "; - $params['search1'] = "%$searchtext%"; - $params['search2'] = "%$searchtext%"; - } - - if ($context->contextlevel > CONTEXT_COURSE) { // mod or block (or group?) - - /************************************************************************ - * * - * context level is above or equal course context level * - * in this case we pull out all users matching search criteria (if any) * - * * - * MDL-11324 * - * a mini get_users_by_capability() call here, this is done instead of * - * get_users_by_capability() because * - * 1) get_users_by_capability() does not deal with searching by name * - * 2) exceptions array can be potentially large for large courses * - * 3) $DB->get_recordset_sql() is more efficient * - * * - ************************************************************************/ - - if ($possibleroles = get_roles_with_capability('moodle/course:view', CAP_ALLOW, $context)) { - - $doanythingroles = get_roles_with_capability('moodle/site:doanything', CAP_ALLOW, get_context_instance(CONTEXT_SYSTEM)); - - $validroleids = array(); - foreach ($possibleroles as $possiblerole) { - if (isset($doanythingroles[$possiblerole->id])) { // We don't want these included - continue; + foreach ($userstoassign as $adduser) { + $allow = true; + if ($inmeta) { + if (has_capability('moodle/course:managemetacourse', $context, $adduser->id)) { + //ok + } else { + $managerroles = get_roles_with_capability('moodle/course:managemetacourse', CAP_ALLOW, $context); + if (!empty($managerroles) and !array_key_exists($roleid, $managerroles)) { + $erruser = $DB->get_record('user', array('id'=>$adduser->id), 'id, firstname, lastname'); + $errors[] = get_string('metaassignerror', 'role', fullname($erruser)); + $allow = false; + } + } } - if ($caps = role_context_capabilities($possiblerole->id, $context, 'moodle/course:view')) { // resolved list - if (isset($caps['moodle/course:view']) && $caps['moodle/course:view'] > 0) { // resolved capability > 0 - $validroleids[] = $possiblerole->id; + + if ($allow) { + switch($extendbase) { + case 0: + $timestart = $course->startdate; + break; + case 3: + $timestart = $today; + break; + case 4: + $timestart = $course->enrolstartdate; + break; + case 5: + $timestart = $course->enrolenddate; + break; + } + + if($extendperiod > 0) { + $timeend = $timestart + $extendperiod; + } else { + $timeend = 0; + } + if (! role_assign($roleid, $adduser->id, 0, $context->id, $timestart, $timeend, $hidden)) { + $a = new stdClass; + $a->role = $rolenames[$roleid]; + $a->user = fullname($adduser); + $errors[] = get_string('assignerror', 'role', $a); } } } - if ($validroleids) { - $roleids = '('.implode(',', $validroleids).')'; + $potentialuserselector->invalidate_selected_users(); + $currentuserselector->invalidate_selected_users(); - $fields = "SELECT u.id, u.firstname, u.lastname, u.email"; - $countfields = "SELECT COUNT('x')"; - - $sql = " FROM {user} u - JOIN {role_assignments} ra ON ra.userid = u.id - JOIN {role} r ON r.id = ra.roleid - WHERE ra.contextid ".get_related_contexts_string($context)." - AND $select AND ra.roleid in $roleids - AND u.id NOT IN ( - SELECT u.id - FROM {role_assignments} r, {user} u - WHERE r.contextid = :contextid - AND u.id = r.userid - AND r.roleid = :roleid)"; - $params['contextid'] = $contextid; - $params['roleid'] = $roleid; - - $availableusers = $DB->get_recordset_sql("$fields $sql", $params); - $usercount = $DB->count_records_sql("$countfields $sql", $params); - - } else { - $availableusers = array(); - $usercount = 0; - } + $rolename = $DB->get_field('role', 'name', array('id'=>$roleid)); + add_to_log($course->id, 'role', 'assign', 'admin/roles/assign.php?contextid='.$context->id.'&roleid='.$roleid, $rolename, '', $USER->id); } - - } else { - - /************************************************************************ - * * - * context level is above or equal course context level * - * in this case we pull out all users matching search criteria (if any) * - * * - ************************************************************************/ - - /// MDL-11111 do not include user already assigned this role in this context as available users - /// so that the number of available users is right and we save time looping later - $fields = "SELECT id, firstname, lastname, email"; - $countfields = "SELECT COUNT('x')"; - - $sql = " FROM {user} - WHERE $select - AND id NOT IN ( - SELECT u.id - FROM {role_assignments} r, {user} u - WHERE r.contextid = :contextid - AND u.id = r.userid - AND r.roleid = :roleid)"; - $order = "ORDER BY lastname ASC, firstname ASC"; - - $params['contextid'] = $contextid; - $params['roleid'] = $roleid; - - $availableusers = $DB->get_recordset_sql("$fields $sql $order", $params); - $usercount = $DB->count_records_sql("$countfields $sql", $params); } - print_simple_box_start('center'); - include('assign.html'); - print_simple_box_end(); + /// Process incoming role unassignments + if (optional_param('remove', false, PARAM_BOOL) && confirm_sesskey()) { + $userstounassign = $currentuserselector->get_selected_users(); + if (!empty($userstounassign)) { + + foreach ($userstounassign as $removeuser) { + if (! role_unassign($roleid, $removeuser->id, 0, $context->id)) { + $a = new stdClass; + $a->role = $rolenames[$roleid]; + $a->user = fullname($removeuser); + $errors[] = get_string('unassignerror', 'role', $a); + } else if ($inmeta) { + sync_metacourse($courseid); + $newroles = get_user_roles($context, $removeuser->id, false); + if (empty($newroles) || array_key_exists($roleid, $newroles)) { + $errors[] = get_string('metaunassignerror', 'role', fullname($removeuser)); + } + } + } + + $potentialuserselector->invalidate_selected_users(); + $currentuserselector->invalidate_selected_users(); + + $rolename = $DB->get_field('role', 'name', array('id'=>$roleid)); + add_to_log($course->id, 'role', 'unassign', 'admin/roles/assign.php?contextid='.$context->id.'&roleid='.$roleid, $rolename, '', $USER->id); + } + } + + /// Print the form. + check_theme_arrows(); +?> +
+ + + + + + + + +
+

+ display() ?> +
+
+
+ +

/> +

+ +


+

+ +


+

+
+ +
+ +
+
+

+ display() ?> +
+
+ +'; @@ -408,7 +337,7 @@ } // Get the names of role holders for roles with between 1 and MAX_USERS_TO_LIST_PER_ROLE users, - // and so determine whether to show the extra column. + // and so determine whether to show the extra column. $rolehodlernames = array(); $strmorethanmax = get_string('morethan', 'role', MAX_USERS_TO_LIST_PER_ROLE); $showroleholders = false; diff --git a/admin/roles/roles.js b/admin/roles/roles.js index 2f85336f0e2..c1588116c97 100644 --- a/admin/roles/roles.js +++ b/admin/roles/roles.js @@ -111,3 +111,17 @@ cap_table_filter = { } } }; + +function init_add_assign_page() { + var addselect = user_selector.get('addselect'); + document.getElementById('add').disabled = addselect.is_selection_empty(); + addselect.subscribe('selectionchanged', function(isempty) { + document.getElementById('add').disabled = isempty; + }); + + var removeselect = user_selector.get('removeselect'); + document.getElementById('remove').disabled = removeselect.is_selection_empty(); + removeselect.subscribe('selectionchanged', function(isempty) { + document.getElementById('remove').disabled = isempty; + }); +} \ No newline at end of file diff --git a/group/members.php b/group/members.php index 14f5daa4df6..daba4070b61 100644 --- a/group/members.php +++ b/group/members.php @@ -94,7 +94,7 @@ check_theme_arrows(); - -
+

@@ -106,7 +106,7 @@ check_theme_arrows();

+

diff --git a/lang/en_utf8/error.php b/lang/en_utf8/error.php index dbaf14e9335..cf47e555cf4 100644 --- a/lang/en_utf8/error.php +++ b/lang/en_utf8/error.php @@ -22,6 +22,7 @@ $string['cannotaddrss'] = 'You do not have permission to add rss feeds'; $string['cannotaddmodule'] = '$a module could not be added to the module list!'; $string['cannotaddnewmodule'] = 'Could not add a new module of $a'; $string['cannotaddnewinstance'] = 'Could not add a new instance of $a'; +$string['cannotassignrolehere'] = 'You are not allowed to assign this role (id = $a->roleid) in this context ($a->context)'; $string['cannotsaveconfig'] = 'Problem saving config \"$a->name\" as \"$a->value\" for plugin \"$a->plugin\"'; $string['cannotsavecomment'] = 'Cannot save comment'; $string['cannotsavefile'] = 'Cannot save the file \"$a\"!'; diff --git a/lang/en_utf8/role.php b/lang/en_utf8/role.php index 15747e54284..b6177efd22f 100644 --- a/lang/en_utf8/role.php +++ b/lang/en_utf8/role.php @@ -8,6 +8,7 @@ $string['allowassign'] = 'Allow role assignments'; $string['allowoverride'] = 'Allow role overrides'; $string['allsiteusers'] = 'All site users'; $string['assignanotherrole'] = 'Assign another role'; +$string['assignerror'] = 'Error while assigning the role $a->role to user $a->user.'; $string['assignroles'] = 'Assign roles'; $string['assignrolesin'] = 'Assign roles in $a'; $string['assignglobalroles'] = 'Assign system roles'; @@ -55,6 +56,7 @@ $string['course:viewhiddenuserfields'] = 'View hidden user fields'; $string['course:viewparticipants'] = 'View participants'; $string['course:viewscales'] = 'View scales'; $string['course:visibility'] = 'Hide/show courses'; +$string['createhiddenassign'] = 'Create hidden role assignments'; $string['deletecourseoverrides'] = 'Delete all overrides in course'; $string['deletelocalroles'] = 'Delete all local role assignments'; $string['grade:edit'] = 'Edit grades'; @@ -87,8 +89,11 @@ $string['explainpermissions'] = 'Explain permissions'; $string['explainpermissionsdetails'] = 'For capability $a->capability in context $a->context when logged in as $a->fullname.'; $string['explainpermissionsinfo'] = '

To use this table:

  1. First look to see if there are any Prohibits. If there are, has_capability will return false.
  2. Otherwise, read across the rows, left-to-right, top-to-bottom, and find the first cell where the number of Prevents and Allows are different. If there are more Allows than Prevents in that cell, then has_capability will return true, otherwise it will return false.
  3. If no cell has different numbers of Prevents and Allows, then has_capability will return false.
'; $string['explainpermissionsdoanything'] = 'Note that this user has the moodle/site:doanything capability, so even though the table above shows that has_capability will return false, this user will actually be deemed to have the capability $a in most circumstances.'; +$string['extusers'] = 'Existing users'; +$string['extusersmatching'] = 'Existing users matching \'$a\''; $string['globalrole'] = 'System role'; $string['globalroleswarning'] = 'WARNING! Any roles you assign from this page will apply to the assigned users throughout the entire system, including the front page and all the courses.'; +$string['hidden'] = 'Hidden'; $string['inherit'] = 'Inherit'; $string['legacy:admin'] = 'LEGACY ROLE: Administrator'; $string['legacy:coursecreator'] = 'LEGACY ROLE: Course Creator'; @@ -116,6 +121,8 @@ $string['overriderolesin'] = 'Override roles in $a'; $string['overrides'] = 'Overrides'; $string['permissions'] = 'Permissions'; $string['potentialusers'] = '$a potential users'; +$string['potusers'] = 'Potential users'; +$string['potusersmatching'] = 'Potential users matching \'$a\''; $string['portfolio:export'] = 'Export to portfolios'; $string['prevent'] = 'Prevent'; $string['prohibit'] = 'Prohibit'; @@ -172,6 +179,7 @@ $string['tag:manage'] = 'Manage all tags'; $string['tag:create'] = 'Create new tags'; $string['tag:edit'] = 'Edit existing tags'; $string['tag:editblocks'] = 'Edit blocks in tags pages'; +$string['unassignerror'] = 'Error while unassigning the role $a->role from user $a->user.'; $string['user:changeownpassword'] = 'Change own password'; $string['user:create'] = 'Create users'; $string['user:delete'] = 'Delete users'; diff --git a/lib/accesslib.php b/lib/accesslib.php index 0122461bb41..05647862688 100755 --- a/lib/accesslib.php +++ b/lib/accesslib.php @@ -525,6 +525,26 @@ function is_siteadmin($userid) { return $DB->record_exists_sql($sql, $params); } +/** + * @param integer $roleid a role id. + * @return boolean, whether this role is an admin role. + */ +function is_admin_role($roleid) { + global $CFG, $DB; + + $sql = "SELECT 1 + FROM {role_capabilities} rc + JOIN {context} ctx ON ctx.id = rc.contextid + WHERE ctx.contextlevel = 10 + AND rc.roleid = ? + AND rc.capability IN (?, ?, ?) + GROUP BY rc.capability + HAVING SUM(rc.permission) > 0"; + $params = array($roleid, 'moodle/site:config', 'moodle/legacy:admin', 'moodle/site:doanything'); + + return $DB->record_exists_sql($sql, $params); +} + function get_course_from_path ($path) { // assume that nothing is more than 1 course deep if (preg_match('!^(/.+)/\d+$!', $path, $matches)) { @@ -4886,7 +4906,9 @@ function sort_by_roleassignment_authority($users, $context, $roles=array(), $sor * @param bool gethidden - whether to fetch hidden enrolments too * @return array() */ -function get_role_users($roleid, $context, $parent=false, $fields='', $sort='u.lastname ASC', $gethidden=true, $group='', $limitfrom='', $limitnum='') { +function get_role_users($roleid, $context, $parent=false, $fields='', + $sort='u.lastname, u.firstname', $gethidden=true, $group='', + $limitfrom='', $limitnum='', $extrawheretest='', $whereparams=array()) { global $DB; if (empty($fields)) { @@ -4927,6 +4949,11 @@ function get_role_users($roleid, $context, $parent=false, $fields='', $sort='u.l array_unshift($params, $context->id); + if ($extrawheretest) { + $extrawheretest = ' AND ' . $extrawheretest; + $params = array_merge($params, $whereparams); + } + $sql = "SELECT $fields, ra.roleid FROM {role_assignments} ra JOIN {user} u ON u.id = ra.userid @@ -4936,6 +4963,7 @@ function get_role_users($roleid, $context, $parent=false, $fields='', $sort='u.l $roleselect $groupselect $hiddensql + $extrawheretest ORDER BY $sort"; // join now so that we can just use fullname() later return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum); diff --git a/theme/standard/styles_layout.css b/theme/standard/styles_layout.css index 1229060051e..4d084c36a37 100644 --- a/theme/standard/styles_layout.css +++ b/theme/standard/styles_layout.css @@ -436,37 +436,40 @@ form.popupform label { .groupmanagementtable { width: 90%; } - .groupmanagementtable td { vertical-align: middle; } - .groupmanagementtable p { - text-align: center; + text-align: left; + margin-bottom: 0.2em; } - -.groupmanagementtable #memberscell, -.groupmanagementtable #nonmemberscell { +.groupmanagementtable #existingcell, +.groupmanagementtable #potentialcell { width: 42%; } -.groupmanagementtable #memberscell label, -.groupmanagementtable #nonmemberscell label { +.groupmanagementtable #existingcell label, +.groupmanagementtable #potentialcell label { font-weight: bold; } .groupmanagementtable #buttonscell { width: 16%; } +.groupmanagementtable #buttonscell p { + text-align: center; +} .groupmanagementtable #buttonscell input { width: 80%; padding: 1em 0; - margin: 2em 0; +} +.groupmanagementtable #buttonscell #remove { + margin: 7em 0; } .groupmanagementtable #backcell { padding-top: 2em; text-align: center; } -#removeselect_wrapper, -#addselect_wrapper { +.groupmanagementtable #removeselect_wrapper, +.groupmanagementtable #addselect_wrapper { width: 100%; } .groupmanagementtable #removeselect_wrapper label, @@ -549,6 +552,9 @@ div.hide { .userselector select { width: 100%; } +.userselector div { + margin-top: 0.2em; +} .userselector div label { margin-right: 0.3em; } @@ -1027,11 +1033,47 @@ body#admin-modules table.generaltable td.c0 padding: 5px; } -#admin-roles-manage .selector, -#admin-roles-assign .selector, -#admin-roles-override .selector { - text-align:center; - margin-bottom:1em; +.roleassigntable { + width: 100%; +} +.roleassigntable td { + vertical-align: middle; + padding: 0 0.3em 1em; +} +.roleassigntable p { + text-align: left; + margin-bottom: 0.2em; +} +.roleassigntable #existingcell, +.roleassigntable #potentialcell { + width: 38%; +} +.roleassigntable #existingcell label, +.roleassigntable #potentialcell label { + font-weight: bold; +} +.roleassigntable #buttonscell { + width: 24%; +} +.roleassigntable #buttonscell #add, +.roleassigntable #buttonscell #remove { + width: 100%; + margin: 0.3em 0; + padding: 0.5em 0; +} +.roleassigntable #buttonscell p { + margin: 0.3em 0; +} +.roleassigntable #buttonscell #remove { + margin-top: 5em; +} +.roleassigntable #removeselect_wrapper, +.roleassigntable #addselect_wrapper { + width: 100%; +} +.roleassigntable #removeselect_wrapper label, +.roleassigntable #addselect_wrapper label { + font-weight: normal; } #admin-roles-manage table.roledesc, diff --git a/user/selector/lib.php b/user/selector/lib.php index 84a43b7f55f..2a6cd8014e0 100644 --- a/user/selector/lib.php +++ b/user/selector/lib.php @@ -71,10 +71,12 @@ abstract class user_selector_base { public function __construct($name, $options = array()) { global $CFG; $this->name = $name; - if (empty($CFG->extrauserselectorfields)) { - $this->extrafields = array(); - } else { + if (isset($options['extrafields'])) { + $this->extrafields = $options['extrafields']; + } else if (!empty($CFG->extrauserselectorfields)) { $this->extrafields = explode(',', $CFG->extrauserselectorfields); + } else { + $this->extrafields = array(); } if (isset($options['exclude']) && is_array($options['exclude'])) { $this->exclude = $options['exclude']; @@ -107,7 +109,7 @@ abstract class user_selector_base { } /** - * @return array the userids that were selected. This is a more sophisticated version + * @return array of user objects. The users that were selected. This is a more sophisticated version * of optional_param($this->name, array(), PARAM_INTEGER) that validates the * returned list of ids against the rules for this user selector. */ @@ -233,7 +235,9 @@ abstract class user_selector_base { * array should be the string names of optgroups. The keys of the inner * arrays should be userids, and the values should be user objects * containing at least the list of fields returned by the method - * required_fields_sql(). + * required_fields_sql(). If a user object has a ->disabled property + * that is true, then that option will be displayed greyed out, and + * will not be returned by get_selected_users. */ public abstract function find_users($search); @@ -245,6 +249,7 @@ abstract class user_selector_base { 'class' => get_class($this), 'name' => $this->name, 'exclude' => $this->exclude, + 'extrafields' => $this->extrafields ); } @@ -274,7 +279,11 @@ abstract class user_selector_base { // Aggregate the resulting list back into a single one. $users = array(); foreach ($groupedusers as $group) { - $users += $group; + foreach ($group as $user) { + if (!isset($users[$user->id]) && empty($user->disabled)) { + $users[$user->id] = $user; + } + } } // If we are only supposed to be selecting a single user, make sure we do. @@ -317,14 +326,18 @@ abstract class user_selector_base { $params = array(); $tests = array(); + if ($u) { + $u .= '.'; + } + // If we have a $search string, put a field LIKE '$search%' condition on each field. if ($search) { $conditions = array( - $DB->sql_fullname($u . '.firstname', $u . '.lastname'), - $conditions[] = $u . '.lastname' + $DB->sql_fullname($u . 'firstname', $u . 'lastname'), + $conditions[] = $u . 'lastname' ); foreach ($this->extrafields as $field) { - $conditions[] = $u . '.' . $field; + $conditions[] = $u . $field; } $ilike = ' ' . $DB->sql_ilike() . ' ?'; foreach ($conditions as &$condition) { @@ -334,17 +347,22 @@ abstract class user_selector_base { $tests[] = '(' . implode(' OR ', $conditions) . ')'; } + // Add some additional sensible conditions + $tests[] = $u . "username <> 'guest'"; + $tests[] = $u . 'deleted = 0'; + $tests[] = $u . 'confirmed = 1'; + // If we are being asked to exclude any users, do that. if (!empty($this->exclude)) { list($usertest, $userparams) = $DB->get_in_or_equal($this->exclude, SQL_PARAMS_QM, '', false); - $tests[] = $u . '.id ' . $usertest; + $tests[] = $u . 'id ' . $usertest; $params = array_merge($params, $userparams); } // If we are validating a set list of userids, add an id IN (...) test. if (!empty($this->validatinguserids)) { list($usertest, $userparams) = $DB->get_in_or_equal($this->validatinguserids); - $tests[] = $u . '.id ' . $usertest; + $tests[] = $u . 'id ' . $usertest; $params = array_merge($params, $userparams); } @@ -411,13 +429,14 @@ abstract class user_selector_base { if (!empty($users)) { $output = ' ' . "\n"; foreach ($users as $user) { - if ($select || isset($this->selected[$user->id])) { - $selectattr = ' selected="selected"'; - } else { - $selectattr = ''; + $attributes = ''; + if (!empty($user->disabled)) { + $attributes .= ' disabled="disabled"'; + } else if ($select || isset($this->selected[$user->id])) { + $attributes .= ' selected="selected"'; } unset($this->selected[$user->id]); - $output .= ' ' . + $output .= ' ' . $this->output_user($user) . "\n"; } } else { @@ -434,7 +453,7 @@ abstract class user_selector_base { * @param object $user the user to display. * @return string a string representation of the user. */ - protected function output_user($user) { + public function output_user($user) { $bits = array( fullname($user) ); @@ -477,13 +496,183 @@ abstract class user_selector_base { } } +// User selectors for managing role assignments ================================ + /** - * User selector subclass for the list of potential users on the assign roles page. - * + * Base class to avoid duplicating code. */ -class role_assign_potential_user_selector extends user_selector_base { +abstract class role_assign_user_selector_base extends user_selector_base { + const MAX_USERS_PER_PAGE = 100; + + protected $roleid; + protected $context; + + /** + * @param string $name control name + * @param array $options should have two elements with keys groupid and courseid. + */ + public function __construct($name, $options) { + global $CFG; + parent::__construct($name, $options); + $this->roleid = $options['roleid']; + if (isset($options['context'])) { + $this->context = $options['context']; + } else { + $this->context = get_context_instance_by_id($options['contextid']); + } + require_once($CFG->dirroot . '/group/lib.php'); + } + + protected function get_options() { + $options = parent::get_options(); + $options['roleid'] = $this->roleid; + $options['contextid'] = $this->context->id; + return $options; + } +} + +/** + * User selector subclass for the list of potential users on the assign roles page, + * when we are assigning in a context below the course level. (CONTEXT_MODULE and + * CONTEXT_BLOCK). + * + * In this case we replicate part of get_users_by_capability() get the users + * with moodle/course:view (or moodle/site:doanything). We can't use + * get_users_by_capability() becuase + * 1) get_users_by_capability() does not deal with searching by name + * 2) exceptions array can be potentially large for large courses + */ +class potential_assignees_below_course extends role_assign_user_selector_base { public function find_users($search) { - return array(); // TODO + global $DB; + + // Get roles with some assignement to the 'moodle/course:view' capability. + $possibleroles = get_roles_with_capability('moodle/course:view', CAP_ALLOW, $this->context); + if (empty($possibleroles)) { + // If there aren't any, we are done. + return array(); + } + + // Now exclude the admin roles, and check the actual permission on + // 'moodle/course:view' to make sure it is allow. + $doanythingroles = get_roles_with_capability('moodle/site:doanything', + CAP_ALLOW, get_context_instance(CONTEXT_SYSTEM)); + $validroleids = array(); + + foreach ($possibleroles as $possiblerole) { + if (isset($doanythingroles[$possiblerole->id])) { + continue; + } + + if ($caps = role_context_capabilities($possiblerole->id, $this->context, 'moodle/course:view')) { // resolved list + if (isset($caps['moodle/course:view']) && $caps['moodle/course:view'] > 0) { // resolved capability > 0 + $validroleids[] = $possiblerole->id; + } + } + } + + // If there are no valid roles, we are done. + if (!$validroleids) { + return array(); + } + + // Now we have to go to the database. + list($wherecondition, $params) = $this->search_sql($search, 'u'); + if ($wherecondition) { + $wherecondition = ' AND ' . $wherecondition; + } + $roleids = '('.implode(',', $validroleids).')'; + + $fields = 'SELECT ' . $this->required_fields_sql('u'); + $countfields = 'SELECT COUNT(1)'; + + $sql = " FROM {user} u + JOIN {role_assignments} ra ON ra.userid = u.id + JOIN {role} r ON r.id = ra.roleid + WHERE ra.contextid " . get_related_contexts_string($this->context)." + $wherecondition + AND ra.roleid IN $roleids + AND u.id NOT IN ( + SELECT u.id + FROM {role_assignments} r, {user} u + WHERE r.contextid = ? + AND u.id = r.userid + AND r.roleid = ?)"; + $order = ' ORDER BY lastname ASC, firstname ASC'; + + $params[] = $this->context->id; + $params[] = $this->roleid; + + // Check to see if there are too many to show sensibly. + $potentialmemberscount = $DB->count_records_sql($countfields . $sql, $params); + if ($potentialmemberscount > role_assign_user_selector_base::MAX_USERS_PER_PAGE) { + return array(get_string('toomanytoshow') => array(), + get_string('trysearching') => array()); + } + + // If not, show them. + $availableusers = $DB->get_records_sql($fields . $sql . $order, $params); + + if (empty($availableusers)) { + return array(); + } + + if ($search) { + $groupname = get_string('potusersmatching', 'role', $search); + } else { + $groupname = get_string('potusers', 'role'); + } + + return array($groupname => $availableusers); + } +} + +/** + * User selector subclass for the list of potential users on the assign roles page, + * when we are assigning in a context at or above the course level. In this case we + * show all the users in the system who do not already have the role. + */ +class potential_assignees_course_and_above extends role_assign_user_selector_base { + public function find_users($search) { + global $DB; + + list($wherecondition, $params) = $this->search_sql($search, ''); + + $fields = 'SELECT ' . $this->required_fields_sql(''); + $countfields = 'SELECT COUNT(1)'; + + $sql = " FROM {user} + WHERE $wherecondition + AND id NOT IN ( + SELECT u.id + FROM {role_assignments} r, {user} u + WHERE r.contextid = ? + AND u.id = r.userid + AND r.roleid = ?)"; + $order = ' ORDER BY lastname ASC, firstname ASC'; + + $params[] = $this->context->id; + $params[] = $this->roleid; + + $potentialmemberscount = $DB->count_records_sql($countfields . $sql, $params); + if ($potentialmemberscount > role_assign_user_selector_base::MAX_USERS_PER_PAGE) { + return array(get_string('toomanytoshow') => array(), + get_string('trysearching') => array()); + } + + $availableusers = $DB->get_records_sql($fields . $sql . $order, $params); + + if (empty($availableusers)) { + return array(); + } + + if ($search) { + $groupname = get_string('potusersmatching', 'role', $search); + } else { + $groupname = get_string('potusers', 'role'); + } + + return array($groupname => $availableusers); } } @@ -491,12 +680,67 @@ class role_assign_potential_user_selector extends user_selector_base { * User selector subclass for the list of users who already have the role in * question on the assign roles page. */ -class role_assign_current_user_selector extends user_selector_base { +class existing_role_holders extends role_assign_user_selector_base { + protected $strhidden; + + public function __construct($name, $options) { + parent::__construct($name, $options); + $this->strhidden = get_string('hiddenassign'); + } + public function find_users($search) { - return array(); // TODO + list($wherecondition, $params) = $this->search_sql($search, 'u'); + $contextusers = get_role_users($this->roleid, $this->context, false, + $this->required_fields_sql('u') . ', ra.hidden', 'u.lastname, u.firstname', + true, '', '', '', $wherecondition, $params); + + if (empty($contextusers)) { + return array(); + } + + if ($search) { + $groupname = get_string('extusersmatching', 'role', $search); + } else { + $groupname = get_string('extusers', 'role'); + } + + return array($groupname => $contextusers); + } + + // Override to add (hidden) to hidden role assignments. + public function output_user($user) { + $output = parent::output_user($user); + if ($user->hidden) { + $output .= ' (' . $this->strhidden . ')'; + } + return $output; } } +/** + * A special subclass to use when unassigning admins at site level. Disables + * the option for admins to unassign themselves. + */ +class existing_role_holders_site_admin extends existing_role_holders { + public function find_users($search) { + global $USER; + $groupeduses = parent::find_users($search); + foreach ($groupeduses as $group) { + foreach ($group as &$user) { + if ($user->id == $USER->id) { + $user->disabled = true; + } + } + } + return $groupeduses; + } +} + +// User selectors for managing group memebers ================================== + +/** + * Base class to avoid duplicating code. + */ abstract class groups_user_selector_base extends user_selector_base { protected $groupid; protected $courseid; @@ -521,8 +765,6 @@ abstract class groups_user_selector_base extends user_selector_base { } /** - * Enter description here... - * * @param array $roles array in the format returned by groups_calculate_role_people. * @return array array in the format find_users is supposed to return. */ @@ -571,7 +813,7 @@ class group_members_selector extends groups_user_selector_base { class group_non_members_selector extends groups_user_selector_base { const MAX_USERS_PER_PAGE = 100; - protected function output_user($user) { + public function output_user($user) { return parent::output_user($user) . ' (' . $user->numgroups . ')'; } diff --git a/user/selector/script.js b/user/selector/script.js index 07e33b93b87..c954a197ad1 100644 --- a/user/selector/script.js +++ b/user/selector/script.js @@ -249,7 +249,7 @@ user_selector.prototype.handle_failure = function() { // If we are in developer debug mode, output a link to help debug the failure. if (moodle_cfg.developerdebug) { var link = document.createElement('a'); - link.href = this.searchurl + this.get_search_text(); + link.href = this.searchurl + this.get_search_text() + '&debug=1'; link.appendChild(document.createTextNode('Ajax call failed. Click here to try the search call directly.')) this.searchfield.parentNode.appendChild(link); } @@ -302,7 +302,7 @@ user_selector.prototype.output_options = function(data) { var option = options[0]; if (option.selected) { var optiontext = option.innerText || option.textContent - this.selected[option.value] = { id: option.value, formatted: optiontext }; + this.selected[option.value] = { id: option.value, name: optiontext, disabled: option.disabled }; } optgroup.removeChild(option); } @@ -360,8 +360,10 @@ user_selector.prototype.output_group = function(groupname, users, select) { var user = users[userid]; var option = document.createElement('option'); option.value = user.id; - option.appendChild(document.createTextNode(this.output_user(user))); - if (select || this.selected[user.id]) { + option.appendChild(document.createTextNode(user.name)); + if (user.disabled) { + option.disabled = 'disabled'; + } else if (select || this.selected[user.id]) { option.selected = 'selected'; } delete this.selected[user.id]; @@ -384,22 +386,5 @@ user_selector.prototype.output_group = function(groupname, users, select) { this.listbox.appendChild(optgroup); } -/** - * Convert a user object to a string suitable for displaying as an option in the list box. - * - * @param Object user the user to display. - * @return string a string representation of the user. - */ -user_selector.prototype.output_user = function(user) { - if (user.formatted) { - return user.formatted; - } - var output = user.fullname; - for (var i = 0; i < this.extrafields.length; i++) { - output += ', ' + user[this.extrafields[i]]; - } - return output; -} - // Say that we want to be a source of custom events. YAHOO.lang.augmentProto(user_selector, YAHOO.util.EventProvider); \ No newline at end of file diff --git a/user/selector/search.php b/user/selector/search.php index 3a269727ac3..00912a34c3f 100644 --- a/user/selector/search.php +++ b/user/selector/search.php @@ -33,6 +33,16 @@ require_once(dirname(__FILE__) . '/../../config.php'); require_once($CFG->dirroot . '/user/selector/lib.php'); +// In developer debug mode, when there is a debug=1 in the URL send as plain text +// for easier debugging. +if (debugging('', DEBUG_DEVELOPER) && optional_param('debug', false, PARAM_BOOL)) { + header('Content-type: text/plain; charset=UTF-8'); + $debugmode = true; +} else { + header('Content-type: application/json'); + $debugmode = false; +} + // Check access. if (!isloggedin()) {; print_error('mustbeloggedin'); @@ -50,8 +60,17 @@ if (!isset($USER->userselectors[$selectorhash])) { print_error('unknownuserselector'); } -// Create the appropriate userselector. +// Get the options. $options = $USER->userselectors[$selectorhash]; + +if ($debugmode) { + echo 'Search string: ', $search, "\n"; + echo 'Options: '; + print_r($options); + echo "\n"; +} + +// Create the appropriate userselector. $classname = $options['class']; unset($options['class']); $name = $options['name']; @@ -65,12 +84,16 @@ $userselector = new $classname($name, $options); // Do the search and output the results. $users = $userselector->find_users($search); foreach ($users as &$group) { - foreach ($group as &$user) { - $user->fullname = fullname($user); + foreach ($group as $user) { + $output = new stdClass; + $output->id = $user->id; + $output->name = $userselector->output_user($user); + if (!empty($user->disabled)) { + $output->disabled = true; + } + $group[$user->id] = $output; } } - -header('Content-type: application/json'); echo json_encode(array('results' => $users)); ?> \ No newline at end of file