Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

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