MDL-69026 user: Correct statuses => status in test
[moodle.git] / user / tests / table / participants_search_test.php
CommitLineData
d525ac4b
AN
1<?php
2// This file is part of Moodle - https://moodle.org/
3//
4// Moodle is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Moodle is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
16
17/**
18 * Provides {@link core_user_table_participants_search_test} class.
19 *
20 * @package core_user
21 * @category test
22 * @copyright 2020 Andrew Nicols <andrew@nicols.co.uk>
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24 */
25
26declare(strict_types=1);
27
28namespace core_user\table;
29
30use advanced_testcase;
31use context_course;
32use context_coursecat;
33use core_table\local\filter\filter;
34use core_table\local\filter\integer_filter;
35use core_table\local\filter\string_filter;
36use core_user\table\participants_filterset;
37use core_user\table\participants_search;
38use moodle_recordset;
39use stdClass;
40
41/**
42 * Tests for the implementation of {@link core_user_table_participants_search} class.
43 *
44 * @copyright 2020 Andrew Nicols <andrew@nicols.co.uk>
45 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
46 */
47class participants_search_test extends advanced_testcase {
48
49 /**
50 * Helper to convert a moodle_recordset to an array of records.
51 *
52 * @param moodle_recordset $recordset
53 * @return array
54 */
55 protected function convert_recordset_to_array(moodle_recordset $recordset): array {
56 $records = [];
57 foreach ($recordset as $record) {
58 $records[$record->id] = $record;
59 }
60 $recordset->close();
61
62 return $records;
63 }
64
65 /**
66 * Create and enrol a set of users into the specified course.
67 *
68 * @param stdClass $course
69 * @param int $count
70 * @param null|string $role
71 * @return array
72 */
73 protected function create_and_enrol_users(stdClass $course, int $count, ?string $role = null): array {
74 $this->resetAfterTest(true);
75 $users = [];
76
77 for ($i = 0; $i < $count; $i++) {
78 $user = $this->getDataGenerator()->create_user();
79 $this->getDataGenerator()->enrol_user($user->id, $course->id, $role);
80 $users[] = $user;
81 }
82
83 return $users;
84 }
85
86 /**
87 * Create a new course with several types of user.
88 *
89 * @param int $editingteachers The number of editing teachers to create in the course.
90 * @param int $teachers The number of non-editing teachers to create in the course.
91 * @param int $students The number of students to create in the course.
92 * @param int $norole The number of users with no role to create in the course.
93 * @return stdClass
94 */
95 protected function create_course_with_users(int $editingteachers, int $teachers, int $students, int $norole): stdClass {
96 $data = (object) [
97 'course' => $this->getDataGenerator()->create_course(),
98 'editingteachers' => [],
99 'teachers' => [],
100 'students' => [],
101 'norole' => [],
102 ];
103
104 $data->context = context_course::instance($data->course->id);
105
106 $data->editingteachers = $this->create_and_enrol_users($data->course, $editingteachers, 'editingteacher');
107 $data->teachers = $this->create_and_enrol_users($data->course, $teachers, 'teacher');
108 $data->students = $this->create_and_enrol_users($data->course, $students, 'student');
109 $data->norole = $this->create_and_enrol_users($data->course, $norole);
110
111 return $data;
112 }
113 /**
114 * Ensure that the roles filter works as expected with the provided test cases.
115 *
116 * @param array $usersdata The list of users and their roles to create
117 * @param array $testroles The list of roles to filter by
118 * @param int $jointype The join type to use when combining filter values
119 * @param int $count The expected count
120 * @param array $expectedusers
121 * @dataProvider role_provider
122 */
123 public function test_roles_filter(array $usersdata, array $testroles, int $jointype, int $count, array $expectedusers): void {
124 global $DB;
125
126 $roles = $DB->get_records_menu('role', [], '', 'shortname, id');
127
128 // Remove the default role.
129 set_config('roleid', 0, 'enrol_manual');
130
131 $course = $this->getDataGenerator()->create_course();
132 $coursecontext = context_course::instance($course->id);
133
134 $category = $DB->get_record('course_categories', ['id' => $course->category]);
135 $categorycontext = context_coursecat::instance($category->id);
136
137 $users = [];
138
139 foreach ($usersdata as $username => $userdata) {
140 $user = $this->getDataGenerator()->create_user(['username' => $username]);
141
142 if (array_key_exists('courseroles', $userdata)) {
143 $this->getDataGenerator()->enrol_user($user->id, $course->id, null);
144 foreach ($userdata['courseroles'] as $rolename) {
145 $this->getDataGenerator()->role_assign($roles[$rolename], $user->id, $coursecontext->id);
146 }
147 }
148
149 if (array_key_exists('categoryroles', $userdata)) {
150 foreach ($userdata['categoryroles'] as $rolename) {
151 $this->getDataGenerator()->role_assign($roles[$rolename], $user->id, $categorycontext->id);
152 }
153 }
154 $users[$username] = $user;
155 }
156
157 // Create a secondary course with users. We should not see these users.
158 $this->create_course_with_users(1, 1, 1, 1);
159
160 // Create the basic filter.
161 $filterset = new participants_filterset();
162 $filterset->add_filter(new integer_filter('courseid', null, [(int) $course->id]));
163
164 // Create the role filter.
165 $rolefilter = new integer_filter('roles');
166 $filterset->add_filter($rolefilter);
167
168 // Configure the filter.
169 foreach ($testroles as $rolename) {
170 $rolefilter->add_filter_value((int) $roles[$rolename]);
171 }
172 $rolefilter->set_join_type($jointype);
173
174 // Run the search.
175 $search = new participants_search($course, $coursecontext, $filterset);
176 $rs = $search->get_participants();
177 $this->assertInstanceOf(moodle_recordset::class, $rs);
178 $records = $this->convert_recordset_to_array($rs);
179
180 $this->assertCount($count, $records);
181 $this->assertEquals($count, $search->get_total_participants_count());
182
183 foreach ($expectedusers as $expecteduser) {
184 $this->assertArrayHasKey($users[$expecteduser]->id, $records);
185 }
186 }
187
188 /**
189 * Data provider for role tests.
190 *
191 * @return array
192 */
193 public function role_provider(): array {
194 $tests = [
195 // Users who only have one role each.
196 'Users in each role' => (object) [
197 'users' => [
198 'a' => [
199 'courseroles' => [
200 'student',
201 ],
202 ],
203 'b' => [
204 'courseroles' => [
205 'student',
206 ],
207 ],
208 'c' => [
209 'courseroles' => [
210 'editingteacher',
211 ],
212 ],
213 'd' => [
214 'courseroles' => [
215 'editingteacher',
216 ],
217 ],
218 'e' => [
219 'courseroles' => [
220 'teacher',
221 ],
222 ],
223 'f' => [
224 'courseroles' => [
225 'teacher',
226 ],
227 ],
228 // User is enrolled in the course without role.
229 'g' => [
230 'courseroles' => [
231 ],
232 ],
233
234 // User is a category manager and also enrolled without role in the course.
235 'h' => [
236 'courseroles' => [
237 ],
238 'categoryroles' => [
239 'manager',
240 ],
241 ],
242
243 // User is a category manager and not enrolled in the course.
244 // This user should not show up in any filter.
245 'i' => [
246 'categoryroles' => [
247 'manager',
248 ],
249 ],
250 ],
251 'expect' => [
252 // Tests for jointype: ANY.
253 'ANY: No role filter' => (object) [
254 'roles' => [],
255 'jointype' => filter::JOINTYPE_ANY,
256 'count' => 8,
257 'expectedusers' => [
258 'a',
259 'b',
260 'c',
261 'd',
262 'e',
263 'f',
264 'g',
265 'h',
266 ],
267 ],
268 'ANY: Filter on student' => (object) [
269 'roles' => ['student'],
270 'jointype' => filter::JOINTYPE_ANY,
271 'count' => 2,
272 'expectedusers' => [
273 'a',
274 'b',
275 ],
276 ],
277 'ANY: Filter on student, teacher' => (object) [
278 'roles' => ['student', 'teacher'],
279 'jointype' => filter::JOINTYPE_ANY,
280 'count' => 4,
281 'expectedusers' => [
282 'a',
283 'b',
284 'e',
285 'f',
286 ],
287 ],
288 'ANY: Filter on student, manager (category level role)' => (object) [
289 'roles' => ['student', 'manager'],
290 'jointype' => filter::JOINTYPE_ANY,
291 'count' => 3,
292 'expectedusers' => [
293 'a',
294 'b',
295 'h',
296 ],
297 ],
298 'ANY: Filter on student, coursecreator (not assigned)' => (object) [
299 'roles' => ['student', 'coursecreator'],
300 'jointype' => filter::JOINTYPE_ANY,
301 'count' => 2,
302 'expectedusers' => [
303 'a',
304 'b',
305 ],
306 ],
307
308 // Tests for jointype: ALL.
309 'ALL: No role filter' => (object) [
310 'roles' => [],
311 'jointype' => filter::JOINTYPE_ALL,
312 'count' => 8,
313 'expectedusers' => [
314 'a',
315 'b',
316 'c',
317 'd',
318 'e',
319 'f',
320 'g',
321 'h',
322 ],
323 ],
324 'ALL: Filter on student' => (object) [
325 'roles' => ['student'],
326 'jointype' => filter::JOINTYPE_ALL,
327 'count' => 2,
328 'expectedusers' => [
329 'a',
330 'b',
331 ],
332 ],
4fa63633
MH
333 'ALL: Filter on student, teacher' => (object) [
334 'roles' => ['student', 'teacher'],
335 'jointype' => filter::JOINTYPE_ALL,
336 'count' => 0,
337 'expectedusers' => [],
338 ],
339 'ALL: Filter on student, manager (category level role))' => (object) [
340 'roles' => ['student', 'manager'],
341 'jointype' => filter::JOINTYPE_ALL,
342 'count' => 0,
343 'expectedusers' => [],
344 ],
345 'ALL: Filter on student, coursecreator (not assigned))' => (object) [
346 'roles' => ['student', 'coursecreator'],
347 'jointype' => filter::JOINTYPE_ALL,
348 'count' => 0,
349 'expectedusers' => [],
350 ],
351
352 // Tests for jointype: NONE.
353 'NONE: No role filter' => (object) [
354 'roles' => [],
355 'jointype' => filter::JOINTYPE_NONE,
356 'count' => 8,
357 'expectedusers' => [
358 'a',
359 'b',
360 'c',
361 'd',
362 'e',
363 'f',
364 'g',
365 'h',
366 ],
367 ],
368 'NONE: Filter on student' => (object) [
369 'roles' => ['student'],
370 'jointype' => filter::JOINTYPE_NONE,
371 'count' => 6,
372 'expectedusers' => [
373 'c',
374 'd',
375 'e',
376 'f',
377 'g',
378 'h',
379 ],
380 ],
381 'NONE: Filter on student, teacher' => (object) [
382 'roles' => ['student', 'teacher'],
383 'jointype' => filter::JOINTYPE_NONE,
384 'count' => 4,
385 'expectedusers' => [
386 'c',
387 'd',
388 'g',
389 'h',
390 ],
391 ],
392 'NONE: Filter on student, manager (category level role))' => (object) [
393 'roles' => ['student', 'manager'],
394 'jointype' => filter::JOINTYPE_NONE,
395 'count' => 5,
396 'expectedusers' => [
397 'c',
398 'd',
399 'e',
400 'f',
401 'g',
402 ],
403 ],
404 'NONE: Filter on student, coursecreator (not assigned))' => (object) [
405 'roles' => ['student', 'coursecreator'],
406 'jointype' => filter::JOINTYPE_NONE,
407 'count' => 6,
408 'expectedusers' => [
409 'c',
410 'd',
411 'e',
412 'f',
413 'g',
414 'h',
415 ],
416 ],
d525ac4b
AN
417 ],
418 ],
419 'Users with multiple roles' => (object) [
420 'users' => [
421 'a' => [
422 'courseroles' => [
423 'student',
424 ],
425 ],
426 'b' => [
427 'courseroles' => [
428 'student',
429 'teacher',
430 ],
431 ],
432 'c' => [
433 'courseroles' => [
434 'editingteacher',
435 ],
436 ],
437 'd' => [
438 'courseroles' => [
439 'editingteacher',
440 ],
441 ],
442 'e' => [
443 'courseroles' => [
444 'teacher',
445 'editingteacher',
446 ],
447 ],
448 'f' => [
449 'courseroles' => [
450 'teacher',
451 ],
452 ],
453
454 // User is enrolled in the course without role.
455 'g' => [
456 'courseroles' => [
457 ],
458 ],
459
460 // User is a category manager and also enrolled without role in the course.
461 'h' => [
462 'courseroles' => [
463 ],
464 'categoryroles' => [
465 'manager',
466 ],
467 ],
468
469 // User is a category manager and not enrolled in the course.
470 // This user should not show up in any filter.
471 'i' => [
472 'categoryroles' => [
473 'manager',
474 ],
475 ],
476 ],
477 'expect' => [
478 // Tests for jointype: ANY.
479 'ANY: No role filter' => (object) [
480 'roles' => [],
481 'jointype' => filter::JOINTYPE_ANY,
482 'count' => 8,
483 'expectedusers' => [
484 'a',
485 'b',
486 'c',
487 'd',
488 'e',
489 'f',
490 'g',
491 'h',
492 ],
493 ],
494 'ANY: Filter on student' => (object) [
495 'roles' => ['student'],
496 'jointype' => filter::JOINTYPE_ANY,
497 'count' => 2,
498 'expectedusers' => [
499 'a',
500 'b',
501 ],
502 ],
503 'ANY: Filter on teacher' => (object) [
504 'roles' => ['teacher'],
505 'jointype' => filter::JOINTYPE_ANY,
506 'count' => 3,
507 'expectedusers' => [
508 'b',
509 'e',
510 'f',
511 ],
512 ],
513 'ANY: Filter on editingteacher' => (object) [
514 'roles' => ['editingteacher'],
515 'jointype' => filter::JOINTYPE_ANY,
516 'count' => 3,
517 'expectedusers' => [
518 'c',
519 'd',
520 'e',
521 ],
522 ],
523 'ANY: Filter on student, teacher' => (object) [
524 'roles' => ['student', 'teacher'],
525 'jointype' => filter::JOINTYPE_ANY,
526 'count' => 4,
527 'expectedusers' => [
528 'a',
529 'b',
530 'e',
531 'f',
532 ],
533 ],
534 'ANY: Filter on teacher, editingteacher' => (object) [
535 'roles' => ['teacher', 'editingteacher'],
536 'jointype' => filter::JOINTYPE_ANY,
537 'count' => 5,
538 'expectedusers' => [
539 'b',
540 'c',
541 'd',
542 'e',
543 'f',
544 ],
545 ],
546 'ANY: Filter on student, manager (category level role)' => (object) [
547 'roles' => ['student', 'manager'],
548 'jointype' => filter::JOINTYPE_ANY,
549 'count' => 3,
550 'expectedusers' => [
551 'a',
552 'b',
553 'h',
554 ],
555 ],
556 'ANY: Filter on student, coursecreator (not assigned)' => (object) [
557 'roles' => ['student', 'coursecreator'],
558 'jointype' => filter::JOINTYPE_ANY,
559 'count' => 2,
560 'expectedusers' => [
561 'a',
562 'b',
563 ],
564 ],
565
566 // Tests for jointype: ALL.
567 'ALL: No role filter' => (object) [
568 'roles' => [],
569 'jointype' => filter::JOINTYPE_ALL,
570 'count' => 8,
571 'expectedusers' => [
572 'a',
573 'b',
574 'c',
575 'd',
576 'e',
577 'f',
578 'g',
579 'h',
580 ],
581 ],
582 'ALL: Filter on student' => (object) [
583 'roles' => ['student'],
584 'jointype' => filter::JOINTYPE_ALL,
585 'count' => 2,
586 'expectedusers' => [
587 'a',
588 'b',
589 ],
590 ],
591 'ALL: Filter on teacher' => (object) [
592 'roles' => ['teacher'],
593 'jointype' => filter::JOINTYPE_ALL,
594 'count' => 3,
595 'expectedusers' => [
596 'b',
597 'e',
598 'f',
599 ],
600 ],
601 'ALL: Filter on editingteacher' => (object) [
602 'roles' => ['editingteacher'],
603 'jointype' => filter::JOINTYPE_ALL,
604 'count' => 3,
605 'expectedusers' => [
606 'c',
607 'd',
608 'e',
609 ],
610 ],
4fa63633
MH
611 'ALL: Filter on student, teacher' => (object) [
612 'roles' => ['student', 'teacher'],
613 'jointype' => filter::JOINTYPE_ALL,
614 'count' => 1,
615 'expectedusers' => [
616 'b',
617 ],
618 ],
619 'ALL: Filter on teacher, editingteacher' => (object) [
620 'roles' => ['teacher', 'editingteacher'],
621 'jointype' => filter::JOINTYPE_ALL,
622 'count' => 1,
623 'expectedusers' => [
624 'e',
625 ],
626 ],
627 'ALL: Filter on student, manager (category level role)' => (object) [
628 'roles' => ['student', 'manager'],
629 'jointype' => filter::JOINTYPE_ALL,
630 'count' => 0,
631 'expectedusers' => [],
632 ],
633 'ALL: Filter on student, coursecreator (not assigned)' => (object) [
634 'roles' => ['student', 'coursecreator'],
635 'jointype' => filter::JOINTYPE_ALL,
636 'count' => 0,
637 'expectedusers' => [],
638 ],
639
640 // Tests for jointype: NONE.
641 'NONE: No role filter' => (object) [
642 'roles' => [],
643 'jointype' => filter::JOINTYPE_NONE,
644 'count' => 8,
645 'expectedusers' => [
646 'a',
647 'b',
648 'c',
649 'd',
650 'e',
651 'f',
652 'g',
653 'h',
654 ],
655 ],
656 'NONE: Filter on student' => (object) [
657 'roles' => ['student'],
658 'jointype' => filter::JOINTYPE_NONE,
659 'count' => 6,
660 'expectedusers' => [
661 'c',
662 'd',
663 'e',
664 'f',
665 'g',
666 'h',
667 ],
668 ],
669 'NONE: Filter on teacher' => (object) [
670 'roles' => ['teacher'],
671 'jointype' => filter::JOINTYPE_NONE,
672 'count' => 5,
673 'expectedusers' => [
674 'a',
675 'c',
676 'd',
677 'g',
678 'h',
679 ],
680 ],
681 'NONE: Filter on editingteacher' => (object) [
682 'roles' => ['editingteacher'],
683 'jointype' => filter::JOINTYPE_NONE,
684 'count' => 5,
685 'expectedusers' => [
686 'a',
687 'b',
688 'f',
689 'g',
690 'h',
691 ],
692 ],
693 'NONE: Filter on student, teacher' => (object) [
694 'roles' => ['student', 'teacher'],
695 'jointype' => filter::JOINTYPE_NONE,
696 'count' => 5,
697 'expectedusers' => [
698 'c',
699 'd',
700 'e',
701 'g',
702 'h',
703 ],
704 ],
705 'NONE: Filter on student, teacher' => (object) [
706 'roles' => ['teacher', 'editingteacher'],
707 'jointype' => filter::JOINTYPE_NONE,
708 'count' => 3,
709 'expectedusers' => [
710 'a',
711 'g',
712 'h',
713 ],
714 ],
715 'NONE: Filter on student, manager (category level role)' => (object) [
716 'roles' => ['student', 'manager'],
717 'jointype' => filter::JOINTYPE_NONE,
718 'count' => 5,
719 'expectedusers' => [
720 'c',
721 'd',
722 'e',
723 'f',
724 'g',
725 ],
726 ],
727 'NONE: Filter on student, coursecreator (not assigned)' => (object) [
728 'roles' => ['student', 'coursecreator'],
729 'jointype' => filter::JOINTYPE_NONE,
730 'count' => 6,
731 'expectedusers' => [
732 'c',
733 'd',
734 'e',
735 'f',
736 'g',
737 'h',
738 ],
739 ],
d525ac4b
AN
740 ],
741 ],
742 ];
743
744 $finaltests = [];
745 foreach ($tests as $testname => $testdata) {
746 foreach ($testdata->expect as $expectname => $expectdata) {
747 $finaltests["{$testname} => {$expectname}"] = [
748 'users' => $testdata->users,
749 'roles' => $expectdata->roles,
750 'jointype' => $expectdata->jointype,
751 'count' => $expectdata->count,
752 'expectedusers' => $expectdata->expectedusers,
753 ];
754 }
755 }
756
757 return $finaltests;
758 }
759
760 /**
761 * Ensure that the keywords filter works as expected with the provided test cases.
762 *
763 * @param array $usersdata The list of users to create
764 * @param array $keywords The list of keywords to filter by
765 * @param int $jointype The join type to use when combining filter values
766 * @param int $count The expected count
767 * @param array $expectedusers
768 * @dataProvider keywords_provider
769 */
770 public function test_keywords_filter(array $usersdata, array $keywords, int $jointype, int $count, array $expectedusers): void {
771 $course = $this->getDataGenerator()->create_course();
772 $coursecontext = context_course::instance($course->id);
773 $users = [];
774
775 foreach ($usersdata as $username => $userdata) {
4fa63633
MH
776 // Prevent randomly generated field values that may cause false fails.
777 $userdata['firstnamephonetic'] = $userdata['firstnamephonetic'] ?? $userdata['firstname'];
778 $userdata['lastnamephonetic'] = $userdata['lastnamephonetic'] ?? $userdata['lastname'];
779 $userdata['middlename'] = $userdata['middlename'] ?? '';
780 $userdata['alternatename'] = $userdata['alternatename'] ?? $username;
781
d525ac4b
AN
782 $user = $this->getDataGenerator()->create_user($userdata);
783 $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
784 $users[$username] = $user;
785 }
786
787 // Create a secondary course with users. We should not see these users.
788 $this->create_course_with_users(10, 10, 10, 10);
789
790 // Create the basic filter.
791 $filterset = new participants_filterset();
792 $filterset->add_filter(new integer_filter('courseid', null, [(int) $course->id]));
793
794 // Create the keyword filter.
795 $keywordfilter = new string_filter('keywords');
796 $filterset->add_filter($keywordfilter);
797
798 // Configure the filter.
799 foreach ($keywords as $keyword) {
800 $keywordfilter->add_filter_value($keyword);
801 }
802 $keywordfilter->set_join_type($jointype);
803
804 // Run the search.
805 $search = new participants_search($course, $coursecontext, $filterset);
806 $rs = $search->get_participants();
807 $this->assertInstanceOf(moodle_recordset::class, $rs);
808 $records = $this->convert_recordset_to_array($rs);
809
810 $this->assertCount($count, $records);
811 $this->assertEquals($count, $search->get_total_participants_count());
812
813 foreach ($expectedusers as $expecteduser) {
814 $this->assertArrayHasKey($users[$expecteduser]->id, $records);
815 }
816 }
817
818 /**
819 * Data provider for keywords tests.
820 *
821 * @return array
822 */
823 public function keywords_provider(): array {
824 $tests = [
4fa63633 825 // Users where the keyword matches basic user fields such as names and email.
d525ac4b
AN
826 'Users with basic names' => (object) [
827 'users' => [
828 'adam.ant' => [
829 'firstname' => 'Adam',
830 'lastname' => 'Ant',
831 ],
832 'barbara.bennett' => [
833 'firstname' => 'Barbara',
834 'lastname' => 'Bennett',
4fa63633
MH
835 'alternatename' => 'Babs',
836 'firstnamephonetic' => 'Barbra',
837 'lastnamephonetic' => 'Benit',
d525ac4b
AN
838 ],
839 'colin.carnforth' => [
840 'firstname' => 'Colin',
841 'lastname' => 'Carnforth',
4fa63633 842 'middlename' => 'Jeffery',
d525ac4b
AN
843 ],
844 'tony.rogers' => [
845 'firstname' => 'Anthony',
846 'lastname' => 'Rogers',
4fa63633 847 'lastnamephonetic' => 'Rowjours',
d525ac4b
AN
848 ],
849 'sarah.rester' => [
850 'firstname' => 'Sarah',
851 'lastname' => 'Rester',
852 'email' => 'zazu@example.com',
4fa63633 853 'firstnamephonetic' => 'Sera',
d525ac4b
AN
854 ],
855 ],
856 'expect' => [
857 // Tests for jointype: ANY.
858 'ANY: No filter' => (object) [
859 'keywords' => [],
860 'jointype' => filter::JOINTYPE_ANY,
861 'count' => 5,
862 'expectedusers' => [
863 'adam.ant',
864 'barbara.bennett',
865 'colin.carnforth',
866 'tony.rogers',
867 'sarah.rester',
868 ],
869 ],
4fa63633 870 'ANY: Filter on first name only' => (object) [
d525ac4b
AN
871 'keywords' => ['adam'],
872 'jointype' => filter::JOINTYPE_ANY,
873 'count' => 1,
874 'expectedusers' => [
875 'adam.ant',
876 ],
877 ],
4fa63633 878 'ANY: Filter on last name only' => (object) [
d525ac4b
AN
879 'keywords' => ['BeNNeTt'],
880 'jointype' => filter::JOINTYPE_ANY,
881 'count' => 1,
882 'expectedusers' => [
883 'barbara.bennett',
884 ],
885 ],
4fa63633 886 'ANY: Filter on first/Last name' => (object) [
d525ac4b
AN
887 'keywords' => ['ant'],
888 'jointype' => filter::JOINTYPE_ANY,
889 'count' => 2,
890 'expectedusers' => [
891 'adam.ant',
892 'tony.rogers',
893 ],
894 ],
4fa63633
MH
895 'ANY: Filter on middlename only' => (object) [
896 'keywords' => ['Jeff'],
897 'jointype' => filter::JOINTYPE_ANY,
898 'count' => 1,
899 'expectedusers' => [
900 'colin.carnforth',
901 ],
902 ],
903 'ANY: Filter on username (no match)' => (object) [
d525ac4b
AN
904 'keywords' => ['sara.rester'],
905 'jointype' => filter::JOINTYPE_ANY,
906 'count' => 0,
907 'expectedusers' => [],
908 ],
4fa63633 909 'ANY: Filter on email only' => (object) [
d525ac4b
AN
910 'keywords' => ['zazu'],
911 'jointype' => filter::JOINTYPE_ANY,
912 'count' => 1,
913 'expectedusers' => [
914 'sarah.rester',
915 ],
916 ],
4fa63633
MH
917 'ANY: Filter on first name phonetic only' => (object) [
918 'keywords' => ['Sera'],
919 'jointype' => filter::JOINTYPE_ANY,
920 'count' => 1,
921 'expectedusers' => [
922 'sarah.rester',
923 ],
924 ],
925 'ANY: Filter on last name phonetic only' => (object) [
926 'keywords' => ['jour'],
927 'jointype' => filter::JOINTYPE_ANY,
928 'count' => 1,
929 'expectedusers' => [
930 'tony.rogers',
931 ],
932 ],
933 'ANY: Filter on alternate name only' => (object) [
934 'keywords' => ['Babs'],
935 'jointype' => filter::JOINTYPE_ANY,
936 'count' => 1,
937 'expectedusers' => [
938 'barbara.bennett',
939 ],
940 ],
941 'ANY: Filter on multiple keywords (first/middle/last name)' => (object) [
942 'keywords' => ['ant', 'Jeff', 'rog'],
943 'jointype' => filter::JOINTYPE_ANY,
944 'count' => 3,
945 'expectedusers' => [
946 'adam.ant',
947 'colin.carnforth',
948 'tony.rogers',
949 ],
950 ],
951 'ANY: Filter on multiple keywords (phonetic/alternate names)' => (object) [
952 'keywords' => ['era', 'Bab', 'ours'],
953 'jointype' => filter::JOINTYPE_ANY,
954 'count' => 3,
955 'expectedusers' => [
956 'barbara.bennett',
957 'sarah.rester',
958 'tony.rogers',
959 ],
960 ],
d525ac4b
AN
961
962 // Tests for jointype: ALL.
963 'ALL: No filter' => (object) [
964 'keywords' => [],
965 'jointype' => filter::JOINTYPE_ALL,
966 'count' => 5,
967 'expectedusers' => [
968 'adam.ant',
969 'barbara.bennett',
970 'colin.carnforth',
971 'tony.rogers',
972 'sarah.rester',
973 ],
974 ],
4fa63633 975 'ALL: Filter on first name only' => (object) [
d525ac4b
AN
976 'keywords' => ['adam'],
977 'jointype' => filter::JOINTYPE_ALL,
978 'count' => 1,
979 'expectedusers' => [
980 'adam.ant',
981 ],
982 ],
4fa63633 983 'ALL: Filter on last name only' => (object) [
d525ac4b
AN
984 'keywords' => ['BeNNeTt'],
985 'jointype' => filter::JOINTYPE_ALL,
986 'count' => 1,
987 'expectedusers' => [
988 'barbara.bennett',
989 ],
990 ],
4fa63633 991 'ALL: Filter on first/Last name' => (object) [
d525ac4b
AN
992 'keywords' => ['ant'],
993 'jointype' => filter::JOINTYPE_ALL,
994 'count' => 2,
995 'expectedusers' => [
996 'adam.ant',
997 'tony.rogers',
998 ],
999 ],
4fa63633
MH
1000 'ALL: Filter on middlename only' => (object) [
1001 'keywords' => ['Jeff'],
1002 'jointype' => filter::JOINTYPE_ALL,
1003 'count' => 1,
1004 'expectedusers' => [
1005 'colin.carnforth',
1006 ],
1007 ],
1008 'ALL: Filter on username (no match)' => (object) [
d525ac4b
AN
1009 'keywords' => ['sara.rester'],
1010 'jointype' => filter::JOINTYPE_ALL,
1011 'count' => 0,
1012 'expectedusers' => [],
1013 ],
4fa63633 1014 'ALL: Filter on email only' => (object) [
d525ac4b
AN
1015 'keywords' => ['zazu'],
1016 'jointype' => filter::JOINTYPE_ALL,
1017 'count' => 1,
1018 'expectedusers' => [
1019 'sarah.rester',
1020 ],
1021 ],
4fa63633
MH
1022 'ALL: Filter on first name phonetic only' => (object) [
1023 'keywords' => ['Sera'],
1024 'jointype' => filter::JOINTYPE_ALL,
1025 'count' => 1,
1026 'expectedusers' => [
1027 'sarah.rester',
1028 ],
1029 ],
1030 'ALL: Filter on last name phonetic only' => (object) [
1031 'keywords' => ['jour'],
1032 'jointype' => filter::JOINTYPE_ALL,
1033 'count' => 1,
1034 'expectedusers' => [
1035 'tony.rogers',
1036 ],
1037 ],
1038 'ALL: Filter on alternate name only' => (object) [
1039 'keywords' => ['Babs'],
1040 'jointype' => filter::JOINTYPE_ALL,
1041 'count' => 1,
1042 'expectedusers' => [
1043 'barbara.bennett',
1044 ],
1045 ],
1046 'ALL: Filter on multiple keywords (first/last name)' => (object) [
d525ac4b
AN
1047 'keywords' => ['ant', 'rog'],
1048 'jointype' => filter::JOINTYPE_ALL,
1049 'count' => 1,
1050 'expectedusers' => [
1051 'tony.rogers',
1052 ],
1053 ],
4fa63633
MH
1054 'ALL: Filter on multiple keywords (first/middle/last name)' => (object) [
1055 'keywords' => ['ant', 'Jeff', 'rog'],
1056 'jointype' => filter::JOINTYPE_ALL,
1057 'count' => 0,
1058 'expectedusers' => [],
1059 ],
1060 'ALL: Filter on multiple keywords (phonetic/alternate names)' => (object) [
1061 'keywords' => ['Bab', 'bra', 'nit'],
1062 'jointype' => filter::JOINTYPE_ALL,
1063 'count' => 1,
1064 'expectedusers' => [
1065 'barbara.bennett',
1066 ],
1067 ],
1068
1069 // Tests for jointype: NONE.
1070 'NONE: No filter' => (object) [
1071 'keywords' => [],
1072 'jointype' => filter::JOINTYPE_NONE,
1073 'count' => 5,
1074 'expectedusers' => [
1075 'adam.ant',
1076 'barbara.bennett',
1077 'colin.carnforth',
1078 'tony.rogers',
1079 'sarah.rester',
1080 ],
1081 ],
1082 'NONE: Filter on first name only' => (object) [
1083 'keywords' => ['ara'],
1084 'jointype' => filter::JOINTYPE_NONE,
1085 'count' => 3,
1086 'expectedusers' => [
1087 'adam.ant',
1088 'colin.carnforth',
1089 'tony.rogers',
1090 ],
1091 ],
1092 'NONE: Filter on last name only' => (object) [
1093 'keywords' => ['BeNNeTt'],
1094 'jointype' => filter::JOINTYPE_NONE,
1095 'count' => 4,
1096 'expectedusers' => [
1097 'adam.ant',
1098 'colin.carnforth',
1099 'tony.rogers',
1100 'sarah.rester',
1101 ],
1102 ],
1103 'NONE: Filter on first/Last name' => (object) [
1104 'keywords' => ['ar'],
1105 'jointype' => filter::JOINTYPE_NONE,
1106 'count' => 2,
1107 'expectedusers' => [
1108 'adam.ant',
1109 'tony.rogers',
1110 ],
1111 ],
1112 'NONE: Filter on middlename only' => (object) [
1113 'keywords' => ['Jeff'],
1114 'jointype' => filter::JOINTYPE_NONE,
1115 'count' => 4,
1116 'expectedusers' => [
1117 'adam.ant',
1118 'barbara.bennett',
1119 'tony.rogers',
1120 'sarah.rester',
1121 ],
1122 ],
1123 'NONE: Filter on username (no match)' => (object) [
1124 'keywords' => ['sara.rester'],
1125 'jointype' => filter::JOINTYPE_NONE,
1126 'count' => 5,
1127 'expectedusers' => [
1128 'adam.ant',
1129 'barbara.bennett',
1130 'colin.carnforth',
1131 'tony.rogers',
1132 'sarah.rester',
1133 ],
1134 ],
1135 'NONE: Filter on email' => (object) [
1136 'keywords' => ['zazu'],
1137 'jointype' => filter::JOINTYPE_NONE,
1138 'count' => 4,
1139 'expectedusers' => [
1140 'adam.ant',
1141 'barbara.bennett',
1142 'colin.carnforth',
1143 'tony.rogers',
1144 ],
1145 ],
1146 'NONE: Filter on first name phonetic only' => (object) [
1147 'keywords' => ['Sera'],
1148 'jointype' => filter::JOINTYPE_NONE,
1149 'count' => 4,
1150 'expectedusers' => [
1151 'adam.ant',
1152 'barbara.bennett',
1153 'colin.carnforth',
1154 'tony.rogers',
1155 ],
1156 ],
1157 'NONE: Filter on last name phonetic only' => (object) [
1158 'keywords' => ['jour'],
1159 'jointype' => filter::JOINTYPE_NONE,
1160 'count' => 4,
1161 'expectedusers' => [
1162 'adam.ant',
1163 'barbara.bennett',
1164 'colin.carnforth',
1165 'sarah.rester',
1166 ],
1167 ],
1168 'NONE: Filter on alternate name only' => (object) [
1169 'keywords' => ['Babs'],
1170 'jointype' => filter::JOINTYPE_NONE,
1171 'count' => 4,
1172 'expectedusers' => [
1173 'adam.ant',
1174 'colin.carnforth',
1175 'tony.rogers',
1176 'sarah.rester',
1177 ],
1178 ],
1179 'NONE: Filter on multiple keywords (first/last name)' => (object) [
1180 'keywords' => ['ara', 'rog'],
1181 'jointype' => filter::JOINTYPE_NONE,
1182 'count' => 2,
1183 'expectedusers' => [
1184 'adam.ant',
1185 'colin.carnforth',
1186 ],
1187 ],
1188 'NONE: Filter on multiple keywords (first/middle/last name)' => (object) [
1189 'keywords' => ['ant', 'Jeff', 'rog'],
1190 'jointype' => filter::JOINTYPE_NONE,
1191 'count' => 2,
1192 'expectedusers' => [
1193 'barbara.bennett',
1194 'sarah.rester',
1195 ],
1196 ],
1197 'NONE: Filter on multiple keywords (phonetic/alternate names)' => (object) [
1198 'keywords' => ['Bab', 'bra', 'nit'],
1199 'jointype' => filter::JOINTYPE_NONE,
1200 'count' => 4,
1201 'expectedusers' => [
1202 'adam.ant',
1203 'colin.carnforth',
1204 'tony.rogers',
1205 'sarah.rester',
1206 ],
1207 ],
d525ac4b
AN
1208 ],
1209 ],
1210 ];
1211
1212 $finaltests = [];
1213 foreach ($tests as $testname => $testdata) {
1214 foreach ($testdata->expect as $expectname => $expectdata) {
1215 $finaltests["{$testname} => {$expectname}"] = [
1216 'users' => $testdata->users,
1217 'keywords' => $expectdata->keywords,
1218 'jointype' => $expectdata->jointype,
1219 'count' => $expectdata->count,
1220 'expectedusers' => $expectdata->expectedusers,
1221 ];
1222 }
1223 }
1224
1225 return $finaltests;
1226 }
1227
1228 /**
1229 * Ensure that the enrolment status filter works as expected with the provided test cases.
1230 *
1231 * @param array $usersdata The list of users to create
1232 * @param array $statuses The list of statuses to filter by
1233 * @param int $jointype The join type to use when combining filter values
1234 * @param int $count The expected count
1235 * @param array $expectedusers
1236 * @dataProvider status_provider
1237 */
1238 public function test_status_filter(array $usersdata, array $statuses, int $jointype, int $count, array $expectedusers): void {
1239 $course = $this->getDataGenerator()->create_course();
1240 $coursecontext = context_course::instance($course->id);
1241 $users = [];
1242
1243 // Ensure sufficient capabilities to view all statuses.
1244 $this->setAdminUser();
1245
1246 // Ensure all enrolment methods enabled.
1247 $enrolinstances = enrol_get_instances($course->id, false);
1248 foreach ($enrolinstances as $instance) {
1249 $plugin = enrol_get_plugin($instance->enrol);
1250 $plugin->update_status($instance, ENROL_INSTANCE_ENABLED);
1251 }
1252
1253 foreach ($usersdata as $username => $userdata) {
1254 $user = $this->getDataGenerator()->create_user(['username' => $username]);
1255
8d5926be
AN
1256 if (array_key_exists('status', $userdata)) {
1257 foreach ($userdata['status'] as $enrolmethod => $status) {
d525ac4b
AN
1258 $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student', $enrolmethod, 0, 0, $status);
1259 }
1260 }
1261
1262 $users[$username] = $user;
1263 }
1264
1265 // Create a secondary course with users. We should not see these users.
1266 $this->create_course_with_users(1, 1, 1, 1);
1267
1268 // Create the basic filter.
1269 $filterset = new participants_filterset();
1270 $filterset->add_filter(new integer_filter('courseid', null, [(int) $course->id]));
1271
1272 // Create the status filter.
1273 $statusfilter = new integer_filter('status');
1274 $filterset->add_filter($statusfilter);
1275
1276 // Configure the filter.
1277 foreach ($statuses as $status) {
1278 $statusfilter->add_filter_value($status);
1279 }
1280 $statusfilter->set_join_type($jointype);
1281
1282 // Run the search.
1283 $search = new participants_search($course, $coursecontext, $filterset);
1284 $rs = $search->get_participants();
1285 $this->assertInstanceOf(moodle_recordset::class, $rs);
1286 $records = $this->convert_recordset_to_array($rs);
1287
1288 $this->assertCount($count, $records);
1289 $this->assertEquals($count, $search->get_total_participants_count());
1290
1291 foreach ($expectedusers as $expecteduser) {
1292 $this->assertArrayHasKey($users[$expecteduser]->id, $records);
1293 }
1294 }
1295
1296 /**
1297 * Data provider for status filter tests.
1298 *
1299 * @return array
1300 */
1301 public function status_provider(): array {
1302 $tests = [
1303 // Users with different statuses and enrolment methods (so multiple statuses are possible for the same user).
1304 'Users with different enrolment statuses' => (object) [
1305 'users' => [
1306 'a' => [
8d5926be 1307 'status' => [
d525ac4b
AN
1308 'manual' => ENROL_USER_ACTIVE,
1309 ]
1310 ],
1311 'b' => [
8d5926be 1312 'status' => [
d525ac4b
AN
1313 'self' => ENROL_USER_ACTIVE,
1314 ]
1315 ],
1316 'c' => [
8d5926be 1317 'status' => [
d525ac4b
AN
1318 'manual' => ENROL_USER_SUSPENDED,
1319 ]
1320 ],
1321 'd' => [
8d5926be 1322 'status' => [
d525ac4b
AN
1323 'self' => ENROL_USER_SUSPENDED,
1324 ]
1325 ],
6a54e58a 1326 'e' => [
8d5926be 1327 'status' => [
6a54e58a
MH
1328 'manual' => ENROL_USER_ACTIVE,
1329 'self' => ENROL_USER_SUSPENDED,
1330 ]
1331 ],
d525ac4b
AN
1332 ],
1333 'expect' => [
1334 // Tests for jointype: ANY.
1335 'ANY: No filter' => (object) [
8d5926be 1336 'status' => [],
d525ac4b 1337 'jointype' => filter::JOINTYPE_ANY,
6a54e58a 1338 'count' => 5,
d525ac4b
AN
1339 'expectedusers' => [
1340 'a',
1341 'b',
1342 'c',
1343 'd',
6a54e58a 1344 'e',
d525ac4b
AN
1345 ],
1346 ],
6a54e58a 1347 'ANY: Filter on active only' => (object) [
8d5926be 1348 'status' => [ENROL_USER_ACTIVE],
d525ac4b 1349 'jointype' => filter::JOINTYPE_ANY,
6a54e58a 1350 'count' => 3,
d525ac4b
AN
1351 'expectedusers' => [
1352 'a',
1353 'b',
6a54e58a 1354 'e',
d525ac4b
AN
1355 ],
1356 ],
6a54e58a 1357 'ANY: Filter on suspended only' => (object) [
8d5926be 1358 'status' => [ENROL_USER_SUSPENDED],
d525ac4b 1359 'jointype' => filter::JOINTYPE_ANY,
6a54e58a 1360 'count' => 3,
d525ac4b
AN
1361 'expectedusers' => [
1362 'c',
1363 'd',
6a54e58a 1364 'e',
d525ac4b
AN
1365 ],
1366 ],
6a54e58a 1367 'ANY: Filter on multiple statuses' => (object) [
8d5926be 1368 'status' => [ENROL_USER_ACTIVE, ENROL_USER_SUSPENDED],
d525ac4b 1369 'jointype' => filter::JOINTYPE_ANY,
6a54e58a 1370 'count' => 5,
d525ac4b
AN
1371 'expectedusers' => [
1372 'a',
1373 'b',
1374 'c',
1375 'd',
6a54e58a 1376 'e',
d525ac4b
AN
1377 ],
1378 ],
1379
1380 // Tests for jointype: ALL.
1381 'ALL: No filter' => (object) [
8d5926be 1382 'status' => [],
d525ac4b 1383 'jointype' => filter::JOINTYPE_ALL,
6a54e58a 1384 'count' => 5,
d525ac4b
AN
1385 'expectedusers' => [
1386 'a',
1387 'b',
1388 'c',
1389 'd',
6a54e58a 1390 'e',
d525ac4b
AN
1391 ],
1392 ],
6a54e58a 1393 'ALL: Filter on active only' => (object) [
8d5926be 1394 'status' => [ENROL_USER_ACTIVE],
d525ac4b 1395 'jointype' => filter::JOINTYPE_ALL,
6a54e58a 1396 'count' => 3,
d525ac4b
AN
1397 'expectedusers' => [
1398 'a',
1399 'b',
6a54e58a 1400 'e',
d525ac4b
AN
1401 ],
1402 ],
6a54e58a 1403 'ALL: Filter on suspended only' => (object) [
8d5926be 1404 'status' => [ENROL_USER_SUSPENDED],
d525ac4b 1405 'jointype' => filter::JOINTYPE_ALL,
6a54e58a 1406 'count' => 3,
d525ac4b
AN
1407 'expectedusers' => [
1408 'c',
1409 'd',
6a54e58a
MH
1410 'e',
1411 ],
1412 ],
1413 'ALL: Filter on multiple statuses' => (object) [
8d5926be 1414 'status' => [ENROL_USER_ACTIVE, ENROL_USER_SUSPENDED],
6a54e58a
MH
1415 'jointype' => filter::JOINTYPE_ALL,
1416 'count' => 1,
1417 'expectedusers' => [
1418 'e',
d525ac4b
AN
1419 ],
1420 ],
6a54e58a
MH
1421
1422 // Tests for jointype: NONE.
1423 'NONE: No filter' => (object) [
8d5926be 1424 'status' => [],
6a54e58a
MH
1425 'jointype' => filter::JOINTYPE_NONE,
1426 'count' => 5,
1427 'expectedusers' => [
1428 'a',
1429 'b',
1430 'c',
1431 'd',
1432 'e',
1433 ],
1434 ],
1435 'NONE: Filter on active only' => (object) [
8d5926be 1436 'status' => [ENROL_USER_ACTIVE],
6a54e58a
MH
1437 'jointype' => filter::JOINTYPE_NONE,
1438 'count' => 3,
1439 'expectedusers' => [
1440 'c',
1441 'd',
1442 'e',
1443 ],
1444 ],
1445 'NONE: Filter on suspended only' => (object) [
8d5926be 1446 'status' => [ENROL_USER_SUSPENDED],
6a54e58a
MH
1447 'jointype' => filter::JOINTYPE_NONE,
1448 'count' => 3,
1449 'expectedusers' => [
1450 'a',
1451 'b',
1452 'e',
1453 ],
1454 ],
1455 'NONE: Filter on multiple statuses' => (object) [
8d5926be 1456 'status' => [ENROL_USER_ACTIVE, ENROL_USER_SUSPENDED],
6a54e58a
MH
1457 'jointype' => filter::JOINTYPE_NONE,
1458 'count' => 0,
1459 'expectedusers' => [],
1460 ],
d525ac4b
AN
1461 ],
1462 ],
1463 ];
1464
1465 $finaltests = [];
1466 foreach ($tests as $testname => $testdata) {
1467 foreach ($testdata->expect as $expectname => $expectdata) {
1468 $finaltests["{$testname} => {$expectname}"] = [
1469 'users' => $testdata->users,
8d5926be 1470 'status' => $expectdata->status,
d525ac4b
AN
1471 'jointype' => $expectdata->jointype,
1472 'count' => $expectdata->count,
1473 'expectedusers' => $expectdata->expectedusers,
1474 ];
1475 }
1476 }
1477
1478 return $finaltests;
1479 }
1480
1481 /**
1482 * Ensure that the enrolment methods filter works as expected with the provided test cases.
1483 *
1484 * @param array $usersdata The list of users to create
1485 * @param array $enrolmethods The list of enrolment methods to filter by
1486 * @param int $jointype The join type to use when combining filter values
1487 * @param int $count The expected count
1488 * @param array $expectedusers
1489 * @dataProvider enrolments_provider
1490 */
1491 public function test_enrolments_filter(array $usersdata, array $enrolmethods, int $jointype, int $count,
1492 array $expectedusers): void {
1493
1494 $course = $this->getDataGenerator()->create_course();
1495 $coursecontext = context_course::instance($course->id);
1496 $users = [];
1497
1498 // Ensure all enrolment methods enabled and mapped for setting the filter later.
1499 $enrolinstances = enrol_get_instances($course->id, false);
1500 $enrolinstancesmap = [];
1501 foreach ($enrolinstances as $instance) {
1502 $plugin = enrol_get_plugin($instance->enrol);
1503 $plugin->update_status($instance, ENROL_INSTANCE_ENABLED);
1504
1505 $enrolinstancesmap[$instance->enrol] = (int) $instance->id;
1506 }
1507
1508 foreach ($usersdata as $username => $userdata) {
1509 $user = $this->getDataGenerator()->create_user(['username' => $username]);
1510
1511 if (array_key_exists('enrolmethods', $userdata)) {
1512 foreach ($userdata['enrolmethods'] as $enrolmethod) {
1513 $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student', $enrolmethod);
1514 }
1515 }
1516
1517 $users[$username] = $user;
1518 }
1519
1520 // Create a secondary course with users. We should not see these users.
1521 $this->create_course_with_users(1, 1, 1, 1);
1522
1523 // Create the basic filter.
1524 $filterset = new participants_filterset();
1525 $filterset->add_filter(new integer_filter('courseid', null, [(int) $course->id]));
1526
1527 // Create the enrolment methods filter.
1528 $enrolmethodfilter = new integer_filter('enrolments');
1529 $filterset->add_filter($enrolmethodfilter);
1530
1531 // Configure the filter.
1532 foreach ($enrolmethods as $enrolmethod) {
1533 $enrolmethodfilter->add_filter_value($enrolinstancesmap[$enrolmethod]);
1534 }
1535 $enrolmethodfilter->set_join_type($jointype);
1536
1537 // Run the search.
1538 $search = new participants_search($course, $coursecontext, $filterset);
1539 $rs = $search->get_participants();
1540 $this->assertInstanceOf(moodle_recordset::class, $rs);
1541 $records = $this->convert_recordset_to_array($rs);
1542
1543 $this->assertCount($count, $records);
1544 $this->assertEquals($count, $search->get_total_participants_count());
1545
1546 foreach ($expectedusers as $expecteduser) {
1547 $this->assertArrayHasKey($users[$expecteduser]->id, $records);
1548 }
1549 }
1550
1551 /**
1552 * Data provider for enrolments filter tests.
1553 *
1554 * @return array
1555 */
1556 public function enrolments_provider(): array {
1557 $tests = [
1558 // Users with different enrolment methods.
1559 'Users with different enrolment methods' => (object) [
1560 'users' => [
1561 'a' => [
1562 'enrolmethods' => [
1563 'manual',
1564 ]
1565 ],
1566 'b' => [
1567 'enrolmethods' => [
1568 'self',
1569 ]
1570 ],
1571 'c' => [
1572 'enrolmethods' => [
1573 'manual',
1574 'self',
1575 ]
1576 ],
1577 ],
1578 'expect' => [
1579 // Tests for jointype: ANY.
1580 'ANY: No filter' => (object) [
1581 'enrolmethods' => [],
1582 'jointype' => filter::JOINTYPE_ANY,
1583 'count' => 3,
1584 'expectedusers' => [
1585 'a',
1586 'b',
1587 'c',
1588 ],
1589 ],
6a54e58a 1590 'ANY: Filter by manual enrolments only' => (object) [
d525ac4b
AN
1591 'enrolmethods' => ['manual'],
1592 'jointype' => filter::JOINTYPE_ANY,
1593 'count' => 2,
1594 'expectedusers' => [
1595 'a',
1596 'c',
1597 ],
1598 ],
6a54e58a 1599 'ANY: Filter by self enrolments only' => (object) [
d525ac4b
AN
1600 'enrolmethods' => ['self'],
1601 'jointype' => filter::JOINTYPE_ANY,
1602 'count' => 2,
1603 'expectedusers' => [
1604 'b',
1605 'c',
1606 ],
1607 ],
6a54e58a 1608 'ANY: Filter by multiple enrolment methods' => (object) [
d525ac4b
AN
1609 'enrolmethods' => ['manual', 'self'],
1610 'jointype' => filter::JOINTYPE_ANY,
1611 'count' => 3,
1612 'expectedusers' => [
1613 'a',
1614 'b',
1615 'c',
1616 ],
1617 ],
1618
1619 // Tests for jointype: ALL.
1620 'ALL: No filter' => (object) [
1621 'enrolmethods' => [],
1622 'jointype' => filter::JOINTYPE_ALL,
1623 'count' => 3,
1624 'expectedusers' => [
1625 'a',
1626 'b',
1627 'c',
1628 ],
1629 ],
6a54e58a 1630 'ALL: Filter by manual enrolments only' => (object) [
d525ac4b
AN
1631 'enrolmethods' => ['manual'],
1632 'jointype' => filter::JOINTYPE_ALL,
1633 'count' => 2,
1634 'expectedusers' => [
1635 'a',
1636 'c',
1637 ],
1638 ],
6a54e58a
MH
1639 'ALL: Filter by multiple enrolment methods' => (object) [
1640 'enrolmethods' => ['manual', 'self'],
1641 'jointype' => filter::JOINTYPE_ALL,
1642 'count' => 1,
1643 'expectedusers' => [
1644 'c',
1645 ],
1646 ],
1647
1648 // Tests for jointype: NONE.
1649 'NONE: No filter' => (object) [
1650 'enrolmethods' => [],
1651 'jointype' => filter::JOINTYPE_NONE,
1652 'count' => 3,
1653 'expectedusers' => [
1654 'a',
1655 'b',
1656 'c',
1657 ],
1658 ],
1659 'NONE: Filter by manual enrolments only' => (object) [
1660 'enrolmethods' => ['manual'],
1661 'jointype' => filter::JOINTYPE_NONE,
1662 'count' => 1,
1663 'expectedusers' => [
1664 'b',
1665 ],
1666 ],
1667 'NONE: Filter by multiple enrolment methods' => (object) [
1668 'enrolmethods' => ['manual', 'self'],
1669 'jointype' => filter::JOINTYPE_NONE,
1670 'count' => 0,
1671 'expectedusers' => [],
1672 ],
d525ac4b
AN
1673 ],
1674 ],
1675 ];
1676
1677 $finaltests = [];
1678 foreach ($tests as $testname => $testdata) {
1679 foreach ($testdata->expect as $expectname => $expectdata) {
1680 $finaltests["{$testname} => {$expectname}"] = [
1681 'users' => $testdata->users,
1682 'enrolmethods' => $expectdata->enrolmethods,
1683 'jointype' => $expectdata->jointype,
1684 'count' => $expectdata->count,
1685 'expectedusers' => $expectdata->expectedusers,
1686 ];
1687 }
1688 }
1689
1690 return $finaltests;
1691 }
25d9dabd 1692
03397c81
MH
1693 /**
1694 * Ensure that the groups filter works as expected with the provided test cases.
1695 *
1696 * @param array $usersdata The list of users to create
1697 * @param array $groupsavailable The names of groups that should be created in the course
1698 * @param array $filtergroups The names of groups to filter by
1699 * @param int $jointype The join type to use when combining filter values
1700 * @param int $count The expected count
1701 * @param array $expectedusers
1702 * @dataProvider groups_provider
1703 */
1704 public function test_groups_filter(array $usersdata, array $groupsavailable, array $filtergroups, int $jointype, int $count,
1705 array $expectedusers): void {
1706
1707 $course = $this->getDataGenerator()->create_course();
1708 $coursecontext = context_course::instance($course->id);
1709 $users = [];
1710
1711 // Prepare data for filtering by users in no groups.
1712 $nogroupsdata = (object) [
1713 'id' => USERSWITHOUTGROUP,
1714 ];
1715
1716 // Map group names to group data.
1717 $groupsdata = ['nogroups' => $nogroupsdata];
1718 foreach ($groupsavailable as $groupname) {
1719 $groupinfo = [
1720 'courseid' => $course->id,
1721 'name' => $groupname,
1722 ];
1723
1724 $groupsdata[$groupname] = $this->getDataGenerator()->create_group($groupinfo);
1725 }
1726
1727 foreach ($usersdata as $username => $userdata) {
1728 $user = $this->getDataGenerator()->create_user(['username' => $username]);
1729 $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
1730
1731 if (array_key_exists('groups', $userdata)) {
1732 foreach ($userdata['groups'] as $groupname) {
1733 $userinfo = [
1734 'userid' => $user->id,
1735 'groupid' => (int) $groupsdata[$groupname]->id,
1736 ];
1737 $this->getDataGenerator()->create_group_member($userinfo);
1738 }
1739 }
1740
1741 $users[$username] = $user;
1742 }
1743
1744 // Create a secondary course with users. We should not see these users.
1745 $this->create_course_with_users(1, 1, 1, 1);
1746
1747 // Create the basic filter.
1748 $filterset = new participants_filterset();
1749 $filterset->add_filter(new integer_filter('courseid', null, [(int) $course->id]));
1750
1751 // Create the groups filter.
1752 $groupsfilter = new integer_filter('groups');
1753 $filterset->add_filter($groupsfilter);
1754
1755 // Configure the filter.
1756 foreach ($filtergroups as $filtergroupname) {
1757 $groupsfilter->add_filter_value((int) $groupsdata[$filtergroupname]->id);
1758 }
1759 $groupsfilter->set_join_type($jointype);
1760
1761 // Run the search.
1762 $search = new participants_search($course, $coursecontext, $filterset);
1763 $rs = $search->get_participants();
1764 $this->assertInstanceOf(moodle_recordset::class, $rs);
1765 $records = $this->convert_recordset_to_array($rs);
1766
1767 $this->assertCount($count, $records);
1768 $this->assertEquals($count, $search->get_total_participants_count());
1769
1770 foreach ($expectedusers as $expecteduser) {
1771 $this->assertArrayHasKey($users[$expecteduser]->id, $records);
1772 }
1773 }
1774
1775 /**
1776 * Data provider for groups filter tests.
1777 *
1778 * @return array
1779 */
1780 public function groups_provider(): array {
1781 $tests = [
1782 'Users in different groups' => (object) [
1783 'groupsavailable' => [
1784 'groupa',
1785 'groupb',
1786 'groupc',
1787 ],
1788 'users' => [
1789 'a' => [
1790 'groups' => ['groupa'],
1791 ],
1792 'b' => [
1793 'groups' => ['groupb'],
1794 ],
1795 'c' => [
1796 'groups' => ['groupa', 'groupb'],
1797 ],
1798 'd' => [
1799 'groups' => [],
1800 ],
1801 ],
1802 'expect' => [
1803 // Tests for jointype: ANY.
1804 'ANY: No filter' => (object) [
1805 'groups' => [],
1806 'jointype' => filter::JOINTYPE_ANY,
1807 'count' => 4,
1808 'expectedusers' => [
1809 'a',
1810 'b',
1811 'c',
1812 'd',
1813 ],
1814 ],
1815 'ANY: Filter on a single group' => (object) [
1816 'groups' => ['groupa'],
1817 'jointype' => filter::JOINTYPE_ANY,
1818 'count' => 2,
1819 'expectedusers' => [
1820 'a',
1821 'c',
1822 ],
1823 ],
1824 'ANY: Filter on a group with no members' => (object) [
1825 'groups' => ['groupc'],
1826 'jointype' => filter::JOINTYPE_ANY,
1827 'count' => 0,
1828 'expectedusers' => [],
1829 ],
1830 'ANY: Filter on multiple groups' => (object) [
1831 'groups' => ['groupa', 'groupb'],
1832 'jointype' => filter::JOINTYPE_ANY,
1833 'count' => 3,
1834 'expectedusers' => [
1835 'a',
1836 'b',
1837 'c',
1838 ],
1839 ],
1840 'ANY: Filter on members of no groups only' => (object) [
1841 'groups' => ['nogroups'],
1842 'jointype' => filter::JOINTYPE_ANY,
1843 'count' => 1,
1844 'expectedusers' => [
1845 'd',
1846 ],
1847 ],
1848 'ANY: Filter on a single group or no groups' => (object) [
1849 'groups' => ['groupa', 'nogroups'],
1850 'jointype' => filter::JOINTYPE_ANY,
1851 'count' => 3,
1852 'expectedusers' => [
1853 'a',
1854 'c',
1855 'd',
1856 ],
1857 ],
1858 'ANY: Filter on multiple groups or no groups' => (object) [
1859 'groups' => ['groupa', 'groupb', 'nogroups'],
1860 'jointype' => filter::JOINTYPE_ANY,
1861 'count' => 4,
1862 'expectedusers' => [
1863 'a',
1864 'b',
1865 'c',
1866 'd',
1867 ],
1868 ],
1869
1870 // Tests for jointype: ALL.
1871 'ALL: No filter' => (object) [
1872 'groups' => [],
1873 'jointype' => filter::JOINTYPE_ALL,
1874 'count' => 4,
1875 'expectedusers' => [
1876 'a',
1877 'b',
1878 'c',
1879 'd',
1880 ],
1881 ],
1882 'ALL: Filter on a single group' => (object) [
1883 'groups' => ['groupa'],
1884 'jointype' => filter::JOINTYPE_ALL,
1885 'count' => 2,
1886 'expectedusers' => [
1887 'a',
1888 'c',
1889 ],
1890 ],
1891 'ALL: Filter on a group with no members' => (object) [
1892 'groups' => ['groupc'],
1893 'jointype' => filter::JOINTYPE_ALL,
1894 'count' => 0,
1895 'expectedusers' => [],
1896 ],
1897 'ALL: Filter on members of no groups only' => (object) [
1898 'groups' => ['nogroups'],
1899 'jointype' => filter::JOINTYPE_ALL,
1900 'count' => 1,
1901 'expectedusers' => [
1902 'd',
1903 ],
1904 ],
1905 'ALL: Filter on multiple groups' => (object) [
1906 'groups' => ['groupa', 'groupb'],
1907 'jointype' => filter::JOINTYPE_ALL,
1908 'count' => 1,
1909 'expectedusers' => [
1910 'c',
1911 ],
1912 ],
1913 'ALL: Filter on a single group and no groups' => (object) [
1914 'groups' => ['groupa', 'nogroups'],
1915 'jointype' => filter::JOINTYPE_ALL,
1916 'count' => 0,
1917 'expectedusers' => [],
1918 ],
1919 'ALL: Filter on multiple groups and no groups' => (object) [
1920 'groups' => ['groupa', 'groupb', 'nogroups'],
1921 'jointype' => filter::JOINTYPE_ALL,
1922 'count' => 0,
1923 'expectedusers' => [],
1924 ],
1925
1926 // Tests for jointype: NONE.
1927 'NONE: No filter' => (object) [
1928 'groups' => [],
1929 'jointype' => filter::JOINTYPE_NONE,
1930 'count' => 4,
1931 'expectedusers' => [
1932 'a',
1933 'b',
1934 'c',
1935 'd',
1936 ],
1937 ],
1938 'NONE: Filter on a single group' => (object) [
1939 'groups' => ['groupa'],
1940 'jointype' => filter::JOINTYPE_NONE,
1941 'count' => 2,
1942 'expectedusers' => [
1943 'b',
1944 'd',
1945 ],
1946 ],
1947 'NONE: Filter on a group with no members' => (object) [
1948 'groups' => ['groupc'],
1949 'jointype' => filter::JOINTYPE_NONE,
1950 'count' => 4,
1951 'expectedusers' => [
1952 'a',
1953 'b',
1954 'c',
1955 'd',
1956 ],
1957 ],
1958 'NONE: Filter on members of no groups only' => (object) [
1959 'groups' => ['nogroups'],
1960 'jointype' => filter::JOINTYPE_NONE,
1961 'count' => 3,
1962 'expectedusers' => [
1963 'a',
1964 'b',
1965 'c',
1966 ],
1967 ],
1968 'NONE: Filter on multiple groups' => (object) [
1969 'groups' => ['groupa', 'groupb'],
1970 'jointype' => filter::JOINTYPE_NONE,
1971 'count' => 1,
1972 'expectedusers' => [
1973 'd',
1974 ],
1975 ],
1976 'NONE: Filter on a single group and no groups' => (object) [
1977 'groups' => ['groupa', 'nogroups'],
1978 'jointype' => filter::JOINTYPE_NONE,
1979 'count' => 1,
1980 'expectedusers' => [
1981 'b',
1982 ],
1983 ],
1984 'NONE: Filter on multiple groups and no groups' => (object) [
1985 'groups' => ['groupa', 'groupb', 'nogroups'],
1986 'jointype' => filter::JOINTYPE_NONE,
1987 'count' => 0,
1988 'expectedusers' => [],
1989 ],
1990 ],
1991 ],
1992 ];
1993
1994 $finaltests = [];
1995 foreach ($tests as $testname => $testdata) {
1996 foreach ($testdata->expect as $expectname => $expectdata) {
1997 $finaltests["{$testname} => {$expectname}"] = [
1998 'users' => $testdata->users,
1999 'groupsavailable' => $testdata->groupsavailable,
2000 'filtergroups' => $expectdata->groups,
2001 'jointype' => $expectdata->jointype,
2002 'count' => $expectdata->count,
2003 'expectedusers' => $expectdata->expectedusers,
2004 ];
2005 }
2006 }
2007
2008 return $finaltests;
2009 }
2010
d85315ee
MH
2011 /**
2012 * Ensure that the groups filter works as expected when separate groups mode is enabled, with the provided test cases.
2013 *
2014 * @param array $usersdata The list of users to create
2015 * @param array $groupsavailable The names of groups that should be created in the course
2016 * @param array $filtergroups The names of groups to filter by
2017 * @param int $jointype The join type to use when combining filter values
2018 * @param int $count The expected count
2019 * @param array $expectedusers
2020 * @param string $loginusername The user to login as for the tests
2021 * @dataProvider groups_separate_provider
2022 */
2023 public function test_groups_filter_separate_groups(array $usersdata, array $groupsavailable, array $filtergroups, int $jointype,
2024 int $count, array $expectedusers, string $loginusername): void {
2025
2026 $course = $this->getDataGenerator()->create_course();
2027 $coursecontext = context_course::instance($course->id);
2028 $users = [];
2029
2030 // Enable separate groups mode on the course.
2031 $course->groupmode = SEPARATEGROUPS;
2032 $course->groupmodeforce = true;
2033 update_course($course);
2034
2035 // Prepare data for filtering by users in no groups.
2036 $nogroupsdata = (object) [
2037 'id' => USERSWITHOUTGROUP,
2038 ];
2039
2040 // Map group names to group data.
2041 $groupsdata = ['nogroups' => $nogroupsdata];
2042 foreach ($groupsavailable as $groupname) {
2043 $groupinfo = [
2044 'courseid' => $course->id,
2045 'name' => $groupname,
2046 ];
2047
2048 $groupsdata[$groupname] = $this->getDataGenerator()->create_group($groupinfo);
2049 }
2050
2051 foreach ($usersdata as $username => $userdata) {
2052 $user = $this->getDataGenerator()->create_user(['username' => $username]);
2053 $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
2054
2055 if (array_key_exists('groups', $userdata)) {
2056 foreach ($userdata['groups'] as $groupname) {
2057 $userinfo = [
2058 'userid' => $user->id,
2059 'groupid' => (int) $groupsdata[$groupname]->id,
2060 ];
2061 $this->getDataGenerator()->create_group_member($userinfo);
2062 }
2063 }
2064
2065 $users[$username] = $user;
2066
2067 if ($username == $loginusername) {
2068 $loginuser = $user;
2069 }
2070 }
2071
2072 // Create a secondary course with users. We should not see these users.
2073 $this->create_course_with_users(1, 1, 1, 1);
2074
2075 // Log in as the user to be tested.
2076 $this->setUser($loginuser);
2077
2078 // Create the basic filter.
2079 $filterset = new participants_filterset();
2080 $filterset->add_filter(new integer_filter('courseid', null, [(int) $course->id]));
2081
2082 // Create the groups filter.
2083 $groupsfilter = new integer_filter('groups');
2084 $filterset->add_filter($groupsfilter);
2085
2086 // Configure the filter.
2087 foreach ($filtergroups as $filtergroupname) {
2088 $groupsfilter->add_filter_value((int) $groupsdata[$filtergroupname]->id);
2089 }
2090 $groupsfilter->set_join_type($jointype);
2091
2092 // Run the search.
2093 $search = new participants_search($course, $coursecontext, $filterset);
2094
2095 // Tests on user in no groups should throw an exception as they are not supported (participants are not visible to them).
2096 if (in_array('exception', $expectedusers)) {
2097 $this->expectException(\coding_exception::class);
2098 $rs = $search->get_participants();
2099 } else {
2100 // All other cases are tested as normal.
2101 $rs = $search->get_participants();
2102 $this->assertInstanceOf(moodle_recordset::class, $rs);
2103 $records = $this->convert_recordset_to_array($rs);
2104
2105 $this->assertCount($count, $records);
2106 $this->assertEquals($count, $search->get_total_participants_count());
2107
2108 foreach ($expectedusers as $expecteduser) {
2109 $this->assertArrayHasKey($users[$expecteduser]->id, $records);
2110 }
2111 }
2112 }
2113
2114 /**
2115 * Data provider for groups filter tests.
2116 *
2117 * @return array
2118 */
2119 public function groups_separate_provider(): array {
2120 $tests = [
2121 'Users in different groups with separate groups mode enabled' => (object) [
2122 'groupsavailable' => [
2123 'groupa',
2124 'groupb',
2125 'groupc',
2126 ],
2127 'users' => [
2128 'a' => [
2129 'groups' => ['groupa'],
2130 ],
2131 'b' => [
2132 'groups' => ['groupb'],
2133 ],
2134 'c' => [
2135 'groups' => ['groupa', 'groupb'],
2136 ],
2137 'd' => [
2138 'groups' => [],
2139 ],
2140 ],
2141 'expect' => [
2142 // Tests for jointype: ANY.
2143 'ANY: No filter, user in one group' => (object) [
2144 'loginuser' => 'a',
2145 'groups' => [],
2146 'jointype' => filter::JOINTYPE_ANY,
2147 'count' => 2,
2148 'expectedusers' => [
2149 'a',
2150 'c',
2151 ],
2152 ],
2153 'ANY: No filter, user in multiple groups' => (object) [
2154 'loginuser' => 'c',
2155 'groups' => [],
2156 'jointype' => filter::JOINTYPE_ANY,
2157 'count' => 3,
2158 'expectedusers' => [
2159 'a',
2160 'b',
2161 'c',
2162 ],
2163 ],
2164 'ANY: No filter, user in no groups' => (object) [
2165 'loginuser' => 'd',
2166 'groups' => [],
2167 'jointype' => filter::JOINTYPE_ANY,
2168 'count' => 0,
2169 'expectedusers' => ['exception'],
2170 ],
2171 'ANY: Filter on a single group, user in one group' => (object) [
2172 'loginuser' => 'a',
2173 'groups' => ['groupa'],
2174 'jointype' => filter::JOINTYPE_ANY,
2175 'count' => 2,
2176 'expectedusers' => [
2177 'a',
2178 'c',
2179 ],
2180 ],
2181 'ANY: Filter on a single group, user in multple groups' => (object) [
2182 'loginuser' => 'c',
2183 'groups' => ['groupa'],
2184 'jointype' => filter::JOINTYPE_ANY,
2185 'count' => 2,
2186 'expectedusers' => [
2187 'a',
2188 'c',
2189 ],
2190 ],
2191 'ANY: Filter on a single group, user in no groups' => (object) [
2192 'loginuser' => 'd',
2193 'groups' => ['groupa'],
2194 'jointype' => filter::JOINTYPE_ANY,
2195 'count' => 0,
2196 'expectedusers' => ['exception'],
2197 ],
2198 'ANY: Filter on multiple groups, user in one group (ignore invalid groups)' => (object) [
2199 'loginuser' => 'a',
2200 'groups' => ['groupa', 'groupb'],
2201 'jointype' => filter::JOINTYPE_ANY,
2202 'count' => 2,
2203 'expectedusers' => [
2204 'a',
2205 'c',
2206 ],
2207 ],
2208 'ANY: Filter on multiple groups, user in multiple groups' => (object) [
2209 'loginuser' => 'c',
2210 'groups' => ['groupa', 'groupb'],
2211 'jointype' => filter::JOINTYPE_ANY,
2212 'count' => 3,
2213 'expectedusers' => [
2214 'a',
2215 'b',
2216 'c',
2217 ],
2218 ],
2219 'ANY: Filter on multiple groups or no groups, user in multiple groups (ignore no groups)' => (object) [
2220 'loginuser' => 'c',
2221 'groups' => ['groupa', 'groupb', 'nogroups'],
2222 'jointype' => filter::JOINTYPE_ANY,
2223 'count' => 3,
2224 'expectedusers' => [
2225 'a',
2226 'b',
2227 'c',
2228 ],
2229 ],
2230
2231 // Tests for jointype: ALL.
2232 'ALL: No filter, user in one group' => (object) [
2233 'loginuser' => 'a',
2234 'groups' => [],
2235 'jointype' => filter::JOINTYPE_ALL,
2236 'count' => 2,
2237 'expectedusers' => [
2238 'a',
2239 'c',
2240 ],
2241 ],
2242 'ALL: No filter, user in multiple groups' => (object) [
2243 'loginuser' => 'c',
2244 'groups' => [],
2245 'jointype' => filter::JOINTYPE_ALL,
2246 'count' => 3,
2247 'expectedusers' => [
2248 'a',
2249 'b',
2250 'c',
2251 ],
2252 ],
2253 'ALL: No filter, user in no groups' => (object) [
2254 'loginuser' => 'd',
2255 'groups' => [],
2256 'jointype' => filter::JOINTYPE_ALL,
2257 'count' => 0,
2258 'expectedusers' => ['exception'],
2259 ],
2260 'ALL: Filter on a single group, user in one group' => (object) [
2261 'loginuser' => 'a',
2262 'groups' => ['groupa'],
2263 'jointype' => filter::JOINTYPE_ALL,
2264 'count' => 2,
2265 'expectedusers' => [
2266 'a',
2267 'c',
2268 ],
2269 ],
2270 'ALL: Filter on a single group, user in multple groups' => (object) [
2271 'loginuser' => 'c',
2272 'groups' => ['groupa'],
2273 'jointype' => filter::JOINTYPE_ALL,
2274 'count' => 2,
2275 'expectedusers' => [
2276 'a',
2277 'c',
2278 ],
2279 ],
2280 'ALL: Filter on a single group, user in no groups' => (object) [
2281 'loginuser' => 'd',
2282 'groups' => ['groupa'],
2283 'jointype' => filter::JOINTYPE_ALL,
2284 'count' => 0,
2285 'expectedusers' => ['exception'],
2286 ],
2287 'ALL: Filter on multiple groups, user in one group (ignore invalid groups)' => (object) [
2288 'loginuser' => 'a',
2289 'groups' => ['groupa', 'groupb'],
2290 'jointype' => filter::JOINTYPE_ALL,
2291 'count' => 2,
2292 'expectedusers' => [
2293 'a',
2294 'c',
2295 ],
2296 ],
2297 'ALL: Filter on multiple groups, user in multiple groups' => (object) [
2298 'loginuser' => 'c',
2299 'groups' => ['groupa', 'groupb'],
2300 'jointype' => filter::JOINTYPE_ALL,
2301 'count' => 1,
2302 'expectedusers' => [
2303 'c',
2304 ],
2305 ],
2306 'ALL: Filter on multiple groups or no groups, user in multiple groups (ignore no groups)' => (object) [
2307 'loginuser' => 'c',
2308 'groups' => ['groupa', 'groupb', 'nogroups'],
2309 'jointype' => filter::JOINTYPE_ALL,
2310 'count' => 1,
2311 'expectedusers' => [
2312 'c',
2313 ],
2314 ],
2315
2316 // Tests for jointype: NONE.
2317 'NONE: No filter, user in one group' => (object) [
2318 'loginuser' => 'a',
2319 'groups' => [],
2320 'jointype' => filter::JOINTYPE_NONE,
2321 'count' => 2,
2322 'expectedusers' => [
2323 'a',
2324 'c',
2325 ],
2326 ],
2327 'NONE: No filter, user in multiple groups' => (object) [
2328 'loginuser' => 'c',
2329 'groups' => [],
2330 'jointype' => filter::JOINTYPE_NONE,
2331 'count' => 3,
2332 'expectedusers' => [
2333 'a',
2334 'b',
2335 'c',
2336 ],
2337 ],
2338 'NONE: No filter, user in no groups' => (object) [
2339 'loginuser' => 'd',
2340 'groups' => [],
2341 'jointype' => filter::JOINTYPE_NONE,
2342 'count' => 0,
2343 'expectedusers' => ['exception'],
2344 ],
2345 'NONE: Filter on a single group, user in one group' => (object) [
2346 'loginuser' => 'a',
2347 'groups' => ['groupa'],
2348 'jointype' => filter::JOINTYPE_NONE,
2349 'count' => 0,
2350 'expectedusers' => [],
2351 ],
2352 'NONE: Filter on a single group, user in multple groups' => (object) [
2353 'loginuser' => 'c',
2354 'groups' => ['groupa'],
2355 'jointype' => filter::JOINTYPE_NONE,
2356 'count' => 1,
2357 'expectedusers' => [
2358 'b',
2359 ],
2360 ],
2361 'NONE: Filter on a single group, user in no groups' => (object) [
2362 'loginuser' => 'd',
2363 'groups' => ['groupa'],
2364 'jointype' => filter::JOINTYPE_NONE,
2365 'count' => 0,
2366 'expectedusers' => ['exception'],
2367 ],
2368 'NONE: Filter on multiple groups, user in one group (ignore invalid groups)' => (object) [
2369 'loginuser' => 'a',
2370 'groups' => ['groupa', 'groupb'],
2371 'jointype' => filter::JOINTYPE_NONE,
2372 'count' => 0,
2373 'expectedusers' => [],
2374 ],
2375 'NONE: Filter on multiple groups, user in multiple groups' => (object) [
2376 'loginuser' => 'c',
2377 'groups' => ['groupa', 'groupb'],
2378 'jointype' => filter::JOINTYPE_NONE,
2379 'count' => 0,
2380 'expectedusers' => [],
2381 ],
2382 'NONE: Filter on multiple groups or no groups, user in multiple groups (ignore no groups)' => (object) [
2383 'loginuser' => 'c',
2384 'groups' => ['groupa', 'groupb', 'nogroups'],
2385 'jointype' => filter::JOINTYPE_NONE,
2386 'count' => 0,
2387 'expectedusers' => [],
2388 ],
2389 ],
2390 ],
2391 ];
2392
2393 $finaltests = [];
2394 foreach ($tests as $testname => $testdata) {
2395 foreach ($testdata->expect as $expectname => $expectdata) {
2396 $finaltests["{$testname} => {$expectname}"] = [
2397 'users' => $testdata->users,
2398 'groupsavailable' => $testdata->groupsavailable,
2399 'filtergroups' => $expectdata->groups,
2400 'jointype' => $expectdata->jointype,
2401 'count' => $expectdata->count,
2402 'expectedusers' => $expectdata->expectedusers,
2403 'loginusername' => $expectdata->loginuser,
2404 ];
2405 }
2406 }
2407
2408 return $finaltests;
2409 }
2410
2411
25d9dabd
MH
2412 /**
2413 * Ensure that the last access filter works as expected with the provided test cases.
2414 *
2415 * @param array $usersdata The list of users to create
2416 * @param array $accesssince The last access data to filter by
2417 * @param int $jointype The join type to use when combining filter values
2418 * @param int $count The expected count
2419 * @param array $expectedusers
2420 * @dataProvider accesssince_provider
2421 */
2422 public function test_accesssince_filter(array $usersdata, array $accesssince, int $jointype, int $count,
2423 array $expectedusers): void {
2424
2425 $course = $this->getDataGenerator()->create_course();
2426 $coursecontext = context_course::instance($course->id);
2427 $users = [];
2428
2429 foreach ($usersdata as $username => $userdata) {
2430 $usertimestamp = empty($userdata['lastlogin']) ? 0 : strtotime($userdata['lastlogin']);
2431
2432 $user = $this->getDataGenerator()->create_user(['username' => $username]);
2433 $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
2434
2435 // Create the record of the user's last access to the course.
2436 if ($usertimestamp > 0) {
2437 $this->getDataGenerator()->create_user_course_lastaccess($user, $course, $usertimestamp);
2438 }
2439
2440 $users[$username] = $user;
2441 }
2442
2443 // Create a secondary course with users. We should not see these users.
2444 $this->create_course_with_users(1, 1, 1, 1);
2445
2446 // Create the basic filter.
2447 $filterset = new participants_filterset();
2448 $filterset->add_filter(new integer_filter('courseid', null, [(int) $course->id]));
2449
2450 // Create the last access filter.
2451 $lastaccessfilter = new integer_filter('accesssince');
2452 $filterset->add_filter($lastaccessfilter);
2453
2454 // Configure the filter.
2455 foreach ($accesssince as $accessstring) {
2456 $lastaccessfilter->add_filter_value(strtotime($accessstring));
2457 }
2458 $lastaccessfilter->set_join_type($jointype);
2459
2460 // Run the search.
2461 $search = new participants_search($course, $coursecontext, $filterset);
2462 $rs = $search->get_participants();
2463 $this->assertInstanceOf(moodle_recordset::class, $rs);
2464 $records = $this->convert_recordset_to_array($rs);
2465
2466 $this->assertCount($count, $records);
2467 $this->assertEquals($count, $search->get_total_participants_count());
2468
2469 foreach ($expectedusers as $expecteduser) {
2470 $this->assertArrayHasKey($users[$expecteduser]->id, $records);
2471 }
2472 }
2473
2474 /**
2475 * Data provider for last access filter tests.
2476 *
2477 * @return array
2478 */
2479 public function accesssince_provider(): array {
2480 $tests = [
2481 // Users with different last access times.
2482 'Users in different groups' => (object) [
2483 'users' => [
2484 'a' => [
2485 'lastlogin' => '-3 days',
2486 ],
2487 'b' => [
2488 'lastlogin' => '-2 weeks',
2489 ],
2490 'c' => [
2491 'lastlogin' => '-5 months',
2492 ],
2493 'd' => [
2494 'lastlogin' => '-11 months',
2495 ],
2496 'e' => [
2497 // Never logged in.
2498 'lastlogin' => '',
2499 ],
2500 ],
2501 'expect' => [
2502 // Tests for jointype: ANY.
2503 'ANY: No filter' => (object) [
2504 'accesssince' => [],
2505 'jointype' => filter::JOINTYPE_ANY,
2506 'count' => 5,
2507 'expectedusers' => [
2508 'a',
2509 'b',
2510 'c',
2511 'd',
2512 'e',
2513 ],
2514 ],
2515 'ANY: Filter on last login more than 1 year ago' => (object) [
2516 'accesssince' => ['-1 year'],
2517 'jointype' => filter::JOINTYPE_ANY,
2518 'count' => 1,
2519 'expectedusers' => [
2520 'e',
2521 ],
2522 ],
2523 'ANY: Filter on last login more than 6 months ago' => (object) [
2524 'accesssince' => ['-6 months'],
2525 'jointype' => filter::JOINTYPE_ANY,
2526 'count' => 2,
2527 'expectedusers' => [
2528 'd',
2529 'e',
2530 ],
2531 ],
2532 'ANY: Filter on last login more than 3 weeks ago' => (object) [
2533 'accesssince' => ['-3 weeks'],
2534 'jointype' => filter::JOINTYPE_ANY,
2535 'count' => 3,
2536 'expectedusers' => [
2537 'c',
2538 'd',
2539 'e',
2540 ],
2541 ],
2542 'ANY: Filter on last login more than 5 days ago' => (object) [
2543 'accesssince' => ['-5 days'],
2544 'jointype' => filter::JOINTYPE_ANY,
2545 'count' => 4,
2546 'expectedusers' => [
2547 'b',
2548 'c',
2549 'd',
2550 'e',
2551 ],
2552 ],
2553 'ANY: Filter on last login more than 2 days ago' => (object) [
2554 'accesssince' => ['-2 days'],
2555 'jointype' => filter::JOINTYPE_ANY,
2556 'count' => 5,
2557 'expectedusers' => [
2558 'a',
2559 'b',
2560 'c',
2561 'd',
2562 'e',
2563 ],
2564 ],
2565
2566 // Tests for jointype: ALL.
2567 'ALL: No filter' => (object) [
2568 'accesssince' => [],
2569 'jointype' => filter::JOINTYPE_ALL,
2570 'count' => 5,
2571 'expectedusers' => [
2572 'a',
2573 'b',
2574 'c',
2575 'd',
2576 'e',
2577 ],
2578 ],
2579 'ALL: Filter on last login more than 1 year ago' => (object) [
2580 'accesssince' => ['-1 year'],
2581 'jointype' => filter::JOINTYPE_ALL,
2582 'count' => 1,
2583 'expectedusers' => [
2584 'e',
2585 ],
2586 ],
2587 'ALL: Filter on last login more than 6 months ago' => (object) [
2588 'accesssince' => ['-6 months'],
2589 'jointype' => filter::JOINTYPE_ALL,
2590 'count' => 2,
2591 'expectedusers' => [
2592 'd',
2593 'e',
2594 ],
2595 ],
2596 'ALL: Filter on last login more than 3 weeks ago' => (object) [
2597 'accesssince' => ['-3 weeks'],
2598 'jointype' => filter::JOINTYPE_ALL,
2599 'count' => 3,
2600 'expectedusers' => [
2601 'c',
2602 'd',
2603 'e',
2604 ],
2605 ],
2606 'ALL: Filter on last login more than 5 days ago' => (object) [
2607 'accesssince' => ['-5 days'],
2608 'jointype' => filter::JOINTYPE_ALL,
2609 'count' => 4,
2610 'expectedusers' => [
2611 'b',
2612 'c',
2613 'd',
2614 'e',
2615 ],
2616 ],
2617 'ALL: Filter on last login more than 2 days ago' => (object) [
2618 'accesssince' => ['-2 days'],
2619 'jointype' => filter::JOINTYPE_ALL,
2620 'count' => 5,
2621 'expectedusers' => [
2622 'a',
2623 'b',
2624 'c',
2625 'd',
2626 'e',
2627 ],
2628 ],
2629
2630 // Tests for jointype: NONE.
2631 'NONE: No filter' => (object) [
2632 'accesssince' => [],
2633 'jointype' => filter::JOINTYPE_NONE,
2634 'count' => 5,
2635 'expectedusers' => [
2636 'a',
2637 'b',
2638 'c',
2639 'd',
2640 'e',
2641 ],
2642 ],
2643 'NONE: Filter on last login more than 1 year ago' => (object) [
2644 'accesssince' => ['-1 year'],
2645 'jointype' => filter::JOINTYPE_NONE,
2646 'count' => 4,
2647 'expectedusers' => [
2648 'a',
2649 'b',
2650 'c',
2651 'd',
2652 ],
2653 ],
2654 'NONE: Filter on last login more than 6 months ago' => (object) [
2655 'accesssince' => ['-6 months'],
2656 'jointype' => filter::JOINTYPE_NONE,
2657 'count' => 3,
2658 'expectedusers' => [
2659 'a',
2660 'b',
2661 'c',
2662 ],
2663 ],
2664 'NONE: Filter on last login more than 3 weeks ago' => (object) [
2665 'accesssince' => ['-3 weeks'],
2666 'jointype' => filter::JOINTYPE_NONE,
2667 'count' => 2,
2668 'expectedusers' => [
2669 'a',
2670 'b',
2671 ],
2672 ],
2673 'NONE: Filter on last login more than 5 days ago' => (object) [
2674 'accesssince' => ['-5 days'],
2675 'jointype' => filter::JOINTYPE_NONE,
2676 'count' => 1,
2677 'expectedusers' => [
2678 'a',
2679 ],
2680 ],
2681 'NONE: Filter on last login more than 2 days ago' => (object) [
2682 'accesssince' => ['-2 days'],
2683 'jointype' => filter::JOINTYPE_NONE,
2684 'count' => 0,
2685 'expectedusers' => [],
2686 ],
2687 ],
2688 ],
2689 ];
2690
2691 $finaltests = [];
2692 foreach ($tests as $testname => $testdata) {
2693 foreach ($testdata->expect as $expectname => $expectdata) {
2694 $finaltests["{$testname} => {$expectname}"] = [
2695 'users' => $testdata->users,
2696 'accesssince' => $expectdata->accesssince,
2697 'jointype' => $expectdata->jointype,
2698 'count' => $expectdata->count,
2699 'expectedusers' => $expectdata->expectedusers,
2700 ];
2701 }
2702 }
2703
2704 return $finaltests;
2705 }
d26b3ad5
MH
2706
2707 /**
2708 * Ensure that the joins between filters in the filterset work as expected with the provided test cases.
2709 *
2710 * @param array $usersdata The list of users to create
2711 * @param array $filterdata The data to filter by
2712 * @param array $groupsavailable The names of groups that should be created in the course
2713 * @param int $jointype The join type to used between each filter being applied
2714 * @param int $count The expected count
2715 * @param array $expectedusers
2716 * @dataProvider filterset_joins_provider
2717 */
2718 public function test_filterset_joins(array $usersdata, array $filterdata, array $groupsavailable, int $jointype, int $count,
2719 array $expectedusers): void {
2720 global $DB;
2721
2722 // Ensure sufficient capabilities to view all statuses.
2723 $this->setAdminUser();
2724
2725 // Remove the default role.
2726 set_config('roleid', 0, 'enrol_manual');
2727
2728 $course = $this->getDataGenerator()->create_course();
2729 $coursecontext = context_course::instance($course->id);
2730 $roles = $DB->get_records_menu('role', [], '', 'shortname, id');
2731 $users = [];
2732
2733 // Ensure all enrolment methods are enabled (and mapped where required for filtering later).
2734 $enrolinstances = enrol_get_instances($course->id, false);
2735 $enrolinstancesmap = [];
2736 foreach ($enrolinstances as $instance) {
2737 $plugin = enrol_get_plugin($instance->enrol);
2738 $plugin->update_status($instance, ENROL_INSTANCE_ENABLED);
2739
2740 $enrolinstancesmap[$instance->enrol] = (int) $instance->id;
2741 }
2742
2743 // Create the required course groups and mapping.
2744 $nogroupsdata = (object) [
2745 'id' => USERSWITHOUTGROUP,
2746 ];
2747
2748 $groupsdata = ['nogroups' => $nogroupsdata];
2749 foreach ($groupsavailable as $groupname) {
2750 $groupinfo = [
2751 'courseid' => $course->id,
2752 'name' => $groupname,
2753 ];
2754
2755 $groupsdata[$groupname] = $this->getDataGenerator()->create_group($groupinfo);
2756 }
2757
2758 // Create test users.
2759 foreach ($usersdata as $username => $userdata) {
2760 $usertimestamp = empty($userdata['lastlogin']) ? 0 : strtotime($userdata['lastlogin']);
2761 unset($userdata['lastlogin']);
2762
2763 // Prevent randomly generated field values that may cause false fails.
2764 $userdata['firstnamephonetic'] = $userdata['firstnamephonetic'] ?? $userdata['firstname'];
2765 $userdata['lastnamephonetic'] = $userdata['lastnamephonetic'] ?? $userdata['lastname'];
2766 $userdata['middlename'] = $userdata['middlename'] ?? '';
2767 $userdata['alternatename'] = $userdata['alternatename'] ?? $username;
2768
2769 $user = $this->getDataGenerator()->create_user($userdata);
2770
2771 foreach ($userdata['enrolments'] as $details) {
2772 $this->getDataGenerator()->enrol_user($user->id, $course->id, $roles[$details['role']],
2773 $details['method'], 0, 0, $details['status']);
2774 }
2775
2776 foreach ($userdata['groups'] as $groupname) {
2777 $userinfo = [
2778 'userid' => $user->id,
2779 'groupid' => (int) $groupsdata[$groupname]->id,
2780 ];
2781 $this->getDataGenerator()->create_group_member($userinfo);
2782 }
2783
2784 if ($usertimestamp > 0) {
2785 $this->getDataGenerator()->create_user_course_lastaccess($user, $course, $usertimestamp);
2786 }
2787
2788 $users[$username] = $user;
2789 }
2790
2791 // Create a secondary course with users. We should not see these users.
2792 $this->create_course_with_users(10, 10, 10, 10);
2793
2794 // Create the basic filterset.
2795 $filterset = new participants_filterset();
2796 $filterset->set_join_type($jointype);
2797 $filterset->add_filter(new integer_filter('courseid', null, [(int) $course->id]));
2798
2799 // Apply the keywords filter if required.
2800 if (array_key_exists('keywords', $filterdata)) {
2801 $keywordfilter = new string_filter('keywords');
2802 $filterset->add_filter($keywordfilter);
2803
2804 foreach ($filterdata['keywords']['values'] as $keyword) {
2805 $keywordfilter->add_filter_value($keyword);
2806 }
2807 $keywordfilter->set_join_type($filterdata['keywords']['jointype']);
2808 }
2809
2810 // Apply enrolment methods filter if required.
2811 if (array_key_exists('enrolmethods', $filterdata)) {
2812 $enrolmethodfilter = new integer_filter('enrolments');
2813 $filterset->add_filter($enrolmethodfilter);
2814
2815 foreach ($filterdata['enrolmethods']['values'] as $enrolmethod) {
2816 $enrolmethodfilter->add_filter_value($enrolinstancesmap[$enrolmethod]);
2817 }
2818 $enrolmethodfilter->set_join_type($filterdata['enrolmethods']['jointype']);
2819 }
2820
2821 // Apply roles filter if required.
2822 if (array_key_exists('courseroles', $filterdata)) {
2823 $rolefilter = new integer_filter('roles');
2824 $filterset->add_filter($rolefilter);
2825
2826 foreach ($filterdata['courseroles']['values'] as $rolename) {
2827 $rolefilter->add_filter_value((int) $roles[$rolename]);
2828 }
2829 $rolefilter->set_join_type($filterdata['courseroles']['jointype']);
2830 }
2831
2832 // Apply status filter if required.
2833 if (array_key_exists('status', $filterdata)) {
2834 $statusfilter = new integer_filter('status');
2835 $filterset->add_filter($statusfilter);
2836
2837 foreach ($filterdata['status']['values'] as $status) {
2838 $statusfilter->add_filter_value($status);
2839 }
2840 $statusfilter->set_join_type($filterdata['status']['jointype']);
2841 }
2842
2843 // Apply groups filter if required.
2844 if (array_key_exists('groups', $filterdata)) {
2845 $groupsfilter = new integer_filter('groups');
2846 $filterset->add_filter($groupsfilter);
2847
2848 foreach ($filterdata['groups']['values'] as $filtergroupname) {
2849 $groupsfilter->add_filter_value((int) $groupsdata[$filtergroupname]->id);
2850 }
2851 $groupsfilter->set_join_type($filterdata['groups']['jointype']);
2852 }
2853
2854 // Apply last access filter if required.
2855 if (array_key_exists('accesssince', $filterdata)) {
2856 $lastaccessfilter = new integer_filter('accesssince');
2857 $filterset->add_filter($lastaccessfilter);
2858
2859 foreach ($filterdata['accesssince']['values'] as $accessstring) {
2860 $lastaccessfilter->add_filter_value(strtotime($accessstring));
2861 }
2862 $lastaccessfilter->set_join_type($filterdata['accesssince']['jointype']);
2863 }
2864
2865 // Run the search.
2866 $search = new participants_search($course, $coursecontext, $filterset);
2867 $rs = $search->get_participants();
2868 $this->assertInstanceOf(moodle_recordset::class, $rs);
2869 $records = $this->convert_recordset_to_array($rs);
2870
2871 $this->assertCount($count, $records);
2872 $this->assertEquals($count, $search->get_total_participants_count());
2873
2874 foreach ($expectedusers as $expecteduser) {
2875 $this->assertArrayHasKey($users[$expecteduser]->id, $records);
2876 }
2877 }
2878
2879 /**
2880 * Data provider for filterset join tests.
2881 *
2882 * @return array
2883 */
2884 public function filterset_joins_provider(): array {
2885 $tests = [
2886 // Users with different configurations.
2887 'Users with different configurations' => (object) [
2888 'groupsavailable' => [
2889 'groupa',
2890 'groupb',
2891 'groupc',
2892 ],
2893 'users' => [
2894 'adam.ant' => [
2895 'firstname' => 'Adam',
2896 'lastname' => 'Ant',
2897 'enrolments' => [
2898 [
2899 'role' => 'student',
2900 'method' => 'manual',
2901 'status' => ENROL_USER_ACTIVE,
2902 ],
2903 ],
2904 'groups' => ['groupa'],
2905 'lastlogin' => '-3 days',
2906 ],
2907 'barbara.bennett' => [
2908 'firstname' => 'Barbara',
2909 'lastname' => 'Bennett',
2910 'enrolments' => [
2911 [
2912 'role' => 'student',
2913 'method' => 'manual',
2914 'status' => ENROL_USER_ACTIVE,
2915 ],
2916 [
2917 'role' => 'teacher',
2918 'method' => 'manual',
2919 'status' => ENROL_USER_ACTIVE,
2920 ],
2921 ],
2922 'groups' => ['groupb'],
2923 'lastlogin' => '-2 weeks',
2924 ],
2925 'colin.carnforth' => [
2926 'firstname' => 'Colin',
2927 'lastname' => 'Carnforth',
2928 'enrolments' => [
2929 [
2930 'role' => 'editingteacher',
2931 'method' => 'self',
2932 'status' => ENROL_USER_SUSPENDED,
2933 ],
2934 ],
2935 'groups' => ['groupa', 'groupb'],
2936 'lastlogin' => '-5 months',
2937 ],
2938 'tony.rogers' => [
2939 'firstname' => 'Anthony',
2940 'lastname' => 'Rogers',
2941 'enrolments' => [
2942 [
2943 'role' => 'editingteacher',
2944 'method' => 'self',
2945 'status' => ENROL_USER_SUSPENDED,
2946 ],
2947 ],
2948 'groups' => [],
2949 'lastlogin' => '-10 months',
2950 ],
2951 'sarah.rester' => [
2952 'firstname' => 'Sarah',
2953 'lastname' => 'Rester',
2954 'email' => 'zazu@example.com',
2955 'enrolments' => [
2956 [
2957 'role' => 'teacher',
2958 'method' => 'manual',
2959 'status' => ENROL_USER_ACTIVE,
2960 ],
2961 [
2962 'role' => 'editingteacher',
2963 'method' => 'self',
2964 'status' => ENROL_USER_SUSPENDED,
2965 ],
2966 ],
2967 'groups' => [],
2968 'lastlogin' => '-11 months',
2969 ],
2970 'morgan.crikeyson' => [
2971 'firstname' => 'Morgan',
2972 'lastname' => 'Crikeyson',
2973 'enrolments' => [
2974 [
2975 'role' => 'teacher',
2976 'method' => 'manual',
2977 'status' => ENROL_USER_ACTIVE,
2978 ],
2979 ],
2980 'groups' => ['groupa'],
2981 'lastlogin' => '-1 week',
2982 ],
2983 'jonathan.bravo' => [
2984 'firstname' => 'Jonathan',
2985 'lastname' => 'Bravo',
2986 'enrolments' => [
2987 [
2988 'role' => 'student',
2989 'method' => 'manual',
2990 'status' => ENROL_USER_ACTIVE,
2991 ],
2992 ],
2993 'groups' => [],
2994 // Never logged in.
2995 'lastlogin' => '',
2996 ],
2997 ],
2998 'expect' => [
2999 // Tests for jointype: ANY.
3000 'ANY: No filters in filterset' => (object) [
3001 'filterdata' => [],
3002 'jointype' => filter::JOINTYPE_ANY,
3003 'count' => 7,
3004 'expectedusers' => [
3005 'adam.ant',
3006 'barbara.bennett',
3007 'colin.carnforth',
3008 'tony.rogers',
3009 'sarah.rester',
3010 'morgan.crikeyson',
3011 'jonathan.bravo',
3012 ],
3013 ],
3014 'ANY: Filterset containing a single filter type' => (object) [
3015 'filterdata' => [
3016 'enrolmethods' => [
3017 'values' => ['self'],
3018 'jointype' => filter::JOINTYPE_ANY,
3019 ],
3020 ],
3021 'jointype' => filter::JOINTYPE_ANY,
3022 'count' => 3,
3023 'expectedusers' => [
3024 'colin.carnforth',
3025 'tony.rogers',
3026 'sarah.rester',
3027 ],
3028 ],
3029 'ANY: Filterset matching all filter types on different users' => (object) [
3030 'filterdata' => [
3031 // Match Adam only.
3032 'keywords' => [
3033 'values' => ['adam'],
3034 'jointype' => filter::JOINTYPE_ALL,
3035 ],
3036 // Match Sarah only.
3037 'enrolmethods' => [
3038 'values' => ['manual', 'self'],
3039 'jointype' => filter::JOINTYPE_ALL,
3040 ],
3041 // Match Barbara only.
3042 'courseroles' => [
3043 'values' => ['student', 'teacher'],
3044 'jointype' => filter::JOINTYPE_ALL,
3045 ],
3046 // Match Sarah only.
8d5926be
AN
3047 'status' => [
3048 'values' => [ENROL_USER_ACTIVE, ENROL_USER_SUSPENDED],
d26b3ad5
MH
3049 'jointype' => filter::JOINTYPE_ALL,
3050 ],
3051 // Match Colin only.
3052 'groups' => [
3053 'values' => ['groupa', 'groupb'],
3054 'jointype' => filter::JOINTYPE_ALL,
3055 ],
3056 // Match Jonathan only.
3057 'accesssince' => [
3058 'values' => ['-1 year'],
3059 'jointype' => filter::JOINTYPE_ALL,
3060 ],
3061 ],
3062 'jointype' => filter::JOINTYPE_ANY,
3063 'count' => 5,
3064 // Morgan and Tony are not matched, to confirm filtering is not just returning all users.
3065 'expectedusers' => [
3066 'adam.ant',
3067 'barbara.bennett',
3068 'colin.carnforth',
3069 'sarah.rester',
3070 'jonathan.bravo',
3071 ],
3072 ],
3073
3074 // Tests for jointype: ALL.
3075 'ALL: No filters in filterset' => (object) [
3076 'filterdata' => [],
3077 'jointype' => filter::JOINTYPE_ALL,
3078 'count' => 7,
3079 'expectedusers' => [
3080 'adam.ant',
3081 'barbara.bennett',
3082 'colin.carnforth',
3083 'tony.rogers',
3084 'sarah.rester',
3085 'morgan.crikeyson',
3086 'jonathan.bravo',
3087 ],
3088 ],
3089 'ALL: Filterset containing a single filter type' => (object) [
3090 'filterdata' => [
3091 'enrolmethods' => [
3092 'values' => ['self'],
3093 'jointype' => filter::JOINTYPE_ANY,
3094 ],
3095 ],
3096 'jointype' => filter::JOINTYPE_ALL,
3097 'count' => 3,
3098 'expectedusers' => [
3099 'colin.carnforth',
3100 'tony.rogers',
3101 'sarah.rester',
3102 ],
3103 ],
3104 'ALL: Filterset combining all filter types' => (object) [
3105 'filterdata' => [
3106 // Exclude Adam, Tony, Morgan and Jonathan.
3107 'keywords' => [
3108 'values' => ['ar'],
3109 'jointype' => filter::JOINTYPE_ANY,
3110 ],
3111 // Exclude Colin and Tony.
3112 'enrolmethods' => [
3113 'values' => ['manual'],
3114 'jointype' => filter::JOINTYPE_ANY,
3115 ],
3116 // Exclude Adam, Barbara and Jonathan.
3117 'courseroles' => [
3118 'values' => ['student'],
3119 'jointype' => filter::JOINTYPE_NONE,
3120 ],
3121 // Exclude Colin and Tony.
8d5926be
AN
3122 'status' => [
3123 'values' => [ENROL_USER_ACTIVE],
d26b3ad5
MH
3124 'jointype' => filter::JOINTYPE_ALL,
3125 ],
3126 // Exclude Barbara.
3127 'groups' => [
3128 'values' => ['groupa', 'nogroups'],
3129 'jointype' => filter::JOINTYPE_ANY,
3130 ],
3131 // Exclude Adam, Colin and Barbara.
3132 'accesssince' => [
3133 'values' => ['-6 months'],
3134 'jointype' => filter::JOINTYPE_ALL,
3135 ],
3136 ],
3137 'jointype' => filter::JOINTYPE_ALL,
3138 'count' => 1,
3139 'expectedusers' => [
3140 'sarah.rester',
3141 ],
3142 ],
3143
3144 // Tests for jointype: NONE.
3145 'NONE: No filters in filterset' => (object) [
3146 'filterdata' => [],
3147 'jointype' => filter::JOINTYPE_NONE,
3148 'count' => 7,
3149 'expectedusers' => [
3150 'adam.ant',
3151 'barbara.bennett',
3152 'colin.carnforth',
3153 'tony.rogers',
3154 'sarah.rester',
3155 'morgan.crikeyson',
3156 'jonathan.bravo',
3157 ],
3158 ],
3159 'NONE: Filterset containing a single filter type' => (object) [
3160 'filterdata' => [
3161 'enrolmethods' => [
3162 'values' => ['self'],
3163 'jointype' => filter::JOINTYPE_ANY,
3164 ],
3165 ],
3166 'jointype' => filter::JOINTYPE_NONE,
3167 'count' => 4,
3168 'expectedusers' => [
3169 'adam.ant',
3170 'barbara.bennett',
3171 'morgan.crikeyson',
3172 'jonathan.bravo',
3173 ],
3174 ],
3175 'NONE: Filterset combining all filter types' => (object) [
3176 'filterdata' => [
3177 // Excludes Adam.
3178 'keywords' => [
3179 'values' => ['adam'],
3180 'jointype' => filter::JOINTYPE_ANY,
3181 ],
3182 // Excludes Colin, Tony and Sarah.
3183 'enrolmethods' => [
3184 'values' => ['self'],
3185 'jointype' => filter::JOINTYPE_ANY,
3186 ],
3187 // Excludes Jonathan.
3188 'courseroles' => [
3189 'values' => ['student'],
3190 'jointype' => filter::JOINTYPE_NONE,
3191 ],
3192 // Excludes Colin, Tony and Sarah.
8d5926be
AN
3193 'status' => [
3194 'values' => [ENROL_USER_SUSPENDED],
d26b3ad5
MH
3195 'jointype' => filter::JOINTYPE_ALL,
3196 ],
3197 // Excludes Adam, Colin, Tony, Sarah, Morgan and Jonathan.
3198 'groups' => [
3199 'values' => ['groupa', 'nogroups'],
3200 'jointype' => filter::JOINTYPE_ANY,
3201 ],
3202 // Excludes Tony and Sarah.
3203 'accesssince' => [
3204 'values' => ['-6 months'],
3205 'jointype' => filter::JOINTYPE_ALL,
8bcf74e9 3206 ],
d26b3ad5
MH
3207 ],
3208 'jointype' => filter::JOINTYPE_NONE,
3209 'count' => 1,
3210 'expectedusers' => [
3211 'barbara.bennett',
3212 ],
3213 ],
8bcf74e9
AN
3214 'NONE: Filterset combining several filter types and a double-negative on keyword' => (object) [
3215 'jointype' => filter::JOINTYPE_NONE,
3216 'filterdata' => [
3217 // Note: This is a jointype NONE on the parent jointype NONE.
3218 // The result therefore negated in this instance.
3219 // Include Adam and Anthony.
3220 'keywords' => [
3221 'values' => ['ant'],
3222 'jointype' => filter::JOINTYPE_NONE,
3223 ],
3224 // Excludes Tony.
3225 'status' => [
3226 'values' => [ENROL_USER_SUSPENDED],
3227 'jointype' => filter::JOINTYPE_ALL,
3228 ],
3229 ],
3230 'count' => 1,
3231 'expectedusers' => [
3232 'adam.ant',
3233 ],
3234 ],
d26b3ad5
MH
3235 ],
3236 ],
3237 ];
3238
3239 $finaltests = [];
3240 foreach ($tests as $testname => $testdata) {
3241 foreach ($testdata->expect as $expectname => $expectdata) {
3242 $finaltests["{$testname} => {$expectname}"] = [
3243 'users' => $testdata->users,
3244 'filterdata' => $expectdata->filterdata,
3245 'groupsavailable' => $testdata->groupsavailable,
3246 'jointype' => $expectdata->jointype,
3247 'count' => $expectdata->count,
3248 'expectedusers' => $expectdata->expectedusers,
3249 ];
3250 }
3251 }
3252
3253 return $finaltests;
3254 }
d525ac4b 3255}