Proyectos de Subversion Moodle

Rev

Ir a la última revisión | | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
 
26
declare(strict_types=1);
27
 
28
namespace core_user\table;
29
 
30
use advanced_testcase;
31
use context_course;
32
use context_coursecat;
33
use core_table\local\filter\filter;
34
use core_table\local\filter\integer_filter;
35
use core_table\local\filter\string_filter;
36
use core_user\table\participants_filterset;
37
use core_user\table\participants_search;
38
use moodle_recordset;
39
use 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
 */
47
class 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
                    ],
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
                    ],
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
                    ],
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
                    ],
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
     * Test participant search country filter
762
     *
763
     * @param array $usersdata
764
     * @param array $countries
765
     * @param int $jointype
766
     * @param array $expectedusers
767
     *
768
     * @dataProvider country_provider
769
     */
770
    public function test_country_filter(array $usersdata, array $countries, int $jointype, array $expectedusers): void {
771
        $this->resetAfterTest();
772
 
773
        $course = $this->getDataGenerator()->create_course();
774
        $users = [];
775
 
776
        foreach ($usersdata as $username => $country) {
777
            $users[$username] = $this->getDataGenerator()->create_and_enrol($course, 'student', (object) [
778
                'username' => $username,
779
                'country' => $country,
780
            ]);
781
        }
782
 
783
        // Add filters (courseid is required).
784
        $filterset = new participants_filterset();
785
        $filterset->add_filter(new integer_filter('courseid', null, [(int) $course->id]));
786
        $filterset->add_filter(new string_filter('country', $jointype, $countries));
787
 
788
        // Run the search, assert count matches the number of expected users.
789
        $search = new participants_search($course, context_course::instance($course->id), $filterset);
790
        $this->assertEquals(count($expectedusers), $search->get_total_participants_count());
791
 
792
        $rs = $search->get_participants();
793
        $this->assertInstanceOf(moodle_recordset::class, $rs);
794
 
795
        // Assert that each expected user is within the participant records.
796
        $records = $this->convert_recordset_to_array($rs);
797
        foreach ($expectedusers as $expecteduser) {
798
            $this->assertArrayHasKey($users[$expecteduser]->id, $records);
799
        }
800
    }
801
 
802
    /**
803
     * Data provider for {@see test_country_filter}
804
     *
805
     * @return array
806
     */
807
    public function country_provider(): array {
808
        $tests = [
809
            'users' => [
810
                'user1' => 'DE',
811
                'user2' => 'ES',
812
                'user3' => 'ES',
813
                'user4' => 'GB',
814
            ],
815
            'expects' => [
816
                // Tests for jointype: ANY.
817
                'ANY: No filter' => (object) [
818
                    'countries' => [],
819
                    'jointype' => filter::JOINTYPE_ANY,
820
                    'expectedusers' => [
821
                        'user1',
822
                        'user2',
823
                        'user3',
824
                        'user4',
825
                    ],
826
                ],
827
                'ANY: Matching filters' => (object) [
828
                    'countries' => [
829
                        'DE',
830
                        'GB',
831
                    ],
832
                    'jointype' => filter::JOINTYPE_ANY,
833
                    'expectedusers' => [
834
                        'user1',
835
                        'user4',
836
                    ],
837
                ],
838
                'ANY: Non-matching filters' => (object) [
839
                    'countries' => [
840
                        'RU',
841
                    ],
842
                    'jointype' => filter::JOINTYPE_ANY,
843
                    'expectedusers' => [],
844
                ],
845
 
846
                // Tests for jointype: ALL.
847
                'ALL: No filter' => (object) [
848
                    'countries' => [],
849
                    'jointype' => filter::JOINTYPE_ALL,
850
                    'expectedusers' => [
851
                        'user1',
852
                        'user2',
853
                        'user3',
854
                        'user4',
855
                    ],
856
                ],
857
                'ALL: Matching filters' => (object) [
858
                    'countries' => [
859
                        'DE',
860
                        'GB',
861
                    ],
862
                    'jointype' => filter::JOINTYPE_ALL,
863
                    'expectedusers' => [
864
                        'user1',
865
                        'user4',
866
                    ],
867
                ],
868
                'ALL: Non-matching filters' => (object) [
869
                    'countries' => [
870
                        'RU',
871
                    ],
872
                    'jointype' => filter::JOINTYPE_ALL,
873
                    'expectedusers' => [],
874
                ],
875
 
876
                // Tests for jointype: NONE.
877
                'NONE: No filter' => (object) [
878
                    'countries' => [],
879
                    'jointype' => filter::JOINTYPE_NONE,
880
                    'expectedusers' => [
881
                        'user1',
882
                        'user2',
883
                        'user3',
884
                        'user4',
885
                    ],
886
                ],
887
                'NONE: Matching filters' => (object) [
888
                    'countries' => [
889
                        'DE',
890
                        'GB',
891
                    ],
892
                    'jointype' => filter::JOINTYPE_NONE,
893
                    'expectedusers' => [
894
                        'user2',
895
                        'user3',
896
                    ],
897
                ],
898
                'NONE: Non-matching filters' => (object) [
899
                    'countries' => [
900
                        'RU',
901
                    ],
902
                    'jointype' => filter::JOINTYPE_NONE,
903
                    'expectedusers' => [
904
                        'user1',
905
                        'user2',
906
                        'user3',
907
                        'user4',
908
                    ],
909
                ],
910
            ],
911
        ];
912
 
913
        $finaltests = [];
914
        foreach ($tests['expects'] as $testname => $test) {
915
            $finaltests[$testname] = [
916
                'users' => $tests['users'],
917
                'countries' => $test->countries,
918
                'jointype' => $test->jointype,
919
                'expectedusers' => $test->expectedusers,
920
            ];
921
        }
922
 
923
        return $finaltests;
924
    }
925
 
926
    /**
927
     * Ensure that the keywords filter works as expected with the provided test cases.
928
     *
929
     * @param array $usersdata The list of users to create
930
     * @param array $keywords The list of keywords to filter by
931
     * @param int $jointype The join type to use when combining filter values
932
     * @param int $count The expected count
933
     * @param array $expectedusers
934
     * @param string $asuser If non-blank, uses that user account (for identify field permission checks)
935
     * @dataProvider keywords_provider
936
     */
937
    public function test_keywords_filter(array $usersdata, array $keywords, int $jointype, int $count,
938
            array $expectedusers, string $asuser): void {
939
        global $DB;
940
 
941
        $course = $this->getDataGenerator()->create_course();
942
        $coursecontext = context_course::instance($course->id);
943
        $users = [];
944
 
945
        // Create the custom user profile field and put it into showuseridentity.
946
        $this->getDataGenerator()->create_custom_profile_field(
947
                ['datatype' => 'text', 'shortname' => 'frog', 'name' => 'Fave frog']);
948
        set_config('showuseridentity', 'email,profile_field_frog');
949
 
950
        foreach ($usersdata as $username => $userdata) {
951
            // Prevent randomly generated field values that may cause false fails.
952
            $userdata['firstnamephonetic'] = $userdata['firstnamephonetic'] ?? $userdata['firstname'];
953
            $userdata['lastnamephonetic'] = $userdata['lastnamephonetic'] ?? $userdata['lastname'];
954
            $userdata['middlename'] = $userdata['middlename'] ?? '';
955
            $userdata['alternatename'] = $userdata['alternatename'] ?? $username;
956
 
957
            $user = $this->getDataGenerator()->create_user($userdata);
958
            $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
959
            $users[$username] = $user;
960
        }
961
 
962
        // Create a secondary course with users. We should not see these users.
963
        $this->create_course_with_users(10, 10, 10, 10);
964
 
965
        // Create the basic filter.
966
        $filterset = new participants_filterset();
967
        $filterset->add_filter(new integer_filter('courseid', null, [(int) $course->id]));
968
 
969
        // Create the keyword filter.
970
        $keywordfilter = new string_filter('keywords');
971
        $filterset->add_filter($keywordfilter);
972
 
973
        // Configure the filter.
974
        foreach ($keywords as $keyword) {
975
            $keywordfilter->add_filter_value($keyword);
976
        }
977
        $keywordfilter->set_join_type($jointype);
978
 
979
        if ($asuser) {
980
            $this->setUser($DB->get_record('user', ['username' => $asuser]));
981
        }
982
 
983
        // Run the search.
984
        $search = new participants_search($course, $coursecontext, $filterset);
985
        $rs = $search->get_participants();
986
        $this->assertInstanceOf(moodle_recordset::class, $rs);
987
        $records = $this->convert_recordset_to_array($rs);
988
 
989
        $this->assertCount($count, $records);
990
        $this->assertEquals($count, $search->get_total_participants_count());
991
 
992
        foreach ($expectedusers as $expecteduser) {
993
            $this->assertArrayHasKey($users[$expecteduser]->id, $records);
994
        }
995
    }
996
 
997
    /**
998
     * Data provider for keywords tests.
999
     *
1000
     * @return array
1001
     */
