Whoops! There was an error.
Whoops \ Exception \ ErrorException (E_USER_NOTICE)
Locale warning (not fatal) U_USING_FALLBACK_WARNING: Requested locale "zh_TW.UTF-8" not found, locale "zh@collation=stroke" used instead. The most specific locale supported by ICU relatively to the requested locale is "zh_Hant". Whoops\Exception\ErrorException thrown with message "Locale warning (not fatal) U_USING_FALLBACK_WARNING: Requested locale "zh_TW.UTF-8" not found, locale "zh@collation=stroke" used instead. The most specific locale supported by ICU relatively to the requested locale is "zh_Hant"." Stacktrace: #25 debugging in /opt/moodle/lib/classes/collator.php:102 #24 core_collator:ensure_collator_available in /opt/moodle/lib/classes/collator.php:188 #23 core_collator:asort in /opt/moodle/lib/classes/collator.php:263 #22 core_collator:asort_objects_by_property in /opt/moodle/filter/activitynames/classes/text_filter.php:113 #21 filter_activitynames\text_filter:get_activity_list in /opt/moodle/filter/activitynames/classes/text_filter.php:79 #20 filter_activitynames\text_filter:get_cached_activity_list in /opt/moodle/filter/activitynames/classes/text_filter.php:43 #19 filter_activitynames\text_filter:filter in /opt/moodle/filter/classes/text_filter.php:116 #18 core_filters\text_filter:filter_stage_post_clean in /opt/moodle/filter/classes/filter_manager.php:185 #17 core_filters\filter_manager:apply_filter_chain in /opt/moodle/filter/classes/filter_manager.php:231 #16 core_filters\filter_manager:filter_text in /opt/moodle/lib/classes/formatting.php:240 #15 core\formatting:format_text in /opt/moodle/lib/weblib.php:617 #14 format_text in /opt/moodle/lib/modinfolib.php:1865 #13 cm_info:get_formatted_content in /opt/moodle/course/format/classes/output/local/content/cm.php:202 #12 core_courseformat\output\local\content\cm:add_alternative_content_data in /opt/moodle/course/format/classes/output/local/content/cm.php:133 #11 core_courseformat\output\local\content\cm:export_for_template in /opt/moodle/course/format/classes/output/local/content/section/cmitem.php:107 #10 core_courseformat\output\local\content\section\cmitem:export_for_template in /opt/moodle/course/format/classes/output/local/content/section/cmlist.php:122 #9 core_courseformat\output\local\content\section\cmlist:export_for_template in /opt/moodle/course/format/classes/output/local/content/section.php:241 #8 core_courseformat\output\local\content\section:add_cm_data in /opt/moodle/course/format/classes/output/local/content/section.php:172 #7 core_courseformat\output\local\content\section:export_for_template in /opt/moodle/course/format/topics/classes/output/courseformat/content/section.php:46 #6 format_topics\output\courseformat\content\section:export_for_template in /opt/moodle/course/format/classes/output/local/content.php:182 #5 core_courseformat\output\local\content:export_sections in /opt/moodle/course/format/classes/output/local/content.php:86 #4 core_courseformat\output\local\content:export_for_template in /opt/moodle/course/format/topics/classes/output/courseformat/content.php:54 #3 format_topics\output\courseformat\content:export_for_template in /opt/moodle/lib/classes/output/plugin_renderer_base.php:87 #2 core\output\plugin_renderer_base:render in /opt/moodle/course/format/classes/output/section_renderer.php:88 #1 core_courseformat\output\section_renderer:render in /opt/moodle/course/format/topics/format.php:60 #0 require in /opt/moodle/course/view.php:351
Stack frames (26)
25
debugging
/lib/classes/collator.php102
24
core_collator ensure_collator_available
/lib/classes/collator.php188
23
core_collator asort
/lib/classes/collator.php263
22
core_collator asort_objects_by_property
/filter/activitynames/classes/text_filter.php113
21
filter_activitynames\text_filter get_activity_list
/filter/activitynames/classes/text_filter.php79
20
filter_activitynames\text_filter get_cached_activity_list
/filter/activitynames/classes/text_filter.php43
19
filter_activitynames\text_filter filter
/filter/classes/text_filter.php116
18
core_filters\text_filter filter_stage_post_clean
/filter/classes/filter_manager.php185
17
core_filters\filter_manager apply_filter_chain
/filter/classes/filter_manager.php231
16
core_filters\filter_manager filter_text
/lib/classes/formatting.php240
15
core\formatting format_text
/lib/weblib.php617
14
format_text
/lib/modinfolib.php1865
13
cm_info get_formatted_content
/course/format/classes/output/local/content/cm.php202
12
core_courseformat\output\local\content\cm add_alternative_content_data
/course/format/classes/output/local/content/cm.php133
11
core_courseformat\output\local\content\cm export_for_template
/course/format/classes/output/local/content/section/cmitem.php107
10
core_courseformat\output\local\content\section\cmitem export_for_template
/course/format/classes/output/local/content/section/cmlist.php122
9
core_courseformat\output\local\content\section\cmlist export_for_template
/course/format/classes/output/local/content/section.php241
8
core_courseformat\output\local\content\section add_cm_data
/course/format/classes/output/local/content/section.php172
7
core_courseformat\output\local\content\section export_for_template
/course/format/topics/classes/output/courseformat/content/section.php46
6
format_topics\output\courseformat\content\section export_for_template
/course/format/classes/output/local/content.php182
5
core_courseformat\output\local\content export_sections
/course/format/classes/output/local/content.php86
4
core_courseformat\output\local\content export_for_template
/course/format/topics/classes/output/courseformat/content.php54
3
format_topics\output\courseformat\content export_for_template
/lib/classes/output/plugin_renderer_base.php87
2
core\output\plugin_renderer_base render
/course/format/classes/output/section_renderer.php88
1
core_courseformat\output\section_renderer render
/course/format/topics/format.php60
0
require
/course/view.php351
/opt/moodle/lib/classes/collator.php
                    if ($errorcode !== 0) {
                        // Get the actual locale being used, e.g. en, he, zh
                        $localeinuse = $collator->getLocale(Locale::ACTUAL_LOCALE);
                        // Check for the common fallback warning error codes. If any of the two
                        // following errors occurred, there is normally little to worry about:
                        // * U_USING_FALLBACK_WARNING (-128) indicates that a fall back locale was
                        //   used. For example, 'de_CH' was requested, but nothing was found
                        //   there, so 'de' was used.
                        // * U_USING_DEFAULT_WARNING (-127) indicates that the default locale
                        //   data was used; neither the requested locale nor any of its fall
                        //   back locales could be found. For example, 'pt' was requested, but
                        //   UCA was used (Unicode Collation Algorithm http://unicode.org/reports/tr10/).
                        // See http://www.icu-project.org/apiref/icu4c/classicu_1_1ResourceBundle.html
                        if ($errorcode === -127 || $errorcode === -128) {
                            // Check if the locale in use is UCA default one ('root') or
                            // if it is anything like the locale we asked for
                            if ($localeinuse !== 'root' && strpos($locale, $localeinuse) !== 0) {
                                // The locale we asked for is completely different to the locale
                                // we have received, let the user know via debugging
                                debugging('Locale warning (not fatal) '.$errormessage.': '.
                                    'Requested locale "'.$locale.'" not found, locale "'.$localeinuse.'" used instead. '.
                                    'The most specific locale supported by ICU relatively to the requested locale is "'.
                                    $collator->getLocale(Locale::VALID_LOCALE).'".');
                            } else {
                                // Nothing to do here, this is expected!
                                // The Moodle locale setting isn't what the collator expected but
                                // it is smart enough to match the first characters of our locale
                                // to find the correct locale or to use UCA collation
                            }
                        } else {
                            // We've received some other sort of non fatal warning - let the
                            // user know about it via debugging.
                            debugging('Problem with locale: '.$errormessage.'. '.
                                'Requested locale: "'.$locale.'", actual locale "'.$localeinuse.'". '.
                                'The most specific locale supported by ICU relatively to the requested locale is "'.
                                $collator->getLocale(Locale::VALID_LOCALE).'".');
                        }
                    }
                    // Store the collator object now that we can be sure it is in a workable condition
                    self::$collator = $collator;
