Proyectos de Subversion Moodle

Rev

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