The Database Document Converter Field
Whoops \ Exception \ ErrorException
(E_USER_NOTICE)
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 (& " < >) 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(' ', ' ', $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