Commit a4bd7175 authored by Richard Mansfield's avatar Richard Mansfield Committed by Gerrit Code Review
Browse files

Merge changes I2460bcf5,Ic2713a65,If232021b,I792fa294

* changes:
  Remove requested/invited users from the admin search
  Add support for revocation of institution membership invitations
  Add support for mass user actions to admin search (bug #801069)
  Add new admin page for bulk user actions (bug #801069)
parents fc107a53 6ba72658
<?php
/**
* Mahara: Electronic portfolio, weblog, resume builder and social networking
* Copyright (C) 2011 Catalyst IT Ltd and others; see:
* http://wiki.mahara.org/Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @package mahara
* @subpackage admin
* @author Richard Mansfield
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL
*
*/
define('INTERNAL', 1);
define('INSTITUTIONALADMIN', 1);
define('MENUITEM', 'configusers');
require(dirname(dirname(dirname(__FILE__))) . '/init.php');
define('TITLE', get_string('bulkactions', 'admin'));
$userids = array_map('intval', param_variable('users'));
$ph = $userids;
$institutionsql = '';
if (!$USER->get('admin')) {
// Filter the users by the admin's institutions
$institutions = array_values($USER->get('admininstitutions'));
$ph = array_merge($ph, $institutions);
$institutionsql = '
AND id IN (
SELECT usr FROM {usr_institution} WHERE institution IN (' . join(',', array_fill(0, count($institutions), '?')) . ')
)';
}
$users = get_records_sql_assoc('
SELECT
u.id, u.username, u.email, u.firstname, u.lastname, u.suspendedcusr, u.authinstance, u.studentid,
u.preferredname, CHAR_LENGTH(u.password) AS haspassword, aru.remoteusername AS remoteuser
FROM {usr} u
LEFT JOIN {auth_remote_user} aru ON u.id = aru.localusr AND u.authinstance = aru.authinstance
WHERE id IN (' . join(',', array_fill(0, count($userids), '?')) . ')
AND deleted = 0' . $institutionsql . '
ORDER BY username',
$ph
);
$userids = array_keys($users);
// Export CSV
$csv = "username,email,firstname,lastname,studentid,preferredname,remoteuser\n";
foreach ($users as $user) {
$u = array();
foreach (array('username', 'email', 'firstname', 'lastname', 'studentid', 'preferredname', 'remoteuser') as $f) {
$u[] = str_replace('"', '""', $user->$f);
}
$csv .= '"' . join('","', $u) . '"' . "\n";
}
$USER->set_download_file($csv, 'users.csv', 'text/csv');
// Hidden drop-down to submit the list of users back to this page.
// Used in all three forms
$userelement = array(
'type' => 'select',
'class' => 'hidden',
'multiple' => 'true',
'options' => array_combine($userids, $userids),
'value' => $userids,
);
// Change authinstance
if ($USER->get('admin')) {
$authinstances = auth_get_auth_instances();
}
else {
$admininstitutions = $USER->get('admininstitutions');
$authinstances = auth_get_auth_instances_for_institutions($admininstitutions);
}
$options = array();
$default = null;
foreach ($authinstances as $authinstance) {
$options[$authinstance->id] = $authinstance->displayname. ': '.$authinstance->instancename;
if (!$default && $authinstance->name == 'mahara') {
$default = $authinstance->id;
}
}
$changeauthform = pieform(array(
'name' => 'changeauth',
'class' => 'bulkactionform',
'renderer' => 'oneline',
'dieaftersubmit' => false,
'elements' => array(
'users' => $userelement,
'title' => array(
'type' => 'html',
'class' => 'bulkaction-title',
'value' => get_string('changeauthmethod', 'admin') . ': ',
),
'authinstance' => array(
'type' => 'select',
'options' => $options,
'defaultvalue' => $default,
),
'changeauth' => array(
'type' => 'submit',
'value' => get_string('submit'),
),
),
));
// Suspend users
$suspendform = pieform(array(
'name' => 'suspend',
'class' => 'bulkactionform',
'renderer' => 'oneline',
'elements' => array(
'users' => $userelement,
'title' => array(
'type' => 'html',
'class' => 'bulkaction-title',
'value' => get_string('suspendusers', 'admin') . ': ',
),
'suspend' => array(
'type' => 'submit',
'value' => get_string('Suspend', 'admin'),
),
'reason' => array(
'type' => 'text',
'title' => ' ' . get_string('reason') . ': ',
),
),
));
// Delete users
$deleteform = pieform(array(
'name' => 'delete',
'class' => 'bulkactionform delete',
'renderer' => 'oneline',
'elements' => array(
'users' => $userelement,
'title' => array(
'type' => 'html',
'class' => 'bulkaction-title',
'value' => get_string('deleteusers', 'admin') . ': ',
),
'delete' => array(
'type' => 'submit',
'confirm' => get_string('confirmdeleteusers', 'admin'),
'value' => get_string('delete'),
),
),
));
$smarty = smarty();
$smarty->assign('PAGEHEADING', TITLE);
$smarty->assign('users', $users);
$smarty->assign('changeauthform', $changeauthform);
$smarty->assign('suspendform', $suspendform);
$smarty->assign('deleteform', $deleteform);
$smarty->display('admin/users/bulk.tpl');
function changeauth_validate(Pieform $form, $values) {
global $userids, $SESSION;
// Make sure all users are members of the institution that
// this authinstance belongs to.
$authobj = AuthFactory::create($values['authinstance']);
if ($authobj->institution != 'mahara') {
$ph = $userids;
$ph[] = $authobj->institution;
$institutionusers = count_records_sql('
SELECT COUNT(usr)
FROM {usr_institution}
WHERE usr IN (' . join(',', array_fill(0, count($userids), '?')) . ') AND institution = ?',
$ph
);
if ($institutionusers != count($userids)) {
$SESSION->add_error_msg(get_string('someusersnotinauthinstanceinstitution', 'admin'));
$form->set_error('authinstance', get_string('someusersnotinauthinstanceinstitution', 'admin'));
}
}
}
function changeauth_submit(Pieform $form, $values) {
global $users, $SESSION, $authinstances;
$newauth = AuthFactory::create($values['authinstance']);
$needspassword = method_exists($newauth, 'change_password');
$updated = 0;
$needpassword = 0;
db_begin();
foreach ($users as $user) {
if ($user->authinstance != $values['authinstance']) {
if ($user->haspassword && !$needspassword) {
$user->password = '';
}
else if ($needspassword && !$user->haspassword) {
$needpassword++;
}
$user->authinstance = $values['authinstance'];
update_record('usr', $user, 'id');
$updated++;
}
}
db_commit();
if ($needpassword) {
// Inform the user that they may need to reset passwords
$SESSION->add_info_msg(get_string('bulkchangeauthmethodresetpassword', 'admin', $needpassword));
}
$message = get_string('bulkchangeauthmethodsuccess', 'admin', $updated);
$form->reply(PIEFORM_OK, array('message' => $message));
}
function suspend_submit(Pieform $form, $values) {
global $users, $SESSION;
$suspended = 0;
db_begin();
foreach ($users as $user) {
if (!$user->suspendedcusr) {
suspend_user($user->id, $values['reason']);
$suspended++;
}
}
db_commit();
$SESSION->add_ok_msg(get_string('bulksuspenduserssuccess', 'admin', $suspended));
redirect('/admin/users/suspended.php');
}
function delete_submit(Pieform $form, $values) {
global $users, $editable, $SESSION;
db_begin();
foreach ($users as $user) {
delete_user($user->id);
}
db_commit();
$SESSION->add_ok_msg(get_string('bulkdeleteuserssuccess', 'admin', count($users)));
redirect('/admin/users/search.php');
}
......@@ -66,6 +66,7 @@ $usertypeselector = pieform(array(
'requesters' => get_string('institutionusersrequesters', 'admin'),
'nonmembers' => get_string('institutionusersnonmembers', 'admin'),
'members' => get_string('institutionusersmembers', 'admin'),
'invited' => get_string('institutionusersinvited', 'admin'),
),
'defaultvalue' => $usertype
),
......@@ -91,7 +92,8 @@ if ($usertype == 'requesters') {
'searchparams' => array('member' => 1),
);
$submittext = get_string('removeusers', 'admin');
} else { // $usertype == nonmembers
}
else if ($usertype == 'nonmembers') {
// Behaviour depends on whether we allow users to have > 1 institution
// LHS either shows all nonmembers or just users with no institution
// RHS shows users to be invited
......@@ -103,6 +105,16 @@ if ($usertype == 'requesters') {
);
$submittext = get_string('inviteusers', 'admin');
}
else if ($usertype == 'invited') {
// Allow invitations to be revoked
$userlistelement = array(
'title' => get_string('revokeinvitations', 'admin'),
'lefttitle' => get_string('invitedusers', 'admin'),
'righttitle' => get_string('userstobeuninvited', 'admin'),
'searchparams' => array('member' => 0, 'invitedby' => 1),
);
$submittext = get_string('revokeinvitations', 'admin');
}
$userlistelement['type'] = 'userlist';
$userlistelement['filter'] = false;
......@@ -159,7 +171,7 @@ function institutionusers_submit(Pieform $form, $values) {
}
$dataerror = false;
if (!in_array($values['usertype'], array('requesters', 'members', 'nonmembers'))
if (!in_array($values['usertype'], array('requesters', 'members', 'nonmembers', 'invited'))
|| !is_array($values['users'])) {
$dataerror = true;
} else {
......@@ -182,9 +194,13 @@ function institutionusers_submit(Pieform $form, $values) {
$action = 'removeMembers';
} else if ($values['usertype'] == 'requesters') {
$action = !empty($values['reject']) ? 'declineRequestFromUser' : 'addUserAsMember';
} else {
}
else if ($values['usertype'] == 'nonmembers') {
$action = (!empty($values['add']) && $USER->get('admin')) ? 'addUserAsMember' : 'inviteUser';
}
else {
$action = 'uninvite_users';
}
$institution = new Institution($values['institution']);
......@@ -214,6 +230,9 @@ function institutionusers_submit(Pieform $form, $values) {
else if ($action == 'declineRequestFromUser') {
$institution->decline_requests($values['users']);
}
else if ($action == 'uninvite_users') {
$institution->uninvite_users($values['users']);
}
$SESSION->add_ok_msg(get_string('institutionusersupdated_'.$action, 'admin'));
if (!$USER->get('admin') && !$USER->is_institutional_admin()) {
......
......@@ -41,21 +41,15 @@ if ($action == 'search') {
$params->institution = param_alphanum('institution', null);
$params->f = param_alpha('f', null);
$params->l = param_alpha('l', null);
$params->institution_requested = param_alphanum('institution_requested', null);
$offset = param_integer('offset', 0);
$limit = param_integer('limit', 10);
$sortby = param_alpha('sortby', 'firstname');
$sortdir = param_alpha('sortdir', 'asc');
json_headers();
if (param_boolean('raw', false)) {
$data = get_admin_user_search_results($params, $offset, $limit, $sortby, $sortdir);
} else {
$data['data'] = build_admin_user_search_results($params, $offset, $limit, $sortby, $sortdir);
}
$data = array();
$data['data'] = build_admin_user_search_results($params, $offset, $limit, $sortby, $sortdir);
$data['error'] = false;
$data['message'] = null;
echo json_encode($data);
exit;
json_reply(false, $data);
}
......@@ -51,7 +51,6 @@ if ($USER->get('admin')) {
$search->institution = param_alphanum('institution', 'all');
} else {
$institutions = get_records_select_array('institution', "name IN ('" . join("','", array_keys($USER->get('admininstitutions'))) . "')", null, 'displayname');
$search->institution_requested = param_alphanum('institution_requested', 'all');
}
$smarty = smarty(array('adminusersearch'));
......
......@@ -1325,4 +1325,26 @@ class LiveUser extends User {
));
}
/**
* Writes a file to dataroot and saves details in the session,
* for later download by the user
*
* @param $content string file contents
* @param $name string filename to be used when downloading the file
* @param $mimetype string
*/
public function set_download_file($content, $name, $mimetype) {
global $SESSION;
$filename = get_random_key();
$dir = get_config('dataroot') . 'export/' . $this->id . '/';
check_dir_exists($dir);
file_put_contents($dir . $filename, $content);
$SESSION->set('downloadfile', array(
'file' => $filename,
'name' => $name,
'mimetype' => $mimetype,
));
}
}
<?php
/**
* Mahara: Electronic portfolio, weblog, resume builder and social networking
* Copyright (C) 2006-2009 Catalyst IT Ltd and others; see:
* http://wiki.mahara.org/Contributors
* Copyright (C) 2011 Catalyst IT Ltd and others; see:
* http://wiki.mahara.org/Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
......@@ -18,33 +18,26 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @package mahara
* @subpackage admin
* @author Catalyst IT Ltd
* @subpackage core
* @author Richard Mansfield
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL
* @copyright (C) 2006-2009 Catalyst IT Ltd http://catalyst.net.nz
*
*/
define('INTERNAL', 1);
define('INSTITUTIONALADMIN', 1);
require(dirname(dirname(dirname(__FILE__))) . '/init.php');
require_once('institution.php');
require('init.php');
require_once('file.php');
$id = param_integer('id');
$institution = new Institution(param_alpha('institution'));
$data = $SESSION->get('downloadfile');
if (!$USER->get('admin')) {
if (!$USER->is_institutional_admin($institution->name)) {
$SESSION->add_error_msg(get_string('notadminforinstitution', 'admin'));
redirect(get_config('wwwroot').'admin/users/search.php');
}
else if (!get_field('usr_institution_request', 'confirmedusr', 'usr', $id, 'institution', $institution->name)) {
$institution->inviteUser($id);
$SESSION->add_ok_msg(get_string('invitationsent', 'admin'));
redirect(get_config('wwwroot').'admin/users/search.php');
}
if (!$USER->is_logged_in() || empty($data) || empty($data['file'])) {
throw new NotFoundException(get_string('filenotfound'));
}
$institution->addUserAsMember($id);
$SESSION->add_ok_msg(get_string('useradded', 'admin'));
redirect(get_config('wwwroot').'admin/users/edit.php?id='.$id);
$path = get_config('dataroot') . 'export/' . $USER->get('id') . '/' . $data['file'];
if (!file_exists($path)) {
throw new NotFoundException(get_string('filenotfound'));
}
serve_file($path, $data['name'], $data['mimetype']);
......@@ -31,6 +31,8 @@ function UserSearch() {
self.rewritePaging();
self.rewriteSorting();
self.rewriteSetLimit();
self.selectusers = {};
self.rewriteCheckboxes();
self.params = {};
}
......@@ -90,7 +92,9 @@ function UserSearch() {
if (children.length == 1) {
var href = getNodeAttribute(children[0], 'href');
self.params = parseQueryString(href.substring(href.indexOf('?')+1, href.length));
self.doSearch();
// Assume this is only changing the page or the order of results,
// so pass true here to avoid clearing the selected users.
self.doSearch(true);
}
}
......@@ -136,18 +140,54 @@ function UserSearch() {
e.stop();
}
this.doSearch = function() {
this.doSearch = function(saveselected) {
self.params.action = 'search';
sendjsonrequest('search.json.php', self.params, 'POST', function(data) {
$('results').innerHTML = data.data;
if (!saveselected) {
self.selectusers = {};
}
if ($('searchresults')) {
self.rewritePaging();
self.rewriteSorting();
self.rewriteCheckboxes();
self.rewriteSetLimit();
}
});
}
this.rewriteCheckboxes = function() {
forEach(getElementsByTagAndClassName('input', 'selectusers', 'searchresults'), function(i) {
connect(i, 'onclick', function() {
if (i.checked) {
self.selectusers[i.value] = 1;
}
else {
delete self.selectusers[i.value];
}
});
if (self.selectusers[i.value]) {
i.checked = true;
}
});
if ($('selectall')) {
connect('selectall', 'onclick', function(e) {
e.stop();
forEach(getElementsByTagAndClassName('input', 'selectusers', 'searchresults'), function(i) {
self.selectusers[i.value] = 1;
i.checked = true;
});
});
connect('selectnone', 'onclick', function(e) {
e.stop();
forEach(getElementsByTagAndClassName('input', 'selectusers', 'searchresults'), function(i) {
delete self.selectusers[i.value];
i.checked = false;
});
});
}
}
this.rewriteSetLimit = function() {
if ($('setlimit')) {
forEach(getElementsByTagAndClassName('a', null, 'setlimit'), function(i) {
......@@ -158,7 +198,7 @@ function UserSearch() {
}
self.params.limit = scrapeText(i);
self.params.offset = Math.floor(self.params.offset / self.params.limit) * self.params.limit;
self.doSearch();
self.doSearch(true);
});
});
}
......@@ -168,3 +208,37 @@ function UserSearch() {
}
userSearch = new UserSearch();
addLoadEvent(function() {
forEach(getElementsByTagAndClassName('input', 'button', 'bulkactions'), function(input) {
connect(input, 'onclick', function() {
// Some of the selected users aren't on the page, so just add them all to the
// form now.
var count = 0;
if (userSearch.selectusers) {
for (j in userSearch.selectusers) {
appendChildNodes('bulkactions', INPUT({
'type': 'checkbox',
'name': 'users[' + j + ']',
'value': j,
'class': 'hidden',
'checked': 'checked'
}));
count++;
}
}
if (count) {
addElementClass('nousersselected', 'hidden');
appendChildNodes('bulkactions', INPUT({
'type': 'hidden',
'name': 'action',
'value': input.name,
}));
$('bulkactions').submit();
return false;
}
removeElementClass('nousersselected', 'hidden');
return false;
})
});
});
......@@ -644,6 +644,7 @@ $string['institutionmemberspagedescription'] = 'On this page you can see users w
$string['institutionusersinstructionsrequesters'] = 'The list of users on the left shows all users who have asked to join your institution. You can use the search box to reduce the number of users displayed. If you would like to add users to the institution, or decline their membership requests, first move some users to the right hand side by selecting one or more users and then clicking on the right arrow. The "Add members" button will add all the users on the right to the institution. The "Decline requests" button will remove the membership requests of the users on the right.';
$string['institutionusersinstructionsnonmembers'] = 'The list of users on the left shows all users who are not yet members of your institution. You can use the search box to reduce the number of users displayed. To invite users to join the institution, first move some users to the right hand side by selecting one or more users and then clicking on the right arrow to move those users to the list on the right. The "Invite Users" button will send invitations to all the users on the right. These users will not be associated with the institution until they accept the invitation.';
$string['institutionusersinstructionsmembers'] = 'The list of users on the left shows all members of the institution. You can use the search box to reduce the number of users displayed. To remove users from the institution, first move some users to the right hand side by selecting one or more users on the left and then clicking on the right arrow. The users you selected will move to the right hand side. The "Remove Users" button will remove all the users on the right from the institution. The users on the left will remain in the institution.';
$string['institutionusersinstructionsinvited'] = 'The list of users on the left shows all users who have been sent an invitation to join the institution and who have not yet accepted or declined. You can use the search box to reduce the number of users displayed. To revoke invitations to the institution, first move some users to the right hand side by selecting one or more users on the left and then clicking on the right arrow. The users you selected will move to the right hand side. The "Revoke Invitations" button will remove all invitations to the users on the right. The users on the left will retain their invitations and will still be able to join at any time.';
$string['editmembers'] = 'Edit Members';
$string['editstaff'] = 'Edit Staff';
......@@ -657,6 +658,7 @@ $string['userstodisplay'] = 'Users to display:';
$string['institutionusersrequesters'] = 'People who have requested institution membership';
$string['institutionusersnonmembers'] = 'People who have not requested membership yet';
$string['institutionusersmembers'] = 'People who are already institution members';
$string['institutionusersinvited'] = 'People who have been invited';
$string['addnewmembers'] = 'Add new members';
$string['usersrequested'] = 'Users who have requested membership';
......@@ -673,11 +675,15 @@ $string['userstoberemoved'] = 'Users to be removed';
$string['removeusers'] = 'Remove Users';
$string['declinerequests'] = 'Decline requests';
$string['nousersupdated'] = 'No users were updated';
$string['revokeinvitations'] = 'Revoke invitations';
$string['invitedusers'] = 'Invited users';