1002
    public function keywords_provider(): array {
1003
        $tests = [
1004
            // Users where the keyword matches basic user fields such as names and email.
1005
            'Users with basic names' => (object) [
1006
                'users' => [
1007
                    'adam.ant' => [
1008
                        'firstname' => 'Adam',
1009
                        'lastname' => 'Ant',
1010
                    ],
1011
                    'barbara.bennett' => [
1012
                        'firstname' => 'Barbara',
1013
                        'lastname' => 'Bennett',
1014
                        'alternatename' => 'Babs',
1015
                        'firstnamephonetic' => 'Barbra',
1016
                        'lastnamephonetic' => 'Benit',
1017
                        'profile_field_frog' => 'Kermit',
1018
                    ],
1019
                    'colin.carnforth' => [
1020
                        'firstname' => 'Colin',
1021
                        'lastname' => 'Carnforth',
1022
                        'middlename' => 'Jeffery',
1023
                    ],
1024
                    'tony.rogers' => [
1025
                        'firstname' => 'Anthony',
1026
                        'lastname' => 'Rogers',
1027
                        'lastnamephonetic' => 'Rowjours',
1028
                        'profile_field_frog' => 'Mr Toad',
1029
                    ],
1030
                    'sarah.rester' => [
1031
                        'firstname' => 'Sarah',
1032
                        'lastname' => 'Rester',
1033
                        'email' => 'zazu@example.com',
1034
                        'firstnamephonetic' => 'Sera',
1035
                    ],
1036
                ],
1037
                'expect' => [
1038
                    // Tests for jointype: ANY.
1039
                    'ANY: No filter' => (object) [
1040
                        'keywords' => [],
1041
                        'jointype' => filter::JOINTYPE_ANY,
1042
                        'count' => 5,
1043
                        'expectedusers' => [
1044
                            'adam.ant',
1045
                            'barbara.bennett',
1046
                            'colin.carnforth',
1047
                            'tony.rogers',
1048
                            'sarah.rester',
1049
                        ],
1050
                    ],
1051
                    'ANY: Filter on first name only' => (object) [
1052
                        'keywords' => ['adam'],
1053
                        'jointype' => filter::JOINTYPE_ANY,
1054
                        'count' => 1,
1055
                        'expectedusers' => [
1056
                            'adam.ant',
1057
                        ],
1058
                    ],
1059
                    'ANY: Filter on last name only' => (object) [
1060
                        'keywords' => ['BeNNeTt'],
1061
                        'jointype' => filter::JOINTYPE_ANY,
1062
                        'count' => 1,
1063
                        'expectedusers' => [
1064
                            'barbara.bennett',
1065
                        ],
1066
                    ],
1067
                    'ANY: Filter on first/Last name' => (object) [
1068
                        'keywords' => ['ant'],
1069
                        'jointype' => filter::JOINTYPE_ANY,
1070
                        'count' => 2,
1071
                        'expectedusers' => [
1072
                            'adam.ant',
1073
                            'tony.rogers',
1074
                        ],
1075
                    ],
1076
                    'ANY: Filter on fullname only' => (object) [
1077
                        'keywords' => ['Barbara Bennett'],
1078
                        'jointype' => filter::JOINTYPE_ANY,
1079
                        'count' => 1,
1080
                        'expectedusers' => [
1081
                            'barbara.bennett',
1082
                        ],
1083
                    ],
1084
                    'ANY: Filter on middlename only' => (object) [
1085
                        'keywords' => ['Jeff'],
1086
                        'jointype' => filter::JOINTYPE_ANY,
1087
                        'count' => 1,
1088
                        'expectedusers' => [
1089
                            'colin.carnforth',
1090
                        ],
1091
                    ],
1092
                    'ANY: Filter on username (no match)' => (object) [
1093
                        'keywords' => ['sara.rester'],
1094
                        'jointype' => filter::JOINTYPE_ANY,
1095
                        'count' => 0,
1096
                        'expectedusers' => [],
1097
                    ],
1098
                    'ANY: Filter on email only' => (object) [
1099
                        'keywords' => ['zazu'],
1100
                        'jointype' => filter::JOINTYPE_ANY,
1101
                        'count' => 1,
1102
                        'expectedusers' => [
1103
                            'sarah.rester',
1104
                        ],
1105
                    ],
1106
                    'ANY: Filter on first name phonetic only' => (object) [
1107
                        'keywords' => ['Sera'],
1108
                        'jointype' => filter::JOINTYPE_ANY,
1109
                        'count' => 1,
1110
                        'expectedusers' => [
1111
                            'sarah.rester',
1112
                        ],
1113
                    ],
1114
                    'ANY: Filter on last name phonetic only' => (object) [
1115
                        'keywords' => ['jour'],
1116
                        'jointype' => filter::JOINTYPE_ANY,
1117
                        'count' => 1,
1118
                        'expectedusers' => [
1119
                            'tony.rogers',
1120
                        ],
1121
                    ],
1122
                    'ANY: Filter on alternate name only' => (object) [
1123
                        'keywords' => ['Babs'],
1124
                        'jointype' => filter::JOINTYPE_ANY,
1125
                        'count' => 1,
1126
                        'expectedusers' => [
1127
                            'barbara.bennett',
1128
                        ],
1129
                    ],
1130
                    'ANY: Filter on multiple keywords (first/middle/last name)' => (object) [
1131
                        'keywords' => ['ant', 'Jeff', 'rog'],
1132
                        'jointype' => filter::JOINTYPE_ANY,
1133
                        'count' => 3,
1134
                        'expectedusers' => [
1135
                            'adam.ant',
1136
                            'colin.carnforth',
1137
                            'tony.rogers',
1138
                        ],
1139
                    ],
1140
                    'ANY: Filter on multiple keywords (phonetic/alternate names)' => (object) [
1141
                        'keywords' => ['era', 'Bab', 'ours'],
1142
                        'jointype' => filter::JOINTYPE_ANY,
1143
                        'count' => 3,
1144
                        'expectedusers' => [
1145
                            'barbara.bennett',
1146
                            'sarah.rester',
1147
                            'tony.rogers',
1148
                        ],
1149
                    ],
1150
                    'ANY: Filter on custom profile field' => (object) [
1151
                        'keywords' => ['Kermit', 'Mr Toad'],
1152
                        'jointype' => filter::JOINTYPE_ANY,
1153
                        'count' => 2,
1154
                        'expectedusers' => [
1155
                            'barbara.bennett',
1156
                            'tony.rogers',
1157
                        ],
1158
                        'asuser' => 'admin'
1159
                    ],
1160
                    'ANY: Filter on custom profile field (no permissions)' => (object) [
1161
                        'keywords' => ['Kermit', 'Mr Toad'],
1162
                        'jointype' => filter::JOINTYPE_ANY,
1163
                        'count' => 0,
1164
                        'expectedusers' => [],
1165
                        'asuser' => 'barbara.bennett'
1166
                    ],
1167
 
1168
                    // Tests for jointype: ALL.
1169
                    'ALL: No filter' => (object) [
1170
                        'keywords' => [],
1171
                        'jointype' => filter::JOINTYPE_ALL,
1172
                        'count' => 5,
1173
                        'expectedusers' => [
1174
                            'adam.ant',
1175
                            'barbara.bennett',
1176
                            'colin.carnforth',
1177
                            'tony.rogers',
1178
                            'sarah.rester',
1179
                        ],
1180
                    ],
1181
                    'ALL: Filter on first name only' => (object) [
1182
                        'keywords' => ['adam'],
1183
                        'jointype' => filter::JOINTYPE_ALL,
1184
                        'count' => 1,
1185
                        'expectedusers' => [
1186
                            'adam.ant',
1187
                        ],
1188
                    ],
1189
                    'ALL: Filter on last name only' => (object) [
1190
                        'keywords' => ['BeNNeTt'],
1191
                        'jointype' => filter::JOINTYPE_ALL,
1192
                        'count' => 1,
1193
                        'expectedusers' => [
1194
                            'barbara.bennett',
1195
                        ],
1196
                    ],
1197
                    'ALL: Filter on first/Last name' => (object) [
1198
                        'keywords' => ['ant'],
1199
                        'jointype' => filter::JOINTYPE_ALL,
1200
                        'count' => 2,
1201
                        'expectedusers' => [
1202
                            'adam.ant',
1203
                            'tony.rogers',
1204
                        ],
1205
                    ],
1206
                    'ALL: Filter on middlename only' => (object) [
1207
                        'keywords' => ['Jeff'],
1208
                        'jointype' => filter::JOINTYPE_ALL,
1209
                        'count' => 1,
1210
                        'expectedusers' => [
1211
                            'colin.carnforth',
1212
                        ],
1213
                    ],
1214
                    'ALL: Filter on username (no match)' => (object) [
1215
                        'keywords' => ['sara.rester'],
1216
                        'jointype' => filter::JOINTYPE_ALL,
1217
                        'count' => 0,
1218
                        'expectedusers' => [],
1219
                    ],
1220
                    'ALL: Filter on email only' => (object) [
1221
                        'keywords' => ['zazu'],
1222
                        'jointype' => filter::JOINTYPE_ALL,
1223
                        'count' => 1,
1224
                        'expectedusers' => [
1225
                            'sarah.rester',
1226
                        ],
1227
                    ],
1228
                    'ALL: Filter on first name phonetic only' => (object) [
1229
                        'keywords' => ['Sera'],
1230
                        'jointype' => filter::JOINTYPE_ALL,
1231
                        'count' => 1,
1232
                        'expectedusers' => [
1233
                            'sarah.rester',
1234
                        ],
1235
                    ],
1236
                    'ALL: Filter on last name phonetic only' => (object) [
1237
                        'keywords' => ['jour'],
1238
                        'jointype' => filter::JOINTYPE_ALL,
1239
                        'count' => 1,
1240
                        'expectedusers' => [
1241
                            'tony.rogers',
1242
                        ],
1243
                    ],
1244
                    'ALL: Filter on alternate name only' => (object) [
1245
                        'keywords' => ['Babs'],
1246
                        'jointype' => filter::JOINTYPE_ALL,
1247
                        'count' => 1,
1248
                        'expectedusers' => [
1249
                            'barbara.bennett',
1250
                        ],
1251
                    ],
1252
                    'ALL: Filter on multiple keywords (first/last name)' => (object) [
1253
                        'keywords' => ['ant', 'rog'],
1254
                        'jointype' => filter::JOINTYPE_ALL,
1255
                        'count' => 1,
1256
                        'expectedusers' => [
1257
                            'tony.rogers',
1258
                        ],
1259
                    ],
1260
                    'ALL: Filter on multiple keywords (first/middle/last name)' => (object) [
1261
                        'keywords' => ['ant', 'Jeff', 'rog'],
1262
                        'jointype' => filter::JOINTYPE_ALL,
1263
                        'count' => 0,
1264
                        'expectedusers' => [],
1265
                    ],
1266
                    'ALL: Filter on multiple keywords (phonetic/alternate names)' => (object) [
1267
                        'keywords' => ['Bab', 'bra', 'nit'],
1268
                        'jointype' => filter::JOINTYPE_ALL,
1269
                        'count' => 1,
1270
                        'expectedusers' => [
1271
                            'barbara.bennett',
1272
                        ],
1273
                    ],
1274
                    'ALL: Filter on custom profile field' => (object) [
1275
                        'keywords' => ['Kermit', 'Kermi'],
1276
                        'jointype' => filter::JOINTYPE_ALL,
1277
                        'count' => 1,
1278
                        'expectedusers' => [
1279
                            'barbara.bennett',
1280
                        ],
1281
                        'asuser' => 'admin',
1282
                    ],
1283
                    'ALL: Filter on custom profile field (no permissions)' => (object) [
1284
                        'keywords' => ['Kermit', 'Kermi'],
1285
                        'jointype' => filter::JOINTYPE_ALL,
1286
                        'count' => 0,
1287
                        'expectedusers' => [],
1288
                        'asuser' => 'barbara.bennett',
1289
                    ],
1290
 
1291
                    // Tests for jointype: NONE.
1292
                    'NONE: No filter' => (object) [
1293
                        'keywords' => [],
1294
                        'jointype' => filter::JOINTYPE_NONE,
1295
                        'count' => 5,
1296
                        'expectedusers' => [
1297
                            'adam.ant',
1298
                            'barbara.bennett',
1299
                            'colin.carnforth',
1300
                            'tony.rogers',
1301
                            'sarah.rester',
1302
                        ],
1303
                    ],
1304
                    'NONE: Filter on first name only' => (object) [
1305
                        'keywords' => ['ara'],
1306
                        'jointype' => filter::JOINTYPE_NONE,
1307
                        'count' => 3,
1308
                        'expectedusers' => [
1309
                            'adam.ant',
1310
                            'colin.carnforth',
1311
                            'tony.rogers',
1312
                        ],
1313
                    ],
1314
                    'NONE: Filter on last name only' => (object) [
1315
                        'keywords' => ['BeNNeTt'],
1316
                        'jointype' => filter::JOINTYPE_NONE,
1317
                        'count' => 4,
1318
                        'expectedusers' => [
1319
                            'adam.ant',
1320
                            'colin.carnforth',
1321
                            'tony.rogers',
1322
                            'sarah.rester',
1323
                        ],
1324
                    ],
1325
                    'NONE: Filter on first/Last name' => (object) [
1326
                        'keywords' => ['ar'],
1327
                        'jointype' => filter::JOINTYPE_NONE,
1328
                        'count' => 2,
1329
                        'expectedusers' => [
1330
                            'adam.ant',
1331
                            'tony.rogers',
1332
                        ],
1333
                    ],
1334
                    'NONE: Filter on middlename only' => (object) [
1335
                        'keywords' => ['Jeff'],
1336
                        'jointype' => filter::JOINTYPE_NONE,
1337
                        'count' => 4,
1338
                        'expectedusers' => [
1339
                            'adam.ant',
1340
                            'barbara.bennett',
1341
                            'tony.rogers',
1342
                            'sarah.rester',
1343
                        ],
1344
                    ],
1345
                    'NONE: Filter on username (no match)' => (object) [
1346
                        'keywords' => ['sara.rester'],
1347
                        'jointype' => filter::JOINTYPE_NONE,
1348
                        'count' => 5,
1349
                        'expectedusers' => [
1350
                            'adam.ant',
1351
                            'barbara.bennett',
1352
                            'colin.carnforth',
1353
                            'tony.rogers',
1354
                            'sarah.rester',
1355
                        ],
1356
                    ],
1357
                    'NONE: Filter on email' => (object) [
1358
                        'keywords' => ['zazu'],
1359
                        'jointype' => filter::JOINTYPE_NONE,
1360
                        'count' => 4,
1361
                        'expectedusers' => [
1362
                            'adam.ant',
1363
                            'barbara.bennett',
1364
                            'colin.carnforth',
1365
                            'tony.rogers',
1366
                        ],
1367
                    ],
1368
                    'NONE: Filter on first name phonetic only' => (object) [
1369
                        'keywords' => ['Sera'],
1370
                        'jointype' => filter::JOINTYPE_NONE,
1371
                        'count' => 4,
1372
                        'expectedusers' => [
1373
                            'adam.ant',
1374
                            'barbara.bennett',
1375
                            'colin.carnforth',
1376
                            'tony.rogers',
1377
                        ],
1378
                    ],
1379
                    'NONE: Filter on last name phonetic only' => (object) [
1380
                        'keywords' => ['jour'],
1381
                        'jointype' => filter::JOINTYPE_NONE,
1382
                        'count' => 4,
1383
                        'expectedusers' => [
1384
                            'adam.ant',
1385
                            'barbara.bennett',
1386
                            'colin.carnforth',
1387
                            'sarah.rester',
1388
                        ],
1389
                    ],
1390
                    'NONE: Filter on alternate name only' => (object) [
1391
                        'keywords' => ['Babs'],
1392
                        'jointype' => filter::JOINTYPE_NONE,
1393
                        'count' => 4,
1394
                        'expectedusers' => [
1395
                            'adam.ant',
1396
                            'colin.carnforth',
1397
                            'tony.rogers',
1398
                            'sarah.rester',
1399
                        ],
1400
                    ],
1401
                    'NONE: Filter on multiple keywords (first/last name)' => (object) [
1402
                        'keywords' => ['ara', 'rog'],
1403
                        'jointype' => filter::JOINTYPE_NONE,
1404
                        'count' => 2,
1405
                        'expectedusers' => [
1406
                            'adam.ant',
1407
                            'colin.carnforth',
1408
                        ],
1409
                    ],
1410
                    'NONE: Filter on multiple keywords (first/middle/last name)' => (object) [
1411
                        'keywords' => ['ant', 'Jeff', 'rog'],
1412
                        'jointype' => filter::JOINTYPE_NONE,
1413
                        'count' => 2,
1414
                        'expectedusers' => [
1415
                            'barbara.bennett',
1416
                            'sarah.rester',
1417
                        ],
1418
                    ],
1419
                    'NONE: Filter on multiple keywords (phonetic/alternate names)' => (object) [
1420
                        'keywords' => ['Bab', 'bra', 'nit'],
1421
                        'jointype' => filter::JOINTYPE_NONE,
1422
                        'count' => 4,
1423
                        'expectedusers' => [
1424
                            'adam.ant',
1425
                            'colin.carnforth',
1426
                            'tony.rogers',
1427
                            'sarah.rester',
1428
                        ],
1429
                    ],
1430
                    'NONE: Filter on custom profile field' => (object) [
1431
                        'keywords' => ['Kermit', 'Mr Toad'],
1432
                        'jointype' => filter::JOINTYPE_NONE,
1433
                        'count' => 3,
1434
                        'expectedusers' => [
1435
                            'adam.ant',
1436
                            'colin.carnforth',
1437
                            'sarah.rester',
1438
                        ],
1439
                        'asuser' => 'admin',
1440
                    ],
1441
                    'NONE: Filter on custom profile field (no permissions)' => (object) [
1442
                        'keywords' => ['Kermit', 'Mr Toad'],
1443
                        'jointype' => filter::JOINTYPE_NONE,
1444
                        'count' => 5,
1445
                        'expectedusers' => [
1446
                            'adam.ant',
1447
                            'barbara.bennett',
1448
                            'colin.carnforth',
1449
                            'tony.rogers',
1450
                            'sarah.rester',
1451
                        ],
1452
                        'asuser' => 'barbara.bennett',
1453
                    ],
1454
                ],
1455
            ],
1456
        ];
1457
 
1458
        $finaltests = [];
1459
        foreach ($tests as $testname => $testdata) {
1460
            foreach ($testdata->expect as $expectname => $expectdata) {
1461
                $finaltests["{$testname} => {$expectname}"] = [
1462
                    'users' => $testdata->users,
1463
                    'keywords' => $expectdata->keywords,
1464
                    'jointype' => $expectdata->jointype,
1465
                    'count' => $expectdata->count,
1466
                    'expectedusers' => $expectdata->expectedusers,
1467
                    'asuser' => $expectdata->asuser ?? ''
1468
                ];
1469
            }
1470
        }
1471
 
1472
        return $finaltests;
1473
    }
