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