Proyectos de Subversion Moodle

Rev

Rev 11 | | 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
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
 */
1441 ariadna 39
final class modinfolib_test extends advanced_testcase {
1 efrain 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');
1441 ariadna 47
        parent::setUpBeforeClass();
1 efrain 48
    }
49
 
11 efrain 50
    public function test_section_info_properties(): void {
1 efrain 51
        global $DB, $CFG;
52
 
53
        $this->resetAfterTest();
54
        $oldcfgenableavailability = $CFG->enableavailability;
55
        $oldcfgenablecompletion = $CFG->enablecompletion;
56
        set_config('enableavailability', 1);
57
        set_config('enablecompletion', 1);
58
        $this->setAdminUser();
59
 
60
        // Generate the course and pre-requisite module.
61
        $course = $this->getDataGenerator()->create_course(
62
                array('format' => 'topics',
63
                    'numsections' => 3,
64
                    'enablecompletion' => 1,
65
                    'groupmode' => SEPARATEGROUPS,
66
                    'forcegroupmode' => 0),
67
                array('createsections' => true));
68
        $coursecontext = context_course::instance($course->id);
69
        $prereqforum = $this->getDataGenerator()->create_module('forum',
70
                array('course' => $course->id),
71
                array('completion' => 1));
72
 
73
        // Add availability conditions.
74
        $availability = '{"op":"&","showc":[true,true,true],"c":[' .
75
                '{"type":"completion","cm":' . $prereqforum->cmid . ',"e":"' .
76
                    COMPLETION_COMPLETE . '"},' .
77
                '{"type":"grade","id":666,"min":0.4},' .
78
                '{"type":"profile","op":"contains","sf":"email","v":"test"}' .
79
                ']}';
80
        $DB->set_field('course_sections', 'availability', $availability,
81
                array('course' => $course->id, 'section' => 2));
82
        rebuild_course_cache($course->id, true);
83
        $sectiondb = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 2));
84
 
85
        // Create and enrol a student.
86
        $studentrole = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
87
        $student = $this->getDataGenerator()->create_user();
88
        role_assign($studentrole->id, $student->id, $coursecontext);
89
        $enrolplugin = enrol_get_plugin('manual');
90
        $enrolinstance = $DB->get_record('enrol', array('courseid' => $course->id, 'enrol' => 'manual'));
91
        $enrolplugin->enrol_user($enrolinstance, $student->id);
92
        $this->setUser($student);
93
 
94
        // Get modinfo.
95
        $modinfo = get_fast_modinfo($course->id);
96
        $si = $modinfo->get_section_info(2);
97
 
98
        $this->assertEquals($sectiondb->id, $si->id);
99
        $this->assertEquals($sectiondb->course, $si->course);
100
        $this->assertEquals($sectiondb->section, $si->section);
101
        $this->assertEquals($sectiondb->name, $si->name);
102
        $this->assertEquals($sectiondb->visible, $si->visible);
103
        $this->assertEquals($sectiondb->summary, $si->summary);
104
        $this->assertEquals($sectiondb->summaryformat, $si->summaryformat);
105
        $this->assertEquals($sectiondb->sequence, $si->sequence); // Since this section does not contain invalid modules.
106
        $this->assertEquals($availability, $si->availability);
107
 
108
        // Dynamic fields, just test that they can be retrieved (must be carefully tested in each activity type).
109
        $this->assertEquals(0, $si->available);
110
        $this->assertNotEmpty($si->availableinfo); // Lists all unmet availability conditions.
111
        $this->assertEquals(0, $si->uservisible);
112
 
113
        // Restore settings.
114
        set_config('enableavailability', $oldcfgenableavailability);
115
        set_config('enablecompletion', $oldcfgenablecompletion);
116
    }
117
 
11 efrain 118
    public function test_cm_info_properties(): void {
1 efrain 119
        global $DB, $CFG;
120
 
121
        $this->resetAfterTest();
122
        $oldcfgenableavailability = $CFG->enableavailability;
123
        $oldcfgenablecompletion = $CFG->enablecompletion;
124
        set_config('enableavailability', 1);
125
        set_config('enablecompletion', 1);
126
        $this->setAdminUser();
127
 
128
        // Generate the course and pre-requisite module.
129
        $course = $this->getDataGenerator()->create_course(
130
                array('format' => 'topics',
131
                    'numsections' => 3,
132
                    'enablecompletion' => 1,
133
                    'groupmode' => SEPARATEGROUPS,
134
                    'forcegroupmode' => 0),
135
                array('createsections' => true));
136
        $coursecontext = context_course::instance($course->id);
137
        $prereqforum = $this->getDataGenerator()->create_module('forum',
138
                array('course' => $course->id),
139
                array('completion' => 1));
140
 
141
        // Generate module and add availability conditions.
142
        $availability = '{"op":"&","showc":[true,true,true],"c":[' .
143
                '{"type":"completion","cm":' . $prereqforum->cmid . ',"e":"' .
144
                    COMPLETION_COMPLETE . '"},' .
145
                '{"type":"grade","id":666,"min":0.4},' .
146
                '{"type":"profile","op":"contains","sf":"email","v":"test"}' .
147
                ']}';
148
        $assign = $this->getDataGenerator()->create_module('assign',
149
                array('course' => $course->id),
150
                array('idnumber' => 123,
151
                    'groupmode' => VISIBLEGROUPS,
152
                    'availability' => $availability));
153
        rebuild_course_cache($course->id, true);
154
 
155
        // Retrieve all related records from DB.
156
        $assigndb = $DB->get_record('assign', array('id' => $assign->id));
157
        $moduletypedb = $DB->get_record('modules', array('name' => 'assign'));
158
        $moduledb = $DB->get_record('course_modules', array('module' => $moduletypedb->id, 'instance' => $assign->id));
159
        $sectiondb = $DB->get_record('course_sections', array('id' => $moduledb->section));
160
        $modnamessingular = get_module_types_names(false);
161
        $modnamesplural = get_module_types_names(true);
162
 
163
        // Create and enrol a student.
164
        $studentrole = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
165
        $student = $this->getDataGenerator()->create_user();
166
        role_assign($studentrole->id, $student->id, $coursecontext);
167
        $enrolplugin = enrol_get_plugin('manual');
168
        $enrolinstance = $DB->get_record('enrol', array('courseid' => $course->id, 'enrol' => 'manual'));
169
        $enrolplugin->enrol_user($enrolinstance, $student->id);
170
        $this->setUser($student);
171
 
172
        // Emulate data used in building course cache to receive the same instance of cached_cm_info as was used in building modinfo.
173
        $rawmods = get_course_mods($course->id);
174
        $cachedcminfo = assign_get_coursemodule_info($rawmods[$moduledb->id]);
175
 
176
        // Get modinfo.
177
        $modinfo = get_fast_modinfo($course->id);
178
        $cm = $modinfo->instances['assign'][$assign->id];
179
 
180
        $this->assertEquals($moduledb->id, $cm->id);
181
        $this->assertEquals($assigndb->id, $cm->instance);
182
        $this->assertEquals($moduledb->course, $cm->course);
183
        $this->assertEquals($moduledb->idnumber, $cm->idnumber);
184
        $this->assertEquals($moduledb->added, $cm->added);
185
        $this->assertEquals($moduledb->visible, $cm->visible);
186
        $this->assertEquals($moduledb->visibleold, $cm->visibleold);
187
        $this->assertEquals($moduledb->groupmode, $cm->groupmode);
188
        $this->assertEquals(VISIBLEGROUPS, $cm->groupmode);
189
        $this->assertEquals($moduledb->groupingid, $cm->groupingid);
190
        $this->assertEquals($course->groupmodeforce, $cm->coursegroupmodeforce);
191
        $this->assertEquals($course->groupmode, $cm->coursegroupmode);
192
        $this->assertEquals(SEPARATEGROUPS, $cm->coursegroupmode);
193
        $this->assertEquals($course->groupmodeforce ? $course->groupmode : $moduledb->groupmode,
194
                $cm->effectivegroupmode); // (since mod_assign supports groups).
195
        $this->assertEquals(VISIBLEGROUPS, $cm->effectivegroupmode);
196
        $this->assertEquals($moduledb->indent, $cm->indent);
197
        $this->assertEquals($moduledb->completion, $cm->completion);
198
        $this->assertEquals($moduledb->completiongradeitemnumber, $cm->completiongradeitemnumber);
199
        $this->assertEquals($moduledb->completionpassgrade, $cm->completionpassgrade);
200
        $this->assertEquals($moduledb->completionview, $cm->completionview);
201
        $this->assertEquals($moduledb->completionexpected, $cm->completionexpected);
202
        $this->assertEquals($moduledb->showdescription, $cm->showdescription);
203
        $this->assertEquals(null, $cm->extra); // Deprecated field. Used in module types that don't return cached_cm_info.
204
        $this->assertEquals($cachedcminfo->icon, $cm->icon);
205
        $this->assertEquals($cachedcminfo->iconcomponent, $cm->iconcomponent);
206
        $this->assertEquals('assign', $cm->modname);
207
        $this->assertEquals($moduledb->module, $cm->module);
208
        $this->assertEquals($cachedcminfo->name, $cm->name);
209
        $this->assertEquals($sectiondb->section, $cm->sectionnum);
210
        $this->assertEquals($moduledb->section, $cm->section);
211
        $this->assertEquals($availability, $cm->availability);
212
        $this->assertEquals(context_module::instance($moduledb->id), $cm->context);
213
        $this->assertEquals($modnamessingular['assign'], $cm->modfullname);
214
        $this->assertEquals($modnamesplural['assign'], $cm->modplural);
215
        $this->assertEquals(new moodle_url('/mod/assign/view.php', array('id' => $moduledb->id)), $cm->url);
216
        $this->assertEquals($cachedcminfo->customdata, $cm->customdata);
217
 
218
        // Dynamic fields, just test that they can be retrieved (must be carefully tested in each activity type).
219
        $this->assertNotEmpty($cm->availableinfo); // Lists all unmet availability conditions.
220
        $this->assertEquals(0, $cm->uservisible);
221
        $this->assertEquals('', $cm->extraclasses);
222
        $this->assertEquals('', $cm->onclick);
223
        $this->assertEquals(null, $cm->afterlink);
224
        $this->assertEquals(null, $cm->afterediticons);
225
        $this->assertEquals('', $cm->content);
226
 
227
        // Attempt to access and set non-existing field.
228
        $this->assertTrue(empty($modinfo->somefield));
229
        $this->assertFalse(isset($modinfo->somefield));
230
        $cm->somefield;
231
        $this->assertDebuggingCalled();
232
        $cm->somefield = 'Some value';
233
        $this->assertDebuggingCalled();
234
        $this->assertEmpty($cm->somefield);
235
        $this->assertDebuggingCalled();
236
 
237
        // Attempt to overwrite an existing field.
238
        $prevvalue = $cm->name;
239
        $this->assertNotEmpty($cm->name);
240
        $this->assertFalse(empty($cm->name));
241
        $this->assertTrue(isset($cm->name));
242
        $cm->name = 'Illegal overwriting';
243
        $this->assertDebuggingCalled();
244
        $this->assertEquals($prevvalue, $cm->name);
245
        $this->assertDebuggingNotCalled();
246
 
247
        // Restore settings.
248
        set_config('enableavailability', $oldcfgenableavailability);
249
        set_config('enablecompletion', $oldcfgenablecompletion);
250
    }
251
 
11 efrain 252
    public function test_matching_cacherev(): void {
1 efrain 253
        global $DB, $CFG;
254
 
255
        $this->resetAfterTest();
256
        $this->setAdminUser();
257
        $cache = cache::make('core', 'coursemodinfo');
258
 
259
        // Generate the course and pre-requisite module.
260
        $course = $this->getDataGenerator()->create_course(
261
                array('format' => 'topics',
262
                    'numsections' => 3),
263
                array('createsections' => true));
264
 
265
        // Make sure the cacherev is set.
266
        $cacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));
267
        $this->assertGreaterThan(0, $cacherev);
268
        $prevcacherev = $cacherev;
269
 
270
        // Reset course cache and make sure cacherev is bumped up but cache is empty.
271
        rebuild_course_cache($course->id, true);
272
        $cacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));
273
        $this->assertGreaterThan($prevcacherev, $cacherev);
274
        $this->assertEmpty($cache->get_versioned($course->id, $prevcacherev));
275
        $prevcacherev = $cacherev;
276
 
277
        // Build course cache. Cacherev should not change but cache is now not empty. Make sure cacherev is the same everywhere.
278
        $modinfo = get_fast_modinfo($course->id);
279
        $cacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));
280
        $this->assertEquals($prevcacherev, $cacherev);
281
        $cachedvalue = $cache->get_versioned($course->id, $cacherev);
282
        $this->assertNotEmpty($cachedvalue);
283
        $this->assertEquals($cacherev, $cachedvalue->cacherev);
284
        $this->assertEquals($cacherev, $modinfo->get_course()->cacherev);
285
        $prevcacherev = $cacherev;
286
 
287
        // 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.
288
        $cache->acquire_lock($course->id);
289
        $cache->set_versioned($course->id, $cacherev, (object)array_merge((array)$cachedvalue, array('secretfield' => 1)));
290
        $cache->release_lock($course->id);
291
 
292
        // Clear static cache and call get_fast_modinfo() again (pretend we are in another request). Cache should not be rebuilt.
293
        course_modinfo::clear_instance_cache();
294
        $modinfo = get_fast_modinfo($course->id);
295
        $cacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));
296
        $this->assertEquals($prevcacherev, $cacherev);
297
        $cachedvalue = $cache->get_versioned($course->id, $cacherev);
298
        $this->assertNotEmpty($cachedvalue);
299
        $this->assertEquals($cacherev, $cachedvalue->cacherev);
300
        $this->assertNotEmpty($cachedvalue->secretfield);
301
        $this->assertEquals($cacherev, $modinfo->get_course()->cacherev);
302
        $prevcacherev = $cacherev;
303
 
304
        // Rebuild course cache. Cacherev must be incremented everywhere.
305
        rebuild_course_cache($course->id);
306
        $cacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));
307
        $this->assertGreaterThan($prevcacherev, $cacherev);
308
        $cachedvalue = $cache->get_versioned($course->id, $cacherev);
309
        $this->assertNotEmpty($cachedvalue);
310
        $this->assertEquals($cacherev, $cachedvalue->cacherev);
