Proyectos de Subversion Moodle

Rev

Ir a la última revisión | | 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
namespace core;
18
 
19
use advanced_testcase;
20
use cache;
21
use cm_info;
22
use coding_exception;
23
use context_course;
24
use context_module;
25
use course_modinfo;
26
use core_courseformat\formatactions;
27
use moodle_exception;
28
use moodle_url;
29
use Exception;
30
 
31
/**
32
 * Unit tests for lib/modinfolib.php.
33
 *
34
 * @package    core
35
 * @category   phpunit
36
 * @copyright  2012 Andrew Davis
37
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
38
 */
39
class modinfolib_test extends advanced_testcase {
40
    /**
41
     * Setup to ensure that fixtures are loaded.
42
     */
43
    public static function setUpBeforeClass(): void {
44
        global $CFG;
45
        require_once($CFG->dirroot . '/course/lib.php');
46
        require_once($CFG->libdir . '/tests/fixtures/sectiondelegatetest.php');
47
    }
48
 
49
    public function test_section_info_properties() {
50
        global $DB, $CFG;
51
 
52
        $this->resetAfterTest();
53
        $oldcfgenableavailability = $CFG->enableavailability;
54
        $oldcfgenablecompletion = $CFG->enablecompletion;
55
        set_config('enableavailability', 1);
56
        set_config('enablecompletion', 1);
57
        $this->setAdminUser();
58
 
59
        // Generate the course and pre-requisite module.
60
        $course = $this->getDataGenerator()->create_course(
61
                array('format' => 'topics',
62
                    'numsections' => 3,
63
                    'enablecompletion' => 1,
64
                    'groupmode' => SEPARATEGROUPS,
65
                    'forcegroupmode' => 0),
66
                array('createsections' => true));
67
        $coursecontext = context_course::instance($course->id);
68
        $prereqforum = $this->getDataGenerator()->create_module('forum',
69
                array('course' => $course->id),
70
                array('completion' => 1));
71
 
72
        // Add availability conditions.
73
        $availability = '{"op":"&","showc":[true,true,true],"c":[' .
74
                '{"type":"completion","cm":' . $prereqforum->cmid . ',"e":"' .
75
                    COMPLETION_COMPLETE . '"},' .
76
                '{"type":"grade","id":666,"min":0.4},' .
77
                '{"type":"profile","op":"contains","sf":"email","v":"test"}' .
78
                ']}';
79
        $DB->set_field('course_sections', 'availability', $availability,
80
                array('course' => $course->id, 'section' => 2));
81
        rebuild_course_cache($course->id, true);
82
        $sectiondb = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 2));
83
 
84
        // Create and enrol a student.
85
        $studentrole = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
86
        $student = $this->getDataGenerator()->create_user();
87
        role_assign($studentrole->id, $student->id, $coursecontext);
88
        $enrolplugin = enrol_get_plugin('manual');
89
        $enrolinstance = $DB->get_record('enrol', array('courseid' => $course->id, 'enrol' => 'manual'));
90
        $enrolplugin->enrol_user($enrolinstance, $student->id);
91
        $this->setUser($student);
92
 
93
        // Get modinfo.
94
        $modinfo = get_fast_modinfo($course->id);
95
        $si = $modinfo->get_section_info(2);
96
 
97
        $this->assertEquals($sectiondb->id, $si->id);
98
        $this->assertEquals($sectiondb->course, $si->course);
99
        $this->assertEquals($sectiondb->section, $si->section);
100
        $this->assertEquals($sectiondb->name, $si->name);
101
        $this->assertEquals($sectiondb->visible, $si->visible);
102
        $this->assertEquals($sectiondb->summary, $si->summary);
103
        $this->assertEquals($sectiondb->summaryformat, $si->summaryformat);
104
        $this->assertEquals($sectiondb->sequence, $si->sequence); // Since this section does not contain invalid modules.
105
        $this->assertEquals($availability, $si->availability);
106
 
107
        // Dynamic fields, just test that they can be retrieved (must be carefully tested in each activity type).
108
        $this->assertEquals(0, $si->available);
109
        $this->assertNotEmpty($si->availableinfo); // Lists all unmet availability conditions.
110
        $this->assertEquals(0, $si->uservisible);
111
 
112
        // Restore settings.
113
        set_config('enableavailability', $oldcfgenableavailability);
114
        set_config('enablecompletion', $oldcfgenablecompletion);
115
    }
116
 
117
    public function test_cm_info_properties() {
118
        global $DB, $CFG;
119
 
120
        $this->resetAfterTest();
121
        $oldcfgenableavailability = $CFG->enableavailability;
122
        $oldcfgenablecompletion = $CFG->enablecompletion;
123
        set_config('enableavailability', 1);
124
        set_config('enablecompletion', 1);
125
        $this->setAdminUser();
126
 
127
        // Generate the course and pre-requisite module.
128
        $course = $this->getDataGenerator()->create_course(
129
                array('format' => 'topics',
130
                    'numsections' => 3,
131
                    'enablecompletion' => 1,
132
                    'groupmode' => SEPARATEGROUPS,
133
                    'forcegroupmode' => 0),
134
                array('createsections' => true));
135
        $coursecontext = context_course::instance($course->id);
136
        $prereqforum = $this->getDataGenerator()->create_module('forum',
137
                array('course' => $course->id),
138
                array('completion' => 1));
139
 
140
        // Generate module and add availability conditions.
141
        $availability = '{"op":"&","showc":[true,true,true],"c":[' .
142
                '{"type":"completion","cm":' . $prereqforum->cmid . ',"e":"' .
143
                    COMPLETION_COMPLETE . '"},' .
144
                '{"type":"grade","id":666,"min":0.4},' .
145
                '{"type":"profile","op":"contains","sf":"email","v":"test"}' .
146
                ']}';
147
        $assign = $this->getDataGenerator()->create_module('assign',
148
                array('course' => $course->id),
149
                array('idnumber' => 123,
150
                    'groupmode' => VISIBLEGROUPS,
151
                    'availability' => $availability));
152
        rebuild_course_cache($course->id, true);
153
 
154
        // Retrieve all related records from DB.
155
        $assigndb = $DB->get_record('assign', array('id' => $assign->id));
156
        $moduletypedb = $DB->get_record('modules', array('name' => 'assign'));
157
        $moduledb = $DB->get_record('course_modules', array('module' => $moduletypedb->id, 'instance' => $assign->id));
158
        $sectiondb = $DB->get_record('course_sections', array('id' => $moduledb->section));
159
        $modnamessingular = get_module_types_names(false);
160
        $modnamesplural = get_module_types_names(true);
161
 
162
        // Create and enrol a student.
163
        $studentrole = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
164
        $student = $this->getDataGenerator()->create_user();
165
        role_assign($studentrole->id, $student->id, $coursecontext);
166
        $enrolplugin = enrol_get_plugin('manual');
167
        $enrolinstance = $DB->get_record('enrol', array('courseid' => $course->id, 'enrol' => 'manual'));
168
        $enrolplugin->enrol_user($enrolinstance, $student->id);
169
        $this->setUser($student);
170
 
171
        // Emulate data used in building course cache to receive the same instance of cached_cm_info as was used in building modinfo.
172
        $rawmods = get_course_mods($course->id);
173
        $cachedcminfo = assign_get_coursemodule_info($rawmods[$moduledb->id]);
174
 
175
        // Get modinfo.
176
        $modinfo = get_fast_modinfo($course->id);
177
        $cm = $modinfo->instances['assign'][$assign->id];
178
 
179
        $this->assertEquals($moduledb->id, $cm->id);
180
        $this->assertEquals($assigndb->id, $cm->instance);
181
        $this->assertEquals($moduledb->course, $cm->course);
182
        $this->assertEquals($moduledb->idnumber, $cm->idnumber);
183
        $this->assertEquals($moduledb->added, $cm->added);
184
        $this->assertEquals($moduledb->visible, $cm->visible);
185
        $this->assertEquals($moduledb->visibleold, $cm->visibleold);
186
        $this->assertEquals($moduledb->groupmode, $cm->groupmode);
187
        $this->assertEquals(VISIBLEGROUPS, $cm->groupmode);
188
        $this->assertEquals($moduledb->groupingid, $cm->groupingid);
189
        $this->assertEquals($course->groupmodeforce, $cm->coursegroupmodeforce);
190
        $this->assertEquals($course->groupmode, $cm->coursegroupmode);
191
        $this->assertEquals(SEPARATEGROUPS, $cm->coursegroupmode);
192
        $this->assertEquals($course->groupmodeforce ? $course->groupmode : $moduledb->groupmode,
193
                $cm->effectivegroupmode); // (since mod_assign supports groups).
194
        $this->assertEquals(VISIBLEGROUPS, $cm->effectivegroupmode);
195
        $this->assertEquals($moduledb->indent, $cm->indent);
196
        $this->assertEquals($moduledb->completion, $cm->completion);
197
        $this->assertEquals($moduledb->completiongradeitemnumber, $cm->completiongradeitemnumber);
198
        $this->assertEquals($moduledb->completionpassgrade, $cm->completionpassgrade);
199
        $this->assertEquals($moduledb->completionview, $cm->completionview);
200
        $this->assertEquals($moduledb->completionexpected, $cm->completionexpected);
201
        $this->assertEquals($moduledb->showdescription, $cm->showdescription);
202
        $this->assertEquals(null, $cm->extra); // Deprecated field. Used in module types that don't return cached_cm_info.
203
        $this->assertEquals($cachedcminfo->icon, $cm->icon);
204
        $this->assertEquals($cachedcminfo->iconcomponent, $cm->iconcomponent);
205
        $this->assertEquals('assign', $cm->modname);
206
        $this->assertEquals($moduledb->module, $cm->module);
207
        $this->assertEquals($cachedcminfo->name, $cm->name);
208
        $this->assertEquals($sectiondb->section, $cm->sectionnum);
209
        $this->assertEquals($moduledb->section, $cm->section);
210
        $this->assertEquals($availability, $cm->availability);
211
        $this->assertEquals(context_module::instance($moduledb->id), $cm->context);
212
        $this->assertEquals($modnamessingular['assign'], $cm->modfullname);
213
        $this->assertEquals($modnamesplural['assign'], $cm->modplural);
214
        $this->assertEquals(new moodle_url('/mod/assign/view.php', array('id' => $moduledb->id)), $cm->url);
215
        $this->assertEquals($cachedcminfo->customdata, $cm->customdata);
216
 
217
        // Dynamic fields, just test that they can be retrieved (must be carefully tested in each activity type).
218
        $this->assertNotEmpty($cm->availableinfo); // Lists all unmet availability conditions.
219
        $this->assertEquals(0, $cm->uservisible);
220
        $this->assertEquals('', $cm->extraclasses);
221
        $this->assertEquals('', $cm->onclick);
222
        $this->assertEquals(null, $cm->afterlink);
223
        $this->assertEquals(null, $cm->afterediticons);
224
        $this->assertEquals('', $cm->content);
225
 
226
        // Attempt to access and set non-existing field.
227
        $this->assertTrue(empty($modinfo->somefield));
228
        $this->assertFalse(isset($modinfo->somefield));
229
        $cm->somefield;
230
        $this->assertDebuggingCalled();
231
        $cm->somefield = 'Some value';
232
        $this->assertDebuggingCalled();
233
        $this->assertEmpty($cm->somefield);
234
        $this->assertDebuggingCalled();
235
 
236
        // Attempt to overwrite an existing field.
237
        $prevvalue = $cm->name;
238
        $this->assertNotEmpty($cm->name);
239
        $this->assertFalse(empty($cm->name));
240
        $this->assertTrue(isset($cm->name));
241
        $cm->name = 'Illegal overwriting';
242
        $this->assertDebuggingCalled();
243
        $this->assertEquals($prevvalue, $cm->name);
244
        $this->assertDebuggingNotCalled();
245
 
246
        // Restore settings.
247
        set_config('enableavailability', $oldcfgenableavailability);
248
        set_config('enablecompletion', $oldcfgenablecompletion);
249
    }
