Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.
/lib/ -> modinfolib.php (source)

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

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