1474
 
1475
    /**
1476
     * Ensure that the enrolment status filter works as expected with the provided test cases.
1477
     *
1478
     * @param array $usersdata The list of users to create
1479
     * @param array $statuses The list of statuses to filter by
1480
     * @param int $jointype The join type to use when combining filter values
1481
     * @param int $count The expected count
1482
     * @param array $expectedusers
1483
     * @dataProvider status_provider
1484
     */
1485
    public function test_status_filter(array $usersdata, array $statuses, int $jointype, int $count, array $expectedusers): void {
1486
        $course = $this->getDataGenerator()->create_course();
1487
        $coursecontext = context_course::instance($course->id);
1488
        $users = [];
1489
 
1490
        // Ensure sufficient capabilities to view all statuses.
1491
        $this->setAdminUser();
1492
 
1493
        // Ensure all enrolment methods enabled.
1494
        $enrolinstances = enrol_get_instances($course->id, false);
1495
        foreach ($enrolinstances as $instance) {
1496
            $plugin = enrol_get_plugin($instance->enrol);
1497
            $plugin->update_status($instance, ENROL_INSTANCE_ENABLED);
1498
        }
1499
 
1500
        foreach ($usersdata as $username => $userdata) {
1501
            $user = $this->getDataGenerator()->create_user(['username' => $username]);
1502
 
1503
            if (array_key_exists('status', $userdata)) {
1504
                foreach ($userdata['status'] as $enrolmethod => $status) {
1505
                    $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student', $enrolmethod, 0, 0, $status);
1506
                }
1507
            }
1508
 
1509
            $users[$username] = $user;
1510
        }
1511
 
1512
        // Create a secondary course with users. We should not see these users.
1513
        $this->create_course_with_users(1, 1, 1, 1);
1514
 
1515
        // Create the basic filter.
1516
        $filterset = new participants_filterset();
1517
        $filterset->add_filter(new integer_filter('courseid', null, [(int) $course->id]));
1518
 
1519
        // Create the status filter.
1520
        $statusfilter = new integer_filter('status');
1521
        $filterset->add_filter($statusfilter);
1522
 
1523
        // Configure the filter.
1524
        foreach ($statuses as $status) {
1525
            $statusfilter->add_filter_value($status);
1526
        }
1527
        $statusfilter->set_join_type($jointype);
1528
 
1529
        // Run the search.
1530
        $search = new participants_search($course, $coursecontext, $filterset);
1531
        $rs = $search->get_participants();
1532
        $this->assertInstanceOf(moodle_recordset::class, $rs);
1533
        $records = $this->convert_recordset_to_array($rs);
1534
 
1535
        $this->assertCount($count, $records);
1536
        $this->assertEquals($count, $search->get_total_participants_count());
1537
 
1538
        foreach ($expectedusers as $expecteduser) {
1539
            $this->assertArrayHasKey($users[$expecteduser]->id, $records);
1540
        }
1541
    }
1542
 
1543
    /**
1544
     * Data provider for status filter tests.
1545
     *
1546
     * @return array
1547
     */
1548
    public function status_provider(): array {
1549
        $tests = [
1550
            // Users with different statuses and enrolment methods (so multiple statuses are possible for the same user).
1551
            'Users with different enrolment statuses' => (object) [
1552
                'users' => [
1553
                    'a' => [
1554
                        'status' => [
1555
                            'manual' => ENROL_USER_ACTIVE,
1556
                        ]
1557
                    ],
1558
                    'b' => [
1559
                        'status' => [
1560
                            'self' => ENROL_USER_ACTIVE,
1561
                        ]
1562
                    ],
1563
                    'c' => [
1564
                        'status' => [
1565
                            'manual' => ENROL_USER_SUSPENDED,
1566
                        ]
1567
                    ],
1568
                    'd' => [
1569
                        'status' => [
1570
                            'self' => ENROL_USER_SUSPENDED,
1571
                        ]
1572
                    ],
1573
                    'e' => [
1574
                        'status' => [
1575
                            'manual' => ENROL_USER_ACTIVE,
1576
                            'self' => ENROL_USER_SUSPENDED,
1577
                        ]
1578
                    ],
1579
                ],
1580
                'expect' => [
1581
                    // Tests for jointype: ANY.
1582
                    'ANY: No filter' => (object) [
1583
                        'status' => [],
1584
                        'jointype' => filter::JOINTYPE_ANY,
1585
                        'count' => 5,
1586
                        'expectedusers' => [
1587
                            'a',
1588
                            'b',
1589
                            'c',
1590
                            'd',
1591
                            'e',
1592
                        ],
1593
                    ],
1594
                    'ANY: Filter on active only' => (object) [
1595
                        'status' => [ENROL_USER_ACTIVE],
1596
                        'jointype' => filter::JOINTYPE_ANY,
1597
                        'count' => 3,
1598
                        'expectedusers' => [
1599
                            'a',
1600
                            'b',
1601
                            'e',
1602
                        ],
1603
                    ],
1604
                    'ANY: Filter on suspended only' => (object) [
1605
                        'status' => [ENROL_USER_SUSPENDED],
1606
                        'jointype' => filter::JOINTYPE_ANY,
1607
                        'count' => 3,
1608
                        'expectedusers' => [
1609
                            'c',
1610
                            'd',
1611
                            'e',
1612
                        ],
1613
                    ],
1614
                    'ANY: Filter on multiple statuses' => (object) [
1615
                        'status' => [ENROL_USER_ACTIVE, ENROL_USER_SUSPENDED],
1616
                        'jointype' => filter::JOINTYPE_ANY,
1617
                        'count' => 5,
1618
                        'expectedusers' => [
1619
                            'a',
1620
                            'b',
1621
                            'c',
1622
                            'd',
1623
                            'e',
1624
                        ],
1625
                    ],
1626
 
1627
                    // Tests for jointype: ALL.
1628
                    'ALL: No filter' => (object) [
1629
                       'status' => [],
1630
                        'jointype' => filter::JOINTYPE_ALL,
1631
                        'count' => 5,
1632
                        'expectedusers' => [
1633
                            'a',
1634
                            'b',
1635
                            'c',
1636
                            'd',
1637
                            'e',
1638
                        ],
1639
                    ],
1640
                    'ALL: Filter on active only' => (object) [
1641
                        'status' => [ENROL_USER_ACTIVE],
1642
                        'jointype' => filter::JOINTYPE_ALL,
1643
                        'count' => 3,
1644
                        'expectedusers' => [
1645
                            'a',
1646
                            'b',
1647
                            'e',
1648
                        ],
1649
                    ],
1650
                    'ALL: Filter on suspended only' => (object) [
1651
                        'status' => [ENROL_USER_SUSPENDED],
1652
                        'jointype' => filter::JOINTYPE_ALL,
1653
                        'count' => 3,
1654
                        'expectedusers' => [
1655
                            'c',
1656
                            'd',
1657
                            'e',
1658
                        ],
1659
                    ],
1660
                    'ALL: Filter on multiple statuses' => (object) [
1661
                        'status' => [ENROL_USER_ACTIVE, ENROL_USER_SUSPENDED],
1662
                        'jointype' => filter::JOINTYPE_ALL,
1663
                        'count' => 1,
1664
                        'expectedusers' => [
1665
                            'e',
1666
                        ],
1667
                    ],
1668
 
1669
                    // Tests for jointype: NONE.
1670
                    'NONE: No filter' => (object) [
1671
                       'status' => [],
1672
                        'jointype' => filter::JOINTYPE_NONE,
1673
                        'count' => 5,
1674
                        'expectedusers' => [
1675
                            'a',
1676
                            'b',
1677
                            'c',
1678
                            'd',
1679
                            'e',
1680
                        ],
1681
                    ],
1682
                    'NONE: Filter on active only' => (object) [
1683
                        'status' => [ENROL_USER_ACTIVE],
1684
                        'jointype' => filter::JOINTYPE_NONE,
1685
                        'count' => 3,
1686
                        'expectedusers' => [
1687
                            'c',
1688
                            'd',
1689
                            'e',
1690
                        ],
1691
                    ],
1692
                    'NONE: Filter on suspended only' => (object) [
1693
                        'status' => [ENROL_USER_SUSPENDED],
1694
                        'jointype' => filter::JOINTYPE_NONE,
1695
                        'count' => 3,
1696
                        'expectedusers' => [
1697
                            'a',
1698
                            'b',
1699
                            'e',
1700
                        ],
1701
                    ],
1702
                    'NONE: Filter on multiple statuses' => (object) [
1703
                        'status' => [ENROL_USER_ACTIVE, ENROL_USER_SUSPENDED],
1704
                        'jointype' => filter::JOINTYPE_NONE,
1705
                        'count' => 0,
1706
                        'expectedusers' => [],
1707
                    ],
1708
                ],
1709
            ],
1710
        ];
1711
 
1712
        $finaltests = [];
1713
        foreach ($tests as $testname => $testdata) {
1714
            foreach ($testdata->expect as $expectname => $expectdata) {
1715
                $finaltests["{$testname} => {$expectname}"] = [
1716
                    'users' => $testdata->users,
1717
                    'status' => $expectdata->status,
1718
                    'jointype' => $expectdata->jointype,
1719
                    'count' => $expectdata->count,
1720
                    'expectedusers' => $expectdata->expectedusers,
1721
                ];
1722
            }
1723
        }
1724
 
1725
        return $finaltests;
1726
    }
1727
 
1728
    /**
1729
     * Ensure that the enrolment methods filter works as expected with the provided test cases.
1730
     *
1731
     * @param array $usersdata The list of users to create
1732
     * @param array $enrolmethods The list of enrolment methods to filter by
1733
     * @param int $jointype The join type to use when combining filter values
1734
     * @param int $count The expected count
1735
     * @param array $expectedusers
1736
     * @dataProvider enrolments_provider
1737
     */
1738
    public function test_enrolments_filter(array $usersdata, array $enrolmethods, int $jointype, int $count,
1739
            array $expectedusers): void {
1740
 
1741
        $course = $this->getDataGenerator()->create_course();
1742
        $coursecontext = context_course::instance($course->id);
1743
        $users = [];
1744
 
1745
        // Ensure all enrolment methods enabled and mapped for setting the filter later.
1746
        $enrolinstances = enrol_get_instances($course->id, false);
1747
        $enrolinstancesmap = [];
1748
        foreach ($enrolinstances as $instance) {
1749
            $plugin = enrol_get_plugin($instance->enrol);
1750
            $plugin->update_status($instance, ENROL_INSTANCE_ENABLED);
1751
 
1752
            $enrolinstancesmap[$instance->enrol] = (int) $instance->id;
1753
        }
1754
 
1755
        foreach ($usersdata as $username => $userdata) {
1756
            $user = $this->getDataGenerator()->create_user(['username' => $username]);
1757
 
1758
            if (array_key_exists('enrolmethods', $userdata)) {
1759
                foreach ($userdata['enrolmethods'] as $enrolmethod) {
1760
                    $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student', $enrolmethod);
1761
                }
1762
            }
1763
 
1764
            $users[$username] = $user;
1765
        }
1766
 
1767
        // Create a secondary course with users. We should not see these users.
1768
        $this->create_course_with_users(1, 1, 1, 1);
1769
 
1770
        // Create the basic filter.
1771
        $filterset = new participants_filterset();
1772
        $filterset->add_filter(new integer_filter('courseid', null, [(int) $course->id]));
1773
 
1774
        // Create the enrolment methods filter.
1775
        $enrolmethodfilter = new integer_filter('enrolments');
1776
        $filterset->add_filter($enrolmethodfilter);
1777
 
1778
        // Configure the filter.
1779
        foreach ($enrolmethods as $enrolmethod) {
1780
            $enrolmethodfilter->add_filter_value($enrolinstancesmap[$enrolmethod]);
1781
        }
1782
        $enrolmethodfilter->set_join_type($jointype);
1783
 
1784
        // Run the search.
1785
        $search = new participants_search($course, $coursecontext, $filterset);
1786
        $rs = $search->get_participants();
1787
        $this->assertInstanceOf(moodle_recordset::class, $rs);
1788
        $records = $this->convert_recordset_to_array($rs);
1789
 
1790
        $this->assertCount($count, $records);
1791
        $this->assertEquals($count, $search->get_total_participants_count());
1792
 
1793
        foreach ($expectedusers as $expecteduser) {
1794
            $this->assertArrayHasKey($users[$expecteduser]->id, $records);
1795
        }
1796
    }