/opt/moodle/lib/classes/collator.php
     * @param array $arr array to be sorted (reference)
     * @param int $sortflag One of core_collator::SORT_NUMERIC, core_collator::SORT_STRING, core_collator::SORT_NATURAL, core_collator::SORT_REGULAR
     *      optionally "|" core_collator::CASE_SENSITIVE
     * @return bool True on success
     */
    public static function asort(array &$arr, $sortflag = core_collator::SORT_STRING) {
        if (empty($arr)) {
            // nothing to do
            return true;
        }
 
        $original = null;
 
        $casesensitive = (bool)($sortflag & core_collator::CASE_SENSITIVE);
        $sortflag = ($sortflag & ~core_collator::CASE_SENSITIVE);
        if ($sortflag != core_collator::SORT_NATURAL and $sortflag != core_collator::SORT_STRING) {
            $casesensitive = false;
        }
 
        if (self::ensure_collator_available()) {
            if ($sortflag == core_collator::SORT_NUMERIC) {
                $flag = Collator::SORT_NUMERIC;
 
            } else if ($sortflag == core_collator::SORT_REGULAR) {
                $flag = Collator::SORT_REGULAR;
 
            } else {
                $flag = Collator::SORT_STRING;
            }
 
            if ($sortflag == core_collator::SORT_NATURAL) {
                $original = $arr;
                if ($sortflag == core_collator::SORT_NATURAL) {
                    foreach ($arr as $key => $value) {
                        $arr[$key] = self::naturalise((string)$value);
                    }
                }
            }
            if ($casesensitive) {
                self::$collator->setAttribute(Collator::CASE_FIRST, Collator::UPPER_FIRST);
/opt/moodle/lib/classes/collator.php
        }
 
        return $result;
    }
 
    /**
     * Locale aware sort of objects by a property in common to all objects
     *
     * @param array $objects An array of objects to sort (handled by reference)
     * @param string $property The property to use for comparison
     * @param int $sortflag One of core_collator::SORT_NUMERIC, core_collator::SORT_STRING, core_collator::SORT_NATURAL, core_collator::SORT_REGULAR
     *      optionally "|" core_collator::CASE_SENSITIVE
     * @return bool True on success
     */
    public static function asort_objects_by_property(array &$objects, $property, $sortflag = core_collator::SORT_STRING) {
        $original = $objects;
        foreach ($objects as $key => $object) {
            $objects[$key] = $object->$property;
        }
        $result = self::asort($objects, $sortflag);
        self::restore_array($objects, $original);
        return $result;
    }
 
    /**
     * Locale aware sort of objects by a method in common to all objects
     *
     * @param array $objects An array of objects to sort (handled by reference)
     * @param string $method The method to call to generate a value for comparison
     * @param int $sortflag One of core_collator::SORT_NUMERIC, core_collator::SORT_STRING, core_collator::SORT_NATURAL, core_collator::SORT_REGULAR
     *      optionally "|" core_collator::CASE_SENSITIVE
     * @return bool True on success
     */
    public static function asort_objects_by_method(array &$objects, $method, $sortflag = core_collator::SORT_STRING) {
        $original = $objects;
        foreach ($objects as $key => $object) {
            $objects[$key] = $object->{$method}();
        }
        $result = self::asort($objects, $sortflag);
        self::restore_array($objects, $original);
/opt/moodle/filter/activitynames/classes/text_filter.php
 
        $modinfo = get_fast_modinfo($courseid);
        if (!empty($modinfo->cms)) {
            $activitylist = []; // We will store all the created filters here.
 
            // Create array of visible activities sorted by the name length (we are only interested in properties name and url).
            $sortedactivities = [];
            foreach ($modinfo->cms as $cm) {
                // Use normal access control and visibility, but exclude labels and hidden activities.
                if ($cm->visible && $cm->has_view() && $cm->uservisible) {
                    $sortedactivities[] = (object)[
                        'name' => $cm->name,
                        'url' => $cm->url,
                        'id' => $cm->id,
                        'namelen' => -strlen($cm->name), // Negative value for reverse sorting.
                    ];
                }
            }
            // Sort activities by the length of the activity name in reverse order.
            core_collator::asort_objects_by_property($sortedactivities, 'namelen', core_collator::SORT_NUMERIC);
 
            foreach ($sortedactivities as $cm) {
                $title = s(trim(strip_tags($cm->name)));
                $currentname = trim($cm->name);
                $entitisedname  = s($currentname);
                // Avoid empty or unlinkable activity names.
                if (!empty($title)) {
                    $hreftagbegin = html_writer::start_tag(
                        'a',
                        ['class' => 'autolink', 'title' => $title,
                        'href' => $cm->url, ]
                    );
                    $activitylist[$cm->id] = new filterobject($currentname, $hreftagbegin, '</a>', false, true);
                    if ($currentname != $entitisedname) {
                        // If name has some entity (&amp; &quot; &lt; &gt;) add that filter too. MDL-17545.
                        $activitylist[$cm->id . '-e'] = new filterobject($entitisedname, $hreftagbegin, '</a>', false, true);
                    }
                }
            }
        }
/opt/moodle/filter/activitynames/classes/text_filter.php
        }
    }
 
    /**
     * Get all the cached activity list for a course
     *
     * @param int $courseid id of the course
     * @return filterobject[] the activities
     */
    protected function get_cached_activity_list($courseid) {
        global $USER;
        $cached = cache::make_from_params(cache_store::MODE_REQUEST, 'filter', 'activitynames');
 
        // Return cached activity list.
        if ($cached->get('cachecourseid') == $courseid && $cached->get('cacheuserid') == $USER->id) {
            return $cached->get('activitylist');
        }
 
        // Not cached yet, get activity list and set cache.
        $activitylist = $this->get_activity_list($courseid);
        $cached->set('cacheuserid', $USER->id);
        $cached->set('cachecourseid', $courseid);
        $cached->set('activitylist', $activitylist);
        return $activitylist;
    }
 
    /**
     * Get all the activity list for a course
     *
     * @param int $courseid id of the course
     * @return filterobject[] the activities
     */
    protected function get_activity_list($courseid) {
        $activitylist = [];
 
        $modinfo = get_fast_modinfo($courseid);
        if (!empty($modinfo->cms)) {
            $activitylist = []; // We will store all the created filters here.
 
            // Create array of visible activities sorted by the name length (we are only interested in properties name and url).
/opt/moodle/filter/activitynames/classes/text_filter.php
 
/**
 * This filter provides automatic linking to
 * activities when its name (title) is found inside every Moodle text
 *
 * @package    filter_activitynames
 * @subpackage activitynames
 * @copyright  2004 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
class text_filter extends \core_filters\text_filter {
    #[\Override]
    public function filter($text, array $options = []) {
        $coursectx = $this->context->get_course_context(false);
        if (!$coursectx) {
            return $text;
        }
        $courseid = $coursectx->instanceid;
 
        $activitylist = $this->get_cached_activity_list($courseid);
 
        $filterslist = [];
        if (!empty($activitylist)) {
            $cmid = $this->context->instanceid;
            if ($this->context->contextlevel == CONTEXT_MODULE && isset($activitylist[$cmid])) {
                // Remove filterobjects for the current module.
                $filterslist = array_values(array_diff_key($activitylist, [$cmid => 1, $cmid . '-e' => 1]));
            } else {
                $filterslist = array_values($activitylist);
            }
        }
 
        if ($filterslist) {
            return $text = filter_phrases($text, $filterslist);
        } else {
            return $text;
        }
    }
 
    /**
/opt/moodle/filter/classes/text_filter.php
     * @param array $options
     * @return string
     */
    public function filter_stage_pre_clean(string $text, array $options): string {
        // NOTE: override if necessary.
        return $text;
    }
 
    /**
     * Filter HTML text at the very end after text is sanitised.
     *
     * NOTE: this is called even if $options['noclean'] is true and text is not cleaned.
     *
     * @param string $text
     * @param array $options
     * @return string
     */
    public function filter_stage_post_clean(string $text, array $options): string {
        // NOTE: override if necessary.
        return $this->filter($text, $options);
    }
 
    /**
     * Filter simple text coming from format_string().
     *
     * Note that unless $CFG->formatstringstriptags is disabled
     * HTML tags are not expected in returned value.
     *
     * @param string $text
     * @param array $options
     * @return string
     */
    public function filter_stage_string(string $text, array $options): string {
        // NOTE: override if necessary.
        return $this->filter($text, $options);
    }
}
 
// Alias this class to the old name.
// This file will be autoloaded by the legacyclasses autoload system.
/opt/moodle/filter/classes/filter_manager.php
        array $options = [],
        ?array $skipfilters = null
    ) {
        if (!isset($options['stage'])) {
            $filtermethod = 'filter';
        } else if (in_array($options['stage'], ['pre_format', 'pre_clean', 'post_clean', 'string'], true)) {
            $filtermethod = 'filter_stage_' . $options['stage'];
        } else {
            $filtermethod = 'filter';
            debugging('Invalid filter stage specified in options: ' . $options['stage'], DEBUG_DEVELOPER);
        }
        if ($text === null || $text === '') {
            // Nothing to filter.
            return '';
        }
        foreach ($filterchain as $filtername => $filter) {
            if ($skipfilters !== null && in_array($filtername, $skipfilters)) {
                continue;
            }
            $text = $filter->$filtermethod($text, $options);
        }
        return $text;
    }
 
    /**
     * Get all the filters that apply to a given context for calls to format_text.
     *
     * @param context $context
     * @return moodle_text_filter[] A text filter
     */
    protected function get_text_filters($context) {
        if (!isset($this->textfilters[$context->id])) {
            $this->load_filters($context);
        }
        return $this->textfilters[$context->id];
    }
 
    /**
     * Get all the filters that apply to a given context for calls to format_string.
     *
/opt/moodle/filter/classes/filter_manager.php
        }
        return $this->stringfilters[$context->id];
    }
 
    /**
     * Filter some text
     *
     * @param string $text The text to filter
     * @param context $context the context.
     * @param array $options options passed to the filters
     * @param null|array $skipfilters of filter names. Any filters that should not be applied to this text.
     * @return string resulting text
     */
    public function filter_text(
        $text,
        $context,
        array $options = [],
        ?array $skipfilters = null
    ) {
        $text = $this->apply_filter_chain($text, $this->get_text_filters($context), $options, $skipfilters);
        if (!isset($options['stage']) || $options['stage'] === 'post_clean') {
            // Remove <nolink> tags for XHTML compatibility after the last filtering stage.
            $text = str_replace(['<nolink>', '</nolink>'], '', $text);
        }
        return $text;
    }
 
    /**
     * Filter a piece of string
     *
     * @param string $string The text to filter
     * @param context $context the context.
     * @return string resulting string
     */
    public function filter_string($string, $context) {
        return $this->apply_filter_chain($string, $this->get_string_filters($context), ['stage' => 'string']);
    }
 
    /**
     * Setup page with filters requirements and other prepare stuff.
/opt/moodle/lib/classes/formatting.php
            ];
        } else {
            $filtermanager = new \null_filter_manager();
            $filteroptions = [];
        }
 
        switch ($format) {
            case FORMAT_HTML:
                $filteroptions['stage'] = 'pre_format';
                $text = $filtermanager->filter_text($text, $context, $filteroptions);
                // Text is already in HTML format, so just continue to the next filtering stage.
                $filteroptions['stage'] = 'pre_clean';
                $text = $filtermanager->filter_text($text, $context, $filteroptions);
                if ($clean) {
                    $text = clean_text($text, FORMAT_HTML, [
                        'allowid' => $allowid,
                    ]);
                }
                $filteroptions['stage'] = 'post_clean';
                $text = $filtermanager->filter_text($text, $context, $filteroptions);
                break;
 
            case FORMAT_PLAIN:
                $text = s($text); // Cleans dangerous JS.
                $text = rebuildnolinktag($text);
                $text = str_replace('  ', '&nbsp; ', $text);
                $text = nl2br($text);
                break;
 
            case FORMAT_MARKDOWN:
                $filteroptions['stage'] = 'pre_format';
                $text = $filtermanager->filter_text($text, $context, $filteroptions);
                $text = markdown_to_html($text);
                $filteroptions['stage'] = 'pre_clean';
                $text = $filtermanager->filter_text($text, $context, $filteroptions);
                if ($clean) {
                    $text = clean_text($text, FORMAT_HTML, [
                        'allowid' => $allowid,
                    ]);
                }
/opt/moodle/lib/weblib.php
                unset($options[$option]);
            }
        }
 
        foreach ($options as $option => $value) {
            $params[$option] = $value;
        }
 
        // The noclean option has been renamed to clean.
        if (array_key_exists('noclean', $params)) {
            $params['clean'] = !$params['noclean'];
            unset($params['noclean']);
        }
    }
 
    if ($format !== null) {
        $params['format'] = $format;
    }
 
    return \core\di::get(\core\formatting::class)->format_text(...$params);
}
 
/**
 * Resets some data related to filters, called during upgrade or when general filter settings change.
 *
 * @param bool $phpunitreset true means called from our PHPUnit integration test reset
 * @return void
 */
