Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.
/lib/ -> modinfolib.php (source)

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

   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   * modinfolib.php - Functions/classes relating to cached information about module instances on
  19   * a course.
  20   * @package    core
  21   * @subpackage lib
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   * @author     sam marshall
  24   */
  25  
  26  
  27  // Maximum number of modinfo items to keep in memory cache. Do not increase this to a large
  28  // number because:
  29  // a) modinfo can be big (megabyte range) for some courses
  30  // b) performance of cache will deteriorate if there are very many items in it
  31  if (!defined('MAX_MODINFO_CACHE_SIZE')) {
  32      define('MAX_MODINFO_CACHE_SIZE', 10);
  33  }
  34  
  35  
  36  /**
  37   * Information about a course that is cached in the course table 'modinfo' field (and then in
  38   * memory) in order to reduce the need for other database queries.
  39   *
  40   * This includes information about the course-modules and the sections on the course. It can also
  41   * include dynamic data that has been updated for the current user.
  42   *
  43   * Use {@link get_fast_modinfo()} to retrieve the instance of the object for particular course
  44   * and particular user.
  45   *
  46   * @property-read int $courseid Course ID
  47   * @property-read int $userid User ID
  48   * @property-read array $sections Array from section number (e.g. 0) to array of course-module IDs in that
  49   *     section; this only includes sections that contain at least one course-module
  50   * @property-read cm_info[] $cms Array from course-module instance to cm_info object within this course, in
  51   *     order of appearance
  52   * @property-read cm_info[][] $instances Array from string (modname) => int (instance id) => cm_info object
  53   * @property-read array $groups Groups that the current user belongs to. Calculated on the first request.
  54   *     Is an array of grouping id => array of group id => group id. Includes grouping id 0 for 'all groups'
  55   */
  56  class course_modinfo {
  57      /** @var int Maximum time the course cache building lock can be held */
  58      const COURSE_CACHE_LOCK_EXPIRY = 180;
  59  
  60      /** @var int Time to wait for the course cache building lock before throwing an exception */
  61      const COURSE_CACHE_LOCK_WAIT = 60;
  62  
  63      /**
  64       * List of fields from DB table 'course' that are cached in MUC and are always present in course_modinfo::$course
  65       * @var array
  66       */
  67      public static $cachedfields = array('shortname', 'fullname', 'format',
  68              'enablecompletion', 'groupmode', 'groupmodeforce', 'cacherev');
  69  
  70      /**
  71       * For convenience we store the course object here as it is needed in other parts of code
  72       * @var stdClass
  73       */
  74      private $course;
  75  
  76      /**
  77       * Array of section data from cache
  78       * @var section_info[]
  79       */
  80      private $sectioninfo;
  81  
  82      /**
  83       * User ID
  84       * @var int
  85       */
  86      private $userid;
  87  
  88      /**
  89       * Array from int (section num, e.g. 0) => array of int (course-module id); this list only
  90       * includes sections that actually contain at least one course-module
  91       * @var array
  92       */
  93      private $sections;
  94  
  95      /**
  96       * Array from int (cm id) => cm_info object
  97       * @var cm_info[]
  98       */
  99      private $cms;
 100  
 101      /**
 102       * Array from string (modname) => int (instance id) => cm_info object
 103       * @var cm_info[][]
 104       */
 105      private $instances;
 106  
 107      /**
 108       * Groups that the current user belongs to. This value is calculated on first
 109       * request to the property or function.
 110       * When set, it is an array of grouping id => array of group id => group id.
 111       * Includes grouping id 0 for 'all groups'.
 112       * @var int[][]
 113       */
 114      private $groups;
 115  
 116      /**
 117       * List of class read-only properties and their getter methods.
 118       * Used by magic functions __get(), __isset(), __empty()
 119       * @var array
 120       */
 121      private static $standardproperties = array(
 122          'courseid' => 'get_course_id',
 123          'userid' => 'get_user_id',
 124          'sections' => 'get_sections',
 125          'cms' => 'get_cms',
 126          'instances' => 'get_instances',
 127          'groups' => 'get_groups_all',
 128      );
 129  
 130      /**
 131       * Magic method getter
 132       *
 133       * @param string $name
 134       * @return mixed
 135       */
 136      public function __get($name) {
 137          if (isset(self::$standardproperties[$name])) {
 138              $method = self::$standardproperties[$name];
 139              return $this->$method();
 140          } else {
 141              debugging('Invalid course_modinfo property accessed: '.$name);
 142              return null;
 143          }
 144      }
 145  
 146      /**
 147       * Magic method for function isset()
 148       *
 149       * @param string $name
 150       * @return bool
 151       */
 152      public function __isset($name) {
 153          if (isset(self::$standardproperties[$name])) {
 154              $value = $this->__get($name);
 155              return isset($value);
 156          }
 157          return false;
 158      }
 159  
 160      /**
 161       * Magic method for function empty()
 162       *
 163       * @param string $name
 164       * @return bool
 165       */
 166      public function __empty($name) {
 167          if (isset(self::$standardproperties[$name])) {
 168              $value = $this->__get($name);
 169              return empty($value);
 170          }
 171          return true;
 172      }
 173  
 174      /**
 175       * Magic method setter
 176       *
 177       * Will display the developer warning when trying to set/overwrite existing property.
 178       *
 179       * @param string $name
 180       * @param mixed $value
 181       */
 182      public function __set($name, $value) {
 183          debugging("It is not allowed to set the property course_modinfo::\${$name}", DEBUG_DEVELOPER);
 184      }
 185  
 186      /**
 187       * Returns course object that was used in the first {@link get_fast_modinfo()} call.
 188       *
 189       * It may not contain all fields from DB table {course} but always has at least the following:
 190       * id,shortname,fullname,format,enablecompletion,groupmode,groupmodeforce,cacherev
 191       *
 192       * @return stdClass
 193       */
 194      public function get_course() {
 195          return $this->course;
 196      }
 197  
 198      /**
 199       * @return int Course ID
 200       */
 201      public function get_course_id() {
 202          return $this->course->id;
 203      }
 204  
 205      /**
 206       * @return int User ID
 207       */
 208      public function get_user_id() {
 209          return $this->userid;
 210      }
 211  
 212      /**
 213       * @return array Array from section number (e.g. 0) to array of course-module IDs in that
 214       *   section; this only includes sections that contain at least one course-module
 215       */
 216      public function get_sections() {
 217          return $this->sections;
 218      }
 219  
 220      /**
 221       * @return cm_info[] Array from course-module instance to cm_info object within this course, in
 222       *   order of appearance
 223       */
 224      public function get_cms() {
 225          return $this->cms;
 226      }
 227  
 228      /**
 229       * Obtains a single course-module object (for a course-module that is on this course).
 230       * @param int $cmid Course-module ID
 231       * @return cm_info Information about that course-module
 232       * @throws moodle_exception If the course-module does not exist
 233       */
 234      public function get_cm($cmid) {
 235          if (empty($this->cms[$cmid])) {
 236              throw new moodle_exception('invalidcoursemodule', 'error');
 237          }
 238          return $this->cms[$cmid];
 239      }
 240  
 241      /**
 242       * Obtains all module instances on this course.
 243       * @return cm_info[][] Array from module name => array from instance id => cm_info
 244       */
 245      public function get_instances() {
 246          return $this->instances;
 247      }
 248  
 249      /**
 250       * Returns array of localised human-readable module names used in this course
 251       *
 252       * @param bool $plural if true returns the plural form of modules names
 253       * @return array
 254       */
 255      public function get_used_module_names($plural = false) {
 256          $modnames = get_module_types_names($plural);
 257          $modnamesused = array();
 258          foreach ($this->get_cms() as $cmid => $mod) {
 259              if (!isset($modnamesused[$mod->modname]) && isset($modnames[$mod->modname]) && $mod->uservisible) {
 260                  $modnamesused[$mod->modname] = $modnames[$mod->modname];
 261              }
 262          }
 263          return $modnamesused;
 264      }
 265  
 266      /**
 267       * Obtains all instances of a particular module on this course.
 268       * @param $modname Name of module (not full frankenstyle) e.g. 'label'
 269       * @return cm_info[] Array from instance id => cm_info for modules on this course; empty if none
 270       */
 271      public function get_instances_of($modname) {
 272          if (empty($this->instances[$modname])) {
 273              return array();
 274          }
 275          return $this->instances[$modname];
 276      }
 277  
 278      /**
 279       * Groups that the current user belongs to organised by grouping id. Calculated on the first request.
 280       * @return int[][] array of grouping id => array of group id => group id. Includes grouping id 0 for 'all groups'
 281       */
 282      private function get_groups_all() {
 283          if (is_null($this->groups)) {
 284              // NOTE: Performance could be improved here. The system caches user groups
 285              // in $USER->groupmember[$courseid] => array of groupid=>groupid. Unfortunately this
 286              // structure does not include grouping information. It probably could be changed to
 287              // do so, without a significant performance hit on login, thus saving this one query
 288              // each request.
 289              $this->groups = groups_get_user_groups($this->course->id, $this->userid);
 290          }
 291          return $this->groups;
 292      }
 293  
 294      /**
 295       * Returns groups that the current user belongs to on the course. Note: If not already
 296       * available, this may make a database query.
 297       * @param int $groupingid Grouping ID or 0 (default) for all groups
 298       * @return int[] Array of int (group id) => int (same group id again); empty array if none
 299       */
 300      public function get_groups($groupingid = 0) {
 301          $allgroups = $this->get_groups_all();
 302          if (!isset($allgroups[$groupingid])) {
 303              return array();
 304          }
 305          return $allgroups[$groupingid];
 306      }
 307  
 308      /**
 309       * Gets all sections as array from section number => data about section.
 310       * @return section_info[] Array of section_info objects organised by section number
 311       */
 312      public function get_section_info_all() {
 313          return $this->sectioninfo;
 314      }
 315  
 316      /**
 317       * Gets data about specific numbered section.
 318       * @param int $sectionnumber Number (not id) of section
 319       * @param int $strictness Use MUST_EXIST to throw exception if it doesn't
 320       * @return section_info Information for numbered section or null if not found
 321       */
 322      public function get_section_info($sectionnumber, $strictness = IGNORE_MISSING) {
 323          if (!array_key_exists($sectionnumber, $this->sectioninfo)) {
 324              if ($strictness === MUST_EXIST) {
 325                  throw new moodle_exception('sectionnotexist');
 326              } else {
 327                  return null;
 328              }
 329          }
 330          return $this->sectioninfo[$sectionnumber];
 331      }
 332  
 333      /**
 334       * Static cache for generated course_modinfo instances
 335       *
 336       * @see course_modinfo::instance()
 337       * @see course_modinfo::clear_instance_cache()
 338       * @var course_modinfo[]
 339       */
 340      protected static $instancecache = array();
 341  
 342      /**
 343       * Timestamps (microtime) when the course_modinfo instances were last accessed
 344       *
 345       * It is used to remove the least recent accessed instances when static cache is full
 346       *
 347       * @var float[]
 348       */
 349      protected static $cacheaccessed = array();
 350  
 351      /**
 352       * Clears the cache used in course_modinfo::instance()
 353       *
 354       * Used in {@link get_fast_modinfo()} when called with argument $reset = true
 355       * and in {@link rebuild_course_cache()}
 356       *
 357       * @param null|int|stdClass $courseorid if specified removes only cached value for this course
 358       */
 359      public static function clear_instance_cache($courseorid = null) {
 360          if (empty($courseorid)) {
 361              self::$instancecache = array();
 362              self::$cacheaccessed = array();
 363              return;
 364          }
 365          if (is_object($courseorid)) {
 366              $courseorid = $courseorid->id;
 367          }
 368          if (isset(self::$instancecache[$courseorid])) {
 369              // Unsetting static variable in PHP is peculiar, it removes the reference,
 370              // but data remain in memory. Prior to unsetting, the varable needs to be
 371              // set to empty to remove its remains from memory.
 372              self::$instancecache[$courseorid] = '';
 373              unset(self::$instancecache[$courseorid]);
 374              unset(self::$cacheaccessed[$courseorid]);
 375          }
 376      }
 377  
 378      /**
 379       * Returns the instance of course_modinfo for the specified course and specified user
 380       *
 381       * This function uses static cache for the retrieved instances. The cache
 382       * size is limited by MAX_MODINFO_CACHE_SIZE. If instance is not found in
 383       * the static cache or it was created for another user or the cacherev validation
 384       * failed - a new instance is constructed and returned.
 385       *
 386       * Used in {@link get_fast_modinfo()}
 387       *
 388       * @param int|stdClass $courseorid object from DB table 'course' (must have field 'id'
 389       *     and recommended to have field 'cacherev') or just a course id
 390       * @param int $userid User id to populate 'availble' and 'uservisible' attributes of modules and sections.
 391       *     Set to 0 for current user (default). Set to -1 to avoid calculation of dynamic user-depended data.
 392       * @return course_modinfo
 393       */
 394      public static function instance($courseorid, $userid = 0) {
 395          global $USER;
 396          if (is_object($courseorid)) {
 397              $course = $courseorid;
 398          } else {
 399              $course = (object)array('id' => $courseorid);
 400          }
 401          if (empty($userid)) {
 402              $userid = $USER->id;
 403          }
 404  
 405          if (!empty(self::$instancecache[$course->id])) {
 406              if (self::$instancecache[$course->id]->userid == $userid &&
 407                      (!isset($course->cacherev) ||
 408                      $course->cacherev == self::$instancecache[$course->id]->get_course()->cacherev)) {
 409                  // This course's modinfo for the same user was recently retrieved, return cached.
 410                  self::$cacheaccessed[$course->id] = microtime(true);
 411                  return self::$instancecache[$course->id];
 412              } else {
 413                  // Prevent potential reference problems when switching users.
 414                  self::clear_instance_cache($course->id);
 415              }
 416          }
 417          $modinfo = new course_modinfo($course, $userid);
 418  
 419          // We have a limit of MAX_MODINFO_CACHE_SIZE entries to store in static variable.
 420          if (count(self::$instancecache) >= MAX_MODINFO_CACHE_SIZE) {
 421              // Find the course that was the least recently accessed.
 422              asort(self::$cacheaccessed, SORT_NUMERIC);
 423              $courseidtoremove = key(array_reverse(self::$cacheaccessed, true));
 424              self::clear_instance_cache($courseidtoremove);
 425          }
 426  
 427          // Add modinfo to the static cache.
 428          self::$instancecache[$course->id] = $modinfo;
 429          self::$cacheaccessed[$course->id] = microtime(true);
 430  
 431          return $modinfo;
 432      }
 433  
 434      /**
 435       * Constructs based on course.
 436       * Note: This constructor should not usually be called directly.
 437       * Use get_fast_modinfo($course) instead as this maintains a cache.
 438       * @param stdClass $course course object, only property id is required.
 439       * @param int $userid User ID
 440       * @throws moodle_exception if course is not found
 441       */
 442      public function __construct($course, $userid) {
 443          global $CFG, $COURSE, $SITE, $DB;
 444  
 445          if (!isset($course->cacherev)) {
 446              // We require presence of property cacherev to validate the course cache.
 447              // No need to clone the $COURSE or $SITE object here because we clone it below anyway.
 448              $course = get_course($course->id, false);
 449          }
 450  
 451          $cachecoursemodinfo = cache::make('core', 'coursemodinfo');
 452  
 453          // Retrieve modinfo from cache. If not present or cacherev mismatches, call rebuild and retrieve again.
 454          $coursemodinfo = $cachecoursemodinfo->get($course->id);
 455          if ($coursemodinfo === false || ($course->cacherev != $coursemodinfo->cacherev)) {
 456              $lock = self::get_course_cache_lock($course->id);
 457              try {
 458                  // Only actually do the build if it's still needed after getting the lock (not if
 459                  // somebody else, who might have been holding the lock, built it already).
 460                  $coursemodinfo = $cachecoursemodinfo->get($course->id);
 461                  if ($coursemodinfo === false || ($course->cacherev != $coursemodinfo->cacherev)) {
 462                      $coursemodinfo = self::inner_build_course_cache($course, $lock);
 463                  }
 464              } finally {
 465                  $lock->release();
 466              }
 467          }
 468  
 469          // Set initial values
 470          $this->userid = $userid;
 471          $this->sections = array();
 472          $this->cms = array();
 473          $this->instances = array();
 474          $this->groups = null;
 475  
 476          // If we haven't already preloaded contexts for the course, do it now
 477          // Modules are also cached here as long as it's the first time this course has been preloaded.
 478          context_helper::preload_course($course->id);
 479  
 480          // Quick integrity check: as a result of race conditions modinfo may not be regenerated after the change.
 481          // It is especially dangerous if modinfo contains the deleted course module, as it results in fatal error.
 482          // We can check it very cheap by validating the existence of module context.
 483          if ($course->id == $COURSE->id || $course->id == $SITE->id) {
 484              // Only verify current course (or frontpage) as pages with many courses may not have module contexts cached.
 485              // (Uncached modules will result in a very slow verification).
 486              foreach ($coursemodinfo->modinfo as $mod) {
 487                  if (!context_module::instance($mod->cm, IGNORE_MISSING)) {
 488                      debugging('Course cache integrity check failed: course module with id '. $mod->cm.
 489                              ' does not have context. Rebuilding cache for course '. $course->id);
 490                      // Re-request the course record from DB as well, don't use get_course() here.
 491                      $course = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST);
 492                      $coursemodinfo = self::build_course_cache($course);
 493                      break;
 494                  }
 495              }
 496          }
 497  
 498          // Overwrite unset fields in $course object with cached values, store the course object.
 499          $this->course = fullclone($course);
 500          foreach ($coursemodinfo as $key => $value) {
 501              if ($key !== 'modinfo' && $key !== 'sectioncache' &&
 502                      (!isset($this->course->$key) || $key === 'cacherev')) {
 503                  $this->course->$key = $value;
 504              }
 505          }
 506  
 507          // Loop through each piece of module data, constructing it
 508          static $modexists = array();
 509          foreach ($coursemodinfo->modinfo as $mod) {
 510              if (!isset($mod->name) || strval($mod->name) === '') {
 511                  // something is wrong here
 512                  continue;
 513              }
 514  
 515              // Skip modules which don't exist
 516              if (!array_key_exists($mod->mod, $modexists)) {
 517                  $modexists[$mod->mod] = file_exists("$CFG->dirroot/mod/$mod->mod/lib.php");
 518              }
 519              if (!$modexists[$mod->mod]) {
 520                  continue;
 521              }
 522  
 523              // Construct info for this module
 524              $cm = new cm_info($this, null, $mod, null);
 525  
 526              // Store module in instances and cms array
 527              if (!isset($this->instances[$cm->modname])) {
 528                  $this->instances[$cm->modname] = array();
 529              }
 530              $this->instances[$cm->modname][$cm->instance] = $cm;
 531              $this->cms[$cm->id] = $cm;
 532  
 533              // Reconstruct sections. This works because modules are stored in order
 534              if (!isset($this->sections[$cm->sectionnum])) {
 535                  $this->sections[$cm->sectionnum] = array();
 536              }
 537              $this->sections[$cm->sectionnum][] = $cm->id;
 538          }
 539  
 540          // Expand section objects
 541          $this->sectioninfo = array();
 542          foreach ($coursemodinfo->sectioncache as $number => $data) {
 543              $this->sectioninfo[$number] = new section_info($data, $number, null, null,
 544                      $this, null);
 545          }
 546      }
 547  
 548      /**
 549       * This method can not be used anymore.
 550       *
 551       * @see course_modinfo::build_course_cache()
 552       * @deprecated since 2.6
 553       */
 554      public static function build_section_cache($courseid) {
 555          throw new coding_exception('Function course_modinfo::build_section_cache() can not be used anymore.' .
 556              ' Please use course_modinfo::build_course_cache() whenever applicable.');
 557      }
 558  
 559      /**
 560       * Builds a list of information about sections on a course to be stored in
 561       * the course cache. (Does not include information that is already cached
 562       * in some other way.)
 563       *
 564       * @param stdClass $course Course object (must contain fields
 565       * @return array Information about sections, indexed by section number (not id)
 566       */
 567      protected static function build_course_section_cache($course) {
 568          global $DB;
 569  
 570          // Get section data
 571          $sections = $DB->get_records('course_sections', array('course' => $course->id), 'section',
 572                  'section, id, course, name, summary, summaryformat, sequence, visible, availability');
 573          $compressedsections = array();
 574  
 575          $formatoptionsdef = course_get_format($course)->section_format_options();
 576          // Remove unnecessary data and add availability
 577          foreach ($sections as $number => $section) {
 578              // Add cached options from course format to $section object
 579              foreach ($formatoptionsdef as $key => $option) {
 580                  if (!empty($option['cache'])) {
 581                      $formatoptions = course_get_format($course)->get_format_options($section);
 582                      if (!array_key_exists('cachedefault', $option) || $option['cachedefault'] !== $formatoptions[$key]) {
 583                          $section->$key = $formatoptions[$key];
 584                      }
 585                  }
 586              }
 587              // Clone just in case it is reused elsewhere
 588              $compressedsections[$number] = clone($section);
 589              section_info::convert_for_section_cache($compressedsections[$number]);
 590          }
 591  
 592          return $compressedsections;
 593      }
 594  
 595      /**
 596       * Gets a lock for rebuilding the cache of a single course.
 597       *
 598       * Caller must release the returned lock.
 599       *
 600       * This is used to ensure that the cache rebuild doesn't happen multiple times in parallel.
 601       * This function will wait up to 1 minute for the lock to be obtained. If the lock cannot
 602       * be obtained, it throws an exception.
 603       *
 604       * @param int $courseid Course id
 605       * @return \core\lock\lock Lock (must be released!)
 606       * @throws moodle_exception If the lock cannot be obtained
 607       */
 608      protected static function get_course_cache_lock($courseid) {
 609          // Get database lock to ensure this doesn't happen multiple times in parallel. Wait a
 610          // reasonable time for the lock to be released, so we can give a suitable error message.
 611          // In case the system crashes while building the course cache, the lock will automatically
 612          // expire after a (slightly longer) period.
 613          $lockfactory = \core\lock\lock_config::get_lock_factory('core_modinfo');
 614          $lock = $lockfactory->get_lock('build_course_cache_' . $courseid,
 615                  self::COURSE_CACHE_LOCK_WAIT, self::COURSE_CACHE_LOCK_EXPIRY);
 616          if (!$lock) {
 617              throw new moodle_exception('locktimeout', '', '', null,
 618                      'core_modinfo/build_course_cache_' . $courseid);
 619          }
 620          return $lock;
 621      }
 622  
 623      /**
 624       * Builds and stores in MUC object containing information about course
 625       * modules and sections together with cached fields from table course.
 626       *
 627       * @param stdClass $course object from DB table course. Must have property 'id'
 628       *     but preferably should have all cached fields.
 629       * @return stdClass object with all cached keys of the course plus fields modinfo and sectioncache.
 630       *     The same object is stored in MUC
 631       * @throws moodle_exception if course is not found (if $course object misses some of the
 632       *     necessary fields it is re-requested from database)
 633       */
 634      public static function build_course_cache($course) {
 635          if (empty($course->id)) {
 636              throw new coding_exception('Object $course is missing required property \id\'');
 637          }
 638  
 639          $lock = self::get_course_cache_lock($course->id);
 640          try {
 641              return self::inner_build_course_cache($course, $lock);
 642          } finally {
 643              $lock->release();
 644          }
 645      }
 646  
 647      /**
 648       * Called to build course cache when there is already a lock obtained.
 649       *
 650       * @param stdClass $course object from DB table course
 651       * @param \core\lock\lock $lock Lock object - not actually used, just there to indicate you have a lock
 652       * @return stdClass Course object that has been stored in MUC
 653       */
 654      protected static function inner_build_course_cache($course, \core\lock\lock $lock) {
 655          global $DB, $CFG;
 656          require_once("{$CFG->dirroot}/course/lib.php");
 657  
 658          // Ensure object has all necessary fields.
 659          foreach (self::$cachedfields as $key) {
 660              if (!isset($course->$key)) {
 661                  $course = $DB->get_record('course', array('id' => $course->id),
 662                          implode(',', array_merge(array('id'), self::$cachedfields)), MUST_EXIST);
 663                  break;
 664              }
 665          }
 666          // Retrieve all information about activities and sections.
 667          // This may take time on large courses and it is possible that another user modifies the same course during this process.
 668          // Field cacherev stored in both DB and cache will ensure that cached data matches the current course state.
 669          $coursemodinfo = new stdClass();
 670          $coursemodinfo->modinfo = self::get_array_of_activities($course);
 671          $coursemodinfo->sectioncache = self::build_course_section_cache($course);
 672          foreach (self::$cachedfields as $key) {
 673              $coursemodinfo->$key = $course->$key;
 674          }
 675          // Set the accumulated activities and sections information in cache, together with cacherev.
 676          $cachecoursemodinfo = cache::make('core', 'coursemodinfo');
 677          $cachecoursemodinfo->set($course->id, $coursemodinfo);
 678          return $coursemodinfo;
 679      }
 680  
 681      /**
 682       * For a given course, returns an array of course activity objects
 683       *
 684       * @param stdClass $course Course object
 685       * @return array list of activities
 686       */
 687      public static function get_array_of_activities(stdClass $course): array {
 688          global $CFG, $DB;
 689  
 690          if (empty($course)) {
 691              throw new moodle_exception('courseidnotfound');
 692          }
 693  
 694          $mod = [];
 695  
 696          $rawmods = get_course_mods($course->id);
 697          if (empty($rawmods)) {
 698              return $mod; // always return array
 699          }
 700          $courseformat = course_get_format($course);
 701  
 702          if ($sections = $DB->get_records('course_sections', ['course' => $course->id],
 703              'section ASC', 'id,section,sequence,visible')) {
 704              // First check and correct obvious mismatches between course_sections.sequence and course_modules.section.
 705              if ($errormessages = course_integrity_check($course->id, $rawmods, $sections)) {
 706                  debugging(join('<br>', $errormessages));
 707                  $rawmods = get_course_mods($course->id);
 708                  $sections = $DB->get_records('course_sections', ['course' => $course->id],
 709                      'section ASC', 'id,section,sequence,visible');
 710              }
 711              // Build array of activities.
 712              foreach ($sections as $section) {
 713                  if (!empty($section->sequence)) {
 714                      $sequence = explode(",", $section->sequence);
 715                      foreach ($sequence as $seq) {
 716                          if (empty($rawmods[$seq])) {
 717                              continue;
 718                          }
 719                          // Adjust visibleoncoursepage, value in DB may not respect format availability.
 720                          $rawmods[$seq]->visibleoncoursepage = (!$rawmods[$seq]->visible
 721                              || $rawmods[$seq]->visibleoncoursepage
 722                              || empty($CFG->allowstealth)
 723                              || !$courseformat->allow_stealth_module_visibility($rawmods[$seq], $section)) ? 1 : 0;
 724  
 725                          // Create an object that will be cached.
 726                          $mod[$seq] = new stdClass();
 727                          $mod[$seq]->id = $rawmods[$seq]->instance;
 728                          $mod[$seq]->cm = $rawmods[$seq]->id;
 729                          $mod[$seq]->mod = $rawmods[$seq]->modname;
 730  
 731                          // Oh dear. Inconsistent names left here for backward compatibility.
 732                          $mod[$seq]->section = $section->section;
 733                          $mod[$seq]->sectionid = $rawmods[$seq]->section;
 734  
 735                          $mod[$seq]->module = $rawmods[$seq]->module;
 736                          $mod[$seq]->added = $rawmods[$seq]->added;
 737                          $mod[$seq]->score = $rawmods[$seq]->score;
 738                          $mod[$seq]->idnumber = $rawmods[$seq]->idnumber;
 739                          $mod[$seq]->visible = $rawmods[$seq]->visible;
 740                          $mod[$seq]->visibleoncoursepage = $rawmods[$seq]->visibleoncoursepage;
 741                          $mod[$seq]->visibleold = $rawmods[$seq]->visibleold;
 742                          $mod[$seq]->groupmode = $rawmods[$seq]->groupmode;
 743                          $mod[$seq]->groupingid = $rawmods[$seq]->groupingid;
 744                          $mod[$seq]->indent = $rawmods[$seq]->indent;
 745                          $mod[$seq]->completion = $rawmods[$seq]->completion;
 746                          $mod[$seq]->extra = "";
 747                          $mod[$seq]->completiongradeitemnumber =
 748                              $rawmods[$seq]->completiongradeitemnumber;
 749                          $mod[$seq]->completionview = $rawmods[$seq]->completionview;
 750                          $mod[$seq]->completionexpected = $rawmods[$seq]->completionexpected;
 751                          $mod[$seq]->showdescription = $rawmods[$seq]->showdescription;
 752                          $mod[$seq]->availability = $rawmods[$seq]->availability;
 753                          $mod[$seq]->deletioninprogress = $rawmods[$seq]->deletioninprogress;
 754  
 755                          $modname = $mod[$seq]->mod;
 756                          $functionname = $modname . "_get_coursemodule_info";
 757  
 758                          if (!file_exists("$CFG->dirroot/mod/$modname/lib.php")) {
 759                              continue;
 760                          }
 761  
 762                          include_once("$CFG->dirroot/mod/$modname/lib.php");
 763  
 764                          if ($hasfunction = function_exists($functionname)) {
 765                              if ($info = $functionname($rawmods[$seq])) {
 766                                  if (!empty($info->icon)) {
 767                                      $mod[$seq]->icon = $info->icon;
 768                                  }
 769                                  if (!empty($info->iconcomponent)) {
 770                                      $mod[$seq]->iconcomponent = $info->iconcomponent;
 771                                  }
 772                                  if (!empty($info->name)) {
 773                                      $mod[$seq]->name = $info->name;
 774                                  }
 775                                  if ($info instanceof cached_cm_info) {
 776                                      // When using cached_cm_info you can include three new fields
 777                                      // that aren't available for legacy code
 778                                      if (!empty($info->content)) {
 779                                          $mod[$seq]->content = $info->content;
 780                                      }
 781                                      if (!empty($info->extraclasses)) {
 782                                          $mod[$seq]->extraclasses = $info->extraclasses;
 783                                      }
 784                                      if (!empty($info->iconurl)) {
 785                                          // Convert URL to string as it's easier to store. Also serialized object contains \0 byte and can not be written to Postgres DB.
 786                                          $url = new moodle_url($info->iconurl);
 787                                          $mod[$seq]->iconurl = $url->out(false);
 788                                      }
 789                                      if (!empty($info->onclick)) {
 790                                          $mod[$seq]->onclick = $info->onclick;
 791                                      }
 792                                      if (!empty($info->customdata)) {
 793                                          $mod[$seq]->customdata = $info->customdata;
 794                                      }
 795                                  } else {
 796                                      // When using a stdclass, the (horrible) deprecated ->extra field
 797                                      // is available for BC
 798                                      if (!empty($info->extra)) {
 799                                          $mod[$seq]->extra = $info->extra;
 800                                      }
 801                                  }
 802                              }
 803                          }
 804                          // When there is no modname_get_coursemodule_info function,
 805                          // but showdescriptions is enabled, then we use the 'intro'
 806                          // and 'introformat' fields in the module table
 807                          if (!$hasfunction && $rawmods[$seq]->showdescription) {
 808                              if ($modvalues = $DB->get_record($rawmods[$seq]->modname,
 809                                  ['id' => $rawmods[$seq]->instance], 'name, intro, introformat')) {
 810                                  // Set content from intro and introformat. Filters are disabled
 811                                  // because we  filter it with format_text at display time
 812                                  $mod[$seq]->content = format_module_intro($rawmods[$seq]->modname,
 813                                      $modvalues, $rawmods[$seq]->id, false);
 814  
 815                                  // To save making another query just below, put name in here
 816                                  $mod[$seq]->name = $modvalues->name;
 817                              }
 818                          }
 819                          if (!isset($mod[$seq]->name)) {
 820                              $mod[$seq]->name =
 821                                  $DB->get_field($rawmods[$seq]->modname, "name", ["id" => $rawmods[$seq]->instance]);
 822                          }
 823  
 824                          // Minimise the database size by unsetting default options when they are
 825                          // 'empty'. This list corresponds to code in the cm_info constructor.
 826                          foreach (['idnumber', 'groupmode', 'groupingid',
 827                              'indent', 'completion', 'extra', 'extraclasses', 'iconurl', 'onclick', 'content',
 828                              'icon', 'iconcomponent', 'customdata', 'availability', 'completionview',
 829                              'completionexpected', 'score', 'showdescription', 'deletioninprogress'] as $property) {
 830                              if (property_exists($mod[$seq], $property) &&
 831                                  empty($mod[$seq]->{$property})) {
 832                                  unset($mod[$seq]->{$property});
 833                              }
 834                          }
 835                          // Special case: this value is usually set to null, but may be 0
 836                          if (property_exists($mod[$seq], 'completiongradeitemnumber') &&
 837                              is_null($mod[$seq]->completiongradeitemnumber)) {
 838                              unset($mod[$seq]->completiongradeitemnumber);
 839                          }
 840                      }
 841                  }
 842              }
 843          }
 844          return $mod;
 845      }
 846  
 847      /**
 848       * Purge the cache of a given course
 849       *
 850       * @param int $courseid Course id
 851       */
 852      public static function purge_course_cache(int $courseid): void {
 853          $cachemodinfo = cache::make('core', 'coursemodinfo');
 854          $cachemodinfo->delete($courseid);
 855      }
 856  }
 857  
 858  
 859  /**
 860   * Data about a single module on a course. This contains most of the fields in the course_modules
 861   * table, plus additional data when required.
 862   *
 863   * The object can be accessed by core or any plugin (i.e. course format, block, filter, etc.) as
 864   * get_fast_modinfo($courseorid)->cms[$coursemoduleid]
 865   * or
 866   * get_fast_modinfo($courseorid)->instances[$moduletype][$instanceid]
 867   *
 868   * There are three stages when activity module can add/modify data in this object:
 869   *
 870   * <b>Stage 1 - during building the cache.</b>
 871   * Allows to add to the course cache static user-independent information about the module.
 872   * Modules should try to include only absolutely necessary information that may be required
 873   * when displaying course view page. The information is stored in application-level cache
 874   * and reset when {@link rebuild_course_cache()} is called or cache is purged by admin.
 875   *
 876   * Modules can implement callback XXX_get_coursemodule_info() returning instance of object
 877   * {@link cached_cm_info}
 878   *
 879   * <b>Stage 2 - dynamic data.</b>
 880   * Dynamic data is user-dependent, it is stored in request-level cache. To reset this cache
 881   * {@link get_fast_modinfo()} with $reset argument may be called.
 882   *
 883   * Dynamic data is obtained when any of the following properties/methods is requested:
 884   * - {@link cm_info::$url}
 885   * - {@link cm_info::$name}
 886   * - {@link cm_info::$onclick}
 887   * - {@link cm_info::get_icon_url()}
 888   * - {@link cm_info::$uservisible}
 889   * - {@link cm_info::$available}
 890   * - {@link cm_info::$availableinfo}
 891   * - plus any of the properties listed in Stage 3.
 892   *
 893   * Modules can implement callback <b>XXX_cm_info_dynamic()</b> and inside this callback they
 894   * are allowed to use any of the following set methods:
 895   * - {@link cm_info::set_available()}
 896   * - {@link cm_info::set_name()}
 897   * - {@link cm_info::set_no_view_link()}
 898   * - {@link cm_info::set_user_visible()}
 899   * - {@link cm_info::set_on_click()}
 900   * - {@link cm_info::set_icon_url()}
 901   * - {@link cm_info::override_customdata()}
 902   * Any methods affecting view elements can also be set in this callback.
 903   *
 904   * <b>Stage 3 (view data).</b>
 905   * Also user-dependend data stored in request-level cache. Second stage is created
 906   * because populating the view data can be expensive as it may access much more
 907   * Moodle APIs such as filters, user information, output renderers and we
 908   * don't want to request it until necessary.
 909   * View data is obtained when any of the following properties/methods is requested:
 910   * - {@link cm_info::$afterediticons}
 911   * - {@link cm_info::$content}
 912   * - {@link cm_info::get_formatted_content()}
 913   * - {@link cm_info::$extraclasses}
 914   * - {@link cm_info::$afterlink}
 915   *
 916   * Modules can implement callback <b>XXX_cm_info_view()</b> and inside this callback they
 917   * are allowed to use any of the following set methods:
 918   * - {@link cm_info::set_after_edit_icons()}
 919   * - {@link cm_info::set_after_link()}
 920   * - {@link cm_info::set_content()}
 921   * - {@link cm_info::set_extra_classes()}
 922   *
 923   * @property-read int $id Course-module ID - from course_modules table
 924   * @property-read int $instance Module instance (ID within module table) - from course_modules table
 925   * @property-read int $course Course ID - from course_modules table
 926   * @property-read string $idnumber 'ID number' from course-modules table (arbitrary text set by user) - from
 927   *    course_modules table
 928   * @property-read int $added Time that this course-module was added (unix time) - from course_modules table
 929   * @property-read int $visible Visible setting (0 or 1; if this is 0, students cannot see/access the activity) - from
 930   *    course_modules table
 931   * @property-read int $visibleoncoursepage Visible on course page setting - from course_modules table, adjusted to
 932   *    whether course format allows this module to have the "stealth" mode
 933   * @property-read int $visibleold Old visible setting (if the entire section is hidden, the previous value for
 934   *    visible is stored in this field) - from course_modules table
 935   * @property-read int $groupmode Group mode (one of the constants NOGROUPS, SEPARATEGROUPS, or VISIBLEGROUPS) - from
 936   *    course_modules table. Use {@link cm_info::$effectivegroupmode} to find the actual group mode that may be forced by course.
 937   * @property-read int $groupingid Grouping ID (0 = all groupings)
 938   * @property-read bool $coursegroupmodeforce Indicates whether the course containing the module has forced the groupmode
 939   *    This means that cm_info::$groupmode should be ignored and cm_info::$coursegroupmode be used instead
 940   * @property-read int $coursegroupmode Group mode (one of the constants NOGROUPS, SEPARATEGROUPS, or VISIBLEGROUPS) - from
 941   *    course table - as specified for the course containing the module
 942   *    Effective only if {@link cm_info::$coursegroupmodeforce} is set
 943   * @property-read int $effectivegroupmode Effective group mode for this module (one of the constants NOGROUPS, SEPARATEGROUPS,
 944   *    or VISIBLEGROUPS). This can be different from groupmode set for the module if the groupmode is forced for the course.
 945   *    This value will always be NOGROUPS if module type does not support group mode.
 946   * @property-read int $indent Indent level on course page (0 = no indent) - from course_modules table
 947   * @property-read int $completion Activity completion setting for this activity, COMPLETION_TRACKING_xx constant - from
 948   *    course_modules table
 949   * @property-read mixed $completiongradeitemnumber Set to the item number (usually 0) if completion depends on a particular
 950   *    grade of this activity, or null if completion does not depend on a grade - from course_modules table
 951   * @property-read int $completionview 1 if 'on view' completion is enabled, 0 otherwise - from course_modules table
 952   * @property-read int $completionexpected Set to a unix time if completion of this activity is expected at a
 953   *    particular time, 0 if no time set - from course_modules table
 954   * @property-read string $availability Availability information as JSON string or null if none -
 955   *    from course_modules table
 956   * @property-read int $showdescription Controls whether the description of the activity displays on the course main page (in
 957   *    addition to anywhere it might display within the activity itself). 0 = do not show
 958   *    on main page, 1 = show on main page.
 959   * @property-read string $extra (deprecated) Extra HTML that is put in an unhelpful part of the HTML when displaying this module in
 960   *    course page - from cached data in modinfo field. Deprecated, replaced by ->extraclasses and ->onclick
 961   * @property-read string $icon Name of icon to use - from cached data in modinfo field
 962   * @property-read string $iconcomponent Component that contains icon - from cached data in modinfo field
 963   * @property-read string $modname Name of module e.g. 'forum' (this is the same name as the module's main database
 964   *    table) - from cached data in modinfo field
 965   * @property-read int $module ID of module type - from course_modules table
 966   * @property-read string $name Name of module instance for display on page e.g. 'General discussion forum' - from cached
 967   *    data in modinfo field
 968   * @property-read int $sectionnum Section number that this course-module is in (section 0 = above the calendar, section 1
 969   *    = week/topic 1, etc) - from cached data in modinfo field
 970   * @property-read int $section Section id - from course_modules table
 971   * @property-read array $conditionscompletion Availability conditions for this course-module based on the completion of other
 972   *    course-modules (array from other course-module id to required completion state for that
 973   *    module) - from cached data in modinfo field
 974   * @property-read array $conditionsgrade Availability conditions for this course-module based on course grades (array from
 975   *    grade item id to object with ->min, ->max fields) - from cached data in modinfo field
 976   * @property-read array $conditionsfield Availability conditions for this course-module based on user fields
 977   * @property-read bool $available True if this course-module is available to students i.e. if all availability conditions
 978   *    are met - obtained dynamically
 979   * @property-read string $availableinfo If course-module is not available to students, this string gives information about
 980   *    availability which can be displayed to students and/or staff (e.g. 'Available from 3
 981   *    January 2010') for display on main page - obtained dynamically
 982   * @property-read bool $uservisible True if this course-module is available to the CURRENT user (for example, if current user
 983   *    has viewhiddenactivities capability, they can access the course-module even if it is not
 984   *    visible or not available, so this would be true in that case)
 985   * @property-read context_module $context Module context
 986   * @property-read string $modfullname Returns a localised human-readable name of the module type - calculated on request
 987   * @property-read string $modplural Returns a localised human-readable name of the module type in plural form - calculated on request
 988   * @property-read string $content Content to display on main (view) page - calculated on request
 989   * @property-read moodle_url $url URL to link to for this module, or null if it doesn't have a view page - calculated on request
 990   * @property-read string $extraclasses Extra CSS classes to add to html output for this activity on main page - calculated on request
 991   * @property-read string $onclick Content of HTML on-click attribute already escaped - calculated on request
 992   * @property-read mixed $customdata Optional custom data stored in modinfo cache for this activity, or null if none
 993   * @property-read string $afterlink Extra HTML code to display after link - calculated on request
 994   * @property-read string $afterediticons Extra HTML code to display after editing icons (e.g. more icons) - calculated on request
 995   * @property-read bool $deletioninprogress True if this course module is scheduled for deletion, false otherwise.
 996   */
 997  class cm_info implements IteratorAggregate {
 998      /**
 999       * State: Only basic data from modinfo cache is available.
1000       */
1001      const STATE_BASIC = 0;
1002  
1003      /**
1004       * State: In the process of building dynamic data (to avoid recursive calls to obtain_dynamic_data())
1005       */
1006      const STATE_BUILDING_DYNAMIC = 1;
1007  
1008      /**
1009       * State: Dynamic data is available too.
1010       */
1011      const STATE_DYNAMIC = 2;
1012  
1013      /**
1014       * State: In the process of building view data (to avoid recursive calls to obtain_view_data())
1015       */
1016      const STATE_BUILDING_VIEW = 3;
1017  
1018      /**
1019       * State: View data (for course page) is available.
1020       */
1021      const STATE_VIEW = 4;
1022  
1023      /**
1024       * Parent object
1025       * @var course_modinfo
1026       */
1027      private $modinfo;
1028  
1029      /**
1030       * Level of information stored inside this object (STATE_xx constant)
1031       * @var int
1032       */
1033      private $state;
1034  
1035      /**
1036       * Course-module ID - from course_modules table
1037       * @var int
1038       */
1039      private $id;
1040  
1041      /**
1042       * Module instance (ID within module table) - from course_modules table
1043       * @var int
1044       */
1045      private $instance;
1046  
1047      /**
1048       * 'ID number' from course-modules table (arbitrary text set by user) - from
1049       * course_modules table
1050       * @var string
1051       */
1052      private $idnumber;
1053  
1054      /**
1055       * Time that this course-module was added (unix time) - from course_modules table
1056       * @var int
1057       */
1058      private $added;
1059  
1060      /**
1061       * This variable is not used and is included here only so it can be documented.
1062       * Once the database entry is removed from course_modules, it should be deleted
1063       * here too.
1064       * @var int
1065       * @deprecated Do not use this variable
1066       */
1067      private $score;
1068  
1069      /**
1070       * Visible setting (0 or 1; if this is 0, students cannot see/access the activity) - from
1071       * course_modules table
1072       * @var int
1073       */
1074      private $visible;
1075  
1076      /**
1077       * Visible on course page setting - from course_modules table
1078       * @var int
1079       */
1080      private $visibleoncoursepage;
1081  
1082      /**
1083       * Old visible setting (if the entire section is hidden, the previous value for
1084       * visible is stored in this field) - from course_modules table
1085       * @var int
1086       */
1087      private $visibleold;
1088  
1089      /**
1090       * Group mode (one of the constants NONE, SEPARATEGROUPS, or VISIBLEGROUPS) - from
1091       * course_modules table
1092       * @var int
1093       */
1094      private $groupmode;
1095  
1096      /**
1097       * Grouping ID (0 = all groupings)
1098       * @var int
1099       */
1100      private $groupingid;
1101  
1102      /**
1103       * Indent level on course page (0 = no indent) - from course_modules table
1104       * @var int
1105       */
1106      private $indent;
1107  
1108      /**
1109       * Activity completion setting for this activity, COMPLETION_TRACKING_xx constant - from
1110       * course_modules table
1111       * @var int
1112       */
1113      private $completion;
1114  
1115      /**
1116       * Set to the item number (usually 0) if completion depends on a particular
1117       * grade of this activity, or null if completion does not depend on a grade - from
1118       * course_modules table
1119       * @var mixed
1120       */
1121      private $completiongradeitemnumber;
1122  
1123      /**
1124       * 1 if 'on view' completion is enabled, 0 otherwise - from course_modules table
1125       * @var int
1126       */
1127      private $completionview;
1128  
1129      /**
1130       * Set to a unix time if completion of this activity is expected at a
1131       * particular time, 0 if no time set - from course_modules table
1132       * @var int
1133       */
1134      private $completionexpected;
1135  
1136      /**
1137       * Availability information as JSON string or null if none - from course_modules table
1138       * @var string
1139       */
1140      private $availability;
1141  
1142      /**
1143       * Controls whether the description of the activity displays on the course main page (in
1144       * addition to anywhere it might display within the activity itself). 0 = do not show
1145       * on main page, 1 = show on main page.
1146       * @var int
1147       */
1148      private $showdescription;
1149  
1150      /**
1151       * Extra HTML that is put in an unhelpful part of the HTML when displaying this module in
1152       * course page - from cached data in modinfo field
1153       * @deprecated This is crazy, don't use it. Replaced by ->extraclasses and ->onclick
1154       * @var string
1155       */
1156      private $extra;
1157  
1158      /**
1159       * Name of icon to use - from cached data in modinfo field
1160       * @var string
1161       */
1162      private $icon;
1163  
1164      /**
1165       * Component that contains icon - from cached data in modinfo field
1166       * @var string
1167       */
1168      private $iconcomponent;
1169  
1170      /**
1171       * Name of module e.g. 'forum' (this is the same name as the module's main database
1172       * table) - from cached data in modinfo field
1173       * @var string
1174       */
1175      private $modname;
1176  
1177      /**
1178       * ID of module - from course_modules table
1179       * @var int
1180       */
1181      private $module;
1182  
1183      /**
1184       * Name of module instance for display on page e.g. 'General discussion forum' - from cached
1185       * data in modinfo field
1186       * @var string
1187       */
1188      private $name;
1189  
1190      /**
1191       * Section number that this course-module is in (section 0 = above the calendar, section 1
1192       * = week/topic 1, etc) - from cached data in modinfo field
1193       * @var int
1194       */
1195      private $sectionnum;
1196  
1197      /**
1198       * Section id - from course_modules table
1199       * @var int
1200       */
1201      private $section;
1202  
1203      /**
1204       * Availability conditions for this course-module based on the completion of other
1205       * course-modules (array from other course-module id to required completion state for that
1206       * module) - from cached data in modinfo field
1207       * @var array
1208       */
1209      private $conditionscompletion;
1210  
1211      /**
1212       * Availability conditions for this course-module based on course grades (array from
1213       * grade item id to object with ->min, ->max fields) - from cached data in modinfo field
1214       * @var array
1215       */
1216      private $conditionsgrade;
1217  
1218      /**
1219       * Availability conditions for this course-module based on user fields
1220       * @var array
1221       */
1222      private $conditionsfield;
1223  
1224      /**
1225       * True if this course-module is available to students i.e. if all availability conditions
1226       * are met - obtained dynamically
1227       * @var bool
1228       */
1229      private $available;
1230  
1231      /**
1232       * If course-module is not available to students, this string gives information about
1233       * availability which can be displayed to students and/or staff (e.g. 'Available from 3
1234       * January 2010') for display on main page - obtained dynamically
1235       * @var string
1236       */
1237      private $availableinfo;
1238  
1239      /**
1240       * True if this course-module is available to the CURRENT user (for example, if current user
1241       * has viewhiddenactivities capability, they can access the course-module even if it is not
1242       * visible or not available, so this would be true in that case)
1243       * @var bool
1244       */
1245      private $uservisible;
1246  
1247      /**
1248       * True if this course-module is visible to the CURRENT user on the course page
1249       * @var bool
1250       */
1251      private $uservisibleoncoursepage;
1252  
1253      /**
1254       * @var moodle_url
1255       */
1256      private $url;
1257  
1258      /**
1259       * @var string
1260       */
1261      private $content;
1262  
1263      /**
1264       * @var bool
1265       */
1266      private $contentisformatted;
1267  
1268      /**
1269       * @var string
1270       */
1271      private $extraclasses;
1272  
1273      /**
1274       * @var moodle_url full external url pointing to icon image for activity
1275       */
1276      private $iconurl;
1277  
1278      /**
1279       * @var string
1280       */
1281      private $onclick;
1282  
1283      /**
1284       * @var mixed
1285       */
1286      private $customdata;
1287  
1288      /**
1289       * @var string
1290       */
1291      private $afterlink;
1292  
1293      /**
1294       * @var string
1295       */
1296      private $afterediticons;
1297  
1298      /**
1299       * @var bool representing the deletion state of the module. True if the mod is scheduled for deletion.
1300       */
1301      private $deletioninprogress;
1302  
1303      /**
1304       * List of class read-only properties and their getter methods.
1305       * Used by magic functions __get(), __isset(), __empty()
1306       * @var array
1307       */
1308      private static $standardproperties = array(
1309          'url' => 'get_url',
1310          'content' => 'get_content',
1311          'extraclasses' => 'get_extra_classes',
1312          'onclick' => 'get_on_click',
1313          'customdata' => 'get_custom_data',
1314          'afterlink' => 'get_after_link',
1315          'afterediticons' => 'get_after_edit_icons',
1316          'modfullname' => 'get_module_type_name',
1317          'modplural' => 'get_module_type_name_plural',
1318          'id' => false,
1319          'added' => false,
1320          'availability' => false,
1321          'available' => 'get_available',
1322          'availableinfo' => 'get_available_info',
1323          'completion' => false,
1324          'completionexpected' => false,
1325          'completiongradeitemnumber' => false,
1326          'completionview' => false,
1327          'conditionscompletion' => false,
1328          'conditionsfield' => false,
1329          'conditionsgrade' => false,
1330          'context' => 'get_context',
1331          'course' => 'get_course_id',
1332          'coursegroupmode' => 'get_course_groupmode',
1333          'coursegroupmodeforce' => 'get_course_groupmodeforce',
1334          'effectivegroupmode' => 'get_effective_groupmode',
1335          'extra' => false,
1336          'groupingid' => false,
1337          'groupmembersonly' => 'get_deprecated_group_members_only',
1338          'groupmode' => false,
1339          'icon' => false,
1340          'iconcomponent' => false,
1341          'idnumber' => false,
1342          'indent' => false,
1343          'instance' => false,
1344          'modname' => false,
1345          'module' => false,
1346          'name' => 'get_name',
1347          'score' => false,
1348          'section' => false,
1349          'sectionnum' => false,
1350          'showdescription' => false,
1351          'uservisible' => 'get_user_visible',
1352          'visible' => false,
1353          'visibleoncoursepage' => false,
1354          'visibleold' => false,
1355          'deletioninprogress' => false
1356      );
1357  
1358      /**
1359       * List of methods with no arguments that were public prior to Moodle 2.6.
1360       *
1361       * They can still be accessed publicly via magic __call() function with no warnings
1362       * but are not listed in the class methods list.
1363       * For the consistency of the code it is better to use corresponding properties.
1364       *
1365       * These methods be deprecated completely in later versions.
1366       *
1367       * @var array $standardmethods
1368       */
1369      private static $standardmethods = array(
1370          // Following methods are not recommended to use because there have associated read-only properties.
1371          'get_url',
1372          'get_content',
1373          'get_extra_classes',
1374          'get_on_click',
1375          'get_custom_data',
1376          'get_after_link',
1377          'get_after_edit_icons',
1378          // Method obtain_dynamic_data() should not be called from outside of this class but it was public before Moodle 2.6.
1379          'obtain_dynamic_data',
1380      );
1381  
1382      /**
1383       * Magic method to call functions that are now declared as private but were public in Moodle before 2.6.
1384       * These private methods can not be used anymore.
1385       *
1386       * @param string $name
1387       * @param array $arguments
1388       * @return mixed
1389       * @throws coding_exception
1390       */
1391      public function __call($name, $arguments) {
1392          if (in_array($name, self::$standardmethods)) {
1393              $message = "cm_info::$name() can not be used anymore.";
1394              if ($alternative = array_search($name, self::$standardproperties)) {
1395                  $message .= " Please use the property cm_info->$alternative instead.";
1396              }
1397              throw new coding_exception($message);
1398          }
1399          throw new coding_exception("Method cm_info::{$name}() does not exist");
1400      }
1401  
1402      /**
1403       * Magic method getter
1404       *
1405       * @param string $name
1406       * @return mixed
1407       */
1408      public function __get($name) {
1409          if (isset(self::$standardproperties[$name])) {
1410              if ($method = self::$standardproperties[$name]) {
1411                  return $this->$method();
1412              } else {
1413                  return $this->$name;
1414              }
1415          } else {
1416              debugging('Invalid cm_info property accessed: '.$name);
1417              return null;
1418          }
1419      }
1420  
1421      /**
1422       * Implementation of IteratorAggregate::getIterator(), allows to cycle through properties
1423       * and use {@link convert_to_array()}
1424       *
1425       * @return ArrayIterator
1426       */
1427      public function getIterator() {
1428          // Make sure dynamic properties are retrieved prior to view properties.
1429          $this->obtain_dynamic_data();
1430          $ret = array();
1431  
1432          // Do not iterate over deprecated properties.
1433          $props = self::$standardproperties;
1434          unset($props['groupmembersonly']);
1435  
1436          foreach ($props as $key => $unused) {
1437              $ret[$key] = $this->__get($key);
1438          }
1439          return new ArrayIterator($ret);
1440      }
1441  
1442      /**
1443       * Magic method for function isset()
1444       *
1445       * @param string $name
1446       * @return bool
1447       */
1448      public function __isset($name) {
1449          if (isset(self::$standardproperties[$name])) {
1450              $value = $this->__get($name);
1451              return isset($value);
1452          }
1453          return false;
1454      }
1455  
1456      /**
1457       * Magic method for function empty()
1458       *
1459       * @param string $name
1460       * @return bool
1461       */
1462      public function __empty($name) {
1463          if (isset(self::$standardproperties[$name])) {
1464              $value = $this->__get($name);
1465              return empty($value);
1466          }
1467          return true;
1468      }
1469  
1470      /**
1471       * Magic method setter
1472       *
1473       * Will display the developer warning when trying to set/overwrite property.
1474       *
1475       * @param string $name
1476       * @param mixed $value
1477       */
1478      public function __set($name, $value) {
1479          debugging("It is not allowed to set the property cm_info::\${$name}", DEBUG_DEVELOPER);
1480      }
1481  
1482      /**
1483       * @return bool True if this module has a 'view' page that should be linked to in navigation
1484       *   etc (note: modules may still have a view.php file, but return false if this is not
1485       *   intended to be linked to from 'normal' parts of the interface; this is what label does).
1486       */
1487      public function has_view() {
1488          return !is_null($this->url);
1489      }
1490  
1491      /**
1492       * Gets the URL to link to for this module.
1493       *
1494       * This method is normally called by the property ->url, but can be called directly if
1495       * there is a case when it might be called recursively (you can't call property values
1496       * recursively).
1497       *
1498       * @return moodle_url URL to link to for this module, or null if it doesn't have a view page
1499       */
1500      public function get_url() {
1501          $this->obtain_dynamic_data();
1502          return $this->url;
1503      }
1504  
1505      /**
1506       * Obtains content to display on main (view) page.
1507       * Note: Will collect view data, if not already obtained.
1508       * @return string Content to display on main page below link, or empty string if none
1509       */
1510      private function get_content() {
1511          $this->obtain_view_data();
1512          return $this->content;
1513      }
1514  
1515      /**
1516       * Returns the content to display on course/overview page, formatted and passed through filters
1517       *
1518       * if $options['context'] is not specified, the module context is used
1519       *
1520       * @param array|stdClass $options formatting options, see {@link format_text()}
1521       * @return string
1522       */
1523      public function get_formatted_content($options = array()) {
1524          $this->obtain_view_data();
1525          if (empty($this->content)) {
1526              return '';
1527          }
1528          if ($this->contentisformatted) {
1529              return $this->content;
1530          }
1531  
1532          // Improve filter performance by preloading filter setttings for all
1533          // activities on the course (this does nothing if called multiple
1534          // times)
1535          filter_preload_activities($this->get_modinfo());
1536  
1537          $options = (array)$options;
1538          if (!isset($options['context'])) {
1539              $options['context'] = $this->get_context();
1540          }
1541          return format_text($this->content, FORMAT_HTML, $options);
1542      }
1543  
1544      /**
1545       * Getter method for property $name, ensures that dynamic data is obtained.
1546       *
1547       * This method is normally called by the property ->name, but can be called directly if there
1548       * is a case when it might be called recursively (you can't call property values recursively).
1549       *
1550       * @return string
1551       */
1552      public function get_name() {
1553          $this->obtain_dynamic_data();
1554          return $this->name;
1555      }
1556  
1557      /**
1558       * Returns the name to display on course/overview page, formatted and passed through filters
1559       *
1560       * if $options['context'] is not specified, the module context is used
1561       *
1562       * @param array|stdClass $options formatting options, see {@link format_string()}
1563       * @return string
1564       */
1565      public function get_formatted_name($options = array()) {
1566          global $CFG;
1567          $options = (array)$options;
1568          if (!isset($options['context'])) {
1569              $options['context'] = $this->get_context();
1570          }
1571          // Improve filter performance by preloading filter setttings for all
1572          // activities on the course (this does nothing if called multiple
1573          // times).
1574          if (!empty($CFG->filterall)) {
1575              filter_preload_activities($this->get_modinfo());
1576          }
1577          return format_string($this->get_name(), true,  $options);
1578      }
1579  
1580      /**
1581       * Note: Will collect view data, if not already obtained.
1582       * @return string Extra CSS classes to add to html output for this activity on main page
1583       */
1584      private function get_extra_classes() {
1585          $this->obtain_view_data();
1586          return $this->extraclasses;
1587      }
1588  
1589      /**
1590       * @return string Content of HTML on-click attribute. This string will be used literally
1591       * as a string so should be pre-escaped.
1592       */
1593      private function get_on_click() {
1594          // Does not need view data; may be used by navigation
1595          $this->obtain_dynamic_data();
1596          return $this->onclick;
1597      }
1598      /**
1599       * Getter method for property $customdata, ensures that dynamic data is retrieved.
1600       *
1601       * This method is normally called by the property ->customdata, but can be called directly if there
1602       * is a case when it might be called recursively (you can't call property values recursively).
1603       *
1604       * @return mixed Optional custom data stored in modinfo cache for this activity, or null if none
1605       */
1606      public function get_custom_data() {
1607          $this->obtain_dynamic_data();
1608          return $this->customdata;
1609      }
1610  
1611      /**
1612       * Note: Will collect view data, if not already obtained.
1613       * @return string Extra HTML code to display after link
1614       */
1615      private function get_after_link() {
1616          $this->obtain_view_data();
1617          return $this->afterlink;
1618      }
1619  
1620      /**
1621       * Note: Will collect view data, if not already obtained.
1622       * @return string Extra HTML code to display after editing icons (e.g. more icons)
1623       */
1624      private function get_after_edit_icons() {
1625          $this->obtain_view_data();
1626          return $this->afterediticons;
1627      }
1628  
1629      /**
1630       * @param moodle_core_renderer $output Output render to use, or null for default (global)
1631       * @return moodle_url Icon URL for a suitable icon to put beside this cm
1632       */
1633      public function get_icon_url($output = null) {
1634          global $OUTPUT;
1635          $this->obtain_dynamic_data();
1636          if (!$output) {
1637              $output = $OUTPUT;
1638          }
1639          // Support modules setting their own, external, icon image
1640          if (!empty($this->iconurl)) {
1641              $icon = $this->iconurl;
1642  
1643          // Fallback to normal local icon + component procesing
1644          } else if (!empty($this->icon)) {
1645              if (substr($this->icon, 0, 4) === 'mod/') {
1646                  list($modname, $iconname) = explode('/', substr($this->icon, 4), 2);
1647                  $icon = $output->image_url($iconname, $modname);
1648              } else {
1649                  if (!empty($this->iconcomponent)) {
1650                      // Icon  has specified component
1651                      $icon = $output->image_url($this->icon, $this->iconcomponent);
1652                  } else {
1653                      // Icon does not have specified component, use default
1654                      $icon = $output->image_url($this->icon);
1655                  }
1656              }
1657          } else {
1658              $icon = $output->image_url('icon', $this->modname);
1659          }
1660          return $icon;
1661      }
1662  
1663      /**
1664       * @param string $textclasses additionnal classes for grouping label
1665       * @return string An empty string or HTML grouping label span tag
1666       */
1667      public function get_grouping_label($textclasses = '') {
1668          $groupinglabel = '';
1669          if ($this->effectivegroupmode != NOGROUPS && !empty($this->groupingid) &&
1670                  has_capability('moodle/course:managegroups', context_course::instance($this->course))) {
1671              $groupings = groups_get_all_groupings($this->course);
1672              $groupinglabel = html_writer::tag('span', '('.format_string($groupings[$this->groupingid]->name).')',
1673                  array('class' => 'groupinglabel '.$textclasses));
1674          }
1675          return $groupinglabel;
1676      }
1677  
1678      /**
1679       * Returns a localised human-readable name of the module type.
1680       *
1681       * @param bool $plural If true, the function returns the plural form of the name.
1682       * @return lang_string
1683       */
1684      public function get_module_type_name($plural = false) {
1685          $modnames = get_module_types_names($plural);
1686          if (isset($modnames[$this->modname])) {
1687              return $modnames[$this->modname];
1688          } else {
1689              return null;
1690          }
1691      }
1692  
1693      /**
1694       * Returns a localised human-readable name of the module type in plural form - calculated on request
1695       *
1696       * @return string
1697       */
1698      private function get_module_type_name_plural() {
1699          return $this->get_module_type_name(true);
1700      }
1701  
1702      /**
1703       * @return course_modinfo Modinfo object that this came from
1704       */
1705      public function get_modinfo() {
1706          return $this->modinfo;
1707      }
1708  
1709      /**
1710       * Returns the section this module belongs to
1711       *
1712       * @return section_info
1713       */
1714      public function get_section_info() {
1715          return $this->modinfo->get_section_info($this->sectionnum);
1716      }
1717  
1718      /**
1719       * Returns course object that was used in the first {@link get_fast_modinfo()} call.
1720       *
1721       * It may not contain all fields from DB table {course} but always has at least the following:
1722       * id,shortname,fullname,format,enablecompletion,groupmode,groupmodeforce,cacherev
1723       *
1724       * If the course object lacks the field you need you can use the global
1725       * function {@link get_course()} that will save extra query if you access
1726       * current course or frontpage course.
1727       *
1728       * @return stdClass
1729       */
1730      public function get_course() {
1731          return $this->modinfo->get_course();
1732      }
1733  
1734      /**
1735       * Returns course id for which the modinfo was generated.
1736       *
1737       * @return int
1738       */
1739      private function get_course_id() {
1740          return $this->modinfo->get_course_id();
1741      }
1742  
1743      /**
1744       * Returns group mode used for the course containing the module
1745       *
1746       * @return int one of constants NOGROUPS, SEPARATEGROUPS, VISIBLEGROUPS
1747       */
1748      private function get_course_groupmode() {
1749          return $this->modinfo->get_course()->groupmode;
1750      }
1751  
1752      /**
1753       * Returns whether group mode is forced for the course containing the module
1754       *
1755       * @return bool
1756       */
1757      private function get_course_groupmodeforce() {
1758          return $this->modinfo->get_course()->groupmodeforce;
1759      }
1760  
1761      /**
1762       * Returns effective groupmode of the module that may be overwritten by forced course groupmode.
1763       *
1764       * @return int one of constants NOGROUPS, SEPARATEGROUPS, VISIBLEGROUPS
1765       */
1766      private function get_effective_groupmode() {
1767          $groupmode = $this->groupmode;
1768          if ($this->modinfo->get_course()->groupmodeforce) {
1769              $groupmode = $this->modinfo->get_course()->groupmode;
1770              if ($groupmode != NOGROUPS && !plugin_supports('mod', $this->modname, FEATURE_GROUPS, false)) {
1771                  $groupmode = NOGROUPS;
1772              }
1773          }
1774          return $groupmode;
1775      }
1776  
1777      /**
1778       * @return context_module Current module context
1779       */
1780      private function get_context() {
1781          return context_module::instance($this->id);
1782      }
1783  
1784      /**
1785       * Returns itself in the form of stdClass.
1786       *
1787       * The object includes all fields that table course_modules has and additionally
1788       * fields 'name', 'modname', 'sectionnum' (if requested).
1789       *
1790       * This can be used as a faster alternative to {@link get_coursemodule_from_id()}
1791       *
1792       * @param bool $additionalfields include additional fields 'name', 'modname', 'sectionnum'
1793       * @return stdClass
1794       */
1795      public function get_course_module_record($additionalfields = false) {
1796          $cmrecord = new stdClass();
1797  
1798          // Standard fields from table course_modules.
1799          static $cmfields = array('id', 'course', 'module', 'instance', 'section', 'idnumber', 'added',
1800              'score', 'indent', 'visible', 'visibleoncoursepage', 'visibleold', 'groupmode', 'groupingid',
1801              'completion', 'completiongradeitemnumber', 'completionview', 'completionexpected',
1802              'showdescription', 'availability', 'deletioninprogress');
1803          foreach ($cmfields as $key) {
1804              $cmrecord->$key = $this->$key;
1805          }
1806  
1807          // Additional fields that function get_coursemodule_from_id() adds.
1808          if ($additionalfields) {
1809              $cmrecord->name = $this->name;
1810              $cmrecord->modname = $this->modname;
1811              $cmrecord->sectionnum = $this->sectionnum;
1812          }
1813  
1814          return $cmrecord;
1815      }
1816  
1817      // Set functions
1818      ////////////////
1819  
1820      /**
1821       * Sets content to display on course view page below link (if present).
1822       * @param string $content New content as HTML string (empty string if none)
1823       * @param bool $isformatted Whether user content is already passed through format_text/format_string and should not
1824       *    be formatted again. This can be useful when module adds interactive elements on top of formatted user text.
1825       * @return void
1826       */
1827      public function set_content($content, $isformatted = false) {
1828          $this->content = $content;
1829          $this->contentisformatted = $isformatted;
1830      }
1831  
1832      /**
1833       * Sets extra classes to include in CSS.
1834       * @param string $extraclasses Extra classes (empty string if none)
1835       * @return void
1836       */
1837      public function set_extra_classes($extraclasses) {
1838          $this->extraclasses = $extraclasses;
1839      }
1840  
1841      /**
1842       * Sets the external full url that points to the icon being used
1843       * by the activity. Useful for external-tool modules (lti...)
1844       * If set, takes precedence over $icon and $iconcomponent
1845       *
1846       * @param moodle_url $iconurl full external url pointing to icon image for activity
1847       * @return void
1848       */
1849      public function set_icon_url(moodle_url $iconurl) {
1850          $this->iconurl = $iconurl;
1851      }
1852  
1853      /**
1854       * Sets value of on-click attribute for JavaScript.
1855       * Note: May not be called from _cm_info_view (only _cm_info_dynamic).
1856       * @param string $onclick New onclick attribute which should be HTML-escaped
1857       *   (empty string if none)
1858       * @return void
1859       */
1860      public function set_on_click($onclick) {
1861          $this->check_not_view_only();
1862          $this->onclick = $onclick;
1863      }
1864  
1865      /**
1866       * Overrides the value of an element in the customdata array.
1867       *
1868       * @param string $name The key in the customdata array
1869       * @param mixed $value The value
1870       */
1871      public function override_customdata($name, $value) {
1872          if (!is_array($this->customdata)) {
1873              $this->customdata = [];
1874          }
1875          $this->customdata[$name] = $value;
1876      }
1877  
1878      /**
1879       * Sets HTML that displays after link on course view page.
1880       * @param string $afterlink HTML string (empty string if none)
1881       * @return void
1882       */
1883      public function set_after_link($afterlink) {
1884          $this->afterlink = $afterlink;
1885      }
1886  
1887      /**
1888       * Sets HTML that displays after edit icons on course view page.
1889       * @param string $afterediticons HTML string (empty string if none)
1890       * @return void
1891       */
1892      public function set_after_edit_icons($afterediticons) {
1893          $this->afterediticons = $afterediticons;
1894      }
1895  
1896      /**
1897       * Changes the name (text of link) for this module instance.
1898       * Note: May not be called from _cm_info_view (only _cm_info_dynamic).
1899       * @param string $name Name of activity / link text
1900       * @return void
1901       */
1902      public function set_name($name) {
1903          if ($this->state < self::STATE_BUILDING_DYNAMIC) {
1904              $this->update_user_visible();
1905          }
1906          $this->name = $name;
1907      }
1908  
1909      /**
1910       * Turns off the view link for this module instance.
1911       * Note: May not be called from _cm_info_view (only _cm_info_dynamic).
1912       * @return void
1913       */
1914      public function set_no_view_link() {
1915          $this->check_not_view_only();
1916          $this->url = null;
1917      }
1918  
1919      /**
1920       * Sets the 'uservisible' flag. This can be used (by setting false) to prevent access and
1921       * display of this module link for the current user.
1922       * Note: May not be called from _cm_info_view (only _cm_info_dynamic).
1923       * @param bool $uservisible
1924       * @return void
1925       */
1926      public function set_user_visible($uservisible) {
1927          $this->check_not_view_only();
1928          $this->uservisible = $uservisible;
1929      }
1930  
1931      /**
1932       * Sets the 'available' flag and related details. This flag is normally used to make
1933       * course modules unavailable until a certain date or condition is met. (When a course
1934       * module is unavailable, it is still visible to users who have viewhiddenactivities
1935       * permission.)
1936       *
1937       * When this is function is called, user-visible status is recalculated automatically.
1938       *
1939       * The $showavailability flag does not really do anything any more, but is retained
1940       * for backward compatibility. Setting this to false will cause $availableinfo to
1941       * be ignored.
1942       *
1943       * Note: May not be called from _cm_info_view (only _cm_info_dynamic).
1944       * @param bool $available False if this item is not 'available'
1945       * @param int $showavailability 0 = do not show this item at all if it's not available,
1946       *   1 = show this item greyed out with the following message
1947       * @param string $availableinfo Information about why this is not available, or
1948       *   empty string if not displaying
1949       * @return void
1950       */
1951      public function set_available($available, $showavailability=0, $availableinfo='') {
1952          $this->check_not_view_only();
1953          $this->available = $available;
1954          if (!$showavailability) {
1955              $availableinfo = '';
1956          }
1957          $this->availableinfo = $availableinfo;
1958          $this->update_user_visible();
1959      }
1960  
1961      /**
1962       * Some set functions can only be called from _cm_info_dynamic and not _cm_info_view.
1963       * This is because they may affect parts of this object which are used on pages other
1964       * than the view page (e.g. in the navigation block, or when checking access on
1965       * module pages).
1966       * @return void
1967       */
1968      private function check_not_view_only() {
1969          if ($this->state >= self::STATE_DYNAMIC) {
1970              throw new coding_exception('Cannot set this data from _cm_info_view because it may ' .
1971                      'affect other pages as well as view');
1972          }
1973      }
1974  
1975      /**
1976       * Constructor should not be called directly; use {@link get_fast_modinfo()}
1977       *
1978       * @param course_modinfo $modinfo Parent object
1979       * @param stdClass $notused1 Argument not used
1980       * @param stdClass $mod Module object from the modinfo field of course table
1981       * @param stdClass $notused2 Argument not used
1982       */
1983      public function __construct(course_modinfo $modinfo, $notused1, $mod, $notused2) {
1984          $this->modinfo = $modinfo;
1985  
1986          $this->id               = $mod->cm;
1987          $this->instance         = $mod->id;
1988          $this->modname          = $mod->mod;
1989          $this->idnumber         = isset($mod->idnumber) ? $mod->idnumber : '';
1990          $this->name             = $mod->name;
1991          $this->visible          = $mod->visible;
1992          $this->visibleoncoursepage = $mod->visibleoncoursepage;
1993          $this->sectionnum       = $mod->section; // Note weirdness with name here
1994          $this->groupmode        = isset($mod->groupmode) ? $mod->groupmode : 0;
1995          $this->groupingid       = isset($mod->groupingid) ? $mod->groupingid : 0;
1996          $this->indent           = isset($mod->indent) ? $mod->indent : 0;
1997          $this->extra            = isset($mod->extra) ? $mod->extra : '';
1998          $this->extraclasses     = isset($mod->extraclasses) ? $mod->extraclasses : '';
1999          // iconurl may be stored as either string or instance of moodle_url.
2000          $this->iconurl          = isset($mod->iconurl) ? new moodle_url($mod->iconurl) : '';
2001          $this->onclick          = isset($mod->onclick) ? $mod->onclick : '';
2002          $this->content          = isset($mod->content) ? $mod->content : '';
2003          $this->icon             = isset($mod->icon) ? $mod->icon : '';
2004          $this->iconcomponent    = isset($mod->iconcomponent) ? $mod->iconcomponent : '';
2005          $this->customdata       = isset($mod->customdata) ? $mod->customdata : '';
2006          $this->showdescription  = isset($mod->showdescription) ? $mod->showdescription : 0;
2007          $this->state = self::STATE_BASIC;
2008  
2009          $this->section = isset($mod->sectionid) ? $mod->sectionid : 0;
2010          $this->module = isset($mod->module) ? $mod->module : 0;
2011          $this->added = isset($mod->added) ? $mod->added : 0;
2012          $this->score = isset($mod->score) ? $mod->score : 0;
2013          $this->visibleold = isset($mod->visibleold) ? $mod->visibleold : 0;
2014          $this->deletioninprogress = isset($mod->deletioninprogress) ? $mod->deletioninprogress : 0;
2015  
2016          // Note: it saves effort and database space to always include the
2017          // availability and completion fields, even if availability or completion
2018          // are actually disabled
2019          $this->completion = isset($mod->completion) ? $mod->completion : 0;
2020          $this->completiongradeitemnumber = isset($mod->completiongradeitemnumber)
2021                  ? $mod->completiongradeitemnumber : null;
2022          $this->completionview = isset($mod->completionview)
2023                  ? $mod->completionview : 0;
2024          $this->completionexpected = isset($mod->completionexpected)
2025                  ? $mod->completionexpected : 0;
2026          $this->availability = isset($mod->availability) ? $mod->availability : null;
2027          $this->conditionscompletion = isset($mod->conditionscompletion)
2028                  ? $mod->conditionscompletion : array();
2029          $this->conditionsgrade = isset($mod->conditionsgrade)
2030                  ? $mod->conditionsgrade : array();
2031          $this->conditionsfield = isset($mod->conditionsfield)
2032                  ? $mod->conditionsfield : array();
2033  
2034          static $modviews = array();
2035          if (!isset($modviews[$this->modname])) {
2036              $modviews[$this->modname] = !plugin_supports('mod', $this->modname,
2037                      FEATURE_NO_VIEW_LINK);
2038          }
2039          $this->url = $modviews[$this->modname]
2040                  ? new moodle_url('/mod/' . $this->modname . '/view.php', array('id'=>$this->id))
2041                  : null;
2042      }
2043  
2044      /**
2045       * Creates a cm_info object from a database record (also accepts cm_info
2046       * in which case it is just returned unchanged).
2047       *
2048       * @param stdClass|cm_info|null|bool $cm Stdclass or cm_info (or null or false)
2049       * @param int $userid Optional userid (default to current)
2050       * @return cm_info|null Object as cm_info, or null if input was null/false
2051       */
2052      public static function create($cm, $userid = 0) {
2053          // Null, false, etc. gets passed through as null.
2054          if (!$cm) {
2055              return null;
2056          }
2057          // If it is already a cm_info object, just return it.
2058          if ($cm instanceof cm_info) {
2059              return $cm;
2060          }
2061          // Otherwise load modinfo.
2062          if (empty($cm->id) || empty($cm->course)) {
2063              throw new coding_exception('$cm must contain ->id and ->course');
2064          }
2065          $modinfo = get_fast_modinfo($cm->course, $userid);
2066          return $modinfo->get_cm($cm->id);
2067      }
2068  
2069      /**
2070       * If dynamic data for this course-module is not yet available, gets it.
2071       *
2072       * This function is automatically called when requesting any course_modinfo property
2073       * that can be modified by modules (have a set_xxx method).
2074       *
2075       * Dynamic data is data which does not come directly from the cache but is calculated at
2076       * runtime based on the current user. Primarily this concerns whether the user can access
2077       * the module or not.
2078       *
2079       * As part of this function, the module's _cm_info_dynamic function from its lib.php will
2080       * be called (if it exists). Make sure that the functions that are called here do not use
2081       * any getter magic method from cm_info.
2082       * @return void
2083       */
2084      private function obtain_dynamic_data() {
2085          global $CFG;
2086          $userid = $this->modinfo->get_user_id();
2087          if ($this->state >= self::STATE_BUILDING_DYNAMIC || $userid == -1) {
2088              return;
2089          }
2090          $this->state = self::STATE_BUILDING_DYNAMIC;
2091  
2092          if (!empty($CFG->enableavailability)) {
2093              // Get availability information.
2094              $ci = new \core_availability\info_module($this);
2095  
2096              // Note that the modinfo currently available only includes minimal details (basic data)
2097              // but we know that this function does not need anything more than basic data.
2098              $this->available = $ci->is_available($this->availableinfo, true,
2099                      $userid, $this->modinfo);
2100          } else {
2101              $this->available = true;
2102          }
2103  
2104          // Check parent section.
2105          if ($this->available) {
2106              $parentsection = $this->modinfo->get_section_info($this->sectionnum);
2107              if (!$parentsection->get_available()) {
2108                  // Do not store info from section here, as that is already
2109                  // presented from the section (if appropriate) - just change
2110                  // the flag
2111                  $this->available = false;
2112              }
2113          }
2114  
2115          // Update visible state for current user.
2116          $this->update_user_visible();
2117  
2118          // Let module make dynamic changes at this point
2119          $this->call_mod_function('cm_info_dynamic');
2120          $this->state = self::STATE_DYNAMIC;
2121      }
2122  
2123      /**
2124       * Getter method for property $uservisible, ensures that dynamic data is retrieved.
2125       *
2126       * This method is normally called by the property ->uservisible, but can be called directly if
2127       * there is a case when it might be called recursively (you can't call property values
2128       * recursively).
2129       *
2130       * @return bool
2131       */
2132      public function get_user_visible() {
2133          $this->obtain_dynamic_data();
2134          return $this->uservisible;
2135      }
2136  
2137      /**
2138       * Returns whether this module is visible to the current user on course page
2139       *
2140       * Activity may be visible on the course page but not available, for example
2141       * when it is hidden conditionally but the condition information is displayed.
2142       *
2143       * @return bool
2144       */
2145      public function is_visible_on_course_page() {
2146          $this->obtain_dynamic_data();
2147          return $this->uservisibleoncoursepage;
2148      }
2149  
2150      /**
2151       * Whether this module is available but hidden from course page
2152       *
2153       * "Stealth" modules are the ones that are not shown on course page but available by following url.
2154       * They are normally also displayed in grade reports and other reports.
2155       * Module will be stealth either if visibleoncoursepage=0 or it is a visible module inside the hidden
2156       * section.
2157       *
2158       * @return bool
2159       */
2160      public function is_stealth() {
2161          return !$this->visibleoncoursepage ||
2162              ($this->visible && ($section = $this->get_section_info()) && !$section->visible);
2163      }
2164  
2165      /**
2166       * Getter method for property $available, ensures that dynamic data is retrieved
2167       * @return bool
2168       */
2169      private function get_available() {
2170          $this->obtain_dynamic_data();
2171          return $this->available;
2172      }
2173  
2174      /**
2175       * This method can not be used anymore.
2176       *
2177       * @see \core_availability\info_module::filter_user_list()
2178       * @deprecated Since Moodle 2.8
2179       */
2180      private function get_deprecated_group_members_only() {
2181          throw new coding_exception('$cm->groupmembersonly can not be used anymore. ' .
2182                  'If used to restrict a list of enrolled users to only those who can ' .
2183                  'access the module, consider \core_availability\info_module::filter_user_list.');
2184      }
2185  
2186      /**
2187       * Getter method for property $availableinfo, ensures that dynamic data is retrieved
2188       *
2189       * @return string Available info (HTML)
2190       */
2191      private function get_available_info() {
2192          $this->obtain_dynamic_data();
2193          return $this->availableinfo;
2194      }
2195  
2196      /**
2197       * Works out whether activity is available to the current user
2198       *
2199       * If the activity is unavailable, additional checks are required to determine if its hidden or greyed out
2200       *
2201       * @return void
2202       */
2203      private function update_user_visible() {
2204          $userid = $this->modinfo->get_user_id();
2205          if ($userid == -1) {
2206              return null;
2207          }
2208          $this->uservisible = true;
2209  
2210          // If the module is being deleted, set the uservisible state to false and return.
2211          if ($this->deletioninprogress) {
2212              $this->uservisible = false;
2213              return null;
2214          }
2215  
2216          // If the user cannot access the activity set the uservisible flag to false.
2217          // Additional checks are required to determine whether the activity is entirely hidden or just greyed out.
2218          if ((!$this->visible && !has_capability('moodle/course:viewhiddenactivities', $this->get_context(), $userid)) ||
2219                  (!$this->get_available() &&
2220                  !has_capability('moodle/course:ignoreavailabilityrestrictions', $this->get_context(), $userid))) {
2221  
2222              $this->uservisible = false;
2223          }
2224  
2225          // Check group membership.
2226          if ($this->is_user_access_restricted_by_capability()) {
2227  
2228               $this->uservisible = false;
2229              // Ensure activity is completely hidden from the user.
2230              $this->availableinfo = '';
2231          }
2232  
2233          $this->uservisibleoncoursepage = $this->uservisible &&
2234              ($this->visibleoncoursepage ||
2235                  has_capability('moodle/course:manageactivities', $this->get_context(), $userid) ||
2236                  has_capability('moodle/course:activityvisibility', $this->get_context(), $userid));
2237          // Activity that is not available, not hidden from course page and has availability
2238          // info is actually visible on the course page (with availability info and without a link).
2239          if (!$this->uservisible && $this->visibleoncoursepage && $this->availableinfo) {
2240              $this->uservisibleoncoursepage = true;
2241          }
2242      }
2243  
2244      /**
2245       * This method has been deprecated and should not be used.
2246       *
2247       * @see $uservisible
2248       * @deprecated Since Moodle 2.8
2249       */
2250      public function is_user_access_restricted_by_group() {
2251          throw new coding_exception('cm_info::is_user_access_restricted_by_group() can not be used any more.' .
2252              ' Use $cm->uservisible to decide whether the current user can access an activity.');
2253      }
2254  
2255      /**
2256       * Checks whether mod/...:view capability restricts the current user's access.
2257       *
2258       * @return bool True if the user access is restricted.
2259       */
2260      public function is_user_access_restricted_by_capability() {
2261          $userid = $this->modinfo->get_user_id();
2262          if ($userid == -1) {
2263              return null;
2264          }
2265          $capability = 'mod/' . $this->modname . ':view';
2266          $capabilityinfo = get_capability_info($capability);
2267          if (!$capabilityinfo) {
2268              // Capability does not exist, no one is prevented from seeing the activity.
2269              return false;
2270          }
2271  
2272          // You are blocked if you don't have the capability.
2273          return !has_capability($capability, $this->get_context(), $userid);
2274      }
2275  
2276      /**
2277       * Checks whether the module's conditional access settings mean that the
2278       * user cannot see the activity at all
2279       *
2280       * @deprecated since 2.7 MDL-44070
2281       */
2282      public function is_user_access_restricted_by_conditional_access() {
2283          throw new coding_exception('cm_info::is_user_access_restricted_by_conditional_access() ' .
2284                  'can not be used any more; this function is not needed (use $cm->uservisible ' .
2285                  'and $cm->availableinfo to decide whether it should be available ' .
2286                  'or appear)');
2287      }
2288  
2289      /**
2290       * Calls a module function (if exists), passing in one parameter: this object.
2291       * @param string $type Name of function e.g. if this is 'grooblezorb' and the modname is
2292       *   'forum' then it will try to call 'mod_forum_grooblezorb' or 'forum_grooblezorb'
2293       * @return void
2294       */
2295      private function call_mod_function($type) {
2296          global $CFG;
2297          $libfile = $CFG->dirroot . '/mod/' . $this->modname . '/lib.php';
2298          if (file_exists($libfile)) {
2299              include_once($libfile);
2300              $function = 'mod_' . $this->modname . '_' . $type;
2301              if (function_exists($function)) {
2302                  $function($this);
2303              } else {
2304                  $function = $this->modname . '_' . $type;
2305                  if (function_exists($function)) {
2306                      $function($this);
2307                  }
2308              }
2309          }
2310      }
2311  
2312      /**
2313       * If view data for this course-module is not yet available, obtains it.
2314       *
2315       * This function is automatically called if any of the functions (marked) which require
2316       * view data are called.
2317       *
2318       * View data is data which is needed only for displaying the course main page (& any similar
2319       * functionality on other pages) but is not needed in general. Obtaining view data may have
2320       * a performance cost.
2321       *
2322       * As part of this function, the module's _cm_info_view function from its lib.php will
2323       * be called (if it exists).
2324       * @return void
2325       */
2326      private function obtain_view_data() {
2327          if ($this->state >= self::STATE_BUILDING_VIEW || $this->modinfo->get_user_id() == -1) {
2328              return;
2329          }
2330          $this->obtain_dynamic_data();
2331          $this->state = self::STATE_BUILDING_VIEW;
2332  
2333          // Let module make changes at this point
2334          $this->call_mod_function('cm_info_view');
2335          $this->state = self::STATE_VIEW;
2336      }
2337  }
2338  
2339  
2340  /**
2341   * Returns reference to full info about modules in course (including visibility).
2342   * Cached and as fast as possible (0 or 1 db query).
2343   *
2344   * use get_fast_modinfo($courseid, 0, true) to reset the static cache for particular course
2345   * use get_fast_modinfo(0, 0, true) to reset the static cache for all courses
2346   *
2347   * use rebuild_course_cache($courseid, true) to reset the application AND static cache
2348   * for particular course when it's contents has changed
2349   *
2350   * @param int|stdClass $courseorid object from DB table 'course' (must have field 'id'
2351   *     and recommended to have field 'cacherev') or just a course id. Just course id
2352   *     is enough when calling get_fast_modinfo() for current course or site or when
2353   *     calling for any other course for the second time.
2354   * @param int $userid User id to populate 'availble' and 'uservisible' attributes of modules and sections.
2355   *     Set to 0 for current user (default). Set to -1 to avoid calculation of dynamic user-depended data.
2356   * @param bool $resetonly whether we want to get modinfo or just reset the cache
2357   * @return course_modinfo|null Module information for course, or null if resetting
2358   * @throws moodle_exception when course is not found (nothing is thrown if resetting)
2359   */
2360  function get_fast_modinfo($courseorid, $userid = 0, $resetonly = false) {
2361      // compartibility with syntax prior to 2.4:
2362      if ($courseorid === 'reset') {
2363          debugging("Using the string 'reset' as the first argument of get_fast_modinfo() is deprecated. Use get_fast_modinfo(0,0,true) instead.", DEBUG_DEVELOPER);
2364          $courseorid = 0;
2365          $resetonly = true;
2366      }
2367  
2368      // Function get_fast_modinfo() can never be called during upgrade unless it is used for clearing cache only.
2369      if (!$resetonly) {
2370          upgrade_ensure_not_running();
2371      }
2372  
2373      // Function is called with $reset = true
2374      if ($resetonly) {
2375          course_modinfo::clear_instance_cache($courseorid);
2376          return null;
2377      }
2378  
2379      // Function is called with $reset = false, retrieve modinfo
2380      return course_modinfo::instance($courseorid, $userid);
2381  }
2382  
2383  /**
2384   * Efficiently retrieves the $course (stdclass) and $cm (cm_info) objects, given
2385   * a cmid. If module name is also provided, it will ensure the cm is of that type.
2386   *
2387   * Usage:
2388   * list($course, $cm) = get_course_and_cm_from_cmid($cmid, 'forum');
2389   *
2390   * Using this method has a performance advantage because it works by loading
2391   * modinfo for the course - which will then be cached and it is needed later
2392   * in most requests. It also guarantees that the $cm object is a cm_info and
2393   * not a stdclass.
2394   *
2395   * The $course object can be supplied if already known and will speed
2396   * up this function - although it is more efficient to use this function to
2397   * get the course if you are starting from a cmid.
2398   *
2399   * To avoid security problems and obscure bugs, you should always specify
2400   * $modulename if the cmid value came from user input.
2401   *
2402   * By default this obtains information (for example, whether user can access
2403   * the activity) for current user, but you can specify a userid if required.
2404   *
2405   * @param stdClass|int $cmorid Id of course-module, or database object
2406   * @param string $modulename Optional modulename (improves security)
2407   * @param stdClass|int $courseorid Optional course object if already loaded
2408   * @param int $userid Optional userid (default = current)
2409   * @return array Array with 2 elements $course and $cm
2410   * @throws moodle_exception If the item doesn't exist or is of wrong module name
2411   */
2412  function get_course_and_cm_from_cmid($cmorid, $modulename = '', $courseorid = 0, $userid = 0) {
2413      global $DB;
2414      if (is_object($cmorid)) {
2415          $cmid = $cmorid->id;
2416          if (isset($cmorid->course)) {
2417              $courseid = (int)$cmorid->course;
2418          } else {
2419              $courseid = 0;
2420          }
2421      } else {
2422          $cmid = (int)$cmorid;
2423          $courseid = 0;
2424      }
2425  
2426      // Validate module name if supplied.
2427      if ($modulename && !core_component::is_valid_plugin_name('mod', $modulename)) {
2428          throw new coding_exception('Invalid modulename parameter');
2429      }
2430  
2431      // Get course from last parameter if supplied.
2432      $course = null;
2433      if (is_object($courseorid)) {
2434          $course = $courseorid;
2435      } else if ($courseorid) {
2436          $courseid = (int)$courseorid;
2437      }
2438  
2439      if (!$course) {
2440          if ($courseid) {
2441              // If course ID is known, get it using normal function.
2442              $course = get_course($courseid);
2443          } else {
2444              // Get course record in a single query based on cmid.
2445              $course = $DB->get_record_sql("
2446                      SELECT c.*
2447                        FROM {course_modules} cm
2448                        JOIN {course} c ON c.id = cm.course
2449                       WHERE cm.id = ?", array($cmid), MUST_EXIST);
2450          }
2451      }
2452  
2453      // Get cm from get_fast_modinfo.
2454      $modinfo = get_fast_modinfo($course, $userid);
2455      $cm = $modinfo->get_cm($cmid);
2456      if ($modulename && $cm->modname !== $modulename) {
2457          throw new moodle_exception('invalidcoursemodule', 'error');
2458      }
2459      return array($course, $cm);
2460  }
2461  
2462  /**
2463   * Efficiently retrieves the $course (stdclass) and $cm (cm_info) objects, given
2464   * an instance id or record and module name.
2465   *
2466   * Usage:
2467   * list($course, $cm) = get_course_and_cm_from_instance($forum, 'forum');
2468   *
2469   * Using this method has a performance advantage because it works by loading
2470   * modinfo for the course - which will then be cached and it is needed later
2471   * in most requests. It also guarantees that the $cm object is a cm_info and
2472   * not a stdclass.
2473   *
2474   * The $course object can be supplied if already known and will speed
2475   * up this function - although it is more efficient to use this function to
2476   * get the course if you are starting from an instance id.
2477   *
2478   * By default this obtains information (for example, whether user can access
2479   * the activity) for current user, but you can specify a userid if required.
2480   *
2481   * @param stdclass|int $instanceorid Id of module instance, or database object
2482   * @param string $modulename Modulename (required)
2483   * @param stdClass|int $courseorid Optional course object if already loaded
2484   * @param int $userid Optional userid (default = current)
2485   * @return array Array with 2 elements $course and $cm
2486   * @throws moodle_exception If the item doesn't exist or is of wrong module name
2487   */
2488  function get_course_and_cm_from_instance($instanceorid, $modulename, $courseorid = 0, $userid = 0) {
2489      global $DB;
2490  
2491      // Get data from parameter.
2492      if (is_object($instanceorid)) {
2493          $instanceid = $instanceorid->id;
2494          if (isset($instanceorid->course)) {
2495              $courseid = (int)$instanceorid->course;
2496          } else {
2497              $courseid = 0;
2498          }
2499      } else {
2500          $instanceid = (int)$instanceorid;
2501          $courseid = 0;
2502      }
2503  
2504      // Get course from last parameter if supplied.
2505      $course = null;
2506      if (is_object($courseorid)) {
2507          $course = $courseorid;
2508      } else if ($courseorid) {
2509          $courseid = (int)$courseorid;
2510      }
2511  
2512      // Validate module name if supplied.
2513      if (!core_component::is_valid_plugin_name('mod', $modulename)) {
2514          throw new coding_exception('Invalid modulename parameter');
2515      }
2516  
2517      if (!$course) {
2518          if ($courseid) {
2519              // If course ID is known, get it using normal function.
2520              $course = get_course($courseid);
2521          } else {
2522              // Get course record in a single query based on instance id.
2523              $pagetable = '{' . $modulename . '}';
2524              $course = $DB->get_record_sql("
2525                      SELECT c.*
2526                        FROM $pagetable instance
2527                        JOIN {course} c ON c.id = instance.course
2528                       WHERE instance.id = ?", array($instanceid), MUST_EXIST);
2529          }
2530      }
2531  
2532      // Get cm from get_fast_modinfo.
2533      $modinfo = get_fast_modinfo($course, $userid);
2534      $instances = $modinfo->get_instances_of($modulename);
2535      if (!array_key_exists($instanceid, $instances)) {
2536          throw new moodle_exception('invalidmoduleid', 'error', $instanceid);
2537      }
2538      return array($course, $instances[$instanceid]);
2539  }
2540  
2541  
2542  /**
2543   * Rebuilds or resets the cached list of course activities stored in MUC.
2544   *
2545   * rebuild_course_cache() must NEVER be called from lib/db/upgrade.php.
2546   * At the same time course cache may ONLY be cleared using this function in
2547   * upgrade scripts of plugins.
2548   *
2549   * During the bulk operations if it is necessary to reset cache of multiple
2550   * courses it is enough to call {@link increment_revision_number()} for the
2551   * table 'course' and field 'cacherev' specifying affected courses in select.
2552   *
2553   * Cached course information is stored in MUC core/coursemodinfo and is
2554   * validated with the DB field {course}.cacherev
2555   *
2556   * @global moodle_database $DB
2557   * @param int $courseid id of course to rebuild, empty means all
2558   * @param boolean $clearonly only clear the cache, gets rebuild automatically on the fly.
2559   *     Recommended to set to true to avoid unnecessary multiple rebuilding.
2560   */
2561  function rebuild_course_cache($courseid=0, $clearonly=false) {
2562      global $COURSE, $SITE, $DB, $CFG;
2563  
2564      // Function rebuild_course_cache() can not be called during upgrade unless it's clear only.
2565      if (!$clearonly && !upgrade_ensure_not_running(true)) {
2566          $clearonly = true;
2567      }
2568  
2569      // Destroy navigation caches
2570      navigation_cache::destroy_volatile_caches();
2571  
2572      if (class_exists('format_base')) {
2573          // if file containing class is not loaded, there is no cache there anyway
2574          format_base::reset_course_cache($courseid);
2575      }
2576  
2577      $cachecoursemodinfo = cache::make('core', 'coursemodinfo');
2578      if (empty($courseid)) {
2579          // Clearing caches for all courses.
2580          increment_revision_number('course', 'cacherev', '');
2581          $cachecoursemodinfo->purge();
2582          course_modinfo::clear_instance_cache();
2583          // Update global values too.
2584          $sitecacherev = $DB->get_field('course', 'cacherev', array('id' => SITEID));
2585          $SITE->cachrev = $sitecacherev;
2586          if ($COURSE->id == SITEID) {
2587              $COURSE->cacherev = $sitecacherev;
2588          } else {
2589              $COURSE->cacherev = $DB->get_field('course', 'cacherev', array('id' => $COURSE->id));
2590          }
2591      } else {
2592          // Clearing cache for one course, make sure it is deleted from user request cache as well.
2593          increment_revision_number('course', 'cacherev', 'id = :id', array('id' => $courseid));
2594          $cachecoursemodinfo->delete($courseid);
2595          course_modinfo::clear_instance_cache($courseid);
2596          // Update global values too.
2597          if ($courseid == $COURSE->id || $courseid == $SITE->id) {
2598              $cacherev = $DB->get_field('course', 'cacherev', array('id' => $courseid));
2599              if ($courseid == $COURSE->id) {
2600                  $COURSE->cacherev = $cacherev;
2601              }
2602              if ($courseid == $SITE->id) {
2603                  $SITE->cachrev = $cacherev;
2604              }
2605          }
2606      }
2607  
2608      if ($clearonly) {
2609          return;
2610      }
2611  
2612      if ($courseid) {
2613          $select = array('id'=>$courseid);
2614      } else {
2615          $select = array();
2616          core_php_time_limit::raise();  // this could take a while!   MDL-10954
2617      }
2618  
2619      $rs = $DB->get_recordset("course", $select,'','id,'.join(',', course_modinfo::$cachedfields));
2620      // Rebuild cache for each course.
2621      foreach ($rs as $course) {
2622          course_modinfo::build_course_cache($course);
2623      }
2624      $rs->close();
2625  }
2626  
2627  
2628  /**
2629   * Class that is the return value for the _get_coursemodule_info module API function.
2630   *
2631   * Note: For backward compatibility, you can also return a stdclass object from that function.
2632   * The difference is that the stdclass object may contain an 'extra' field (deprecated,
2633   * use extraclasses and onclick instead). The stdclass object may not contain
2634   * the new fields defined here (content, extraclasses, customdata).
2635   */
2636  class cached_cm_info {
2637      /**
2638       * Name (text of link) for this activity; Leave unset to accept default name
2639       * @var string
2640       */
2641      public $name;
2642  
2643      /**
2644       * Name of icon for this activity. Normally, this should be used together with $iconcomponent
2645       * to define the icon, as per image_url function.
2646       * For backward compatibility, if this value is of the form 'mod/forum/icon' then an icon
2647       * within that module will be used.
2648       * @see cm_info::get_icon_url()
2649       * @see renderer_base::image_url()
2650       * @var string
2651       */
2652      public $icon;
2653  
2654      /**
2655       * Component for icon for this activity, as per image_url; leave blank to use default 'moodle'
2656       * component
2657       * @see renderer_base::image_url()
2658       * @var string
2659       */
2660      public $iconcomponent;
2661  
2662      /**
2663       * HTML content to be displayed on the main page below the link (if any) for this course-module
2664       * @var string
2665       */
2666      public $content;
2667  
2668      /**
2669       * Custom data to be stored in modinfo for this activity; useful if there are cases when
2670       * internal information for this activity type needs to be accessible from elsewhere on the
2671       * course without making database queries. May be of any type but should be short.
2672       * @var mixed
2673       */
2674      public $customdata;
2675  
2676      /**
2677       * Extra CSS class or classes to be added when this activity is displayed on the main page;
2678       * space-separated string
2679       * @var string
2680       */
2681      public $extraclasses;
2682  
2683      /**
2684       * External URL image to be used by activity as icon, useful for some external-tool modules
2685       * like lti. If set, takes precedence over $icon and $iconcomponent
2686       * @var $moodle_url
2687       */
2688      public $iconurl;
2689  
2690      /**
2691       * Content of onclick JavaScript; escaped HTML to be inserted as attribute value
2692       * @var string
2693       */
2694      public $onclick;
2695  }
2696  
2697  
2698  /**
2699   * Data about a single section on a course. This contains the fields from the
2700   * course_sections table, plus additional data when required.
2701   *
2702   * @property-read int $id Section ID - from course_sections table
2703   * @property-read int $course Course ID - from course_sections table
2704   * @property-read int $section Section number - from course_sections table
2705   * @property-read string $name Section name if specified - from course_sections table
2706   * @property-read int $visible Section visibility (1 = visible) - from course_sections table
2707   * @property-read string $summary Section summary text if specified - from course_sections table
2708   * @property-read int $summaryformat Section summary text format (FORMAT_xx constant) - from course_sections table
2709   * @property-read string $availability Availability information as JSON string -
2710   *    from course_sections table
2711   * @property-read array $conditionscompletion Availability conditions for this section based on the completion of
2712   *    course-modules (array from course-module id to required completion state
2713   *    for that module) - from cached data in sectioncache field
2714   * @property-read array $conditionsgrade Availability conditions for this section based on course grades (array from
2715   *    grade item id to object with ->min, ->max fields) - from cached data in
2716   *    sectioncache field
2717   * @property-read array $conditionsfield Availability conditions for this section based on user fields
2718   * @property-read bool $available True if this section is available to the given user i.e. if all availability conditions
2719   *    are met - obtained dynamically
2720   * @property-read string $availableinfo If section is not available to some users, this string gives information about
2721   *    availability which can be displayed to students and/or staff (e.g. 'Available from 3 January 2010')
2722   *    for display on main page - obtained dynamically
2723   * @property-read bool $uservisible True if this section is available to the given user (for example, if current user
2724   *    has viewhiddensections capability, they can access the section even if it is not
2725   *    visible or not available, so this would be true in that case) - obtained dynamically
2726   * @property-read string $sequence Comma-separated list of all modules in the section. Note, this field may not exactly
2727   *    match course_sections.sequence if later has references to non-existing modules or not modules of not available module types.
2728   * @property-read course_modinfo $modinfo
2729   */
2730  class section_info implements IteratorAggregate {
2731      /**
2732       * Section ID - from course_sections table
2733       * @var int
2734       */
2735      private $_id;
2736  
2737      /**
2738       * Section number - from course_sections table
2739       * @var int
2740       */
2741      private $_section;
2742  
2743      /**
2744       * Section name if specified - from course_sections table
2745       * @var string
2746       */
2747      private $_name;
2748  
2749      /**
2750       * Section visibility (1 = visible) - from course_sections table
2751       * @var int
2752       */
2753      private $_visible;
2754  
2755      /**
2756       * Section summary text if specified - from course_sections table
2757       * @var string
2758       */
2759      private $_summary;
2760  
2761      /**
2762       * Section summary text format (FORMAT_xx constant) - from course_sections table
2763       * @var int
2764       */
2765      private $_summaryformat;
2766  
2767      /**
2768       * Availability information as JSON string - from course_sections table
2769       * @var string
2770       */
2771      private $_availability;
2772  
2773      /**
2774       * Availability conditions for this section based on the completion of
2775       * course-modules (array from course-module id to required completion state
2776       * for that module) - from cached data in sectioncache field
2777       * @var array
2778       */
2779      private $_conditionscompletion;
2780  
2781      /**
2782       * Availability conditions for this section based on course grades (array from
2783       * grade item id to object with ->min, ->max fields) - from cached data in
2784       * sectioncache field
2785       * @var array
2786       */
2787      private $_conditionsgrade;
2788  
2789      /**
2790       * Availability conditions for this section based on user fields
2791       * @var array
2792       */
2793      private $_conditionsfield;
2794  
2795      /**
2796       * True if this section is available to students i.e. if all availability conditions
2797       * are met - obtained dynamically on request, see function {@link section_info::get_available()}
2798       * @var bool|null
2799       */
2800      private $_available;
2801  
2802      /**
2803       * If section is not available to some users, this string gives information about
2804       * availability which can be displayed to students and/or staff (e.g. 'Available from 3
2805       * January 2010') for display on main page - obtained dynamically on request, see
2806       * function {@link section_info::get_availableinfo()}
2807       * @var string
2808       */
2809      private $_availableinfo;
2810  
2811      /**
2812       * True if this section is available to the CURRENT user (for example, if current user
2813       * has viewhiddensections capability, they can access the section even if it is not
2814       * visible or not available, so this would be true in that case) - obtained dynamically
2815       * on request, see function {@link section_info::get_uservisible()}
2816       * @var bool|null
2817       */
2818      private $_uservisible;
2819  
2820      /**
2821       * Default values for sectioncache fields; if a field has this value, it won't
2822       * be stored in the sectioncache cache, to save space. Checks are done by ===
2823       * which means values must all be strings.
2824       * @var array
2825       */
2826      private static $sectioncachedefaults = array(
2827          'name' => null,
2828          'summary' => '',
2829          'summaryformat' => '1', // FORMAT_HTML, but must be a string
2830          'visible' => '1',
2831          'availability' => null
2832      );
2833  
2834      /**
2835       * Stores format options that have been cached when building 'coursecache'
2836       * When the format option is requested we look first if it has been cached
2837       * @var array
2838       */
2839      private $cachedformatoptions = array();
2840  
2841      /**
2842       * Stores the list of all possible section options defined in each used course format.
2843       * @var array
2844       */
2845      static private $sectionformatoptions = array();
2846  
2847      /**
2848       * Stores the modinfo object passed in constructor, may be used when requesting
2849       * dynamically obtained attributes such as available, availableinfo, uservisible.
2850       * Also used to retrun information about current course or user.
2851       * @var course_modinfo
2852       */
2853      private $modinfo;
2854  
2855      /**
2856       * Constructs object from database information plus extra required data.
2857       * @param object $data Array entry from cached sectioncache
2858       * @param int $number Section number (array key)
2859       * @param int $notused1 argument not used (informaion is available in $modinfo)
2860       * @param int $notused2 argument not used (informaion is available in $modinfo)
2861       * @param course_modinfo $modinfo Owner (needed for checking availability)
2862       * @param int $notused3 argument not used (informaion is available in $modinfo)
2863       */
2864      public function __construct($data, $number, $notused1, $notused2, $modinfo, $notused3) {
2865          global $CFG;
2866          require_once($CFG->dirroot.'/course/lib.php');
2867  
2868          // Data that is always present
2869          $this->_id = $data->id;
2870  
2871          $defaults = self::$sectioncachedefaults +
2872                  array('conditionscompletion' => array(),
2873                      'conditionsgrade' => array(),
2874                      'conditionsfield' => array());
2875  
2876          // Data that may use default values to save cache size
2877          foreach ($defaults as $field => $value) {
2878              if (isset($data->{$field})) {
2879                  $this->{'_'.$field} = $data->{$field};
2880              } else {
2881                  $this->{'_'.$field} = $value;
2882              }
2883          }
2884  
2885          // Other data from constructor arguments.
2886          $this->_section = $number;
2887          $this->modinfo = $modinfo;
2888  
2889          // Cached course format data.
2890          $course = $modinfo->get_course();
2891          if (!isset(self::$sectionformatoptions[$course->format])) {
2892              // Store list of section format options defined in each used course format.
2893              // They do not depend on particular course but only on its format.
2894              self::$sectionformatoptions[$course->format] =
2895                      course_get_format($course)->section_format_options();
2896          }
2897          foreach (self::$sectionformatoptions[$course->format] as $field => $option) {
2898              if (!empty($option['cache'])) {
2899                  if (isset($data->{$field})) {
2900                      $this->cachedformatoptions[$field] = $data->{$field};
2901                  } else if (array_key_exists('cachedefault', $option)) {
2902                      $this->cachedformatoptions[$field] = $option['cachedefault'];
2903                  }
2904              }
2905          }
2906      }
2907  
2908      /**
2909       * Magic method to check if the property is set
2910       *
2911       * @param string $name name of the property
2912       * @return bool
2913       */
2914      public function __isset($name) {
2915          if (method_exists($this, 'get_'.$name) ||
2916                  property_exists($this, '_'.$name) ||
2917                  array_key_exists($name, self::$sectionformatoptions[$this->modinfo->get_course()->format])) {
2918              $value = $this->__get($name);
2919              return isset($value);
2920          }
2921          return false;
2922      }
2923  
2924      /**
2925       * Magic method to check if the property is empty
2926       *
2927       * @param string $name name of the property
2928       * @return bool
2929       */
2930      public function __empty($name) {
2931          if (method_exists($this, 'get_'.$name) ||
2932                  property_exists($this, '_'.$name) ||
2933                  array_key_exists($name, self::$sectionformatoptions[$this->modinfo->get_course()->format])) {
2934              $value = $this->__get($name);
2935              return empty($value);
2936          }
2937          return true;
2938      }
2939  
2940      /**
2941       * Magic method to retrieve the property, this is either basic section property
2942       * or availability information or additional properties added by course format
2943       *
2944       * @param string $name name of the property
2945       * @return bool
2946       */
2947      public function __get($name) {
2948          if (method_exists($this, 'get_'.$name)) {
2949              return $this->{'get_'.$name}();
2950          }
2951          if (property_exists($this, '_'.$name)) {
2952              return $this->{'_'.$name};
2953          }
2954          if (array_key_exists($name, $this->cachedformatoptions)) {
2955              return $this->cachedformatoptions[$name];
2956          }
2957          // precheck if the option is defined in format to avoid unnecessary DB queries in get_format_options()
2958          if (array_key_exists($name, self::$sectionformatoptions[$this->modinfo->get_course()->format])) {
2959              $formatoptions = course_get_format($this->modinfo->get_course())->get_format_options($this);
2960              return $formatoptions[$name];
2961          }
2962          debugging('Invalid section_info property accessed! '.$name);
2963          return null;
2964      }
2965  
2966      /**
2967       * Finds whether this section is available at the moment for the current user.
2968       *
2969       * The value can be accessed publicly as $sectioninfo->available, but can be called directly if there
2970       * is a case when it might be called recursively (you can't call property values recursively).
2971       *
2972       * @return bool
2973       */
2974      public function get_available() {
2975          global $CFG;
2976          $userid = $this->modinfo->get_user_id();
2977          if ($this->_available !== null || $userid == -1) {
2978              // Has already been calculated or does not need calculation.
2979              return $this->_available;
2980          }
2981          $this->_available = true;
2982          $this->_availableinfo = '';
2983          if (!empty($CFG->enableavailability)) {
2984              // Get availability information.
2985              $ci = new \core_availability\info_section($this);
2986              $this->_available = $ci->is_available($this->_availableinfo, true,
2987                      $userid, $this->modinfo);
2988          }
2989          // Execute the hook from the course format that may override the available/availableinfo properties.
2990          $currentavailable = $this->_available;
2991          course_get_format($this->modinfo->get_course())->
2992              section_get_available_hook($this, $this->_available, $this->_availableinfo);
2993          if (!$currentavailable && $this->_available) {
2994              debugging('section_get_available_hook() can not make unavailable section available', DEBUG_DEVELOPER);
2995              $this->_available = $currentavailable;
2996          }
2997          return $this->_available;
2998      }
2999  
3000      /**
3001       * Returns the availability text shown next to the section on course page.
3002       *
3003       * @return string
3004       */
3005      private function get_availableinfo() {
3006          // Calling get_available() will also fill the availableinfo property
3007          // (or leave it null if there is no userid).
3008          $this->get_available();
3009          return $this->_availableinfo;
3010      }
3011  
3012      /**
3013       * Implementation of IteratorAggregate::getIterator(), allows to cycle through properties
3014       * and use {@link convert_to_array()}
3015       *
3016       * @return ArrayIterator
3017       */
3018      public function getIterator() {
3019          $ret = array();
3020          foreach (get_object_vars($this) as $key => $value) {
3021              if (substr($key, 0, 1) == '_') {
3022                  if (method_exists($this, 'get'.$key)) {
3023                      $ret[substr($key, 1)] = $this->{'get'.$key}();
3024                  } else {
3025                      $ret[substr($key, 1)] = $this->$key;
3026                  }
3027              }
3028          }
3029          $ret['sequence'] = $this->get_sequence();
3030          $ret['course'] = $this->get_course();
3031          $ret = array_merge($ret, course_get_format($this->modinfo->get_course())->get_format_options($this->_section));
3032          return new ArrayIterator($ret);
3033      }
3034  
3035      /**
3036       * Works out whether activity is visible *for current user* - if this is false, they
3037       * aren't allowed to access it.
3038       *
3039       * @return bool
3040       */
3041      private function get_uservisible() {
3042          $userid = $this->modinfo->get_user_id();
3043          if ($this->_uservisible !== null || $userid == -1) {
3044              // Has already been calculated or does not need calculation.
3045              return $this->_uservisible;
3046          }
3047          $this->_uservisible = true;
3048          if (!$this->_visible || !$this->get_available()) {
3049              $coursecontext = context_course::instance($this->get_course());
3050              if (!$this->_visible && !has_capability('moodle/course:viewhiddensections', $coursecontext, $userid) ||
3051                      (!$this->get_available() &&
3052                      !has_capability('moodle/course:ignoreavailabilityrestrictions', $coursecontext, $userid))) {
3053  
3054                  $this->_uservisible = false;
3055              }
3056          }
3057          return $this->_uservisible;
3058      }
3059  
3060      /**
3061       * Restores the course_sections.sequence value
3062       *
3063       * @return string
3064       */
3065      private function get_sequence() {
3066          if (!empty($this->modinfo->sections[$this->_section])) {
3067              return implode(',', $this->modinfo->sections[$this->_section]);
3068          } else {
3069              return '';
3070          }
3071      }
3072  
3073      /**
3074       * Returns course ID - from course_sections table
3075       *
3076       * @return int
3077       */
3078      private function get_course() {
3079          return $this->modinfo->get_course_id();
3080      }
3081  
3082      /**
3083       * Modinfo object
3084       *
3085       * @return course_modinfo
3086       */
3087      private function get_modinfo() {
3088          return $this->modinfo;
3089      }
3090  
3091      /**
3092       * Prepares section data for inclusion in sectioncache cache, removing items
3093       * that are set to defaults, and adding availability data if required.
3094       *
3095       * Called by build_section_cache in course_modinfo only; do not use otherwise.
3096       * @param object $section Raw section data object
3097       */
3098      public static function convert_for_section_cache($section) {
3099          global $CFG;
3100  
3101          // Course id stored in course table
3102          unset($section->course);
3103          // Section number stored in array key
3104          unset($section->section);
3105          // Sequence stored implicity in modinfo $sections array
3106          unset($section->sequence);
3107  
3108          // Remove default data
3109          foreach (self::$sectioncachedefaults as $field => $value) {
3110              // Exact compare as strings to avoid problems if some strings are set
3111              // to "0" etc.
3112              if (isset($section->{$field}) && $section->{$field} === $value) {
3113                  unset($section->{$field});
3114              }
3115          }
3116      }
3117  }