1797
 
1798
    /**
1799
     * Data provider for enrolments filter tests.
1800
     *
1801
     * @return array
1802
     */
1803
    public function enrolments_provider(): array {
1804
        $tests = [
1805
            // Users with different enrolment methods.
1806
            'Users with different enrolment methods' => (object) [
1807
                'users' => [
1808
                    'a' => [
1809
                        'enrolmethods' => [
1810
                            'manual',
1811
                        ]
1812
                    ],
1813
                    'b' => [
1814
                        'enrolmethods' => [
1815
                            'self',
1816
                        ]
1817
                    ],
1818
                    'c' => [
1819
                        'enrolmethods' => [
1820
                            'manual',
1821
                            'self',
1822
                        ]
1823
                    ],
1824
                ],
1825
                'expect' => [
1826
                    // Tests for jointype: ANY.
1827
                    'ANY: No filter' => (object) [
1828
                        'enrolmethods' => [],
1829
                        'jointype' => filter::JOINTYPE_ANY,
1830
                        'count' => 3,
1831
                        'expectedusers' => [
1832
                            'a',
1833
                            'b',
1834
                            'c',
1835
                        ],
1836
                    ],
1837
                    'ANY: Filter by manual enrolments only' => (object) [
1838
                        'enrolmethods' => ['manual'],
1839
                        'jointype' => filter::JOINTYPE_ANY,
1840
                        'count' => 2,
1841
                        'expectedusers' => [
1842
                            'a',
1843
                            'c',
1844
                        ],
1845
                    ],
1846
                    'ANY: Filter by self enrolments only' => (object) [
1847
                        'enrolmethods' => ['self'],
1848
                        'jointype' => filter::JOINTYPE_ANY,
1849
                        'count' => 2,
1850
                        'expectedusers' => [
1851
                            'b',
1852
                            'c',
1853
                        ],
1854
                    ],
1855
                    'ANY: Filter by multiple enrolment methods' => (object) [
1856
                        'enrolmethods' => ['manual', 'self'],
1857
                        'jointype' => filter::JOINTYPE_ANY,
1858
                        'count' => 3,
1859
                        'expectedusers' => [
1860
                            'a',
1861
                            'b',
1862
                            'c',
1863
                        ],
1864
                    ],
1865
 
1866
                    // Tests for jointype: ALL.
1867
                    'ALL: No filter' => (object) [
1868
                       'enrolmethods' => [],
1869
                        'jointype' => filter::JOINTYPE_ALL,
1870
                        'count' => 3,
1871
                        'expectedusers' => [
1872
                            'a',
1873
                            'b',
1874
                            'c',
1875
                        ],
1876
                    ],
1877
                    'ALL: Filter by manual enrolments only' => (object) [
1878
                        'enrolmethods' => ['manual'],
1879
                        'jointype' => filter::JOINTYPE_ALL,
1880
                        'count' => 2,
1881
                        'expectedusers' => [
1882
                            'a',
1883
                            'c',
1884
                        ],
1885
                    ],
1886
                    'ALL: Filter by multiple enrolment methods' => (object) [
1887
                        'enrolmethods' => ['manual', 'self'],
1888
                        'jointype' => filter::JOINTYPE_ALL,
1889
                        'count' => 1,
1890
                        'expectedusers' => [
1891
                            'c',
1892
                        ],
1893
                    ],
1894
 
1895
                    // Tests for jointype: NONE.
1896
                    'NONE: No filter' => (object) [
1897
                       'enrolmethods' => [],
1898
                        'jointype' => filter::JOINTYPE_NONE,
1899
                        'count' => 3,
1900
                        'expectedusers' => [
1901
                            'a',
1902
                            'b',
1903
                            'c',
1904
                        ],
1905
                    ],
1906
                    'NONE: Filter by manual enrolments only' => (object) [
1907
                        'enrolmethods' => ['manual'],
1908
                        'jointype' => filter::JOINTYPE_NONE,
1909
                        'count' => 1,
1910
                        'expectedusers' => [
1911
                            'b',
1912
                        ],
1913
                    ],
1914
                    'NONE: Filter by multiple enrolment methods' => (object) [
1915
                        'enrolmethods' => ['manual', 'self'],
1916
                        'jointype' => filter::JOINTYPE_NONE,
1917
                        'count' => 0,
1918
                        'expectedusers' => [],
1919
                    ],
1920
                ],
1921
            ],
1922
        ];
1923
 
1924
        $finaltests = [];
1925
        foreach ($tests as $testname => $testdata) {
1926
            foreach ($testdata->expect as $expectname => $expectdata) {
1927
                $finaltests["{$testname} => {$expectname}"] = [
1928
                    'users' => $testdata->users,
1929
                    'enrolmethods' => $expectdata->enrolmethods,
1930
                    'jointype' => $expectdata->jointype,
1931
                    'count' => $expectdata->count,
1932
                    'expectedusers' => $expectdata->expectedusers,
1933
                ];
1934
            }
1935
        }
1936
 
1937
        return $finaltests;
1938
    }
1939
 
1940
    /**
1941
     * Ensure that the groups filter works as expected with the provided test cases.
1942
     *
1943
     * @param array $usersdata The list of users to create
1944
     * @param array $groupsavailable The names of groups that should be created in the course
1945
     * @param array $filtergroups The names of groups to filter by
1946
     * @param int $jointype The join type to use when combining filter values
1947
     * @param int $count The expected count
1948
     * @param array $expectedusers
1949
     * @dataProvider groups_provider
1950
     */
1951
    public function test_groups_filter(array $usersdata, array $groupsavailable, array $filtergroups, int $jointype, int $count,
1952
            array $expectedusers): void {
1953
 
1954
        $course = $this->getDataGenerator()->create_course();
1955
        $coursecontext = context_course::instance($course->id);
1956
        $users = [];
1957
 
1958
        // Prepare data for filtering by users in no groups.
1959
        $nogroupsdata = (object) [
1960
            'id' => USERSWITHOUTGROUP,
1961
        ];
1962
 
1963
        // Map group names to group data.
1964
         $groupsdata = ['nogroups' => $nogroupsdata];
1965
        foreach ($groupsavailable as $groupname) {
1966
            $groupinfo = [
1967
                'courseid' => $course->id,
1968
                'name' => $groupname,
1969
            ];
1970
 
1971
            $groupsdata[$groupname] = $this->getDataGenerator()->create_group($groupinfo);
1972
        }
1973
 
1974
        foreach ($usersdata as $username => $userdata) {
1975
            $user = $this->getDataGenerator()->create_user(['username' => $username]);
1976
            $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
1977
 
1978
            if (array_key_exists('groups', $userdata)) {
1979
                foreach ($userdata['groups'] as $groupname) {
1980
                    $userinfo = [
1981
                        'userid' => $user->id,
1982
                        'groupid' => (int) $groupsdata[$groupname]->id,
1983
                    ];
1984
                    $this->getDataGenerator()->create_group_member($userinfo);
1985
                }
1986
            }
1987
 
1988
            $users[$username] = $user;
1989
        }
1990
 
1991
        // Create a secondary course with users. We should not see these users.
1992
        $this->create_course_with_users(1, 1, 1, 1);
1993
 
1994
        // Create the basic filter.
1995
        $filterset = new participants_filterset();
1996
        $filterset->add_filter(new integer_filter('courseid', null, [(int) $course->id]));
1997
 
1998
        // Create the groups filter.
1999
        $groupsfilter = new integer_filter('groups');
2000
        $filterset->add_filter($groupsfilter);
2001
 
2002
        // Configure the filter.
2003
        foreach ($filtergroups as $filtergroupname) {
2004
            $groupsfilter->add_filter_value((int) $groupsdata[$filtergroupname]->id);
2005
        }
2006
        $groupsfilter->set_join_type($jointype);
2007
 
2008
        // Run the search.
2009
        $search = new participants_search($course, $coursecontext, $filterset);
2010
        $rs = $search->get_participants();
2011
        $this->assertInstanceOf(moodle_recordset::class, $rs);
2012
        $records = $this->convert_recordset_to_array($rs);
2013
 
2014
        $this->assertCount($count, $records);
2015
        $this->assertEquals($count, $search->get_total_participants_count());
2016
 
2017
        foreach ($expectedusers as $expecteduser) {
2018
            $this->assertArrayHasKey($users[$expecteduser]->id, $records);
2019
        }
2020
    }
2021
 
2022
    /**
2023
     * Data provider for groups filter tests.
2024
     *
2025
     * @return array
2026
     */
2027
    public function groups_provider(): array {
2028
        $tests = [
2029
            'Users in different groups' => (object) [
2030
                'groupsavailable' => [
2031
                    'groupa',
2032
                    'groupb',
2033
                    'groupc',
2034
                ],
2035
                'users' => [
2036
                    'a' => [
2037
                        'groups' => ['groupa'],
2038
                    ],
2039
                    'b' => [
2040
                        'groups' => ['groupb'],
2041
                    ],
2042
                    'c' => [
2043
                        'groups' => ['groupa', 'groupb'],
2044
                    ],
2045
                    'd' => [
2046
                        'groups' => [],
2047
                    ],
2048
                ],
2049
                'expect' => [
2050
                    // Tests for jointype: ANY.
2051
                    'ANY: No filter' => (object) [
2052
                        'groups' => [],
2053
                        'jointype' => filter::JOINTYPE_ANY,
2054
                        'count' => 4,
2055
                        'expectedusers' => [
2056
                            'a',
2057
                            'b',
2058
                            'c',
2059
                            'd',
2060
                        ],
2061
                    ],
2062
                    'ANY: Filter on a single group' => (object) [
2063
                        'groups' => ['groupa'],
2064
                        'jointype' => filter::JOINTYPE_ANY,
2065
                        'count' => 2,
2066
                        'expectedusers' => [
2067
                            'a',
2068
                            'c',
2069
                        ],
2070
                    ],
2071
                    'ANY: Filter on a group with no members' => (object) [
2072
                        'groups' => ['groupc'],
2073
                        'jointype' => filter::JOINTYPE_ANY,
2074
                        'count' => 0,
2075
                        'expectedusers' => [],
2076
                    ],
2077
                    'ANY: Filter on multiple groups' => (object) [
2078
                        'groups' => ['groupa', 'groupb'],
2079
                        'jointype' => filter::JOINTYPE_ANY,
2080
                        'count' => 3,
2081
                        'expectedusers' => [
2082
                            'a',
2083
                            'b',
2084
                            'c',
2085
                        ],
2086
                    ],
2087
                    'ANY: Filter on members of no groups only' => (object) [
2088
                        'groups' => ['nogroups'],
2089
                        'jointype' => filter::JOINTYPE_ANY,
2090
                        'count' => 1,
2091
                        'expectedusers' => [
2092
                            'd',
2093
                        ],
2094
                    ],
2095
                    'ANY: Filter on a single group or no groups' => (object) [
2096
                        'groups' => ['groupa', 'nogroups'],
2097
                        'jointype' => filter::JOINTYPE_ANY,
2098
                        'count' => 3,
2099
                        'expectedusers' => [
2100
                            'a',
2101
                            'c',
2102
                            'd',
2103
                        ],
2104
                    ],
2105
                    'ANY: Filter on multiple groups or no groups' => (object) [
2106
                        'groups' => ['groupa', 'groupb', 'nogroups'],
2107
                        'jointype' => filter::JOINTYPE_ANY,
2108
                        'count' => 4,
2109
                        'expectedusers' => [
2110
                            'a',
2111
                            'b',
2112
                            'c',
2113
                            'd',
2114
                        ],
2115
                    ],
2116
 
2117
                    // Tests for jointype: ALL.
2118
                    'ALL: No filter' => (object) [
2119
                        'groups' => [],
2120
                        'jointype' => filter::JOINTYPE_ALL,
2121
                        'count' => 4,
2122
                        'expectedusers' => [
2123
                            'a',
2124
                            'b',
2125
                            'c',
2126
                            'd',
2127
                        ],
2128
                    ],
2129
                    'ALL: Filter on a single group' => (object) [
2130
                        'groups' => ['groupa'],
2131
                        'jointype' => filter::JOINTYPE_ALL,
2132
                        'count' => 2,
2133
                        'expectedusers' => [
2134
                            'a',
2135
                            'c',
2136
                        ],
2137
                    ],
2138
                    'ALL: Filter on a group with no members' => (object) [
2139
                        'groups' => ['groupc'],
2140
                        'jointype' => filter::JOINTYPE_ALL,
2141
                        'count' => 0,
2142
                        'expectedusers' => [],
2143
                    ],
2144
                    'ALL: Filter on members of no groups only' => (object) [
2145
                        'groups' => ['nogroups'],
2146
                        'jointype' => filter::JOINTYPE_ALL,
2147
                        'count' => 1,
2148
                        'expectedusers' => [
2149
                            'd',
2150
                        ],
2151
                    ],
2152
                    'ALL: Filter on multiple groups' => (object) [
2153
                        'groups' => ['groupa', 'groupb'],
2154
                        'jointype' => filter::JOINTYPE_ALL,
2155
                        'count' => 1,
2156
                        'expectedusers' => [
2157
                            'c',
2158
                        ],
2159
                    ],
2160
                    'ALL: Filter on a single group and no groups' => (object) [
2161
                        'groups' => ['groupa', 'nogroups'],
2162
                        'jointype' => filter::JOINTYPE_ALL,
2163
                        'count' => 0,
2164
                        'expectedusers' => [],
2165
                    ],
2166
                    'ALL: Filter on multiple groups and no groups' => (object) [
2167
                        'groups' => ['groupa', 'groupb', 'nogroups'],
2168
                        'jointype' => filter::JOINTYPE_ALL,
2169
                        'count' => 0,
2170
                        'expectedusers' => [],
2171
                    ],
2172
 
2173
                    // Tests for jointype: NONE.
2174
                    'NONE: No filter' => (object) [
2175
                        'groups' => [],
2176
                        'jointype' => filter::JOINTYPE_NONE,
2177
                        'count' => 4,
2178
                        'expectedusers' => [
2179
                            'a',
2180
                            'b',
2181
                            'c',
2182
                            'd',
2183
                        ],
2184
                    ],
2185
                    'NONE: Filter on a single group' => (object) [
2186
                        'groups' => ['groupa'],
2187
                        'jointype' => filter::JOINTYPE_NONE,
2188
                        'count' => 2,
2189
                        'expectedusers' => [
2190
                            'b',
2191
                            'd',
2192
                        ],
2193
                    ],
2194
                    'NONE: Filter on a group with no members' => (object) [
2195
                        'groups' => ['groupc'],
2196
                        'jointype' => filter::JOINTYPE_NONE,
2197
                        'count' => 4,
2198
                        'expectedusers' => [
2199
                            'a',
2200
                            'b',
2201
                            'c',
2202
                            'd',
2203
                        ],
2204
                    ],
2205
                    'NONE: Filter on members of no groups only' => (object) [
2206
                        'groups' => ['nogroups'],
2207
                        'jointype' => filter::JOINTYPE_NONE,
2208
                        'count' => 3,
2209
                        'expectedusers' => [
2210
                            'a',
2211
                            'b',
2212
                            'c',
2213
                        ],
2214
                    ],
2215
                    'NONE: Filter on multiple groups' => (object) [
2216
                        'groups' => ['groupa', 'groupb'],
2217
                        'jointype' => filter::JOINTYPE_NONE,
2218
                        'count' => 1,
2219
                        'expectedusers' => [
2220
                            'd',
2221
                        ],
2222
                    ],
2223
                    'NONE: Filter on a single group and no groups' => (object) [
2224
                        'groups' => ['groupa', 'nogroups'],
2225
                        'jointype' => filter::JOINTYPE_NONE,
2226
                        'count' => 1,
2227
                        'expectedusers' => [
2228
                            'b',
2229
                        ],
2230
                    ],
2231
                    'NONE: Filter on multiple groups and no groups' => (object) [
2232
                        'groups' => ['groupa', 'groupb', 'nogroups'],
2233
                        'jointype' => filter::JOINTYPE_NONE,
2234
                        'count' => 0,
2235
                        'expectedusers' => [],
2236
                    ],
2237
                ],
2238
            ],
2239
        ];
2240
 
2241
        $finaltests = [];
2242
        foreach ($tests as $testname => $testdata) {
2243
            foreach ($testdata->expect as $expectname => $expectdata) {
2244
                $finaltests["{$testname} => {$expectname}"] = [
2245
                    'users' => $testdata->users,
2246
                    'groupsavailable' => $testdata->groupsavailable,
2247
                    'filtergroups' => $expectdata->groups,
2248
                    'jointype' => $expectdata->jointype,
2249
                    'count' => $expectdata->count,
2250
                    'expectedusers' => $expectdata->expectedusers,
2251
                ];
2252
            }
2253
        }
2254
 
2255
        return $finaltests;
2256
    }