250
 
251
    public function test_matching_cacherev() {
252
        global $DB, $CFG;
253
 
254
        $this->resetAfterTest();
255
        $this->setAdminUser();
256
        $cache = cache::make('core', 'coursemodinfo');
257
 
258
        // Generate the course and pre-requisite module.
259
        $course = $this->getDataGenerator()->create_course(
260
                array('format' => 'topics',
261
                    'numsections' => 3),
262
                array('createsections' => true));
263
 
264
        // Make sure the cacherev is set.
265
        $cacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));
266
        $this->assertGreaterThan(0, $cacherev);
267
        $prevcacherev = $cacherev;
268
 
269
        // Reset course cache and make sure cacherev is bumped up but cache is empty.
270
        rebuild_course_cache($course->id, true);
271
        $cacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));
272
        $this->assertGreaterThan($prevcacherev, $cacherev);
273
        $this->assertEmpty($cache->get_versioned($course->id, $prevcacherev));
274
        $prevcacherev = $cacherev;
275
 
276
        // Build course cache. Cacherev should not change but cache is now not empty. Make sure cacherev is the same everywhere.
277
        $modinfo = get_fast_modinfo($course->id);
278
        $cacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));
279
        $this->assertEquals($prevcacherev, $cacherev);
280
        $cachedvalue = $cache->get_versioned($course->id, $cacherev);
281
        $this->assertNotEmpty($cachedvalue);
282
        $this->assertEquals($cacherev, $cachedvalue->cacherev);
283
        $this->assertEquals($cacherev, $modinfo->get_course()->cacherev);
284
        $prevcacherev = $cacherev;
285
 
286
        // Little trick to check that cache is not rebuilt druing the next step - substitute the value in MUC and later check that it is still there.
287
        $cache->acquire_lock($course->id);
288
        $cache->set_versioned($course->id, $cacherev, (object)array_merge((array)$cachedvalue, array('secretfield' => 1)));
289
        $cache->release_lock($course->id);
290
 
291
        // Clear static cache and call get_fast_modinfo() again (pretend we are in another request). Cache should not be rebuilt.
292
        course_modinfo::clear_instance_cache();
293
        $modinfo = get_fast_modinfo($course->id);
294
        $cacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));
295
        $this->assertEquals($prevcacherev, $cacherev);
296
        $cachedvalue = $cache->get_versioned($course->id, $cacherev);
297
        $this->assertNotEmpty($cachedvalue);
298
        $this->assertEquals($cacherev, $cachedvalue->cacherev);
299
        $this->assertNotEmpty($cachedvalue->secretfield);
300
        $this->assertEquals($cacherev, $modinfo->get_course()->cacherev);
301
        $prevcacherev = $cacherev;
302
 
303
        // Rebuild course cache. Cacherev must be incremented everywhere.
304
        rebuild_course_cache($course->id);
305
        $cacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));
306
        $this->assertGreaterThan($prevcacherev, $cacherev);
307
        $cachedvalue = $cache->get_versioned($course->id, $cacherev);
308
        $this->assertNotEmpty($cachedvalue);
309
        $this->assertEquals($cacherev, $cachedvalue->cacherev);
310
        $modinfo = get_fast_modinfo($course->id);
311
        $this->assertEquals($cacherev, $modinfo->get_course()->cacherev);
312
        $prevcacherev = $cacherev;
313
 
314
        // Update cacherev in DB and make sure the cache will be rebuilt on the next call to get_fast_modinfo().
315
        increment_revision_number('course', 'cacherev', 'id = ?', array($course->id));
316
        // We need to clear static cache for course_modinfo instances too.
317
        course_modinfo::clear_instance_cache();
318
        $modinfo = get_fast_modinfo($course->id);
319
        $cacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));
320
        $this->assertGreaterThan($prevcacherev, $cacherev);
321
        $cachedvalue = $cache->get_versioned($course->id, $cacherev);
322
        $this->assertNotEmpty($cachedvalue);
323
        $this->assertEquals($cacherev, $cachedvalue->cacherev);
324
        $this->assertEquals($cacherev, $modinfo->get_course()->cacherev);
325
        $prevcacherev = $cacherev;
326
 
327
        // Reset cache for all courses and make sure this course cache is reset.
328
        rebuild_course_cache(0, true);
329
        $cacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));
330
        $this->assertGreaterThan($prevcacherev, $cacherev);
331
        $this->assertEmpty($cache->get_versioned($course->id, $cacherev));
332
        // Rebuild again.
333
        $modinfo = get_fast_modinfo($course->id);
334
        $cachedvalue = $cache->get_versioned($course->id, $cacherev);
335
        $this->assertNotEmpty($cachedvalue);
336
        $this->assertEquals($cacherev, $cachedvalue->cacherev);
337
        $this->assertEquals($cacherev, $modinfo->get_course()->cacherev);
338
        $prevcacherev = $cacherev;
339
 
340
        // Purge all caches and make sure cacherev is increased and data from MUC erased.
341
        purge_all_caches();
342
        $cacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));
343
        $this->assertGreaterThan($prevcacherev, $cacherev);
344
        $this->assertEmpty($cache->get($course->id));
345
    }
346
 
347
    /**
348
     * The cacherev is updated when we rebuild course cache, but there are scenarios where an
349
     * existing course object with old cacherev might be reused within the same request after
350
     * clearing the cache. In that case, we need to check that the new data is loaded and it
351
     * does not reuse the old cached data with old cacherev.
352
     *
353
     * @covers ::rebuild_course_cache()
354
     */
355
    public function test_cache_clear_wrong_cacherev(): void {
356
        global $DB;
357
 
358
        $this->resetAfterTest();
359
        $originalcourse = $this->getDataGenerator()->create_course();
360
        $course = $DB->get_record('course', ['id' => $originalcourse->id]);
361
        $page = $this->getDataGenerator()->create_module('page',
362
                ['course' => $course->id, 'name' => 'frog']);
363
        $oldmodinfo = get_fast_modinfo($course);
364
        $this->assertEquals('frog', $oldmodinfo->get_cm($page->cmid)->name);
365
 
366
        // Change page name and rebuild cache.
367
        $DB->set_field('page', 'name', 'Frog', ['id' => $page->id]);
368
        rebuild_course_cache($course->id, true);
369
 
370
        // Get modinfo using original course object which has old cacherev.
371
        $newmodinfo = get_fast_modinfo($course);
372
        $this->assertEquals('Frog', $newmodinfo->get_cm($page->cmid)->name);
373
    }
374
 
375
    /**
376
     * When cacherev is updated for a course, it is supposed to update in the $COURSE and $SITE
377
     * globals automatically. Check this is working.
378
     *
379
     * @covers ::rebuild_course_cache()
380
     */
381
    public function test_cacherev_update_in_globals(): void {
382
        global $DB, $COURSE, $SITE;
383
 
384
        $this->resetAfterTest();
385
 
386
        // Create a course and get modinfo.
387
        $originalcourse = $this->getDataGenerator()->create_course();
388
        $oldmodinfo = get_fast_modinfo($originalcourse->id);
389
 
390
        // Store (two clones of) the course in COURSE and SITE globals.
391
        $COURSE = get_course($originalcourse->id);
392
        $SITE = get_course($originalcourse->id);
393
 
394
        // Note original cacherev.
395
        $originalcacherev = $oldmodinfo->get_course()->cacherev;
396
        $this->assertEquals($COURSE->cacherev, $originalcacherev);
397
        $this->assertEquals($SITE->cacherev, $originalcacherev);
398
 
399
        // Clear the cache and check cacherev updated.
400
        rebuild_course_cache($originalcourse->id, true);
401
 
402
        $newcourse = $DB->get_record('course', ['id' => $originalcourse->id]);
403
        $this->assertGreaterThan($originalcacherev, $newcourse->cacherev);
404
 
405
        // Check that the in-memory $COURSE and $SITE have updated.
406
        $this->assertEquals($newcourse->cacherev, $COURSE->cacherev);
407
        $this->assertEquals($newcourse->cacherev, $SITE->cacherev);
408
    }
409
 
