user selection: MDL-16993 Convert the assign roles page to use the new components.

At the same time, I took the opportunity to try to work on some of the usability issues on this page. Note that I have not quite finished! So don't comment until tomorrow.

In the course of doing this, I also did: half of

MDL-11529 Show the number of assignees of each role in the change role dropdown on this page.
MDL-17067 Make it clear in the UI that admins are not allowed to unassign themselves.
This commit is contained in:
tjhunt 2008-10-30 10:49:15 +00:00
parent 993dcb0524
commit 6cab02ac52
11 changed files with 577 additions and 416 deletions

View File

@ -1,111 +0,0 @@
<form id="assignform" method="post" action="">
<div style="text-align:center;">
<label for="extendperiod"><?php print_string('enrolperiod') ?></label> <?php choose_from_menu($periodmenu, "extendperiod", $defaultperiod, $unlimitedperiod); ?>
<label for="extendbase"><?php print_string('startingfrom') ?></label> <?php choose_from_menu($basemenu, "extendbase", 3, ""); ?>
<input type="hidden" name="userid" value="<?php p($userid) ?>" />
<input type="hidden" name="courseid" value="<?php p($courseid) ?>" />
<input type="hidden" name="sesskey" value="<?php p(sesskey()) ?>" />
<input type="hidden" name="contextid" value="<?php p($contextid) ?>" />
<input type="hidden" name="roleid" value="<?php p($roleid) ?>" />
<table summary="" style="margin-left:auto;margin-right:auto" border="0" cellpadding="5" cellspacing="0">
<tr>
<td valign="top">
<label for="removeselect"><?php print_string('existingusers', 'role', count($contextusers)); ?></label>
<br />
<select name="removeselect[]" size="20" id="removeselect" multiple="multiple"
onfocus="getElementById('assignform').add.disabled=true;
getElementById('assignform').remove.disabled=false;
getElementById('assignform').addselect.selectedIndex=-1;">
<?php
$i = 0;
foreach ($contextusers as $contextuser) {
$fullname = fullname($contextuser, true);
if ($contextuser->hidden) {
$hidden=' ('.get_string('hiddenassign').') ';
} else {
$hidden="";
}
echo "<option value=\"$contextuser->id\">".$fullname.", ".$contextuser->email.$hidden."</option>\n";
$i++;
}
if ($i==0) {
echo '<option/>'; // empty select breaks xhtml strict
}
?>
</select></td>
<td valign="top">
<br />
<label title="<?php print_string('hiddenassign') ?>">
<input type="checkbox" name="hidden" value="1" />
<img src="<?php echo $CFG->pixpath; ?>/t/hide.gif" alt="<?php print_string('hiddenassign') ?>" class="hide-show-image" />
<?php helpbutton('hiddenassign', get_string('hiddenassign')); ?>
</label>
<?php check_theme_arrows(); ?>
<p class="arrow_button">
<input name="add" id="add" type="submit" value="<?php echo $THEME->larrow.'&nbsp;'.get_string('add'); ?>" title="<?php print_string('add'); ?>" /><br />
<input name="remove" id="remove" type="submit" value="<?php echo get_string('remove').'&nbsp;'.$THEME->rarrow; ?>" title="<?php print_string('remove'); ?>" />
</p>
</td>
<td valign="top">
<label for="addselect"><?php print_string('potentialusers', 'role', $usercount); ?></label>
<br />
<select name="addselect[]" size="20" id="addselect" multiple="multiple"
onfocus="getElementById('assignform').add.disabled=false;
getElementById('assignform').remove.disabled=true;
getElementById('assignform').removeselect.selectedIndex=-1;">
<?php
$i=0;
if (!empty($searchtext)) {
echo "<optgroup label=\"$strsearchresults (" . $usercount . ")\">\n";
foreach ($availableusers as $user) {
$fullname = fullname($user, true);
echo "<option value=\"$user->id\">".$fullname.", ".$user->email."</option>\n";
$i++;
}
echo "</optgroup>\n";
} else {
if ($usercount > MAX_USERS_PER_PAGE) {
echo '<optgroup label="'.get_string('toomanytoshow').'"><option></option></optgroup>'."\n"
.'<optgroup label="'.get_string('trysearching').'"><option></option></optgroup>'."\n";
} else {
foreach ($availableusers as $user) {
$fullname = fullname($user, true);
echo "<option value=\"$user->id\">".$fullname.", ".$user->email."</option>\n";
$i++;
}
}
}
if ($i==0) {
echo '<option/>'; // empty select breaks xhtml strict
}
?>
</select>
<br />
<label for="searchtext" class="accesshide"><?php p($strsearch) ?></label>
<input type="text" name="searchtext" id="searchtext" size="30" value="<?php p($searchtext, true) ?>"
onfocus ="getElementById('assignform').add.disabled=true;
getElementById('assignform').remove.disabled=true;
getElementById('assignform').removeselect.selectedIndex=-1;
getElementById('assignform').addselect.selectedIndex=-1;"
onkeydown = "var keyCode = event.which ? event.which : event.keyCode;
if (keyCode == 13) {
getElementById('assignform').previoussearch.value=1;
getElementById('assignform').submit();
} " />
<input name="search" id="search" type="submit" value="<?php p($strsearch) ?>" />
<?php
if (!empty($searchtext)) {
echo '<input name="showall" id="showall" type="submit" value="'.$strshowall.'" />'."\n";
}
?>
</td>
</tr>
</table>
</div>
</form>