function reset_text_filters_cache($phpunitreset = false) {
    global $CFG, $DB;
 
    if ($phpunitreset) {
        // HTMLPurifier does not change, DB is already reset to defaults,
        // nothing to do here, the dataroot was cleared too.
        return;
    }
 
    // The purge_all_caches() deals with cachedir and localcachedir purging,
    // the individual filter caches are invalidated as necessary elsewhere.
 
/opt/moodle/lib/modinfolib.php
     */
    public function get_formatted_content($options = array()) {
        $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).
/opt/moodle/course/format/classes/output/local/content/cm.php
        $availability = new $this->availabilityclass(
            $this->format,
            $this->section,
            $this->mod,
            $this->displayoptions
        );
        $modavailability = $availability->export_for_template($output);
        $data->modavailability = $modavailability ?? false;
        return $availability->has_availability($output);
    }
 
    /**
     * Add the alternative content to the data structure.
     *
     * @param stdClass $data the current cm data reference
     * @param renderer_base $output typically, the renderer that's calling this function
     * @return bool if the cm has alternative content
     */
    protected function add_alternative_content_data(stdClass &$data, renderer_base $output): bool {
        $altcontent = $this->mod->get_formatted_content(
            ['overflowdiv' => true, 'noclean' => true]
        );
        $data->altcontent = (empty($altcontent)) ? false : $altcontent;
        $data->afterlink = $this->mod->afterlink;
 
        $activitybadgedata = $this->mod->get_activitybadge($output);
        if (!empty($activitybadgedata)) {
            $data->activitybadge = $activitybadgedata;
        }
 
        return !empty($data->altcontent);
    }
 
    /**
     * Add activity dates information to the data structure.
     *
     * @param stdClass $data the current cm data reference
     * @param renderer_base $output typically, the renderer that's calling this function
     * @return bool the module has completion information
     */
