language: php
-dist: trusty
+dist: xenial
+
+services:
+ - mysql
php:
# We only run the highest and lowest supported versions to reduce the load on travis-ci.org.
addons:
postgresql: "9.6"
- packages:
- - mysql-server-5.6
- - mysql-client-core-5.6
- - mysql-client-5.6
env:
# Although we want to run these jobs and see failures as quickly as possible, we also want to get the slowest job to
then
sudo mkdir /mnt/ramdisk
sudo mount -t tmpfs -o size=1024m tmpfs /mnt/ramdisk
- sudo stop mysql
+ sudo service mysql stop
sudo mv /var/lib/mysql /mnt/ramdisk
sudo ln -s /mnt/ramdisk/mysql /var/lib/mysql
- sudo start mysql
+ sudo service mysql restart
fi
- >
if [ "$DB" = 'pgsql' ];
if (optional_param('submit', false, PARAM_BOOL) && data_submitted() && confirm_sesskey()) {
$controller->process_submission();
- $event = null;
- // Create event depending on mode.
- switch ($mode) {
- case 'assign':
- $event = \core\event\role_allow_assign_updated::create(array('context' => $syscontext));
- break;
- case 'override':
- $event = \core\event\role_allow_override_updated::create(array('context' => $syscontext));
- break;
- case 'switch':
- $event = \core\event\role_allow_switch_updated::create(array('context' => $syscontext));
- break;
- case 'view':
- $event = \core\event\role_allow_view_updated::create(array('context' => $syscontext));
- break;
- }
- if ($event) {
- $event->trigger();
- }
redirect($baseurl);
}
public function get_intro_text() {
return get_string('configallowassign', 'core_admin');
}
+
+ protected function get_eventclass() {
+ return \core\event\role_allow_assign_updated::class;
+ }
}
public function get_intro_text() {
return get_string('configallowoverride2', 'core_admin');
}
+
+ protected function get_eventclass() {
+ return \core\event\role_allow_override_updated::class;
+ }
}
*/
public function process_submission() {
global $DB;
+
+ $context = context_system::instance();
+ $this->load_current_settings();
+
// Delete all records, then add back the ones that should be allowed.
$DB->delete_records($this->tablename);
foreach ($this->roles as $fromroleid => $notused) {
foreach ($this->roles as $targetroleid => $alsonotused) {
+ $isallowed = $this->allowed[$fromroleid][$targetroleid];
if (optional_param('s_' . $fromroleid . '_' . $targetroleid, false, PARAM_BOOL)) {
$this->set_allow($fromroleid, $targetroleid);
+ // Only trigger events if this role allow relationship did not exist and the checkbox element
+ // has been submitted.
+ if (!$isallowed) {
+ $eventclass = $this->get_eventclass();
+ $eventclass::create([
+ 'context' => $context,
+ 'objectid' => $fromroleid,
+ 'other' => ['targetroleid' => $targetroleid, 'allow' => true]
+ ])->trigger();
+ }
+ } else if ($isallowed) {
+ // When the user has deselect an existing role allow checkbox but it is in the list of roles
+ // allowances.
+ $eventclass = $this->get_eventclass();
+ $eventclass::create([
+ 'context' => $context,
+ 'objectid' => $fromroleid,
+ 'other' => ['targetroleid' => $targetroleid, 'allow' => false]
+ ])->trigger();
}
}
}
* @return string
*/
public abstract function get_intro_text();
+
+ /**
+ * Returns the allow class respective event class name.
+ * @return string
+ */
+ protected abstract function get_eventclass();
}
public function get_intro_text() {
return get_string('configallowswitch', 'core_admin');
}
+
+ protected function get_eventclass() {
+ return \core\event\role_allow_switch_updated::class;
+ }
}
public function get_intro_text() {
return get_string('configallowview', 'core_admin');
}
+
+ protected function get_eventclass() {
+ return \core\event\role_allow_view_updated::class;
+ }
}
}
public function save_changes() {
- global $DB;
+ global $DB, $USER;
if (!$this->roleid) {
// Creating role.
// Updating role.
$DB->update_record('role', $this->role);
+ // Trigger role updated event.
+ \core\event\role_updated::create([
+ 'userid' => $USER->id,
+ 'objectid' => $this->role->id,
+ 'context' => $this->context,
+ 'other' => [
+ 'name' => $this->role->name,
+ 'shortname' => $this->role->shortname,
+ 'description' => $this->role->description,
+ 'archetype' => $this->role->archetype,
+ 'contextlevels' => $this->contextlevels
+ ]
+ ])->trigger();
+
// This will ensure the course contacts cache is purged so name changes get updated in
// the UI. It would be better to do this only when we know that fields affected are
// updated. But thats getting into the weeds of the coursecat cache and role edits
$addfunction = "core_role_set_{$type}_allowed";
$deltable = 'role_allow_'.$type;
$field = 'allow'.$type;
+ $eventclass = "\\core\\event\\role_allow_" . $type . "_updated";
+ $context = context_system::instance();
foreach ($current as $roleid) {
if (!in_array($roleid, $wanted)) {
$DB->delete_records($deltable, array('roleid'=>$this->roleid, $field=>$roleid));
+ $eventclass::create([
+ 'context' => $context,
+ 'objectid' => $this->roleid,
+ 'other' => ['targetroleid' => $roleid, 'allow' => false]
+ ])->trigger();
continue;
}
$key = array_search($roleid, $wanted);
$roleid = $this->roleid;
}
$addfunction($this->roleid, $roleid);
+
+ if (in_array($roleid, $wanted)) {
+ $eventclass::create([
+ 'context' => $context,
+ 'objectid' => $this->roleid,
+ 'other' => ['targetroleid' => $roleid, 'allow' => true]
+ ])->trigger();
+ }
}
}
if (optional_param('savechanges', false, PARAM_BOOL) && confirm_sesskey() && $definitiontable->is_submission_valid()) {
$definitiontable->save_changes();
$tableroleid = $definitiontable->get_role_id();
- // Trigger event.
- $event = \core\event\role_capabilities_updated::create(
- array(
- 'context' => $systemcontext,
- 'objectid' => $tableroleid
- )
- );
- $event->set_legacy_logdata(array(SITEID, 'role', $action, 'admin/roles/define.php?action=view&roleid=' . $tableroleid,
- $definitiontable->get_role_name(), '', $USER->id));
- if (!empty($role)) {
- $event->add_record_snapshot('role', $role);
- }
- $event->trigger();
if ($action === 'add') {
redirect(new moodle_url('/admin/roles/define.php', array('action'=>'view', 'roleid'=>$definitiontable->get_role_id())));
if (optional_param('savechanges', false, PARAM_BOOL) && confirm_sesskey()) {
$overridestable->save_changes();
$rolename = $overridableroles[$roleid];
- // Trigger event.
- $event = \core\event\role_capabilities_updated::create(
- array(
- 'context' => $context,
- 'objectid' => $roleid,
- )
- );
-
- $event->set_legacy_logdata(
- array(
- $course->id, 'role', 'override', 'admin/roles/override.php?contextid=' . $context->id . '&roleid=' . $roleid,
- $rolename, '', $USER->id
- )
- );
- $event->add_record_snapshot('role', $role);
- $event->trigger();
redirect($returnurl);
}
}
if (!empty($data->sensitivedatareasons) && is_array($data->sensitivedatareasons)) {
$data->sensitivedatareasons = implode(',', $data->sensitivedatareasons);
+ } else {
+ // Nothing selected. Set default value of null.
+ $data->sensitivedatareasons = null;
}
// A single value.
module.exports = ({ template, types }) => {
const fs = require('fs');
+ const path = require('path');
const glob = require('glob');
const cwd = process.cwd();
var rawContents = fs.readFileSync(file);
var subplugins = JSON.parse(rawContents);
- for (const [component, path] of Object.entries(subplugins)) {
+ for (const [component, path] of Object.entries(subplugins.plugintypes)) {
if (path) {
moodlePlugins[path] = component;
}
*/
function getModuleNameFromFileName(searchFileName) {
searchFileName = fs.realpathSync(searchFileName);
- const relativeFileName = searchFileName.replace(`${cwd}/`, '');
+ const relativeFileName = searchFileName.replace(`${cwd}${path.sep}`, '').replace(/\\/g, '/');
const [componentPath, file] = relativeFileName.split('/amd/src/');
const fileName = file.replace('.js', '');
}
}
};
-};
\ No newline at end of file
+};
redirect($returnurl);
} else if ($data = $mform->get_data()) {
+ if ($data->duration && $data->timeend == 0) {
+ $data->timeend = $data->timestart + $data->duration;
+ }
if ($manager->edit_enrolment($ue, $data)) {
redirect($returnurl);
}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
+use core_enrol\enrol_helper;
+
defined('MOODLE_INTERNAL') || die();
require_once("$CFG->libdir/formslib.php");
$instancename = $this->_customdata['enrolinstancename'];
$modal = !empty($this->_customdata['modal']);
+ $periodmenu = enrol_get_period_list();
+ $duration = enrol_calculate_duration($ue->timestart, $ue->timeend);
+
$mform->addElement('static', 'enrolmentmethod', get_string('enrolmentmethod', 'enrol'), $instancename);
$options = array(ENROL_USER_ACTIVE => get_string('participationactive', 'enrol'),
$mform->addElement('date_time_selector', 'timestart', get_string('enroltimestart', 'enrol'), array('optional' => true));
+ $mform->addElement('select', 'duration', get_string('enrolperiod', 'enrol'), $periodmenu);
+ $mform->setDefault('duration', $duration);
+ $mform->disabledIf('duration', 'timestart[enabled]', 'notchecked', 1);
+ $mform->disabledIf('duration', 'timeend[enabled]', 'checked', 1);
+
$mform->addElement('date_time_selector', 'timeend', get_string('enroltimeend', 'enrol'), array('optional' => true));
$mform->addElement('static', 'timecreated', get_string('enroltimecreated', 'enrol'), userdate($ue->timecreated));
require_once($CFG->dirroot.'/group/lib.php');
require_once($CFG->dirroot.'/enrol/manual/locallib.php');
require_once($CFG->dirroot.'/cohort/lib.php');
+require_once($CFG->dirroot . '/enrol/manual/classes/enrol_users_form.php');
$id = required_param('id', PARAM_INT); // Course id.
$action = required_param('action', PARAM_ALPHANUMEXT);
$duration = optional_param('duration', 0, PARAM_INT);
$startdate = optional_param('startdate', 0, PARAM_INT);
$recovergrades = optional_param('recovergrades', 0, PARAM_INT);
+ $timeend = optional_param_array('timeend', [], PARAM_INT);
if (empty($roleid)) {
$roleid = null;
$timestart = $today;
break;
}
- if ($duration <= 0) {
+ if ($timeend) {
+ $timeend = make_timestamp($timeend['year'], $timeend['month'], $timeend['day'], $timeend['hour'], $timeend['minute']);
+ } else if ($duration <= 0) {
$timeend = 0;
} else {
$timeend = $timestart + $duration;
}
+ $mform = new enrol_manual_enrol_users_form(null, (object)["context" => $context]);
+ $userenroldata = [
+ 'startdate' => $timestart,
+ 'timeend' => $timeend,
+ ];
+ $mform->set_data($userenroldata);
+ $validationerrors = $mform->validation($userenroldata, null);
+ if (!empty($validationerrors)) {
+ throw new enrol_ajax_exception('invalidenrolduration');
+ }
+
$instances = $manager->get_enrolment_instances();
$plugins = $manager->get_enrolment_plugins(true); // Do not allow actions on disabled plugins.
if (!array_key_exists($enrolid, $instances)) {
$mform = $this->_form;
$mform->setDisableShortforms();
$mform->disable_form_change_checker();
- // Build the list of options for the enrolment period dropdown.
- $unlimitedperiod = get_string('unlimited');
- $periodmenu = array();
- $periodmenu[''] = $unlimitedperiod;
- for ($i=1; $i<=365; $i++) {
- $seconds = $i * 86400;
- $periodmenu[$seconds] = get_string('numdays', '', $i);
- }
+ $periodmenu = enrol_get_period_list();
// Work out the apropriate default settings.
$defaultperiod = $instance->enrolperiod;
if ($instance->enrolperiod > 0 && !isset($periodmenu[$instance->enrolperiod])) {
$mform->addElement('checkbox', 'recovergrades', get_string('recovergrades', 'enrol'));
$mform->setAdvanced('recovergrades');
$mform->setDefault('recovergrades', $CFG->recovergradesdefault);
- $mform->addElement('select', 'duration', get_string('defaultperiod', 'enrol_manual'), $periodmenu);
- $mform->setDefault('duration', $defaultperiod);
- $mform->setAdvanced('duration');
$mform->addElement('select', 'startdate', get_string('startingfrom'), $basemenu);
$mform->setDefault('startdate', $extendbase);
$mform->setAdvanced('startdate');
-
+ $mform->addElement('select', 'duration', get_string('enrolperiod', 'enrol'), $periodmenu);
+ $mform->setDefault('duration', $defaultperiod);
+ $mform->setAdvanced('duration');
+ $mform->disabledIf('duration', 'timeend[enabled]', 'checked', 1);
+ $mform->addElement('date_time_selector', 'timeend', get_string('enroltimeend', 'enrol'), ['optional' => true]);
+ $mform->setAdvanced('timeend');
$mform->addElement('hidden', 'id', $course->id);
$mform->setType('id', PARAM_INT);
$mform->addElement('hidden', 'action', 'enrol');
$mform->addElement('hidden', 'enrolid', $instance->id);
$mform->setType('enrolid', PARAM_INT);
}
+
+ /**
+ * Validate the submitted form data.
+ *
+ * @param array $data array of ("fieldname"=>value) of submitted data
+ * @param array $files array of uploaded files "element_name"=>tmp_file_path
+ * @return array of "element_name"=>"error_description" if there are errors,
+ * or an empty array if everything is OK (true allowed for backwards compatibility too).
+ */
+ public function validation($data, $files) {
+ $errors = parent::validation($data, $files);
+ if (!empty($data['startdate']) && !empty($data['timeend'])) {
+ if ($data['startdate'] >= $data['timeend']) {
+ $errors['timeend'] = get_string('enroltimeendinvalid', 'enrol');
+ }
+ }
+ return $errors;
+ }
}
$roleid = optional_param('roleid', -1, PARAM_INT);
$extendperiod = optional_param('extendperiod', 0, PARAM_INT);
$extendbase = optional_param('extendbase', 0, PARAM_INT);
+$timeend = optional_param_array('timeend', [], PARAM_INT);
$instance = $DB->get_record('enrol', array('id'=>$enrolid, 'enrol'=>'manual'), '*', MUST_EXIST);
$course = $DB->get_record('course', array('id'=>$instance->courseid), '*', MUST_EXIST);
break;
}
- if ($extendperiod <= 0) {
+ if ($timeend) {
+ $timeend = make_timestamp($timeend['year'], $timeend['month'], $timeend['day'], $timeend['hour'],
+ $timeend['minute']);
+ } else if ($extendperiod <= 0) {
$timeend = 0;
} else {
$timeend = $timestart + $extendperiod;
| student098 | Student | 098 | student098@example.com |
| student099 | Student | 099 | student099@example.com |
And the following "courses" exist:
- | fullname | shortname |
- | Course 001 | C001 |
+ | fullname | shortname | format | startdate |
+ | Course 001 | C001 | weeks | ##1 month ago## |
And the following "course enrolments" exist:
- | user | course | role |
- | teacher001 | C001 | editingteacher |
+ | user | course | role | timestart |
+ | teacher001 | C001 | editingteacher | ##1 month ago## |
And I log in as "teacher001"
And I am on "Course 001" course homepage
When I set the field "Select users" to "student100@example.com"
And I click on ".form-autocomplete-downarrow" "css_element" in the "Select users" "form_row"
Then I should see "student100@example.com, 1234567892, 1234567893, ABC1, ABC2"
+
+ @javascript
+ Scenario: Enrol user from participants page
+ Given I navigate to course participants
+ # Enrol user to course
+ And I press "Enrol users"
+ And I set the field "Select users" to "example.com"
+ And I expand the "Select users" autocomplete
+ When I click on "Student 099" item in the autocomplete list
+ Then I should see "Student 099" in the list of options for the "Select users" autocomplete
+ And I click on "Show more" "button"
+ # Fill data to input duration
+ And "input[name='timeend[enabled]'][checked=checked]" "css_element" should not exist
+ And the "Enrolment duration" "select" should be enabled
+ And I set the field "duration" to "2"
+ # Fill data to input end time
+ And I set the field "Starting from" to "2"
+ And I set the field "timeend[enabled]" to "1"
+ And I set the field "timeend[day]" to "10"
+ And the "Enrolment duration" "select" should be disabled
+ And I click on "Enrol users" "button" in the "Enrol users" "dialogue"
+ And I am on "Course 001" course homepage
+ And I navigate to course participants
+ And I should see "Student 099" in the "participants" "table"
+ And I click on "Edit enrolment" "icon" in the "Student 099" "table_row"
+ And the field "timeend[day]" matches value "10"
+
+ @javascript
+ Scenario: Update Enrol user
+ Given I am on "Course 001" course homepage
+ And I navigate to course participants
+ When I click on "Edit enrolment" "icon" in the "Teacher 001" "table_row"
+ Then the "Enrolment duration" "select" should be enabled
+ # Fill duration
+ And "input[name='timeend[enabled]'][checked=checked]" "css_element" should not exist
+ And the "Enrolment duration" "select" should be enabled
+ And I set the field "duration" to "2"
+ # Fill end time
+ And I set the field "timeend[enabled]" to "1"
+ And I set the field "timeend[day]" to "28"
+ And the "Enrolment duration" "select" should be disabled
+ And I press "Save changes"
+ And I click on "Edit enrolment" "icon" in the "Teacher 001" "table_row"
+ And the field "timeend[day]" matches value "28"
$this->assertArrayHasKey($roles['student'], $return[$user2->id]);
$this->assertArrayNotHasKey($roles['teacher'], $return[$user2->id]);
}
+
+ /**
+ * Test enrol_calculate_duration function
+ */
+ public function test_enrol_calculate_duration() {
+ // Start time 07/01/2019 @ 12:00am (UTC).
+ $timestart = 1561939200;
+ // End time 07/05/2019 @ 12:00am (UTC).
+ $timeend = 1562284800;
+ $duration = enrol_calculate_duration($timestart, $timeend);
+ $durationinday = $duration / DAYSECS;
+ $this->assertEquals(4, $durationinday);
+
+ // End time 07/10/2019 @ 12:00am (UTC).
+ $timeend = 1562716800;
+ $duration = enrol_calculate_duration($timestart, $timeend);
+ $durationinday = $duration / DAYSECS;
+ $this->assertEquals(9, $durationinday);
+ }
}
error:nogroups,core_badges
purgedefinitionsuccess,core_cache
purgestoresuccess,core_cache
+eventrolecapabilitiesupdated,core_role
\ No newline at end of file
$string['instanceeditselfwarning'] = 'Warning:';
$string['instanceeditselfwarningtext'] = 'You are enrolled into this course through this enrolment method, changes may affect your access to this course.';
$string['invalidenrolinstance'] = 'Invalid enrolment instance';
+$string['invalidenrolduration'] = 'Invalid enrolment duration';
$string['invalidrole'] = 'Invalid role';
$string['invalidrequest'] = 'Invalid request';
$string['manageenrols'] = 'Manage enrol plugins';
$string['errorexistsrolename'] = 'Role name already exists';
$string['errorexistsroleshortname'] = 'Role name already exists';
$string['errorroleshortnametoolong'] = 'The short name must not exceed 100 characters';
+$string['eventcapabilityassigned'] = 'Capability assigned';
+$string['eventcapabilityunassigned'] = 'Capability unassigned';
$string['eventroleallowassignupdated'] = 'Allow role assignment';
$string['eventroleallowoverrideupdated'] = 'Allow role override';
$string['eventroleallowswitchupdated'] = 'Allow role switch';
$string['eventroleallowviewupdated'] = 'Allow role view';
$string['eventroleassigned'] = 'Role assigned';
-$string['eventrolecapabilitiesupdated'] = 'Role capabilities updated';
$string['eventroledeleted'] = 'Role deleted';
$string['eventroleunassigned'] = 'Role unassigned';
+$string['eventroleupdated'] = 'Role updated';
$string['existingadmins'] = 'Current site administrators';
$string['existingusers'] = '{$a} existing users';
$string['explanation'] = 'Explanation';
$string['privacy:metadata:role_capabilities:timemodified'] = 'The date when the capability was created or modified.';
$string['privacy:metadata:role_cohortroles'] = 'Roles to cohort';
$string['course:togglecompletion'] = 'Manually mark activities as complete';
+
+// Deprecated since Moodle 3.8.
+$string['eventrolecapabilitiesupdated'] = 'Role capabilities updated';
\ No newline at end of file
}
}
+ // Trigger capability_assigned event.
+ \core\event\capability_assigned::create([
+ 'userid' => $cap->modifierid,
+ 'context' => $context,
+ 'objectid' => $roleid,
+ 'other' => [
+ 'capability' => $capability,
+ 'oldpermission' => $existing->permission ?? CAP_INHERIT,
+ 'permission' => $permission
+ ]
+ ])->trigger();
+
// Reset any cache of this role, including MUC.
accesslib_clear_role_cache($roleid);
* @return boolean true or exception
*/
function unassign_capability($capability, $roleid, $contextid = null) {
- global $DB;
+ global $DB, $USER;
// Capability must exist.
if (!$capinfo = get_capability_info($capability)) {
$DB->delete_records('role_capabilities', array('capability'=>$capability, 'roleid'=>$roleid));
}
+ // Trigger capability_assigned event.
+ \core\event\capability_unassigned::create([
+ 'userid' => $USER->id,
+ 'context' => $context ?? context_system::instance(),
+ 'objectid' => $roleid,
+ 'other' => [
+ 'capability' => $capability,
+ ]
+ ])->trigger();
+
// Reset any cache of this role, including MUC.
accesslib_clear_role_cache($roleid);
--- /dev/null
+<?php
+// 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/>.
+
+/**
+ * Capability assigned event.
+ *
+ * @package core
+ * @since Moodle 3.8
+ * @copyright 2019 Simey Lameze <simey@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Capability assigned event class.
+ *
+ * @package core
+ * @since Moodle 3.8
+ * @copyright 2019 Simey Lameze <simey@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class capability_assigned extends base {
+ /**
+ * Initialise event parameters.
+ */
+ protected function init() {
+ $this->data['objecttable'] = 'role_capabilities';
+ $this->data['crud'] = 'u';
+ $this->data['edulevel'] = self::LEVEL_OTHER;
+ }
+
+ /**
+ * Returns localised event name.
+ *
+ * @return string
+ */
+ public static function get_name() {
+ return get_string('eventcapabilityassigned', 'role');
+ }
+
+ /**
+ * Returns non-localised event description with id's for admin use only.
+ *
+ * @return string
+ */
+ public function get_description() {
+
+ $strpermissions = [
+ CAP_INHERIT => get_string('notset', 'role'),
+ CAP_ALLOW => get_string('allow', 'role'),
+ CAP_PREVENT => get_string('prevent', 'role'),
+ CAP_PROHIBIT => get_string('prohibit', 'role')
+ ];
+
+ $capability = $this->other['capability'];
+ $oldpermission = $this->other['oldpermission'];
+ $permission = $this->other['permission'];
+
+ if ($oldpermission == CAP_INHERIT && $permission == CAP_ALLOW) {
+ $description = "The user id '$this->userid' assigned the '$capability' capability for " .
+ "role '$this->objectid' with '$strpermissions[$permission]' permission";
+ } else {
+ $description = "The user id '$this->userid' changed the '$capability' capability permission for " .
+ "role '$this->objectid' from '$strpermissions[$oldpermission]' to '$strpermissions[$permission]'";
+ }
+
+ return $description;
+ }
+
+ /**
+ * Returns relevant URL.
+ *
+ * @return \moodle_url
+ */
+ public function get_url() {
+ if ($this->contextlevel == CONTEXT_SYSTEM) {
+ return new \moodle_url('/admin/roles/define.php', ['action' => 'edit', 'roleid' => $this->objectid]);
+ } else {
+ return new \moodle_url('/admin/roles/override.php', ['contextid' => $this->contextid,
+ 'roleid' => $this->objectid]);
+ }
+ }
+}
--- /dev/null
+<?php
+// 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/>.
+
+/**
+ * Capability unassigned event.
+ *
+ * @package core
+ * @since Moodle 3.8
+ * @copyright 2019 Simey Lameze <simey@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Capability unassigned event class.
+ *
+ * @package core
+ * @since Moodle 3.8
+ * @copyright 2019 Simey Lameze <simey@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class capability_unassigned extends base {
+ /**
+ * Initialise event parameters.
+ */
+ protected function init() {
+ $this->data['objecttable'] = 'role_capabilities';
+ $this->data['crud'] = 'u';
+ $this->data['edulevel'] = self::LEVEL_OTHER;
+ }
+
+ /**
+ * Returns localised event name.
+ *
+ * @return string
+ */
+ public static function get_name() {
+ return get_string('eventcapabilityunassigned', 'role');
+ }
+
+ /**
+ * Returns non-localised event description with id's for admin use only.
+ *
+ * @return string
+ */
+ public function get_description() {
+ $capability = $this->other['capability'];
+
+ return "The user id id '$this->userid' has unassigned the '$capability' capability for role '$this->objectid'";
+ }
+
+ /**
+ * Returns relevant URL.
+ *
+ * @return \moodle_url
+ */
+ public function get_url() {
+ if ($this->contextlevel == CONTEXT_SYSTEM) {
+ return new \moodle_url('/admin/roles/define.php', ['action' => 'view', 'roleid' => $this->objectid]);
+ } else {
+ return new \moodle_url('/admin/roles/override.php', ['contextid' => $this->contextid,
+ 'roleid' => $this->objectid]);
+ }
+ }
+}
protected function init() {
$this->data['crud'] = 'u';
$this->data['edulevel'] = self::LEVEL_OTHER;
+ $this->data['objecttable'] = 'role_allow_assign';
}
/**
* @return string
*/
public function get_description() {
- return "The user with id '$this->userid' updated Allow role assignments.";
+ $action = ($this->other['allow']) ? 'allow' : 'stop allowing';
+ return "The user with id '$this->userid' modified the role with id '" . $this->other['targetroleid']
+ . "' to $action users with that role from assigning the role with id '" . $this->objectid . "' to users";
}
/**
protected function init() {
$this->data['crud'] = 'u';
$this->data['edulevel'] = self::LEVEL_OTHER;
+ $this->data['objecttable'] = 'role_allow_override';
}
/**
* @return string
*/
public function get_description() {
- return "The user with id '$this->userid' updated Allow role overrides.";
+ $action = ($this->other['allow']) ? 'allow' : 'stop allowing';
+ return "The user with id '$this->userid' modified the role with id '" . $this->other['targetroleid']
+ . "' to $action users with that role from overriding the role with id '" . $this->objectid . "' to users";
}
/**
protected function init() {
$this->data['crud'] = 'u';
$this->data['edulevel'] = self::LEVEL_OTHER;
+ $this->data['objecttable'] = 'role_allow_switch';
}
/**
* @return string
*/
public function get_description() {
- return "The user with id '$this->userid' updated Allow role switches.";
+ $action = ($this->other['allow']) ? 'allow' : 'stop allowing';
+ return "The user with id '$this->userid' modified the role with id '" . $this->other['targetroleid']
+ . "' to $action users with that role from switching the role with id '" . $this->objectid . "' to users";
}
/**
protected function init() {
$this->data['crud'] = 'u';
$this->data['edulevel'] = self::LEVEL_OTHER;
+ $this->data['objecttable'] = 'role_allow_view';
}
/**
* @return string
*/
public function get_description() {
- return "The user with id '$this->userid' updated Allow role views.";
+ $action = ($this->other['allow']) ? 'allow' : 'stop allowing';
+ return "The user with id '$this->userid' modified the role with id '" . $this->other['targetroleid']
+ . "' to $action users with that role from viewing the role with id '" . $this->objectid . "' to users";
}
/**
defined('MOODLE_INTERNAL') || die();
+debugging('core\\event\\role_capabilities_updated has been deprecated. Please use
+ core\\event\\capability_assigned instead', DEBUG_DEVELOPER);
+
/**
* Role updated event class.
*
public static function get_objectid_mapping() {
return array('db' => 'role', 'restore' => 'role');
}
+
+
+ /**
+ * This event has been deprecated.
+ *
+ * @return boolean
+ */
+ public static function is_deprecated() {
+ return true;
+ }
}
--- /dev/null
+<?php
+// 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/>.
+
+/**
+ * Role updated event.
+ *
+ * @package core
+ * @copyright 2019 Simey Lameze <simey@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Role updated event class.
+ *
+ * @package core
+ * @since Moodle 3.8
+ * @copyright 2019 Simey Lameze <simey@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class role_updated extends base {
+ /**
+ * Initialise event parameters.
+ */
+ protected function init() {
+ $this->data['objecttable'] = 'role';
+ $this->data['crud'] = 'u';
+ $this->data['edulevel'] = self::LEVEL_OTHER;
+ }
+
+ /**
+ * Returns localised event name.
+ *
+ * @return string
+ */
+ public static function get_name() {
+ return get_string('eventroleupdated', 'role');
+ }
+
+ /**
+ * Returns non-localised event description with id's for admin use only.
+ *
+ * @return string
+ */
+ public function get_description() {
+ return "The user with id '$this->userid' updated the role with id '$this->objectid'.";
+ }
+
+ /**
+ * Returns relevant URL.
+ *
+ * @return \moodle_url
+ */
+ public function get_url() {
+ return new \moodle_url('/admin/roles/define.php', ['action' => 'edit', 'roleid' => $this->objectid]);
+ }
+
+ /**
+ * Returns array of parameters to be passed to legacy add_to_log() function.
+ *
+ * @return array
+ */
+ protected function get_legacy_logdata() {
+ return [SITEID, 'role', 'update', 'admin/roles/manage.php?action=edit&roleid=' . $this->objectid,
+ $this->other['shortname'], ''];
+ }
+
+ /**
+ * Custom validation.
+ *
+ * @throws \coding_exception
+ * @return void
+ */
+ protected function validate_data() {
+ parent::validate_data();
+
+ if (!isset($this->other['shortname'])) {
+ throw new \coding_exception('The \'shortname\' value must be set in other.');
+ }
+ }
+}
// Get the requested template source.
$templatesource = $this->load($templatecomponent, $templatename, $themename, $includecomments);
// This is a helper function to save a value in one of the result arrays (either $templates or $strings).
- $save = function(array $results, array $seenlist, string $component, string $id, $value) {
+ $save = function(array $results, array $seenlist, string $component, string $id, $value) use ($lang) {
if (!isset($results[$component])) {
// If the results list doesn't already contain this component then initialise it.
$results[$component] = [];
};
// This is a helper function for processing a dependency. Does stuff like ignore duplicate processing,
// common result formatting etc.
- $handler = function(array $dependency, array $ignorelist, callable $processcallback) {
+ $handler = function(array $dependency, array $ignorelist, callable $processcallback) use ($lang) {
foreach ($dependency as $component => $ids) {
foreach ($ids as $id) {
$dependencyid = "$component/$id";
&$seenstrings,
&$templates,
&$strings,
- $save
+ $save,
+ $lang
) {
// We haven't seen this template yet so load it and it's dependencies.
$subdependencies = $this->load_with_dependencies(
$themename,
$includecomments,
$seentemplates,
- $seenstrings
+ $seenstrings,
+ $lang
);
foreach ($subdependencies['templates'] as $component => $ids) {
defined('MOODLE_INTERNAL') || die();
+use core\task\database_logger;
+use core\task\logmanager;
+
/**
* A task to cleanup log entries for tasks.
*
* Perform the cleanup task.
*/
public function execute() {
- if (\core\task\database_logger::class == \core\task\logmanager::get_logger_classname()) {
- \core\task\database_logger::cleanup();
+ $logger = logmanager::get_logger_classname();
+ if (is_a($logger, database_logger::class, true)) {
+ $logger::cleanup();
}
}
}
return $DB->get_records_sql($sql . ' ' . implode(' AND ', $conditions), $params);
}
+/**
+ * Get the list of options for the enrolment period dropdown
+ *
+ * @return array List of options for the enrolment period dropdown
+ */
+function enrol_get_period_list() {
+ $periodmenu = [];
+ $periodmenu[''] = get_string('unlimited');
+ for ($i = 1; $i <= 365; $i++) {
+ $seconds = $i * DAYSECS;
+ $periodmenu[$seconds] = get_string('numdays', '', $i);
+ }
+ return $periodmenu;
+}
+
+/**
+ * Calculate duration base on start time and end time
+ *
+ * @param int $timestart Time start
+ * @param int $timeend Time end
+ * @return float|int Calculated duration
+ */
+function enrol_calculate_duration($timestart, $timeend) {
+ $duration = floor(($timeend - $timestart) / DAYSECS) * DAYSECS;
+ return $duration;
+}
+
/**
* Enrolment plugins abstract class.
*
* Test adding of capabilities to roles.
*/
public function test_assign_capability() {
- global $DB;
+ global $DB, $USER;
$this->resetAfterTest();
$permission = $DB->get_record('role_capabilities', array('contextid'=>$frontcontext->id, 'roleid'=>$student->id, 'capability'=>'moodle/backup:backupcourse'));
$this->assertEmpty($permission);
- // Test event trigger.
- $rolecapabilityevent = \core\event\role_capabilities_updated::create(array('context' => $syscontext,
- 'objectid' => $student->id,
- 'other' => array('name' => $student->shortname)
- ));
- $expectedlegacylog = array(SITEID, 'role', 'view', 'admin/roles/define.php?action=view&roleid=' . $student->id,
- $student->shortname, '', $user->id);
- $rolecapabilityevent->set_legacy_logdata($expectedlegacylog);
- $rolecapabilityevent->add_record_snapshot('role', $student);
-
+ // Test event triggered.
$sink = $this->redirectEvents();
- $rolecapabilityevent->trigger();
+ $capability = 'moodle/backup:backupcourse';
+ assign_capability($capability, CAP_ALLOW, $student->id, $syscontext);
$events = $sink->get_events();
$sink->close();
- $event = array_pop($events);
-
- $this->assertInstanceOf('\core\event\role_capabilities_updated', $event);
- $expectedurl = new moodle_url('/admin/roles/define.php', array('action' => 'view', 'roleid' => $student->id));
- $this->assertEquals($expectedurl, $event->get_url());
- $this->assertEventLegacyLogData($expectedlegacylog, $event);
- $this->assertEventContextNotUsed($event);
+ $this->assertCount(1, $events);
+ $event = $events[0];
+ $this->assertInstanceOf('\core\event\capability_assigned', $event);
+ $this->assertSame('role_capabilities', $event->objecttable);
+ $this->assertEquals($student->id, $event->objectid);
+ $this->assertEquals($syscontext->id, $event->contextid);
+ $other = ['capability' => $capability, 'oldpermission' => CAP_INHERIT, 'permission' => CAP_ALLOW];
+ $this->assertEquals($other, $event->other);
+ $description = "The user id '$USER->id' assigned the '$capability' capability for " .
+ "role '$student->id' with 'Allow' permission";
+ $this->assertEquals($description, $event->get_description());
+
+ // Test if the event has different description when updating the capability permission.
+ $sink = $this->redirectEvents();
+ assign_capability($capability, CAP_PROHIBIT, $student->id, $syscontext, true);
+ $events = $sink->get_events();
+ $sink->close();
+ $event = $events[0];
+ $description = "The user id '$USER->id' changed the '$capability' capability permission for " .
+ "role '$student->id' from 'Allow' to 'Prohibit'";
+ $this->assertEquals($description, $event->get_description());
}
/**
* Test removing of capabilities from roles.
*/
public function test_unassign_capability() {
- global $DB;
+ global $DB, $USER;
$this->resetAfterTest();
$this->assertTrue($result);
$this->assertFalse($DB->record_exists('role_capabilities', array('contextid'=>$syscontext->id, 'roleid'=>$manager->id, 'capability'=>'moodle/backup:backupcourse')));
$this->assertFalse($DB->record_exists('role_capabilities', array('contextid'=>$frontcontext->id, 'roleid'=>$manager->id, 'capability'=>'moodle/backup:backupcourse')));
+
+ // Test event triggered.
+ $sink = $this->redirectEvents();
+ $capability = 'moodle/backup:backupcourse';
+ unassign_capability($capability, CAP_ALLOW, $manager->id);
+ $events = $sink->get_events();
+ $sink->close();
+ $this->assertCount(1, $events);
+ $event = $events[0];
+ $this->assertInstanceOf('\core\event\capability_unassigned', $event);
+ $this->assertSame('role_capabilities', $event->objecttable);
+ $this->assertEquals($manager->id, $event->objectid);
+ $this->assertEquals($syscontext->id, $event->contextid);
+ $this->assertEquals($capability, $event->other['capability']);
+ $description = "The user id id '$USER->id' has unassigned the '$capability' capability for role '$manager->id'";
+ $this->assertEquals($description, $event->get_description());
}
/**
$this->assertTrue($DB->record_exists('role_allow_assign', array('roleid'=>$otherid, 'allowassign'=>$student->id)));
// Test event trigger.
- $allowroleassignevent = \core\event\role_allow_assign_updated::create(array('context' => context_system::instance()));
+ $allowroleassignevent = \core\event\role_allow_assign_updated::create([
+ 'context' => context_system::instance(),
+ 'objectid' => $otherid,
+ 'other' => ['targetroleid' => $student->id]
+ ]);
$sink = $this->redirectEvents();
$allowroleassignevent->trigger();
$events = $sink->get_events();
$this->assertTrue($DB->record_exists('role_allow_override', array('roleid'=>$otherid, 'allowoverride'=>$student->id)));
// Test event trigger.
- $allowroleassignevent = \core\event\role_allow_override_updated::create(array('context' => context_system::instance()));
+ $allowroleassignevent = \core\event\role_allow_override_updated::create([
+ 'context' => context_system::instance(),
+ 'objectid' => $otherid,
+ 'other' => ['targetroleid' => $student->id]
+ ]);
$sink = $this->redirectEvents();
$allowroleassignevent->trigger();
$events = $sink->get_events();
$this->assertTrue($DB->record_exists('role_allow_switch', array('roleid'=>$otherid, 'allowswitch'=>$student->id)));
// Test event trigger.
- $allowroleassignevent = \core\event\role_allow_switch_updated::create(array('context' => context_system::instance()));
+ $allowroleassignevent = \core\event\role_allow_switch_updated::create([
+ 'context' => context_system::instance(),
+ 'objectid' => $otherid,
+ 'other' => ['targetroleid' => $student->id]
+ ]);
$sink = $this->redirectEvents();
$allowroleassignevent->trigger();
$events = $sink->get_events();
$this->assertTrue($DB->record_exists('role_allow_view', array('roleid' => $otherid, 'allowview' => $student->id)));
// Test event trigger.
- $allowroleassignevent = \core\event\role_allow_view_updated::create(array('context' => context_system::instance()));
+ $allowroleassignevent = \core\event\role_allow_view_updated::create([
+ 'context' => context_system::instance(),
+ 'objectid' => $otherid,
+ 'other' => ['targetroleid' => $student->id]
+ ]);
$sink = $this->redirectEvents();
$allowroleassignevent->trigger();
$events = $sink->get_events();
$csstarget = ".form-autocomplete-downarrow";
$this->execute('behat_general::i_click_on', [$csstarget, 'css_element']);
}
+
+ /**
+ * Expand the given autocomplete list
+ *
+ * @Given /^I expand the "(?P<field_string>(?:[^"]|\\")*)" autocomplete$/
+ *
+ * @param string $field Field name
+ */
+ public function i_expand_the_autocomplete($field) {
+ $csstarget = '.form-autocomplete-downarrow';
+ $node = $this->get_node_in_container('css_element', $csstarget, 'form_row', $field);
+ $this->ensure_node_is_visible($node);
+ $node->click();
+ }
+
+ /**
+ * Assert the given option exist in the given autocomplete list
+ *
+ * @Given /^I should see "(?P<option_string>(?:[^"]|\\")*)" in the list of options for the "(?P<field_string>(?:[^"]|\\")*)" autocomplete$$/
+ *
+ * @param string $option Name of option
+ * @param string $field Field name
+ */
+ public function i_should_see_in_the_list_of_option_for_the_autocomplete($option, $field) {
+ $xpathtarget = "//div[contains(@class, 'form-autocomplete-selection') and contains(.//div, '" . $option . "')]";
+ $node = $this->get_node_in_container('xpath_element', $xpathtarget, 'form_row', $field);
+ $this->ensure_node_is_visible($node);
+ }
}
public function exportprocess($checkcapabilities = true) {
global $CFG, $DB;
+ // Raise time and memory, as exporting can be quite intensive.
+ core_php_time_limit::raise();
+ raise_memory_limit(MEMORY_EXTRA);
+
// Get the parents (from database) for this category.
$parents = [];
if ($this->category) {
params.timeend = timeEnd.getTime() / 1000;
}
+ // Enrol duration.
+ var enrolDuration = $(form).find('[name="duration"]');
+ if (enrolDuration.is(':enabled')) {
+ params.timeend = params.timestart + parseInt(enrolDuration.val());
+ }
+
var request = {
methodname: 'core_enrol_edit_user_enrolment',
args: params
* @param int $courseid The course id
* @param int $groupid The groupid, 0 means all groups and USERSWITHOUTGROUP no group
* @param int $accesssince The time since last access, 0 means any time
- * @param int $roleid The role id, 0 means all roles
+ * @param int $roleid The role id, 0 means all roles and -1 no roles
* @param int $enrolid The enrolment id, 0 means all enrolment methods will be returned.
* @param int $statusid The user enrolment status, -1 means all enrolments regardless of the status will be returned, if allowed.
* @param string|array $search The search that was performed, empty means perform no search
list($relatedctxsql, $relatedctxparams) = $DB->get_in_or_equal($context->get_parent_context_ids(true),
SQL_PARAMS_NAMED, 'relatedctx');
- $wheres[] = "u.id IN (SELECT userid FROM {role_assignments} WHERE roleid = :roleid AND contextid $relatedctxsql)";
- $params = array_merge($params, array('roleid' => $roleid), $relatedctxparams);
+ // Get users without any role.
+ if ($roleid == -1) {
+ $wheres[] = "u.id NOT IN (SELECT userid FROM {role_assignments} WHERE contextid $relatedctxsql)";
+ $params = array_merge($params, $relatedctxparams);
+ } else {
+ $wheres[] = "u.id IN (SELECT userid FROM {role_assignments} WHERE roleid = :roleid AND contextid $relatedctxsql)";
+ $params = array_merge($params, array('roleid' => $roleid), $relatedctxparams);
+ }
}
if (!empty($search)) {
}
$criteria = get_string('role');
- $roleoptions = [];
+ $roleoptions = $this->format_filter_option(USER_FILTER_ROLE, $criteria, -1, get_string('noroles', 'role'));
foreach ($roles as $id => $role) {
$roleoptions += $this->format_filter_option(USER_FILTER_ROLE, $criteria, $id, $role);
}
| Group: Group A | Student 1 | Student 2 | | Student 3 | XX-IGNORE-XX |
| Group: Group B | Student 2 | | | Student 1 | Student 3 |
+ @javascript
+ Scenario: Filter users who have no role in a course
+ Given I log in as "teacher1"
+ And I am on "Course 1" course homepage
+ And I navigate to course participants
+ And I click on "Student 1's role assignments" "link"
+ And I click on ".form-autocomplete-selection [aria-selected=true]" "css_element"
+ And I press key "27" in the field "Student 1's role assignments"
+ And I click on "Save changes" "link"
+ When I open the autocomplete suggestions list
+ And I click on "Role: No roles" item in the autocomplete list
+ Then I should see "Student 1" in the "participants" "table"
+ And I should not see "Student 2" in the "participants" "table"
+ And I should not see "Student 3" in the "participants" "table"
+ And I should not see "Student 4" in the "participants" "table"
+ And I should not see "Teacher 1" in the "participants" "table"
+
@javascript
Scenario: Multiple filters applied
Given I log in as "teacher1"