<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.

namespace core_course;

use ArrayIterator;
use core\exception\coding_exception;
use IteratorAggregate;
use stdClass;
use Traversable;
use core\url;
use core\output\core_renderer;
use core\output\renderer_base;
use core\lang_string;
use section_info;
use core\context\module as context_module;
use core\context\course as context_course;
use core_courseformat\output\activitybadge;
use core\component;
use core\output\html_writer;

/**
 * Data about a single module on a course.
 *
 * This contains most of the fields in the course_modules table, plus additional data when required.
 *
 * The object can be accessed by core or any plugin (i.e. course format, block, filter, etc.) as
 * get_fast_modinfo($courseorid)->cms[$coursemoduleid]
 * or
 * get_fast_modinfo($courseorid)->instances[$moduletype][$instanceid]
 *
 * There are three stages when activity module can add/modify data in this object:
 *
 * <b>Stage 1 - during building the cache.</b>
 * Allows to add to the course cache static user-independent information about the module.
 * Modules should try to include only absolutely necessary information that may be required
 * when displaying course view page. The information is stored in application-level cache
 * and reset when {@see rebuild_course_cache()} is called or cache is purged by admin.
 *
 * Modules can implement callback XXX_get_coursemodule_info() returning instance of object
 * {@see cached_cm_info}
 *
 * <b>Stage 2 - dynamic data.</b>
 * Dynamic data is user-dependent, it is stored in request-level cache. To reset this cache
 * {@see get_fast_modinfo()} with $reset argument may be called.
 *
 * Dynamic data is obtained when any of the following properties/methods is requested:
 * - {@see cm_info::$url}
 * - {@see cm_info::$name}
 * - {@see cm_info::$onclick}
 * - {@see cm_info::get_icon_url()}
 * - {@see cm_info::$uservisible}
 * - {@see cm_info::$available}
 * - {@see cm_info::$availableinfo}
 * - plus any of the properties listed in Stage 3.
 *
 * Modules can implement callback <b>XXX_cm_info_dynamic()</b> and inside this callback they
 * are allowed to use any of the following set methods:
 * - {@see cm_info::set_available()}
 * - {@see cm_info::set_name()}
 * - {@see cm_info::set_no_view_link()}
 * - {@see cm_info::set_user_visible()}
 * - {@see cm_info::set_on_click()}
 * - {@see cm_info::set_icon_url()}
 * - {@see cm_info::override_customdata()}
 * Any methods affecting view elements can also be set in this callback.
 *
 * <b>Stage 3 (view data).</b>
 * Also user-dependend data stored in request-level cache. Second stage is created
 * because populating the view data can be expensive as it may access much more
 * Moodle APIs such as filters, user information, output renderers and we
 * don't want to request it until necessary.
 * View data is obtained when any of the following properties/methods is requested:
 * - {@see cm_info::$afterediticons}
 * - {@see cm_info::$content}
 * - {@see cm_info::get_formatted_content()}
 * - {@see cm_info::$extraclasses}
 * - {@see cm_info::$afterlink}
 *
 * Modules can implement callback <b>XXX_cm_info_view()</b> and inside this callback they
 * are allowed to use any of the following set methods:
 * - {@see cm_info::set_after_edit_icons()}
 * - {@see cm_info::set_after_link()}
 * - {@see cm_info::set_content()}
 * - {@see cm_info::set_extra_classes()}
 *
 * @package core_course
 * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 * @copyright Sam Marshall
 * @property-read int $id Course-module ID - from course_modules table
 * @property-read int $instance Module instance (ID within module table) - from course_modules table
 * @property-read int $course Course ID - from course_modules table
 * @property-read string $idnumber 'ID number' from course-modules table (arbitrary text set by user) - from
 *    course_modules table
 * @property-read int $added Time that this course-module was added (unix time) - from course_modules table
 * @property-read int $visible Visible setting (0 or 1; if this is 0, students cannot see/access the activity) - from
 *    course_modules table
 * @property-read int $visibleoncoursepage Visible on course page setting - from course_modules table, adjusted to
 *    whether course format allows this module to have the "stealth" mode
 * @property-read int $visibleold Old visible setting (if the entire section is hidden, the previous value for
 *    visible is stored in this field) - from course_modules table
 * @property-read int $groupmode Group mode (one of the constants NOGROUPS, SEPARATEGROUPS, or VISIBLEGROUPS) - from
 *    course_modules table. Use {@see cm_info::$effectivegroupmode} to find the actual group mode that may be forced by course.
 * @property-read int $groupingid Grouping ID (0 = all groupings)
 * @property-read bool $coursegroupmodeforce Indicates whether the course containing the module has forced the groupmode
 *    This means that cm_info::$groupmode should be ignored and cm_info::$coursegroupmode be used instead
 * @property-read int $coursegroupmode Group mode (one of the constants NOGROUPS, SEPARATEGROUPS, or VISIBLEGROUPS) - from
 *    course table - as specified for the course containing the module
 *    Effective only if {@see cm_info::$coursegroupmodeforce} is set
 * @property-read int $effectivegroupmode Effective group mode for this module (one of the constants NOGROUPS, SEPARATEGROUPS,
 *    or VISIBLEGROUPS). This can be different from groupmode set for the module if the groupmode is forced for the course.
 *    This value will always be NOGROUPS if module type does not support group mode.
 * @property-read int $indent Indent level on course page (0 = no indent) - from course_modules table
 * @property-read int $completion Activity completion setting for this activity, COMPLETION_TRACKING_xx constant - from
 *    course_modules table
 * @property-read mixed $completiongradeitemnumber Set to the item number (usually 0) if completion depends on a particular
 *    grade of this activity, or null if completion does not depend on a grade - from course_modules table
 * @property-read int $completionview 1 if 'on view' completion is enabled, 0 otherwise - from course_modules table
 * @property-read int $completionexpected Set to a unix time if completion of this activity is expected at a
 *    particular time, 0 if no time set - from course_modules table
 * @property-read string $availability Availability information as JSON string or null if none -
 *    from course_modules table
 * @property-read int $showdescription Controls whether the description of the activity displays on the course main page (in
 *    addition to anywhere it might display within the activity itself). 0 = do not show
 *    on main page, 1 = show on main page.
 * @property-read string $extra (deprecated) Extra HTML that is put in an unhelpful part of the HTML when displaying this module in
 *    course page - from cached data in modinfo field. Deprecated, replaced by ->extraclasses and ->onclick
 * @property-read string $icon Name of icon to use - from cached data in modinfo field
 * @property-read string $iconcomponent Component that contains icon - from cached data in modinfo field
 * @property-read string $modname Name of module e.g. 'forum' (this is the same name as the module's main database
 *    table) - from cached data in modinfo field
 * @property-read int $module ID of module type - from course_modules table
 * @property-read string $name Name of module instance for display on page e.g. 'General discussion forum' - from cached
 *    data in modinfo field
 * @property-read int $sectionnum Section number that this course-module is in (section 0 = above the calendar, section 1
 *    = week/topic 1, etc) - from cached data in modinfo field
 * @property-read int $sectionid Section id - from course_modules table
 * @property-read array $conditionscompletion Availability conditions for this course-module based on the completion of other
 *    course-modules (array from other course-module id to required completion state for that
 *    module) - from cached data in modinfo field
 * @property-read array $conditionsgrade Availability conditions for this course-module based on course grades (array from
 *    grade item id to object with ->min, ->max fields) - from cached data in modinfo field
 * @property-read array $conditionsfield Availability conditions for this course-module based on user fields
 * @property-read bool $available True if this course-module is available to students i.e. if all availability conditions
 *    are met - obtained dynamically
 * @property-read string $availableinfo If course-module is not available to students, this string gives information about
 *    availability which can be displayed to students and/or staff (e.g. 'Available from 3
 *    January 2010') for display on main page - obtained dynamically
 * @property-read bool $uservisible True if this course-module is available to the CURRENT user (for example, if current user
 *    has viewhiddenactivities capability, they can access the course-module even if it is not
 *    visible or not available, so this would be true in that case)
 * @property-read context_module $context Module context
 * @property-read string $modfullname Returns a localised human-readable name of the module type - calculated on request
 * @property-read string $modplural Returns a localised human-readable name of the module type in plural form
 *      Calculated on request
 * @property-read string $content Content to display on main (view) page - calculated on request
 * @property-read url|null $url URL to link to for this module, or null if it doesn't have a view page - calculated on request
 * @property-read string $extraclasses Extra CSS classes to add to html output for this activity on main page
 *      C'alculated on request
 * @property-read string $onclick Content of HTML on-click attribute already escaped - calculated on request
 * @property-read mixed $customdata Optional custom data stored in modinfo cache for this activity, or null if none
 * @property-read string $afterlink Extra HTML code to display after link - calculated on request
 * @property-read string $afterediticons Extra HTML code to display after editing icons (e.g. more icons) - calculated on request
 * @property-read bool $deletioninprogress True if this course module is scheduled for deletion, false otherwise.
 * @property-read bool $downloadcontent True if content download is enabled for this course module, false otherwise.
 * @property-read bool $lang the forced language for this activity (language pack name). Null means not forced.
 * @property-read int|null $enableaitools AI tools for course_modules table
 * @property-read string|null $enabledaiactions AI actions for course_modules table
 */