/opt/moodle/course/format/classes/output/local/content/cm.php
        $displayoptions = $this->displayoptions;
 
        $data = (object)[
            'grouping' => $mod->get_grouping_label($displayoptions['textclasses']),
            'modname' => get_string('pluginname', 'mod_' . $mod->modname),
            'url' => $mod->url,
            'activityname' => $mod->get_formatted_name(),
            'textclasses' => $displayoptions['textclasses'],
            'classlist' => [],
            'cmid' => $mod->id,
            'editing' => $PAGE->user_is_editing(),
            'sectionnum' => $this->section->section,
            'cmbulk' => !$mod->get_delegated_section_info(),
        ];
 
        // Add partial data segments.
        $haspartials = [];
        $haspartials['cmname'] = $this->add_cm_name_data($data, $output);
        $haspartials['availability'] = $this->add_availability_data($data, $output);
        $haspartials['alternative'] = $this->add_alternative_content_data($data, $output);
        $haspartials['completion'] = $this->add_completion_data($data, $output);
        $haspartials['dates'] = $this->add_dates_data($data, $output);
        $haspartials['editor'] = $this->add_editor_data($data, $output);
        $haspartials['groupmode'] = $this->add_groupmode_data($data, $output);
        $haspartials['visibility'] = $this->add_visibility_data($data, $output);
        $this->add_actvitychooserbutton_data($data, $output);
        $this->add_format_data($data, $haspartials, $output);
 
        // Calculated fields.
        if (!empty($data->url)) {
            $data->hasurl = true;
        }
        return $data;
    }
 
    /**
     * Add course module name attributes to the data structure.
     *
     * @param stdClass $data the current cm data reference
     * @param renderer_base $output typically, the renderer that's calling this function
/opt/moodle/course/format/classes/output/local/content/section/cmitem.php
        $course = $format->get_course();
        $mod = $this->mod;
 
        $data = new stdClass();
        $data->cms = [];
 
        $completionenabled = $course->enablecompletion == COMPLETION_ENABLED;
        $showactivityconditions = $completionenabled && $course->showcompletionconditions == COMPLETION_SHOW_CONDITIONS;
        $showactivitydates = !empty($course->showactivitydates);
 
        // This will apply styles to the course homepage when the activity information output component is displayed.
        $hasinfo = $showactivityconditions || $showactivitydates;
 
        $item = new $this->cmclass($format, $this->section, $mod, $this->displayoptions);
        return (object)[
            'id' => $mod->id,
            'anchor' => "module-{$mod->id}",
            'module' => $mod->modname,
            'extraclasses' => $mod->extraclasses,
            'cmformat' => $item->export_for_template($output),
            'hasinfo' => $hasinfo,
            'indent' => ($format->uses_indentation()) ? $mod->indent : 0,
            'groupmode' => $mod->groupmode,
        ];
    }
}
 
/opt/moodle/course/format/classes/output/local/content/section/cmlist.php
            $data->strmovefull = strip_tags(get_string("movefull", "", "'$user->activitycopyname'"));
            $data->movetosectionurl = new moodle_url('/course/mod.php', ['movetosection' => $section->id, 'sesskey' => sesskey()]);
            $data->movingstr = strip_tags(get_string('activityclipboard', '', $user->activitycopyname));
            $data->cancelcopyurl = new moodle_url('/course/mod.php', ['cancelcopy' => 'true', 'sesskey' => sesskey()]);
        }
 
        if (empty($modinfo->sections[$section->section])) {
            return $data;
        }
 
        foreach ($modinfo->sections[$section->section] as $modnumber) {
            $mod = $modinfo->cms[$modnumber];
            // If the old non-ajax move is necessary, we do not print the selected cm.
            if ($showmovehere && $USER->activitycopy == $mod->id) {
                continue;
            }
            if ($mod->is_visible_on_course_page()) {
                $item = new $this->itemclass($format, $section, $mod, $this->displayoptions);
                $data->cms[] = (object)[
                    'cmitem' => $item->export_for_template($output),
                    'moveurl' => new moodle_url('/course/mod.php', array('moveto' => $modnumber, 'sesskey' => sesskey())),
                ];
            }
        }
 
        if (!empty($data->cms)) {
            $data->hascms = true;
        }
 
        return $data;
    }
}
 
/opt/moodle/course/format/classes/output/local/content/section.php
        );
 
        $showcmlist = $section->uservisible;
 
        // Add activities summary if necessary.
        if ($showsummary) {
            $cmsummary = new $this->cmsummaryclass($format, $section);
            $data->cmsummary = $cmsummary->export_for_template($output);
            $data->onlysummary = true;
            $result = true;
 
            if (!$format->is_section_current($section)) {
                // In multipage, only the current section (and the section zero) has elements.
                $showcmlist = false;
            }
        }
        // Add the cm list.
        if ($showcmlist) {
            $cmlist = new $this->cmlistclass($format, $section);
            $data->cmlist = $cmlist->export_for_template($output);
            $result = true;
        }
        return $result;
    }
 
    /**
     * Add the section availability to the data structure.
     *
     * @param stdClass $data the current cm data reference
     * @param renderer_base $output typically, the renderer that's calling this function
     * @return bool if the cm has name data
     */
    protected function add_availability_data(stdClass &$data, renderer_base $output): bool {
        $availability = new $this->availabilityclass($this->format, $this->section);
        $data->availability = $availability->export_for_template($output);
        $data->restrictionlock = !empty($this->section->availableinfo);
        $data->hasavailability = $availability->has_availability($output);
        return true;
    }
 
