diff --git a/grade/classes/external/get_gradable_users.php b/grade/classes/external/get_gradable_users.php new file mode 100644 index 00000000000..26cdc6fb144 --- /dev/null +++ b/grade/classes/external/get_gradable_users.php @@ -0,0 +1,161 @@ +. + +namespace core_grades\external; + +use coding_exception; +use core_external\external_description; +use core_user; +use core_external\external_api; +use core_external\external_function_parameters; +use core_external\external_multiple_structure; +use core_external\external_single_structure; +use core_external\external_value; +use core_external\external_warnings; +use core_external\restricted_context_exception; +use invalid_parameter_exception; +use moodle_exception; +use user_picture; + +defined('MOODLE_INTERNAL') || die; + +require_once($CFG->dirroot . '/grade/lib.php'); +require_once($CFG->dirroot . '/user/externallib.php'); + +/** + * Get the gradable users in a course. + * + * @package core_grades + * @copyright 2023 Ilya Tregubov + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @since Moodle 4.1 + */ +class get_gradable_users extends external_api { + + /** + * Returns description of method parameters. + * + * @return external_function_parameters + */ + public static function execute_parameters(): external_function_parameters { + return new external_function_parameters ( + [ + 'courseid' => new external_value(PARAM_INT, 'Course Id', VALUE_REQUIRED), + 'groupid' => new external_value(PARAM_INT, 'Group Id', VALUE_DEFAULT, 0), + 'onlyactive' => new external_value(PARAM_BOOL, 'Only active enrolment', VALUE_DEFAULT, false), + ] + ); + } + + /** + * Given a course ID find the gradable users within a group. + * + * @param int $courseid + * @param int|null $groupid + * @param bool $onlyactive + * @return array Users and warnings. + * @throws coding_exception + * @throws invalid_parameter_exception + * @throws moodle_exception + * @throws restricted_context_exception + */ + public static function execute(int $courseid, ?int $groupid = 0, bool $onlyactive = false): array { + global $DB, $PAGE; + + $params = self::validate_parameters( + self::execute_parameters(), + [ + 'courseid' => $courseid, + 'groupid' => $groupid, + 'onlyactive' => $onlyactive, + ] + ); + + $warnings = []; + $coursecontext = \context_course::instance($params['courseid']); + parent::validate_context($coursecontext); + + require_capability('moodle/course:viewparticipants', $coursecontext); + + $course = $DB->get_record('course', ['id' => $params['courseid']]); + // Create a graded_users_iterator because it will properly check the groups etc. + $onlyactive = $onlyactive || !has_capability('moodle/course:viewsuspendedusers', $coursecontext); + + $gui = new \graded_users_iterator($course, null, $params['groupid']); + $gui->require_active_enrolment($onlyactive); + $gui->init(); + + // Flatten the users. + $users = []; + while ($user = $gui->next_user()) { + $users[$user->user->id] = $user->user; + } + $gui->close(); + + $users = array_map(function ($user) use ($PAGE) { + $user->fullname = fullname($user); + $userpicture = new user_picture($user); + $userpicture->size = 1; + $user->profileimage = $userpicture->get_url($PAGE)->out(false); + return $user; + }, $users); + sort($users); + + return [ + 'users' => $users, + 'warnings' => $warnings, + ]; + } + + /** + * Returns description of method result value. + * + * @return external_single_structure + */ + public static function execute_returns(): external_single_structure { + return new external_single_structure([ + 'users' => new external_multiple_structure(self::user_description()), + 'warnings' => new external_warnings(), + ]); + } + + /** + * Create user return value description. + * + * @return external_description + */ + public static function user_description(): external_description { + $userfields = [ + 'id' => new external_value(core_user::get_property_type('id'), 'ID of the user'), + 'profileimage' => new external_value( + PARAM_URL, + 'The location of the users larger image', + VALUE_OPTIONAL + ), + 'fullname' => new external_value(PARAM_TEXT, 'The full name of the user', VALUE_OPTIONAL), + 'firstname' => new external_value( + core_user::get_property_type('firstname'), + 'The first name(s) of the user', + VALUE_OPTIONAL), + 'lastname' => new external_value( + core_user::get_property_type('lastname'), + 'The family name of the user', + VALUE_OPTIONAL), + ]; + return new external_single_structure($userfields); + } + +} diff --git a/grade/tests/external/get_gradable_users_test.php b/grade/tests/external/get_gradable_users_test.php new file mode 100644 index 00000000000..90ecac41c3f --- /dev/null +++ b/grade/tests/external/get_gradable_users_test.php @@ -0,0 +1,197 @@ +. + +namespace core_grades\external; + +use core_external\external_api; + +defined('MOODLE_INTERNAL') || die; + +global $CFG; + +require_once($CFG->dirroot . '/webservice/tests/helpers.php'); + +/** + * Unit tests for the core_grades\external\get_gradable_users. + * + * @package core_grades + * @category external + * @copyright 2023 Ilya Tregubov + * @covers \core_grades\external\get_gradable_users + */ +class get_gradable_users_test extends \externallib_advanced_testcase { + + /** + * Test the behaviour of get_gradable_users. + * + * @dataProvider execute_data + * @param bool $onlyactiveenrol if we should only return active enrolments + * @param bool $grouprestricted if we should only return users within a group + * @param array $expected expected users + */ + public function test_execute(bool $onlyactiveenrol, bool $grouprestricted, array $expected): void { + global $DB; + + $this->resetAfterTest(); + $this->setAdminUser(); + $generator = $this->getDataGenerator(); + $course = $generator->create_course(); + + // Create and enrol test users. + $student1 = $generator->create_user(['username' => 'student1', 'firstname' => 'Apple', 'lastname' => 'Apricot']); + $student2 = $generator->create_user(['username' => 'student2', 'firstname' => 'Banana', 'lastname' => 'Blueberry']); + $student3 = $generator->create_user(['username' => 'student3', 'firstname' => 'Cherry', 'lastname' => 'Cranberry']); + $student4 = $generator->create_user(['username' => 'student4', 'firstname' => 'Durian', 'lastname' => 'Dracontomelon']); + $student5 = $generator->create_user(['username' => 'student5', 'firstname' => 'Eggplant', 'lastname' => 'Ensete']); + + $role = $DB->get_record('role', ['shortname' => 'student'], '*', MUST_EXIST); + $generator->enrol_user($student1->id, $course->id, $role->id); + $generator->enrol_user($student2->id, $course->id, $role->id); + $generator->enrol_user($student3->id, $course->id, $role->id); + $generator->enrol_user($student4->id, $course->id, $role->id); + $generator->enrol_user($student5->id, $course->id, $role->id, 'manual', 0, 0, ENROL_USER_SUSPENDED); + + $group1 = $generator->create_group(['courseid' => $course->id]); + $group2 = $generator->create_group(['courseid' => $course->id]); + + $generator->create_group_member(['userid' => $student1->id, 'groupid' => $group1->id]); + $generator->create_group_member(['userid' => $student1->id, 'groupid' => $group2->id]); + $generator->create_group_member(['userid' => $student2->id, 'groupid' => $group2->id]); + $generator->create_group_member(['userid' => $student3->id, 'groupid' => $group2->id]); + + $DB->set_field('course', 'groupmode', SEPARATEGROUPS, ['id' => $course->id]); + + $teacher = $generator->create_user(['username' => 'teacher1']); + $role = $DB->get_record('role', ['shortname' => 'editingteacher'], '*', MUST_EXIST); + $generator->enrol_user($teacher->id, $course->id, $role->id); + + $generator->create_module('assign', ['course' => $course->id]); + + $groupid = $grouprestricted ? $group2->id : 0; + $result = get_gradable_users::execute($course->id, $groupid, $onlyactiveenrol); + $result = external_api::clean_returnvalue(get_gradable_users::execute_returns(), $result); + + $mapped = array_map(function($user) { + return [ + 'fullname' => $user['fullname'], + 'firstname' => $user['firstname'], + 'lastname' => $user['lastname'], + 'profileimage' => $user['profileimage'], + ]; + }, array_values($result['users'])); + $this->assertEquals($expected, $mapped); + } + + /** + * Data provider for test_execute. + * + * @return array + */ + public function execute_data(): array { + return [ + 'All users' => [ + false, + false, + [ + [ + 'profileimage' => 'https://www.example.com/moodle/theme/image.php/boost/core/1/u/f1', + 'firstname' => 'Apple', + 'lastname' => 'Apricot', + 'fullname' => 'Apple Apricot', + ], + [ + 'profileimage' => 'https://www.example.com/moodle/theme/image.php/boost/core/1/u/f1', + 'firstname' => 'Banana', + 'lastname' => 'Blueberry', + 'fullname' => 'Banana Blueberry', + ], + [ + 'profileimage' => 'https://www.example.com/moodle/theme/image.php/boost/core/1/u/f1', + 'firstname' => 'Cherry', + 'lastname' => 'Cranberry', + 'fullname' => 'Cherry Cranberry', + ], + [ + 'profileimage' => 'https://www.example.com/moodle/theme/image.php/boost/core/1/u/f1', + 'firstname' => 'Durian', + 'lastname' => 'Dracontomelon', + 'fullname' => 'Durian Dracontomelon', + ], + [ + 'profileimage' => 'https://www.example.com/moodle/theme/image.php/boost/core/1/u/f1', + 'firstname' => 'Eggplant', + 'lastname' => 'Ensete', + 'fullname' => 'Eggplant Ensete', + ], + ], + ], + 'Only active enrolment' => [ + true, + false, + [ + [ + 'profileimage' => 'https://www.example.com/moodle/theme/image.php/boost/core/1/u/f1', + 'firstname' => 'Apple', + 'lastname' => 'Apricot', + 'fullname' => 'Apple Apricot', + ], + [ + 'profileimage' => 'https://www.example.com/moodle/theme/image.php/boost/core/1/u/f1', + 'firstname' => 'Banana', + 'lastname' => 'Blueberry', + 'fullname' => 'Banana Blueberry', + ], + [ + 'profileimage' => 'https://www.example.com/moodle/theme/image.php/boost/core/1/u/f1', + 'firstname' => 'Cherry', + 'lastname' => 'Cranberry', + 'fullname' => 'Cherry Cranberry', + ], + [ + 'profileimage' => 'https://www.example.com/moodle/theme/image.php/boost/core/1/u/f1', + 'firstname' => 'Durian', + 'lastname' => 'Dracontomelon', + 'fullname' => 'Durian Dracontomelon', + ], + ], + ], + 'Group restricted' => [ + false, + true, + [ + [ + 'profileimage' => 'https://www.example.com/moodle/theme/image.php/boost/core/1/u/f1', + 'firstname' => 'Apple', + 'lastname' => 'Apricot', + 'fullname' => 'Apple Apricot', + ], + [ + 'profileimage' => 'https://www.example.com/moodle/theme/image.php/boost/core/1/u/f1', + 'firstname' => 'Banana', + 'lastname' => 'Blueberry', + 'fullname' => 'Banana Blueberry', + ], + [ + 'profileimage' => 'https://www.example.com/moodle/theme/image.php/boost/core/1/u/f1', + 'firstname' => 'Cherry', + 'lastname' => 'Cranberry', + 'fullname' => 'Cherry Cranberry', + ], + ], + ], + ]; + } +} diff --git a/lib/db/services.php b/lib/db/services.php index ed7bb941726..a86c651d1f7 100644 --- a/lib/db/services.php +++ b/lib/db/services.php @@ -1023,6 +1023,13 @@ $functions = array( 'type' => 'read', 'ajax' => true, ], + 'core_grades_get_gradable_users' => [ + 'classname' => 'core_grades\external\get_gradable_users', + 'description' => 'Returns the gradable users in a course', + 'type' => 'read', + 'ajax' => true, + 'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE], + ], 'core_grading_get_definitions' => array( 'classname' => 'core_grading_external', 'methodname' => 'get_definitions', diff --git a/version.php b/version.php index 5024b851df7..0b368dce6f1 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2023120100.00; // YYYYMMDD = weekly release date of this DEV branch. +$version = 2023120100.01; // YYYYMMDD = weekly release date of this DEV branch. // RR = release increments - 00 in DEV branches. // .XX = incremental changes. $release = '4.4dev (Build: 20231201)'; // Human-friendly version name