class cm_info implements IteratorAggregate {
    /**
     * State: Only basic data from modinfo cache is available.
     */
    private const STATE_BASIC = 0;

    /**
     * State: In the process of building dynamic data (to avoid recursive calls to obtain_dynamic_data())
     */
    private const STATE_BUILDING_DYNAMIC = 1;

    /**
     * State: Dynamic data is available too.
     */
    private const STATE_DYNAMIC = 2;

    /**
     * State: In the process of building view data (to avoid recursive calls to obtain_view_data())
     */
    private const STATE_BUILDING_VIEW = 3;

    /**
     * State: View data (for course page) is available.
     */
    private const STATE_VIEW = 4;

    /** @var modinfo Parent object */
    private $modinfo;

    /**
     * Level of information stored inside this object (STATE_xx constant)
     * @var int
     */
    private $state;

    /**
     * Course-module ID - from course_modules table
     * @var int
     */
    private $id;

    /**
     * Module instance (ID within module table) - from course_modules table
     * @var int
     */
    private $instance;

    /**
     * 'ID number' from course-modules table (arbitrary text set by user) - from
     * course_modules table
     * @var string
     */
    private $idnumber;

    /**
     * Time that this course-module was added (unix time) - from course_modules table
     * @var int
     */
    private $added;

    /**
     * @var int
     *
     * This variable is not used and is included here only so it can be documented.
     * Once the database entry is removed from course_modules, it should be deleted
     * here too.
     * @deprecated Do not use this variable
     */
    #[\core\attribute\deprecated(
        since: '2.0',
        mdl: 'MDL-26781',
    )]
    private $score;

    /**
     * Visible setting (0 or 1; if this is 0, students cannot see/access the activity) - from
     * course_modules table
     * @var int
     */
    private $visible;

    /**
     * Visible on course page setting - from course_modules table
     * @var int
     */
    private $visibleoncoursepage;

    /**
     * Old visible setting (if the entire section is hidden, the previous value for
     * visible is stored in this field) - from course_modules table
     * @var int
     */
    private $visibleold;

    /**
     * Group mode (one of the constants NONE, SEPARATEGROUPS, or VISIBLEGROUPS) - from
     * course_modules table
     * @var int
     */
    private $groupmode;

    /**
     * Grouping ID (0 = all groupings)
     * @var int
     */
    private $groupingid;

    /**
     * Indent level on course page (0 = no indent) - from course_modules table
     * @var int
     */
    private $indent;

    /**
     * Activity completion setting for this activity, COMPLETION_TRACKING_xx constant - from
     * course_modules table
     * @var int
     */
    private $completion;

    /**
     * Set to the item number (usually 0) if completion depends on a particular
     * grade of this activity, or null if completion does not depend on a grade - from
     * course_modules table
     * @var mixed
     */
    private $completiongradeitemnumber;

    /**
     * 1 if pass grade completion is enabled, 0 otherwise - from course_modules table
     * @var int
     */
    private $completionpassgrade;

    /**
     * 1 if 'on view' completion is enabled, 0 otherwise - from course_modules table
     * @var int
     */
    private $completionview;

    /** @var int Set to a unix time if completion of this activity is expected at a particular time, 0 if no time set - from course_modules table */
    private $completionexpected;

    /** @var string Availability information as JSON string or null if none - from course_modules table */
    private $availability;

    /**
     * @var int Whether the description of this activity is displayed on the course main page.
     *
     * Note: This would be in addition to anywhere it might display within the activity itself.
     *
     * 0 = do not show on main page, 1 = show on main page.
     */
    private $showdescription;

    /**
     * @var string Extra HTML
     *
     * Extra HTML that is put in an unhelpful part of the HTML when displaying this module in
     * course page - from cached data in modinfo field
     * @deprecated This is crazy, don't use it. Replaced by ->extraclasses and ->onclick
     */
    #[\core\attribute\deprecated(
        replacement: '->extraclasses and ->onclick',
        since: '2.0',
        mdl: 'MDL-25981',
        reason: 'This is crazy, don\'t use it.'
    )]
    private $extra;

    /**
     * Name of icon to use - from cached data in modinfo field
     * @var string
     */
    private $icon;

    /**
     * Component that contains icon - from cached data in modinfo field
     * @var string
     */
    private $iconcomponent;

    /**
     * The instance record form the module table
     * @var stdClass
     */
    private $instancerecord;

    /**
     * Name of module e.g. 'forum' (this is the same name as the module's main database
     * table) - from cached data in modinfo field
     * @var string
     */
    private $modname;

    /**
     * ID of module - from course_modules table
     * @var int
     */
    private $module;

    /**
     * Name of module instance for display on page e.g. 'General discussion forum' - from cached
     * data in modinfo field
     * @var string
     */
    private $name;

    /**
     * Section number that this course-module is in (section 0 = above the calendar, section 1
     * = week/topic 1, etc) - from cached data in modinfo field
     * @var int
     */
    private $sectionnum;

    /**
     * Section id - from course_modules table
     * @var int
     */
    private $sectionid;

    /**
     * Availability conditions for this course-module based on the completion of other
     * course-modules (array from other course-module id to required completion state for that
     * module) - from cached data in modinfo field
     * @var array
     */
    private $conditionscompletion;

    /**
     * Availability conditions for this course-module based on course grades (array from
     * grade item id to object with ->min, ->max fields) - from cached data in modinfo field
     * @var array
     */
    private $conditionsgrade;

    /**
     * Availability conditions for this course-module based on user fields
     * @var array
     */
    private $conditionsfield;

    /**
     * True if this course-module is available to students i.e. if all availability conditions
     * are met - obtained dynamically
     * @var bool
     */
    private $available;

    /**
     * If course-module is not available to students, this string gives information about
     * availability which can be displayed to students and/or staff (e.g. 'Available from 3
     * January 2010') for display on main page - obtained dynamically
     * @var string
     */
    private $availableinfo;

    /**
     * True if this course-module is available to the CURRENT user (for example, if current user
     * has viewhiddenactivities capability, they can access the course-module even if it is not
     * visible or not available, so this would be true in that case)
     * @var bool
     */
    private $uservisible;

    /**
     * True if this course-module is visible to the CURRENT user on the course page
     * @var bool
     */
    private $uservisibleoncoursepage;

    /**
     * @var url|null The activity URL, if any.
     */
    private $url;

    /**
     * @var string
     */
    private $content;

    /**
     * @var bool
     */
    private $contentisformatted;

    /**
     * @var bool True if the content has a special course item display like labels.
     */
    private $customcmlistitem;

    /**
     * @var string
     */
    private $extraclasses;

    /**
     * @var url full external url pointing to icon image for activity
     */
    private $iconurl;

    /**
     * @var string
     */
    private $onclick;

    /**
     * @var mixed
     */
    private $customdata;

    /**
     * @var string
     */
    private $afterlink;

    /**
     * @var string
     */
    private $afterediticons;

    /**
     * @var bool representing the deletion state of the module. True if the mod is scheduled for deletion.
     */
    private $deletioninprogress;

    /**
     * @var int enable/disable download content for this course module
     */
    private $downloadcontent;

    /**
     * @var string|null the forced language for this activity (language pack name). Null means not forced.
     */
    private $lang;

    /**
     * @var int|null enable/disable AI tools for this course module
     */
    private $enableaitools;

    /**
     * @var string|null enabled AI actions for this course module
     */
    private $enabledaiactions;

    /**
     * List of class read-only properties and their getter methods.
     * Used by magic functions __get(), __isset().
     *
     * @var array
     */
    private static $standardproperties = [
        'url' => 'get_url',
        'content' => 'get_content',
        'extraclasses' => 'get_extra_classes',
        'onclick' => 'get_on_click',
        'customdata' => 'get_custom_data',
        'afterlink' => 'get_after_link',
        'afterediticons' => 'get_after_edit_icons',
        'modfullname' => 'get_module_type_name',
        'modplural' => 'get_module_type_name_plural',
        'id' => false,
        'added' => false,
        'availability' => false,
        'available' => 'get_available',
        'availableinfo' => 'get_available_info',
        'completion' => false,
        'completionexpected' => false,
        'completiongradeitemnumber' => false,
        'completionpassgrade' => false,
        'completionview' => false,
        'conditionscompletion' => false,
        'conditionsfield' => false,
        'conditionsgrade' => false,
        'context' => 'get_context',
        'course' => 'get_course_id',
        'coursegroupmode' => 'get_course_groupmode',
        'coursegroupmodeforce' => 'get_course_groupmodeforce',
        'customcmlistitem' => 'has_custom_cmlist_item',
        'effectivegroupmode' => 'get_effective_groupmode',
        'extra' => false,
        'groupingid' => false,
        'groupmembersonly' => 'get_deprecated_group_members_only',
        'groupmode' => false,
        'icon' => false,
        'iconcomponent' => false,
        'idnumber' => false,
        'indent' => false,
        'instance' => false,
        'modname' => false,
        'module' => false,
        'name' => 'get_name',
        'score' => false,
        'section' => 'get_section_id',
        'sectionid' => false,
        'sectionnum' => false,
        'showdescription' => false,
        'uservisible' => 'get_user_visible',
        'visible' => false,
        'visibleoncoursepage' => false,
        'visibleold' => false,
        'deletioninprogress' => false,
        'downloadcontent' => false,
        'lang' => false,
        'enableaitools' => false,
        'enabledaiactions' => false,
    ];

    /**
     * List of methods with no arguments that were public prior to Moodle 2.6.
     *
     * They can still be accessed publicly via magic __call() function with no warnings
     * but are not listed in the class methods list.
     * For the consistency of the code it is better to use corresponding properties.
     *
     * These methods be deprecated completely in later versions.
     *
     * @var array $standardmethods
     */
    private static $standardmethods = [
        // Following methods are not recommended to use because there have associated read-only properties.
        'get_url',
        'get_content',
        'get_extra_classes',
        'get_on_click',
        'get_custom_data',
        'get_after_link',
        'get_after_edit_icons',
        // Method obtain_dynamic_data() should not be called from outside of this class but it was public before Moodle 2.6.
        'obtain_dynamic_data',
    ];

    /**
     * Magic method to call functions that are now declared as private but were public in Moodle before 2.6.
     * These private methods can not be used anymore.
     *
     * @param string $name
     * @param array $arguments
     * @return mixed
     * @throws coding_exception
     */
    public function __call($name, $arguments) {
        if (in_array($name, self::$standardmethods)) {
            $message = "cm_info::$name() can not be used anymore.";
            if ($alternative = array_search($name, self::$standardproperties)) {
                $message .= " Please use the property cm_info->$alternative instead.";
            }
            throw new coding_exception($message);
        }
        throw new coding_exception("Method cm_info::{$name}() does not exist");
    }

    /**
     * Magic method getter
     *
     * @param string $name
     * @return mixed
     */
    public function __get($name) {
        \core\deprecation::emit_deprecation_if_present([__CLASS__, $name]);
        if (isset(self::$standardproperties[$name])) {
            if ($method = self::$standardproperties[$name]) {
                return $this->$method();
            } else {
                return $this->$name;
            }
        } else {
            debugging('Invalid cm_info property accessed: ' . $name);
            return null;
        }
    }

    #[\Override]
    public function getIterator(): Traversable {
        // Make sure dynamic properties are retrieved prior to view properties.
        $this->obtain_dynamic_data();
        $ret = [];

        // Do not iterate over deprecated properties.
        $props = self::$standardproperties;
        unset($props['groupmembersonly']);

        foreach ($props as $key => $unused) {
            if (\core\deprecation::is_deprecated([__CLASS__, $key])) {
                continue;
            }

            $ret[$key] = $this->__get($key);
        }
        return new ArrayIterator($ret);
    }

    /**
     * Magic method for function isset()
     *
     * @param string $name
     * @return bool
     */
    public function __isset($name) {
        \core\deprecation::emit_deprecation_if_present([__CLASS__, $name]);
        if (isset(self::$standardproperties[$name])) {
            $value = $this->__get($name);
            return isset($value);
        }
        return false;
    }

    /**
     * Magic method setter
     *
     * Will display the developer warning when trying to set/overwrite property.
     *
     * @param string $name
     * @param mixed $value
     */
    public function __set($name, $value) {
        debugging("It is not allowed to set the property cm_info::\${$name}", DEBUG_DEVELOPER);
    }

    /**
     * Whether this activity has a view page.
     *
     * Note: modules may still have a view.php file, but return false if this is not intended to be linked to
     * from standard parts of the interface.
     * For an example of these, see `mod_label`.
     *
     * @return bool True if this module has a 'view' page that should be linked to in navigation etc.
     */
    public function has_view() {
        return !is_null($this->url);
    }

    /**
     * Gets the URL to link to for this module.
     *
     * This method is normally called by the property ->url, but can be called directly if
     * there is a case when it might be called recursively (you can't call property values
     * recursively).
     *
     * @return url URL to link to for this module, or null if it doesn't have a view page
     */
    public function get_url() {
        $this->obtain_dynamic_data();
        return $this->url;
    }

    /**
     * Obtains content to display on main (view) page.
     * Note: Will collect view data, if not already obtained.
     * @return string Content to display on main page below link, or empty string if none
     */
    private function get_content() {
        $this->obtain_view_data();
        return $this->content;
    }

    /**
     * Returns the content to display on course/overview page, formatted and passed through filters
     *
     * if $options['context'] is not specified, the module context is used
     *
     * @param array|stdClass $options formatting options, see {@see format_text()}
     * @return string
     */
    public function get_formatted_content($options = []) {
        $this->obtain_view_data();
        if (empty($this->content)) {
            return '';
        }
        if ($this->contentisformatted) {
            return $this->content;
        }

        // Improve filter performance by preloading filter setttings for all activities on the course.
        // This does nothing if called multiple times.
        filter_preload_activities($this->get_modinfo());

        $options = (array)$options;
        if (!isset($options['context'])) {
            $options['context'] = $this->get_context();
        }
        return format_text($this->content, FORMAT_HTML, $options);
    }

    /**
     * Return the module custom cmlist item flag.
     *
     * Activities like label uses this flag to indicate that it should be
     * displayed as a custom course item instead of a tipical activity card.
     *
     * @return bool
     */
    public function has_custom_cmlist_item(): bool {
        $this->obtain_view_data();
        return $this->customcmlistitem ?? false;
    }

    /**
     * Getter method for property $name, ensures that dynamic data is obtained.
     *
     * This method is normally called by the property ->name, but can be called directly if there
     * is a case when it might be called recursively (you can't call property values recursively).
     *
     * @return string
     */
    public function get_name() {
        $this->obtain_dynamic_data();
        return $this->name;
    }

    /**
     * Returns the name to display on course/overview page, formatted and passed through filters
     *
     * if $options['context'] is not specified, the module context is used
     *
     * @param array|stdClass $options formatting options, see {@see format_string()}
     * @return string
     */
    public function get_formatted_name($options = []) {
        global $CFG;
        $options = (array)$options;
        if (!isset($options['context'])) {
            $options['context'] = $this->get_context();
        }
        // Improve filter performance by preloading filter setttings for all
        // activities on the course (this does nothing if called multiple
        // times).
        if (!empty($CFG->filterall)) {
            filter_preload_activities($this->get_modinfo());
        }
        return format_string($this->get_name(), true, $options);
    }

    /**
     * Note: Will collect view data, if not already obtained.
     * @return string Extra CSS classes to add to html output for this activity on main page
     */
    private function get_extra_classes() {
        $this->obtain_view_data();
        return $this->extraclasses;
    }

    /**
     * Get the on-click attribute.
     *
     * Note: This string will be used literally as a string so should be pre-escaped.
     *
     * @return string Content of HTML on-click attribute.
     */
    private function get_on_click() {
        // Does not need view data; may be used by navigation.
        $this->obtain_dynamic_data();
        return $this->onclick;
    }
    /**
     * Getter method for property $customdata, ensures that dynamic data is retrieved.
     *
     * This method is normally called by the property ->customdata, but can be called directly if there
     * is a case when it might be called recursively (you can't call property values recursively).
     *
     * @return mixed Optional custom data stored in modinfo cache for this activity, or null if none
     */
    public function get_custom_data() {
        $this->obtain_dynamic_data();
        return $this->customdata;
    }

    /**
     * Note: Will collect view data, if not already obtained.
     * @return string Extra HTML code to display after link
     */
    private function get_after_link() {
        $this->obtain_view_data();
        return $this->afterlink;
    }

    /**
     * Get the activity badge data associated to this course module (if the module supports it).
     * Modules can use this method to provide additional data to be displayed in the activity badge.
     *
     * @param renderer_base|null $output Output render to use, or null for default (global)
     * @return stdClass|null The activitybadge data (badgecontent, badgestyle...) or null if the module doesn't implement it.
     */
    public function get_activitybadge(?renderer_base $output = null): ?stdClass {
        global $OUTPUT;

        $activibybadgeclass = activitybadge::create_instance($this);
        if (empty($activibybadgeclass)) {
            return null;
        }

        if (!isset($output)) {
            $output = $OUTPUT;
        }

        return $activibybadgeclass->export_for_template($output);
    }

    /**
     * Note: Will collect view data, if not already obtained.
     * @return string Extra HTML code to display after editing icons (e.g. more icons)
     */
    private function get_after_edit_icons() {
        $this->obtain_view_data();
        return $this->afterediticons;
    }

    /**
     * Fetch the module's icon URL.
     *
     * This function fetches the course module instance's icon URL.
     * This method adds a `filtericon` parameter in the URL when rendering the monologo version of the course module icon or when
     * the plugin declares, via its `filtericon` custom data, that the icon needs to be filtered.
     * This additional information can be used by plugins when rendering the module icon to determine whether to apply
     * CSS filtering to the icon.
     *
     * @param core_renderer $output Output render to use, or null for default (global)
     * @return url Icon URL for a suitable icon to put beside this cm
     */
    public function get_icon_url($output = null) {
        global $OUTPUT;
        $this->obtain_dynamic_data();
        if (!$output) {
            $output = $OUTPUT;
        }

        $ismonologo = false;
        if (!empty($this->iconurl)) {
            // Support modules setting their own, external, icon image.
            $icon = $this->iconurl;
        } else if (!empty($this->icon)) {
            // Fallback to normal local icon + component processing.
            if (substr($this->icon, 0, 4) === 'mod/') {
                [$modname, $iconname] = explode('/', substr($this->icon, 4), 2);
                $icon = $output->image_url($iconname, $modname);
            } else {
                if (!empty($this->iconcomponent)) {
                    // Icon has specified component.
                    $icon = $output->image_url($this->icon, $this->iconcomponent);
                } else {
                    // Icon does not have specified component, use default.
                    $icon = $output->image_url($this->icon);
                }
            }
        } else {
            $icon = $output->image_url('monologo', $this->modname);
            // Activity modules may only have an `icon` icon instead of a `monologo` icon.
            // So we need to determine if the module really has a `monologo` icon.
            $ismonologo = component::has_monologo_icon('mod', $this->modname);
        }

        // Determine whether the icon will be filtered in the CSS.
        // This can be controlled by the module by declaring a 'filtericon' custom data.
        // If the 'filtericon' custom data is not set, icon filtering will be determined whether the module has a `monologo` icon.
        // Additionally, we need to cast custom data to array as some modules may treat it as an object.
        $filtericon = ((array)$this->customdata)['filtericon'] ?? $ismonologo;
        if ($filtericon) {
            $icon->param('filtericon', 1);
        }
        return $icon;
    }

    /**
     * Get the grouping label
     *
     * @param string $textclasses additional classes for grouping label
     * @return string An empty string or HTML grouping label span tag
     */
    public function get_grouping_label($textclasses = '') {
        $groupinglabel = '';
        if (
            $this->effectivegroupmode != NOGROUPS
            && !empty($this->groupingid)
            && has_capability('moodle/course:managegroups', context_course::instance($this->course))
        ) {
            $groupings = groups_get_all_groupings($this->course);
            $groupinglabel = html_writer::tag(
                'span',
                '(' . format_string($groupings[$this->groupingid]->name) . ')',
                ['class' => 'groupinglabel ' . $textclasses],
            );
        }
        return $groupinglabel;
    }

    /**
     * Returns a localised human-readable name of the module type.
     *
     * @param bool $plural If true, the function returns the plural form of the name.
     * @return ?lang_string
     */
    public function get_module_type_name($plural = false) {
        $modnames = get_module_types_names($plural);
        if (isset($modnames[$this->modname])) {
            return $modnames[$this->modname];
        } else {
            return null;
        }
    }

    /**
     * Returns a localised human-readable name of the module type in plural form - calculated on request
     *
     * @return string
     */
    private function get_module_type_name_plural() {
        return $this->get_module_type_name(true);
    }

    /**
     * Get the modinfo object that this cm_info came from.
     *
     * @return modinfo Modinfo object that this came from
     */
    public function get_modinfo(): modinfo {
        return $this->modinfo;
    }

    /**
     * Returns the section this module belongs to
     *
     * @return section_info
     */
    public function get_section_info() {
        return $this->modinfo->get_section_info_by_id($this->sectionid);
    }

    /**
     * Getter method for property $section that returns section id.
     *
     * This method is called by the property ->section.
     *
     * @return int
     */
    private function get_section_id(): int {
        return $this->sectionid;
    }

    /**
     * Returns course object that was used in the first {@see get_fast_modinfo()} call.
     *
     * It may not contain all fields from DB table {course} but always has at least the following:
     * id,shortname,fullname,format,enablecompletion,groupmode,groupmodeforce,cacherev
     *
     * If the course object lacks the field you need you can use the global
     * function {@see get_course()} that will save extra query if you access
     * current course or frontpage course.
     *
     * @return stdClass
     */
    public function get_course() {
        return $this->modinfo->get_course();
    }

    /**
     * Returns course id for which the modinfo was generated.
     *
     * @return int
     */
    private function get_course_id() {
        return $this->modinfo->get_course_id();
    }

    /**
     * Returns group mode used for the course containing the module
     *
     * @return int one of constants NOGROUPS, SEPARATEGROUPS, VISIBLEGROUPS
     */
    private function get_course_groupmode() {
        return $this->modinfo->get_course()->groupmode;
    }

    /**
     * Returns whether group mode is forced for the course containing the module
     *
     * @return bool
     */
    private function get_course_groupmodeforce() {
        return $this->modinfo->get_course()->groupmodeforce;
    }

    /**
     * Returns effective groupmode of the module that may be overwritten by forced course groupmode.
     *
     * @return int one of constants NOGROUPS, SEPARATEGROUPS, VISIBLEGROUPS
     */
    private function get_effective_groupmode() {
        $groupmode = $this->groupmode;
        if ($this->modinfo->get_course()->groupmodeforce) {
            $groupmode = $this->modinfo->get_course()->groupmode;
            if ($groupmode != NOGROUPS && !plugin_supports('mod', $this->modname, FEATURE_GROUPS, false)) {
                $groupmode = NOGROUPS;
            }
        }
        return $groupmode;
    }

    /**
     * Get the context for that activity.
     *
     * @return context_module Current module context
     */
    private function get_context() {
        return context_module::instance($this->id);
    }

    /**
     * Returns itself in the form of stdClass.
     *
     * The object includes all fields that table course_modules has and additionally
     * fields 'name', 'modname', 'sectionnum' (if requested).
     *
     * This can be used as a faster alternative to {@see get_coursemodule_from_id()}
     *
     * @param bool $additionalfields include additional fields 'name', 'modname', 'sectionnum'
     * @return stdClass
     */
    public function get_course_module_record($additionalfields = false) {
        $cmrecord = new stdClass();

        // Standard fields from table course_modules.
        static $cmfields = ['id', 'course', 'module', 'instance', 'section', 'idnumber', 'added',
            'score', 'indent', 'visible', 'visibleoncoursepage', 'visibleold', 'groupmode', 'groupingid',
            'completion', 'completiongradeitemnumber', 'completionview', 'completionexpected', 'completionpassgrade',
            'showdescription', 'availability', 'deletioninprogress', 'downloadcontent', 'lang',
            'enableaitools', 'enabledaiactions',
        ];

        foreach ($cmfields as $key) {
            $cmrecord->$key = $this->$key;
        }

        // Additional fields that function get_coursemodule_from_id() adds.
        if ($additionalfields) {
            $cmrecord->name = $this->name;
            $cmrecord->modname = $this->modname;
            $cmrecord->sectionnum = $this->sectionnum;
        }

        return $cmrecord;
    }

    /**
     * Return the activity database table record.
     *
     * The instance record will be cached after the first call.
     *
     * @return stdClass
     */
    public function get_instance_record() {
        global $DB;
        if (!isset($this->instancerecord)) {
            $this->instancerecord = $DB->get_record(
                table: $this->modname,
                conditions: ['id' => $this->instance],
                strictness: MUST_EXIST,
            );
        }
        return $this->instancerecord;
    }

    /**
     * Returns the section delegated by this module, if any.
     *
     * @return ?section_info
     */
    public function get_delegated_section_info(): ?section_info {
        $delegatedsections = $this->modinfo->get_sections_delegated_by_cm();
        if (!array_key_exists($this->id, $delegatedsections)) {
            return null;
        }
        return $delegatedsections[$this->id];
    }

    /**
     * Sets content to display on course view page below link (if present).
     * @param string $content New content as HTML string (empty string if none)
     * @param bool $isformatted Whether user content is already passed through format_text/format_string and should not
     *    be formatted again. This can be useful when module adds interactive elements on top of formatted user text.
     * @return void
     */
    public function set_content($content, $isformatted = false) {
        $this->content = $content;
        $this->contentisformatted = $isformatted;
    }

    /**
     * Sets extra classes to include in CSS.
     * @param string $extraclasses Extra classes (empty string if none)
     * @return void
     */
    public function set_extra_classes($extraclasses) {
        $this->extraclasses = $extraclasses;
    }

    /**
     * Sets the external full url that points to the icon being used
     * by the activity. Useful for external-tool modules (lti...)
     * If set, takes precedence over $icon and $iconcomponent
     *
     * @param url $iconurl full external url pointing to icon image for activity
     * @return void
     */
    public function set_icon_url(url $iconurl) {
        $this->iconurl = $iconurl;
    }

    /**
     * Sets value of on-click attribute for JavaScript.
     * Note: May not be called from _cm_info_view (only _cm_info_dynamic).
     * @param string $onclick New onclick attribute which should be HTML-escaped
     *   (empty string if none)
     * @return void
     */
    public function set_on_click($onclick) {
        $this->check_not_view_only();
        $this->onclick = $onclick;
    }

    /**
     * Overrides the value of an element in the customdata array.
     *
     * @param string $name The key in the customdata array
     * @param mixed $value The value
     */
    public function override_customdata($name, $value) {
        if (!is_array($this->customdata)) {
            $this->customdata = [];
        }
        $this->customdata[$name] = $value;
    }

    /**
     * Sets HTML that displays after link on course view page.
     * @param string $afterlink HTML string (empty string if none)
     * @return void
     */
    public function set_after_link($afterlink) {
        $this->afterlink = $afterlink;
    }

    /**
     * Sets HTML that displays after edit icons on course view page.
     * @param string $afterediticons HTML string (empty string if none)
     * @return void
     */
    public function set_after_edit_icons($afterediticons) {
        $this->afterediticons = $afterediticons;
    }

    /**
     * Changes the name (text of link) for this module instance.
     * Note: May not be called from _cm_info_view (only _cm_info_dynamic).
     * @param string $name Name of activity / link text
     * @return void
     */
    public function set_name($name) {
        if ($this->state < self::STATE_BUILDING_DYNAMIC) {
            $this->update_user_visible();
        }
        $this->name = $name;
    }

    /**
     * Turns off the view link for this module instance.
     * Note: May not be called from _cm_info_view (only _cm_info_dynamic).
     * @return void
     */
    public function set_no_view_link() {
        $this->check_not_view_only();
        $this->url = null;
    }

    /**
     * Sets the 'uservisible' flag. This can be used (by setting false) to prevent access and
     * display of this module link for the current user.
     * Note: May not be called from _cm_info_view (only _cm_info_dynamic).
     * @param bool $uservisible
     * @return void
     */
    public function set_user_visible($uservisible) {
        $this->check_not_view_only();
        $this->uservisible = $uservisible;
    }

    /**
     * Sets the 'customcmlistitem' flag
     *
     * This can be used (by setting true) to prevent the course from rendering the
     * activity item as a regular activity card. This is applied to activities like labels.
     *
     * @param bool $customcmlistitem if the cmlist item of that activity has a special dysplay other than a card.
     */
    public function set_custom_cmlist_item(bool $customcmlistitem) {
        $this->customcmlistitem = $customcmlistitem;
    }

    /**
     * Sets the 'available' flag and related details. This flag is normally used to make
     * course modules unavailable until a certain date or condition is met. (When a course
     * module is unavailable, it is still visible to users who have viewhiddenactivities
     * permission.)
     *
     * When this is function is called, user-visible status is recalculated automatically.
     *
     * The $showavailability flag does not really do anything any more, but is retained
     * for backward compatibility. Setting this to false will cause $availableinfo to
     * be ignored.
     *
     * Note: May not be called from _cm_info_view (only _cm_info_dynamic).
     * @param bool $available False if this item is not 'available'
     * @param int $showavailability 0 = do not show this item at all if it's not available,
     *   1 = show this item greyed out with the following message
     * @param string $availableinfo Information about why this is not available, or
     *   empty string if not displaying
     * @return void
     */
    public function set_available($available, $showavailability = 0, $availableinfo = '') {
        $this->check_not_view_only();
        $this->available = $available;
        if (!$showavailability) {
            $availableinfo = '';
        }
        $this->availableinfo = $availableinfo;
        $this->update_user_visible();
    }

    /**
     * Some set functions can only be called from _cm_info_dynamic and not _cm_info_view.
     * This is because they may affect parts of this object which are used on pages other
     * than the view page (e.g. in the navigation block, or when checking access on
     * module pages).
     * @return void
     */
    private function check_not_view_only() {
        if ($this->state >= self::STATE_DYNAMIC) {
            throw new coding_exception(
                'Cannot set this data from _cm_info_view because it may ' .
                'affect other pages as well as view'
            );
        }
    }

    /**
     * Constructor should not be called directly; use {@see get_fast_modinfo()}
     *
     * @param modinfo $modinfo Parent object
     * @param mixed $notused1 Argument not used
     * @param stdClass $mod Module object from the modinfo field of course table
     * @param mixed $notused2 Argument not used
     */
    public function __construct(modinfo $modinfo, $notused1, $mod, $notused2) {
        $this->modinfo = $modinfo;

        $this->id               = $mod->cm;
        $this->instance         = $mod->id;
        $this->modname          = $mod->mod;
        $this->idnumber         = isset($mod->idnumber) ? $mod->idnumber : '';
        $this->name             = $mod->name;
        $this->visible          = $mod->visible;
        $this->visibleoncoursepage = $mod->visibleoncoursepage;
        $this->sectionnum       = $mod->section; // Note weirdness with name here. Keeping for backwards compatibility.
        $this->groupmode        = isset($mod->groupmode) ? $mod->groupmode : 0;
        $this->groupingid       = isset($mod->groupingid) ? $mod->groupingid : 0;
        $this->indent           = isset($mod->indent) ? $mod->indent : 0;
        $this->extra            = isset($mod->extra) ? $mod->extra : '';
        $this->extraclasses     = isset($mod->extraclasses) ? $mod->extraclasses : '';
        // The iconurl may be stored as either string or instance of url.
        $this->iconurl          = isset($mod->iconurl) ? new url($mod->iconurl) : '';
        $this->onclick          = isset($mod->onclick) ? $mod->onclick : '';
        $this->content          = isset($mod->content) ? $mod->content : '';
        $this->icon             = isset($mod->icon) ? $mod->icon : '';
        $this->iconcomponent    = isset($mod->iconcomponent) ? $mod->iconcomponent : '';
        $this->customdata       = isset($mod->customdata) ? $mod->customdata : '';
        $this->showdescription  = isset($mod->showdescription) ? $mod->showdescription : 0;
        $this->state = self::STATE_BASIC;

        $this->sectionid = isset($mod->sectionid) ? $mod->sectionid : 0;
        $this->module = isset($mod->module) ? $mod->module : 0;
        $this->added = isset($mod->added) ? $mod->added : 0;
        $this->score = isset($mod->score) ? $mod->score : 0;
        $this->visibleold = isset($mod->visibleold) ? $mod->visibleold : 0;
        $this->deletioninprogress = isset($mod->deletioninprogress) ? $mod->deletioninprogress : 0;
        $this->downloadcontent = $mod->downloadcontent ?? null;
        $this->lang = $mod->lang ?? null;

        // Note: it saves effort and database space to always include the
        // availability and completion fields, even if availability or completion
        // are actually disabled.
        $this->completion = isset($mod->completion) ? $mod->completion : 0;
        $this->completionpassgrade = isset($mod->completionpassgrade) ? $mod->completionpassgrade : 0;
        $this->completiongradeitemnumber = isset($mod->completiongradeitemnumber)
                ? $mod->completiongradeitemnumber : null;
        $this->completionview = isset($mod->completionview)
                ? $mod->completionview : 0;
        $this->completionexpected = isset($mod->completionexpected)
                ? $mod->completionexpected : 0;
        $this->availability = isset($mod->availability) ? $mod->availability : null;
        $this->conditionscompletion = isset($mod->conditionscompletion)
                ? $mod->conditionscompletion : [];
        $this->conditionsgrade = isset($mod->conditionsgrade)
                ? $mod->conditionsgrade : [];
        $this->conditionsfield = isset($mod->conditionsfield)
                ? $mod->conditionsfield : [];

        static $modviews = [];
        if (!isset($modviews[$this->modname])) {
            $modviews[$this->modname] = !plugin_supports(
                'mod',
                $this->modname,
                FEATURE_NO_VIEW_LINK,
            );
        }
        $this->url = $modviews[$this->modname]
                ? new url('/mod/' . $this->modname . '/view.php', ['id' => $this->id])
                : null;
    }

    /**
     * Creates a cm_info object from a database record (also accepts cm_info
     * in which case it is just returned unchanged).
     *
     * @param stdClass|cm_info|null|bool $cm Stdclass or cm_info (or null or false)
     * @param int $userid Optional userid (default to current)
     * @return cm_info|null Object as cm_info, or null if input was null/false
     */
    public static function create($cm, $userid = 0) {
        // Null, false, etc. gets passed through as null.
        if (!$cm) {
            return null;
        }
        // If it is already a cm_info object with the right user, just return it.
        if (($cm instanceof cm_info) && ($cm->get_modinfo()->userid == $userid)) {
            return $cm;
        }
        // Otherwise load modinfo.
        if (empty($cm->id) || empty($cm->course)) {
            throw new coding_exception('$cm must contain ->id and ->course');
        }
        $modinfo = get_fast_modinfo($cm->course, $userid);
        return $modinfo->get_cm($cm->id);
    }

    /**
     * If dynamic data for this course-module is not yet available, gets it.
     *
     * This function is automatically called when requesting any modinfo property
     * that can be modified by modules (have a set_xxx method).
     *
     * Dynamic data is data which does not come directly from the cache but is calculated at
     * runtime based on the current user. Primarily this concerns whether the user can access
     * the module or not.
     *
     * As part of this function, the module's _cm_info_dynamic function from its lib.php will
     * be called (if it exists). Make sure that the functions that are called here do not use
     * any getter magic method from cm_info.
     * @return void
     */
    private function obtain_dynamic_data() {
        global $CFG;
        $userid = $this->modinfo->get_user_id();
        if ($this->state >= self::STATE_BUILDING_DYNAMIC || $userid == -1) {
            return;
        }
        $this->state = self::STATE_BUILDING_DYNAMIC;

        if (!empty($CFG->enableavailability)) {
            // Get availability information.
            $ci = new \core_availability\info_module($this);

            // Note that the modinfo currently available only includes minimal details (basic data)
            // but we know that this function does not need anything more than basic data.
            $this->available = $ci->is_available(
                $this->availableinfo,
                true,
                $userid,
                $this->modinfo,
            );
        } else {
            $this->available = true;
        }

        // Check parent section.
        if ($this->available) {
            $parentsection = $this->modinfo->get_section_info($this->sectionnum);
            if (!$parentsection->get_available()) {
                // Do not store info from section here, as that is already presented from the section (if appropriate).
                // Just change the flag.
                $this->available = false;
            }
        }

        // Update visible state for current user.
        $this->update_user_visible();

        // Let module make dynamic changes at this point.
        $this->call_mod_function('cm_info_dynamic');
        $this->state = self::STATE_DYNAMIC;
    }

    /**
     * Getter method for property $uservisible, ensures that dynamic data is retrieved.
     *
     * This method is normally called by the property ->uservisible, but can be called directly if
     * there is a case when it might be called recursively (you can't call property values
     * recursively).
     *
     * @return bool
     */
    public function get_user_visible() {
        $this->obtain_dynamic_data();
        return $this->uservisible;
    }

    /**
     * Returns whether this module is visible to the current user on course page
     *
     * Activity may be visible on the course page but not available, for example
     * when it is hidden conditionally but the condition information is displayed.
     *
     * @return bool
     */
    public function is_visible_on_course_page() {
        $this->obtain_dynamic_data();
        return $this->uservisibleoncoursepage;
    }

    /**
     * Use this method if you want to check if the plugin overrides any visibility checks to block rendering to the display.
     *
     * @return bool
     */
    public function is_of_type_that_can_display(): bool {
        return modinfo::is_mod_type_visible_on_course($this->modname);
    }

    /**
     * Whether this module is available but hidden from course page
     *
     * "Stealth" modules are the ones that are not shown on course page but available by following url.
     * They are normally also displayed in grade reports and other reports.
     * Module will be stealth either if visibleoncoursepage=0 or it is a visible module inside the hidden
     * section.
     *
     * @return bool
     */
    public function is_stealth() {
        return !$this->visibleoncoursepage ||
            ($this->visible && ($section = $this->get_section_info()) && !$section->visible);
    }

    /**
     * Getter method for property $available, ensures that dynamic data is retrieved
     * @return bool
     */
    private function get_available() {
        $this->obtain_dynamic_data();
        return $this->available;
    }

    /**
     * Getter method for property $availableinfo, ensures that dynamic data is retrieved
     *
     * @return string Available info (HTML)
     */
    private function get_available_info() {
        $this->obtain_dynamic_data();
        return $this->availableinfo;
    }

    /**
     * Works out whether activity is available to the current user
     *
     * If the activity is unavailable, additional checks are required to determine if its hidden or greyed out
     *
     * @return void
     */
    private function update_user_visible() {
        $userid = $this->modinfo->get_user_id();
        if ($userid == -1) {
            return null;
        }
        $this->uservisible = true;

        // If the module is being deleted, set the uservisible state to false and return.
        if ($this->deletioninprogress) {
            $this->uservisible = false;
            return null;
        }

        // If the user cannot access the activity set the uservisible flag to false.
        // Additional checks are required to determine whether the activity is entirely hidden or just greyed out.
        if (
            !$this->visible && !has_capability('moodle/course:viewhiddenactivities', $this->get_context(), $userid)
            || (
                !$this->get_available()
                && !has_capability('moodle/course:ignoreavailabilityrestrictions', $this->get_context(), $userid)
            )
        ) {
            $this->uservisible = false;
        }

        // Check group membership.
        if ($this->is_user_access_restricted_by_capability()) {
            $this->uservisible = false;
            // Ensure activity is completely hidden from the user.
            $this->availableinfo = '';
        }

        $capabilities = [
            'moodle/course:manageactivities',
            'moodle/course:activityvisibility',
            'moodle/course:viewhiddenactivities',
        ];
        $this->uservisibleoncoursepage = $this->uservisible &&
            ($this->visibleoncoursepage || has_any_capability($capabilities, $this->get_context(), $userid));
        // Activity that is not available, not hidden from course page and has availability
        // info is actually visible on the course page (with availability info and without a link).
        if ($this->visibleoncoursepage && $this->visible && $this->availableinfo) {
            $this->uservisibleoncoursepage = true;
        }
    }

    /**
     * Checks whether mod/...:view capability restricts the current user's access.
     *
     * @return bool|null True if the user access is restricted.
     */
    public function is_user_access_restricted_by_capability() {
        $userid = $this->modinfo->get_user_id();
        if ($userid == -1) {
            return null;
        }
        $capability = 'mod/' . $this->modname . ':view';
        $capabilityinfo = get_capability_info($capability);
        if (!$capabilityinfo) {
            // Capability does not exist, no one is prevented from seeing the activity.
            return false;
        }

        // You are blocked if you don't have the capability.
        return !has_capability($capability, $this->get_context(), $userid);
    }

    /**
     * Calls a module function (if exists), passing in one parameter: this object.
     * @param string $type Name of function e.g. if this is 'grooblezorb' and the modname is
     *   'forum' then it will try to call 'mod_forum_grooblezorb' or 'forum_grooblezorb'
     * @return void
     */
    private function call_mod_function($type) {
        global $CFG;
        $libfile = $CFG->dirroot . '/mod/' . $this->modname . '/lib.php';
        if (file_exists($libfile)) {
            include_once($libfile);
            $function = 'mod_' . $this->modname . '_' . $type;
            if (function_exists($function)) {
                $function($this);
            } else {
                $function = $this->modname . '_' . $type;
                if (function_exists($function)) {
                    $function($this);
                }
            }
        }
    }

    /**
     * If view data for this course-module is not yet available, obtains it.
     *
     * This function is automatically called if any of the functions (marked) which require
     * view data are called.
     *
     * View data is data which is needed only for displaying the course main page (& any similar
     * functionality on other pages) but is not needed in general. Obtaining view data may have
     * a performance cost.
     *
     * As part of this function, the module's _cm_info_view function from its lib.php will
     * be called (if it exists).
     * @return void
     */
    private function obtain_view_data() {
        if ($this->state >= self::STATE_BUILDING_VIEW || $this->modinfo->get_user_id() == -1) {
            return;
        }
        $this->obtain_dynamic_data();
        $this->state = self::STATE_BUILDING_VIEW;

        // Let module make changes at this point.
        $this->call_mod_function('cm_info_view');
        $this->state = self::STATE_VIEW;
    }
}

// Alias this class to the old name.
// This file will be autoloaded by the legacyclasses autoload system.
// In future all uses of this class will be corrected and the legacy references will be removed.
class_alias(cm_info::class, \cm_info::class);