311
        $modinfo = get_fast_modinfo($course->id);
312
        $this->assertEquals($cacherev, $modinfo->get_course()->cacherev);
313
        $prevcacherev = $cacherev;
314
 
315
        // Update cacherev in DB and make sure the cache will be rebuilt on the next call to get_fast_modinfo().
316
        increment_revision_number('course', 'cacherev', 'id = ?', array($course->id));
317
        // We need to clear static cache for course_modinfo instances too.
318
        course_modinfo::clear_instance_cache();
319
        $modinfo = get_fast_modinfo($course->id);
320
        $cacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));
321
        $this->assertGreaterThan($prevcacherev, $cacherev);
322
        $cachedvalue = $cache->get_versioned($course->id, $cacherev);
323
        $this->assertNotEmpty($cachedvalue);
324
        $this->assertEquals($cacherev, $cachedvalue->cacherev);
325
        $this->assertEquals($cacherev, $modinfo->get_course()->cacherev);
326
        $prevcacherev = $cacherev;
327
 
328
        // Reset cache for all courses and make sure this course cache is reset.
329
        rebuild_course_cache(0, true);
330
        $cacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));
331
        $this->assertGreaterThan($prevcacherev, $cacherev);
332
        $this->assertEmpty($cache->get_versioned($course->id, $cacherev));
333
        // Rebuild again.
334
        $modinfo = get_fast_modinfo($course->id);
335
        $cachedvalue = $cache->get_versioned($course->id, $cacherev);
336
        $this->assertNotEmpty($cachedvalue);
337
        $this->assertEquals($cacherev, $cachedvalue->cacherev);
338
        $this->assertEquals($cacherev, $modinfo->get_course()->cacherev);
339
        $prevcacherev = $cacherev;
340
 
341
        // Purge all caches and make sure cacherev is increased and data from MUC erased.
342
        purge_all_caches();
343
        $cacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));
344
        $this->assertGreaterThan($prevcacherev, $cacherev);
345
        $this->assertEmpty($cache->get($course->id));
346
    }
347
 
348
    /**
349
     * The cacherev is updated when we rebuild course cache, but there are scenarios where an
350
     * existing course object with old cacherev might be reused within the same request after
351
     * clearing the cache. In that case, we need to check that the new data is loaded and it
352
     * does not reuse the old cached data with old cacherev.
353
     *
354
     * @covers ::rebuild_course_cache()
355
     */
356
    public function test_cache_clear_wrong_cacherev(): void {
357
        global $DB;
358
 
359
        $this->resetAfterTest();
360
        $originalcourse = $this->getDataGenerator()->create_course();
361
        $course = $DB->get_record('course', ['id' => $originalcourse->id]);
362
        $page = $this->getDataGenerator()->create_module('page',
363
                ['course' => $course->id, 'name' => 'frog']);
364
        $oldmodinfo = get_fast_modinfo($course);
365
        $this->assertEquals('frog', $oldmodinfo->get_cm($page->cmid)->name);
366
 
367
        // Change page name and rebuild cache.
368
        $DB->set_field('page', 'name', 'Frog', ['id' => $page->id]);
369
        rebuild_course_cache($course->id, true);
370
 
371
        // Get modinfo using original course object which has old cacherev.
372
        $newmodinfo = get_fast_modinfo($course);
373
        $this->assertEquals('Frog', $newmodinfo->get_cm($page->cmid)->name);
374
    }
375
 
376
    /**
377
     * When cacherev is updated for a course, it is supposed to update in the $COURSE and $SITE
378
     * globals automatically. Check this is working.
379
     *
380
     * @covers ::rebuild_course_cache()
381
     */
382
    public function test_cacherev_update_in_globals(): void {
383
        global $DB, $COURSE, $SITE;
384
 
385
        $this->resetAfterTest();
386
 
387
        // Create a course and get modinfo.
388
        $originalcourse = $this->getDataGenerator()->create_course();
389
        $oldmodinfo = get_fast_modinfo($originalcourse->id);
390
 
391
        // Store (two clones of) the course in COURSE and SITE globals.
392
        $COURSE = get_course($originalcourse->id);
393
        $SITE = get_course($originalcourse->id);
394
 
395
        // Note original cacherev.
396
        $originalcacherev = $oldmodinfo->get_course()->cacherev;
397
        $this->assertEquals($COURSE->cacherev, $originalcacherev);
398
        $this->assertEquals($SITE->cacherev, $originalcacherev);
399
 
400
        // Clear the cache and check cacherev updated.
401
        rebuild_course_cache($originalcourse->id, true);
402
 
403
        $newcourse = $DB->get_record('course', ['id' => $originalcourse->id]);
404
        $this->assertGreaterThan($originalcacherev, $newcourse->cacherev);
405
 
406
        // Check that the in-memory $COURSE and $SITE have updated.
407
        $this->assertEquals($newcourse->cacherev, $COURSE->cacherev);
408
        $this->assertEquals($newcourse->cacherev, $SITE->cacherev);
409
    }
410
 
11 efrain 411
    public function test_course_modinfo_properties(): void {
1 efrain 412
        global $USER, $DB;
413
 
414
        $this->resetAfterTest();
415
        $this->setAdminUser();
416
 
1441 ariadna 417
        set_config('allowstealth', true);
418
 
1 efrain 419
        // Generate the course and some modules. Make one section hidden.
420
        $course = $this->getDataGenerator()->create_course(
1441 ariadna 421
                ['format' => 'topics', 'numsections' => 3],
422
                ['createsections' => true]);
1 efrain 423
        $DB->execute('UPDATE {course_sections} SET visible = 0 WHERE course = ? and section = ?',
1441 ariadna 424
                [$course->id, 3]);
1 efrain 425
        $forum0 = $this->getDataGenerator()->create_module('forum',
1441 ariadna 426
                ['course' => $course->id, 'section' => 0]);
1 efrain 427
        $assign0 = $this->getDataGenerator()->create_module('assign',
1441 ariadna 428
                ['course' => $course->id, 'section' => 0, 'visible' => 0]);
429
        $page0 = $this->getDataGenerator()->create_module('page',
430
                ['course' => $course->id, 'section' => 0, 'visibleoncoursepage' => 0]);
1 efrain 431
        $forum1 = $this->getDataGenerator()->create_module('forum',
1441 ariadna 432
                ['course' => $course->id, 'section' => 1]);
1 efrain 433
        $assign1 = $this->getDataGenerator()->create_module('assign',
1441 ariadna 434
                ['course' => $course->id, 'section' => 1]);
1 efrain 435
        $page1 = $this->getDataGenerator()->create_module('page',
1441 ariadna 436
                ['course' => $course->id, 'section' => 1]);
1 efrain 437
        $page3 = $this->getDataGenerator()->create_module('page',
1441 ariadna 438
                ['course' => $course->id, 'section' => 3]);
1 efrain 439
 
440
        $modinfo = get_fast_modinfo($course->id);
441
 
1441 ariadna 442
        $this->assertEquals(
443
                [$forum0->cmid, $assign0->cmid, $page0->cmid, $forum1->cmid, $assign1->cmid, $page1->cmid, $page3->cmid],
1 efrain 444
                array_keys($modinfo->cms));
445
        $this->assertEquals($course->id, $modinfo->courseid);
446
        $this->assertEquals($USER->id, $modinfo->userid);
1441 ariadna 447
        $this->assertEquals([
448
 
449
                1 => [$forum1->cmid, $assign1->cmid, $page1->cmid],
450
                3 => [$page3->cmid],
451
            ], $modinfo->sections);
452
        $this->assertEquals(['forum', 'assign', 'page'], array_keys($modinfo->instances));
453
        $this->assertEquals([$assign0->id, $assign1->id], array_keys($modinfo->instances['assign']));
454
        $this->assertEquals([$forum0->id, $forum1->id], array_keys($modinfo->instances['forum']));
455
        $this->assertEquals([$page0->id, $page1->id, $page3->id], array_keys($modinfo->instances['page']));
1 efrain 456
        $this->assertEquals(groups_get_user_groups($course->id), $modinfo->groups);
1441 ariadna 457
        $this->assertEquals([
458
 
459
                1 => [$forum1->cmid, $assign1->cmid, $page1->cmid],
460
                3 => [$page3->cmid],
461
            ], $modinfo->get_sections());
462
        $this->assertEquals([0, 1, 2, 3], array_keys($modinfo->get_section_info_all()));
463
        $this->assertEquals($forum0->cmid . ',' . $assign0->cmid . ',' . $page0->cmid, $modinfo->get_section_info(0)->sequence);
1 efrain 464
        $this->assertEquals($forum1->cmid . ',' . $assign1->cmid . ',' . $page1->cmid, $modinfo->get_section_info(1)->sequence);
465
        $this->assertEquals('', $modinfo->get_section_info(2)->sequence);
466
        $this->assertEquals($page3->cmid, $modinfo->get_section_info(3)->sequence);
467
        $this->assertEquals($course->id, $modinfo->get_course()->id);
468
        $names = array_keys($modinfo->get_used_module_names());
469
        sort($names);
1441 ariadna 470
        $this->assertEquals(['assign', 'forum', 'page'], $names);
1 efrain 471
        $names = array_keys($modinfo->get_used_module_names(true));
472
        sort($names);
1441 ariadna 473
        $this->assertEquals(['assign', 'forum', 'page'], $names);
1 efrain 474
        // Admin can see hidden modules/sections.
475
        $this->assertTrue($modinfo->cms[$assign0->cmid]->uservisible);
1441 ariadna 476
        $this->assertTrue($modinfo->cms[$assign0->cmid]->is_visible_on_course_page());
477
        $this->assertTrue($modinfo->cms[$page0->cmid]->uservisible);
478
        $this->assertTrue($modinfo->cms[$page0->cmid]->is_visible_on_course_page());
1 efrain 479
        $this->assertTrue($modinfo->get_section_info(3)->uservisible);
480
 
1441 ariadna 481
        $this->assertFalse($modinfo->cms[$assign0->cmid]->is_stealth());
482
        $this->assertFalse($modinfo->cms[$assign0->cmid]->is_stealth());
483
        $this->assertTrue($modinfo->cms[$page0->cmid]->is_stealth());
484
        $this->assertTrue($modinfo->cms[$page3->cmid]->is_stealth());
1 efrain 485
 
1441 ariadna 486
        // Get modinfo for user with student role (without capability to view hidden activities/sections).
487
        $student = $this->getDataGenerator()->create_user();
488
        $this->getDataGenerator()->enrol_user($student->id, $course->id, 'student');
489
        $studentmodinfo = get_fast_modinfo($course->id, $student->id);
490
        $this->assertEquals($student->id, $studentmodinfo->userid);
491
        $this->assertTrue($studentmodinfo->cms[$forum0->cmid]->uservisible);
492
        $this->assertTrue($studentmodinfo->cms[$forum0->cmid]->is_visible_on_course_page());
493
        $this->assertFalse($studentmodinfo->cms[$assign0->cmid]->uservisible);
494
        $this->assertFalse($studentmodinfo->cms[$assign0->cmid]->is_visible_on_course_page());
495
        $this->assertTrue($studentmodinfo->cms[$page0->cmid]->uservisible);
496
        $this->assertFalse($studentmodinfo->cms[$page0->cmid]->is_visible_on_course_page());
497
        $this->assertFalse($studentmodinfo->get_section_info(3)->uservisible);
498
        $this->assertTrue($studentmodinfo->cms[$page3->cmid]->uservisible);
499
        $this->assertTrue($studentmodinfo->cms[$page3->cmid]->is_visible_on_course_page());
500
 
501
        // Get modinfo for user with teacher role (with capability to view hidden activities but not sections).
502
        $teacher = $this->getDataGenerator()->create_user();
503
        $this->getDataGenerator()->enrol_user($teacher->id, $course->id, 'teacher');
504
        $teachermodinfo = get_fast_modinfo($course->id, $teacher->id);
505
        $this->assertEquals($teacher->id, $teachermodinfo->userid);
506
        $this->assertTrue($teachermodinfo->cms[$forum0->cmid]->uservisible);
507
        $this->assertTrue($teachermodinfo->cms[$forum0->cmid]->is_visible_on_course_page());
508
        $this->assertTrue($teachermodinfo->cms[$assign0->cmid]->uservisible);
509
        $this->assertTrue($teachermodinfo->cms[$assign0->cmid]->is_visible_on_course_page());
510
        $this->assertTrue($teachermodinfo->cms[$page0->cmid]->uservisible);
511
        $this->assertTrue($teachermodinfo->cms[$page0->cmid]->is_visible_on_course_page());
512
        $this->assertFalse($teachermodinfo->get_section_info(3)->uservisible);
513
        $this->assertTrue($teachermodinfo->cms[$page3->cmid]->uservisible);
514
        $this->assertTrue($teachermodinfo->cms[$page3->cmid]->is_visible_on_course_page());
515
 
516
        // Get modinfo for user with editingteacher role (with capability to view hidden activities/sections).
517
        $editingteacher = $this->getDataGenerator()->create_user();
518
        $this->getDataGenerator()->enrol_user($editingteacher->id, $course->id, 'editingteacher');
519
        $editingteachermodinfo = get_fast_modinfo($course->id, $editingteacher->id);
520
        $this->assertEquals($editingteacher->id, $editingteachermodinfo->userid);
521
        $this->assertTrue($editingteachermodinfo->cms[$forum0->cmid]->uservisible);
522
        $this->assertTrue($editingteachermodinfo->cms[$forum0->cmid]->is_visible_on_course_page());
523
        $this->assertTrue($editingteachermodinfo->cms[$assign0->cmid]->uservisible);
524
        $this->assertTrue($editingteachermodinfo->cms[$assign0->cmid]->is_visible_on_course_page());
525
        $this->assertTrue($editingteachermodinfo->cms[$page0->cmid]->uservisible);
526
        $this->assertTrue($editingteachermodinfo->cms[$page0->cmid]->is_visible_on_course_page());
527
        $this->assertTrue($editingteachermodinfo->get_section_info(3)->uservisible);
528
        $this->assertTrue($editingteachermodinfo->cms[$page3->cmid]->uservisible);
529
        $this->assertTrue($editingteachermodinfo->cms[$page3->cmid]->is_visible_on_course_page());
530
 
1 efrain 531
        // Attempt to access and set non-existing field.
532
        $this->assertTrue(empty($modinfo->somefield));
533
        $this->assertFalse(isset($modinfo->somefield));
534
        $modinfo->somefield;
535
        $this->assertDebuggingCalled();
536
        $modinfo->somefield = 'Some value';
537
        $this->assertDebuggingCalled();
538
        $this->assertEmpty($modinfo->somefield);
539
        $this->assertDebuggingCalled();
540
 
541
        // Attempt to overwrite existing field.
542
        $this->assertFalse(empty($modinfo->cms));
543
        $this->assertTrue(isset($modinfo->cms));
544
        $modinfo->cms = 'Illegal overwriting';
545
        $this->assertDebuggingCalled();
546
        $this->assertNotEquals('Illegal overwriting', $modinfo->cms);
547
    }