/opt/moodle/course/format/classes/output/local/content/section.php
        $data = (object)[
            'num' => $section->section ?? '0',
            'id' => $section->id,
            'sectionreturnnum' => $format->get_sectionnum(),
            'insertafter' => false,
            'summary' => $summary->export_for_template($output),
            'highlightedlabel' => $format->get_section_highlighted_name(),
            'sitehome' => $course->id == SITEID,
            'editing' => $PAGE->user_is_editing(),
            'displayonesection' => ($course->id != SITEID && $format->get_sectionid() == $section->id),
            // Section name is used as data attribute is to facilitate behat locators.
            'sectionname' => $format->get_section_name($section),
        ];
 
        $haspartials = [];
        $haspartials['availability'] = $this->add_availability_data($data, $output);
        $haspartials['visibility'] = $this->add_visibility_data($data, $output);
        $haspartials['editor'] = $this->add_editor_data($data, $output);
        $haspartials['header'] = $this->add_header_data($data, $output);
        $haspartials['cm'] = $this->add_cm_data($data, $output);
        $this->add_format_data($data, $haspartials, $output);
 
        return $data;
    }
 
    /**
     * Add the section header to the data structure.
     *
     * @param stdClass $data the current cm data reference
     * @param renderer_base $output typically, the renderer that's calling this function
     * @return bool if the cm has name data
     */
    protected function add_header_data(stdClass &$data, renderer_base $output): bool {
        if (!empty($this->hidetitle)) {
            return false;
        }
 
        $section = $this->section;
        $format = $this->format;
 
/opt/moodle/course/format/topics/classes/output/courseformat/content/section.php
use core_courseformat\base as course_format;
use core_courseformat\output\local\content\section as section_base;
use stdClass;
 
/**
 * Base class to render a course section.
 *
 * @package   format_topics
 * @copyright 2020 Ferran Recio <ferran@moodle.com>
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
class section extends section_base {
 
    /** @var course_format the course format */
    protected $format;
 
    public function export_for_template(\renderer_base $output): stdClass {
        $format = $this->format;
 
        $data = parent::export_for_template($output);
 
        if (!$this->format->get_sectionnum() && !$this->section->is_delegated()) {
            $addsectionclass = $format->get_output_classname('content\\addsection');
            $addsection = new $addsectionclass($format);
            $data->numsections = $addsection->export_for_template($output);
            $data->insertafter = true;
        }
 
        return $data;
    }
}
 
