MDL-80548 admin: Add bulk user actions also to user list

This commit is contained in:
Marina Glancy 2024-01-08 16:26:46 +00:00
parent debed3eace
commit d395925a40
23 changed files with 298 additions and 35 deletions

View File

@ -0,0 +1,10 @@
define("core_admin/bulk_user_actions",["exports","core_reportbuilder/local/selectors","core_table/local/dynamic/events","core_form/changechecker","core/custom_interaction_events","jquery"],(function(_exports,reportSelectors,tableEvents,FormChangeChecker,CustomEvents,_jquery){var obj;function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireWildcard(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj}
/**
* Add bulk actions to the users list report
*
* @module core_admin/bulk_user_actions
* @copyright 2024 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,reportSelectors=_interopRequireWildcard(reportSelectors),tableEvents=_interopRequireWildcard(tableEvents),FormChangeChecker=_interopRequireWildcard(FormChangeChecker),CustomEvents=_interopRequireWildcard(CustomEvents),_jquery=(obj=_jquery)&&obj.__esModule?obj:{default:obj};const Selectors_bulkActionsForm="form#user-bulk-action-form",Selectors_userReportWrapper='[data-region="report-user-list-wrapper"]',Selectors_checkbox='input[type="checkbox"][data-togglegroup="report-select-all"][data-toggle="slave"]',Selectors_masterCheckbox='input[type="checkbox"][data-togglegroup="report-select-all"][data-toggle="master"]',Selectors_checkedRows='[data-togglegroup="report-select-all"][data-toggle="slave"]:checked';_exports.init=()=>{var _userBulkForm$closest;const userBulkForm=document.querySelector(Selectors_bulkActionsForm),userReport=null==userBulkForm||null===(_userBulkForm$closest=userBulkForm.closest(Selectors_userReportWrapper))||void 0===_userBulkForm$closest?void 0:_userBulkForm$closest.querySelector(reportSelectors.regions.report);if(!userBulkForm||!userReport)return;const actionSelect=userBulkForm.querySelector("select");CustomEvents.define(actionSelect,[CustomEvents.events.accessibleChange]),(0,_jquery.default)(actionSelect).on(CustomEvents.events.accessibleChange,(event=>{if(event.target.value&&"0"!=="".concat(event.target.value)){const e=new Event("submit",{cancelable:!0});userBulkForm.dispatchEvent(e),e.defaultPrevented||(FormChangeChecker.markFormSubmitted(userBulkForm),userBulkForm.submit())}}));const updateUserIds=()=>{const selectedUsers=[...userReport.querySelectorAll(Selectors_checkedRows)],selectedUserIds=selectedUsers.map((check=>parseInt(check.value)));userBulkForm.querySelector('[name="userids"]').value=selectedUserIds.join(","),actionSelect.disabled=0===selectedUsers.length;const selectedUsersNames=selectedUsers.map((check=>document.querySelector('label[for="'.concat(check.id,'"]')).textContent));userBulkForm.data={userids:selectedUserIds,usernames:selectedUsersNames}};updateUserIds(),document.addEventListener("change",(event=>{(event.target.matches(Selectors_checkbox)||event.target.matches(Selectors_masterCheckbox))&&userReport.contains(event.target)&&updateUserIds()})),document.addEventListener(tableEvents.tableContentRefreshed,(event=>{userReport.contains(event.target)&&updateUserIds()}))}}));
//# sourceMappingURL=bulk_user_actions.min.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,91 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle 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.
//
// Moodle 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 Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Add bulk actions to the users list report
*
* @module core_admin/bulk_user_actions
* @copyright 2024 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import * as reportSelectors from 'core_reportbuilder/local/selectors';
import * as tableEvents from 'core_table/local/dynamic/events';
import * as FormChangeChecker from 'core_form/changechecker';
import * as CustomEvents from 'core/custom_interaction_events';
import jQuery from 'jquery';
const Selectors = {
bulkActionsForm: 'form#user-bulk-action-form',
userReportWrapper: '[data-region="report-user-list-wrapper"]',
checkbox: 'input[type="checkbox"][data-togglegroup="report-select-all"][data-toggle="slave"]',
masterCheckbox: 'input[type="checkbox"][data-togglegroup="report-select-all"][data-toggle="master"]',
checkedRows: '[data-togglegroup="report-select-all"][data-toggle="slave"]:checked',
};
/**
* Initialise module
*/
export const init = () => {
const userBulkForm = document.querySelector(Selectors.bulkActionsForm);
const userReport = userBulkForm?.closest(Selectors.userReportWrapper)?.querySelector(reportSelectors.regions.report);
if (!userBulkForm || !userReport) {
return;
}
const actionSelect = userBulkForm.querySelector('select');
CustomEvents.define(actionSelect, [CustomEvents.events.accessibleChange]);
jQuery(actionSelect).on(CustomEvents.events.accessibleChange, event => {
if (event.target.value && `${event.target.value}` !== "0") {
const e = new Event('submit', {cancelable: true});
userBulkForm.dispatchEvent(e);
if (!e.defaultPrevented) {
FormChangeChecker.markFormSubmitted(userBulkForm);
userBulkForm.submit();
}
}
});
// Every time the checkboxes in the report are changed, update the list of users in the form values
// and enable/disable the action select.
const updateUserIds = () => {
const selectedUsers = [...userReport.querySelectorAll(Selectors.checkedRows)];
const selectedUserIds = selectedUsers.map(check => parseInt(check.value));
userBulkForm.querySelector('[name="userids"]').value = selectedUserIds.join(',');
actionSelect.disabled = selectedUsers.length === 0;
const selectedUsersNames = selectedUsers.map(check => document.querySelector(`label[for="${check.id}"]`).textContent);
// Add the user ids and names to the form data attributes so they can be available from the
// other JS modules that listen to the form submit event.
userBulkForm.data = {userids: selectedUserIds, usernames: selectedUsersNames};
};
updateUserIds();
document.addEventListener('change', event => {
// When checkboxes are checked next to individual users or the master toggle (Select all/none).
if ((event.target.matches(Selectors.checkbox) || event.target.matches(Selectors.masterCheckbox))
&& userReport.contains(event.target)) {
updateUserIds();
}
});
document.addEventListener(tableEvents.tableContentRefreshed, event => {
// When the report contents is updated (i.e. page is changed, filters applied, etc).
if (userReport.contains(event.target)) {
updateUserIds();
}
});
};

View File

@ -67,6 +67,13 @@ class users extends system_report {
$this->add_base_fields("{$entityuseralias}.id, {$entityuseralias}.confirmed, {$entityuseralias}.mnethostid,
{$entityuseralias}.suspended, {$entityuseralias}.username, " . implode(', ', $fullnamefields));
if ($this->get_parameter('withcheckboxes', false, PARAM_BOOL)) {
$canviewfullnames = has_capability('moodle/site:viewfullnames', \context_system::instance());
$this->set_checkbox_toggleall(static function(\stdClass $row) use ($canviewfullnames): array {
return [$row->id, fullname($row, $canviewfullnames)];
});
}
$paramguest = database::generate_param_name();
$this->add_base_condition_sql("{$entityuseralias}.deleted <> 1 AND {$entityuseralias}.id <> :{$paramguest}",
[$paramguest => $CFG->siteguest]);

View File

@ -195,3 +195,29 @@ Feature: An administrator can browse user accounts
Then I should see "Username"
And I should see "User picture"
And I should see "Additional names"
@javascript
Scenario: Browse user list as a person with limited capabilities
Given the following "users" exist:
| username | firstname | lastname | email |
| manager | Max | Manager | manager@example.com |
And the following "roles" exist:
| name | shortname | description | archetype |
| Custom manager | custom1 | My custom role 1 | |
And the following "permission overrides" exist:
| capability | permission | role | contextlevel | reference |
| moodle/site:configview | Allow | custom1 | System | |
| moodle/user:update | Allow | custom1 | System | |
And the following "role assigns" exist:
| user | role | contextlevel | reference |
| manager | custom1 | System | |
When I log in as "manager"
And I navigate to "Users > Accounts > Browse list of users" in site administration
And I click on "User One" "checkbox"
And the "Bulk user actions" select box should contain "Confirm"
And the "Bulk user actions" select box should not contain "Delete"
And I set the field "Bulk user actions" to "Force password change"
And I should see "Are you absolutely sure you want to force a password change to User One ?"
And I press "Yes"
And I press "Continue"
And I should see "Browse list of users"

View File

@ -40,6 +40,9 @@ class reset_factor extends \moodleform {
$mform->addElement('hidden', 'bulkaction', $bulkaction);
$mform->setType('bulkaction', PARAM_BOOL);
$mform->addElement('hidden', 'returnurl');
$mform->setType('returnurl', PARAM_LOCALURL);
$factors = array_map(function ($element) {
return $element->get_display_name();
}, $factors);

View File

@ -112,6 +112,9 @@ function tool_mfa_after_config(): void {
* @return array
*/
function tool_mfa_bulk_user_actions(): array {
if (!has_capability('moodle/site:config', context_system::instance())) {
return [];
}
return [
'tool_mfa_reset_factors' => new action_link(
new moodle_url('/admin/tool/mfa/reset_factor.php'),

View File

@ -28,17 +28,19 @@ require_once($CFG->libdir . '/adminlib.php');
admin_externalpage_setup('tool_mfa_resetfactor');
$bulk = !empty($SESSION->bulk_users);
$returnurl = optional_param('returnurl', '', PARAM_LOCALURL);
$factors = \tool_mfa\plugininfo\factor::get_factors();
$form = new \tool_mfa\local\form\reset_factor(null, ['factors' => $factors, 'bulk' => $bulk]);
if ($bulk) {
$form->set_data(['returnurl' => $returnurl]);
$return = new moodle_url($returnurl ?: '/admin/user/user_bulk.php');
} else {
$return = new moodle_url('/admin/category.php', ['category' => 'toolmfafolder']);
}
if ($form->is_cancelled()) {
if ($bulk) {
$url = new moodle_url('/admin/user/user_bulk.php');
} else {
$url = new moodle_url('/admin/category.php', ['category' => 'toolmfafolder']);
}
redirect($url);
redirect($return);
} else if ($fromform = $form->get_data()) {
// Get factor from select index.
if ($fromform->factor !== 'all') {
@ -82,7 +84,7 @@ if ($form->is_cancelled()) {
\core\notification::success(get_string('resetsuccessbulk', 'tool_mfa', $stringvar));
unset($SESSION->bulk_users);
// Redirect to bulk actions page.
redirect(new moodle_url('/admin/user/user_bulk.php'));
redirect($return);
}
echo $OUTPUT->header();

View File

@ -4,6 +4,7 @@
require_once($CFG->libdir.'/adminlib.php');
require_once($CFG->libdir.'/authlib.php');
require_once($CFG->dirroot.'/user/lib.php');
require_once($CFG->dirroot.'/'.$CFG->admin.'/user/user_bulk_forms.php');
$delete = optional_param('delete', 0, PARAM_INT);
$confirm = optional_param('confirm', '', PARAM_ALPHANUM); //md5 confirmation hash
@ -172,8 +173,23 @@
echo html_writer::end_div();
}
echo html_writer::start_div('', ['data-region' => 'report-user-list-wrapper']);
$bulkactions = new user_bulk_action_form(new moodle_url('/admin/user/user_bulk.php'),
['excludeactions' => ['displayonpage'], 'passuserids' => true, 'hidesubmit' => true],
'post', '',
['id' => 'user-bulk-action-form']);
$bulkactions->set_data(['returnurl' => $PAGE->url->out_as_local_url(false)]);
$report = \core_reportbuilder\system_report_factory::create(\core_admin\reportbuilder\local\systemreports\users::class,
context_system::instance());
context_system::instance(), parameters: ['withcheckboxes' => $bulkactions->has_bulk_actions()]);
echo $report->output();
if ($bulkactions->has_bulk_actions()) {
$PAGE->requires->js_call_amd('core_admin/bulk_user_actions', 'init');
$bulkactions->display();
}
echo html_writer::end_div();
echo $OUTPUT->footer();

View File

@ -37,11 +37,18 @@ $ufiltering = new user_filtering();
// Create the bulk operations form.
$actionform = new user_bulk_action_form();
$actionform->set_data(['returnurl' => $PAGE->url->out_as_local_url(false)]);
if ($data = $actionform->get_data()) {
if ($data->passuserids) {
// This means we called the form from /admin/user.php or similar and the userids should be taken from the form
// data and not from $SESSION->bulk_users. For backwards compatibility we still set $SESSION->bulk_users.
$users = preg_split('/,/', $data->userids, -1, PREG_SPLIT_NO_EMPTY);
$SESSION->bulk_users = array_combine($users, $users);
}
// Check if an action should be performed and do so.
$bulkactions = $actionform->get_actions();
if (array_key_exists($data->action, $bulkactions)) {
redirect($bulkactions[$data->action]->url);
redirect(new moodle_url($bulkactions[$data->action]->url, ['returnurl' => $data->returnurl ?: null]));
}
}

View File

@ -34,6 +34,9 @@ $dir = optional_param('dir', 'asc', PARAM_ALPHA);
admin_externalpage_setup('userbulk');
require_capability('moodle/cohort:assign', context_system::instance());
$returnurl = optional_param('returnurl', '', PARAM_LOCALURL);
$return = new moodle_url($returnurl ?: '/admin/user/user_bulk.php');
$users = $SESSION->bulk_users;
$strnever = get_string('never');
@ -60,7 +63,7 @@ foreach ($allcohorts as $c) {
unset($allcohorts);
if (count($cohorts) < 2) {
redirect(new moodle_url('/admin/user/user_bulk.php'), get_string('bulknocohort', 'core_cohort'));
redirect($return, get_string('bulknocohort', 'core_cohort'));
}
$countries = get_string_manager()->get_list_of_countries(true);
@ -78,9 +81,10 @@ foreach ($users as $key => $id) {
unset($countries);
$mform = new user_bulk_cohortadd_form(null, $cohorts);
$mform->set_data(['returnurl' => $returnurl]);
if (empty($users) or $mform->is_cancelled()) {
redirect(new moodle_url('/admin/user/user_bulk.php'));
redirect($return);
} else if ($data = $mform->get_data()) {
// process request
@ -89,7 +93,7 @@ if (empty($users) or $mform->is_cancelled()) {
cohort_add_member($data->cohort, $user->id);
}
}
redirect(new moodle_url('/admin/user/user_bulk.php'));
redirect($return);
}
// Need to sort by date

View File

@ -31,6 +31,9 @@ class user_bulk_cohortadd_form extends moodleform {
$mform = $this->_form;
$cohorts = $this->_customdata;
$mform->addElement('hidden', 'returnurl');
$mform->setType('returnurl', PARAM_LOCALURL);
$mform->addElement('select', 'cohort', get_string('cohort', 'core_cohort'), $cohorts);
$mform->addRule('cohort', get_string('required'), 'required', null, 'client');

View File

@ -11,7 +11,8 @@ $confirm = optional_param('confirm', 0, PARAM_BOOL);
admin_externalpage_setup('userbulk');
require_capability('moodle/user:update', context_system::instance());
$return = $CFG->wwwroot.'/'.$CFG->admin.'/user/user_bulk.php';
$returnurl = optional_param('returnurl', '', PARAM_LOCALURL);
$return = new moodle_url($returnurl ?: '/admin/user/user_bulk.php');
if (empty($SESSION->bulk_users)) {
redirect($return);
@ -45,7 +46,7 @@ if ($confirm and confirm_sesskey()) {
} else {
echo $OUTPUT->notification(get_string('changessaved'), 'notifysuccess');
}
$continue = new single_button(new moodle_url($return), get_string('continue'), 'post');
$continue = new single_button($return, get_string('continue'), 'post');
echo $OUTPUT->render($continue);
echo $OUTPUT->box_end();
} else {
@ -53,8 +54,9 @@ if ($confirm and confirm_sesskey()) {
$userlist = $DB->get_records_select_menu('user', "id $in", $params, 'fullname', 'id,'.$DB->sql_fullname().' AS fullname');
$usernames = implode(', ', $userlist);
echo $OUTPUT->heading(get_string('confirmation', 'admin'));
$formcontinue = new single_button(new moodle_url('user_bulk_confirm.php', array('confirm' => 1)), get_string('yes'));
$formcancel = new single_button(new moodle_url('user_bulk.php'), get_string('no'), 'get');
$formcontinue = new single_button(new moodle_url('user_bulk_confirm.php',
['confirm' => 1, 'returnurl' => $returnurl]), get_string('yes'));
$formcancel = new single_button($return, get_string('no'), 'get');
echo $OUTPUT->confirm(get_string('confirmcheckfull', '', $usernames), $formcontinue, $formcancel);
}

View File

@ -11,7 +11,8 @@ $confirm = optional_param('confirm', 0, PARAM_BOOL);
admin_externalpage_setup('userbulk');
require_capability('moodle/user:delete', context_system::instance());
$return = $CFG->wwwroot.'/'.$CFG->admin.'/user/user_bulk.php';
$returnurl = optional_param('returnurl', '', PARAM_LOCALURL);
$return = new moodle_url($returnurl ?: '/admin/user/user_bulk.php');
if (empty($SESSION->bulk_users)) {
redirect($return);
@ -43,7 +44,7 @@ if ($confirm and confirm_sesskey()) {
} else {
echo $OUTPUT->notification(get_string('changessaved'), 'notifysuccess');
}
$continue = new single_button(new moodle_url($return), get_string('continue'), 'post');
$continue = new single_button($return, get_string('continue'), 'post');
echo $OUTPUT->render($continue);
echo $OUTPUT->box_end();
} else {
@ -51,8 +52,9 @@ if ($confirm and confirm_sesskey()) {
$userlist = $DB->get_records_select_menu('user', "id $in", $params, 'fullname', 'id,'.$DB->sql_fullname().' AS fullname');
$usernames = implode(', ', $userlist);
echo $OUTPUT->heading(get_string('confirmation', 'admin'));
$formcontinue = new single_button(new moodle_url('user_bulk_delete.php', array('confirm' => 1)), get_string('yes'));
$formcancel = new single_button(new moodle_url('user_bulk.php'), get_string('no'), 'get');
$formcontinue = new single_button(new moodle_url('user_bulk_delete.php',
['confirm' => 1, 'returnurl' => $returnurl]), get_string('yes'));
$formcancel = new single_button($return, get_string('no'), 'get');
echo $OUTPUT->confirm(get_string('deletecheckfull', '', $usernames), $formcontinue, $formcancel);
}

View File

@ -8,7 +8,8 @@ $dir = optional_param('dir', 'asc', PARAM_ALPHA);
admin_externalpage_setup('userbulk');
$return = $CFG->wwwroot.'/'.$CFG->admin.'/user/user_bulk.php';
$returnurl = optional_param('returnurl', '', PARAM_LOCALURL);
$return = new moodle_url($returnurl ?: '/admin/user/user_bulk.php');
if (empty($SESSION->bulk_users)) {
redirect($return);

View File

@ -34,8 +34,11 @@ $dataformat = optional_param('dataformat', '', PARAM_ALPHA);
admin_externalpage_setup('userbulk');
require_capability('moodle/user:update', context_system::instance());
$returnurl = optional_param('returnurl', '', PARAM_LOCALURL);
$return = new moodle_url($returnurl ?: '/admin/user/user_bulk.php');
if (empty($SESSION->bulk_users)) {
redirect(new moodle_url('/admin/user/user_bulk.php'));
redirect($return);
}
if ($dataformat) {

View File

@ -12,7 +12,8 @@ $confirm = optional_param('confirm', 0, PARAM_BOOL);
admin_externalpage_setup('userbulk');
require_capability('moodle/user:update', context_system::instance());
$return = $CFG->wwwroot.'/'.$CFG->admin.'/user/user_bulk.php';
$returnurl = optional_param('returnurl', '', PARAM_LOCALURL);
$return = new moodle_url($returnurl ?: '/admin/user/user_bulk.php');
if (empty($SESSION->bulk_users)) {
redirect($return);
@ -62,8 +63,9 @@ if ($confirm and confirm_sesskey()) {
$usernames .= ', ...';
}
echo $OUTPUT->heading(get_string('confirmation', 'admin'));
$formcontinue = new single_button(new moodle_url('/admin/user/user_bulk_forcepasswordchange.php', array('confirm' => 1)), get_string('yes'));
$formcancel = new single_button(new moodle_url('/admin/user/user_bulk.php'), get_string('no'), 'get');
$formcontinue = new single_button(new moodle_url('/admin/user/user_bulk_forcepasswordchange.php',
['confirm' => 1, 'returnurl' => $returnurl]), get_string('yes'));
$formcancel = new single_button($return, get_string('no'), 'get');
echo $OUTPUT->confirm(get_string('forcepasswordchangecheckfull', '', $usernames), $formcontinue, $formcancel);
}

View File

@ -35,6 +35,9 @@ require_once($CFG->libdir.'/datalib.php');
*/
class user_bulk_action_form extends moodleform {
/** @var bool */
protected $hasbulkactions = false;
/**
* Returns an array of action_link's of all bulk actions available for this user.
*
@ -44,6 +47,8 @@ class user_bulk_action_form extends moodleform {
global $CFG;
$canaccessbulkactions = has_any_capability(['moodle/user:update', 'moodle/user:delete'], context_system::instance());
$syscontext = context_system::instance();
$actions = [];
if (has_capability('moodle/user:update', $syscontext)) {
@ -51,7 +56,7 @@ class user_bulk_action_form extends moodleform {
new moodle_url('/admin/user/user_bulk_confirm.php'),
get_string('confirm'));
}
if (has_capability('moodle/site:readallmessages', $syscontext) && !empty($CFG->messaging)) {
if ($canaccessbulkactions && has_capability('moodle/site:readallmessages', $syscontext) && !empty($CFG->messaging)) {
$actions['message'] = new action_link(
new moodle_url('/admin/user/user_bulk_message.php'),
get_string('messageselectadd'));
@ -61,9 +66,11 @@ class user_bulk_action_form extends moodleform {
new moodle_url('/admin/user/user_bulk_delete.php'),
get_string('delete'));
}
$actions['displayonpage'] = new action_link(
if ($canaccessbulkactions) {
$actions['displayonpage'] = new action_link(
new moodle_url('/admin/user/user_bulk_display.php'),
get_string('displayonpage'));
}
if (has_capability('moodle/user:update', $syscontext)) {
$actions['download'] = new action_link(
@ -76,7 +83,7 @@ class user_bulk_action_form extends moodleform {
new moodle_url('/admin/user/user_bulk_forcepasswordchange.php'),
get_string('forcepasswordchange'));
}
if (has_capability('moodle/cohort:assign', $syscontext)) {
if ($canaccessbulkactions && has_capability('moodle/cohort:assign', $syscontext)) {
$actions['addtocohort'] = new action_link(
new moodle_url('/admin/user/user_bulk_cohortadd.php'),
get_string('bulkadd', 'core_cohort'));
@ -93,6 +100,15 @@ class user_bulk_action_form extends moodleform {
}
}
// This method may be called from 'Bulk actions' and 'Browse user list' pages. Some actions
// may be irrelevant in one of the contexts and they can be excluded by specifying the
// 'excludeactions' customdata.
$excludeactions = $this->_customdata['excludeactions'] ?? [];
foreach ($excludeactions as $excludeaction) {
unset($actions[$excludeaction]);
}
$this->hasbulkactions = !empty($actions);
return $actions;
}
@ -101,20 +117,42 @@ class user_bulk_action_form extends moodleform {
* Form definition
*/
public function definition() {
global $CFG;
$mform =& $this->_form;
$mform->addElement('hidden', 'returnurl');
$mform->setType('returnurl', PARAM_LOCALURL);
// When 'passuserids' is specified in the customdata, the user ids are expected in the form
// data rather than in the $SESSION->bulk_users .
$passuserids = !empty($this->_customdata['passuserids']);
$mform->addElement('hidden', 'passuserids', $passuserids);
$mform->setType('passuserids', PARAM_BOOL);
$mform->addElement('hidden', 'userids');
$mform->setType('userids', PARAM_SEQUENCE);
$actions = [0 => get_string('choose') . '...'];
$bulkactions = $this->get_actions();
foreach ($bulkactions as $key => $action) {
$actions[$key] = $action->text;
}
$objs = array();
$objs[] =& $mform->createElement('select', 'action', null, $actions);
$objs[] =& $mform->createElement('submit', 'doaction', get_string('go'));
$objs[] = $selectel = $mform->createElement('select', 'action', get_string('userbulk', 'admin'), $actions);
$selectel->setHiddenLabel(true);
if (empty($this->_customdata['hidesubmit'])) {
$objs[] =& $mform->createElement('submit', 'doaction', get_string('go'));
}
$mform->addElement('group', 'actionsgrp', get_string('withselectedusers'), $objs, ' ', false);
}
/**
* Is there at least one available bulk action in this form
*
* @return bool
*/
public function has_bulk_actions(): bool {
return $this->hasbulkactions;
}
}
/**

View File

@ -10,7 +10,8 @@ $confirm = optional_param('confirm', 0, PARAM_BOOL);
admin_externalpage_setup('userbulk');
require_capability('moodle/site:manageallmessaging', context_system::instance());
$return = $CFG->wwwroot.'/'.$CFG->admin.'/user/user_bulk.php';
$returnurl = optional_param('returnurl', '', PARAM_LOCALURL);
$return = new moodle_url($returnurl ?: '/admin/user/user_bulk.php');
if (empty($SESSION->bulk_users)) {
redirect($return);
@ -39,6 +40,7 @@ if ($confirm and !empty($msg) and confirm_sesskey()) {
}
$msgform = new user_message_form('user_bulk_message.php');
$msgform->set_data(['returnurl' => $returnurl]);
if ($msgform->is_cancelled()) {
redirect($return);
@ -58,8 +60,10 @@ if ($msgform->is_cancelled()) {
echo $OUTPUT->heading(get_string('confirmation', 'admin'));
echo $OUTPUT->box($msg, 'boxwidthnarrow boxaligncenter generalbox', 'preview'); //TODO: clean once we start using proper text formats here
$formcontinue = new single_button(new moodle_url('user_bulk_message.php', array('confirm' => 1, 'msg' => $msg)), get_string('yes')); //TODO: clean once we start using proper text formats here
$formcancel = new single_button(new moodle_url('user_bulk.php'), get_string('no'), 'get');
$formcontinue = new single_button(new moodle_url('user_bulk_message.php',
['confirm' => 1, 'msg' => $msg, 'returnurl' => $returnurl]),
get_string('yes')); // TODO: clean once we start using proper text formats here.
$formcancel = new single_button($return, get_string('no'), 'get');
echo $OUTPUT->confirm(get_string('confirmmessage', 'bulkusers', $usernames), $formcontinue, $formcancel);
echo $OUTPUT->footer();
die;

View File

@ -12,6 +12,8 @@ class user_message_form extends moodleform {
$mform =& $this->_form;
$mform->addElement('header', 'general', get_string('message', 'message'));
$mform->addElement('hidden', 'returnurl');
$mform->setType('returnurl', PARAM_LOCALURL);
$mform->addElement('editor', 'messagebody', get_string('messagebody'), null, null);
$mform->addRule('messagebody', '', 'required', null, 'server');

View File

@ -100,6 +100,24 @@ Feature: Add cohorts of users
And the "Current users" select box should contain "Forth User (forth@example.com)"
And the "Current users" select box should not contain "First User (first@example.com)"
@javascript
Scenario: Add users to a cohort using a user list bulk action
When I navigate to "Users > Accounts > Browse list of users" in site administration
And I click on "Third User" "checkbox"
And I click on "Forth User" "checkbox"
And I set the field "Bulk user actions" to "Add to cohort"
And I set the field "Cohort" to "Test cohort name [333]"
And I press "Add to cohort"
And I should see "Browse list of users"
And I navigate to "Users > Accounts > Cohorts" in site administration
Then the following should exist in the "reportbuilder-table" table:
| Name | Cohort size |
| Test cohort name | 2 |
And I press "Assign" action in the "Test cohort name" report row
And the "Current users" select box should contain "Third User (third@example.com)"
And the "Current users" select box should contain "Forth User (forth@example.com)"
And the "Current users" select box should not contain "First User (first@example.com)"
@javascript
Scenario: Edit cohort name in-place
When I navigate to "Users > Accounts > Cohorts" in site administration

View File

@ -64,6 +64,8 @@ information provided here is intended especially for developers.
- $numtasks
- $mode
* Removed \zip_writer::sanitise_filepath and \zipwriter::sanitise_filename as they are now automatically sanitised in the zipstream.
* Plugins implementing callback `bulk_user_actions()` should be aware that bulk user actions can be executed
from /admin/user.php as well as from the bulk actions page. The 'returnurl' parameter will be passed in the request.
=== 4.3 ===

View File

@ -57,6 +57,22 @@ Feature: Deleting users
And the "Available" select box should not contain "User Three"
And the "Available" select box should contain "User One"
@javascript
Scenario: Deleting users from bulk actions in the user list
When I log in as "admin"
And I navigate to "Users > Accounts > Browse list of users" in site administration
And I click on "User Four" "checkbox"
And I click on "User Three" "checkbox"
And I set the field "Bulk user actions" to "Delete"
And I should see "Are you absolutely sure you want to completely delete the user User Four, User Three, including their enrolments, activity and other user data?"
And I press "Yes"
And I should see "Changes saved"
And I press "Continue"
And I should see "Browse list of users"
And I should not see "User Four"
And I should not see "User Three"
And I should see "User One"
@javascript @core_message
Scenario: Deleting users who have unread messages sent or received
When I log in as "user1"