410
    public function test_course_modinfo_properties() {
411
        global $USER, $DB;
412
 
413
        $this->resetAfterTest();
414
        $this->setAdminUser();
415
 
416
        // Generate the course and some modules. Make one section hidden.
417
        $course = $this->getDataGenerator()->create_course(
418
                array('format' => 'topics',
419
                    'numsections' => 3),
420
                array('createsections' => true));
421
        $DB->execute('UPDATE {course_sections} SET visible = 0 WHERE course = ? and section = ?',
422
                array($course->id, 3));
423
        $coursecontext = context_course::instance($course->id);
424
        $forum0 = $this->getDataGenerator()->create_module('forum',
425
                array('course' => $course->id), array('section' => 0));
426
        $assign0 = $this->getDataGenerator()->create_module('assign',
427
                array('course' => $course->id), array('section' => 0, 'visible' => 0));
428
        $forum1 = $this->getDataGenerator()->create_module('forum',
429
                array('course' => $course->id), array('section' => 1));
430
        $assign1 = $this->getDataGenerator()->create_module('assign',
431
                array('course' => $course->id), array('section' => 1));
432
        $page1 = $this->getDataGenerator()->create_module('page',
433
                array('course' => $course->id), array('section' => 1));
434
        $page3 = $this->getDataGenerator()->create_module('page',
435
                array('course' => $course->id), array('section' => 3));
436
 
437
        $modinfo = get_fast_modinfo($course->id);
438
 
439
        $this->assertEquals(array($forum0->cmid, $assign0->cmid, $forum1->cmid, $assign1->cmid, $page1->cmid, $page3->cmid),
440
                array_keys($modinfo->cms));
441
        $this->assertEquals($course->id, $modinfo->courseid);
442
        $this->assertEquals($USER->id, $modinfo->userid);
443
        $this->assertEquals(array(0 => array($forum0->cmid, $assign0->cmid),
444
            1 => array($forum1->cmid, $assign1->cmid, $page1->cmid), 3 => array($page3->cmid)), $modinfo->sections);
445
        $this->assertEquals(array('forum', 'assign', 'page'), array_keys($modinfo->instances));
446
        $this->assertEquals(array($assign0->id, $assign1->id), array_keys($modinfo->instances['assign']));
447
        $this->assertEquals(array($forum0->id, $forum1->id), array_keys($modinfo->instances['forum']));
448
        $this->assertEquals(array($page1->id, $page3->id), array_keys($modinfo->instances['page']));
449
        $this->assertEquals(groups_get_user_groups($course->id), $modinfo->groups);
450
        $this->assertEquals(array(0 => array($forum0->cmid, $assign0->cmid),
451
            1 => array($forum1->cmid, $assign1->cmid, $page1->cmid),
452
            3 => array($page3->cmid)), $modinfo->get_sections());
453
        $this->assertEquals(array(0, 1, 2, 3), array_keys($modinfo->get_section_info_all()));
454
        $this->assertEquals($forum0->cmid . ',' . $assign0->cmid, $modinfo->get_section_info(0)->sequence);
455
        $this->assertEquals($forum1->cmid . ',' . $assign1->cmid . ',' . $page1->cmid, $modinfo->get_section_info(1)->sequence);
456
        $this->assertEquals('', $modinfo->get_section_info(2)->sequence);
457
        $this->assertEquals($page3->cmid, $modinfo->get_section_info(3)->sequence);
458
        $this->assertEquals($course->id, $modinfo->get_course()->id);
459
        $names = array_keys($modinfo->get_used_module_names());
460
        sort($names);
461
        $this->assertEquals(array('assign', 'forum', 'page'), $names);
462
        $names = array_keys($modinfo->get_used_module_names(true));
463
        sort($names);
464
        $this->assertEquals(array('assign', 'forum', 'page'), $names);
465
        // Admin can see hidden modules/sections.
466
        $this->assertTrue($modinfo->cms[$assign0->cmid]->uservisible);
467
        $this->assertTrue($modinfo->get_section_info(3)->uservisible);
468
 
469
        // Get modinfo for non-current user (without capability to view hidden activities/sections).
470
        $user = $this->getDataGenerator()->create_user();
471
        $modinfo = get_fast_modinfo($course->id, $user->id);
472
        $this->assertEquals($user->id, $modinfo->userid);
473
        $this->assertFalse($modinfo->cms[$assign0->cmid]->uservisible);
474
        $this->assertFalse($modinfo->get_section_info(3)->uservisible);
475
 
476
        // Attempt to access and set non-existing field.
477
        $this->assertTrue(empty($modinfo->somefield));
478
        $this->assertFalse(isset($modinfo->somefield));
479
        $modinfo->somefield;
480
        $this->assertDebuggingCalled();
481
        $modinfo->somefield = 'Some value';
482
        $this->assertDebuggingCalled();
483
        $this->assertEmpty($modinfo->somefield);
484
        $this->assertDebuggingCalled();
485
 
486
        // Attempt to overwrite existing field.
487
        $this->assertFalse(empty($modinfo->cms));
488
        $this->assertTrue(isset($modinfo->cms));
489
        $modinfo->cms = 'Illegal overwriting';
490
        $this->assertDebuggingCalled();
491
        $this->assertNotEquals('Illegal overwriting', $modinfo->cms);
492
    }
493
 
494
    public function test_is_user_access_restricted_by_capability() {
495
        global $DB;
496
 
497
        $this->resetAfterTest();
498
 
499
        // Create a course and a mod_assign instance.
500
        $course = $this->getDataGenerator()->create_course();
501
        $assign = $this->getDataGenerator()->create_module('assign', array('course'=>$course->id));
502
 
503
        // Create and enrol a student.
504
        $coursecontext = context_course::instance($course->id);
505
        $studentrole = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
506
        $student = $this->getDataGenerator()->create_user();
507
        role_assign($studentrole->id, $student->id, $coursecontext);
508
        $enrolplugin = enrol_get_plugin('manual');
509
        $enrolinstance = $DB->get_record('enrol', array('courseid' => $course->id, 'enrol' => 'manual'));
510
        $enrolplugin->enrol_user($enrolinstance, $student->id);
511
        $this->setUser($student);
512
 
513
        // Make sure student can see the module.
514
        $cm = get_fast_modinfo($course->id)->instances['assign'][$assign->id];
515
        $this->assertTrue($cm->uservisible);
516
        $this->assertFalse($cm->is_user_access_restricted_by_capability());
517
 
518
        // Prohibit student to view mod_assign for the course.
519
        role_change_permission($studentrole->id, $coursecontext, 'mod/assign:view', CAP_PROHIBIT);
520
        get_fast_modinfo($course->id, 0, true);
521
        $cm = get_fast_modinfo($course->id)->instances['assign'][$assign->id];
522
        $this->assertFalse($cm->uservisible);
523
        $this->assertTrue($cm->is_user_access_restricted_by_capability());
524
 
525
        // Restore permission to student to view mod_assign for the course.
526
        role_change_permission($studentrole->id, $coursecontext, 'mod/assign:view', CAP_INHERIT);
527
        get_fast_modinfo($course->id, 0, true);
528
        $cm = get_fast_modinfo($course->id)->instances['assign'][$assign->id];
529
        $this->assertTrue($cm->uservisible);
530
        $this->assertFalse($cm->is_user_access_restricted_by_capability());
531
 
532
        // Prohibit student to view mod_assign for the particular module.
533
        role_change_permission($studentrole->id, context_module::instance($cm->id), 'mod/assign:view', CAP_PROHIBIT);
534
        get_fast_modinfo($course->id, 0, true);
535
        $cm = get_fast_modinfo($course->id)->instances['assign'][$assign->id];
536
        $this->assertFalse($cm->uservisible);
537
        $this->assertTrue($cm->is_user_access_restricted_by_capability());
538
 
539
        // Check calling get_fast_modinfo() for different user:
540
        $this->setAdminUser();
541
        $cm = get_fast_modinfo($course->id)->instances['assign'][$assign->id];
542
        $this->assertTrue($cm->uservisible);
543
        $this->assertFalse($cm->is_user_access_restricted_by_capability());
544
        $cm = get_fast_modinfo($course->id, $student->id)->instances['assign'][$assign->id];
545
        $this->assertFalse($cm->uservisible);
546
        $this->assertTrue($cm->is_user_access_restricted_by_capability());
547
    }
548
 
549
    /**
550
     * Tests for function cm_info::get_course_module_record()
551
     */
552
    public function test_cm_info_get_course_module_record() {
553
        global $DB;
554
 
555
        $this->resetAfterTest();
556
        $this->setAdminUser();
557
 
558
        set_config('enableavailability', 1);
559
        set_config('enablecompletion', 1);
560
 
561
        $course = $this->getDataGenerator()->create_course(
562
                array('format' => 'topics', 'numsections' => 3, 'enablecompletion' => 1),
563
                array('createsections' => true));
564
        $mods = array();
565
        $mods[0] = $this->getDataGenerator()->create_module('forum', array('course' => $course->id));
566
        $mods[1] = $this->getDataGenerator()->create_module('assign',
567
                array('course' => $course->id,
568
                    'section' => 3,
569
                    'idnumber' => '12345',
570
                    'showdescription' => true
571
                    ));
572
        // Pick a small valid availability value to use.
573
        $availabilityvalue = '{"op":"|","show":true,"c":[{"type":"date","d":">=","t":4}]}';
574
        $mods[2] = $this->getDataGenerator()->create_module('book',
575
                array('course' => $course->id,
576
                    'indent' => 5,
577
                    'availability' => $availabilityvalue,
578
                    'showdescription' => false,
579
                    'completion' => true,
580
                    'completionview' => true,
581
                    'completionexpected' => time() + 5000,
582
                    ));
583
        $mods[3] = $this->getDataGenerator()->create_module('forum',
584
                array('course' => $course->id,
585
                    'visible' => 0,
586
                    'groupmode' => 1,
587
                    'availability' => null));
588
        $mods[4] = $this->getDataGenerator()->create_module('forum',
589
                array('course' => $course->id,
590
                    'grouping' => 12));
591
 
592
        $modinfo = get_fast_modinfo($course->id);
593
 
594
        // Make sure that object returned by get_course_module_record(false) has exactly the same fields as DB table 'course_modules'.
595
        $dbfields = array_keys($DB->get_columns('course_modules'));
596
        sort($dbfields);
597
        $cmrecord = $modinfo->get_cm($mods[0]->cmid)->get_course_module_record();
598
        $cmrecordfields = array_keys((array)$cmrecord);
599
        sort($cmrecordfields);
600
        $this->assertEquals($dbfields, $cmrecordfields);
601
 
602
        // Make sure that object returned by get_course_module_record(true) has exactly the same fields
603
        // as object returned by get_coursemodule_from_id(,,,true,);
604
        $cmrecordfull = $modinfo->get_cm($mods[0]->cmid)->get_course_module_record(true);
605
        $cmrecordfullfields = array_keys((array)$cmrecordfull);
606
        $cm = get_coursemodule_from_id(null, $mods[0]->cmid, 0, true, MUST_EXIST);
607
        $cmfields = array_keys((array)$cm);
608
        $this->assertEquals($cmfields, $cmrecordfullfields);
609
 
610
        // Make sure that object returned by get_course_module_record(true) has exactly the same fields
611
        // as object returned by get_coursemodule_from_instance(,,,true,);
612
        $cm = get_coursemodule_from_instance('forum', $mods[0]->id, null, true, MUST_EXIST);
613
        $cmfields = array_keys((array)$cm);
614
        $this->assertEquals($cmfields, $cmrecordfullfields);
615
 
616
        // Make sure the objects have the same properties.
617
        $cm1 = get_coursemodule_from_id(null, $mods[0]->cmid, 0, true, MUST_EXIST);
618
        $cm2 = get_coursemodule_from_instance('forum', $mods[0]->id, 0, true, MUST_EXIST);
619
        $cminfo = $modinfo->get_cm($mods[0]->cmid);
620
        $record = $DB->get_record('course_modules', array('id' => $mods[0]->cmid));
621
        $this->assertEquals($record, $cminfo->get_course_module_record());
622
        $this->assertEquals($cm1, $cminfo->get_course_module_record(true));
623
        $this->assertEquals($cm2, $cminfo->get_course_module_record(true));
624
 
625
        $cm1 = get_coursemodule_from_id(null, $mods[1]->cmid, 0, true, MUST_EXIST);
626
        $cm2 = get_coursemodule_from_instance('assign', $mods[1]->id, 0, true, MUST_EXIST);
627
        $cminfo = $modinfo->get_cm($mods[1]->cmid);
628
        $record = $DB->get_record('course_modules', array('id' => $mods[1]->cmid));
629
        $this->assertEquals($record, $cminfo->get_course_module_record());
630
        $this->assertEquals($cm1, $cminfo->get_course_module_record(true));
631
        $this->assertEquals($cm2, $cminfo->get_course_module_record(true));
632
 
633
        $cm1 = get_coursemodule_from_id(null, $mods[2]->cmid, 0, true, MUST_EXIST);
634
        $cm2 = get_coursemodule_from_instance('book', $mods[2]->id, 0, true, MUST_EXIST);
635
        $cminfo = $modinfo->get_cm($mods[2]->cmid);
636
        $record = $DB->get_record('course_modules', array('id' => $mods[2]->cmid));
637
        $this->assertEquals($record, $cminfo->get_course_module_record());
638
        $this->assertEquals($cm1, $cminfo->get_course_module_record(true));
639
        $this->assertEquals($cm2, $cminfo->get_course_module_record(true));
640
 
641
        $cm1 = get_coursemodule_from_id(null, $mods[3]->cmid, 0, true, MUST_EXIST);
642
        $cm2 = get_coursemodule_from_instance('forum', $mods[3]->id, 0, true, MUST_EXIST);
643
        $cminfo = $modinfo->get_cm($mods[3]->cmid);
644
        $record = $DB->get_record('course_modules', array('id' => $mods[3]->cmid));
645
        $this->assertEquals($record, $cminfo->get_course_module_record());
646
        $this->assertEquals($cm1, $cminfo->get_course_module_record(true));
647
        $this->assertEquals($cm2, $cminfo->get_course_module_record(true));
648
 
649
        $cm1 = get_coursemodule_from_id(null, $mods[4]->cmid, 0, true, MUST_EXIST);
650
        $cm2 = get_coursemodule_from_instance('forum', $mods[4]->id, 0, true, MUST_EXIST);
651
        $cminfo = $modinfo->get_cm($mods[4]->cmid);
652
        $record = $DB->get_record('course_modules', array('id' => $mods[4]->cmid));
653
        $this->assertEquals($record, $cminfo->get_course_module_record());
654
        $this->assertEquals($cm1, $cminfo->get_course_module_record(true));
655
        $this->assertEquals($cm2, $cminfo->get_course_module_record(true));
656
 
657
    }
