diff --git a/enrol/tests/externallib_test.php b/enrol/tests/externallib_test.php index 1b9bf3af939..8535d3ddc45 100644 --- a/enrol/tests/externallib_test.php +++ b/enrol/tests/externallib_test.php @@ -32,6 +32,325 @@ require_once($CFG->dirroot . '/enrol/externallib.php'); */ class core_enrol_externallib_testcase extends externallib_advanced_testcase { + /** + * dataProvider for test_get_enrolled_users_visibility(). + */ + public function get_enrolled_users_visibility_provider() { + return array( + 'Course without groups, default behavior (not filtering by cap, group, active)' => + array( + 'settings' => array( + 'coursegroupmode' => NOGROUPS, + 'withcapability' => null, + 'groupid' => null, + 'onlyactive' => false, + 'allowedcaps' => array(), + ), + 'results' => array( // Everybody can view everybody. + 'user0' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')), + 'user1' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')), + 'user2' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')), + 'user31' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')), + 'userall' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')), + ), + ), + + 'Course with visible groups, default behavior (not filtering by cap, group, active)' => + array( + 'settings' => array( + 'coursegroupmode' => VISIBLEGROUPS, + 'withcapability' => null, + 'groupid' => null, + 'onlyactive' => false, + 'allowedcaps' => array(), + ), + 'results' => array( // Everybody can view everybody. + 'user0' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')), + 'user1' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')), + 'user2' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')), + 'user31' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')), + 'userall' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')), + ), + ), + + 'Course with separate groups, default behavior (not filtering by cap, group, active)' => + array( + 'settings' => array( + 'coursegroupmode' => SEPARATEGROUPS, + 'withcapability' => null, + 'groupid' => null, + 'onlyactive' => false, + 'allowedcaps' => array(), + ), + 'results' => array( // Only users from own groups are visible. + 'user0' => array('canview' => array()), // Poor guy, cannot see anybody, himself included. + 'user1' => array('canview' => array('user1', 'userall')), + 'user2' => array('canview' => array('user2', 'user2su', 'userall')), + 'user31' => array('canview' => array('user31', 'user32', 'userall')), + 'userall' => array('canview' => array('user1', 'user2', 'user2su', 'user31', 'user32', 'userall')), + ), + ), + + 'Course with separate groups, default behavior (not filtering but having moodle/site:accessallgroups)' => + array( + 'settings' => array( + 'coursegroupmode' => VISIBLEGROUPS, + 'withcapability' => null, + 'groupid' => null, + 'onlyactive' => false, + 'allowedcaps' => array('moodle/site:accessallgroups'), + ), + 'results' => array( // Everybody can view everybody. + 'user0' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')), + 'user1' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')), + 'user2' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')), + 'user31' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')), + 'userall' => array('canview' => array('user0', 'user1', 'user2', 'user2su', 'user31', 'user32', 'userall')), + ), + ), + + 'Course with separate groups, filtering onlyactive (missing moodle/course:enrolreview)' => + array( + 'settings' => array( + 'coursegroupmode' => SEPARATEGROUPS, + 'withcapability' => null, + 'groupid' => null, + 'onlyactive' => true, + 'allowedcaps' => array(), + ), + 'results' => array( // returns exception, cannot view anybody without the cap. + 'user2' => array('exception' => array( + 'type' => 'required_capability_exception', + 'message' => 'Review course enrolments')), + 'userall' => array('exception' => array( + 'type' => 'required_capability_exception', + 'message' => 'Review course enrolments')), + ), + ), + + 'Course with separate groups, filtering onlyactive (having moodle/course:enrolreview)' => + array( + 'settings' => array( + 'coursegroupmode' => SEPARATEGROUPS, + 'withcapability' => null, + 'groupid' => null, + 'onlyactive' => true, + 'allowedcaps' => array('moodle/course:enrolreview'), + ), + 'results' => array( // Suspended are not returned. + 'user2' => array('canview' => array('user2', 'userall')), + 'user31' => array('canview' => array('user31', 'user32', 'userall')), + 'userall' => array('canview' => array('user1', 'user2', 'user31', 'user32', 'userall')), + ), + ), + + 'Course with separate groups, filtering by groupid (not having moodle/site:accessallgroups)' => + array( + 'settings' => array( + 'coursegroupmode' => SEPARATEGROUPS, + 'withcapability' => null, + 'groupid' => 'group2', + 'onlyactive' => false, + 'allowedcaps' => array(), + ), + 'results' => array( // Only group 2 members and only for members. Exception for non-members. + 'user0' => array('exception' => array( + 'type' => 'required_capability_exception', + 'message' => 'Access all groups')), + 'user1' => array('exception' => array( + 'type' => 'required_capability_exception', + 'message' => 'Access all groups')), + 'user2' => array('canview' => array('user2', 'user2su', 'userall')), + 'userall' => array('canview' => array('user2', 'user2su', 'userall')), + ), + ), + + 'Course with separate groups, filtering by groupid (having moodle/site:accessallgroups)' => + array( + 'settings' => array( + 'coursegroupmode' => SEPARATEGROUPS, + 'withcapability' => null, + 'groupid' => 'group2', + 'onlyactive' => false, + 'allowedcaps' => array('moodle/site:accessallgroups'), + ), + 'results' => array( // All users with 'moodle/site:accessallgroups' can view group 2 + 'user0' => array('canview' => array('user2', 'user2su', 'userall')), + 'user1' => array('canview' => array('user2', 'user2su', 'userall')), + 'user2' => array('canview' => array('user2', 'user2su', 'userall')), + 'userall' => array('canview' => array('user2', 'user2su', 'userall')), + ), + ), + + 'Course with separate groups, filtering by withcapability (not having moodle/role:review)' => + array( + 'settings' => array( + 'coursegroupmode' => SEPARATEGROUPS, + 'withcapability' => 'moodle/course:bulkmessaging', + 'groupid' => null, + 'onlyactive' => false, + 'allowedcaps' => array(), + ), + 'results' => array( // No user has 'moodle/role:review' so exception. + 'user0' => array('exception' => array( + 'type' => 'required_capability_exception', + 'message' => 'Review permissions for others')), + 'user1' => array('exception' => array( + 'type' => 'required_capability_exception', + 'message' => 'Review permissions for others')), + 'user2' => array('exception' => array( + 'type' => 'required_capability_exception', + 'message' => 'Review permissions for others')), + 'userall' => array('exception' => array( + 'type' => 'required_capability_exception', + 'message' => 'Review permissions for others')), + ), + ), + + 'Course with separate groups, filtering by withcapability (having moodle/role:review)' => + array( + 'settings' => array( + 'coursegroupmode' => SEPARATEGROUPS, + 'withcapability' => 'moodle/course:bulkmessaging', + 'groupid' => null, + 'onlyactive' => false, + 'allowedcaps' => array('moodle/role:review'), + ), + 'results' => array( // No user has withcapability, but all have 'moodle/role:review'. Empties. + 'user0' => array('canview' => array()), + 'user1' => array('canview' => array()), + 'user2' => array('canview' => array()), + 'userall' => array('canview' => array()), + ), + ), + + 'Course with separate groups, filtering by withcapability (having moodle/role:review)' => + array( + 'settings' => array( + 'coursegroupmode' => SEPARATEGROUPS, + 'withcapability' => 'moodle/course:bulkmessaging', + 'groupid' => null, + 'onlyactive' => false, + 'allowedcaps' => array('moodle/role:review', 'moodle/course:bulkmessaging'), + ), + 'results' => array( // Users (previous) have withcapability, and all have 'moodle/role:review'. + 'user0' => array('canview' => array()), + 'user1' => array('canview' => array('user1')), + 'user2' => array('canview' => array('user2')), + 'userall' => array('canview' => array('user1', 'user2', 'userall')), + ), + ), + ); + } + + /** + * Verify get_enrolled_users() returned users are the expected in every situation. + * + * @dataProvider get_enrolled_users_visibility_provider + */ + public function test_get_enrolled_users_visibility($settings, $results) { + + global $USER; + + $this->resetAfterTest(); + + // Create the course and the users. + $course = $this->getDataGenerator()->create_course(array('groupmode' => $settings['coursegroupmode'])); + $coursecontext = context_course::instance($course->id); + $user0 = $this->getDataGenerator()->create_user(array('username' => 'user0')); // A user without group. + $user1 = $this->getDataGenerator()->create_user(array('username' => 'user1')); // User for group 1. + $user2 = $this->getDataGenerator()->create_user(array('username' => 'user2')); // Two users for group 2. + $user2su = $this->getDataGenerator()->create_user(array('username' => 'user2su')); // (one suspended). + $user31 = $this->getDataGenerator()->create_user(array('username' => 'user31')); // Two users for group 3. + $user32 = $this->getDataGenerator()->create_user(array('username' => 'user32')); // (both enabled). + $userall = $this->getDataGenerator()->create_user(array('username' => 'userall')); // A user in all groups. + + // Create utility array of created users, to produce better assertion messages. + $createdusers = array(); + foreach (array($user0, $user1, $user2, $user2su, $user31, $user32, $userall) as $createduser) { + $createdusers[$createduser->id] = $createduser->username; + } + + // 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($user31->id, $course->id); + $this->getDataGenerator()->enrol_user($user32->id, $course->id); + $this->getDataGenerator()->enrol_user($userall->id, $course->id); + + // Create 3 groups. + $group1 = $this->getDataGenerator()->create_group(array('courseid' => $course->id)); + $group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id)); + $group3 = $this->getDataGenerator()->create_group(array('courseid' => $course->id)); + + // Add the users to the groups. + $this->getDataGenerator()->create_group_member(array('groupid' => $group1->id, 'userid' => $user1->id)); + $this->getDataGenerator()->create_group_member(array('groupid' => $group2->id, 'userid' => $user2->id)); + $this->getDataGenerator()->create_group_member(array('groupid' => $group2->id, 'userid' => $user2su->id)); + $this->getDataGenerator()->create_group_member(array('groupid' => $group3->id, 'userid' => $user31->id)); + $this->getDataGenerator()->create_group_member(array('groupid' => $group3->id, 'userid' => $user32->id)); + $this->getDataGenerator()->create_group_member(array('groupid' => $group1->id, 'userid' => $userall->id)); + $this->getDataGenerator()->create_group_member(array('groupid' => $group2->id, 'userid' => $userall->id)); + $this->getDataGenerator()->create_group_member(array('groupid' => $group3->id, 'userid' => $userall->id)); + + // Create a role to add the allowedcaps. Users will have this role assigned. + $roleid = $this->getDataGenerator()->create_role(); + // Allow the specified capabilities. + if (!empty($settings['allowedcaps'])) { + foreach ($settings['allowedcaps'] as $capability) { + assign_capability($capability, CAP_ALLOW, $roleid, $coursecontext); + } + } + + // For each of the users, configure everything, perform the call, and assert results. + foreach ($results as $user => $expectations) { + // Convert canview expectations into a nice array of ids for easier handling. + $canview = array(); + $exception = null; + // Analyse the expectations. + if (isset($expectations['canview'])) { + foreach ($expectations['canview'] as $canviewuser) { + $canview[] = $createdusers[${$canviewuser}->id]; + } + } else if (isset($expectations['exception'])) { + $exception = $expectations['exception']; + $this->setExpectedException($exception['type'], $exception['message']); + } else { + // Failed, only canview and exception are supported. + $this->markTestIncomplete('Incomplete, only canview and exception are supported'); + } + // Switch to the user and assign the role. + $this->setUser(${$user}); + role_assign($roleid, $USER->id, $coursecontext); + + // Convert groupid to proper id. + $groupid = 0; + if (isset($settings['groupid'])) { + $groupid = ${$settings['groupid']}->id; + } + + // Call to the function. + $options = array( + array('name' => 'withcapability', 'value' => $settings['withcapability']), + array('name' => 'groupid', 'value' => $groupid), + array('name' => 'onlyactive', 'value' => $settings['onlyactive']), + array('name' => 'userfields', 'value' => 'id') + ); + $enrolledusers = core_enrol_external::get_enrolled_users($course->id, $options); + + // We are only interested in ids to check visibility. + $viewed = array(); + // Verify the user canview the expected users. + foreach ($enrolledusers as $enrolleduser) { + $viewed[] = $createdusers[$enrolleduser['id']]; + } + // Verify viewed matches canview expectation (using canonicalize to ignore ordering). + $this->assertEquals($canview, $viewed, "Problem checking visible users for '{$createdusers[$USER->id]}'", 0, 1, true); + } + } + /** * Test get_users_courses */