Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.
/lib/ -> modinfolib.php (source)

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