40
40
* This includes information about the course-modules and the sections on the course. It can also
41
41
* include dynamic data that has been updated for the current user.
43
* Use {@link get_fast_modinfo()} to retrieve the instance of the object for particular course
44
* and particular user.
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
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'
43
class course_modinfo extends stdClass {
44
// For convenience we store the course object here as it is needed in other parts of code
56
class course_modinfo {
58
* List of fields from DB table 'course' that are cached in MUC and are always present in course_modinfo::$course
61
public static $cachedfields = array('shortname', 'fullname', 'format',
62
'enablecompletion', 'groupmode', 'groupmodeforce', 'cacherev');
65
* For convenience we store the course object here as it is needed in other parts of code
46
// Array of section data from cache
71
* Array of section data from cache
47
74
private $sectioninfo;
49
// Existing data fields
50
///////////////////////
52
// These are public for backward compatibility. Note: it is not possible to retain BC
53
// using PHP magic get methods because behaviour is different with regard to empty().
58
* @deprecated For new code, use get_course_id instead.
65
* @deprecated For new code, use get_user_id instead.
70
83
* Array from int (section num, e.g. 0) => array of int (course-module id); this list only
71
84
* includes sections that actually contain at least one course-module
73
* @deprecated For new code, use get_sections instead
78
90
* Array from int (cm id) => cm_info object
80
* @deprecated For new code, use get_cms or get_cm instead.
85
96
* Array from string (modname) => int (instance id) => cm_info object
87
* @deprecated For new code, use get_instances or get_instances_of instead.
92
102
* Groups that the current user belongs to. This value is usually not available (set to null)
93
103
* unless the course has activities set to groupmembersonly. When set, it is an array of
94
104
* grouping id => array of group id => group id. Includes grouping id 0 for 'all groups'.
110
* List of class read-only properties and their getter methods.
111
* Used by magic functions __get(), __isset(), __empty()
96
* @deprecated Don't use this! For new code, use get_groups.
100
// Get methods for data
101
///////////////////////
104
* @return object Moodle course object that was used to construct this data
114
private static $standardproperties = array(
115
'courseid' => 'get_course_id',
116
'userid' => 'get_user_id',
117
'sections' => 'get_sections',
119
'instances' => 'get_instances',
120
'groups' => 'get_groups_all',
124
* Magic method getter
126
* @param string $name
129
public function __get($name) {
130
if (isset(self::$standardproperties[$name])) {
131
$method = self::$standardproperties[$name];
132
return $this->$method();
134
debugging('Invalid course_modinfo property accessed: '.$name);
140
* Magic method for function isset()
142
* @param string $name
145
public function __isset($name) {
146
if (isset(self::$standardproperties[$name])) {
147
$value = $this->__get($name);
148
return isset($value);
154
* Magic method for function empty()
156
* @param string $name
159
public function __empty($name) {
160
if (isset(self::$standardproperties[$name])) {
161
$value = $this->__get($name);
162
return empty($value);
168
* Magic method setter
170
* Will display the developer warning when trying to set/overwrite existing property.
172
* @param string $name
173
* @param mixed $value
175
public function __set($name, $value) {
176
debugging("It is not allowed to set the property course_modinfo::\${$name}", DEBUG_DEVELOPER);
180
* Returns course object that was used in the first {@link get_fast_modinfo()} call.
182
* It may not contain all fields from DB table {course} but always has at least the following:
183
* id,shortname,fullname,format,enablecompletion,groupmode,groupmodeforce,cacherev
106
187
public function get_course() {
107
188
return $this->course;
192
* Returns groups that the current user belongs to on the course. Note: If not already
193
* available, this may make a database query.
194
* @param int $groupingid Grouping ID or 0 (default) for all groups
195
* @return array Array of int (group id) => int (same group id again); empty array if none
273
* Groups that the current user belongs to organised by grouping id. Calculated on the first request.
274
* @return int[][] array of grouping id => array of group id => group id. Includes grouping id 0 for 'all groups'
197
public function get_groups($groupingid=0) {
276
private function get_groups_all() {
198
277
if (is_null($this->groups)) {
199
278
// NOTE: Performance could be improved here. The system caches user groups
200
279
// in $USER->groupmember[$courseid] => array of groupid=>groupid. Unfortunately this
201
280
// structure does not include grouping information. It probably could be changed to
202
281
// do so, without a significant performance hit on login, thus saving this one query
204
$this->groups = groups_get_user_groups($this->courseid, $this->userid);
283
$this->groups = groups_get_user_groups($this->course->id, $this->userid);
206
if (!isset($this->groups[$groupingid])) {
285
return $this->groups;
289
* Returns groups that the current user belongs to on the course. Note: If not already
290
* available, this may make a database query.
291
* @param int $groupingid Grouping ID or 0 (default) for all groups
292
* @return int[] Array of int (group id) => int (same group id again); empty array if none
294
public function get_groups($groupingid = 0) {
295
$allgroups = $this->get_groups_all();
296
if (!isset($allgroups[$groupingid])) {
209
return $this->groups[$groupingid];
299
return $allgroups[$groupingid];
213
303
* Gets all sections as array from section number => data about section.
214
* @return array Array of section_info objects organised by section number
304
* @return section_info[] Array of section_info objects organised by section number
216
306
public function get_section_info_all() {
217
307
return $this->sectioninfo;
328
* Static cache for generated course_modinfo instances
330
* @see course_modinfo::instance()
331
* @see course_modinfo::clear_instance_cache()
332
* @var course_modinfo[]
334
protected static $instancecache = array();
337
* Timestamps (microtime) when the course_modinfo instances were last accessed
339
* It is used to remove the least recent accessed instances when static cache is full
343
protected static $cacheaccessed = array();
346
* Clears the cache used in course_modinfo::instance()
348
* Used in {@link get_fast_modinfo()} when called with argument $reset = true
349
* and in {@link rebuild_course_cache()}
351
* @param null|int|stdClass $courseorid if specified removes only cached value for this course
353
public static function clear_instance_cache($courseorid = null) {
354
if (empty($courseorid)) {
355
self::$instancecache = array();
356
self::$cacheaccessed = array();
359
if (is_object($courseorid)) {
360
$courseorid = $courseorid->id;
362
if (isset(self::$instancecache[$courseorid])) {
363
// Unsetting static variable in PHP is peculiar, it removes the reference,
364
// but data remain in memory. Prior to unsetting, the varable needs to be
365
// set to empty to remove its remains from memory.
366
self::$instancecache[$courseorid] = '';
367
unset(self::$instancecache[$courseorid]);
368
unset(self::$cacheaccessed[$courseorid]);
373
* Returns the instance of course_modinfo for the specified course and specified user
375
* This function uses static cache for the retrieved instances. The cache
376
* size is limited by MAX_MODINFO_CACHE_SIZE. If instance is not found in
377
* the static cache or it was created for another user or the cacherev validation
378
* failed - a new instance is constructed and returned.
380
* Used in {@link get_fast_modinfo()}
382
* @param int|stdClass $courseorid object from DB table 'course' (must have field 'id'
383
* and recommended to have field 'cacherev') or just a course id
384
* @param int $userid User id to populate 'availble' and 'uservisible' attributes of modules and sections.
385
* Set to 0 for current user (default). Set to -1 to avoid calculation of dynamic user-depended data.
386
* @return course_modinfo
388
public static function instance($courseorid, $userid = 0) {
390
if (is_object($courseorid)) {
391
$course = $courseorid;
393
$course = (object)array('id' => $courseorid);
395
if (empty($userid)) {
399
if (!empty(self::$instancecache[$course->id])) {
400
if (self::$instancecache[$course->id]->userid == $userid &&
401
(!isset($course->cacherev) ||
402
$course->cacherev == self::$instancecache[$course->id]->get_course()->cacherev)) {
403
// This course's modinfo for the same user was recently retrieved, return cached.
404
self::$cacheaccessed[$course->id] = microtime(true);
405
return self::$instancecache[$course->id];
407
// Prevent potential reference problems when switching users.
408
self::clear_instance_cache($course->id);
411
$modinfo = new course_modinfo($course, $userid);
413
// We have a limit of MAX_MODINFO_CACHE_SIZE entries to store in static variable.
414
if (count(self::$instancecache) >= MAX_MODINFO_CACHE_SIZE) {
415
// Find the course that was the least recently accessed.
416
asort(self::$cacheaccessed, SORT_NUMERIC);
417
$courseidtoremove = key(array_reverse(self::$cacheaccessed, true));
418
self::clear_instance_cache($courseidtoremove);
421
// Add modinfo to the static cache.
422
self::$instancecache[$course->id] = $modinfo;
423
self::$cacheaccessed[$course->id] = microtime(true);
238
429
* Constructs based on course.
239
430
* Note: This constructor should not usually be called directly.
240
431
* Use get_fast_modinfo($course) instead as this maintains a cache.
241
* @param object $course Moodle course object, which may include modinfo
432
* @param stdClass $course course object, only property id is required.
242
433
* @param int $userid User ID
434
* @throws moodle_exception if course is not found
244
436
public function __construct($course, $userid) {
245
global $CFG, $DB, $COURSE, $SITE;
437
global $CFG, $COURSE, $SITE, $DB;
247
if (!isset($course->modinfo) || !isset($course->sectioncache)) {
439
if (!isset($course->cacherev)) {
440
// We require presence of property cacherev to validate the course cache.
441
// No need to clone the $COURSE or $SITE object here because we clone it below anyway.
248
442
$course = get_course($course->id, false);
251
// Check modinfo field is set. If not, build and load it.
252
if (empty($course->modinfo) || empty($course->sectioncache)) {
253
rebuild_course_cache($course->id);
254
$course = $DB->get_record('course', array('id'=>$course->id), '*', MUST_EXIST);
445
$cachecoursemodinfo = cache::make('core', 'coursemodinfo');
447
// Retrieve modinfo from cache. If not present or cacherev mismatches, call rebuild and retrieve again.
448
$coursemodinfo = $cachecoursemodinfo->get($course->id);
449
if ($coursemodinfo === false || ($course->cacherev != $coursemodinfo->cacherev)) {
450
$coursemodinfo = self::build_course_cache($course);
257
453
// Set initial values
258
$this->courseid = $course->id;
259
454
$this->userid = $userid;
260
455
$this->sections = array();
261
456
$this->cms = array();
262
457
$this->instances = array();
263
458
$this->groups = null;
264
$this->course = $course;
266
// Load modinfo field into memory as PHP object and check it's valid
267
$info = unserialize($course->modinfo);
268
if (!is_array($info)) {
269
// hmm, something is wrong - lets try to fix it
270
rebuild_course_cache($course->id);
271
$course->modinfo = $DB->get_field('course', 'modinfo', array('id'=>$course->id));
272
$info = unserialize($course->modinfo);
273
if (!is_array($info)) {
274
// If it still fails, abort
275
debugging('Problem with "modinfo" data for this course');
280
// Load sectioncache field into memory as PHP object and check it's valid
281
$sectioncache = unserialize($course->sectioncache);
282
if (!is_array($sectioncache)) {
283
// hmm, something is wrong - let's fix it
284
rebuild_course_cache($course->id);
285
$course->sectioncache = $DB->get_field('course', 'sectioncache', array('id'=>$course->id));
286
$sectioncache = unserialize($course->sectioncache);
287
if (!is_array($sectioncache)) {
288
// If it still fails, abort
289
debugging('Problem with "sectioncache" data for this course');
294
// If we haven't already preloaded contexts for the course, do it now.
460
// If we haven't already preloaded contexts for the course, do it now
295
461
// Modules are also cached here as long as it's the first time this course has been preloaded.
296
preload_course_contexts($course->id);
462
context_helper::preload_course($course->id);
298
464
// Quick integrity check: as a result of race conditions modinfo may not be regenerated after the change.
299
465
// It is especially dangerous if modinfo contains the deleted course module, as it results in fatal error.
301
467
if ($course->id == $COURSE->id || $course->id == $SITE->id) {
302
468
// Only verify current course (or frontpage) as pages with many courses may not have module contexts cached.
303
469
// (Uncached modules will result in a very slow verification).
304
foreach ($info as $mod) {
470
foreach ($coursemodinfo->modinfo as $mod) {
305
471
if (!context_module::instance($mod->cm, IGNORE_MISSING)) {
306
472
debugging('Course cache integrity check failed: course module with id '. $mod->cm.
307
473
' does not have context. Rebuilding cache for course '. $course->id);
308
rebuild_course_cache($course->id);
309
$this->course = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST);
310
$info = unserialize($this->course->modinfo);
311
$sectioncache = unserialize($this->course->sectioncache);
474
// Re-request the course record from DB as well, don't use get_course() here.
475
$course = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST);
476
$coursemodinfo = self::build_course_cache($course);
482
// Overwrite unset fields in $course object with cached values, store the course object.
483
$this->course = fullclone($course);
484
foreach ($coursemodinfo as $key => $value) {
485
if ($key !== 'modinfo' && $key !== 'sectioncache' &&
486
(!isset($this->course->$key) || $key === 'cacherev')) {
487
$this->course->$key = $value;
317
491
// Loop through each piece of module data, constructing it
318
$modexists = array();
319
foreach ($info as $mod) {
492
static $modexists = array();
493
foreach ($coursemodinfo->modinfo as $mod) {
320
494
if (empty($mod->name)) {
321
495
// something is wrong here
325
499
// Skip modules which don't exist
326
if (empty($modexists[$mod->mod])) {
327
if (!file_exists("$CFG->dirroot/mod/$mod->mod/lib.php")) {
330
$modexists[$mod->mod] = true;
500
if (!array_key_exists($mod->mod, $modexists)) {
501
$modexists[$mod->mod] = file_exists("$CFG->dirroot/mod/$mod->mod/lib.php");
503
if (!$modexists[$mod->mod]) {
333
507
// Construct info for this module
334
$cm = new cm_info($this, $course, $mod, $info);
508
$cm = new cm_info($this, null, $mod, null);
336
510
// Store module in instances and cms array
337
511
if (!isset($this->instances[$cm->modname])) {
375
534
* the course cache. (Does not include information that is already cached
376
535
* in some other way.)
378
* Used internally by rebuild_course_cache function; do not use otherwise.
537
* This function will be removed in 2.7
539
* @deprecated since 2.6
379
540
* @param int $courseid Course ID
380
541
* @return array Information about sections, indexed by section number (not id)
382
543
public static function build_section_cache($courseid) {
545
debugging('Function course_modinfo::build_section_cache() is deprecated. It can only be used internally to build course cache.');
546
$course = $DB->get_record('course', array('id' => $course->id),
547
array_merge(array('id'), self::$cachedfields), MUST_EXIST);
548
return self::build_course_section_cache($course);
552
* Builds a list of information about sections on a course to be stored in
553
* the course cache. (Does not include information that is already cached
554
* in some other way.)
556
* @param stdClass $course Course object (must contain fields
557
* @return array Information about sections, indexed by section number (not id)
559
protected static function build_course_section_cache($course) {
385
562
// Get section data
386
$sections = $DB->get_records('course_sections', array('course' => $courseid), 'section',
563
$sections = $DB->get_records('course_sections', array('course' => $course->id), 'section',
387
564
'section, id, course, name, summary, summaryformat, sequence, visible, ' .
388
565
'availablefrom, availableuntil, showavailability, groupingid');
389
566
$compressedsections = array();
391
$formatoptionsdef = course_get_format($courseid)->section_format_options();
568
$formatoptionsdef = course_get_format($course)->section_format_options();
392
569
// Remove unnecessary data and add availability
393
570
foreach ($sections as $number => $section) {
394
571
// Add cached options from course format to $section object
395
572
foreach ($formatoptionsdef as $key => $option) {
396
573
if (!empty($option['cache'])) {
397
$formatoptions = course_get_format($courseid)->get_format_options($section);
574
$formatoptions = course_get_format($course)->get_format_options($section);
398
575
if (!array_key_exists('cachedefault', $option) || $option['cachedefault'] !== $formatoptions[$key]) {
399
576
$section->$key = $formatoptions[$key];
408
585
return $compressedsections;
589
* Builds and stores in MUC object containing information about course
590
* modules and sections together with cached fields from table course.
592
* @param stdClass $course object from DB table course. Must have property 'id'
593
* but preferably should have all cached fields.
594
* @return stdClass object with all cached keys of the course plus fields modinfo and sectioncache.
595
* The same object is stored in MUC
596
* @throws moodle_exception if course is not found (if $course object misses some of the
597
* necessary fields it is re-requested from database)
599
public static function build_course_cache($course) {
601
require_once("$CFG->dirroot/course/lib.php");
602
if (empty($course->id)) {
603
throw new coding_exception('Object $course is missing required property \id\'');
605
// Ensure object has all necessary fields.
606
foreach (self::$cachedfields as $key) {
607
if (!isset($course->$key)) {
608
$course = $DB->get_record('course', array('id' => $course->id),
609
implode(',', array_merge(array('id'), self::$cachedfields)), MUST_EXIST);
613
// Retrieve all information about activities and sections.
614
// This may take time on large courses and it is possible that another user modifies the same course during this process.
615
// Field cacherev stored in both DB and cache will ensure that cached data matches the current course state.
616
$coursemodinfo = new stdClass();
617
$coursemodinfo->modinfo = get_array_of_activities($course->id);
618
$coursemodinfo->sectioncache = self::build_course_section_cache($course);
619
foreach (self::$cachedfields as $key) {
620
$coursemodinfo->$key = $course->$key;
622
// Set the accumulated activities and sections information in cache, together with cacherev.
623
$cachecoursemodinfo = cache::make('core', 'coursemodinfo');
624
$cachecoursemodinfo->set($course->id, $coursemodinfo);
625
return $coursemodinfo;
414
631
* Data about a single module on a course. This contains most of the fields in the course_modules
415
632
* table, plus additional data when required.
417
* This object has many public fields; code should treat all these fields as read-only and set
418
* data only using the supplied set functions. Setting the fields directly is not supported
419
* and may cause problems later.
634
* The object can be accessed by core or any plugin (i.e. course format, block, filter, etc.) as
635
* get_fast_modinfo($courseorid)->cms[$coursemoduleid]
637
* get_fast_modinfo($courseorid)->instances[$moduletype][$instanceid]
639
* There are three stages when activity module can add/modify data in this object:
641
* <b>Stage 1 - during building the cache.</b>
642
* Allows to add to the course cache static user-independent information about the module.
643
* Modules should try to include only absolutely necessary information that may be required
644
* when displaying course view page. The information is stored in application-level cache
645
* and reset when {@link rebuild_course_cache()} is called or cache is purged by admin.
647
* Modules can implement callback XXX_get_coursemodule_info() returning instance of object
648
* {@link cached_cm_info}
650
* <b>Stage 2 - dynamic data.</b>
651
* Dynamic data is user-dependend, it is stored in request-level cache. To reset this cache
652
* {@link get_fast_modinfo()} with $reset argument may be called.
654
* Dynamic data is obtained when any of the following properties/methods is requested:
655
* - {@link cm_info::$url}
656
* - {@link cm_info::$name}
657
* - {@link cm_info::$onclick}
658
* - {@link cm_info::get_icon_url()}
659
* - {@link cm_info::$uservisible}
660
* - {@link cm_info::$available}
661
* - {@link cm_info::$showavailability}
662
* - {@link cm_info::$availableinfo}
663
* - plus any of the properties listed in Stage 3.
665
* Modules can implement callback <b>XXX_cm_info_dynamic()</b> and inside this callback they
666
* are allowed to use any of the following set methods:
667
* - {@link cm_info::set_available()}
668
* - {@link cm_info::set_name()}
669
* - {@link cm_info::set_no_view_link()}
670
* - {@link cm_info::set_user_visible()}
671
* - {@link cm_info::set_on_click()}
672
* - {@link cm_info::set_icon_url()}
673
* Any methods affecting view elements can also be set in this callback.
675
* <b>Stage 3 (view data).</b>
676
* Also user-dependend data stored in request-level cache. Second stage is created
677
* because populating the view data can be expensive as it may access much more
678
* Moodle APIs such as filters, user information, output renderers and we
679
* don't want to request it until necessary.
680
* View data is obtained when any of the following properties/methods is requested:
681
* - {@link cm_info::$afterediticons}
682
* - {@link cm_info::$content}
683
* - {@link cm_info::get_formatted_content()}
684
* - {@link cm_info::$extraclasses}
685
* - {@link cm_info::$afterlink}
687
* Modules can implement callback <b>XXX_cm_info_view()</b> and inside this callback they
688
* are allowed to use any of the following set methods:
689
* - {@link cm_info::set_after_edit_icons()}
690
* - {@link cm_info::set_after_link()}
691
* - {@link cm_info::set_content()}
692
* - {@link cm_info::set_extra_classes()}
694
* @property-read int $id Course-module ID - from course_modules table
695
* @property-read int $instance Module instance (ID within module table) - from course_modules table
696
* @property-read int $course Course ID - from course_modules table
697
* @property-read string $idnumber 'ID number' from course-modules table (arbitrary text set by user) - from
698
* course_modules table
699
* @property-read int $added Time that this course-module was added (unix time) - from course_modules table
700
* @property-read int $visible Visible setting (0 or 1; if this is 0, students cannot see/access the activity) - from
701
* course_modules table
702
* @property-read int $visibleold Old visible setting (if the entire section is hidden, the previous value for
703
* visible is stored in this field) - from course_modules table
704
* @property-read int $groupmode Group mode (one of the constants NOGROUPS, SEPARATEGROUPS, or VISIBLEGROUPS) - from
705
* course_modules table. Use {@link cm_info::$effectivegroupmode} to find the actual group mode that may be forced by course.
706
* @property-read int $groupingid Grouping ID (0 = all groupings)
707
* @property-read int $groupmembersonly Group members only (if set to 1, only members of a suitable group see this link on the
708
* course page; 0 = everyone sees it even if they don't belong to a suitable group) - from
709
* course_modules table
710
* @property-read bool $coursegroupmodeforce Indicates whether the course containing the module has forced the groupmode
711
* This means that cm_info::$groupmode should be ignored and cm_info::$coursegroupmode be used instead
712
* @property-read int $coursegroupmode Group mode (one of the constants NOGROUPS, SEPARATEGROUPS, or VISIBLEGROUPS) - from
713
* course table - as specified for the course containing the module
714
* Effective only if {@link cm_info::$coursegroupmodeforce} is set
715
* @property-read int $effectivegroupmode Effective group mode for this module (one of the constants NOGROUPS, SEPARATEGROUPS,
716
* or VISIBLEGROUPS). This can be different from groupmode set for the module if the groupmode is forced for the course.
717
* This value will always be NOGROUPS if module type does not support group mode.
718
* @property-read int $indent Indent level on course page (0 = no indent) - from course_modules table
719
* @property-read int $completion Activity completion setting for this activity, COMPLETION_TRACKING_xx constant - from
720
* course_modules table
721
* @property-read mixed $completiongradeitemnumber Set to the item number (usually 0) if completion depends on a particular
722
* grade of this activity, or null if completion does not depend on a grade - from course_modules table
723
* @property-read int $completionview 1 if 'on view' completion is enabled, 0 otherwise - from course_modules table
724
* @property-read int $completionexpected Set to a unix time if completion of this activity is expected at a
725
* particular time, 0 if no time set - from course_modules table
726
* @property-read int $availablefrom Available date for this activity (0 if not set, or set to seconds since epoch; before this
727
* date, activity does not display to students) - from course_modules table
728
* @property-read int $availableuntil Available until date for this activity (0 if not set, or set to seconds since epoch; from
729
* this date, activity does not display to students) - from course_modules table
730
* @property-read int $showavailability When activity is unavailable, this field controls whether it is shown to students (0 =
731
* hide completely, 1 = show greyed out with information about when it will be available) -
732
* from course_modules table
733
* @property-read int $showdescription Controls whether the description of the activity displays on the course main page (in
734
* addition to anywhere it might display within the activity itself). 0 = do not show
735
* on main page, 1 = show on main page.
736
* @property-read string $extra (deprecated) Extra HTML that is put in an unhelpful part of the HTML when displaying this module in
737
* course page - from cached data in modinfo field. Deprecated, replaced by ->extraclasses and ->onclick
738
* @property-read string $icon Name of icon to use - from cached data in modinfo field
739
* @property-read string $iconcomponent Component that contains icon - from cached data in modinfo field
740
* @property-read string $modname Name of module e.g. 'forum' (this is the same name as the module's main database
741
* table) - from cached data in modinfo field
742
* @property-read int $module ID of module type - from course_modules table
743
* @property-read string $name Name of module instance for display on page e.g. 'General discussion forum' - from cached
744
* data in modinfo field
745
* @property-read int $sectionnum Section number that this course-module is in (section 0 = above the calendar, section 1
746
* = week/topic 1, etc) - from cached data in modinfo field
747
* @property-read int $section Section id - from course_modules table
748
* @property-read array $conditionscompletion Availability conditions for this course-module based on the completion of other
749
* course-modules (array from other course-module id to required completion state for that
750
* module) - from cached data in modinfo field
751
* @property-read array $conditionsgrade Availability conditions for this course-module based on course grades (array from
752
* grade item id to object with ->min, ->max fields) - from cached data in modinfo field
753
* @property-read array $conditionsfield Availability conditions for this course-module based on user fields
754
* @property-read bool $available True if this course-module is available to students i.e. if all availability conditions
755
* are met - obtained dynamically
756
* @property-read string $availableinfo If course-module is not available to students, this string gives information about
757
* availability which can be displayed to students and/or staff (e.g. 'Available from 3
758
* January 2010') for display on main page - obtained dynamically
759
* @property-read bool $uservisible True if this course-module is available to the CURRENT user (for example, if current user
760
* has viewhiddenactivities capability, they can access the course-module even if it is not
761
* visible or not available, so this would be true in that case)
762
* @property-read context_module $context Module context
763
* @property-read string $modfullname Returns a localised human-readable name of the module type - calculated on request
764
* @property-read string $modplural Returns a localised human-readable name of the module type in plural form - calculated on request
765
* @property-read string $content Content to display on main (view) page - calculated on request
766
* @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
767
* @property-read string $extraclasses Extra CSS classes to add to html output for this activity on main page - calculated on request
768
* @property-read string $onclick Content of HTML on-click attribute already escaped - calculated on request
769
* @property-read mixed $customdata Optional custom data stored in modinfo cache for this activity, or null if none
770
* @property-read string $afterlink Extra HTML code to display after link - calculated on request
771
* @property-read string $afterediticons Extra HTML code to display after editing icons (e.g. more icons) - calculated on request
421
class cm_info extends stdClass {
773
class cm_info implements IteratorAggregate {
423
775
* State: Only basic data from modinfo cache is available.
425
777
const STATE_BASIC = 0;
780
* State: In the process of building dynamic data (to avoid recursive calls to obtain_dynamic_data())
782
const STATE_BUILDING_DYNAMIC = 1;
428
785
* State: Dynamic data is available too.
430
const STATE_DYNAMIC = 1;
787
const STATE_DYNAMIC = 2;
790
* State: In the process of building view data (to avoid recursive calls to obtain_view_data())
792
const STATE_BUILDING_VIEW = 3;
433
795
* State: View data (for course page) is available.
435
const STATE_VIEW = 2;
797
const STATE_VIEW = 4;
753
1079
private $afterediticons;
1082
* List of class read-only properties and their getter methods.
1083
* Used by magic functions __get(), __isset(), __empty()
1086
private static $standardproperties = array(
1088
'content' => 'get_content',
1089
'extraclasses' => 'get_extra_classes',
1090
'onclick' => 'get_on_click',
1091
'customdata' => 'get_custom_data',
1092
'afterlink' => 'get_after_link',
1093
'afterediticons' => 'get_after_edit_icons',
1094
'modfullname' => 'get_module_type_name',
1095
'modplural' => 'get_module_type_name_plural',
1098
'available' => 'get_available',
1099
'availablefrom' => false,
1100
'availableinfo' => 'get_available_info',
1101
'availableuntil' => false,
1102
'completion' => false,
1103
'completionexpected' => false,
1104
'completiongradeitemnumber' => false,
1105
'completionview' => false,
1106
'conditionscompletion' => false,
1107
'conditionsfield' => false,
1108
'conditionsgrade' => false,
1109
'context' => 'get_context',
1110
'course' => 'get_course_id',
1111
'coursegroupmode' => 'get_course_groupmode',
1112
'coursegroupmodeforce' => 'get_course_groupmodeforce',
1113
'effectivegroupmode' => 'get_effective_groupmode',
1115
'groupingid' => false,
1116
'groupmembersonly' => false,
1117
'groupmode' => false,
1119
'iconcomponent' => false,
1120
'idnumber' => false,
1122
'instance' => false,
1125
'name' => 'get_name',
1128
'sectionnum' => false,
1129
'showavailability' => 'get_show_availability',
1130
'showdescription' => false,
1131
'uservisible' => 'get_user_visible',
1133
'visibleold' => false,
1137
* List of methods with no arguments that were public prior to Moodle 2.6.
1139
* They can still be accessed publicly via magic __call() function with no warnings
1140
* but are not listed in the class methods list.
1141
* For the consistency of the code it is better to use corresponding properties.
1143
* These methods be deprecated completely in later versions.
1145
* @var array $standardmethods
1147
private static $standardmethods = array(
1148
// Following methods are not recommended to use because there have associated read-only properties.
1151
'get_extra_classes',
1155
'get_after_edit_icons',
1156
// Method obtain_dynamic_data() should not be called from outside of this class but it was public before Moodle 2.6.
1157
'obtain_dynamic_data',
1161
* Magic method to call functions that are now declared as private now but
1162
* were public in Moodle before 2.6. Developers can access them without
1163
* any warnings but they are not listed in the class methods list.
1165
* @param string $name
1166
* @param array $arguments
1169
public function __call($name, $arguments) {
1170
if (in_array($name, self::$standardmethods)) {
1171
// All standard methods do not have arguments anyway.
1172
return $this->$name();
1174
throw new coding_exception("Method cm_info::{$name}() does not exist");
756
1178
* Magic method getter
758
1180
* @param string $name
761
1183
public function __get($name) {
764
return $this->get_module_type_name(true);
766
return $this->get_module_type_name();
768
debugging('Invalid cm_info property accessed: '.$name);
1184
if (isset(self::$standardproperties[$name])) {
1185
if ($method = self::$standardproperties[$name]) {
1186
return $this->$method();
1188
return $this->$name;
1191
debugging('Invalid cm_info property accessed: '.$name);
1197
* Implementation of IteratorAggregate::getIterator(), allows to cycle through properties
1198
* and use {@link convert_to_array()}
1200
* @return ArrayIterator
1202
public function getIterator() {
1203
// Make sure dynamic properties are retrieved prior to view properties.
1204
$this->obtain_dynamic_data();
1206
foreach (self::$standardproperties as $key => $unused) {
1207
$ret[$key] = $this->__get($key);
1209
return new ArrayIterator($ret);
1213
* Magic method for function isset()
1215
* @param string $name
1218
public function __isset($name) {
1219
if (isset(self::$standardproperties[$name])) {
1220
$value = $this->__get($name);
1221
return isset($value);
1227
* Magic method for function empty()
1229
* @param string $name
1232
public function __empty($name) {
1233
if (isset(self::$standardproperties[$name])) {
1234
$value = $this->__get($name);
1235
return empty($value);
1241
* Magic method setter
1243
* Will display the developer warning when trying to set/overwrite property.
1245
* @param string $name
1246
* @param mixed $value
1248
public function __set($name, $value) {
1249
debugging("It is not allowed to set the property cm_info::\${$name}", DEBUG_DEVELOPER);
938
* @return object Moodle course object that was used to construct this data
1438
* Returns course object that was used in the first {@link get_fast_modinfo()} call.
1440
* It may not contain all fields from DB table {course} but always has at least the following:
1441
* id,shortname,fullname,format,enablecompletion,groupmode,groupmodeforce,cacherev
1443
* If the course object lacks the field you need you can use the global
1444
* function {@link get_course()} that will save extra query if you access
1445
* current course or frontpage course.
940
1449
public function get_course() {
941
1450
return $this->modinfo->get_course();
1454
* Returns course id for which the modinfo was generated.
1458
private function get_course_id() {
1459
return $this->modinfo->get_course_id();
1463
* Returns group mode used for the course containing the module
1465
* @return int one of constants NOGROUPS, SEPARATEGROUPS, VISIBLEGROUPS
1467
private function get_course_groupmode() {
1468
return $this->modinfo->get_course()->groupmode;
1472
* Returns whether group mode is forced for the course containing the module
1476
private function get_course_groupmodeforce() {
1477
return $this->modinfo->get_course()->groupmodeforce;
1481
* Returns effective groupmode of the module that may be overwritten by forced course groupmode.
1483
* @return int one of constants NOGROUPS, SEPARATEGROUPS, VISIBLEGROUPS
1485
private function get_effective_groupmode() {
1486
$groupmode = $this->groupmode;
1487
if ($this->modinfo->get_course()->groupmodeforce) {
1488
$groupmode = $this->modinfo->get_course()->groupmode;
1489
if ($groupmode != NOGROUPS && !plugin_supports('mod', $this->modname, FEATURE_GROUPS, 0)) {
1490
$groupmode = NOGROUPS;
1497
* @return context_module Current module context
1499
private function get_context() {
1500
return context_module::instance($this->id);
944
1503
// Set functions
945
1504
////////////////
1390
1993
upgrade_ensure_not_running();
1393
if (is_object($courseorid)) {
1394
$course = $courseorid;
1396
$course = (object)array('id' => $courseorid);
1399
1996
// Function is called with $reset = true
1400
1997
if ($resetonly) {
1401
if (isset($course->id) && $course->id > 0) {
1402
$cache[$course->id] = false;
1404
foreach (array_keys($cache) as $key) {
1405
$cache[$key] = false;
1998
course_modinfo::clear_instance_cache($courseorid);
1411
2002
// Function is called with $reset = false, retrieve modinfo
1412
if (empty($userid)) {
1413
$userid = $USER->id;
1416
if (array_key_exists($course->id, $cache)) {
1417
if ($cache[$course->id] === false) {
1418
// this course has been recently reset, do not rely on modinfo and sectioncache in $course
1419
$course->modinfo = null;
1420
$course->sectioncache = null;
1421
} else if ($cache[$course->id]->userid == $userid) {
1422
// this course's modinfo for the same user was recently retrieved, return cached
1423
return $cache[$course->id];
1427
unset($cache[$course->id]); // prevent potential reference problems when switching users
1429
$cache[$course->id] = new course_modinfo($course, $userid);
1431
// Ensure cache does not use too much RAM
1432
if (count($cache) > MAX_MODINFO_CACHE_SIZE) {
1435
// Unsetting static variable in PHP is percular, it removes the reference,
1436
// but data remain in memory. Prior to unsetting, the varable needs to be
1437
// set to empty to remove its remains from memory.
1439
unset($cache[$key]);
1442
return $cache[$course->id];
2003
return course_modinfo::instance($courseorid, $userid);
1446
* Rebuilds the cached list of course activities stored in the database
1447
* @param int $courseid - id of course to rebuild, empty means all
1448
* @param boolean $clearonly - only clear the modinfo fields, gets rebuild automatically on the fly
2007
* Rebuilds or resets the cached list of course activities stored in MUC.
2009
* rebuild_course_cache() must NEVER be called from lib/db/upgrade.php.
2010
* At the same time course cache may ONLY be cleared using this function in
2011
* upgrade scripts of plugins.
2013
* During the bulk operations if it is necessary to reset cache of multiple
2014
* courses it is enough to call {@link increment_revision_number()} for the
2015
* table 'course' and field 'cacherev' specifying affected courses in select.
2017
* Cached course information is stored in MUC core/coursemodinfo and is
2018
* validated with the DB field {course}.cacherev
2020
* @global moodle_database $DB
2021
* @param int $courseid id of course to rebuild, empty means all
2022
* @param boolean $clearonly only clear the cache, gets rebuild automatically on the fly.
2023
* Recommended to set to true to avoid unnecessary multiple rebuilding.
1450
2025
function rebuild_course_cache($courseid=0, $clearonly=false) {
1451
2026
global $COURSE, $SITE, $DB, $CFG;
1463
2038
format_base::reset_course_cache($courseid);
2041
$cachecoursemodinfo = cache::make('core', 'coursemodinfo');
2042
if (empty($courseid)) {
2043
// Clearing caches for all courses.
2044
increment_revision_number('course', 'cacherev', '');
2045
$cachecoursemodinfo->purge();
2046
course_modinfo::clear_instance_cache();
2047
// Update global values too.
2048
$sitecacherev = $DB->get_field('course', 'cacherev', array('id' => SITEID));
2049
$SITE->cachrev = $sitecacherev;
2050
if ($COURSE->id == SITEID) {
2051
$COURSE->cacherev = $sitecacherev;
2053
$COURSE->cacherev = $DB->get_field('course', 'cacherev', array('id' => $COURSE->id));
2056
// Clearing cache for one course, make sure it is deleted from user request cache as well.
2057
increment_revision_number('course', 'cacherev', 'id = :id', array('id' => $courseid));
2058
$cachecoursemodinfo->delete($courseid);
2059
course_modinfo::clear_instance_cache($courseid);
2060
// Update global values too.
2061
if ($courseid == $COURSE->id || $courseid == $SITE->id) {
2062
$cacherev = $DB->get_field('course', 'cacherev', array('id' => $courseid));
2063
if ($courseid == $COURSE->id) {
2064
$COURSE->cacherev = $cacherev;
2066
if ($courseid == $SITE->id) {
2067
$SITE->cachrev = $cacherev;
1466
2072
if ($clearonly) {
1467
if (empty($courseid)) {
1468
$DB->execute('UPDATE {course} set modinfo = ?, sectioncache = ?', array(null, null));
1470
// Clear both fields in one update
1471
$resetobj = (object)array('id' => $courseid, 'modinfo' => null, 'sectioncache' => null);
1472
$DB->update_record('course', $resetobj);
1474
// update cached global COURSE too ;-)
1475
if ($courseid == $COURSE->id or empty($courseid)) {
1476
$COURSE->modinfo = null;
1477
$COURSE->sectioncache = null;
1479
if ($courseid == $SITE->id) {
1480
$SITE->modinfo = null;
1481
$SITE->sectioncache = null;
1483
// reset the fast modinfo cache
1484
get_fast_modinfo($courseid, 0, true);
1488
require_once("$CFG->dirroot/course/lib.php");
1490
2076
if ($courseid) {
1491
2077
$select = array('id'=>$courseid);
1591
2163
* Data about a single section on a course. This contains the fields from the
1592
2164
* course_sections table, plus additional data when required.
2166
* @property-read int $id Section ID - from course_sections table
2167
* @property-read int $course Course ID - from course_sections table
2168
* @property-read int $section Section number - from course_sections table
2169
* @property-read string $name Section name if specified - from course_sections table
2170
* @property-read int $visible Section visibility (1 = visible) - from course_sections table
2171
* @property-read string $summary Section summary text if specified - from course_sections table
2172
* @property-read int $summaryformat Section summary text format (FORMAT_xx constant) - from course_sections table
2173
* @property-read int $showavailability When section is unavailable, this field controls whether it is shown to students (0 =
2174
* hide completely, 1 = show greyed out with information about when it will be available) -
2175
* from course_sections table
2176
* @property-read int $availablefrom Available date for this section (0 if not set, or set to seconds since epoch;
2177
* before this date, section does not display to students) - from course_sections table
2178
* @property-read int $availableuntil Available until date for this section (0 if not set, or set to seconds since epoch;
2179
* from this date, section does not display to students) - from course_sections table
2180
* @property-read int $groupingid If section is restricted to users of a particular grouping, this is its id (0 if not set) -
2181
* from course_sections table
2182
* @property-read array $conditionscompletion Availability conditions for this section based on the completion of
2183
* course-modules (array from course-module id to required completion state
2184
* for that module) - from cached data in sectioncache field
2185
* @property-read array $conditionsgrade Availability conditions for this section based on course grades (array from
2186
* grade item id to object with ->min, ->max fields) - from cached data in
2187
* sectioncache field
2188
* @property-read array $conditionsfield Availability conditions for this section based on user fields
2189
* @property-read bool $available True if this section is available to the given user i.e. if all availability conditions
2190
* are met - obtained dynamically
2191
* @property-read string $availableinfo If section is not available to some users, this string gives information about
2192
* availability which can be displayed to students and/or staff (e.g. 'Available from 3 January 2010')
2193
* for display on main page - obtained dynamically
2194
* @property-read bool $uservisible True if this section is available to the given user (for example, if current user
2195
* has viewhiddensections capability, they can access the section even if it is not
2196
* visible or not available, so this would be true in that case) - obtained dynamically
2197
* @property-read string $sequence Comma-separated list of all modules in the section. Note, this field may not exactly
2198
* match course_sections.sequence if later has references to non-existing modules or not modules of not available module types.
1594
2200
class section_info implements IteratorAggregate {
2463
* Finds whether this section is available at the moment for the current user.
2465
* The value can be accessed publicly as $sectioninfo->available
2469
private function get_available() {
2471
$userid = $this->modinfo->get_user_id();
2472
if ($this->_available !== null || $userid == -1) {
2473
// Has already been calculated or does not need calculation.
2474
return $this->_available;
2476
if (!empty($CFG->enableavailability)) {
2477
require_once($CFG->libdir. '/conditionlib.php');
2478
// Get availability information
2479
$ci = new condition_info_section($this);
2480
$this->_available = $ci->is_available($this->_availableinfo, true,
2481
$userid, $this->modinfo);
2482
if ($this->_availableinfo === '' && $this->_groupingid) {
2483
// Still may have some extra text in availableinfo because of groupping.
2484
// Set as undefined so the next call to get_availabeinfo() calculates it.
2485
$this->_availableinfo = null;
2488
$this->_available = true;
2489
$this->_availableinfo = '';
2491
return $this->_available;
2495
* Returns the availability text shown next to the section on course page.
2499
private function get_availableinfo() {
2500
// Make sure $this->_available has been calculated, it may also fill the _availableinfo property.
2501
$this->get_available();
2502
$userid = $this->modinfo->get_user_id();
2503
if ($this->_availableinfo !== null || $userid == -1) {
2504
// It has been already calculated or does not need calculation.
2505
return $this->_availableinfo;
2507
$this->_availableinfo = '';
2508
// Display grouping info if available & not already displaying
2509
// (it would already display if current user doesn't have access)
2510
// for people with managegroups - same logic/class as grouping label
2511
// on individual activities.
2512
$context = context_course::instance($this->get_course());
2513
if ($this->_groupingid && has_capability('moodle/course:managegroups', $context, $userid)) {
2514
$groupings = groups_get_all_groupings($this->get_course());
2515
$this->_availableinfo = html_writer::tag('span', '(' . format_string(
2516
$groupings[$this->_groupingid]->name, true, array('context' => $context)) .
2517
')', array('class' => 'groupinglabel'));
2519
return $this->_availableinfo;
1870
2523
* Implementation of IteratorAggregate::getIterator(), allows to cycle through properties
1871
2524
* and use {@link convert_to_array()}
1876
2529
$ret = array();
1877
2530
foreach (get_object_vars($this) as $key => $value) {
1878
2531
if (substr($key, 0, 1) == '_') {
1879
$ret[substr($key, 1)] = $this->$key;
2532
if (method_exists($this, 'get'.$key)) {
2533
$ret[substr($key, 1)] = $this->{'get'.$key}();
2535
$ret[substr($key, 1)] = $this->$key;
1882
$ret = array_merge($ret, course_get_format($this->_course)->get_format_options($this));
2539
$ret['sequence'] = $this->get_sequence();
2540
$ret['course'] = $this->get_course();
2541
$ret = array_merge($ret, course_get_format($this->modinfo->get_course())->get_format_options($this->_section));
1883
2542
return new ArrayIterator($ret);
1887
2546
* Works out whether activity is visible *for current user* - if this is false, they
1888
2547
* aren't allowed to access it.
1889
* @param int $userid User ID
1892
private function update_user_visible($userid) {
1894
$coursecontext = context_course::instance($this->_course);
2551
private function get_uservisible() {
2552
$userid = $this->modinfo->get_user_id();
2553
if ($this->_uservisible !== null || $userid == -1) {
2554
// Has already been calculated or does not need calculation.
2555
return $this->_uservisible;
1895
2557
$this->_uservisible = true;
1896
if ((!$this->_visible || !$this->_available) &&
1897
!has_capability('moodle/course:viewhiddensections', $coursecontext, $userid)) {
1898
$this->_uservisible = false;
2558
if (!$this->_visible || !$this->get_available()) {
2559
$coursecontext = context_course::instance($this->get_course());
2560
if (!has_capability('moodle/course:viewhiddensections', $coursecontext, $userid)) {
2561
$this->_uservisible = false;
2564
return $this->_uservisible;
2568
* Restores the course_sections.sequence value
2572
private function get_sequence() {
2573
if (!empty($this->modinfo->sections[$this->_section])) {
2574
return implode(',', $this->modinfo->sections[$this->_section]);
2581
* Returns course ID - from course_sections table
2585
private function get_course() {
2586
return $this->modinfo->get_course_id();