Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [Versions 401 and 403] [Versions 402 and 403]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 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 moodle_exception; 27 use moodle_url; 28 use Exception; 29 30 /** 31 * Unit tests for lib/modinfolib.php. 32 * 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 37 */ 38 class modinfolib_test extends advanced_testcase { 39 public function test_section_info_properties() { 40 global $DB, $CFG; 41 42 $this->resetAfterTest(); 43 $oldcfgenableavailability = $CFG->enableavailability; 44 $oldcfgenablecompletion = $CFG->enablecompletion; 45 set_config('enableavailability', 1); 46 set_config('enablecompletion', 1); 47 $this->setAdminUser(); 48 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)); 61 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)); 73 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); 82 83 // Get modinfo. 84 $modinfo = get_fast_modinfo($course->id); 85 $si = $modinfo->get_section_info(2); 86 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); 96 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); 101 102 // Restore settings. 103 set_config('enableavailability', $oldcfgenableavailability); 104 set_config('enablecompletion', $oldcfgenablecompletion); 105 } 106 107 public function test_cm_info_properties() { 108 global $DB, $CFG; 109 110 $this->resetAfterTest(); 111 $oldcfgenableavailability = $CFG->enableavailability; 112 $oldcfgenablecompletion = $CFG->enablecompletion; 113 set_config('enableavailability', 1); 114 set_config('enablecompletion', 1); 115 $this->setAdminUser(); 116 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)); 129 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); 143 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); 151 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); 160 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]); 164 165 // Get modinfo. 166 $modinfo = get_fast_modinfo($course->id); 167 $cm = $modinfo->instances['assign'][$assign->id]; 168 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); 206 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); 215 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(); 225 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(); 235 236 // Restore settings. 237 set_config('enableavailability', $oldcfgenableavailability); 238 set_config('enablecompletion', $oldcfgenablecompletion); 239 } 240 241 public function test_matching_cacherev() { 242 global $DB, $CFG; 243 244 $this->resetAfterTest(); 245 $this->setAdminUser(); 246 $cache = cache::make('core', 'coursemodinfo'); 247 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)); 253 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; 258 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; 265 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; 275 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); 280 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; 292 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; 303 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; 316 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; 329 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)); 335 } 336 337 /** 338 * The cacherev is updated when we rebuild course cache, but there are scenarios where an 339 * existing course object with old cacherev might be reused within the same request after 340 * clearing the cache. In that case, we need to check that the new data is loaded and it 341 * does not reuse the old cached data with old cacherev. 342 * 343 * @covers ::rebuild_course_cache() 344 */ 345 public function test_cache_clear_wrong_cacherev(): void { 346 global $DB; 347 348 $this->resetAfterTest(); 349 $originalcourse = $this->getDataGenerator()->create_course(); 350 $course = $DB->get_record('course', ['id' => $originalcourse->id]); 351 $page = $this->getDataGenerator()->create_module('page', 352 ['course' => $course->id, 'name' => 'frog']); 353 $oldmodinfo = get_fast_modinfo($course); 354 $this->assertEquals('frog', $oldmodinfo->get_cm($page->cmid)->name); 355 356 // Change page name and rebuild cache. 357 $DB->set_field('page', 'name', 'Frog', ['id' => $page->id]); 358 rebuild_course_cache($course->id, true); 359 360 // Get modinfo using original course object which has old cacherev. 361 $newmodinfo = get_fast_modinfo($course); 362 $this->assertEquals('Frog', $newmodinfo->get_cm($page->cmid)->name); 363 } 364 365 /** 366 * When cacherev is updated for a course, it is supposed to update in the $COURSE and $SITE 367 * globals automatically. Check this is working. 368 * 369 * @covers ::rebuild_course_cache() 370 */ 371 public function test_cacherev_update_in_globals(): void { 372 global $DB, $COURSE, $SITE; 373 374 $this->resetAfterTest(); 375 376 // Create a course and get modinfo. 377 $originalcourse = $this->getDataGenerator()->create_course(); 378 $oldmodinfo = get_fast_modinfo($originalcourse->id); 379 380 // Store (two clones of) the course in COURSE and SITE globals. 381 $COURSE = get_course($originalcourse->id); 382 $SITE = get_course($originalcourse->id); 383 384 // Note original cacherev. 385 $originalcacherev = $oldmodinfo->get_course()->cacherev; 386 $this->assertEquals($COURSE->cacherev, $originalcacherev); 387 $this->assertEquals($SITE->cacherev, $originalcacherev); 388 389 // Clear the cache and check cacherev updated. 390 rebuild_course_cache($originalcourse->id, true); 391 392 $newcourse = $DB->get_record('course', ['id' => $originalcourse->id]); 393 $this->assertGreaterThan($originalcacherev, $newcourse->cacherev); 394 395 // Check that the in-memory $COURSE and $SITE have updated. 396 $this->assertEquals($newcourse->cacherev, $COURSE->cacherev); 397 $this->assertEquals($newcourse->cacherev, $SITE->cacherev); 398 } 399 400 public function test_course_modinfo_properties() { 401 global $USER, $DB; 402 403 $this->resetAfterTest(); 404 $this->setAdminUser(); 405 406 // Generate the course and some modules. Make one section hidden. 407 $course = $this->getDataGenerator()->create_course( 408 array('format' => 'topics', 409 'numsections' => 3), 410 array('createsections' => true)); 411 $DB->execute('UPDATE {course_sections} SET visible = 0 WHERE course = ? and section = ?', 412 array($course->id, 3)); 413 $coursecontext = context_course::instance($course->id); 414 $forum0 = $this->getDataGenerator()->create_module('forum', 415 array('course' => $course->id), array('section' => 0)); 416 $assign0 = $this->getDataGenerator()->create_module('assign', 417 array('course' => $course->id), array('section' => 0, 'visible' => 0)); 418 $forum1 = $this->getDataGenerator()->create_module('forum', 419 array('course' => $course->id), array('section' => 1)); 420 $assign1 = $this->getDataGenerator()->create_module('assign', 421 array('course' => $course->id), array('section' => 1)); 422 $page1 = $this->getDataGenerator()->create_module('page', 423 array('course' => $course->id), array('section' => 1)); 424 $page3 = $this->getDataGenerator()->create_module('page', 425 array('course' => $course->id), array('section' => 3)); 426 427 $modinfo = get_fast_modinfo($course->id); 428 429 $this->assertEquals(array($forum0->cmid, $assign0->cmid, $forum1->cmid, $assign1->cmid, $page1->cmid, $page3->cmid), 430 array_keys($modinfo->cms)); 431 $this->assertEquals($course->id, $modinfo->courseid); 432 $this->assertEquals($USER->id, $modinfo->userid); 433 $this->assertEquals(array(0 => array($forum0->cmid, $assign0->cmid), 434 1 => array($forum1->cmid, $assign1->cmid, $page1->cmid), 3 => array($page3->cmid)), $modinfo->sections); 435 $this->assertEquals(array('forum', 'assign', 'page'), array_keys($modinfo->instances)); 436 $this->assertEquals(array($assign0->id, $assign1->id), array_keys($modinfo->instances['assign'])); 437 $this->assertEquals(array($forum0->id, $forum1->id), array_keys($modinfo->instances['forum'])); 438 $this->assertEquals(array($page1->id, $page3->id), array_keys($modinfo->instances['page'])); 439 $this->assertEquals(groups_get_user_groups($course->id), $modinfo->groups); 440 $this->assertEquals(array(0 => array($forum0->cmid, $assign0->cmid), 441 1 => array($forum1->cmid, $assign1->cmid, $page1->cmid), 442 3 => array($page3->cmid)), $modinfo->get_sections()); 443 $this->assertEquals(array(0, 1, 2, 3), array_keys($modinfo->get_section_info_all())); 444 $this->assertEquals($forum0->cmid . ',' . $assign0->cmid, $modinfo->get_section_info(0)->sequence); 445 $this->assertEquals($forum1->cmid . ',' . $assign1->cmid . ',' . $page1->cmid, $modinfo->get_section_info(1)->sequence); 446 $this->assertEquals('', $modinfo->get_section_info(2)->sequence); 447 $this->assertEquals($page3->cmid, $modinfo->get_section_info(3)->sequence); 448 $this->assertEquals($course->id, $modinfo->get_course()->id); 449 $names = array_keys($modinfo->get_used_module_names()); 450 sort($names); 451 $this->assertEquals(array('assign', 'forum', 'page'), $names); 452 $names = array_keys($modinfo->get_used_module_names(true)); 453 sort($names); 454 $this->assertEquals(array('assign', 'forum', 'page'), $names); 455 // Admin can see hidden modules/sections. 456 $this->assertTrue($modinfo->cms[$assign0->cmid]->uservisible); 457 $this->assertTrue($modinfo->get_section_info(3)->uservisible); 458 459 // Get modinfo for non-current user (without capability to view hidden activities/sections). 460 $user = $this->getDataGenerator()->create_user(); 461 $modinfo = get_fast_modinfo($course->id, $user->id); 462 $this->assertEquals($user->id, $modinfo->userid); 463 $this->assertFalse($modinfo->cms[$assign0->cmid]->uservisible); 464 $this->assertFalse($modinfo->get_section_info(3)->uservisible); 465 466 // Attempt to access and set non-existing field. 467 $this->assertTrue(empty($modinfo->somefield)); 468 $this->assertFalse(isset($modinfo->somefield)); 469 $modinfo->somefield; 470 $this->assertDebuggingCalled(); 471 $modinfo->somefield = 'Some value'; 472 $this->assertDebuggingCalled(); 473 $this->assertEmpty($modinfo->somefield); 474 $this->assertDebuggingCalled(); 475 476 // Attempt to overwrite existing field. 477 $this->assertFalse(empty($modinfo->cms)); 478 $this->assertTrue(isset($modinfo->cms)); 479 $modinfo->cms = 'Illegal overwriting'; 480 $this->assertDebuggingCalled(); 481 $this->assertNotEquals('Illegal overwriting', $modinfo->cms); 482 } 483 484 public function test_is_user_access_restricted_by_capability() { 485 global $DB; 486 487 $this->resetAfterTest(); 488 489 // Create a course and a mod_assign instance. 490 $course = $this->getDataGenerator()->create_course(); 491 $assign = $this->getDataGenerator()->create_module('assign', array('course'=>$course->id)); 492 493 // Create and enrol a student. 494 $coursecontext = context_course::instance($course->id); 495 $studentrole = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST); 496 $student = $this->getDataGenerator()->create_user(); 497 role_assign($studentrole->id, $student->id, $coursecontext); 498 $enrolplugin = enrol_get_plugin('manual'); 499 $enrolinstance = $DB->get_record('enrol', array('courseid' => $course->id, 'enrol' => 'manual')); 500 $enrolplugin->enrol_user($enrolinstance, $student->id); 501 $this->setUser($student); 502 503 // Make sure student can see the module. 504 $cm = get_fast_modinfo($course->id)->instances['assign'][$assign->id]; 505 $this->assertTrue($cm->uservisible); 506 $this->assertFalse($cm->is_user_access_restricted_by_capability()); 507 508 // Prohibit student to view mod_assign for the course. 509 role_change_permission($studentrole->id, $coursecontext, 'mod/assign:view', CAP_PROHIBIT); 510 get_fast_modinfo($course->id, 0, true); 511 $cm = get_fast_modinfo($course->id)->instances['assign'][$assign->id]; 512 $this->assertFalse($cm->uservisible); 513 $this->assertTrue($cm->is_user_access_restricted_by_capability()); 514 515 // Restore permission to student to view mod_assign for the course. 516 role_change_permission($studentrole->id, $coursecontext, 'mod/assign:view', CAP_INHERIT); 517 get_fast_modinfo($course->id, 0, true); 518 $cm = get_fast_modinfo($course->id)->instances['assign'][$assign->id]; 519 $this->assertTrue($cm->uservisible); 520 $this->assertFalse($cm->is_user_access_restricted_by_capability()); 521 522 // Prohibit student to view mod_assign for the particular module. 523 role_change_permission($studentrole->id, context_module::instance($cm->id), 'mod/assign:view', CAP_PROHIBIT); 524 get_fast_modinfo($course->id, 0, true); 525 $cm = get_fast_modinfo($course->id)->instances['assign'][$assign->id]; 526 $this->assertFalse($cm->uservisible); 527 $this->assertTrue($cm->is_user_access_restricted_by_capability()); 528 529 // Check calling get_fast_modinfo() for different user: 530 $this->setAdminUser(); 531 $cm = get_fast_modinfo($course->id)->instances['assign'][$assign->id]; 532 $this->assertTrue($cm->uservisible); 533 $this->assertFalse($cm->is_user_access_restricted_by_capability()); 534 $cm = get_fast_modinfo($course->id, $student->id)->instances['assign'][$assign->id]; 535 $this->assertFalse($cm->uservisible); 536 $this->assertTrue($cm->is_user_access_restricted_by_capability()); 537 } 538 539 /** 540 * Tests for function cm_info::get_course_module_record() 541 */ 542 public function test_cm_info_get_course_module_record() { 543 global $DB; 544 545 $this->resetAfterTest(); 546 $this->setAdminUser(); 547 548 set_config('enableavailability', 1); 549 set_config('enablecompletion', 1); 550 551 $course = $this->getDataGenerator()->create_course( 552 array('format' => 'topics', 'numsections' => 3, 'enablecompletion' => 1), 553 array('createsections' => true)); 554 $mods = array(); 555 $mods[0] = $this->getDataGenerator()->create_module('forum', array('course' => $course->id)); 556 $mods[1] = $this->getDataGenerator()->create_module('assign', 557 array('course' => $course->id, 558 'section' => 3, 559 'idnumber' => '12345', 560 'showdescription' => true 561 )); 562 // Pick a small valid availability value to use. 563 $availabilityvalue = '{"op":"|","show":true,"c":[{"type":"date","d":">=","t":4}]}'; 564 $mods[2] = $this->getDataGenerator()->create_module('book', 565 array('course' => $course->id, 566 'indent' => 5, 567 'availability' => $availabilityvalue, 568 'showdescription' => false, 569 'completion' => true, 570 'completionview' => true, 571 'completionexpected' => time() + 5000, 572 )); 573 $mods[3] = $this->getDataGenerator()->create_module('forum', 574 array('course' => $course->id, 575 'visible' => 0, 576 'groupmode' => 1, 577 'availability' => null)); 578 $mods[4] = $this->getDataGenerator()->create_module('forum', 579 array('course' => $course->id, 580 'grouping' => 12)); 581 582 $modinfo = get_fast_modinfo($course->id); 583 584 // Make sure that object returned by get_course_module_record(false) has exactly the same fields as DB table 'course_modules'. 585 $dbfields = array_keys($DB->get_columns('course_modules')); 586 sort($dbfields); 587 $cmrecord = $modinfo->get_cm($mods[0]->cmid)->get_course_module_record(); 588 $cmrecordfields = array_keys((array)$cmrecord); 589 sort($cmrecordfields); 590 $this->assertEquals($dbfields, $cmrecordfields); 591 592 // Make sure that object returned by get_course_module_record(true) has exactly the same fields 593 // as object returned by get_coursemodule_from_id(,,,true,); 594 $cmrecordfull = $modinfo->get_cm($mods[0]->cmid)->get_course_module_record(true); 595 $cmrecordfullfields = array_keys((array)$cmrecordfull); 596 $cm = get_coursemodule_from_id(null, $mods[0]->cmid, 0, true, MUST_EXIST); 597 $cmfields = array_keys((array)$cm); 598 $this->assertEquals($cmfields, $cmrecordfullfields); 599 600 // Make sure that object returned by get_course_module_record(true) has exactly the same fields 601 // as object returned by get_coursemodule_from_instance(,,,true,); 602 $cm = get_coursemodule_from_instance('forum', $mods[0]->id, null, true, MUST_EXIST); 603 $cmfields = array_keys((array)$cm); 604 $this->assertEquals($cmfields, $cmrecordfullfields); 605 606 // Make sure the objects have the same properties. 607 $cm1 = get_coursemodule_from_id(null, $mods[0]->cmid, 0, true, MUST_EXIST); 608 $cm2 = get_coursemodule_from_instance('forum', $mods[0]->id, 0, true, MUST_EXIST); 609 $cminfo = $modinfo->get_cm($mods[0]->cmid); 610 $record = $DB->get_record('course_modules', array('id' => $mods[0]->cmid)); 611 $this->assertEquals($record, $cminfo->get_course_module_record()); 612 $this->assertEquals($cm1, $cminfo->get_course_module_record(true)); 613 $this->assertEquals($cm2, $cminfo->get_course_module_record(true)); 614 615 $cm1 = get_coursemodule_from_id(null, $mods[1]->cmid, 0, true, MUST_EXIST); 616 $cm2 = get_coursemodule_from_instance('assign', $mods[1]->id, 0, true, MUST_EXIST); 617 $cminfo = $modinfo->get_cm($mods[1]->cmid); 618 $record = $DB->get_record('course_modules', array('id' => $mods[1]->cmid)); 619 $this->assertEquals($record, $cminfo->get_course_module_record()); 620 $this->assertEquals($cm1, $cminfo->get_course_module_record(true)); 621 $this->assertEquals($cm2, $cminfo->get_course_module_record(true)); 622 623 $cm1 = get_coursemodule_from_id(null, $mods[2]->cmid, 0, true, MUST_EXIST); 624 $cm2 = get_coursemodule_from_instance('book', $mods[2]->id, 0, true, MUST_EXIST); 625 $cminfo = $modinfo->get_cm($mods[2]->cmid); 626 $record = $DB->get_record('course_modules', array('id' => $mods[2]->cmid)); 627 $this->assertEquals($record, $cminfo->get_course_module_record()); 628 $this->assertEquals($cm1, $cminfo->get_course_module_record(true)); 629 $this->assertEquals($cm2, $cminfo->get_course_module_record(true)); 630 631 $cm1 = get_coursemodule_from_id(null, $mods[3]->cmid, 0, true, MUST_EXIST); 632 $cm2 = get_coursemodule_from_instance('forum', $mods[3]->id, 0, true, MUST_EXIST); 633 $cminfo = $modinfo->get_cm($mods[3]->cmid); 634 $record = $DB->get_record('course_modules', array('id' => $mods[3]->cmid)); 635 $this->assertEquals($record, $cminfo->get_course_module_record()); 636 $this->assertEquals($cm1, $cminfo->get_course_module_record(true)); 637 $this->assertEquals($cm2, $cminfo->get_course_module_record(true)); 638 639 $cm1 = get_coursemodule_from_id(null, $mods[4]->cmid, 0, true, MUST_EXIST); 640 $cm2 = get_coursemodule_from_instance('forum', $mods[4]->id, 0, true, MUST_EXIST); 641 $cminfo = $modinfo->get_cm($mods[4]->cmid); 642 $record = $DB->get_record('course_modules', array('id' => $mods[4]->cmid)); 643 $this->assertEquals($record, $cminfo->get_course_module_record()); 644 $this->assertEquals($cm1, $cminfo->get_course_module_record(true)); 645 $this->assertEquals($cm2, $cminfo->get_course_module_record(true)); 646 647 } 648 649 /** 650 * Tests for function cm_info::get_activitybadge(). 651 * 652 * @covers \cm_info::get_activitybadge 653 */ 654 public function test_cm_info_get_activitybadge(): void { 655 global $PAGE; 656 657 $this->resetAfterTest(); 658 $this->setAdminUser(); 659 660 $course = $this->getDataGenerator()->create_course(); 661 $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]); 662 $resource = $this->getDataGenerator()->create_module('resource', ['course' => $course->id]); 663 $assign = $this->getDataGenerator()->create_module('assign', ['course' => $course->id]); 664 $label = $this->getDataGenerator()->create_module('label', ['course' => $course->id]); 665 666 $renderer = $PAGE->get_renderer('core'); 667 $modinfo = get_fast_modinfo($course->id); 668 669 // Forum and resource implements the activitybadge feature. 670 $cminfo = $modinfo->get_cm($forum->cmid); 671 $this->assertNotNull($cminfo->get_activitybadge($renderer)); 672 $cminfo = $modinfo->get_cm($resource->cmid); 673 $this->assertNotNull($cminfo->get_activitybadge($renderer)); 674 675 // Assign and label don't implement the activitybadge feature (at least for now). 676 $cminfo = $modinfo->get_cm($assign->cmid); 677 $this->assertNull($cminfo->get_activitybadge($renderer)); 678 $cminfo = $modinfo->get_cm($label->cmid); 679 $this->assertNull($cminfo->get_activitybadge($renderer)); 680 } 681 682 /** 683 * Tests the availability property that has been added to course modules 684 * and sections (just to see that it is correctly saved and accessed). 685 */ 686 public function test_availability_property() { 687 global $DB, $CFG; 688 689 $this->resetAfterTest(); 690 691 // Create a course with two modules and three sections. 692 $course = $this->getDataGenerator()->create_course( 693 array('format' => 'topics', 'numsections' => 3), 694 array('createsections' => true)); 695 $forum = $this->getDataGenerator()->create_module('forum', 696 array('course' => $course->id)); 697 $forum2 = $this->getDataGenerator()->create_module('forum', 698 array('course' => $course->id)); 699 700 // Get modinfo. Check that availability is null for both cm and sections. 701 $modinfo = get_fast_modinfo($course->id); 702 $cm = $modinfo->get_cm($forum->cmid); 703 $this->assertNull($cm->availability); 704 $section = $modinfo->get_section_info(1, MUST_EXIST); 705 $this->assertNull($section->availability); 706 707 // Update availability for cm and section in database. 708 $DB->set_field('course_modules', 'availability', '{}', array('id' => $cm->id)); 709 $DB->set_field('course_sections', 'availability', '{}', array('id' => $section->id)); 710 711 // Clear cache and get modinfo again. 712 rebuild_course_cache($course->id, true); 713 get_fast_modinfo(0, 0, true); 714 $modinfo = get_fast_modinfo($course->id); 715 716 // Check values that were changed. 717 $cm = $modinfo->get_cm($forum->cmid); 718 $this->assertEquals('{}', $cm->availability); 719 $section = $modinfo->get_section_info(1, MUST_EXIST); 720 $this->assertEquals('{}', $section->availability); 721 722 // Check other values are still null. 723 $cm = $modinfo->get_cm($forum2->cmid); 724 $this->assertNull($cm->availability); 725 $section = $modinfo->get_section_info(2, MUST_EXIST); 726 $this->assertNull($section->availability); 727 } 728 729 /** 730 * Tests for get_groups() method. 731 */ 732 public function test_get_groups() { 733 $this->resetAfterTest(); 734 $generator = $this->getDataGenerator(); 735 736 // Create courses. 737 $course1 = $generator->create_course(); 738 $course2 = $generator->create_course(); 739 $course3 = $generator->create_course(); 740 741 // Create users. 742 $user1 = $generator->create_user(); 743 $user2 = $generator->create_user(); 744 $user3 = $generator->create_user(); 745 746 // Enrol users on courses. 747 $generator->enrol_user($user1->id, $course1->id); 748 $generator->enrol_user($user2->id, $course2->id); 749 $generator->enrol_user($user3->id, $course2->id); 750 $generator->enrol_user($user3->id, $course3->id); 751 752 // Create groups. 753 $group1 = $generator->create_group(array('courseid' => $course1->id)); 754 $group2 = $generator->create_group(array('courseid' => $course2->id)); 755 $group3 = $generator->create_group(array('courseid' => $course2->id)); 756 757 // Assign users to groups and assert the result. 758 $this->assertTrue($generator->create_group_member(array('groupid' => $group1->id, 'userid' => $user1->id))); 759 $this->assertTrue($generator->create_group_member(array('groupid' => $group2->id, 'userid' => $user2->id))); 760 $this->assertTrue($generator->create_group_member(array('groupid' => $group3->id, 'userid' => $user2->id))); 761 $this->assertTrue($generator->create_group_member(array('groupid' => $group2->id, 'userid' => $user3->id))); 762 763 // Create groupings. 764 $grouping1 = $generator->create_grouping(array('courseid' => $course1->id)); 765 $grouping2 = $generator->create_grouping(array('courseid' => $course2->id)); 766 767 // Assign and assert group to groupings. 768 groups_assign_grouping($grouping1->id, $group1->id); 769 groups_assign_grouping($grouping2->id, $group2->id); 770 groups_assign_grouping($grouping2->id, $group3->id); 771 772 // Test with one single group. 773 $modinfo = get_fast_modinfo($course1, $user1->id); 774 $groups = $modinfo->get_groups($grouping1->id); 775 $this->assertCount(1, $groups); 776 $this->assertArrayHasKey($group1->id, $groups); 777 778 // Test with two groups. 779 $modinfo = get_fast_modinfo($course2, $user2->id); 780 $groups = $modinfo->get_groups(); 781 $this->assertCount(2, $groups); 782 $this->assertTrue(in_array($group2->id, $groups)); 783 $this->assertTrue(in_array($group3->id, $groups)); 784 785 // Test with no groups. 786 $modinfo = get_fast_modinfo($course3, $user3->id); 787 $groups = $modinfo->get_groups(); 788 $this->assertCount(0, $groups); 789 $this->assertArrayNotHasKey($group1->id, $groups); 790 } 791 792 /** 793 * Tests the function for constructing a cm_info from mixed data. 794 */ 795 public function test_create() { 796 global $CFG, $DB; 797 $this->resetAfterTest(); 798 799 // Create a course and an activity. 800 $generator = $this->getDataGenerator(); 801 $course = $generator->create_course(); 802 $page = $generator->create_module('page', array('course' => $course->id, 803 'name' => 'Annie')); 804 805 // Null is passed through. 806 $this->assertNull(cm_info::create(null)); 807 808 // Stdclass object turns into cm_info. 809 $cm = cm_info::create( 810 (object)array('id' => $page->cmid, 'course' => $course->id)); 811 $this->assertInstanceOf('cm_info', $cm); 812 $this->assertEquals('Annie', $cm->name); 813 814 // A cm_info object stays as cm_info. 815 $this->assertSame($cm, cm_info::create($cm)); 816 817 // Invalid object (missing fields) causes error. 818 try { 819 cm_info::create((object)array('id' => $page->cmid)); 820 $this->fail(); 821 } catch (Exception $e) { 822 $this->assertInstanceOf('coding_exception', $e); 823 } 824 825 // Create a second hidden activity. 826 $hiddenpage = $generator->create_module('page', array('course' => $course->id, 827 'name' => 'Annie', 'visible' => 0)); 828 829 // Create 2 user accounts, one is a manager who can see everything. 830 $user = $generator->create_user(); 831 $generator->enrol_user($user->id, $course->id); 832 $manager = $generator->create_user(); 833 $generator->enrol_user($manager->id, $course->id, 834 $DB->get_field('role', 'id', array('shortname' => 'manager'), MUST_EXIST)); 835 836 // User can see the normal page but not the hidden one. 837 $cm = cm_info::create((object)array('id' => $page->cmid, 'course' => $course->id), 838 $user->id); 839 $this->assertTrue($cm->uservisible); 840 $cm = cm_info::create((object)array('id' => $hiddenpage->cmid, 'course' => $course->id), 841 $user->id); 842 $this->assertFalse($cm->uservisible); 843 844 // Manager can see the hidden one too. 845 $cm = cm_info::create((object)array('id' => $hiddenpage->cmid, 'course' => $course->id), 846 $manager->id); 847 $this->assertTrue($cm->uservisible); 848 } 849 850 /** 851 * Tests function for getting $course and $cm at once quickly from modinfo 852 * based on cmid or cm record. 853 */ 854 public function test_get_course_and_cm_from_cmid() { 855 global $CFG, $DB; 856 $this->resetAfterTest(); 857 858 // Create a course and an activity. 859 $generator = $this->getDataGenerator(); 860 $course = $generator->create_course(array('shortname' => 'Halls')); 861 $page = $generator->create_module('page', array('course' => $course->id, 862 'name' => 'Annie')); 863 864 // Successful usage. 865 list($course, $cm) = get_course_and_cm_from_cmid($page->cmid); 866 $this->assertEquals('Halls', $course->shortname); 867 $this->assertInstanceOf('cm_info', $cm); 868 $this->assertEquals('Annie', $cm->name); 869 870 // Specified module type. 871 list($course, $cm) = get_course_and_cm_from_cmid($page->cmid, 'page'); 872 $this->assertEquals('Annie', $cm->name); 873 874 // With id in object. 875 $fakecm = (object)array('id' => $page->cmid); 876 list($course, $cm) = get_course_and_cm_from_cmid($fakecm); 877 $this->assertEquals('Halls', $course->shortname); 878 $this->assertEquals('Annie', $cm->name); 879 880 // With both id and course in object. 881 $fakecm->course = $course->id; 882 list($course, $cm) = get_course_and_cm_from_cmid($fakecm); 883 $this->assertEquals('Halls', $course->shortname); 884 $this->assertEquals('Annie', $cm->name); 885 886 // With supplied course id. 887 list($course, $cm) = get_course_and_cm_from_cmid($page->cmid, 'page', $course->id); 888 $this->assertEquals('Annie', $cm->name); 889 890 // With supplied course object (modified just so we can check it is 891 // indeed reusing the supplied object). 892 $course->silly = true; 893 list($course, $cm) = get_course_and_cm_from_cmid($page->cmid, 'page', $course); 894 $this->assertEquals('Annie', $cm->name); 895 $this->assertTrue($course->silly); 896 897 // Incorrect module type. 898 try { 899 get_course_and_cm_from_cmid($page->cmid, 'forum'); 900 $this->fail(); 901 } catch (moodle_exception $e) { 902 $this->assertEquals('invalidcoursemoduleid', $e->errorcode); 903 } 904 905 // Invalid module name. 906 try { 907 get_course_and_cm_from_cmid($page->cmid, 'pigs can fly'); 908 $this->fail(); 909 } catch (coding_exception $e) { 910 $this->assertStringContainsString('Invalid modulename parameter', $e->getMessage()); 911 } 912 913 // Doesn't exist. 914 try { 915 get_course_and_cm_from_cmid($page->cmid + 1); 916 $this->fail(); 917 } catch (moodle_exception $e) { 918 $this->assertInstanceOf('dml_exception', $e); 919 } 920 921 // Create a second hidden activity. 922 $hiddenpage = $generator->create_module('page', array('course' => $course->id, 923 'name' => 'Annie', 'visible' => 0)); 924 925 // Create 2 user accounts, one is a manager who can see everything. 926 $user = $generator->create_user(); 927 $generator->enrol_user($user->id, $course->id); 928 $manager = $generator->create_user(); 929 $generator->enrol_user($manager->id, $course->id, 930 $DB->get_field('role', 'id', array('shortname' => 'manager'), MUST_EXIST)); 931 932 // User can see the normal page but not the hidden one. 933 list($course, $cm) = get_course_and_cm_from_cmid($page->cmid, 'page', 0, $user->id); 934 $this->assertTrue($cm->uservisible); 935 list($course, $cm) = get_course_and_cm_from_cmid($hiddenpage->cmid, 'page', 0, $user->id); 936 $this->assertFalse($cm->uservisible); 937 938 // Manager can see the hidden one too. 939 list($course, $cm) = get_course_and_cm_from_cmid($hiddenpage->cmid, 'page', 0, $manager->id); 940 $this->assertTrue($cm->uservisible); 941 } 942 943 /** 944 * Tests function for getting $course and $cm at once quickly from modinfo 945 * based on instance id or record. 946 */ 947 public function test_get_course_and_cm_from_instance() { 948 global $CFG, $DB; 949 $this->resetAfterTest(); 950 951 // Create a course and an activity. 952 $generator = $this->getDataGenerator(); 953 $course = $generator->create_course(array('shortname' => 'Halls')); 954 $page = $generator->create_module('page', array('course' => $course->id, 955 'name' => 'Annie')); 956 957 // Successful usage. 958 list($course, $cm) = get_course_and_cm_from_instance($page->id, 'page'); 959 $this->assertEquals('Halls', $course->shortname); 960 $this->assertInstanceOf('cm_info', $cm); 961 $this->assertEquals('Annie', $cm->name); 962 963 // With id in object. 964 $fakeinstance = (object)array('id' => $page->id); 965 list($course, $cm) = get_course_and_cm_from_instance($fakeinstance, 'page'); 966 $this->assertEquals('Halls', $course->shortname); 967 $this->assertEquals('Annie', $cm->name); 968 969 // With both id and course in object. 970 $fakeinstance->course = $course->id; 971 list($course, $cm) = get_course_and_cm_from_instance($fakeinstance, 'page'); 972 $this->assertEquals('Halls', $course->shortname); 973 $this->assertEquals('Annie', $cm->name); 974 975 // With supplied course id. 976 list($course, $cm) = get_course_and_cm_from_instance($page->id, 'page', $course->id); 977 $this->assertEquals('Annie', $cm->name); 978 979 // With supplied course object (modified just so we can check it is 980 // indeed reusing the supplied object). 981 $course->silly = true; 982 list($course, $cm) = get_course_and_cm_from_instance($page->id, 'page', $course); 983 $this->assertEquals('Annie', $cm->name); 984 $this->assertTrue($course->silly); 985 986 // Doesn't exist (or is wrong type). 987 try { 988 get_course_and_cm_from_instance($page->id, 'forum'); 989 $this->fail(); 990 } catch (moodle_exception $e) { 991 $this->assertInstanceOf('dml_exception', $e); 992 } 993 994 // Invalid module name. 995 try { 996 get_course_and_cm_from_cmid($page->cmid, '1337 h4x0ring'); 997 $this->fail(); 998 } catch (coding_exception $e) { 999 $this->assertStringContainsString('Invalid modulename parameter', $e->getMessage()); 1000 } 1001 1002 // Create a second hidden activity. 1003 $hiddenpage = $generator->create_module('page', array('course' => $course->id, 1004 'name' => 'Annie', 'visible' => 0)); 1005 1006 // Create 2 user accounts, one is a manager who can see everything. 1007 $user = $generator->create_user(); 1008 $generator->enrol_user($user->id, $course->id); 1009 $manager = $generator->create_user(); 1010 $generator->enrol_user($manager->id, $course->id, 1011 $DB->get_field('role', 'id', array('shortname' => 'manager'), MUST_EXIST)); 1012 1013 // User can see the normal page but not the hidden one. 1014 list($course, $cm) = get_course_and_cm_from_cmid($page->cmid, 'page', 0, $user->id); 1015 $this->assertTrue($cm->uservisible); 1016 list($course, $cm) = get_course_and_cm_from_cmid($hiddenpage->cmid, 'page', 0, $user->id); 1017 $this->assertFalse($cm->uservisible); 1018 1019 // Manager can see the hidden one too. 1020 list($course, $cm) = get_course_and_cm_from_cmid($hiddenpage->cmid, 'page', 0, $manager->id); 1021 $this->assertTrue($cm->uservisible); 1022 } 1023 1024 /** 1025 * Test test_get_section_info_by_id method 1026 * 1027 * @dataProvider get_section_info_by_id_provider 1028 * @covers \course_modinfo::get_section_info_by_id 1029 * 1030 * @param int $sectionnum the section number 1031 * @param int $strictness the search strict mode 1032 * @param bool $expectnull if the function will return a null 1033 * @param bool $expectexception if the function will throw an exception 1034 */ 1035 public function test_get_section_info_by_id( 1036 int $sectionnum, 1037 int $strictness = IGNORE_MISSING, 1038 bool $expectnull = false, 1039 bool $expectexception = false 1040 ) { 1041 global $DB; 1042 1043 $this->resetAfterTest(); 1044 1045 // Create a course with 4 sections. 1046 $course = $this->getDataGenerator()->create_course(['numsections' => 4]); 1047 1048 // Index sections. 1049 $sectionindex = []; 1050 $modinfo = get_fast_modinfo($course); 1051 $allsections = $modinfo->get_section_info_all(); 1052 foreach ($allsections as $section) { 1053 $sectionindex[$section->section] = $section->id; 1054 } 1055 1056 if ($expectexception) { 1057 $this->expectException(moodle_exception::class); 1058 } 1059 1060 $sectionid = $sectionindex[$sectionnum] ?? -1; 1061 1062 $section = $modinfo->get_section_info_by_id($sectionid, $strictness); 1063 1064 if ($expectnull) { 1065 $this->assertNull($section); 1066 } else { 1067 $this->assertEquals($sectionid, $section->id); 1068 $this->assertEquals($sectionnum, $section->section); 1069 } 1070 } 1071 1072 /** 1073 * Data provider for test_get_section_info_by_id(). 1074 * 1075 * @return array 1076 */ 1077 public function get_section_info_by_id_provider() { 1078 return [ 1079 'Valid section id' => [ 1080 'sectionnum' => 1, 1081 'strictness' => IGNORE_MISSING, 1082 'expectnull' => false, 1083 'expectexception' => false, 1084 ], 1085 'Section zero' => [ 1086 'sectionnum' => 0, 1087 'strictness' => IGNORE_MISSING, 1088 'expectnull' => false, 1089 'expectexception' => false, 1090 ], 1091 'invalid section ignore missing' => [ 1092 'sectionnum' => -1, 1093 'strictness' => IGNORE_MISSING, 1094 'expectnull' => true, 1095 'expectexception' => false, 1096 ], 1097 'invalid section must exists' => [ 1098 'sectionnum' => -1, 1099 'strictness' => MUST_EXIST, 1100 'expectnull' => false, 1101 'expectexception' => true, 1102 ], 1103 ]; 1104 } 1105 1106 /** 1107 * Test purge_section_cache_by_id method 1108 * 1109 * @covers \course_modinfo::purge_course_section_cache_by_id 1110 * @return void 1111 */ 1112 public function test_purge_section_cache_by_id(): void { 1113 $this->resetAfterTest(); 1114 $this->setAdminUser(); 1115 $cache = cache::make('core', 'coursemodinfo'); 1116 1117 // Generate the course and pre-requisite section. 1118 $course = $this->getDataGenerator()->create_course(['format' => 'topics', 'numsections' => 3], ['createsections' => true]); 1119 // Reset course cache. 1120 rebuild_course_cache($course->id, true); 1121 // Build course cache. 1122 get_fast_modinfo($course->id); 1123 // Get the course modinfo cache. 1124 $coursemodinfo = $cache->get_versioned($course->id, $course->cacherev); 1125 // Get the section cache. 1126 $sectioncaches = $coursemodinfo->sectioncache; 1127 1128 // Make sure that we will have 4 section caches here. 1129 $this->assertCount(4, $sectioncaches); 1130 $this->assertArrayHasKey(0, $sectioncaches); 1131 $this->assertArrayHasKey(1, $sectioncaches); 1132 $this->assertArrayHasKey(2, $sectioncaches); 1133 $this->assertArrayHasKey(3, $sectioncaches); 1134 1135 // Purge cache for the section by id. 1136 course_modinfo::purge_course_section_cache_by_id($course->id, $sectioncaches[1]->id); 1137 // Get the course modinfo cache. 1138 $coursemodinfo = $cache->get_versioned($course->id, $course->cacherev); 1139 // Get the section cache. 1140 $sectioncaches = $coursemodinfo->sectioncache; 1141 1142 // Make sure that we will have 3 section caches left. 1143 $this->assertCount(3, $sectioncaches); 1144 $this->assertArrayNotHasKey(1, $sectioncaches); 1145 $this->assertArrayHasKey(0, $sectioncaches); 1146 $this->assertArrayHasKey(2, $sectioncaches); 1147 $this->assertArrayHasKey(3, $sectioncaches); 1148 // Make sure that the cacherev will be reset. 1149 $this->assertEquals(-1, $coursemodinfo->cacherev); 1150 } 1151 1152 /** 1153 * Test purge_section_cache_by_number method 1154 * 1155 * @covers \course_modinfo::purge_course_section_cache_by_number 1156 * @return void 1157 */ 1158 public function test_section_cache_by_number(): void { 1159 $this->resetAfterTest(); 1160 $this->setAdminUser(); 1161 $cache = cache::make('core', 'coursemodinfo'); 1162 1163 // Generate the course and pre-requisite section. 1164 $course = $this->getDataGenerator()->create_course(['format' => 'topics', 'numsections' => 3], ['createsections' => true]); 1165 // Reset course cache. 1166 rebuild_course_cache($course->id, true); 1167 // Build course cache. 1168 get_fast_modinfo($course->id); 1169 // Get the course modinfo cache. 1170 $coursemodinfo = $cache->get_versioned($course->id, $course->cacherev); 1171 // Get the section cache. 1172 $sectioncaches = $coursemodinfo->sectioncache; 1173 1174 // Make sure that we will have 4 section caches here. 1175 $this->assertCount(4, $sectioncaches); 1176 $this->assertArrayHasKey(0, $sectioncaches); 1177 $this->assertArrayHasKey(1, $sectioncaches); 1178 $this->assertArrayHasKey(2, $sectioncaches); 1179 $this->assertArrayHasKey(3, $sectioncaches); 1180 1181 // Purge cache for the section with section number is 1. 1182 course_modinfo::purge_course_section_cache_by_number($course->id, 1); 1183 // Get the course modinfo cache. 1184 $coursemodinfo = $cache->get_versioned($course->id, $course->cacherev); 1185 // Get the section cache. 1186 $sectioncaches = $coursemodinfo->sectioncache; 1187 1188 // Make sure that we will have 3 section caches left. 1189 $this->assertCount(3, $sectioncaches); 1190 $this->assertArrayNotHasKey(1, $sectioncaches); 1191 $this->assertArrayHasKey(0, $sectioncaches); 1192 $this->assertArrayHasKey(2, $sectioncaches); 1193 $this->assertArrayHasKey(3, $sectioncaches); 1194 // Make sure that the cacherev will be reset. 1195 $this->assertEquals(-1, $coursemodinfo->cacherev); 1196 } 1197 1198 /** 1199 * Purge a single course module from the cache. 1200 * 1201 * @return void 1202 * @covers \course_modinfo::purge_course_module_cache 1203 */ 1204 public function test_purge_course_module(): void { 1205 $this->resetAfterTest(); 1206 $this->setAdminUser(); 1207 $cache = cache::make('core', 'coursemodinfo'); 1208 1209 // Generate the course and pre-requisite section. 1210 $course = $this->getDataGenerator()->create_course(); 1211 $cm1 = $this->getDataGenerator()->create_module('page', ['course' => $course]); 1212 $cm2 = $this->getDataGenerator()->create_module('page', ['course' => $course]); 1213 $cm3 = $this->getDataGenerator()->create_module('page', ['course' => $course]); 1214 $cm4 = $this->getDataGenerator()->create_module('page', ['course' => $course]); 1215 // Reset course cache. 1216 rebuild_course_cache($course->id, true); 1217 // Build course cache. 1218 get_fast_modinfo($course->id); 1219 // Get the course modinfo cache. 1220 $coursemodinfo = $cache->get_versioned($course->id, $course->cacherev); 1221 $this->assertCount(4, $coursemodinfo->modinfo); 1222 $this->assertArrayHasKey($cm1->cmid, $coursemodinfo->modinfo); 1223 $this->assertArrayHasKey($cm2->cmid, $coursemodinfo->modinfo); 1224 $this->assertArrayHasKey($cm3->cmid, $coursemodinfo->modinfo); 1225 $this->assertArrayHasKey($cm4->cmid, $coursemodinfo->modinfo); 1226 1227 course_modinfo::purge_course_module_cache($course->id, $cm1->cmid); 1228 1229 $coursemodinfo = $cache->get_versioned($course->id, $course->cacherev); 1230 $this->assertCount(3, $coursemodinfo->modinfo); 1231 $this->assertArrayNotHasKey($cm1->cmid, $coursemodinfo->modinfo); 1232 $this->assertArrayHasKey($cm2->cmid, $coursemodinfo->modinfo); 1233 $this->assertArrayHasKey($cm3->cmid, $coursemodinfo->modinfo); 1234 $this->assertArrayHasKey($cm4->cmid, $coursemodinfo->modinfo); 1235 // Make sure that the cacherev will be reset. 1236 $this->assertEquals(-1, $coursemodinfo->cacherev); 1237 } 1238 1239 /** 1240 * Purge a multiple course modules from the cache. 1241 * 1242 * @return void 1243 * @covers \course_modinfo::purge_course_modules_cache 1244 */ 1245 public function test_purge_multiple_course_modules(): void { 1246 $this->resetAfterTest(); 1247 $this->setAdminUser(); 1248 $cache = cache::make('core', 'coursemodinfo'); 1249 1250 // Generate the course and pre-requisite section. 1251 $course = $this->getDataGenerator()->create_course(); 1252 $cm1 = $this->getDataGenerator()->create_module('page', ['course' => $course]); 1253 $cm2 = $this->getDataGenerator()->create_module('page', ['course' => $course]); 1254 $cm3 = $this->getDataGenerator()->create_module('page', ['course' => $course]); 1255 $cm4 = $this->getDataGenerator()->create_module('page', ['course' => $course]); 1256 // Reset course cache. 1257 rebuild_course_cache($course->id, true); 1258 // Build course cache. 1259 get_fast_modinfo($course->id); 1260 // Get the course modinfo cache. 1261 $coursemodinfo = $cache->get_versioned($course->id, $course->cacherev); 1262 $this->assertCount(4, $coursemodinfo->modinfo); 1263 $this->assertArrayHasKey($cm1->cmid, $coursemodinfo->modinfo); 1264 $this->assertArrayHasKey($cm2->cmid, $coursemodinfo->modinfo); 1265 $this->assertArrayHasKey($cm3->cmid, $coursemodinfo->modinfo); 1266 $this->assertArrayHasKey($cm4->cmid, $coursemodinfo->modinfo); 1267 1268 course_modinfo::purge_course_modules_cache($course->id, [$cm2->cmid, $cm3->cmid]); 1269 1270 $coursemodinfo = $cache->get_versioned($course->id, $course->cacherev); 1271 $this->assertCount(2, $coursemodinfo->modinfo); 1272 $this->assertArrayHasKey($cm1->cmid, $coursemodinfo->modinfo); 1273 $this->assertArrayNotHasKey($cm2->cmid, $coursemodinfo->modinfo); 1274 $this->assertArrayNotHasKey($cm3->cmid, $coursemodinfo->modinfo); 1275 $this->assertArrayHasKey($cm4->cmid, $coursemodinfo->modinfo); 1276 // Make sure that the cacherev will be reset. 1277 $this->assertEquals(-1, $coursemodinfo->cacherev); 1278 } 1279 1280 /** 1281 * Test get_cm() method to output course module id in the exception text. 1282 * 1283 * @covers \course_modinfo::get_cm 1284 * @return void 1285 */ 1286 public function test_invalid_course_module_id(): void { 1287 global $DB; 1288 $this->resetAfterTest(); 1289 1290 $course = $this->getDataGenerator()->create_course(); 1291 $forum0 = $this->getDataGenerator()->create_module('assign', ['course' => $course->id], ['section' => 0]); 1292 $forum1 = $this->getDataGenerator()->create_module('assign', ['course' => $course->id], ['section' => 0]); 1293 $forum2 = $this->getDataGenerator()->create_module('assign', ['course' => $course->id], ['section' => 0]); 1294 1295 // Break section sequence. 1296 $modinfo = get_fast_modinfo($course->id); 1297 $sectionid = $modinfo->get_section_info(0)->id; 1298 $section = $DB->get_record('course_sections', ['id' => $sectionid]); 1299 $sequence = explode(',', $section->sequence); 1300 $sequence = array_diff($sequence, [$forum1->cmid]); 1301 $section->sequence = implode(',', $sequence); 1302 $DB->update_record('course_sections', $section); 1303 1304 // Assert exception text. 1305 $this->expectException(\moodle_exception::class); 1306 $this->expectExceptionMessage('Invalid course module ID: ' . $forum1->cmid); 1307 delete_course($course, false); 1308 } 1309 1310 /** 1311 * Tests that if the modinfo cache returns a newer-than-expected version, Moodle won't rebuild 1312 * it. 1313 * 1314 * This is important to avoid wasted time/effort and poor performance, for example in cases 1315 * where multiple requests are accessing the course. 1316 * 1317 * Certain cases could be particularly bad if this test fails. For example, if using clustered 1318 * databases where there is a 100ms delay between updates to the course table being available 1319 * to all users (but no such delay on the cache infrastructure), then during that 100ms, every 1320 * request that calls get_fast_modinfo and uses the read-only database will rebuild the course 1321 * cache. Since these will then create a still-newer version, future requests for the next 1322 * 100ms will also rebuild it again... etc. 1323 * 1324 * @covers \course_modinfo 1325 */ 1326 public function test_get_modinfo_with_newer_version(): void { 1327 global $DB; 1328 1329 $this->resetAfterTest(); 1330 1331 // Get info about a course and build the initial cache, then drop it from memory. 1332 $course = $this->getDataGenerator()->create_course(); 1333 get_fast_modinfo($course); 1334 get_fast_modinfo(0, 0, true); 1335 1336 // User A starts a request, which takes some time... 1337 $useracourse = $DB->get_record('course', ['id' => $course->id]); 1338 1339 // User B also starts a request and makes a change to the course. 1340 $userbcourse = $DB->get_record('course', ['id' => $course->id]); 1341 $this->getDataGenerator()->create_module('page', ['course' => $course->id]); 1342 rebuild_course_cache($userbcourse->id, false); 1343 1344 // Finally, user A's request now gets modinfo. It should accept the version from B even 1345 // though the course version (of cache) is newer than the one expected by A. 1346 $before = $DB->perf_get_queries(); 1347 $modinfo = get_fast_modinfo($useracourse); 1348 $after = $DB->perf_get_queries(); 1349 $this->assertEquals($after, $before, 'Should use cached version, making no DB queries'); 1350 1351 // Obviously, modinfo should include the Page now. 1352 $this->assertCount(1, $modinfo->get_instances_of('page')); 1353 } 1354 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body