2257
 
2258
    /**
2259
     * Ensure that the groups filter works as expected when separate groups mode is enabled, with the provided test cases.
2260
     *
2261
     * @param array $usersdata The list of users to create
2262
     * @param array $groupsavailable The names of groups that should be created in the course
2263
     * @param array $filtergroups The names of groups to filter by
2264
     * @param int $jointype The join type to use when combining filter values
2265
     * @param int $count The expected count
2266
     * @param array $expectedusers
2267
     * @param string $loginusername The user to login as for the tests
2268
     * @dataProvider groups_separate_provider
2269
     */
2270
    public function test_groups_filter_separate_groups(array $usersdata, array $groupsavailable, array $filtergroups, int $jointype,
2271
            int $count, array $expectedusers, string $loginusername): void {
2272
 
2273
        $course = $this->getDataGenerator()->create_course();
2274
        $coursecontext = context_course::instance($course->id);
2275
        $users = [];
2276
 
2277
        // Enable separate groups mode on the course.
2278
        $course->groupmode = SEPARATEGROUPS;
2279
        $course->groupmodeforce = true;
2280
        update_course($course);
2281
 
2282
        // Prepare data for filtering by users in no groups.
2283
        $nogroupsdata = (object) [
2284
            'id' => USERSWITHOUTGROUP,
2285
        ];
2286
 
2287
        // Map group names to group data.
2288
         $groupsdata = ['nogroups' => $nogroupsdata];
2289
        foreach ($groupsavailable as $groupname) {
2290
            $groupinfo = [
2291
                'courseid' => $course->id,
2292
                'name' => $groupname,
2293
            ];
2294
 
2295
            $groupsdata[$groupname] = $this->getDataGenerator()->create_group($groupinfo);
2296
        }
2297
 
2298
        foreach ($usersdata as $username => $userdata) {
2299
            $user = $this->getDataGenerator()->create_user(['username' => $username]);
2300
            $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
2301
 
2302
            if (array_key_exists('groups', $userdata)) {
2303
                foreach ($userdata['groups'] as $groupname) {
2304
                    $userinfo = [
2305
                        'userid' => $user->id,
2306
                        'groupid' => (int) $groupsdata[$groupname]->id,
2307
                    ];
2308
                    $this->getDataGenerator()->create_group_member($userinfo);
2309
                }
2310
            }
2311
 
2312
            $users[$username] = $user;
2313
 
2314
            if ($username == $loginusername) {
2315
                $loginuser = $user;
2316
            }
2317
        }
2318
 
2319
        // Create a secondary course with users. We should not see these users.
2320
        $this->create_course_with_users(1, 1, 1, 1);
2321
 
2322
        // Log in as the user to be tested.
2323
        $this->setUser($loginuser);
2324
 
2325
        // Create the basic filter.
2326
        $filterset = new participants_filterset();
2327
        $filterset->add_filter(new integer_filter('courseid', null, [(int) $course->id]));
2328
 
2329
        // Create the groups filter.
2330
        $groupsfilter = new integer_filter('groups');
2331
        $filterset->add_filter($groupsfilter);
2332
 
2333
        // Configure the filter.
2334
        foreach ($filtergroups as $filtergroupname) {
2335
            $groupsfilter->add_filter_value((int) $groupsdata[$filtergroupname]->id);
2336
        }
2337
        $groupsfilter->set_join_type($jointype);
2338
 
2339
        // Run the search.
2340
        $search = new participants_search($course, $coursecontext, $filterset);
2341
 
2342
        // Tests on user in no groups should throw an exception as they are not supported (participants are not visible to them).
2343
        if (in_array('exception', $expectedusers)) {
2344
            $this->expectException(\coding_exception::class);
2345
            $rs = $search->get_participants();
2346
        } else {
2347
            // All other cases are tested as normal.
2348
            $rs = $search->get_participants();
2349
            $this->assertInstanceOf(moodle_recordset::class, $rs);
2350
            $records = $this->convert_recordset_to_array($rs);
2351
 
2352
            $this->assertCount($count, $records);
2353
            $this->assertEquals($count, $search->get_total_participants_count());
2354
 
2355
            foreach ($expectedusers as $expecteduser) {
2356
                $this->assertArrayHasKey($users[$expecteduser]->id, $records);
2357
            }
2358
        }
2359
    }
2360
 
2361
    /**
2362
     * Data provider for groups filter tests.
2363
     *
2364
     * @return array
2365
     */