658
 
659
    /**
660
     * Tests for function cm_info::get_activitybadge().
661
     *
662
     * @covers \cm_info::get_activitybadge
663
     */
664
    public function test_cm_info_get_activitybadge(): void {
665
        global $PAGE;
666
 
667
        $this->resetAfterTest();
668
        $this->setAdminUser();
669
 
670
        $course = $this->getDataGenerator()->create_course();
671
        $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
672
        $resource = $this->getDataGenerator()->create_module('resource', ['course' => $course->id]);
673
        $assign = $this->getDataGenerator()->create_module('assign', ['course' => $course->id]);
674
        $label = $this->getDataGenerator()->create_module('label', ['course' => $course->id]);
675
 
676
        $renderer = $PAGE->get_renderer('core');
677
        $modinfo = get_fast_modinfo($course->id);
678
 
679
        // Forum and resource implements the activitybadge feature.
680
        $cminfo = $modinfo->get_cm($forum->cmid);
681
        $this->assertNotNull($cminfo->get_activitybadge($renderer));
682
        $cminfo = $modinfo->get_cm($resource->cmid);
683
        $this->assertNotNull($cminfo->get_activitybadge($renderer));
684
 
685
        // Assign and label don't implement the activitybadge feature (at least for now).
686
        $cminfo = $modinfo->get_cm($assign->cmid);
687
        $this->assertNull($cminfo->get_activitybadge($renderer));
688
        $cminfo = $modinfo->get_cm($label->cmid);
689
        $this->assertNull($cminfo->get_activitybadge($renderer));
690
    }
691
 
692
    /**
693
     * Tests the availability property that has been added to course modules
694
     * and sections (just to see that it is correctly saved and accessed).
695
     */
696
    public function test_availability_property() {
697
        global $DB, $CFG;
698
 
699
        $this->resetAfterTest();
700
 
701
        // Create a course with two modules and three sections.
702
        $course = $this->getDataGenerator()->create_course(
703
                array('format' => 'topics', 'numsections' => 3),
704
                array('createsections' => true));
705
        $forum = $this->getDataGenerator()->create_module('forum',
706
                array('course' => $course->id));
707
        $forum2 = $this->getDataGenerator()->create_module('forum',
708
                array('course' => $course->id));
709
 
710
        // Get modinfo. Check that availability is null for both cm and sections.
711
        $modinfo = get_fast_modinfo($course->id);
712
        $cm = $modinfo->get_cm($forum->cmid);
713
        $this->assertNull($cm->availability);
714
        $section = $modinfo->get_section_info(1, MUST_EXIST);
715
        $this->assertNull($section->availability);
716
 
717
        // Update availability for cm and section in database.
718
        $DB->set_field('course_modules', 'availability', '{}', array('id' => $cm->id));
719
        $DB->set_field('course_sections', 'availability', '{}', array('id' => $section->id));
720
 
721
        // Clear cache and get modinfo again.
722
        rebuild_course_cache($course->id, true);
723
        get_fast_modinfo(0, 0, true);
724
        $modinfo = get_fast_modinfo($course->id);
725
 
726
        // Check values that were changed.
727
        $cm = $modinfo->get_cm($forum->cmid);
728
        $this->assertEquals('{}', $cm->availability);
729
        $section = $modinfo->get_section_info(1, MUST_EXIST);
730
        $this->assertEquals('{}', $section->availability);
731
 
732
        // Check other values are still null.
733
        $cm = $modinfo->get_cm($forum2->cmid);
734
        $this->assertNull($cm->availability);
735
        $section = $modinfo->get_section_info(2, MUST_EXIST);
736
        $this->assertNull($section->availability);
737
    }
738
 
739
    /**
740
     * Tests for get_groups() method.
741
     */
742
    public function test_get_groups() {
743
        $this->resetAfterTest();
744
        $generator = $this->getDataGenerator();
745
 
746
        // Create courses.
747
        $course1 = $generator->create_course();
748
        $course2 = $generator->create_course();
749
        $course3 = $generator->create_course();
750
 
751
        // Create users.
752
        $user1 = $generator->create_user();
753
        $user2 = $generator->create_user();
754
        $user3 = $generator->create_user();
755
 
756
        // Enrol users on courses.
757
        $generator->enrol_user($user1->id, $course1->id);
758
        $generator->enrol_user($user2->id, $course2->id);
759
        $generator->enrol_user($user3->id, $course2->id);
760
        $generator->enrol_user($user3->id, $course3->id);
761
 
762
        // Create groups.
763
        $group1 = $generator->create_group(array('courseid' => $course1->id));
764
        $group2 = $generator->create_group(array('courseid' => $course2->id));
765
        $group3 = $generator->create_group(array('courseid' => $course2->id));
766
 
767
        // Assign users to groups and assert the result.
768
        $this->assertTrue($generator->create_group_member(array('groupid' => $group1->id, 'userid' => $user1->id)));
769
        $this->assertTrue($generator->create_group_member(array('groupid' => $group2->id, 'userid' => $user2->id)));
770
        $this->assertTrue($generator->create_group_member(array('groupid' => $group3->id, 'userid' => $user2->id)));
771
        $this->assertTrue($generator->create_group_member(array('groupid' => $group2->id, 'userid' => $user3->id)));
772
 
773
        // Create groupings.
774
        $grouping1 = $generator->create_grouping(array('courseid' => $course1->id));
775
        $grouping2 = $generator->create_grouping(array('courseid' => $course2->id));
776
 
777
        // Assign and assert group to groupings.
778
        groups_assign_grouping($grouping1->id, $group1->id);
779
        groups_assign_grouping($grouping2->id, $group2->id);
780
        groups_assign_grouping($grouping2->id, $group3->id);
781
 
782
        // Test with one single group.
783
        $modinfo = get_fast_modinfo($course1, $user1->id);
784
        $groups = $modinfo->get_groups($grouping1->id);
785
        $this->assertCount(1, $groups);
786
        $this->assertArrayHasKey($group1->id, $groups);
787
 
788
        // Test with two groups.
789
        $modinfo = get_fast_modinfo($course2, $user2->id);
790
        $groups = $modinfo->get_groups();
791
        $this->assertCount(2, $groups);
792
        $this->assertTrue(in_array($group2->id, $groups));
793
        $this->assertTrue(in_array($group3->id, $groups));
794
 
795
        // Test with no groups.
796
        $modinfo = get_fast_modinfo($course3, $user3->id);
797
        $groups = $modinfo->get_groups();
798
        $this->assertCount(0, $groups);
799
        $this->assertArrayNotHasKey($group1->id, $groups);
800
    }
801
 
802
    /**
803
     * Tests the function for constructing a cm_info from mixed data.
804
     */
805
    public function test_create() {
806
        global $CFG, $DB;
807
        $this->resetAfterTest();
808
 
809
        // Create a course and an activity.
810
        $generator = $this->getDataGenerator();
811
        $course = $generator->create_course();
812
        $page = $generator->create_module('page', array('course' => $course->id,
813
                'name' => 'Annie'));
814
 
815
        // Null is passed through.
816
        $this->assertNull(cm_info::create(null));
817
 
818
        // Stdclass object turns into cm_info.
819
        $cm = cm_info::create(
820
                (object)array('id' => $page->cmid, 'course' => $course->id));
821
        $this->assertInstanceOf('cm_info', $cm);
822
        $this->assertEquals('Annie', $cm->name);
823
 
824
        // A cm_info object stays as cm_info.
825
        $this->assertSame($cm, cm_info::create($cm));
826
 
827
        // Invalid object (missing fields) causes error.
828
        try {
829
            cm_info::create((object)array('id' => $page->cmid));
830
            $this->fail();
831
        } catch (Exception $e) {
832
            $this->assertInstanceOf('coding_exception', $e);
833
        }
834
 
835
        // Create a second hidden activity.
836
        $hiddenpage = $generator->create_module('page', array('course' => $course->id,
837
                'name' => 'Annie', 'visible' => 0));
838
 
839
        // Create 2 user accounts, one is a manager who can see everything.
840
        $user = $generator->create_user();
841
        $generator->enrol_user($user->id, $course->id);
842
        $manager = $generator->create_user();
843
        $generator->enrol_user($manager->id, $course->id,
844
                $DB->get_field('role', 'id', array('shortname' => 'manager'), MUST_EXIST));
845
 
846
        // User can see the normal page but not the hidden one.
847
        $cm = cm_info::create((object)array('id' => $page->cmid, 'course' => $course->id),
848
                $user->id);
849
        $this->assertTrue($cm->uservisible);
850
        $cm = cm_info::create((object)array('id' => $hiddenpage->cmid, 'course' => $course->id),
851
                $user->id);
852
        $this->assertFalse($cm->uservisible);
853
 
854
        // Manager can see the hidden one too.
855
        $cm = cm_info::create((object)array('id' => $hiddenpage->cmid, 'course' => $course->id),
856
                $manager->id);
857
        $this->assertTrue($cm->uservisible);
858
    }