548
 
11 efrain 549
    public function test_is_user_access_restricted_by_capability(): void {
1 efrain 550
        global $DB;
551
 
552
        $this->resetAfterTest();
553
 
554
        // Create a course and a mod_assign instance.
555
        $course = $this->getDataGenerator()->create_course();
556
        $assign = $this->getDataGenerator()->create_module('assign', array('course'=>$course->id));
557
 
558
        // Create and enrol a student.
559
        $coursecontext = context_course::instance($course->id);
560
        $studentrole = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
561
        $student = $this->getDataGenerator()->create_user();
562
        role_assign($studentrole->id, $student->id, $coursecontext);
563
        $enrolplugin = enrol_get_plugin('manual');
564
        $enrolinstance = $DB->get_record('enrol', array('courseid' => $course->id, 'enrol' => 'manual'));
565
        $enrolplugin->enrol_user($enrolinstance, $student->id);
566
        $this->setUser($student);
567
 
568
        // Make sure student can see the module.
569
        $cm = get_fast_modinfo($course->id)->instances['assign'][$assign->id];
570
        $this->assertTrue($cm->uservisible);
571
        $this->assertFalse($cm->is_user_access_restricted_by_capability());
572
 
573
        // Prohibit student to view mod_assign for the course.
574
        role_change_permission($studentrole->id, $coursecontext, 'mod/assign:view', CAP_PROHIBIT);
575
        get_fast_modinfo($course->id, 0, true);
576
        $cm = get_fast_modinfo($course->id)->instances['assign'][$assign->id];
577
        $this->assertFalse($cm->uservisible);
578
        $this->assertTrue($cm->is_user_access_restricted_by_capability());
579
 
580
        // Restore permission to student to view mod_assign for the course.
581
        role_change_permission($studentrole->id, $coursecontext, 'mod/assign:view', CAP_INHERIT);
582
        get_fast_modinfo($course->id, 0, true);
583
        $cm = get_fast_modinfo($course->id)->instances['assign'][$assign->id];
584
        $this->assertTrue($cm->uservisible);
585
        $this->assertFalse($cm->is_user_access_restricted_by_capability());
586
 
587
        // Prohibit student to view mod_assign for the particular module.
588
        role_change_permission($studentrole->id, context_module::instance($cm->id), 'mod/assign:view', CAP_PROHIBIT);
589
        get_fast_modinfo($course->id, 0, true);
590
        $cm = get_fast_modinfo($course->id)->instances['assign'][$assign->id];
591
        $this->assertFalse($cm->uservisible);
592
        $this->assertTrue($cm->is_user_access_restricted_by_capability());
593
 
594
        // Check calling get_fast_modinfo() for different user:
595
        $this->setAdminUser();
596
        $cm = get_fast_modinfo($course->id)->instances['assign'][$assign->id];
597
        $this->assertTrue($cm->uservisible);
598
        $this->assertFalse($cm->is_user_access_restricted_by_capability());
599
        $cm = get_fast_modinfo($course->id, $student->id)->instances['assign'][$assign->id];
600
        $this->assertFalse($cm->uservisible);
601
        $this->assertTrue($cm->is_user_access_restricted_by_capability());
602
    }
603
 
604
    /**
605
     * Tests for function cm_info::get_course_module_record()
606
     */
11 efrain 607
    public function test_cm_info_get_course_module_record(): void {
1 efrain 608
        global $DB;
609
 
610
        $this->resetAfterTest();
611
        $this->setAdminUser();
612
 
613
        set_config('enableavailability', 1);
614
        set_config('enablecompletion', 1);
615
 
616
        $course = $this->getDataGenerator()->create_course(
617
                array('format' => 'topics', 'numsections' => 3, 'enablecompletion' => 1),
618
                array('createsections' => true));
619
        $mods = array();
620
        $mods[0] = $this->getDataGenerator()->create_module('forum', array('course' => $course->id));
621
        $mods[1] = $this->getDataGenerator()->create_module('assign',
622
                array('course' => $course->id,
623
                    'section' => 3,
624
                    'idnumber' => '12345',
625
                    'showdescription' => true
626
                    ));
627
        // Pick a small valid availability value to use.
628
        $availabilityvalue = '{"op":"|","show":true,"c":[{"type":"date","d":">=","t":4}]}';
629
        $mods[2] = $this->getDataGenerator()->create_module('book',
630
                array('course' => $course->id,
631
                    'indent' => 5,
632
                    'availability' => $availabilityvalue,
633
                    'showdescription' => false,
634
                    'completion' => true,
635
                    'completionview' => true,
636
                    'completionexpected' => time() + 5000,
637
                    ));
638
        $mods[3] = $this->getDataGenerator()->create_module('forum',
639
                array('course' => $course->id,
640
                    'visible' => 0,
641
                    'groupmode' => 1,
642
                    'availability' => null));
643
        $mods[4] = $this->getDataGenerator()->create_module('forum',
644
                array('course' => $course->id,
645
                    'grouping' => 12));
646
 
647
        $modinfo = get_fast_modinfo($course->id);
648
 
649
        // Make sure that object returned by get_course_module_record(false) has exactly the same fields as DB table 'course_modules'.
650
        $dbfields = array_keys($DB->get_columns('course_modules'));
651
        sort($dbfields);
652
        $cmrecord = $modinfo->get_cm($mods[0]->cmid)->get_course_module_record();
653
        $cmrecordfields = array_keys((array)$cmrecord);
654
        sort($cmrecordfields);
655
        $this->assertEquals($dbfields, $cmrecordfields);
656
 
657
        // Make sure that object returned by get_course_module_record(true) has exactly the same fields
658
        // as object returned by get_coursemodule_from_id(,,,true,);
659
        $cmrecordfull = $modinfo->get_cm($mods[0]->cmid)->get_course_module_record(true);
660
        $cmrecordfullfields = array_keys((array)$cmrecordfull);
661
        $cm = get_coursemodule_from_id(null, $mods[0]->cmid, 0, true, MUST_EXIST);
662
        $cmfields = array_keys((array)$cm);
663
        $this->assertEquals($cmfields, $cmrecordfullfields);
664
 
665
        // Make sure that object returned by get_course_module_record(true) has exactly the same fields
666
        // as object returned by get_coursemodule_from_instance(,,,true,);
667
        $cm = get_coursemodule_from_instance('forum', $mods[0]->id, null, true, MUST_EXIST);
668
        $cmfields = array_keys((array)$cm);
669
        $this->assertEquals($cmfields, $cmrecordfullfields);
670
 
671
        // Make sure the objects have the same properties.
672
        $cm1 = get_coursemodule_from_id(null, $mods[0]->cmid, 0, true, MUST_EXIST);
673
        $cm2 = get_coursemodule_from_instance('forum', $mods[0]->id, 0, true, MUST_EXIST);
674
        $cminfo = $modinfo->get_cm($mods[0]->cmid);
675
        $record = $DB->get_record('course_modules', array('id' => $mods[0]->cmid));
676
        $this->assertEquals($record, $cminfo->get_course_module_record());
677
        $this->assertEquals($cm1, $cminfo->get_course_module_record(true));
678
        $this->assertEquals($cm2, $cminfo->get_course_module_record(true));
679
 
680
        $cm1 = get_coursemodule_from_id(null, $mods[1]->cmid, 0, true, MUST_EXIST);
681
        $cm2 = get_coursemodule_from_instance('assign', $mods[1]->id, 0, true, MUST_EXIST);
682
        $cminfo = $modinfo->get_cm($mods[1]->cmid);
683
        $record = $DB->get_record('course_modules', array('id' => $mods[1]->cmid));
684
        $this->assertEquals($record, $cminfo->get_course_module_record());
685
        $this->assertEquals($cm1, $cminfo->get_course_module_record(true));
686
        $this->assertEquals($cm2, $cminfo->get_course_module_record(true));
687
 
688
        $cm1 = get_coursemodule_from_id(null, $mods[2]->cmid, 0, true, MUST_EXIST);
689
        $cm2 = get_coursemodule_from_instance('book', $mods[2]->id, 0, true, MUST_EXIST);
690
        $cminfo = $modinfo->get_cm($mods[2]->cmid);
691
        $record = $DB->get_record('course_modules', array('id' => $mods[2]->cmid));
692
        $this->assertEquals($record, $cminfo->get_course_module_record());
693
        $this->assertEquals($cm1, $cminfo->get_course_module_record(true));
694
        $this->assertEquals($cm2, $cminfo->get_course_module_record(true));
695
 
696
        $cm1 = get_coursemodule_from_id(null, $mods[3]->cmid, 0, true, MUST_EXIST);
697
        $cm2 = get_coursemodule_from_instance('forum', $mods[3]->id, 0, true, MUST_EXIST);
698
        $cminfo = $modinfo->get_cm($mods[3]->cmid);
699
        $record = $DB->get_record('course_modules', array('id' => $mods[3]->cmid));
700
        $this->assertEquals($record, $cminfo->get_course_module_record());
701
        $this->assertEquals($cm1, $cminfo->get_course_module_record(true));
702
        $this->assertEquals($cm2, $cminfo->get_course_module_record(true));
703
 
704
        $cm1 = get_coursemodule_from_id(null, $mods[4]->cmid, 0, true, MUST_EXIST);
705
        $cm2 = get_coursemodule_from_instance('forum', $mods[4]->id, 0, true, MUST_EXIST);
706
        $cminfo = $modinfo->get_cm($mods[4]->cmid);
707
        $record = $DB->get_record('course_modules', array('id' => $mods[4]->cmid));
708
        $this->assertEquals($record, $cminfo->get_course_module_record());
709
        $this->assertEquals($cm1, $cminfo->get_course_module_record(true));
710
        $this->assertEquals($cm2, $cminfo->get_course_module_record(true));
711
 
712
    }
713
 
714
    /**
715
     * Tests for function cm_info::get_activitybadge().
716
     *
717
     * @covers \cm_info::get_activitybadge
718
     */
719
    public function test_cm_info_get_activitybadge(): void {
720
        global $PAGE;
721
 
722
        $this->resetAfterTest();
723
        $this->setAdminUser();
724
 
725
        $course = $this->getDataGenerator()->create_course();
726
        $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
727
        $resource = $this->getDataGenerator()->create_module('resource', ['course' => $course->id]);
728
        $assign = $this->getDataGenerator()->create_module('assign', ['course' => $course->id]);
729
        $label = $this->getDataGenerator()->create_module('label', ['course' => $course->id]);
730
 
731
        $renderer = $PAGE->get_renderer('core');
732
        $modinfo = get_fast_modinfo($course->id);
733
 
734
        // Forum and resource implements the activitybadge feature.
735
        $cminfo = $modinfo->get_cm($forum->cmid);
736
        $this->assertNotNull($cminfo->get_activitybadge($renderer));
737
        $cminfo = $modinfo->get_cm($resource->cmid);
738
        $this->assertNotNull($cminfo->get_activitybadge($renderer));
739
 
740
        // Assign and label don't implement the activitybadge feature (at least for now).
741
        $cminfo = $modinfo->get_cm($assign->cmid);
742
        $this->assertNull($cminfo->get_activitybadge($renderer));
743
        $cminfo = $modinfo->get_cm($label->cmid);
744
        $this->assertNull($cminfo->get_activitybadge($renderer));
745
    }
746
 
747
    /**
748
     * Tests the availability property that has been added to course modules
749
     * and sections (just to see that it is correctly saved and accessed).
750
     */
11 efrain 751
    public function test_availability_property(): void {
1 efrain 752
        global $DB, $CFG;
753
 
754
        $this->resetAfterTest();
755
 
756
        // Create a course with two modules and three sections.
757
        $course = $this->getDataGenerator()->create_course(
758
                array('format' => 'topics', 'numsections' => 3),
759
                array('createsections' => true));
760
        $forum = $this->getDataGenerator()->create_module('forum',
761
                array('course' => $course->id));
762
        $forum2 = $this->getDataGenerator()->create_module('forum',
763
                array('course' => $course->id));
764
 
765
        // Get modinfo. Check that availability is null for both cm and sections.
766
        $modinfo = get_fast_modinfo($course->id);
767
        $cm = $modinfo->get_cm($forum->cmid);
768
        $this->assertNull($cm->availability);
769
        $section = $modinfo->get_section_info(1, MUST_EXIST);
770
        $this->assertNull($section->availability);
771
 
772
        // Update availability for cm and section in database.
773
        $DB->set_field('course_modules', 'availability', '{}', array('id' => $cm->id));
774
        $DB->set_field('course_sections', 'availability', '{}', array('id' => $section->id));
775
 
776
        // Clear cache and get modinfo again.
777
        rebuild_course_cache($course->id, true);
778
        get_fast_modinfo(0, 0, true);
779
        $modinfo = get_fast_modinfo($course->id);
780
 
781
        // Check values that were changed.
782
        $cm = $modinfo->get_cm($forum->cmid);
783
        $this->assertEquals('{}', $cm->availability);
784
        $section = $modinfo->get_section_info(1, MUST_EXIST);
785
        $this->assertEquals('{}', $section->availability);
786
 
787
        // Check other values are still null.
788
        $cm = $modinfo->get_cm($forum2->cmid);
789
        $this->assertNull($cm->availability);
790
        $section = $modinfo->get_section_info(2, MUST_EXIST);
791
        $this->assertNull($section->availability);
792
    }
793
 
794
    /**
795
     * Tests for get_groups() method.
796
     */
