Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.

Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402] [Versions 401 and 402]

   1  <?php
   2  // This file is part of Moodle - http://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   * Full functional accesslib test.
  19   *
  20   * @package    core
  21   * @category   phpunit
  22   * @copyright  2011 Petr Skoda {@link http://skodak.org}
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  defined('MOODLE_INTERNAL') || die();
  27  
  28  /**
  29   * Functional test for accesslib.php
  30   *
  31   * Note: execution may take many minutes especially on slower servers.
  32   */
  33  class accesslib_test extends advanced_testcase {
  34      /**
  35       * Verify comparison of context instances in phpunit asserts.
  36       */
  37      public function test_context_comparisons() {
  38          $frontpagecontext1 = context_course::instance(SITEID);
  39          context_helper::reset_caches();
  40          $frontpagecontext2 = context_course::instance(SITEID);
  41          $this->assertEquals($frontpagecontext1, $frontpagecontext2);
  42  
  43          $user1 = context_user::instance(1);
  44          $user2 = context_user::instance(2);
  45          $this->assertNotEquals($user1, $user2);
  46      }
  47  
  48      /**
  49       * Test resetting works.
  50       *
  51       * @covers ::accesslib_clear_all_caches_for_unit_testing
  52       */
  53      public function test_accesslib_clear_all_caches() {
  54          global $ACCESSLIB_PRIVATE;
  55  
  56          $this->resetAfterTest();
  57  
  58          $this->setAdminUser();
  59          load_all_capabilities();
  60  
  61          $this->assertNotEmpty($ACCESSLIB_PRIVATE->accessdatabyuser);
  62          accesslib_clear_all_caches_for_unit_testing();
  63          $this->assertEmpty($ACCESSLIB_PRIVATE->dirtycontexts);
  64          $this->assertEmpty($ACCESSLIB_PRIVATE->accessdatabyuser);
  65      }
  66  
  67      /**
  68       * Check modifying capability record is not exposed to other code.
  69       */
  70      public function test_capabilities_mutation() {
  71          $oldcap = get_capability_info('moodle/site:config');
  72          $cap = get_capability_info('moodle/site:config');
  73          unset($cap->name);
  74          $newcap = get_capability_info('moodle/site:config');
  75  
  76          $this->assertFalse(isset($cap->name));
  77          $this->assertTrue(isset($newcap->name));
  78          $this->assertTrue(isset($oldcap->name));
  79      }
  80  
  81      /**
  82       * Test getting of role access
  83       *
  84       * @covers ::get_role_access
  85       */
  86      public function test_get_role_access() {
  87          global $DB;
  88  
  89          $roles = $DB->get_records('role');
  90          foreach ($roles as $role) {
  91              $access = get_role_access($role->id);
  92  
  93              $this->assertTrue(is_array($access));
  94              $this->assertTrue(is_array($access['ra']));
  95              $this->assertFalse(isset($access['rdef']));
  96              $this->assertFalse(isset($access['rdef_count']));
  97              $this->assertFalse(isset($access['loaded']));
  98              $this->assertTrue(isset($access['time']));
  99              $this->assertTrue(is_array($access['rsw']));
 100          }
 101  
 102          // Note: the data is validated in the functional permission evaluation test at the end of this testcase.
 103      }
 104  
 105      /**
 106       * Test getting of guest role.
 107       *
 108       * @covers ::get_guest_role
 109       */
 110      public function test_get_guest_role() {
 111          global $CFG;
 112  
 113          $guest = get_guest_role();
 114          $this->assertEquals('guest', $guest->archetype);
 115          $this->assertEquals('guest', $guest->shortname);
 116  
 117          $this->assertEquals($CFG->guestroleid, $guest->id);
 118      }
 119  
 120      /**
 121       * Test if user is admin.
 122       *
 123       * @covers ::is_siteadmin
 124       */
 125      public function test_is_siteadmin() {
 126          global $DB, $CFG;
 127  
 128          $this->resetAfterTest();
 129  
 130          $users = $DB->get_records('user');
 131  
 132          foreach ($users as $user) {
 133              $this->setUser(0);
 134              if ($user->username === 'admin') {
 135                  $this->assertTrue(is_siteadmin($user));
 136                  $this->assertTrue(is_siteadmin($user->id));
 137                  $this->setUser($user);
 138                  $this->assertTrue(is_siteadmin());
 139                  $this->assertTrue(is_siteadmin(null));
 140              } else {
 141                  $this->assertFalse(is_siteadmin($user));
 142                  $this->assertFalse(is_siteadmin($user->id));
 143                  $this->setUser($user);
 144                  $this->assertFalse(is_siteadmin());
 145                  $this->assertFalse(is_siteadmin(null));
 146              }
 147          }
 148  
 149          // Change the site admin list and check that it still works with
 150          // multiple admins. We do this with userids only (not real user
 151          // accounts) because it makes the test simpler.
 152          $before = $CFG->siteadmins;
 153          set_config('siteadmins', '666,667,668');
 154          $this->assertTrue(is_siteadmin(666));
 155          $this->assertTrue(is_siteadmin(667));
 156          $this->assertTrue(is_siteadmin(668));
 157          $this->assertFalse(is_siteadmin(669));
 158          set_config('siteadmins', '13');
 159          $this->assertTrue(is_siteadmin(13));
 160          $this->assertFalse(is_siteadmin(666));
 161          set_config('siteadmins', $before);
 162      }
 163  
 164      /**
 165       * Test if user is enrolled in a course
 166       *
 167       * @covers ::is_enrolled
 168       */
 169      public function test_is_enrolled() {
 170          global $DB;
 171  
 172          $this->resetAfterTest();
 173  
 174          // Generate data.
 175          $user = $this->getDataGenerator()->create_user();
 176          $course = $this->getDataGenerator()->create_course();
 177          $coursecontext = context_course::instance($course->id);
 178          $role = $DB->get_record('role', array('shortname'=>'student'));
 179  
 180          // There should be a manual enrolment as part of the default install.
 181          $plugin = enrol_get_plugin('manual');
 182          $instance = $DB->get_record('enrol', array(
 183              'courseid' => $course->id,
 184              'enrol' => 'manual',
 185          ));
 186          $this->assertNotSame(false, $instance);
 187  
 188          // Enrol the user in the course.
 189          $plugin->enrol_user($instance, $user->id, $role->id);
 190  
 191          // We'll test with the mod/assign:submit capability.
 192          $capability= 'mod/assign:submit';
 193          $this->assertTrue($DB->record_exists('capabilities', array('name' => $capability)));
 194  
 195          // Switch to our user.
 196          $this->setUser($user);
 197  
 198          // Ensure that the user has the capability first.
 199          $this->assertTrue(has_capability($capability, $coursecontext, $user->id));
 200  
 201          // We first test whether the user is enrolled on the course as this
 202          // seeds the cache, then we test for the capability.
 203          $this->assertTrue(is_enrolled($coursecontext, $user, '', true));
 204          $this->assertTrue(is_enrolled($coursecontext, $user, $capability));
 205  
 206          // Prevent the capability for this user role.
 207          assign_capability($capability, CAP_PROHIBIT, $role->id, $coursecontext);
 208          $this->assertFalse(has_capability($capability, $coursecontext, $user->id));
 209  
 210          // Again, we seed the cache first by checking initial enrolment,
 211          // and then we test the actual capability.
 212          $this->assertTrue(is_enrolled($coursecontext, $user, '', true));
 213          $this->assertFalse(is_enrolled($coursecontext, $user, $capability));
 214      }
 215  
 216      /**
 217       * Test logged in test.
 218       *
 219       * @covers ::isloggedin
 220       */
 221      public function test_isloggedin() {
 222          global $USER;
 223  
 224          $this->resetAfterTest();
 225  
 226          $USER->id = 0;
 227          $this->assertFalse(isloggedin());
 228          $USER->id = 1;
 229          $this->assertTrue(isloggedin());
 230      }
 231  
 232      /**
 233       * Test guest user test.
 234       *
 235       * @covers ::isguestuser
 236       */
 237      public function test_isguestuser() {
 238          global $DB;
 239  
 240          $this->resetAfterTest();
 241  
 242          $guest = $DB->get_record('user', array('username'=>'guest'));
 243          $this->setUser(0);
 244          $this->assertFalse(isguestuser());
 245          $this->setAdminUser();
 246          $this->assertFalse(isguestuser());
 247          $this->assertTrue(isguestuser($guest));
 248          $this->assertTrue(isguestuser($guest->id));
 249          $this->setUser($guest);
 250          $this->assertTrue(isguestuser());
 251  
 252          $users = $DB->get_records('user');
 253          foreach ($users as $user) {
 254              if ($user->username === 'guest') {
 255                  continue;
 256              }
 257              $this->assertFalse(isguestuser($user));
 258          }
 259      }
 260  
 261      /**
 262       * Test capability riskiness.
 263       *
 264       * @covers ::is_safe_capability
 265       */
 266      public function test_is_safe_capability() {
 267          global $DB;
 268          // Note: there is not much to test, just make sure no notices are throw for the most dangerous cap.
 269          $capability = $DB->get_record('capabilities', array('name'=>'moodle/site:config'), '*', MUST_EXIST);
 270          $this->assertFalse(is_safe_capability($capability));
 271      }
 272  
 273      /**
 274       * Test context fetching.
 275       *
 276       * @covers ::get_context_info_array
 277       */
 278      public function test_get_context_info_array() {
 279          $this->resetAfterTest();
 280  
 281          $syscontext = context_system::instance();
 282          $user = $this->getDataGenerator()->create_user();
 283          $usercontext = context_user::instance($user->id);
 284          $course = $this->getDataGenerator()->create_course();
 285          $catcontext = context_coursecat::instance($course->category);
 286          $coursecontext = context_course::instance($course->id);
 287          $page = $this->getDataGenerator()->create_module('page', array('course'=>$course->id));
 288          $modcontext = context_module::instance($page->cmid);
 289          $cm = get_coursemodule_from_instance('page', $page->id);
 290          $block1 = $this->getDataGenerator()->create_block('online_users', array('parentcontextid'=>$coursecontext->id));
 291          $block1context = context_block::instance($block1->id);
 292          $block2 = $this->getDataGenerator()->create_block('online_users', array('parentcontextid'=>$modcontext->id));
 293          $block2context = context_block::instance($block2->id);
 294  
 295          $result = get_context_info_array($syscontext->id);
 296          $this->assertCount(3, $result);
 297          $this->assertEquals($syscontext, $result[0]);
 298          $this->assertNull($result[1]);
 299          $this->assertNull($result[2]);
 300  
 301          $result = get_context_info_array($usercontext->id);
 302          $this->assertCount(3, $result);
 303          $this->assertEquals($usercontext, $result[0]);
 304          $this->assertNull($result[1]);
 305          $this->assertNull($result[2]);
 306  
 307          $result = get_context_info_array($catcontext->id);
 308          $this->assertCount(3, $result);
 309          $this->assertEquals($catcontext, $result[0]);
 310          $this->assertNull($result[1]);
 311          $this->assertNull($result[2]);
 312  
 313          $result = get_context_info_array($coursecontext->id);
 314          $this->assertCount(3, $result);
 315          $this->assertEquals($coursecontext, $result[0]);
 316          $this->assertEquals($course->id, $result[1]->id);
 317          $this->assertSame($course->shortname, $result[1]->shortname);
 318          $this->assertNull($result[2]);
 319  
 320          $result = get_context_info_array($block1context->id);
 321          $this->assertCount(3, $result);
 322          $this->assertEquals($block1context, $result[0]);
 323          $this->assertEquals($course->id, $result[1]->id);
 324          $this->assertEquals($course->shortname, $result[1]->shortname);
 325          $this->assertNull($result[2]);
 326  
 327          $result = get_context_info_array($modcontext->id);
 328          $this->assertCount(3, $result);
 329          $this->assertEquals($modcontext, $result[0]);
 330          $this->assertEquals($course->id, $result[1]->id);
 331          $this->assertSame($course->shortname, $result[1]->shortname);
 332          $this->assertEquals($cm->id, $result[2]->id);
 333  
 334          $result = get_context_info_array($block2context->id);
 335          $this->assertCount(3, $result);
 336          $this->assertEquals($block2context, $result[0]);
 337          $this->assertEquals($course->id, $result[1]->id);
 338          $this->assertSame($course->shortname, $result[1]->shortname);
 339          $this->assertEquals($cm->id, $result[2]->id);
 340      }
 341  
 342      /**
 343       * Test looking for course contacts.
 344       *
 345       * @covers ::has_coursecontact_role
 346       */
 347      public function test_has_coursecontact_role() {
 348          global $DB, $CFG;
 349  
 350          $this->resetAfterTest();
 351  
 352          $users = $DB->get_records('user');
 353  
 354          // Nobody is expected to have any course level roles.
 355          $this->assertNotEmpty($CFG->coursecontact);
 356          foreach ($users as $user) {
 357              $this->assertFalse(has_coursecontact_role($user->id));
 358          }
 359  
 360          $user = $this->getDataGenerator()->create_user();
 361          $course = $this->getDataGenerator()->create_course();
 362          $contactroles = preg_split('/,/', $CFG->coursecontact);
 363          $roleid = reset($contactroles);
 364          role_assign($roleid, $user->id, context_course::instance($course->id));
 365          $this->assertTrue(has_coursecontact_role($user->id));
 366      }
 367  
 368      /**
 369       * Test creation of roles.
 370       *
 371       * @covers ::create_role
 372       */
 373      public function test_create_role() {
 374          global $DB;
 375  
 376          $this->resetAfterTest();
 377  
 378          // Create role and get event.
 379          $sink = $this->redirectEvents();
 380          $id = create_role('New student role', 'student2', 'New student description', 'student');
 381          $events = $sink->get_events();
 382          $sink->close();
 383          $event = array_pop($events);
 384          $role = $DB->get_record('role', ['id' => $id]);
 385  
 386          $this->assertNotEmpty($role);
 387          $this->assertSame('New student role', $role->name);
 388          $this->assertSame('student2', $role->shortname);
 389          $this->assertSame('New student description', $role->description);
 390          $this->assertSame('student', $role->archetype);
 391  
 392          // Test triggered event.
 393          $this->assertInstanceOf('\core\event\role_created', $event);
 394          $this->assertSame('role', $event->target);
 395          $this->assertSame('role', $event->objecttable);
 396          $this->assertSame((int)$role->id, $event->objectid);
 397          $this->assertEquals(context_system::instance(), $event->get_context());
 398          $this->assertSame($role->shortname, $event->other['shortname']);
 399          $this->assertSame($role->archetype, $event->other['archetype']);
 400      }
 401  
 402      /**
 403       * Test adding of capabilities to roles.
 404       *
 405       * @covers ::assign_capability
 406       */
 407      public function test_assign_capability() {
 408          global $DB, $USER;
 409  
 410          $this->resetAfterTest();
 411  
 412          $user = $this->getDataGenerator()->create_user();
 413          $syscontext = context_system::instance();
 414          $frontcontext = context_course::instance(SITEID);
 415          $student = $DB->get_record('role', array('shortname'=>'student'), '*', MUST_EXIST);
 416          $this->assertTrue($DB->record_exists('capabilities', array('name'=>'moodle/backup:backupcourse'))); // Any capability assigned to student by default.
 417          $this->assertFalse($DB->record_exists('role_capabilities', array('contextid'=>$syscontext->id, 'roleid'=>$student->id, 'capability'=>'moodle/backup:backupcourse')));
 418          $this->assertFalse($DB->record_exists('role_capabilities', array('contextid'=>$frontcontext->id, 'roleid'=>$student->id, 'capability'=>'moodle/backup:backupcourse')));
 419  
 420          $this->setUser($user);
 421          $result = assign_capability('moodle/backup:backupcourse', CAP_ALLOW, $student->id, $frontcontext->id);
 422          $this->assertTrue($result);
 423          $permission = $DB->get_record('role_capabilities', array('contextid'=>$frontcontext->id, 'roleid'=>$student->id, 'capability'=>'moodle/backup:backupcourse'));
 424          $this->assertNotEmpty($permission);
 425          $this->assertEquals(CAP_ALLOW, $permission->permission);
 426          $this->assertEquals($user->id, $permission->modifierid);
 427  
 428          $this->setUser(0);
 429          $result = assign_capability('moodle/backup:backupcourse', CAP_PROHIBIT, $student->id, $frontcontext->id, false);
 430          $this->assertTrue($result);
 431          $permission = $DB->get_record('role_capabilities', array('contextid'=>$frontcontext->id, 'roleid'=>$student->id, 'capability'=>'moodle/backup:backupcourse'));
 432          $this->assertNotEmpty($permission);
 433          $this->assertEquals(CAP_ALLOW, $permission->permission);
 434          $this->assertEquals($user->id, $permission->modifierid);
 435  
 436          $result = assign_capability('moodle/backup:backupcourse', CAP_PROHIBIT, $student->id, $frontcontext->id, true);
 437          $this->assertTrue($result);
 438          $permission = $DB->get_record('role_capabilities', array('contextid'=>$frontcontext->id, 'roleid'=>$student->id, 'capability'=>'moodle/backup:backupcourse'));
 439          $this->assertNotEmpty($permission);
 440          $this->assertEquals(CAP_PROHIBIT, $permission->permission);
 441          $this->assertEquals(0, $permission->modifierid);
 442  
 443          $result = assign_capability('moodle/backup:backupcourse', CAP_INHERIT, $student->id, $frontcontext->id);
 444          $this->assertTrue($result);
 445          $permission = $DB->get_record('role_capabilities', array('contextid'=>$frontcontext->id, 'roleid'=>$student->id, 'capability'=>'moodle/backup:backupcourse'));
 446          $this->assertEmpty($permission);
 447  
 448          // Test event triggered.
 449          $sink = $this->redirectEvents();
 450          $capability = 'moodle/backup:backupcourse';
 451          assign_capability($capability, CAP_ALLOW, $student->id, $syscontext);
 452          $events = $sink->get_events();
 453          $sink->close();
 454          $this->assertCount(1, $events);
 455          $event = $events[0];
 456          $this->assertInstanceOf('\core\event\capability_assigned', $event);
 457          $this->assertSame('role_capabilities', $event->objecttable);
 458          $this->assertEquals($student->id, $event->objectid);
 459          $this->assertEquals($syscontext->id, $event->contextid);
 460          $other = ['capability' => $capability, 'oldpermission' => CAP_INHERIT, 'permission' => CAP_ALLOW];
 461          $this->assertEquals($other, $event->other);
 462          $description = "The user id '$USER->id' assigned the '$capability' capability for " .
 463              "role '$student->id' with 'Allow' permission";
 464          $this->assertEquals($description, $event->get_description());
 465  
 466          // Test if the event has different description when updating the capability permission.
 467          $sink = $this->redirectEvents();
 468          assign_capability($capability, CAP_PROHIBIT, $student->id, $syscontext, true);
 469          $events = $sink->get_events();
 470          $sink->close();
 471          $event = $events[0];
 472          $description = "The user id '$USER->id' changed the '$capability' capability permission for " .
 473              "role '$student->id' from 'Allow' to 'Prohibit'";
 474          $this->assertEquals($description, $event->get_description());
 475      }
 476  
 477      /**
 478       * Test removing of capabilities from roles.
 479       *
 480       * @covers ::unassign_capability
 481       */
 482      public function test_unassign_capability() {
 483          global $DB, $USER;
 484  
 485          $this->resetAfterTest();
 486  
 487          $syscontext = context_system::instance();
 488          $frontcontext = context_course::instance(SITEID);
 489          $manager = $DB->get_record('role', array('shortname'=>'manager'), '*', MUST_EXIST);
 490          $this->assertTrue($DB->record_exists('capabilities', array('name'=>'moodle/backup:backupcourse'))); // Any capability assigned to manager by default.
 491          assign_capability('moodle/backup:backupcourse', CAP_ALLOW, $manager->id, $frontcontext->id);
 492  
 493          $this->assertTrue($DB->record_exists('role_capabilities', array('contextid'=>$syscontext->id, 'roleid'=>$manager->id, 'capability'=>'moodle/backup:backupcourse')));
 494          $this->assertTrue($DB->record_exists('role_capabilities', array('contextid'=>$frontcontext->id, 'roleid'=>$manager->id, 'capability'=>'moodle/backup:backupcourse')));
 495  
 496          $result = unassign_capability('moodle/backup:backupcourse', $manager->id, $syscontext->id);
 497          $this->assertTrue($result);
 498          $this->assertFalse($DB->record_exists('role_capabilities', array('contextid'=>$syscontext->id, 'roleid'=>$manager->id, 'capability'=>'moodle/backup:backupcourse')));
 499          $this->assertTrue($DB->record_exists('role_capabilities', array('contextid'=>$frontcontext->id, 'roleid'=>$manager->id, 'capability'=>'moodle/backup:backupcourse')));
 500          unassign_capability('moodle/backup:backupcourse', $manager->id, $frontcontext);
 501          $this->assertFalse($DB->record_exists('role_capabilities', array('contextid'=>$frontcontext->id, 'roleid'=>$manager->id, 'capability'=>'moodle/backup:backupcourse')));
 502  
 503          assign_capability('moodle/backup:backupcourse', CAP_ALLOW, $manager->id, $syscontext->id);
 504          assign_capability('moodle/backup:backupcourse', CAP_ALLOW, $manager->id, $frontcontext->id);
 505          $this->assertTrue($DB->record_exists('role_capabilities', array('contextid'=>$frontcontext->id, 'roleid'=>$manager->id, 'capability'=>'moodle/backup:backupcourse')));
 506  
 507          $result = unassign_capability('moodle/backup:backupcourse', $manager->id);
 508          $this->assertTrue($result);
 509          $this->assertFalse($DB->record_exists('role_capabilities', array('contextid'=>$syscontext->id, 'roleid'=>$manager->id, 'capability'=>'moodle/backup:backupcourse')));
 510          $this->assertFalse($DB->record_exists('role_capabilities', array('contextid'=>$frontcontext->id, 'roleid'=>$manager->id, 'capability'=>'moodle/backup:backupcourse')));
 511  
 512          // Test event triggered.
 513          $sink = $this->redirectEvents();
 514          $capability = 'moodle/backup:backupcourse';
 515          unassign_capability($capability, CAP_ALLOW, $manager->id);
 516          $events = $sink->get_events();
 517          $sink->close();
 518          $this->assertCount(1, $events);
 519          $event = $events[0];
 520          $this->assertInstanceOf('\core\event\capability_unassigned', $event);
 521          $this->assertSame('role_capabilities', $event->objecttable);
 522          $this->assertEquals($manager->id, $event->objectid);
 523          $this->assertEquals($syscontext->id, $event->contextid);
 524          $this->assertEquals($capability, $event->other['capability']);
 525          $description = "The user id id '$USER->id' has unassigned the '$capability' capability for role '$manager->id'";
 526          $this->assertEquals($description, $event->get_description());
 527      }
 528  
 529      /**
 530       * Test role assigning.
 531       *
 532       * @covers ::role_assign
 533       */
 534      public function test_role_assign() {
 535          global $DB, $USER;
 536  
 537          $this->resetAfterTest();
 538  
 539          $user = $this->getDataGenerator()->create_user();
 540          $course = $this->getDataGenerator()->create_course();
 541          $role = $DB->get_record('role', array('shortname'=>'student'));
 542  
 543          $this->setUser(0);
 544          $context = context_system::instance();
 545          $this->assertFalse($DB->record_exists('role_assignments', array('userid'=>$user->id, 'roleid'=>$role->id, 'contextid'=>$context->id)));
 546          role_assign($role->id, $user->id, $context->id);
 547          $ras = $DB->get_record('role_assignments', array('userid'=>$user->id, 'roleid'=>$role->id, 'contextid'=>$context->id));
 548          $this->assertNotEmpty($ras);
 549          $this->assertSame('', $ras->component);
 550          $this->assertSame('0', $ras->itemid);
 551          $this->assertEquals($USER->id, $ras->modifierid);
 552  
 553          $this->setAdminUser();
 554          $context = context_course::instance($course->id);
 555          $this->assertFalse($DB->record_exists('role_assignments', array('userid'=>$user->id, 'roleid'=>$role->id, 'contextid'=>$context->id)));
 556          role_assign($role->id, $user->id, $context->id, 'enrol_self', 1, 666);
 557          $ras = $DB->get_record('role_assignments', array('userid'=>$user->id, 'roleid'=>$role->id, 'contextid'=>$context->id));
 558          $this->assertNotEmpty($ras);
 559          $this->assertSame('enrol_self', $ras->component);
 560          $this->assertSame('1', $ras->itemid);
 561          $this->assertEquals($USER->id, $ras->modifierid);
 562          $this->assertEquals(666, $ras->timemodified);
 563  
 564          // Test event triggered.
 565  
 566          $user2 = $this->getDataGenerator()->create_user();
 567          $sink = $this->redirectEvents();
 568          $raid = role_assign($role->id, $user2->id, $context->id);
 569          $events = $sink->get_events();
 570          $sink->close();
 571          $this->assertCount(1, $events);
 572          $event = $events[0];
 573          $this->assertInstanceOf('\core\event\role_assigned', $event);
 574          $this->assertSame('role', $event->target);
 575          $this->assertSame('role', $event->objecttable);
 576          $this->assertEquals($role->id, $event->objectid);
 577          $this->assertEquals($context->id, $event->contextid);
 578          $this->assertEquals($user2->id, $event->relateduserid);
 579          $this->assertCount(3, $event->other);
 580          $this->assertEquals($raid, $event->other['id']);
 581          $this->assertSame('', $event->other['component']);
 582          $this->assertEquals(0, $event->other['itemid']);
 583          $this->assertInstanceOf('moodle_url', $event->get_url());
 584      }
 585  
 586      /**
 587       * Test role unassigning.
 588       *
 589       * @covers ::role_unassign
 590       */
 591      public function test_role_unassign() {
 592          global $DB, $USER;
 593  
 594          $this->resetAfterTest();
 595  
 596          $user = $this->getDataGenerator()->create_user();
 597          $course = $this->getDataGenerator()->create_course();
 598          $role = $DB->get_record('role', array('shortname'=>'student'));
 599  
 600          $context = context_course::instance($course->id);
 601          role_assign($role->id, $user->id, $context->id);
 602          $this->assertTrue($DB->record_exists('role_assignments', array('userid'=>$user->id, 'roleid'=>$role->id, 'contextid'=>$context->id)));
 603          role_unassign($role->id, $user->id, $context->id);
 604          $this->assertFalse($DB->record_exists('role_assignments', array('userid'=>$user->id, 'roleid'=>$role->id, 'contextid'=>$context->id)));
 605  
 606          role_assign($role->id, $user->id, $context->id, 'enrol_self', 1);
 607          $this->assertTrue($DB->record_exists('role_assignments', array('userid'=>$user->id, 'roleid'=>$role->id, 'contextid'=>$context->id)));
 608          role_unassign($role->id, $user->id, $context->id, 'enrol_self', 1);
 609          $this->assertFalse($DB->record_exists('role_assignments', array('userid'=>$user->id, 'roleid'=>$role->id, 'contextid'=>$context->id)));
 610  
 611          // Test event triggered.
 612  
 613          role_assign($role->id, $user->id, $context->id);
 614          $sink = $this->redirectEvents();
 615          role_unassign($role->id, $user->id, $context->id);
 616          $events = $sink->get_events();
 617          $sink->close();
 618          $this->assertCount(1, $events);
 619          $event = $events[0];
 620          $this->assertInstanceOf('\core\event\role_unassigned', $event);
 621          $this->assertSame('role', $event->target);
 622          $this->assertSame('role', $event->objecttable);
 623          $this->assertEquals($role->id, $event->objectid);
 624          $this->assertEquals($context->id, $event->contextid);
 625          $this->assertEquals($user->id, $event->relateduserid);
 626          $this->assertCount(3, $event->other);
 627          $this->assertSame('', $event->other['component']);
 628          $this->assertEquals(0, $event->other['itemid']);
 629          $this->assertInstanceOf('moodle_url', $event->get_url());
 630      }
 631  
 632      /**
 633       * Test role unassigning.
 634       *
 635       * @covers ::role_unassign_all
 636       */
 637      public function test_role_unassign_all() {
 638          global $DB;
 639  
 640          $this->resetAfterTest();
 641  
 642          $user = $this->getDataGenerator()->create_user();
 643          $course = $this->getDataGenerator()->create_course();
 644          $role = $DB->get_record('role', array('shortname'=>'student'));
 645          $role2 = $DB->get_record('role', array('shortname'=>'teacher'));
 646          $syscontext = context_system::instance();
 647          $coursecontext = context_course::instance($course->id);
 648          $page = $this->getDataGenerator()->create_module('page', array('course'=>$course->id));
 649          $modcontext = context_module::instance($page->cmid);
 650  
 651          role_assign($role->id, $user->id, $syscontext->id);
 652          role_assign($role->id, $user->id, $coursecontext->id, 'enrol_self', 1);
 653          $this->assertEquals(2, $DB->count_records('role_assignments', array('userid'=>$user->id)));
 654          role_unassign_all(array('userid'=>$user->id, 'roleid'=>$role->id));
 655          $this->assertEquals(0, $DB->count_records('role_assignments', array('userid'=>$user->id)));
 656  
 657          role_assign($role->id, $user->id, $syscontext->id);
 658          role_assign($role->id, $user->id, $coursecontext->id, 'enrol_self', 1);
 659          role_assign($role->id, $user->id, $modcontext->id);
 660          $this->assertEquals(3, $DB->count_records('role_assignments', array('userid'=>$user->id)));
 661          role_unassign_all(array('userid'=>$user->id, 'contextid'=>$coursecontext->id), false);
 662          $this->assertEquals(2, $DB->count_records('role_assignments', array('userid'=>$user->id)));
 663          role_unassign_all(array('userid'=>$user->id, 'contextid'=>$coursecontext->id), true);
 664          $this->assertEquals(1, $DB->count_records('role_assignments', array('userid'=>$user->id)));
 665          role_unassign_all(array('userid'=>$user->id));
 666          $this->assertEquals(0, $DB->count_records('role_assignments', array('userid'=>$user->id)));
 667  
 668          role_assign($role->id, $user->id, $syscontext->id);
 669          role_assign($role->id, $user->id, $coursecontext->id, 'enrol_self', 1);
 670          role_assign($role->id, $user->id, $coursecontext->id);
 671          role_assign($role->id, $user->id, $modcontext->id);
 672          $this->assertEquals(4, $DB->count_records('role_assignments', array('userid'=>$user->id)));
 673          role_unassign_all(array('userid'=>$user->id, 'contextid'=>$coursecontext->id, 'component'=>'enrol_self'), true, true);
 674          $this->assertEquals(1, $DB->count_records('role_assignments', array('userid'=>$user->id)));
 675  
 676          // Test events triggered.
 677  
 678          role_assign($role2->id, $user->id, $coursecontext->id);
 679          role_assign($role2->id, $user->id, $modcontext->id);
 680          $sink = $this->redirectEvents();
 681          role_unassign_all(array('userid'=>$user->id, 'roleid'=>$role2->id));
 682          $events = $sink->get_events();
 683          $sink->close();
 684          $this->assertCount(2, $events);
 685          $this->assertInstanceOf('\core\event\role_unassigned', $events[0]);
 686          $this->assertInstanceOf('\core\event\role_unassigned', $events[1]);
 687      }
 688  
 689      /**
 690       * Test role queries.
 691       *
 692       * @covers ::get_roles_with_capability
 693       */
 694      public function test_get_roles_with_capability() {
 695          global $DB;
 696  
 697          $this->resetAfterTest();
 698  
 699          $syscontext = context_system::instance();
 700          $frontcontext = context_course::instance(SITEID);
 701          $manager = $DB->get_record('role', array('shortname'=>'manager'), '*', MUST_EXIST);
 702          $teacher = $DB->get_record('role', array('shortname'=>'teacher'), '*', MUST_EXIST);
 703  
 704          $this->assertTrue($DB->record_exists('capabilities', array('name'=>'moodle/backup:backupcourse'))); // Any capability is ok.
 705          $DB->delete_records('role_capabilities', array('capability'=>'moodle/backup:backupcourse'));
 706  
 707          $roles = get_roles_with_capability('moodle/backup:backupcourse');
 708          $this->assertEquals(array(), $roles);
 709  
 710          assign_capability('moodle/backup:backupcourse', CAP_ALLOW, $manager->id, $syscontext->id);
 711          assign_capability('moodle/backup:backupcourse', CAP_PROHIBIT, $manager->id, $frontcontext->id);
 712          assign_capability('moodle/backup:backupcourse', CAP_PREVENT, $teacher->id, $frontcontext->id);
 713  
 714          $roles = get_roles_with_capability('moodle/backup:backupcourse');
 715          $this->assertEqualsCanonicalizing(array($teacher->id, $manager->id), array_keys($roles), true);
 716  
 717          $roles = get_roles_with_capability('moodle/backup:backupcourse', CAP_ALLOW);
 718          $this->assertEqualsCanonicalizing(array($manager->id), array_keys($roles), true);
 719  
 720          $roles = get_roles_with_capability('moodle/backup:backupcourse', null, $syscontext);
 721          $this->assertEqualsCanonicalizing(array($manager->id), array_keys($roles), true);
 722      }
 723  
 724      /**
 725       * Test deleting of roles.
 726       *
 727       * @covers ::delete_role
 728       */
 729      public function test_delete_role() {
 730          global $DB;
 731  
 732          $this->resetAfterTest();
 733  
 734          $role = $DB->get_record('role', array('shortname'=>'manager'), '*', MUST_EXIST);
 735          $user = $this->getDataGenerator()->create_user();
 736          role_assign($role->id, $user->id, context_system::instance());
 737          $course = $this->getDataGenerator()->create_course();
 738          $rolename = (object)array('roleid'=>$role->id, 'name'=>'Man', 'contextid'=>context_course::instance($course->id)->id);
 739          $DB->insert_record('role_names', $rolename);
 740  
 741          $this->assertTrue($DB->record_exists('role_assignments', array('roleid'=>$role->id)));
 742          $this->assertTrue($DB->record_exists('role_capabilities', array('roleid'=>$role->id)));
 743          $this->assertTrue($DB->record_exists('role_names', array('roleid'=>$role->id)));
 744          $this->assertTrue($DB->record_exists('role_context_levels', array('roleid'=>$role->id)));
 745          $this->assertTrue($DB->record_exists('role_allow_assign', array('roleid'=>$role->id)));
 746          $this->assertTrue($DB->record_exists('role_allow_assign', array('allowassign'=>$role->id)));
 747          $this->assertTrue($DB->record_exists('role_allow_override', array('roleid'=>$role->id)));
 748          $this->assertTrue($DB->record_exists('role_allow_override', array('allowoverride'=>$role->id)));
 749  
 750          // Delete role and get event.
 751          $sink = $this->redirectEvents();
 752          $result = delete_role($role->id);
 753          $events = $sink->get_events();
 754          $sink->close();
 755          $event = array_pop($events);
 756  
 757          $this->assertTrue($result);
 758          $this->assertFalse($DB->record_exists('role', array('id'=>$role->id)));
 759          $this->assertFalse($DB->record_exists('role_assignments', array('roleid'=>$role->id)));
 760          $this->assertFalse($DB->record_exists('role_capabilities', array('roleid'=>$role->id)));
 761          $this->assertFalse($DB->record_exists('role_names', array('roleid'=>$role->id)));
 762          $this->assertFalse($DB->record_exists('role_context_levels', array('roleid'=>$role->id)));
 763          $this->assertFalse($DB->record_exists('role_allow_assign', array('roleid'=>$role->id)));
 764          $this->assertFalse($DB->record_exists('role_allow_assign', array('allowassign'=>$role->id)));
 765          $this->assertFalse($DB->record_exists('role_allow_override', array('roleid'=>$role->id)));
 766          $this->assertFalse($DB->record_exists('role_allow_override', array('allowoverride'=>$role->id)));
 767  
 768          // Test triggered event.
 769          $this->assertInstanceOf('\core\event\role_deleted', $event);
 770          $this->assertSame('role', $event->target);
 771          $this->assertSame('role', $event->objecttable);
 772          $this->assertSame($role->id, $event->objectid);
 773          $this->assertEquals(context_system::instance(), $event->get_context());
 774          $this->assertSame($role->shortname, $event->other['shortname']);
 775          $this->assertSame($role->description, $event->other['description']);
 776          $this->assertSame($role->archetype, $event->other['archetype']);
 777      }
 778  
 779      /**
 780       * Test fetching of all roles.
 781       *
 782       * @covers ::get_all_roles
 783       */
 784      public function test_get_all_roles() {
 785          global $DB;
 786  
 787          $this->resetAfterTest();
 788  
 789          $allroles = get_all_roles();
 790          $this->assertIsArray($allroles);
 791          $initialrolescount = count($allroles);
 792          $this->assertTrue($initialrolescount >= 8); // There are 8 roles is standard install.
 793          $rolenames = array_column($allroles, 'shortname');
 794          foreach (get_role_archetypes() as $archetype) {
 795              $this->assertContains($archetype, $rolenames);
 796          }
 797  
 798          $role = reset($allroles);
 799          $role = (array)$role;
 800  
 801          $this->assertEqualsCanonicalizing(array('id', 'name', 'shortname', 'description', 'sortorder', 'archetype'),
 802              array_keys($role));
 803  
 804          foreach ($allroles as $roleid => $role) {
 805              $this->assertEquals($role->id, $roleid);
 806          }
 807  
 808          $teacher = $DB->get_record('role', array('shortname'=>'teacher'), '*', MUST_EXIST);
 809          $course = $this->getDataGenerator()->create_course();
 810          $coursecontext = context_course::instance($course->id);
 811          $otherid = create_role('Other role', 'other', 'Some other role', '');
 812          $teacherename = (object)array('roleid'=>$teacher->id, 'name'=>'Učitel', 'contextid'=>$coursecontext->id);
 813          $DB->insert_record('role_names', $teacherename);
 814          $otherrename = (object)array('roleid'=>$otherid, 'name'=>'Ostatní', 'contextid'=>$coursecontext->id);
 815          $DB->insert_record('role_names', $otherrename);
 816          $renames = $DB->get_records_menu('role_names', array('contextid'=>$coursecontext->id), '', 'roleid, name');
 817  
 818          $allroles = get_all_roles($coursecontext);
 819          $this->assertIsArray($allroles);
 820          $this->assertCount($initialrolescount + 1, $allroles);
 821          $role = reset($allroles);
 822          $role = (array)$role;
 823  
 824          $this->assertEqualsCanonicalizing(array('id', 'name', 'shortname', 'description', 'sortorder', 'archetype', 'coursealias'), array_keys($role));
 825  
 826          foreach ($allroles as $roleid => $role) {
 827              $this->assertEquals($role->id, $roleid);
 828              if (isset($renames[$roleid])) {
 829                  $this->assertSame($renames[$roleid], $role->coursealias);
 830              } else {
 831                  $this->assertNull($role->coursealias);
 832              }
 833          }
 834      }
 835  
 836      /**
 837       * Test getting of all archetypes.
 838       *
 839       * @covers ::get_role_archetypes
 840       */
 841      public function test_get_role_archetypes() {
 842          $archetypes = get_role_archetypes();
 843          $this->assertCount(8, $archetypes); // There are 8 archetypes in standard install.
 844          foreach ($archetypes as $k => $v) {
 845              $this->assertSame($k, $v);
 846          }
 847      }
 848  
 849      /**
 850       * Test getting of roles with given archetype.
 851       *
 852       * @covers ::get_archetype_roles
 853       */
 854      public function test_get_archetype_roles() {
 855          $this->resetAfterTest();
 856  
 857          // New install should have at least 1 role for each archetype.
 858          $archetypes = get_role_archetypes();
 859          foreach ($archetypes as $archetype) {
 860              $roles = get_archetype_roles($archetype);
 861              $this->assertGreaterThanOrEqual(1, count($roles));
 862              $role = reset($roles);
 863              $this->assertSame($archetype, $role->archetype);
 864          }
 865  
 866          create_role('New student role', 'student2', 'New student description', 'student');
 867          $roles = get_archetype_roles('student');
 868          $this->assertGreaterThanOrEqual(2, count($roles));
 869      }
 870  
 871      /**
 872       * Test aliased role names.
 873       *
 874       * @covers ::role_get_name
 875       */
 876      public function test_role_get_name() {
 877          global $DB;
 878  
 879          $this->resetAfterTest();
 880  
 881          $allroles = $DB->get_records('role');
 882          $teacher = $DB->get_record('role', array('shortname'=>'teacher'), '*', MUST_EXIST);
 883          $course = $this->getDataGenerator()->create_course();
 884          $coursecontext = context_course::instance($course->id);
 885          $otherid = create_role('Other role', 'other', 'Some other role', '');
 886          $teacherename = (object)array('roleid'=>$teacher->id, 'name'=>'Učitel', 'contextid'=>$coursecontext->id);
 887          $DB->insert_record('role_names', $teacherename);
 888          $otherrename = (object)array('roleid'=>$otherid, 'name'=>'Ostatní', 'contextid'=>$coursecontext->id);
 889          $DB->insert_record('role_names', $otherrename);
 890          $renames = $DB->get_records_menu('role_names', array('contextid'=>$coursecontext->id), '', 'roleid, name');
 891  
 892          foreach ($allroles as $role) {
 893              if (in_array($role->shortname, get_role_archetypes())) {
 894                  // Standard roles do not have a set name.
 895                  $this->assertSame('', $role->name);
 896              }
 897              // Get localised name from lang pack.
 898              $name = role_get_name($role, null, ROLENAME_ORIGINAL);
 899              $this->assertNotEmpty($name);
 900              $this->assertNotEquals($role->shortname, $name);
 901  
 902              if (isset($renames[$role->id])) {
 903                  $this->assertSame($renames[$role->id], role_get_name($role, $coursecontext));
 904                  $this->assertSame($renames[$role->id], role_get_name($role, $coursecontext, ROLENAME_ALIAS));
 905                  $this->assertSame($renames[$role->id], role_get_name($role, $coursecontext, ROLENAME_ALIAS_RAW));
 906                  $this->assertSame("{$renames[$role->id]} ($name)", role_get_name($role, $coursecontext, ROLENAME_BOTH));
 907              } else {
 908                  $this->assertSame($name, role_get_name($role, $coursecontext));
 909                  $this->assertSame($name, role_get_name($role, $coursecontext, ROLENAME_ALIAS));
 910                  $this->assertNull(role_get_name($role, $coursecontext, ROLENAME_ALIAS_RAW));
 911                  $this->assertSame($name, role_get_name($role, $coursecontext, ROLENAME_BOTH));
 912              }
 913              $this->assertSame($name, role_get_name($role));
 914              $this->assertSame($name, role_get_name($role, $coursecontext, ROLENAME_ORIGINAL));
 915              $this->assertSame($name, role_get_name($role, null, ROLENAME_ORIGINAL));
 916              $this->assertSame($role->shortname, role_get_name($role, $coursecontext, ROLENAME_SHORT));
 917              $this->assertSame($role->shortname, role_get_name($role, null, ROLENAME_SHORT));
 918              $this->assertSame("$name ($role->shortname)", role_get_name($role, $coursecontext, ROLENAME_ORIGINALANDSHORT));
 919              $this->assertSame("$name ($role->shortname)", role_get_name($role, null, ROLENAME_ORIGINALANDSHORT));
 920              $this->assertNull(role_get_name($role, null, ROLENAME_ALIAS_RAW));
 921          }
 922      }
 923  
 924      /**
 925       * Test tweaking of role name arrays.
 926       *
 927       * @covers ::role_fix_names
 928       */
 929      public function test_role_fix_names() {
 930          global $DB;
 931  
 932          $this->resetAfterTest();
 933  
 934          $teacher = $DB->get_record('role', array('shortname'=>'teacher'), '*', MUST_EXIST);
 935          $student = $DB->get_record('role', array('shortname'=>'student'), '*', MUST_EXIST);
 936          $otherid = create_role('Other role', 'other', 'Some other role', '');
 937          $anotherid = create_role('Another role', 'another', 'Yet another other role', '');
 938          $allroles = $DB->get_records('role');
 939  
 940          $syscontext = context_system::instance();
 941          $frontcontext = context_course::instance(SITEID);
 942          $course = $this->getDataGenerator()->create_course();
 943          $coursecontext = context_course::instance($course->id);
 944          $category = $DB->get_record('course_categories', array('id'=>$course->category), '*', MUST_EXIST);
 945          $categorycontext = context_coursecat::instance($category->id);
 946  
 947          $teacherename = (object)array('roleid'=>$teacher->id, 'name'=>'Učitel', 'contextid'=>$coursecontext->id);
 948          $DB->insert_record('role_names', $teacherename);
 949          $otherrename = (object)array('roleid'=>$otherid, 'name'=>'Ostatní', 'contextid'=>$coursecontext->id);
 950          $DB->insert_record('role_names', $otherrename);
 951          $renames = $DB->get_records_menu('role_names', array('contextid'=>$coursecontext->id), '', 'roleid, name');
 952  
 953          // Make sure all localname contain proper values for each ROLENAME_ constant,
 954          // note role_get_name() on frontpage is used to get the original name for future compatibility.
 955          $roles = $allroles;
 956          unset($roles[$student->id]); // Remove one role to make sure no role is added or removed.
 957          $rolenames = array();
 958          foreach ($roles as $role) {
 959              $rolenames[$role->id] = $role->name;
 960          }
 961  
 962          $alltypes = array(ROLENAME_ALIAS, ROLENAME_ALIAS_RAW, ROLENAME_BOTH, ROLENAME_ORIGINAL, ROLENAME_ORIGINALANDSHORT, ROLENAME_SHORT);
 963          foreach ($alltypes as $type) {
 964              $fixed = role_fix_names($roles, $coursecontext, $type);
 965              $this->assertCount(count($roles), $fixed);
 966              foreach ($fixed as $roleid => $rolename) {
 967                  $this->assertInstanceOf('stdClass', $rolename);
 968                  $role = $allroles[$roleid];
 969                  $name = role_get_name($role, $coursecontext, $type);
 970                  $this->assertSame($name, $rolename->localname);
 971              }
 972              $fixed = role_fix_names($rolenames, $coursecontext, $type);
 973              $this->assertCount(count($rolenames), $fixed);
 974              foreach ($fixed as $roleid => $rolename) {
 975                  $role = $allroles[$roleid];
 976                  $name = role_get_name($role, $coursecontext, $type);
 977                  $this->assertSame($name, $rolename);
 978              }
 979          }
 980      }
 981  
 982      /**
 983       * Test role default allows.
 984       *
 985       * @covers ::get_default_role_archetype_allows
 986       */
 987      public function test_get_default_role_archetype_allows() {
 988          $archetypes = get_role_archetypes();
 989          foreach ($archetypes as $archetype) {
 990  
 991              $result = get_default_role_archetype_allows('assign', $archetype);
 992              $this->assertIsArray($result);
 993  
 994              $result = get_default_role_archetype_allows('override', $archetype);
 995              $this->assertIsArray($result);
 996  
 997              $result = get_default_role_archetype_allows('switch', $archetype);
 998              $this->assertIsArray($result);
 999  
1000              $result = get_default_role_archetype_allows('view', $archetype);
1001              $this->assertIsArray($result);
1002          }
1003  
1004          $result = get_default_role_archetype_allows('assign', '');
1005          $this->assertSame(array(), $result);
1006  
1007          $result = get_default_role_archetype_allows('override', '');
1008          $this->assertSame(array(), $result);
1009  
1010          $result = get_default_role_archetype_allows('switch', '');
1011          $this->assertSame(array(), $result);
1012  
1013          $result = get_default_role_archetype_allows('view', '');
1014          $this->assertSame(array(), $result);
1015  
1016          $result = get_default_role_archetype_allows('assign', 'wrongarchetype');
1017          $this->assertSame(array(), $result);
1018          $this->assertDebuggingCalled();
1019  
1020          $result = get_default_role_archetype_allows('override', 'wrongarchetype');
1021          $this->assertSame(array(), $result);
1022          $this->assertDebuggingCalled();
1023  
1024          $result = get_default_role_archetype_allows('switch', 'wrongarchetype');
1025          $this->assertSame(array(), $result);
1026          $this->assertDebuggingCalled();
1027  
1028          $result = get_default_role_archetype_allows('view', 'wrongarchetype');
1029          $this->assertSame(array(), $result);
1030          $this->assertDebuggingCalled();
1031      }
1032  
1033      /**
1034       * Test allowing of role assignments.
1035       *
1036       * @covers ::core_role_set_assign_allowed
1037       */
1038      public function test_core_role_set_assign_allowed() {
1039          global $DB, $CFG;
1040  
1041          $this->resetAfterTest();
1042  
1043          $otherid = create_role('Other role', 'other', 'Some other role', '');
1044          $student = $DB->get_record('role', array('shortname'=>'student'), '*', MUST_EXIST);
1045  
1046          $this->assertFalse($DB->record_exists('role_allow_assign', array('roleid'=>$otherid, 'allowassign'=>$student->id)));
1047          core_role_set_assign_allowed($otherid, $student->id);
1048          $this->assertTrue($DB->record_exists('role_allow_assign', array('roleid'=>$otherid, 'allowassign'=>$student->id)));
1049  
1050          // Test event trigger.
1051          $allowroleassignevent = \core\event\role_allow_assign_updated::create([
1052              'context' => context_system::instance(),
1053              'objectid' => $otherid,
1054              'other' => ['targetroleid' => $student->id]
1055          ]);
1056          $sink = $this->redirectEvents();
1057          $allowroleassignevent->trigger();
1058          $events = $sink->get_events();
1059          $sink->close();
1060          $event = array_pop($events);
1061          $this->assertInstanceOf('\core\event\role_allow_assign_updated', $event);
1062      }
1063  
1064      /**
1065       * Test allowing of role overrides.
1066       *
1067       * @covers ::core_role_set_override_allowed
1068       */
1069      public function test_core_role_set_override_allowed() {
1070          global $DB, $CFG;
1071  
1072          $this->resetAfterTest();
1073  
1074          $otherid = create_role('Other role', 'other', 'Some other role', '');
1075          $student = $DB->get_record('role', array('shortname'=>'student'), '*', MUST_EXIST);
1076  
1077          $this->assertFalse($DB->record_exists('role_allow_override', array('roleid'=>$otherid, 'allowoverride'=>$student->id)));
1078          core_role_set_override_allowed($otherid, $student->id);
1079          $this->assertTrue($DB->record_exists('role_allow_override', array('roleid'=>$otherid, 'allowoverride'=>$student->id)));
1080  
1081          // Test event trigger.
1082          $allowroleassignevent = \core\event\role_allow_override_updated::create([
1083              'context' => context_system::instance(),
1084              'objectid' => $otherid,
1085              'other' => ['targetroleid' => $student->id]
1086          ]);
1087          $sink = $this->redirectEvents();
1088          $allowroleassignevent->trigger();
1089          $events = $sink->get_events();
1090          $sink->close();
1091          $event = array_pop($events);
1092          $this->assertInstanceOf('\core\event\role_allow_override_updated', $event);
1093      }
1094  
1095      /**
1096       * Test allowing of role switching.
1097       *
1098       * @covers ::core_role_set_switch_allowed
1099       */
1100      public function test_core_role_set_switch_allowed() {
1101          global $DB, $CFG;
1102  
1103          $this->resetAfterTest();
1104  
1105          $otherid = create_role('Other role', 'other', 'Some other role', '');
1106          $student = $DB->get_record('role', array('shortname'=>'student'), '*', MUST_EXIST);
1107  
1108          $this->assertFalse($DB->record_exists('role_allow_switch', array('roleid'=>$otherid, 'allowswitch'=>$student->id)));
1109          core_role_set_switch_allowed($otherid, $student->id);
1110          $this->assertTrue($DB->record_exists('role_allow_switch', array('roleid'=>$otherid, 'allowswitch'=>$student->id)));
1111  
1112          // Test event trigger.
1113          $allowroleassignevent = \core\event\role_allow_switch_updated::create([
1114              'context' => context_system::instance(),
1115              'objectid' => $otherid,
1116              'other' => ['targetroleid' => $student->id]
1117          ]);
1118          $sink = $this->redirectEvents();
1119          $allowroleassignevent->trigger();
1120          $events = $sink->get_events();
1121          $sink->close();
1122          $event = array_pop($events);
1123          $this->assertInstanceOf('\core\event\role_allow_switch_updated', $event);
1124      }
1125  
1126      /**
1127       * Test allowing of role switching.
1128       *
1129       * @covers ::core_role_set_view_allowed
1130       */
1131      public function test_core_role_set_view_allowed() {
1132          global $DB, $CFG;
1133  
1134          $this->resetAfterTest();
1135  
1136          $otherid = create_role('Other role', 'other', 'Some other role', '');
1137          $student = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
1138  
1139          $this->assertFalse($DB->record_exists('role_allow_view', array('roleid' => $otherid, 'allowview' => $student->id)));
1140          core_role_set_view_allowed($otherid, $student->id);
1141          $this->assertTrue($DB->record_exists('role_allow_view', array('roleid' => $otherid, 'allowview' => $student->id)));
1142  
1143          // Test event trigger.
1144          $allowroleassignevent = \core\event\role_allow_view_updated::create([
1145              'context' => context_system::instance(),
1146              'objectid' => $otherid,
1147              'other' => ['targetroleid' => $student->id]
1148          ]);
1149          $sink = $this->redirectEvents();
1150          $allowroleassignevent->trigger();
1151          $events = $sink->get_events();
1152          $sink->close();
1153          $event = array_pop($events);
1154          $this->assertInstanceOf('\core\event\role_allow_view_updated', $event);
1155      }
1156  
1157      /**
1158       * Test returning of assignable roles in context.
1159       *
1160       * @covers ::get_assignable_roles
1161       */
1162      public function test_get_assignable_roles() {
1163          global $DB;
1164  
1165          $this->resetAfterTest();
1166  
1167          $course = $this->getDataGenerator()->create_course();
1168          $coursecontext = context_course::instance($course->id);
1169  
1170          $teacherrole = $DB->get_record('role', array('shortname'=>'editingteacher'), '*', MUST_EXIST);
1171          $teacher = $this->getDataGenerator()->create_user();
1172          role_assign($teacherrole->id, $teacher->id, $coursecontext);
1173          $teacherename = (object)array('roleid'=>$teacherrole->id, 'name'=>'Učitel', 'contextid'=>$coursecontext->id);
1174          $DB->insert_record('role_names', $teacherename);
1175  
1176          $studentrole = $DB->get_record('role', array('shortname'=>'student'), '*', MUST_EXIST);
1177          $student = $this->getDataGenerator()->create_user();
1178          role_assign($studentrole->id, $student->id, $coursecontext);
1179  
1180          $contexts = $DB->get_records('context');
1181          $users = $DB->get_records('user');
1182          $allroles = $DB->get_records('role');
1183  
1184          // Evaluate all results for all users in all contexts.
1185          foreach ($users as $user) {
1186              $this->setUser($user);
1187              foreach ($contexts as $contextid => $unused) {
1188                  $context = context_helper::instance_by_id($contextid);
1189                  $roles = get_assignable_roles($context, ROLENAME_SHORT);
1190                  foreach ($allroles as $roleid => $role) {
1191                      if (isset($roles[$roleid])) {
1192                          if (is_siteadmin()) {
1193                              $this->assertTrue($DB->record_exists('role_context_levels', array('contextlevel'=>$context->contextlevel, 'roleid'=>$roleid)));
1194                          } else {
1195                              $this->assertTrue(user_can_assign($context, $roleid), "u:$user->id r:$roleid");
1196                          }
1197                          $this->assertEquals($role->shortname, $roles[$roleid]);
1198                      } else {
1199                          $allowed = $DB->record_exists('role_context_levels', array('contextlevel'=>$context->contextlevel, 'roleid'=>$roleid));
1200                          if (is_siteadmin()) {
1201                              $this->assertFalse($allowed);
1202                          } else {
1203                              $this->assertFalse($allowed and user_can_assign($context, $roleid), "u:$user->id, r:{$allroles[$roleid]->name}, c:$context->contextlevel");
1204                          }
1205                      }
1206                  }
1207              }
1208          }
1209  
1210          // Not-logged-in user.
1211          $this->setUser(0);
1212          foreach ($contexts as $contextid => $unused) {
1213              $context = context_helper::instance_by_id($contextid);
1214              $roles = get_assignable_roles($context, ROLENAME_SHORT);
1215              $this->assertSame(array(), $roles);
1216          }
1217  
1218          // Test current user.
1219          $this->setUser(0);
1220          $admin = $DB->get_record('user', array('username'=>'admin'), '*', MUST_EXIST);
1221          $roles1 = get_assignable_roles($coursecontext, ROLENAME_SHORT, false, $admin);
1222          $roles2 = get_assignable_roles($coursecontext, ROLENAME_SHORT, false, $admin->id);
1223          $this->setAdminUser();
1224          $roles3 = get_assignable_roles($coursecontext, ROLENAME_SHORT);
1225          $this->assertSame($roles1, $roles3);
1226          $this->assertSame($roles2, $roles3);
1227  
1228          // Test parameter defaults.
1229          $this->setAdminUser();
1230          $roles1 = get_assignable_roles($coursecontext);
1231          $roles2 = get_assignable_roles($coursecontext, ROLENAME_ALIAS, false, $admin);
1232          $this->assertEquals($roles2, $roles1);
1233  
1234          // Verify returned names - let's allow all roles everywhere to simplify this a bit.
1235          $alllevels = context_helper::get_all_levels();
1236          $alllevels = array_keys($alllevels);
1237          foreach ($allroles as $roleid => $role) {
1238              set_role_contextlevels($roleid, $alllevels);
1239          }
1240          $alltypes = array(ROLENAME_ALIAS, ROLENAME_ALIAS_RAW, ROLENAME_BOTH, ROLENAME_ORIGINAL, ROLENAME_ORIGINALANDSHORT, ROLENAME_SHORT);
1241          foreach ($alltypes as $type) {
1242              $rolenames = role_fix_names($allroles, $coursecontext, $type);
1243              $roles = get_assignable_roles($coursecontext, $type, false, $admin);
1244              foreach ($roles as $roleid => $rolename) {
1245                  $this->assertSame($rolenames[$roleid]->localname, $rolename);
1246              }
1247          }
1248  
1249          // Verify counts.
1250          $alltypes = array(ROLENAME_ALIAS, ROLENAME_ALIAS_RAW, ROLENAME_BOTH, ROLENAME_ORIGINAL, ROLENAME_ORIGINALANDSHORT, ROLENAME_SHORT);
1251          foreach ($alltypes as $type) {
1252              $roles = get_assignable_roles($coursecontext, $type, false, $admin);
1253              list($rolenames, $rolecounts, $nameswithcounts) = get_assignable_roles($coursecontext, $type, true, $admin);
1254              $this->assertEquals($roles, $rolenames);
1255              foreach ($rolenames as $roleid => $name) {
1256                  if ($roleid == $teacherrole->id or $roleid == $studentrole->id) {
1257                      $this->assertEquals(1, $rolecounts[$roleid]);
1258                  } else {
1259                      $this->assertEquals(0, $rolecounts[$roleid]);
1260                  }
1261                  $this->assertSame("$name ($rolecounts[$roleid])", $nameswithcounts[$roleid]);
1262              }
1263          }
1264      }
1265  
1266      /**
1267       * Test user count of assignable roles in context where users are assigned the role via different components.
1268       *
1269       * @covers ::get_assignable_roles
1270       */
1271      public function test_get_assignable_roles_distinct_usercount() {
1272          global $DB;
1273  
1274          $this->resetAfterTest(true);
1275  
1276          $this->setAdminUser();
1277  
1278          $course = $this->getDataGenerator()->create_course();
1279          $context = \context_course::instance($course->id);
1280  
1281          $user1 = $this->getDataGenerator()->create_user();
1282          $user2 = $this->getDataGenerator()->create_user();
1283  
1284          $studentrole = $DB->get_record('role', ['shortname' => 'student']);
1285  
1286          // Assign each user the student role in course.
1287          role_assign($studentrole->id, $user1->id, $context->id);
1288          role_assign($studentrole->id, $user2->id, $context->id);
1289  
1290          list($rolenames, $rolecounts, $nameswithcounts) = get_assignable_roles($context, ROLENAME_SHORT, true);
1291          $this->assertEquals(2, $rolecounts[$studentrole->id]);
1292  
1293          // Assign first user the student role in course again (this time via 'enrol_self' component).
1294          role_assign($studentrole->id, $user1->id, $context->id, 'enrol_self', 1);
1295  
1296          // There are still only two distinct users.
1297          list($rolenames, $rolecounts, $nameswithcounts) = get_assignable_roles($context, ROLENAME_SHORT, true);
1298          $this->assertEquals(2, $rolecounts[$studentrole->id]);
1299      }
1300  
1301      /**
1302       * Test getting of all switchable roles.
1303       *
1304       * @covers ::get_switchable_roles
1305       */
1306      public function test_get_switchable_roles() {
1307          global $DB;
1308  
1309          $this->resetAfterTest();
1310  
1311          $course = $this->getDataGenerator()->create_course();
1312          $coursecontext = context_course::instance($course->id);
1313  
1314          $teacherrole = $DB->get_record('role', array('shortname'=>'editingteacher'), '*', MUST_EXIST);
1315          $teacher = $this->getDataGenerator()->create_user();
1316          role_assign($teacherrole->id, $teacher->id, $coursecontext);
1317          $teacherename = (object)array('roleid'=>$teacherrole->id, 'name'=>'Učitel', 'contextid'=>$coursecontext->id);
1318          $DB->insert_record('role_names', $teacherename);
1319  
1320          $contexts = $DB->get_records('context');
1321          $users = $DB->get_records('user');
1322          $allroles = $DB->get_records('role');
1323  
1324          // Evaluate all results for all users in all contexts.
1325          foreach ($users as $user) {
1326              $this->setUser($user);
1327              foreach ($contexts as $contextid => $unused) {
1328                  $context = context_helper::instance_by_id($contextid);
1329                  $roles = get_switchable_roles($context);
1330                  foreach ($allroles as $roleid => $role) {
1331                      if (is_siteadmin()) {
1332                          $this->assertTrue(isset($roles[$roleid]));
1333                      } else {
1334                          $parents = $context->get_parent_context_ids(true);
1335                          $pcontexts = implode(',' , $parents);
1336                          $allowed = $DB->record_exists_sql(
1337                              "SELECT r.id
1338                                 FROM {role} r
1339                                 JOIN {role_allow_switch} ras ON ras.allowswitch = r.id
1340                                 JOIN {role_assignments} ra ON ra.roleid = ras.roleid
1341                                WHERE ra.userid = :userid AND ra.contextid IN ($pcontexts) AND r.id = :roleid
1342                              ",
1343                              array('userid'=>$user->id, 'roleid'=>$roleid)
1344                          );
1345                          if (isset($roles[$roleid])) {
1346                              $this->assertTrue($allowed);
1347                          } else {
1348                              $this->assertFalse($allowed);
1349                          }
1350                      }
1351  
1352                      if (isset($roles[$roleid])) {
1353                          $coursecontext = $context->get_course_context(false);
1354                          $this->assertSame(role_get_name($role, $coursecontext), $roles[$roleid]);
1355                      }
1356                  }
1357              }
1358          }
1359      }
1360  
1361      /**
1362       * Test getting of all overridable roles.
1363       *
1364       * @covers ::get_overridable_roles
1365       */
1366      public function test_get_overridable_roles() {
1367          global $DB;
1368  
1369          $this->resetAfterTest();
1370  
1371          $course = $this->getDataGenerator()->create_course();
1372          $coursecontext = context_course::instance($course->id);
1373  
1374          $teacherrole = $DB->get_record('role', array('shortname'=>'editingteacher'), '*', MUST_EXIST);
1375          $teacher = $this->getDataGenerator()->create_user();
1376          role_assign($teacherrole->id, $teacher->id, $coursecontext);
1377          $teacherename = (object)array('roleid'=>$teacherrole->id, 'name'=>'Učitel', 'contextid'=>$coursecontext->id);
1378          $DB->insert_record('role_names', $teacherename);
1379          $this->assertTrue($DB->record_exists('capabilities', array('name'=>'moodle/backup:backupcourse'))); // Any capability is ok.
1380          assign_capability('moodle/backup:backupcourse', CAP_PROHIBIT, $teacherrole->id, $coursecontext->id);
1381  
1382          $studentrole = $DB->get_record('role', array('shortname'=>'student'), '*', MUST_EXIST);
1383          $student = $this->getDataGenerator()->create_user();
1384          role_assign($studentrole->id, $student->id, $coursecontext);
1385  
1386          $contexts = $DB->get_records('context');
1387          $users = $DB->get_records('user');
1388          $allroles = $DB->get_records('role');
1389  
1390          // Evaluate all results for all users in all contexts.
1391          foreach ($users as $user) {
1392              $this->setUser($user);
1393              foreach ($contexts as $contextid => $unused) {
1394                  $context = context_helper::instance_by_id($contextid);
1395                  $roles = get_overridable_roles($context, ROLENAME_SHORT);
1396                  foreach ($allroles as $roleid => $role) {
1397                      $hascap = has_any_capability(array('moodle/role:safeoverride', 'moodle/role:override'), $context);
1398                      if (is_siteadmin()) {
1399                          $this->assertTrue(isset($roles[$roleid]));
1400                      } else {
1401                          $parents = $context->get_parent_context_ids(true);
1402                          $pcontexts = implode(',' , $parents);
1403                          $allowed = $DB->record_exists_sql(
1404                              "SELECT r.id
1405                                 FROM {role} r
1406                                 JOIN {role_allow_override} rao ON r.id = rao.allowoverride
1407                                 JOIN {role_assignments} ra ON rao.roleid = ra.roleid
1408                                WHERE ra.userid = :userid AND ra.contextid IN ($pcontexts) AND r.id = :roleid
1409                              ",
1410                              array('userid'=>$user->id, 'roleid'=>$roleid)
1411                          );
1412                          if (isset($roles[$roleid])) {
1413                              $this->assertTrue($hascap);
1414                              $this->assertTrue($allowed);
1415                          } else {
1416                              $this->assertFalse($hascap and $allowed);
1417                          }
1418                      }
1419  
1420                      if (isset($roles[$roleid])) {
1421                          $this->assertEquals($role->shortname, $roles[$roleid]);
1422                      }
1423                  }
1424              }
1425          }
1426  
1427          // Test parameter defaults.
1428          $this->setAdminUser();
1429          $roles1 = get_overridable_roles($coursecontext);
1430          $roles2 = get_overridable_roles($coursecontext, ROLENAME_ALIAS, false);
1431          $this->assertEquals($roles2, $roles1);
1432  
1433          $alltypes = array(ROLENAME_ALIAS, ROLENAME_ALIAS_RAW, ROLENAME_BOTH, ROLENAME_ORIGINAL, ROLENAME_ORIGINALANDSHORT, ROLENAME_SHORT);
1434          foreach ($alltypes as $type) {
1435              $rolenames = role_fix_names($allroles, $coursecontext, $type);
1436              $roles = get_overridable_roles($coursecontext, $type, false);
1437              foreach ($roles as $roleid => $rolename) {
1438                  $this->assertSame($rolenames[$roleid]->localname, $rolename);
1439              }
1440          }
1441  
1442          // Verify counts.
1443          $roles = get_overridable_roles($coursecontext, ROLENAME_ALIAS, false);
1444          list($rolenames, $rolecounts, $nameswithcounts) = get_overridable_roles($coursecontext, ROLENAME_ALIAS, true);
1445          $this->assertEquals($roles, $rolenames);
1446          foreach ($rolenames as $roleid => $name) {
1447              if ($roleid == $teacherrole->id) {
1448                  $this->assertEquals(1, $rolecounts[$roleid]);
1449              } else {
1450                  $this->assertEquals(0, $rolecounts[$roleid]);
1451              }
1452              $this->assertSame("$name ($rolecounts[$roleid])", $nameswithcounts[$roleid]);
1453          }
1454      }
1455  
1456      /**
1457       * Test getting of all overridable roles.
1458       *
1459       * @covers ::get_viewable_roles
1460       */
1461      public function test_get_viewable_roles_course() {
1462          global $DB;
1463  
1464          $this->resetAfterTest();
1465  
1466          $course = $this->getDataGenerator()->create_course();
1467          $coursecontext = context_course::instance($course->id);
1468  
1469          $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'), '*', MUST_EXIST);
1470          $teacher = $this->getDataGenerator()->create_user();
1471          role_assign($teacherrole->id, $teacher->id, $coursecontext);
1472  
1473          $studentrole = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
1474          $studentrolerename = (object) array('roleid' => $studentrole->id, 'name' => 'Učitel', 'contextid' => $coursecontext->id);
1475          $DB->insert_record('role_names', $studentrolerename);
1476  
1477          // By default teacher can see student.
1478          $this->setUser($teacher);
1479          $viewableroles = get_viewable_roles($coursecontext);
1480          $this->assertContains($studentrolerename->name, array_values($viewableroles));
1481          // Remove view permission.
1482          $DB->delete_records('role_allow_view', array('roleid' => $teacherrole->id, 'allowview' => $studentrole->id));
1483          $viewableroles = get_viewable_roles($coursecontext);
1484          // Teacher can no longer see student role.
1485          $this->assertNotContains($studentrolerename->name, array_values($viewableroles));
1486          // Allow again teacher to view student.
1487          core_role_set_view_allowed($teacherrole->id, $studentrole->id);
1488          // Teacher can now see student role.
1489          $viewableroles = get_viewable_roles($coursecontext);
1490          $this->assertContains($studentrolerename->name, array_values($viewableroles));
1491      }
1492  
1493      /**
1494       * Test getting of all overridable roles.
1495       *
1496       * @covers ::get_viewable_roles
1497       */
1498      public function test_get_viewable_roles_system() {
1499          global $DB;
1500  
1501          $this->resetAfterTest();
1502  
1503          $context = context_system::instance();
1504  
1505          $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'), '*', MUST_EXIST);
1506          $teacher = $this->getDataGenerator()->create_user();
1507          role_assign($teacherrole->id, $teacher->id, $context);
1508  
1509          $studentrole = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
1510          $studentrolename = role_get_name($studentrole, $context);
1511  
1512          // By default teacher can see student.
1513          $this->setUser($teacher);
1514          $viewableroles = get_viewable_roles($context);
1515          $this->assertContains($studentrolename, array_values($viewableroles));
1516          // Remove view permission.
1517          $DB->delete_records('role_allow_view', array('roleid' => $teacherrole->id, 'allowview' => $studentrole->id));
1518          $viewableroles = get_viewable_roles($context);
1519          // Teacher can no longer see student role.
1520          $this->assertNotContains($studentrolename, array_values($viewableroles));
1521          // Allow again teacher to view student.
1522          core_role_set_view_allowed($teacherrole->id, $studentrole->id);
1523          // Teacher can now see student role.
1524          $viewableroles = get_viewable_roles($context);
1525          $this->assertContains($studentrolename, array_values($viewableroles));
1526      }
1527  
1528      /**
1529       * Test we have context level defaults.
1530       *
1531       * @covers ::get_default_contextlevels
1532       */
1533      public function test_get_default_contextlevels() {
1534          $archetypes = get_role_archetypes();
1535          $alllevels = context_helper::get_all_levels();
1536          foreach ($archetypes as $archetype) {
1537              $defaults = get_default_contextlevels($archetype);
1538              $this->assertIsArray($defaults);
1539              foreach ($defaults as $level) {
1540                  $this->assertTrue(isset($alllevels[$level]));
1541              }
1542          }
1543      }
1544  
1545      /**
1546       * Test role context level setup.
1547       *
1548       * @covers ::set_role_contextlevels
1549       */
1550      public function test_set_role_contextlevels() {
1551          global $DB;
1552  
1553          $this->resetAfterTest();
1554  
1555          $roleid = create_role('New student role', 'student2', 'New student description', 'student');
1556  
1557          $this->assertFalse($DB->record_exists('role_context_levels', array('roleid' => $roleid)));
1558  
1559          set_role_contextlevels($roleid, array(CONTEXT_COURSE, CONTEXT_MODULE));
1560          $levels = $DB->get_records('role_context_levels', array('roleid' => $roleid), '', 'contextlevel, contextlevel');
1561          $this->assertCount(2, $levels);
1562          $this->assertTrue(isset($levels[CONTEXT_COURSE]));
1563          $this->assertTrue(isset($levels[CONTEXT_MODULE]));
1564  
1565          set_role_contextlevels($roleid, array(CONTEXT_COURSE));
1566          $levels = $DB->get_records('role_context_levels', array('roleid' => $roleid), '', 'contextlevel, contextlevel');
1567          $this->assertCount(1, $levels);
1568          $this->assertTrue(isset($levels[CONTEXT_COURSE]));
1569      }
1570  
1571      /**
1572       * Test getting of role context levels
1573       *
1574       * @covers ::get_roles_for_contextlevels
1575       */
1576      public function test_get_roles_for_contextlevels() {
1577          global $DB;
1578  
1579          $allroles = get_all_roles();
1580          foreach (context_helper::get_all_levels() as $level => $unused) {
1581              $roles = get_roles_for_contextlevels($level);
1582              foreach ($allroles as $roleid => $unused) {
1583                  $exists = $DB->record_exists('role_context_levels', array('contextlevel'=>$level, 'roleid'=>$roleid));
1584                  if (in_array($roleid, $roles)) {
1585                      $this->assertTrue($exists);
1586                  } else {
1587                      $this->assertFalse($exists);
1588                  }
1589              }
1590          }
1591      }
1592  
1593      /**
1594       * Test default enrol roles.
1595       *
1596       * @covers ::get_default_enrol_roles
1597       */
1598      public function test_get_default_enrol_roles() {
1599          $this->resetAfterTest();
1600  
1601          $course = $this->getDataGenerator()->create_course();
1602          $coursecontext = context_course::instance($course->id);
1603  
1604          $id2 = create_role('New student role', 'student2', 'New student description', 'student');
1605          set_role_contextlevels($id2, array(CONTEXT_COURSE));
1606  
1607          $allroles = get_all_roles();
1608          $expected = array($id2=>$allroles[$id2]);
1609  
1610          foreach (get_roles_for_contextlevels(CONTEXT_COURSE) as $roleid) {
1611              $expected[$roleid] = $roleid;
1612          }
1613  
1614          $roles = get_default_enrol_roles($coursecontext);
1615          foreach ($allroles as $role) {
1616              $this->assertEquals(isset($expected[$role->id]), isset($roles[$role->id]));
1617              if (isset($roles[$role->id])) {
1618                  $this->assertSame(role_get_name($role, $coursecontext), $roles[$role->id]);
1619              }
1620          }
1621      }
1622  
1623      /**
1624       * Test getting of role users.
1625       *
1626       * @covers ::get_role_users
1627       */
1628      public function test_get_role_users() {
1629          global $DB;
1630  
1631          $this->resetAfterTest();
1632  
1633          $systemcontext = context_system::instance();
1634          $studentrole = $DB->get_record('role', array('shortname'=>'student'), '*', MUST_EXIST);
1635          $teacherrole = $DB->get_record('role', array('shortname'=>'editingteacher'), '*', MUST_EXIST);
1636          $noeditteacherrole = $DB->get_record('role', array('shortname' => 'teacher'), '*', MUST_EXIST);
1637          $course = $this->getDataGenerator()->create_course();
1638          $coursecontext = context_course::instance($course->id);
1639          $otherid = create_role('Other role', 'other', 'Some other role', '');
1640          $teacherrename = (object)array('roleid'=>$teacherrole->id, 'name'=>'Učitel', 'contextid'=>$coursecontext->id);
1641          $DB->insert_record('role_names', $teacherrename);
1642          $otherrename = (object)array('roleid'=>$otherid, 'name'=>'Ostatní', 'contextid'=>$coursecontext->id);
1643          $DB->insert_record('role_names', $otherrename);
1644  
1645          $user1 = $this->getDataGenerator()->create_user(array('firstname'=>'John', 'lastname'=>'Smith'));
1646          role_assign($teacherrole->id, $user1->id, $coursecontext->id);
1647          $user2 = $this->getDataGenerator()->create_user(array('firstname'=>'Jan', 'lastname'=>'Kovar'));
1648          role_assign($teacherrole->id, $user2->id, $systemcontext->id);
1649          $user3 = $this->getDataGenerator()->create_user();
1650          $this->getDataGenerator()->enrol_user($user3->id, $course->id, $teacherrole->id);
1651          $user4 = $this->getDataGenerator()->create_user();
1652          $this->getDataGenerator()->enrol_user($user4->id, $course->id, $studentrole->id);
1653          $this->getDataGenerator()->enrol_user($user4->id, $course->id, $noeditteacherrole->id);
1654  
1655          $group = $this->getDataGenerator()->create_group(array('courseid'=>$course->id));
1656          groups_add_member($group, $user3);
1657  
1658          $users = get_role_users($teacherrole->id, $coursecontext);
1659          $this->assertCount(2, $users);
1660          $this->assertArrayHasKey($user1->id, $users);
1661          $this->assertEquals($users[$user1->id]->id, $user1->id);
1662          $this->assertEquals($users[$user1->id]->roleid, $teacherrole->id);
1663          $this->assertEquals($users[$user1->id]->rolename, $teacherrole->name);
1664          $this->assertEquals($users[$user1->id]->roleshortname, $teacherrole->shortname);
1665          $this->assertEquals($users[$user1->id]->rolecoursealias, $teacherrename->name);
1666          $this->assertArrayHasKey($user3->id, $users);
1667          $this->assertEquals($users[$user3->id]->id, $user3->id);
1668          $this->assertEquals($users[$user3->id]->roleid, $teacherrole->id);
1669          $this->assertEquals($users[$user3->id]->rolename, $teacherrole->name);
1670          $this->assertEquals($users[$user3->id]->roleshortname, $teacherrole->shortname);
1671          $this->assertEquals($users[$user3->id]->rolecoursealias, $teacherrename->name);
1672  
1673          $users = get_role_users($teacherrole->id, $coursecontext, true);
1674          $this->assertCount(3, $users);
1675  
1676          $users = get_role_users($teacherrole->id, $coursecontext, true, '', null, null, '', 2, 1);
1677          $this->assertCount(1, $users);
1678  
1679          $users = get_role_users($teacherrole->id, $coursecontext, false, 'u.id, u.email, u.idnumber', 'u.idnumber');
1680          $this->assertCount(2, $users);
1681          $this->assertArrayHasKey($user1->id, $users);
1682          $this->assertArrayHasKey($user3->id, $users);
1683  
1684          $users = get_role_users($teacherrole->id, $coursecontext, false, 'u.id, u.email');
1685          $this->assertDebuggingCalled('get_role_users() adding u.lastname, u.firstname to the query result because they were required by $sort but missing in $fields');
1686          $this->assertCount(2, $users);
1687          $this->assertArrayHasKey($user1->id, $users);
1688          $this->assertObjectHasAttribute('lastname', $users[$user1->id]);
1689          $this->assertObjectHasAttribute('firstname', $users[$user1->id]);
1690          $this->assertArrayHasKey($user3->id, $users);
1691          $this->assertObjectHasAttribute('lastname', $users[$user3->id]);
1692          $this->assertObjectHasAttribute('firstname', $users[$user3->id]);
1693  
1694          $users = get_role_users($teacherrole->id, $coursecontext, false, 'u.id AS id_alias');
1695          $this->assertDebuggingCalled('get_role_users() adding u.lastname, u.firstname to the query result because they were required by $sort but missing in $fields');
1696          $this->assertCount(2, $users);
1697          $this->assertArrayHasKey($user1->id, $users);
1698          $this->assertObjectHasAttribute('id_alias', $users[$user1->id]);
1699          $this->assertObjectHasAttribute('lastname', $users[$user1->id]);
1700          $this->assertObjectHasAttribute('firstname', $users[$user1->id]);
1701          $this->assertArrayHasKey($user3->id, $users);
1702          $this->assertObjectHasAttribute('id_alias', $users[$user3->id]);
1703          $this->assertObjectHasAttribute('lastname', $users[$user3->id]);
1704          $this->assertObjectHasAttribute('firstname', $users[$user3->id]);
1705  
1706          $users = get_role_users($teacherrole->id, $coursecontext, false, 'u.id, u.email, u.idnumber', 'u.idnumber', null, $group->id);
1707          $this->assertCount(1, $users);
1708          $this->assertArrayHasKey($user3->id, $users);
1709  
1710          $users = get_role_users($teacherrole->id, $coursecontext, true, 'u.id, u.email, u.idnumber, u.firstname', 'u.idnumber', null, '', '', '', 'u.firstname = :xfirstname', array('xfirstname'=>'John'));
1711          $this->assertCount(1, $users);
1712          $this->assertArrayHasKey($user1->id, $users);
1713  
1714          $users = get_role_users(array($noeditteacherrole->id, $studentrole->id), $coursecontext, false, 'ra.id', 'ra.id');
1715          $this->assertDebuggingNotCalled();
1716          $users = get_role_users(array($noeditteacherrole->id, $studentrole->id), $coursecontext, false, 'ra.userid', 'ra.userid');
1717          $this->assertDebuggingCalled('get_role_users() without specifying one single roleid needs to be called prefixing ' .
1718              'role assignments id (ra.id) as unique field, you can use $fields param for it.');
1719          $users = get_role_users(array($noeditteacherrole->id, $studentrole->id), $coursecontext, false);
1720          $this->assertDebuggingCalled('get_role_users() without specifying one single roleid needs to be called prefixing ' .
1721              'role assignments id (ra.id) as unique field, you can use $fields param for it.');
1722          $users = get_role_users(array($noeditteacherrole->id, $studentrole->id), $coursecontext,
1723              false, 'u.id, u.firstname', 'u.id, u.firstname');
1724          $this->assertDebuggingCalled('get_role_users() without specifying one single roleid needs to be called prefixing ' .
1725              'role assignments id (ra.id) as unique field, you can use $fields param for it.');
1726      }
1727  
1728      /**
1729       * Test used role query.
1730       *
1731       * @covers ::get_roles_used_in_context
1732       */
1733      public function test_get_roles_used_in_context() {
1734          global $DB;
1735  
1736          $this->resetAfterTest();
1737  
1738          $systemcontext = context_system::instance();
1739          $teacherrole = $DB->get_record('role', array('shortname'=>'editingteacher'), '*', MUST_EXIST);
1740          $course = $this->getDataGenerator()->create_course();
1741          $coursecontext = context_course::instance($course->id);
1742          $otherid = create_role('Other role', 'other', 'Some other role', '');
1743          $teacherrename = (object)array('roleid'=>$teacherrole->id, 'name'=>'Učitel', 'contextid'=>$coursecontext->id);
1744          $DB->insert_record('role_names', $teacherrename);
1745          $otherrename = (object)array('roleid'=>$otherid, 'name'=>'Ostatní', 'contextid'=>$coursecontext->id);
1746          $DB->insert_record('role_names', $otherrename);
1747  
1748          $user1 = $this->getDataGenerator()->create_user();
1749          role_assign($teacherrole->id, $user1->id, $coursecontext->id);
1750  
1751          $roles = get_roles_used_in_context($coursecontext);
1752          $this->assertCount(1, $roles);
1753          $role = reset($roles);
1754          $roleid = key($roles);
1755          $this->assertEquals($roleid, $role->id);
1756          $this->assertEquals($teacherrole->id, $role->id);
1757          $this->assertSame($teacherrole->name, $role->name);
1758          $this->assertSame($teacherrole->shortname, $role->shortname);
1759          $this->assertEquals($teacherrole->sortorder, $role->sortorder);
1760          $this->assertSame($teacherrename->name, $role->coursealias);
1761  
1762          $user2 = $this->getDataGenerator()->create_user();
1763          role_assign($teacherrole->id, $user2->id, $systemcontext->id);
1764          role_assign($otherid, $user2->id, $systemcontext->id);
1765  
1766          $roles = get_roles_used_in_context($systemcontext);
1767          $this->assertCount(2, $roles);
1768      }
1769  
1770      /**
1771       * Test roles used in course.
1772       *
1773       * @covers ::get_user_roles_in_course
1774       */
1775      public function test_get_user_roles_in_course() {
1776          global $DB, $CFG;
1777  
1778          $this->resetAfterTest();
1779  
1780          $teacherrole = $DB->get_record('role', array('shortname'=>'editingteacher'), '*', MUST_EXIST);
1781          $studentrole = $DB->get_record('role', array('shortname'=>'student'), '*', MUST_EXIST);
1782          $managerrole = $DB->get_record('role', array('shortname' => 'manager'), '*', MUST_EXIST);
1783          $course = $this->getDataGenerator()->create_course();
1784          $coursecontext = context_course::instance($course->id);
1785          $teacherrename = (object)array('roleid'=>$teacherrole->id, 'name'=>'Učitel', 'contextid'=>$coursecontext->id);
1786          $DB->insert_record('role_names', $teacherrename);
1787  
1788          $roleids = explode(',', $CFG->profileroles); // Should include teacher and student in new installs.
1789          $this->assertTrue(in_array($teacherrole->id, $roleids));
1790          $this->assertTrue(in_array($studentrole->id, $roleids));
1791          $this->assertFalse(in_array($managerrole->id, $roleids));
1792  
1793          $user1 = $this->getDataGenerator()->create_user();
1794          role_assign($teacherrole->id, $user1->id, $coursecontext->id);
1795          role_assign($studentrole->id, $user1->id, $coursecontext->id);
1796          $user2 = $this->getDataGenerator()->create_user();
1797          role_assign($studentrole->id, $user2->id, $coursecontext->id);
1798          $user3 = $this->getDataGenerator()->create_user();
1799          $user4 = $this->getDataGenerator()->create_user();
1800          role_assign($managerrole->id, $user4->id, $coursecontext->id);
1801  
1802          $this->setAdminUser();
1803  
1804          $roles = get_user_roles_in_course($user1->id, $course->id);
1805          $this->assertEquals([
1806              role_get_name($teacherrole, $coursecontext),
1807              role_get_name($studentrole, $coursecontext),
1808          ], array_map('strip_tags', explode(', ', $roles)));
1809  
1810          $roles = get_user_roles_in_course($user2->id, $course->id);
1811          $this->assertEquals([
1812              role_get_name($studentrole, $coursecontext),
1813          ], array_map('strip_tags', explode(', ', $roles)));
1814  
1815          $roles = get_user_roles_in_course($user3->id, $course->id);
1816          $this->assertEmpty($roles);
1817  
1818          // Managers should be able to see a link to their own role type, given they can assign it in the context.
1819          $this->setUser($user4);
1820          $roles = get_user_roles_in_course($user4->id, $course->id);
1821          $this->assertEquals([
1822              role_get_name($managerrole, $coursecontext),
1823          ], array_map('strip_tags', explode(', ', $roles)));
1824  
1825          // Managers should see 2 roles if viewing a user who has been enrolled as a student and a teacher in the course.
1826          $roles = get_user_roles_in_course($user1->id, $course->id);
1827          $this->assertEquals([
1828              role_get_name($teacherrole, $coursecontext),
1829              role_get_name($studentrole, $coursecontext),
1830          ], array_map('strip_tags', explode(', ', $roles)));
1831  
1832          // Students should not see the manager role if viewing a manager's profile.
1833          $this->setUser($user2);
1834          $roles = get_user_roles_in_course($user4->id, $course->id);
1835          $this->assertEmpty($roles); // Should see 0 roles on the manager's profile.
1836      }
1837  
1838      /**
1839       * Test get_user_roles and get_users_roles
1840       *
1841       * @covers ::get_user_roles
1842       */
1843      public function test_get_user_roles() {
1844          global $DB, $CFG;
1845  
1846          $this->resetAfterTest();
1847  
1848          $teacherrole = $DB->get_record('role', array('shortname'=>'editingteacher'), '*', MUST_EXIST);
1849          $studentrole = $DB->get_record('role', array('shortname'=>'student'), '*', MUST_EXIST);
1850          $course = $this->getDataGenerator()->create_course();
1851          $coursecontext = context_course::instance($course->id);
1852          $teacherrename = (object)array('roleid'=>$teacherrole->id, 'name'=>'Učitel', 'contextid'=>$coursecontext->id);
1853          $DB->insert_record('role_names', $teacherrename);
1854  
1855          $roleids = explode(',', $CFG->profileroles); // Should include teacher and student in new installs.
1856  
1857          $user1 = $this->getDataGenerator()->create_user();
1858          role_assign($teacherrole->id, $user1->id, $coursecontext->id);
1859          role_assign($studentrole->id, $user1->id, $coursecontext->id);
1860          $user2 = $this->getDataGenerator()->create_user();
1861          role_assign($studentrole->id, $user2->id, $coursecontext->id);
1862          $user3 = $this->getDataGenerator()->create_user();
1863  
1864          $u1roles = get_user_roles($coursecontext, $user1->id);
1865  
1866          $u2roles = get_user_roles($coursecontext, $user2->id);
1867  
1868          $allroles = get_users_roles($coursecontext, [], false);
1869          $specificuserroles = get_users_roles($coursecontext, [$user1->id, $user2->id]);
1870          $this->assertEquals($u1roles, $allroles[$user1->id]);
1871          $this->assertEquals($u1roles, $specificuserroles[$user1->id]);
1872          $this->assertEquals($u2roles, $allroles[$user2->id]);
1873          $this->assertEquals($u2roles, $specificuserroles[$user2->id]);
1874      }
1875  
1876      /**
1877       * Test has_capability(), has_any_capability() and has_all_capabilities().
1878       *
1879       * @covers ::has_capability
1880       * @covers ::has_any_capability
1881       * @covers ::has_all_capabilities
1882       */
1883      public function test_has_capability_and_friends() {
1884          global $DB;
1885  
1886          $this->resetAfterTest();
1887  
1888          $course = $this->getDataGenerator()->create_course();
1889          $coursecontext = context_course::instance($course->id);
1890          $teacherrole = $DB->get_record('role', array('shortname'=>'editingteacher'), '*', MUST_EXIST);
1891          $teacher = $this->getDataGenerator()->create_user();
1892          role_assign($teacherrole->id, $teacher->id, $coursecontext);
1893          $admin = $DB->get_record('user', array('username'=>'admin'));
1894  
1895          // Note: Here are used default capabilities, the full test is in permission evaluation bellow,
1896          // use two capabilities that teacher has and one does not, none of them should be allowed for not-logged-in user.
1897  
1898          $this->assertTrue($DB->record_exists('capabilities', array('name'=>'moodle/backup:backupsection')));
1899          $this->assertTrue($DB->record_exists('capabilities', array('name'=>'moodle/backup:backupcourse')));
1900          $this->assertTrue($DB->record_exists('capabilities', array('name'=>'moodle/site:approvecourse')));
1901  
1902          $sca = array('moodle/backup:backupsection', 'moodle/backup:backupcourse', 'moodle/site:approvecourse');
1903          $sc = array('moodle/backup:backupsection', 'moodle/backup:backupcourse');
1904  
1905          $this->setUser(0);
1906          $this->assertFalse(has_capability('moodle/backup:backupsection', $coursecontext));
1907          $this->assertFalse(has_capability('moodle/backup:backupcourse', $coursecontext));
1908          $this->assertFalse(has_capability('moodle/site:approvecourse', $coursecontext));
1909          $this->assertFalse(has_any_capability($sca, $coursecontext));
1910          $this->assertFalse(has_all_capabilities($sca, $coursecontext));
1911  
1912          $this->assertTrue(has_capability('moodle/backup:backupsection', $coursecontext, $teacher));
1913          $this->assertTrue(has_capability('moodle/backup:backupcourse', $coursecontext, $teacher));
1914          $this->assertFalse(has_capability('moodle/site:approvecourse', $coursecontext, $teacher));
1915          $this->assertTrue(has_any_capability($sca, $coursecontext, $teacher));
1916          $this->assertTrue(has_all_capabilities($sc, $coursecontext, $teacher));
1917          $this->assertFalse(has_all_capabilities($sca, $coursecontext, $teacher));
1918  
1919          $this->assertTrue(has_capability('moodle/backup:backupsection', $coursecontext, $admin));
1920          $this->assertTrue(has_capability('moodle/backup:backupcourse', $coursecontext, $admin));
1921          $this->assertTrue(has_capability('moodle/site:approvecourse', $coursecontext, $admin));
1922          $this->assertTrue(has_any_capability($sca, $coursecontext, $admin));
1923          $this->assertTrue(has_all_capabilities($sc, $coursecontext, $admin));
1924          $this->assertTrue(has_all_capabilities($sca, $coursecontext, $admin));
1925  
1926          $this->assertFalse(has_capability('moodle/backup:backupsection', $coursecontext, $admin, false));
1927          $this->assertFalse(has_capability('moodle/backup:backupcourse', $coursecontext, $admin, false));
1928          $this->assertFalse(has_capability('moodle/site:approvecourse', $coursecontext, $admin, false));
1929          $this->assertFalse(has_any_capability($sca, $coursecontext, $admin, false));
1930          $this->assertFalse(has_all_capabilities($sc, $coursecontext, $admin, false));
1931          $this->assertFalse(has_all_capabilities($sca, $coursecontext, $admin, false));
1932  
1933          $this->setUser($teacher);
1934          $this->assertTrue(has_capability('moodle/backup:backupsection', $coursecontext));
1935          $this->assertTrue(has_capability('moodle/backup:backupcourse', $coursecontext));
1936          $this->assertFalse(has_capability('moodle/site:approvecourse', $coursecontext));
1937          $this->assertTrue(has_any_capability($sca, $coursecontext));
1938          $this->assertTrue(has_all_capabilities($sc, $coursecontext));
1939          $this->assertFalse(has_all_capabilities($sca, $coursecontext));
1940  
1941          $this->setAdminUser();
1942          $this->assertTrue(has_capability('moodle/backup:backupsection', $coursecontext));
1943          $this->assertTrue(has_capability('moodle/backup:backupcourse', $coursecontext));
1944          $this->assertTrue(has_capability('moodle/site:approvecourse', $coursecontext));
1945          $this->assertTrue(has_any_capability($sca, $coursecontext));
1946          $this->assertTrue(has_all_capabilities($sc, $coursecontext));
1947          $this->assertTrue(has_all_capabilities($sca, $coursecontext));
1948  
1949          $this->assertFalse(has_capability('moodle/backup:backupsection', $coursecontext, 0));
1950          $this->assertFalse(has_capability('moodle/backup:backupcourse', $coursecontext, 0));
1951          $this->assertFalse(has_capability('moodle/site:approvecourse', $coursecontext, 0));
1952          $this->assertFalse(has_any_capability($sca, $coursecontext, 0));
1953          $this->assertFalse(has_all_capabilities($sca, $coursecontext, 0));
1954      }
1955  
1956      /**
1957       * Utility method to fake a plugin
1958       *
1959       * @param string $pluginname plugin name
1960       * @return void
1961       */
1962      protected function setup_fake_plugin($pluginname) {
1963          global $CFG;
1964          // Here we have to hack the component loader so we can insert our fake plugin and test that
1965          // the access.php works.
1966          $mockedcomponent = new ReflectionClass(core_component::class);
1967          $mockedplugins = $mockedcomponent->getProperty('plugins');
1968          $mockedplugins->setAccessible(true);
1969          $plugins = $mockedplugins->getValue();
1970          $plugins['fake'] = [$pluginname => "{$CFG->dirroot}/lib/tests/fixtures/fakeplugins/$pluginname"];
1971          $mockedplugins->setValue(null, $plugins);
1972          update_capabilities('fake_access');
1973          $this->resetDebugging(); // We have debugging messages here that we need to get rid of.
1974          // End of the component loader mock.
1975      }
1976  
1977      /**
1978       * Test get_deprecated_capability_info()
1979       *
1980       * @covers ::get_deprecated_capability_info
1981       */
1982      public function test_get_deprecated_capability_info() {
1983          $this->resetAfterTest();
1984          $course = $this->getDataGenerator()->create_course();
1985          $coursecontext = context_course::instance($course->id);
1986          $user = $this->getDataGenerator()->create_and_enrol($course);
1987          $this->setup_fake_plugin('access');
1988  
1989          // For now we have deprecated fake/access:fakecapability.
1990          $capinfo = get_deprecated_capability_info('fake/access:fakecapability');
1991          $this->assertNotEmpty($capinfo);
1992          $this->assertEquals("The capability 'fake/access:fakecapability' is"
1993              . " deprecated.This capability should not be used anymore.", $capinfo['fullmessage']);
1994      }
1995  
1996      /**
1997       * Test get_deprecated_capability_info() through has_capability
1998       *
1999       * @covers ::get_deprecated_capability_info
2000       */
2001      public function test_get_deprecated_capability_info_through_has_capability() {
2002          $this->resetAfterTest();
2003          $course = $this->getDataGenerator()->create_course();
2004          $coursecontext = context_course::instance($course->id);
2005          $user = $this->getDataGenerator()->create_and_enrol($course);
2006          $this->setup_fake_plugin('access');
2007  
2008          // For now we have deprecated fake/access:fakecapability.
2009          $hascap = has_capability('fake/access:fakecapability', $coursecontext, $user);
2010          $this->assertTrue($hascap);
2011          $this->assertDebuggingCalled("The capability 'fake/access:fakecapability' is deprecated."
2012              . "This capability should not be used anymore.");
2013      }
2014  
2015      /**
2016       * Test get_deprecated_capability_info() through get_user_capability_contexts()
2017       *
2018       * @covers ::get_deprecated_capability_info
2019       */
2020      public function test_get_deprecated_capability_info_through_get_user_capability_contexts() {
2021          $this->resetAfterTest();
2022          $category = $this->getDataGenerator()->create_category();
2023          $course = $this->getDataGenerator()->create_course(['categoryid' => $category->id]);
2024          $user = $this->getDataGenerator()->create_and_enrol($course);
2025          $this->setup_fake_plugin('access');
2026  
2027          // For now we have deprecated fake/access:fakecapability.
2028          list($categories, $courses) = get_user_capability_contexts('fake/access:fakecapability', false, $user->id);
2029          $this->assertNotEmpty($courses);
2030          $this->assertDebuggingCalled("The capability 'fake/access:fakecapability' is deprecated."
2031                  . "This capability should not be used anymore.");
2032      }
2033  
2034      /**
2035       * Test get_deprecated_capability_info with a capability that does not exist
2036       *
2037       * @param string $capability the capability name
2038       * @param array $debugmessages the debug messsages we expect
2039       * @param bool $expectedexisting does the capability exist
2040       * @covers ::get_deprecated_capability_info
2041       * @dataProvider deprecated_capabilities_use_cases
2042       */
2043      public function test_get_deprecated_capability_specific_cases(string $capability, array $debugmessages,
2044          bool $expectedexisting) {
2045          $this->resetAfterTest();
2046          $course = $this->getDataGenerator()->create_course();
2047          $coursecontext = context_course::instance($course->id);
2048          $user = $this->getDataGenerator()->create_and_enrol($course);
2049          $this->setup_fake_plugin('access');
2050  
2051          // For now we have deprecated fake/access:fakecapability.
2052          $this->resetDebugging();
2053          $hascap = has_capability($capability, $coursecontext, $user);
2054          $this->assertEquals($expectedexisting, $hascap);
2055          $this->assertDebuggingCalledCount(count($debugmessages), $debugmessages);
2056      }
2057  
2058      /**
2059       * Specific use case for deprecated capabilities
2060       *
2061       * @return array
2062       */
2063      public function deprecated_capabilities_use_cases() {
2064          return [
2065              'capability missing' => [
2066                  'fake/access:missingcapability',
2067                  [
2068                      "Capability \"fake/access:missingcapability\" was not found! This has to be fixed in code."
2069                  ],
2070                  false
2071              ],
2072              'replacement no info' => [
2073                  'fake/access:replacementnoinfo',
2074                  [
2075                      "The capability 'fake/access:replacementnoinfo' is deprecated.",
2076                  ],
2077                  true
2078              ],
2079              'replacement missing' => [
2080                  'fake/access:replacementmissing',
2081                  [
2082                      "The capability 'fake/access:replacementmissing' is deprecated.This capability should not be used anymore.",
2083                  ],
2084                  true
2085              ],
2086              'replacement with non existing cap' => [
2087                  'fake/access:replacementwithwrongcapability',
2088                  [
2089                      "Capability 'fake/access:replacementwithwrongcapability' was supposed to be replaced with"
2090                      . " 'fake/access:nonexistingcapabilty', which does not exist !",
2091                      "The capability 'fake/access:replacementwithwrongcapability' is deprecated."
2092                      . "This capability should not be used anymore.It will be replaced by 'fake/access:nonexistingcapabilty'."
2093                  ],
2094                  true
2095              ],
2096              'replacement with existing' => [
2097                  'fake/access:replacementwithexisting', // Existing capability buf for a different role.
2098                  [
2099                      "The capability 'fake/access:replacementwithexisting' is deprecated.This capability should not be used anymore."
2100                      . "It will be replaced by 'fake/access:existingcapability'.",
2101                  ],
2102                  false // As the capability is applied to managers, we should not have this capability for this simple user.
2103              ],
2104          ];
2105      }
2106  
2107      /**
2108       * Test that assigning a fake cap does not return.
2109       *
2110       * @covers ::get_users_by_capability
2111       * @covers ::get_with_capability_join
2112       * @covers ::get_with_capability_sql
2113       * @covers ::has_capability
2114       */
2115      public function test_fake_capability() {
2116          global $DB;
2117  
2118          $this->resetAfterTest();
2119  
2120          $course = $this->getDataGenerator()->create_course();
2121          $coursecontext = context_course::instance($course->id);
2122          $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'), '*', MUST_EXIST);
2123          $teacher = $this->getDataGenerator()->create_user();
2124  
2125          $fakecapname = 'moodle/fake:capability';
2126  
2127          role_assign($teacherrole->id, $teacher->id, $coursecontext);
2128          $admin = $DB->get_record('user', array('username' => 'admin'));
2129  
2130          // Test a capability which does not exist.
2131          // Note: Do not use assign_capability because it will not allow fake caps.
2132          $DB->insert_record('role_capabilities', (object) [
2133              'contextid' => $coursecontext->id,
2134              'roleid' => $teacherrole->id,
2135              'capability' => $fakecapname,
2136              'permission' => CAP_ALLOW,
2137              'timemodified' => time(),
2138              'modifierid' => 0,
2139          ]);
2140  
2141          // Check `has_capability`.
2142          $this->assertFalse(has_capability($fakecapname, $coursecontext, $teacher));
2143          $this->assertDebuggingCalled("Capability \"{$fakecapname}\" was not found! This has to be fixed in code.");
2144          $this->assertFalse(has_capability($fakecapname, $coursecontext, $admin));
2145          $this->assertDebuggingCalled("Capability \"{$fakecapname}\" was not found! This has to be fixed in code.");
2146  
2147          // Check `get_with_capability_sql` (with uses `get_with_capability_join`).
2148          list($sql, $params) = get_with_capability_sql($coursecontext, $fakecapname);
2149          $users = $DB->get_records_sql($sql, $params);
2150  
2151          $this->assertFalse(array_key_exists($teacher->id, $users));
2152          $this->assertFalse(array_key_exists($admin->id, $users));
2153  
2154          // Check `get_users_by_capability`.
2155          $users = get_users_by_capability($coursecontext, $fakecapname);
2156  
2157          $this->assertFalse(array_key_exists($teacher->id, $users));
2158          $this->assertFalse(array_key_exists($admin->id, $users));
2159      }
2160  
2161      /**
2162       * Test that assigning a fake cap does not return.
2163       *
2164       * @covers ::assign_capability
2165       */
2166      public function test_fake_capability_assign() {
2167          global $DB;
2168  
2169          $this->resetAfterTest();
2170  
2171          $course = $this->getDataGenerator()->create_course();
2172          $coursecontext = context_course::instance($course->id);
2173          $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'), '*', MUST_EXIST);
2174          $teacher = $this->getDataGenerator()->create_user();
2175  
2176          $capability = 'moodle/fake:capability';
2177  
2178          role_assign($teacherrole->id, $teacher->id, $coursecontext);
2179          $admin = $DB->get_record('user', array('username' => 'admin'));
2180  
2181          $this->expectException('coding_exception');
2182          $this->expectExceptionMessage("Capability '{$capability}' was not found! This has to be fixed in code.");
2183          assign_capability($capability, CAP_ALLOW, $teacherrole->id, $coursecontext);
2184      }
2185  
2186      /**
2187       * Test that assigning a fake cap does not return.
2188       *
2189       * @covers ::unassign_capability
2190       */
2191      public function test_fake_capability_unassign() {
2192          global $DB;
2193  
2194          $this->resetAfterTest();
2195  
2196          $course = $this->getDataGenerator()->create_course();
2197          $coursecontext = context_course::instance($course->id);
2198          $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'), '*', MUST_EXIST);
2199          $teacher = $this->getDataGenerator()->create_user();
2200  
2201          $capability = 'moodle/fake:capability';
2202  
2203          role_assign($teacherrole->id, $teacher->id, $coursecontext);
2204          $admin = $DB->get_record('user', array('username' => 'admin'));
2205  
2206          $this->expectException('coding_exception');
2207          $this->expectExceptionMessage("Capability '{$capability}' was not found! This has to be fixed in code.");
2208          unassign_capability($capability, CAP_ALLOW, $teacherrole->id, $coursecontext);
2209      }
2210  
2211      /**
2212       * Test that the caching in get_role_definitions() and get_role_definitions_uncached()
2213       * works as intended.
2214       *
2215       * @covers ::get_role_definitions
2216       * @covers ::role_change_permission
2217       */
2218      public function test_role_definition_caching() {
2219          global $DB;
2220  
2221          $this->resetAfterTest();
2222  
2223          // Get some role ids.
2224          $authenticatedrole = $DB->get_record('role', array('shortname' => 'user'), '*', MUST_EXIST);
2225          $studentrole = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
2226          $emptyroleid = create_role('No capabilities', 'empty', 'A role with no capabilties');
2227          $course = $this->getDataGenerator()->create_course();
2228          $coursecontext = context_course::instance($course->id);
2229  
2230          // Instantiate the cache instance, since that does DB queries (get_config)
2231          // and we don't care about those.
2232          cache::make('core', 'roledefs');
2233  
2234          // One database query is not necessarily one database read, it seems. Find out how many.
2235          $startdbreads = $DB->perf_get_reads();
2236          $rs = $DB->get_recordset('user');
2237          $rs->close();
2238          $readsperquery = $DB->perf_get_reads() - $startdbreads;
2239  
2240          // Now load some role definitions, and check when it queries the database.
2241  
2242          // Load the capabilities for two roles. Should be one query.
2243          $startdbreads = $DB->perf_get_reads();
2244          get_role_definitions([$authenticatedrole->id, $studentrole->id]);
2245          $this->assertEquals(1 * $readsperquery, $DB->perf_get_reads() - $startdbreads);
2246  
2247          // Load the capabilities for same two roles. Should not query the DB.
2248          $startdbreads = $DB->perf_get_reads();
2249          get_role_definitions([$authenticatedrole->id, $studentrole->id]);
2250          $this->assertEquals(0 * $readsperquery, $DB->perf_get_reads() - $startdbreads);
2251  
2252          // Include a third role. Should do one DB query.
2253          $startdbreads = $DB->perf_get_reads();
2254          get_role_definitions([$authenticatedrole->id, $studentrole->id, $emptyroleid]);
2255          $this->assertEquals(1 * $readsperquery, $DB->perf_get_reads() - $startdbreads);
2256  
2257          // Repeat call. No DB queries.
2258          $startdbreads = $DB->perf_get_reads();
2259          get_role_definitions([$authenticatedrole->id, $studentrole->id, $emptyroleid]);
2260          $this->assertEquals(0 * $readsperquery, $DB->perf_get_reads() - $startdbreads);
2261  
2262          // Alter a role.
2263          role_change_permission($studentrole->id, $coursecontext, 'moodle/course:tag', CAP_ALLOW);
2264  
2265          // Should now know to do one query.
2266          $startdbreads = $DB->perf_get_reads();
2267          get_role_definitions([$authenticatedrole->id, $studentrole->id]);
2268          $this->assertEquals(1 * $readsperquery, $DB->perf_get_reads() - $startdbreads);
2269  
2270          // Now clear the in-memory cache, and verify that it does not query the DB.
2271          // Cannot use accesslib_clear_all_caches_for_unit_testing since that also
2272          // clears the MUC cache.
2273          global $ACCESSLIB_PRIVATE;
2274          $ACCESSLIB_PRIVATE->cacheroledefs = array();
2275  
2276          // Get all roles. Should not need the DB.
2277          $startdbreads = $DB->perf_get_reads();
2278          get_role_definitions([$authenticatedrole->id, $studentrole->id, $emptyroleid]);
2279          $this->assertEquals(0 * $readsperquery, $DB->perf_get_reads() - $startdbreads);
2280      }
2281  
2282      /**
2283       * Tests get_user_capability_course() which checks a capability across all courses.
2284       *
2285       * @covers ::get_user_capability_course
2286       */
2287      public function test_get_user_capability_course() {
2288          global $CFG, $USER;
2289  
2290          $this->resetAfterTest();
2291  
2292          $generator = $this->getDataGenerator();
2293          $cap = 'moodle/course:view';
2294  
2295          // The structure being created here is this:
2296          //
2297          // All tests work with the single capability 'moodle/course:view'.
2298          //
2299          //             ROLE DEF/OVERRIDE                        ROLE ASSIGNS
2300          //    Role:  Allow    Prohib    Empty   Def user      u1  u2  u3  u4   u5  u6  u7  u8
2301          // System    ALLOW    PROHIBIT                            A   E   A+E
2302          //   cat1                       ALLOW
2303          //     C1                               (ALLOW)                            P
2304          //     C2             ALLOW                                                    E   P
2305          //     cat2                     PREVENT
2306          //       C3                     ALLOW                                      E
2307          //       C4
2308          //   Misc.                                                             A
2309          //     C5    PREVENT                                                       A
2310          //     C6                       PROHIBIT
2311          //
2312          // Front-page and guest role stuff from the end of this test not included in the diagram.
2313  
2314          // Create a role which allows course:view and one that prohibits it, and one neither.
2315          $allowroleid = $generator->create_role();
2316          $prohibitroleid = $generator->create_role();
2317          $emptyroleid = $generator->create_role();
2318          $systemcontext = context_system::instance();
2319          assign_capability($cap, CAP_ALLOW, $allowroleid, $systemcontext->id);
2320          assign_capability($cap, CAP_PROHIBIT, $prohibitroleid, $systemcontext->id);
2321  
2322          // Create two categories (nested).
2323          $cat1 = $generator->create_category();
2324          $cat2 = $generator->create_category(['parent' => $cat1->id]);
2325  
2326          // Create six courses - two in cat1, two in cat2, and two in default category.
2327          // Shortnames are used for a sorting test. Otherwise they are not significant.
2328          $c1 = $generator->create_course(['category' => $cat1->id, 'shortname' => 'Z']);
2329          $c2 = $generator->create_course(['category' => $cat1->id, 'shortname' => 'Y']);
2330          $c3 = $generator->create_course(['category' => $cat2->id, 'shortname' => 'X']);
2331          $c4 = $generator->create_course(['category' => $cat2->id]);
2332          $c5 = $generator->create_course();
2333          $c6 = $generator->create_course();
2334  
2335          // Category overrides: in cat 1, empty role is allowed; in cat 2, empty role is prevented.
2336          assign_capability($cap, CAP_ALLOW, $emptyroleid,
2337                  context_coursecat::instance($cat1->id)->id);
2338          assign_capability($cap, CAP_PREVENT, $emptyroleid,
2339                  context_coursecat::instance($cat2->id)->id);
2340  
2341          // Course overrides: in C5, allow role is prevented; in C6, empty role is prohibited; in
2342          // C3, empty role is allowed.
2343          assign_capability($cap, CAP_PREVENT, $allowroleid,
2344                  context_course::instance($c5->id)->id);
2345          assign_capability($cap, CAP_PROHIBIT, $emptyroleid,
2346                  context_course::instance($c6->id)->id);
2347          assign_capability($cap, CAP_ALLOW, $emptyroleid,
2348                  context_course::instance($c3->id)->id);
2349          assign_capability($cap, CAP_ALLOW, $prohibitroleid,
2350                  context_course::instance($c2->id)->id);
2351  
2352          // User 1 has no roles except default user role.
2353          $u1 = $generator->create_user();
2354  
2355          // It returns false (annoyingly) if there are no courses.
2356          $this->assertFalse(get_user_capability_course($cap, $u1->id, true, '', 'id'));
2357  
2358          // Final override: in C1, default user role is allowed.
2359          assign_capability($cap, CAP_ALLOW, $CFG->defaultuserroleid,
2360                  context_course::instance($c1->id)->id);
2361  
2362          // Should now get C1 only.
2363          $courses = get_user_capability_course($cap, $u1->id, true, '', 'id');
2364          $this->assert_course_ids([$c1->id], $courses);
2365  
2366          // User 2 has allow role (system wide).
2367          $u2 = $generator->create_user();
2368          role_assign($allowroleid, $u2->id, $systemcontext->id);
2369  
2370          // Should get everything except C5.
2371          $courses = get_user_capability_course($cap, $u2->id, true, '', 'id');
2372          $this->assert_course_ids([SITEID, $c1->id, $c2->id, $c3->id, $c4->id, $c6->id], $courses);
2373  
2374          // User 3 has empty role (system wide).
2375          $u3 = $generator->create_user();
2376          role_assign($emptyroleid, $u3->id, $systemcontext->id);
2377  
2378          // Should get cat 1 courses but not cat2, except C3.
2379          $courses = get_user_capability_course($cap, $u3->id, true, '', 'id');
2380          $this->assert_course_ids([$c1->id, $c2->id, $c3->id], $courses);
2381  
2382          // User 4 has allow and empty role (system wide).
2383          $u4 = $generator->create_user();
2384          role_assign($allowroleid, $u4->id, $systemcontext->id);
2385          role_assign($emptyroleid, $u4->id, $systemcontext->id);
2386  
2387          // Should get everything except C5 and C6.
2388          $courses = get_user_capability_course($cap, $u4->id, true, '', 'id');
2389          $this->assert_course_ids([SITEID, $c1->id, $c2->id, $c3->id, $c4->id], $courses);
2390  
2391          // User 5 has allow role in default category only.
2392          $u5 = $generator->create_user();
2393          role_assign($allowroleid, $u5->id, context_coursecat::instance($c5->category)->id);
2394  
2395          // Should get C1 and the default category courses but not C5.
2396          $courses = get_user_capability_course($cap, $u5->id, true, '', 'id');
2397          $this->assert_course_ids([$c1->id, $c6->id], $courses);
2398  
2399          // User 6 has a bunch of course roles: prohibit role in C1, empty role in C3, allow role in
2400          // C6.
2401          $u6 = $generator->create_user();
2402          role_assign($prohibitroleid, $u6->id, context_course::instance($c1->id)->id);
2403          role_assign($emptyroleid, $u6->id, context_course::instance($c3->id)->id);
2404          role_assign($allowroleid, $u6->id, context_course::instance($c5->id)->id);
2405  
2406          // Should get C3 only because the allow role is prevented in C5.
2407          $courses = get_user_capability_course($cap, $u6->id, true, '', 'id');
2408          $this->assert_course_ids([$c3->id], $courses);
2409  
2410          // User 7 has empty role in C2.
2411          $u7 = $generator->create_user();
2412          role_assign($emptyroleid, $u7->id, context_course::instance($c2->id)->id);
2413  
2414          // Should get C1 by the default user role override, and C2 by the cat1 level override.
2415          $courses = get_user_capability_course($cap, $u7->id, true, '', 'id');
2416          $this->assert_course_ids([$c1->id, $c2->id], $courses);
2417  
2418          // User 8 has prohibit role as system context, to verify that prohibits can't be overridden.
2419          $u8 = $generator->create_user();
2420          role_assign($prohibitroleid, $u8->id, context_course::instance($c2->id)->id);
2421  
2422          // Should get C1 by the default user role override, no other courses because the prohibit cannot be overridden.
2423          $courses = get_user_capability_course($cap, $u8->id, true, '', 'id');
2424          $this->assert_course_ids([$c1->id], $courses);
2425  
2426          // Admin user gets everything....
2427          $courses = get_user_capability_course($cap, get_admin()->id, true, '', 'id');
2428          $this->assert_course_ids([SITEID, $c1->id, $c2->id, $c3->id, $c4->id, $c5->id, $c6->id],
2429                  $courses);
2430  
2431          // Unless you turn off doanything, when it only has the things a user with no role does.
2432          $courses = get_user_capability_course($cap, get_admin()->id, false, '', 'id');
2433          $this->assert_course_ids([$c1->id], $courses);
2434  
2435          // Using u3 as an example, test the limit feature.
2436          $courses = get_user_capability_course($cap, $u3->id, true, '', 'id', 2);
2437          $this->assert_course_ids([$c1->id, $c2->id], $courses);
2438  
2439          // Check sorting.
2440          $courses = get_user_capability_course($cap, $u3->id, true, '', 'shortname');
2441          $this->assert_course_ids([$c3->id, $c2->id, $c1->id], $courses);
2442  
2443          // Check returned fields - default.
2444          $courses = get_user_capability_course($cap, $u3->id, true, '', 'id');
2445          $this->assertEquals((object)['id' => $c1->id], $courses[0]);
2446  
2447          // Add a selection of fields, including the context ones with special handling.
2448          $courses = get_user_capability_course($cap, $u3->id, true, 'shortname, ctxlevel, ctxdepth, ctxinstance', 'id');
2449          $this->assertEquals((object)['id' => $c1->id, 'shortname' => 'Z', 'ctxlevel' => 50,
2450                  'ctxdepth' => 3, 'ctxinstance' => $c1->id], $courses[0]);
2451  
2452          // Test front page role - user 1 has no roles, but if we change the front page role
2453          // definition so that it has our capability, then they should see the front page course.
2454          // as well as C1.
2455          assign_capability($cap, CAP_ALLOW, $CFG->defaultfrontpageroleid, $systemcontext->id);
2456          $courses = get_user_capability_course($cap, $u1->id, true, '', 'id');
2457          $this->assert_course_ids([SITEID, $c1->id], $courses);
2458  
2459          // Check that temporary guest access (in this case, given on course 2 for user 1)
2460          // also is included, if it has this capability.
2461          assign_capability($cap, CAP_ALLOW, $CFG->guestroleid, $systemcontext->id);
2462          $this->setUser($u1);
2463          load_temp_course_role(context_course::instance($c2->id), $CFG->guestroleid);
2464          $courses = get_user_capability_course($cap, $USER->id, true, '', 'id');
2465          $this->assert_course_ids([SITEID, $c1->id, $c2->id], $courses);
2466      }
2467  
2468      /**
2469       * Tests get_user_capability_contexts() which checks a capability across all courses and categories.
2470       * Testing for categories only because courses results are covered by test_get_user_capability_course.
2471       *
2472       * @covers ::get_user_capability_contexts
2473       */
2474      public function test_get_user_capability_contexts() {
2475          $this->resetAfterTest();
2476  
2477          $generator = $this->getDataGenerator();
2478          $cap = 'moodle/contentbank:access';
2479          $defaultcategoryid = 1;
2480  
2481  //         The structure being created here is this:
2482  //
2483  //         All tests work with the single capability 'moodle/contentbank:access'.
2484  //         ROLE DEF/OVERRIDE                                                    .
2485  //         Role:                Allow       Prohibit        Empty               .
2486  //                  System      ALLOW       PROHIBIT                            .
2487  //                  cat1        PREVENT     ALLOW           ALLOW               .
2488  //                      cat3    ALLOW       PROHIBIT                            .
2489  //                 cat2        PROHIBIT    PROHIBIT        PROHIBIT             .
2490  
2491          // Create a role which allows contentbank:access and one that prohibits it, and one neither.
2492          $allowroleid = $generator->create_role();
2493          $prohibitroleid = $generator->create_role();
2494          $emptyroleid = $generator->create_role();
2495          $systemcontext = context_system::instance();
2496          assign_capability($cap, CAP_ALLOW, $allowroleid, $systemcontext->id);
2497          assign_capability($cap, CAP_PROHIBIT, $prohibitroleid, $systemcontext->id);
2498  
2499          // Create three categories (two of them nested).
2500          $cat1 = $generator->create_category(['name' => 'Aardvarks']);
2501          $cat2 = $generator->create_category(['name' => 'Badgers']);
2502          $cat3 = $generator->create_category(['parent' => $cat1->id, 'name' => 'Cheetahs']);
2503  
2504          // Category overrides: in cat 1, empty role is allowed; in cat 2, empty role is prevented.
2505          assign_capability($cap, CAP_ALLOW, $emptyroleid,
2506              context_coursecat::instance($cat1->id)->id);
2507          assign_capability($cap, CAP_PREVENT, $emptyroleid,
2508              context_coursecat::instance($cat2->id)->id);
2509  
2510          // Course category overrides: in cat1, allow role is prevented and prohibit role is allowed;
2511          // in Cat2, allow role is prohibited.
2512          assign_capability($cap, CAP_PREVENT, $allowroleid,
2513              context_coursecat::instance($cat1->id)->id);
2514          assign_capability($cap, CAP_ALLOW, $prohibitroleid,
2515              context_coursecat::instance($cat1->id)->id);
2516          assign_capability($cap, CAP_PROHIBIT, $allowroleid,
2517              context_coursecat::instance($cat2->id)->id);
2518  
2519          // User 1 has no roles except default user role.
2520          $u1 = $generator->create_user();
2521  
2522          // It returns false (annoyingly) if there are no course categories.
2523          list($categories, $courses) = get_user_capability_contexts($cap, true, $u1->id);
2524          $this->assertFalse($categories);
2525  
2526          // User 2 has allow role (system wide).
2527          $u2 = $generator->create_user();
2528          role_assign($allowroleid, $u2->id, $systemcontext->id);
2529  
2530          // Should get $defaultcategory only. cat2 is prohibited; cat1 is prevented, so cat3 is not allowed.
2531          list($categories, $courses) = get_user_capability_contexts($cap, true, $u2->id);
2532          // Using same assert_course_ids helper even when we are checking course category ids.
2533          $this->assert_course_ids([$defaultcategoryid], $categories);
2534  
2535          // User 3 has empty role (system wide).
2536          $u3 = $generator->create_user();
2537          role_assign($emptyroleid, $u3->id, $systemcontext->id);
2538  
2539          // Should get cat1 and cat3. cat2 is prohibited; no access to system level. Sorted by category name.
2540          list($categories, $courses) = get_user_capability_contexts($cap, true, $u3->id, true, '', '', '', 'name');
2541          $this->assert_course_ids([$cat1->id, $cat3->id], $categories);
2542  
2543          // User 4 has prohibit role (system wide).
2544          $u4 = $generator->create_user();
2545          role_assign($prohibitroleid, $u4->id, $systemcontext->id);
2546  
2547          // Should not get any, because all of them are prohibited at system level.
2548          // Even if we try to allow an specific category.
2549          list($categories, $courses) = get_user_capability_contexts($cap, true, $u4->id);
2550          $this->assertFalse($categories);
2551      }
2552  
2553      /**
2554       * Extracts an array of course ids to make the above test script shorter.
2555       *
2556       * @param int[] $expected Array of expected course ids
2557       * @param stdClass[] $courses Array of course objects
2558       */
2559      protected function assert_course_ids(array $expected, array $courses) {
2560          $courseids = array_map(function($c) {
2561              return $c->id;
2562          }, $courses);
2563          $this->assertEquals($expected, $courseids);
2564      }
2565  
2566      /**
2567       * Test if course creator future capability lookup works.
2568       *
2569       * @covers ::guess_if_creator_will_have_course_capability
2570       * @covers ::has_capability
2571       */
2572      public function test_guess_if_creator_will_have_course_capability() {
2573          global $DB, $CFG, $USER;
2574  
2575          $this->resetAfterTest();
2576  
2577          $category = $this->getDataGenerator()->create_category();
2578          $course = $this->getDataGenerator()->create_course(array('category'=>$category->id));
2579  
2580          $syscontext = context_system::instance();
2581          $categorycontext = context_coursecat::instance($category->id);
2582          $coursecontext = context_course::instance($course->id);
2583          $studentrole = $DB->get_record('role', array('shortname'=>'student'), '*', MUST_EXIST);
2584          $teacherrole = $DB->get_record('role', array('shortname'=>'editingteacher'), '*', MUST_EXIST);
2585          $creatorrole = $DB->get_record('role', array('shortname'=>'coursecreator'), '*', MUST_EXIST);
2586          $managerrole = $DB->get_record('role', array('shortname'=>'manager'), '*', MUST_EXIST);
2587  
2588          $this->assertEquals($teacherrole->id, $CFG->creatornewroleid);
2589  
2590          $creator = $this->getDataGenerator()->create_user();
2591          $manager = $this->getDataGenerator()->create_user();
2592          role_assign($managerrole->id, $manager->id, $categorycontext);
2593  
2594          $this->assertFalse(has_capability('moodle/course:view', $categorycontext, $creator));
2595          $this->assertFalse(has_capability('moodle/role:assign', $categorycontext, $creator));
2596          $this->assertFalse(has_capability('moodle/course:visibility', $categorycontext, $creator));
2597          $this->assertFalse(has_capability('moodle/course:visibility', $coursecontext, $creator));
2598          $this->assertFalse(guess_if_creator_will_have_course_capability('moodle/course:visibility', $categorycontext, $creator));
2599          $this->assertFalse(guess_if_creator_will_have_course_capability('moodle/course:visibility', $coursecontext, $creator));
2600  
2601          $this->assertTrue(has_capability('moodle/role:assign', $categorycontext, $manager));
2602          $this->assertTrue(has_capability('moodle/course:visibility', $categorycontext, $manager));
2603          $this->assertTrue(has_capability('moodle/course:visibility', $coursecontext, $manager));
2604          $this->assertTrue(guess_if_creator_will_have_course_capability('moodle/course:visibility', $categorycontext, $manager->id));
2605          $this->assertTrue(guess_if_creator_will_have_course_capability('moodle/course:visibility', $coursecontext, $manager->id));
2606  
2607          $this->assertEquals(0, $USER->id);
2608          $this->assertFalse(has_capability('moodle/course:view', $categorycontext));
2609          $this->assertFalse(has_capability('moodle/role:assign', $categorycontext));
2610          $this->assertFalse(has_capability('moodle/course:visibility', $categorycontext));
2611          $this->assertFalse(has_capability('moodle/course:visibility', $coursecontext));
2612          $this->assertFalse(guess_if_creator_will_have_course_capability('moodle/course:visibility', $categorycontext));
2613          $this->assertFalse(guess_if_creator_will_have_course_capability('moodle/course:visibility', $coursecontext));
2614  
2615          $this->setUser($manager);
2616          $this->assertTrue(has_capability('moodle/role:assign', $categorycontext));
2617          $this->assertTrue(has_capability('moodle/course:visibility', $categorycontext));
2618          $this->assertTrue(has_capability('moodle/course:visibility', $coursecontext));
2619          $this->assertTrue(guess_if_creator_will_have_course_capability('moodle/course:visibility', $categorycontext));
2620          $this->assertTrue(guess_if_creator_will_have_course_capability('moodle/course:visibility', $coursecontext));
2621  
2622          $this->setAdminUser();
2623          $this->assertTrue(has_capability('moodle/role:assign', $categorycontext));
2624          $this->assertTrue(has_capability('moodle/course:visibility', $categorycontext));
2625          $this->assertTrue(has_capability('moodle/course:visibility', $coursecontext));
2626          $this->assertTrue(guess_if_creator_will_have_course_capability('moodle/course:visibility', $categorycontext));
2627          $this->assertTrue(guess_if_creator_will_have_course_capability('moodle/course:visibility', $coursecontext));
2628          $this->setUser(0);
2629  
2630          role_assign($creatorrole->id, $creator->id, $categorycontext);
2631  
2632          $this->assertFalse(has_capability('moodle/role:assign', $categorycontext, $creator));
2633          $this->assertFalse(has_capability('moodle/course:visibility', $categorycontext, $creator));
2634          $this->assertFalse(has_capability('moodle/course:visibility', $coursecontext, $creator));
2635          $this->assertTrue(guess_if_creator_will_have_course_capability('moodle/course:visibility', $categorycontext, $creator));
2636          $this->assertTrue(guess_if_creator_will_have_course_capability('moodle/course:visibility', $coursecontext, $creator));
2637  
2638          $this->setUser($creator);
2639          $this->assertFalse(has_capability('moodle/role:assign', $categorycontext, null));
2640          $this->assertFalse(has_capability('moodle/course:visibility', $categorycontext, null));
2641          $this->assertFalse(has_capability('moodle/course:visibility', $coursecontext, null));
2642          $this->assertTrue(guess_if_creator_will_have_course_capability('moodle/course:visibility', $categorycontext, null));
2643          $this->assertTrue(guess_if_creator_will_have_course_capability('moodle/course:visibility', $coursecontext, null));
2644          $this->setUser(0);
2645  
2646          set_config('creatornewroleid', $studentrole->id);
2647  
2648          $this->assertFalse(has_capability('moodle/course:visibility', $categorycontext, $creator));
2649          $this->assertFalse(has_capability('moodle/course:visibility', $coursecontext, $creator));
2650          $this->assertFalse(guess_if_creator_will_have_course_capability('moodle/course:visibility', $categorycontext, $creator));
2651          $this->assertFalse(guess_if_creator_will_have_course_capability('moodle/course:visibility', $coursecontext, $creator));
2652  
2653          set_config('creatornewroleid', $teacherrole->id);
2654  
2655          role_change_permission($managerrole->id, $categorycontext, 'moodle/course:visibility', CAP_PREVENT);
2656          role_assign($creatorrole->id, $manager->id, $categorycontext);
2657  
2658          $this->assertTrue(has_capability('moodle/course:view', $categorycontext, $manager));
2659          $this->assertTrue(has_capability('moodle/course:view', $coursecontext, $manager));
2660          $this->assertTrue(has_capability('moodle/role:assign', $categorycontext, $manager));
2661          $this->assertTrue(has_capability('moodle/role:assign', $coursecontext, $manager));
2662          $this->assertFalse(has_capability('moodle/course:visibility', $categorycontext, $manager));
2663          $this->assertFalse(has_capability('moodle/course:visibility', $coursecontext, $manager));
2664          $this->assertFalse(guess_if_creator_will_have_course_capability('moodle/course:visibility', $categorycontext, $manager));
2665          $this->assertFalse(guess_if_creator_will_have_course_capability('moodle/course:visibility', $coursecontext, $manager));
2666  
2667          role_change_permission($managerrole->id, $categorycontext, 'moodle/course:view', CAP_PREVENT);
2668          $this->assertTrue(has_capability('moodle/role:assign', $categorycontext, $manager));
2669          $this->assertFalse(has_capability('moodle/course:visibility', $categorycontext, $manager));
2670          $this->assertFalse(has_capability('moodle/course:visibility', $coursecontext, $manager));
2671          $this->assertTrue(guess_if_creator_will_have_course_capability('moodle/course:visibility', $categorycontext, $manager));
2672          $this->assertTrue(guess_if_creator_will_have_course_capability('moodle/course:visibility', $coursecontext, $manager));
2673  
2674          $this->getDataGenerator()->enrol_user($manager->id, $course->id, 0);
2675  
2676          $this->assertTrue(has_capability('moodle/role:assign', $categorycontext, $manager));
2677          $this->assertTrue(has_capability('moodle/role:assign', $coursecontext, $manager));
2678          $this->assertTrue(is_enrolled($coursecontext, $manager));
2679          $this->assertFalse(has_capability('moodle/course:visibility', $categorycontext, $manager));
2680          $this->assertFalse(has_capability('moodle/course:visibility', $coursecontext, $manager));
2681          $this->assertTrue(guess_if_creator_will_have_course_capability('moodle/course:visibility', $categorycontext, $manager));
2682          $this->assertFalse(guess_if_creator_will_have_course_capability('moodle/course:visibility', $coursecontext, $manager));
2683  
2684          // Test problems.
2685  
2686          try {
2687              guess_if_creator_will_have_course_capability('moodle/course:visibility', $syscontext, $creator);
2688              $this->fail('Exception expected when non course/category context passed to guess_if_creator_will_have_course_capability()');
2689          } catch (moodle_exception $e) {
2690              $this->assertInstanceOf('coding_exception', $e);
2691          }
2692      }
2693  
2694      /**
2695       * Test require_capability() exceptions.
2696       *
2697       * @covers ::require_capability
2698       */
2699      public function test_require_capability() {
2700          $this->resetAfterTest();
2701  
2702          $syscontext = context_system::instance();
2703  
2704          $this->setUser(0);
2705          $this->assertFalse(has_capability('moodle/site:config', $syscontext));
2706          try {
2707              require_capability('moodle/site:config', $syscontext);
2708              $this->fail('Exception expected from require_capability()');
2709          } catch (moodle_exception $e) {
2710              $this->assertInstanceOf('required_capability_exception', $e);
2711          }
2712          $this->setAdminUser();
2713          $this->assertFalse(has_capability('moodle/site:config', $syscontext, 0));
2714          try {
2715              require_capability('moodle/site:config', $syscontext, 0);
2716              $this->fail('Exception expected from require_capability()');
2717          } catch (moodle_exception $e) {
2718              $this->assertInstanceOf('required_capability_exception', $e);
2719          }
2720          $this->assertFalse(has_capability('moodle/site:config', $syscontext, null, false));
2721          try {
2722              require_capability('moodle/site:config', $syscontext, null, false);
2723              $this->fail('Exception expected from require_capability()');
2724          } catch (moodle_exception $e) {
2725              $this->assertInstanceOf('required_capability_exception', $e);
2726          }
2727      }
2728  
2729      /**
2730       * Test that enrolled users SQL does not return any values for users in
2731       * other courses.
2732       *
2733       *
2734       * @covers ::get_enrolled_users
2735       * @covers ::get_enrolled_sql
2736       * @covers ::get_enrolled_with_capabilities_join
2737       * @covers ::get_enrolled_join
2738       * @covers ::get_with_capability_join
2739       * @covers ::groups_get_members_join
2740       * @covers ::get_suspended_userids
2741       */
2742      public function test_get_enrolled_sql_different_course() {
2743          global $DB;
2744  
2745          $this->resetAfterTest();
2746  
2747          $course = $this->getDataGenerator()->create_course();
2748          $context = context_course::instance($course->id);
2749          $student = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
2750          $user = $this->getDataGenerator()->create_user();
2751  
2752          // This user should not appear anywhere, we're not interested in that context.
2753          $course2 = $this->getDataGenerator()->create_course();
2754          $this->getDataGenerator()->enrol_user($user->id, $course2->id, $student->id);
2755  
2756          $enrolled   = get_enrolled_users($context, '', 0, 'u.id', null, 0, 0, false);
2757          $active     = get_enrolled_users($context, '', 0, 'u.id', null, 0, 0, true);
2758          $suspended  = get_suspended_userids($context);
2759  
2760          $this->assertFalse(isset($enrolled[$user->id]));
2761          $this->assertFalse(isset($active[$user->id]));
2762          $this->assertFalse(isset($suspended[$user->id]));
2763          $this->assertCount(0, $enrolled);
2764          $this->assertCount(0, $active);
2765          $this->assertCount(0, $suspended);
2766      }
2767  
2768      /**
2769       * Test that enrolled users SQL does not return any values for role
2770       * assignments without an enrolment.
2771       *
2772       *
2773       * @covers ::get_enrolled_users
2774       * @covers ::get_enrolled_sql
2775       * @covers ::get_enrolled_with_capabilities_join
2776       * @covers ::get_enrolled_join
2777       * @covers ::get_with_capability_join
2778       * @covers ::groups_get_members_join
2779       * @covers ::get_suspended_userids
2780       */
2781      public function test_get_enrolled_sql_role_only() {
2782          global $DB;
2783  
2784          $this->resetAfterTest();
2785  
2786          $course = $this->getDataGenerator()->create_course();
2787          $context = context_course::instance($course->id);
2788          $student = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
2789          $user = $this->getDataGenerator()->create_user();
2790  
2791          // Role assignment is not the same as course enrollment.
2792          role_assign($student->id, $user->id, $context->id);
2793  
2794          $enrolled   = get_enrolled_users($context, '', 0, 'u.id', null, 0, 0, false);
2795          $active     = get_enrolled_users($context, '', 0, 'u.id', null, 0, 0, true);
2796          $suspended  = get_suspended_userids($context);
2797  
2798          $this->assertFalse(isset($enrolled[$user->id]));
2799          $this->assertFalse(isset($active[$user->id]));
2800          $this->assertFalse(isset($suspended[$user->id]));
2801          $this->assertCount(0, $enrolled);
2802          $this->assertCount(0, $active);
2803          $this->assertCount(0, $suspended);
2804      }
2805  
2806      /**
2807       * Test that multiple enrolments for the same user are counted correctly.
2808       *
2809       * @covers ::get_enrolled_users
2810       * @covers ::get_enrolled_sql
2811       * @covers ::get_enrolled_with_capabilities_join
2812       * @covers ::get_enrolled_join
2813       * @covers ::get_with_capability_join
2814       * @covers ::groups_get_members_join
2815       * @covers ::get_suspended_userids
2816       */
2817      public function test_get_enrolled_sql_multiple_enrolments() {
2818          global $DB;
2819  
2820          $this->resetAfterTest();
2821  
2822          $course = $this->getDataGenerator()->create_course();
2823          $context = context_course::instance($course->id);
2824          $student = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
2825          $user = $this->getDataGenerator()->create_user();
2826  
2827          // Add a suspended enrol.
2828          $selfinstance = $DB->get_record('enrol', array('courseid' => $course->id, 'enrol' => 'self'));
2829          $selfplugin = enrol_get_plugin('self');
2830          $selfplugin->update_status($selfinstance, ENROL_INSTANCE_ENABLED);
2831          $this->getDataGenerator()->enrol_user($user->id, $course->id, $student->id, 'self', 0, 0, ENROL_USER_SUSPENDED);
2832  
2833          // Should be enrolled, but not active - user is suspended.
2834          $enrolled   = get_enrolled_users($context, '', 0, 'u.id', null, 0, 0, false);
2835          $active     = get_enrolled_users($context, '', 0, 'u.id', null, 0, 0, true);
2836          $suspended  = get_suspended_userids($context);
2837  
2838          $this->assertTrue(isset($enrolled[$user->id]));
2839          $this->assertFalse(isset($active[$user->id]));
2840          $this->assertTrue(isset($suspended[$user->id]));
2841          $this->assertCount(1, $enrolled);
2842          $this->assertCount(0, $active);
2843          $this->assertCount(1, $suspended);
2844  
2845          // Add an active enrol for the user. Any active enrol makes them enrolled.
2846          $this->getDataGenerator()->enrol_user($user->id, $course->id, $student->id);
2847  
2848          // User should be active now.
2849          $enrolled   = get_enrolled_users($context, '', 0, 'u.id', null, 0, 0, false);
2850          $active     = get_enrolled_users($context, '', 0, 'u.id', null, 0, 0, true);
2851          $suspended  = get_suspended_userids($context);
2852  
2853          $this->assertTrue(isset($enrolled[$user->id]));
2854          $this->assertTrue(isset($active[$user->id]));
2855          $this->assertFalse(isset($suspended[$user->id]));
2856          $this->assertCount(1, $enrolled);
2857          $this->assertCount(1, $active);
2858          $this->assertCount(0, $suspended);
2859  
2860      }
2861  
2862      /**
2863       * Test that enrolled users returns only users in those groups that are
2864       * specified.
2865       *
2866       * @covers ::get_enrolled_users
2867       * @covers ::get_enrolled_sql
2868       * @covers ::get_enrolled_with_capabilities_join
2869       * @covers ::get_enrolled_join
2870       * @covers ::get_with_capability_join
2871       * @covers ::groups_get_members_join
2872       * @covers ::get_suspended_userids
2873       */
2874      public function test_get_enrolled_sql_userswithgroups() {
2875          $this->resetAfterTest();
2876  
2877          $systemcontext = context_system::instance();
2878          $course = $this->getDataGenerator()->create_course();
2879          $coursecontext = context_course::instance($course->id);
2880          $user1 = $this->getDataGenerator()->create_user();
2881          $user2 = $this->getDataGenerator()->create_user();
2882  
2883          $this->getDataGenerator()->enrol_user($user1->id, $course->id);
2884          $this->getDataGenerator()->enrol_user($user2->id, $course->id);
2885  
2886          $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2887          groups_add_member($group1, $user1);
2888          $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2889          groups_add_member($group2, $user2);
2890  
2891          // Get user from group 1.
2892          $group1users   = get_enrolled_users($coursecontext, '', $group1->id);
2893          $this->assertCount(1, $group1users);
2894          $this->assertArrayHasKey($user1->id, $group1users);
2895          $this->assertEquals(1, count_enrolled_users($coursecontext, '', $group1->id));
2896  
2897          // Get user from group 2.
2898          $group2users   = get_enrolled_users($coursecontext, '', $group2->id);
2899          $this->assertCount(1, $group2users);
2900          $this->assertArrayHasKey($user2->id, $group2users);
2901          $this->assertEquals(1, count_enrolled_users($coursecontext, '', $group2->id));
2902  
2903          // Get users from multiple groups.
2904          $groupusers   = get_enrolled_users($coursecontext, '', [$group1->id, $group2->id]);
2905          $this->assertCount(2, $groupusers);
2906          $this->assertArrayHasKey($user1->id, $groupusers);
2907          $this->assertArrayHasKey($user2->id, $groupusers);
2908          $this->assertEquals(2, count_enrolled_users($coursecontext, '', [$group1->id, $group2->id]));
2909      }
2910  
2911      /**
2912       * Test that enrolled users SQL does not return any values for users
2913       * without a group when $context is not a valid course context.
2914       *
2915       * @covers ::get_enrolled_users
2916       * @covers ::get_enrolled_sql
2917       * @covers ::get_enrolled_with_capabilities_join
2918       * @covers ::get_enrolled_join
2919       * @covers ::get_with_capability_join
2920       * @covers ::groups_get_members_join
2921       * @covers ::get_suspended_userids
2922       */
2923      public function test_get_enrolled_sql_userswithoutgroup() {
2924          global $DB;
2925  
2926          $this->resetAfterTest();
2927  
2928          $systemcontext = context_system::instance();
2929          $course = $this->getDataGenerator()->create_course();
2930          $coursecontext = context_course::instance($course->id);
2931          $user1 = $this->getDataGenerator()->create_user();
2932          $user2 = $this->getDataGenerator()->create_user();
2933  
2934          $this->getDataGenerator()->enrol_user($user1->id, $course->id);
2935          $this->getDataGenerator()->enrol_user($user2->id, $course->id);
2936  
2937          $group = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
2938          groups_add_member($group, $user1);
2939  
2940          $enrolled = get_enrolled_users($coursecontext);
2941          $this->assertCount(2, $enrolled);
2942  
2943          // Get users without any group on the course context.
2944          $enrolledwithoutgroup = get_enrolled_users($coursecontext, '', USERSWITHOUTGROUP);
2945          $this->assertCount(1, $enrolledwithoutgroup);
2946          $this->assertFalse(isset($enrolledwithoutgroup[$user1->id]));
2947  
2948          // Get users without any group on the system context (it should throw an exception).
2949          $this->expectException('coding_exception');
2950          get_enrolled_users($systemcontext, '', USERSWITHOUTGROUP);
2951      }
2952  
2953      /**
2954       * Test that enrolled users returns only users in those groups that are
2955       * specified, and they are allowed to see members of.
2956       *
2957       * @covers ::get_enrolled_users
2958       * @covers ::get_enrolled_sql
2959       * @covers ::get_enrolled_with_capabilities_join
2960       * @covers ::get_enrolled_join
2961       * @covers ::get_with_capability_join
2962       * @covers ::groups_get_members_join
2963       * @covers ::get_suspended_userids
2964       */
2965      public function test_get_enrolled_sql_userswithhiddengroups() {
2966          $this->resetAfterTest();
2967  
2968          $course = $this->getDataGenerator()->create_course();
2969          $coursecontext = context_course::instance($course->id);
2970          $user1 = $this->getDataGenerator()->create_user();
2971          $user2 = $this->getDataGenerator()->create_user();
2972          $user3 = $this->getDataGenerator()->create_user();
2973          $user4 = $this->getDataGenerator()->create_user();
2974          $user5 = $this->getDataGenerator()->create_user();
2975          $user6 = $this->getDataGenerator()->create_user();
2976  
2977          $this->getDataGenerator()->enrol_user($user1->id, $course->id);
2978          $this->getDataGenerator()->enrol_user($user2->id, $course->id);
2979          $this->getDataGenerator()->enrol_user($user3->id, $course->id);
2980          $this->getDataGenerator()->enrol_user($user4->id, $course->id);
2981          $this->getDataGenerator()->enrol_user($user5->id, $course->id);
2982          $this->getDataGenerator()->enrol_user($user6->id, $course->id);
2983  
2984          $group1 = $this->getDataGenerator()->create_group([
2985                  'courseid' => $course->id,
2986                  'visibility' => GROUPS_VISIBILITY_ALL,
2987          ]);
2988          groups_add_member($group1, $user1);
2989          $group2 = $this->getDataGenerator()->create_group([
2990                  'courseid' => $course->id,
2991                  'visibility' => GROUPS_VISIBILITY_MEMBERS,
2992          ]);
2993          groups_add_member($group2, $user2);
2994          groups_add_member($group2, $user5);
2995          $group3 = $this->getDataGenerator()->create_group([
2996                  'courseid' => $course->id,
2997                  'visibility' => GROUPS_VISIBILITY_OWN,
2998          ]);
2999          groups_add_member($group3, $user3);
3000          groups_add_member($group3, $user6);
3001          $group4 = $this->getDataGenerator()->create_group([
3002                  'courseid' => $course->id,
3003                  'visibility' => GROUPS_VISIBILITY_NONE,
3004          ]);
3005          groups_add_member($group4, $user4);
3006  
3007          $groupids = [$group1->id, $group2->id, $group3->id, $group4->id];
3008          // User 1 can only see members of Group 1.
3009          $this->setUser($user1);
3010          $user1groupusers = get_enrolled_users($coursecontext, '', $groupids);
3011          $this->assertCount(1, $user1groupusers);
3012          $this->assertArrayHasKey($user1->id, $user1groupusers);
3013          $this->assertEquals(1, count_enrolled_users($coursecontext, '', $groupids));
3014          // User 2 can see all members of Group 1 and Group 2.
3015          $this->setUser($user2);
3016          $user2groupusers = get_enrolled_users($coursecontext, '', $groupids);
3017          $this->assertCount(3, $user2groupusers);
3018          $this->assertArrayHasKey($user1->id, $user2groupusers);
3019          $this->assertArrayHasKey($user2->id, $user2groupusers);
3020          $this->assertArrayHasKey($user5->id, $user2groupusers);
3021          $this->assertEquals(3, count_enrolled_users($coursecontext, '', $groupids));
3022          // User 3 can see members of Group 1, and themselves in Group 3 but not other members.
3023          $this->setUser($user3);
3024          $user3groupusers = get_enrolled_users($coursecontext, '', $groupids);
3025          $this->assertCount(2, $user3groupusers);
3026          $this->assertArrayHasKey($user1->id, $user3groupusers);
3027          $this->assertArrayHasKey($user3->id, $user3groupusers);
3028          $this->assertEquals(2, count_enrolled_users($coursecontext, '', $groupids));
3029          // User 4 can only see members of Group 1.
3030          $this->setUser($user4);
3031          $user4groupusers = get_enrolled_users($coursecontext, '', $groupids);
3032          $this->assertCount(1, $user4groupusers);
3033          $this->assertArrayHasKey($user1->id, $user4groupusers);
3034          $this->assertEquals(1, count_enrolled_users($coursecontext, '', $groupids));
3035      }
3036  
3037      public function get_enrolled_sql_provider() {
3038          return array(
3039              array(
3040                  // Two users who are enrolled.
3041                  'users' => array(
3042                      array(
3043                          'enrolled'  => true,
3044                          'active'    => true,
3045                      ),
3046                      array(
3047                          'enrolled'  => true,
3048                          'active'    => true,
3049                      ),
3050                  ),
3051                  'counts' => array(
3052                      'enrolled'      => 2,
3053                      'active'        => 2,
3054                      'suspended'     => 0,
3055                  ),
3056              ),
3057              array(
3058                  // A user who is suspended.
3059                  'users' => array(
3060                      array(
3061                          'status'    => ENROL_USER_SUSPENDED,
3062                          'enrolled'  => true,
3063                          'suspended' => true,
3064                      ),
3065                  ),
3066                  'counts' => array(
3067                      'enrolled'      => 1,
3068                      'active'        => 0,
3069                      'suspended'     => 1,
3070                  ),
3071              ),
3072              array(
3073                  // One of each.
3074                  'users' => array(
3075                      array(
3076                          'enrolled'  => true,
3077                          'active'    => true,
3078                      ),
3079                      array(
3080                          'status'    => ENROL_USER_SUSPENDED,
3081                          'enrolled'  => true,
3082                          'suspended' => true,
3083                      ),
3084                  ),
3085                  'counts' => array(
3086                      'enrolled'      => 2,
3087                      'active'        => 1,
3088                      'suspended'     => 1,
3089                  ),
3090              ),
3091              array(
3092                  // One user who is not yet enrolled.
3093                  'users' => array(
3094                      array(
3095                          'timestart' => DAYSECS,
3096                          'enrolled'  => true,
3097                          'active'    => false,
3098                          'suspended' => true,
3099                      ),
3100                  ),
3101                  'counts' => array(
3102                      'enrolled'      => 1,
3103                      'active'        => 0,
3104                      'suspended'     => 1,
3105                  ),
3106              ),
3107              array(
3108                  // One user who is no longer enrolled
3109                  'users' => array(
3110                      array(
3111                          'timeend'   => -DAYSECS,
3112                          'enrolled'  => true,
3113                          'active'    => false,
3114                          'suspended' => true,
3115                      ),
3116                  ),
3117                  'counts' => array(
3118                      'enrolled'      => 1,
3119                      'active'        => 0,
3120                      'suspended'     => 1,
3121                  ),
3122              ),
3123              array(
3124                  // One user who is not yet enrolled, and one who is no longer enrolled.
3125                  'users' => array(
3126                      array(
3127                          'timeend'   => -DAYSECS,
3128                          'enrolled'  => true,
3129                          'active'    => false,
3130                          'suspended' => true,
3131                      ),
3132                      array(
3133                          'timestart' => DAYSECS,
3134                          'enrolled'  => true,
3135                          'active'    => false,
3136                          'suspended' => true,
3137                      ),
3138                  ),
3139                  'counts' => array(
3140                      'enrolled'      => 2,
3141                      'active'        => 0,
3142                      'suspended'     => 2,
3143                  ),
3144              ),
3145          );
3146      }
3147  
3148      /**
3149       * @dataProvider get_enrolled_sql_provider
3150       * @covers ::get_enrolled_users
3151       * @covers ::get_suspended_userids
3152       */
3153      public function test_get_enrolled_sql_course($users, $counts) {
3154          global $DB;
3155  
3156          $this->resetAfterTest();
3157  
3158          $course = $this->getDataGenerator()->create_course();
3159          $context = context_course::instance($course->id);
3160          $student = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
3161          $createdusers = array();
3162  
3163          foreach ($users as &$userdata) {
3164              $user = $this->getDataGenerator()->create_user();
3165              $userdata['id'] = $user->id;
3166  
3167              $timestart  = 0;
3168              $timeend    = 0;
3169              $status     = null;
3170              if (isset($userdata['timestart'])) {
3171                  $timestart = time() + $userdata['timestart'];
3172              }
3173              if (isset($userdata['timeend'])) {
3174                  $timeend = time() + $userdata['timeend'];
3175              }
3176              if (isset($userdata['status'])) {
3177                  $status = $userdata['status'];
3178              }
3179  
3180              // Enrol the user in the course.
3181              $this->getDataGenerator()->enrol_user($user->id, $course->id, $student->id, 'manual', $timestart, $timeend, $status);
3182          }
3183  
3184          // After all users have been enroled, check expectations.
3185          $enrolled   = get_enrolled_users($context, '', 0, 'u.id', null, 0, 0, false);
3186          $active     = get_enrolled_users($context, '', 0, 'u.id', null, 0, 0, true);
3187          $suspended  = get_suspended_userids($context);
3188  
3189          foreach ($users as $userdata) {
3190              if (isset($userdata['enrolled']) && $userdata['enrolled']) {
3191                  $this->assertTrue(isset($enrolled[$userdata['id']]));
3192              } else {
3193                  $this->assertFalse(isset($enrolled[$userdata['id']]));
3194              }
3195  
3196              if (isset($userdata['active']) && $userdata['active']) {
3197                  $this->assertTrue(isset($active[$userdata['id']]));
3198              } else {
3199                  $this->assertFalse(isset($active[$userdata['id']]));
3200              }
3201  
3202              if (isset($userdata['suspended']) && $userdata['suspended']) {
3203                  $this->assertTrue(isset($suspended[$userdata['id']]));
3204              } else {
3205                  $this->assertFalse(isset($suspended[$userdata['id']]));
3206              }
3207          }
3208  
3209          $this->assertCount($counts['enrolled'],     $enrolled);
3210          $this->assertCount($counts['active'],       $active);
3211          $this->assertCount($counts['suspended'],    $suspended);
3212      }
3213  
3214      /**
3215       * A small functional test of permission evaluations.
3216       */
3217      public function test_permission_evaluation() {
3218          global $USER, $SITE, $CFG, $DB, $ACCESSLIB_PRIVATE;
3219  
3220          $this->resetAfterTest();
3221  
3222          $generator = $this->getDataGenerator();
3223  
3224          // Fill the site with some real data.
3225          $testcategories = array();
3226          $testcourses = array();
3227          $testpages = array();
3228          $testblocks = array();
3229          $allroles = $DB->get_records_menu('role', array(), 'id', 'shortname, id');
3230  
3231          $systemcontext = context_system::instance();
3232          $frontpagecontext = context_course::instance(SITEID);
3233  
3234          // Add block to system context.
3235          $bi = $generator->create_block('online_users');
3236          context_block::instance($bi->id);
3237          $testblocks[] = $bi->id;
3238  
3239          // Some users.
3240          $testusers = array();
3241          for ($i=0; $i<20; $i++) {
3242              $user = $generator->create_user();
3243              $testusers[$i] = $user->id;
3244              $usercontext = context_user::instance($user->id);
3245  
3246              // Add block to user profile.
3247              $bi = $generator->create_block('online_users', array('parentcontextid'=>$usercontext->id));
3248              $testblocks[] = $bi->id;
3249          }
3250  
3251          // Add block to frontpage.
3252          $bi = $generator->create_block('online_users', array('parentcontextid'=>$frontpagecontext->id));
3253          $frontpageblockcontext = context_block::instance($bi->id);
3254          $testblocks[] = $bi->id;
3255  
3256          // Add a resource to frontpage.
3257          $page = $generator->create_module('page', array('course'=>$SITE->id));
3258          $testpages[] = $page->cmid;
3259          $frontpagepagecontext = context_module::instance($page->cmid);
3260  
3261          // Add block to frontpage resource.
3262          $bi = $generator->create_block('online_users', array('parentcontextid'=>$frontpagepagecontext->id));
3263          $frontpagepageblockcontext = context_block::instance($bi->id);
3264          $testblocks[] = $bi->id;
3265  
3266          // Some nested course categories with courses.
3267          $manualenrol = enrol_get_plugin('manual');
3268          $parentcat = 0;
3269          for ($i=0; $i<5; $i++) {
3270              $cat = $generator->create_category(array('parent'=>$parentcat));
3271              $testcategories[] = $cat->id;
3272              $catcontext = context_coursecat::instance($cat->id);
3273              $parentcat = $cat->id;
3274  
3275              if ($i >= 4) {
3276                  continue;
3277              }
3278  
3279              // Add resource to each category.
3280              $bi = $generator->create_block('online_users', array('parentcontextid'=>$catcontext->id));
3281              context_block::instance($bi->id);
3282  
3283              // Add a few courses to each category.
3284              for ($j=0; $j<6; $j++) {
3285                  $course = $generator->create_course(array('category'=>$cat->id));
3286                  $testcourses[] = $course->id;
3287                  $coursecontext = context_course::instance($course->id);
3288  
3289                  if ($j >= 5) {
3290                      continue;
3291                  }
3292                  // Add manual enrol instance.
3293                  $manualenrol->add_default_instance($DB->get_record('course', array('id'=>$course->id)));
3294  
3295                  // Add block to each course.
3296                  $bi = $generator->create_block('online_users', array('parentcontextid'=>$coursecontext->id));
3297                  $testblocks[] = $bi->id;
3298  
3299                  // Add a resource to each course.
3300                  $page = $generator->create_module('page', array('course'=>$course->id));
3301                  $testpages[] = $page->cmid;
3302                  $modcontext = context_module::instance($page->cmid);
3303  
3304                  // Add block to each module.
3305                  $bi = $generator->create_block('online_users', array('parentcontextid'=>$modcontext->id));
3306                  $testblocks[] = $bi->id;
3307              }
3308          }
3309  
3310          // Make sure all contexts were created properly.
3311          $count = 1; // System.
3312          $count += $DB->count_records('user', array('deleted'=>0));
3313          $count += $DB->count_records('course_categories');
3314          $count += $DB->count_records('course');
3315          $count += $DB->count_records('course_modules');
3316          $count += $DB->count_records('block_instances');
3317          $this->assertEquals($count, $DB->count_records('context'));
3318          $this->assertEquals(0, $DB->count_records('context', array('depth'=>0)));
3319          $this->assertEquals(0, $DB->count_records('context', array('path'=>null)));
3320  
3321  
3322          // Test context_helper::get_level_name() method.
3323  
3324          $levels = context_helper::get_all_levels();
3325          foreach ($levels as $level => $classname) {
3326              $name = context_helper::get_level_name($level);
3327              $this->assertNotEmpty($name);
3328          }
3329  
3330  
3331          // Test context::instance_by_id(), context_xxx::instance() methods.
3332  
3333          $context = context::instance_by_id($frontpagecontext->id);
3334          $this->assertSame(CONTEXT_COURSE, $context->contextlevel);
3335          $this->assertFalse(context::instance_by_id(-1, IGNORE_MISSING));
3336          try {
3337              context::instance_by_id(-1);
3338              $this->fail('exception expected');
3339          } catch (moodle_exception $e) {
3340              $this->assertTrue(true);
3341          }
3342          $this->assertInstanceOf('context_system', context_system::instance());
3343          $this->assertInstanceOf('context_coursecat', context_coursecat::instance($testcategories[0]));
3344          $this->assertInstanceOf('context_course', context_course::instance($testcourses[0]));
3345          $this->assertInstanceOf('context_module', context_module::instance($testpages[0]));
3346          $this->assertInstanceOf('context_block', context_block::instance($testblocks[0]));
3347  
3348          $this->assertFalse(context_coursecat::instance(-1, IGNORE_MISSING));
3349          $this->assertFalse(context_course::instance(-1, IGNORE_MISSING));
3350          $this->assertFalse(context_module::instance(-1, IGNORE_MISSING));
3351          $this->assertFalse(context_block::instance(-1, IGNORE_MISSING));
3352          try {
3353              context_coursecat::instance(-1);
3354              $this->fail('exception expected');
3355          } catch (moodle_exception $e) {
3356              $this->assertTrue(true);
3357          }
3358          try {
3359              context_course::instance(-1);
3360              $this->fail('exception expected');
3361          } catch (moodle_exception $e) {
3362              $this->assertTrue(true);
3363          }
3364          try {
3365              context_module::instance(-1);
3366              $this->fail('exception expected');
3367          } catch (moodle_exception $e) {
3368              $this->assertTrue(true);
3369          }
3370          try {
3371              context_block::instance(-1);
3372              $this->fail('exception expected');
3373          } catch (moodle_exception $e) {
3374              $this->assertTrue(true);
3375          }
3376  
3377  
3378          // Test $context->get_url(), $context->get_context_name(), $context->get_capabilities() methods.
3379  
3380          $testcontexts = array();
3381          $testcontexts[CONTEXT_SYSTEM]    = context_system::instance();
3382          $testcontexts[CONTEXT_COURSECAT] = context_coursecat::instance($testcategories[0]);
3383          $testcontexts[CONTEXT_COURSE]    = context_course::instance($testcourses[0]);
3384          $testcontexts[CONTEXT_MODULE]    = context_module::instance($testpages[0]);
3385          $testcontexts[CONTEXT_BLOCK]     = context_block::instance($testblocks[0]);
3386  
3387          foreach ($testcontexts as $context) {
3388              $name = $context->get_context_name(true, true);
3389              $this->assertNotEmpty($name);
3390  
3391              $this->assertInstanceOf('moodle_url', $context->get_url());
3392  
3393              $caps = $context->get_capabilities();
3394              $this->assertTrue(is_array($caps));
3395              foreach ($caps as $cap) {
3396                  $cap = (array)$cap;
3397                  $this->assertSame(array_keys($cap), array('id', 'name', 'captype', 'contextlevel', 'component', 'riskbitmask'));
3398              }
3399          }
3400          unset($testcontexts);
3401  
3402          // Test $context->get_course_context() method.
3403  
3404          $this->assertFalse($systemcontext->get_course_context(false));
3405          try {
3406              $systemcontext->get_course_context();
3407              $this->fail('exception expected');
3408          } catch (moodle_exception $e) {
3409              $this->assertInstanceOf('coding_exception', $e);
3410          }
3411          $context = context_coursecat::instance($testcategories[0]);
3412          $this->assertFalse($context->get_course_context(false));
3413          try {
3414              $context->get_course_context();
3415              $this->fail('exception expected');
3416          } catch (moodle_exception $e) {
3417              $this->assertInstanceOf('coding_exception', $e);
3418          }
3419          $this->assertEquals($frontpagecontext, $frontpagecontext->get_course_context(true));
3420          $this->assertEquals($frontpagecontext, $frontpagepagecontext->get_course_context(true));
3421          $this->assertEquals($frontpagecontext, $frontpagepageblockcontext->get_course_context(true));
3422  
3423  
3424          // Test $context->get_parent_context(), $context->get_parent_contexts(), $context->get_parent_context_ids() methods.
3425  
3426          $userid = reset($testusers);
3427          $usercontext = context_user::instance($userid);
3428          $this->assertEquals($systemcontext, $usercontext->get_parent_context());
3429          $this->assertEquals(array($systemcontext->id=>$systemcontext), $usercontext->get_parent_contexts());
3430          $this->assertEquals(array($usercontext->id=>$usercontext, $systemcontext->id=>$systemcontext), $usercontext->get_parent_contexts(true));
3431  
3432          $this->assertEquals(array(), $systemcontext->get_parent_contexts());
3433          $this->assertEquals(array($systemcontext->id=>$systemcontext), $systemcontext->get_parent_contexts(true));
3434          $this->assertEquals(array(), $systemcontext->get_parent_context_ids());
3435          $this->assertEquals(array($systemcontext->id), $systemcontext->get_parent_context_ids(true));
3436          $this->assertEquals(array(), $systemcontext->get_parent_context_paths());
3437          $this->assertEquals(array($systemcontext->id => $systemcontext->path), $systemcontext->get_parent_context_paths(true));
3438  
3439          $this->assertEquals($systemcontext, $frontpagecontext->get_parent_context());
3440          $this->assertEquals(array($systemcontext->id=>$systemcontext), $frontpagecontext->get_parent_contexts());
3441          $this->assertEquals(array($frontpagecontext->id=>$frontpagecontext, $systemcontext->id=>$systemcontext), $frontpagecontext->get_parent_contexts(true));
3442          $this->assertEquals(array($systemcontext->id), $frontpagecontext->get_parent_context_ids());
3443          $this->assertEquals(array($frontpagecontext->id, $systemcontext->id), $frontpagecontext->get_parent_context_ids(true));
3444          $this->assertEquals(array($systemcontext->id => $systemcontext->path), $frontpagecontext->get_parent_context_paths());
3445          $expected = array($systemcontext->id => $systemcontext->path, $frontpagecontext->id => $frontpagecontext->path);
3446          $this->assertEquals($expected, $frontpagecontext->get_parent_context_paths(true));
3447  
3448          $this->assertFalse($systemcontext->get_parent_context());
3449          $frontpagecontext = context_course::instance($SITE->id);
3450          $parent = $systemcontext;
3451          foreach ($testcategories as $catid) {
3452              $catcontext = context_coursecat::instance($catid);
3453              $this->assertEquals($parent, $catcontext->get_parent_context());
3454              $parent = $catcontext;
3455          }
3456          $this->assertEquals($frontpagecontext, $frontpagepagecontext->get_parent_context());
3457          $this->assertEquals($frontpagecontext, $frontpageblockcontext->get_parent_context());
3458          $this->assertEquals($frontpagepagecontext, $frontpagepageblockcontext->get_parent_context());
3459  
3460  
3461          // Test $context->get_child_contexts() method.
3462  
3463          $children = $systemcontext->get_child_contexts();
3464          $this->resetDebugging();
3465          $this->assertEquals(count($children)+1, $DB->count_records('context'));
3466  
3467          $context = context_coursecat::instance($testcategories[3]);
3468          $children = $context->get_child_contexts();
3469          $countcats    = 0;
3470          $countcourses = 0;
3471          $countblocks  = 0;
3472          foreach ($children as $child) {
3473              if ($child->contextlevel == CONTEXT_COURSECAT) {
3474                  $countcats++;
3475              }
3476              if ($child->contextlevel == CONTEXT_COURSE) {
3477                  $countcourses++;
3478              }
3479              if ($child->contextlevel == CONTEXT_BLOCK) {
3480                  $countblocks++;
3481              }
3482          }
3483          $this->assertCount(8, $children);
3484          $this->assertEquals(1, $countcats);
3485          $this->assertEquals(6, $countcourses);
3486          $this->assertEquals(1, $countblocks);
3487  
3488          $context = context_course::instance($testcourses[2]);
3489          $children = $context->get_child_contexts();
3490  
3491          $context = context_module::instance($testpages[3]);
3492          $children = $context->get_child_contexts();
3493          $this->assertCount(1, $children);
3494  
3495          $context = context_block::instance($testblocks[1]);
3496          $children = $context->get_child_contexts();
3497          $this->assertCount(0, $children);
3498  
3499          unset($children);
3500          unset($countcats);
3501          unset($countcourses);
3502          unset($countblocks);
3503  
3504  
3505          // Test context_helper::reset_caches() method.
3506  
3507          context_helper::reset_caches();
3508          $this->assertEquals(0, context_inspection::test_context_cache_size());
3509          context_course::instance($SITE->id);
3510          $this->assertEquals(1, context_inspection::test_context_cache_size());
3511  
3512  
3513          // Test context preloading.
3514  
3515          context_helper::reset_caches();
3516          $sql = "SELECT ".context_helper::get_preload_record_columns_sql('c')."
3517                    FROM {context} c
3518                   WHERE c.contextlevel <> ".CONTEXT_SYSTEM;
3519          $records = $DB->get_records_sql($sql);
3520          $firstrecord = reset($records);
3521          $columns = context_helper::get_preload_record_columns('c');
3522          $firstrecord = (array)$firstrecord;
3523          $this->assertSame(array_keys($firstrecord), array_values($columns));
3524          context_helper::reset_caches();
3525          foreach ($records as $record) {
3526              context_helper::preload_from_record($record);
3527              $this->assertEquals(new stdClass(), $record);
3528          }
3529          $this->assertEquals(count($records), context_inspection::test_context_cache_size());
3530          unset($records);
3531          unset($columns);
3532  
3533          context_helper::reset_caches();
3534          context_helper::preload_course($SITE->id);
3535          $numfrontpagemodules = $DB->count_records('course_modules', array('course' => $SITE->id));
3536          $this->assertEquals(3 + $numfrontpagemodules, context_inspection::test_context_cache_size()); // Depends on number of default blocks.
3537  
3538          // Test assign_capability(), unassign_capability() functions.
3539  
3540          $rc = $DB->get_record('role_capabilities', array('contextid'=>$frontpagecontext->id, 'roleid'=>$allroles['teacher'], 'capability'=>'moodle/site:accessallgroups'));
3541          $this->assertFalse($rc);
3542          assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $allroles['teacher'], $frontpagecontext->id);
3543          $rc = $DB->get_record('role_capabilities', array('contextid'=>$frontpagecontext->id, 'roleid'=>$allroles['teacher'], 'capability'=>'moodle/site:accessallgroups'));
3544          $this->assertEquals(CAP_ALLOW, $rc->permission);
3545          assign_capability('moodle/site:accessallgroups', CAP_PREVENT, $allroles['teacher'], $frontpagecontext->id);
3546          $rc = $DB->get_record('role_capabilities', array('contextid'=>$frontpagecontext->id, 'roleid'=>$allroles['teacher'], 'capability'=>'moodle/site:accessallgroups'));
3547          $this->assertEquals(CAP_ALLOW, $rc->permission);
3548          assign_capability('moodle/site:accessallgroups', CAP_PREVENT, $allroles['teacher'], $frontpagecontext, true);
3549          $rc = $DB->get_record('role_capabilities', array('contextid'=>$frontpagecontext->id, 'roleid'=>$allroles['teacher'], 'capability'=>'moodle/site:accessallgroups'));
3550          $this->assertEquals(CAP_PREVENT, $rc->permission);
3551  
3552          assign_capability('moodle/site:accessallgroups', CAP_INHERIT, $allroles['teacher'], $frontpagecontext);
3553          $rc = $DB->get_record('role_capabilities', array('contextid'=>$frontpagecontext->id, 'roleid'=>$allroles['teacher'], 'capability'=>'moodle/site:accessallgroups'));
3554          $this->assertFalse($rc);
3555          assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $allroles['teacher'], $frontpagecontext);
3556          unassign_capability('moodle/site:accessallgroups', $allroles['teacher'], $frontpagecontext, true);
3557          $rc = $DB->get_record('role_capabilities', array('contextid'=>$frontpagecontext->id, 'roleid'=>$allroles['teacher'], 'capability'=>'moodle/site:accessallgroups'));
3558          $this->assertFalse($rc);
3559          unassign_capability('moodle/site:accessallgroups', $allroles['teacher'], $frontpagecontext->id, true);
3560          unset($rc);
3561  
3562          accesslib_clear_all_caches_for_unit_testing(); // Must be done after assign_capability().
3563  
3564  
3565          // Test role_assign(), role_unassign(), role_unassign_all() functions.
3566  
3567          $context = context_course::instance($testcourses[1]);
3568          $this->assertEquals(0, $DB->count_records('role_assignments', array('contextid'=>$context->id)));
3569          role_assign($allroles['teacher'], $testusers[1], $context->id);
3570          role_assign($allroles['teacher'], $testusers[2], $context->id);
3571          role_assign($allroles['manager'], $testusers[1], $context->id);
3572          $this->assertEquals(3, $DB->count_records('role_assignments', array('contextid'=>$context->id)));
3573          role_unassign($allroles['teacher'], $testusers[1], $context->id);
3574          $this->assertEquals(2, $DB->count_records('role_assignments', array('contextid'=>$context->id)));
3575          role_unassign_all(array('contextid'=>$context->id));
3576          $this->assertEquals(0, $DB->count_records('role_assignments', array('contextid'=>$context->id)));
3577          unset($context);
3578  
3579          accesslib_clear_all_caches_for_unit_testing(); // Just in case.
3580  
3581  
3582          // Test has_capability(), get_users_by_capability(), role_switch(), reload_all_capabilities() and friends functions.
3583  
3584          $adminid = get_admin()->id;
3585          $guestid = $CFG->siteguest;
3586  
3587          // Enrol some users into some courses.
3588          $course1 = $DB->get_record('course', array('id'=>$testcourses[22]), '*', MUST_EXIST);
3589          $course2 = $DB->get_record('course', array('id'=>$testcourses[7]), '*', MUST_EXIST);
3590          $cms = $DB->get_records('course_modules', array('course'=>$course1->id), 'id');
3591          $cm1 = reset($cms);
3592          $blocks = $DB->get_records('block_instances', array('parentcontextid'=>context_module::instance($cm1->id)->id), 'id');
3593          $block1 = reset($blocks);
3594          $instance1 = $DB->get_record('enrol', array('enrol'=>'manual', 'courseid'=>$course1->id));
3595          $instance2 = $DB->get_record('enrol', array('enrol'=>'manual', 'courseid'=>$course2->id));
3596          for ($i=0; $i<9; $i++) {
3597              $manualenrol->enrol_user($instance1, $testusers[$i], $allroles['student']);
3598          }
3599          $manualenrol->enrol_user($instance1, $testusers[8], $allroles['teacher']);
3600          $manualenrol->enrol_user($instance1, $testusers[9], $allroles['editingteacher']);
3601  
3602          for ($i=10; $i<15; $i++) {
3603              $manualenrol->enrol_user($instance2, $testusers[$i], $allroles['student']);
3604          }
3605          $manualenrol->enrol_user($instance2, $testusers[15], $allroles['editingteacher']);
3606  
3607          // Add tons of role assignments - the more the better.
3608          role_assign($allroles['coursecreator'], $testusers[11], context_coursecat::instance($testcategories[2]));
3609          role_assign($allroles['manager'], $testusers[12], context_coursecat::instance($testcategories[1]));
3610          role_assign($allroles['student'], $testusers[9], context_module::instance($cm1->id));
3611          role_assign($allroles['teacher'], $testusers[8], context_module::instance($cm1->id));
3612          role_assign($allroles['guest'], $testusers[13], context_course::instance($course1->id));
3613          role_assign($allroles['teacher'], $testusers[7], context_block::instance($block1->id));
3614          role_assign($allroles['manager'], $testusers[9], context_block::instance($block1->id));
3615          role_assign($allroles['editingteacher'], $testusers[9], context_course::instance($course1->id));
3616  
3617          role_assign($allroles['teacher'], $adminid, context_course::instance($course1->id));
3618          role_assign($allroles['editingteacher'], $adminid, context_block::instance($block1->id));
3619  
3620          // Add tons of overrides - the more the better.
3621          assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $CFG->defaultuserroleid, $frontpageblockcontext, true);
3622          assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $CFG->defaultfrontpageroleid, $frontpageblockcontext, true);
3623          assign_capability('moodle/block:view', CAP_PROHIBIT, $allroles['guest'], $frontpageblockcontext, true);
3624          assign_capability('block/online_users:viewlist', CAP_PREVENT, $allroles['user'], $frontpageblockcontext, true);
3625          assign_capability('block/online_users:viewlist', CAP_PREVENT, $allroles['student'], $frontpageblockcontext, true);
3626  
3627          assign_capability('moodle/site:accessallgroups', CAP_PREVENT, $CFG->defaultuserroleid, $frontpagepagecontext, true);
3628          assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $CFG->defaultfrontpageroleid, $frontpagepagecontext, true);
3629          assign_capability('mod/page:view', CAP_PREVENT, $allroles['guest'], $frontpagepagecontext, true);
3630          assign_capability('mod/page:view', CAP_ALLOW, $allroles['user'], $frontpagepagecontext, true);
3631          assign_capability('mod/page:view', CAP_ALLOW, $allroles['student'], $frontpagepagecontext, true);
3632  
3633          assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $CFG->defaultuserroleid, $frontpagecontext, true);
3634          assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $CFG->defaultfrontpageroleid, $frontpagecontext, true);
3635          assign_capability('mod/page:view', CAP_ALLOW, $allroles['guest'], $frontpagecontext, true);
3636          assign_capability('mod/page:view', CAP_PROHIBIT, $allroles['user'], $frontpagecontext, true);
3637  
3638          assign_capability('mod/page:view', CAP_PREVENT, $allroles['guest'], $systemcontext, true);
3639  
3640          // Prepare for prohibit test.
3641          role_assign($allroles['editingteacher'], $testusers[19], context_system::instance());
3642          role_assign($allroles['teacher'], $testusers[19], context_course::instance($testcourses[17]));
3643          role_assign($allroles['editingteacher'], $testusers[19], context_course::instance($testcourses[17]));
3644          assign_capability('moodle/course:update', CAP_PROHIBIT, $allroles['teacher'], context_course::instance($testcourses[17]), true);
3645  
3646          accesslib_clear_all_caches_for_unit_testing(); /// Must be done after assign_capability().
3647  
3648          // Extra tests for guests and not-logged-in users because they can not be verified by cross checking
3649          // with get_users_by_capability() where they are ignored.
3650          $this->assertFalse(has_capability('moodle/block:view', $frontpageblockcontext, $guestid));
3651          $this->assertFalse(has_capability('mod/page:view', $frontpagepagecontext, $guestid));
3652          $this->assertTrue(has_capability('mod/page:view', $frontpagecontext, $guestid));
3653          $this->assertFalse(has_capability('mod/page:view', $systemcontext, $guestid));
3654  
3655          $this->assertFalse(has_capability('moodle/block:view', $frontpageblockcontext, 0));
3656          $this->assertFalse(has_capability('mod/page:view', $frontpagepagecontext, 0));
3657          $this->assertTrue(has_capability('mod/page:view', $frontpagecontext, 0));
3658          $this->assertFalse(has_capability('mod/page:view', $systemcontext, 0));
3659  
3660          $this->assertFalse(has_capability('moodle/course:create', $systemcontext, $testusers[11]));
3661          $this->assertTrue(has_capability('moodle/course:create', context_coursecat::instance($testcategories[2]), $testusers[11]));
3662          $this->assertFalse(has_capability('moodle/course:create', context_course::instance($testcourses[1]), $testusers[11]));
3663          $this->assertTrue(has_capability('moodle/course:create', context_course::instance($testcourses[19]), $testusers[11]));
3664  
3665          $this->assertFalse(has_capability('moodle/course:update', context_course::instance($testcourses[1]), $testusers[9]));
3666          $this->assertFalse(has_capability('moodle/course:update', context_course::instance($testcourses[19]), $testusers[9]));
3667          $this->assertFalse(has_capability('moodle/course:update', $systemcontext, $testusers[9]));
3668  
3669          // Test prohibits.
3670          $this->assertTrue(has_capability('moodle/course:update', context_system::instance(), $testusers[19]));
3671          $ids = get_users_by_capability(context_system::instance(), 'moodle/course:update', 'u.id');
3672          $this->assertArrayHasKey($testusers[19], $ids);
3673          $this->assertFalse(has_capability('moodle/course:update', context_course::instance($testcourses[17]), $testusers[19]));
3674          $ids = get_users_by_capability(context_course::instance($testcourses[17]), 'moodle/course:update', 'u.id');
3675          $this->assertArrayNotHasKey($testusers[19], $ids);
3676  
3677          // Test the list of enrolled users.
3678          $coursecontext = context_course::instance($course1->id);
3679          $enrolled = get_enrolled_users($coursecontext);
3680          $this->assertCount(10, $enrolled);
3681          for ($i=0; $i<10; $i++) {
3682              $this->assertTrue(isset($enrolled[$testusers[$i]]));
3683          }
3684          $enrolled = get_enrolled_users($coursecontext, 'moodle/course:update');
3685          $this->assertCount(1, $enrolled);
3686          $this->assertTrue(isset($enrolled[$testusers[9]]));
3687          unset($enrolled);
3688  
3689          // Role switching.
3690          $userid = $testusers[9];
3691          $USER = $DB->get_record('user', array('id'=>$userid));
3692          load_all_capabilities();
3693          $coursecontext = context_course::instance($course1->id);
3694          $this->assertTrue(has_capability('moodle/course:update', $coursecontext));
3695          $this->assertFalse(is_role_switched($course1->id));
3696          role_switch($allroles['student'], $coursecontext);
3697          $this->assertTrue(is_role_switched($course1->id));
3698          $this->assertEquals($allroles['student'], $USER->access['rsw'][$coursecontext->path]);
3699          $this->assertFalse(has_capability('moodle/course:update', $coursecontext));
3700          reload_all_capabilities();
3701          $this->assertFalse(has_capability('moodle/course:update', $coursecontext));
3702          role_switch(0, $coursecontext);
3703          $this->assertTrue(has_capability('moodle/course:update', $coursecontext));
3704          $userid = $adminid;
3705          $USER = $DB->get_record('user', array('id'=>$userid));
3706          load_all_capabilities();
3707          $coursecontext = context_course::instance($course1->id);
3708          $blockcontext = context_block::instance($block1->id);
3709          $this->assertTrue(has_capability('moodle/course:update', $blockcontext));
3710          role_switch($allroles['student'], $coursecontext);
3711          $this->assertEquals($allroles['student'], $USER->access['rsw'][$coursecontext->path]);
3712          $this->assertFalse(has_capability('moodle/course:update', $blockcontext));
3713          reload_all_capabilities();
3714          $this->assertFalse(has_capability('moodle/course:update', $blockcontext));
3715          load_all_capabilities();
3716          $this->assertTrue(has_capability('moodle/course:update', $blockcontext));
3717  
3718          // Temp course role for enrol.
3719          $DB->delete_records('cache_flags', array()); // This prevents problem with dirty contexts immediately resetting the temp role - this is a known problem...
3720          $userid = $testusers[5];
3721          $roleid = $allroles['editingteacher'];
3722          $USER = $DB->get_record('user', array('id'=>$userid));
3723          load_all_capabilities();
3724          $coursecontext = context_course::instance($course1->id);
3725          $this->assertFalse(has_capability('moodle/course:update', $coursecontext));
3726          $this->assertFalse(isset($USER->access['ra'][$coursecontext->path][$roleid]));
3727          load_temp_course_role($coursecontext, $roleid);
3728          $this->assertEquals($USER->access['ra'][$coursecontext->path][$roleid], $roleid);
3729          $this->assertTrue(has_capability('moodle/course:update', $coursecontext));
3730          remove_temp_course_roles($coursecontext);
3731          $this->assertFalse(has_capability('moodle/course:update', $coursecontext, $userid));
3732          load_temp_course_role($coursecontext, $roleid);
3733          reload_all_capabilities();
3734          $this->assertFalse(has_capability('moodle/course:update', $coursecontext, $userid));
3735          $USER = new stdClass();
3736          $USER->id = 0;
3737  
3738          // Now cross check has_capability() with get_users_by_capability(), each using different code paths,
3739          // they have to be kept in sync, usually only one of them breaks, so we know when something is wrong,
3740          // at the same time validate extra restrictions (guest read only no risks, admin exception, non existent and deleted users).
3741          $contexts = $DB->get_records('context', array(), 'id');
3742          $contexts = array_values($contexts);
3743          $capabilities = $DB->get_records('capabilities', array(), 'id');
3744          $capabilities = array_values($capabilities);
3745          $roles = array($allroles['guest'], $allroles['user'], $allroles['teacher'], $allroles['editingteacher'], $allroles['coursecreator'], $allroles['manager']);
3746          $userids = array_values($testusers);
3747          $userids[] = get_admin()->id;
3748  
3749          if (!PHPUNIT_LONGTEST) {
3750              $contexts = array_slice($contexts, 0, 10);
3751              $capabilities = array_slice($capabilities, 0, 5);
3752              $userids = array_slice($userids, 0, 5);
3753          }
3754  
3755          foreach ($userids as $userid) { // No guest or deleted.
3756              // Each user gets 0-10 random roles.
3757              $rcount = rand(0, 10);
3758              for ($j=0; $j<$rcount; $j++) {
3759                  $roleid = $roles[rand(0, count($roles)-1)];
3760                  $contextid = $contexts[rand(0, count($contexts)-1)]->id;
3761                  role_assign($roleid, $userid, $contextid);
3762              }
3763          }
3764  
3765          $permissions = array(CAP_ALLOW, CAP_PREVENT, CAP_INHERIT, CAP_PREVENT);
3766          $maxoverrides = count($contexts)*10;
3767          for ($j=0; $j<$maxoverrides; $j++) {
3768              $roleid = $roles[rand(0, count($roles)-1)];
3769              $contextid = $contexts[rand(0, count($contexts)-1)]->id;
3770              $permission = $permissions[rand(0, count($permissions)-1)];
3771              $capname = $capabilities[rand(0, count($capabilities)-1)]->name;
3772              assign_capability($capname, $permission, $roleid, $contextid, true);
3773          }
3774          unset($permissions);
3775          unset($roles);
3776  
3777          accesslib_clear_all_caches_for_unit_testing(); // must be done after assign_capability().
3778  
3779          // Test time - let's set up some real user, just in case the logic for USER affects the others...
3780          $USER = $DB->get_record('user', array('id'=>$testusers[3]));
3781          load_all_capabilities();
3782  
3783          $userids[] = $CFG->siteguest;
3784          $userids[] = 0; // Not-logged-in user.
3785          $userids[] = -1; // Non-existent user.
3786  
3787          foreach ($contexts as $crecord) {
3788              $context = context::instance_by_id($crecord->id);
3789              if ($coursecontext = $context->get_course_context(false)) {
3790                  $enrolled = get_enrolled_users($context);
3791              } else {
3792                  $enrolled = array();
3793              }
3794              foreach ($capabilities as $cap) {
3795                  $allowed = get_users_by_capability($context, $cap->name, 'u.id, u.username');
3796                  if ($enrolled) {
3797                      $enrolledwithcap = get_enrolled_users($context, $cap->name);
3798                  } else {
3799                      $enrolledwithcap = array();
3800                  }
3801                  foreach ($userids as $userid) {
3802                      if ($userid == 0 or isguestuser($userid)) {
3803                          if ($userid == 0) {
3804                              $CFG->forcelogin = true;
3805                              $this->assertFalse(has_capability($cap->name, $context, $userid));
3806                              unset($CFG->forcelogin);
3807                          }
3808                          if (($cap->captype === 'write') or ($cap->riskbitmask & (RISK_XSS | RISK_CONFIG | RISK_DATALOSS))) {
3809                              $this->assertFalse(has_capability($cap->name, $context, $userid));
3810                          }
3811                          $this->assertFalse(isset($allowed[$userid]));
3812                      } else {
3813                          if (is_siteadmin($userid)) {
3814                              $this->assertTrue(has_capability($cap->name, $context, $userid, true));
3815                          }
3816                          $hascap = has_capability($cap->name, $context, $userid, false);
3817                          $this->assertSame($hascap, isset($allowed[$userid]), "Capability result mismatch user:$userid, context:$context->id, $cap->name, hascap: ".(int)$hascap." ");
3818                          if (isset($enrolled[$userid])) {
3819                              $this->assertSame(isset($allowed[$userid]), isset($enrolledwithcap[$userid]), "Enrolment with capability result mismatch user:$userid, context:$context->id, $cap->name, hascap: ".(int)$hascap." ");
3820                          }
3821                      }
3822                  }
3823              }
3824          }
3825          // Back to nobody.
3826          $USER = new stdClass();
3827          $USER->id = 0;
3828          unset($contexts);
3829          unset($userids);
3830          unset($capabilities);
3831  
3832          // Now let's do all the remaining tests that break our carefully prepared fake site.
3833  
3834  
3835          // Test $context->mark_dirty() method.
3836  
3837          $DB->delete_records('cache_flags', array());
3838          accesslib_clear_all_caches(false);
3839          $systemcontext->mark_dirty();
3840          $dirty = get_cache_flags('accesslib/dirtycontexts', time()-2);
3841          $this->assertTrue(isset($dirty[$systemcontext->path]));
3842          $this->assertTrue(isset($ACCESSLIB_PRIVATE->dirtycontexts[$systemcontext->path]));
3843  
3844  
3845          // Test $context->reload_if_dirty() method.
3846  
3847          $DB->delete_records('cache_flags', array());
3848          accesslib_clear_all_caches(false);
3849          load_all_capabilities();
3850          $context = context_course::instance($testcourses[2]);
3851          $page = $DB->get_record('page', array('course'=>$testcourses[2]));
3852          $pagecm = get_coursemodule_from_instance('page', $page->id);
3853          $pagecontext = context_module::instance($pagecm->id);
3854  
3855          $context->mark_dirty();
3856          $this->assertTrue(isset($ACCESSLIB_PRIVATE->dirtycontexts[$context->path]));
3857          $USER->access['test'] = true;
3858          $context->reload_if_dirty();
3859          $this->assertFalse(isset($USER->access['test']));
3860  
3861          $context->mark_dirty();
3862          $this->assertTrue(isset($ACCESSLIB_PRIVATE->dirtycontexts[$context->path]));
3863          $USER->access['test'] = true;
3864          $pagecontext->reload_if_dirty();
3865          $this->assertFalse(isset($USER->access['test']));
3866  
3867  
3868          // Test context_helper::build_all_paths() method.
3869  
3870          $oldcontexts = $DB->get_records('context', array(), 'id');
3871          $DB->set_field_select('context', 'path', null, "contextlevel <> ".CONTEXT_SYSTEM);
3872          $DB->set_field_select('context', 'depth', 0, "contextlevel <> ".CONTEXT_SYSTEM);
3873          context_helper::build_all_paths();
3874          $newcontexts = $DB->get_records('context', array(), 'id');
3875          $this->assertEquals($oldcontexts, $newcontexts);
3876          unset($oldcontexts);
3877          unset($newcontexts);
3878  
3879  
3880          // Test $context->reset_paths() method.
3881  
3882          $context = context_course::instance($testcourses[2]);
3883          $children = $context->get_child_contexts();
3884          $context->reset_paths(false);
3885          $this->assertNull($DB->get_field('context', 'path', array('id'=>$context->id)));
3886          $this->assertEquals(0, $DB->get_field('context', 'depth', array('id'=>$context->id)));
3887          foreach ($children as $child) {
3888              $this->assertNull($DB->get_field('context', 'path', array('id'=>$child->id)));
3889              $this->assertEquals(0, $DB->get_field('context', 'depth', array('id'=>$child->id)));
3890          }
3891          $this->assertEquals(count($children)+1, $DB->count_records('context', array('depth'=>0)));
3892          $this->assertEquals(count($children)+1, $DB->count_records('context', array('path'=>null)));
3893  
3894          $context = context_course::instance($testcourses[2]);
3895          $context->reset_paths(true);
3896          $context = context_course::instance($testcourses[2]);
3897          $this->assertSame($context->path, $DB->get_field('context', 'path', array('id'=>$context->id)));
3898          $this->assertSame($context->depth, $DB->get_field('context', 'depth', array('id'=>$context->id)));
3899          $this->assertEquals(0, $DB->count_records('context', array('depth'=>0)));
3900          $this->assertEquals(0, $DB->count_records('context', array('path'=>null)));
3901  
3902  
3903          // Test $context->update_moved() method.
3904  
3905          accesslib_clear_all_caches(false);
3906          $DB->delete_records('cache_flags', array());
3907          $course = $DB->get_record('course', array('id'=>$testcourses[0]));
3908          $context = context_course::instance($course->id);
3909          $oldpath = $context->path;
3910          $miscid = $DB->get_field_sql("SELECT MIN(id) FROM {course_categories}");
3911          $categorycontext = context_coursecat::instance($miscid);
3912          $course->category = $miscid;
3913          $DB->update_record('course', $course);
3914          $context->update_moved($categorycontext);
3915  
3916          $context = context_course::instance($course->id);
3917          $this->assertEquals($categorycontext, $context->get_parent_context());
3918          $dirty = get_cache_flags('accesslib/dirtycontexts', time()-2);
3919          $this->assertFalse(isset($dirty[$oldpath]));
3920          $this->assertTrue(isset($dirty[$context->path]));
3921  
3922  
3923          // Test $context->delete_content() method.
3924  
3925          context_helper::reset_caches();
3926          $context = context_module::instance($testpages[3]);
3927          $this->assertTrue($DB->record_exists('context', array('id'=>$context->id)));
3928          $this->assertEquals(1, $DB->count_records('block_instances', array('parentcontextid'=>$context->id)));
3929          $context->delete_content();
3930          $this->assertTrue($DB->record_exists('context', array('id'=>$context->id)));
3931          $this->assertEquals(0, $DB->count_records('block_instances', array('parentcontextid'=>$context->id)));
3932  
3933  
3934          // Test $context->delete() method.
3935  
3936          context_helper::reset_caches();
3937          $context = context_module::instance($testpages[4]);
3938          $this->assertTrue($DB->record_exists('context', array('id'=>$context->id)));
3939          $this->assertEquals(1, $DB->count_records('block_instances', array('parentcontextid'=>$context->id)));
3940          $bi = $DB->get_record('block_instances', array('parentcontextid'=>$context->id));
3941          $bicontext = context_block::instance($bi->id);
3942          $DB->delete_records('cache_flags', array());
3943          $context->delete(); // Should delete also linked blocks.
3944          $dirty = get_cache_flags('accesslib/dirtycontexts', time()-2);
3945          $this->assertFalse(isset($dirty[$context->path]));
3946          $this->assertFalse($DB->record_exists('context', array('id'=>$context->id)));
3947          $this->assertFalse($DB->record_exists('context', array('id'=>$bicontext->id)));
3948          $this->assertFalse($DB->record_exists('context', array('contextlevel'=>CONTEXT_MODULE, 'instanceid'=>$testpages[4])));
3949          $this->assertFalse($DB->record_exists('context', array('contextlevel'=>CONTEXT_BLOCK, 'instanceid'=>$bi->id)));
3950          $this->assertEquals(0, $DB->count_records('block_instances', array('parentcontextid'=>$context->id)));
3951          context_module::instance($testpages[4]);
3952  
3953  
3954          // Test context_helper::delete_instance() method.
3955  
3956          context_helper::reset_caches();
3957          $lastcourse = array_pop($testcourses);
3958          $this->assertTrue($DB->record_exists('context', array('contextlevel'=>CONTEXT_COURSE, 'instanceid'=>$lastcourse)));
3959          $coursecontext = context_course::instance($lastcourse);
3960          $this->assertEquals(1, context_inspection::test_context_cache_size());
3961          $this->assertNotEquals(CONTEXT_COURSE, $coursecontext->instanceid);
3962          $DB->delete_records('cache_flags', array());
3963          context_helper::delete_instance(CONTEXT_COURSE, $lastcourse);
3964          $dirty = get_cache_flags('accesslib/dirtycontexts', time()-2);
3965          $this->assertFalse(isset($dirty[$coursecontext->path]));
3966          $this->assertEquals(0, context_inspection::test_context_cache_size());
3967          $this->assertFalse($DB->record_exists('context', array('contextlevel'=>CONTEXT_COURSE, 'instanceid'=>$lastcourse)));
3968          context_course::instance($lastcourse);
3969  
3970  
3971          // Test context_helper::create_instances() method.
3972  
3973          $prevcount = $DB->count_records('context');
3974          $DB->delete_records('context', array('contextlevel'=>CONTEXT_BLOCK));
3975          context_helper::create_instances(null, true);
3976          $this->assertSame($DB->count_records('context'), $prevcount);
3977          $this->assertEquals(0, $DB->count_records('context', array('depth'=>0)));
3978          $this->assertEquals(0, $DB->count_records('context', array('path'=>null)));
3979  
3980          $DB->delete_records('context', array('contextlevel'=>CONTEXT_BLOCK));
3981          $DB->delete_records('block_instances', array());
3982          $prevcount = $DB->count_records('context');
3983          $DB->delete_records_select('context', 'contextlevel <> '.CONTEXT_SYSTEM);
3984          context_helper::create_instances(null, true);
3985          $this->assertSame($prevcount, $DB->count_records('context'));
3986          $this->assertEquals(0, $DB->count_records('context', array('depth'=>0)));
3987          $this->assertEquals(0, $DB->count_records('context', array('path'=>null)));
3988  
3989          // Test context_helper::cleanup_instances() method.
3990  
3991          $lastcourse = $DB->get_field_sql("SELECT MAX(id) FROM {course}");
3992          $DB->delete_records('course', array('id'=>$lastcourse));
3993          $lastcategory = $DB->get_field_sql("SELECT MAX(id) FROM {course_categories}");
3994          $DB->delete_records('course_categories', array('id'=>$lastcategory));
3995          $lastuser = $DB->get_field_sql("SELECT MAX(id) FROM {user} WHERE deleted=0");
3996          $DB->delete_records('user', array('id'=>$lastuser));
3997          $DB->delete_records('block_instances', array('parentcontextid'=>$frontpagepagecontext->id));
3998          $DB->delete_records('course_modules', array('id'=>$frontpagepagecontext->instanceid));
3999          context_helper::cleanup_instances();
4000          $count = 1; // System.
4001          $count += $DB->count_records('user', array('deleted'=>0));
4002          $count += $DB->count_records('course_categories');
4003          $count += $DB->count_records('course');
4004          $count += $DB->count_records('course_modules');
4005          $count += $DB->count_records('block_instances');
4006          $this->assertEquals($count, $DB->count_records('context'));
4007  
4008  
4009          // Test context cache size restrictions.
4010  
4011          $testusers= array();
4012          for ($i=0; $i<CONTEXT_CACHE_MAX_SIZE + 100; $i++) {
4013              $user = $generator->create_user();
4014              $testusers[$i] = $user->id;
4015          }
4016          context_helper::create_instances(null, true);
4017          context_helper::reset_caches();
4018          for ($i=0; $i<CONTEXT_CACHE_MAX_SIZE + 100; $i++) {
4019              context_user::instance($testusers[$i]);
4020              if ($i == CONTEXT_CACHE_MAX_SIZE - 1) {
4021                  $this->assertEquals(CONTEXT_CACHE_MAX_SIZE, context_inspection::test_context_cache_size());
4022              } else if ($i == CONTEXT_CACHE_MAX_SIZE) {
4023                  // Once the limit is reached roughly 1/3 of records should be removed from cache.
4024                  $this->assertEquals((int)ceil(CONTEXT_CACHE_MAX_SIZE * (2/3) + 101), context_inspection::test_context_cache_size());
4025              }
4026          }
4027          // We keep the first 100 cached.
4028          $prevsize = context_inspection::test_context_cache_size();
4029          for ($i=0; $i<100; $i++) {
4030              context_user::instance($testusers[$i]);
4031              $this->assertEquals($prevsize, context_inspection::test_context_cache_size());
4032          }
4033          context_user::instance($testusers[102]);
4034          $this->assertEquals($prevsize+1, context_inspection::test_context_cache_size());
4035          unset($testusers);
4036  
4037  
4038  
4039          // Test basic test of legacy functions.
4040          // Note: watch out, the fake site might be pretty borked already.
4041  
4042          $this->assertEquals(get_system_context(), context_system::instance());
4043          $this->assertDebuggingCalled('get_system_context() is deprecated, please use context_system::instance() instead.', DEBUG_DEVELOPER);
4044  
4045          foreach ($DB->get_records('context') as $contextid => $record) {
4046              $context = context::instance_by_id($contextid);
4047              $this->assertEquals($context, get_context_instance($record->contextlevel, $record->instanceid));
4048              $this->assertDebuggingCalled('get_context_instance() is deprecated, please use context_xxxx::instance() instead.', DEBUG_DEVELOPER);
4049          }
4050  
4051          // Make sure a debugging is thrown.
4052          get_context_instance($record->contextlevel, $record->instanceid);
4053          $this->assertDebuggingCalled('get_context_instance() is deprecated, please use context_xxxx::instance() instead.', DEBUG_DEVELOPER);
4054          get_system_context();
4055          $this->assertDebuggingCalled('get_system_context() is deprecated, please use context_system::instance() instead.', DEBUG_DEVELOPER);
4056      }
4057  
4058      /**
4059       * Helper that verifies a list of capabilities, as returned by
4060       * $context->get_capabilities() contains certain capabilities.
4061       *
4062       * @param array $expected a list of capability names
4063       * @param array $actual a list of capability info from $context->get_capabilities().
4064       */
4065      protected function assert_capability_list_contains($expected, $actual) {
4066          $actualnames = [];
4067          foreach ($actual as $cap) {
4068              $actualnames[] = $cap->name;
4069          }
4070          // Verify each expected element exists.
4071          foreach ($expected as $key => $value) {
4072              $this->assertContains($value, $actualnames);
4073          }
4074      }
4075  
4076      /**
4077       * Test that context_system::get_capabilities returns capabilities relevant to all modules.
4078       *
4079       * @covers \context_system::get_capabilities
4080       */
4081      public function test_context_module_caps_returned_by_get_capabilities_in_sys_context() {
4082          $actual = context_system::instance()->get_capabilities();
4083  
4084          // Just test a few representative capabilities.
4085          $expectedcapabilities = ['moodle/site:accessallgroups', 'moodle/site:viewfullnames',
4086                  'repository/upload:view', 'atto/recordrtc:recordaudio'];
4087  
4088          $this->assert_capability_list_contains($expectedcapabilities, $actual);
4089      }
4090  
4091      /**
4092       * Test that context_coursecat::get_capabilities returns capabilities relevant to all modules.
4093       *
4094       * @covers \context_coursecat::get_capabilities
4095       */
4096      public function test_context_module_caps_returned_by_get_capabilities_in_course_cat_context() {
4097          $this->resetAfterTest(true);
4098          $generator = $this->getDataGenerator();
4099          $cat = $generator->create_category();
4100  
4101          $actual = context_coursecat::instance($cat->id)->get_capabilities();
4102  
4103          // Just test a few representative capabilities.
4104          $expectedcapabilities = ['moodle/site:accessallgroups', 'moodle/site:viewfullnames',
4105                  'repository/upload:view', 'atto/recordrtc:recordaudio'];
4106  
4107          $this->assert_capability_list_contains($expectedcapabilities, $actual);
4108      }
4109  
4110      /**
4111       * Test that context_course::get_capabilities returns capabilities relevant to all modules.
4112       *
4113       * @covers \context_course::get_capabilities
4114       */
4115      public function test_context_module_caps_returned_by_get_capabilities_in_course_context() {
4116          $this->resetAfterTest(true);
4117          $generator = $this->getDataGenerator();
4118          $cat = $generator->create_category();
4119          $course = $generator->create_course(['category' => $cat->id]);
4120  
4121          $actual = context_course::instance($course->id)->get_capabilities();
4122  
4123          // Just test a few representative capabilities.
4124          $expectedcapabilities = ['moodle/site:accessallgroups', 'moodle/site:viewfullnames',
4125                  'repository/upload:view', 'atto/recordrtc:recordaudio'];
4126  
4127          $this->assert_capability_list_contains($expectedcapabilities, $actual);
4128      }
4129  
4130      /**
4131       * Test that context_module::get_capabilities returns capabilities relevant to all modules.
4132       *
4133       * @covers \context_module::get_capabilities
4134       */
4135      public function test_context_module_caps_returned_by_get_capabilities_mod_context() {
4136          $this->resetAfterTest(true);
4137          $generator = $this->getDataGenerator();
4138          $cat = $generator->create_category();
4139          $course = $generator->create_course(['category' => $cat->id]);
4140          $page = $generator->create_module('page', ['course' => $course->id]);
4141  
4142          $actual = context_module::instance($page->cmid)->get_capabilities();
4143  
4144          // Just test a few representative capabilities.
4145          $expectedcapabilities = ['moodle/site:accessallgroups', 'moodle/site:viewfullnames',
4146                  'repository/upload:view', 'atto/recordrtc:recordaudio'];
4147  
4148          $this->assert_capability_list_contains($expectedcapabilities, $actual);
4149      }
4150  
4151      /**
4152       * Test that {@see context_block::get_capabilities} returns capabilities relevant to blocks
4153       *
4154       * @covers \context_block::get_capabilities
4155       */
4156      public function test_context_block_caps_returned_by_get_capabilities_block_context(): void {
4157          $this->resetAfterTest();
4158  
4159          $course = $this->getDataGenerator()->create_course();
4160          $block = $this->getDataGenerator()->create_block('online_users', [
4161              'parentcontextid' => context_course::instance($course->id)->id,
4162          ]);
4163  
4164          $capabilities = context_block::instance($block->id)->get_capabilities();
4165  
4166          // Just test a few representative capabilities.
4167          $expected = ['block/online_users:addinstance', 'moodle/block:edit', 'moodle/block:view'];
4168          $this->assert_capability_list_contains($expected, $capabilities);
4169  
4170          // Now test with different sorting.
4171          $capabilitiesbyname = context_block::instance($block->id)->get_capabilities('riskbitmask');
4172  
4173          $capabilitynames = array_column($capabilities, 'name');
4174          $capabilitynamesordered = array_column($capabilitiesbyname, 'name');
4175  
4176          // Each array should contain the same data, ordered differently.
4177          $this->assertEqualsCanonicalizing($capabilitynames, $capabilitynamesordered);
4178          $this->assertNotSame($capabilitynames, $capabilitynamesordered);
4179      }
4180  
4181      /**
4182       * Test that {@see context_user::get_capabilities} returns capabilities relevant to users
4183       *
4184       * @covers \context_user::get_capabilities
4185       */
4186      public function test_context_user_caps_returned_by_get_capabilities_user_context(): void {
4187          $this->resetAfterTest();
4188  
4189          $user = $this->getDataGenerator()->create_user();
4190          $capabilities = context_user::instance($user->id)->get_capabilities();
4191  
4192          // Just test a few representative capabilities.
4193          $expected = ['moodle/user:editmessageprofile', 'moodle/user:editprofile', 'moodle/user:viewalldetails'];
4194          $this->assert_capability_list_contains($expected, $capabilities);
4195  
4196          // Now test with different sorting.
4197          $capabilitiesbyname = context_user::instance($user->id)->get_capabilities('name');
4198  
4199          $capabilitynames = array_column($capabilities, 'name');
4200          $capabilitynamesordered = array_column($capabilitiesbyname, 'name');
4201  
4202          // Each array should contain the same data, ordered differently.
4203          $this->assertEqualsCanonicalizing($capabilitynames, $capabilitynamesordered);
4204          $this->assertNotSame($capabilitynames, $capabilitynamesordered);
4205      }
4206  
4207      /**
4208       * Test updating of role capabilities during upgrade
4209       *
4210       * @covers ::update_capabilities
4211       * @covers ::update_capabilities
4212       */
4213      public function test_update_capabilities() {
4214          global $DB, $SITE;
4215  
4216          $this->resetAfterTest(true);
4217  
4218          $froncontext = context_course::instance($SITE->id);
4219          $student = $DB->get_record('role', array('shortname'=>'student'));
4220          $teacher = $DB->get_record('role', array('shortname'=>'teacher'));
4221  
4222          $existingcaps = $DB->get_records('capabilities', array(), 'id', 'name, captype, contextlevel, component, riskbitmask');
4223  
4224          $this->assertFalse(isset($existingcaps['moodle/site:restore']));         // Moved to new 'moodle/restore:restorecourse'.
4225          $this->assertTrue(isset($existingcaps['moodle/restore:restorecourse'])); // New cap from 'moodle/site:restore'.
4226          $this->assertTrue(isset($existingcaps['moodle/site:sendmessage']));      // New capability.
4227          $this->assertTrue(isset($existingcaps['moodle/backup:backupcourse']));
4228          $this->assertTrue(isset($existingcaps['moodle/backup:backupsection']));  // Cloned from 'moodle/backup:backupcourse'.
4229          $this->assertTrue(isset($existingcaps['moodle/site:approvecourse']));    // Updated bitmask.
4230          $this->assertTrue(isset($existingcaps['moodle/course:manageactivities']));
4231          $this->assertTrue(isset($existingcaps['mod/page:addinstance']));         // Cloned from core 'moodle/course:manageactivities'.
4232  
4233          // Fake state before upgrade.
4234          $DB->set_field('capabilities', 'name', 'moodle/site:restore', array('name'=>'moodle/restore:restorecourse'));
4235          $DB->set_field('role_capabilities', 'capability', 'moodle/site:restore', array('capability'=>'moodle/restore:restorecourse'));
4236          assign_capability('moodle/site:restore', CAP_PROHIBIT, $teacher->id, $froncontext->id, true);
4237          $perms1 = array_values($DB->get_records('role_capabilities', array('capability'=>'moodle/site:restore', 'roleid'=>$teacher->id), 'contextid, permission', 'contextid, permission'));
4238  
4239          $DB->delete_records('role_capabilities', array('capability'=>'moodle/site:sendmessage'));
4240          $DB->delete_records('capabilities', array('name'=>'moodle/site:sendmessage'));
4241  
4242          $DB->delete_records('role_capabilities', array('capability'=>'moodle/backup:backupsection'));
4243          $DB->delete_records('capabilities', array('name'=>'moodle/backup:backupsection'));
4244          assign_capability('moodle/backup:backupcourse', CAP_PROHIBIT, $student->id, $froncontext->id, true);
4245          assign_capability('moodle/backup:backupcourse', CAP_ALLOW, $teacher->id, $froncontext->id, true);
4246  
4247          $DB->set_field('capabilities', 'riskbitmask', 0, array('name'=>'moodle/site:approvecourse'));
4248  
4249          $DB->delete_records('role_capabilities', array('capability'=>'mod/page:addinstance'));
4250          $DB->delete_records('capabilities', array('name'=>'mod/page:addinstance'));
4251          assign_capability('moodle/course:manageactivities', CAP_PROHIBIT, $student->id, $froncontext->id, true);
4252          assign_capability('moodle/course:manageactivities', CAP_ALLOW, $teacher->id, $froncontext->id, true);
4253  
4254          // Execute core.
4255          update_capabilities('moodle');
4256  
4257          // Only core should be upgraded.
4258          $caps = $DB->get_records('capabilities', array(), 'id', 'name, captype, contextlevel, component, riskbitmask');
4259  
4260          $this->assertFalse(isset($existingcaps['moodle/site:restore']));
4261          $this->assertTrue(isset($caps['moodle/restore:restorecourse']));
4262          $this->assertEquals($existingcaps['moodle/restore:restorecourse'], $caps['moodle/restore:restorecourse']);
4263          $perms2 = array_values($DB->get_records('role_capabilities', array('capability'=>'moodle/restore:restorecourse', 'roleid'=>$teacher->id), 'contextid, permission', 'contextid, permission'));
4264          $this->assertEquals($perms1, $perms2);
4265  
4266          $this->assertTrue(isset($caps['moodle/site:sendmessage']));
4267          $this->assertEquals($existingcaps['moodle/site:sendmessage'], $caps['moodle/site:sendmessage']);
4268  
4269          $this->assertTrue(isset($caps['moodle/backup:backupsection']));
4270          $this->assertEquals($existingcaps['moodle/backup:backupsection'], $caps['moodle/backup:backupsection']);
4271          $roles = $DB->get_records_sql('SELECT DISTINCT roleid AS id FROM {role_capabilities} WHERE capability=? OR capability=?', array('moodle/backup:backupcourse', 'moodle/backup:backupsection'));
4272          foreach ($roles as $role) {
4273              $perms1 = array_values($DB->get_records('role_capabilities', array('capability'=>'moodle/backup:backupcourse', 'roleid'=>$role->id), 'contextid, permission', 'contextid, permission'));
4274              $perms2 = array_values($DB->get_records('role_capabilities', array('capability'=>'moodle/backup:backupsection', 'roleid'=>$role->id), 'contextid, permission', 'contextid, permission'));
4275              $this->assertEquals($perms1, $perms2);
4276          }
4277  
4278          $this->assertTrue(isset($caps['moodle/site:approvecourse']));
4279          $this->assertEquals($existingcaps['moodle/site:approvecourse'], $caps['moodle/site:approvecourse']);
4280  
4281          $this->assertFalse(isset($caps['mod/page:addinstance']));
4282  
4283          // Execute plugin.
4284          update_capabilities('mod_page');
4285          $caps = $DB->get_records('capabilities', array(), 'id', 'name, captype, contextlevel, component, riskbitmask');
4286          $this->assertTrue(isset($caps['mod/page:addinstance']));
4287          $roles = $DB->get_records_sql('SELECT DISTINCT roleid AS id FROM {role_capabilities} WHERE capability=? OR capability=?', array('moodle/course:manageactivities', 'mod/page:addinstance'));
4288          foreach ($roles as $role) {
4289              $perms1 = array_values($DB->get_records('role_capabilities', array('capability'=>'moodle/course:manageactivities', 'roleid'=>$role->id), 'contextid, permission', 'contextid, permission'));
4290              $perms2 = array_values($DB->get_records('role_capabilities', array('capability'=>'mod/page:addinstance', 'roleid'=>$role->id), 'contextid, permission', 'contextid, permission'));
4291          }
4292          $this->assertEquals($perms1, $perms2);
4293      }
4294  
4295      /**
4296       * Checks install performance in update_capabilities.
4297       *
4298       * @covers ::update_capabilities()
4299       */
4300      public function test_update_capabilities_install_performance(): void {
4301          global $DB;
4302  
4303          $this->resetAfterTest();
4304  
4305          // Get rid of all the capabilities for forum.
4306          $testmodule = 'forum';
4307          $DB->delete_records_select('capabilities', 'name LIKE ?', ['mod/' . $testmodule . ':%']);
4308  
4309          $beforeq = $DB->perf_get_queries();
4310          update_capabilities('mod_' . $testmodule);
4311          $afterq = $DB->perf_get_queries();
4312  
4313          // In my testing there are currently 237 queries; there were 373 before a performance
4314          // fix. This test confirms performance doesn't degrade to near the previous level.
4315          $this->assertLessThan(300, $afterq - $beforeq);
4316      }
4317  
4318      /**
4319       * Checks install performance in update_capabilities when a new capability is cloned.
4320       *
4321       * This only has impact if there are a significant number of overrides of the existing
4322       * capability.
4323       *
4324       * @covers ::update_capabilities()
4325       */
4326      public function test_update_capabilities_clone_performance(): void {
4327          global $DB;
4328  
4329          $this->resetAfterTest();
4330  
4331          // Create a bunch of activities in a course. In each one, override so manager doesn't have
4332          // moodle/course:manageactivities.
4333          $generator = $this->getDataGenerator();
4334          $course = $generator->create_course();
4335          $roleid = $DB->get_field('role', 'id', ['shortname' => 'manager']);
4336          for ($i = 0; $i < 100; $i++) {
4337              $page = $generator->create_module('page', ['course' => $course->id]);
4338              $contextid = context_module::instance($page->cmid)->id;
4339              assign_capability('moodle/course:manageactivities', CAP_PREVENT, $roleid, $contextid);
4340          }
4341  
4342          // Get rid of one of the capabilities for forum, which clones moodle/course:manageactivities.
4343          $DB->delete_records('capabilities', ['name' => 'mod/forum:addinstance']);
4344  
4345          // Clear the context cache to simulate a realistic situation where we don't already have
4346          // all those contexts in the cache.
4347          accesslib_clear_all_caches_for_unit_testing();
4348  
4349          $beforeq = $DB->perf_get_queries();
4350          update_capabilities('mod_forum');
4351          $afterq = $DB->perf_get_queries();
4352  
4353          // In my testing there are currently 214 queries after performance was improved for cloning,
4354          // compared to 414 before. This test confirms performance doesn't degrade to near the
4355          // previous level.
4356          $this->assertLessThan(300, $afterq - $beforeq);
4357      }
4358  
4359      /**
4360       * Tests update_capabilities when a capability is cloned, but there are existing settings
4361       * for that capability.
4362       *
4363       * Under normal circumstances this shouldn't happen as it is only used for new capabilities,
4364       * but it's possible there could be incorrect data in database.)
4365       *
4366       * @covers ::update_capabilities()
4367       */
4368      public function test_update_capabilities_clone_existing(): void {
4369          global $DB;
4370  
4371          $this->resetAfterTest();
4372  
4373          // Create activities in a course. In each one, override so manager doesn't have
4374          // moodle/course:manageactivities. In one of them, also override mod/forum:addinstance
4375          // to something different.
4376          $generator = $this->getDataGenerator();
4377          $course = $generator->create_course();
4378          $roleid = $DB->get_field('role', 'id', ['shortname' => 'manager']);
4379          $page1 = $generator->create_module('page', ['course' => $course->id]);
4380          $context1 = context_module::instance($page1->cmid);
4381          assign_capability('moodle/course:manageactivities', CAP_PREVENT, $roleid, $context1->id);
4382          $page2 = $generator->create_module('page', ['course' => $course->id]);
4383          $context2 = context_module::instance($page2->cmid);
4384          assign_capability('moodle/course:manageactivities', CAP_PREVENT, $roleid, $context2->id);
4385          assign_capability('mod/forum:addinstance', CAP_PROHIBIT, $roleid, $context2->id);
4386  
4387          // Get rid of one of the capabilities for forum, which clones moodle/course:manageactivities.
4388          $DB->delete_records('capabilities', ['name' => 'mod/forum:addinstance']);
4389  
4390          // Reinstall the capability.
4391          update_capabilities('mod_forum');
4392  
4393          // Check the results: we should duplicate the manageactivities setting (PREVENT).
4394          $rec1 = $DB->get_record('role_capabilities', ['roleid' => $roleid,
4395                  'contextid' => $context1->id, 'capability' => 'mod/forum:addinstance']);
4396          $this->assertEquals(CAP_PREVENT, $rec1->permission);
4397          // The second page, we should overwrite the previous existing permission setting.
4398          $rec2 = $DB->get_record('role_capabilities', ['roleid' => $roleid,
4399                  'contextid' => $context2->id, 'capability' => 'mod/forum:addinstance']);
4400          $this->assertEquals(CAP_PREVENT, $rec2->permission);
4401      }
4402  
4403      /**
4404       * Tests reset_role_capabilities function.
4405       *
4406       * @covers ::reset_role_capabilities
4407       */
4408      public function test_reset_role_capabilities() {
4409          global $DB;
4410          $this->resetAfterTest(true);
4411          $generator = $this->getDataGenerator();
4412  
4413          // Create test course and user, enrol one in the other.
4414          $course = $generator->create_course();
4415          $user = $generator->create_user();
4416          $roleid = $DB->get_field('role', 'id', array('shortname' => 'student'), MUST_EXIST);
4417          $generator->enrol_user($user->id, $course->id, $roleid);
4418  
4419          // Change student role so it DOES have 'mod/forum:addinstance'.
4420          $systemcontext = context_system::instance();
4421          assign_capability('mod/forum:addinstance', CAP_ALLOW, $roleid, $systemcontext->id);
4422  
4423          // Override course so it does NOT allow students 'mod/forum:viewdiscussion'.
4424          $coursecontext = context_course::instance($course->id);
4425          assign_capability('mod/forum:viewdiscussion', CAP_PREVENT, $roleid, $coursecontext->id);
4426  
4427          // Check expected capabilities so far.
4428          $this->assertTrue(has_capability('mod/forum:addinstance', $coursecontext, $user));
4429          $this->assertFalse(has_capability('mod/forum:viewdiscussion', $coursecontext, $user));
4430  
4431          // Oops, allowing student to add forums was a mistake, let's reset the role.
4432          reset_role_capabilities($roleid);
4433  
4434          // Check new expected capabilities - role capabilities should have been reset,
4435          // while the override at course level should remain.
4436          $this->assertFalse(has_capability('mod/forum:addinstance', $coursecontext, $user));
4437          $this->assertFalse(has_capability('mod/forum:viewdiscussion', $coursecontext, $user));
4438      }
4439  
4440      /**
4441       * Tests count_role_users function.
4442       *
4443       * @covers ::count_role_users
4444       */
4445      public function test_count_role_users() {
4446          global $DB;
4447          $this->resetAfterTest(true);
4448          $generator = self::getDataGenerator();
4449          // Create a course in a category, and some users.
4450          $category = $generator->create_category();
4451          $course = $generator->create_course(array('category' => $category->id));
4452          $user1 = $generator->create_user();
4453          $user2 = $generator->create_user();
4454          $user3 = $generator->create_user();
4455          $user4 = $generator->create_user();
4456          $user5 = $generator->create_user();
4457          $roleid1 = $DB->get_field('role', 'id', array('shortname' => 'manager'), MUST_EXIST);
4458          $roleid2 = $DB->get_field('role', 'id', array('shortname' => 'coursecreator'), MUST_EXIST);
4459          // Enrol two users as managers onto the course, and 1 onto the category.
4460          $generator->enrol_user($user1->id, $course->id, $roleid1);
4461          $generator->enrol_user($user2->id, $course->id, $roleid1);
4462          $generator->role_assign($roleid1, $user3->id, context_coursecat::instance($category->id));
4463          // Enrol 1 user as a coursecreator onto the course, and another onto the category.
4464          // This is to ensure we do not count users with roles that are not specified.
4465          $generator->enrol_user($user4->id, $course->id, $roleid2);
4466          $generator->role_assign($roleid2, $user5->id, context_coursecat::instance($category->id));
4467          // Check that the correct users are found on the course.
4468          $this->assertEquals(2, count_role_users($roleid1, context_course::instance($course->id), false));
4469          $this->assertEquals(3, count_role_users($roleid1, context_course::instance($course->id), true));
4470          // Check for the category.
4471          $this->assertEquals(1, count_role_users($roleid1, context_coursecat::instance($category->id), false));
4472          $this->assertEquals(1, count_role_users($roleid1, context_coursecat::instance($category->id), true));
4473          // Have a user with the same role at both the category and course level.
4474          $generator->role_assign($roleid1, $user1->id, context_coursecat::instance($category->id));
4475          // The course level checks should remain the same.
4476          $this->assertEquals(2, count_role_users($roleid1, context_course::instance($course->id), false));
4477          $this->assertEquals(3, count_role_users($roleid1, context_course::instance($course->id), true));
4478      }
4479  
4480      /**
4481       * Test fetching users by capability.
4482       *
4483       * @covers ::get_users_by_capability
4484       */
4485      public function test_get_users_by_capability() {
4486          global $DB;
4487  
4488          $this->resetAfterTest();
4489  
4490          $course = $this->getDataGenerator()->create_course();
4491          $coursecontext = context_course::instance($course->id);
4492          $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'), '*', MUST_EXIST);
4493          $teacher = $this->getDataGenerator()->create_user();
4494          $studentrole = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
4495          $student = $this->getDataGenerator()->create_user();
4496          $guest = $DB->get_record('user', array('username' => 'guest'));
4497  
4498          role_assign($teacherrole->id, $teacher->id, $coursecontext);
4499          role_assign($studentrole->id, $student->id, $coursecontext);
4500          $admin = $DB->get_record('user', array('username' => 'admin'));
4501  
4502          // Note: Here are used default capabilities, the full test is in permission evaluation below,
4503          // use two capabilities that teacher has and one does not, none of them should be allowed for not-logged-in user.
4504          $this->assertTrue($DB->record_exists('capabilities', array('name' => 'moodle/backup:backupcourse')));
4505          $this->assertTrue($DB->record_exists('capabilities', array('name' => 'moodle/site:approvecourse')));
4506  
4507          $users = get_users_by_capability($coursecontext, 'moodle/backup:backupcourse');
4508  
4509          $this->assertTrue(array_key_exists($teacher->id, $users));
4510          $this->assertFalse(array_key_exists($admin->id, $users));
4511          $this->assertFalse(array_key_exists($student->id, $users));
4512          $this->assertFalse(array_key_exists($guest->id, $users));
4513  
4514          $users = get_users_by_capability($coursecontext, 'moodle/site:approvecourse');
4515  
4516          $this->assertFalse(array_key_exists($teacher->id, $users));
4517          $this->assertFalse(array_key_exists($admin->id, $users));
4518          $this->assertFalse(array_key_exists($student->id, $users));
4519          $this->assertFalse(array_key_exists($guest->id, $users));
4520  
4521          // Test role override.
4522          assign_capability('moodle/backup:backupcourse', CAP_PROHIBIT, $teacherrole->id, $coursecontext, true);
4523          assign_capability('moodle/backup:backupcourse', CAP_ALLOW, $studentrole->id, $coursecontext, true);
4524  
4525          $users = get_users_by_capability($coursecontext, 'moodle/backup:backupcourse');
4526  
4527          $this->assertFalse(array_key_exists($teacher->id, $users));
4528          $this->assertFalse(array_key_exists($admin->id, $users));
4529          $this->assertTrue(array_key_exists($student->id, $users));
4530          $this->assertFalse(array_key_exists($guest->id, $users));
4531      }
4532  
4533  
4534      /**
4535       * @covers ::get_with_capability_sql
4536       */
4537      public function test_get_with_capability_sql() {
4538          global $DB;
4539  
4540          $this->resetAfterTest();
4541  
4542          $course = $this->getDataGenerator()->create_course();
4543          $coursecontext = context_course::instance($course->id);
4544          $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'), '*', MUST_EXIST);
4545          $teacher = $this->getDataGenerator()->create_user();
4546          $studentrole = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
4547          $student = $this->getDataGenerator()->create_user();
4548          $guest = $DB->get_record('user', array('username' => 'guest'));
4549  
4550          role_assign($teacherrole->id, $teacher->id, $coursecontext);
4551          role_assign($studentrole->id, $student->id, $coursecontext);
4552          $admin = $DB->get_record('user', array('username' => 'admin'));
4553  
4554          // Note: Here are used default capabilities, the full test is in permission evaluation below,
4555          // use two capabilities that teacher has and one does not, none of them should be allowed for not-logged-in user.
4556          $this->assertTrue($DB->record_exists('capabilities', array('name' => 'moodle/backup:backupcourse')));
4557          $this->assertTrue($DB->record_exists('capabilities', array('name' => 'moodle/site:approvecourse')));
4558  
4559          list($sql, $params) = get_with_capability_sql($coursecontext, 'moodle/backup:backupcourse');
4560          $users = $DB->get_records_sql($sql, $params);
4561  
4562          $this->assertTrue(array_key_exists($teacher->id, $users));
4563          $this->assertFalse(array_key_exists($admin->id, $users));
4564          $this->assertFalse(array_key_exists($student->id, $users));
4565          $this->assertFalse(array_key_exists($guest->id, $users));
4566  
4567          list($sql, $params) = get_with_capability_sql($coursecontext, 'moodle/site:approvecourse');
4568          $users = $DB->get_records_sql($sql, $params);
4569  
4570          $this->assertFalse(array_key_exists($teacher->id, $users));
4571          $this->assertFalse(array_key_exists($admin->id, $users));
4572          $this->assertFalse(array_key_exists($student->id, $users));
4573          $this->assertFalse(array_key_exists($guest->id, $users));
4574  
4575          // Test role override.
4576          assign_capability('moodle/backup:backupcourse', CAP_PROHIBIT, $teacherrole->id, $coursecontext, true);
4577          assign_capability('moodle/backup:backupcourse', CAP_ALLOW, $studentrole->id, $coursecontext, true);
4578  
4579          list($sql, $params) = get_with_capability_sql($coursecontext, 'moodle/backup:backupcourse');
4580          $users = $DB->get_records_sql($sql, $params);
4581  
4582          $this->assertFalse(array_key_exists($teacher->id, $users));
4583          $this->assertFalse(array_key_exists($admin->id, $users));
4584          $this->assertTrue(array_key_exists($student->id, $users));
4585          $this->assertFalse(array_key_exists($guest->id, $users));
4586      }
4587  
4588  
4589      /**
4590       * Get the test cases for {@link test_get_with_capability_join_when_overrides_present()}.
4591       *
4592       * The particular capabilties used here do not really matter. What is important is
4593       * that they are capabilities which the Student roles has by default, but the
4594       * authenticated suser role does not.
4595       *
4596       * @return array
4597       */
4598      public function get_get_with_capability_join_override_cases() {
4599          return [
4600                  'no overrides' => [true, []],
4601                  'one override' => [true, ['moodle/course:viewscales']],
4602                  'both overrides' => [false, ['moodle/course:viewscales', 'moodle/question:flag']],
4603          ];
4604      }
4605  
4606      /**
4607       * Test get_with_capability_join.
4608       *
4609       * @dataProvider get_get_with_capability_join_override_cases
4610       * @covers ::get_with_capability_join
4611       *
4612       * @param bool $studentshouldbereturned whether, with this combination of capabilities, the student should be in the results.
4613       * @param array $capabilitiestoprevent capabilities to override to prevent in the course context.
4614       */
4615      public function test_get_with_capability_join_when_overrides_present(
4616              bool $studentshouldbereturned, array $capabilitiestoprevent) {
4617          global $DB;
4618          $this->resetAfterTest();
4619          $generator = $this->getDataGenerator();
4620  
4621          // Create a course.
4622          $category = $generator->create_category();
4623          $course = $generator->create_course(['category' => $category->id]);
4624  
4625          // Create a user.
4626          $student = $generator->create_user();
4627          $studentrole = $DB->get_record('role', ['shortname' => 'student'], '*', MUST_EXIST);
4628          $generator->enrol_user($student->id, $course->id, $studentrole->id);
4629  
4630          // This test assumes that by default the student roles has the two
4631          // capabilities. Check this now in case the role definitions are every changed.
4632          $coursecontext = context_course::instance($course->id);
4633          $this->assertTrue(has_capability('moodle/course:viewscales', $coursecontext, $student));
4634          $this->assertTrue(has_capability('moodle/question:flag', $coursecontext, $student));
4635  
4636          // We test cases where there are a varying number of prevent overrides.
4637          foreach ($capabilitiestoprevent as $capability) {
4638              role_change_permission($studentrole->id, $coursecontext, $capability, CAP_PREVENT);
4639          }
4640  
4641          // So now, assemble our query using the method under test, and verify that it returns the student.
4642          $sqljoin = get_with_capability_join($coursecontext,
4643                  ['moodle/course:viewscales', 'moodle/question:flag'], 'u.id');
4644  
4645          $users = $DB->get_records_sql("SELECT u.*
4646                    FROM {user} u
4647                         {$sqljoin->joins}
4648                   WHERE {$sqljoin->wheres}", $sqljoin->params);
4649          if ($studentshouldbereturned) {
4650              $this->assertEquals([$student->id], array_keys($users));
4651          } else {
4652              $this->assertEmpty($users);
4653          }
4654      }
4655  
4656      /**
4657       * Test the get_profile_roles() function.
4658       *
4659       * @covers ::get_profile_roles
4660       */
4661      public function test_get_profile_roles() {
4662          global $DB;
4663          $this->resetAfterTest();
4664  
4665          $course = $this->getDataGenerator()->create_course();
4666          $coursecontext = context_course::instance($course->id);
4667  
4668          // Assign a student role.
4669          $studentrole = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
4670          $user1 = $this->getDataGenerator()->create_user();
4671          role_assign($studentrole->id, $user1->id, $coursecontext);
4672  
4673          // Assign an editing teacher role.
4674          $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'), '*', MUST_EXIST);
4675          $user2 = $this->getDataGenerator()->create_user();
4676          role_assign($teacherrole->id, $user2->id, $coursecontext);
4677  
4678          // Create a custom role that can be assigned at course level, but don't assign it yet.
4679          create_role('Custom role', 'customrole', 'Custom course role');
4680          $customrole = $DB->get_record('role', array('shortname' => 'customrole'), '*', MUST_EXIST);
4681          set_role_contextlevels($customrole->id, [CONTEXT_COURSE]);
4682          core_role_set_assign_allowed($teacherrole->id, $customrole->id); // Allow teacher to assign the role in the course.
4683  
4684          // Set the site policy 'profileroles' to show student, teacher and non-editing teacher roles (i.e. not the custom role).
4685          $neteacherrole = $DB->get_record('role', array('shortname' => 'teacher'), '*', MUST_EXIST);
4686          set_config('profileroles', "{$studentrole->id}, {$teacherrole->id}, {$neteacherrole->id}");
4687  
4688          // A student in the course (given they can't assign roles) should see those roles which are:
4689          // - listed in the 'profileroles' site policy AND
4690          // - are assigned in the course context (or parent contexts).
4691          // In this case, the non-editing teacher role is not assigned and should not be returned.
4692          $expected = [
4693              $teacherrole->id => (object) [
4694                  'id' => $teacherrole->id,
4695                  'name' => '',
4696                  'shortname' => $teacherrole->shortname,
4697                  'sortorder' => $teacherrole->sortorder,
4698                  'coursealias' => null
4699              ],
4700              $studentrole->id => (object) [
4701                  'id' => $studentrole->id,
4702                  'name' => '',
4703                  'shortname' => $studentrole->shortname,
4704                  'sortorder' => $studentrole->sortorder,
4705                  'coursealias' => null
4706              ]
4707          ];
4708          $this->setUser($user1);
4709          $this->assertEquals($expected, get_profile_roles($coursecontext));
4710  
4711          // An editing teacher should also see only 2 roles at this stage as only 2 roles are assigned: 'teacher' and 'student'.
4712          $this->setUser($user2);
4713          $this->assertEquals($expected, get_profile_roles($coursecontext));
4714  
4715          // Assign a custom role in the course.
4716          $user3 = $this->getDataGenerator()->create_user();
4717          role_assign($customrole->id, $user3->id, $coursecontext);
4718  
4719          // Confirm that the teacher can see the custom role now that it's assigned.
4720          $expectedteacher = [
4721              $teacherrole->id => (object) [
4722                  'id' => $teacherrole->id,
4723                  'name' => '',
4724                  'shortname' => $teacherrole->shortname,
4725                  'sortorder' => $teacherrole->sortorder,
4726                  'coursealias' => null
4727              ],
4728              $studentrole->id => (object) [
4729                  'id' => $studentrole->id,
4730                  'name' => '',
4731                  'shortname' => $studentrole->shortname,
4732                  'sortorder' => $studentrole->sortorder,
4733                  'coursealias' => null
4734              ],
4735              $customrole->id => (object) [
4736                  'id' => $customrole->id,
4737                  'name' => 'Custom role',
4738                  'shortname' => $customrole->shortname,
4739                  'sortorder' => $customrole->sortorder,
4740                  'coursealias' => null
4741              ]
4742          ];
4743          $this->setUser($user2);
4744          $this->assertEquals($expectedteacher, get_profile_roles($coursecontext));
4745  
4746          // And that the student can't, because the role isn't included in the 'profileroles' site policy.
4747          $expectedstudent = [
4748              $teacherrole->id => (object) [
4749                  'id' => $teacherrole->id,
4750                  'name' => '',
4751                  'shortname' => $teacherrole->shortname,
4752                  'sortorder' => $teacherrole->sortorder,
4753                  'coursealias' => null
4754              ],
4755              $studentrole->id => (object) [
4756                  'id' => $studentrole->id,
4757                  'name' => '',
4758                  'shortname' => $studentrole->shortname,
4759                  'sortorder' => $studentrole->sortorder,
4760                  'coursealias' => null
4761              ]
4762          ];
4763          $this->setUser($user1);
4764          $this->assertEquals($expectedstudent, get_profile_roles($coursecontext));
4765  
4766          // If we have no roles listed in the site policy, the teacher should be able to see the assigned roles.
4767          $expectedteacher = [
4768              $studentrole->id => (object) [
4769                  'id' => $studentrole->id,
4770                  'name' => '',
4771                  'shortname' => $studentrole->shortname,
4772                  'sortorder' => $studentrole->sortorder,
4773                  'coursealias' => null
4774              ],
4775              $customrole->id => (object) [
4776                  'id' => $customrole->id,
4777                  'name' => 'Custom role',
4778                  'shortname' => $customrole->shortname,
4779                  'sortorder' => $customrole->sortorder,
4780                  'coursealias' => null
4781              ],
4782              $teacherrole->id => (object) [
4783                  'id' => $teacherrole->id,
4784                  'name' => '',
4785                  'shortname' => $teacherrole->shortname,
4786                  'sortorder' => $teacherrole->sortorder,
4787                  'coursealias' => null
4788              ],
4789          ];
4790          set_config('profileroles', "");
4791          $this->setUser($user2);
4792          $this->assertEquals($expectedteacher, get_profile_roles($coursecontext));
4793      }
4794  
4795      /**
4796       * Data provider for is_parent_of context checks.
4797       *
4798       * @return  array
4799       */
4800      public function is_parent_of_provider(): array {
4801          $provideboth = function(string $desc, string $contextpath, string $testpath, bool $expected): array {
4802              return [
4803                  "includeself: true; {$desc}" => [
4804                      $contextpath,
4805                      $testpath,
4806                      true,
4807                      $expected,
4808                  ],
4809                  "includeself: false; {$desc}" => [
4810                      $contextpath,
4811                      $testpath,
4812                      false,
4813                      $expected,
4814                  ],
4815              ];
4816          };
4817  
4818          return array_merge(
4819              [
4820                  'includeself: true, testing self' => [
4821                      '/1/4/17/291/1001/17105',
4822                      '/1/4/17/291/1001/17105',
4823                      true,
4824                      true,
4825                  ],
4826                  'includeself: false, testing self' => [
4827                      '/1/4/17/291/1001/17105',
4828                      '/1/4/17/291/1001/17105',
4829                      false,
4830                      false,
4831                  ],
4832              ],
4833              $provideboth(
4834                  'testing parent',
4835                  '/1/4/17/291/1001/17105',
4836                  '/1/4/17/291/1001',
4837                  false
4838              ),
4839              $provideboth(
4840                  'testing child',
4841                  '/1/4/17/291/1001',
4842                  '/1/4/17/291/1001/17105',
4843                  true
4844              ),
4845              $provideboth(
4846                  'testing grandchild',
4847                  '/1',
4848                  '/1/4/17/291/1001/17105',
4849                  true
4850              )
4851          );
4852      }
4853  
4854      /**
4855       * Ensure that the is_parent_of() function works as anticipated.
4856       *
4857       * @dataProvider is_parent_of_provider
4858       * @covers \context::is_parent_of
4859       * @covers \context_block::is_parent_of
4860       * @covers \context_course::is_parent_of
4861       * @covers \context_coursecat::is_parent_of
4862       * @covers \context_module::is_parent_of
4863       * @covers \context_system::is_parent_of
4864       * @covers \context_user::is_parent_of
4865       * @param   string $contextpath The path of the context being compared with
4866       * @param   string $testpath The path of the context being compared
4867       * @param   bool $testself Whether to check the current context
4868       * @param   bool $expected The expected result
4869       */
4870      public function test_is_parent_of(string $contextpath, string $testpath, bool $testself, bool $expected): void {
4871          $context = $this->getMockBuilder(\context::class)
4872              ->disableOriginalConstructor()
4873              ->onlyMethods([
4874                  'get_url',
4875                  'get_capabilities',
4876              ])
4877              ->getMock();
4878  
4879          $rcp = new ReflectionProperty($context, '_path');
4880          $rcp->setAccessible(true);
4881          $rcp->setValue($context, $contextpath);
4882  
4883          $comparisoncontext = $this->getMockBuilder(\context::class)
4884              ->disableOriginalConstructor()
4885              ->onlyMethods([
4886                  'get_url',
4887                  'get_capabilities',
4888              ])
4889              ->getMock();
4890  
4891          $rcp = new ReflectionProperty($comparisoncontext, '_path');
4892          $rcp->setAccessible(true);
4893          $rcp->setValue($comparisoncontext, $testpath);
4894  
4895          $this->assertEquals($expected, $context->is_parent_of($comparisoncontext, $testself));
4896      }
4897  
4898      /**
4899       * Data provider for is_child_of context checks.
4900       *
4901       * @return  array
4902       */
4903      public function is_child_of_provider(): array {
4904          $provideboth = function(string $desc, string $contextpath, string $testpath, bool $expected): array {
4905              return [
4906                  "includeself: true; {$desc}" => [
4907                      $contextpath,
4908                      $testpath,
4909                      true,
4910                      $expected,
4911                  ],
4912                  "includeself: false; {$desc}" => [
4913                      $contextpath,
4914                      $testpath,
4915                      false,
4916                      $expected,
4917                  ],
4918              ];
4919          };
4920  
4921          return array_merge(
4922              [
4923                  'includeself: true, testing self' => [
4924                      '/1/4/17/291/1001/17105',
4925                      '/1/4/17/291/1001/17105',
4926                      true,
4927                      true,
4928                  ],
4929                  'includeself: false, testing self' => [
4930                      '/1/4/17/291/1001/17105',
4931                      '/1/4/17/291/1001/17105',
4932                      false,
4933                      false,
4934                  ],
4935              ],
4936              $provideboth(
4937                  'testing child',
4938                  '/1/4/17/291/1001/17105',
4939                  '/1/4/17/291/1001',
4940                  true
4941              ),
4942              $provideboth(
4943                  'testing parent',
4944                  '/1/4/17/291/1001',
4945                  '/1/4/17/291/1001/17105',
4946                  false
4947              ),
4948              $provideboth(
4949                  'testing grandchild',
4950                  '/1/4/17/291/1001/17105',
4951                  '/1',
4952                  true
4953              ),
4954              $provideboth(
4955                  'testing grandparent',
4956                  '/1',
4957                  '/1/4/17/291/1001/17105',
4958                  false
4959              )
4960          );
4961      }
4962  
4963      /**
4964       * Ensure that the is_child_of() function works as anticipated.
4965       *
4966       * @dataProvider is_child_of_provider
4967       * @covers \context::is_child_of
4968       * @covers \context_block::is_child_of
4969       * @covers \context_course::is_child_of
4970       * @covers \context_coursecat::is_child_of
4971       * @covers \context_module::is_child_of
4972       * @covers \context_system::is_child_of
4973       * @covers \context_user::is_child_of
4974       * @param   string $contextpath The path of the context being compared with
4975       * @param   string $testpath The path of the context being compared
4976       * @param   bool $testself Whether to check the current context
4977       * @param   bool $expected The expected result
4978       */
4979      public function test_is_child_of(string $contextpath, string $testpath, bool $testself, bool $expected): void {
4980          $context = $this->getMockBuilder(\context::class)
4981              ->disableOriginalConstructor()
4982              ->onlyMethods([
4983                  'get_url',
4984                  'get_capabilities',
4985              ])
4986              ->getMock();
4987  
4988          $rcp = new ReflectionProperty($context, '_path');
4989          $rcp->setAccessible(true);
4990          $rcp->setValue($context, $contextpath);
4991  
4992          $comparisoncontext = $this->getMockBuilder(\context::class)
4993              ->disableOriginalConstructor()
4994              ->onlyMethods([
4995                  'get_url',
4996                  'get_capabilities',
4997              ])
4998              ->getMock();
4999  
5000          $rcp = new ReflectionProperty($comparisoncontext, '_path');
5001          $rcp->setAccessible(true);
5002          $rcp->setValue($comparisoncontext, $testpath);
5003  
5004          $this->assertEquals($expected, $context->is_child_of($comparisoncontext, $testself));
5005      }
5006  
5007      /**
5008       * Ensure that the get_parent_contexts() function limits the number of queries it performs.
5009       *
5010       * @covers ::get_parent_contexts
5011       */
5012      public function test_get_parent_contexts_preload() {
5013          global $DB;
5014  
5015          $this->resetAfterTest();
5016  
5017          /*
5018           * Given the following data structure:
5019           * System
5020           * - Category
5021           * --- Category
5022           * ----- Category
5023           * ------- Category
5024           * --------- Course
5025           * ----------- Activity (Forum)
5026           */
5027  
5028          $contexts = [];
5029  
5030          $cat1 = $this->getDataGenerator()->create_category();
5031          $cat2 = $this->getDataGenerator()->create_category(['parent' => $cat1->id]);
5032          $cat3 = $this->getDataGenerator()->create_category(['parent' => $cat2->id]);
5033          $cat4 = $this->getDataGenerator()->create_category(['parent' => $cat3->id]);
5034          $course = $this->getDataGenerator()->create_course(['category' => $cat4->id]);
5035          $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
5036  
5037          $modcontext = context_module::instance($forum->cmid);
5038  
5039          context_helper::reset_caches();
5040  
5041          // There should only be a single DB query.
5042          $predbqueries = $DB->perf_get_reads();
5043  
5044          $parents = $modcontext->get_parent_contexts();
5045          // Note: For some databases There is one read, plus one FETCH, plus one CLOSE.
5046          // These all show as reads, when there has actually only been a single query.
5047          $this->assertLessThanOrEqual(3, $DB->perf_get_reads() - $predbqueries);
5048      }
5049  
5050      /**
5051       * Ensure that get_with_capability_sql and get_with_capability_join respect context locking.
5052       *
5053       * @covers ::get_with_capability_join
5054       * @covers ::get_with_capability_sql
5055       */
5056      public function test_get_with_capability_sql_locked() {
5057          global $DB;
5058  
5059          $this->resetAfterTest();
5060  
5061          $generator = $this->getDataGenerator();
5062  
5063          $cat1 = $generator->create_category();
5064          $cat2 = $generator->create_category();
5065          $cat1course1 = $generator->create_course(['category' => $cat1->id]);
5066          $cat1course1forum = $generator->create_module('forum', ['course' => $cat1course1]);
5067  
5068          $contexts = (object) [
5069              'system' => \context_system::instance(),
5070              'cat1' => \context_coursecat::instance($cat1->id),
5071              'cat2' => \context_coursecat::instance($cat2->id),
5072              'cat1course1' => \context_course::instance($cat1course1->id),
5073              'cat1course1forum' => \context_module::instance($cat1course1forum->cmid),
5074          ];
5075  
5076          // Test with the 'mod/forum:startdiscussion' capability.
5077          $caput = 'mod/forum:startdiscussion';
5078  
5079          // Create a test user.
5080          $uut = $generator->create_and_enrol($cat1course1, 'teacher');
5081  
5082          // Initially the user will be returned by get_users_by_capability.
5083          list($sql, $params) = get_with_capability_sql($contexts->cat1course1forum, $caput);
5084          $users = $DB->get_records_sql($sql, $params);
5085          $this->assertArrayHasKey($uut->id, $users);
5086  
5087          // Freezing the forum will remove the user.
5088          set_config('contextlocking', 1);
5089          $contexts->cat1course1forum->set_locked(true);
5090          list($sql, $params) = get_with_capability_sql($contexts->cat1course1forum, $caput);
5091          $users = $DB->get_records_sql($sql, $params);
5092          $this->assertArrayNotHasKey($uut->id, $users);
5093  
5094          // But not if context locking is disabled.
5095          set_config('contextlocking', 0);
5096          list($sql, $params) = get_with_capability_sql($contexts->cat1course1forum, $caput);
5097          $users = $DB->get_records_sql($sql, $params);
5098          $this->assertArrayHasKey($uut->id, $users);
5099  
5100          $contexts->cat1course1forum->set_locked(false);
5101  
5102          // Freezing the course will have the same effect.
5103          set_config('contextlocking', 1);
5104          $contexts->cat1course1->set_locked(true);
5105          list($sql, $params) = get_with_capability_sql($contexts->cat1course1forum, $caput);
5106          $users = $DB->get_records_sql($sql, $params);
5107          $this->assertArrayNotHasKey($uut->id, $users);
5108  
5109          // But not if context locking is disabled.
5110          set_config('contextlocking', 0);
5111          list($sql, $params) = get_with_capability_sql($contexts->cat1course1forum, $caput);
5112          $users = $DB->get_records_sql($sql, $params);
5113          $this->assertArrayHasKey($uut->id, $users);
5114  
5115          $contexts->cat1course1->set_locked(false);
5116  
5117          // Freezing the category will have the same effect.
5118          set_config('contextlocking', 1);
5119          $contexts->cat1->set_locked(true);
5120          list($sql, $params) = get_with_capability_sql($contexts->cat1course1forum, $caput);
5121          $users = $DB->get_records_sql($sql, $params);
5122          $this->assertArrayNotHasKey($uut->id, $users);
5123  
5124          // But not if context locking is disabled.
5125          set_config('contextlocking', 0);
5126          list($sql, $params) = get_with_capability_sql($contexts->cat1course1forum, $caput);
5127          $users = $DB->get_records_sql($sql, $params);
5128          $this->assertArrayHasKey($uut->id, $users);
5129  
5130          $contexts->cat1->set_locked(false);
5131  
5132          // Freezing an unrelated category will have no effect.
5133          set_config('contextlocking', 1);
5134          $contexts->cat2->set_locked(true);
5135          list($sql, $params) = get_with_capability_sql($contexts->cat1course1forum, $caput);
5136          $users = $DB->get_records_sql($sql, $params);
5137          $this->assertArrayHasKey($uut->id, $users);
5138      }
5139  
5140      /**
5141       * Ensure that get_users_by_capability respects context freezing.
5142       *
5143       * @covers ::get_users_by_capability
5144       */
5145      public function test_get_users_by_capability_locked() {
5146          $this->resetAfterTest();
5147  
5148          $generator = $this->getDataGenerator();
5149  
5150          $cat1 = $generator->create_category();
5151          $cat2 = $generator->create_category();
5152          $cat1course1 = $generator->create_course(['category' => $cat1->id]);
5153          $cat1course1forum = $generator->create_module('forum', ['course' => $cat1course1]);
5154  
5155          $contexts = (object) [
5156              'system' => \context_system::instance(),
5157              'cat1' => \context_coursecat::instance($cat1->id),
5158              'cat2' => \context_coursecat::instance($cat2->id),
5159              'cat1course1' => \context_course::instance($cat1course1->id),
5160              'cat1course1forum' => \context_module::instance($cat1course1forum->cmid),
5161          ];
5162  
5163          // Test with the 'mod/forum:startdiscussion' capability.
5164          $caput = 'mod/forum:startdiscussion';
5165  
5166          // Create a test user.
5167          $uut = $generator->create_and_enrol($cat1course1, 'teacher');
5168  
5169          // Initially the user will be returned by get_users_by_capability.
5170          $users = get_users_by_capability($contexts->cat1course1forum, $caput);
5171          $this->assertArrayHasKey($uut->id, $users);
5172  
5173          // Freezing the forum will remove the user.
5174          set_config('contextlocking', 1);
5175          $contexts->cat1course1forum->set_locked(true);
5176          $users = get_users_by_capability($contexts->cat1course1forum, $caput);
5177          $this->assertArrayNotHasKey($uut->id, $users);
5178  
5179          // But not if context locking is disabled.
5180          set_config('contextlocking', 0);
5181          $users = get_users_by_capability($contexts->cat1course1forum, $caput);
5182          $this->assertArrayHasKey($uut->id, $users);
5183  
5184          $contexts->cat1course1forum->set_locked(false);
5185  
5186          // Freezing the course will have the same effect.
5187          set_config('contextlocking', 1);
5188          $contexts->cat1course1->set_locked(true);
5189          $users = get_users_by_capability($contexts->cat1course1forum, $caput);
5190          $this->assertArrayNotHasKey($uut->id, $users);
5191  
5192          // But not if context locking is disabled.
5193          set_config('contextlocking', 0);
5194          $users = get_users_by_capability($contexts->cat1course1forum, $caput);
5195          $this->assertArrayHasKey($uut->id, $users);
5196  
5197          $contexts->cat1course1->set_locked(false);
5198  
5199          // Freezing the category will have the same effect.
5200          set_config('contextlocking', 1);
5201          $contexts->cat1->set_locked(true);
5202          $users = get_users_by_capability($contexts->cat1course1forum, $caput);
5203          $this->assertArrayNotHasKey($uut->id, $users);
5204  
5205          // But not if context locking is disabled.
5206          set_config('contextlocking', 0);
5207          $users = get_users_by_capability($contexts->cat1course1forum, $caput);
5208          $this->assertArrayHasKey($uut->id, $users);
5209  
5210          $contexts->cat1->set_locked(false);
5211  
5212          // Freezing an unrelated category will have no effect.
5213          set_config('contextlocking', 1);
5214          $contexts->cat2->set_locked(true);
5215          $users = get_users_by_capability($contexts->cat1course1forum, $caput);
5216          $this->assertArrayHasKey($uut->id, $users);
5217      }
5218  
5219      /**
5220       * Test require_all_capabilities.
5221       *
5222       * @covers ::require_all_capabilities
5223       */
5224      public function test_require_all_capabilities() {
5225          global $DB;
5226  
5227          $this->resetAfterTest();
5228  
5229          $course = $this->getDataGenerator()->create_course();
5230          $coursecontext = context_course::instance($course->id);
5231          $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'), '*', MUST_EXIST);
5232          $teacher = $this->getDataGenerator()->create_user();
5233          role_assign($teacherrole->id, $teacher->id, $coursecontext);
5234  
5235          // Note: Here are used default capabilities, the full test is in permission evaluation bellow,
5236          // use two capabilities that teacher has and one does not, none of them should be allowed for not-logged-in user.
5237          $this->assertTrue($DB->record_exists('capabilities', array('name' => 'moodle/backup:backupsection')));
5238          $this->assertTrue($DB->record_exists('capabilities', array('name' => 'moodle/backup:backupcourse')));
5239  
5240          $sca = array('moodle/backup:backupsection', 'moodle/backup:backupcourse');
5241  
5242          $this->setUser($teacher);
5243          require_all_capabilities($sca, $coursecontext);
5244          require_all_capabilities($sca, $coursecontext, $teacher);
5245  
5246          // Guest users should not have any of these perms.
5247          $this->setUser(0);
5248          $this->expectException(\required_capability_exception::class);
5249          require_all_capabilities($sca, $coursecontext);
5250      }
5251  
5252      /**
5253       * Test get_navigation_filter_context.
5254       *
5255       * @covers ::get_navigation_filter_context
5256       */
5257      public function test_get_navigation_filter_context() {
5258          $this->resetAfterTest();
5259          $course = $this->getDataGenerator()->create_course();
5260          set_config('filternavigationwithsystemcontext', 0);
5261          // First test passed values are returned if disabled.
5262          $this->assertNull(context_helper::get_navigation_filter_context(null));
5263          $coursecontext = context_course::instance($course->id);
5264          $filtercontext = context_helper::get_navigation_filter_context($coursecontext);
5265          $this->assertEquals($coursecontext->id, $filtercontext->id);
5266  
5267          // Now test that any input returns system context if enabled.
5268          set_config('filternavigationwithsystemcontext', 1);
5269          $filtercontext = context_helper::get_navigation_filter_context(null);
5270          $this->assertInstanceOf('\context_system', $filtercontext);
5271          $filtercontext = context_helper::get_navigation_filter_context($coursecontext);
5272          $this->assertInstanceOf('\context_system', $filtercontext);
5273      }
5274  }
5275  
5276  /**
5277   * Context caching fixture
5278   */
5279  abstract class context_inspection extends \core\context_helper {
5280      public static function test_context_cache_size() {
5281          return self::$cache_count;
5282      }
5283  }