Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.
/lib/ -> modinfolib.php (source)

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