Rev 1 | Ir a la última revisión | Autoría | Comparar con el anterior | Ultima modificación | Ver Log |
<?php// This file is part of Moodle - https://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/>.declare(strict_types=1);namespace core_user\table;use advanced_testcase;use context_course;use context_coursecat;use core_table\local\filter\filter;use core_table\local\filter\integer_filter;use core_table\local\filter\string_filter;use core_user\table\participants_filterset;use core_user\table\participants_search;use moodle_recordset;use stdClass;/*** Tests for the implementation of {@link core_user_table_participants_search} class.** @package core_user* @category test* @copyright 2020 Andrew Nicols <andrew@nicols.co.uk>* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later*/final class participants_search_test extends advanced_testcase {/*** Helper to convert a moodle_recordset to an array of records.** @param moodle_recordset $recordset* @return array*/protected function convert_recordset_to_array(moodle_recordset $recordset): array {$records = [];foreach ($recordset as $record) {$records[$record->id] = $record;}$recordset->close();return $records;}/*** Create and enrol a set of users into the specified course.** @param stdClass $course* @param int $count* @param null|string $role* @return array*/protected function create_and_enrol_users(stdClass $course, int $count, ?string $role = null): array {$this->resetAfterTest(true);$users = [];for ($i = 0; $i < $count; $i++) {$user = $this->getDataGenerator()->create_user();$this->getDataGenerator()->enrol_user($user->id, $course->id, $role);$users[] = $user;}return $users;}/*** Create a new course with several types of user.** @param int $editingteachers The number of editing teachers to create in the course.* @param int $teachers The number of non-editing teachers to create in the course.* @param int $students The number of students to create in the course.* @param int $norole The number of users with no role to create in the course.* @return stdClass*/protected function create_course_with_users(int $editingteachers, int $teachers, int $students, int $norole): stdClass {$data = (object) ['course' => $this->getDataGenerator()->create_course(),'editingteachers' => [],'teachers' => [],'students' => [],'norole' => [],];$data->context = context_course::instance($data->course->id);$data->editingteachers = $this->create_and_enrol_users($data->course, $editingteachers, 'editingteacher');$data->teachers = $this->create_and_enrol_users($data->course, $teachers, 'teacher');$data->students = $this->create_and_enrol_users($data->course, $students, 'student');$data->norole = $this->create_and_enrol_users($data->course, $norole);return $data;}/*** Ensure that the roles filter works as expected with the provided test cases.** @param array $usersdata The list of users and their roles to create* @param array $testroles The list of roles to filter by* @param int $jointype The join type to use when combining filter values* @param int $count The expected count* @param array $expectedusers* @dataProvider role_provider*/public function test_roles_filter(array $usersdata, array $testroles, int $jointype, int $count, array $expectedusers): void {global $DB;$roles = $DB->get_records_menu('role', [], '', 'shortname, id');// Remove the default role.set_config('roleid', 0, 'enrol_manual');$course = $this->getDataGenerator()->create_course();$coursecontext = context_course::instance($course->id);$category = $DB->get_record('course_categories', ['id' => $course->category]);$categorycontext = context_coursecat::instance($category->id);$users = [];foreach ($usersdata as $username => $userdata) {$user = $this->getDataGenerator()->create_user(['username' => $username]);if (array_key_exists('courseroles', $userdata)) {$this->getDataGenerator()->enrol_user($user->id, $course->id, null);foreach ($userdata['courseroles'] as $rolename) {$this->getDataGenerator()->role_assign($roles[$rolename], $user->id, $coursecontext->id);}}if (array_key_exists('categoryroles', $userdata)) {foreach ($userdata['categoryroles'] as $rolename) {$this->getDataGenerator()->role_assign($roles[$rolename], $user->id, $categorycontext->id);}}$users[$username] = $user;}// Create a secondary course with users. We should not see these users.$this->create_course_with_users(1, 1, 1, 1);// Create the basic filter.$filterset = new participants_filterset();$filterset->add_filter(new integer_filter('courseid', null, [(int) $course->id]));// Create the role filter.$rolefilter = new integer_filter('roles');$filterset->add_filter($rolefilter);// Configure the filter.foreach ($testroles as $rolename) {$rolefilter->add_filter_value((int) $roles[$rolename]);}$rolefilter->set_join_type($jointype);// Run the search.$search = new participants_search($course, $coursecontext, $filterset);$rs = $search->get_participants();$this->assertInstanceOf(moodle_recordset::class, $rs);$records = $this->convert_recordset_to_array($rs);$this->assertCount($count, $records);$this->assertEquals($count, $search->get_total_participants_count());foreach ($expectedusers as $expecteduser) {$this->assertArrayHasKey($users[$expecteduser]->id, $records);}}/*** Data provider for role tests.** @return array*/public static function role_provider(): array {$tests = [// Users who only have one role each.'Users in each role' => (object) ['users' => ['a' => ['courseroles' => ['student',],],'b' => ['courseroles' => ['student',],],'c' => ['courseroles' => ['editingteacher',],],'d' => ['courseroles' => ['editingteacher',],],'e' => ['courseroles' => ['teacher',],],'f' => ['courseroles' => ['teacher',],],// User is enrolled in the course without role.'g' => ['courseroles' => [],],// User is a category manager and also enrolled without role in the course.'h' => ['courseroles' => [],'categoryroles' => ['manager',],],// User is a category manager and not enrolled in the course.// This user should not show up in any filter.'i' => ['categoryroles' => ['manager',],],],'expect' => [// Tests for jointype: ANY.'ANY: No role filter' => (object) ['roles' => [],'jointype' => filter::JOINTYPE_ANY,'count' => 8,'expectedusers' => ['a','b','c','d','e','f','g','h',],],'ANY: Filter on student' => (object) ['roles' => ['student'],'jointype' => filter::JOINTYPE_ANY,'count' => 2,'expectedusers' => ['a','b',],],'ANY: Filter on student, teacher' => (object) ['roles' => ['student', 'teacher'],'jointype' => filter::JOINTYPE_ANY,'count' => 4,'expectedusers' => ['a','b','e','f',],],'ANY: Filter on student, manager (category level role)' => (object) ['roles' => ['student', 'manager'],'jointype' => filter::JOINTYPE_ANY,'count' => 3,'expectedusers' => ['a','b','h',],],'ANY: Filter on student, coursecreator (not assigned)' => (object) ['roles' => ['student', 'coursecreator'],'jointype' => filter::JOINTYPE_ANY,'count' => 2,'expectedusers' => ['a','b',],],// Tests for jointype: ALL.'ALL: No role filter' => (object) ['roles' => [],'jointype' => filter::JOINTYPE_ALL,'count' => 8,'expectedusers' => ['a','b','c','d','e','f','g','h',],],'ALL: Filter on student' => (object) ['roles' => ['student'],'jointype' => filter::JOINTYPE_ALL,'count' => 2,'expectedusers' => ['a','b',],],'ALL: Filter on student, teacher' => (object) ['roles' => ['student', 'teacher'],'jointype' => filter::JOINTYPE_ALL,'count' => 0,'expectedusers' => [],],'ALL: Filter on student, manager (category level role))' => (object) ['roles' => ['student', 'manager'],'jointype' => filter::JOINTYPE_ALL,'count' => 0,'expectedusers' => [],],'ALL: Filter on student, coursecreator (not assigned))' => (object) ['roles' => ['student', 'coursecreator'],'jointype' => filter::JOINTYPE_ALL,'count' => 0,'expectedusers' => [],],// Tests for jointype: NONE.'NONE: No role filter' => (object) ['roles' => [],'jointype' => filter::JOINTYPE_NONE,'count' => 8,'expectedusers' => ['a','b','c','d','e','f','g','h',],],'NONE: Filter on student' => (object) ['roles' => ['student'],'jointype' => filter::JOINTYPE_NONE,'count' => 6,'expectedusers' => ['c','d','e','f','g','h',],],'NONE: Filter on student, teacher' => (object) ['roles' => ['student', 'teacher'],'jointype' => filter::JOINTYPE_NONE,'count' => 4,'expectedusers' => ['c','d','g','h',],],'NONE: Filter on student, manager (category level role))' => (object) ['roles' => ['student', 'manager'],'jointype' => filter::JOINTYPE_NONE,'count' => 5,'expectedusers' => ['c','d','e','f','g',],],'NONE: Filter on student, coursecreator (not assigned))' => (object) ['roles' => ['student', 'coursecreator'],'jointype' => filter::JOINTYPE_NONE,'count' => 6,'expectedusers' => ['c','d','e','f','g','h',],],],],'Users with multiple roles' => (object) ['users' => ['a' => ['courseroles' => ['student',],],'b' => ['courseroles' => ['student','teacher',],],'c' => ['courseroles' => ['editingteacher',],],'d' => ['courseroles' => ['editingteacher',],],'e' => ['courseroles' => ['teacher','editingteacher',],],'f' => ['courseroles' => ['teacher',],],// User is enrolled in the course without role.'g' => ['courseroles' => [],],// User is a category manager and also enrolled without role in the course.'h' => ['courseroles' => [],'categoryroles' => ['manager',],],// User is a category manager and not enrolled in the course.// This user should not show up in any filter.'i' => ['categoryroles' => ['manager',],],],'expect' => [// Tests for jointype: ANY.'ANY: No role filter' => (object) ['roles' => [],'jointype' => filter::JOINTYPE_ANY,'count' => 8,'expectedusers' => ['a','b','c','d','e','f','g','h',],],'ANY: Filter on student' => (object) ['roles' => ['student'],'jointype' => filter::JOINTYPE_ANY,'count' => 2,'expectedusers' => ['a','b',],],'ANY: Filter on teacher' => (object) ['roles' => ['teacher'],'jointype' => filter::JOINTYPE_ANY,'count' => 3,'expectedusers' => ['b','e','f',],],'ANY: Filter on editingteacher' => (object) ['roles' => ['editingteacher'],'jointype' => filter::JOINTYPE_ANY,'count' => 3,'expectedusers' => ['c','d','e',],],'ANY: Filter on student, teacher' => (object) ['roles' => ['student', 'teacher'],'jointype' => filter::JOINTYPE_ANY,'count' => 4,'expectedusers' => ['a','b','e','f',],],'ANY: Filter on teacher, editingteacher' => (object) ['roles' => ['teacher', 'editingteacher'],'jointype' => filter::JOINTYPE_ANY,'count' => 5,'expectedusers' => ['b','c','d','e','f',],],'ANY: Filter on student, manager (category level role)' => (object) ['roles' => ['student', 'manager'],'jointype' => filter::JOINTYPE_ANY,'count' => 3,'expectedusers' => ['a','b','h',],],'ANY: Filter on student, coursecreator (not assigned)' => (object) ['roles' => ['student', 'coursecreator'],'jointype' => filter::JOINTYPE_ANY,'count' => 2,'expectedusers' => ['a','b',],],// Tests for jointype: ALL.'ALL: No role filter' => (object) ['roles' => [],'jointype' => filter::JOINTYPE_ALL,'count' => 8,'expectedusers' => ['a','b','c','d','e','f','g','h',],],'ALL: Filter on student' => (object) ['roles' => ['student'],'jointype' => filter::JOINTYPE_ALL,'count' => 2,'expectedusers' => ['a','b',],],'ALL: Filter on teacher' => (object) ['roles' => ['teacher'],'jointype' => filter::JOINTYPE_ALL,'count' => 3,'expectedusers' => ['b','e','f',],],'ALL: Filter on editingteacher' => (object) ['roles' => ['editingteacher'],'jointype' => filter::JOINTYPE_ALL,'count' => 3,'expectedusers' => ['c','d','e',],],'ALL: Filter on student, teacher' => (object) ['roles' => ['student', 'teacher'],'jointype' => filter::JOINTYPE_ALL,'count' => 1,'expectedusers' => ['b',],],'ALL: Filter on teacher, editingteacher' => (object) ['roles' => ['teacher', 'editingteacher'],'jointype' => filter::JOINTYPE_ALL,'count' => 1,'expectedusers' => ['e',],],'ALL: Filter on student, manager (category level role)' => (object) ['roles' => ['student', 'manager'],'jointype' => filter::JOINTYPE_ALL,'count' => 0,'expectedusers' => [],],'ALL: Filter on student, coursecreator (not assigned)' => (object) ['roles' => ['student', 'coursecreator'],'jointype' => filter::JOINTYPE_ALL,'count' => 0,'expectedusers' => [],],// Tests for jointype: NONE.'NONE: No role filter' => (object) ['roles' => [],'jointype' => filter::JOINTYPE_NONE,'count' => 8,'expectedusers' => ['a','b','c','d','e','f','g','h',],],'NONE: Filter on student' => (object) ['roles' => ['student'],'jointype' => filter::JOINTYPE_NONE,'count' => 6,'expectedusers' => ['c','d','e','f','g','h',],],'NONE: Filter on teacher' => (object) ['roles' => ['teacher'],'jointype' => filter::JOINTYPE_NONE,'count' => 5,'expectedusers' => ['a','c','d','g','h',],],'NONE: Filter on editingteacher' => (object) ['roles' => ['editingteacher'],'jointype' => filter::JOINTYPE_NONE,'count' => 5,'expectedusers' => ['a','b','f','g','h',],],'NONE: Filter on student, teacher' => (object) ['roles' => ['student', 'teacher'],'jointype' => filter::JOINTYPE_NONE,'count' => 4,'expectedusers' => ['c','d','g','h',],],'NONE: Filter on teacher, editingteacher' => (object) ['roles' => ['teacher', 'editingteacher'],'jointype' => filter::JOINTYPE_NONE,'count' => 3,'expectedusers' => ['a','g','h',],],'NONE: Filter on student, manager (category level role)' => (object) ['roles' => ['student', 'manager'],'jointype' => filter::JOINTYPE_NONE,'count' => 5,'expectedusers' => ['c','d','e','f','g',],],'NONE: Filter on student, coursecreator (not assigned)' => (object) ['roles' => ['student', 'coursecreator'],'jointype' => filter::JOINTYPE_NONE,'count' => 6,'expectedusers' => ['c','d','e','f','g','h',],],],],];$finaltests = [];foreach ($tests as $testname => $testdata) {foreach ($testdata->expect as $expectname => $expectdata) {$finaltests["{$testname} => {$expectname}"] = ['users' => $testdata->users,'roles' => $expectdata->roles,'jointype' => $expectdata->jointype,'count' => $expectdata->count,'expectedusers' => $expectdata->expectedusers,];}}return $finaltests;}/*** Test participant search country filter** @param array $usersdata* @param array $countries* @param int $jointype* @param array $expectedusers** @dataProvider country_provider*/public function test_country_filter(array $usersdata, array $countries, int $jointype, array $expectedusers): void {$this->resetAfterTest();$course = $this->getDataGenerator()->create_course();$users = [];foreach ($usersdata as $username => $country) {$users[$username] = $this->getDataGenerator()->create_and_enrol($course, 'student', (object) ['username' => $username,'country' => $country,]);}// Add filters (courseid is required).$filterset = new participants_filterset();$filterset->add_filter(new integer_filter('courseid', null, [(int) $course->id]));$filterset->add_filter(new string_filter('country', $jointype, $countries));// Run the search, assert count matches the number of expected users.$search = new participants_search($course, context_course::instance($course->id), $filterset);$this->assertEquals(count($expectedusers), $search->get_total_participants_count());$rs = $search->get_participants();$this->assertInstanceOf(moodle_recordset::class, $rs);// Assert that each expected user is within the participant records.$records = $this->convert_recordset_to_array($rs);foreach ($expectedusers as $expecteduser) {$this->assertArrayHasKey($users[$expecteduser]->id, $records);}}/*** Data provider for {@see test_country_filter}** @return array*/public function country_provider(): array {$tests = ['users' => ['user1' => 'DE','user2' => 'ES','user3' => 'ES','user4' => 'GB',],'expects' => [// Tests for jointype: ANY.'ANY: No filter' => (object) ['countries' => [],'jointype' => filter::JOINTYPE_ANY,'expectedusers' => ['user1','user2','user3','user4',],],'ANY: Matching filters' => (object) ['countries' => ['DE','GB',],'jointype' => filter::JOINTYPE_ANY,'expectedusers' => ['user1','user4',],],'ANY: Non-matching filters' => (object) ['countries' => ['RU',],'jointype' => filter::JOINTYPE_ANY,'expectedusers' => [],],// Tests for jointype: ALL.'ALL: No filter' => (object) ['countries' => [],'jointype' => filter::JOINTYPE_ALL,'expectedusers' => ['user1','user2','user3','user4',],],'ALL: Matching filters' => (object) ['countries' => ['DE','GB',],'jointype' => filter::JOINTYPE_ALL,'expectedusers' => ['user1','user4',],],'ALL: Non-matching filters' => (object) ['countries' => ['RU',],'jointype' => filter::JOINTYPE_ALL,'expectedusers' => [],],// Tests for jointype: NONE.'NONE: No filter' => (object) ['countries' => [],'jointype' => filter::JOINTYPE_NONE,'expectedusers' => ['user1','user2','user3','user4',],],'NONE: Matching filters' => (object) ['countries' => ['DE','GB',],'jointype' => filter::JOINTYPE_NONE,'expectedusers' => ['user2','user3',],],'NONE: Non-matching filters' => (object) ['countries' => ['RU',],'jointype' => filter::JOINTYPE_NONE,'expectedusers' => ['user1','user2','user3','user4',],],],];$finaltests = [];foreach ($tests['expects'] as $testname => $test) {$finaltests[$testname] = ['users' => $tests['users'],'countries' => $test->countries,'jointype' => $test->jointype,'expectedusers' => $test->expectedusers,];}return $finaltests;}/*** Ensure that the keywords filter works as expected with the provided test cases.** @param array $usersdata The list of users to create* @param array $keywords The list of keywords to filter by* @param int $jointype The join type to use when combining filter values* @param int $count The expected count* @param array $expectedusers* @param string $asuser If non-blank, uses that user account (for identify field permission checks)* @dataProvider keywords_provider*/public function test_keywords_filter(array $usersdata, array $keywords, int $jointype, int $count,array $expectedusers, string $asuser): void {global $DB;$course = $this->getDataGenerator()->create_course();$coursecontext = context_course::instance($course->id);$users = [];// Create the custom user profile field and put it into showuseridentity.$this->getDataGenerator()->create_custom_profile_field(['datatype' => 'text', 'shortname' => 'frog', 'name' => 'Fave frog']);set_config('showuseridentity', 'email,profile_field_frog');foreach ($usersdata as $username => $userdata) {// Prevent randomly generated field values that may cause false fails.$userdata['firstnamephonetic'] = $userdata['firstnamephonetic'] ?? $userdata['firstname'];$userdata['lastnamephonetic'] = $userdata['lastnamephonetic'] ?? $userdata['lastname'];$userdata['middlename'] = $userdata['middlename'] ?? '';$userdata['alternatename'] = $userdata['alternatename'] ?? $username;$user = $this->getDataGenerator()->create_user($userdata);$this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');$users[$username] = $user;}// Create a secondary course with users. We should not see these users.$this->create_course_with_users(10, 10, 10, 10);// Create the basic filter.$filterset = new participants_filterset();$filterset->add_filter(new integer_filter('courseid', null, [(int) $course->id]));// Create the keyword filter.$keywordfilter = new string_filter('keywords');$filterset->add_filter($keywordfilter);// Configure the filter.foreach ($keywords as $keyword) {$keywordfilter->add_filter_value($keyword);}$keywordfilter->set_join_type($jointype);if ($asuser) {$this->setUser($DB->get_record('user', ['username' => $asuser]));}// Run the search.$search = new participants_search($course, $coursecontext, $filterset);$rs = $search->get_participants();$this->assertInstanceOf(moodle_recordset::class, $rs);$records = $this->convert_recordset_to_array($rs);$this->assertCount($count, $records);$this->assertEquals($count, $search->get_total_participants_count());foreach ($expectedusers as $expecteduser) {$this->assertArrayHasKey($users[$expecteduser]->id, $records);}}/*** Data provider for keywords tests.** @return array*/public function keywords_provider(): array {$tests = [// Users where the keyword matches basic user fields such as names and email.'Users with basic names' => (object) ['users' => ['adam.ant' => ['firstname' => 'Adam','lastname' => 'Ant',],'barbara.bennett' => ['firstname' => 'Barbara','lastname' => 'Bennett','alternatename' => 'Babs','firstnamephonetic' => 'Barbra','lastnamephonetic' => 'Benit','profile_field_frog' => 'Kermit',],'colin.carnforth' => ['firstname' => 'Colin','lastname' => 'Carnforth','middlename' => 'Jeffery',],'tony.rogers' => ['firstname' => 'Anthony','lastname' => 'Rogers','lastnamephonetic' => 'Rowjours','profile_field_frog' => 'Mr Toad',],'sarah.rester' => ['firstname' => 'Sarah','lastname' => 'Rester','email' => 'zazu@example.com','firstnamephonetic' => 'Sera',],],'expect' => [// Tests for jointype: ANY.'ANY: No filter' => (object) ['keywords' => [],'jointype' => filter::JOINTYPE_ANY,'count' => 5,'expectedusers' => ['adam.ant','barbara.bennett','colin.carnforth','tony.rogers','sarah.rester',],],'ANY: Filter on first name only' => (object) ['keywords' => ['adam'],'jointype' => filter::JOINTYPE_ANY,'count' => 1,'expectedusers' => ['adam.ant',],],'ANY: Filter on last name only' => (object) ['keywords' => ['BeNNeTt'],'jointype' => filter::JOINTYPE_ANY,'count' => 1,'expectedusers' => ['barbara.bennett',],],'ANY: Filter on first/Last name' => (object) ['keywords' => ['ant'],'jointype' => filter::JOINTYPE_ANY,'count' => 2,'expectedusers' => ['adam.ant','tony.rogers',],],'ANY: Filter on fullname only' => (object) ['keywords' => ['Barbara Bennett'],'jointype' => filter::JOINTYPE_ANY,'count' => 1,'expectedusers' => ['barbara.bennett',],],'ANY: Filter on middlename only' => (object) ['keywords' => ['Jeff'],'jointype' => filter::JOINTYPE_ANY,'count' => 1,'expectedusers' => ['colin.carnforth',],],'ANY: Filter on username (no match)' => (object) ['keywords' => ['sara.rester'],'jointype' => filter::JOINTYPE_ANY,'count' => 0,'expectedusers' => [],],'ANY: Filter on email only' => (object) ['keywords' => ['zazu'],'jointype' => filter::JOINTYPE_ANY,'count' => 1,'expectedusers' => ['sarah.rester',],],'ANY: Filter on first name phonetic only' => (object) ['keywords' => ['Sera'],'jointype' => filter::JOINTYPE_ANY,'count' => 1,'expectedusers' => ['sarah.rester',],],'ANY: Filter on last name phonetic only' => (object) ['keywords' => ['jour'],'jointype' => filter::JOINTYPE_ANY,'count' => 1,'expectedusers' => ['tony.rogers',],],'ANY: Filter on alternate name only' => (object) ['keywords' => ['Babs'],'jointype' => filter::JOINTYPE_ANY,'count' => 1,'expectedusers' => ['barbara.bennett',],],'ANY: Filter on multiple keywords (first/middle/last name)' => (object) ['keywords' => ['ant', 'Jeff', 'rog'],'jointype' => filter::JOINTYPE_ANY,'count' => 3,'expectedusers' => ['adam.ant','colin.carnforth','tony.rogers',],],'ANY: Filter on multiple keywords (phonetic/alternate names)' => (object) ['keywords' => ['era', 'Bab', 'ours'],'jointype' => filter::JOINTYPE_ANY,'count' => 3,'expectedusers' => ['barbara.bennett','sarah.rester','tony.rogers',],],'ANY: Filter on custom profile field' => (object) ['keywords' => ['Kermit', 'Mr Toad'],'jointype' => filter::JOINTYPE_ANY,'count' => 2,'expectedusers' => ['barbara.bennett','tony.rogers',],'asuser' => 'admin'],'ANY: Filter on custom profile field (no permissions)' => (object) ['keywords' => ['Kermit', 'Mr Toad'],'jointype' => filter::JOINTYPE_ANY,'count' => 0,'expectedusers' => [],'asuser' => 'barbara.bennett'],// Tests for jointype: ALL.'ALL: No filter' => (object) ['keywords' => [],'jointype' => filter::JOINTYPE_ALL,'count' => 5,'expectedusers' => ['adam.ant','barbara.bennett','colin.carnforth','tony.rogers','sarah.rester',],],'ALL: Filter on first name only' => (object) ['keywords' => ['adam'],'jointype' => filter::JOINTYPE_ALL,'count' => 1,'expectedusers' => ['adam.ant',],],'ALL: Filter on last name only' => (object) ['keywords' => ['BeNNeTt'],'jointype' => filter::JOINTYPE_ALL,'count' => 1,'expectedusers' => ['barbara.bennett',],],'ALL: Filter on first/Last name' => (object) ['keywords' => ['ant'],'jointype' => filter::JOINTYPE_ALL,'count' => 2,'expectedusers' => ['adam.ant','tony.rogers',],],'ALL: Filter on middlename only' => (object) ['keywords' => ['Jeff'],'jointype' => filter::JOINTYPE_ALL,'count' => 1,'expectedusers' => ['colin.carnforth',],],'ALL: Filter on username (no match)' => (object) ['keywords' => ['sara.rester'],'jointype' => filter::JOINTYPE_ALL,'count' => 0,'expectedusers' => [],],'ALL: Filter on email only' => (object) ['keywords' => ['zazu'],'jointype' => filter::JOINTYPE_ALL,'count' => 1,'expectedusers' => ['sarah.rester',],],'ALL: Filter on first name phonetic only' => (object) ['keywords' => ['Sera'],'jointype' => filter::JOINTYPE_ALL,'count' => 1,'expectedusers' => ['sarah.rester',],],'ALL: Filter on last name phonetic only' => (object) ['keywords' => ['jour'],'jointype' => filter::JOINTYPE_ALL,'count' => 1,'expectedusers' => ['tony.rogers',],],'ALL: Filter on alternate name only' => (object) ['keywords' => ['Babs'],'jointype' => filter::JOINTYPE_ALL,'count' => 1,'expectedusers' => ['barbara.bennett',],],'ALL: Filter on multiple keywords (first/last name)' => (object) ['keywords' => ['ant', 'rog'],'jointype' => filter::JOINTYPE_ALL,'count' => 1,'expectedusers' => ['tony.rogers',],],'ALL: Filter on multiple keywords (first/middle/last name)' => (object) ['keywords' => ['ant', 'Jeff', 'rog'],'jointype' => filter::JOINTYPE_ALL,'count' => 0,'expectedusers' => [],],'ALL: Filter on multiple keywords (phonetic/alternate names)' => (object) ['keywords' => ['Bab', 'bra', 'nit'],'jointype' => filter::JOINTYPE_ALL,'count' => 1,'expectedusers' => ['barbara.bennett',],],'ALL: Filter on custom profile field' => (object) ['keywords' => ['Kermit', 'Kermi'],'jointype' => filter::JOINTYPE_ALL,'count' => 1,'expectedusers' => ['barbara.bennett',],'asuser' => 'admin',],'ALL: Filter on custom profile field (no permissions)' => (object) ['keywords' => ['Kermit', 'Kermi'],'jointype' => filter::JOINTYPE_ALL,'count' => 0,'expectedusers' => [],'asuser' => 'barbara.bennett',],// Tests for jointype: NONE.'NONE: No filter' => (object) ['keywords' => [],'jointype' => filter::JOINTYPE_NONE,'count' => 5,'expectedusers' => ['adam.ant','barbara.bennett','colin.carnforth','tony.rogers','sarah.rester',],],'NONE: Filter on first name only' => (object) ['keywords' => ['ara'],'jointype' => filter::JOINTYPE_NONE,'count' => 3,'expectedusers' => ['adam.ant','colin.carnforth','tony.rogers',],],'NONE: Filter on last name only' => (object) ['keywords' => ['BeNNeTt'],'jointype' => filter::JOINTYPE_NONE,'count' => 4,'expectedusers' => ['adam.ant','colin.carnforth','tony.rogers','sarah.rester',],],'NONE: Filter on first/Last name' => (object) ['keywords' => ['ar'],'jointype' => filter::JOINTYPE_NONE,'count' => 2,'expectedusers' => ['adam.ant','tony.rogers',],],'NONE: Filter on middlename only' => (object) ['keywords' => ['Jeff'],'jointype' => filter::JOINTYPE_NONE,'count' => 4,'expectedusers' => ['adam.ant','barbara.bennett','tony.rogers','sarah.rester',],],'NONE: Filter on username (no match)' => (object) ['keywords' => ['sara.rester'],'jointype' => filter::JOINTYPE_NONE,'count' => 5,'expectedusers' => ['adam.ant','barbara.bennett','colin.carnforth','tony.rogers','sarah.rester',],],'NONE: Filter on email' => (object) ['keywords' => ['zazu'],'jointype' => filter::JOINTYPE_NONE,'count' => 4,'expectedusers' => ['adam.ant','barbara.bennett','colin.carnforth','tony.rogers',],],'NONE: Filter on first name phonetic only' => (object) ['keywords' => ['Sera'],'jointype' => filter::JOINTYPE_NONE,'count' => 4,'expectedusers' => ['adam.ant','barbara.bennett','colin.carnforth','tony.rogers',],],'NONE: Filter on last name phonetic only' => (object) ['keywords' => ['jour'],'jointype' => filter::JOINTYPE_NONE,'count' => 4,'expectedusers' => ['adam.ant','barbara.bennett','colin.carnforth','sarah.rester',],],'NONE: Filter on alternate name only' => (object) ['keywords' => ['Babs'],'jointype' => filter::JOINTYPE_NONE,'count' => 4,'expectedusers' => ['adam.ant','colin.carnforth','tony.rogers','sarah.rester',],],'NONE: Filter on multiple keywords (first/last name)' => (object) ['keywords' => ['ara', 'rog'],'jointype' => filter::JOINTYPE_NONE,'count' => 2,'expectedusers' => ['adam.ant','colin.carnforth',],],'NONE: Filter on multiple keywords (first/middle/last name)' => (object) ['keywords' => ['ant', 'Jeff', 'rog'],'jointype' => filter::JOINTYPE_NONE,'count' => 2,'expectedusers' => ['barbara.bennett','sarah.rester',],],'NONE: Filter on multiple keywords (phonetic/alternate names)' => (object) ['keywords' => ['Bab', 'bra', 'nit'],'jointype' => filter::JOINTYPE_NONE,'count' => 4,'expectedusers' => ['adam.ant','colin.carnforth','tony.rogers','sarah.rester',],],'NONE: Filter on custom profile field' => (object) ['keywords' => ['Kermit', 'Mr Toad'],'jointype' => filter::JOINTYPE_NONE,'count' => 3,'expectedusers' => ['adam.ant','colin.carnforth','sarah.rester',],'asuser' => 'admin',],'NONE: Filter on custom profile field (no permissions)' => (object) ['keywords' => ['Kermit', 'Mr Toad'],'jointype' => filter::JOINTYPE_NONE,'count' => 5,'expectedusers' => ['adam.ant','barbara.bennett','colin.carnforth','tony.rogers','sarah.rester',],'asuser' => 'barbara.bennett',],],],];$finaltests = [];foreach ($tests as $testname => $testdata) {foreach ($testdata->expect as $expectname => $expectdata) {$finaltests["{$testname} => {$expectname}"] = ['users' => $testdata->users,'keywords' => $expectdata->keywords,'jointype' => $expectdata->jointype,'count' => $expectdata->count,'expectedusers' => $expectdata->expectedusers,'asuser' => $expectdata->asuser ?? ''];}}return $finaltests;}/*** Ensure that the enrolment status filter works as expected with the provided test cases.** @param array $usersdata The list of users to create* @param array $statuses The list of statuses to filter by* @param int $jointype The join type to use when combining filter values* @param int $count The expected count* @param array $expectedusers* @dataProvider status_provider*/public function test_status_filter(array $usersdata, array $statuses, int $jointype, int $count, array $expectedusers): void {$course = $this->getDataGenerator()->create_course();$coursecontext = context_course::instance($course->id);$users = [];// Ensure sufficient capabilities to view all statuses.$this->setAdminUser();// Ensure all enrolment methods enabled.$enrolinstances = enrol_get_instances($course->id, false);foreach ($enrolinstances as $instance) {$plugin = enrol_get_plugin($instance->enrol);$plugin->update_status($instance, ENROL_INSTANCE_ENABLED);}foreach ($usersdata as $username => $userdata) {$user = $this->getDataGenerator()->create_user(['username' => $username]);if (array_key_exists('status', $userdata)) {foreach ($userdata['status'] as $enrolmethod => $status) {$this->getDataGenerator()->enrol_user($user->id, $course->id, 'student', $enrolmethod, 0, 0, $status);}}$users[$username] = $user;}// Create a secondary course with users. We should not see these users.$this->create_course_with_users(1, 1, 1, 1);// Create the basic filter.$filterset = new participants_filterset();$filterset->add_filter(new integer_filter('courseid', null, [(int) $course->id]));// Create the status filter.$statusfilter = new integer_filter('status');$filterset->add_filter($statusfilter);// Configure the filter.foreach ($statuses as $status) {$statusfilter->add_filter_value($status);}$statusfilter->set_join_type($jointype);// Run the search.$search = new participants_search($course, $coursecontext, $filterset);$rs = $search->get_participants();$this->assertInstanceOf(moodle_recordset::class, $rs);$records = $this->convert_recordset_to_array($rs);$this->assertCount($count, $records);$this->assertEquals($count, $search->get_total_participants_count());foreach ($expectedusers as $expecteduser) {$this->assertArrayHasKey($users[$expecteduser]->id, $records);}}/*** Data provider for status filter tests.** @return array*/public function status_provider(): array {$tests = [// Users with different statuses and enrolment methods (so multiple statuses are possible for the same user).'Users with different enrolment statuses' => (object) ['users' => ['a' => ['status' => ['manual' => ENROL_USER_ACTIVE,]],'b' => ['status' => ['self' => ENROL_USER_ACTIVE,]],'c' => ['status' => ['manual' => ENROL_USER_SUSPENDED,]],'d' => ['status' => ['self' => ENROL_USER_SUSPENDED,]],'e' => ['status' => ['manual' => ENROL_USER_ACTIVE,'self' => ENROL_USER_SUSPENDED,]],],'expect' => [// Tests for jointype: ANY.'ANY: No filter' => (object) ['status' => [],'jointype' => filter::JOINTYPE_ANY,'count' => 5,'expectedusers' => ['a','b','c','d','e',],],'ANY: Filter on active only' => (object) ['status' => [ENROL_USER_ACTIVE],'jointype' => filter::JOINTYPE_ANY,'count' => 3,'expectedusers' => ['a','b','e',],],'ANY: Filter on suspended only' => (object) ['status' => [ENROL_USER_SUSPENDED],'jointype' => filter::JOINTYPE_ANY,'count' => 3,'expectedusers' => ['c','d','e',],],'ANY: Filter on multiple statuses' => (object) ['status' => [ENROL_USER_ACTIVE, ENROL_USER_SUSPENDED],'jointype' => filter::JOINTYPE_ANY,'count' => 5,'expectedusers' => ['a','b','c','d','e',],],// Tests for jointype: ALL.'ALL: No filter' => (object) ['status' => [],'jointype' => filter::JOINTYPE_ALL,'count' => 5,'expectedusers' => ['a','b','c','d','e',],],'ALL: Filter on active only' => (object) ['status' => [ENROL_USER_ACTIVE],'jointype' => filter::JOINTYPE_ALL,'count' => 3,'expectedusers' => ['a','b','e',],],'ALL: Filter on suspended only' => (object) ['status' => [ENROL_USER_SUSPENDED],'jointype' => filter::JOINTYPE_ALL,'count' => 3,'expectedusers' => ['c','d','e',],],'ALL: Filter on multiple statuses' => (object) ['status' => [ENROL_USER_ACTIVE, ENROL_USER_SUSPENDED],'jointype' => filter::JOINTYPE_ALL,'count' => 1,'expectedusers' => ['e',],],// Tests for jointype: NONE.'NONE: No filter' => (object) ['status' => [],'jointype' => filter::JOINTYPE_NONE,'count' => 5,'expectedusers' => ['a','b','c','d','e',],],'NONE: Filter on active only' => (object) ['status' => [ENROL_USER_ACTIVE],'jointype' => filter::JOINTYPE_NONE,'count' => 3,'expectedusers' => ['c','d','e',],],'NONE: Filter on suspended only' => (object) ['status' => [ENROL_USER_SUSPENDED],'jointype' => filter::JOINTYPE_NONE,'count' => 3,'expectedusers' => ['a','b','e',],],'NONE: Filter on multiple statuses' => (object) ['status' => [ENROL_USER_ACTIVE, ENROL_USER_SUSPENDED],'jointype' => filter::JOINTYPE_NONE,'count' => 0,'expectedusers' => [],],],],];$finaltests = [];foreach ($tests as $testname => $testdata) {foreach ($testdata->expect as $expectname => $expectdata) {$finaltests["{$testname} => {$expectname}"] = ['users' => $testdata->users,'status' => $expectdata->status,'jointype' => $expectdata->jointype,'count' => $expectdata->count,'expectedusers' => $expectdata->expectedusers,];}}return $finaltests;}/*** Ensure that the enrolment methods filter works as expected with the provided test cases.** @param array $usersdata The list of users to create* @param array $enrolmethods The list of enrolment methods to filter by* @param int $jointype The join type to use when combining filter values* @param int $count The expected count* @param array $expectedusers* @dataProvider enrolments_provider*/public function test_enrolments_filter(array $usersdata, array $enrolmethods, int $jointype, int $count,array $expectedusers): void {$course = $this->getDataGenerator()->create_course();$coursecontext = context_course::instance($course->id);$users = [];// Ensure all enrolment methods enabled and mapped for setting the filter later.$enrolinstances = enrol_get_instances($course->id, false);$enrolinstancesmap = [];foreach ($enrolinstances as $instance) {$plugin = enrol_get_plugin($instance->enrol);$plugin->update_status($instance, ENROL_INSTANCE_ENABLED);$enrolinstancesmap[$instance->enrol] = (int) $instance->id;}foreach ($usersdata as $username => $userdata) {$user = $this->getDataGenerator()->create_user(['username' => $username]);if (array_key_exists('enrolmethods', $userdata)) {foreach ($userdata['enrolmethods'] as $enrolmethod) {$this->getDataGenerator()->enrol_user($user->id, $course->id, 'student', $enrolmethod);}}$users[$username] = $user;}// Create a secondary course with users. We should not see these users.$this->create_course_with_users(1, 1, 1, 1);// Create the basic filter.$filterset = new participants_filterset();$filterset->add_filter(new integer_filter('courseid', null, [(int) $course->id]));// Create the enrolment methods filter.$enrolmethodfilter = new integer_filter('enrolments');$filterset->add_filter($enrolmethodfilter);// Configure the filter.foreach ($enrolmethods as $enrolmethod) {$enrolmethodfilter->add_filter_value($enrolinstancesmap[$enrolmethod]);}$enrolmethodfilter->set_join_type($jointype);// Run the search.$search = new participants_search($course, $coursecontext, $filterset);$rs = $search->get_participants();$this->assertInstanceOf(moodle_recordset::class, $rs);$records = $this->convert_recordset_to_array($rs);$this->assertCount($count, $records);$this->assertEquals($count, $search->get_total_participants_count());foreach ($expectedusers as $expecteduser) {$this->assertArrayHasKey($users[$expecteduser]->id, $records);}}/*** Data provider for enrolments filter tests.** @return array*/public function enrolments_provider(): array {$tests = [// Users with different enrolment methods.'Users with different enrolment methods' => (object) ['users' => ['a' => ['enrolmethods' => ['manual',]],'b' => ['enrolmethods' => ['self',]],'c' => ['enrolmethods' => ['manual','self',]],],'expect' => [// Tests for jointype: ANY.'ANY: No filter' => (object) ['enrolmethods' => [],'jointype' => filter::JOINTYPE_ANY,'count' => 3,'expectedusers' => ['a','b','c',],],'ANY: Filter by manual enrolments only' => (object) ['enrolmethods' => ['manual'],'jointype' => filter::JOINTYPE_ANY,'count' => 2,'expectedusers' => ['a','c',],],'ANY: Filter by self enrolments only' => (object) ['enrolmethods' => ['self'],'jointype' => filter::JOINTYPE_ANY,'count' => 2,'expectedusers' => ['b','c',],],'ANY: Filter by multiple enrolment methods' => (object) ['enrolmethods' => ['manual', 'self'],'jointype' => filter::JOINTYPE_ANY,'count' => 3,'expectedusers' => ['a','b','c',],],// Tests for jointype: ALL.'ALL: No filter' => (object) ['enrolmethods' => [],'jointype' => filter::JOINTYPE_ALL,'count' => 3,'expectedusers' => ['a','b','c',],],'ALL: Filter by manual enrolments only' => (object) ['enrolmethods' => ['manual'],'jointype' => filter::JOINTYPE_ALL,'count' => 2,'expectedusers' => ['a','c',],],'ALL: Filter by multiple enrolment methods' => (object) ['enrolmethods' => ['manual', 'self'],'jointype' => filter::JOINTYPE_ALL,'count' => 1,'expectedusers' => ['c',],],// Tests for jointype: NONE.'NONE: No filter' => (object) ['enrolmethods' => [],'jointype' => filter::JOINTYPE_NONE,'count' => 3,'expectedusers' => ['a','b','c',],],'NONE: Filter by manual enrolments only' => (object) ['enrolmethods' => ['manual'],'jointype' => filter::JOINTYPE_NONE,'count' => 1,'expectedusers' => ['b',],],'NONE: Filter by multiple enrolment methods' => (object) ['enrolmethods' => ['manual', 'self'],'jointype' => filter::JOINTYPE_NONE,'count' => 0,'expectedusers' => [],],],],];$finaltests = [];foreach ($tests as $testname => $testdata) {foreach ($testdata->expect as $expectname => $expectdata) {$finaltests["{$testname} => {$expectname}"] = ['users' => $testdata->users,'enrolmethods' => $expectdata->enrolmethods,'jointype' => $expectdata->jointype,'count' => $expectdata->count,'expectedusers' => $expectdata->expectedusers,];}}return $finaltests;}/*** Ensure that the groups filter works as expected with the provided test cases.** @param array $usersdata The list of users to create* @param array $groupsavailable The names of groups that should be created in the course* @param array $filtergroups The names of groups to filter by* @param int $jointype The join type to use when combining filter values* @param int $count The expected count* @param array $expectedusers* @dataProvider groups_provider*/public function test_groups_filter(array $usersdata, array $groupsavailable, array $filtergroups, int $jointype, int $count,array $expectedusers): void {$course = $this->getDataGenerator()->create_course();$coursecontext = context_course::instance($course->id);$users = [];// Prepare data for filtering by users in no groups.$nogroupsdata = (object) ['id' => USERSWITHOUTGROUP,];// Map group names to group data.$groupsdata = ['nogroups' => $nogroupsdata];foreach ($groupsavailable as $groupname) {$groupinfo = ['courseid' => $course->id,'name' => $groupname,];$groupsdata[$groupname] = $this->getDataGenerator()->create_group($groupinfo);}foreach ($usersdata as $username => $userdata) {$user = $this->getDataGenerator()->create_user(['username' => $username]);$this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');if (array_key_exists('groups', $userdata)) {foreach ($userdata['groups'] as $groupname) {$userinfo = ['userid' => $user->id,'groupid' => (int) $groupsdata[$groupname]->id,];$this->getDataGenerator()->create_group_member($userinfo);}}$users[$username] = $user;}// Create a secondary course with users. We should not see these users.$this->create_course_with_users(1, 1, 1, 1);// Create the basic filter.$filterset = new participants_filterset();$filterset->add_filter(new integer_filter('courseid', null, [(int) $course->id]));// Create the groups filter.$groupsfilter = new integer_filter('groups');$filterset->add_filter($groupsfilter);// Configure the filter.foreach ($filtergroups as $filtergroupname) {$groupsfilter->add_filter_value((int) $groupsdata[$filtergroupname]->id);}$groupsfilter->set_join_type($jointype);// Run the search.$search = new participants_search($course, $coursecontext, $filterset);$rs = $search->get_participants();$this->assertInstanceOf(moodle_recordset::class, $rs);$records = $this->convert_recordset_to_array($rs);$this->assertCount($count, $records);$this->assertEquals($count, $search->get_total_participants_count());foreach ($expectedusers as $expecteduser) {$this->assertArrayHasKey($users[$expecteduser]->id, $records);}}/*** Data provider for groups filter tests.** @return array*/public function groups_provider(): array {$tests = ['Users in different groups' => (object) ['groupsavailable' => ['groupa','groupb','groupc',],'users' => ['a' => ['groups' => ['groupa'],],'b' => ['groups' => ['groupb'],],'c' => ['groups' => ['groupa', 'groupb'],],'d' => ['groups' => [],],],'expect' => [// Tests for jointype: ANY.'ANY: No filter' => (object) ['groups' => [],'jointype' => filter::JOINTYPE_ANY,'count' => 4,'expectedusers' => ['a','b','c','d',],],'ANY: Filter on a single group' => (object) ['groups' => ['groupa'],'jointype' => filter::JOINTYPE_ANY,'count' => 2,'expectedusers' => ['a','c',],],'ANY: Filter on a group with no members' => (object) ['groups' => ['groupc'],'jointype' => filter::JOINTYPE_ANY,'count' => 0,'expectedusers' => [],],'ANY: Filter on multiple groups' => (object) ['groups' => ['groupa', 'groupb'],'jointype' => filter::JOINTYPE_ANY,'count' => 3,'expectedusers' => ['a','b','c',],],'ANY: Filter on members of no groups only' => (object) ['groups' => ['nogroups'],'jointype' => filter::JOINTYPE_ANY,'count' => 1,'expectedusers' => ['d',],],'ANY: Filter on a single group or no groups' => (object) ['groups' => ['groupa', 'nogroups'],'jointype' => filter::JOINTYPE_ANY,'count' => 3,'expectedusers' => ['a','c','d',],],'ANY: Filter on multiple groups or no groups' => (object) ['groups' => ['groupa', 'groupb', 'nogroups'],'jointype' => filter::JOINTYPE_ANY,'count' => 4,'expectedusers' => ['a','b','c','d',],],// Tests for jointype: ALL.'ALL: No filter' => (object) ['groups' => [],'jointype' => filter::JOINTYPE_ALL,'count' => 4,'expectedusers' => ['a','b','c','d',],],'ALL: Filter on a single group' => (object) ['groups' => ['groupa'],'jointype' => filter::JOINTYPE_ALL,'count' => 2,'expectedusers' => ['a','c',],],'ALL: Filter on a group with no members' => (object) ['groups' => ['groupc'],'jointype' => filter::JOINTYPE_ALL,'count' => 0,'expectedusers' => [],],'ALL: Filter on members of no groups only' => (object) ['groups' => ['nogroups'],'jointype' => filter::JOINTYPE_ALL,'count' => 1,'expectedusers' => ['d',],],'ALL: Filter on multiple groups' => (object) ['groups' => ['groupa', 'groupb'],'jointype' => filter::JOINTYPE_ALL,'count' => 1,'expectedusers' => ['c',],],'ALL: Filter on a single group and no groups' => (object) ['groups' => ['groupa', 'nogroups'],'jointype' => filter::JOINTYPE_ALL,'count' => 0,'expectedusers' => [],],'ALL: Filter on multiple groups and no groups' => (object) ['groups' => ['groupa', 'groupb', 'nogroups'],'jointype' => filter::JOINTYPE_ALL,'count' => 0,'expectedusers' => [],],// Tests for jointype: NONE.'NONE: No filter' => (object) ['groups' => [],'jointype' => filter::JOINTYPE_NONE,'count' => 4,'expectedusers' => ['a','b','c','d',],],'NONE: Filter on a single group' => (object) ['groups' => ['groupa'],'jointype' => filter::JOINTYPE_NONE,'count' => 2,'expectedusers' => ['b','d',],],'NONE: Filter on a group with no members' => (object) ['groups' => ['groupc'],'jointype' => filter::JOINTYPE_NONE,'count' => 4,'expectedusers' => ['a','b','c','d',],],'NONE: Filter on members of no groups only' => (object) ['groups' => ['nogroups'],'jointype' => filter::JOINTYPE_NONE,'count' => 3,'expectedusers' => ['a','b','c',],],'NONE: Filter on multiple groups' => (object) ['groups' => ['groupa', 'groupb'],'jointype' => filter::JOINTYPE_NONE,'count' => 1,'expectedusers' => ['d',],],'NONE: Filter on a single group and no groups' => (object) ['groups' => ['groupa', 'nogroups'],'jointype' => filter::JOINTYPE_NONE,'count' => 1,'expectedusers' => ['b',],],'NONE: Filter on multiple groups and no groups' => (object) ['groups' => ['groupa', 'groupb', 'nogroups'],'jointype' => filter::JOINTYPE_NONE,'count' => 0,'expectedusers' => [],],],],];$finaltests = [];foreach ($tests as $testname => $testdata) {foreach ($testdata->expect as $expectname => $expectdata) {$finaltests["{$testname} => {$expectname}"] = ['users' => $testdata->users,'groupsavailable' => $testdata->groupsavailable,'filtergroups' => $expectdata->groups,'jointype' => $expectdata->jointype,'count' => $expectdata->count,'expectedusers' => $expectdata->expectedusers,];}}return $finaltests;}/*** Ensure that the groups filter works as expected when separate groups mode is enabled, with the provided test cases.** @param array $usersdata The list of users to create* @param array $groupsavailable The names of groups that should be created in the course* @param array $filtergroups The names of groups to filter by* @param int $jointype The join type to use when combining filter values* @param int $count The expected count* @param array $expectedusers* @param string $loginusername The user to login as for the tests* @dataProvider groups_separate_provider*/public function test_groups_filter_separate_groups(array $usersdata, array $groupsavailable, array $filtergroups, int $jointype,int $count, array $expectedusers, string $loginusername): void {$course = $this->getDataGenerator()->create_course();$coursecontext = context_course::instance($course->id);$users = [];// Enable separate groups mode on the course.$course->groupmode = SEPARATEGROUPS;$course->groupmodeforce = true;update_course($course);// Prepare data for filtering by users in no groups.$nogroupsdata = (object) ['id' => USERSWITHOUTGROUP,];// Map group names to group data.$groupsdata = ['nogroups' => $nogroupsdata];foreach ($groupsavailable as $groupname) {$groupinfo = ['courseid' => $course->id,'name' => $groupname,];$groupsdata[$groupname] = $this->getDataGenerator()->create_group($groupinfo);}foreach ($usersdata as $username => $userdata) {$user = $this->getDataGenerator()->create_user(['username' => $username]);$this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');if (array_key_exists('groups', $userdata)) {foreach ($userdata['groups'] as $groupname) {$userinfo = ['userid' => $user->id,'groupid' => (int) $groupsdata[$groupname]->id,];$this->getDataGenerator()->create_group_member($userinfo);}}$users[$username] = $user;if ($username == $loginusername) {$loginuser = $user;}}// Create a secondary course with users. We should not see these users.$this->create_course_with_users(1, 1, 1, 1);// Log in as the user to be tested.$this->setUser($loginuser);// Create the basic filter.$filterset = new participants_filterset();$filterset->add_filter(new integer_filter('courseid', null, [(int) $course->id]));// Create the groups filter.$groupsfilter = new integer_filter('groups');$filterset->add_filter($groupsfilter);// Configure the filter.foreach ($filtergroups as $filtergroupname) {$groupsfilter->add_filter_value((int) $groupsdata[$filtergroupname]->id);}$groupsfilter->set_join_type($jointype);// Run the search.$search = new participants_search($course, $coursecontext, $filterset);// Tests on user in no groups should throw an exception as they are not supported (participants are not visible to them).if (in_array('exception', $expectedusers)) {$this->expectException(\coding_exception::class);$rs = $search->get_participants();} else {// All other cases are tested as normal.$rs = $search->get_participants();$this->assertInstanceOf(moodle_recordset::class, $rs);$records = $this->convert_recordset_to_array($rs);$this->assertCount($count, $records);$this->assertEquals($count, $search->get_total_participants_count());foreach ($expectedusers as $expecteduser) {$this->assertArrayHasKey($users[$expecteduser]->id, $records);}}}/*** Data provider for groups filter tests.** @return array*/public function groups_separate_provider(): array {$tests = ['Users in different groups with separate groups mode enabled' => (object) ['groupsavailable' => ['groupa','groupb','groupc',],'users' => ['a' => ['groups' => ['groupa'],],'b' => ['groups' => ['groupb'],],'c' => ['groups' => ['groupa', 'groupb'],],'d' => ['groups' => [],],],'expect' => [// Tests for jointype: ANY.'ANY: No filter, user in one group' => (object) ['loginuser' => 'a','groups' => [],'jointype' => filter::JOINTYPE_ANY,'count' => 2,'expectedusers' => ['a','c',],],'ANY: No filter, user in multiple groups' => (object) ['loginuser' => 'c','groups' => [],'jointype' => filter::JOINTYPE_ANY,'count' => 3,'expectedusers' => ['a','b','c',],],'ANY: No filter, user in no groups' => (object) ['loginuser' => 'd','groups' => [],'jointype' => filter::JOINTYPE_ANY,'count' => 0,'expectedusers' => ['exception'],],'ANY: Filter on a single group, user in one group' => (object) ['loginuser' => 'a','groups' => ['groupa'],'jointype' => filter::JOINTYPE_ANY,'count' => 2,'expectedusers' => ['a','c',],],'ANY: Filter on a single group, user in multple groups' => (object) ['loginuser' => 'c','groups' => ['groupa'],'jointype' => filter::JOINTYPE_ANY,'count' => 2,'expectedusers' => ['a','c',],],'ANY: Filter on a single group, user in no groups' => (object) ['loginuser' => 'd','groups' => ['groupa'],'jointype' => filter::JOINTYPE_ANY,'count' => 0,'expectedusers' => ['exception'],],'ANY: Filter on multiple groups, user in one group (ignore invalid groups)' => (object) ['loginuser' => 'a','groups' => ['groupa', 'groupb'],'jointype' => filter::JOINTYPE_ANY,'count' => 2,'expectedusers' => ['a','c',],],'ANY: Filter on multiple groups, user in multiple groups' => (object) ['loginuser' => 'c','groups' => ['groupa', 'groupb'],'jointype' => filter::JOINTYPE_ANY,'count' => 3,'expectedusers' => ['a','b','c',],],'ANY: Filter on multiple groups or no groups, user in multiple groups (ignore no groups)' => (object) ['loginuser' => 'c','groups' => ['groupa', 'groupb', 'nogroups'],'jointype' => filter::JOINTYPE_ANY,'count' => 3,'expectedusers' => ['a','b','c',],],// Tests for jointype: ALL.'ALL: No filter, user in one group' => (object) ['loginuser' => 'a','groups' => [],'jointype' => filter::JOINTYPE_ALL,'count' => 2,'expectedusers' => ['a','c',],],'ALL: No filter, user in multiple groups' => (object) ['loginuser' => 'c','groups' => [],'jointype' => filter::JOINTYPE_ALL,'count' => 3,'expectedusers' => ['a','b','c',],],'ALL: No filter, user in no groups' => (object) ['loginuser' => 'd','groups' => [],'jointype' => filter::JOINTYPE_ALL,'count' => 0,'expectedusers' => ['exception'],],'ALL: Filter on a single group, user in one group' => (object) ['loginuser' => 'a','groups' => ['groupa'],'jointype' => filter::JOINTYPE_ALL,'count' => 2,'expectedusers' => ['a','c',],],'ALL: Filter on a single group, user in multple groups' => (object) ['loginuser' => 'c','groups' => ['groupa'],'jointype' => filter::JOINTYPE_ALL,'count' => 2,'expectedusers' => ['a','c',],],'ALL: Filter on a single group, user in no groups' => (object) ['loginuser' => 'd','groups' => ['groupa'],'jointype' => filter::JOINTYPE_ALL,'count' => 0,'expectedusers' => ['exception'],],'ALL: Filter on multiple groups, user in one group (ignore invalid groups)' => (object) ['loginuser' => 'a','groups' => ['groupa', 'groupb'],'jointype' => filter::JOINTYPE_ALL,'count' => 2,'expectedusers' => ['a','c',],],'ALL: Filter on multiple groups, user in multiple groups' => (object) ['loginuser' => 'c','groups' => ['groupa', 'groupb'],'jointype' => filter::JOINTYPE_ALL,'count' => 1,'expectedusers' => ['c',],],'ALL: Filter on multiple groups or no groups, user in multiple groups (ignore no groups)' => (object) ['loginuser' => 'c','groups' => ['groupa', 'groupb', 'nogroups'],'jointype' => filter::JOINTYPE_ALL,'count' => 1,'expectedusers' => ['c',],],// Tests for jointype: NONE.'NONE: No filter, user in one group' => (object) ['loginuser' => 'a','groups' => [],'jointype' => filter::JOINTYPE_NONE,'count' => 2,'expectedusers' => ['a','c',],],'NONE: No filter, user in multiple groups' => (object) ['loginuser' => 'c','groups' => [],'jointype' => filter::JOINTYPE_NONE,'count' => 3,'expectedusers' => ['a','b','c',],],'NONE: No filter, user in no groups' => (object) ['loginuser' => 'd','groups' => [],'jointype' => filter::JOINTYPE_NONE,'count' => 0,'expectedusers' => ['exception'],],'NONE: Filter on a single group, user in one group' => (object) ['loginuser' => 'a','groups' => ['groupa'],'jointype' => filter::JOINTYPE_NONE,'count' => 0,'expectedusers' => [],],'NONE: Filter on a single group, user in multple groups' => (object) ['loginuser' => 'c','groups' => ['groupa'],'jointype' => filter::JOINTYPE_NONE,'count' => 1,'expectedusers' => ['b',],],'NONE: Filter on a single group, user in no groups' => (object) ['loginuser' => 'd','groups' => ['groupa'],'jointype' => filter::JOINTYPE_NONE,'count' => 0,'expectedusers' => ['exception'],],'NONE: Filter on multiple groups, user in one group (ignore invalid groups)' => (object) ['loginuser' => 'a','groups' => ['groupa', 'groupb'],'jointype' => filter::JOINTYPE_NONE,'count' => 0,'expectedusers' => [],],'NONE: Filter on multiple groups, user in multiple groups' => (object) ['loginuser' => 'c','groups' => ['groupa', 'groupb'],'jointype' => filter::JOINTYPE_NONE,'count' => 0,'expectedusers' => [],],'NONE: Filter on multiple groups or no groups, user in multiple groups (ignore no groups)' => (object) ['loginuser' => 'c','groups' => ['groupa', 'groupb', 'nogroups'],'jointype' => filter::JOINTYPE_NONE,'count' => 0,'expectedusers' => [],],],],];$finaltests = [];foreach ($tests as $testname => $testdata) {foreach ($testdata->expect as $expectname => $expectdata) {$finaltests["{$testname} => {$expectname}"] = ['users' => $testdata->users,'groupsavailable' => $testdata->groupsavailable,'filtergroups' => $expectdata->groups,'jointype' => $expectdata->jointype,'count' => $expectdata->count,'expectedusers' => $expectdata->expectedusers,'loginusername' => $expectdata->loginuser,];}}return $finaltests;}/*** Ensure that the last access filter works as expected with the provided test cases.** @param array $usersdata The list of users to create* @param array $accesssince The last access data to filter by* @param int $jointype The join type to use when combining filter values* @param int $count The expected count* @param array $expectedusers* @dataProvider accesssince_provider*/public function test_accesssince_filter(array $usersdata, array $accesssince, int $jointype, int $count,array $expectedusers): void {$course = $this->getDataGenerator()->create_course();$coursecontext = context_course::instance($course->id);$users = [];foreach ($usersdata as $username => $userdata) {$usertimestamp = empty($userdata['lastlogin']) ? 0 : strtotime($userdata['lastlogin']);$user = $this->getDataGenerator()->create_user(['username' => $username]);$this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');// Create the record of the user's last access to the course.if ($usertimestamp > 0) {$this->getDataGenerator()->create_user_course_lastaccess($user, $course, $usertimestamp);}$users[$username] = $user;}// Create a secondary course with users. We should not see these users.$this->create_course_with_users(1, 1, 1, 1);// Create the basic filter.$filterset = new participants_filterset();$filterset->add_filter(new integer_filter('courseid', null, [(int) $course->id]));// Create the last access filter.$lastaccessfilter = new integer_filter('accesssince');$filterset->add_filter($lastaccessfilter);// Configure the filter.foreach ($accesssince as $accessstring) {$lastaccessfilter->add_filter_value(strtotime($accessstring));}$lastaccessfilter->set_join_type($jointype);// Run the search.$search = new participants_search($course, $coursecontext, $filterset);$rs = $search->get_participants();$this->assertInstanceOf(moodle_recordset::class, $rs);$records = $this->convert_recordset_to_array($rs);$this->assertCount($count, $records);$this->assertEquals($count, $search->get_total_participants_count());foreach ($expectedusers as $expecteduser) {$this->assertArrayHasKey($users[$expecteduser]->id, $records);}}/*** Data provider for last access filter tests.** @return array*/public function accesssince_provider(): array {$tests = [// Users with different last access times.'Users in different groups' => (object) ['users' => ['a' => ['lastlogin' => '-3 days',],'b' => ['lastlogin' => '-2 weeks',],'c' => ['lastlogin' => '-5 months',],'d' => ['lastlogin' => '-11 months',],'e' => [// Never logged in.'lastlogin' => '',],],'expect' => [// Tests for jointype: ANY.'ANY: No filter' => (object) ['accesssince' => [],'jointype' => filter::JOINTYPE_ANY,'count' => 5,'expectedusers' => ['a','b','c','d','e',],],'ANY: Filter on last login more than 1 year ago' => (object) ['accesssince' => ['-1 year'],'jointype' => filter::JOINTYPE_ANY,'count' => 1,'expectedusers' => ['e',],],'ANY: Filter on last login more than 6 months ago' => (object) ['accesssince' => ['-6 months'],'jointype' => filter::JOINTYPE_ANY,'count' => 2,'expectedusers' => ['d','e',],],'ANY: Filter on last login more than 3 weeks ago' => (object) ['accesssince' => ['-3 weeks'],'jointype' => filter::JOINTYPE_ANY,'count' => 3,'expectedusers' => ['c','d','e',],],'ANY: Filter on last login more than 5 days ago' => (object) ['accesssince' => ['-5 days'],'jointype' => filter::JOINTYPE_ANY,'count' => 4,'expectedusers' => ['b','c','d','e',],],'ANY: Filter on last login more than 2 days ago' => (object) ['accesssince' => ['-2 days'],'jointype' => filter::JOINTYPE_ANY,'count' => 5,'expectedusers' => ['a','b','c','d','e',],],// Tests for jointype: ALL.'ALL: No filter' => (object) ['accesssince' => [],'jointype' => filter::JOINTYPE_ALL,'count' => 5,'expectedusers' => ['a','b','c','d','e',],],'ALL: Filter on last login more than 1 year ago' => (object) ['accesssince' => ['-1 year'],'jointype' => filter::JOINTYPE_ALL,'count' => 1,'expectedusers' => ['e',],],'ALL: Filter on last login more than 6 months ago' => (object) ['accesssince' => ['-6 months'],'jointype' => filter::JOINTYPE_ALL,'count' => 2,'expectedusers' => ['d','e',],],'ALL: Filter on last login more than 3 weeks ago' => (object) ['accesssince' => ['-3 weeks'],'jointype' => filter::JOINTYPE_ALL,'count' => 3,'expectedusers' => ['c','d','e',],],'ALL: Filter on last login more than 5 days ago' => (object) ['accesssince' => ['-5 days'],'jointype' => filter::JOINTYPE_ALL,'count' => 4,'expectedusers' => ['b','c','d','e',],],'ALL: Filter on last login more than 2 days ago' => (object) ['accesssince' => ['-2 days'],'jointype' => filter::JOINTYPE_ALL,'count' => 5,'expectedusers' => ['a','b','c','d','e',],],// Tests for jointype: NONE.'NONE: No filter' => (object) ['accesssince' => [],'jointype' => filter::JOINTYPE_NONE,'count' => 5,'expectedusers' => ['a','b','c','d','e',],],'NONE: Filter on last login more than 1 year ago' => (object) ['accesssince' => ['-1 year'],'jointype' => filter::JOINTYPE_NONE,'count' => 4,'expectedusers' => ['a','b','c','d',],],'NONE: Filter on last login more than 6 months ago' => (object) ['accesssince' => ['-6 months'],'jointype' => filter::JOINTYPE_NONE,'count' => 3,'expectedusers' => ['a','b','c',],],'NONE: Filter on last login more than 3 weeks ago' => (object) ['accesssince' => ['-3 weeks'],'jointype' => filter::JOINTYPE_NONE,'count' => 2,'expectedusers' => ['a','b',],],'NONE: Filter on last login more than 5 days ago' => (object) ['accesssince' => ['-5 days'],'jointype' => filter::JOINTYPE_NONE,'count' => 1,'expectedusers' => ['a',],],'NONE: Filter on last login more than 2 days ago' => (object) ['accesssince' => ['-2 days'],'jointype' => filter::JOINTYPE_NONE,'count' => 0,'expectedusers' => [],],],],];$finaltests = [];foreach ($tests as $testname => $testdata) {foreach ($testdata->expect as $expectname => $expectdata) {$finaltests["{$testname} => {$expectname}"] = ['users' => $testdata->users,'accesssince' => $expectdata->accesssince,'jointype' => $expectdata->jointype,'count' => $expectdata->count,'expectedusers' => $expectdata->expectedusers,];}}return $finaltests;}/*** Ensure that the joins between filters in the filterset work as expected with the provided test cases.** @param array $usersdata The list of users to create* @param array $filterdata The data to filter by* @param array $groupsavailable The names of groups that should be created in the course* @param int $jointype The join type to used between each filter being applied* @param int $count The expected count* @param array $expectedusers* @dataProvider filterset_joins_provider*/public function test_filterset_joins(array $usersdata, array $filterdata, array $groupsavailable, int $jointype, int $count,array $expectedusers): void {global $DB;// Ensure sufficient capabilities to view all statuses.$this->setAdminUser();// Remove the default role.set_config('roleid', 0, 'enrol_manual');$course = $this->getDataGenerator()->create_course();$coursecontext = context_course::instance($course->id);$roles = $DB->get_records_menu('role', [], '', 'shortname, id');$users = [];// Ensure all enrolment methods are enabled (and mapped where required for filtering later).$enrolinstances = enrol_get_instances($course->id, false);$enrolinstancesmap = [];foreach ($enrolinstances as $instance) {$plugin = enrol_get_plugin($instance->enrol);$plugin->update_status($instance, ENROL_INSTANCE_ENABLED);$enrolinstancesmap[$instance->enrol] = (int) $instance->id;}// Create the required course groups and mapping.$nogroupsdata = (object) ['id' => USERSWITHOUTGROUP,];$groupsdata = ['nogroups' => $nogroupsdata];foreach ($groupsavailable as $groupname) {$groupinfo = ['courseid' => $course->id,'name' => $groupname,];$groupsdata[$groupname] = $this->getDataGenerator()->create_group($groupinfo);}// Create test users.foreach ($usersdata as $username => $userdata) {$usertimestamp = empty($userdata['lastlogin']) ? 0 : strtotime($userdata['lastlogin']);unset($userdata['lastlogin']);// Prevent randomly generated field values that may cause false fails.$userdata['firstnamephonetic'] = $userdata['firstnamephonetic'] ?? $userdata['firstname'];$userdata['lastnamephonetic'] = $userdata['lastnamephonetic'] ?? $userdata['lastname'];$userdata['middlename'] = $userdata['middlename'] ?? '';$userdata['alternatename'] = $userdata['alternatename'] ?? $username;$user = $this->getDataGenerator()->create_user($userdata);foreach ($userdata['enrolments'] as $details) {$this->getDataGenerator()->enrol_user($user->id, $course->id, $roles[$details['role']],$details['method'], 0, 0, $details['status']);}foreach ($userdata['groups'] as $groupname) {$userinfo = ['userid' => $user->id,'groupid' => (int) $groupsdata[$groupname]->id,];$this->getDataGenerator()->create_group_member($userinfo);}if ($usertimestamp > 0) {$this->getDataGenerator()->create_user_course_lastaccess($user, $course, $usertimestamp);}$users[$username] = $user;}// Create a secondary course with users. We should not see these users.$this->create_course_with_users(10, 10, 10, 10);// Create the basic filterset.$filterset = new participants_filterset();$filterset->set_join_type($jointype);$filterset->add_filter(new integer_filter('courseid', null, [(int) $course->id]));// Apply the keywords filter if required.if (array_key_exists('keywords', $filterdata)) {$keywordfilter = new string_filter('keywords');$filterset->add_filter($keywordfilter);foreach ($filterdata['keywords']['values'] as $keyword) {$keywordfilter->add_filter_value($keyword);}$keywordfilter->set_join_type($filterdata['keywords']['jointype']);}// Apply enrolment methods filter if required.if (array_key_exists('enrolmethods', $filterdata)) {$enrolmethodfilter = new integer_filter('enrolments');$filterset->add_filter($enrolmethodfilter);foreach ($filterdata['enrolmethods']['values'] as $enrolmethod) {$enrolmethodfilter->add_filter_value($enrolinstancesmap[$enrolmethod]);}$enrolmethodfilter->set_join_type($filterdata['enrolmethods']['jointype']);}// Apply roles filter if required.if (array_key_exists('courseroles', $filterdata)) {$rolefilter = new integer_filter('roles');$filterset->add_filter($rolefilter);foreach ($filterdata['courseroles']['values'] as $rolename) {$rolefilter->add_filter_value((int) $roles[$rolename]);}$rolefilter->set_join_type($filterdata['courseroles']['jointype']);}// Apply status filter if required.if (array_key_exists('status', $filterdata)) {$statusfilter = new integer_filter('status');$filterset->add_filter($statusfilter);foreach ($filterdata['status']['values'] as $status) {$statusfilter->add_filter_value($status);}$statusfilter->set_join_type($filterdata['status']['jointype']);}// Apply groups filter if required.if (array_key_exists('groups', $filterdata)) {$groupsfilter = new integer_filter('groups');$filterset->add_filter($groupsfilter);foreach ($filterdata['groups']['values'] as $filtergroupname) {$groupsfilter->add_filter_value((int) $groupsdata[$filtergroupname]->id);}$groupsfilter->set_join_type($filterdata['groups']['jointype']);}// Apply last access filter if required.if (array_key_exists('accesssince', $filterdata)) {$lastaccessfilter = new integer_filter('accesssince');$filterset->add_filter($lastaccessfilter);foreach ($filterdata['accesssince']['values'] as $accessstring) {$lastaccessfilter->add_filter_value(strtotime($accessstring));}$lastaccessfilter->set_join_type($filterdata['accesssince']['jointype']);}// Run the search.$search = new participants_search($course, $coursecontext, $filterset);$rs = $search->get_participants();$this->assertInstanceOf(moodle_recordset::class, $rs);$records = $this->convert_recordset_to_array($rs);$this->assertCount($count, $records);$this->assertEquals($count, $search->get_total_participants_count());foreach ($expectedusers as $expecteduser) {$this->assertArrayHasKey($users[$expecteduser]->id, $records);}}/*** Data provider for filterset join tests.** @return array*/public function filterset_joins_provider(): array {$tests = [// Users with different configurations.'Users with different configurations' => (object) ['groupsavailable' => ['groupa','groupb','groupc',],'users' => ['adam.ant' => ['firstname' => 'Adam','lastname' => 'Ant','enrolments' => [['role' => 'student','method' => 'manual','status' => ENROL_USER_ACTIVE,],],'groups' => ['groupa'],'lastlogin' => '-3 days',],'barbara.bennett' => ['firstname' => 'Barbara','lastname' => 'Bennett','enrolments' => [['role' => 'student','method' => 'manual','status' => ENROL_USER_ACTIVE,],['role' => 'teacher','method' => 'manual','status' => ENROL_USER_ACTIVE,],],'groups' => ['groupb'],'lastlogin' => '-2 weeks',],'colin.carnforth' => ['firstname' => 'Colin','lastname' => 'Carnforth','enrolments' => [['role' => 'editingteacher','method' => 'self','status' => ENROL_USER_SUSPENDED,],],'groups' => ['groupa', 'groupb'],'lastlogin' => '-5 months',],'tony.rogers' => ['firstname' => 'Anthony','lastname' => 'Rogers','enrolments' => [['role' => 'editingteacher','method' => 'self','status' => ENROL_USER_SUSPENDED,],],'groups' => [],'lastlogin' => '-10 months',],'sarah.rester' => ['firstname' => 'Sarah','lastname' => 'Rester','email' => 'zazu@example.com','enrolments' => [['role' => 'teacher','method' => 'manual','status' => ENROL_USER_ACTIVE,],['role' => 'editingteacher','method' => 'self','status' => ENROL_USER_SUSPENDED,],],'groups' => [],'lastlogin' => '-11 months',],'morgan.crikeyson' => ['firstname' => 'Morgan','lastname' => 'Crikeyson','enrolments' => [['role' => 'teacher','method' => 'manual','status' => ENROL_USER_ACTIVE,],],'groups' => ['groupa'],'lastlogin' => '-1 week',],'jonathan.bravo' => ['firstname' => 'Jonathan','lastname' => 'Bravo','enrolments' => [['role' => 'student','method' => 'manual','status' => ENROL_USER_ACTIVE,],],'groups' => [],// Never logged in.'lastlogin' => '',],],'expect' => [// Tests for jointype: ANY.'ANY: No filters in filterset' => (object) ['filterdata' => [],'jointype' => filter::JOINTYPE_ANY,'count' => 7,'expectedusers' => ['adam.ant','barbara.bennett','colin.carnforth','tony.rogers','sarah.rester','morgan.crikeyson','jonathan.bravo',],],'ANY: Filterset containing a single filter type' => (object) ['filterdata' => ['enrolmethods' => ['values' => ['self'],'jointype' => filter::JOINTYPE_ANY,],],'jointype' => filter::JOINTYPE_ANY,'count' => 3,'expectedusers' => ['colin.carnforth','tony.rogers','sarah.rester',],],'ANY: Filterset matching all filter types on different users' => (object) ['filterdata' => [// Match Adam only.'keywords' => ['values' => ['adam'],'jointype' => filter::JOINTYPE_ALL,],// Match Sarah only.'enrolmethods' => ['values' => ['manual', 'self'],'jointype' => filter::JOINTYPE_ALL,],// Match Barbara only.'courseroles' => ['values' => ['student', 'teacher'],'jointype' => filter::JOINTYPE_ALL,],// Match Sarah only.'status' => ['values' => [ENROL_USER_ACTIVE, ENROL_USER_SUSPENDED],'jointype' => filter::JOINTYPE_ALL,],// Match Colin only.'groups' => ['values' => ['groupa', 'groupb'],'jointype' => filter::JOINTYPE_ALL,],// Match Jonathan only.'accesssince' => ['values' => ['-1 year'],'jointype' => filter::JOINTYPE_ALL,],],'jointype' => filter::JOINTYPE_ANY,'count' => 5,// Morgan and Tony are not matched, to confirm filtering is not just returning all users.'expectedusers' => ['adam.ant','barbara.bennett','colin.carnforth','sarah.rester','jonathan.bravo',],],// Tests for jointype: ALL.'ALL: No filters in filterset' => (object) ['filterdata' => [],'jointype' => filter::JOINTYPE_ALL,'count' => 7,'expectedusers' => ['adam.ant','barbara.bennett','colin.carnforth','tony.rogers','sarah.rester','morgan.crikeyson','jonathan.bravo',],],'ALL: Filterset containing a single filter type' => (object) ['filterdata' => ['enrolmethods' => ['values' => ['self'],'jointype' => filter::JOINTYPE_ANY,],],'jointype' => filter::JOINTYPE_ALL,'count' => 3,'expectedusers' => ['colin.carnforth','tony.rogers','sarah.rester',],],'ALL: Filterset combining all filter types' => (object) ['filterdata' => [// Exclude Adam, Tony, Morgan and Jonathan.'keywords' => ['values' => ['ar'],'jointype' => filter::JOINTYPE_ANY,],// Exclude Colin and Tony.'enrolmethods' => ['values' => ['manual'],'jointype' => filter::JOINTYPE_ANY,],// Exclude Adam, Barbara and Jonathan.'courseroles' => ['values' => ['student'],'jointype' => filter::JOINTYPE_NONE,],// Exclude Colin and Tony.'status' => ['values' => [ENROL_USER_ACTIVE],'jointype' => filter::JOINTYPE_ALL,],// Exclude Barbara.'groups' => ['values' => ['groupa', 'nogroups'],'jointype' => filter::JOINTYPE_ANY,],// Exclude Adam, Colin and Barbara.'accesssince' => ['values' => ['-6 months'],'jointype' => filter::JOINTYPE_ALL,],],'jointype' => filter::JOINTYPE_ALL,'count' => 1,'expectedusers' => ['sarah.rester',],],// Tests for jointype: NONE.'NONE: No filters in filterset' => (object) ['filterdata' => [],'jointype' => filter::JOINTYPE_NONE,'count' => 7,'expectedusers' => ['adam.ant','barbara.bennett','colin.carnforth','tony.rogers','sarah.rester','morgan.crikeyson','jonathan.bravo',],],'NONE: Filterset containing a single filter type' => (object) ['filterdata' => ['enrolmethods' => ['values' => ['self'],'jointype' => filter::JOINTYPE_ANY,],],'jointype' => filter::JOINTYPE_NONE,'count' => 4,'expectedusers' => ['adam.ant','barbara.bennett','morgan.crikeyson','jonathan.bravo',],],'NONE: Filterset combining all filter types' => (object) ['filterdata' => [// Excludes Adam.'keywords' => ['values' => ['adam'],'jointype' => filter::JOINTYPE_ANY,],// Excludes Colin, Tony and Sarah.'enrolmethods' => ['values' => ['self'],'jointype' => filter::JOINTYPE_ANY,],// Excludes Jonathan.'courseroles' => ['values' => ['student'],'jointype' => filter::JOINTYPE_NONE,],// Excludes Colin, Tony and Sarah.'status' => ['values' => [ENROL_USER_SUSPENDED],'jointype' => filter::JOINTYPE_ALL,],// Excludes Adam, Colin, Tony, Sarah, Morgan and Jonathan.'groups' => ['values' => ['groupa', 'nogroups'],'jointype' => filter::JOINTYPE_ANY,],// Excludes Tony and Sarah.'accesssince' => ['values' => ['-6 months'],'jointype' => filter::JOINTYPE_ALL,],],'jointype' => filter::JOINTYPE_NONE,'count' => 1,'expectedusers' => ['barbara.bennett',],],'NONE: Filterset combining several filter types and a double-negative on keyword' => (object) ['jointype' => filter::JOINTYPE_NONE,'filterdata' => [// Note: This is a jointype NONE on the parent jointype NONE.// The result therefore negated in this instance.// Include Adam and Anthony.'keywords' => ['values' => ['ant'],'jointype' => filter::JOINTYPE_NONE,],// Excludes Tony.'status' => ['values' => [ENROL_USER_SUSPENDED],'jointype' => filter::JOINTYPE_ALL,],],'count' => 1,'expectedusers' => ['adam.ant',],],],],];$finaltests = [];foreach ($tests as $testname => $testdata) {foreach ($testdata->expect as $expectname => $expectdata) {$finaltests["{$testname} => {$expectname}"] = ['users' => $testdata->users,'filterdata' => $expectdata->filterdata,'groupsavailable' => $testdata->groupsavailable,'jointype' => $expectdata->jointype,'count' => $expectdata->count,'expectedusers' => $expectdata->expectedusers,];}}return $finaltests;}}