859
 
860
    /**
861
     * Tests function for getting $course and $cm at once quickly from modinfo
862
     * based on cmid or cm record.
863
     */
864
    public function test_get_course_and_cm_from_cmid() {
865
        global $CFG, $DB;
866
        $this->resetAfterTest();
867
 
868
        // Create a course and an activity.
869
        $generator = $this->getDataGenerator();
870
        $course = $generator->create_course(array('shortname' => 'Halls'));
871
        $page = $generator->create_module('page', array('course' => $course->id,
872
                'name' => 'Annie'));
873
 
874
        // Successful usage.
875
        list($course, $cm) = get_course_and_cm_from_cmid($page->cmid);
876
        $this->assertEquals('Halls', $course->shortname);
877
        $this->assertInstanceOf('cm_info', $cm);
878
        $this->assertEquals('Annie', $cm->name);
879
 
880
        // Specified module type.
881
        list($course, $cm) = get_course_and_cm_from_cmid($page->cmid, 'page');
882
        $this->assertEquals('Annie', $cm->name);
883
 
884
        // With id in object.
885
        $fakecm = (object)array('id' => $page->cmid);
886
        list($course, $cm) = get_course_and_cm_from_cmid($fakecm);
887
        $this->assertEquals('Halls', $course->shortname);
888
        $this->assertEquals('Annie', $cm->name);
889
 
890
        // With both id and course in object.
891
        $fakecm->course = $course->id;
892
        list($course, $cm) = get_course_and_cm_from_cmid($fakecm);
893
        $this->assertEquals('Halls', $course->shortname);
894
        $this->assertEquals('Annie', $cm->name);
895
 
896
        // With supplied course id.
897
        list($course, $cm) = get_course_and_cm_from_cmid($page->cmid, 'page', $course->id);
898
        $this->assertEquals('Annie', $cm->name);
899
 
900
        // With supplied course object (modified just so we can check it is
901
        // indeed reusing the supplied object).
902
        $course->silly = true;
903
        list($course, $cm) = get_course_and_cm_from_cmid($page->cmid, 'page', $course);
904
        $this->assertEquals('Annie', $cm->name);
905
        $this->assertTrue($course->silly);
906
 
907
        // Incorrect module type.
908
        try {
909
            get_course_and_cm_from_cmid($page->cmid, 'forum');
910
            $this->fail();
911
        } catch (moodle_exception $e) {
912
            $this->assertEquals('invalidcoursemoduleid', $e->errorcode);
913
        }
914
 
915
        // Invalid module name.
916
        try {
917
            get_course_and_cm_from_cmid($page->cmid, 'pigs can fly');
918
            $this->fail();
919
        } catch (coding_exception $e) {
920
            $this->assertStringContainsString('Invalid modulename parameter', $e->getMessage());
921
        }
922
 
923
        // Doesn't exist.
924
        try {
925
            get_course_and_cm_from_cmid($page->cmid + 1);
926
            $this->fail();
927
        } catch (moodle_exception $e) {
928
            $this->assertInstanceOf('dml_exception', $e);
929
        }
930
 
931
        // Create a second hidden activity.
932
        $hiddenpage = $generator->create_module('page', array('course' => $course->id,
933
                'name' => 'Annie', 'visible' => 0));
934
 
935
        // Create 2 user accounts, one is a manager who can see everything.
936
        $user = $generator->create_user();
937
        $generator->enrol_user($user->id, $course->id);
938
        $manager = $generator->create_user();
939
        $generator->enrol_user($manager->id, $course->id,
940
                $DB->get_field('role', 'id', array('shortname' => 'manager'), MUST_EXIST));
941
 
942
        // User can see the normal page but not the hidden one.
943
        list($course, $cm) = get_course_and_cm_from_cmid($page->cmid, 'page', 0, $user->id);
944
        $this->assertTrue($cm->uservisible);
945
        list($course, $cm) = get_course_and_cm_from_cmid($hiddenpage->cmid, 'page', 0, $user->id);
946
        $this->assertFalse($cm->uservisible);
947
 
948
        // Manager can see the hidden one too.
949
        list($course, $cm) = get_course_and_cm_from_cmid($hiddenpage->cmid, 'page', 0, $manager->id);
950
        $this->assertTrue($cm->uservisible);
951
    }
952
 
953
    /**
954
     * Tests function for getting $course and $cm at once quickly from modinfo
955
     * based on instance id or record.
956
     */
957
    public function test_get_course_and_cm_from_instance() {
958
        global $CFG, $DB;
959
        $this->resetAfterTest();
960
 
961
        // Create a course and an activity.
962
        $generator = $this->getDataGenerator();
963
        $course = $generator->create_course(array('shortname' => 'Halls'));
964
        $page = $generator->create_module('page', array('course' => $course->id,
965
                'name' => 'Annie'));
966
 
967
        // Successful usage.
968
        list($course, $cm) = get_course_and_cm_from_instance($page->id, 'page');
969
        $this->assertEquals('Halls', $course->shortname);
970
        $this->assertInstanceOf('cm_info', $cm);
971
        $this->assertEquals('Annie', $cm->name);
972
 
973
        // With id in object.
974
        $fakeinstance = (object)array('id' => $page->id);
975
        list($course, $cm) = get_course_and_cm_from_instance($fakeinstance, 'page');
976
        $this->assertEquals('Halls', $course->shortname);
977
        $this->assertEquals('Annie', $cm->name);
978
 
979
        // With both id and course in object.
980
        $fakeinstance->course = $course->id;
981
        list($course, $cm) = get_course_and_cm_from_instance($fakeinstance, 'page');
982
        $this->assertEquals('Halls', $course->shortname);
983
        $this->assertEquals('Annie', $cm->name);
984
 
985
        // With supplied course id.
986
        list($course, $cm) = get_course_and_cm_from_instance($page->id, 'page', $course->id);
987
        $this->assertEquals('Annie', $cm->name);
988
 
989
        // With supplied course object (modified just so we can check it is
990
        // indeed reusing the supplied object).
991
        $course->silly = true;
992
        list($course, $cm) = get_course_and_cm_from_instance($page->id, 'page', $course);
993
        $this->assertEquals('Annie', $cm->name);
994
        $this->assertTrue($course->silly);
995
 
996
        // Doesn't exist (or is wrong type).
997
        try {
998
            get_course_and_cm_from_instance($page->id, 'forum');
999
            $this->fail();
1000
        } catch (moodle_exception $e) {
1001
            $this->assertInstanceOf('dml_exception', $e);
1002
        }
1003
 
1004
        // Invalid module ID.
1005
        try {
1006
            get_course_and_cm_from_instance(-1, 'page', $course);
1007
            $this->fail();
1008
        } catch (moodle_exception $e) {
1009
            $this->assertStringContainsString('Invalid module ID: -1', $e->getMessage());
1010
        }
1011
 
1012
        // Invalid module name.
1013
        try {
1014
            get_course_and_cm_from_cmid($page->cmid, '1337 h4x0ring');
1015
            $this->fail();
1016
        } catch (coding_exception $e) {
1017
            $this->assertStringContainsString('Invalid modulename parameter', $e->getMessage());
1018
        }
1019
 
1020
        // Create a second hidden activity.
1021
        $hiddenpage = $generator->create_module('page', array('course' => $course->id,
1022
                'name' => 'Annie', 'visible' => 0));
1023
 
1024
        // Create 2 user accounts, one is a manager who can see everything.
1025
        $user = $generator->create_user();
1026
        $generator->enrol_user($user->id, $course->id);
1027
        $manager = $generator->create_user();
1028
        $generator->enrol_user($manager->id, $course->id,
1029
                $DB->get_field('role', 'id', array('shortname' => 'manager'), MUST_EXIST));
1030
 
1031
        // User can see the normal page but not the hidden one.
1032
        list($course, $cm) = get_course_and_cm_from_cmid($page->cmid, 'page', 0, $user->id);
1033
        $this->assertTrue($cm->uservisible);
1034
        list($course, $cm) = get_course_and_cm_from_cmid($hiddenpage->cmid, 'page', 0, $user->id);
1035
        $this->assertFalse($cm->uservisible);
1036
 
1037
        // Manager can see the hidden one too.
1038
        list($course, $cm) = get_course_and_cm_from_cmid($hiddenpage->cmid, 'page', 0, $manager->id);
1039
        $this->assertTrue($cm->uservisible);
1040
    }
1041
 
1042
    /**
1043
     * Test for get_listed_section_info_all method.
1044
     * @covers \course_modinfo::get_listed_section_info_all
1045
     * @covers \course_modinfo::get_section_info_all
1046
     */
1047
    public function test_get_listed_section_info_all(): void {
1048
        $this->resetAfterTest();
1049
 
1050
        // Create a course with 4 sections.
1051
        $course = $this->getDataGenerator()->create_course(['numsections' => 3]);
1052
 
1053
        $listed = get_fast_modinfo($course)->get_section_info_all();
1054
        $this->assertCount(4, $listed);
1055
 
1056
        // Generate some delegated sections (not listed).
1057
        formatactions::section($course)->create_delegated('mod_label', 0);
1058
        formatactions::section($course)->create_delegated('mod_label', 1);
1059
 
1060
        $this->assertCount(6, get_fast_modinfo($course)->get_section_info_all());
1061
 
1062
        $result = get_fast_modinfo($course)->get_listed_section_info_all();
1063
 
1064
        $this->assertCount(4, $result);
1065
        $this->assertEquals($listed[0]->id, $result[0]->id);
1066
        $this->assertEquals($listed[1]->id, $result[1]->id);
1067
        $this->assertEquals($listed[2]->id, $result[2]->id);
1068
        $this->assertEquals($listed[3]->id, $result[3]->id);
1069
    }
