Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

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