11 efrain 797
    public function test_get_groups(): void {
1 efrain 798
        $this->resetAfterTest();
799
        $generator = $this->getDataGenerator();
800
 
801
        // Create courses.
802
        $course1 = $generator->create_course();
803
        $course2 = $generator->create_course();
804
        $course3 = $generator->create_course();
805
 
806
        // Create users.
807
        $user1 = $generator->create_user();
808
        $user2 = $generator->create_user();
809
        $user3 = $generator->create_user();
810
 
811
        // Enrol users on courses.
812
        $generator->enrol_user($user1->id, $course1->id);
813
        $generator->enrol_user($user2->id, $course2->id);
814
        $generator->enrol_user($user3->id, $course2->id);
815
        $generator->enrol_user($user3->id, $course3->id);
816
 
817
        // Create groups.
818
        $group1 = $generator->create_group(array('courseid' => $course1->id));
819
        $group2 = $generator->create_group(array('courseid' => $course2->id));
820
        $group3 = $generator->create_group(array('courseid' => $course2->id));
821
 
822
        // Assign users to groups and assert the result.
823
        $this->assertTrue($generator->create_group_member(array('groupid' => $group1->id, 'userid' => $user1->id)));
824
        $this->assertTrue($generator->create_group_member(array('groupid' => $group2->id, 'userid' => $user2->id)));
825
        $this->assertTrue($generator->create_group_member(array('groupid' => $group3->id, 'userid' => $user2->id)));
826
        $this->assertTrue($generator->create_group_member(array('groupid' => $group2->id, 'userid' => $user3->id)));
827
 
828
        // Create groupings.
829
        $grouping1 = $generator->create_grouping(array('courseid' => $course1->id));
830
        $grouping2 = $generator->create_grouping(array('courseid' => $course2->id));
831
 
832
        // Assign and assert group to groupings.
833
        groups_assign_grouping($grouping1->id, $group1->id);
834
        groups_assign_grouping($grouping2->id, $group2->id);
835
        groups_assign_grouping($grouping2->id, $group3->id);
836
 
837
        // Test with one single group.
838
        $modinfo = get_fast_modinfo($course1, $user1->id);
839
        $groups = $modinfo->get_groups($grouping1->id);
840
        $this->assertCount(1, $groups);
841
        $this->assertArrayHasKey($group1->id, $groups);
842
 
843
        // Test with two groups.
844
        $modinfo = get_fast_modinfo($course2, $user2->id);
845
        $groups = $modinfo->get_groups();
846
        $this->assertCount(2, $groups);
847
        $this->assertTrue(in_array($group2->id, $groups));
848
        $this->assertTrue(in_array($group3->id, $groups));
849
 
850
        // Test with no groups.
851
        $modinfo = get_fast_modinfo($course3, $user3->id);
852
        $groups = $modinfo->get_groups();
853
        $this->assertCount(0, $groups);
854
        $this->assertArrayNotHasKey($group1->id, $groups);
855
    }
856
 
857
    /**
858
     * Tests the function for constructing a cm_info from mixed data.
859
     */
11 efrain 860
    public function test_create(): void {
1 efrain 861
        global $CFG, $DB;
862
        $this->resetAfterTest();
863
 
864
        // Create a course and an activity.
865
        $generator = $this->getDataGenerator();
866
        $course = $generator->create_course();
867
        $page = $generator->create_module('page', array('course' => $course->id,
868
                'name' => 'Annie'));
869
 
870
        // Null is passed through.
871
        $this->assertNull(cm_info::create(null));
872
 
873
        // Stdclass object turns into cm_info.
874
        $cm = cm_info::create(
875
                (object)array('id' => $page->cmid, 'course' => $course->id));
876
        $this->assertInstanceOf('cm_info', $cm);
877
        $this->assertEquals('Annie', $cm->name);
878
 
879
        // A cm_info object stays as cm_info.
880
        $this->assertSame($cm, cm_info::create($cm));
881
 
882
        // Invalid object (missing fields) causes error.
883
        try {
884
            cm_info::create((object)array('id' => $page->cmid));
885
            $this->fail();
886
        } catch (Exception $e) {
887
            $this->assertInstanceOf('coding_exception', $e);
888
        }
889
 
890
        // Create a second hidden activity.
891
        $hiddenpage = $generator->create_module('page', array('course' => $course->id,
892
                'name' => 'Annie', 'visible' => 0));
893
 
894
        // Create 2 user accounts, one is a manager who can see everything.
895
        $user = $generator->create_user();
896
        $generator->enrol_user($user->id, $course->id);
897
        $manager = $generator->create_user();
898
        $generator->enrol_user($manager->id, $course->id,
899
                $DB->get_field('role', 'id', array('shortname' => 'manager'), MUST_EXIST));
900
 
901
        // User can see the normal page but not the hidden one.
902
        $cm = cm_info::create((object)array('id' => $page->cmid, 'course' => $course->id),
903
                $user->id);
904
        $this->assertTrue($cm->uservisible);
905
        $cm = cm_info::create((object)array('id' => $hiddenpage->cmid, 'course' => $course->id),
906
                $user->id);
907
        $this->assertFalse($cm->uservisible);
908
 
909
        // Manager can see the hidden one too.
910
        $cm = cm_info::create((object)array('id' => $hiddenpage->cmid, 'course' => $course->id),
911
                $manager->id);
912
        $this->assertTrue($cm->uservisible);
913
    }
914
 
915
    /**
916
     * Tests function for getting $course and $cm at once quickly from modinfo
917
     * based on cmid or cm record.
918
     */
11 efrain 919
    public function test_get_course_and_cm_from_cmid(): void {
1 efrain 920
        global $CFG, $DB;
921
        $this->resetAfterTest();
922
 
923
        // Create a course and an activity.
924
        $generator = $this->getDataGenerator();
925
        $course = $generator->create_course(array('shortname' => 'Halls'));
926
        $page = $generator->create_module('page', array('course' => $course->id,
927
                'name' => 'Annie'));
928
 
929
        // Successful usage.
930
        list($course, $cm) = get_course_and_cm_from_cmid($page->cmid);
931
        $this->assertEquals('Halls', $course->shortname);
932
        $this->assertInstanceOf('cm_info', $cm);
933
        $this->assertEquals('Annie', $cm->name);
934
 
935
        // Specified module type.
936
        list($course, $cm) = get_course_and_cm_from_cmid($page->cmid, 'page');
937
        $this->assertEquals('Annie', $cm->name);
938
 
939
        // With id in object.
940
        $fakecm = (object)array('id' => $page->cmid);
941
        list($course, $cm) = get_course_and_cm_from_cmid($fakecm);
942
        $this->assertEquals('Halls', $course->shortname);
943
        $this->assertEquals('Annie', $cm->name);
944
 
945
        // With both id and course in object.
946
        $fakecm->course = $course->id;
947
        list($course, $cm) = get_course_and_cm_from_cmid($fakecm);
948
        $this->assertEquals('Halls', $course->shortname);
949
        $this->assertEquals('Annie', $cm->name);
950
 
951
        // With supplied course id.
952
        list($course, $cm) = get_course_and_cm_from_cmid($page->cmid, 'page', $course->id);
953
        $this->assertEquals('Annie', $cm->name);
954
 
955
        // With supplied course object (modified just so we can check it is
956
        // indeed reusing the supplied object).
957
        $course->silly = true;
958
        list($course, $cm) = get_course_and_cm_from_cmid($page->cmid, 'page', $course);
959
        $this->assertEquals('Annie', $cm->name);
960
        $this->assertTrue($course->silly);
961
 
962
        // Incorrect module type.
963
        try {
964
            get_course_and_cm_from_cmid($page->cmid, 'forum');
965
            $this->fail();
966
        } catch (moodle_exception $e) {
967
            $this->assertEquals('invalidcoursemoduleid', $e->errorcode);
968
        }
969
 
970
        // Invalid module name.
971
        try {
972
            get_course_and_cm_from_cmid($page->cmid, 'pigs can fly');
973
            $this->fail();
974
        } catch (coding_exception $e) {
975
            $this->assertStringContainsString('Invalid modulename parameter', $e->getMessage());
976
        }
977
 
978
        // Doesn't exist.
979
        try {
980
            get_course_and_cm_from_cmid($page->cmid + 1);
981
            $this->fail();
982
        } catch (moodle_exception $e) {
983
            $this->assertInstanceOf('dml_exception', $e);
984
        }
985
 
986
        // Create a second hidden activity.
987
        $hiddenpage = $generator->create_module('page', array('course' => $course->id,
988
                'name' => 'Annie', 'visible' => 0));
989
 
990
        // Create 2 user accounts, one is a manager who can see everything.
991
        $user = $generator->create_user();
992
        $generator->enrol_user($user->id, $course->id);
993
        $manager = $generator->create_user();
994
        $generator->enrol_user($manager->id, $course->id,
995
                $DB->get_field('role', 'id', array('shortname' => 'manager'), MUST_EXIST));
996
 
997
        // User can see the normal page but not the hidden one.
998
        list($course, $cm) = get_course_and_cm_from_cmid($page->cmid, 'page', 0, $user->id);
999
        $this->assertTrue($cm->uservisible);
1000
        list($course, $cm) = get_course_and_cm_from_cmid($hiddenpage->cmid, 'page', 0, $user->id);
1001
        $this->assertFalse($cm->uservisible);
1002
 
1003
        // Manager can see the hidden one too.
1004
        list($course, $cm) = get_course_and_cm_from_cmid($hiddenpage->cmid, 'page', 0, $manager->id);
1005
        $this->assertTrue($cm->uservisible);
1006
    }
1007
 
1008
    /**
1009
     * Tests function for getting $course and $cm at once quickly from modinfo
1010
     * based on instance id or record.
1011
     */
11 efrain 1012
    public function test_get_course_and_cm_from_instance(): void {
1 efrain 1013
        global $CFG, $DB;
1014
        $this->resetAfterTest();
1015
 
1016
        // Create a course and an activity.
1017
        $generator = $this->getDataGenerator();
1018
        $course = $generator->create_course(array('shortname' => 'Halls'));
1019
        $page = $generator->create_module('page', array('course' => $course->id,
1020
                'name' => 'Annie'));
1021
 
1022
        // Successful usage.
1023
        list($course, $cm) = get_course_and_cm_from_instance($page->id, 'page');
1024
        $this->assertEquals('Halls', $course->shortname);
1025
        $this->assertInstanceOf('cm_info', $cm);
1026
        $this->assertEquals('Annie', $cm->name);
1027
 
1028
        // With id in object.
1029
        $fakeinstance = (object)array('id' => $page->id);
1030
        list($course, $cm) = get_course_and_cm_from_instance($fakeinstance, 'page');
1031
        $this->assertEquals('Halls', $course->shortname);
1032
        $this->assertEquals('Annie', $cm->name);
1033
 
1034
        // With both id and course in object.
1035
        $fakeinstance->course = $course->id;
1036
        list($course, $cm) = get_course_and_cm_from_instance($fakeinstance, 'page');
1037
        $this->assertEquals('Halls', $course->shortname);
1038
        $this->assertEquals('Annie', $cm->name);
1039
 
1040
        // With supplied course id.
1041
        list($course, $cm) = get_course_and_cm_from_instance($page->id, 'page', $course->id);
1042
        $this->assertEquals('Annie', $cm->name);
1043
 
1044
        // With supplied course object (modified just so we can check it is
1045
        // indeed reusing the supplied object).
1046
        $course->silly = true;
1047
        list($course, $cm) = get_course_and_cm_from_instance($page->id, 'page', $course);
1048
        $this->assertEquals('Annie', $cm->name);
1049
        $this->assertTrue($course->silly);
1050
 
1051
        // Doesn't exist (or is wrong type).
1052
        try {
1053
            get_course_and_cm_from_instance($page->id, 'forum');
1054
            $this->fail();
1055
        } catch (moodle_exception $e) {
1056
            $this->assertInstanceOf('dml_exception', $e);
1057
        }
1058
 
1059
        // Invalid module ID.
1060
        try {
1061
            get_course_and_cm_from_instance(-1, 'page', $course);
1062
            $this->fail();
1063
        } catch (moodle_exception $e) {
1064
            $this->assertStringContainsString('Invalid module ID: -1', $e->getMessage());
1065
        }
1066
 
1067
        // Invalid module name.
1068
        try {
1069
            get_course_and_cm_from_cmid($page->cmid, '1337 h4x0ring');
1070
            $this->fail();
1071
        } catch (coding_exception $e) {
1072
            $this->assertStringContainsString('Invalid modulename parameter', $e->getMessage());
1073
        }
1074
 
1075
        // Create a second hidden activity.
1076
        $hiddenpage = $generator->create_module('page', array('course' => $course->id,
1077
                'name' => 'Annie', 'visible' => 0));
1078
 
1079
        // Create 2 user accounts, one is a manager who can see everything.
1080
        $user = $generator->create_user();
1081
        $generator->enrol_user($user->id, $course->id);
1082
        $manager = $generator->create_user();
1083
        $generator->enrol_user($manager->id, $course->id,
1084
                $DB->get_field('role', 'id', array('shortname' => 'manager'), MUST_EXIST));
1085
 
1086
        // User can see the normal page but not the hidden one.
1087
        list($course, $cm) = get_course_and_cm_from_cmid($page->cmid, 'page', 0, $user->id);
1088
        $this->assertTrue($cm->uservisible);
1089
        list($course, $cm) = get_course_and_cm_from_cmid($hiddenpage->cmid, 'page', 0, $user->id);
1090
        $this->assertFalse($cm->uservisible);
1091
 
1092
        // Manager can see the hidden one too.
1093
        list($course, $cm) = get_course_and_cm_from_cmid($hiddenpage->cmid, 'page', 0, $manager->id);
1094
        $this->assertTrue($cm->uservisible);
1095
    }
1096
 
1097
    /**
1098
     * Test for get_listed_section_info_all method.
1099
     * @covers \course_modinfo::get_listed_section_info_all
1100
     * @covers \course_modinfo::get_section_info_all
1101
     */
1102
    public function test_get_listed_section_info_all(): void {
1103
        $this->resetAfterTest();
1104
 
1105
        // Create a course with 4 sections.
1106
        $course = $this->getDataGenerator()->create_course(['numsections' => 3]);
1107
 
1108
        $listed = get_fast_modinfo($course)->get_section_info_all();
1109
        $this->assertCount(4, $listed);
1110
 
1111
        // Generate some delegated sections (not listed).
1441 ariadna 1112
        formatactions::section($course)->create_delegated('test_component', 0);
1113
        formatactions::section($course)->create_delegated('test_component', 1);
1 efrain 1114
 
1115
        $this->assertCount(6, get_fast_modinfo($course)->get_section_info_all());
1116
 
1117
        $result = get_fast_modinfo($course)->get_listed_section_info_all();
1118
 
1119
        $this->assertCount(4, $result);
1120
        $this->assertEquals($listed[0]->id, $result[0]->id);
1121
        $this->assertEquals($listed[1]->id, $result[1]->id);
1122
        $this->assertEquals($listed[2]->id, $result[2]->id);
1123
        $this->assertEquals($listed[3]->id, $result[3]->id);
1124
    }