1070
 
1071
    /**
1072
     * Test test_get_section_info_by_id method
1073
     *
1074
     * @dataProvider get_section_info_by_id_provider
1075
     * @covers \course_modinfo::get_section_info_by_id
1076
     *
1077
     * @param int $sectionnum the section number
1078
     * @param int $strictness the search strict mode
1079
     * @param bool $expectnull if the function will return a null
1080
     * @param bool $expectexception if the function will throw an exception
1081
     */
1082
    public function test_get_section_info_by_id(
1083
        int $sectionnum,
1084
        int $strictness = IGNORE_MISSING,
1085
        bool $expectnull = false,
1086
        bool $expectexception = false
1087
    ) {
1088
        global $DB;
1089
 
1090
        $this->resetAfterTest();
1091
 
1092
        // Create a course with 4 sections.
1093
        $course = $this->getDataGenerator()->create_course(['numsections' => 4]);
1094
 
1095
        // Index sections.
1096
        $sectionindex = [];
1097
        $modinfo = get_fast_modinfo($course);
1098
        $allsections = $modinfo->get_section_info_all();
1099
        foreach ($allsections as $section) {
1100
            $sectionindex[$section->section] = $section->id;
1101
        }
1102
 
1103
        if ($expectexception) {
1104
            $this->expectException(moodle_exception::class);
1105
        }
1106
 
1107
        $sectionid = $sectionindex[$sectionnum] ?? -1;
1108
 
1109
        $section = $modinfo->get_section_info_by_id($sectionid, $strictness);
1110
 
1111
        if ($expectnull) {
1112
            $this->assertNull($section);
1113
        } else {
1114
            $this->assertEquals($sectionid, $section->id);
1115
            $this->assertEquals($sectionnum, $section->section);
1116
        }
1117
    }
1118
 
1119
    /**
1120
     * Data provider for test_get_section_info_by_id().
1121
     *
1122
     * @return array
1123
     */
1124
    public function get_section_info_by_id_provider() {
1125
        return [
1126
            'Valid section id' => [
1127
                'sectionnum' => 1,
1128
                'strictness' => IGNORE_MISSING,
1129
                'expectnull' => false,
1130
                'expectexception' => false,
1131
            ],
1132
            'Section zero' => [
1133
                'sectionnum' => 0,
1134
                'strictness' => IGNORE_MISSING,
1135
                'expectnull' => false,
1136
                'expectexception' => false,
1137
            ],
1138
            'invalid section ignore missing' => [
1139
                'sectionnum' => -1,
1140
                'strictness' => IGNORE_MISSING,
1141
                'expectnull' => true,
1142
                'expectexception' => false,
1143
            ],
1144
            'invalid section must exists' => [
1145
                'sectionnum' => -1,
1146
                'strictness' => MUST_EXIST,
1147
                'expectnull' => false,
1148
                'expectexception' => true,
1149
            ],
1150
        ];
1151
    }
1152
 
1153
    /**
1154
     * Test get_section_info_by_component method
1155
     *
1156
     * @covers \course_modinfo::get_section_info_by_component
1157
     * @dataProvider get_section_info_by_component_provider
1158
     *
1159
     * @param string $component the component name
1160
     * @param int $itemid the section number
1161
     * @param int $strictness the search strict mode
1162
     * @param bool $expectnull if the function will return a null
1163
     * @param bool $expectexception if the function will throw an exception
1164
     */
1165
    public function test_get_section_info_by_component(
1166
        string $component,
1167
        int $itemid,
1168
        int $strictness,
1169
        bool $expectnull,
1170
        bool $expectexception
1171
    ): void {
1172
        $this->resetAfterTest();
1173
        $course = $this->getDataGenerator()->create_course(['numsections' => 1]);
1174
 
1175
        formatactions::section($course)->create_delegated('mod_forum', 42);
1176
 
1177
        $modinfo = get_fast_modinfo($course);
1178
 
1179
        if ($expectexception) {
1180
            $this->expectException(moodle_exception::class);
1181
        }
1182
 
1183
        $section = $modinfo->get_section_info_by_component($component, $itemid, $strictness);
1184
 
1185
        if ($expectnull) {
1186
            $this->assertNull($section);
1187
        } else {
1188
            $this->assertEquals($component, $section->component);
1189
            $this->assertEquals($itemid, $section->itemid);
1190
        }
1191
    }
1192
 
1193
    /**
1194
     * Data provider for test_get_section_info_by_component().
1195
     *
1196
     * @return array
1197
     */
1198
    public static function get_section_info_by_component_provider(): array {
1199
        return [
1200
            'Valid component and itemid' => [
1201
                'component' => 'mod_forum',
1202
                'itemid' => 42,
1203
                'strictness' => IGNORE_MISSING,
1204
                'expectnull' => false,
1205
                'expectexception' => false,
1206
            ],
1207
            'Invalid component' => [
1208
                'component' => 'mod_nonexisting',
1209
                'itemid' => 42,
1210
                'strictness' => IGNORE_MISSING,
1211
                'expectnull' => true,
1212
                'expectexception' => false,
1213
            ],
1214
            'Invalid itemid' => [
1215
                'component' => 'mod_forum',
1216
                'itemid' => 0,
1217
                'strictness' => IGNORE_MISSING,
1218
                'expectnull' => true,
1219
                'expectexception' => false,
1220
            ],
1221
            'Invalid component and itemid' => [
1222
                'component' => 'mod_nonexisting',
1223
                'itemid' => 0,
1224
                'strictness' => IGNORE_MISSING,
1225
                'expectnull' => true,
1226
                'expectexception' => false,
1227
            ],
1228
            'Invalid component must exists' => [
1229
                'component' => 'mod_nonexisting',
1230
                'itemid' => 42,
1231
                'strictness' => MUST_EXIST,
1232
                'expectnull' => true,
1233
                'expectexception' => true,
1234
            ],
1235
            'Invalid itemid must exists' => [
1236
                'component' => 'mod_forum',
1237
                'itemid' => 0,
1238
                'strictness' => MUST_EXIST,
1239
                'expectnull' => true,
1240
                'expectexception' => true,
1241
            ],
1242
            'Invalid component and itemid must exists' => [
1243
                'component' => 'mod_nonexisting',
1244
                'itemid' => 0,
1245
                'strictness' => MUST_EXIST,
1246
                'expectnull' => false,
1247
                'expectexception' => true,
1248
            ],
1249
        ];
1250
    }
1251
 
1252
    /**
1253
     * Test has_delegated_sections method
1254
     *
1255
     * @covers \course_modinfo::has_delegated_sections
1256
     */
1257
    public function test_has_delegated_sections(): void {
1258
        $this->resetAfterTest();
1259
        $course = $this->getDataGenerator()->create_course(['numsections' => 1]);
1260
 
1261
        $modinfo = get_fast_modinfo($course);
1262
        $this->assertFalse($modinfo->has_delegated_sections());
1263
 
1264
        formatactions::section($course)->create_delegated('mod_forum', 42);
1265
 
1266
        $modinfo = get_fast_modinfo($course);
1267
        $this->assertTrue($modinfo->has_delegated_sections());
1268
    }
1269
 
1270
    /**
1271
     * Test purge_section_cache_by_id method
1272
     *
1273
     * @covers \course_modinfo::purge_course_section_cache_by_id
1274
     * @return void
1275
     */
1276
    public function test_purge_section_cache_by_id(): void {
1277
        $this->resetAfterTest();
1278
        $this->setAdminUser();
1279
        $cache = cache::make('core', 'coursemodinfo');
1280
 
1281
        // Generate the course and pre-requisite section.
1282
        $course = $this->getDataGenerator()->create_course(['format' => 'topics', 'numsections' => 3], ['createsections' => true]);
1283
        // Reset course cache.
1284
        rebuild_course_cache($course->id, true);
1285
        // Build course cache.
1286
        $modinfo = get_fast_modinfo($course->id);
1287
        // Get the course modinfo cache.
1288
        $coursemodinfo = $cache->get_versioned($course->id, $course->cacherev);
1289
        // Get the section cache.
1290
        $sectioncaches = $coursemodinfo->sectioncache;
1291
 
1292
        $numberedsections = $modinfo->get_section_info_all();
1293
 
1294
        // Make sure that we will have 4 section caches here.
1295
        $this->assertCount(4, $sectioncaches);
1296
        $this->assertArrayHasKey($numberedsections[0]->id, $sectioncaches);
1297
        $this->assertArrayHasKey($numberedsections[1]->id, $sectioncaches);
1298
        $this->assertArrayHasKey($numberedsections[2]->id, $sectioncaches);
1299
        $this->assertArrayHasKey($numberedsections[3]->id, $sectioncaches);
1300
 
1301
        // Purge cache for the section by id.
1302
        course_modinfo::purge_course_section_cache_by_id(
1303
            $course->id,
1304
            $numberedsections[1]->id
1305
        );
1306
        // Get the course modinfo cache.
1307
        $coursemodinfo = $cache->get_versioned($course->id, $course->cacherev);
1308
        // Get the section cache.
1309
        $sectioncaches = $coursemodinfo->sectioncache;
1310
 
1311
        // Make sure that we will have 3 section caches left.
1312
        $this->assertCount(3, $sectioncaches);
1313
        $this->assertArrayNotHasKey($numberedsections[1]->id, $sectioncaches);
1314
        $this->assertArrayHasKey($numberedsections[0]->id, $sectioncaches);
1315
        $this->assertArrayHasKey($numberedsections[2]->id, $sectioncaches);
1316
        $this->assertArrayHasKey($numberedsections[3]->id, $sectioncaches);
1317
        // Make sure that the cacherev will be reset.
1318
        $this->assertEquals(-1, $coursemodinfo->cacherev);
1319
    }
1320
 
1321
    /**
1322
     * Test purge_section_cache_by_number method
1323
     *
1324
     * @covers \course_modinfo::purge_course_section_cache_by_number
1325
     * @return void
1326
     */
