Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

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

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