Rev 1 | Ir a la última revisión | Autoría | Comparar con el anterior | Ultima modificación | Ver Log |
<?php// This file is part of Moodle - http://moodle.org///// Moodle is free software: you can redistribute it and/or modify// it under the terms of the GNU General Public License as published by// the Free Software Foundation, either version 3 of the License, or// (at your option) any later version.//// Moodle is distributed in the hope that it will be useful,// but WITHOUT ANY WARRANTY; without even the implied warranty of// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the// GNU General Public License for more details.//// You should have received a copy of the GNU General Public License// along with Moodle. If not, see <http://www.gnu.org/licenses/>.namespace core;use advanced_testcase;use cache;use cm_info;use coding_exception;use context_course;use context_module;use course_modinfo;use core_courseformat\formatactions;use moodle_exception;use moodle_url;use Exception;/*** Unit tests for lib/modinfolib.php.** @package core* @category phpunit* @copyright 2012 Andrew Davis* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later*/class modinfolib_test extends advanced_testcase {/*** Setup to ensure that fixtures are loaded.*/public static function setUpBeforeClass(): void {global $CFG;require_once($CFG->dirroot . '/course/lib.php');require_once($CFG->libdir . '/tests/fixtures/sectiondelegatetest.php');}public function test_section_info_properties(): void {global $DB, $CFG;$this->resetAfterTest();$oldcfgenableavailability = $CFG->enableavailability;$oldcfgenablecompletion = $CFG->enablecompletion;set_config('enableavailability', 1);set_config('enablecompletion', 1);$this->setAdminUser();// Generate the course and pre-requisite module.$course = $this->getDataGenerator()->create_course(array('format' => 'topics','numsections' => 3,'enablecompletion' => 1,'groupmode' => SEPARATEGROUPS,'forcegroupmode' => 0),array('createsections' => true));$coursecontext = context_course::instance($course->id);$prereqforum = $this->getDataGenerator()->create_module('forum',array('course' => $course->id),array('completion' => 1));// Add availability conditions.$availability = '{"op":"&","showc":[true,true,true],"c":[' .'{"type":"completion","cm":' . $prereqforum->cmid . ',"e":"' .COMPLETION_COMPLETE . '"},' .'{"type":"grade","id":666,"min":0.4},' .'{"type":"profile","op":"contains","sf":"email","v":"test"}' .']}';$DB->set_field('course_sections', 'availability', $availability,array('course' => $course->id, 'section' => 2));rebuild_course_cache($course->id, true);$sectiondb = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 2));// Create and enrol a student.$studentrole = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);$student = $this->getDataGenerator()->create_user();role_assign($studentrole->id, $student->id, $coursecontext);$enrolplugin = enrol_get_plugin('manual');$enrolinstance = $DB->get_record('enrol', array('courseid' => $course->id, 'enrol' => 'manual'));$enrolplugin->enrol_user($enrolinstance, $student->id);$this->setUser($student);// Get modinfo.$modinfo = get_fast_modinfo($course->id);$si = $modinfo->get_section_info(2);$this->assertEquals($sectiondb->id, $si->id);$this->assertEquals($sectiondb->course, $si->course);$this->assertEquals($sectiondb->section, $si->section);$this->assertEquals($sectiondb->name, $si->name);$this->assertEquals($sectiondb->visible, $si->visible);$this->assertEquals($sectiondb->summary, $si->summary);$this->assertEquals($sectiondb->summaryformat, $si->summaryformat);$this->assertEquals($sectiondb->sequence, $si->sequence); // Since this section does not contain invalid modules.$this->assertEquals($availability, $si->availability);// Dynamic fields, just test that they can be retrieved (must be carefully tested in each activity type).$this->assertEquals(0, $si->available);$this->assertNotEmpty($si->availableinfo); // Lists all unmet availability conditions.$this->assertEquals(0, $si->uservisible);// Restore settings.set_config('enableavailability', $oldcfgenableavailability);set_config('enablecompletion', $oldcfgenablecompletion);}public function test_cm_info_properties(): void {global $DB, $CFG;$this->resetAfterTest();$oldcfgenableavailability = $CFG->enableavailability;$oldcfgenablecompletion = $CFG->enablecompletion;set_config('enableavailability', 1);set_config('enablecompletion', 1);$this->setAdminUser();// Generate the course and pre-requisite module.$course = $this->getDataGenerator()->create_course(array('format' => 'topics','numsections' => 3,'enablecompletion' => 1,'groupmode' => SEPARATEGROUPS,'forcegroupmode' => 0),array('createsections' => true));$coursecontext = context_course::instance($course->id);$prereqforum = $this->getDataGenerator()->create_module('forum',array('course' => $course->id),array('completion' => 1));// Generate module and add availability conditions.$availability = '{"op":"&","showc":[true,true,true],"c":[' .'{"type":"completion","cm":' . $prereqforum->cmid . ',"e":"' .COMPLETION_COMPLETE . '"},' .'{"type":"grade","id":666,"min":0.4},' .'{"type":"profile","op":"contains","sf":"email","v":"test"}' .']}';$assign = $this->getDataGenerator()->create_module('assign',array('course' => $course->id),array('idnumber' => 123,'groupmode' => VISIBLEGROUPS,'availability' => $availability));rebuild_course_cache($course->id, true);// Retrieve all related records from DB.$assigndb = $DB->get_record('assign', array('id' => $assign->id));$moduletypedb = $DB->get_record('modules', array('name' => 'assign'));$moduledb = $DB->get_record('course_modules', array('module' => $moduletypedb->id, 'instance' => $assign->id));$sectiondb = $DB->get_record('course_sections', array('id' => $moduledb->section));$modnamessingular = get_module_types_names(false);$modnamesplural = get_module_types_names(true);// Create and enrol a student.$studentrole = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);$student = $this->getDataGenerator()->create_user();role_assign($studentrole->id, $student->id, $coursecontext);$enrolplugin = enrol_get_plugin('manual');$enrolinstance = $DB->get_record('enrol', array('courseid' => $course->id, 'enrol' => 'manual'));$enrolplugin->enrol_user($enrolinstance, $student->id);$this->setUser($student);// Emulate data used in building course cache to receive the same instance of cached_cm_info as was used in building modinfo.$rawmods = get_course_mods($course->id);$cachedcminfo = assign_get_coursemodule_info($rawmods[$moduledb->id]);// Get modinfo.$modinfo = get_fast_modinfo($course->id);$cm = $modinfo->instances['assign'][$assign->id];$this->assertEquals($moduledb->id, $cm->id);$this->assertEquals($assigndb->id, $cm->instance);$this->assertEquals($moduledb->course, $cm->course);$this->assertEquals($moduledb->idnumber, $cm->idnumber);$this->assertEquals($moduledb->added, $cm->added);$this->assertEquals($moduledb->visible, $cm->visible);$this->assertEquals($moduledb->visibleold, $cm->visibleold);$this->assertEquals($moduledb->groupmode, $cm->groupmode);$this->assertEquals(VISIBLEGROUPS, $cm->groupmode);$this->assertEquals($moduledb->groupingid, $cm->groupingid);$this->assertEquals($course->groupmodeforce, $cm->coursegroupmodeforce);$this->assertEquals($course->groupmode, $cm->coursegroupmode);$this->assertEquals(SEPARATEGROUPS, $cm->coursegroupmode);$this->assertEquals($course->groupmodeforce ? $course->groupmode : $moduledb->groupmode,$cm->effectivegroupmode); // (since mod_assign supports groups).$this->assertEquals(VISIBLEGROUPS, $cm->effectivegroupmode);$this->assertEquals($moduledb->indent, $cm->indent);$this->assertEquals($moduledb->completion, $cm->completion);$this->assertEquals($moduledb->completiongradeitemnumber, $cm->completiongradeitemnumber);$this->assertEquals($moduledb->completionpassgrade, $cm->completionpassgrade);$this->assertEquals($moduledb->completionview, $cm->completionview);$this->assertEquals($moduledb->completionexpected, $cm->completionexpected);$this->assertEquals($moduledb->showdescription, $cm->showdescription);$this->assertEquals(null, $cm->extra); // Deprecated field. Used in module types that don't return cached_cm_info.$this->assertEquals($cachedcminfo->icon, $cm->icon);$this->assertEquals($cachedcminfo->iconcomponent, $cm->iconcomponent);$this->assertEquals('assign', $cm->modname);$this->assertEquals($moduledb->module, $cm->module);$this->assertEquals($cachedcminfo->name, $cm->name);$this->assertEquals($sectiondb->section, $cm->sectionnum);$this->assertEquals($moduledb->section, $cm->section);$this->assertEquals($availability, $cm->availability);$this->assertEquals(context_module::instance($moduledb->id), $cm->context);$this->assertEquals($modnamessingular['assign'], $cm->modfullname);$this->assertEquals($modnamesplural['assign'], $cm->modplural);$this->assertEquals(new moodle_url('/mod/assign/view.php', array('id' => $moduledb->id)), $cm->url);$this->assertEquals($cachedcminfo->customdata, $cm->customdata);// Dynamic fields, just test that they can be retrieved (must be carefully tested in each activity type).$this->assertNotEmpty($cm->availableinfo); // Lists all unmet availability conditions.$this->assertEquals(0, $cm->uservisible);$this->assertEquals('', $cm->extraclasses);$this->assertEquals('', $cm->onclick);$this->assertEquals(null, $cm->afterlink);$this->assertEquals(null, $cm->afterediticons);$this->assertEquals('', $cm->content);// Attempt to access and set non-existing field.$this->assertTrue(empty($modinfo->somefield));$this->assertFalse(isset($modinfo->somefield));$cm->somefield;$this->assertDebuggingCalled();$cm->somefield = 'Some value';$this->assertDebuggingCalled();$this->assertEmpty($cm->somefield);$this->assertDebuggingCalled();// Attempt to overwrite an existing field.$prevvalue = $cm->name;$this->assertNotEmpty($cm->name);$this->assertFalse(empty($cm->name));$this->assertTrue(isset($cm->name));$cm->name = 'Illegal overwriting';$this->assertDebuggingCalled();$this->assertEquals($prevvalue, $cm->name);$this->assertDebuggingNotCalled();// Restore settings.set_config('enableavailability', $oldcfgenableavailability);set_config('enablecompletion', $oldcfgenablecompletion);}public function test_matching_cacherev(): void {global $DB, $CFG;$this->resetAfterTest();$this->setAdminUser();$cache = cache::make('core', 'coursemodinfo');// Generate the course and pre-requisite module.$course = $this->getDataGenerator()->create_course(array('format' => 'topics','numsections' => 3),array('createsections' => true));// Make sure the cacherev is set.$cacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));$this->assertGreaterThan(0, $cacherev);$prevcacherev = $cacherev;// Reset course cache and make sure cacherev is bumped up but cache is empty.rebuild_course_cache($course->id, true);$cacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));$this->assertGreaterThan($prevcacherev, $cacherev);$this->assertEmpty($cache->get_versioned($course->id, $prevcacherev));$prevcacherev = $cacherev;// Build course cache. Cacherev should not change but cache is now not empty. Make sure cacherev is the same everywhere.$modinfo = get_fast_modinfo($course->id);$cacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));$this->assertEquals($prevcacherev, $cacherev);$cachedvalue = $cache->get_versioned($course->id, $cacherev);$this->assertNotEmpty($cachedvalue);$this->assertEquals($cacherev, $cachedvalue->cacherev);$this->assertEquals($cacherev, $modinfo->get_course()->cacherev);$prevcacherev = $cacherev;// 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.$cache->acquire_lock($course->id);$cache->set_versioned($course->id, $cacherev, (object)array_merge((array)$cachedvalue, array('secretfield' => 1)));$cache->release_lock($course->id);// Clear static cache and call get_fast_modinfo() again (pretend we are in another request). Cache should not be rebuilt.course_modinfo::clear_instance_cache();$modinfo = get_fast_modinfo($course->id);$cacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));$this->assertEquals($prevcacherev, $cacherev);$cachedvalue = $cache->get_versioned($course->id, $cacherev);$this->assertNotEmpty($cachedvalue);$this->assertEquals($cacherev, $cachedvalue->cacherev);$this->assertNotEmpty($cachedvalue->secretfield);$this->assertEquals($cacherev, $modinfo->get_course()->cacherev);$prevcacherev = $cacherev;// Rebuild course cache. Cacherev must be incremented everywhere.rebuild_course_cache($course->id);$cacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));$this->assertGreaterThan($prevcacherev, $cacherev);$cachedvalue = $cache->get_versioned($course->id, $cacherev);$this->assertNotEmpty($cachedvalue);$this->assertEquals($cacherev, $cachedvalue->cacherev);$modinfo = get_fast_modinfo($course->id);$this->assertEquals($cacherev, $modinfo->get_course()->cacherev);$prevcacherev = $cacherev;// Update cacherev in DB and make sure the cache will be rebuilt on the next call to get_fast_modinfo().increment_revision_number('course', 'cacherev', 'id = ?', array($course->id));// We need to clear static cache for course_modinfo instances too.course_modinfo::clear_instance_cache();$modinfo = get_fast_modinfo($course->id);$cacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));$this->assertGreaterThan($prevcacherev, $cacherev);$cachedvalue = $cache->get_versioned($course->id, $cacherev);$this->assertNotEmpty($cachedvalue);$this->assertEquals($cacherev, $cachedvalue->cacherev);$this->assertEquals($cacherev, $modinfo->get_course()->cacherev);$prevcacherev = $cacherev;// Reset cache for all courses and make sure this course cache is reset.rebuild_course_cache(0, true);$cacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));$this->assertGreaterThan($prevcacherev, $cacherev);$this->assertEmpty($cache->get_versioned($course->id, $cacherev));// Rebuild again.$modinfo = get_fast_modinfo($course->id);$cachedvalue = $cache->get_versioned($course->id, $cacherev);$this->assertNotEmpty($cachedvalue);$this->assertEquals($cacherev, $cachedvalue->cacherev);$this->assertEquals($cacherev, $modinfo->get_course()->cacherev);$prevcacherev = $cacherev;// Purge all caches and make sure cacherev is increased and data from MUC erased.purge_all_caches();$cacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));$this->assertGreaterThan($prevcacherev, $cacherev);$this->assertEmpty($cache->get($course->id));}/*** The cacherev is updated when we rebuild course cache, but there are scenarios where an* existing course object with old cacherev might be reused within the same request after* clearing the cache. In that case, we need to check that the new data is loaded and it* does not reuse the old cached data with old cacherev.** @covers ::rebuild_course_cache()*/public function test_cache_clear_wrong_cacherev(): void {global $DB;$this->resetAfterTest();$originalcourse = $this->getDataGenerator()->create_course();$course = $DB->get_record('course', ['id' => $originalcourse->id]);$page = $this->getDataGenerator()->create_module('page',['course' => $course->id, 'name' => 'frog']);$oldmodinfo = get_fast_modinfo($course);$this->assertEquals('frog', $oldmodinfo->get_cm($page->cmid)->name);// Change page name and rebuild cache.$DB->set_field('page', 'name', 'Frog', ['id' => $page->id]);rebuild_course_cache($course->id, true);// Get modinfo using original course object which has old cacherev.$newmodinfo = get_fast_modinfo($course);$this->assertEquals('Frog', $newmodinfo->get_cm($page->cmid)->name);}/*** When cacherev is updated for a course, it is supposed to update in the $COURSE and $SITE* globals automatically. Check this is working.** @covers ::rebuild_course_cache()*/public function test_cacherev_update_in_globals(): void {global $DB, $COURSE, $SITE;$this->resetAfterTest();// Create a course and get modinfo.$originalcourse = $this->getDataGenerator()->create_course();$oldmodinfo = get_fast_modinfo($originalcourse->id);// Store (two clones of) the course in COURSE and SITE globals.$COURSE = get_course($originalcourse->id);$SITE = get_course($originalcourse->id);// Note original cacherev.$originalcacherev = $oldmodinfo->get_course()->cacherev;$this->assertEquals($COURSE->cacherev, $originalcacherev);$this->assertEquals($SITE->cacherev, $originalcacherev);// Clear the cache and check cacherev updated.rebuild_course_cache($originalcourse->id, true);$newcourse = $DB->get_record('course', ['id' => $originalcourse->id]);$this->assertGreaterThan($originalcacherev, $newcourse->cacherev);// Check that the in-memory $COURSE and $SITE have updated.$this->assertEquals($newcourse->cacherev, $COURSE->cacherev);$this->assertEquals($newcourse->cacherev, $SITE->cacherev);}public function test_course_modinfo_properties(): void {global $USER, $DB;$this->resetAfterTest();$this->setAdminUser();// Generate the course and some modules. Make one section hidden.$course = $this->getDataGenerator()->create_course(array('format' => 'topics','numsections' => 3),array('createsections' => true));$DB->execute('UPDATE {course_sections} SET visible = 0 WHERE course = ? and section = ?',array($course->id, 3));$coursecontext = context_course::instance($course->id);$forum0 = $this->getDataGenerator()->create_module('forum',array('course' => $course->id), array('section' => 0));$assign0 = $this->getDataGenerator()->create_module('assign',array('course' => $course->id), array('section' => 0, 'visible' => 0));$forum1 = $this->getDataGenerator()->create_module('forum',array('course' => $course->id), array('section' => 1));$assign1 = $this->getDataGenerator()->create_module('assign',array('course' => $course->id), array('section' => 1));$page1 = $this->getDataGenerator()->create_module('page',array('course' => $course->id), array('section' => 1));$page3 = $this->getDataGenerator()->create_module('page',array('course' => $course->id), array('section' => 3));$modinfo = get_fast_modinfo($course->id);$this->assertEquals(array($forum0->cmid, $assign0->cmid, $forum1->cmid, $assign1->cmid, $page1->cmid, $page3->cmid),array_keys($modinfo->cms));$this->assertEquals($course->id, $modinfo->courseid);$this->assertEquals($USER->id, $modinfo->userid);$this->assertEquals(array(0 => array($forum0->cmid, $assign0->cmid),1 => array($forum1->cmid, $assign1->cmid, $page1->cmid), 3 => array($page3->cmid)), $modinfo->sections);$this->assertEquals(array('forum', 'assign', 'page'), array_keys($modinfo->instances));$this->assertEquals(array($assign0->id, $assign1->id), array_keys($modinfo->instances['assign']));$this->assertEquals(array($forum0->id, $forum1->id), array_keys($modinfo->instances['forum']));$this->assertEquals(array($page1->id, $page3->id), array_keys($modinfo->instances['page']));$this->assertEquals(groups_get_user_groups($course->id), $modinfo->groups);$this->assertEquals(array(0 => array($forum0->cmid, $assign0->cmid),1 => array($forum1->cmid, $assign1->cmid, $page1->cmid),3 => array($page3->cmid)), $modinfo->get_sections());$this->assertEquals(array(0, 1, 2, 3), array_keys($modinfo->get_section_info_all()));$this->assertEquals($forum0->cmid . ',' . $assign0->cmid, $modinfo->get_section_info(0)->sequence);$this->assertEquals($forum1->cmid . ',' . $assign1->cmid . ',' . $page1->cmid, $modinfo->get_section_info(1)->sequence);$this->assertEquals('', $modinfo->get_section_info(2)->sequence);$this->assertEquals($page3->cmid, $modinfo->get_section_info(3)->sequence);$this->assertEquals($course->id, $modinfo->get_course()->id);$names = array_keys($modinfo->get_used_module_names());sort($names);$this->assertEquals(array('assign', 'forum', 'page'), $names);$names = array_keys($modinfo->get_used_module_names(true));sort($names);$this->assertEquals(array('assign', 'forum', 'page'), $names);// Admin can see hidden modules/sections.$this->assertTrue($modinfo->cms[$assign0->cmid]->uservisible);$this->assertTrue($modinfo->get_section_info(3)->uservisible);// Get modinfo for non-current user (without capability to view hidden activities/sections).$user = $this->getDataGenerator()->create_user();$modinfo = get_fast_modinfo($course->id, $user->id);$this->assertEquals($user->id, $modinfo->userid);$this->assertFalse($modinfo->cms[$assign0->cmid]->uservisible);$this->assertFalse($modinfo->get_section_info(3)->uservisible);// Attempt to access and set non-existing field.$this->assertTrue(empty($modinfo->somefield));$this->assertFalse(isset($modinfo->somefield));$modinfo->somefield;$this->assertDebuggingCalled();$modinfo->somefield = 'Some value';$this->assertDebuggingCalled();$this->assertEmpty($modinfo->somefield);$this->assertDebuggingCalled();// Attempt to overwrite existing field.$this->assertFalse(empty($modinfo->cms));$this->assertTrue(isset($modinfo->cms));$modinfo->cms = 'Illegal overwriting';$this->assertDebuggingCalled();$this->assertNotEquals('Illegal overwriting', $modinfo->cms);}public function test_is_user_access_restricted_by_capability(): void {global $DB;$this->resetAfterTest();// Create a course and a mod_assign instance.$course = $this->getDataGenerator()->create_course();$assign = $this->getDataGenerator()->create_module('assign', array('course'=>$course->id));// Create and enrol a student.$coursecontext = context_course::instance($course->id);$studentrole = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);$student = $this->getDataGenerator()->create_user();role_assign($studentrole->id, $student->id, $coursecontext);$enrolplugin = enrol_get_plugin('manual');$enrolinstance = $DB->get_record('enrol', array('courseid' => $course->id, 'enrol' => 'manual'));$enrolplugin->enrol_user($enrolinstance, $student->id);$this->setUser($student);// Make sure student can see the module.$cm = get_fast_modinfo($course->id)->instances['assign'][$assign->id];$this->assertTrue($cm->uservisible);$this->assertFalse($cm->is_user_access_restricted_by_capability());// Prohibit student to view mod_assign for the course.role_change_permission($studentrole->id, $coursecontext, 'mod/assign:view', CAP_PROHIBIT);get_fast_modinfo($course->id, 0, true);$cm = get_fast_modinfo($course->id)->instances['assign'][$assign->id];$this->assertFalse($cm->uservisible);$this->assertTrue($cm->is_user_access_restricted_by_capability());// Restore permission to student to view mod_assign for the course.role_change_permission($studentrole->id, $coursecontext, 'mod/assign:view', CAP_INHERIT);get_fast_modinfo($course->id, 0, true);$cm = get_fast_modinfo($course->id)->instances['assign'][$assign->id];$this->assertTrue($cm->uservisible);$this->assertFalse($cm->is_user_access_restricted_by_capability());// Prohibit student to view mod_assign for the particular module.role_change_permission($studentrole->id, context_module::instance($cm->id), 'mod/assign:view', CAP_PROHIBIT);get_fast_modinfo($course->id, 0, true);$cm = get_fast_modinfo($course->id)->instances['assign'][$assign->id];$this->assertFalse($cm->uservisible);$this->assertTrue($cm->is_user_access_restricted_by_capability());// Check calling get_fast_modinfo() for different user:$this->setAdminUser();$cm = get_fast_modinfo($course->id)->instances['assign'][$assign->id];$this->assertTrue($cm->uservisible);$this->assertFalse($cm->is_user_access_restricted_by_capability());$cm = get_fast_modinfo($course->id, $student->id)->instances['assign'][$assign->id];$this->assertFalse($cm->uservisible);$this->assertTrue($cm->is_user_access_restricted_by_capability());}/*** Tests for function cm_info::get_course_module_record()*/public function test_cm_info_get_course_module_record(): void {global $DB;$this->resetAfterTest();$this->setAdminUser();set_config('enableavailability', 1);set_config('enablecompletion', 1);$course = $this->getDataGenerator()->create_course(array('format' => 'topics', 'numsections' => 3, 'enablecompletion' => 1),array('createsections' => true));$mods = array();$mods[0] = $this->getDataGenerator()->create_module('forum', array('course' => $course->id));$mods[1] = $this->getDataGenerator()->create_module('assign',array('course' => $course->id,'section' => 3,'idnumber' => '12345','showdescription' => true));// Pick a small valid availability value to use.$availabilityvalue = '{"op":"|","show":true,"c":[{"type":"date","d":">=","t":4}]}';$mods[2] = $this->getDataGenerator()->create_module('book',array('course' => $course->id,'indent' => 5,'availability' => $availabilityvalue,'showdescription' => false,'completion' => true,'completionview' => true,'completionexpected' => time() + 5000,));$mods[3] = $this->getDataGenerator()->create_module('forum',array('course' => $course->id,'visible' => 0,'groupmode' => 1,'availability' => null));$mods[4] = $this->getDataGenerator()->create_module('forum',array('course' => $course->id,'grouping' => 12));$modinfo = get_fast_modinfo($course->id);// Make sure that object returned by get_course_module_record(false) has exactly the same fields as DB table 'course_modules'.$dbfields = array_keys($DB->get_columns('course_modules'));sort($dbfields);$cmrecord = $modinfo->get_cm($mods[0]->cmid)->get_course_module_record();$cmrecordfields = array_keys((array)$cmrecord);sort($cmrecordfields);$this->assertEquals($dbfields, $cmrecordfields);// Make sure that object returned by get_course_module_record(true) has exactly the same fields// as object returned by get_coursemodule_from_id(,,,true,);$cmrecordfull = $modinfo->get_cm($mods[0]->cmid)->get_course_module_record(true);$cmrecordfullfields = array_keys((array)$cmrecordfull);$cm = get_coursemodule_from_id(null, $mods[0]->cmid, 0, true, MUST_EXIST);$cmfields = array_keys((array)$cm);$this->assertEquals($cmfields, $cmrecordfullfields);// Make sure that object returned by get_course_module_record(true) has exactly the same fields// as object returned by get_coursemodule_from_instance(,,,true,);$cm = get_coursemodule_from_instance('forum', $mods[0]->id, null, true, MUST_EXIST);$cmfields = array_keys((array)$cm);$this->assertEquals($cmfields, $cmrecordfullfields);// Make sure the objects have the same properties.$cm1 = get_coursemodule_from_id(null, $mods[0]->cmid, 0, true, MUST_EXIST);$cm2 = get_coursemodule_from_instance('forum', $mods[0]->id, 0, true, MUST_EXIST);$cminfo = $modinfo->get_cm($mods[0]->cmid);$record = $DB->get_record('course_modules', array('id' => $mods[0]->cmid));$this->assertEquals($record, $cminfo->get_course_module_record());$this->assertEquals($cm1, $cminfo->get_course_module_record(true));$this->assertEquals($cm2, $cminfo->get_course_module_record(true));$cm1 = get_coursemodule_from_id(null, $mods[1]->cmid, 0, true, MUST_EXIST);$cm2 = get_coursemodule_from_instance('assign', $mods[1]->id, 0, true, MUST_EXIST);$cminfo = $modinfo->get_cm($mods[1]->cmid);$record = $DB->get_record('course_modules', array('id' => $mods[1]->cmid));$this->assertEquals($record, $cminfo->get_course_module_record());$this->assertEquals($cm1, $cminfo->get_course_module_record(true));$this->assertEquals($cm2, $cminfo->get_course_module_record(true));$cm1 = get_coursemodule_from_id(null, $mods[2]->cmid, 0, true, MUST_EXIST);$cm2 = get_coursemodule_from_instance('book', $mods[2]->id, 0, true, MUST_EXIST);$cminfo = $modinfo->get_cm($mods[2]->cmid);$record = $DB->get_record('course_modules', array('id' => $mods[2]->cmid));$this->assertEquals($record, $cminfo->get_course_module_record());$this->assertEquals($cm1, $cminfo->get_course_module_record(true));$this->assertEquals($cm2, $cminfo->get_course_module_record(true));$cm1 = get_coursemodule_from_id(null, $mods[3]->cmid, 0, true, MUST_EXIST);$cm2 = get_coursemodule_from_instance('forum', $mods[3]->id, 0, true, MUST_EXIST);$cminfo = $modinfo->get_cm($mods[3]->cmid);$record = $DB->get_record('course_modules', array('id' => $mods[3]->cmid));$this->assertEquals($record, $cminfo->get_course_module_record());$this->assertEquals($cm1, $cminfo->get_course_module_record(true));$this->assertEquals($cm2, $cminfo->get_course_module_record(true));$cm1 = get_coursemodule_from_id(null, $mods[4]->cmid, 0, true, MUST_EXIST);$cm2 = get_coursemodule_from_instance('forum', $mods[4]->id, 0, true, MUST_EXIST);$cminfo = $modinfo->get_cm($mods[4]->cmid);$record = $DB->get_record('course_modules', array('id' => $mods[4]->cmid));$this->assertEquals($record, $cminfo->get_course_module_record());$this->assertEquals($cm1, $cminfo->get_course_module_record(true));$this->assertEquals($cm2, $cminfo->get_course_module_record(true));}/*** Tests for function cm_info::get_activitybadge().** @covers \cm_info::get_activitybadge*/public function test_cm_info_get_activitybadge(): void {global $PAGE;$this->resetAfterTest();$this->setAdminUser();$course = $this->getDataGenerator()->create_course();$forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);$resource = $this->getDataGenerator()->create_module('resource', ['course' => $course->id]);$assign = $this->getDataGenerator()->create_module('assign', ['course' => $course->id]);$label = $this->getDataGenerator()->create_module('label', ['course' => $course->id]);$renderer = $PAGE->get_renderer('core');$modinfo = get_fast_modinfo($course->id);// Forum and resource implements the activitybadge feature.$cminfo = $modinfo->get_cm($forum->cmid);$this->assertNotNull($cminfo->get_activitybadge($renderer));$cminfo = $modinfo->get_cm($resource->cmid);$this->assertNotNull($cminfo->get_activitybadge($renderer));// Assign and label don't implement the activitybadge feature (at least for now).$cminfo = $modinfo->get_cm($assign->cmid);$this->assertNull($cminfo->get_activitybadge($renderer));$cminfo = $modinfo->get_cm($label->cmid);$this->assertNull($cminfo->get_activitybadge($renderer));}/*** Tests the availability property that has been added to course modules* and sections (just to see that it is correctly saved and accessed).*/public function test_availability_property(): void {global $DB, $CFG;$this->resetAfterTest();// Create a course with two modules and three sections.$course = $this->getDataGenerator()->create_course(array('format' => 'topics', 'numsections' => 3),array('createsections' => true));$forum = $this->getDataGenerator()->create_module('forum',array('course' => $course->id));$forum2 = $this->getDataGenerator()->create_module('forum',array('course' => $course->id));// Get modinfo. Check that availability is null for both cm and sections.$modinfo = get_fast_modinfo($course->id);$cm = $modinfo->get_cm($forum->cmid);$this->assertNull($cm->availability);$section = $modinfo->get_section_info(1, MUST_EXIST);$this->assertNull($section->availability);// Update availability for cm and section in database.$DB->set_field('course_modules', 'availability', '{}', array('id' => $cm->id));$DB->set_field('course_sections', 'availability', '{}', array('id' => $section->id));// Clear cache and get modinfo again.rebuild_course_cache($course->id, true);get_fast_modinfo(0, 0, true);$modinfo = get_fast_modinfo($course->id);// Check values that were changed.$cm = $modinfo->get_cm($forum->cmid);$this->assertEquals('{}', $cm->availability);$section = $modinfo->get_section_info(1, MUST_EXIST);$this->assertEquals('{}', $section->availability);// Check other values are still null.$cm = $modinfo->get_cm($forum2->cmid);$this->assertNull($cm->availability);$section = $modinfo->get_section_info(2, MUST_EXIST);$this->assertNull($section->availability);}/*** Tests for get_groups() method.*/public function test_get_groups(): void {$this->resetAfterTest();$generator = $this->getDataGenerator();// Create courses.$course1 = $generator->create_course();$course2 = $generator->create_course();$course3 = $generator->create_course();// Create users.$user1 = $generator->create_user();$user2 = $generator->create_user();$user3 = $generator->create_user();// Enrol users on courses.$generator->enrol_user($user1->id, $course1->id);$generator->enrol_user($user2->id, $course2->id);$generator->enrol_user($user3->id, $course2->id);$generator->enrol_user($user3->id, $course3->id);// Create groups.$group1 = $generator->create_group(array('courseid' => $course1->id));$group2 = $generator->create_group(array('courseid' => $course2->id));$group3 = $generator->create_group(array('courseid' => $course2->id));// Assign users to groups and assert the result.$this->assertTrue($generator->create_group_member(array('groupid' => $group1->id, 'userid' => $user1->id)));$this->assertTrue($generator->create_group_member(array('groupid' => $group2->id, 'userid' => $user2->id)));$this->assertTrue($generator->create_group_member(array('groupid' => $group3->id, 'userid' => $user2->id)));$this->assertTrue($generator->create_group_member(array('groupid' => $group2->id, 'userid' => $user3->id)));// Create groupings.$grouping1 = $generator->create_grouping(array('courseid' => $course1->id));$grouping2 = $generator->create_grouping(array('courseid' => $course2->id));// Assign and assert group to groupings.groups_assign_grouping($grouping1->id, $group1->id);groups_assign_grouping($grouping2->id, $group2->id);groups_assign_grouping($grouping2->id, $group3->id);// Test with one single group.$modinfo = get_fast_modinfo($course1, $user1->id);$groups = $modinfo->get_groups($grouping1->id);$this->assertCount(1, $groups);$this->assertArrayHasKey($group1->id, $groups);// Test with two groups.$modinfo = get_fast_modinfo($course2, $user2->id);$groups = $modinfo->get_groups();$this->assertCount(2, $groups);$this->assertTrue(in_array($group2->id, $groups));$this->assertTrue(in_array($group3->id, $groups));// Test with no groups.$modinfo = get_fast_modinfo($course3, $user3->id);$groups = $modinfo->get_groups();$this->assertCount(0, $groups);$this->assertArrayNotHasKey($group1->id, $groups);}/*** Tests the function for constructing a cm_info from mixed data.*/public function test_create(): void {global $CFG, $DB;$this->resetAfterTest();// Create a course and an activity.$generator = $this->getDataGenerator();$course = $generator->create_course();$page = $generator->create_module('page', array('course' => $course->id,'name' => 'Annie'));// Null is passed through.$this->assertNull(cm_info::create(null));// Stdclass object turns into cm_info.$cm = cm_info::create((object)array('id' => $page->cmid, 'course' => $course->id));$this->assertInstanceOf('cm_info', $cm);$this->assertEquals('Annie', $cm->name);// A cm_info object stays as cm_info.$this->assertSame($cm, cm_info::create($cm));// Invalid object (missing fields) causes error.try {cm_info::create((object)array('id' => $page->cmid));$this->fail();} catch (Exception $e) {$this->assertInstanceOf('coding_exception', $e);}// Create a second hidden activity.$hiddenpage = $generator->create_module('page', array('course' => $course->id,'name' => 'Annie', 'visible' => 0));// Create 2 user accounts, one is a manager who can see everything.$user = $generator->create_user();$generator->enrol_user($user->id, $course->id);$manager = $generator->create_user();$generator->enrol_user($manager->id, $course->id,$DB->get_field('role', 'id', array('shortname' => 'manager'), MUST_EXIST));// User can see the normal page but not the hidden one.$cm = cm_info::create((object)array('id' => $page->cmid, 'course' => $course->id),$user->id);$this->assertTrue($cm->uservisible);$cm = cm_info::create((object)array('id' => $hiddenpage->cmid, 'course' => $course->id),$user->id);$this->assertFalse($cm->uservisible);// Manager can see the hidden one too.$cm = cm_info::create((object)array('id' => $hiddenpage->cmid, 'course' => $course->id),$manager->id);$this->assertTrue($cm->uservisible);}/*** Tests function for getting $course and $cm at once quickly from modinfo* based on cmid or cm record.*/public function test_get_course_and_cm_from_cmid(): void {global $CFG, $DB;$this->resetAfterTest();// Create a course and an activity.$generator = $this->getDataGenerator();$course = $generator->create_course(array('shortname' => 'Halls'));$page = $generator->create_module('page', array('course' => $course->id,'name' => 'Annie'));// Successful usage.list($course, $cm) = get_course_and_cm_from_cmid($page->cmid);$this->assertEquals('Halls', $course->shortname);$this->assertInstanceOf('cm_info', $cm);$this->assertEquals('Annie', $cm->name);// Specified module type.list($course, $cm) = get_course_and_cm_from_cmid($page->cmid, 'page');$this->assertEquals('Annie', $cm->name);// With id in object.$fakecm = (object)array('id' => $page->cmid);list($course, $cm) = get_course_and_cm_from_cmid($fakecm);$this->assertEquals('Halls', $course->shortname);$this->assertEquals('Annie', $cm->name);// With both id and course in object.$fakecm->course = $course->id;list($course, $cm) = get_course_and_cm_from_cmid($fakecm);$this->assertEquals('Halls', $course->shortname);$this->assertEquals('Annie', $cm->name);// With supplied course id.list($course, $cm) = get_course_and_cm_from_cmid($page->cmid, 'page', $course->id);$this->assertEquals('Annie', $cm->name);// With supplied course object (modified just so we can check it is// indeed reusing the supplied object).$course->silly = true;list($course, $cm) = get_course_and_cm_from_cmid($page->cmid, 'page', $course);$this->assertEquals('Annie', $cm->name);$this->assertTrue($course->silly);// Incorrect module type.try {get_course_and_cm_from_cmid($page->cmid, 'forum');$this->fail();} catch (moodle_exception $e) {$this->assertEquals('invalidcoursemoduleid', $e->errorcode);}// Invalid module name.try {get_course_and_cm_from_cmid($page->cmid, 'pigs can fly');$this->fail();} catch (coding_exception $e) {$this->assertStringContainsString('Invalid modulename parameter', $e->getMessage());}// Doesn't exist.try {get_course_and_cm_from_cmid($page->cmid + 1);$this->fail();} catch (moodle_exception $e) {$this->assertInstanceOf('dml_exception', $e);}// Create a second hidden activity.$hiddenpage = $generator->create_module('page', array('course' => $course->id,'name' => 'Annie', 'visible' => 0));// Create 2 user accounts, one is a manager who can see everything.$user = $generator->create_user();$generator->enrol_user($user->id, $course->id);$manager = $generator->create_user();$generator->enrol_user($manager->id, $course->id,$DB->get_field('role', 'id', array('shortname' => 'manager'), MUST_EXIST));// User can see the normal page but not the hidden one.list($course, $cm) = get_course_and_cm_from_cmid($page->cmid, 'page', 0, $user->id);$this->assertTrue($cm->uservisible);list($course, $cm) = get_course_and_cm_from_cmid($hiddenpage->cmid, 'page', 0, $user->id);$this->assertFalse($cm->uservisible);// Manager can see the hidden one too.list($course, $cm) = get_course_and_cm_from_cmid($hiddenpage->cmid, 'page', 0, $manager->id);$this->assertTrue($cm->uservisible);}/*** Tests function for getting $course and $cm at once quickly from modinfo* based on instance id or record.*/public function test_get_course_and_cm_from_instance(): void {global $CFG, $DB;$this->resetAfterTest();// Create a course and an activity.$generator = $this->getDataGenerator();$course = $generator->create_course(array('shortname' => 'Halls'));$page = $generator->create_module('page', array('course' => $course->id,'name' => 'Annie'));// Successful usage.list($course, $cm) = get_course_and_cm_from_instance($page->id, 'page');$this->assertEquals('Halls', $course->shortname);$this->assertInstanceOf('cm_info', $cm);$this->assertEquals('Annie', $cm->name);// With id in object.$fakeinstance = (object)array('id' => $page->id);list($course, $cm) = get_course_and_cm_from_instance($fakeinstance, 'page');$this->assertEquals('Halls', $course->shortname);$this->assertEquals('Annie', $cm->name);// With both id and course in object.$fakeinstance->course = $course->id;list($course, $cm) = get_course_and_cm_from_instance($fakeinstance, 'page');$this->assertEquals('Halls', $course->shortname);$this->assertEquals('Annie', $cm->name);// With supplied course id.list($course, $cm) = get_course_and_cm_from_instance($page->id, 'page', $course->id);$this->assertEquals('Annie', $cm->name);// With supplied course object (modified just so we can check it is// indeed reusing the supplied object).$course->silly = true;list($course, $cm) = get_course_and_cm_from_instance($page->id, 'page', $course);$this->assertEquals('Annie', $cm->name);$this->assertTrue($course->silly);// Doesn't exist (or is wrong type).try {get_course_and_cm_from_instance($page->id, 'forum');$this->fail();} catch (moodle_exception $e) {$this->assertInstanceOf('dml_exception', $e);}// Invalid module ID.try {get_course_and_cm_from_instance(-1, 'page', $course);$this->fail();} catch (moodle_exception $e) {$this->assertStringContainsString('Invalid module ID: -1', $e->getMessage());}// Invalid module name.try {get_course_and_cm_from_cmid($page->cmid, '1337 h4x0ring');$this->fail();} catch (coding_exception $e) {$this->assertStringContainsString('Invalid modulename parameter', $e->getMessage());}// Create a second hidden activity.$hiddenpage = $generator->create_module('page', array('course' => $course->id,'name' => 'Annie', 'visible' => 0));// Create 2 user accounts, one is a manager who can see everything.$user = $generator->create_user();$generator->enrol_user($user->id, $course->id);$manager = $generator->create_user();$generator->enrol_user($manager->id, $course->id,$DB->get_field('role', 'id', array('shortname' => 'manager'), MUST_EXIST));// User can see the normal page but not the hidden one.list($course, $cm) = get_course_and_cm_from_cmid($page->cmid, 'page', 0, $user->id);$this->assertTrue($cm->uservisible);list($course, $cm) = get_course_and_cm_from_cmid($hiddenpage->cmid, 'page', 0, $user->id);$this->assertFalse($cm->uservisible);// Manager can see the hidden one too.list($course, $cm) = get_course_and_cm_from_cmid($hiddenpage->cmid, 'page', 0, $manager->id);$this->assertTrue($cm->uservisible);}/*** Test for get_listed_section_info_all method.* @covers \course_modinfo::get_listed_section_info_all* @covers \course_modinfo::get_section_info_all*/public function test_get_listed_section_info_all(): void {$this->resetAfterTest();// Create a course with 4 sections.$course = $this->getDataGenerator()->create_course(['numsections' => 3]);$listed = get_fast_modinfo($course)->get_section_info_all();$this->assertCount(4, $listed);// Generate some delegated sections (not listed).formatactions::section($course)->create_delegated('mod_label', 0);formatactions::section($course)->create_delegated('mod_label', 1);$this->assertCount(6, get_fast_modinfo($course)->get_section_info_all());$result = get_fast_modinfo($course)->get_listed_section_info_all();$this->assertCount(4, $result);$this->assertEquals($listed[0]->id, $result[0]->id);$this->assertEquals($listed[1]->id, $result[1]->id);$this->assertEquals($listed[2]->id, $result[2]->id);$this->assertEquals($listed[3]->id, $result[3]->id);}/*** Test test_get_section_info_by_id method** @dataProvider get_section_info_by_id_provider* @covers \course_modinfo::get_section_info_by_id** @param int $sectionnum the section number* @param int $strictness the search strict mode* @param bool $expectnull if the function will return a null* @param bool $expectexception if the function will throw an exception*/public function test_get_section_info_by_id(int $sectionnum,int $strictness = IGNORE_MISSING,bool $expectnull = false,bool $expectexception = false): void {global $DB;$this->resetAfterTest();// Create a course with 4 sections.$course = $this->getDataGenerator()->create_course(['numsections' => 4]);// Index sections.$sectionindex = [];$modinfo = get_fast_modinfo($course);$allsections = $modinfo->get_section_info_all();foreach ($allsections as $section) {$sectionindex[$section->section] = $section->id;}if ($expectexception) {$this->expectException(moodle_exception::class);}$sectionid = $sectionindex[$sectionnum] ?? -1;$section = $modinfo->get_section_info_by_id($sectionid, $strictness);if ($expectnull) {$this->assertNull($section);} else {$this->assertEquals($sectionid, $section->id);$this->assertEquals($sectionnum, $section->section);}}/*** Data provider for test_get_section_info_by_id().** @return array*/public function get_section_info_by_id_provider() {return ['Valid section id' => ['sectionnum' => 1,'strictness' => IGNORE_MISSING,'expectnull' => false,'expectexception' => false,],'Section zero' => ['sectionnum' => 0,'strictness' => IGNORE_MISSING,'expectnull' => false,'expectexception' => false,],'invalid section ignore missing' => ['sectionnum' => -1,'strictness' => IGNORE_MISSING,'expectnull' => true,'expectexception' => false,],'invalid section must exists' => ['sectionnum' => -1,'strictness' => MUST_EXIST,'expectnull' => false,'expectexception' => true,],];}/*** Test get_section_info_by_component method** @covers \course_modinfo::get_section_info_by_component* @dataProvider get_section_info_by_component_provider** @param string $component the component name* @param int $itemid the section number* @param int $strictness the search strict mode* @param bool $expectnull if the function will return a null* @param bool $expectexception if the function will throw an exception*/public function test_get_section_info_by_component(string $component,int $itemid,int $strictness,bool $expectnull,bool $expectexception): void {$this->resetAfterTest();$course = $this->getDataGenerator()->create_course(['numsections' => 1]);formatactions::section($course)->create_delegated('mod_forum', 42);$modinfo = get_fast_modinfo($course);if ($expectexception) {$this->expectException(moodle_exception::class);}$section = $modinfo->get_section_info_by_component($component, $itemid, $strictness);if ($expectnull) {$this->assertNull($section);} else {$this->assertEquals($component, $section->component);$this->assertEquals($itemid, $section->itemid);}}/*** Data provider for test_get_section_info_by_component().** @return array*/public static function get_section_info_by_component_provider(): array {return ['Valid component and itemid' => ['component' => 'mod_forum','itemid' => 42,'strictness' => IGNORE_MISSING,'expectnull' => false,'expectexception' => false,],'Invalid component' => ['component' => 'mod_nonexisting','itemid' => 42,'strictness' => IGNORE_MISSING,'expectnull' => true,'expectexception' => false,],'Invalid itemid' => ['component' => 'mod_forum','itemid' => 0,'strictness' => IGNORE_MISSING,'expectnull' => true,'expectexception' => false,],'Invalid component and itemid' => ['component' => 'mod_nonexisting','itemid' => 0,'strictness' => IGNORE_MISSING,'expectnull' => true,'expectexception' => false,],'Invalid component must exists' => ['component' => 'mod_nonexisting','itemid' => 42,'strictness' => MUST_EXIST,'expectnull' => true,'expectexception' => true,],'Invalid itemid must exists' => ['component' => 'mod_forum','itemid' => 0,'strictness' => MUST_EXIST,'expectnull' => true,'expectexception' => true,],'Invalid component and itemid must exists' => ['component' => 'mod_nonexisting','itemid' => 0,'strictness' => MUST_EXIST,'expectnull' => false,'expectexception' => true,],];}/*** Test has_delegated_sections method** @covers \course_modinfo::has_delegated_sections*/public function test_has_delegated_sections(): void {$this->resetAfterTest();$course = $this->getDataGenerator()->create_course(['numsections' => 1]);$modinfo = get_fast_modinfo($course);$this->assertFalse($modinfo->has_delegated_sections());formatactions::section($course)->create_delegated('mod_forum', 42);$modinfo = get_fast_modinfo($course);$this->assertTrue($modinfo->has_delegated_sections());}/*** Test purge_section_cache_by_id method** @covers \course_modinfo::purge_course_section_cache_by_id* @return void*/public function test_purge_section_cache_by_id(): void {$this->resetAfterTest();$this->setAdminUser();$cache = cache::make('core', 'coursemodinfo');// Generate the course and pre-requisite section.$course = $this->getDataGenerator()->create_course(['format' => 'topics', 'numsections' => 3], ['createsections' => true]);// Reset course cache.rebuild_course_cache($course->id, true);// Build course cache.$modinfo = get_fast_modinfo($course->id);// Get the course modinfo cache.$coursemodinfo = $cache->get_versioned($course->id, $course->cacherev);// Get the section cache.$sectioncaches = $coursemodinfo->sectioncache;$numberedsections = $modinfo->get_section_info_all();// Make sure that we will have 4 section caches here.$this->assertCount(4, $sectioncaches);$this->assertArrayHasKey($numberedsections[0]->id, $sectioncaches);$this->assertArrayHasKey($numberedsections[1]->id, $sectioncaches);$this->assertArrayHasKey($numberedsections[2]->id, $sectioncaches);$this->assertArrayHasKey($numberedsections[3]->id, $sectioncaches);// Purge cache for the section by id.course_modinfo::purge_course_section_cache_by_id($course->id,$numberedsections[1]->id);// Get the course modinfo cache.$coursemodinfo = $cache->get_versioned($course->id, $course->cacherev);// Get the section cache.$sectioncaches = $coursemodinfo->sectioncache;// Make sure that we will have 3 section caches left.$this->assertCount(3, $sectioncaches);$this->assertArrayNotHasKey($numberedsections[1]->id, $sectioncaches);$this->assertArrayHasKey($numberedsections[0]->id, $sectioncaches);$this->assertArrayHasKey($numberedsections[2]->id, $sectioncaches);$this->assertArrayHasKey($numberedsections[3]->id, $sectioncaches);// Make sure that the cacherev will be reset.$this->assertEquals(-1, $coursemodinfo->cacherev);}/*** Test purge_section_cache_by_number method** @covers \course_modinfo::purge_course_section_cache_by_number* @return void*/public function test_section_cache_by_number(): void {$this->resetAfterTest();$this->setAdminUser();$cache = cache::make('core', 'coursemodinfo');// Generate the course and pre-requisite section.$course = $this->getDataGenerator()->create_course(['format' => 'topics', 'numsections' => 3], ['createsections' => true]);// Reset course cache.rebuild_course_cache($course->id, true);// Build course cache.$modinfo = get_fast_modinfo($course->id);// Get the course modinfo cache.$coursemodinfo = $cache->get_versioned($course->id, $course->cacherev);// Get the section cache.$sectioncaches = $coursemodinfo->sectioncache;$numberedsections = $modinfo->get_section_info_all();// Make sure that we will have 4 section caches here.$this->assertCount(4, $sectioncaches);$this->assertArrayHasKey($numberedsections[0]->id, $sectioncaches);$this->assertArrayHasKey($numberedsections[1]->id, $sectioncaches);$this->assertArrayHasKey($numberedsections[2]->id, $sectioncaches);$this->assertArrayHasKey($numberedsections[3]->id, $sectioncaches);// Purge cache for the section with section number is 1.course_modinfo::purge_course_section_cache_by_number($course->id, 1);// Get the course modinfo cache.$coursemodinfo = $cache->get_versioned($course->id, $course->cacherev);// Get the section cache.$sectioncaches = $coursemodinfo->sectioncache;// Make sure that we will have 3 section caches left.$this->assertCount(3, $sectioncaches);$this->assertArrayNotHasKey($numberedsections[1]->id, $sectioncaches);$this->assertArrayHasKey($numberedsections[0]->id, $sectioncaches);$this->assertArrayHasKey($numberedsections[2]->id, $sectioncaches);$this->assertArrayHasKey($numberedsections[3]->id, $sectioncaches);// Make sure that the cacherev will be reset.$this->assertEquals(-1, $coursemodinfo->cacherev);}/*** Purge a single course module from the cache.** @return void* @covers \course_modinfo::purge_course_module_cache*/public function test_purge_course_module(): void {$this->resetAfterTest();$this->setAdminUser();$cache = cache::make('core', 'coursemodinfo');// Generate the course and pre-requisite section.$course = $this->getDataGenerator()->create_course();$cm1 = $this->getDataGenerator()->create_module('page', ['course' => $course]);$cm2 = $this->getDataGenerator()->create_module('page', ['course' => $course]);$cm3 = $this->getDataGenerator()->create_module('page', ['course' => $course]);$cm4 = $this->getDataGenerator()->create_module('page', ['course' => $course]);// Reset course cache.rebuild_course_cache($course->id, true);// Build course cache.get_fast_modinfo($course->id);// Get the course modinfo cache.$coursemodinfo = $cache->get_versioned($course->id, $course->cacherev);$this->assertCount(4, $coursemodinfo->modinfo);$this->assertArrayHasKey($cm1->cmid, $coursemodinfo->modinfo);$this->assertArrayHasKey($cm2->cmid, $coursemodinfo->modinfo);$this->assertArrayHasKey($cm3->cmid, $coursemodinfo->modinfo);$this->assertArrayHasKey($cm4->cmid, $coursemodinfo->modinfo);course_modinfo::purge_course_module_cache($course->id, $cm1->cmid);$coursemodinfo = $cache->get_versioned($course->id, $course->cacherev);$this->assertCount(3, $coursemodinfo->modinfo);$this->assertArrayNotHasKey($cm1->cmid, $coursemodinfo->modinfo);$this->assertArrayHasKey($cm2->cmid, $coursemodinfo->modinfo);$this->assertArrayHasKey($cm3->cmid, $coursemodinfo->modinfo);$this->assertArrayHasKey($cm4->cmid, $coursemodinfo->modinfo);// Make sure that the cacherev will be reset.$this->assertEquals(-1, $coursemodinfo->cacherev);}/*** Purge a multiple course modules from the cache.** @return void* @covers \course_modinfo::purge_course_modules_cache*/public function test_purge_multiple_course_modules(): void {$this->resetAfterTest();$this->setAdminUser();$cache = cache::make('core', 'coursemodinfo');// Generate the course and pre-requisite section.$course = $this->getDataGenerator()->create_course();$cm1 = $this->getDataGenerator()->create_module('page', ['course' => $course]);$cm2 = $this->getDataGenerator()->create_module('page', ['course' => $course]);$cm3 = $this->getDataGenerator()->create_module('page', ['course' => $course]);$cm4 = $this->getDataGenerator()->create_module('page', ['course' => $course]);// Reset course cache.rebuild_course_cache($course->id, true);// Build course cache.get_fast_modinfo($course->id);// Get the course modinfo cache.$coursemodinfo = $cache->get_versioned($course->id, $course->cacherev);$this->assertCount(4, $coursemodinfo->modinfo);$this->assertArrayHasKey($cm1->cmid, $coursemodinfo->modinfo);$this->assertArrayHasKey($cm2->cmid, $coursemodinfo->modinfo);$this->assertArrayHasKey($cm3->cmid, $coursemodinfo->modinfo);$this->assertArrayHasKey($cm4->cmid, $coursemodinfo->modinfo);course_modinfo::purge_course_modules_cache($course->id, [$cm2->cmid, $cm3->cmid]);$coursemodinfo = $cache->get_versioned($course->id, $course->cacherev);$this->assertCount(2, $coursemodinfo->modinfo);$this->assertArrayHasKey($cm1->cmid, $coursemodinfo->modinfo);$this->assertArrayNotHasKey($cm2->cmid, $coursemodinfo->modinfo);$this->assertArrayNotHasKey($cm3->cmid, $coursemodinfo->modinfo);$this->assertArrayHasKey($cm4->cmid, $coursemodinfo->modinfo);// Make sure that the cacherev will be reset.$this->assertEquals(-1, $coursemodinfo->cacherev);}/*** Test get_cm() method to output course module id in the exception text.** @covers \course_modinfo::get_cm* @return void*/public function test_invalid_course_module_id(): void {global $DB;$this->resetAfterTest();$course = $this->getDataGenerator()->create_course();$forum0 = $this->getDataGenerator()->create_module('assign', ['course' => $course->id], ['section' => 0]);$forum1 = $this->getDataGenerator()->create_module('assign', ['course' => $course->id], ['section' => 0]);$forum2 = $this->getDataGenerator()->create_module('assign', ['course' => $course->id], ['section' => 0]);// Break section sequence.$modinfo = get_fast_modinfo($course->id);$sectionid = $modinfo->get_section_info(0)->id;$section = $DB->get_record('course_sections', ['id' => $sectionid]);$sequence = explode(',', $section->sequence);$sequence = array_diff($sequence, [$forum1->cmid]);$section->sequence = implode(',', $sequence);$DB->update_record('course_sections', $section);// Assert exception text.$this->expectException(\moodle_exception::class);$this->expectExceptionMessage('Invalid course module ID: ' . $forum1->cmid);delete_course($course, false);}/*** Tests that if the modinfo cache returns a newer-than-expected version, Moodle won't rebuild* it.** This is important to avoid wasted time/effort and poor performance, for example in cases* where multiple requests are accessing the course.** Certain cases could be particularly bad if this test fails. For example, if using clustered* databases where there is a 100ms delay between updates to the course table being available* to all users (but no such delay on the cache infrastructure), then during that 100ms, every* request that calls get_fast_modinfo and uses the read-only database will rebuild the course* cache. Since these will then create a still-newer version, future requests for the next* 100ms will also rebuild it again... etc.** @covers \course_modinfo*/public function test_get_modinfo_with_newer_version(): void {global $DB;$this->resetAfterTest();// Get info about a course and build the initial cache, then drop it from memory.$course = $this->getDataGenerator()->create_course();get_fast_modinfo($course);get_fast_modinfo(0, 0, true);// User A starts a request, which takes some time...$useracourse = $DB->get_record('course', ['id' => $course->id]);// User B also starts a request and makes a change to the course.$userbcourse = $DB->get_record('course', ['id' => $course->id]);$this->getDataGenerator()->create_module('page', ['course' => $course->id]);rebuild_course_cache($userbcourse->id, false);// Finally, user A's request now gets modinfo. It should accept the version from B even// though the course version (of cache) is newer than the one expected by A.$before = $DB->perf_get_queries();$modinfo = get_fast_modinfo($useracourse);$after = $DB->perf_get_queries();$this->assertEquals($after, $before, 'Should use cached version, making no DB queries');// Obviously, modinfo should include the Page now.$this->assertCount(1, $modinfo->get_instances_of('page'));}/*** Test for get_component_instance.* @covers \section_info::get_component_instance*/public function test_get_component_instance(): void {global $DB;$this->resetAfterTest();$course = $this->getDataGenerator()->create_course(['format' => 'topics', 'numsections' => 2]);course_update_section($course,$DB->get_record('course_sections', ['course' => $course->id, 'section' => 2]),['component' => 'test_component','itemid' => 1,]);$modinfo = get_fast_modinfo($course->id);$sectioninfos = $modinfo->get_section_info_all();$this->assertNull($sectioninfos[1]->get_component_instance());$this->assertNull($sectioninfos[1]->component);$this->assertNull($sectioninfos[1]->itemid);$this->assertInstanceOf('\core_courseformat\sectiondelegate', $sectioninfos[2]->get_component_instance());$this->assertInstanceOf('\test_component\courseformat\sectiondelegate', $sectioninfos[2]->get_component_instance());$this->assertEquals('test_component', $sectioninfos[2]->component);$this->assertEquals(1, $sectioninfos[2]->itemid);}/*** Test for section_info is_delegated.* @covers \section_info::is_delegated*/public function test_is_delegated(): void {$this->resetAfterTest();$course = $this->getDataGenerator()->create_course(['format' => 'topics', 'numsections' => 1]);formatactions::section($course)->create_delegated('mod_label', 0);$modinfo = get_fast_modinfo($course->id);$sectioninfos = $modinfo->get_section_info_all();$this->assertFalse($sectioninfos[1]->is_delegated());$this->assertTrue($sectioninfos[2]->is_delegated());}/*** Test the course_modinfo::purge_course_caches() function with a* one-course array, a two-course array, and an empty array, and ensure* that only the courses specified have their course cache version* incremented (or all course caches if none specified).** @covers \course_modinfo*/public function test_multiple_modinfo_cache_purge(): void {global $DB;$this->resetAfterTest();$this->setAdminUser();$cache = cache::make('core', 'coursemodinfo');// Generate two courses and pre-requisite modules for targeted course// cache tests.$courseone = $this->getDataGenerator()->create_course(['format' => 'topics','numsections' => 3,],['createsections' => true,]);$coursetwo = $this->getDataGenerator()->create_course(['format' => 'topics','numsections' => 3,],['createsections' => true,]);$coursethree = $this->getDataGenerator()->create_course(['format' => 'topics','numsections' => 3,],['createsections' => true,]);// Make sure the cacherev is set for all three.$cacherevone = $DB->get_field('course', 'cacherev', ['id' => $courseone->id]);$this->assertGreaterThan(0, $cacherevone);$prevcacherevone = $cacherevone;$cacherevtwo = $DB->get_field('course', 'cacherev', ['id' => $coursetwo->id]);$this->assertGreaterThan(0, $cacherevtwo);$prevcacherevtwo = $cacherevtwo;$cacherevthree = $DB->get_field('course', 'cacherev', ['id' => $coursethree->id]);$this->assertGreaterThan(0, $cacherevthree);$prevcacherevthree = $cacherevthree;// Reset course caches and make sure cacherev is bumped up but cache is empty.rebuild_course_cache($courseone->id, true);$cacherevone = $DB->get_field('course', 'cacherev', ['id' => $courseone->id]);$this->assertGreaterThan($prevcacherevone, $cacherevone);$this->assertEmpty($cache->get_versioned($courseone->id, $prevcacherevone));$prevcacherevone = $cacherevone;rebuild_course_cache($coursetwo->id, true);$cacherevtwo = $DB->get_field('course', 'cacherev', ['id' => $coursetwo->id]);$this->assertGreaterThan($prevcacherevtwo, $cacherevtwo);$this->assertEmpty($cache->get_versioned($coursetwo->id, $prevcacherevtwo));$prevcacherevtwo = $cacherevtwo;rebuild_course_cache($coursethree->id, true);$cacherevthree = $DB->get_field('course', 'cacherev', ['id' => $coursethree->id]);$this->assertGreaterThan($prevcacherevthree, $cacherevthree);$this->assertEmpty($cache->get_versioned($coursethree->id, $prevcacherevthree));$prevcacherevthree = $cacherevthree;// Build course caches. Cacherev should not change but caches are now not empty. Make sure cacherev is the same everywhere.$modinfoone = get_fast_modinfo($courseone->id);$cacherevone = $DB->get_field('course', 'cacherev', ['id' => $courseone->id]);$this->assertEquals($prevcacherevone, $cacherevone);$cachedvalueone = $cache->get_versioned($courseone->id, $cacherevone);$this->assertNotEmpty($cachedvalueone);$this->assertEquals($cacherevone, $cachedvalueone->cacherev);$this->assertEquals($cacherevone, $modinfoone->get_course()->cacherev);$prevcacherevone = $cacherevone;$modinfotwo = get_fast_modinfo($coursetwo->id);$cacherevtwo = $DB->get_field('course', 'cacherev', ['id' => $coursetwo->id]);$this->assertEquals($prevcacherevtwo, $cacherevtwo);$cachedvaluetwo = $cache->get_versioned($coursetwo->id, $cacherevtwo);$this->assertNotEmpty($cachedvaluetwo);$this->assertEquals($cacherevtwo, $cachedvaluetwo->cacherev);$this->assertEquals($cacherevtwo, $modinfotwo->get_course()->cacherev);$prevcacherevtwo = $cacherevtwo;$modinfothree = get_fast_modinfo($coursethree->id);$cacherevthree = $DB->get_field('course', 'cacherev', ['id' => $coursethree->id]);$this->assertEquals($prevcacherevthree, $cacherevthree);$cachedvaluethree = $cache->get_versioned($coursethree->id, $cacherevthree);$this->assertNotEmpty($cachedvaluethree);$this->assertEquals($cacherevthree, $cachedvaluethree->cacherev);$this->assertEquals($cacherevthree, $modinfothree->get_course()->cacherev);$prevcacherevthree = $cacherevthree;// Purge course one's cache. Cacherev must be incremented (but only for// course one, check course two and three in next step).course_modinfo::purge_course_caches([$courseone->id]);get_fast_modinfo($courseone->id);$cacherevone = $DB->get_field('course', 'cacherev', ['id' => $courseone->id]);$this->assertGreaterThan($prevcacherevone, $cacherevone);$prevcacherevone = $cacherevone;// Confirm course two and three's cache shouldn't have been affected.get_fast_modinfo($coursetwo->id);$cacherevtwo = $DB->get_field('course', 'cacherev', ['id' => $coursetwo->id]);$this->assertEquals($prevcacherevtwo, $cacherevtwo);$prevcacherevtwo = $cacherevtwo;get_fast_modinfo($coursethree->id);$cacherevthree = $DB->get_field('course', 'cacherev', ['id' => $coursethree->id]);$this->assertEquals($prevcacherevthree, $cacherevthree);$prevcacherevthree = $cacherevthree;// Purge course two and three's cache. Cacherev must be incremented (but only for// course two and three, then check course one hasn't changed in next step).course_modinfo::purge_course_caches([$coursetwo->id, $coursethree->id]);get_fast_modinfo($coursetwo->id);$cacherevtwo = $DB->get_field('course', 'cacherev', ['id' => $coursetwo->id]);$this->assertGreaterThan($prevcacherevtwo, $cacherevtwo);$prevcacherevtwo = $cacherevtwo;get_fast_modinfo($coursethree->id);$cacherevthree = $DB->get_field('course', 'cacherev', ['id' => $coursethree->id]);$this->assertGreaterThan($prevcacherevthree, $cacherevthree);$prevcacherevthree = $cacherevthree;// Confirm course one's cache shouldn't have been affected.get_fast_modinfo($courseone->id);$cacherevone = $DB->get_field('course', 'cacherev', ['id' => $courseone->id]);$this->assertEquals($prevcacherevone, $cacherevone);$prevcacherevone = $cacherevone;// Purge all course caches. Cacherev must be incremented for all three courses.course_modinfo::purge_course_caches();get_fast_modinfo($courseone->id);$cacherevone = $DB->get_field('course', 'cacherev', ['id' => $courseone->id]);$this->assertGreaterThan($prevcacherevone, $cacherevone);get_fast_modinfo($coursetwo->id);$cacherevtwo = $DB->get_field('course', 'cacherev', ['id' => $coursetwo->id]);$this->assertGreaterThan($prevcacherevtwo, $cacherevtwo);get_fast_modinfo($coursethree->id);$cacherevthree = $DB->get_field('course', 'cacherev', ['id' => $coursethree->id]);$this->assertGreaterThan($prevcacherevthree, $cacherevthree);}}