1125
 
1126
    /**
1127
     * Test test_get_section_info_by_id method
1128
     *
1129
     * @dataProvider get_section_info_by_id_provider
1130
     * @covers \course_modinfo::get_section_info_by_id
1131
     *
1132
     * @param int $sectionnum the section number
1133
     * @param int $strictness the search strict mode
1134
     * @param bool $expectnull if the function will return a null
1135
     * @param bool $expectexception if the function will throw an exception
1136
     */
1137
    public function test_get_section_info_by_id(
1138
        int $sectionnum,
1139
        int $strictness = IGNORE_MISSING,
1140
        bool $expectnull = false,
1141
        bool $expectexception = false
11 efrain 1142
    ): void {
1 efrain 1143
        global $DB;
1144
 
1145
        $this->resetAfterTest();
1146
 
1147
        // Create a course with 4 sections.
1148
        $course = $this->getDataGenerator()->create_course(['numsections' => 4]);
1149
 
1150
        // Index sections.
1151
        $sectionindex = [];
1152
        $modinfo = get_fast_modinfo($course);
1153
        $allsections = $modinfo->get_section_info_all();
1154
        foreach ($allsections as $section) {
1155
            $sectionindex[$section->section] = $section->id;
1156
        }
1157
 
1158
        if ($expectexception) {
1159
            $this->expectException(moodle_exception::class);
1160
        }
1161
 
1162
        $sectionid = $sectionindex[$sectionnum] ?? -1;
1163
 
1164
        $section = $modinfo->get_section_info_by_id($sectionid, $strictness);
1165
 
1166
        if ($expectnull) {
1167
            $this->assertNull($section);
1168
        } else {
1169
            $this->assertEquals($sectionid, $section->id);
1170
            $this->assertEquals($sectionnum, $section->section);
1171
        }
1172
    }
1173
 
1174
    /**
1175
     * Data provider for test_get_section_info_by_id().
1176
     *
1177
     * @return array
1178
     */
1441 ariadna 1179
    public static function get_section_info_by_id_provider(): array {
1 efrain 1180
        return [
1181
            'Valid section id' => [
1182
                'sectionnum' => 1,
1183
                'strictness' => IGNORE_MISSING,
1184
                'expectnull' => false,
1185
                'expectexception' => false,
1186
            ],
1187
            'Section zero' => [
1188
                'sectionnum' => 0,
1189
                'strictness' => IGNORE_MISSING,
1190
                'expectnull' => false,
1191
                'expectexception' => false,
1192
            ],
1193
            'invalid section ignore missing' => [
1194
                'sectionnum' => -1,
1195
                'strictness' => IGNORE_MISSING,
1196
                'expectnull' => true,
1197
                'expectexception' => false,
1198
            ],
1199
            'invalid section must exists' => [
1200
                'sectionnum' => -1,
1201
                'strictness' => MUST_EXIST,
1202
                'expectnull' => false,
1203
                'expectexception' => true,
1204
            ],
1205
        ];
1206
    }
1207
 
1208
    /**
1209
     * Test get_section_info_by_component method
1210
     *
1211
     * @covers \course_modinfo::get_section_info_by_component
1212
     * @dataProvider get_section_info_by_component_provider
1213
     *
1214
     * @param string $component the component name
1215
     * @param int $itemid the section number
1216
     * @param int $strictness the search strict mode
1217
     * @param bool $expectnull if the function will return a null
1218
     * @param bool $expectexception if the function will throw an exception
1219
     */
1220
    public function test_get_section_info_by_component(
1221
        string $component,
1222
        int $itemid,
1223
        int $strictness,
1224
        bool $expectnull,
1225
        bool $expectexception
1226
    ): void {
1227
        $this->resetAfterTest();
1228
        $course = $this->getDataGenerator()->create_course(['numsections' => 1]);
1229
 
1230
        formatactions::section($course)->create_delegated('mod_forum', 42);
1231
 
1232
        $modinfo = get_fast_modinfo($course);
1233
 
1234
        if ($expectexception) {
1235
            $this->expectException(moodle_exception::class);
1236
        }
1237
 
1238
        $section = $modinfo->get_section_info_by_component($component, $itemid, $strictness);
1239
 
1240
        if ($expectnull) {
1241
            $this->assertNull($section);
1242
        } else {
1243
            $this->assertEquals($component, $section->component);
1244
            $this->assertEquals($itemid, $section->itemid);
1245
        }
1246
    }
1247
 
1248
    /**
1249
     * Data provider for test_get_section_info_by_component().
1250
     *
1251
     * @return array
1252
     */
1253
    public static function get_section_info_by_component_provider(): array {
1254
        return [
1255
            'Valid component and itemid' => [
1256
                'component' => 'mod_forum',
1257
                'itemid' => 42,
1258
                'strictness' => IGNORE_MISSING,
1259
                'expectnull' => false,
1260
                'expectexception' => false,
1261
            ],
1262
            'Invalid component' => [
1263
                'component' => 'mod_nonexisting',
1264
                'itemid' => 42,
1265
                'strictness' => IGNORE_MISSING,
1266
                'expectnull' => true,
1267
                'expectexception' => false,
1268
            ],
1269
            'Invalid itemid' => [
1270
                'component' => 'mod_forum',
1271
                'itemid' => 0,
1272
                'strictness' => IGNORE_MISSING,
1273
                'expectnull' => true,
1274
                'expectexception' => false,
1275
            ],
1276
            'Invalid component and itemid' => [
1277
                'component' => 'mod_nonexisting',
1278
                'itemid' => 0,
1279
                'strictness' => IGNORE_MISSING,
1280
                'expectnull' => true,
1281
                'expectexception' => false,
1282
            ],
1283
            'Invalid component must exists' => [
1284
                'component' => 'mod_nonexisting',
1285
                'itemid' => 42,
1286
                'strictness' => MUST_EXIST,
1287
                'expectnull' => true,
1288
                'expectexception' => true,
1289
            ],
1290
            'Invalid itemid must exists' => [
1291
                'component' => 'mod_forum',
1292
                'itemid' => 0,
1293
                'strictness' => MUST_EXIST,
1294
                'expectnull' => true,
1295
                'expectexception' => true,
1296
            ],
1297
            'Invalid component and itemid must exists' => [
1298
                'component' => 'mod_nonexisting',
1299
                'itemid' => 0,
1300
                'strictness' => MUST_EXIST,
1301
                'expectnull' => false,
1302
                'expectexception' => true,
1303
            ],
1304
        ];
1305
    }
1306
 
1307
    /**
1308
     * Test has_delegated_sections method
1309
     *
1310
     * @covers \course_modinfo::has_delegated_sections
1311
     */
1312
    public function test_has_delegated_sections(): void {
1313
        $this->resetAfterTest();
1314
        $course = $this->getDataGenerator()->create_course(['numsections' => 1]);
1315
 
1316
        $modinfo = get_fast_modinfo($course);
1317
        $this->assertFalse($modinfo->has_delegated_sections());
1318
 
1319
        formatactions::section($course)->create_delegated('mod_forum', 42);
1320
 
1321
        $modinfo = get_fast_modinfo($course);
1322
        $this->assertTrue($modinfo->has_delegated_sections());
1323
    }
1324
 
1325
    /**
1326
     * Test purge_section_cache_by_id method
1327
     *
1328
     * @covers \course_modinfo::purge_course_section_cache_by_id
1329
     * @return void
1330
     */
1331
    public function test_purge_section_cache_by_id(): void {
1332
        $this->resetAfterTest();
1333
        $this->setAdminUser();
1334
        $cache = cache::make('core', 'coursemodinfo');
1335
 
1336
        // Generate the course and pre-requisite section.
1337
        $course = $this->getDataGenerator()->create_course(['format' => 'topics', 'numsections' => 3], ['createsections' => true]);
1338
        // Reset course cache.
1339
        rebuild_course_cache($course->id, true);
1340
        // Build course cache.
1341
        $modinfo = get_fast_modinfo($course->id);
1342
        // Get the course modinfo cache.
1343
        $coursemodinfo = $cache->get_versioned($course->id, $course->cacherev);
1344
        // Get the section cache.
1345
        $sectioncaches = $coursemodinfo->sectioncache;
1346
 
1347
        $numberedsections = $modinfo->get_section_info_all();
1348
 
1349
        // Make sure that we will have 4 section caches here.
1350
        $this->assertCount(4, $sectioncaches);
1351
        $this->assertArrayHasKey($numberedsections[0]->id, $sectioncaches);
1352
        $this->assertArrayHasKey($numberedsections[1]->id, $sectioncaches);
1353
        $this->assertArrayHasKey($numberedsections[2]->id, $sectioncaches);
1354
        $this->assertArrayHasKey($numberedsections[3]->id, $sectioncaches);
1355
 
1356
        // Purge cache for the section by id.
1357
        course_modinfo::purge_course_section_cache_by_id(
1358
            $course->id,
1359
            $numberedsections[1]->id
1360
        );
1361
        // Get the course modinfo cache.
1362
        $coursemodinfo = $cache->get_versioned($course->id, $course->cacherev);
1363
        // Get the section cache.
1364
        $sectioncaches = $coursemodinfo->sectioncache;
1365
 
1366
        // Make sure that we will have 3 section caches left.
1367
        $this->assertCount(3, $sectioncaches);
1368
        $this->assertArrayNotHasKey($numberedsections[1]->id, $sectioncaches);
1369
        $this->assertArrayHasKey($numberedsections[0]->id, $sectioncaches);
1370
        $this->assertArrayHasKey($numberedsections[2]->id, $sectioncaches);
1371
        $this->assertArrayHasKey($numberedsections[3]->id, $sectioncaches);
1372
        // Make sure that the cacherev will be reset.
1373
        $this->assertEquals(-1, $coursemodinfo->cacherev);
1374
    }
1375
 
1376
    /**
1377
     * Test purge_section_cache_by_number method
1378
     *
1379
     * @covers \course_modinfo::purge_course_section_cache_by_number
1380
     * @return void
1381
     */
1382
    public function test_section_cache_by_number(): void {
1383
        $this->resetAfterTest();
1384
        $this->setAdminUser();
1385
        $cache = cache::make('core', 'coursemodinfo');
1386
 
1387
        // Generate the course and pre-requisite section.
1388
        $course = $this->getDataGenerator()->create_course(['format' => 'topics', 'numsections' => 3], ['createsections' => true]);
1389
        // Reset course cache.
1390
        rebuild_course_cache($course->id, true);
1391
        // Build course cache.
1392
        $modinfo = get_fast_modinfo($course->id);
1393
        // Get the course modinfo cache.
1394
        $coursemodinfo = $cache->get_versioned($course->id, $course->cacherev);
1395
        // Get the section cache.
1396
        $sectioncaches = $coursemodinfo->sectioncache;
1397
 
1398
        $numberedsections = $modinfo->get_section_info_all();
1399
 
1400
        // Make sure that we will have 4 section caches here.
1401
        $this->assertCount(4, $sectioncaches);
1402
        $this->assertArrayHasKey($numberedsections[0]->id, $sectioncaches);
1403
        $this->assertArrayHasKey($numberedsections[1]->id, $sectioncaches);
1404
        $this->assertArrayHasKey($numberedsections[2]->id, $sectioncaches);
1405
        $this->assertArrayHasKey($numberedsections[3]->id, $sectioncaches);
1406
 
1407
        // Purge cache for the section with section number is 1.
1408
        course_modinfo::purge_course_section_cache_by_number($course->id, 1);
1409
        // Get the course modinfo cache.
1410
        $coursemodinfo = $cache->get_versioned($course->id, $course->cacherev);
1411
        // Get the section cache.
1412
        $sectioncaches = $coursemodinfo->sectioncache;
1413
 
1414
        // Make sure that we will have 3 section caches left.
1415
        $this->assertCount(3, $sectioncaches);
1416
        $this->assertArrayNotHasKey($numberedsections[1]->id, $sectioncaches);
1417
        $this->assertArrayHasKey($numberedsections[0]->id, $sectioncaches);
1418
        $this->assertArrayHasKey($numberedsections[2]->id, $sectioncaches);
1419
        $this->assertArrayHasKey($numberedsections[3]->id, $sectioncaches);
1420
        // Make sure that the cacherev will be reset.
1421
        $this->assertEquals(-1, $coursemodinfo->cacherev);
1422
    }
1423
 
1424
    /**
1425
     * Purge a single course module from the cache.
1426
     *
1427
     * @return void
1428
     * @covers \course_modinfo::purge_course_module_cache
1429
     */
1430
    public function test_purge_course_module(): void {
1431
        $this->resetAfterTest();
1432
        $this->setAdminUser();
1433
        $cache = cache::make('core', 'coursemodinfo');
1434
 
1435
        // Generate the course and pre-requisite section.
1436
        $course = $this->getDataGenerator()->create_course();
1437
        $cm1 = $this->getDataGenerator()->create_module('page', ['course' => $course]);
1438
        $cm2 = $this->getDataGenerator()->create_module('page', ['course' => $course]);
1439
        $cm3 = $this->getDataGenerator()->create_module('page', ['course' => $course]);
1440
        $cm4 = $this->getDataGenerator()->create_module('page', ['course' => $course]);
1441
        // Reset course cache.
1442
        rebuild_course_cache($course->id, true);
1443
        // Build course cache.
1444
        get_fast_modinfo($course->id);
1445
        // Get the course modinfo cache.
1446
        $coursemodinfo = $cache->get_versioned($course->id, $course->cacherev);
1447
        $this->assertCount(4, $coursemodinfo->modinfo);
1448
        $this->assertArrayHasKey($cm1->cmid, $coursemodinfo->modinfo);
1449
        $this->assertArrayHasKey($cm2->cmid, $coursemodinfo->modinfo);
1450
        $this->assertArrayHasKey($cm3->cmid, $coursemodinfo->modinfo);
1451
        $this->assertArrayHasKey($cm4->cmid, $coursemodinfo->modinfo);
1452
 
1453
        course_modinfo::purge_course_module_cache($course->id, $cm1->cmid);
1454
 
1455
        $coursemodinfo = $cache->get_versioned($course->id, $course->cacherev);
1456
        $this->assertCount(3, $coursemodinfo->modinfo);
1457
        $this->assertArrayNotHasKey($cm1->cmid, $coursemodinfo->modinfo);
1458
        $this->assertArrayHasKey($cm2->cmid, $coursemodinfo->modinfo);
1459
        $this->assertArrayHasKey($cm3->cmid, $coursemodinfo->modinfo);
1460
        $this->assertArrayHasKey($cm4->cmid, $coursemodinfo->modinfo);
1461
        // Make sure that the cacherev will be reset.
1462
        $this->assertEquals(-1, $coursemodinfo->cacherev);
1463
    }