2366
    public function groups_separate_provider(): array {
2367
        $tests = [
2368
            'Users in different groups with separate groups mode enabled' => (object) [
2369
                'groupsavailable' => [
2370
                    'groupa',
2371
                    'groupb',
2372
                    'groupc',
2373
                ],
2374
                'users' => [
2375
                    'a' => [
2376
                        'groups' => ['groupa'],
2377
                    ],
2378
                    'b' => [
2379
                        'groups' => ['groupb'],
2380
                    ],
2381
                    'c' => [
2382
                        'groups' => ['groupa', 'groupb'],
2383
                    ],
2384
                    'd' => [
2385
                        'groups' => [],
2386
                    ],
2387
                ],
2388
                'expect' => [
2389
                    // Tests for jointype: ANY.
2390
                    'ANY: No filter, user in one group' => (object) [
2391
                        'loginuser' => 'a',
2392
                        'groups' => [],
2393
                        'jointype' => filter::JOINTYPE_ANY,
2394
                        'count' => 2,
2395
                        'expectedusers' => [
2396
                            'a',
2397
                            'c',
2398
                        ],
2399
                    ],
2400
                    'ANY: No filter, user in multiple groups' => (object) [
2401
                        'loginuser' => 'c',
2402
                        'groups' => [],
2403
                        'jointype' => filter::JOINTYPE_ANY,
2404
                        'count' => 3,
2405
                        'expectedusers' => [
2406
                            'a',
2407
                            'b',
2408
                            'c',
2409
                        ],
2410
                    ],
2411
                    'ANY: No filter, user in no groups' => (object) [
2412
                        'loginuser' => 'd',
2413
                        'groups' => [],
2414
                        'jointype' => filter::JOINTYPE_ANY,
2415
                        'count' => 0,
2416
                        'expectedusers' => ['exception'],
2417
                    ],
2418
                    'ANY: Filter on a single group, user in one group' => (object) [
2419
                        'loginuser' => 'a',
2420
                        'groups' => ['groupa'],
2421
                        'jointype' => filter::JOINTYPE_ANY,
2422
                        'count' => 2,
2423
                        'expectedusers' => [
2424
                            'a',
2425
                            'c',
2426
                        ],
2427
                    ],
2428
                    'ANY: Filter on a single group, user in multple groups' => (object) [
2429
                        'loginuser' => 'c',
2430
                        'groups' => ['groupa'],
2431
                        'jointype' => filter::JOINTYPE_ANY,
2432
                        'count' => 2,
2433
                        'expectedusers' => [
2434
                            'a',
2435
                            'c',
2436
                        ],
2437
                    ],
2438
                    'ANY: Filter on a single group, user in no groups' => (object) [
2439
                        'loginuser' => 'd',
2440
                        'groups' => ['groupa'],
2441
                        'jointype' => filter::JOINTYPE_ANY,
2442
                        'count' => 0,
2443
                        'expectedusers' => ['exception'],
2444
                    ],
2445
                    'ANY: Filter on multiple groups, user in one group (ignore invalid groups)' => (object) [
2446
                        'loginuser' => 'a',
2447
                        'groups' => ['groupa', 'groupb'],
2448
                        'jointype' => filter::JOINTYPE_ANY,
2449
                        'count' => 2,
2450
                        'expectedusers' => [
2451
                            'a',
2452
                            'c',
2453
                        ],
2454
                    ],
2455
                    'ANY: Filter on multiple groups, user in multiple groups' => (object) [
2456
                        'loginuser' => 'c',
2457
                        'groups' => ['groupa', 'groupb'],
2458
                        'jointype' => filter::JOINTYPE_ANY,
2459
                        'count' => 3,
2460
                        'expectedusers' => [
2461
                            'a',
2462
                            'b',
2463
                            'c',
2464
                        ],
2465
                    ],
2466
                    'ANY: Filter on multiple groups or no groups, user in multiple groups (ignore no groups)' => (object) [
2467
                        'loginuser' => 'c',
2468
                        'groups' => ['groupa', 'groupb', 'nogroups'],
2469
                        'jointype' => filter::JOINTYPE_ANY,
2470
                        'count' => 3,
2471
                        'expectedusers' => [
2472
                            'a',
2473
                            'b',
2474
                            'c',
2475
                        ],
2476
                    ],
2477
 
2478
                    // Tests for jointype: ALL.
2479
                    'ALL: No filter, user in one group' => (object) [
2480
                        'loginuser' => 'a',
2481
                        'groups' => [],
2482
                        'jointype' => filter::JOINTYPE_ALL,
2483
                        'count' => 2,
2484
                        'expectedusers' => [
2485
                            'a',
2486
                            'c',
2487
                        ],
2488
                    ],
2489
                    'ALL: No filter, user in multiple groups' => (object) [
2490
                        'loginuser' => 'c',
2491
                        'groups' => [],
2492
                        'jointype' => filter::JOINTYPE_ALL,
2493
                        'count' => 3,
2494
                        'expectedusers' => [
2495
                            'a',
2496
                            'b',
2497
                            'c',
2498
                        ],
2499
                    ],
2500
                    'ALL: No filter, user in no groups' => (object) [
2501
                        'loginuser' => 'd',
2502
                        'groups' => [],
2503
                        'jointype' => filter::JOINTYPE_ALL,
2504
                        'count' => 0,
2505
                        'expectedusers' => ['exception'],
2506
                    ],
2507
                    'ALL: Filter on a single group, user in one group' => (object) [
2508
                        'loginuser' => 'a',
2509
                        'groups' => ['groupa'],
2510
                        'jointype' => filter::JOINTYPE_ALL,
2511
                        'count' => 2,
2512
                        'expectedusers' => [
2513
                            'a',
2514
                            'c',
2515
                        ],
2516
                    ],
2517
                    'ALL: Filter on a single group, user in multple groups' => (object) [
2518
                        'loginuser' => 'c',
2519
                        'groups' => ['groupa'],
2520
                        'jointype' => filter::JOINTYPE_ALL,
2521
                        'count' => 2,
2522
                        'expectedusers' => [
2523
                            'a',
2524
                            'c',
2525
                        ],
2526
                    ],
2527
                    'ALL: Filter on a single group, user in no groups' => (object) [
2528
                        'loginuser' => 'd',
2529
                        'groups' => ['groupa'],
2530
                        'jointype' => filter::JOINTYPE_ALL,
2531
                        'count' => 0,
2532
                        'expectedusers' => ['exception'],
2533
                    ],
2534
                    'ALL: Filter on multiple groups, user in one group (ignore invalid groups)' => (object) [
2535
                        'loginuser' => 'a',
2536
                        'groups' => ['groupa', 'groupb'],
2537
                        'jointype' => filter::JOINTYPE_ALL,
2538
                        'count' => 2,
2539
                        'expectedusers' => [
2540
                            'a',
2541
                            'c',
2542
                        ],
2543
                    ],
2544
                    'ALL: Filter on multiple groups, user in multiple groups' => (object) [
2545
                        'loginuser' => 'c',
2546
                        'groups' => ['groupa', 'groupb'],
2547
                        'jointype' => filter::JOINTYPE_ALL,
2548
                        'count' => 1,
2549
                        'expectedusers' => [
2550
                            'c',
2551
                        ],
2552
                    ],
2553
                    'ALL: Filter on multiple groups or no groups, user in multiple groups (ignore no groups)' => (object) [
2554
                        'loginuser' => 'c',
2555
                        'groups' => ['groupa', 'groupb', 'nogroups'],
2556
                        'jointype' => filter::JOINTYPE_ALL,
2557
                        'count' => 1,
2558
                        'expectedusers' => [
2559
                            'c',
2560
                        ],
2561
                    ],
2562
 
2563
                    // Tests for jointype: NONE.
2564
                    'NONE: No filter, user in one group' => (object) [
2565
                        'loginuser' => 'a',
2566
                        'groups' => [],
2567
                        'jointype' => filter::JOINTYPE_NONE,
2568
                        'count' => 2,
2569
                        'expectedusers' => [
2570
                            'a',
2571
                            'c',
2572
                        ],
2573
                    ],
2574
                    'NONE: No filter, user in multiple groups' => (object) [
2575
                        'loginuser' => 'c',
2576
                        'groups' => [],
2577
                        'jointype' => filter::JOINTYPE_NONE,
2578
                        'count' => 3,
2579
                        'expectedusers' => [
2580
                            'a',
2581
                            'b',
2582
                            'c',
2583
                        ],
2584
                    ],
2585
                    'NONE: No filter, user in no groups' => (object) [
2586
                        'loginuser' => 'd',
2587
                        'groups' => [],
2588
                        'jointype' => filter::JOINTYPE_NONE,
2589
                        'count' => 0,
2590
                        'expectedusers' => ['exception'],
2591
                    ],
2592
                    'NONE: Filter on a single group, user in one group' => (object) [
2593
                        'loginuser' => 'a',
2594
                        'groups' => ['groupa'],
2595
                        'jointype' => filter::JOINTYPE_NONE,
2596
                        'count' => 0,
2597
                        'expectedusers' => [],
2598
                    ],
2599
                    'NONE: Filter on a single group, user in multple groups' => (object) [
2600
                        'loginuser' => 'c',
2601
                        'groups' => ['groupa'],
2602
                        'jointype' => filter::JOINTYPE_NONE,
2603
                        'count' => 1,
2604
                        'expectedusers' => [
2605
                            'b',
2606
                        ],
2607
                    ],
2608
                    'NONE: Filter on a single group, user in no groups' => (object) [
2609
                        'loginuser' => 'd',
2610
                        'groups' => ['groupa'],
2611
                        'jointype' => filter::JOINTYPE_NONE,
2612
                        'count' => 0,
2613
                        'expectedusers' => ['exception'],
2614
                    ],
2615
                    'NONE: Filter on multiple groups, user in one group (ignore invalid groups)' => (object) [
2616
                        'loginuser' => 'a',
2617
                        'groups' => ['groupa', 'groupb'],
2618
                        'jointype' => filter::JOINTYPE_NONE,
2619
                        'count' => 0,
2620
                        'expectedusers' => [],
2621
                    ],
2622
                    'NONE: Filter on multiple groups, user in multiple groups' => (object) [
2623
                        'loginuser' => 'c',
2624
                        'groups' => ['groupa', 'groupb'],
2625
                        'jointype' => filter::JOINTYPE_NONE,
2626
                        'count' => 0,
2627
                        'expectedusers' => [],
2628
                    ],
2629
                    'NONE: Filter on multiple groups or no groups, user in multiple groups (ignore no groups)' => (object) [
2630
                        'loginuser' => 'c',
2631
                        'groups' => ['groupa', 'groupb', 'nogroups'],
2632
                        'jointype' => filter::JOINTYPE_NONE,
2633
                        'count' => 0,
2634
                        'expectedusers' => [],
2635
                    ],
2636
                ],
2637
            ],
2638
        ];
2639
 
2640
        $finaltests = [];
2641
        foreach ($tests as $testname => $testdata) {
2642
            foreach ($testdata->expect as $expectname => $expectdata) {
2643
                $finaltests["{$testname} => {$expectname}"] = [
2644
                    'users' => $testdata->users,
2645
                    'groupsavailable' => $testdata->groupsavailable,
2646
                    'filtergroups' => $expectdata->groups,
2647
                    'jointype' => $expectdata->jointype,
2648
                    'count' => $expectdata->count,
2649
                    'expectedusers' => $expectdata->expectedusers,
2650
                    'loginusername' => $expectdata->loginuser,
2651
                ];
2652
            }
2653
        }
2654
 
2655
        return $finaltests;
2656
    }
2657
 
2658
 
2659
    /**
2660
     * Ensure that the last access filter works as expected with the provided test cases.
2661
     *
2662
     * @param array $usersdata The list of users to create
2663
     * @param array $accesssince The last access data to filter by
2664
     * @param int $jointype The join type to use when combining filter values
2665
     * @param int $count The expected count
2666
     * @param array $expectedusers
2667
     * @dataProvider accesssince_provider
2668
     */
2669
    public function test_accesssince_filter(array $usersdata, array $accesssince, int $jointype, int $count,
2670
            array $expectedusers): void {
2671
 
2672
        $course = $this->getDataGenerator()->create_course();
2673
        $coursecontext = context_course::instance($course->id);
2674
        $users = [];
2675
 
2676
        foreach ($usersdata as $username => $userdata) {
2677
            $usertimestamp = empty($userdata['lastlogin']) ? 0 : strtotime($userdata['lastlogin']);
2678
 
2679
            $user = $this->getDataGenerator()->create_user(['username' => $username]);
2680
            $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
2681
 
2682
            // Create the record of the user's last access to the course.
2683
            if ($usertimestamp > 0) {
2684
                $this->getDataGenerator()->create_user_course_lastaccess($user, $course, $usertimestamp);
2685
            }
2686
 
2687
            $users[$username] = $user;
2688
        }
2689
 
2690
        // Create a secondary course with users. We should not see these users.
2691
        $this->create_course_with_users(1, 1, 1, 1);
2692
 
2693
        // Create the basic filter.
2694
        $filterset = new participants_filterset();
2695
        $filterset->add_filter(new integer_filter('courseid', null, [(int) $course->id]));
2696
 
2697
        // Create the last access filter.
2698
        $lastaccessfilter = new integer_filter('accesssince');
2699
        $filterset->add_filter($lastaccessfilter);
2700
 
2701
        // Configure the filter.
2702
        foreach ($accesssince as $accessstring) {
2703
            $lastaccessfilter->add_filter_value(strtotime($accessstring));
2704
        }
2705
        $lastaccessfilter->set_join_type($jointype);
2706
 
2707
        // Run the search.
2708
        $search = new participants_search($course, $coursecontext, $filterset);
2709
        $rs = $search->get_participants();
2710
        $this->assertInstanceOf(moodle_recordset::class, $rs);
2711
        $records = $this->convert_recordset_to_array($rs);
2712
 
2713
        $this->assertCount($count, $records);
2714
        $this->assertEquals($count, $search->get_total_participants_count());
2715
 
2716
        foreach ($expectedusers as $expecteduser) {
2717
            $this->assertArrayHasKey($users[$expecteduser]->id, $records);
2718
        }
2719
    }
2720
 
2721
    /**
2722
     * Data provider for last access filter tests.
2723
     *
2724
     * @return array
2725
     */
