3503 lines
141 KiB
PHP
3503 lines
141 KiB
PHP
<?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/>.
|
|
|
|
/**
|
|
* Provides {@link core_user_table_participants_search_test} 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
|
|
*/
|
|
|
|
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.
|
|
*
|
|
* @copyright 2020 Andrew Nicols <andrew@nicols.co.uk>
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
*/
|
|
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 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' => 5,
|
|
'expectedusers' => [
|
|
'c',
|
|
'd',
|
|
'e',
|
|
'g',
|
|
'h',
|
|
],
|
|
],
|
|
'NONE: Filter on student, teacher' => (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;
|
|
}
|
|
}
|