1464
 
1465
    /**
1466
     * Purge a multiple course modules from the cache.
1467
     *
1468
     * @return void
1469
     * @covers \course_modinfo::purge_course_modules_cache
1470
     */
1471
    public function test_purge_multiple_course_modules(): void {
1472
        $this->resetAfterTest();
1473
        $this->setAdminUser();
1474
        $cache = cache::make('core', 'coursemodinfo');
1475
 
1476
        // Generate the course and pre-requisite section.
1477
        $course = $this->getDataGenerator()->create_course();
1478
        $cm1 = $this->getDataGenerator()->create_module('page', ['course' => $course]);
1479
        $cm2 = $this->getDataGenerator()->create_module('page', ['course' => $course]);
1480
        $cm3 = $this->getDataGenerator()->create_module('page', ['course' => $course]);
1481
        $cm4 = $this->getDataGenerator()->create_module('page', ['course' => $course]);
1482
        // Reset course cache.
1483
        rebuild_course_cache($course->id, true);
1484
        // Build course cache.
1485
        get_fast_modinfo($course->id);
1486
        // Get the course modinfo cache.
1487
        $coursemodinfo = $cache->get_versioned($course->id, $course->cacherev);
1488
        $this->assertCount(4, $coursemodinfo->modinfo);
1489
        $this->assertArrayHasKey($cm1->cmid, $coursemodinfo->modinfo);
1490
        $this->assertArrayHasKey($cm2->cmid, $coursemodinfo->modinfo);
1491
        $this->assertArrayHasKey($cm3->cmid, $coursemodinfo->modinfo);
1492
        $this->assertArrayHasKey($cm4->cmid, $coursemodinfo->modinfo);
1493
 
1494
        course_modinfo::purge_course_modules_cache($course->id, [$cm2->cmid, $cm3->cmid]);
1495
 
1496
        $coursemodinfo = $cache->get_versioned($course->id, $course->cacherev);
1497
        $this->assertCount(2, $coursemodinfo->modinfo);
1498
        $this->assertArrayHasKey($cm1->cmid, $coursemodinfo->modinfo);
1499
        $this->assertArrayNotHasKey($cm2->cmid, $coursemodinfo->modinfo);
1500
        $this->assertArrayNotHasKey($cm3->cmid, $coursemodinfo->modinfo);
1501
        $this->assertArrayHasKey($cm4->cmid, $coursemodinfo->modinfo);
1502
        // Make sure that the cacherev will be reset.
1503
        $this->assertEquals(-1, $coursemodinfo->cacherev);
1504
    }
1505
 
1506
    /**
1507
     * Test get_cm() method to output course module id in the exception text.
1508
     *
1509
     * @covers \course_modinfo::get_cm
1510
     * @return void
1511
     */
1512
    public function test_invalid_course_module_id(): void {
1513
        global $DB;
1514
        $this->resetAfterTest();
1515
 
1516
        $course = $this->getDataGenerator()->create_course();
1517
        $forum0 = $this->getDataGenerator()->create_module('assign', ['course' => $course->id], ['section' => 0]);
1518
        $forum1 = $this->getDataGenerator()->create_module('assign', ['course' => $course->id], ['section' => 0]);
1519
        $forum2 = $this->getDataGenerator()->create_module('assign', ['course' => $course->id], ['section' => 0]);
1520
 
1521
        // Break section sequence.
1522
        $modinfo = get_fast_modinfo($course->id);
1523
        $sectionid = $modinfo->get_section_info(0)->id;
1524
        $section = $DB->get_record('course_sections', ['id' => $sectionid]);
1525
        $sequence = explode(',', $section->sequence);
1526
        $sequence = array_diff($sequence, [$forum1->cmid]);
1527
        $section->sequence = implode(',', $sequence);
1528
        $DB->update_record('course_sections', $section);
1529
 
1530
        // Assert exception text.
1531
        $this->expectException(\moodle_exception::class);
1532
        $this->expectExceptionMessage('Invalid course module ID: ' . $forum1->cmid);
1533
        delete_course($course, false);
1534
    }
1535
 
1536
    /**
1537
     * Tests that if the modinfo cache returns a newer-than-expected version, Moodle won't rebuild
1538
     * it.
1539
     *
1540
     * This is important to avoid wasted time/effort and poor performance, for example in cases
1541
     * where multiple requests are accessing the course.
1542
     *
1543
     * Certain cases could be particularly bad if this test fails. For example, if using clustered
1544
     * databases where there is a 100ms delay between updates to the course table being available
1545
     * to all users (but no such delay on the cache infrastructure), then during that 100ms, every
1546
     * request that calls get_fast_modinfo and uses the read-only database will rebuild the course
1547
     * cache. Since these will then create a still-newer version, future requests for the next
1548
     * 100ms will also rebuild it again... etc.
1549
     *
1550
     * @covers \course_modinfo
1551
     */
1552
    public function test_get_modinfo_with_newer_version(): void {
1553
        global $DB;
1554
 
1555
        $this->resetAfterTest();
1556
 
1557
        // Get info about a course and build the initial cache, then drop it from memory.
1558
        $course = $this->getDataGenerator()->create_course();
1559
        get_fast_modinfo($course);
1560
        get_fast_modinfo(0, 0, true);
1561
 
1562
        // User A starts a request, which takes some time...
1563
        $useracourse = $DB->get_record('course', ['id' => $course->id]);
1564
 
1565
        // User B also starts a request and makes a change to the course.
1566
        $userbcourse = $DB->get_record('course', ['id' => $course->id]);
1567
        $this->getDataGenerator()->create_module('page', ['course' => $course->id]);
1568
        rebuild_course_cache($userbcourse->id, false);
1569
 
1570
        // Finally, user A's request now gets modinfo. It should accept the version from B even
1571
        // though the course version (of cache) is newer than the one expected by A.
1572
        $before = $DB->perf_get_queries();
1573
        $modinfo = get_fast_modinfo($useracourse);
1574
        $after = $DB->perf_get_queries();
1575
        $this->assertEquals($after, $before, 'Should use cached version, making no DB queries');
1576
 
1577
        // Obviously, modinfo should include the Page now.
1578
        $this->assertCount(1, $modinfo->get_instances_of('page'));
1579
    }
1580
 
1581
    /**
1582
     * Test for get_component_instance.
1583
     * @covers \section_info::get_component_instance
1584
     */
1585
    public function test_get_component_instance(): void {
1586
        global $DB;
1587
        $this->resetAfterTest();
1588
 
1589
        $course = $this->getDataGenerator()->create_course(['format' => 'topics', 'numsections' => 2]);
1590
 
1591
        course_update_section(
1592
            $course,
1593
            $DB->get_record('course_sections', ['course' => $course->id, 'section' => 2]),
1594
            [
1595
                'component' => 'test_component',
1596
                'itemid' => 1,
1597
            ]
1598
        );
1599
 
1600
        $modinfo = get_fast_modinfo($course->id);
1601
        $sectioninfos = $modinfo->get_section_info_all();
1602
 
1603
        $this->assertNull($sectioninfos[1]->get_component_instance());
1604
        $this->assertNull($sectioninfos[1]->component);
1605
        $this->assertNull($sectioninfos[1]->itemid);
1606
 
1607
        $this->assertInstanceOf('\core_courseformat\sectiondelegate', $sectioninfos[2]->get_component_instance());
1608
        $this->assertInstanceOf('\test_component\courseformat\sectiondelegate', $sectioninfos[2]->get_component_instance());
1609
        $this->assertEquals('test_component', $sectioninfos[2]->component);
1610
        $this->assertEquals(1, $sectioninfos[2]->itemid);
1611
    }
1612
 
1613
    /**
1614
     * Test for section_info is_delegated.
1615
     * @covers \section_info::is_delegated
1616
     */
1617
    public function test_is_delegated(): void {
1618
        $this->resetAfterTest();
1619
 
1620
        $course = $this->getDataGenerator()->create_course(['format' => 'topics', 'numsections' => 1]);
1621
 
1622
        formatactions::section($course)->create_delegated('mod_label', 0);
1623
 
1624
        $modinfo = get_fast_modinfo($course->id);
1625
        $sectioninfos = $modinfo->get_section_info_all();
1626
 
1627
        $this->assertFalse($sectioninfos[1]->is_delegated());
1628
        $this->assertTrue($sectioninfos[2]->is_delegated());
1629
    }
1630
 
1631
    /**
1632
     * Test the course_modinfo::purge_course_caches() function with a
1633
     * one-course array, a two-course array, and an empty array, and ensure
1634
     * that only the courses specified have their course cache version
1635
     * incremented (or all course caches if none specified).
1636
     *
1637
     * @covers \course_modinfo
1638
     */
1639
    public function test_multiple_modinfo_cache_purge(): void {
1640
        global $DB;
1641
 
1642
        $this->resetAfterTest();
1643
        $this->setAdminUser();
1644
        $cache = cache::make('core', 'coursemodinfo');
1645
 
1646
        // Generate two courses and pre-requisite modules for targeted course
1647
        // cache tests.
1648
        $courseone = $this->getDataGenerator()->create_course(
1649
            [
1650
                'format' => 'topics',
1651
                'numsections' => 3,
1652
            ],
1653
            [
1654
                'createsections' => true,
1655
            ]
1656
        );
1657
        $coursetwo = $this->getDataGenerator()->create_course(
1658
            [
1659
                'format' => 'topics',
1660
                'numsections' => 3,
1661
            ],
1662
            [
1663
                'createsections' => true,
1664
            ]
1665
        );
1666
        $coursethree = $this->getDataGenerator()->create_course(
1667
            [
1668
                'format' => 'topics',
1669
                'numsections' => 3,
1670
            ],
1671
            [
1672
                'createsections' => true,
1673
            ]
1674
        );
1675
 
1676
        // Make sure the cacherev is set for all three.
1677
        $cacherevone = $DB->get_field('course', 'cacherev', ['id' => $courseone->id]);
1678
        $this->assertGreaterThan(0, $cacherevone);
1679
        $prevcacherevone = $cacherevone;
1680
 
1681
        $cacherevtwo = $DB->get_field('course', 'cacherev', ['id' => $coursetwo->id]);
1682
        $this->assertGreaterThan(0, $cacherevtwo);
1683
        $prevcacherevtwo = $cacherevtwo;
1684
 
1685
        $cacherevthree = $DB->get_field('course', 'cacherev', ['id' => $coursethree->id]);
1686
        $this->assertGreaterThan(0, $cacherevthree);
1687
        $prevcacherevthree = $cacherevthree;
1688
 
1689
        // Reset course caches and make sure cacherev is bumped up but cache is empty.
1690
        rebuild_course_cache($courseone->id, true);
1691
        $cacherevone = $DB->get_field('course', 'cacherev', ['id' => $courseone->id]);
1692
        $this->assertGreaterThan($prevcacherevone, $cacherevone);
1693
        $this->assertEmpty($cache->get_versioned($courseone->id, $prevcacherevone));
1694
        $prevcacherevone = $cacherevone;
1695
 
1696
        rebuild_course_cache($coursetwo->id, true);
1697
        $cacherevtwo = $DB->get_field('course', 'cacherev', ['id' => $coursetwo->id]);
1698
        $this->assertGreaterThan($prevcacherevtwo, $cacherevtwo);
1699
        $this->assertEmpty($cache->get_versioned($coursetwo->id, $prevcacherevtwo));
1700
        $prevcacherevtwo = $cacherevtwo;
1701
 
1702
        rebuild_course_cache($coursethree->id, true);
1703
        $cacherevthree = $DB->get_field('course', 'cacherev', ['id' => $coursethree->id]);
1704
        $this->assertGreaterThan($prevcacherevthree, $cacherevthree);
1705
        $this->assertEmpty($cache->get_versioned($coursethree->id, $prevcacherevthree));
1706
        $prevcacherevthree = $cacherevthree;
1707
 
1708
        // Build course caches. Cacherev should not change but caches are now not empty. Make sure cacherev is the same everywhere.
1709
        $modinfoone = get_fast_modinfo($courseone->id);
1710
        $cacherevone = $DB->get_field('course', 'cacherev', ['id' => $courseone->id]);
1711
        $this->assertEquals($prevcacherevone, $cacherevone);
1712
        $cachedvalueone = $cache->get_versioned($courseone->id, $cacherevone);
1713
        $this->assertNotEmpty($cachedvalueone);
1714
        $this->assertEquals($cacherevone, $cachedvalueone->cacherev);
1715
        $this->assertEquals($cacherevone, $modinfoone->get_course()->cacherev);
1716
        $prevcacherevone = $cacherevone;
1717
 
1718
        $modinfotwo = get_fast_modinfo($coursetwo->id);
1719
        $cacherevtwo = $DB->get_field('course', 'cacherev', ['id' => $coursetwo->id]);
1720
        $this->assertEquals($prevcacherevtwo, $cacherevtwo);
1721
        $cachedvaluetwo = $cache->get_versioned($coursetwo->id, $cacherevtwo);
1722
        $this->assertNotEmpty($cachedvaluetwo);
1723
        $this->assertEquals($cacherevtwo, $cachedvaluetwo->cacherev);
1724
        $this->assertEquals($cacherevtwo, $modinfotwo->get_course()->cacherev);
1725
        $prevcacherevtwo = $cacherevtwo;
1726
 
1727
        $modinfothree = get_fast_modinfo($coursethree->id);
1728
        $cacherevthree = $DB->get_field('course', 'cacherev', ['id' => $coursethree->id]);
1729
        $this->assertEquals($prevcacherevthree, $cacherevthree);
1730
        $cachedvaluethree = $cache->get_versioned($coursethree->id, $cacherevthree);
1731
        $this->assertNotEmpty($cachedvaluethree);
1732
        $this->assertEquals($cacherevthree, $cachedvaluethree->cacherev);
1733
        $this->assertEquals($cacherevthree, $modinfothree->get_course()->cacherev);
1734
        $prevcacherevthree = $cacherevthree;
1735
 
1736
        // Purge course one's cache. Cacherev must be incremented (but only for
1737
        // course one, check course two and three in next step).
1738
        course_modinfo::purge_course_caches([$courseone->id]);
1739
 
1740
        get_fast_modinfo($courseone->id);
1741
        $cacherevone = $DB->get_field('course', 'cacherev', ['id' => $courseone->id]);
1742
        $this->assertGreaterThan($prevcacherevone, $cacherevone);
1743
        $prevcacherevone = $cacherevone;
1744
 
1745
        // Confirm course two and three's cache shouldn't have been affected.
1746
        get_fast_modinfo($coursetwo->id);
1747
        $cacherevtwo = $DB->get_field('course', 'cacherev', ['id' => $coursetwo->id]);
1748
        $this->assertEquals($prevcacherevtwo, $cacherevtwo);
1749
        $prevcacherevtwo = $cacherevtwo;
1750
 
1751
        get_fast_modinfo($coursethree->id);
1752
        $cacherevthree = $DB->get_field('course', 'cacherev', ['id' => $coursethree->id]);
1753
        $this->assertEquals($prevcacherevthree, $cacherevthree);
1754
        $prevcacherevthree = $cacherevthree;
1755
 
1756
        // Purge course two and three's cache. Cacherev must be incremented (but only for
1757
        // course two and three, then check course one hasn't changed in next step).
1758
        course_modinfo::purge_course_caches([$coursetwo->id, $coursethree->id]);
1759
 
1760
        get_fast_modinfo($coursetwo->id);
1761
        $cacherevtwo = $DB->get_field('course', 'cacherev', ['id' => $coursetwo->id]);
1762
        $this->assertGreaterThan($prevcacherevtwo, $cacherevtwo);
1763
        $prevcacherevtwo = $cacherevtwo;
1764
 
1765
        get_fast_modinfo($coursethree->id);
1766
        $cacherevthree = $DB->get_field('course', 'cacherev', ['id' => $coursethree->id]);
1767
        $this->assertGreaterThan($prevcacherevthree, $cacherevthree);
1768
        $prevcacherevthree = $cacherevthree;
1769
 
1770
        // Confirm course one's cache shouldn't have been affected.
1771
        get_fast_modinfo($courseone->id);
1772
        $cacherevone = $DB->get_field('course', 'cacherev', ['id' => $courseone->id]);
1773
        $this->assertEquals($prevcacherevone, $cacherevone);
1774
        $prevcacherevone = $cacherevone;
1775
 
1776
        // Purge all course caches. Cacherev must be incremented for all three courses.
1777
        course_modinfo::purge_course_caches();
1778
        get_fast_modinfo($courseone->id);
1779
        $cacherevone = $DB->get_field('course', 'cacherev', ['id' => $courseone->id]);
1780
        $this->assertGreaterThan($prevcacherevone, $cacherevone);
1781
 
1782
        get_fast_modinfo($coursetwo->id);
1783
        $cacherevtwo = $DB->get_field('course', 'cacherev', ['id' => $coursetwo->id]);
1784
        $this->assertGreaterThan($prevcacherevtwo, $cacherevtwo);
1785
 
1786
        get_fast_modinfo($coursethree->id);
1787
        $cacherevthree = $DB->get_field('course', 'cacherev', ['id' => $coursethree->id]);
1788
        $this->assertGreaterThan($prevcacherevthree, $cacherevthree);
1789
    }