2726
    public function accesssince_provider(): array {
2727
        $tests = [
2728
            // Users with different last access times.
2729
            'Users in different groups' => (object) [
2730
                'users' => [
2731
                    'a' => [
2732
                        'lastlogin' => '-3 days',
2733
                    ],
2734
                    'b' => [
2735
                        'lastlogin' => '-2 weeks',
2736
                    ],
2737
                    'c' => [
2738
                        'lastlogin' => '-5 months',
2739
                    ],
2740
                    'd' => [
2741
                        'lastlogin' => '-11 months',
2742
                    ],
2743
                    'e' => [
2744
                        // Never logged in.
2745
                        'lastlogin' => '',
2746
                    ],
2747
                ],
2748
                'expect' => [
2749
                    // Tests for jointype: ANY.
2750
                    'ANY: No filter' => (object) [
2751
                        'accesssince' => [],
2752
                        'jointype' => filter::JOINTYPE_ANY,
2753
                        'count' => 5,
2754
                        'expectedusers' => [
2755
                            'a',
2756
                            'b',
2757
                            'c',
2758
                            'd',
2759
                            'e',
2760
                        ],
2761
                    ],
2762
                    'ANY: Filter on last login more than 1 year ago' => (object) [
2763
                        'accesssince' => ['-1 year'],
2764
                        'jointype' => filter::JOINTYPE_ANY,
2765
                        'count' => 1,
2766
                        'expectedusers' => [
2767
                            'e',
2768
                        ],
2769
                    ],
2770
                    'ANY: Filter on last login more than 6 months ago' => (object) [
2771
                        'accesssince' => ['-6 months'],
2772
                        'jointype' => filter::JOINTYPE_ANY,
2773
                        'count' => 2,
2774
                        'expectedusers' => [
2775
                            'd',
2776
                            'e',
2777
                        ],
2778
                    ],
2779
                    'ANY: Filter on last login more than 3 weeks ago' => (object) [
2780
                        'accesssince' => ['-3 weeks'],
2781
                        'jointype' => filter::JOINTYPE_ANY,
2782
                        'count' => 3,
2783
                        'expectedusers' => [
2784
                            'c',
2785
                            'd',
2786
                            'e',
2787
                        ],
2788
                    ],
2789
                    'ANY: Filter on last login more than 5 days ago' => (object) [
2790
                        'accesssince' => ['-5 days'],
2791
                        'jointype' => filter::JOINTYPE_ANY,
2792
                        'count' => 4,
2793
                        'expectedusers' => [
2794
                            'b',
2795
                            'c',
2796
                            'd',
2797
                            'e',
2798
                        ],
2799
                    ],
2800
                    'ANY: Filter on last login more than 2 days ago' => (object) [
2801
                        'accesssince' => ['-2 days'],
2802
                        'jointype' => filter::JOINTYPE_ANY,
2803
                        'count' => 5,
2804
                        'expectedusers' => [
2805
                            'a',
2806
                            'b',
2807
                            'c',
2808
                            'd',
2809
                            'e',
2810
                        ],
2811
                    ],
2812
 
2813
                    // Tests for jointype: ALL.
2814
                    'ALL: No filter' => (object) [
2815
                        'accesssince' => [],
2816
                        'jointype' => filter::JOINTYPE_ALL,
2817
                        'count' => 5,
2818
                        'expectedusers' => [
2819
                            'a',
2820
                            'b',
2821
                            'c',
2822
                            'd',
2823
                            'e',
2824
                        ],
2825
                    ],
2826
                    'ALL: Filter on last login more than 1 year ago' => (object) [
2827
                        'accesssince' => ['-1 year'],
2828
                        'jointype' => filter::JOINTYPE_ALL,
2829
                        'count' => 1,
2830
                        'expectedusers' => [
2831
                            'e',
2832
                        ],
2833
                    ],
2834
                    'ALL: Filter on last login more than 6 months ago' => (object) [
2835
                        'accesssince' => ['-6 months'],
2836
                        'jointype' => filter::JOINTYPE_ALL,
2837
                        'count' => 2,
2838
                        'expectedusers' => [
2839
                            'd',
2840
                            'e',
2841
                        ],
2842
                    ],
2843
                    'ALL: Filter on last login more than 3 weeks ago' => (object) [
2844
                        'accesssince' => ['-3 weeks'],
2845
                        'jointype' => filter::JOINTYPE_ALL,
2846
                        'count' => 3,
2847
                        'expectedusers' => [
2848
                            'c',
2849
                            'd',
2850
                            'e',
2851
                        ],
2852
                    ],
2853
                    'ALL: Filter on last login more than 5 days ago' => (object) [
2854
                        'accesssince' => ['-5 days'],
2855
                        'jointype' => filter::JOINTYPE_ALL,
2856
                        'count' => 4,
2857
                        'expectedusers' => [
2858
                            'b',
2859
                            'c',
2860
                            'd',
2861
                            'e',
2862
                        ],
2863
                    ],
2864
                    'ALL: Filter on last login more than 2 days ago' => (object) [
2865
                        'accesssince' => ['-2 days'],
2866
                        'jointype' => filter::JOINTYPE_ALL,
2867
                        'count' => 5,
2868
                        'expectedusers' => [
2869
                            'a',
2870
                            'b',
2871
                            'c',
2872
                            'd',
2873
                            'e',
2874
                        ],
2875
                    ],
2876
 
2877
                    // Tests for jointype: NONE.
2878
                    'NONE: No filter' => (object) [
2879
                        'accesssince' => [],
2880
                        'jointype' => filter::JOINTYPE_NONE,
2881
                        'count' => 5,
2882
                        'expectedusers' => [
2883
                            'a',
2884
                            'b',
2885
                            'c',
2886
                            'd',
2887
                            'e',
2888
                        ],
2889
                    ],
2890
                    'NONE: Filter on last login more than 1 year ago' => (object) [
2891
                        'accesssince' => ['-1 year'],
2892
                        'jointype' => filter::JOINTYPE_NONE,
2893
                        'count' => 4,
2894
                        'expectedusers' => [
2895
                            'a',
2896
                            'b',
2897
                            'c',
2898
                            'd',
2899
                        ],
2900
                    ],
2901
                    'NONE: Filter on last login more than 6 months ago' => (object) [
2902
                        'accesssince' => ['-6 months'],
2903
                        'jointype' => filter::JOINTYPE_NONE,
2904
                        'count' => 3,
2905
                        'expectedusers' => [
2906
                            'a',
2907
                            'b',
2908
                            'c',
2909
                        ],
2910
                    ],
2911
                    'NONE: Filter on last login more than 3 weeks ago' => (object) [
2912
                        'accesssince' => ['-3 weeks'],
2913
                        'jointype' => filter::JOINTYPE_NONE,
2914
                        'count' => 2,
2915
                        'expectedusers' => [
2916
                            'a',
2917
                            'b',
2918
                        ],
2919
                    ],
2920
                    'NONE: Filter on last login more than 5 days ago' => (object) [
2921
                        'accesssince' => ['-5 days'],
2922
                        'jointype' => filter::JOINTYPE_NONE,
2923
                        'count' => 1,
2924
                        'expectedusers' => [
2925
                            'a',
2926
                        ],
2927
                    ],
2928
                    'NONE: Filter on last login more than 2 days ago' => (object) [
2929
                        'accesssince' => ['-2 days'],
2930
                        'jointype' => filter::JOINTYPE_NONE,
2931
                        'count' => 0,
2932
                        'expectedusers' => [],
2933
                    ],
2934
                ],
2935
            ],
2936
        ];
2937
 
2938
        $finaltests = [];
2939
        foreach ($tests as $testname => $testdata) {
2940
            foreach ($testdata->expect as $expectname => $expectdata) {
2941
                $finaltests["{$testname} => {$expectname}"] = [
2942
                    'users' => $testdata->users,
2943
                    'accesssince' => $expectdata->accesssince,
2944
                    'jointype' => $expectdata->jointype,
2945
                    'count' => $expectdata->count,
2946
                    'expectedusers' => $expectdata->expectedusers,
2947
                ];
2948
            }
2949
        }
2950
 
2951
        return $finaltests;
2952
    }
2953
 
2954
    /**
2955
     * Ensure that the joins between filters in the filterset work as expected with the provided test cases.
2956
     *
2957
     * @param array $usersdata The list of users to create
2958
     * @param array $filterdata The data to filter by
2959
     * @param array $groupsavailable The names of groups that should be created in the course
2960
     * @param int $jointype The join type to used between each filter being applied
2961
     * @param int $count The expected count
2962
     * @param array $expectedusers
2963
     * @dataProvider filterset_joins_provider
2964
     */
2965
    public function test_filterset_joins(array $usersdata, array $filterdata, array $groupsavailable, int $jointype, int $count,
2966
            array $expectedusers): void {
2967
        global $DB;
2968
 
2969
        // Ensure sufficient capabilities to view all statuses.
2970
        $this->setAdminUser();
2971
 
2972
        // Remove the default role.
2973
        set_config('roleid', 0, 'enrol_manual');
2974
 
2975
        $course = $this->getDataGenerator()->create_course();
2976
        $coursecontext = context_course::instance($course->id);
2977
        $roles = $DB->get_records_menu('role', [], '', 'shortname, id');
2978
        $users = [];
2979
 
2980
        // Ensure all enrolment methods are enabled (and mapped where required for filtering later).
2981
        $enrolinstances = enrol_get_instances($course->id, false);
2982
        $enrolinstancesmap = [];
2983
        foreach ($enrolinstances as $instance) {
2984
            $plugin = enrol_get_plugin($instance->enrol);
2985
            $plugin->update_status($instance, ENROL_INSTANCE_ENABLED);
2986
 
2987
            $enrolinstancesmap[$instance->enrol] = (int) $instance->id;
2988
        }
2989
 
2990
        // Create the required course groups and mapping.
2991
        $nogroupsdata = (object) [
2992
            'id' => USERSWITHOUTGROUP,
2993
        ];
2994
 
2995
         $groupsdata = ['nogroups' => $nogroupsdata];
2996
        foreach ($groupsavailable as $groupname) {
2997
            $groupinfo = [
2998
                'courseid' => $course->id,
2999
                'name' => $groupname,
3000
            ];
3001
 
3002
            $groupsdata[$groupname] = $this->getDataGenerator()->create_group($groupinfo);
3003
        }
3004
 
3005
        // Create test users.
3006
        foreach ($usersdata as $username => $userdata) {
3007
            $usertimestamp = empty($userdata['lastlogin']) ? 0 : strtotime($userdata['lastlogin']);
3008
            unset($userdata['lastlogin']);
3009
 
3010
            // Prevent randomly generated field values that may cause false fails.
3011
            $userdata['firstnamephonetic'] = $userdata['firstnamephonetic'] ?? $userdata['firstname'];
3012
            $userdata['lastnamephonetic'] = $userdata['lastnamephonetic'] ?? $userdata['lastname'];
3013
            $userdata['middlename'] = $userdata['middlename'] ?? '';
3014
            $userdata['alternatename'] = $userdata['alternatename'] ?? $username;
3015
 
3016
            $user = $this->getDataGenerator()->create_user($userdata);
3017
 
3018
            foreach ($userdata['enrolments'] as $details) {
3019
                $this->getDataGenerator()->enrol_user($user->id, $course->id, $roles[$details['role']],
3020
                        $details['method'], 0, 0, $details['status']);
3021
            }
3022
 
3023
            foreach ($userdata['groups'] as $groupname) {
3024
                $userinfo = [
3025
                    'userid' => $user->id,
3026
                    'groupid' => (int) $groupsdata[$groupname]->id,
3027
                ];
3028
                $this->getDataGenerator()->create_group_member($userinfo);
3029
            }
3030
 
3031
            if ($usertimestamp > 0) {
3032
                $this->getDataGenerator()->create_user_course_lastaccess($user, $course, $usertimestamp);
3033
            }
3034
 
3035
            $users[$username] = $user;
3036
        }
3037
 
3038
        // Create a secondary course with users. We should not see these users.
3039
        $this->create_course_with_users(10, 10, 10, 10);
3040
 
3041
        // Create the basic filterset.
3042
        $filterset = new participants_filterset();
3043
        $filterset->set_join_type($jointype);
3044
        $filterset->add_filter(new integer_filter('courseid', null, [(int) $course->id]));
3045
 
3046
        // Apply the keywords filter if required.
3047
        if (array_key_exists('keywords', $filterdata)) {
3048
            $keywordfilter = new string_filter('keywords');
3049
            $filterset->add_filter($keywordfilter);
3050
 
3051
            foreach ($filterdata['keywords']['values'] as $keyword) {
3052
                $keywordfilter->add_filter_value($keyword);
3053
            }
3054
            $keywordfilter->set_join_type($filterdata['keywords']['jointype']);
3055
        }
3056
 
3057
        // Apply enrolment methods filter if required.
3058
        if (array_key_exists('enrolmethods', $filterdata)) {
3059
            $enrolmethodfilter = new integer_filter('enrolments');
3060
            $filterset->add_filter($enrolmethodfilter);
3061
 
3062
            foreach ($filterdata['enrolmethods']['values'] as $enrolmethod) {
3063
                $enrolmethodfilter->add_filter_value($enrolinstancesmap[$enrolmethod]);
3064
            }
3065
            $enrolmethodfilter->set_join_type($filterdata['enrolmethods']['jointype']);
3066
        }
3067
 
3068
        // Apply roles filter if required.
3069
        if (array_key_exists('courseroles', $filterdata)) {
3070
            $rolefilter = new integer_filter('roles');
3071
            $filterset->add_filter($rolefilter);
3072
 
3073
            foreach ($filterdata['courseroles']['values'] as $rolename) {
3074
                $rolefilter->add_filter_value((int) $roles[$rolename]);
3075
            }
3076
            $rolefilter->set_join_type($filterdata['courseroles']['jointype']);
3077
        }
3078
 
3079
        // Apply status filter if required.
3080
        if (array_key_exists('status', $filterdata)) {
3081
            $statusfilter = new integer_filter('status');
3082
            $filterset->add_filter($statusfilter);
3083
 
3084
            foreach ($filterdata['status']['values'] as $status) {
3085
                $statusfilter->add_filter_value($status);
3086
            }
3087
            $statusfilter->set_join_type($filterdata['status']['jointype']);
3088
        }
3089
 
3090
        // Apply groups filter if required.
3091
        if (array_key_exists('groups', $filterdata)) {
3092
            $groupsfilter = new integer_filter('groups');
3093
            $filterset->add_filter($groupsfilter);
3094
 
3095
            foreach ($filterdata['groups']['values'] as $filtergroupname) {
3096
                $groupsfilter->add_filter_value((int) $groupsdata[$filtergroupname]->id);
3097
            }
3098
            $groupsfilter->set_join_type($filterdata['groups']['jointype']);
3099
        }
3100
 
3101
        // Apply last access filter if required.
3102
        if (array_key_exists('accesssince', $filterdata)) {
3103
            $lastaccessfilter = new integer_filter('accesssince');
3104
            $filterset->add_filter($lastaccessfilter);
3105
 
3106
            foreach ($filterdata['accesssince']['values'] as $accessstring) {
3107
                $lastaccessfilter->add_filter_value(strtotime($accessstring));
3108
            }
3109
            $lastaccessfilter->set_join_type($filterdata['accesssince']['jointype']);
3110
        }
3111
 
3112
        // Run the search.
3113
        $search = new participants_search($course, $coursecontext, $filterset);
3114
        $rs = $search->get_participants();
3115
        $this->assertInstanceOf(moodle_recordset::class, $rs);
3116
        $records = $this->convert_recordset_to_array($rs);
3117
 
3118
        $this->assertCount($count, $records);
3119
        $this->assertEquals($count, $search->get_total_participants_count());
3120
 
3121
        foreach ($expectedusers as $expecteduser) {
3122
            $this->assertArrayHasKey($users[$expecteduser]->id, $records);
3123
        }
3124
    }
3125
 
3126
    /**
3127
     * Data provider for filterset join tests.
3128
     *
3129
     * @return array
3130
     */