1327
    public function test_section_cache_by_number(): void {
1328
        $this->resetAfterTest();
1329
        $this->setAdminUser();
1330
        $cache = cache::make('core', 'coursemodinfo');
1331
 
1332
        // Generate the course and pre-requisite section.
1333
        $course = $this->getDataGenerator()->create_course(['format' => 'topics', 'numsections' => 3], ['createsections' => true]);
1334
        // Reset course cache.
1335
        rebuild_course_cache($course->id, true);
1336
        // Build course cache.
1337
        $modinfo = get_fast_modinfo($course->id);
1338
        // Get the course modinfo cache.
1339
        $coursemodinfo = $cache->get_versioned($course->id, $course->cacherev);
1340
        // Get the section cache.
1341
        $sectioncaches = $coursemodinfo->sectioncache;
1342
 
1343
        $numberedsections = $modinfo->get_section_info_all();
1344
 
1345
        // Make sure that we will have 4 section caches here.
1346
        $this->assertCount(4, $sectioncaches);
1347
        $this->assertArrayHasKey($numberedsections[0]->id, $sectioncaches);
1348
        $this->assertArrayHasKey($numberedsections[1]->id, $sectioncaches);
1349
        $this->assertArrayHasKey($numberedsections[2]->id, $sectioncaches);
1350
        $this->assertArrayHasKey($numberedsections[3]->id, $sectioncaches);
1351
 
1352
        // Purge cache for the section with section number is 1.
1353
        course_modinfo::purge_course_section_cache_by_number($course->id, 1);
1354
        // Get the course modinfo cache.
1355
        $coursemodinfo = $cache->get_versioned($course->id, $course->cacherev);
1356
        // Get the section cache.
1357
        $sectioncaches = $coursemodinfo->sectioncache;
1358
 
1359
        // Make sure that we will have 3 section caches left.
1360
        $this->assertCount(3, $sectioncaches);
1361
        $this->assertArrayNotHasKey($numberedsections[1]->id, $sectioncaches);
1362
        $this->assertArrayHasKey($numberedsections[0]->id, $sectioncaches);
1363
        $this->assertArrayHasKey($numberedsections[2]->id, $sectioncaches);
1364
        $this->assertArrayHasKey($numberedsections[3]->id, $sectioncaches);
1365
        // Make sure that the cacherev will be reset.
1366
        $this->assertEquals(-1, $coursemodinfo->cacherev);
1367
    }
1368
 
1369
    /**
1370
     * Purge a single course module from the cache.
1371
     *
1372
     * @return void
1373
     * @covers \course_modinfo::purge_course_module_cache
1374
     */
1375
    public function test_purge_course_module(): void {
1376
        $this->resetAfterTest();
1377
        $this->setAdminUser();
1378
        $cache = cache::make('core', 'coursemodinfo');
1379
 
1380
        // Generate the course and pre-requisite section.
1381
        $course = $this->getDataGenerator()->create_course();
1382
        $cm1 = $this->getDataGenerator()->create_module('page', ['course' => $course]);
1383
        $cm2 = $this->getDataGenerator()->create_module('page', ['course' => $course]);
1384
        $cm3 = $this->getDataGenerator()->create_module('page', ['course' => $course]);
1385
        $cm4 = $this->getDataGenerator()->create_module('page', ['course' => $course]);
1386
        // Reset course cache.
1387
        rebuild_course_cache($course->id, true);
1388
        // Build course cache.
1389
        get_fast_modinfo($course->id);
1390
        // Get the course modinfo cache.
1391
        $coursemodinfo = $cache->get_versioned($course->id, $course->cacherev);
1392
        $this->assertCount(4, $coursemodinfo->modinfo);
1393
        $this->assertArrayHasKey($cm1->cmid, $coursemodinfo->modinfo);
1394
        $this->assertArrayHasKey($cm2->cmid, $coursemodinfo->modinfo);
1395
        $this->assertArrayHasKey($cm3->cmid, $coursemodinfo->modinfo);
1396
        $this->assertArrayHasKey($cm4->cmid, $coursemodinfo->modinfo);
1397
 
1398
        course_modinfo::purge_course_module_cache($course->id, $cm1->cmid);
1399
 
1400
        $coursemodinfo = $cache->get_versioned($course->id, $course->cacherev);
1401
        $this->assertCount(3, $coursemodinfo->modinfo);
1402
        $this->assertArrayNotHasKey($cm1->cmid, $coursemodinfo->modinfo);
1403
        $this->assertArrayHasKey($cm2->cmid, $coursemodinfo->modinfo);
1404
        $this->assertArrayHasKey($cm3->cmid, $coursemodinfo->modinfo);
1405
        $this->assertArrayHasKey($cm4->cmid, $coursemodinfo->modinfo);
1406
        // Make sure that the cacherev will be reset.
1407
        $this->assertEquals(-1, $coursemodinfo->cacherev);
1408
    }
1409
 
1410
    /**
1411
     * Purge a multiple course modules from the cache.
1412
     *
1413
     * @return void
1414
     * @covers \course_modinfo::purge_course_modules_cache
1415
     */
1416
    public function test_purge_multiple_course_modules(): void {
1417
        $this->resetAfterTest();
1418
        $this->setAdminUser();
1419
        $cache = cache::make('core', 'coursemodinfo');
1420
 
1421
        // Generate the course and pre-requisite section.
1422
        $course = $this->getDataGenerator()->create_course();
1423
        $cm1 = $this->getDataGenerator()->create_module('page', ['course' => $course]);
1424
        $cm2 = $this->getDataGenerator()->create_module('page', ['course' => $course]);
1425
        $cm3 = $this->getDataGenerator()->create_module('page', ['course' => $course]);
1426
        $cm4 = $this->getDataGenerator()->create_module('page', ['course' => $course]);
1427
        // Reset course cache.
1428
        rebuild_course_cache($course->id, true);
1429
        // Build course cache.
1430
        get_fast_modinfo($course->id);
1431
        // Get the course modinfo cache.
1432
        $coursemodinfo = $cache->get_versioned($course->id, $course->cacherev);
1433
        $this->assertCount(4, $coursemodinfo->modinfo);
1434
        $this->assertArrayHasKey($cm1->cmid, $coursemodinfo->modinfo);
1435
        $this->assertArrayHasKey($cm2->cmid, $coursemodinfo->modinfo);
1436
        $this->assertArrayHasKey($cm3->cmid, $coursemodinfo->modinfo);
1437
        $this->assertArrayHasKey($cm4->cmid, $coursemodinfo->modinfo);
1438
 
1439
        course_modinfo::purge_course_modules_cache($course->id, [$cm2->cmid, $cm3->cmid]);
1440
 
1441
        $coursemodinfo = $cache->get_versioned($course->id, $course->cacherev);
1442
        $this->assertCount(2, $coursemodinfo->modinfo);
1443
        $this->assertArrayHasKey($cm1->cmid, $coursemodinfo->modinfo);
1444
        $this->assertArrayNotHasKey($cm2->cmid, $coursemodinfo->modinfo);
1445
        $this->assertArrayNotHasKey($cm3->cmid, $coursemodinfo->modinfo);
1446
        $this->assertArrayHasKey($cm4->cmid, $coursemodinfo->modinfo);
1447
        // Make sure that the cacherev will be reset.
1448
        $this->assertEquals(-1, $coursemodinfo->cacherev);
1449
    }
1450
 
1451
    /**
1452
     * Test get_cm() method to output course module id in the exception text.
1453
     *
1454
     * @covers \course_modinfo::get_cm
1455
     * @return void
1456
     */
1457
    public function test_invalid_course_module_id(): void {
1458
        global $DB;
1459
        $this->resetAfterTest();
1460
 
1461
        $course = $this->getDataGenerator()->create_course();
1462
        $forum0 = $this->getDataGenerator()->create_module('assign', ['course' => $course->id], ['section' => 0]);
1463
        $forum1 = $this->getDataGenerator()->create_module('assign', ['course' => $course->id], ['section' => 0]);
1464
        $forum2 = $this->getDataGenerator()->create_module('assign', ['course' => $course->id], ['section' => 0]);
1465
 
1466
        // Break section sequence.
1467
        $modinfo = get_fast_modinfo($course->id);
1468
        $sectionid = $modinfo->get_section_info(0)->id;
1469
        $section = $DB->get_record('course_sections', ['id' => $sectionid]);
1470
        $sequence = explode(',', $section->sequence);
1471
        $sequence = array_diff($sequence, [$forum1->cmid]);
1472
        $section->sequence = implode(',', $sequence);
1473
        $DB->update_record('course_sections', $section);
1474
 
1475
        // Assert exception text.
1476
        $this->expectException(\moodle_exception::class);
1477
        $this->expectExceptionMessage('Invalid course module ID: ' . $forum1->cmid);
1478
        delete_course($course, false);
1479
    }
1480
 
1481
    /**
1482
     * Tests that if the modinfo cache returns a newer-than-expected version, Moodle won't rebuild
1483
     * it.
1484
     *
1485
     * This is important to avoid wasted time/effort and poor performance, for example in cases
1486
     * where multiple requests are accessing the course.
1487
     *
1488
     * Certain cases could be particularly bad if this test fails. For example, if using clustered
1489
     * databases where there is a 100ms delay between updates to the course table being available
1490
     * to all users (but no such delay on the cache infrastructure), then during that 100ms, every
1491
     * request that calls get_fast_modinfo and uses the read-only database will rebuild the course
1492
     * cache. Since these will then create a still-newer version, future requests for the next
1493
     * 100ms will also rebuild it again... etc.
1494
     *
1495
     * @covers \course_modinfo
1496
     */
1497
    public function test_get_modinfo_with_newer_version(): void {
1498
        global $DB;
1499
 
1500
        $this->resetAfterTest();
1501
 
1502
        // Get info about a course and build the initial cache, then drop it from memory.
1503
        $course = $this->getDataGenerator()->create_course();
1504
        get_fast_modinfo($course);
1505
        get_fast_modinfo(0, 0, true);
1506
 
1507
        // User A starts a request, which takes some time...
1508
        $useracourse = $DB->get_record('course', ['id' => $course->id]);
1509
 
1510
        // User B also starts a request and makes a change to the course.
1511
        $userbcourse = $DB->get_record('course', ['id' => $course->id]);
1512
        $this->getDataGenerator()->create_module('page', ['course' => $course->id]);
1513
        rebuild_course_cache($userbcourse->id, false);
1514
 
1515
        // Finally, user A's request now gets modinfo. It should accept the version from B even
1516
        // though the course version (of cache) is newer than the one expected by A.
1517
        $before = $DB->perf_get_queries();
1518
        $modinfo = get_fast_modinfo($useracourse);
1519
        $after = $DB->perf_get_queries();
1520
        $this->assertEquals($after, $before, 'Should use cached version, making no DB queries');
1521
 
1522
        // Obviously, modinfo should include the Page now.
1523
        $this->assertCount(1, $modinfo->get_instances_of('page'));
1524
    }
1525
 
1526
    /**
1527
     * Test for get_component_instance.
1528
     * @covers \section_info::get_component_instance
1529
     */
