Proyectos de Subversion Moodle

Rev

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