3131
    public function filterset_joins_provider(): array {
3132
        $tests = [
3133
            // Users with different configurations.
3134
            'Users with different configurations' => (object) [
3135
                'groupsavailable' => [
3136
                    'groupa',
3137
                    'groupb',
3138
                    'groupc',
3139
                ],
3140
                'users' => [
3141
                    'adam.ant' => [
3142
                        'firstname' => 'Adam',
3143
                        'lastname' => 'Ant',
3144
                        'enrolments' => [
3145
                            [
3146
                                'role' => 'student',
3147
                                'method' => 'manual',
3148
                                'status' => ENROL_USER_ACTIVE,
3149
                            ],
3150
                        ],
3151
                        'groups' => ['groupa'],
3152
                        'lastlogin' => '-3 days',
3153
                    ],
3154
                    'barbara.bennett' => [
3155
                        'firstname' => 'Barbara',
3156
                        'lastname' => 'Bennett',
3157
                        'enrolments' => [
3158
                            [
3159
                                'role' => 'student',
3160
                                'method' => 'manual',
3161
                                'status' => ENROL_USER_ACTIVE,
3162
                            ],
3163
                            [
3164
                                'role' => 'teacher',
3165
                                'method' => 'manual',
3166
                                'status' => ENROL_USER_ACTIVE,
3167
                            ],
3168
                        ],
3169
                        'groups' => ['groupb'],
3170
                        'lastlogin' => '-2 weeks',
3171
                    ],
3172
                    'colin.carnforth' => [
3173
                        'firstname' => 'Colin',
3174
                        'lastname' => 'Carnforth',
3175
                        'enrolments' => [
3176
                            [
3177
                                'role' => 'editingteacher',
3178
                                'method' => 'self',
3179
                                'status' => ENROL_USER_SUSPENDED,
3180
                            ],
3181
                        ],
3182
                        'groups' => ['groupa', 'groupb'],
3183
                        'lastlogin' => '-5 months',
3184
                    ],
3185
                    'tony.rogers' => [
3186
                        'firstname' => 'Anthony',
3187
                        'lastname' => 'Rogers',
3188
                        'enrolments' => [
3189
                            [
3190
                                'role' => 'editingteacher',
3191
                                'method' => 'self',
3192
                                'status' => ENROL_USER_SUSPENDED,
3193
                            ],
3194
                        ],
3195
                        'groups' => [],
3196
                        'lastlogin' => '-10 months',
3197
                    ],
3198
                    'sarah.rester' => [
3199
                        'firstname' => 'Sarah',
3200
                        'lastname' => 'Rester',
3201
                        'email' => 'zazu@example.com',
3202
                        'enrolments' => [
3203
                            [
3204
                                'role' => 'teacher',
3205
                                'method' => 'manual',
3206
                                'status' => ENROL_USER_ACTIVE,
3207
                            ],
3208
                            [
3209
                                'role' => 'editingteacher',
3210
                                'method' => 'self',
3211
                                'status' => ENROL_USER_SUSPENDED,
3212
                            ],
3213
                        ],
3214
                        'groups' => [],
3215
                        'lastlogin' => '-11 months',
3216
                    ],
3217
                    'morgan.crikeyson' => [
3218
                        'firstname' => 'Morgan',
3219
                        'lastname' => 'Crikeyson',
3220
                        'enrolments' => [
3221
                            [
3222
                                'role' => 'teacher',
3223
                                'method' => 'manual',
3224
                                'status' => ENROL_USER_ACTIVE,
3225
                            ],
3226
                        ],
3227
                        'groups' => ['groupa'],
3228
                        'lastlogin' => '-1 week',
3229
                    ],
3230
                    'jonathan.bravo' => [
3231
                        'firstname' => 'Jonathan',
3232
                        'lastname' => 'Bravo',
3233
                        'enrolments' => [
3234
                            [
3235
                                'role' => 'student',
3236
                                'method' => 'manual',
3237
                                'status' => ENROL_USER_ACTIVE,
3238
                            ],
3239
                        ],
3240
                        'groups' => [],
3241
                        // Never logged in.
3242
                        'lastlogin' => '',
3243
                    ],
3244
                ],
3245
                'expect' => [
3246
                    // Tests for jointype: ANY.
3247
                    'ANY: No filters in filterset' => (object) [
3248
                        'filterdata' => [],
3249
                        'jointype' => filter::JOINTYPE_ANY,
3250
                        'count' => 7,
3251
                        'expectedusers' => [
3252
                            'adam.ant',
3253
                            'barbara.bennett',
3254
                            'colin.carnforth',
3255
                            'tony.rogers',
3256
                            'sarah.rester',
3257
                            'morgan.crikeyson',
3258
                            'jonathan.bravo',
3259
                        ],
3260
                    ],
3261
                    'ANY: Filterset containing a single filter type' => (object) [
3262
                        'filterdata' => [
3263
                            'enrolmethods' => [
3264
                                'values' => ['self'],
3265
                                'jointype' => filter::JOINTYPE_ANY,
3266
                            ],
3267
                        ],
3268
                        'jointype' => filter::JOINTYPE_ANY,
3269
                        'count' => 3,
3270
                        'expectedusers' => [
3271
                            'colin.carnforth',
3272
                            'tony.rogers',
3273
                            'sarah.rester',
3274
                        ],
3275
                    ],
3276
                    'ANY: Filterset matching all filter types on different users' => (object) [
3277
                        'filterdata' => [
3278
                            // Match Adam only.
3279
                            'keywords' => [
3280
                                'values' => ['adam'],
3281
                                'jointype' => filter::JOINTYPE_ALL,
3282
                            ],
3283
                            // Match Sarah only.
3284
                            'enrolmethods' => [
3285
                                'values' => ['manual', 'self'],
3286
                                'jointype' => filter::JOINTYPE_ALL,
3287
                            ],
3288
                            // Match Barbara only.
3289
                            'courseroles' => [
3290
                                'values' => ['student', 'teacher'],
3291
                                'jointype' => filter::JOINTYPE_ALL,
3292
                            ],
3293
                            // Match Sarah only.
3294
                            'status' => [
3295
                                'values' => [ENROL_USER_ACTIVE, ENROL_USER_SUSPENDED],
3296
                                'jointype' => filter::JOINTYPE_ALL,
3297
                            ],
3298
                            // Match Colin only.
3299
                            'groups' => [
3300
                                'values' => ['groupa', 'groupb'],
3301
                                'jointype' => filter::JOINTYPE_ALL,
3302
                            ],
3303
                            // Match Jonathan only.
3304
                            'accesssince' => [
3305
                                'values' => ['-1 year'],
3306
                                'jointype' => filter::JOINTYPE_ALL,
3307
                                ],
3308
                        ],
3309
                        'jointype' => filter::JOINTYPE_ANY,
3310
                        'count' => 5,
3311
                        // Morgan and Tony are not matched, to confirm filtering is not just returning all users.
3312
                        'expectedusers' => [
3313
                            'adam.ant',
3314
                            'barbara.bennett',
3315
                            'colin.carnforth',
3316
                            'sarah.rester',
3317
                            'jonathan.bravo',
3318
                        ],
3319
                    ],
3320
 
3321
                    // Tests for jointype: ALL.
3322
                    'ALL: No filters in filterset' => (object) [
3323
                        'filterdata' => [],
3324
                        'jointype' => filter::JOINTYPE_ALL,
3325
                        'count' => 7,
3326
                        'expectedusers' => [
3327
                            'adam.ant',
3328
                            'barbara.bennett',
3329
                            'colin.carnforth',
3330
                            'tony.rogers',
3331
                            'sarah.rester',
3332
                            'morgan.crikeyson',
3333
                            'jonathan.bravo',
3334
                        ],
3335
                    ],
3336
                    'ALL: Filterset containing a single filter type' => (object) [
3337
                        'filterdata' => [
3338
                            'enrolmethods' => [
3339
                                'values' => ['self'],
3340
                                'jointype' => filter::JOINTYPE_ANY,
3341
                            ],
3342
                        ],
3343
                        'jointype' => filter::JOINTYPE_ALL,
3344
                        'count' => 3,
3345
                        'expectedusers' => [
3346
                            'colin.carnforth',
3347
                            'tony.rogers',
3348
                            'sarah.rester',
3349
                        ],
3350
                    ],
3351
                    'ALL: Filterset combining all filter types' => (object) [
3352
                        'filterdata' => [
3353
                            // Exclude Adam, Tony, Morgan and Jonathan.
3354
                            'keywords' => [
3355
                                'values' => ['ar'],
3356
                                'jointype' => filter::JOINTYPE_ANY,
3357
                            ],
3358
                            // Exclude Colin and Tony.
3359
                            'enrolmethods' => [
3360
                                'values' => ['manual'],
3361
                                'jointype' => filter::JOINTYPE_ANY,
3362
                            ],
3363
                            // Exclude Adam, Barbara and Jonathan.
3364
                            'courseroles' => [
3365
                                'values' => ['student'],
3366
                                'jointype' => filter::JOINTYPE_NONE,
3367
                            ],
3368
                            // Exclude Colin and Tony.
3369
                            'status' => [
3370
                                'values' => [ENROL_USER_ACTIVE],
3371
                                'jointype' => filter::JOINTYPE_ALL,
3372
                            ],
3373
                            // Exclude Barbara.
3374
                            'groups' => [
3375
                                'values' => ['groupa', 'nogroups'],
3376
                                'jointype' => filter::JOINTYPE_ANY,
3377
                            ],
3378
                            // Exclude Adam, Colin and Barbara.
3379
                            'accesssince' => [
3380
                                'values' => ['-6 months'],
3381
                                'jointype' => filter::JOINTYPE_ALL,
3382
                                ],
3383
                        ],
3384
                        'jointype' => filter::JOINTYPE_ALL,
3385
                        'count' => 1,
3386
                        'expectedusers' => [
3387
                            'sarah.rester',
3388
                        ],
3389
                    ],
3390
 
3391
                    // Tests for jointype: NONE.
3392
                    'NONE: No filters in filterset' => (object) [
3393
                        'filterdata' => [],
3394
                        'jointype' => filter::JOINTYPE_NONE,
3395
                        'count' => 7,
3396
                        'expectedusers' => [
3397
                            'adam.ant',
3398
                            'barbara.bennett',
3399
                            'colin.carnforth',
3400
                            'tony.rogers',
3401
                            'sarah.rester',
3402
                            'morgan.crikeyson',
3403
                            'jonathan.bravo',
3404
                        ],
3405
                    ],
3406
                    'NONE: Filterset containing a single filter type' => (object) [
3407
                        'filterdata' => [
3408
                            'enrolmethods' => [
3409
                                'values' => ['self'],
3410
                                'jointype' => filter::JOINTYPE_ANY,
3411
                            ],
3412
                        ],
3413
                        'jointype' => filter::JOINTYPE_NONE,
3414
                        'count' => 4,
3415
                        'expectedusers' => [
3416
                            'adam.ant',
3417
                            'barbara.bennett',
3418
                            'morgan.crikeyson',
3419
                            'jonathan.bravo',
3420
                        ],
3421
                    ],
3422
                    'NONE: Filterset combining all filter types' => (object) [
3423
                        'filterdata' => [
3424
                            // Excludes Adam.
3425
                            'keywords' => [
3426
                                'values' => ['adam'],
3427
                                'jointype' => filter::JOINTYPE_ANY,
3428
                            ],
3429
                            // Excludes Colin, Tony and Sarah.
3430
                            'enrolmethods' => [
3431
                                'values' => ['self'],
3432
                                'jointype' => filter::JOINTYPE_ANY,
3433
                            ],
3434
                            // Excludes Jonathan.
3435
                            'courseroles' => [
3436
                                'values' => ['student'],
3437
                                'jointype' => filter::JOINTYPE_NONE,
3438
                            ],
3439
                            // Excludes Colin, Tony and Sarah.
3440
                            'status' => [
3441
                                'values' => [ENROL_USER_SUSPENDED],
3442
                                'jointype' => filter::JOINTYPE_ALL,
3443
                            ],
3444
                            // Excludes Adam, Colin, Tony, Sarah, Morgan and Jonathan.
3445
                            'groups' => [
3446
                                'values' => ['groupa', 'nogroups'],
3447
                                'jointype' => filter::JOINTYPE_ANY,
3448
                            ],
3449
                            // Excludes Tony and Sarah.
3450
                            'accesssince' => [
3451
                                'values' => ['-6 months'],
3452
                                'jointype' => filter::JOINTYPE_ALL,
3453
                            ],
3454
                        ],
3455
                        'jointype' => filter::JOINTYPE_NONE,
3456
                        'count' => 1,
3457
                        'expectedusers' => [
3458
                            'barbara.bennett',
3459
                        ],
3460
                    ],
3461
                    'NONE: Filterset combining several filter types and a double-negative on keyword' => (object) [
3462
                        'jointype' => filter::JOINTYPE_NONE,
3463
                        'filterdata' => [
3464
                            // Note: This is a jointype NONE on the parent jointype NONE.
3465
                            // The result therefore negated in this instance.
3466
                            // Include Adam and Anthony.
3467
                            'keywords' => [
3468
                                'values' => ['ant'],
3469
                                'jointype' => filter::JOINTYPE_NONE,
3470
                            ],
3471
                            // Excludes Tony.
3472
                            'status' => [
3473
                                'values' => [ENROL_USER_SUSPENDED],
3474
                                'jointype' => filter::JOINTYPE_ALL,
3475
                            ],
3476
                        ],
3477
                        'count' => 1,
3478
                        'expectedusers' => [
3479
                            'adam.ant',
3480
                        ],
3481
                    ],
3482
                ],
3483
            ],
3484
        ];
3485
 
3486
        $finaltests = [];
3487
        foreach ($tests as $testname => $testdata) {
3488
            foreach ($testdata->expect as $expectname => $expectdata) {
3489
                $finaltests["{$testname} => {$expectname}"] = [
3490
                    'users' => $testdata->users,
3491
                    'filterdata' => $expectdata->filterdata,
3492
                    'groupsavailable' => $testdata->groupsavailable,
3493
                    'jointype' => $expectdata->jointype,
3494
                    'count' => $expectdata->count,
3495
                    'expectedusers' => $expectdata->expectedusers,
3496
                ];
3497
            }
3498
        }
3499
 
3500
        return $finaltests;
3501
    }
3502
}