1441 ariadna 1790
 
1791
    /**
1792
     * Test get_sections_delegated_by_cm method
1793
     *
1794
     * @covers \course_modinfo::get_sections_delegated_by_cm
1795
     */
1796
    public function test_get_sections_delegated_by_cm(): void {
1797
        $this->resetAfterTest();
1798
 
1799
        $course = $this->getDataGenerator()->create_course(['numsections' => 1]);
1800
 
1801
        $modinfo = get_fast_modinfo($course);
1802
        $delegatedsections = $modinfo->get_sections_delegated_by_cm();
1803
        $this->assertEmpty($delegatedsections);
1804
 
1805
        // Add a section delegated by a course module.
1806
        $subsection = $this->getDataGenerator()->create_module('subsection', ['course' => $course]);
1807
        $modinfo = get_fast_modinfo($course);
1808
        $delegatedsections = $modinfo->get_sections_delegated_by_cm();
1809
        $this->assertCount(1, $delegatedsections);
1810
        $this->assertArrayHasKey($subsection->cmid, $delegatedsections);
1811
 
1812
        // Add a section delegated by a block.
1813
        formatactions::section($course)->create_delegated('block_site_main_menu', 1);
1814
        $modinfo = get_fast_modinfo($course);
1815
        $delegatedsections = $modinfo->get_sections_delegated_by_cm();
1816
        // Sections delegated by a block shouldn't be returned.
1817
        $this->assertCount(1, $delegatedsections);
1818
    }
1819
 
1820
    /**
1821
     * Test get_sections_delegated_by_cm method
1822
     *
1823
     * @covers \cm_info::get_delegated_section_info
1824
     */
1825
    public function test_get_delegated_section_info(): void {
1826
        $this->resetAfterTest();
1827
 
1828
        $course = $this->getDataGenerator()->create_course(['numsections' => 1]);
1829
 
1830
        // Add a section delegated by a course module.
1831
        $subsection = $this->getDataGenerator()->create_module('subsection', ['course' => $course]);
1832
        $otheractivity = $this->getDataGenerator()->create_module('page', ['course' => $course]);
1833
 
1834
        $modinfo = get_fast_modinfo($course);
1835
        $delegatedsections = $modinfo->get_sections_delegated_by_cm();
1836
 
1837
        $delegated = $modinfo->get_cm($subsection->cmid)->get_delegated_section_info();
1838
        $this->assertNotNull($delegated);
1839
        $this->assertEquals($delegated, $delegatedsections[$subsection->cmid]);
1840
 
1841
        $delegated = $modinfo->get_cm($otheractivity->cmid)->get_delegated_section_info();
1842
        $this->assertNull($delegated);
1843
    }
1844
 
1845
    /**
1846
     * Test get_uservisible method when the section is delegated.
1847
     *
1848
     * @covers \section_info::get_uservisible
1849
     * @dataProvider data_provider_get_uservisible_delegate
1850
     * @param string $role The role to assign to the user.
1851
     * @param bool $parentvisible The visibility of the parent section.
1852
     * @param bool $delegatedvisible The visibility of the delegated section.
1853
     * @param bool $expected The expected visibility of the delegated section.
1854
     * @return void
1855
     */
1856
    public function test_get_uservisible_delegate(
1857
        string $role,
1858
        bool $parentvisible,
1859
        bool $delegatedvisible,
1860
        bool $expected,
1861
    ): void {
1862
        $this->resetAfterTest();
1863
 
1864
        $course = $this->getDataGenerator()->create_course(['numsections' => 1]);
1865
        $subsection = $this->getDataGenerator()->create_module('subsection', ['course' => $course], ['section' => 1]);
1866
 
1867
        $student = $this->getDataGenerator()->create_and_enrol($course, $role);
1868
 
1869
        $modinfo = get_fast_modinfo($course);
1870
 
1871
        formatactions::section($course)->update(
1872
            $modinfo->get_section_info(1),
1873
            ['visible' => $parentvisible]
1874
        );
1875
 
1876
        formatactions::cm($course)->set_visibility(
1877
            $subsection->cmid,
1878
            $delegatedvisible,
1879
        );
1880
 
1881
        $this->setUser($student);
1882
        $modinfo = get_fast_modinfo($course);
1883
 
1884
        $delegatedsection = $modinfo->get_cm($subsection->cmid)->get_delegated_section_info();
1885
 
1886
        // The get_uservisible is a magic getter.
1887
        $this->assertEquals($expected, $delegatedsection->uservisible);
1888
    }
1889
 
1890
    /**
1891
     * Data provider for test_get_uservisible_delegate.
1892
     *
1893
     * @return array
1894
     */
1895
    public static function data_provider_get_uservisible_delegate(): array {
1896
        return [
1897
            'Student on a visible subsection inside a visible parent' => [
1898
                'role' => 'student',
1899
                'parentvisible' => true,
1900
                'delegatedvisible' => true,
1901
                'expected' => true,
1902
            ],
1903
            'Student on a hidden subsection inside a visible parent' => [
1904
                'role' => 'student',
1905
                'parentvisible' => true,
1906
                'delegatedvisible' => false,
1907
                'expected' => false,
1908
            ],
1909
            'Student on a visible subsection inside a hidden parent' => [
1910
                'role' => 'student',
1911
                'parentvisible' => false,
1912
                'delegatedvisible' => true,
1913
                'expected' => false,
1914
            ],
1915
            'Student on a hidden subsection inside a hidden parent' => [
1916
                'role' => 'student',
1917
                'parentvisible' => false,
1918
                'delegatedvisible' => false,
1919
                'expected' => false,
1920
            ],
1921
            'Teacher on a visible subsection inside a visible parent' => [
1922
                'role' => 'editingteacher',
1923
                'parentvisible' => true,
1924
                'delegatedvisible' => true,
1925
                'expected' => true,
1926
            ],
1927
            'Teacher on a hidden subsection inside a visible parent' => [
1928
                'role' => 'editingteacher',
1929
                'parentvisible' => true,
1930
                'delegatedvisible' => false,
1931
                'expected' => true,
1932
            ],
1933
            'Teacher on a visible subsection inside a hidden parent' => [
1934
                'role' => 'editingteacher',
1935
                'parentvisible' => false,
1936
                'delegatedvisible' => true,
1937
                'expected' => true,
1938
            ],
1939
            'Teacher on a hidden subsection inside a hidden parent' => [
1940
                'role' => 'editingteacher',
1941
                'parentvisible' => false,
1942
                'delegatedvisible' => false,
1943
                'expected' => true,
1944
            ],
1945
        ];
1946
    }
1947
 
1948
    /**
1949
     * Test get_uservisible method when the section is delegated and depending on if the plugin is enabled.
1950
     *
1951
     * @covers \section_info::get_uservisible
1952
     * @dataProvider provider_test_get_uservisible_delegate_enabled
1953
     * @param string $role The role to assign to the user.
1954
     * @param bool $enabled Whether the plugin is enabled.
1955
     * @param bool $expected The expected visibility of the delegated section.
1956
     * @return void
1957
     */
1958
    public function test_get_uservisible_delegate_enabled(
1959
        string $role,
1960
        bool $enabled,
1961
        bool $expected,
1962
    ): void {
1963
        $this->resetAfterTest();
1964
 
1965
        $course = $this->getDataGenerator()->create_course(['numsections' => 1]);
1966
        $subsection = $this->getDataGenerator()->create_module('subsection', ['course' => $course], ['section' => 1]);
1967
 
1968
        $modinfo = get_fast_modinfo($course);
1969
        $delegatedsection = $modinfo->get_cm($subsection->cmid)->get_delegated_section_info();
1970
 
1971
        $user = $this->getDataGenerator()->create_and_enrol($course, $role);
1972
 
1973
        if (!$enabled) {
1974
            $manager = \core_plugin_manager::resolve_plugininfo_class('mod');
1975
            $manager::enable_plugin('subsection', 0);
1976
            rebuild_course_cache($course->id, true);
1977
        }
1978
 
1979
        $this->setUser($user);
1980
        $modinfo = get_fast_modinfo($course);
1981
 
1982
        $delegatedsection = $modinfo->get_section_info($delegatedsection->section);
1983
 
1984
        // The get_uservisible is a magic getter.
1985
        $this->assertEquals($expected, $delegatedsection->uservisible);
1986
    }
1987
 
1988
    /**
1989
     * Data provider for test_get_uservisible_delegate_enabled.
1990
     *
1991
     * @return array
1992
     */
1993
    public static function provider_test_get_uservisible_delegate_enabled(): array {
1994
        return [
1995
            'Student with plugin enabled' => [
1996
                'role' => 'student',
1997
                'enabled' => true,
1998
                'expected' => true,
1999
            ],
2000
            'Student with plugin disabled' => [
2001
                'role' => 'student',
2002
                'enabled' => false,
2003
                'expected' => false,
2004
            ],
2005
            'Teacher with plugin enabled' => [
2006
                'role' => 'editingteacher',
2007
                'enabled' => true,
2008
                'expected' => true,
2009
            ],
2010
            'Teacher with plugin disabled' => [
2011
                'role' => 'editingteacher',
2012
                'enabled' => false,
2013
                'expected' => true,
2014
            ],
2015
        ];
2016
    }
2017
 
2018
    /**
2019
     * Test get_available method when the section is delegated.
2020
     *
2021
     * @covers \section_info::get_available
2022
     * @covers \section_info::get_uservisible
2023
     * @dataProvider data_provider_get_available_delegated
2024
     * @param string $role The role to assign to the user.
2025
     * @param bool $parentavailable The parent section is available.
2026
     * @param bool $delegatedavailable The delegated section is available..
2027
     * @param bool $expectedavailable The expected availability of the delegated section.
2028
     * @param bool $expecteduservisible The expected uservisibility of the delegated section.
2029
     * @return void
2030
     */