1530
    public function test_get_component_instance(): void {
1531
        global $DB;
1532
        $this->resetAfterTest();
1533
 
1534
        $course = $this->getDataGenerator()->create_course(['format' => 'topics', 'numsections' => 2]);
1535
 
1536
        course_update_section(
1537
            $course,
1538
            $DB->get_record('course_sections', ['course' => $course->id, 'section' => 2]),
1539
            [
1540
                'component' => 'test_component',
1541
                'itemid' => 1,
1542
            ]
1543
        );
1544
 
1545
        $modinfo = get_fast_modinfo($course->id);
1546
        $sectioninfos = $modinfo->get_section_info_all();
1547
 
1548
        $this->assertNull($sectioninfos[1]->get_component_instance());
1549
        $this->assertNull($sectioninfos[1]->component);
1550
        $this->assertNull($sectioninfos[1]->itemid);
1551
 
1552
        $this->assertInstanceOf('\core_courseformat\sectiondelegate', $sectioninfos[2]->get_component_instance());
1553
        $this->assertInstanceOf('\test_component\courseformat\sectiondelegate', $sectioninfos[2]->get_component_instance());
1554
        $this->assertEquals('test_component', $sectioninfos[2]->component);
1555
        $this->assertEquals(1, $sectioninfos[2]->itemid);
1556
    }
1557
 
1558
    /**
1559
     * Test for section_info is_delegated.
1560
     * @covers \section_info::is_delegated
1561
     */
1562
    public function test_is_delegated(): void {
1563
        $this->resetAfterTest();
1564
 
1565
        $course = $this->getDataGenerator()->create_course(['format' => 'topics', 'numsections' => 1]);
1566
 
1567
        formatactions::section($course)->create_delegated('mod_label', 0);
1568
 
1569
        $modinfo = get_fast_modinfo($course->id);
1570
        $sectioninfos = $modinfo->get_section_info_all();
1571
 
1572
        $this->assertFalse($sectioninfos[1]->is_delegated());
1573
        $this->assertTrue($sectioninfos[2]->is_delegated());
1574
    }
1575
 
1576
    /**
1577
     * Test the course_modinfo::purge_course_caches() function with a
1578
     * one-course array, a two-course array, and an empty array, and ensure
1579
     * that only the courses specified have their course cache version
1580
     * incremented (or all course caches if none specified).
1581
     *
1582
     * @covers \course_modinfo
1583
     */
1584
    public function test_multiple_modinfo_cache_purge(): void {
1585
        global $DB;
1586
 
1587
        $this->resetAfterTest();
1588
        $this->setAdminUser();
1589
        $cache = cache::make('core', 'coursemodinfo');
1590
 
1591
        // Generate two courses and pre-requisite modules for targeted course
1592
        // cache tests.
1593
        $courseone = $this->getDataGenerator()->create_course(
1594
            [
1595
                'format' => 'topics',
1596
                'numsections' => 3,
1597
            ],
1598
            [
1599
                'createsections' => true,
1600
            ]
1601
        );
1602
        $coursetwo = $this->getDataGenerator()->create_course(
1603
            [
1604
                'format' => 'topics',
1605
                'numsections' => 3,
1606
            ],
1607
            [
1608
                'createsections' => true,
1609
            ]
1610
        );
1611
        $coursethree = $this->getDataGenerator()->create_course(
1612
            [
1613
                'format' => 'topics',
1614
                'numsections' => 3,
1615
            ],
1616
            [
1617
                'createsections' => true,
1618
            ]
1619
        );
1620
 
1621
        // Make sure the cacherev is set for all three.
1622
        $cacherevone = $DB->get_field('course', 'cacherev', ['id' => $courseone->id]);
1623
        $this->assertGreaterThan(0, $cacherevone);
1624
        $prevcacherevone = $cacherevone;
1625
 
1626
        $cacherevtwo = $DB->get_field('course', 'cacherev', ['id' => $coursetwo->id]);
1627
        $this->assertGreaterThan(0, $cacherevtwo);
1628
        $prevcacherevtwo = $cacherevtwo;
1629
 
1630
        $cacherevthree = $DB->get_field('course', 'cacherev', ['id' => $coursethree->id]);
1631
        $this->assertGreaterThan(0, $cacherevthree);
1632
        $prevcacherevthree = $cacherevthree;
1633
 
1634
        // Reset course caches and make sure cacherev is bumped up but cache is empty.
1635
        rebuild_course_cache($courseone->id, true);
1636
        $cacherevone = $DB->get_field('course', 'cacherev', ['id' => $courseone->id]);
1637
        $this->assertGreaterThan($prevcacherevone, $cacherevone);
1638
        $this->assertEmpty($cache->get_versioned($courseone->id, $prevcacherevone));
1639
        $prevcacherevone = $cacherevone;
1640
 
1641
        rebuild_course_cache($coursetwo->id, true);
1642
        $cacherevtwo = $DB->get_field('course', 'cacherev', ['id' => $coursetwo->id]);
1643
        $this->assertGreaterThan($prevcacherevtwo, $cacherevtwo);
1644
        $this->assertEmpty($cache->get_versioned($coursetwo->id, $prevcacherevtwo));
1645
        $prevcacherevtwo = $cacherevtwo;
1646
 
1647
        rebuild_course_cache($coursethree->id, true);
1648
        $cacherevthree = $DB->get_field('course', 'cacherev', ['id' => $coursethree->id]);
1649
        $this->assertGreaterThan($prevcacherevthree, $cacherevthree);
1650
        $this->assertEmpty($cache->get_versioned($coursethree->id, $prevcacherevthree));
1651
        $prevcacherevthree = $cacherevthree;
1652
 
1653
        // Build course caches. Cacherev should not change but caches are now not empty. Make sure cacherev is the same everywhere.
1654
        $modinfoone = get_fast_modinfo($courseone->id);
1655
        $cacherevone = $DB->get_field('course', 'cacherev', ['id' => $courseone->id]);
1656
        $this->assertEquals($prevcacherevone, $cacherevone);
1657
        $cachedvalueone = $cache->get_versioned($courseone->id, $cacherevone);
1658
        $this->assertNotEmpty($cachedvalueone);
1659
        $this->assertEquals($cacherevone, $cachedvalueone->cacherev);
1660
        $this->assertEquals($cacherevone, $modinfoone->get_course()->cacherev);
1661
        $prevcacherevone = $cacherevone;
1662
 
1663
        $modinfotwo = get_fast_modinfo($coursetwo->id);
1664
        $cacherevtwo = $DB->get_field('course', 'cacherev', ['id' => $coursetwo->id]);
1665
        $this->assertEquals($prevcacherevtwo, $cacherevtwo);
1666
        $cachedvaluetwo = $cache->get_versioned($coursetwo->id, $cacherevtwo);
1667
        $this->assertNotEmpty($cachedvaluetwo);
1668
        $this->assertEquals($cacherevtwo, $cachedvaluetwo->cacherev);
1669
        $this->assertEquals($cacherevtwo, $modinfotwo->get_course()->cacherev);
1670
        $prevcacherevtwo = $cacherevtwo;
1671
 
1672
        $modinfothree = get_fast_modinfo($coursethree->id);
1673
        $cacherevthree = $DB->get_field('course', 'cacherev', ['id' => $coursethree->id]);
1674
        $this->assertEquals($prevcacherevthree, $cacherevthree);
1675
        $cachedvaluethree = $cache->get_versioned($coursethree->id, $cacherevthree);
1676
        $this->assertNotEmpty($cachedvaluethree);
1677
        $this->assertEquals($cacherevthree, $cachedvaluethree->cacherev);
1678
        $this->assertEquals($cacherevthree, $modinfothree->get_course()->cacherev);
1679
        $prevcacherevthree = $cacherevthree;
1680
 
1681
        // Purge course one's cache. Cacherev must be incremented (but only for
1682
        // course one, check course two and three in next step).
1683
        course_modinfo::purge_course_caches([$courseone->id]);
1684
 
1685
        get_fast_modinfo($courseone->id);
1686
        $cacherevone = $DB->get_field('course', 'cacherev', ['id' => $courseone->id]);
1687
        $this->assertGreaterThan($prevcacherevone, $cacherevone);
1688
        $prevcacherevone = $cacherevone;
1689
 
1690
        // Confirm course two and three's cache shouldn't have been affected.
1691
        get_fast_modinfo($coursetwo->id);
1692
        $cacherevtwo = $DB->get_field('course', 'cacherev', ['id' => $coursetwo->id]);
1693
        $this->assertEquals($prevcacherevtwo, $cacherevtwo);
1694
        $prevcacherevtwo = $cacherevtwo;
1695
 
1696
        get_fast_modinfo($coursethree->id);
1697
        $cacherevthree = $DB->get_field('course', 'cacherev', ['id' => $coursethree->id]);
1698
        $this->assertEquals($prevcacherevthree, $cacherevthree);
1699
        $prevcacherevthree = $cacherevthree;
1700
 
1701
        // Purge course two and three's cache. Cacherev must be incremented (but only for
1702
        // course two and three, then check course one hasn't changed in next step).
1703
        course_modinfo::purge_course_caches([$coursetwo->id, $coursethree->id]);
1704
 
1705
        get_fast_modinfo($coursetwo->id);
1706
        $cacherevtwo = $DB->get_field('course', 'cacherev', ['id' => $coursetwo->id]);
1707
        $this->assertGreaterThan($prevcacherevtwo, $cacherevtwo);
1708
        $prevcacherevtwo = $cacherevtwo;
1709
 
1710
        get_fast_modinfo($coursethree->id);
1711
        $cacherevthree = $DB->get_field('course', 'cacherev', ['id' => $coursethree->id]);
1712
        $this->assertGreaterThan($prevcacherevthree, $cacherevthree);
1713
        $prevcacherevthree = $cacherevthree;
1714
 
1715
        // Confirm course one's cache shouldn't have been affected.
1716
        get_fast_modinfo($courseone->id);
1717
        $cacherevone = $DB->get_field('course', 'cacherev', ['id' => $courseone->id]);
1718
        $this->assertEquals($prevcacherevone, $cacherevone);
1719
        $prevcacherevone = $cacherevone;
1720
 
1721
        // Purge all course caches. Cacherev must be incremented for all three courses.
1722
        course_modinfo::purge_course_caches();
1723
        get_fast_modinfo($courseone->id);
1724
        $cacherevone = $DB->get_field('course', 'cacherev', ['id' => $courseone->id]);
1725
        $this->assertGreaterThan($prevcacherevone, $cacherevone);
1726
 
1727
        get_fast_modinfo($coursetwo->id);
1728
        $cacherevtwo = $DB->get_field('course', 'cacherev', ['id' => $coursetwo->id]);
1729
        $this->assertGreaterThan($prevcacherevtwo, $cacherevtwo);
1730
 
1731
        get_fast_modinfo($coursethree->id);
1732
        $cacherevthree = $DB->get_field('course', 'cacherev', ['id' => $coursethree->id]);
1733
        $this->assertGreaterThan($prevcacherevthree, $cacherevthree);
1734
    }
1735
}