MDL-67271 core: Add test to find missing SVG icons
[moodle.git] / lib / tests / modinfolib_test.php
bloba0c20cc5c567a3152dcfe408ee23994dc01d32df
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.
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/>.
17 namespace core;
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 moodle_exception;
27 use moodle_url;
28 use Exception;
30 /**
31 * Unit tests for lib/modinfolib.php.
33 * @package core
34 * @category phpunit
35 * @copyright 2012 Andrew Davis
36 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
38 class modinfolib_test extends advanced_testcase {
39 public function test_section_info_properties() {
40 global $DB, $CFG;
42 $this->resetAfterTest();
43 $oldcfgenableavailability = $CFG->enableavailability;
44 $oldcfgenablecompletion = $CFG->enablecompletion;
45 set_config('enableavailability', 1);
46 set_config('enablecompletion', 1);
47 $this->setAdminUser();
49 // Generate the course and pre-requisite module.
50 $course = $this->getDataGenerator()->create_course(
51 array('format' => 'topics',
52 'numsections' => 3,
53 'enablecompletion' => 1,
54 'groupmode' => SEPARATEGROUPS,
55 'forcegroupmode' => 0),
56 array('createsections' => true));
57 $coursecontext = context_course::instance($course->id);
58 $prereqforum = $this->getDataGenerator()->create_module('forum',
59 array('course' => $course->id),
60 array('completion' => 1));
62 // Add availability conditions.
63 $availability = '{"op":"&","showc":[true,true,true],"c":[' .
64 '{"type":"completion","cm":' . $prereqforum->cmid . ',"e":"' .
65 COMPLETION_COMPLETE . '"},' .
66 '{"type":"grade","id":666,"min":0.4},' .
67 '{"type":"profile","op":"contains","sf":"email","v":"test"}' .
68 ']}';
69 $DB->set_field('course_sections', 'availability', $availability,
70 array('course' => $course->id, 'section' => 2));
71 rebuild_course_cache($course->id, true);
72 $sectiondb = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 2));
74 // Create and enrol a student.
75 $studentrole = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
76 $student = $this->getDataGenerator()->create_user();
77 role_assign($studentrole->id, $student->id, $coursecontext);
78 $enrolplugin = enrol_get_plugin('manual');
79 $enrolinstance = $DB->get_record('enrol', array('courseid' => $course->id, 'enrol' => 'manual'));
80 $enrolplugin->enrol_user($enrolinstance, $student->id);
81 $this->setUser($student);
83 // Get modinfo.
84 $modinfo = get_fast_modinfo($course->id);
85 $si = $modinfo->get_section_info(2);
87 $this->assertEquals($sectiondb->id, $si->id);
88 $this->assertEquals($sectiondb->course, $si->course);
89 $this->assertEquals($sectiondb->section, $si->section);
90 $this->assertEquals($sectiondb->name, $si->name);
91 $this->assertEquals($sectiondb->visible, $si->visible);
92 $this->assertEquals($sectiondb->summary, $si->summary);
93 $this->assertEquals($sectiondb->summaryformat, $si->summaryformat);
94 $this->assertEquals($sectiondb->sequence, $si->sequence); // Since this section does not contain invalid modules.
95 $this->assertEquals($availability, $si->availability);
97 // Dynamic fields, just test that they can be retrieved (must be carefully tested in each activity type).
98 $this->assertEquals(0, $si->available);
99 $this->assertNotEmpty($si->availableinfo); // Lists all unmet availability conditions.
100 $this->assertEquals(0, $si->uservisible);
102 // Restore settings.
103 set_config('enableavailability', $oldcfgenableavailability);
104 set_config('enablecompletion', $oldcfgenablecompletion);
107 public function test_cm_info_properties() {
108 global $DB, $CFG;
110 $this->resetAfterTest();
111 $oldcfgenableavailability = $CFG->enableavailability;
112 $oldcfgenablecompletion = $CFG->enablecompletion;
113 set_config('enableavailability', 1);
114 set_config('enablecompletion', 1);
115 $this->setAdminUser();
117 // Generate the course and pre-requisite module.
118 $course = $this->getDataGenerator()->create_course(
119 array('format' => 'topics',
120 'numsections' => 3,
121 'enablecompletion' => 1,
122 'groupmode' => SEPARATEGROUPS,
123 'forcegroupmode' => 0),
124 array('createsections' => true));
125 $coursecontext = context_course::instance($course->id);
126 $prereqforum = $this->getDataGenerator()->create_module('forum',
127 array('course' => $course->id),
128 array('completion' => 1));
130 // Generate module and add availability conditions.
131 $availability = '{"op":"&","showc":[true,true,true],"c":[' .
132 '{"type":"completion","cm":' . $prereqforum->cmid . ',"e":"' .
133 COMPLETION_COMPLETE . '"},' .
134 '{"type":"grade","id":666,"min":0.4},' .
135 '{"type":"profile","op":"contains","sf":"email","v":"test"}' .
136 ']}';
137 $assign = $this->getDataGenerator()->create_module('assign',
138 array('course' => $course->id),
139 array('idnumber' => 123,
140 'groupmode' => VISIBLEGROUPS,
141 'availability' => $availability));
142 rebuild_course_cache($course->id, true);
144 // Retrieve all related records from DB.
145 $assigndb = $DB->get_record('assign', array('id' => $assign->id));
146 $moduletypedb = $DB->get_record('modules', array('name' => 'assign'));
147 $moduledb = $DB->get_record('course_modules', array('module' => $moduletypedb->id, 'instance' => $assign->id));
148 $sectiondb = $DB->get_record('course_sections', array('id' => $moduledb->section));
149 $modnamessingular = get_module_types_names(false);
150 $modnamesplural = get_module_types_names(true);
152 // Create and enrol a student.
153 $studentrole = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
154 $student = $this->getDataGenerator()->create_user();
155 role_assign($studentrole->id, $student->id, $coursecontext);
156 $enrolplugin = enrol_get_plugin('manual');
157 $enrolinstance = $DB->get_record('enrol', array('courseid' => $course->id, 'enrol' => 'manual'));
158 $enrolplugin->enrol_user($enrolinstance, $student->id);
159 $this->setUser($student);
161 // Emulate data used in building course cache to receive the same instance of cached_cm_info as was used in building modinfo.
162 $rawmods = get_course_mods($course->id);
163 $cachedcminfo = assign_get_coursemodule_info($rawmods[$moduledb->id]);
165 // Get modinfo.
166 $modinfo = get_fast_modinfo($course->id);
167 $cm = $modinfo->instances['assign'][$assign->id];
169 $this->assertEquals($moduledb->id, $cm->id);
170 $this->assertEquals($assigndb->id, $cm->instance);
171 $this->assertEquals($moduledb->course, $cm->course);
172 $this->assertEquals($moduledb->idnumber, $cm->idnumber);
173 $this->assertEquals($moduledb->added, $cm->added);
174 $this->assertEquals($moduledb->visible, $cm->visible);
175 $this->assertEquals($moduledb->visibleold, $cm->visibleold);
176 $this->assertEquals($moduledb->groupmode, $cm->groupmode);
177 $this->assertEquals(VISIBLEGROUPS, $cm->groupmode);
178 $this->assertEquals($moduledb->groupingid, $cm->groupingid);
179 $this->assertEquals($course->groupmodeforce, $cm->coursegroupmodeforce);
180 $this->assertEquals($course->groupmode, $cm->coursegroupmode);
181 $this->assertEquals(SEPARATEGROUPS, $cm->coursegroupmode);
182 $this->assertEquals($course->groupmodeforce ? $course->groupmode : $moduledb->groupmode,
183 $cm->effectivegroupmode); // (since mod_assign supports groups).
184 $this->assertEquals(VISIBLEGROUPS, $cm->effectivegroupmode);
185 $this->assertEquals($moduledb->indent, $cm->indent);
186 $this->assertEquals($moduledb->completion, $cm->completion);
187 $this->assertEquals($moduledb->completiongradeitemnumber, $cm->completiongradeitemnumber);
188 $this->assertEquals($moduledb->completionpassgrade, $cm->completionpassgrade);
189 $this->assertEquals($moduledb->completionview, $cm->completionview);
190 $this->assertEquals($moduledb->completionexpected, $cm->completionexpected);
191 $this->assertEquals($moduledb->showdescription, $cm->showdescription);
192 $this->assertEquals(null, $cm->extra); // Deprecated field. Used in module types that don't return cached_cm_info.
193 $this->assertEquals($cachedcminfo->icon, $cm->icon);
194 $this->assertEquals($cachedcminfo->iconcomponent, $cm->iconcomponent);
195 $this->assertEquals('assign', $cm->modname);
196 $this->assertEquals($moduledb->module, $cm->module);
197 $this->assertEquals($cachedcminfo->name, $cm->name);
198 $this->assertEquals($sectiondb->section, $cm->sectionnum);
199 $this->assertEquals($moduledb->section, $cm->section);
200 $this->assertEquals($availability, $cm->availability);
201 $this->assertEquals(context_module::instance($moduledb->id), $cm->context);
202 $this->assertEquals($modnamessingular['assign'], $cm->modfullname);
203 $this->assertEquals($modnamesplural['assign'], $cm->modplural);
204 $this->assertEquals(new moodle_url('/mod/assign/view.php', array('id' => $moduledb->id)), $cm->url);
205 $this->assertEquals($cachedcminfo->customdata, $cm->customdata);
207 // Dynamic fields, just test that they can be retrieved (must be carefully tested in each activity type).
208 $this->assertNotEmpty($cm->availableinfo); // Lists all unmet availability conditions.
209 $this->assertEquals(0, $cm->uservisible);
210 $this->assertEquals('', $cm->extraclasses);
211 $this->assertEquals('', $cm->onclick);
212 $this->assertEquals(null, $cm->afterlink);
213 $this->assertEquals(null, $cm->afterediticons);
214 $this->assertEquals('', $cm->content);
216 // Attempt to access and set non-existing field.
217 $this->assertTrue(empty($modinfo->somefield));
218 $this->assertFalse(isset($modinfo->somefield));
219 $cm->somefield;
220 $this->assertDebuggingCalled();
221 $cm->somefield = 'Some value';
222 $this->assertDebuggingCalled();
223 $this->assertEmpty($cm->somefield);
224 $this->assertDebuggingCalled();
226 // Attempt to overwrite an existing field.
227 $prevvalue = $cm->name;
228 $this->assertNotEmpty($cm->name);
229 $this->assertFalse(empty($cm->name));
230 $this->assertTrue(isset($cm->name));
231 $cm->name = 'Illegal overwriting';
232 $this->assertDebuggingCalled();
233 $this->assertEquals($prevvalue, $cm->name);
234 $this->assertDebuggingNotCalled();
236 // Restore settings.
237 set_config('enableavailability', $oldcfgenableavailability);
238 set_config('enablecompletion', $oldcfgenablecompletion);
241 public function test_matching_cacherev() {
242 global $DB, $CFG;
244 $this->resetAfterTest();
245 $this->setAdminUser();
246 $cache = cache::make('core', 'coursemodinfo');
248 // Generate the course and pre-requisite module.
249 $course = $this->getDataGenerator()->create_course(
250 array('format' => 'topics',
251 'numsections' => 3),
252 array('createsections' => true));
254 // Make sure the cacherev is set.
255 $cacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));
256 $this->assertGreaterThan(0, $cacherev);
257 $prevcacherev = $cacherev;
259 // Reset course cache and make sure cacherev is bumped up but cache is empty.
260 rebuild_course_cache($course->id, true);
261 $cacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));
262 $this->assertGreaterThan($prevcacherev, $cacherev);
263 $this->assertEmpty($cache->get_versioned($course->id, $prevcacherev));
264 $prevcacherev = $cacherev;
266 // Build course cache. Cacherev should not change but cache is now not empty. Make sure cacherev is the same everywhere.
267 $modinfo = get_fast_modinfo($course->id);
268 $cacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));
269 $this->assertEquals($prevcacherev, $cacherev);
270 $cachedvalue = $cache->get_versioned($course->id, $cacherev);
271 $this->assertNotEmpty($cachedvalue);
272 $this->assertEquals($cacherev, $cachedvalue->cacherev);
273 $this->assertEquals($cacherev, $modinfo->get_course()->cacherev);
274 $prevcacherev = $cacherev;
276 // 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.
277 $cache->acquire_lock($course->id);
278 $cache->set_versioned($course->id, $cacherev, (object)array_merge((array)$cachedvalue, array('secretfield' => 1)));
279 $cache->release_lock($course->id);
281 // Clear static cache and call get_fast_modinfo() again (pretend we are in another request). Cache should not be rebuilt.
282 course_modinfo::clear_instance_cache();
283 $modinfo = get_fast_modinfo($course->id);
284 $cacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));
285 $this->assertEquals($prevcacherev, $cacherev);
286 $cachedvalue = $cache->get_versioned($course->id, $cacherev);
287 $this->assertNotEmpty($cachedvalue);
288 $this->assertEquals($cacherev, $cachedvalue->cacherev);
289 $this->assertNotEmpty($cachedvalue->secretfield);
290 $this->assertEquals($cacherev, $modinfo->get_course()->cacherev);
291 $prevcacherev = $cacherev;
293 // Rebuild course cache. Cacherev must be incremented everywhere.
294 rebuild_course_cache($course->id);
295 $cacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));
296 $this->assertGreaterThan($prevcacherev, $cacherev);
297 $cachedvalue = $cache->get_versioned($course->id, $cacherev);
298 $this->assertNotEmpty($cachedvalue);
299 $this->assertEquals($cacherev, $cachedvalue->cacherev);
300 $modinfo = get_fast_modinfo($course->id);
301 $this->assertEquals($cacherev, $modinfo->get_course()->cacherev);
302 $prevcacherev = $cacherev;
304 // Update cacherev in DB and make sure the cache will be rebuilt on the next call to get_fast_modinfo().
305 increment_revision_number('course', 'cacherev', 'id = ?', array($course->id));
306 // We need to clear static cache for course_modinfo instances too.
307 course_modinfo::clear_instance_cache();
308 $modinfo = get_fast_modinfo($course->id);
309 $cacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));
310 $this->assertGreaterThan($prevcacherev, $cacherev);
311 $cachedvalue = $cache->get_versioned($course->id, $cacherev);
312 $this->assertNotEmpty($cachedvalue);
313 $this->assertEquals($cacherev, $cachedvalue->cacherev);
314 $this->assertEquals($cacherev, $modinfo->get_course()->cacherev);
315 $prevcacherev = $cacherev;
317 // Reset cache for all courses and make sure this course cache is reset.
318 rebuild_course_cache(0, true);
319 $cacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));
320 $this->assertGreaterThan($prevcacherev, $cacherev);
321 $this->assertEmpty($cache->get_versioned($course->id, $cacherev));
322 // Rebuild again.
323 $modinfo = get_fast_modinfo($course->id);
324 $cachedvalue = $cache->get_versioned($course->id, $cacherev);
325 $this->assertNotEmpty($cachedvalue);
326 $this->assertEquals($cacherev, $cachedvalue->cacherev);
327 $this->assertEquals($cacherev, $modinfo->get_course()->cacherev);
328 $prevcacherev = $cacherev;
330 // Purge all caches and make sure cacherev is increased and data from MUC erased.
331 purge_all_caches();
332 $cacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));
333 $this->assertGreaterThan($prevcacherev, $cacherev);
334 $this->assertEmpty($cache->get($course->id));
337 public function test_course_modinfo_properties() {
338 global $USER, $DB;
340 $this->resetAfterTest();
341 $this->setAdminUser();
343 // Generate the course and some modules. Make one section hidden.
344 $course = $this->getDataGenerator()->create_course(
345 array('format' => 'topics',
346 'numsections' => 3),
347 array('createsections' => true));
348 $DB->execute('UPDATE {course_sections} SET visible = 0 WHERE course = ? and section = ?',
349 array($course->id, 3));
350 $coursecontext = context_course::instance($course->id);
351 $forum0 = $this->getDataGenerator()->create_module('forum',
352 array('course' => $course->id), array('section' => 0));
353 $assign0 = $this->getDataGenerator()->create_module('assign',
354 array('course' => $course->id), array('section' => 0, 'visible' => 0));
355 $forum1 = $this->getDataGenerator()->create_module('forum',
356 array('course' => $course->id), array('section' => 1));
357 $assign1 = $this->getDataGenerator()->create_module('assign',
358 array('course' => $course->id), array('section' => 1));
359 $page1 = $this->getDataGenerator()->create_module('page',
360 array('course' => $course->id), array('section' => 1));
361 $page3 = $this->getDataGenerator()->create_module('page',
362 array('course' => $course->id), array('section' => 3));
364 $modinfo = get_fast_modinfo($course->id);
366 $this->assertEquals(array($forum0->cmid, $assign0->cmid, $forum1->cmid, $assign1->cmid, $page1->cmid, $page3->cmid),
367 array_keys($modinfo->cms));
368 $this->assertEquals($course->id, $modinfo->courseid);
369 $this->assertEquals($USER->id, $modinfo->userid);
370 $this->assertEquals(array(0 => array($forum0->cmid, $assign0->cmid),
371 1 => array($forum1->cmid, $assign1->cmid, $page1->cmid), 3 => array($page3->cmid)), $modinfo->sections);
372 $this->assertEquals(array('forum', 'assign', 'page'), array_keys($modinfo->instances));
373 $this->assertEquals(array($assign0->id, $assign1->id), array_keys($modinfo->instances['assign']));
374 $this->assertEquals(array($forum0->id, $forum1->id), array_keys($modinfo->instances['forum']));
375 $this->assertEquals(array($page1->id, $page3->id), array_keys($modinfo->instances['page']));
376 $this->assertEquals(groups_get_user_groups($course->id), $modinfo->groups);
377 $this->assertEquals(array(0 => array($forum0->cmid, $assign0->cmid),
378 1 => array($forum1->cmid, $assign1->cmid, $page1->cmid),
379 3 => array($page3->cmid)), $modinfo->get_sections());
380 $this->assertEquals(array(0, 1, 2, 3), array_keys($modinfo->get_section_info_all()));
381 $this->assertEquals($forum0->cmid . ',' . $assign0->cmid, $modinfo->get_section_info(0)->sequence);
382 $this->assertEquals($forum1->cmid . ',' . $assign1->cmid . ',' . $page1->cmid, $modinfo->get_section_info(1)->sequence);
383 $this->assertEquals('', $modinfo->get_section_info(2)->sequence);
384 $this->assertEquals($page3->cmid, $modinfo->get_section_info(3)->sequence);
385 $this->assertEquals($course->id, $modinfo->get_course()->id);
386 $names = array_keys($modinfo->get_used_module_names());
387 sort($names);
388 $this->assertEquals(array('assign', 'forum', 'page'), $names);
389 $names = array_keys($modinfo->get_used_module_names(true));
390 sort($names);
391 $this->assertEquals(array('assign', 'forum', 'page'), $names);
392 // Admin can see hidden modules/sections.
393 $this->assertTrue($modinfo->cms[$assign0->cmid]->uservisible);
394 $this->assertTrue($modinfo->get_section_info(3)->uservisible);
396 // Get modinfo for non-current user (without capability to view hidden activities/sections).
397 $user = $this->getDataGenerator()->create_user();
398 $modinfo = get_fast_modinfo($course->id, $user->id);
399 $this->assertEquals($user->id, $modinfo->userid);
400 $this->assertFalse($modinfo->cms[$assign0->cmid]->uservisible);
401 $this->assertFalse($modinfo->get_section_info(3)->uservisible);
403 // Attempt to access and set non-existing field.
404 $this->assertTrue(empty($modinfo->somefield));
405 $this->assertFalse(isset($modinfo->somefield));
406 $modinfo->somefield;
407 $this->assertDebuggingCalled();
408 $modinfo->somefield = 'Some value';
409 $this->assertDebuggingCalled();
410 $this->assertEmpty($modinfo->somefield);
411 $this->assertDebuggingCalled();
413 // Attempt to overwrite existing field.
414 $this->assertFalse(empty($modinfo->cms));
415 $this->assertTrue(isset($modinfo->cms));
416 $modinfo->cms = 'Illegal overwriting';
417 $this->assertDebuggingCalled();
418 $this->assertNotEquals('Illegal overwriting', $modinfo->cms);
421 public function test_is_user_access_restricted_by_capability() {
422 global $DB;
424 $this->resetAfterTest();
426 // Create a course and a mod_assign instance.
427 $course = $this->getDataGenerator()->create_course();
428 $assign = $this->getDataGenerator()->create_module('assign', array('course'=>$course->id));
430 // Create and enrol a student.
431 $coursecontext = context_course::instance($course->id);
432 $studentrole = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
433 $student = $this->getDataGenerator()->create_user();
434 role_assign($studentrole->id, $student->id, $coursecontext);
435 $enrolplugin = enrol_get_plugin('manual');
436 $enrolinstance = $DB->get_record('enrol', array('courseid' => $course->id, 'enrol' => 'manual'));
437 $enrolplugin->enrol_user($enrolinstance, $student->id);
438 $this->setUser($student);
440 // Make sure student can see the module.
441 $cm = get_fast_modinfo($course->id)->instances['assign'][$assign->id];
442 $this->assertTrue($cm->uservisible);
443 $this->assertFalse($cm->is_user_access_restricted_by_capability());
445 // Prohibit student to view mod_assign for the course.
446 role_change_permission($studentrole->id, $coursecontext, 'mod/assign:view', CAP_PROHIBIT);
447 get_fast_modinfo($course->id, 0, true);
448 $cm = get_fast_modinfo($course->id)->instances['assign'][$assign->id];
449 $this->assertFalse($cm->uservisible);
450 $this->assertTrue($cm->is_user_access_restricted_by_capability());
452 // Restore permission to student to view mod_assign for the course.
453 role_change_permission($studentrole->id, $coursecontext, 'mod/assign:view', CAP_INHERIT);
454 get_fast_modinfo($course->id, 0, true);
455 $cm = get_fast_modinfo($course->id)->instances['assign'][$assign->id];
456 $this->assertTrue($cm->uservisible);
457 $this->assertFalse($cm->is_user_access_restricted_by_capability());
459 // Prohibit student to view mod_assign for the particular module.
460 role_change_permission($studentrole->id, context_module::instance($cm->id), 'mod/assign:view', CAP_PROHIBIT);
461 get_fast_modinfo($course->id, 0, true);
462 $cm = get_fast_modinfo($course->id)->instances['assign'][$assign->id];
463 $this->assertFalse($cm->uservisible);
464 $this->assertTrue($cm->is_user_access_restricted_by_capability());
466 // Check calling get_fast_modinfo() for different user:
467 $this->setAdminUser();
468 $cm = get_fast_modinfo($course->id)->instances['assign'][$assign->id];
469 $this->assertTrue($cm->uservisible);
470 $this->assertFalse($cm->is_user_access_restricted_by_capability());
471 $cm = get_fast_modinfo($course->id, $student->id)->instances['assign'][$assign->id];
472 $this->assertFalse($cm->uservisible);
473 $this->assertTrue($cm->is_user_access_restricted_by_capability());
477 * Tests for function cm_info::get_course_module_record()
479 public function test_cm_info_get_course_module_record() {
480 global $DB;
482 $this->resetAfterTest();
483 $this->setAdminUser();
485 set_config('enableavailability', 1);
486 set_config('enablecompletion', 1);
488 $course = $this->getDataGenerator()->create_course(
489 array('format' => 'topics', 'numsections' => 3, 'enablecompletion' => 1),
490 array('createsections' => true));
491 $mods = array();
492 $mods[0] = $this->getDataGenerator()->create_module('forum', array('course' => $course->id));
493 $mods[1] = $this->getDataGenerator()->create_module('assign',
494 array('course' => $course->id,
495 'section' => 3,
496 'idnumber' => '12345',
497 'showdescription' => true
499 // Pick a small valid availability value to use.
500 $availabilityvalue = '{"op":"|","show":true,"c":[{"type":"date","d":">=","t":4}]}';
501 $mods[2] = $this->getDataGenerator()->create_module('book',
502 array('course' => $course->id,
503 'indent' => 5,
504 'availability' => $availabilityvalue,
505 'showdescription' => false,
506 'completion' => true,
507 'completionview' => true,
508 'completionexpected' => time() + 5000,
510 $mods[3] = $this->getDataGenerator()->create_module('forum',
511 array('course' => $course->id,
512 'visible' => 0,
513 'groupmode' => 1,
514 'availability' => null));
515 $mods[4] = $this->getDataGenerator()->create_module('forum',
516 array('course' => $course->id,
517 'grouping' => 12));
519 $modinfo = get_fast_modinfo($course->id);
521 // Make sure that object returned by get_course_module_record(false) has exactly the same fields as DB table 'course_modules'.
522 $dbfields = array_keys($DB->get_columns('course_modules'));
523 sort($dbfields);
524 $cmrecord = $modinfo->get_cm($mods[0]->cmid)->get_course_module_record();
525 $cmrecordfields = array_keys((array)$cmrecord);
526 sort($cmrecordfields);
527 $this->assertEquals($dbfields, $cmrecordfields);
529 // Make sure that object returned by get_course_module_record(true) has exactly the same fields
530 // as object returned by get_coursemodule_from_id(,,,true,);
531 $cmrecordfull = $modinfo->get_cm($mods[0]->cmid)->get_course_module_record(true);
532 $cmrecordfullfields = array_keys((array)$cmrecordfull);
533 $cm = get_coursemodule_from_id(null, $mods[0]->cmid, 0, true, MUST_EXIST);
534 $cmfields = array_keys((array)$cm);
535 $this->assertEquals($cmfields, $cmrecordfullfields);
537 // Make sure that object returned by get_course_module_record(true) has exactly the same fields
538 // as object returned by get_coursemodule_from_instance(,,,true,);
539 $cm = get_coursemodule_from_instance('forum', $mods[0]->id, null, true, MUST_EXIST);
540 $cmfields = array_keys((array)$cm);
541 $this->assertEquals($cmfields, $cmrecordfullfields);
543 // Make sure the objects have the same properties.
544 $cm1 = get_coursemodule_from_id(null, $mods[0]->cmid, 0, true, MUST_EXIST);
545 $cm2 = get_coursemodule_from_instance('forum', $mods[0]->id, 0, true, MUST_EXIST);
546 $cminfo = $modinfo->get_cm($mods[0]->cmid);
547 $record = $DB->get_record('course_modules', array('id' => $mods[0]->cmid));
548 $this->assertEquals($record, $cminfo->get_course_module_record());
549 $this->assertEquals($cm1, $cminfo->get_course_module_record(true));
550 $this->assertEquals($cm2, $cminfo->get_course_module_record(true));
552 $cm1 = get_coursemodule_from_id(null, $mods[1]->cmid, 0, true, MUST_EXIST);
553 $cm2 = get_coursemodule_from_instance('assign', $mods[1]->id, 0, true, MUST_EXIST);
554 $cminfo = $modinfo->get_cm($mods[1]->cmid);
555 $record = $DB->get_record('course_modules', array('id' => $mods[1]->cmid));
556 $this->assertEquals($record, $cminfo->get_course_module_record());
557 $this->assertEquals($cm1, $cminfo->get_course_module_record(true));
558 $this->assertEquals($cm2, $cminfo->get_course_module_record(true));
560 $cm1 = get_coursemodule_from_id(null, $mods[2]->cmid, 0, true, MUST_EXIST);
561 $cm2 = get_coursemodule_from_instance('book', $mods[2]->id, 0, true, MUST_EXIST);
562 $cminfo = $modinfo->get_cm($mods[2]->cmid);
563 $record = $DB->get_record('course_modules', array('id' => $mods[2]->cmid));
564 $this->assertEquals($record, $cminfo->get_course_module_record());
565 $this->assertEquals($cm1, $cminfo->get_course_module_record(true));
566 $this->assertEquals($cm2, $cminfo->get_course_module_record(true));
568 $cm1 = get_coursemodule_from_id(null, $mods[3]->cmid, 0, true, MUST_EXIST);
569 $cm2 = get_coursemodule_from_instance('forum', $mods[3]->id, 0, true, MUST_EXIST);
570 $cminfo = $modinfo->get_cm($mods[3]->cmid);
571 $record = $DB->get_record('course_modules', array('id' => $mods[3]->cmid));
572 $this->assertEquals($record, $cminfo->get_course_module_record());
573 $this->assertEquals($cm1, $cminfo->get_course_module_record(true));
574 $this->assertEquals($cm2, $cminfo->get_course_module_record(true));
576 $cm1 = get_coursemodule_from_id(null, $mods[4]->cmid, 0, true, MUST_EXIST);
577 $cm2 = get_coursemodule_from_instance('forum', $mods[4]->id, 0, true, MUST_EXIST);
578 $cminfo = $modinfo->get_cm($mods[4]->cmid);
579 $record = $DB->get_record('course_modules', array('id' => $mods[4]->cmid));
580 $this->assertEquals($record, $cminfo->get_course_module_record());
581 $this->assertEquals($cm1, $cminfo->get_course_module_record(true));
582 $this->assertEquals($cm2, $cminfo->get_course_module_record(true));
587 * Tests for function cm_info::get_activitybadge().
589 * @covers \cm_info::get_activitybadge
591 public function test_cm_info_get_activitybadge(): void {
592 global $PAGE;
594 $this->resetAfterTest();
595 $this->setAdminUser();
597 $course = $this->getDataGenerator()->create_course();
598 $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
599 $resource = $this->getDataGenerator()->create_module('resource', ['course' => $course->id]);
600 $assign = $this->getDataGenerator()->create_module('assign', ['course' => $course->id]);
601 $label = $this->getDataGenerator()->create_module('label', ['course' => $course->id]);
603 $renderer = $PAGE->get_renderer('core');
604 $modinfo = get_fast_modinfo($course->id);
606 // Forum and resource implements the activitybadge feature.
607 $cminfo = $modinfo->get_cm($forum->cmid);
608 $this->assertNotNull($cminfo->get_activitybadge($renderer));
609 $cminfo = $modinfo->get_cm($resource->cmid);
610 $this->assertNotNull($cminfo->get_activitybadge($renderer));
612 // Assign and label don't implement the activitybadge feature (at least for now).
613 $cminfo = $modinfo->get_cm($assign->cmid);
614 $this->assertNull($cminfo->get_activitybadge($renderer));
615 $cminfo = $modinfo->get_cm($label->cmid);
616 $this->assertNull($cminfo->get_activitybadge($renderer));
620 * Tests the availability property that has been added to course modules
621 * and sections (just to see that it is correctly saved and accessed).
623 public function test_availability_property() {
624 global $DB, $CFG;
626 $this->resetAfterTest();
628 // Create a course with two modules and three sections.
629 $course = $this->getDataGenerator()->create_course(
630 array('format' => 'topics', 'numsections' => 3),
631 array('createsections' => true));
632 $forum = $this->getDataGenerator()->create_module('forum',
633 array('course' => $course->id));
634 $forum2 = $this->getDataGenerator()->create_module('forum',
635 array('course' => $course->id));
637 // Get modinfo. Check that availability is null for both cm and sections.
638 $modinfo = get_fast_modinfo($course->id);
639 $cm = $modinfo->get_cm($forum->cmid);
640 $this->assertNull($cm->availability);
641 $section = $modinfo->get_section_info(1, MUST_EXIST);
642 $this->assertNull($section->availability);
644 // Update availability for cm and section in database.
645 $DB->set_field('course_modules', 'availability', '{}', array('id' => $cm->id));
646 $DB->set_field('course_sections', 'availability', '{}', array('id' => $section->id));
648 // Clear cache and get modinfo again.
649 rebuild_course_cache($course->id, true);
650 get_fast_modinfo(0, 0, true);
651 $modinfo = get_fast_modinfo($course->id);
653 // Check values that were changed.
654 $cm = $modinfo->get_cm($forum->cmid);
655 $this->assertEquals('{}', $cm->availability);
656 $section = $modinfo->get_section_info(1, MUST_EXIST);
657 $this->assertEquals('{}', $section->availability);
659 // Check other values are still null.
660 $cm = $modinfo->get_cm($forum2->cmid);
661 $this->assertNull($cm->availability);
662 $section = $modinfo->get_section_info(2, MUST_EXIST);
663 $this->assertNull($section->availability);
667 * Tests for get_groups() method.
669 public function test_get_groups() {
670 $this->resetAfterTest();
671 $generator = $this->getDataGenerator();
673 // Create courses.
674 $course1 = $generator->create_course();
675 $course2 = $generator->create_course();
676 $course3 = $generator->create_course();
678 // Create users.
679 $user1 = $generator->create_user();
680 $user2 = $generator->create_user();
681 $user3 = $generator->create_user();
683 // Enrol users on courses.
684 $generator->enrol_user($user1->id, $course1->id);
685 $generator->enrol_user($user2->id, $course2->id);
686 $generator->enrol_user($user3->id, $course2->id);
687 $generator->enrol_user($user3->id, $course3->id);
689 // Create groups.
690 $group1 = $generator->create_group(array('courseid' => $course1->id));
691 $group2 = $generator->create_group(array('courseid' => $course2->id));
692 $group3 = $generator->create_group(array('courseid' => $course2->id));
694 // Assign users to groups and assert the result.
695 $this->assertTrue($generator->create_group_member(array('groupid' => $group1->id, 'userid' => $user1->id)));
696 $this->assertTrue($generator->create_group_member(array('groupid' => $group2->id, 'userid' => $user2->id)));
697 $this->assertTrue($generator->create_group_member(array('groupid' => $group3->id, 'userid' => $user2->id)));
698 $this->assertTrue($generator->create_group_member(array('groupid' => $group2->id, 'userid' => $user3->id)));
700 // Create groupings.
701 $grouping1 = $generator->create_grouping(array('courseid' => $course1->id));
702 $grouping2 = $generator->create_grouping(array('courseid' => $course2->id));
704 // Assign and assert group to groupings.
705 groups_assign_grouping($grouping1->id, $group1->id);
706 groups_assign_grouping($grouping2->id, $group2->id);
707 groups_assign_grouping($grouping2->id, $group3->id);
709 // Test with one single group.
710 $modinfo = get_fast_modinfo($course1, $user1->id);
711 $groups = $modinfo->get_groups($grouping1->id);
712 $this->assertCount(1, $groups);
713 $this->assertArrayHasKey($group1->id, $groups);
715 // Test with two groups.
716 $modinfo = get_fast_modinfo($course2, $user2->id);
717 $groups = $modinfo->get_groups();
718 $this->assertCount(2, $groups);
719 $this->assertTrue(in_array($group2->id, $groups));
720 $this->assertTrue(in_array($group3->id, $groups));
722 // Test with no groups.
723 $modinfo = get_fast_modinfo($course3, $user3->id);
724 $groups = $modinfo->get_groups();
725 $this->assertCount(0, $groups);
726 $this->assertArrayNotHasKey($group1->id, $groups);
730 * Tests the function for constructing a cm_info from mixed data.
732 public function test_create() {
733 global $CFG, $DB;
734 $this->resetAfterTest();
736 // Create a course and an activity.
737 $generator = $this->getDataGenerator();
738 $course = $generator->create_course();
739 $page = $generator->create_module('page', array('course' => $course->id,
740 'name' => 'Annie'));
742 // Null is passed through.
743 $this->assertNull(cm_info::create(null));
745 // Stdclass object turns into cm_info.
746 $cm = cm_info::create(
747 (object)array('id' => $page->cmid, 'course' => $course->id));
748 $this->assertInstanceOf('cm_info', $cm);
749 $this->assertEquals('Annie', $cm->name);
751 // A cm_info object stays as cm_info.
752 $this->assertSame($cm, cm_info::create($cm));
754 // Invalid object (missing fields) causes error.
755 try {
756 cm_info::create((object)array('id' => $page->cmid));
757 $this->fail();
758 } catch (Exception $e) {
759 $this->assertInstanceOf('coding_exception', $e);
762 // Create a second hidden activity.
763 $hiddenpage = $generator->create_module('page', array('course' => $course->id,
764 'name' => 'Annie', 'visible' => 0));
766 // Create 2 user accounts, one is a manager who can see everything.
767 $user = $generator->create_user();
768 $generator->enrol_user($user->id, $course->id);
769 $manager = $generator->create_user();
770 $generator->enrol_user($manager->id, $course->id,
771 $DB->get_field('role', 'id', array('shortname' => 'manager'), MUST_EXIST));
773 // User can see the normal page but not the hidden one.
774 $cm = cm_info::create((object)array('id' => $page->cmid, 'course' => $course->id),
775 $user->id);
776 $this->assertTrue($cm->uservisible);
777 $cm = cm_info::create((object)array('id' => $hiddenpage->cmid, 'course' => $course->id),
778 $user->id);
779 $this->assertFalse($cm->uservisible);
781 // Manager can see the hidden one too.
782 $cm = cm_info::create((object)array('id' => $hiddenpage->cmid, 'course' => $course->id),
783 $manager->id);
784 $this->assertTrue($cm->uservisible);
788 * Tests function for getting $course and $cm at once quickly from modinfo
789 * based on cmid or cm record.
791 public function test_get_course_and_cm_from_cmid() {
792 global $CFG, $DB;
793 $this->resetAfterTest();
795 // Create a course and an activity.
796 $generator = $this->getDataGenerator();
797 $course = $generator->create_course(array('shortname' => 'Halls'));
798 $page = $generator->create_module('page', array('course' => $course->id,
799 'name' => 'Annie'));
801 // Successful usage.
802 list($course, $cm) = get_course_and_cm_from_cmid($page->cmid);
803 $this->assertEquals('Halls', $course->shortname);
804 $this->assertInstanceOf('cm_info', $cm);
805 $this->assertEquals('Annie', $cm->name);
807 // Specified module type.
808 list($course, $cm) = get_course_and_cm_from_cmid($page->cmid, 'page');
809 $this->assertEquals('Annie', $cm->name);
811 // With id in object.
812 $fakecm = (object)array('id' => $page->cmid);
813 list($course, $cm) = get_course_and_cm_from_cmid($fakecm);
814 $this->assertEquals('Halls', $course->shortname);
815 $this->assertEquals('Annie', $cm->name);
817 // With both id and course in object.
818 $fakecm->course = $course->id;
819 list($course, $cm) = get_course_and_cm_from_cmid($fakecm);
820 $this->assertEquals('Halls', $course->shortname);
821 $this->assertEquals('Annie', $cm->name);
823 // With supplied course id.
824 list($course, $cm) = get_course_and_cm_from_cmid($page->cmid, 'page', $course->id);
825 $this->assertEquals('Annie', $cm->name);
827 // With supplied course object (modified just so we can check it is
828 // indeed reusing the supplied object).
829 $course->silly = true;
830 list($course, $cm) = get_course_and_cm_from_cmid($page->cmid, 'page', $course);
831 $this->assertEquals('Annie', $cm->name);
832 $this->assertTrue($course->silly);
834 // Incorrect module type.
835 try {
836 get_course_and_cm_from_cmid($page->cmid, 'forum');
837 $this->fail();
838 } catch (moodle_exception $e) {
839 $this->assertEquals('invalidcoursemoduleid', $e->errorcode);
842 // Invalid module name.
843 try {
844 get_course_and_cm_from_cmid($page->cmid, 'pigs can fly');
845 $this->fail();
846 } catch (coding_exception $e) {
847 $this->assertStringContainsString('Invalid modulename parameter', $e->getMessage());
850 // Doesn't exist.
851 try {
852 get_course_and_cm_from_cmid($page->cmid + 1);
853 $this->fail();
854 } catch (moodle_exception $e) {
855 $this->assertInstanceOf('dml_exception', $e);
858 // Create a second hidden activity.
859 $hiddenpage = $generator->create_module('page', array('course' => $course->id,
860 'name' => 'Annie', 'visible' => 0));
862 // Create 2 user accounts, one is a manager who can see everything.
863 $user = $generator->create_user();
864 $generator->enrol_user($user->id, $course->id);
865 $manager = $generator->create_user();
866 $generator->enrol_user($manager->id, $course->id,
867 $DB->get_field('role', 'id', array('shortname' => 'manager'), MUST_EXIST));
869 // User can see the normal page but not the hidden one.
870 list($course, $cm) = get_course_and_cm_from_cmid($page->cmid, 'page', 0, $user->id);
871 $this->assertTrue($cm->uservisible);
872 list($course, $cm) = get_course_and_cm_from_cmid($hiddenpage->cmid, 'page', 0, $user->id);
873 $this->assertFalse($cm->uservisible);
875 // Manager can see the hidden one too.
876 list($course, $cm) = get_course_and_cm_from_cmid($hiddenpage->cmid, 'page', 0, $manager->id);
877 $this->assertTrue($cm->uservisible);
881 * Tests function for getting $course and $cm at once quickly from modinfo
882 * based on instance id or record.
884 public function test_get_course_and_cm_from_instance() {
885 global $CFG, $DB;
886 $this->resetAfterTest();
888 // Create a course and an activity.
889 $generator = $this->getDataGenerator();
890 $course = $generator->create_course(array('shortname' => 'Halls'));
891 $page = $generator->create_module('page', array('course' => $course->id,
892 'name' => 'Annie'));
894 // Successful usage.
895 list($course, $cm) = get_course_and_cm_from_instance($page->id, 'page');
896 $this->assertEquals('Halls', $course->shortname);
897 $this->assertInstanceOf('cm_info', $cm);
898 $this->assertEquals('Annie', $cm->name);
900 // With id in object.
901 $fakeinstance = (object)array('id' => $page->id);
902 list($course, $cm) = get_course_and_cm_from_instance($fakeinstance, 'page');
903 $this->assertEquals('Halls', $course->shortname);
904 $this->assertEquals('Annie', $cm->name);
906 // With both id and course in object.
907 $fakeinstance->course = $course->id;
908 list($course, $cm) = get_course_and_cm_from_instance($fakeinstance, 'page');
909 $this->assertEquals('Halls', $course->shortname);
910 $this->assertEquals('Annie', $cm->name);
912 // With supplied course id.
913 list($course, $cm) = get_course_and_cm_from_instance($page->id, 'page', $course->id);
914 $this->assertEquals('Annie', $cm->name);
916 // With supplied course object (modified just so we can check it is
917 // indeed reusing the supplied object).
918 $course->silly = true;
919 list($course, $cm) = get_course_and_cm_from_instance($page->id, 'page', $course);
920 $this->assertEquals('Annie', $cm->name);
921 $this->assertTrue($course->silly);
923 // Doesn't exist (or is wrong type).
924 try {
925 get_course_and_cm_from_instance($page->id, 'forum');
926 $this->fail();
927 } catch (moodle_exception $e) {
928 $this->assertInstanceOf('dml_exception', $e);
931 // Invalid module name.
932 try {
933 get_course_and_cm_from_cmid($page->cmid, '1337 h4x0ring');
934 $this->fail();
935 } catch (coding_exception $e) {
936 $this->assertStringContainsString('Invalid modulename parameter', $e->getMessage());
939 // Create a second hidden activity.
940 $hiddenpage = $generator->create_module('page', array('course' => $course->id,
941 'name' => 'Annie', 'visible' => 0));
943 // Create 2 user accounts, one is a manager who can see everything.
944 $user = $generator->create_user();
945 $generator->enrol_user($user->id, $course->id);
946 $manager = $generator->create_user();
947 $generator->enrol_user($manager->id, $course->id,
948 $DB->get_field('role', 'id', array('shortname' => 'manager'), MUST_EXIST));
950 // User can see the normal page but not the hidden one.
951 list($course, $cm) = get_course_and_cm_from_cmid($page->cmid, 'page', 0, $user->id);
952 $this->assertTrue($cm->uservisible);
953 list($course, $cm) = get_course_and_cm_from_cmid($hiddenpage->cmid, 'page', 0, $user->id);
954 $this->assertFalse($cm->uservisible);
956 // Manager can see the hidden one too.
957 list($course, $cm) = get_course_and_cm_from_cmid($hiddenpage->cmid, 'page', 0, $manager->id);
958 $this->assertTrue($cm->uservisible);
962 * Test test_get_section_info_by_id method
964 * @dataProvider get_section_info_by_id_provider
965 * @covers \course_modinfo::get_section_info_by_id
967 * @param int $sectionnum the section number
968 * @param int $strictness the search strict mode
969 * @param bool $expectnull if the function will return a null
970 * @param bool $expectexception if the function will throw an exception
972 public function test_get_section_info_by_id(
973 int $sectionnum,
974 int $strictness = IGNORE_MISSING,
975 bool $expectnull = false,
976 bool $expectexception = false
978 global $DB;
980 $this->resetAfterTest();
982 // Create a course with 4 sections.
983 $course = $this->getDataGenerator()->create_course(['numsections' => 4]);
985 // Index sections.
986 $sectionindex = [];
987 $modinfo = get_fast_modinfo($course);
988 $allsections = $modinfo->get_section_info_all();
989 foreach ($allsections as $section) {
990 $sectionindex[$section->section] = $section->id;
993 if ($expectexception) {
994 $this->expectException(moodle_exception::class);
997 $sectionid = $sectionindex[$sectionnum] ?? -1;
999 $section = $modinfo->get_section_info_by_id($sectionid, $strictness);
1001 if ($expectnull) {
1002 $this->assertNull($section);
1003 } else {
1004 $this->assertEquals($sectionid, $section->id);
1005 $this->assertEquals($sectionnum, $section->section);
1010 * Data provider for test_get_section_info_by_id().
1012 * @return array
1014 public function get_section_info_by_id_provider() {
1015 return [
1016 'Valid section id' => [
1017 'sectionnum' => 1,
1018 'strictness' => IGNORE_MISSING,
1019 'expectnull' => false,
1020 'expectexception' => false,
1022 'Section zero' => [
1023 'sectionnum' => 0,
1024 'strictness' => IGNORE_MISSING,
1025 'expectnull' => false,
1026 'expectexception' => false,
1028 'invalid section ignore missing' => [
1029 'sectionnum' => -1,
1030 'strictness' => IGNORE_MISSING,
1031 'expectnull' => true,
1032 'expectexception' => false,
1034 'invalid section must exists' => [
1035 'sectionnum' => -1,
1036 'strictness' => MUST_EXIST,
1037 'expectnull' => false,
1038 'expectexception' => true,
1044 * Test purge_section_cache_by_id method
1046 * @covers \course_modinfo::purge_course_section_cache_by_id
1047 * @return void
1049 public function test_purge_section_cache_by_id(): void {
1050 $this->resetAfterTest();
1051 $this->setAdminUser();
1052 $cache = cache::make('core', 'coursemodinfo');
1054 // Generate the course and pre-requisite section.
1055 $course = $this->getDataGenerator()->create_course(['format' => 'topics', 'numsections' => 3], ['createsections' => true]);
1056 // Reset course cache.
1057 rebuild_course_cache($course->id, true);
1058 // Build course cache.
1059 get_fast_modinfo($course->id);
1060 // Get the course modinfo cache.
1061 $coursemodinfo = $cache->get_versioned($course->id, $course->cacherev);
1062 // Get the section cache.
1063 $sectioncaches = $coursemodinfo->sectioncache;
1065 // Make sure that we will have 4 section caches here.
1066 $this->assertCount(4, $sectioncaches);
1067 $this->assertArrayHasKey(0, $sectioncaches);
1068 $this->assertArrayHasKey(1, $sectioncaches);
1069 $this->assertArrayHasKey(2, $sectioncaches);
1070 $this->assertArrayHasKey(3, $sectioncaches);
1072 // Purge cache for the section by id.
1073 course_modinfo::purge_course_section_cache_by_id($course->id, $sectioncaches[1]->id);
1074 // Get the course modinfo cache.
1075 $coursemodinfo = $cache->get_versioned($course->id, $course->cacherev);
1076 // Get the section cache.
1077 $sectioncaches = $coursemodinfo->sectioncache;
1079 // Make sure that we will have 3 section caches left.
1080 $this->assertCount(3, $sectioncaches);
1081 $this->assertArrayNotHasKey(1, $sectioncaches);
1082 $this->assertArrayHasKey(0, $sectioncaches);
1083 $this->assertArrayHasKey(2, $sectioncaches);
1084 $this->assertArrayHasKey(3, $sectioncaches);
1085 // Make sure that the cacherev will be reset.
1086 $this->assertEquals(-1, $coursemodinfo->cacherev);
1090 * Test purge_section_cache_by_number method
1092 * @covers \course_modinfo::purge_course_section_cache_by_number
1093 * @return void
1095 public function test_section_cache_by_number(): void {
1096 $this->resetAfterTest();
1097 $this->setAdminUser();
1098 $cache = cache::make('core', 'coursemodinfo');
1100 // Generate the course and pre-requisite section.
1101 $course = $this->getDataGenerator()->create_course(['format' => 'topics', 'numsections' => 3], ['createsections' => true]);
1102 // Reset course cache.
1103 rebuild_course_cache($course->id, true);
1104 // Build course cache.
1105 get_fast_modinfo($course->id);
1106 // Get the course modinfo cache.
1107 $coursemodinfo = $cache->get_versioned($course->id, $course->cacherev);
1108 // Get the section cache.
1109 $sectioncaches = $coursemodinfo->sectioncache;
1111 // Make sure that we will have 4 section caches here.
1112 $this->assertCount(4, $sectioncaches);
1113 $this->assertArrayHasKey(0, $sectioncaches);
1114 $this->assertArrayHasKey(1, $sectioncaches);
1115 $this->assertArrayHasKey(2, $sectioncaches);
1116 $this->assertArrayHasKey(3, $sectioncaches);
1118 // Purge cache for the section with section number is 1.
1119 course_modinfo::purge_course_section_cache_by_number($course->id, 1);
1120 // Get the course modinfo cache.
1121 $coursemodinfo = $cache->get_versioned($course->id, $course->cacherev);
1122 // Get the section cache.
1123 $sectioncaches = $coursemodinfo->sectioncache;
1125 // Make sure that we will have 3 section caches left.
1126 $this->assertCount(3, $sectioncaches);
1127 $this->assertArrayNotHasKey(1, $sectioncaches);
1128 $this->assertArrayHasKey(0, $sectioncaches);
1129 $this->assertArrayHasKey(2, $sectioncaches);
1130 $this->assertArrayHasKey(3, $sectioncaches);
1131 // Make sure that the cacherev will be reset.
1132 $this->assertEquals(-1, $coursemodinfo->cacherev);
1136 * Test get_cm() method to output course module id in the exception text.
1138 * @covers \course_modinfo::get_cm
1139 * @return void
1141 public function test_invalid_course_module_id(): void {
1142 global $DB;
1143 $this->resetAfterTest();
1145 $course = $this->getDataGenerator()->create_course();
1146 $forum0 = $this->getDataGenerator()->create_module('assign', ['course' => $course->id], ['section' => 0]);
1147 $forum1 = $this->getDataGenerator()->create_module('assign', ['course' => $course->id], ['section' => 0]);
1148 $forum2 = $this->getDataGenerator()->create_module('assign', ['course' => $course->id], ['section' => 0]);
1150 // Break section sequence.
1151 $modinfo = get_fast_modinfo($course->id);
1152 $sectionid = $modinfo->get_section_info(0)->id;
1153 $section = $DB->get_record('course_sections', ['id' => $sectionid]);
1154 $sequence = explode(',', $section->sequence);
1155 $sequence = array_diff($sequence, [$forum1->cmid]);
1156 $section->sequence = implode(',', $sequence);
1157 $DB->update_record('course_sections', $section);
1159 // Assert exception text.
1160 $this->expectException(\moodle_exception::class);
1161 $this->expectExceptionMessage('Invalid course module ID: ' . $forum1->cmid);
1162 delete_course($course, false);
1166 * Tests that if the modinfo cache returns a newer-than-expected version, Moodle won't rebuild
1167 * it.
1169 * This is important to avoid wasted time/effort and poor performance, for example in cases
1170 * where multiple requests are accessing the course.
1172 * Certain cases could be particularly bad if this test fails. For example, if using clustered
1173 * databases where there is a 100ms delay between updates to the course table being available
1174 * to all users (but no such delay on the cache infrastructure), then during that 100ms, every
1175 * request that calls get_fast_modinfo and uses the read-only database will rebuild the course
1176 * cache. Since these will then create a still-newer version, future requests for the next
1177 * 100ms will also rebuild it again... etc.
1179 * @covers \course_modinfo
1181 public function test_get_modinfo_with_newer_version(): void {
1182 global $DB;
1184 $this->resetAfterTest();
1186 // Get info about a course and build the initial cache, then drop it from memory.
1187 $course = $this->getDataGenerator()->create_course();
1188 get_fast_modinfo($course);
1189 get_fast_modinfo(0, 0, true);
1191 // User A starts a request, which takes some time...
1192 $useracourse = $DB->get_record('course', ['id' => $course->id]);
1194 // User B also starts a request and makes a change to the course.
1195 $userbcourse = $DB->get_record('course', ['id' => $course->id]);
1196 $this->getDataGenerator()->create_module('page', ['course' => $course->id]);
1197 rebuild_course_cache($userbcourse->id, false);
1199 // Finally, user A's request now gets modinfo. It should accept the version from B even
1200 // though the course version (of cache) is newer than the one expected by A.
1201 $before = $DB->perf_get_queries();
1202 $modinfo = get_fast_modinfo($useracourse);
1203 $after = $DB->perf_get_queries();
1204 $this->assertEquals($after, $before, 'Should use cached version, making no DB queries');
1206 // Obviously, modinfo should include the Page now.
1207 $this->assertCount(1, $modinfo->get_instances_of('page'));