2031
    public function test_get_available_delegated(
2032
        string $role,
2033
        bool $parentavailable,
2034
        bool $delegatedavailable,
2035
        bool $expectedavailable,
2036
        bool $expecteduservisible,
2037
    ): void {
2038
        $this->resetAfterTest();
2039
 
2040
        // The element will be available tomorrow.
2041
        $availability = json_encode(
2042
            (object) [
2043
                'op' => '&',
2044
                'showc' => [true],
2045
                'c' => [
2046
                    [
2047
                        'type' => 'date',
2048
                        'd' => '>=',
2049
                        't' => time() + DAYSECS,
2050
                    ],
2051
                ],
2052
            ]
2053
        );
2054
 
2055
        $course = $this->getDataGenerator()->create_course(['numsections' => 1]);
2056
 
2057
        $cmparams = ['section' => 1];
2058
        if (!$delegatedavailable) {
2059
            $cmparams['availability'] = $availability;
2060
        }
2061
 
2062
        $subsection = $this->getDataGenerator()->create_module(
2063
            'subsection',
2064
            ['course' => $course],
2065
            $cmparams
2066
        );
2067
 
2068
        $student = $this->getDataGenerator()->create_and_enrol($course, $role);
2069
 
2070
        $modinfo = get_fast_modinfo($course);
2071
 
2072
        if (!$parentavailable) {
2073
            formatactions::section($course)->update(
2074
                $modinfo->get_section_info(1),
2075
                ['availability' => $availability]
2076
            );
2077
        }
2078
 
2079
        $this->setUser($student);
2080
        $modinfo = get_fast_modinfo($course);
2081
 
2082
        $delegatedsection = $modinfo->get_cm($subsection->cmid)->get_delegated_section_info();
2083
 
2084
        // All section_info getters are magic methods.
2085
        $this->assertEquals($expectedavailable, $delegatedsection->available);
2086
        $this->assertEquals($expecteduservisible, $delegatedsection->uservisible);
2087
    }
2088
 
2089
    /**
2090
     * Data provider for test_get_available_delegated.
2091
     *
2092
     * @return array
2093
     */
2094
    public static function data_provider_get_available_delegated(): array {
2095
        return [
2096
            'Student on an available subsection inside an available parent' => [
2097
                'role' => 'student',
2098
                'parentavailable' => true,
2099
                'delegatedavailable' => true,
2100
                'expectedavailable' => true,
2101
                'expecteduservisible' => true,
2102
            ],
2103
            'Student on an unavailable subsection inside an available parent' => [
2104
                'role' => 'student',
2105
                'parentavailable' => true,
2106
                'delegatedavailable' => false,
2107
                'expectedavailable' => false,
2108
                'expecteduservisible' => false,
2109
            ],
2110
            'Student on an available subsection inside an unavailable parent' => [
2111
                'role' => 'student',
2112
                'parentavailable' => false,
2113
                'delegatedavailable' => true,
2114
                'expectedavailable' => false,
2115
                'expecteduservisible' => false,
2116
            ],
2117
            'Student on an unavailable subsection inside an unavailable parent' => [
2118
                'role' => 'student',
2119
                'parentavailable' => false,
2120
                'delegatedavailable' => false,
2121
                'expectedavailable' => false,
2122
                'expecteduservisible' => false,
2123
            ],
2124
            'Teacher on an available subsection inside an available parent' => [
2125
                'role' => 'editingteacher',
2126
                'parentavailable' => true,
2127
                'delegatedavailable' => true,
2128
                'expectedavailable' => true,
2129
                'expecteduservisible' => true,
2130
            ],
2131
            'Teacher on an unavailable subsection inside an available parent' => [
2132
                'role' => 'editingteacher',
2133
                'parentavailable' => true,
2134
                'delegatedavailable' => false,
2135
                'expectedavailable' => false,
2136
                'expecteduservisible' => true,
2137
            ],
2138
            'Teacher on an available subsection inside an unavailable parent' => [
2139
                'role' => 'editingteacher',
2140
                'parentavailable' => false,
2141
                'delegatedavailable' => true,
2142
                'expectedavailable' => false,
2143
                'expecteduservisible' => true,
2144
            ],
2145
            'Teacher on an unavailable subsection inside an unavailable parent' => [
2146
                'role' => 'editingteacher',
2147
                'parentavailable' => false,
2148
                'delegatedavailable' => false,
2149
                'expectedavailable' => false,
2150
                'expecteduservisible' => true,
2151
            ],
2152
        ];
2153
    }
2154
 
2155
    /**
2156
     * Test when a section is considered orphan.
2157
     *
2158
     * @covers \section_info::is_orphan
2159
     * @return void
2160
     */
2161
    public function test_is_orphan(): void {
2162
 
2163
        $this->resetAfterTest();
2164
 
2165
        $course = $this->getDataGenerator()->create_course(['numsections' => 1]);
2166
        $subsection = $this->getDataGenerator()->create_module('subsection', ['course' => $course], ['section' => 1]);
2167
 
2168
        $modinfo = get_fast_modinfo($course);
2169
        $delegatedsection = $modinfo->get_cm($subsection->cmid)->get_delegated_section_info();
2170
 
2171
        // If mod_subsection is enabled, a subsection is not orphan.
2172
        $modinfo = get_fast_modinfo($course);
2173
        $this->assertFalse($delegatedsection->is_orphan());
2174
 
2175
        // Delegated sections without a component instance (disabled mod_subsection) is considered orphan.
2176
        $manager = \core_plugin_manager::resolve_plugininfo_class('mod');
2177
        $manager::enable_plugin('subsection', 0);
2178
        rebuild_course_cache($course->id, true);
2179
 
2180
        $modinfo = get_fast_modinfo($course);
2181
        $delegatedsection = $modinfo->get_section_info($delegatedsection->section);
2182
        $this->assertTrue($delegatedsection->is_orphan());
2183
 
2184
        // Check enabling the plugin restore the previous state.
2185
        $manager::enable_plugin('subsection', 1);
2186
        rebuild_course_cache($course->id, true);
2187
 
2188
        $modinfo = get_fast_modinfo($course);
2189
        $delegatedsection = $modinfo->get_section_info($delegatedsection->section);
2190
        $this->assertFalse($delegatedsection->is_orphan());
2191
 
2192
        // Force section limit in the course format instance.
2193
        rebuild_course_cache($course->id, true);
2194
        $modinfo = get_fast_modinfo($course);
2195
 
2196
        // Core formats does not use numsections anymore. We need to use reflection to change the value.
2197
        $format = course_get_format($course);
2198
        // Add a fake numsections format data (Force loading format data first).
2199
        $format->get_course();
2200
        $reflection = new \ReflectionObject($format);
2201
        $property = $reflection->getProperty('course');
2202
        $courseobject = $property->getValue($format);
2203
        $courseobject->numsections = 1;
2204
        $property->setValue($format, $courseobject);
2205
 
2206
        $delegatedsection = $modinfo->get_section_info($delegatedsection->section);
2207
        $this->assertTrue($delegatedsection->is_orphan());
2208
    }
2209
 
2210
    /**
2211
     * Test for section_info::get_sequence_cm_infos
2212
     *
2213
     * @covers \section_info::get_sequence_cm_infos
2214
     * @return void
2215
     */
2216
    public function test_section_get_sequence_cm_infos(): void {
2217
        $this->resetAfterTest();
2218
 
2219
        $course = $this->getDataGenerator()->create_course(['numsections' => 2]);
2220
        $cm1 = $this->getDataGenerator()->create_module('page', ['course' => $course], ['section' => 0]);
2221
        $cm2 = $this->getDataGenerator()->create_module('page', ['course' => $course], ['section' => 1]);
2222
        $cm3 = $this->getDataGenerator()->create_module('page', ['course' => $course], ['section' => 1]);
2223
        $cm4 = $this->getDataGenerator()->create_module('page', ['course' => $course], ['section' => 1]);
2224
 
2225
        $modinfo = get_fast_modinfo($course->id);
2226
 
2227
        $sectioninfo = $modinfo->get_section_info(0);
2228
        $cms = $sectioninfo->get_sequence_cm_infos();
2229
        $this->assertCount(1, $cms);
2230
        $this->assertEquals($cm1->cmid, $cms[0]->id);
2231
 
2232
        $sectioninfo = $modinfo->get_section_info(1);
2233
        $cms = $sectioninfo->get_sequence_cm_infos();
2234
        $this->assertCount(3, $cms);
2235
        $this->assertEquals($cm2->cmid, $cms[0]->id);
2236
        $this->assertEquals($cm3->cmid, $cms[1]->id);
2237
        $this->assertEquals($cm4->cmid, $cms[2]->id);
2238
 
2239
        $sectioninfo = $modinfo->get_section_info(2);
2240
        $cms = $sectioninfo->get_sequence_cm_infos();
2241
        $this->assertCount(0, $cms);
2242
    }
2243
 
2244
    /**
2245
     * Test for cm_info::get_instance_record
2246
     *
2247
     * @covers \cm_info::get_instance_record
2248
     * @return void
2249
     */
2250
    public function test_section_get_instance_record(): void {
2251
        global $DB;
2252
 
2253
        $this->resetAfterTest();
2254
 
2255
        $course = $this->getDataGenerator()->create_course(['numsections' => 2]);
2256
        $activity = $this->getDataGenerator()->create_module('page', ['course' => $course], ['section' => 0]);
2257
 
2258
        $modinfo = get_fast_modinfo($course->id);
2259
        $cminfo = $modinfo->get_cm($activity->cmid);
2260
 
2261
        $instancerecord = $DB->get_record('page', ['id' => $activity->id]);
2262
 
2263
        $instance = $cminfo->get_instance_record();
2264
        $this->assertEquals($instancerecord, $instance);
2265
 
2266
        // The instance record should be cached.
2267
        $DB->delete_records('page', ['id' => $activity->id]);
2268
 
2269
        $instance2 = $cminfo->get_instance_record();
2270
        $this->assertEquals($instancerecord, $instance);
2271
        $this->assertEquals($instance, $instance2);
2272
    }
2273
 
2274
    /**
2275
     * Test for sort_cm_array method.
2276
     */
2277
    public function test_sort_cm_array(): void {
2278
        $this->resetAfterTest();
2279
        $generator = $this->getDataGenerator();
2280
        // Create a course with 4 sections.
2281
        $course = $generator->create_course(['numsections' => 3]);
2282
        $generator->create_module('page', ['name' => 'Page s1', 'course' => $course->id, 'section' => 0]);
2283
        $generator->create_module('page', ['name' => 'Page s2', 'course' => $course->id, 'section' => 1]);
2284
        $generator->create_module('assign', ['name' => 'Assign s3', 'course' => $course->id, 'section' => 2]);
2285
        $generator->create_module('page', ['name' => 'Page s3', 'course' => $course->id, 'section' => 3]);
2286
        // Check we return all cms in order.
2287
        $cms = get_fast_modinfo($course)->get_instances_of('page');
2288
        get_fast_modinfo($course)->sort_cm_array($cms);
2289
        $this->assertCount(3, $cms);
2290
        $this->assertEquals(['Page s1', 'Page s2', 'Page s3'], array_column($cms, 'name'));
2291
 
2292
        // Generate some delegated sections (not listed).
2293
        $module = $this->getDataGenerator()->create_module('subsection', (object) ['course' => $course->id, 'section' => 1]);
2294
        $sub1 = get_fast_modinfo($course)->get_section_info_by_component('mod_subsection', $module->id);
2295
        $generator->create_module('page', ['name' => 'Page sub1', 'course' => $course->id, 'section' => $sub1->sectionnum]);
2296
        $generator->create_module('page', ['name' => 'Page sub2', 'course' => $course->id, 'section' => $sub1->sectionnum]);
2297
        $generator->create_module('assign', ['name' => 'Assign sub1', 'course' => $course->id, 'section' => $sub1->sectionnum]);
2298
 
2299
        $cms = get_fast_modinfo($course)->get_instances_of('page');
2300
        get_fast_modinfo($course)->sort_cm_array($cms);
2301
        $this->assertCount(5, $cms);
2302
        $this->assertEquals(['Page s1', 'Page s2', 'Page sub1', 'Page sub2', 'Page s3'], array_column($cms, 'name'));
2303
 
2304
        $cms = get_fast_modinfo($course)->get_instances_of('assign');
2305
        get_fast_modinfo($course)->sort_cm_array($cms);
2306
        $this->assertCount(2, $cms);
2307
        $this->assertEquals(['Assign sub1', 'Assign s3'], array_column($cms, 'name'));
2308
    }
2309
 
2310
    /**
2311
     * Test for get_instance_of method.
2312
     */
2313
    public function test_get_instance_of(): void {
2314
        $this->resetAfterTest();
2315
        $generator = $this->getDataGenerator();
2316
        // Create a course with 4 sections.
2317
        $course = $generator->create_course(['numsections' => 3]);
2318
        $generator->create_module('page', ['name' => 'Page s1', 'course' => $course->id, 'section' => 0]);
2319
        $generator->create_module('page', ['name' => 'Page s2', 'course' => $course->id, 'section' => 1]);
2320
        $generator->create_module('assign', ['name' => 'Assign s3', 'course' => $course->id, 'section' => 2]);
2321
 
2322
        $modinfo = get_fast_modinfo($course);
2323
        $pagecms = array_values($modinfo->get_instances_of('page'));
2324
        $assigncms = array_values($modinfo->get_instances_of('assign'));
2325
        $this->assertCount(2, $pagecms);
2326
        $this->assertCount(1, $assigncms);
2327
 
2328
        $this->assertEquals('Page s1', $modinfo->get_instance_of('page', $pagecms[0]->instance)->name);
2329
        $this->assertEquals('Page s2', $modinfo->get_instance_of('page', $pagecms[1]->instance)->name);
2330
        $this->assertEquals('Assign s3', $modinfo->get_instance_of('assign', $assigncms[0]->instance)->name);
2331
 
2332
        $this->assertNull($modinfo->get_instance_of('page', 99999));
2333
        $this->assertNull($modinfo->get_instance_of('assign', 99999));
2334
        $this->assertNull($modinfo->get_instance_of('nonexisting', 99999));
2335
    }
2336
 
2337
    /**
2338
     * Test for get_instance_of method when asking for a non existing module with MUST_EXIST.
2339
     */
2340
    public function test_get_instance_of_exception(): void {
2341
        $this->resetAfterTest();
2342
        $generator = $this->getDataGenerator();
2343
        // Create a course with 4 sections.
2344
        $course = $generator->create_course(['numsections' => 3]);
2345
        $generator->create_module('page', ['name' => 'Page s1', 'course' => $course->id, 'section' => 0]);
2346
        $generator->create_module('page', ['name' => 'Page s2', 'course' => $course->id, 'section' => 1]);
2347
        $generator->create_module('assign', ['name' => 'Assign s3', 'course' => $course->id, 'section' => 2]);
2348
 
2349
        $modinfo = get_fast_modinfo($course);
2350
 
2351
        $this->expectException(moodle_exception::class);
2352
        $this->expectExceptionMessage('Invalid module ID: 99999');
2353
        $modinfo->get_instance_of('page', 99999, MUST_EXIST);
2354
    }
1 efrain 2355
}