/opt/moodle/course/format/classes/output/local/content.php
                throw new \moodle_exception('unknowncoursesection', 'error', course_get_url($course),
                    format_string($course->fullname));
            }
 
            if (!$format->is_section_visible($thissection)) {
                continue;
            }
 
            /** @var \core_courseformat\output\local\content\section $section */
            $section = new $this->sectionclass($format, $thissection);
 
            if ($section->is_stealth()) {
                // Activities inside this section are 'orphaned', this section will be printed as 'stealth' below.
                if (!empty($modinfo->sections[$sectionnum])) {
                    $stealthsections[] = $section->export_for_template($output);
                }
                continue;
            }
 
            $sections[] = $section->export_for_template($output);
        }
        if (!empty($stealthsections)) {
            $sections = array_merge($sections, $stealthsections);
        }
        return $sections;
    }
 
    /**
     * Return an array of sections to display.
     *
     * This method is used to differentiate between display a specific section
     * or a list of them.
     *
     * @param course_modinfo $modinfo the current course modinfo object
     * @return section_info[] an array of section_info to display
     */
    private function get_sections_to_display(course_modinfo $modinfo): array {
        $singlesectionid = $this->format->get_sectionid();
        if ($singlesectionid) {
            return [
/opt/moodle/course/format/classes/output/local/content.php
        // Load output classes names from format.
        $this->sectionclass = $format->get_output_classname('content\\section');
        $this->addsectionclass = $format->get_output_classname('content\\addsection');
        $this->sectionnavigationclass = $format->get_output_classname('content\\sectionnavigation');
        $this->sectionselectorclass = $format->get_output_classname('content\\sectionselector');
        $this->bulkedittoolsclass = $format->get_output_classname('content\\bulkedittools');
        $this->sectioncontrolmenuclass = $format->get_output_classname('content\\section\\controlmenu');
    }
 
    /**
     * Export this data so it can be used as the context for a mustache template (core/inplace_editable).
     *
     * @param \renderer_base $output typically, the renderer that's calling this function
     * @return \stdClass data context for a mustache template
     */
    public function export_for_template(\renderer_base $output) {
        global $PAGE;
        $format = $this->format;
 
        $sections = $this->export_sections($output);
        $initialsection = '';
 
        $data = (object)[
            'title' => $format->page_title(), // This method should be in the course_format class.
            'initialsection' => $initialsection,
            'sections' => $sections,
            'format' => $format->get_format(),
            'sectionreturn' => 'null', // Mustache templates don't display NULL, so pass a string value.
            'pagesectionid' => $this->format->get_sectionid() ?? 'null', // Pass a string value if NULL.
        ];
 
        // The single section format has extra navigation.
        if ($this->format->get_sectionid()) {
            $singlesectionnum = $this->format->get_sectionnum();
            if (!$PAGE->theme->usescourseindex) {
                $sectionnavigation = new $this->sectionnavigationclass($format, $singlesectionnum);
                $data->sectionnavigation = $sectionnavigation->export_for_template($output);
 
                $sectionselector = new $this->sectionselectorclass($format, $sectionnavigation);
                $data->sectionselector = $sectionselector->export_for_template($output);
/opt/moodle/course/format/topics/classes/output/courseformat/content.php
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
class content extends content_base {
 
    /**
     * @var bool Topic format has also add section after each topic.
     */
    protected $hasaddsection = true;
 
    /**
     * Export this data so it can be used as the context for a mustache template (core/inplace_editable).
     *
     * @param renderer_base $output typically, the renderer that's calling this function
     * @return stdClass data context for a mustache template
     */
    public function export_for_template(renderer_base $output) {
        global $PAGE;
        $PAGE->requires->js_call_amd('format_topics/mutations', 'init');
        $PAGE->requires->js_call_amd('format_topics/section', 'init');
        return parent::export_for_template($output);
    }
 
}
 
/opt/moodle/lib/classes/output/plugin_renderer_base.php
        // Keep a copy at this point, we may need to look for a deprecated method.
        $deprecatedmethod = "render_{$classname}";
 
        // Remove _renderable suffixes.
        $classname = preg_replace('/_renderable$/', '', $classname);
        $rendermethod = "render_{$classname}";
 
        if (method_exists($this, $rendermethod)) {
            // Call the render_[widget_name] function.
            // Note: This has a higher priority than the named_templatable to allow the theme to override the template.
            return $this->$rendermethod($widget);
        }
 
        if ($widget instanceof named_templatable) {
            // This is a named templatable.
            // Fetch the template name from the get_template_name function instead.
            // Note: This has higher priority than the deprecated method which is not overridable by themes anyway.
            return $this->render_from_template(
                $widget->get_template_name($this),
                $widget->export_for_template($this)
            );
        }
 
        if ($rendermethod !== $deprecatedmethod && method_exists($this, $deprecatedmethod)) {
            // This is exactly where we don't want to be.
            // If you have arrived here you have a renderable component within your plugin that has the name
            // blah_renderable, and you have a render method render_blah_renderable on your plugin.
            // In 2.8 we revamped output, as part of this change we changed slightly how renderables got rendered
            // and the _renderable suffix now gets removed when looking for a render method.
            // You need to change your renderers render_blah_renderable to render_blah.
            // Until you do this it will not be possible for a theme to override the renderer to override your method.
            // Please do it ASAP.
            static $debugged = [];
            if (!isset($debugged[$deprecatedmethod])) {
                debugging(sprintf(
                    'Deprecated call. Please rename your renderables render method from %s to %s.',
                    $deprecatedmethod,
                    $rendermethod
                ), DEBUG_DEVELOPER);
                $debugged[$deprecatedmethod] = true;
/opt/moodle/course/format/classes/output/section_renderer.php
     *
     * @param renderable $widget instance with renderable interface
     * @return string the widget HTML
     */
    public function render(renderable $widget) {
        global $CFG;
        $fullpath = str_replace('\\', '/', get_class($widget));
        $classparts = explode('/', $fullpath);
        // Strip namespaces.
        $classname = array_pop($classparts);
        // Remove _renderable suffixes.
        $classname = preg_replace('/_renderable$/', '', $classname);
 
        $rendermethod = 'render_' . $classname;
        if (method_exists($this, $rendermethod)) {
            return $this->$rendermethod($widget);
        }
 
        // If nothing works, let the parent class decide.
        return parent::render($widget);
    }
 
    /**
     * Generate the section title, wraps it in a link to the section page if page is to be displayed on a separate page
     *
     * @param stdClass $section The course_section entry from DB
     * @param stdClass $course The course entry from DB
     * @return string HTML to output.
     */
    public function section_title($section, $course) {
        $title = get_section_name($course, $section);
        $url = course_get_url($course, $section->section, array('navigation' => true));
        if ($url) {
            $title = html_writer::link($url, $title);
        }
        return $title;
    }
 
    /**
     * Generate the section title to be displayed on the section page, without a link
/opt/moodle/course/format/topics/format.php
$format = course_get_format($course);
$course = $format->get_course();
$context = context_course::instance($course->id);
 
if (($marker >= 0) && has_capability('moodle/course:setcurrentsection', $context) && confirm_sesskey()) {
    $course->marker = $marker;
    course_set_marker($course->id, $marker);
}
 
// Make sure section 0 is created.
course_create_sections_if_missing($course, 0);
 
$renderer = $PAGE->get_renderer('format_topics');
 
if (!is_null($displaysection)) {
    $format->set_sectionnum($displaysection);
}
$outputclass = $format->get_output_classname('content');
$widget = new $outputclass($format);
echo $renderer->render($widget);
 
/opt/moodle/course/view.php
 
// Get information about course modules and existing module types.
// format.php in course formats may rely on presence of these variables.
$modinfo = get_fast_modinfo($course);
$modnames = get_module_types_names();
$modnamesplural = get_module_types_names(true);
$modnamesused = $modinfo->get_used_module_names();
$mods = $modinfo->get_cms();
$sections = $modinfo->get_section_info_all();
 
// CAUTION, hacky fundamental variable defintion to follow!
// Note that because of the way course fromats are constructed though
// inclusion we pass parameters around this way.
$displaysection = $section;
 
// Include course AJAX.
include_course_ajax($course, $modnamesused);
 
// Include the actual course format.
require($CFG->dirroot .'/course/format/'. $course->format .'/format.php');
// Content wrapper end.
 
echo html_writer::end_tag('div');
 
// Trigger course viewed event.
// We don't trust $context here. Course format inclusion above executes in the global space. We can't assume
// anything after that point.
course_view(context_course::instance($course->id), $section);
 
// If available, include the JS to prepare the download course content modal.
if ($candownloadcourse) {
    $PAGE->requires->js_call_amd('core_course/downloadcontent', 'init');
}
 
// Load the view JS module if completion tracking is enabled for this course.
$completion = new completion_info($course);
if ($completion->is_enabled()) {
    $PAGE->requires->js_call_amd('core_course/view', 'init');
}
 

Environment & details:

Key Value
id 51
lang zh_tw
empty
empty
empty
Key Value
USER stdClass Object ( [id] => 1 [auth] => manual [confirmed] => 1 [policyagreed] => 0 [deleted] => 0 [suspended] => 0 [mnethostid] => 1 [username] => guest [idnumber] => [firstname] => 訪客用戶 [lastname] => [email] => root@localhost [emailstop] => 0 [phone1] => [phone2] => [institution] => [department] => [address] => [city] => [country] => [lang] => zh_tw [calendartype] => gregorian [theme] => [timezone] => 99 [firstaccess] => 0 [lastaccess] => 0 [lastlogin] => 0 [currentlogin] => 0 [lastip] => [secret] => [picture] => 0 [descriptionformat] => 1 [mailformat] => 1 [maildigest] => 0 [maildisplay] => 2 [autosubscribe] => 1 [trackforums] => 0 [timecreated] => 0 [timemodified] => 1566869212 [trustbitmask] => 0 [imagealt] => [lastnamephonetic] => [firstnamephonetic] => [middlename] => [alternatename] => [moodlenetprofile] => [lastcourseaccess] => Array ( ) [currentcourseaccess] => Array ( ) [profile] => Array ( ) [sesskey] => Uen4RdlqEt [preference] => Array ( ) [autologinguest] => 1 [access] => Array ( [ra] => Array ( [/1] => Array ( [6] => 6 ) [/1/483/484] => Array ( [6] => 6 ) ) [time] => 1761864820 [rsw] => Array ( ) ) [enrol] => Array ( [enrolled] => Array ( ) [tempguest] => Array ( [51] => 2147483647 ) ) [editing] => 0 )
SESSION stdClass Object ( [isnewsessioncookie] => 1 [cachestore_session] => Array ( [default_session-core/courseeditorstate] => Array ( [u1_05ar29slv22flhrfguhbpfvjdo_51-9c2da51ccf478beee423a2ff95f19327] => Array ( [0] => 1760360671_1761864820 [1] => 0 ) ) [default_session-core/navigation_cache] => Array ( [__lastaccess__u1_qbm5kbi3em9f87438d202g68d8] => Array ( [0] => 1761864820 [1] => 1761864820 ) ) [default_session-core/coursecat] => Array ( [__lastaccess__u1_qbm5kbi3em9f87438d202g68d8] => Array ( [0] => 1761864820 [1] => 1761864820 ) [u1_qbm5kbi3em9f87438d202g68d8_ddc4bc4754192dc4e0b41497b22ffd5dd660bcf6] => Array ( [0] => 1761864820.8209-6903ec74c86815.20719191 [1] => 1761864820 ) [u1_qbm5kbi3em9f87438d202g68d8_a432eb3ace283db7eeab490890072df5526386cd] => Array ( [0] => Array ( [0] => 1 [1] => 2 ) [1] => 1761864820 ) [u1_qbm5kbi3em9f87438d202g68d8_f0ce9beeda1b212e5bdf3402a555b5c93aafce2f] => Array ( [0] => Array ( [0] => 6 [1] => 3 ) [1] => 1761864820 ) ) ) [lang] => zh_tw [fromdiscussion] => https://moodle.openlearner.org/course/view.php?id=51 )
Key Value
USER www-data
HOME /var/www
HTTP_HOST moodle.openlearner.org
HTTP_ACCEPT_ENCODING gzip, br, zstd, deflate
HTTP_USER_AGENT Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; ClaudeBot/1.0; +claudebot@anthropic.com)
HTTP_ACCEPT */*
SCRIPT_FILENAME /opt/moodle/course/view.php
PATH_INFO
REDIRECT_STATUS 200
SERVER_NAME moodle.openlearner.org
SERVER_PORT 443
SERVER_ADDR 204.48.16.209
REMOTE_USER
REMOTE_PORT 10910
REMOTE_ADDR 216.73.216.56
SERVER_SOFTWARE nginx/1.26.3
GATEWAY_INTERFACE CGI/1.1
HTTPS on
REQUEST_SCHEME https
SERVER_PROTOCOL HTTP/1.1
DOCUMENT_ROOT /opt/moodle
DOCUMENT_URI /course/view.php
REQUEST_URI /course/view.php?id=51&lang=zh_tw
SCRIPT_NAME /course/view.php
CONTENT_LENGTH
CONTENT_TYPE
REQUEST_METHOD GET
QUERY_STRING id=51&lang=zh_tw
FCGI_ROLE RESPONDER
PHP_SELF /course/view.php
REQUEST_TIME_FLOAT 1761864820.6369
REQUEST_TIME 1761864820
empty
0. Whoops\Handler\PrettyPageHandler
1. Whoops\Handler\CallbackHandler