Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403] [Versions 39 and 310]

   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  /**
  18   * Unit tests for lib/modinfolib.php.
  19   *
  20   * @package    core
  21   * @category   phpunit
  22   * @copyright  2012 Andrew Davis
  23   */
  24  
  25  defined('MOODLE_INTERNAL') || die();
  26  
  27  global $CFG;
  28  require_once($CFG->libdir . '/modinfolib.php');
  29  
  30  /**
  31   * Unit tests for modinfolib.php
  32   *
  33   * @copyright 2012 Andrew Davis
  34   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  35   */
  36  class core_modinfolib_testcase extends advanced_testcase {
  37      public function test_section_info_properties() {
  38          global $DB, $CFG;
  39  
  40          $this->resetAfterTest();
  41          $oldcfgenableavailability = $CFG->enableavailability;
  42          $oldcfgenablecompletion = $CFG->enablecompletion;
  43          set_config('enableavailability', 1);
  44          set_config('enablecompletion', 1);
  45          $this->setAdminUser();
  46  
  47          // Generate the course and pre-requisite module.
  48          $course = $this->getDataGenerator()->create_course(
  49                  array('format' => 'topics',
  50                      'numsections' => 3,
  51                      'enablecompletion' => 1,
  52                      'groupmode' => SEPARATEGROUPS,
  53                      'forcegroupmode' => 0),
  54                  array('createsections' => true));
  55          $coursecontext = context_course::instance($course->id);
  56          $prereqforum = $this->getDataGenerator()->create_module('forum',
  57                  array('course' => $course->id),
  58                  array('completion' => 1));
  59  
  60          // Add availability conditions.
  61          $availability = '{"op":"&","showc":[true,true,true],"c":[' .
  62                  '{"type":"completion","cm":' . $prereqforum->cmid . ',"e":"' .
  63                      COMPLETION_COMPLETE . '"},' .
  64                  '{"type":"grade","id":666,"min":0.4},' .
  65                  '{"type":"profile","op":"contains","sf":"email","v":"test"}' .
  66                  ']}';
  67          $DB->set_field('course_sections', 'availability', $availability,
  68                  array('course' => $course->id, 'section' => 2));
  69          rebuild_course_cache($course->id, true);
  70          $sectiondb = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 2));
  71  
  72          // Create and enrol a student.
  73          $studentrole = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
  74          $student = $this->getDataGenerator()->create_user();
  75          role_assign($studentrole->id, $student->id, $coursecontext);
  76          $enrolplugin = enrol_get_plugin('manual');
  77          $enrolinstance = $DB->get_record('enrol', array('courseid' => $course->id, 'enrol' => 'manual'));
  78          $enrolplugin->enrol_user($enrolinstance, $student->id);
  79          $this->setUser($student);
  80  
  81          // Get modinfo.
  82          $modinfo = get_fast_modinfo($course->id);
  83          $si = $modinfo->get_section_info(2);
  84  
  85          $this->assertEquals($sectiondb->id, $si->id);
  86          $this->assertEquals($sectiondb->course, $si->course);
  87          $this->assertEquals($sectiondb->section, $si->section);
  88          $this->assertEquals($sectiondb->name, $si->name);
  89          $this->assertEquals($sectiondb->visible, $si->visible);
  90          $this->assertEquals($sectiondb->summary, $si->summary);
  91          $this->assertEquals($sectiondb->summaryformat, $si->summaryformat);
  92          $this->assertEquals($sectiondb->sequence, $si->sequence); // Since this section does not contain invalid modules.
  93          $this->assertEquals($availability, $si->availability);
  94  
  95          // Dynamic fields, just test that they can be retrieved (must be carefully tested in each activity type).
  96          $this->assertEquals(0, $si->available);
  97          $this->assertNotEmpty($si->availableinfo); // Lists all unmet availability conditions.
  98          $this->assertEquals(0, $si->uservisible);
  99  
 100          // Restore settings.
 101          set_config('enableavailability', $oldcfgenableavailability);
 102          set_config('enablecompletion', $oldcfgenablecompletion);
 103      }
 104  
 105      public function test_cm_info_properties() {
 106          global $DB, $CFG;
 107  
 108          $this->resetAfterTest();
 109          $oldcfgenableavailability = $CFG->enableavailability;
 110          $oldcfgenablecompletion = $CFG->enablecompletion;
 111          set_config('enableavailability', 1);
 112          set_config('enablecompletion', 1);
 113          $this->setAdminUser();
 114  
 115          // Generate the course and pre-requisite module.
 116          $course = $this->getDataGenerator()->create_course(
 117                  array('format' => 'topics',
 118                      'numsections' => 3,
 119                      'enablecompletion' => 1,
 120                      'groupmode' => SEPARATEGROUPS,
 121                      'forcegroupmode' => 0),
 122                  array('createsections' => true));
 123          $coursecontext = context_course::instance($course->id);
 124          $prereqforum = $this->getDataGenerator()->create_module('forum',
 125                  array('course' => $course->id),
 126                  array('completion' => 1));
 127  
 128          // Generate module and add availability conditions.
 129          $availability = '{"op":"&","showc":[true,true,true],"c":[' .
 130                  '{"type":"completion","cm":' . $prereqforum->cmid . ',"e":"' .
 131                      COMPLETION_COMPLETE . '"},' .
 132                  '{"type":"grade","id":666,"min":0.4},' .
 133                  '{"type":"profile","op":"contains","sf":"email","v":"test"}' .
 134                  ']}';
 135          $assign = $this->getDataGenerator()->create_module('assign',
 136                  array('course' => $course->id),
 137                  array('idnumber' => 123,
 138                      'groupmode' => VISIBLEGROUPS,
 139                      'availability' => $availability));
 140          rebuild_course_cache($course->id, true);
 141  
 142          // Retrieve all related records from DB.
 143          $assigndb = $DB->get_record('assign', array('id' => $assign->id));
 144          $moduletypedb = $DB->get_record('modules', array('name' => 'assign'));
 145          $moduledb = $DB->get_record('course_modules', array('module' => $moduletypedb->id, 'instance' => $assign->id));
 146          $sectiondb = $DB->get_record('course_sections', array('id' => $moduledb->section));
 147          $modnamessingular = get_module_types_names(false);
 148          $modnamesplural = get_module_types_names(true);
 149  
 150          // Create and enrol a student.
 151          $studentrole = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
 152          $student = $this->getDataGenerator()->create_user();
 153          role_assign($studentrole->id, $student->id, $coursecontext);
 154          $enrolplugin = enrol_get_plugin('manual');
 155          $enrolinstance = $DB->get_record('enrol', array('courseid' => $course->id, 'enrol' => 'manual'));
 156          $enrolplugin->enrol_user($enrolinstance, $student->id);
 157          $this->setUser($student);
 158  
 159          // Emulate data used in building course cache to receive the same instance of cached_cm_info as was used in building modinfo.
 160          $rawmods = get_course_mods($course->id);
 161          $cachedcminfo = assign_get_coursemodule_info($rawmods[$moduledb->id]);
 162  
 163          // Get modinfo.
 164          $modinfo = get_fast_modinfo($course->id);
 165          $cm = $modinfo->instances['assign'][$assign->id];
 166  
 167          $this->assertEquals($moduledb->id, $cm->id);
 168          $this->assertEquals($assigndb->id, $cm->instance);
 169          $this->assertEquals($moduledb->course, $cm->course);
 170          $this->assertEquals($moduledb->idnumber, $cm->idnumber);
 171          $this->assertEquals($moduledb->added, $cm->added);
 172          $this->assertEquals($moduledb->visible, $cm->visible);
 173          $this->assertEquals($moduledb->visibleold, $cm->visibleold);
 174          $this->assertEquals($moduledb->groupmode, $cm->groupmode);
 175          $this->assertEquals(VISIBLEGROUPS, $cm->groupmode);
 176          $this->assertEquals($moduledb->groupingid, $cm->groupingid);
 177          $this->assertEquals($course->groupmodeforce, $cm->coursegroupmodeforce);
 178          $this->assertEquals($course->groupmode, $cm->coursegroupmode);
 179          $this->assertEquals(SEPARATEGROUPS, $cm->coursegroupmode);
 180          $this->assertEquals($course->groupmodeforce ? $course->groupmode : $moduledb->groupmode,
 181                  $cm->effectivegroupmode); // (since mod_assign supports groups).
 182          $this->assertEquals(VISIBLEGROUPS, $cm->effectivegroupmode);
 183          $this->assertEquals($moduledb->indent, $cm->indent);
 184          $this->assertEquals($moduledb->completion, $cm->completion);
 185          $this->assertEquals($moduledb->completiongradeitemnumber, $cm->completiongradeitemnumber);
 186          $this->assertEquals($moduledb->completionview, $cm->completionview);
 187          $this->assertEquals($moduledb->completionexpected, $cm->completionexpected);
 188          $this->assertEquals($moduledb->showdescription, $cm->showdescription);
 189          $this->assertEquals(null, $cm->extra); // Deprecated field. Used in module types that don't return cached_cm_info.
 190          $this->assertEquals($cachedcminfo->icon, $cm->icon);
 191          $this->assertEquals($cachedcminfo->iconcomponent, $cm->iconcomponent);
 192          $this->assertEquals('assign', $cm->modname);
 193          $this->assertEquals($moduledb->module, $cm->module);
 194          $this->assertEquals($cachedcminfo->name, $cm->name);
 195          $this->assertEquals($sectiondb->section, $cm->sectionnum);
 196          $this->assertEquals($moduledb->section, $cm->section);
 197          $this->assertEquals($availability, $cm->availability);
 198          $this->assertEquals(context_module::instance($moduledb->id), $cm->context);
 199          $this->assertEquals($modnamessingular['assign'], $cm->modfullname);
 200          $this->assertEquals($modnamesplural['assign'], $cm->modplural);
 201          $this->assertEquals(new moodle_url('/mod/assign/view.php', array('id' => $moduledb->id)), $cm->url);
 202          $this->assertEquals($cachedcminfo->customdata, $cm->customdata);
 203  
 204          // Dynamic fields, just test that they can be retrieved (must be carefully tested in each activity type).
 205          $this->assertNotEmpty($cm->availableinfo); // Lists all unmet availability conditions.
 206          $this->assertEquals(0, $cm->uservisible);
 207          $this->assertEquals('', $cm->extraclasses);
 208          $this->assertEquals('', $cm->onclick);
 209          $this->assertEquals(null, $cm->afterlink);
 210          $this->assertEquals(null, $cm->afterediticons);
 211          $this->assertEquals('', $cm->content);
 212  
 213          // Attempt to access and set non-existing field.
 214          $this->assertTrue(empty($modinfo->somefield));
 215          $this->assertFalse(isset($modinfo->somefield));
 216          $cm->somefield;
 217          $this->assertDebuggingCalled();
 218          $cm->somefield = 'Some value';
 219          $this->assertDebuggingCalled();
 220          $this->assertEmpty($cm->somefield);
 221          $this->assertDebuggingCalled();
 222  
 223          // Attempt to overwrite an existing field.
 224          $prevvalue = $cm->name;
 225          $this->assertNotEmpty($cm->name);
 226          $this->assertFalse(empty($cm->name));
 227          $this->assertTrue(isset($cm->name));
 228          $cm->name = 'Illegal overwriting';
 229          $this->assertDebuggingCalled();
 230          $this->assertEquals($prevvalue, $cm->name);
 231          $this->assertDebuggingNotCalled();
 232  
 233          // Restore settings.
 234          set_config('enableavailability', $oldcfgenableavailability);
 235          set_config('enablecompletion', $oldcfgenablecompletion);
 236      }
 237  
 238      public function test_matching_cacherev() {
 239          global $DB, $CFG;
 240  
 241          $this->resetAfterTest();
 242          $this->setAdminUser();
 243          $cache = cache::make('core', 'coursemodinfo');
 244  
 245          // Generate the course and pre-requisite module.
 246          $course = $this->getDataGenerator()->create_course(
 247                  array('format' => 'topics',
 248                      'numsections' => 3),
 249                  array('createsections' => true));
 250  
 251          // Make sure the cacherev is set.
 252          $cacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));
 253          $this->assertGreaterThan(0, $cacherev);
 254          $prevcacherev = $cacherev;
 255  
 256          // Reset course cache and make sure cacherev is bumped up but cache is empty.
 257          rebuild_course_cache($course->id, true);
 258          $cacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));
 259          $this->assertGreaterThan($prevcacherev, $cacherev);
 260          $this->assertEmpty($cache->get($course->id));
 261          $prevcacherev = $cacherev;
 262  
 263          // Build course cache. Cacherev should not change but cache is now not empty. Make sure cacherev is the same everywhere.
 264          $modinfo = get_fast_modinfo($course->id);
 265          $cacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));
 266          $this->assertEquals($prevcacherev, $cacherev);
 267          $cachedvalue = $cache->get($course->id);
 268          $this->assertNotEmpty($cachedvalue);
 269          $this->assertEquals($cacherev, $cachedvalue->cacherev);
 270          $this->assertEquals($cacherev, $modinfo->get_course()->cacherev);
 271          $prevcacherev = $cacherev;
 272  
 273          // 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.
 274          $cache->set($course->id, (object)array_merge((array)$cachedvalue, array('secretfield' => 1)));
 275  
 276          // Clear static cache and call get_fast_modinfo() again (pretend we are in another request). Cache should not be rebuilt.
 277          course_modinfo::clear_instance_cache();
 278          $modinfo = get_fast_modinfo($course->id);
 279          $cacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));
 280          $this->assertEquals($prevcacherev, $cacherev);
 281          $cachedvalue = $cache->get($course->id);
 282          $this->assertNotEmpty($cachedvalue);
 283          $this->assertEquals($cacherev, $cachedvalue->cacherev);
 284          $this->assertNotEmpty($cachedvalue->secretfield);
 285          $this->assertEquals($cacherev, $modinfo->get_course()->cacherev);
 286          $prevcacherev = $cacherev;
 287  
 288          // Rebuild course cache. Cacherev must be incremented everywhere.
 289          rebuild_course_cache($course->id);
 290          $cacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));
 291          $this->assertGreaterThan($prevcacherev, $cacherev);
 292          $cachedvalue = $cache->get($course->id);
 293          $this->assertNotEmpty($cachedvalue);
 294          $this->assertEquals($cacherev, $cachedvalue->cacherev);
 295          $modinfo = get_fast_modinfo($course->id);
 296          $this->assertEquals($cacherev, $modinfo->get_course()->cacherev);
 297          $prevcacherev = $cacherev;
 298  
 299          // Update cacherev in DB and make sure the cache will be rebuilt on the next call to get_fast_modinfo().
 300          increment_revision_number('course', 'cacherev', 'id = ?', array($course->id));
 301          // We need to clear static cache for course_modinfo instances too.
 302          course_modinfo::clear_instance_cache();
 303          $modinfo = get_fast_modinfo($course->id);
 304          $cacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));
 305          $this->assertGreaterThan($prevcacherev, $cacherev);
 306          $cachedvalue = $cache->get($course->id);
 307          $this->assertNotEmpty($cachedvalue);
 308          $this->assertEquals($cacherev, $cachedvalue->cacherev);
 309          $this->assertEquals($cacherev, $modinfo->get_course()->cacherev);
 310          $prevcacherev = $cacherev;
 311  
 312          // Reset cache for all courses and make sure this course cache is reset.
 313          rebuild_course_cache(0, true);
 314          $cacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));
 315          $this->assertGreaterThan($prevcacherev, $cacherev);
 316          $this->assertEmpty($cache->get($course->id));
 317          // Rebuild again.
 318          $modinfo = get_fast_modinfo($course->id);
 319          $cachedvalue = $cache->get($course->id);
 320          $this->assertNotEmpty($cachedvalue);
 321          $this->assertEquals($cacherev, $cachedvalue->cacherev);
 322          $this->assertEquals($cacherev, $modinfo->get_course()->cacherev);
 323          $prevcacherev = $cacherev;
 324  
 325          // Purge all caches and make sure cacherev is increased and data from MUC erased.
 326          purge_all_caches();
 327          $cacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));
 328          $this->assertGreaterThan($prevcacherev, $cacherev);
 329          $this->assertEmpty($cache->get($course->id));
 330      }
 331  
 332      public function test_course_modinfo_properties() {
 333          global $USER, $DB;
 334  
 335          $this->resetAfterTest();
 336          $this->setAdminUser();
 337  
 338          // Generate the course and some modules. Make one section hidden.
 339          $course = $this->getDataGenerator()->create_course(
 340                  array('format' => 'topics',
 341                      'numsections' => 3),
 342                  array('createsections' => true));
 343          $DB->execute('UPDATE {course_sections} SET visible = 0 WHERE course = ? and section = ?',
 344                  array($course->id, 3));
 345          $coursecontext = context_course::instance($course->id);
 346          $forum0 = $this->getDataGenerator()->create_module('forum',
 347                  array('course' => $course->id), array('section' => 0));
 348          $assign0 = $this->getDataGenerator()->create_module('assign',
 349                  array('course' => $course->id), array('section' => 0, 'visible' => 0));
 350          $forum1 = $this->getDataGenerator()->create_module('forum',
 351                  array('course' => $course->id), array('section' => 1));
 352          $assign1 = $this->getDataGenerator()->create_module('assign',
 353                  array('course' => $course->id), array('section' => 1));
 354          $page1 = $this->getDataGenerator()->create_module('page',
 355                  array('course' => $course->id), array('section' => 1));
 356          $page3 = $this->getDataGenerator()->create_module('page',
 357                  array('course' => $course->id), array('section' => 3));
 358  
 359          $modinfo = get_fast_modinfo($course->id);
 360  
 361          $this->assertEquals(array($forum0->cmid, $assign0->cmid, $forum1->cmid, $assign1->cmid, $page1->cmid, $page3->cmid),
 362                  array_keys($modinfo->cms));
 363          $this->assertEquals($course->id, $modinfo->courseid);
 364          $this->assertEquals($USER->id, $modinfo->userid);
 365          $this->assertEquals(array(0 => array($forum0->cmid, $assign0->cmid),
 366              1 => array($forum1->cmid, $assign1->cmid, $page1->cmid), 3 => array($page3->cmid)), $modinfo->sections);
 367          $this->assertEquals(array('forum', 'assign', 'page'), array_keys($modinfo->instances));
 368          $this->assertEquals(array($assign0->id, $assign1->id), array_keys($modinfo->instances['assign']));
 369          $this->assertEquals(array($forum0->id, $forum1->id), array_keys($modinfo->instances['forum']));
 370          $this->assertEquals(array($page1->id, $page3->id), array_keys($modinfo->instances['page']));
 371          $this->assertEquals(groups_get_user_groups($course->id), $modinfo->groups);
 372          $this->assertEquals(array(0 => array($forum0->cmid, $assign0->cmid),
 373              1 => array($forum1->cmid, $assign1->cmid, $page1->cmid),
 374              3 => array($page3->cmid)), $modinfo->get_sections());
 375          $this->assertEquals(array(0, 1, 2, 3), array_keys($modinfo->get_section_info_all()));
 376          $this->assertEquals($forum0->cmid . ',' . $assign0->cmid, $modinfo->get_section_info(0)->sequence);
 377          $this->assertEquals($forum1->cmid . ',' . $assign1->cmid . ',' . $page1->cmid, $modinfo->get_section_info(1)->sequence);
 378          $this->assertEquals('', $modinfo->get_section_info(2)->sequence);
 379          $this->assertEquals($page3->cmid, $modinfo->get_section_info(3)->sequence);
 380          $this->assertEquals($course->id, $modinfo->get_course()->id);
 381          $names = array_keys($modinfo->get_used_module_names());
 382          sort($names);
 383          $this->assertEquals(array('assign', 'forum', 'page'), $names);
 384          $names = array_keys($modinfo->get_used_module_names(true));
 385          sort($names);
 386          $this->assertEquals(array('assign', 'forum', 'page'), $names);
 387          // Admin can see hidden modules/sections.
 388          $this->assertTrue($modinfo->cms[$assign0->cmid]->uservisible);
 389          $this->assertTrue($modinfo->get_section_info(3)->uservisible);
 390  
 391          // Get modinfo for non-current user (without capability to view hidden activities/sections).
 392          $user = $this->getDataGenerator()->create_user();
 393          $modinfo = get_fast_modinfo($course->id, $user->id);
 394          $this->assertEquals($user->id, $modinfo->userid);
 395          $this->assertFalse($modinfo->cms[$assign0->cmid]->uservisible);
 396          $this->assertFalse($modinfo->get_section_info(3)->uservisible);
 397  
 398          // Attempt to access and set non-existing field.
 399          $this->assertTrue(empty($modinfo->somefield));
 400          $this->assertFalse(isset($modinfo->somefield));
 401          $modinfo->somefield;
 402          $this->assertDebuggingCalled();
 403          $modinfo->somefield = 'Some value';
 404          $this->assertDebuggingCalled();
 405          $this->assertEmpty($modinfo->somefield);
 406          $this->assertDebuggingCalled();
 407  
 408          // Attempt to overwrite existing field.
 409          $this->assertFalse(empty($modinfo->cms));
 410          $this->assertTrue(isset($modinfo->cms));
 411          $modinfo->cms = 'Illegal overwriting';
 412          $this->assertDebuggingCalled();
 413          $this->assertNotEquals('Illegal overwriting', $modinfo->cms);
 414      }
 415  
 416      public function test_is_user_access_restricted_by_capability() {
 417          global $DB;
 418  
 419          $this->resetAfterTest();
 420  
 421          // Create a course and a mod_assign instance.
 422          $course = $this->getDataGenerator()->create_course();
 423          $assign = $this->getDataGenerator()->create_module('assign', array('course'=>$course->id));
 424  
 425          // Create and enrol a student.
 426          $coursecontext = context_course::instance($course->id);
 427          $studentrole = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
 428          $student = $this->getDataGenerator()->create_user();
 429          role_assign($studentrole->id, $student->id, $coursecontext);
 430          $enrolplugin = enrol_get_plugin('manual');
 431          $enrolinstance = $DB->get_record('enrol', array('courseid' => $course->id, 'enrol' => 'manual'));
 432          $enrolplugin->enrol_user($enrolinstance, $student->id);
 433          $this->setUser($student);
 434  
 435          // Make sure student can see the module.
 436          $cm = get_fast_modinfo($course->id)->instances['assign'][$assign->id];
 437          $this->assertTrue($cm->uservisible);
 438          $this->assertFalse($cm->is_user_access_restricted_by_capability());
 439  
 440          // Prohibit student to view mod_assign for the course.
 441          role_change_permission($studentrole->id, $coursecontext, 'mod/assign:view', CAP_PROHIBIT);
 442          get_fast_modinfo($course->id, 0, true);
 443          $cm = get_fast_modinfo($course->id)->instances['assign'][$assign->id];
 444          $this->assertFalse($cm->uservisible);
 445          $this->assertTrue($cm->is_user_access_restricted_by_capability());
 446  
 447          // Restore permission to student to view mod_assign for the course.
 448          role_change_permission($studentrole->id, $coursecontext, 'mod/assign:view', CAP_INHERIT);
 449          get_fast_modinfo($course->id, 0, true);
 450          $cm = get_fast_modinfo($course->id)->instances['assign'][$assign->id];
 451          $this->assertTrue($cm->uservisible);
 452          $this->assertFalse($cm->is_user_access_restricted_by_capability());
 453  
 454          // Prohibit student to view mod_assign for the particular module.
 455          role_change_permission($studentrole->id, context_module::instance($cm->id), 'mod/assign:view', CAP_PROHIBIT);
 456          get_fast_modinfo($course->id, 0, true);
 457          $cm = get_fast_modinfo($course->id)->instances['assign'][$assign->id];
 458          $this->assertFalse($cm->uservisible);
 459          $this->assertTrue($cm->is_user_access_restricted_by_capability());
 460  
 461          // Check calling get_fast_modinfo() for different user:
 462          $this->setAdminUser();
 463          $cm = get_fast_modinfo($course->id)->instances['assign'][$assign->id];
 464          $this->assertTrue($cm->uservisible);
 465          $this->assertFalse($cm->is_user_access_restricted_by_capability());
 466          $cm = get_fast_modinfo($course->id, $student->id)->instances['assign'][$assign->id];
 467          $this->assertFalse($cm->uservisible);
 468          $this->assertTrue($cm->is_user_access_restricted_by_capability());
 469      }
 470  
 471      /**
 472       * Tests for function cm_info::get_course_module_record()
 473       */
 474      public function test_cm_info_get_course_module_record() {
 475          global $DB;
 476  
 477          $this->resetAfterTest();
 478          $this->setAdminUser();
 479  
 480          set_config('enableavailability', 1);
 481          set_config('enablecompletion', 1);
 482  
 483          $course = $this->getDataGenerator()->create_course(
 484                  array('format' => 'topics', 'numsections' => 3, 'enablecompletion' => 1),
 485                  array('createsections' => true));
 486          $mods = array();
 487          $mods[0] = $this->getDataGenerator()->create_module('forum', array('course' => $course->id));
 488          $mods[1] = $this->getDataGenerator()->create_module('assign',
 489                  array('course' => $course->id,
 490                      'section' => 3,
 491                      'idnumber' => '12345',
 492                      'showdescription' => true
 493                      ));
 494          // Pick a small valid availability value to use.
 495          $availabilityvalue = '{"op":"|","show":true,"c":[{"type":"date","d":">=","t":4}]}';
 496          $mods[2] = $this->getDataGenerator()->create_module('book',
 497                  array('course' => $course->id,
 498                      'indent' => 5,
 499                      'availability' => $availabilityvalue,
 500                      'showdescription' => false,
 501                      'completion' => true,
 502                      'completionview' => true,
 503                      'completionexpected' => time() + 5000,
 504                      ));
 505          $mods[3] = $this->getDataGenerator()->create_module('forum',
 506                  array('course' => $course->id,
 507                      'visible' => 0,
 508                      'groupmode' => 1,
 509                      'availability' => null));
 510          $mods[4] = $this->getDataGenerator()->create_module('forum',
 511                  array('course' => $course->id,
 512                      'grouping' => 12));
 513  
 514          $modinfo = get_fast_modinfo($course->id);
 515  
 516          // Make sure that object returned by get_course_module_record(false) has exactly the same fields as DB table 'course_modules'.
 517          $dbfields = array_keys($DB->get_columns('course_modules'));
 518          sort($dbfields);
 519          $cmrecord = $modinfo->get_cm($mods[0]->cmid)->get_course_module_record();
 520          $cmrecordfields = array_keys((array)$cmrecord);
 521          sort($cmrecordfields);
 522          $this->assertEquals($dbfields, $cmrecordfields);
 523  
 524          // Make sure that object returned by get_course_module_record(true) has exactly the same fields
 525          // as object returned by get_coursemodule_from_id(,,,true,);
 526          $cmrecordfull = $modinfo->get_cm($mods[0]->cmid)->get_course_module_record(true);
 527          $cmrecordfullfields = array_keys((array)$cmrecordfull);
 528          $cm = get_coursemodule_from_id(null, $mods[0]->cmid, 0, true, MUST_EXIST);
 529          $cmfields = array_keys((array)$cm);
 530          $this->assertEquals($cmfields, $cmrecordfullfields);
 531  
 532          // Make sure that object returned by get_course_module_record(true) has exactly the same fields
 533          // as object returned by get_coursemodule_from_instance(,,,true,);
 534          $cm = get_coursemodule_from_instance('forum', $mods[0]->id, null, true, MUST_EXIST);
 535          $cmfields = array_keys((array)$cm);
 536          $this->assertEquals($cmfields, $cmrecordfullfields);
 537  
 538          // Make sure the objects have the same properties.
 539          $cm1 = get_coursemodule_from_id(null, $mods[0]->cmid, 0, true, MUST_EXIST);
 540          $cm2 = get_coursemodule_from_instance('forum', $mods[0]->id, 0, true, MUST_EXIST);
 541          $cminfo = $modinfo->get_cm($mods[0]->cmid);
 542          $record = $DB->get_record('course_modules', array('id' => $mods[0]->cmid));
 543          $this->assertEquals($record, $cminfo->get_course_module_record());
 544          $this->assertEquals($cm1, $cminfo->get_course_module_record(true));
 545          $this->assertEquals($cm2, $cminfo->get_course_module_record(true));
 546  
 547          $cm1 = get_coursemodule_from_id(null, $mods[1]->cmid, 0, true, MUST_EXIST);
 548          $cm2 = get_coursemodule_from_instance('assign', $mods[1]->id, 0, true, MUST_EXIST);
 549          $cminfo = $modinfo->get_cm($mods[1]->cmid);
 550          $record = $DB->get_record('course_modules', array('id' => $mods[1]->cmid));
 551          $this->assertEquals($record, $cminfo->get_course_module_record());
 552          $this->assertEquals($cm1, $cminfo->get_course_module_record(true));
 553          $this->assertEquals($cm2, $cminfo->get_course_module_record(true));
 554  
 555          $cm1 = get_coursemodule_from_id(null, $mods[2]->cmid, 0, true, MUST_EXIST);
 556          $cm2 = get_coursemodule_from_instance('book', $mods[2]->id, 0, true, MUST_EXIST);
 557          $cminfo = $modinfo->get_cm($mods[2]->cmid);
 558          $record = $DB->get_record('course_modules', array('id' => $mods[2]->cmid));
 559          $this->assertEquals($record, $cminfo->get_course_module_record());
 560          $this->assertEquals($cm1, $cminfo->get_course_module_record(true));
 561          $this->assertEquals($cm2, $cminfo->get_course_module_record(true));
 562  
 563          $cm1 = get_coursemodule_from_id(null, $mods[3]->cmid, 0, true, MUST_EXIST);
 564          $cm2 = get_coursemodule_from_instance('forum', $mods[3]->id, 0, true, MUST_EXIST);
 565          $cminfo = $modinfo->get_cm($mods[3]->cmid);
 566          $record = $DB->get_record('course_modules', array('id' => $mods[3]->cmid));
 567          $this->assertEquals($record, $cminfo->get_course_module_record());
 568          $this->assertEquals($cm1, $cminfo->get_course_module_record(true));
 569          $this->assertEquals($cm2, $cminfo->get_course_module_record(true));
 570  
 571          $cm1 = get_coursemodule_from_id(null, $mods[4]->cmid, 0, true, MUST_EXIST);
 572          $cm2 = get_coursemodule_from_instance('forum', $mods[4]->id, 0, true, MUST_EXIST);
 573          $cminfo = $modinfo->get_cm($mods[4]->cmid);
 574          $record = $DB->get_record('course_modules', array('id' => $mods[4]->cmid));
 575          $this->assertEquals($record, $cminfo->get_course_module_record());
 576          $this->assertEquals($cm1, $cminfo->get_course_module_record(true));
 577          $this->assertEquals($cm2, $cminfo->get_course_module_record(true));
 578  
 579      }
 580  
 581      /**
 582       * Tests the availability property that has been added to course modules
 583       * and sections (just to see that it is correctly saved and accessed).
 584       */
 585      public function test_availability_property() {
 586          global $DB, $CFG;
 587  
 588          $this->resetAfterTest();
 589  
 590          // Create a course with two modules and three sections.
 591          $course = $this->getDataGenerator()->create_course(
 592                  array('format' => 'topics', 'numsections' => 3),
 593                  array('createsections' => true));
 594          $forum = $this->getDataGenerator()->create_module('forum',
 595                  array('course' => $course->id));
 596          $forum2 = $this->getDataGenerator()->create_module('forum',
 597                  array('course' => $course->id));
 598  
 599          // Get modinfo. Check that availability is null for both cm and sections.
 600          $modinfo = get_fast_modinfo($course->id);
 601          $cm = $modinfo->get_cm($forum->cmid);
 602          $this->assertNull($cm->availability);
 603          $section = $modinfo->get_section_info(1, MUST_EXIST);
 604          $this->assertNull($section->availability);
 605  
 606          // Update availability for cm and section in database.
 607          $DB->set_field('course_modules', 'availability', '{}', array('id' => $cm->id));
 608          $DB->set_field('course_sections', 'availability', '{}', array('id' => $section->id));
 609  
 610          // Clear cache and get modinfo again.
 611          rebuild_course_cache($course->id, true);
 612          get_fast_modinfo(0, 0, true);
 613          $modinfo = get_fast_modinfo($course->id);
 614  
 615          // Check values that were changed.
 616          $cm = $modinfo->get_cm($forum->cmid);
 617          $this->assertEquals('{}', $cm->availability);
 618          $section = $modinfo->get_section_info(1, MUST_EXIST);
 619          $this->assertEquals('{}', $section->availability);
 620  
 621          // Check other values are still null.
 622          $cm = $modinfo->get_cm($forum2->cmid);
 623          $this->assertNull($cm->availability);
 624          $section = $modinfo->get_section_info(2, MUST_EXIST);
 625          $this->assertNull($section->availability);
 626      }
 627  
 628      /**
 629       * Tests for get_groups() method.
 630       */
 631      public function test_get_groups() {
 632          $this->resetAfterTest();
 633          $generator = $this->getDataGenerator();
 634  
 635          // Create courses.
 636          $course1 = $generator->create_course();
 637          $course2 = $generator->create_course();
 638          $course3 = $generator->create_course();
 639  
 640          // Create users.
 641          $user1 = $generator->create_user();
 642          $user2 = $generator->create_user();
 643          $user3 = $generator->create_user();
 644  
 645          // Enrol users on courses.
 646          $generator->enrol_user($user1->id, $course1->id);
 647          $generator->enrol_user($user2->id, $course2->id);
 648          $generator->enrol_user($user3->id, $course2->id);
 649          $generator->enrol_user($user3->id, $course3->id);
 650  
 651          // Create groups.
 652          $group1 = $generator->create_group(array('courseid' => $course1->id));
 653          $group2 = $generator->create_group(array('courseid' => $course2->id));
 654          $group3 = $generator->create_group(array('courseid' => $course2->id));
 655  
 656          // Assign users to groups and assert the result.
 657          $this->assertTrue($generator->create_group_member(array('groupid' => $group1->id, 'userid' => $user1->id)));
 658          $this->assertTrue($generator->create_group_member(array('groupid' => $group2->id, 'userid' => $user2->id)));
 659          $this->assertTrue($generator->create_group_member(array('groupid' => $group3->id, 'userid' => $user2->id)));
 660          $this->assertTrue($generator->create_group_member(array('groupid' => $group2->id, 'userid' => $user3->id)));
 661  
 662          // Create groupings.
 663          $grouping1 = $generator->create_grouping(array('courseid' => $course1->id));
 664          $grouping2 = $generator->create_grouping(array('courseid' => $course2->id));
 665  
 666          // Assign and assert group to groupings.
 667          groups_assign_grouping($grouping1->id, $group1->id);
 668          groups_assign_grouping($grouping2->id, $group2->id);
 669          groups_assign_grouping($grouping2->id, $group3->id);
 670  
 671          // Test with one single group.
 672          $modinfo = get_fast_modinfo($course1, $user1->id);
 673          $groups = $modinfo->get_groups($grouping1->id);
 674          $this->assertCount(1, $groups);
 675          $this->assertArrayHasKey($group1->id, $groups);
 676  
 677          // Test with two groups.
 678          $modinfo = get_fast_modinfo($course2, $user2->id);
 679          $groups = $modinfo->get_groups();
 680          $this->assertCount(2, $groups);
 681          $this->assertTrue(in_array($group2->id, $groups));
 682          $this->assertTrue(in_array($group3->id, $groups));
 683  
 684          // Test with no groups.
 685          $modinfo = get_fast_modinfo($course3, $user3->id);
 686          $groups = $modinfo->get_groups();
 687          $this->assertCount(0, $groups);
 688          $this->assertArrayNotHasKey($group1->id, $groups);
 689      }
 690  
 691      /**
 692       * Tests the function for constructing a cm_info from mixed data.
 693       */
 694      public function test_create() {
 695          global $CFG, $DB;
 696          $this->resetAfterTest();
 697  
 698          // Create a course and an activity.
 699          $generator = $this->getDataGenerator();
 700          $course = $generator->create_course();
 701          $page = $generator->create_module('page', array('course' => $course->id,
 702                  'name' => 'Annie'));
 703  
 704          // Null is passed through.
 705          $this->assertNull(cm_info::create(null));
 706  
 707          // Stdclass object turns into cm_info.
 708          $cm = cm_info::create(
 709                  (object)array('id' => $page->cmid, 'course' => $course->id));
 710          $this->assertInstanceOf('cm_info', $cm);
 711          $this->assertEquals('Annie', $cm->name);
 712  
 713          // A cm_info object stays as cm_info.
 714          $this->assertSame($cm, cm_info::create($cm));
 715  
 716          // Invalid object (missing fields) causes error.
 717          try {
 718              cm_info::create((object)array('id' => $page->cmid));
 719              $this->fail();
 720          } catch (Exception $e) {
 721              $this->assertInstanceOf('coding_exception', $e);
 722          }
 723  
 724          // Create a second hidden activity.
 725          $hiddenpage = $generator->create_module('page', array('course' => $course->id,
 726                  'name' => 'Annie', 'visible' => 0));
 727  
 728          // Create 2 user accounts, one is a manager who can see everything.
 729          $user = $generator->create_user();
 730          $generator->enrol_user($user->id, $course->id);
 731          $manager = $generator->create_user();
 732          $generator->enrol_user($manager->id, $course->id,
 733                  $DB->get_field('role', 'id', array('shortname' => 'manager'), MUST_EXIST));
 734  
 735          // User can see the normal page but not the hidden one.
 736          $cm = cm_info::create((object)array('id' => $page->cmid, 'course' => $course->id),
 737                  $user->id);
 738          $this->assertTrue($cm->uservisible);
 739          $cm = cm_info::create((object)array('id' => $hiddenpage->cmid, 'course' => $course->id),
 740                  $user->id);
 741          $this->assertFalse($cm->uservisible);
 742  
 743          // Manager can see the hidden one too.
 744          $cm = cm_info::create((object)array('id' => $hiddenpage->cmid, 'course' => $course->id),
 745                  $manager->id);
 746          $this->assertTrue($cm->uservisible);
 747      }
 748  
 749      /**
 750       * Tests function for getting $course and $cm at once quickly from modinfo
 751       * based on cmid or cm record.
 752       */
 753      public function test_get_course_and_cm_from_cmid() {
 754          global $CFG, $DB;
 755          $this->resetAfterTest();
 756  
 757          // Create a course and an activity.
 758          $generator = $this->getDataGenerator();
 759          $course = $generator->create_course(array('shortname' => 'Halls'));
 760          $page = $generator->create_module('page', array('course' => $course->id,
 761                  'name' => 'Annie'));
 762  
 763          // Successful usage.
 764          list($course, $cm) = get_course_and_cm_from_cmid($page->cmid);
 765          $this->assertEquals('Halls', $course->shortname);
 766          $this->assertInstanceOf('cm_info', $cm);
 767          $this->assertEquals('Annie', $cm->name);
 768  
 769          // Specified module type.
 770          list($course, $cm) = get_course_and_cm_from_cmid($page->cmid, 'page');
 771          $this->assertEquals('Annie', $cm->name);
 772  
 773          // With id in object.
 774          $fakecm = (object)array('id' => $page->cmid);
 775          list($course, $cm) = get_course_and_cm_from_cmid($fakecm);
 776          $this->assertEquals('Halls', $course->shortname);
 777          $this->assertEquals('Annie', $cm->name);
 778  
 779          // With both id and course in object.
 780          $fakecm->course = $course->id;
 781          list($course, $cm) = get_course_and_cm_from_cmid($fakecm);
 782          $this->assertEquals('Halls', $course->shortname);
 783          $this->assertEquals('Annie', $cm->name);
 784  
 785          // With supplied course id.
 786          list($course, $cm) = get_course_and_cm_from_cmid($page->cmid, 'page', $course->id);
 787          $this->assertEquals('Annie', $cm->name);
 788  
 789          // With supplied course object (modified just so we can check it is
 790          // indeed reusing the supplied object).
 791          $course->silly = true;
 792          list($course, $cm) = get_course_and_cm_from_cmid($page->cmid, 'page', $course);
 793          $this->assertEquals('Annie', $cm->name);
 794          $this->assertTrue($course->silly);
 795  
 796          // Incorrect module type.
 797          try {
 798              get_course_and_cm_from_cmid($page->cmid, 'forum');
 799              $this->fail();
 800          } catch (moodle_exception $e) {
 801              $this->assertEquals('invalidcoursemodule', $e->errorcode);
 802          }
 803  
 804          // Invalid module name.
 805          try {
 806              get_course_and_cm_from_cmid($page->cmid, 'pigs can fly');
 807              $this->fail();
 808          } catch (coding_exception $e) {
 809              $this->assertStringContainsString('Invalid modulename parameter', $e->getMessage());
 810          }
 811  
 812          // Doesn't exist.
 813          try {
 814              get_course_and_cm_from_cmid($page->cmid + 1);
 815              $this->fail();
 816          } catch (moodle_exception $e) {
 817              $this->assertInstanceOf('dml_exception', $e);
 818          }
 819  
 820          // Create a second hidden activity.
 821          $hiddenpage = $generator->create_module('page', array('course' => $course->id,
 822                  'name' => 'Annie', 'visible' => 0));
 823  
 824          // Create 2 user accounts, one is a manager who can see everything.
 825          $user = $generator->create_user();
 826          $generator->enrol_user($user->id, $course->id);
 827          $manager = $generator->create_user();
 828          $generator->enrol_user($manager->id, $course->id,
 829                  $DB->get_field('role', 'id', array('shortname' => 'manager'), MUST_EXIST));
 830  
 831          // User can see the normal page but not the hidden one.
 832          list($course, $cm) = get_course_and_cm_from_cmid($page->cmid, 'page', 0, $user->id);
 833          $this->assertTrue($cm->uservisible);
 834          list($course, $cm) = get_course_and_cm_from_cmid($hiddenpage->cmid, 'page', 0, $user->id);
 835          $this->assertFalse($cm->uservisible);
 836  
 837          // Manager can see the hidden one too.
 838          list($course, $cm) = get_course_and_cm_from_cmid($hiddenpage->cmid, 'page', 0, $manager->id);
 839          $this->assertTrue($cm->uservisible);
 840      }
 841  
 842      /**
 843       * Tests function for getting $course and $cm at once quickly from modinfo
 844       * based on instance id or record.
 845       */
 846      public function test_get_course_and_cm_from_instance() {
 847          global $CFG, $DB;
 848          $this->resetAfterTest();
 849  
 850          // Create a course and an activity.
 851          $generator = $this->getDataGenerator();
 852          $course = $generator->create_course(array('shortname' => 'Halls'));
 853          $page = $generator->create_module('page', array('course' => $course->id,
 854                  'name' => 'Annie'));
 855  
 856          // Successful usage.
 857          list($course, $cm) = get_course_and_cm_from_instance($page->id, 'page');
 858          $this->assertEquals('Halls', $course->shortname);
 859          $this->assertInstanceOf('cm_info', $cm);
 860          $this->assertEquals('Annie', $cm->name);
 861  
 862          // With id in object.
 863          $fakeinstance = (object)array('id' => $page->id);
 864          list($course, $cm) = get_course_and_cm_from_instance($fakeinstance, 'page');
 865          $this->assertEquals('Halls', $course->shortname);
 866          $this->assertEquals('Annie', $cm->name);
 867  
 868          // With both id and course in object.
 869          $fakeinstance->course = $course->id;
 870          list($course, $cm) = get_course_and_cm_from_instance($fakeinstance, 'page');
 871          $this->assertEquals('Halls', $course->shortname);
 872          $this->assertEquals('Annie', $cm->name);
 873  
 874          // With supplied course id.
 875          list($course, $cm) = get_course_and_cm_from_instance($page->id, 'page', $course->id);
 876          $this->assertEquals('Annie', $cm->name);
 877  
 878          // With supplied course object (modified just so we can check it is
 879          // indeed reusing the supplied object).
 880          $course->silly = true;
 881          list($course, $cm) = get_course_and_cm_from_instance($page->id, 'page', $course);
 882          $this->assertEquals('Annie', $cm->name);
 883          $this->assertTrue($course->silly);
 884  
 885          // Doesn't exist (or is wrong type).
 886          try {
 887              get_course_and_cm_from_instance($page->id, 'forum');
 888              $this->fail();
 889          } catch (moodle_exception $e) {
 890              $this->assertInstanceOf('dml_exception', $e);
 891          }
 892  
 893          // Invalid module name.
 894          try {
 895              get_course_and_cm_from_cmid($page->cmid, '1337 h4x0ring');
 896              $this->fail();
 897          } catch (coding_exception $e) {
 898              $this->assertStringContainsString('Invalid modulename parameter', $e->getMessage());
 899          }
 900  
 901          // Create a second hidden activity.
 902          $hiddenpage = $generator->create_module('page', array('course' => $course->id,
 903                  'name' => 'Annie', 'visible' => 0));
 904  
 905          // Create 2 user accounts, one is a manager who can see everything.
 906          $user = $generator->create_user();
 907          $generator->enrol_user($user->id, $course->id);
 908          $manager = $generator->create_user();
 909          $generator->enrol_user($manager->id, $course->id,
 910                  $DB->get_field('role', 'id', array('shortname' => 'manager'), MUST_EXIST));
 911  
 912          // User can see the normal page but not the hidden one.
 913          list($course, $cm) = get_course_and_cm_from_cmid($page->cmid, 'page', 0, $user->id);
 914          $this->assertTrue($cm->uservisible);
 915          list($course, $cm) = get_course_and_cm_from_cmid($hiddenpage->cmid, 'page', 0, $user->id);
 916          $this->assertFalse($cm->uservisible);
 917  
 918          // Manager can see the hidden one too.
 919          list($course, $cm) = get_course_and_cm_from_cmid($hiddenpage->cmid, 'page', 0, $manager->id);
 920          $this->assertTrue($cm->uservisible);
 921      }
 922  }