View File

@ -1,28 +1,25 @@
<?php // $Id$
// Script to assign users to contexts
require_once('../../config.php');
require_once($CFG->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 .= '&amp;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();
?>
<form id="assignform" method="post" action="<?php echo $baseurl . '&amp;roleid=' . $roleid ?>">
<input type="hidden" name="sesskey" value="<?php echo sesskey() ?>" />
<table summary="" class="roleassigntable generaltable generalbox boxaligncenter" cellspacing="0">
<tr>
<td id="existingcell">
<p><label for="removeselect"><?php print_string('extusers', 'role'); ?></label></p>
<?php $currentuserselector->display() ?>
</td>
<td id="buttonscell">
<div id="addcontrols">
<input name="add" id="add" type="submit" value="<?php echo $THEME->larrow.'&nbsp;'.get_string('add'); ?>" title="<?php print_string('add'); ?>" /><br />
<p><input type="checkbox" name="hidden" id="hidden" value="1" <?php
if ($hidden) { echo 'checked="checked" '; } ?>/>
<label for="hidden" title="<?php print_string('createhiddenassign', 'role'); ?>">
<?php print_string('hidden', 'role'); ?>
<?php helpbutton('hiddenassign', get_string('createhiddenassign', 'role')); ?>
</label></p>
<p><label for="extendperiod"><?php print_string('enrolperiod') ?></label><br />
<?php choose_from_menu($periodmenu, "extendperiod", $defaultperiod, $unlimitedperiod); ?></p>
<p><label for="extendbase"><?php print_string('startingfrom') ?></label><br />
<?php choose_from_menu($basemenu, "extendbase", 3, ""); ?></p>
</div>
<div id="removecontrols">
<input name="remove" id="remove" type="submit" value="<?php echo get_string('remove').'&nbsp;'.$THEME->rarrow; ?>" title="<?php print_string('remove'); ?>" />
</div>
</td>
<td id="potentialcell">
<p><label for="addselect"><?php print_string('potusers', 'role'); ?></label></p>
<?php $potentialuserselector->display() ?>
</td>
</tr>
</table>
</form>
<?php
print_js_call('init_add_assign_page');
if (!empty($errors)) {
$msg = '<p>';
@ -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;

View File

@ -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;
});
}

View File

@ -94,7 +94,7 @@ check_theme_arrows();
<table class="generaltable generalbox groupmanagementtable boxaligncenter" summary="">
<tr>
<td id='memberscell'>
<td id='existingcell'>
<p>
<label for="removeselect"><?php print_string('groupmembers', 'group'); ?></label>
</p>
@ -106,7 +106,7 @@ check_theme_arrows();
<input name="remove" id="remove" type="submit" value="<?php echo get_string('remove').'&nbsp;'.$THEME->rarrow; ?>" title="<?php print_string('remove'); ?>" />
</p>
</td>
<td id='nonmemberscell'>
<td id='potentialcell'>
<p>
<label for="addselect"><?php print_string('potentialmembs', 'group'); ?></label>
</p>

View File

@ -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\"!';

View File

@ -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'] = '<p>To use this table:</p><ol><li>First look to see if there are any Prohibits. If there are, has_capability will return false.</li><li>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.</li><li>If no cell has different numbers of Prevents and Allows, then has_capability will return false.</li></ol>';
$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';

View File

@ -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);

View File

@ -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,

View File

@ -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 = ' <optgroup label="' . s($groupname) . ' (' . count($users) . ')">' . "\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 .= ' <option' . $selectattr . ' value="' . $user->id . '">' .
$output .= ' <option' . $attributes . ' value="' . $user->id . '">' .
$this->output_user($user) . "</option>\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 . ')';
}

View File

@ -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);

View File

@ -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));
?>