Skip to content

Commit

Permalink
Merge branch 'MDL-69869-master' of git://github.com/ilya-catalyst/moodle
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewnicols committed Dec 9, 2020
2 parents e26cfc1 + aa1691d commit ad31539
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 68 deletions.
147 changes: 79 additions & 68 deletions enrol/externallib.php
Expand Up @@ -679,28 +679,34 @@ public static function search_users_returns(): external_multiple_structure {
*/
public static function get_enrolled_users_parameters() {
return new external_function_parameters(
array(
[
'courseid' => new external_value(PARAM_INT, 'course id'),
'options' => new external_multiple_structure(
new external_single_structure(
array(
[
'name' => new external_value(PARAM_ALPHANUMEXT, 'option name'),
'value' => new external_value(PARAM_RAW, 'option value')
)
]
), 'Option names:
* withcapability (string) return only users with this capability. This option requires \'moodle/role:review\' on the course context.
* groupid (integer) return only users in this group id. If the course has groups enabled and this param
isn\'t defined, returns all the viewable users.
This option requires \'moodle/site:accessallgroups\' on the course context if the
user doesn\'t belong to the group.
* onlyactive (integer) return only users with active enrolments and matching time restrictions. This option requires \'moodle/course:enrolreview\' on the course context.
* onlyactive (integer) return only users with active enrolments and matching time restrictions.
This option requires \'moodle/course:enrolreview\' on the course context.
Please note that this option can\'t
be used together with onlysuspended (only one can be active).
* onlysuspended (integer) return only suspended users. This option requires
\'moodle/course:enrolreview\' on the course context. Please note that this option can\'t
be used together with onlyactive (only one can be active).
* userfields (\'string, string, ...\') return only the values of these user fields.
* limitfrom (integer) sql limit from.
* limitnumber (integer) maximum number of returned users.
* sortby (string) sort by id, firstname or lastname. For ordering like the site does, use siteorder.
* sortdirection (string) ASC or DESC',
VALUE_DEFAULT, array()),
)
VALUE_DEFAULT, []),
]
);
}

Expand All @@ -714,75 +720,79 @@ public static function get_enrolled_users_parameters() {
* }
* @return array An array of users
*/
public static function get_enrolled_users($courseid, $options = array()) {
public static function get_enrolled_users($courseid, $options = []) {
global $CFG, $USER, $DB;

require_once($CFG->dirroot . '/course/lib.php');
require_once($CFG->dirroot . "/user/lib.php");

$params = self::validate_parameters(
self::get_enrolled_users_parameters(),
array(
[
'courseid'=>$courseid,
'options'=>$options
)
]
);
$withcapability = '';
$groupid = 0;
$onlyactive = false;
$userfields = array();
$onlysuspended = false;
$userfields = [];
$limitfrom = 0;
$limitnumber = 0;
$sortby = 'us.id';
$sortparams = array();
$sortparams = [];
$sortdirection = 'ASC';
foreach ($options as $option) {
switch ($option['name']) {
case 'withcapability':
$withcapability = $option['value'];
break;
case 'groupid':
$groupid = (int)$option['value'];
break;
case 'onlyactive':
$onlyactive = !empty($option['value']);
break;
case 'userfields':
$thefields = explode(',', $option['value']);
foreach ($thefields as $f) {
$userfields[] = clean_param($f, PARAM_ALPHANUMEXT);
}
break;
case 'limitfrom' :
$limitfrom = clean_param($option['value'], PARAM_INT);
break;
case 'limitnumber' :
$limitnumber = clean_param($option['value'], PARAM_INT);
break;
case 'sortby':
$sortallowedvalues = array('id', 'firstname', 'lastname', 'siteorder');
if (!in_array($option['value'], $sortallowedvalues)) {
throw new invalid_parameter_exception('Invalid value for sortby parameter (value: ' . $option['value'] . '),' .
'allowed values are: ' . implode(',', $sortallowedvalues));
}
if ($option['value'] == 'siteorder') {
list($sortby, $sortparams) = users_order_by_sql('us');
} else {
$sortby = 'us.' . $option['value'];
}
break;
case 'sortdirection':
$sortdirection = strtoupper($option['value']);
$directionallowedvalues = array('ASC', 'DESC');
if (!in_array($sortdirection, $directionallowedvalues)) {
throw new invalid_parameter_exception('Invalid value for sortdirection parameter
case 'withcapability':
$withcapability = $option['value'];
break;
case 'groupid':
$groupid = (int)$option['value'];
break;
case 'onlyactive':
$onlyactive = !empty($option['value']);
break;
case 'onlysuspended':
$onlysuspended = !empty($option['value']);
break;
case 'userfields':
$thefields = explode(',', $option['value']);
foreach ($thefields as $f) {
$userfields[] = clean_param($f, PARAM_ALPHANUMEXT);
}
break;
case 'limitfrom' :
$limitfrom = clean_param($option['value'], PARAM_INT);
break;
case 'limitnumber' :
$limitnumber = clean_param($option['value'], PARAM_INT);
break;
case 'sortby':
$sortallowedvalues = ['id', 'firstname', 'lastname', 'siteorder'];
if (!in_array($option['value'], $sortallowedvalues)) {
throw new invalid_parameter_exception('Invalid value for sortby parameter (value: ' .
$option['value'] . '), allowed values are: ' . implode(',', $sortallowedvalues));
}
if ($option['value'] == 'siteorder') {
list($sortby, $sortparams) = users_order_by_sql('us');
} else {
$sortby = 'us.' . $option['value'];
}
break;
case 'sortdirection':
$sortdirection = strtoupper($option['value']);
$directionallowedvalues = ['ASC', 'DESC'];
if (!in_array($sortdirection, $directionallowedvalues)) {
throw new invalid_parameter_exception('Invalid value for sortdirection parameter
(value: ' . $sortdirection . '),' . 'allowed values are: ' . implode(',', $directionallowedvalues));
}
break;
}
break;
}
}

$course = $DB->get_record('course', array('id'=>$courseid), '*', MUST_EXIST);
$course = $DB->get_record('course', ['id' => $courseid], '*', MUST_EXIST);
$coursecontext = context_course::instance($courseid, IGNORE_MISSING);
if ($courseid == SITEID) {
$context = context_system::instance();
Expand All @@ -809,11 +819,12 @@ public static function get_enrolled_users($courseid, $options = array()) {
require_capability('moodle/site:accessallgroups', $coursecontext);
}
// to overwrite this option, you need course:enrolereview permission
if ($onlyactive) {
if ($onlyactive || $onlysuspended) {
require_capability('moodle/course:enrolreview', $coursecontext);
}

list($enrolledsql, $enrolledparams) = get_enrolled_sql($coursecontext, $withcapability, $groupid, $onlyactive);
list($enrolledsql, $enrolledparams) = get_enrolled_sql($coursecontext, $withcapability, $groupid, $onlyactive,
$onlysuspended);
$ctxselect = ', ' . context_helper::get_preload_record_columns_sql('ctx');
$ctxjoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = u.id AND ctx.contextlevel = :contextlevel)";
$enrolledparams['contextlevel'] = CONTEXT_USER;
Expand All @@ -829,7 +840,7 @@ public static function get_enrolled_users($courseid, $options = array()) {
$enrolledparams = array_merge($enrolledparams, $groupparams);
} else {
// User doesn't belong to any group, so he can't see any user. Return an empty array.
return array();
return [];
}
}
$sql = "SELECT us.*, COALESCE(ul.timeaccess, 0) AS lastcourseaccess
Expand All @@ -845,7 +856,7 @@ public static function get_enrolled_users($courseid, $options = array()) {
$enrolledparams['courseid'] = $courseid;

$enrolledusers = $DB->get_recordset_sql($sql, $enrolledparams, $limitfrom, $limitnumber);
$users = array();
$users = [];
foreach ($enrolledusers as $user) {
context_helper::preload_from_record($user);
if ($userdetails = user_get_user_details($user, $course, $userfields)) {
Expand All @@ -865,7 +876,7 @@ public static function get_enrolled_users($courseid, $options = array()) {
public static function get_enrolled_users_returns() {
return new external_multiple_structure(
new external_single_structure(
array(
[
'id' => new external_value(PARAM_INT, 'ID of the user'),
'username' => new external_value(PARAM_RAW, 'Username policy is defined in Moodle security config', VALUE_OPTIONAL),
'firstname' => new external_value(PARAM_NOTAGS, 'The first name(s) of the user', VALUE_OPTIONAL),
Expand Down Expand Up @@ -896,47 +907,47 @@ public static function get_enrolled_users_returns() {
'profileimageurl' => new external_value(PARAM_URL, 'User image profile URL - big version', VALUE_OPTIONAL),
'customfields' => new external_multiple_structure(
new external_single_structure(
array(
[
'type' => new external_value(PARAM_ALPHANUMEXT, 'The type of the custom field - text field, checkbox...'),
'value' => new external_value(PARAM_RAW, 'The value of the custom field'),
'name' => new external_value(PARAM_RAW, 'The name of the custom field'),
'shortname' => new external_value(PARAM_RAW, 'The shortname of the custom field - to be able to build the field class in the code'),
)
]
), 'User custom fields (also known as user profil fields)', VALUE_OPTIONAL),
'groups' => new external_multiple_structure(
new external_single_structure(
array(
[
'id' => new external_value(PARAM_INT, 'group id'),
'name' => new external_value(PARAM_RAW, 'group name'),
'description' => new external_value(PARAM_RAW, 'group description'),
'descriptionformat' => new external_format_value('description'),
)
]
), 'user groups', VALUE_OPTIONAL),
'roles' => new external_multiple_structure(
new external_single_structure(
array(
[
'roleid' => new external_value(PARAM_INT, 'role id'),
'name' => new external_value(PARAM_RAW, 'role name'),
'shortname' => new external_value(PARAM_ALPHANUMEXT, 'role shortname'),
'sortorder' => new external_value(PARAM_INT, 'role sortorder')
)
]
), 'user roles', VALUE_OPTIONAL),
'preferences' => new external_multiple_structure(
new external_single_structure(
array(
[
'name' => new external_value(PARAM_RAW, 'The name of the preferences'),
'value' => new external_value(PARAM_RAW, 'The value of the custom field'),
)
]
), 'User preferences', VALUE_OPTIONAL),
'enrolledcourses' => new external_multiple_structure(
new external_single_structure(
array(
[
'id' => new external_value(PARAM_INT, 'Id of the course'),
'fullname' => new external_value(PARAM_RAW, 'Fullname of the course'),
'shortname' => new external_value(PARAM_RAW, 'Shortname of the course')
)
]
), 'Courses where the user is enrolled - limited by which courses the user is able to see', VALUE_OPTIONAL)
)
]
)
);
}
Expand Down
89 changes: 89 additions & 0 deletions enrol/tests/externallib_test.php
Expand Up @@ -355,6 +355,95 @@ public function test_get_enrolled_users_visibility($settings, $results) {
}
}

/**
* Verify get_enrolled_users() returned users according to their status.
*/
public function test_get_enrolled_users_active_suspended() {
global $USER;

$this->resetAfterTest();

// Create the course and the users.
$course = $this->getDataGenerator()->create_course();
$coursecontext = context_course::instance($course->id);
$user0 = $this->getDataGenerator()->create_user(['username' => 'user0active']);
$user1 = $this->getDataGenerator()->create_user(['username' => 'user1active']);
$user2 = $this->getDataGenerator()->create_user(['username' => 'user2active']);
$user2su = $this->getDataGenerator()->create_user(['username' => 'user2suspended']); // Suspended user.
$user3 = $this->getDataGenerator()->create_user(['username' => 'user3active']);
$user3su = $this->getDataGenerator()->create_user(['username' => 'user3suspended']); // Suspended user.

// Enrol the users in the course.
$this->getDataGenerator()->enrol_user($user0->id, $course->id);
$this->getDataGenerator()->enrol_user($user1->id, $course->id);
$this->getDataGenerator()->enrol_user($user2->id, $course->id);
$this->getDataGenerator()->enrol_user($user2su->id, $course->id, null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
$this->getDataGenerator()->enrol_user($user3->id, $course->id);
$this->getDataGenerator()->enrol_user($user3su->id, $course->id, null, 'manual', 0, 0, ENROL_USER_SUSPENDED);

// Create a role to add the allowedcaps. Users will have this role assigned.
$roleid = $this->getDataGenerator()->create_role();
// Allow the specified capabilities.
assign_capability('moodle/course:enrolreview', CAP_ALLOW, $roleid, $coursecontext);
assign_capability('moodle/user:viewalldetails', CAP_ALLOW, $roleid, $coursecontext);

// Switch to the user and assign the role.
$this->setUser($user0);
role_assign($roleid, $USER->id, $coursecontext);

// Suspended users.
$options = [
['name' => 'onlysuspended', 'value' => true],
['name' => 'userfields', 'value' => 'id,username']
];
$suspendedusers = core_enrol_external::get_enrolled_users($course->id, $options);

// We need to execute the return values cleaning process to simulate the web service server.
$suspendedusers = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_returns(), $suspendedusers);
$this->assertCount(2, $suspendedusers);

foreach ($suspendedusers as $suspendeduser) {
$this->assertStringContainsString('suspended', $suspendeduser['username']);
}

// Active users.
$options = [
['name' => 'onlyactive', 'value' => true],
['name' => 'userfields', 'value' => 'id,username']
];
$activeusers = core_enrol_external::get_enrolled_users($course->id, $options);

// We need to execute the return values cleaning process to simulate the web service server.
$activeusers = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_returns(), $activeusers);
$this->assertCount(4, $activeusers);

foreach ($activeusers as $activeuser) {
$this->assertStringContainsString('active', $activeuser['username']);
}

// All enrolled users.
$options = [
['name' => 'userfields', 'value' => 'id,username']
];
$allusers = core_enrol_external::get_enrolled_users($course->id, $options);

// We need to execute the return values cleaning process to simulate the web service server.
$allusers = external_api::clean_returnvalue(core_enrol_external::get_enrolled_users_returns(), $allusers);
$this->assertCount(6, $allusers);

// Active and suspended. Test exception is thrown.
$options = [
['name' => 'onlyactive', 'value' => true],
['name' => 'onlysuspended', 'value' => true],
['name' => 'userfields', 'value' => 'id,username']
];
$this->expectException('coding_exception');
$message = 'Coding error detected, it must be fixed by a programmer: Both onlyactive ' .
'and onlysuspended are set, this is probably not what you want!';
$this->expectExceptionMessage($message);
core_enrol_external::get_enrolled_users($course->id, $options);
}

/**
* Test get_users_courses
*/
Expand Down
4 changes: 4 additions & 0 deletions enrol/upgrade.txt
@@ -1,6 +1,10 @@
This files describes API changes in /enrol/* - plugins,
information provided here is intended especially for developers.

=== 3.11 ===

* Added onlysuspended option to core_enrol_get_enrolled_users webservice to retrieve only suspended users.

=== 3.8 ===

* Function enrol_manual_plugin::enrol_cohort now return the number of enrolled cohort users.
Expand Down

0 comments on commit ad31539

Please sign in to comment.