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