/lib/outputrenderers.php
PHP | 5473 lines | 2923 code | 563 blank | 1987 comment | 611 complexity | e1717d9a56c505af49a5c0f2c2e92c7a MD5 | raw file
Possible License(s): BSD-3-Clause, MIT, GPL-3.0, Apache-2.0, LGPL-2.1
- <?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/>.
- /**
- * Classes for rendering HTML output for Moodle.
- *
- * Please see {@link http://docs.moodle.org/en/Developement:How_Moodle_outputs_HTML}
- * for an overview.
- *
- * Included in this file are the primary renderer classes:
- * - renderer_base: The renderer outline class that all renderers
- * should inherit from.
- * - core_renderer: The standard HTML renderer.
- * - core_renderer_cli: An adaption of the standard renderer for CLI scripts.
- * - core_renderer_ajax: An adaption of the standard renderer for AJAX scripts.
- * - plugin_renderer_base: A renderer class that should be extended by all
- * plugin renderers.
- *
- * @package core
- * @category output
- * @copyright 2009 Tim Hunt
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
- use core_completion\cm_completion_details;
- use core_course\output\activity_information;
- defined('MOODLE_INTERNAL') || die();
- /**
- * Simple base class for Moodle renderers.
- *
- * Tracks the xhtml_container_stack to use, which is passed in in the constructor.
- *
- * Also has methods to facilitate generating HTML output.
- *
- * @copyright 2009 Tim Hunt
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- * @since Moodle 2.0
- * @package core
- * @category output
- */
- class renderer_base {
- /**
- * @var xhtml_container_stack The xhtml_container_stack to use.
- */
- protected $opencontainers;
- /**
- * @var moodle_page The Moodle page the renderer has been created to assist with.
- */
- protected $page;
- /**
- * @var string The requested rendering target.
- */
- protected $target;
- /**
- * @var Mustache_Engine $mustache The mustache template compiler
- */
- private $mustache;
- /**
- * Return an instance of the mustache class.
- *
- * @since 2.9
- * @return Mustache_Engine
- */
- protected function get_mustache() {
- global $CFG;
- if ($this->mustache === null) {
- require_once("{$CFG->libdir}/filelib.php");
- $themename = $this->page->theme->name;
- $themerev = theme_get_revision();
- // Create new localcache directory.
- $cachedir = make_localcache_directory("mustache/$themerev/$themename");
- // Remove old localcache directories.
- $mustachecachedirs = glob("{$CFG->localcachedir}/mustache/*", GLOB_ONLYDIR);
- foreach ($mustachecachedirs as $localcachedir) {
- $cachedrev = [];
- preg_match("/\/mustache\/([0-9]+)$/", $localcachedir, $cachedrev);
- $cachedrev = isset($cachedrev[1]) ? intval($cachedrev[1]) : 0;
- if ($cachedrev > 0 && $cachedrev < $themerev) {
- fulldelete($localcachedir);
- }
- }
- $loader = new \core\output\mustache_filesystem_loader();
- $stringhelper = new \core\output\mustache_string_helper();
- $cleanstringhelper = new \core\output\mustache_clean_string_helper();
- $quotehelper = new \core\output\mustache_quote_helper();
- $jshelper = new \core\output\mustache_javascript_helper($this->page);
- $pixhelper = new \core\output\mustache_pix_helper($this);
- $shortentexthelper = new \core\output\mustache_shorten_text_helper();
- $userdatehelper = new \core\output\mustache_user_date_helper();
- // We only expose the variables that are exposed to JS templates.
- $safeconfig = $this->page->requires->get_config_for_javascript($this->page, $this);
- $helpers = array('config' => $safeconfig,
- 'str' => array($stringhelper, 'str'),
- 'cleanstr' => array($cleanstringhelper, 'cleanstr'),
- 'quote' => array($quotehelper, 'quote'),
- 'js' => array($jshelper, 'help'),
- 'pix' => array($pixhelper, 'pix'),
- 'shortentext' => array($shortentexthelper, 'shorten'),
- 'userdate' => array($userdatehelper, 'transform'),
- );
- $this->mustache = new \core\output\mustache_engine(array(
- 'cache' => $cachedir,
- 'escape' => 's',
- 'loader' => $loader,
- 'helpers' => $helpers,
- 'pragmas' => [Mustache_Engine::PRAGMA_BLOCKS],
- // Don't allow the JavaScript helper to be executed from within another
- // helper. If it's allowed it can be used by users to inject malicious
- // JS into the page.
- 'disallowednestedhelpers' => ['js']));
- }
- return $this->mustache;
- }
- /**
- * Constructor
- *
- * The constructor takes two arguments. The first is the page that the renderer
- * has been created to assist with, and the second is the target.
- * The target is an additional identifier that can be used to load different
- * renderers for different options.
- *
- * @param moodle_page $page the page we are doing output for.
- * @param string $target one of rendering target constants
- */
- public function __construct(moodle_page $page, $target) {
- $this->opencontainers = $page->opencontainers;
- $this->page = $page;
- $this->target = $target;
- }
- /**
- * Renders a template by name with the given context.
- *
- * The provided data needs to be array/stdClass made up of only simple types.
- * Simple types are array,stdClass,bool,int,float,string
- *
- * @since 2.9
- * @param array|stdClass $context Context containing data for the template.
- * @return string|boolean
- */
- public function render_from_template($templatename, $context) {
- static $templatecache = array();
- $mustache = $this->get_mustache();
- try {
- // Grab a copy of the existing helper to be restored later.
- $uniqidhelper = $mustache->getHelper('uniqid');
- } catch (Mustache_Exception_UnknownHelperException $e) {
- // Helper doesn't exist.
- $uniqidhelper = null;
- }
- // Provide 1 random value that will not change within a template
- // but will be different from template to template. This is useful for
- // e.g. aria attributes that only work with id attributes and must be
- // unique in a page.
- $mustache->addHelper('uniqid', new \core\output\mustache_uniqid_helper());
- if (isset($templatecache[$templatename])) {
- $template = $templatecache[$templatename];
- } else {
- try {
- $template = $mustache->loadTemplate($templatename);
- $templatecache[$templatename] = $template;
- } catch (Mustache_Exception_UnknownTemplateException $e) {
- throw new moodle_exception('Unknown template: ' . $templatename);
- }
- }
- $renderedtemplate = trim($template->render($context));
- // If we had an existing uniqid helper then we need to restore it to allow
- // handle nested calls of render_from_template.
- if ($uniqidhelper) {
- $mustache->addHelper('uniqid', $uniqidhelper);
- }
- return $renderedtemplate;
- }
- /**
- * Returns rendered widget.
- *
- * The provided widget needs to be an object that extends the renderable
- * interface.
- * If will then be rendered by a method based upon the classname for the widget.
- * For instance a widget of class `crazywidget` will be rendered by a protected
- * render_crazywidget method of this renderer.
- * If no render_crazywidget method exists and crazywidget implements templatable,
- * look for the 'crazywidget' template in the same component and render that.
- *
- * @param renderable $widget instance with renderable interface
- * @return string
- */
- public function render(renderable $widget) {
- $classparts = explode('\\', get_class($widget));
- // 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 ($widget instanceof templatable) {
- $component = array_shift($classparts);
- if (!$component) {
- $component = 'core';
- }
- $template = $component . '/' . $classname;
- $context = $widget->export_for_template($this);
- return $this->render_from_template($template, $context);
- }
- throw new coding_exception('Can not render widget, renderer method ('.$rendermethod.') not found.');
- }
- /**
- * Adds a JS action for the element with the provided id.
- *
- * This method adds a JS event for the provided component action to the page
- * and then returns the id that the event has been attached to.
- * If no id has been provided then a new ID is generated by {@link html_writer::random_id()}
- *
- * @param component_action $action
- * @param string $id
- * @return string id of element, either original submitted or random new if not supplied
- */
- public function add_action_handler(component_action $action, $id = null) {
- if (!$id) {
- $id = html_writer::random_id($action->event);
- }
- $this->page->requires->event_handler("#$id", $action->event, $action->jsfunction, $action->jsfunctionargs);
- return $id;
- }
- /**
- * Returns true is output has already started, and false if not.
- *
- * @return boolean true if the header has been printed.
- */
- public function has_started() {
- return $this->page->state >= moodle_page::STATE_IN_BODY;
- }
- /**
- * Given an array or space-separated list of classes, prepares and returns the HTML class attribute value
- *
- * @param mixed $classes Space-separated string or array of classes
- * @return string HTML class attribute value
- */
- public static function prepare_classes($classes) {
- if (is_array($classes)) {
- return implode(' ', array_unique($classes));
- }
- return $classes;
- }
- /**
- * Return the direct URL for an image from the pix folder.
- *
- * Use this function sparingly and never for icons. For icons use pix_icon or the pix helper in a mustache template.
- *
- * @deprecated since Moodle 3.3
- * @param string $imagename the name of the icon.
- * @param string $component specification of one plugin like in get_string()
- * @return moodle_url
- */
- public function pix_url($imagename, $component = 'moodle') {
- debugging('pix_url is deprecated. Use image_url for images and pix_icon for icons.', DEBUG_DEVELOPER);
- return $this->page->theme->image_url($imagename, $component);
- }
- /**
- * Return the moodle_url for an image.
- *
- * The exact image location and extension is determined
- * automatically by searching for gif|png|jpg|jpeg, please
- * note there can not be diferent images with the different
- * extension. The imagename is for historical reasons
- * a relative path name, it may be changed later for core
- * images. It is recommended to not use subdirectories
- * in plugin and theme pix directories.
- *
- * There are three types of images:
- * 1/ theme images - stored in theme/mytheme/pix/,
- * use component 'theme'
- * 2/ core images - stored in /pix/,
- * overridden via theme/mytheme/pix_core/
- * 3/ plugin images - stored in mod/mymodule/pix,
- * overridden via theme/mytheme/pix_plugins/mod/mymodule/,
- * example: image_url('comment', 'mod_glossary')
- *
- * @param string $imagename the pathname of the image
- * @param string $component full plugin name (aka component) or 'theme'
- * @return moodle_url
- */
- public function image_url($imagename, $component = 'moodle') {
- return $this->page->theme->image_url($imagename, $component);
- }
- /**
- * Return the site's logo URL, if any.
- *
- * @param int $maxwidth The maximum width, or null when the maximum width does not matter.
- * @param int $maxheight The maximum height, or null when the maximum height does not matter.
- * @return moodle_url|false
- */
- public function get_logo_url($maxwidth = null, $maxheight = 200) {
- global $CFG;
- $logo = get_config('core_admin', 'logo');
- if (empty($logo)) {
- return false;
- }
- // 200px high is the default image size which should be displayed at 100px in the page to account for retina displays.
- // It's not worth the overhead of detecting and serving 2 different images based on the device.
- // Hide the requested size in the file path.
- $filepath = ((int) $maxwidth . 'x' . (int) $maxheight) . '/';
- // Use $CFG->themerev to prevent browser caching when the file changes.
- return moodle_url::make_pluginfile_url(context_system::instance()->id, 'core_admin', 'logo', $filepath,
- theme_get_revision(), $logo);
- }
- /**
- * Return the site's compact logo URL, if any.
- *
- * @param int $maxwidth The maximum width, or null when the maximum width does not matter.
- * @param int $maxheight The maximum height, or null when the maximum height does not matter.
- * @return moodle_url|false
- */
- public function get_compact_logo_url($maxwidth = 300, $maxheight = 300) {
- global $CFG;
- $logo = get_config('core_admin', 'logocompact');
- if (empty($logo)) {
- return false;
- }
- // Hide the requested size in the file path.
- $filepath = ((int) $maxwidth . 'x' . (int) $maxheight) . '/';
- // Use $CFG->themerev to prevent browser caching when the file changes.
- return moodle_url::make_pluginfile_url(context_system::instance()->id, 'core_admin', 'logocompact', $filepath,
- theme_get_revision(), $logo);
- }
- /**
- * Whether we should display the logo in the navbar.
- *
- * We will when there are no main logos, and we have compact logo.
- *
- * @return bool
- */
- public function should_display_navbar_logo() {
- $logo = $this->get_compact_logo_url();
- return !empty($logo) && !$this->should_display_main_logo();
- }
- /**
- * Whether we should display the main logo.
- *
- * @param int $headinglevel The heading level we want to check against.
- * @return bool
- */
- public function should_display_main_logo($headinglevel = 1) {
- // Only render the logo if we're on the front page or login page and the we have a logo.
- $logo = $this->get_logo_url();
- if ($headinglevel == 1 && !empty($logo)) {
- if ($this->page->pagelayout == 'frontpage' || $this->page->pagelayout == 'login') {
- return true;
- }
- }
- return false;
- }
- }
- /**
- * Basis for all plugin renderers.
- *
- * @copyright Petr Skoda (skodak)
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- * @since Moodle 2.0
- * @package core
- * @category output
- */
- class plugin_renderer_base extends renderer_base {
- /**
- * @var renderer_base|core_renderer A reference to the current renderer.
- * The renderer provided here will be determined by the page but will in 90%
- * of cases by the {@link core_renderer}
- */
- protected $output;
- /**
- * Constructor method, calls the parent constructor
- *
- * @param moodle_page $page
- * @param string $target one of rendering target constants
- */
- public function __construct(moodle_page $page, $target) {
- if (empty($target) && $page->pagelayout === 'maintenance') {
- // If the page is using the maintenance layout then we're going to force the target to maintenance.
- // This way we'll get a special maintenance renderer that is designed to block access to API's that are likely
- // unavailable for this page layout.
- $target = RENDERER_TARGET_MAINTENANCE;
- }
- $this->output = $page->get_renderer('core', null, $target);
- parent::__construct($page, $target);
- }
- /**
- * Renders the provided widget and returns the HTML to display it.
- *
- * @param renderable $widget instance with renderable interface
- * @return string
- */
- public function render(renderable $widget) {
- $classname = get_class($widget);
- // Strip namespaces.
- $classname = preg_replace('/^.*\\\/', '', $classname);
- // 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)) {
- return $this->$rendermethod($widget);
- }
- 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 = array();
- 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;
- }
- return $this->$deprecatedmethod($widget);
- }
- // pass to core renderer if method not found here
- return $this->output->render($widget);
- }
- /**
- * Magic method used to pass calls otherwise meant for the standard renderer
- * to it to ensure we don't go causing unnecessary grief.
- *
- * @param string $method
- * @param array $arguments
- * @return mixed
- */
- public function __call($method, $arguments) {
- if (method_exists('renderer_base', $method)) {
- throw new coding_exception('Protected method called against '.get_class($this).' :: '.$method);
- }
- if (method_exists($this->output, $method)) {
- return call_user_func_array(array($this->output, $method), $arguments);
- } else {
- throw new coding_exception('Unknown method called against '.get_class($this).' :: '.$method);
- }
- }
- }
- /**
- * The standard implementation of the core_renderer interface.
- *
- * @copyright 2009 Tim Hunt
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- * @since Moodle 2.0
- * @package core
- * @category output
- */
- class core_renderer extends renderer_base {
- /**
- * Do NOT use, please use <?php echo $OUTPUT->main_content() ?>
- * in layout files instead.
- * @deprecated
- * @var string used in {@link core_renderer::header()}.
- */
- const MAIN_CONTENT_TOKEN = '[MAIN CONTENT GOES HERE]';
- /**
- * @var string Used to pass information from {@link core_renderer::doctype()} to
- * {@link core_renderer::standard_head_html()}.
- */
- protected $contenttype;
- /**
- * @var string Used by {@link core_renderer::redirect_message()} method to communicate
- * with {@link core_renderer::header()}.
- */
- protected $metarefreshtag = '';
- /**
- * @var string Unique token for the closing HTML
- */
- protected $unique_end_html_token;
- /**
- * @var string Unique token for performance information
- */
- protected $unique_performance_info_token;
- /**
- * @var string Unique token for the main content.
- */
- protected $unique_main_content_token;
- /** @var custom_menu_item language The language menu if created */
- protected $language = null;
- /**
- * Constructor
- *
- * @param moodle_page $page the page we are doing output for.
- * @param string $target one of rendering target constants
- */
- public function __construct(moodle_page $page, $target) {
- $this->opencontainers = $page->opencontainers;
- $this->page = $page;
- $this->target = $target;
- $this->unique_end_html_token = '%%ENDHTML-'.sesskey().'%%';
- $this->unique_performance_info_token = '%%PERFORMANCEINFO-'.sesskey().'%%';
- $this->unique_main_content_token = '[MAIN CONTENT GOES HERE - '.sesskey().']';
- }
- /**
- * Get the DOCTYPE declaration that should be used with this page. Designed to
- * be called in theme layout.php files.
- *
- * @return string the DOCTYPE declaration that should be used.
- */
- public function doctype() {
- if ($this->page->theme->doctype === 'html5') {
- $this->contenttype = 'text/html; charset=utf-8';
- return "<!DOCTYPE html>\n";
- } else if ($this->page->theme->doctype === 'xhtml5') {
- $this->contenttype = 'application/xhtml+xml; charset=utf-8';
- return "<!DOCTYPE html>\n";
- } else {
- // legacy xhtml 1.0
- $this->contenttype = 'text/html; charset=utf-8';
- return ('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">' . "\n");
- }
- }
- /**
- * The attributes that should be added to the <html> tag. Designed to
- * be called in theme layout.php files.
- *
- * @return string HTML fragment.
- */
- public function htmlattributes() {
- $return = get_html_lang(true);
- $attributes = array();
- if ($this->page->theme->doctype !== 'html5') {
- $attributes['xmlns'] = 'http://www.w3.org/1999/xhtml';
- }
- // Give plugins an opportunity to add things like xml namespaces to the html element.
- // This function should return an array of html attribute names => values.
- $pluginswithfunction = get_plugins_with_function('add_htmlattributes', 'lib.php');
- foreach ($pluginswithfunction as $plugins) {
- foreach ($plugins as $function) {
- $newattrs = $function();
- unset($newattrs['dir']);
- unset($newattrs['lang']);
- unset($newattrs['xmlns']);
- unset($newattrs['xml:lang']);
- $attributes += $newattrs;
- }
- }
- foreach ($attributes as $key => $val) {
- $val = s($val);
- $return .= " $key=\"$val\"";
- }
- return $return;
- }
- /**
- * The standard tags (meta tags, links to stylesheets and JavaScript, etc.)
- * that should be included in the <head> tag. Designed to be called in theme
- * layout.php files.
- *
- * @return string HTML fragment.
- */
- public function standard_head_html() {
- global $CFG, $SESSION, $SITE;
- // Before we output any content, we need to ensure that certain
- // page components are set up.
- // Blocks must be set up early as they may require javascript which
- // has to be included in the page header before output is created.
- foreach ($this->page->blocks->get_regions() as $region) {
- $this->page->blocks->ensure_content_created($region, $this);
- }
- $output = '';
- // Give plugins an opportunity to add any head elements. The callback
- // must always return a string containing valid html head content.
- $pluginswithfunction = get_plugins_with_function('before_standard_html_head', 'lib.php');
- foreach ($pluginswithfunction as $plugins) {
- foreach ($plugins as $function) {
- $output .= $function();
- }
- }
- // Allow a url_rewrite plugin to setup any dynamic head content.
- if (isset($CFG->urlrewriteclass) && !isset($CFG->upgraderunning)) {
- $class = $CFG->urlrewriteclass;
- $output .= $class::html_head_setup();
- }
- $output .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />' . "\n";
- $output .= '<meta name="keywords" content="moodle, ' . $this->page->title . '" />' . "\n";
- // This is only set by the {@link redirect()} method
- $output .= $this->metarefreshtag;
- // Check if a periodic refresh delay has been set and make sure we arn't
- // already meta refreshing
- if ($this->metarefreshtag=='' && $this->page->periodicrefreshdelay!==null) {
- $output .= '<meta http-equiv="refresh" content="'.$this->page->periodicrefreshdelay.';url='.$this->page->url->out().'" />';
- }
- // Set up help link popups for all links with the helptooltip class
- $this->page->requires->js_init_call('M.util.help_popups.setup');
- $focus = $this->page->focuscontrol;
- if (!empty($focus)) {
- if (preg_match("#forms\['([a-zA-Z0-9]+)'\].elements\['([a-zA-Z0-9]+)'\]#", $focus, $matches)) {
- // This is a horrifically bad way to handle focus but it is passed in
- // through messy formslib::moodleform
- $this->page->requires->js_function_call('old_onload_focus', array($matches[1], $matches[2]));
- } else if (strpos($focus, '.')!==false) {
- // Old style of focus, bad way to do it
- debugging('This code is using the old style focus event, Please update this code to focus on an element id or the moodleform focus method.', DEBUG_DEVELOPER);
- $this->page->requires->js_function_call('old_onload_focus', explode('.', $focus, 2));
- } else {
- // Focus element with given id
- $this->page->requires->js_function_call('focuscontrol', array($focus));
- }
- }
- // Get the theme stylesheet - this has to be always first CSS, this loads also styles.css from all plugins;
- // any other custom CSS can not be overridden via themes and is highly discouraged
- $urls = $this->page->theme->css_urls($this->page);
- foreach ($urls as $url) {
- $this->page->requires->css_theme($url);
- }
- // Get the theme javascript head and footer
- if ($jsurl = $this->page->theme->javascript_url(true)) {
- $this->page->requires->js($jsurl, true);
- }
- if ($jsurl = $this->page->theme->javascript_url(false)) {
- $this->page->requires->js($jsurl);
- }
- // Get any HTML from the page_requirements_manager.
- $output .= $this->page->requires->get_head_code($this->page, $this);
- // List alternate versions.
- foreach ($this->page->alternateversions as $type => $alt) {
- $output .= html_writer::empty_tag('link', array('rel' => 'alternate',
- 'type' => $type, 'title' => $alt->title, 'href' => $alt->url));
- }
- // Add noindex tag if relevant page and setting applied.
- $allowindexing = isset($CFG->allowindexing) ? $CFG->allowindexing : 0;
- $loginpages = array('login-index', 'login-signup');
- if ($allowindexing == 2 || ($allowindexing == 0 && in_array($this->page->pagetype, $loginpages))) {
- if (!isset($CFG->additionalhtmlhead)) {
- $CFG->additionalhtmlhead = '';
- }
- $CFG->additionalhtmlhead .= '<meta name="robots" content="noindex" />';
- }
- if (!empty($CFG->additionalhtmlhead)) {
- $output .= "\n".$CFG->additionalhtmlhead;
- }
- if ($this->page->pagelayout == 'frontpage') {
- $summary = s(strip_tags(format_text($SITE->summary, FORMAT_HTML)));
- if (!empty($summary)) {
- $output .= "<meta name=\"description\" content=\"$summary\" />\n";
- }
- }
- return $output;
- }
- /**
- * The standard tags (typically skip links) that should be output just inside
- * the start of the <body> tag. Designed to be called in theme layout.php files.
- *
- * @return string HTML fragment.
- */
- public function standard_top_of_body_html() {
- global $CFG;
- $output = $this->page->requires->get_top_of_body_code($this);
- if ($this->page->pagelayout !== 'embedded' && !empty($CFG->additionalhtmltopofbody)) {
- $output .= "\n".$CFG->additionalhtmltopofbody;
- }
- // Give subsystems an opportunity to inject extra html content. The callback
- // must always return a string containing valid html.
- foreach (\core_component::get_core_subsystems() as $name => $path) {
- if ($path) {
- $output .= component_callback($name, 'before_standard_top_of_body_html', [], '');
- }
- }
- // Give plugins an opportunity to inject extra html content. The callback
- // must always return a string containing valid html.
- $pluginswithfunction = get_plugins_with_function('before_standard_top_of_body_html', 'lib.php');
- foreach ($pluginswithfunction as $plugins) {
- foreach ($plugins as $function) {
- $output .= $function();
- }
- }
- $output .= $this->maintenance_warning();
- return $output;
- }
- /**
- * Scheduled maintenance warning message.
- *
- * Note: This is a nasty hack to display maintenance notice, this should be moved
- * to some general notification area once we have it.
- *
- * @return string
- */
- public function maintenance_warning() {
- global $CFG;
- $output = '';
- if (isset($CFG->maintenance_later) and $CFG->maintenance_later > time()) {
- $timeleft = $CFG->maintenance_later - time();
- // If timeleft less than 30 sec, set the class on block to error to highlight.
- $errorclass = ($timeleft < 30) ? 'alert-error alert-danger' : 'alert-warning';
- $output .= $this->box_start($errorclass . ' moodle-has-zindex maintenancewarning m-3 alert');
- $a = new stdClass();
- $a->hour = (int)($timeleft / 3600);
- $a->min = (int)(($timeleft / 60) % 60);
- $a->sec = (int)($timeleft % 60);
- if ($a->hour > 0) {
- $output .= get_string('maintenancemodeisscheduledlong', 'admin', $a);
- } else {
- $output .= get_string('maintenancemodeisscheduled', 'admin', $a);
- }
- $output .= $this->box_end();
- $this->page->requires->yui_module('moodle-core-maintenancemodetimer', 'M.core.maintenancemodetimer',
- array(array('timeleftinsec' => $timeleft)));
- $this->page->requires->strings_for_js(
- array('maintenancemodeisscheduled', 'maintenancemodeisscheduledlong', 'sitemaintenance'),
- 'admin');
- }
- return $output;
- }
- /**
- * content that should be output in the footer area
- * of the page. Designed to be called in theme layout.php files.
- *
- * @return string HTML fragment.
- */
- public function standard_footer_html() {
- global $CFG, $SCRIPT;
- $output = '';
- if (during_initial_install()) {
- // Debugging info can not work before install is finished,
- // in any case we do not want any links during installation!
- return $output;
- }
- // Give plugins an opportunity to add any footer elements.
- // The callback must always return a string containing valid html footer content.
- $pluginswithfunction = get_plugins_with_function('standard_footer_html', 'lib.php');
- foreach ($pluginswithfunction as $plugins) {
- foreach ($plugins as $function) {
- $output .= $function();
- }
- }
- if (core_userfeedback::can_give_feedback()) {
- $output .= html_writer::div(
- $this->render_from_template('core/userfeedback_footer_link', ['url' => core_userfeedback::make_link()->out(false)])
- );
- }
- if ($this->page->devicetypeinuse == 'legacy') {
- // The legacy theme is in use print the notification
- $output .= html_writer::tag('div', get_string('legacythemeinuse'), array('class'=>'legacythemeinuse'));
- }
- // Get links to switch device types (only shown for users not on a default device)
- $output .= $this->theme_switch_links();
- return $output;
- }
- /**
- * Performance information and validation links for debugging.
- *
- * @return string HTML fragment.
- */
- public function debug_footer_html() {
- global $CFG;
- $output = '';
- if (during_initial_install()) {
- // Debugging info can not work before install is finished.
- return $output;
- }
- // This function is normally called from a layout.php file
- // but some of the content won't be known until later, so we return a placeholder
- // for now. This will be replaced with the real content in the footer.
- $output .= $this->unique_performance_info_token;
- if (!empty($CFG->debugpageinfo)) {
- $output .= '<div class="performanceinfo pageinfo">' . get_string('pageinfodebugsummary', 'core_admin',
- $this->page->debug_summary()) . '</div>';
- }
- if (debugging(null, DEBUG_DEVELOPER) and has_capability('moodle/site:config', context_system::instance())) { // Only in developer mode
- // Add link to profiling report if necessary
- if (function_exists('profiling_is_running') && profiling_is_running()) {
- $txt = get_string('profiledscript', 'admin');
- $title = get_string('profiledscriptview', 'admin');
- $url = $CFG->wwwroot . '/admin/tool/profiling/index.php?script=' . urlencode($SCRIPT);
- $link= '<a title="' . $title . '" href="' . $url . '">' . $txt . '</a>';
- $output .= '<div class="profilingfooter">' . $link . '</div>';
- }
- $purgeurl = new moodle_url('/admin/purgecaches.php', array('confirm' => 1,
- 'sesskey' => sesskey(), 'returnurl' => $this->page->url->out_as_local_url(false)));
- $output .= '<div class="purgecaches">' .
- html_writer::link($purgeurl, get_string('purgecaches', 'admin')) . '</div>';
- // Reactive module debug panel.
- $output .= $this->render_from_template('core/local/reactive/debugpanel', []);
- }
- if (!empty($CFG->debugvalidators)) {
- // NOTE: this is not a nice hack, $this->page->url is not always accurate and
- // $FULLME neither, it is not a bug if it fails. --skodak.
- $output .= '<div class="validators"><ul class="list-unstyled ml-1">
- <li><a href="http://validator.w3.org/check?verbose=1&ss=1&uri=' . urlencode(qualified_me()) . '">Validate HTML</a></li>
- <li><a href="http://www.contentquality.com/mynewtester/cynthia.exe?rptmode=-1&url1=' . urlencode(qualified_me()) . '">Section 508 Check</a></li>
- <li><a href="http://www.contentquality.com/mynewtester/cynthia.exe?rptmode=0&warnp2n3e=1&url1=' . urlencode(qualified_me()) . '">WCAG 1 (2,3) Check</a></li>
- </ul></div>';
- }
- return $output;
- }
- /**
- * Returns standard main content placeholder.
- * Designed to be called in theme layout.php files.
- *
- * @return string HTML fragment.
- */
- public function main_content() {
- // This is here because it is the only place we can inject the "main" role over the entire main content area
- // without requiring all theme's to manually do it, and without creating yet another thing people need to
- // remember in the theme.
- // This is an unfortunate hack. DO NO EVER add anything more here.
- // DO NOT add classes.
- // DO NOT add an id.
- return '<div role="main">'.$this->unique_main_content_token.'</div>';
- }
- /**
- * Returns information about an activity.
- *
- * @param cm_info $cminfo The course module information.
- * @param cm_completion_details $completiondetails The completion details for this activity module.
- * @param array $activitydates The dates for this activity module.
- * @return string the activity information HTML.
- * @throws coding_exception
- */
- public function activity_information(cm_info $cminfo, cm_completion_details $completiondetails, array $activitydates): string {
- if (!$completiondetails->has_completion() && empty($activitydates)) {
- // No need to render the activity information when there's no completion info and activity dates to show.
- return '';
- }
- $activityinfo = new activity_information($cminfo, $completiondetails, $activitydates);
- $renderer = $this->page->get_renderer('core', 'course');
- return $renderer->render($activityinfo);
- }
- /**
- * Returns standard navigation between activities in a course.
- *
- * @return string the navigation HTML.
- */
- public function activity_navigation() {
- // First we should check if we want to add navigation.
- $context = $this->page->context;
- if (($this->page->pagelayout !== 'incourse' && $this->page->pagelayout !== 'frametop')
- || $context->contextlevel != CONTEXT_MODULE) {
- return '';
- }
- // If the activity is in stealth mode, show no links.
- if ($this->page->cm->is_stealth()) {
- return '';
- }
- $course = $this->page->cm->get_course();
- $courseformat = course_get_format($course);
- // If the theme implements course index and the current course format uses course index and the current
- // page layout is not 'frametop' (this layout does not support course index), show no links.
- if ($this->page->theme->usescourseindex && $courseformat->uses_course_index() &&
- $this->page->pagelayout !== 'frametop') {
- return '';
- }
- // Get a list of all the activities in the course.
- $modules = get_fast_modinfo($course->id)->get_cms();
- // Put the modules into an array in order by the position they are shown in the course.
- $mods = [];
- $activitylist = [];
- foreach ($modules as $module) {
- // Only add activities the user can access, aren't in stealth mode and have a url (eg. mod_label does not).
- if (!$module->uservisible || $module->is_stealth() || empty($module->url)) {
- continue;
- }
- $mods[$module->id] = $module;
- // No need to add the current module to the list for the activity dropdown menu.
- if ($module->id == $this->page->cm->id) {
- continue;
- }
- // Module name.
- $modname = $module->get_formatted_name();
- // Display the hidden text if necessary.
- if (!$module->visible) {
- $modname .= ' ' . get_string('hiddenwithbrackets');
- }
- // Module URL.
- $linkurl = new moodle_url($module->url, array('forceview' => 1));
- // Add module URL (as key) and name (as value) to the activity list array.
- $activitylist[$linkurl->out(false)] = $modname;
- }
- $nummods = count($mods);
- // If there is only one mod then do nothing.
- if ($nummods == 1) {
- return '';
- }
- // Get an array of just the course module ids used to get the cmid value based on their position in the course.
- $modids = array_keys($mods);
- // Get the position in the array of the course module we are viewing.
- $position = array_search($this->page->cm->id, $modids);
- $prevmod = null;
- $nextmod = null;
- // Check if we have a previous mod to show.
- if ($position > 0) {
- $prevmod = $mods[$modids[$position - 1]];
- }
- // Check if we have a next mod to show.
- if ($position < ($nummods - 1)) {
- $nextmod = $mods[$modids[$position + 1]];
- }
- $activitynav = new \core_course\output\activity_navigation($prevmod, $nextmod, $activitylist);
- $renderer = $this->page->get_renderer('core', 'course');
- return $renderer->render($activitynav);
- }
- /**
- * The standard tags (typically script tags that are not needed earlier) that
- * should be output after everything else. Designed to be called in theme layout.php files.
- *
- * @return string HTML fragment.
- */
- public function standard_end_of_body_html() {
- global $CFG;
- // This function is normally called from a layout.php file in {@link core_renderer::header()}
- // but some of the content won't be known until later, so we return a placeholder
- // for now. This will be replaced with the real content in {@link core_renderer::footer()}.
- $output = '';
- if ($this->page->pagelayout !== 'embedded' && !empty($CFG->additionalhtmlfooter)) {
- $output .= "\n".$CFG->additionalhtmlfooter;
- }
- $output .= $this->unique_end_html_token;
- return $output;
- }
- /**
- * The standard HTML that should be output just before the <footer> tag.
- * Designed to be called in theme layout.php files.
- *
- * @return string HTML fragment.
- */
- public function standard_after_main_region_html() {
- global $CFG;
- $output = '';
- if ($this->page->pagelayout !== 'embedded' && !empty($CFG->additionalhtmlbottomofbody)) {
- $output .= "\n".$CFG->additionalhtmlbottomofbody;
- }
- // Give subsystems an opportunity to inject extra html content. The callback
- // must always return a string containing valid html.
- foreach (\core_component::get_core_subsystems() as $name => $path) {
- if ($path) {
- $output .= component_callback($name, 'standard_after_main_region_html', [], '');
- }
- }
- // Give plugins an opportunity to inject extra html content. The callback
- // must always return a string containing valid html.
- $pluginswithfunction = get_plugins_with_function('standard_after_main_region_html', 'lib.php');
- foreach ($pluginswithfunction as $plugins) {
- foreach ($plugins as $function) {
- $output .= $function();
- }
- }
- return $output;
- }
- /**
- * Return the standard string that says whether you are logged in (and switched
- * roles/logged in as another user).
- * @param bool $withlinks if false, then don't include any links in the HTML produced.
- * If not set, the default is the nologinlinks option from the theme config.php file,
- * and if that is not set, then links are included.
- * @return string HTML fragment.
- */
- public function login_info($withlinks = null) {
- global $USER, $CFG, $DB, $SESSION;
- if (during_initial_install()) {
- return '';
- }
- if (is_null($withlinks)) {
- $withlinks = empty($this->page->layout_options['nologinlinks']);
- }
- $course = $this->page->course;
- if (\core\session\manager::is_loggedinas()) {
- $realuser = \core\session\manager::get_realuser();
- $fullname = fullname($realuser);
- if ($withlinks) {
- $loginastitle = get_string('loginas');
- $realuserinfo = " [<a href=\"$CFG->wwwroot/course/loginas.php?id=$course->id&sesskey=".sesskey()."\"";
- $realuserinfo .= "title =\"".$loginastitle."\">$fullname</a>] ";
- } else {
- $realuserinfo = " [$fullname] ";
- }
- } else {
- $realuserinfo = '';
- }
- $loginpage = $this->is_login_page();
- $loginurl = get_login_url();
- if (empty($course->id)) {
- // $course->id is not defined during installation
- return '';
- } else if (isloggedin()) {
- $context = context_course::instance($course->id);
- $fullname = fullname($USER);
- // Since Moodle 2.0 this link always goes to the public profile page (not the course profile page)
- if ($withlinks) {
- $linktitle = get_string('viewprofile');
- $username = "<a href=\"$CFG->wwwroot/user/profile.php?id=$USER->id\" title=\"$linktitle\">$fullname</a>";
- } else {
- $username = $fullname;
- }
- if (is_mnet_remote_user($USER) and $idprovider = $DB->get_record('mnet_host', array('id'=>$USER->mnethostid))) {
- if ($withlinks) {
- $username .= " from <a href=\"{$idprovider->wwwroot}\">{$idprovider->name}</a>";
- } else {
- $username .= " from {$idprovider->name}";
- }
- }
- if (isguestuser()) {
- $loggedinas = $realuserinfo.get_string('loggedinasguest');
- if (!$loginpage && $withlinks) {
- $loggedinas .= " (<a href=\"$loginurl\">".get_string('login').'</a>)';
- }
- } else if (is_role_switched($course->id)) { // Has switched roles
- $rolename = '';
- if ($role = $DB->get_record('role', array('id'=>$USER->access['rsw'][$context->path]))) {
- $rolename = ': '.role_get_name($role, $context);
- }
- $loggedinas = get_string('loggedinas', 'moodle', $username).$rolename;
- if ($withlinks) {
- $url = new moodle_url('/course/switchrole.php', array('id'=>$course->id,'sesskey'=>sesskey(), 'switchrole'=>0, 'returnurl'=>$this->page->url->out_as_local_url(false)));
- $loggedinas .= ' ('.html_writer::tag('a', get_string('switchrolereturn'), array('href' => $url)).')';
- }
- } else {
- $loggedinas = $realuserinfo.get_string('loggedinas', 'moodle', $username);
- if ($withlinks) {
- $loggedinas .= " (<a href=\"$CFG->wwwroot/login/logout.php?sesskey=".sesskey()."\">".get_string('logout').'</a>)';
- }
- }
- } else {
- $loggedinas = get_string('loggedinnot', 'moodle');
- if (!$loginpage && $withlinks) {
- $loggedinas .= " (<a href=\"$loginurl\">".get_string('login').'</a>)';
- }
- }
- $loggedinas = '<div class="logininfo">'.$loggedinas.'</div>';
- if (isset($SESSION->justloggedin)) {
- unset($SESSION->justloggedin);
- if (!empty($CFG->displayloginfailures)) {
- if (!isguestuser()) {
- // Include this file only when required.
- require_once($CFG->dirroot . '/user/lib.php');
- if ($count = user_count_login_failures($USER)) {
- $loggedinas .= '<div class="loginfailures">';
- $a = new stdClass();
- $a->attempts = $count;
- $loggedinas .= get_string('failedloginattempts', '', $a);
- if (file_exists("$CFG->dirroot/report/log/index.php") and has_capability('report/log:view', context_system::instance())) {
- $loggedinas .= ' ('.html_writer::link(new moodle_url('/report/log/index.php', array('chooselog' => 1,
- 'id' => 0 , 'modid' => 'site_errors')), get_string('logs')).')';
- }
- $loggedinas .= '</div>';
- }
- }
- }
- }
- return $loggedinas;
- }
- /**
- * Check whether the current page is a login page.
- *
- * @since Moodle 2.9
- * @return bool
- */
- protected function is_login_page() {
- // This is a real bit of a hack, but its a rarety that we need to do something like this.
- // In fact the login pages should be only these two pages and as exposing this as an option for all pages
- // could lead to abuse (or at least unneedingly complex code) the hack is the way to go.
- return in_array(
- $this->page->url->out_as_local_url(false, array()),
- array(
- '/login/index.php',
- '/login/forgot_password.php',
- )
- );
- }
- /**
- * Return the 'back' link that normally appears in the footer.
- *
- * @return string HTML fragment.
- */
- public function home_link() {
- global $CFG, $SITE;
- if ($this->page->pagetype == 'site-index') {
- // Special case for site home page - please do not remove
- return '<div class="sitelink">' .
- '<a title="Moodle" class="d-inline-block aalink" href="http://moodle.org/">' .
- '<img src="' . $this->image_url('moodlelogo_grayhat') . '" alt="'.get_string('moodlelogo').'" /></a></div>';
- } else if (!empty($CFG->target_release) && $CFG->target_release != $CFG->release) {
- // Special case for during install/upgrade.
- return '<div class="sitelink">'.
- '<a title="Moodle" href="http://docs.moodle.org/en/Administrator_documentation" onclick="this.target=\'_blank\'">' .
- '<img src="' . $this->image_url('moodlelogo_grayhat') . '" alt="'.get_string('moodlelogo').'" /></a></div>';
- } else if ($this->page->course->id == $SITE->id || strpos($this->page->pagetype, 'course-view') === 0) {
- return '<div class="homelink"><a href="' . $CFG->wwwroot . '/">' .
- get_string('home') . '</a></div>';
- } else {
- return '<div class="homelink"><a href="' . $CFG->wwwroot . '/course/view.php?id=' . $this->page->course->id . '">' .
- format_string($this->page->course->shortname, true, array('context' => $this->page->context)) . '</a></div>';
- }
- }
- /**
- * Redirects the user by any means possible given the current state
- *
- * This function should not be called directly, it should always be called using
- * the redirect function in lib/weblib.php
- *
- * The redirect function should really only be called before page output has started
- * however it will allow itself to be called during the state STATE_IN_BODY
- *
- * @param string $encodedurl The URL to send to encoded if required
- * @param string $message The message to display to the user if any
- * @param int $delay The delay before redirecting a user, if $message has been
- * set this is a requirement and defaults to 3, set to 0 no delay
- * @param boolean $debugdisableredirect this redirect has been disabled for
- * debugging purposes. Display a message that explains, and don't
- * trigger the redirect.
- * @param string $messagetype The type of notification to show the message in.
- * See constants on \core\output\notification.
- * @return string The HTML to display to the user before dying, may contain
- * meta refresh, javascript refresh, and may have set header redirects
- */
- public function redirect_message($encodedurl, $message, $delay, $debugdisableredirect,
- $messagetype = \core\output\notification::NOTIFY_INFO) {
- global $CFG;
- $url = str_replace('&', '&', $encodedurl);
- switch ($this->page->state) {
- case moodle_page::STATE_BEFORE_HEADER :
- // No output yet it is safe to delivery the full arsenal of redirect methods
- if (!$debugdisableredirect) {
- // Don't use exactly the same time here, it can cause problems when both redirects fire at the same time.
- $this->metarefreshtag = '<meta http-equiv="refresh" content="'. $delay .'; url='. $encodedurl .'" />'."\n";
- $this->page->requires->js_function_call('document.location.replace', array($url), false, ($delay + 3));
- }
- $output = $this->header();
- break;
- case moodle_page::STATE_PRINTING_HEADER :
- // We should hopefully never get here
- throw new coding_exception('You cannot redirect while printing the page header');
- break;
- case moodle_page::STATE_IN_BODY :
- // We really shouldn't be here but we can deal with this
- debugging("You should really redirect before you start page output");
- if (!$debugdisableredirect) {
- $this->page->requires->js_function_call('document.location.replace', array($url), false, $delay);
- }
- $output = $this->opencontainers->pop_all_but_last();
- break;
- case moodle_page::STATE_DONE :
- // Too late to be calling redirect now
- throw new coding_exception('You cannot redirect after the entire page has been generated');
- break;
- }
- $output .= $this->notification($message, $messagetype);
- $output .= '<div class="continuebutton">(<a href="'. $encodedurl .'">'. get_string('continue') .'</a>)</div>';
- if ($debugdisableredirect) {
- $output .= '<p><strong>'.get_string('erroroutput', 'error').'</strong></p>';
- }
- $output .= $this->footer();
- return $output;
- }
- /**
- * Start output by sending the HTTP headers, and printing the HTML <head>
- * and the start of the <body>.
- *
- * To control what is printed, you should set properties on $PAGE.
- *
- * @return string HTML that you must output this, preferably immediately.
- */
- public function header() {
- global $USER, $CFG, $SESSION;
- // Give plugins an opportunity touch things before the http headers are sent
- // such as adding additional headers. The return value is ignored.
- $pluginswithfunction = get_plugins_with_function('before_http_headers', 'lib.php');
- foreach ($pluginswithfunction as $plugins) {
- foreach ($plugins as $function) {
- $function();
- }
- }
- if (\core\session\manager::is_loggedinas()) {
- $this->page->add_body_class('userloggedinas');
- }
- if (isset($SESSION->justloggedin) && !empty($CFG->displayloginfailures)) {
- require_once($CFG->dirroot . '/user/lib.php');
- // Set second parameter to false as we do not want reset the counter, the same message appears on footer.
- if ($count = user_count_login_failures($USER, false)) {
- $this->page->add_body_class('loginfailures');
- }
- }
- // If the user is logged in, and we're not in initial install,
- // check to see if the user is role-switched and add the appropriate
- // CSS class to the body element.
- if (!during_initial_install() && isloggedin() && is_role_switched($this->page->course->id)) {
- $this->page->add_body_class('userswitchedrole');
- }
- // Give themes a chance to init/alter the page object.
- $this->page->theme->init_page($this->page);
- $this->page->set_state(moodle_page::STATE_PRINTING_HEADER);
- // Find the appropriate page layout file, based on $this->page->pagelayout.
- $layoutfile = $this->page->theme->layout_file($this->page->pagelayout);
- // Render the layout using the layout file.
- $rendered = $this->render_page_layout($layoutfile);
- // Slice the rendered output into header and footer.
- $cutpos = strpos($rendered, $this->unique_main_content_token);
- if ($cutpos === false) {
- $cutpos = strpos($rendered, self::MAIN_CONTENT_TOKEN);
- $token = self::MAIN_CONTENT_TOKEN;
- } else {
- $token = $this->unique_main_content_token;
- }
- if ($cutpos === false) {
- throw new coding_exception('page layout file ' . $layoutfile . ' does not contain the main content placeholder, please include "<?php echo $OUTPUT->main_content() ?>" in theme layout file.');
- }
- $header = substr($rendered, 0, $cutpos);
- $footer = substr($rendered, $cutpos + strlen($token));
- if (empty($this->contenttype)) {
- debugging('The page layout file did not call $OUTPUT->doctype()');
- $header = $this->doctype() . $header;
- }
- // If this theme version is below 2.4 release and this is a course view page
- if ((!isset($this->page->theme->settings->version) || $this->page->theme->settings->version < 2012101500) &&
- $this->page->pagelayout === 'course' && $this->page->url->compare(new moodle_url('/course/view.php'), URL_MATCH_BASE)) {
- // check if course content header/footer have not been output during render of theme layout
- $coursecontentheader = $this->course_content_header(true);
- $coursecontentfooter = $this->course_content_footer(true);
- if (!empty($coursecontentheader)) {
- // display debug message and add header and footer right above and below main content
- // Please note that course header and footer (to be displayed above and below the whole page)
- // are not displayed in this case at all.
- // Besides the content header and footer are not displayed on any other course page
- debugging('The current theme is not optimised for 2.4, the course-specific header and footer defined in course format will not be output', DEBUG_DEVELOPER);
- $header .= $coursecontentheader;
- $footer = $coursecontentfooter. $footer;
- }
- }
- send_headers($this->contenttype, $this->page->cacheable);
- $this->opencontainers->push('header/footer', $footer);
- $this->page->set_state(moodle_page::STATE_IN_BODY);
- return $header . $this->skip_link_target('maincontent');
- }
- /**
- * Renders and outputs the page layout file.
- *
- * This is done by preparing the normal globals available to a script, and
- * then including the layout file provided by the current theme for the
- * requested layout.
- *
- * @param string $layoutfile The name of the layout file
- * @return string HTML code
- */
- protected function render_page_layout($layoutfile) {
- global $CFG, $SITE, $USER;
- // The next lines are a bit tricky. The point is, here we are in a method
- // of a renderer class, and this object may, or may not, be the same as
- // the global $OUTPUT object. When rendering the page layout file, we want to use
- // this object. However, people writing Moodle code expect the current
- // renderer to be called $OUTPUT, not $this, so define a variable called
- // $OUTPUT pointing at $this. The same comment applies to $PAGE and $COURSE.
- $OUTPUT = $this;
- $PAGE = $this->page;
- $COURSE = $this->page->course;
- ob_start();
- include($layoutfile);
- $rendered = ob_get_contents();
- ob_end_clean();
- return $rendered;
- }
- /**
- * Outputs the page's footer
- *
- * @return string HTML fragment
- */
- public function footer() {
- global $CFG, $DB;
- $output = '';
- // Give plugins an opportunity to touch the page before JS is finalized.
- $pluginswithfunction = get_plugins_with_function('before_footer', 'lib.php');
- foreach ($pluginswithfunction as $plugins) {
- foreach ($plugins as $function) {
- $extrafooter = $function();
- if (is_string($extrafooter)) {
- $output .= $extrafooter;
- }
- }
- }
- $output .= $this->container_end_all(true);
- $footer = $this->opencontainers->pop('header/footer');
- if (debugging() and $DB and $DB->is_transaction_started()) {
- // TODO: MDL-20625 print warning - transaction will be rolled back
- }
- // Provide some performance info if required
- $performanceinfo = '';
- if (defined('MDL_PERF') || (!empty($CFG->perfdebug) and $CFG->perfdebug > 7)) {
- $perf = get_performance_info();
- if (defined('MDL_PERFTOFOOT') || debugging() || $CFG->perfdebug > 7) {
- $performanceinfo = $perf['html'];
- }
- }
- // We always want performance data when running a performance test, even if the user is redirected to another page.
- if (MDL_PERF_TEST && strpos($footer, $this->unique_performance_info_token) === false) {
- $footer = $this->unique_performance_info_token . $footer;
- }
- $footer = str_replace($this->unique_performance_info_token, $performanceinfo, $footer);
- // Only show notifications when the current page has a context id.
- if (!empty($this->page->context->id)) {
- $this->page->requires->js_call_amd('core/notification', 'init', array(
- $this->page->context->id,
- \core\notification::fetch_as_array($this)
- ));
- }
- $footer = str_replace($this->unique_end_html_token, $this->page->requires->get_end_code(), $footer);
- $this->page->set_state(moodle_page::STATE_DONE);
- return $output . $footer;
- }
- /**
- * Close all but the last open container. This is useful in places like error
- * handling, where you want to close all the open containers (apart from <body>)
- * before outputting the error message.
- *
- * @param bool $shouldbenone assert that the stack should be empty now - causes a
- * developer debug warning if it isn't.
- * @return string the HTML required to close any open containers inside <body>.
- */
- public function container_end_all($shouldbenone = false) {
- return $this->opencontainers->pop_all_but_last($shouldbenone);
- }
- /**
- * Returns course-specific information to be output immediately above content on any course page
- * (for the current course)
- *
- * @param bool $onlyifnotcalledbefore output content only if it has not been output before
- * @return string
- */
- public function course_content_header($onlyifnotcalledbefore = false) {
- global $CFG;
- static $functioncalled = false;
- if ($functioncalled && $onlyifnotcalledbefore) {
- // we have already output the content header
- return '';
- }
- // Output any session notification.
- $notifications = \core\notification::fetch();
- $bodynotifications = '';
- foreach ($notifications as $notification) {
- $bodynotifications .= $this->render_from_template(
- $notification->get_template_name(),
- $notification->export_for_template($this)
- );
- }
- $output = html_writer::span($bodynotifications, 'notifications', array('id' => 'user-notifications'));
- if ($this->page->course->id == SITEID) {
- // return immediately and do not include /course/lib.php if not necessary
- return $output;
- }
- require_once($CFG->dirroot.'/course/lib.php');
- $functioncalled = true;
- $courseformat = course_get_format($this->page->course);
- if (($obj = $courseformat->course_content_header()) !== null) {
- $output .= html_writer::div($courseformat->get_renderer($this->page)->render($obj), 'course-content-header');
- }
- return $output;
- }
- /**
- * Returns course-specific information to be output immediately below content on any course page
- * (for the current course)
- *
- * @param bool $onlyifnotcalledbefore output content only if it has not been output before
- * @return string
- */
- public function course_content_footer($onlyifnotcalledbefore = false) {
- global $CFG;
- if ($this->page->course->id == SITEID) {
- // return immediately and do not include /course/lib.php if not necessary
- return '';
- }
- static $functioncalled = false;
- if ($functioncalled && $onlyifnotcalledbefore) {
- // we have already output the content footer
- return '';
- }
- $functioncalled = true;
- require_once($CFG->dirroot.'/course/lib.php');
- $courseformat = course_get_format($this->page->course);
- if (($obj = $courseformat->course_content_footer()) !== null) {
- return html_writer::div($courseformat->get_renderer($this->page)->render($obj), 'course-content-footer');
- }
- return '';
- }
- /**
- * Returns course-specific information to be output on any course page in the header area
- * (for the current course)
- *
- * @return string
- */
- public function course_header() {
- global $CFG;
- if ($this->page->course->id == SITEID) {
- // return immediately and do not include /course/lib.php if not necessary
- return '';
- }
- require_once($CFG->dirroot.'/course/lib.php');
- $courseformat = course_get_format($this->page->course);
- if (($obj = $courseformat->course_header()) !== null) {
- return $courseformat->get_renderer($this->page)->render($obj);
- }
- return '';
- }
- /**
- * Returns course-specific information to be output on any course page in the footer area
- * (for the current course)
- *
- * @return string
- */
- public function course_footer() {
- global $CFG;
- if ($this->page->course->id == SITEID) {
- // return immediately and do not include /course/lib.php if not necessary
- return '';
- }
- require_once($CFG->dirroot.'/course/lib.php');
- $courseformat = course_get_format($this->page->course);
- if (($obj = $courseformat->course_footer()) !== null) {
- return $courseformat->get_renderer($this->page)->render($obj);
- }
- return '';
- }
- /**
- * Get the course pattern datauri to show on a course card.
- *
- * The datauri is an encoded svg that can be passed as a url.
- * @param int $id Id to use when generating the pattern
- * @return string datauri
- */
- public function get_generated_image_for_id($id) {
- $color = $this->get_generated_color_for_id($id);
- $pattern = new \core_geopattern();
- $pattern->setColor($color);
- $pattern->patternbyid($id);
- return $pattern->datauri();
- }
- /**
- * Get the course color to show on a course card.
- *
- * @param int $id Id to use when generating the color.
- * @return string hex color code.
- */
- public function get_generated_color_for_id($id) {
- $colornumbers = range(1, 10);
- $basecolors = [];
- foreach ($colornumbers as $number) {
- $basecolors[] = get_config('core_admin', 'coursecolor' . $number);
- }
- $color = $basecolors[$id % 10];
- return $color;
- }
- /**
- * Returns lang menu or '', this method also checks forcing of languages in courses.
- *
- * This function calls {@link core_renderer::render_single_select()} to actually display the language menu.
- *
- * @return string The lang menu HTML or empty string
- */
- public function lang_menu() {
- $languagemenu = new \core\output\language_menu($this->page);
- $data = $languagemenu->export_for_single_select($this);
- if ($data) {
- return $this->render_from_template('core/single_select', $data);
- }
- return '';
- }
- /**
- * Output the row of editing icons for a block, as defined by the controls array.
- *
- * @param array $controls an array like {@link block_contents::$controls}.
- * @param string $blockid The ID given to the block.
- * @return string HTML fragment.
- */
- public function block_controls($actions, $blockid = null) {
- global $CFG;
- if (empty($actions)) {
- return '';
- }
- $menu = new action_menu($actions);
- if ($blockid !== null) {
- $menu->set_owner_selector('#'.$blockid);
- }
- $menu->set_constraint('.block-region');
- $menu->attributes['class'] .= ' block-control-actions commands';
- return $this->render($menu);
- }
- /**
- * Returns the HTML for a basic textarea field.
- *
- * @param string $name Name to use for the textarea element
- * @param string $id The id to use fort he textarea element
- * @param string $value Initial content to display in the textarea
- * @param int $rows Number of rows to display
- * @param int $cols Number of columns to display
- * @return string the HTML to display
- */
- public function print_textarea($name, $id, $value, $rows, $cols) {
- editors_head_setup();
- $editor = editors_get_preferred_editor(FORMAT_HTML);
- $editor->set_text($value);
- $editor->use_editor($id, []);
- $context = [
- 'id' => $id,
- 'name' => $name,
- 'value' => $value,
- 'rows' => $rows,
- 'cols' => $cols
- ];
- return $this->render_from_template('core_form/editor_textarea', $context);
- }
- /**
- * Renders an action menu component.
- *
- * @param action_menu $menu
- * @return string HTML
- */
- public function render_action_menu(action_menu $menu) {
- // We don't want the class icon there!
- foreach ($menu->get_secondary_actions() as $action) {
- if ($action instanceof \action_menu_link && $action->has_class('icon')) {
- $action->attributes['class'] = preg_replace('/(^|\s+)icon(\s+|$)/i', '', $action->attributes['class']);
- }
- }
- if ($menu->is_empty()) {
- return '';
- }
- $context = $menu->export_for_template($this);
- return $this->render_from_template('core/action_menu', $context);
- }
- /**
- * Renders a Check API result
- *
- * @param result $result
- * @return string HTML fragment
- */
- protected function render_check_result(core\check\result $result) {
- return $this->render_from_template($result->get_template_name(), $result->export_for_template($this));
- }
- /**
- * Renders a Check API result
- *
- * @param result $result
- * @return string HTML fragment
- */
- public function check_result(core\check\result $result) {
- return $this->render_check_result($result);
- }
- /**
- * Renders an action_menu_link item.
- *
- * @param action_menu_link $action
- * @return string HTML fragment
- */
- protected function render_action_menu_link(action_menu_link $action) {
- return $this->render_from_template('core/action_menu_link', $action->export_for_template($this));
- }
- /**
- * Renders a primary action_menu_filler item.
- *
- * @param action_menu_link_filler $action
- * @return string HTML fragment
- */
- protected function render_action_menu_filler(action_menu_filler $action) {
- return html_writer::span(' ', 'filler');
- }
- /**
- * Renders a primary action_menu_link item.
- *
- * @param action_menu_link_primary $action
- * @return string HTML fragment
- */
- protected function render_action_menu_link_primary(action_menu_link_primary $action) {
- return $this->render_action_menu_link($action);
- }
- /**
- * Renders a secondary action_menu_link item.
- *
- * @param action_menu_link_secondary $action
- * @return string HTML fragment
- */
- protected function render_action_menu_link_secondary(action_menu_link_secondary $action) {
- return $this->render_action_menu_link($action);
- }
- /**
- * Prints a nice side block with an optional header.
- *
- * @param block_contents $bc HTML for the content
- * @param string $region the region the block is appearing in.
- * @return string the HTML to be output.
- */
- public function block(block_contents $bc, $region) {
- $bc = clone($bc); // Avoid messing up the object passed in.
- if (empty($bc->blockinstanceid) || !strip_tags($bc->title)) {
- $bc->collapsible = block_contents::NOT_HIDEABLE;
- }
- $id = !empty($bc->attributes['id']) ? $bc->attributes['id'] : uniqid('block-');
- $context = new stdClass();
- $context->skipid = $bc->skipid;
- $context->blockinstanceid = $bc->blockinstanceid ?: uniqid('fakeid-');
- $context->dockable = $bc->dockable;
- $context->id = $id;
- $context->hidden = $bc->collapsible == block_contents::HIDDEN;
- $context->skiptitle = strip_tags($bc->title);
- $context->showskiplink = !empty($context->skiptitle);
- $context->arialabel = $bc->arialabel;
- $context->ariarole = !empty($bc->attributes['role']) ? $bc->attributes['role'] : 'complementary';
- $context->class = $bc->attributes['class'];
- $context->type = $bc->attributes['data-block'];
- $context->title = $bc->title;
- $context->content = $bc->content;
- $context->annotation = $bc->annotation;
- $context->footer = $bc->footer;
- $context->hascontrols = !empty($bc->controls);
- if ($context->hascontrols) {
- $context->controls = $this->block_controls($bc->controls, $id);
- }
- return $this->render_from_template('core/block', $context);
- }
- /**
- * Render the contents of a block_list.
- *
- * @param array $icons the icon for each item.
- * @param array $items the content of each item.
- * @return string HTML
- */
- public function list_block_contents($icons, $items) {
- $row = 0;
- $lis = array();
- foreach ($items as $key => $string) {
- $item = html_writer::start_tag('li', array('class' => 'r' . $row));
- if (!empty($icons[$key])) { //test if the content has an assigned icon
- $item .= html_writer::tag('div', $icons[$key], array('class' => 'icon column c0'));
- }
- $item .= html_writer::tag('div', $string, array('class' => 'column c1'));
- $item .= html_writer::end_tag('li');
- $lis[] = $item;
- $row = 1 - $row; // Flip even/odd.
- }
- return html_writer::tag('ul', implode("\n", $lis), array('class' => 'unlist'));
- }
- /**
- * Output all the blocks in a particular region.
- *
- * @param string $region the name of a region on this page.
- * @param boolean $fakeblocksonly Output fake block only.
- * @return string the HTML to be output.
- */
- public function blocks_for_region($region, $fakeblocksonly = false) {
- $blockcontents = $this->page->blocks->get_content_for_region($region, $this);
- $lastblock = null;
- $zones = array();
- foreach ($blockcontents as $bc) {
- if ($bc instanceof block_contents) {
- $zones[] = $bc->title;
- }
- }
- $output = '';
- foreach ($blockcontents as $bc) {
- if ($bc instanceof block_contents) {
- if ($fakeblocksonly && !$bc->is_fake()) {
- // Skip rendering real blocks if we only want to show fake blocks.
- continue;
- }
- $output .= $this->block($bc, $region);
- $lastblock = $bc->title;
- } else if ($bc instanceof block_move_target) {
- if (!$fakeblocksonly) {
- $output .= $this->block_move_target($bc, $zones, $lastblock, $region);
- }
- } else {
- throw new coding_exception('Unexpected type of thing (' . get_class($bc) . ') found in list of block contents.');
- }
- }
- return $output;
- }
- /**
- * Output a place where the block that is currently being moved can be dropped.
- *
- * @param block_move_target $target with the necessary details.
- * @param array $zones array of areas where the block can be moved to
- * @param string $previous the block located before the area currently being rendered.
- * @param string $region the name of the region
- * @return string the HTML to be output.
- */
- public function block_move_target($target, $zones, $previous, $region) {
- if ($previous == null) {
- if (empty($zones)) {
- // There are no zones, probably because there are no blocks.
- $regions = $this->page->theme->get_all_block_regions();
- $position = get_string('moveblockinregion', 'block', $regions[$region]);
- } else {
- $position = get_string('moveblockbefore', 'block', $zones[0]);
- }
- } else {
- $position = get_string('moveblockafter', 'block', $previous);
- }
- return html_writer::tag('a', html_writer::tag('span', $position, array('class' => 'accesshide')), array('href' => $target->url, 'class' => 'blockmovetarget'));
- }
- /**
- * Renders a special html link with attached action
- *
- * Theme developers: DO NOT OVERRIDE! Please override function
- * {@link core_renderer::render_action_link()} instead.
- *
- * @param string|moodle_url $url
- * @param string $text HTML fragment
- * @param component_action $action
- * @param array $attributes associative array of html link attributes + disabled
- * @param pix_icon optional pix icon to render with the link
- * @return string HTML fragment
- */
- public function action_link($url, $text, component_action $action = null, array $attributes = null, $icon = null) {
- if (!($url instanceof moodle_url)) {
- $url = new moodle_url($url);
- }
- $link = new action_link($url, $text, $action, $attributes, $icon);
- return $this->render($link);
- }
- /**
- * Renders an action_link object.
- *
- * The provided link is renderer and the HTML returned. At the same time the
- * associated actions are setup in JS by {@link core_renderer::add_action_handler()}
- *
- * @param action_link $link
- * @return string HTML fragment
- */
- protected function render_action_link(action_link $link) {
- return $this->render_from_template('core/action_link', $link->export_for_template($this));
- }
- /**
- * Renders an action_icon.
- *
- * This function uses the {@link core_renderer::action_link()} method for the
- * most part. What it does different is prepare the icon as HTML and use it
- * as the link text.
- *
- * Theme developers: If you want to change how action links and/or icons are rendered,
- * consider overriding function {@link core_renderer::render_action_link()} and
- * {@link core_renderer::render_pix_icon()}.
- *
- * @param string|moodle_url $url A string URL or moodel_url
- * @param pix_icon $pixicon
- * @param component_action $action
- * @param array $attributes associative array of html link attributes + disabled
- * @param bool $linktext show title next to image in link
- * @return string HTML fragment
- */
- public function action_icon($url, pix_icon $pixicon, component_action $action = null, array $attributes = null, $linktext=false) {
- if (!($url instanceof moodle_url)) {
- $url = new moodle_url($url);
- }
- $attributes = (array)$attributes;
- if (empty($attributes['class'])) {
- // let ppl override the class via $options
- $attributes['class'] = 'action-icon';
- }
- $icon = $this->render($pixicon);
- if ($linktext) {
- $text = $pixicon->attributes['alt'];
- } else {
- $text = '';
- }
- return $this->action_link($url, $text.$icon, $action, $attributes);
- }
- /**
- * Print a message along with button choices for Continue/Cancel
- *
- * If a string or moodle_url is given instead of a single_button, method defaults to post.
- *
- * @param string $message The question to ask the user
- * @param single_button|moodle_url|string $continue The single_button component representing the Continue answer. Can also be a moodle_url or string URL
- * @param single_button|moodle_url|string $cancel The single_button component representing the Cancel answer. Can also be a moodle_url or string URL
- * @return string HTML fragment
- */
- public function confirm($message, $continue, $cancel) {
- if ($continue instanceof single_button) {
- // ok
- $continue->primary = true;
- } else if (is_string($continue)) {
- $continue = new single_button(new moodle_url($continue), get_string('continue'), 'post', true);
- } else if ($continue instanceof moodle_url) {
- $continue = new single_button($continue, get_string('continue'), 'post', true);
- } else {
- throw new coding_exception('The continue param to $OUTPUT->confirm() must be either a URL (string/moodle_url) or a single_button instance.');
- }
- if ($cancel instanceof single_button) {
- // ok
- } else if (is_string($cancel)) {
- $cancel = new single_button(new moodle_url($cancel), get_string('cancel'), 'get');
- } else if ($cancel instanceof moodle_url) {
- $cancel = new single_button($cancel, get_string('cancel'), 'get');
- } else {
- throw new coding_exception('The cancel param to $OUTPUT->confirm() must be either a URL (string/moodle_url) or a single_button instance.');
- }
- $attributes = [
- 'role'=>'alertdialog',
- 'aria-labelledby'=>'modal-header',
- 'aria-describedby'=>'modal-body',
- 'aria-modal'=>'true'
- ];
- $output = $this->box_start('generalbox modal modal-dialog modal-in-page show', 'notice', $attributes);
- $output .= $this->box_start('modal-content', 'modal-content');
- $output .= $this->box_start('modal-header px-3', 'modal-header');
- $output .= html_writer::tag('h4', get_string('confirm'));
- $output .= $this->box_end();
- $attributes = [
- 'role'=>'alert',
- 'data-aria-autofocus'=>'true'
- ];
- $output .= $this->box_start('modal-body', 'modal-body', $attributes);
- $output .= html_writer::tag('p', $message);
- $output .= $this->box_end();
- $output .= $this->box_start('modal-footer', 'modal-footer');
- $output .= html_writer::tag('div', $this->render($continue) . $this->render($cancel), array('class' => 'buttons'));
- $output .= $this->box_end();
- $output .= $this->box_end();
- $output .= $this->box_end();
- return $output;
- }
- /**
- * Returns a form with a single button.
- *
- * Theme developers: DO NOT OVERRIDE! Please override function
- * {@link core_renderer::render_single_button()} instead.
- *
- * @param string|moodle_url $url
- * @param string $label button text
- * @param string $method get or post submit method
- * @param array $options associative array {disabled, title, etc.}
- * @return string HTML fragment
- */
- public function single_button($url, $label, $method='post', array $options=null) {
- if (!($url instanceof moodle_url)) {
- $url = new moodle_url($url);
- }
- $button = new single_button($url, $label, $method);
- foreach ((array)$options as $key=>$value) {
- if (property_exists($button, $key)) {
- $button->$key = $value;
- } else {
- $button->set_attribute($key, $value);
- }
- }
- return $this->render($button);
- }
- /**
- * Renders a single button widget.
- *
- * This will return HTML to display a form containing a single button.
- *
- * @param single_button $button
- * @return string HTML fragment
- */
- protected function render_single_button(single_button $button) {
- return $this->render_from_template('core/single_button', $button->export_for_template($this));
- }
- /**
- * Returns a form with a single select widget.
- *
- * Theme developers: DO NOT OVERRIDE! Please override function
- * {@link core_renderer::render_single_select()} instead.
- *
- * @param moodle_url $url form action target, includes hidden fields
- * @param string $name name of selection field - the changing parameter in url
- * @param array $options list of options
- * @param string $selected selected element
- * @param array $nothing
- * @param string $formid
- * @param array $attributes other attributes for the single select
- * @return string HTML fragment
- */
- public function single_select($url, $name, array $options, $selected = '',
- $nothing = array('' => 'choosedots'), $formid = null, $attributes = array()) {
- if (!($url instanceof moodle_url)) {
- $url = new moodle_url($url);
- }
- $select = new single_select($url, $name, $options, $selected, $nothing, $formid);
- if (array_key_exists('label', $attributes)) {
- $select->set_label($attributes['label']);
- unset($attributes['label']);
- }
- $select->attributes = $attributes;
- return $this->render($select);
- }
- /**
- * Returns a dataformat selection and download form
- *
- * @param string $label A text label
- * @param moodle_url|string $base The download page url
- * @param string $name The query param which will hold the type of the download
- * @param array $params Extra params sent to the download page
- * @return string HTML fragment
- */
- public function download_dataformat_selector($label, $base, $name = 'dataformat', $params = array()) {
- $formats = core_plugin_manager::instance()->get_plugins_of_type('dataformat');
- $options = array();
- foreach ($formats as $format) {
- if ($format->is_enabled()) {
- $options[] = array(
- 'value' => $format->name,
- 'label' => get_string('dataformat', $format->component),
- );
- }
- }
- $hiddenparams = array();
- foreach ($params as $key => $value) {
- $hiddenparams[] = array(
- 'name' => $key,
- 'value' => $value,
- );
- }
- $data = array(
- 'label' => $label,
- 'base' => $base,
- 'name' => $name,
- 'params' => $hiddenparams,
- 'options' => $options,
- 'sesskey' => sesskey(),
- 'submit' => get_string('download'),
- );
- return $this->render_from_template('core/dataformat_selector', $data);
- }
- /**
- * Internal implementation of single_select rendering
- *
- * @param single_select $select
- * @return string HTML fragment
- */
- protected function render_single_select(single_select $select) {
- return $this->render_from_template('core/single_select', $select->export_for_template($this));
- }
- /**
- * Returns a form with a url select widget.
- *
- * Theme developers: DO NOT OVERRIDE! Please override function
- * {@link core_renderer::render_url_select()} instead.
- *
- * @param array $urls list of urls - array('/course/view.php?id=1'=>'Frontpage', ....)
- * @param string $selected selected element
- * @param array $nothing
- * @param string $formid
- * @return string HTML fragment
- */
- public function url_select(array $urls, $selected, $nothing = array('' => 'choosedots'), $formid = null) {
- $select = new url_select($urls, $selected, $nothing, $formid);
- return $this->render($select);
- }
- /**
- * Internal implementation of url_select rendering
- *
- * @param url_select $select
- * @return string HTML fragment
- */
- protected function render_url_select(url_select $select) {
- return $this->render_from_template('core/url_select', $select->export_for_template($this));
- }
- /**
- * Returns a string containing a link to the user documentation.
- * Also contains an icon by default. Shown to teachers and admin only.
- *
- * @param string $path The page link after doc root and language, no leading slash.
- * @param string $text The text to be displayed for the link
- * @param boolean $forcepopup Whether to force a popup regardless of the value of $CFG->doctonewwindow
- * @param array $attributes htm attributes
- * @return string
- */
- public function doc_link($path, $text = '', $forcepopup = false, array $attributes = []) {
- global $CFG;
- $icon = $this->pix_icon('book', '', 'moodle', array('class' => 'iconhelp icon-pre', 'role' => 'presentation'));
- $attributes['href'] = new moodle_url(get_docs_url($path));
- if (!empty($CFG->doctonewwindow) || $forcepopup) {
- $attributes['class'] = 'helplinkpopup';
- }
- return html_writer::tag('a', $icon.$text, $attributes);
- }
- /**
- * Return HTML for an image_icon.
- *
- * Theme developers: DO NOT OVERRIDE! Please override function
- * {@link core_renderer::render_image_icon()} instead.
- *
- * @param string $pix short pix name
- * @param string $alt mandatory alt attribute
- * @param string $component standard compoennt name like 'moodle', 'mod_forum', etc.
- * @param array $attributes htm attributes
- * @return string HTML fragment
- */
- public function image_icon($pix, $alt, $component='moodle', array $attributes = null) {
- $icon = new image_icon($pix, $alt, $component, $attributes);
- return $this->render($icon);
- }
- /**
- * Renders a pix_icon widget and returns the HTML to display it.
- *
- * @param image_icon $icon
- * @return string HTML fragment
- */
- protected function render_image_icon(image_icon $icon) {
- $system = \core\output\icon_system::instance(\core\output\icon_system::STANDARD);
- return $system->render_pix_icon($this, $icon);
- }
- /**
- * Return HTML for a pix_icon.
- *
- * Theme developers: DO NOT OVERRIDE! Please override function
- * {@link core_renderer::render_pix_icon()} instead.
- *
- * @param string $pix short pix name
- * @param string $alt mandatory alt attribute
- * @param string $component standard compoennt name like 'moodle', 'mod_forum', etc.
- * @param array $attributes htm lattributes
- * @return string HTML fragment
- */
- public function pix_icon($pix, $alt, $component='moodle', array $attributes = null) {
- $icon = new pix_icon($pix, $alt, $component, $attributes);
- return $this->render($icon);
- }
- /**
- * Renders a pix_icon widget and returns the HTML to display it.
- *
- * @param pix_icon $icon
- * @return string HTML fragment
- */
- protected function render_pix_icon(pix_icon $icon) {
- $system = \core\output\icon_system::instance();
- return $system->render_pix_icon($this, $icon);
- }
- /**
- * Return HTML to display an emoticon icon.
- *
- * @param pix_emoticon $emoticon
- * @return string HTML fragment
- */
- protected function render_pix_emoticon(pix_emoticon $emoticon) {
- $system = \core\output\icon_system::instance(\core\output\icon_system::STANDARD);
- return $system->render_pix_icon($this, $emoticon);
- }
- /**
- * Produces the html that represents this rating in the UI
- *
- * @param rating $rating the page object on which this rating will appear
- * @return string
- */
- function render_rating(rating $rating) {
- global $CFG, $USER;
- if ($rating->settings->aggregationmethod == RATING_AGGREGATE_NONE) {
- return null;//ratings are turned off
- }
- $ratingmanager = new rating_manager();
- // Initialise the JavaScript so ratings can be done by AJAX.
- $ratingmanager->initialise_rating_javascript($this->page);
- $strrate = get_string("rate", "rating");
- $ratinghtml = ''; //the string we'll return
- // permissions check - can they view the aggregate?
- if ($rating->user_can_view_aggregate()) {
- $aggregatelabel = $ratingmanager->get_aggregate_label($rating->settings->aggregationmethod);
- $aggregatelabel = html_writer::tag('span', $aggregatelabel, array('class'=>'rating-aggregate-label'));
- $aggregatestr = $rating->get_aggregate_string();
- $aggregatehtml = html_writer::tag('span', $aggregatestr, array('id' => 'ratingaggregate'.$rating->itemid, 'class' => 'ratingaggregate')).' ';
- if ($rating->count > 0) {
- $countstr = "({$rating->count})";
- } else {
- $countstr = '-';
- }
- $aggregatehtml .= html_writer::tag('span', $countstr, array('id'=>"ratingcount{$rating->itemid}", 'class' => 'ratingcount')).' ';
- if ($rating->settings->permissions->viewall && $rating->settings->pluginpermissions->viewall) {
- $nonpopuplink = $rating->get_view_ratings_url();
- $popuplink = $rating->get_view_ratings_url(true);
- $action = new popup_action('click', $popuplink, 'ratings', array('height' => 400, 'width' => 600));
- $aggregatehtml = $this->action_link($nonpopuplink, $aggregatehtml, $action);
- }
- $ratinghtml .= html_writer::tag('span', $aggregatelabel . $aggregatehtml, array('class' => 'rating-aggregate-container'));
- }
- $formstart = null;
- // if the item doesn't belong to the current user, the user has permission to rate
- // and we're within the assessable period
- if ($rating->user_can_rate()) {
- $rateurl = $rating->get_rate_url();
- $inputs = $rateurl->params();
- //start the rating form
- $formattrs = array(
- 'id' => "postrating{$rating->itemid}",
- 'class' => 'postratingform',
- 'method' => 'post',
- 'action' => $rateurl->out_omit_querystring()
- );
- $formstart = html_writer::start_tag('form', $formattrs);
- $formstart .= html_writer::start_tag('div', array('class' => 'ratingform'));
- // add the hidden inputs
- foreach ($inputs as $name => $value) {
- $attributes = array('type' => 'hidden', 'class' => 'ratinginput', 'name' => $name, 'value' => $value);
- $formstart .= html_writer::empty_tag('input', $attributes);
- }
- if (empty($ratinghtml)) {
- $ratinghtml .= $strrate.': ';
- }
- $ratinghtml = $formstart.$ratinghtml;
- $scalearray = array(RATING_UNSET_RATING => $strrate.'...') + $rating->settings->scale->scaleitems;
- $scaleattrs = array('class'=>'postratingmenu ratinginput','id'=>'menurating'.$rating->itemid);
- $ratinghtml .= html_writer::label($rating->rating, 'menurating'.$rating->itemid, false, array('class' => 'accesshide'));
- $ratinghtml .= html_writer::select($scalearray, 'rating', $rating->rating, false, $scaleattrs);
- //output submit button
- $ratinghtml .= html_writer::start_tag('span', array('class'=>"ratingsubmit"));
- $attributes = array('type' => 'submit', 'class' => 'postratingmenusubmit', 'id' => 'postratingsubmit'.$rating->itemid, 'value' => s(get_string('rate', 'rating')));
- $ratinghtml .= html_writer::empty_tag('input', $attributes);
- if (!$rating->settings->scale->isnumeric) {
- // If a global scale, try to find current course ID from the context
- if (empty($rating->settings->scale->courseid) and $coursecontext = $rating->context->get_course_context(false)) {
- $courseid = $coursecontext->instanceid;
- } else {
- $courseid = $rating->settings->scale->courseid;
- }
- $ratinghtml .= $this->help_icon_scale($courseid, $rating->settings->scale);
- }
- $ratinghtml .= html_writer::end_tag('span');
- $ratinghtml .= html_writer::end_tag('div');
- $ratinghtml .= html_writer::end_tag('form');
- }
- return $ratinghtml;
- }
- /**
- * Centered heading with attached help button (same title text)
- * and optional icon attached.
- *
- * @param string $text A heading text
- * @param string $helpidentifier The keyword that defines a help page
- * @param string $component component name
- * @param string|moodle_url $icon
- * @param string $iconalt icon alt text
- * @param int $level The level of importance of the heading. Defaulting to 2
- * @param string $classnames A space-separated list of CSS classes. Defaulting to null
- * @return string HTML fragment
- */
- public function heading_with_help($text, $helpidentifier, $component = 'moodle', $icon = '', $iconalt = '', $level = 2, $classnames = null) {
- $image = '';
- if ($icon) {
- $image = $this->pix_icon($icon, $iconalt, $component, array('class'=>'icon iconlarge'));
- }
- $help = '';
- if ($helpidentifier) {
- $help = $this->help_icon($helpidentifier, $component);
- }
- return $this->heading($image.$text.$help, $level, $classnames);
- }
- /**
- * Returns HTML to display a help icon.
- *
- * @deprecated since Moodle 2.0
- */
- public function old_help_icon($helpidentifier, $title, $component = 'moodle', $linktext = '') {
- throw new coding_exception('old_help_icon() can not be used any more, please see help_icon().');
- }
- /**
- * Returns HTML to display a help icon.
- *
- * Theme developers: DO NOT OVERRIDE! Please override function
- * {@link core_renderer::render_help_icon()} instead.
- *
- * @param string $identifier The keyword that defines a help page
- * @param string $component component name
- * @param string|bool $linktext true means use $title as link text, string means link text value
- * @return string HTML fragment
- */
- public function help_icon($identifier, $component = 'moodle', $linktext = '') {
- $icon = new help_icon($identifier, $component);
- $icon->diag_strings();
- if ($linktext === true) {
- $icon->linktext = get_string($icon->identifier, $icon->component);
- } else if (!empty($linktext)) {
- $icon->linktext = $linktext;
- }
- return $this->render($icon);
- }
- /**
- * Implementation of user image rendering.
- *
- * @param help_icon $helpicon A help icon instance
- * @return string HTML fragment
- */
- protected function render_help_icon(help_icon $helpicon) {
- $context = $helpicon->export_for_template($this);
- return $this->render_from_template('core/help_icon', $context);
- }
- /**
- * Returns HTML to display a scale help icon.
- *
- * @param int $courseid
- * @param stdClass $scale instance
- * @return string HTML fragment
- */
- public function help_icon_scale($courseid, stdClass $scale) {
- global $CFG;
- $title = get_string('helpprefix2', '', $scale->name) .' ('.get_string('newwindow').')';
- $icon = $this->pix_icon('help', get_string('scales'), 'moodle', array('class'=>'iconhelp'));
- $scaleid = abs($scale->id);
- $link = new moodle_url('/course/scales.php', array('id' => $courseid, 'list' => true, 'scaleid' => $scaleid));
- $action = new popup_action('click', $link, 'ratingscale');
- return html_writer::tag('span', $this->action_link($link, $icon, $action), array('class' => 'helplink'));
- }
- /**
- * Creates and returns a spacer image with optional line break.
- *
- * @param array $attributes Any HTML attributes to add to the spaced.
- * @param bool $br Include a BR after the spacer.... DON'T USE THIS. Don't be
- * laxy do it with CSS which is a much better solution.
- * @return string HTML fragment
- */
- public function spacer(array $attributes = null, $br = false) {
- $attributes = (array)$attributes;
- if (empty($attributes['width'])) {
- $attributes['width'] = 1;
- }
- if (empty($attributes['height'])) {
- $attributes['height'] = 1;
- }
- $attributes['class'] = 'spacer';
- $output = $this->pix_icon('spacer', '', 'moodle', $attributes);
- if (!empty($br)) {
- $output .= '<br />';
- }
- return $output;
- }
- /**
- * Returns HTML to display the specified user's avatar.
- *
- * User avatar may be obtained in two ways:
- * <pre>
- * // Option 1: (shortcut for simple cases, preferred way)
- * // $user has come from the DB and has fields id, picture, imagealt, firstname and lastname
- * $OUTPUT->user_picture($user, array('popup'=>true));
- *
- * // Option 2:
- * $userpic = new user_picture($user);
- * // Set properties of $userpic
- * $userpic->popup = true;
- * $OUTPUT->render($userpic);
- * </pre>
- *
- * Theme developers: DO NOT OVERRIDE! Please override function
- * {@link core_renderer::render_user_picture()} instead.
- *
- * @param stdClass $user Object with at least fields id, picture, imagealt, firstname, lastname
- * If any of these are missing, the database is queried. Avoid this
- * if at all possible, particularly for reports. It is very bad for performance.
- * @param array $options associative array with user picture options, used only if not a user_picture object,
- * options are:
- * - courseid=$this->page->course->id (course id of user profile in link)
- * - size=35 (size of image)
- * - link=true (make image clickable - the link leads to user profile)
- * - popup=false (open in popup)
- * - alttext=true (add image alt attribute)
- * - class = image class attribute (default 'userpicture')
- * - visibletoscreenreaders=true (whether to be visible to screen readers)
- * - includefullname=false (whether to include the user's full name together with the user picture)
- * - includetoken = false (whether to use a token for authentication. True for current user, int value for other user id)
- * @return string HTML fragment
- */
- public function user_picture(stdClass $user, array $options = null) {
- $userpicture = new user_picture($user);
- foreach ((array)$options as $key=>$value) {
- if (property_exists($userpicture, $key)) {
- $userpicture->$key = $value;
- }
- }
- return $this->render($userpicture);
- }
- /**
- * Internal implementation of user image rendering.
- *
- * @param user_picture $userpicture
- * @return string
- */
- protected function render_user_picture(user_picture $userpicture) {
- $user = $userpicture->user;
- $canviewfullnames = has_capability('moodle/site:viewfullnames', $this->page->context);
- $alt = '';
- if ($userpicture->alttext) {
- if (!empty($user->imagealt)) {
- $alt = $user->imagealt;
- }
- }
- if (empty($userpicture->size)) {
- $size = 35;
- } else if ($userpicture->size === true or $userpicture->size == 1) {
- $size = 100;
- } else {
- $size = $userpicture->size;
- }
- $class = $userpicture->class;
- if ($user->picture == 0) {
- $class .= ' defaultuserpic';
- }
- $src = $userpicture->get_url($this->page, $this);
- $attributes = array('src' => $src, 'class' => $class, 'width' => $size, 'height' => $size);
- if (!$userpicture->visibletoscreenreaders) {
- $alt = '';
- }
- $attributes['alt'] = $alt;
- if (!empty($alt)) {
- $attributes['title'] = $alt;
- }
- // get the image html output first
- if ($user->picture == 0 && !defined('BEHAT_SITE_RUNNING')) {
- $output = html_writer::tag('span', mb_substr($user->firstname, 0, 1) . mb_substr($user->lastname, 0, 1),
- ['class' => 'userinitials size-' . $size]);
- } else {
- $output = html_writer::empty_tag('img', $attributes);
- }
- // Show fullname together with the picture when desired.
- if ($userpicture->includefullname) {
- $output .= fullname($userpicture->user, $canviewfullnames);
- }
- if (empty($userpicture->courseid)) {
- $courseid = $this->page->course->id;
- } else {
- $courseid = $userpicture->courseid;
- }
- if ($courseid == SITEID) {
- $url = new moodle_url('/user/profile.php', array('id' => $user->id));
- } else {
- $url = new moodle_url('/user/view.php', array('id' => $user->id, 'course' => $courseid));
- }
- // Then wrap it in link if needed. Also we don't wrap it in link if the link redirects to itself.
- if (!$userpicture->link ||
- ($this->page->has_set_url() && $this->page->url == $url)) { // Protect against unset page->url.
- return $output;
- }
- $attributes = array('href' => $url, 'class' => 'd-inline-block aabtn');
- if (!$userpicture->visibletoscreenreaders) {
- $attributes['tabindex'] = '-1';
- $attributes['aria-hidden'] = 'true';
- }
- if ($userpicture->popup) {
- $id = html_writer::random_id('userpicture');
- $attributes['id'] = $id;
- $this->add_action_handler(new popup_action('click', $url), $id);
- }
- return html_writer::tag('a', $output, $attributes);
- }
- /**
- * Internal implementation of file tree viewer items rendering.
- *
- * @param array $dir
- * @return string
- */
- public function htmllize_file_tree($dir) {
- if (empty($dir['subdirs']) and empty($dir['files'])) {
- return '';
- }
- $result = '<ul>';
- foreach ($dir['subdirs'] as $subdir) {
- $result .= '<li>'.s($subdir['dirname']).' '.$this->htmllize_file_tree($subdir).'</li>';
- }
- foreach ($dir['files'] as $file) {
- $filename = $file->get_filename();
- $result .= '<li><span>'.html_writer::link($file->fileurl, $filename).'</span></li>';
- }
- $result .= '</ul>';
- return $result;
- }
- /**
- * Returns HTML to display the file picker
- *
- * <pre>
- * $OUTPUT->file_picker($options);
- * </pre>
- *
- * Theme developers: DO NOT OVERRIDE! Please override function
- * {@link core_renderer::render_file_picker()} instead.
- *
- * @param array $options associative array with file manager options
- * options are:
- * maxbytes=>-1,
- * itemid=>0,
- * client_id=>uniqid(),
- * acepted_types=>'*',
- * return_types=>FILE_INTERNAL,
- * context=>current page context
- * @return string HTML fragment
- */
- public function file_picker($options) {
- $fp = new file_picker($options);
- return $this->render($fp);
- }
- /**
- * Internal implementation of file picker rendering.
- *
- * @param file_picker $fp
- * @return string
- */
- public function render_file_picker(file_picker $fp) {
- $options = $fp->options;
- $client_id = $options->client_id;
- $strsaved = get_string('filesaved', 'repository');
- $straddfile = get_string('openpicker', 'repository');
- $strloading = get_string('loading', 'repository');
- $strdndenabled = get_string('dndenabled_inbox', 'moodle');
- $strdroptoupload = get_string('droptoupload', 'moodle');
- $iconprogress = $this->pix_icon('i/loading_small', $strloading).'';
- $currentfile = $options->currentfile;
- if (empty($currentfile)) {
- $currentfile = '';
- } else {
- $currentfile .= ' - ';
- }
- if ($options->maxbytes) {
- $size = $options->maxbytes;
- } else {
- $size = get_max_upload_file_size();
- }
- if ($size == -1) {
- $maxsize = '';
- } else {
- $maxsize = get_string('maxfilesize', 'moodle', display_size($size, 0));
- }
- if ($options->buttonname) {
- $buttonname = ' name="' . $options->buttonname . '"';
- } else {
- $buttonname = '';
- }
- $html = <<<EOD
- <div class="filemanager-loading mdl-align" id='filepicker-loading-{$client_id}'>
- $iconprogress
- </div>
- <div id="filepicker-wrapper-{$client_id}" class="mdl-left w-100" style="display:none">
- <div>
- <input type="button" class="btn btn-secondary fp-btn-choose" id="filepicker-button-{$client_id}" value="{$straddfile}"{$buttonname}/>
- <span> $maxsize </span>
- </div>
- EOD;
- if ($options->env != 'url') {
- $html .= <<<EOD
- <div id="file_info_{$client_id}" class="mdl-left filepicker-filelist" style="position: relative">
- <div class="filepicker-filename">
- <div class="filepicker-container">$currentfile<div class="dndupload-message">$strdndenabled <br/><div class="dndupload-arrow"></div></div></div>
- <div class="dndupload-progressbars"></div>
- </div>
- <div><div class="dndupload-target">{$strdroptoupload}<br/><div class="dndupload-arrow"></div></div></div>
- </div>
- EOD;
- }
- $html .= '</div>';
- return $html;
- }
- /**
- * @deprecated since Moodle 3.2
- */
- public function update_module_button() {
- throw new coding_exception('core_renderer::update_module_button() can not be used anymore. Activity ' .
- 'modules should not add the edit module button, the link is already available in the Administration block. ' .
- 'Themes can choose to display the link in the buttons row consistently for all module types.');
- }
- /**
- * Returns HTML to display a "Turn editing on/off" button in a form.
- *
- * @param moodle_url $url The URL + params to send through when clicking the button
- * @return string HTML the button
- */
- public function edit_button(moodle_url $url) {
- if ($this->page->theme->haseditswitch == true) {
- return;
- }
- $url->param('sesskey', sesskey());
- if ($this->page->user_is_editing()) {
- $url->param('edit', 'off');
- $editstring = get_string('turneditingoff');
- } else {
- $url->param('edit', 'on');
- $editstring = get_string('turneditingon');
- }
- return $this->single_button($url, $editstring);
- }
- /**
- * Create a navbar switch for toggling editing mode.
- *
- * @return string Html containing the edit switch
- */
- public function edit_switch() {
- if ($this->page->user_allowed_editing()) {
- $temp = (object) [
- 'legacyseturl' => (new moodle_url('/editmode.php'))->out(false),
- 'pagecontextid' => $this->page->context->id,
- 'pageurl' => $this->page->url,
- 'sesskey' => sesskey(),
- ];
- if ($this->page->user_is_editing()) {
- $temp->checked = true;
- }
- return $this->render_from_template('core/editswitch', $temp);
- }
- }
- /**
- * Returns HTML to display a simple button to close a window
- *
- * @param string $text The lang string for the button's label (already output from get_string())
- * @return string html fragment
- */
- public function close_window_button($text='') {
- if (empty($text)) {
- $text = get_string('closewindow');
- }
- $button = new single_button(new moodle_url('#'), $text, 'get');
- $button->add_action(new component_action('click', 'close_window'));
- return $this->container($this->render($button), 'closewindow');
- }
- /**
- * Output an error message. By default wraps the error message in <span class="error">.
- * If the error message is blank, nothing is output.
- *
- * @param string $message the error message.
- * @return string the HTML to output.
- */
- public function error_text($message) {
- if (empty($message)) {
- return '';
- }
- $message = $this->pix_icon('i/warning', get_string('error'), '', array('class' => 'icon icon-pre', 'title'=>'')) . $message;
- return html_writer::tag('span', $message, array('class' => 'error'));
- }
- /**
- * Do not call this function directly.
- *
- * To terminate the current script with a fatal error, call the {@link print_error}
- * function, or throw an exception. Doing either of those things will then call this
- * function to display the error, before terminating the execution.
- *
- * @param string $message The message to output
- * @param string $moreinfourl URL where more info can be found about the error
- * @param string $link Link for the Continue button
- * @param array $backtrace The execution backtrace
- * @param string $debuginfo Debugging information
- * @return string the HTML to output.
- */
- public function fatal_error($message, $moreinfourl, $link, $backtrace, $debuginfo = null, $errorcode = "") {
- global $CFG;
- $output = '';
- $obbuffer = '';
- if ($this->has_started()) {
- // we can not always recover properly here, we have problems with output buffering,
- // html tables, etc.
- $output .= $this->opencontainers->pop_all_but_last();
- } else {
- // It is really bad if library code throws exception when output buffering is on,
- // because the buffered text would be printed before our start of page.
- // NOTE: this hack might be behave unexpectedly in case output buffering is enabled in PHP.ini
- error_reporting(0); // disable notices from gzip compression, etc.
- while (ob_get_level() > 0) {
- $buff = ob_get_clean();
- if ($buff === false) {
- break;
- }
- $obbuffer .= $buff;
- }
- error_reporting($CFG->debug);
- // Output not yet started.
- $protocol = (isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0');
- if (empty($_SERVER['HTTP_RANGE'])) {
- @header($protocol . ' 404 Not Found');
- } else if (core_useragent::check_safari_ios_version(602) && !empty($_SERVER['HTTP_X_PLAYBACK_SESSION_ID'])) {
- // Coax iOS 10 into sending the session cookie.
- @header($protocol . ' 403 Forbidden');
- } else {
- // Must stop byteserving attempts somehow,
- // this is weird but Chrome PDF viewer can be stopped only with 407!
- @header($protocol . ' 407 Proxy Authentication Required');
- }
- $this->page->set_context(null); // ugly hack - make sure page context is set to something, we do not want bogus warnings here
- $this->page->set_url('/'); // no url
- //$this->page->set_pagelayout('base'); //TODO: MDL-20676 blocks on error pages are weird, unfortunately it somehow detect the pagelayout from URL :-(
- $this->page->set_title(get_string('error'));
- $this->page->set_heading($this->page->course->fullname);
- $output .= $this->header();
- }
- $message = '<p class="errormessage">' . s($message) . '</p>'.
- '<p class="errorcode"><a href="' . s($moreinfourl) . '">' .
- get_string('moreinformation') . '</a></p>';
- if (empty($CFG->rolesactive)) {
- $message .= '<p class="errormessage">' . get_string('installproblem', 'error') . '</p>';
- //It is usually not possible to recover from errors triggered during installation, you may need to create a new database or use a different database prefix for new installation.
- }
- $output .= $this->box($message, 'errorbox alert alert-danger', null, array('data-rel' => 'fatalerror'));
- if ($CFG->debugdeveloper) {
- $labelsep = get_string('labelsep', 'langconfig');
- if (!empty($debuginfo)) {
- $debuginfo = s($debuginfo); // removes all nasty JS
- $debuginfo = str_replace("\n", '<br />', $debuginfo); // keep newlines
- $label = get_string('debuginfo', 'debug') . $labelsep;
- $output .= $this->notification("<strong>$label</strong> " . $debuginfo, 'notifytiny');
- }
- if (!empty($backtrace)) {
- $label = get_string('stacktrace', 'debug') . $labelsep;
- $output .= $this->notification("<strong>$label</strong> " . format_backtrace($backtrace), 'notifytiny');
- }
- if ($obbuffer !== '' ) {
- $label = get_string('outputbuffer', 'debug') . $labelsep;
- $output .= $this->notification("<strong>$label</strong> " . s($obbuffer), 'notifytiny');
- }
- }
- if (empty($CFG->rolesactive)) {
- // continue does not make much sense if moodle is not installed yet because error is most probably not recoverable
- } else if (!empty($link)) {
- $output .= $this->continue_button($link);
- }
- $output .= $this->footer();
- // Padding to encourage IE to display our error page, rather than its own.
- $output .= str_repeat(' ', 512);
- return $output;
- }
- /**
- * Output a notification (that is, a status message about something that has just happened).
- *
- * Note: \core\notification::add() may be more suitable for your usage.
- *
- * @param string $message The message to print out.
- * @param ?string $type The type of notification. See constants on \core\output\notification.
- * @param bool $closebutton Whether to show a close icon to remove the notification (default true).
- * @return string the HTML to output.
- */
- public function notification($message, $type = null, $closebutton = true) {
- $typemappings = [
- // Valid types.
- 'success' => \core\output\notification::NOTIFY_SUCCESS,
- 'info' => \core\output\notification::NOTIFY_INFO,
- 'warning' => \core\output\notification::NOTIFY_WARNING,
- 'error' => \core\output\notification::NOTIFY_ERROR,
- // Legacy types mapped to current types.
- 'notifyproblem' => \core\output\notification::NOTIFY_ERROR,
- 'notifytiny' => \core\output\notification::NOTIFY_ERROR,
- 'notifyerror' => \core\output\notification::NOTIFY_ERROR,
- 'notifysuccess' => \core\output\notification::NOTIFY_SUCCESS,
- 'notifymessage' => \core\output\notification::NOTIFY_INFO,
- 'notifyredirect' => \core\output\notification::NOTIFY_INFO,
- 'redirectmessage' => \core\output\notification::NOTIFY_INFO,
- ];
- $extraclasses = [];
- if ($type) {
- if (strpos($type, ' ') === false) {
- // No spaces in the list of classes, therefore no need to loop over and determine the class.
- if (isset($typemappings[$type])) {
- $type = $typemappings[$type];
- } else {
- // The value provided did not match a known type. It must be an extra class.
- $extraclasses = [$type];
- }
- } else {
- // Identify what type of notification this is.
- $classarray = explode(' ', self::prepare_classes($type));
- // Separate out the type of notification from the extra classes.
- foreach ($classarray as $class) {
- if (isset($typemappings[$class])) {
- $type = $typemappings[$class];
- } else {
- $extraclasses[] = $class;
- }
- }
- }
- }
- $notification = new \core\output\notification($message, $type, $closebutton);
- if (count($extraclasses)) {
- $notification->set_extra_classes($extraclasses);
- }
- // Return the rendered template.
- return $this->render_from_template($notification->get_template_name(), $notification->export_for_template($this));
- }
- /**
- * @deprecated since Moodle 3.1 MDL-30811 - please do not use this function any more.
- */
- public function notify_problem() {
- throw new coding_exception('core_renderer::notify_problem() can not be used any more, '.
- 'please use \core\notification::add(), or \core\output\notification as required.');
- }
- /**
- * @deprecated since Moodle 3.1 MDL-30811 - please do not use this function any more.
- */
- public function notify_success() {
- throw new coding_exception('core_renderer::notify_success() can not be used any more, '.
- 'please use \core\notification::add(), or \core\output\notification as required.');
- }
- /**
- * @deprecated since Moodle 3.1 MDL-30811 - please do not use this function any more.
- */
- public function notify_message() {
- throw new coding_exception('core_renderer::notify_message() can not be used any more, '.
- 'please use \core\notification::add(), or \core\output\notification as required.');
- }
- /**
- * @deprecated since Moodle 3.1 MDL-30811 - please do not use this function any more.
- */
- public function notify_redirect() {
- throw new coding_exception('core_renderer::notify_redirect() can not be used any more, '.
- 'please use \core\notification::add(), or \core\output\notification as required.');
- }
- /**
- * Render a notification (that is, a status message about something that has
- * just happened).
- *
- * @param \core\output\notification $notification the notification to print out
- * @return string the HTML to output.
- */
- protected function render_notification(\core\output\notification $notification) {
- return $this->render_from_template($notification->get_template_name(), $notification->export_for_template($this));
- }
- /**
- * Returns HTML to display a continue button that goes to a particular URL.
- *
- * @param string|moodle_url $url The url the button goes to.
- * @return string the HTML to output.
- */
- public function continue_button($url) {
- if (!($url instanceof moodle_url)) {
- $url = new moodle_url($url);
- }
- $button = new single_button($url, get_string('continue'), 'get', true);
- $button->class = 'continuebutton';
- return $this->render($button);
- }
- /**
- * Returns HTML to display a single paging bar to provide access to other pages (usually in a search)
- *
- * Theme developers: DO NOT OVERRIDE! Please override function
- * {@link core_renderer::render_paging_bar()} instead.
- *
- * @param int $totalcount The total number of entries available to be paged through
- * @param int $page The page you are currently viewing
- * @param int $perpage The number of entries that should be shown per page
- * @param string|moodle_url $baseurl url of the current page, the $pagevar parameter is added
- * @param string $pagevar name of page parameter that holds the page number
- * @return string the HTML to output.
- */
- public function paging_bar($totalcount, $page, $perpage, $baseurl, $pagevar = 'page') {
- $pb = new paging_bar($totalcount, $page, $perpage, $baseurl, $pagevar);
- return $this->render($pb);
- }
- /**
- * Returns HTML to display the paging bar.
- *
- * @param paging_bar $pagingbar
- * @return string the HTML to output.
- */
- protected function render_paging_bar(paging_bar $pagingbar) {
- // Any more than 10 is not usable and causes weird wrapping of the pagination.
- $pagingbar->maxdisplay = 10;
- return $this->render_from_template('core/paging_bar', $pagingbar->export_for_template($this));
- }
- /**
- * Returns HTML to display initials bar to provide access to other pages (usually in a search)
- *
- * @param string $current the currently selected letter.
- * @param string $class class name to add to this initial bar.
- * @param string $title the name to put in front of this initial bar.
- * @param string $urlvar URL parameter name for this initial.
- * @param string $url URL object.
- * @param array $alpha of letters in the alphabet.
- * @return string the HTML to output.
- */
- public function initials_bar($current, $class, $title, $urlvar, $url, $alpha = null) {
- $ib = new initials_bar($current, $class, $title, $urlvar, $url, $alpha);
- return $this->render($ib);
- }
- /**
- * Internal implementation of initials bar rendering.
- *
- * @param initials_bar $initialsbar
- * @return string
- */
- protected function render_initials_bar(initials_bar $initialsbar) {
- return $this->render_from_template('core/initials_bar', $initialsbar->export_for_template($this));
- }
- /**
- * Output the place a skip link goes to.
- *
- * @param string $id The target name from the corresponding $PAGE->requires->skip_link_to($target) call.
- * @return string the HTML to output.
- */
- public function skip_link_target($id = null) {
- return html_writer::span('', '', array('id' => $id));
- }
- /**
- * Outputs a heading
- *
- * @param string $text The text of the heading
- * @param int $level The level of importance of the heading. Defaulting to 2
- * @param string $classes A space-separated list of CSS classes. Defaulting to null
- * @param string $id An optional ID
- * @return string the HTML to output.
- */
- public function heading($text, $level = 2, $classes = null, $id = null) {
- $level = (integer) $level;
- if ($level < 1 or $level > 6) {
- throw new coding_exception('Heading level must be an integer between 1 and 6.');
- }
- return html_writer::tag('h' . $level, $text, array('id' => $id, 'class' => renderer_base::prepare_classes($classes)));
- }
- /**
- * Outputs a box.
- *
- * @param string $contents The contents of the box
- * @param string $classes A space-separated list of CSS classes
- * @param string $id An optional ID
- * @param array $attributes An array of other attributes to give the box.
- * @return string the HTML to output.
- */
- public function box($contents, $classes = 'generalbox', $id = null, $attributes = array()) {
- return $this->box_start($classes, $id, $attributes) . $contents . $this->box_end();
- }
- /**
- * Outputs the opening section of a box.
- *
- * @param string $classes A space-separated list of CSS classes
- * @param string $id An optional ID
- * @param array $attributes An array of other attributes to give the box.
- * @return string the HTML to output.
- */
- public function box_start($classes = 'generalbox', $id = null, $attributes = array()) {
- $this->opencontainers->push('box', html_writer::end_tag('div'));
- $attributes['id'] = $id;
- $attributes['class'] = 'box py-3 ' . renderer_base::prepare_classes($classes);
- return html_writer::start_tag('div', $attributes);
- }
- /**
- * Outputs the closing section of a box.
- *
- * @return string the HTML to output.
- */
- public function box_end() {
- return $this->opencontainers->pop('box');
- }
- /**
- * Outputs a container.
- *
- * @param string $contents The contents of the box
- * @param string $classes A space-separated list of CSS classes
- * @param string $id An optional ID
- * @return string the HTML to output.
- */
- public function container($contents, $classes = null, $id = null) {
- return $this->container_start($classes, $id) . $contents . $this->container_end();
- }
- /**
- * Outputs the opening section of a container.
- *
- * @param string $classes A space-separated list of CSS classes
- * @param string $id An optional ID
- * @return string the HTML to output.
- */
- public function container_start($classes = null, $id = null) {
- $this->opencontainers->push('container', html_writer::end_tag('div'));
- return html_writer::start_tag('div', array('id' => $id,
- 'class' => renderer_base::prepare_classes($classes)));
- }
- /**
- * Outputs the closing section of a container.
- *
- * @return string the HTML to output.
- */
- public function container_end() {
- return $this->opencontainers->pop('container');
- }
- /**
- * Make nested HTML lists out of the items
- *
- * The resulting list will look something like this:
- *
- * <pre>
- * <<ul>>
- * <<li>><div class='tree_item parent'>(item contents)</div>
- * <<ul>
- * <<li>><div class='tree_item'>(item contents)</div><</li>>
- * <</ul>>
- * <</li>>
- * <</ul>>
- * </pre>
- *
- * @param array $items
- * @param array $attrs html attributes passed to the top ofs the list
- * @return string HTML
- */
- public function tree_block_contents($items, $attrs = array()) {
- // exit if empty, we don't want an empty ul element
- if (empty($items)) {
- return '';
- }
- // array of nested li elements
- $lis = array();
- foreach ($items as $item) {
- // this applies to the li item which contains all child lists too
- $content = $item->content($this);
- $liclasses = array($item->get_css_type());
- if (!$item->forceopen || (!$item->forceopen && $item->collapse) || ($item->children->count()==0 && $item->nodetype==navigation_node::NODETYPE_BRANCH)) {
- $liclasses[] = 'collapsed';
- }
- if ($item->isactive === true) {
- $liclasses[] = 'current_branch';
- }
- $liattr = array('class'=>join(' ',$liclasses));
- // class attribute on the div item which only contains the item content
- $divclasses = array('tree_item');
- if ($item->children->count()>0 || $item->nodetype==navigation_node::NODETYPE_BRANCH) {
- $divclasses[] = 'branch';
- } else {
- $divclasses[] = 'leaf';
- }
- if (!empty($item->classes) && count($item->classes)>0) {
- $divclasses[] = join(' ', $item->classes);
- }
- $divattr = array('class'=>join(' ', $divclasses));
- if (!empty($item->id)) {
- $divattr['id'] = $item->id;
- }
- $content = html_writer::tag('p', $content, $divattr) . $this->tree_block_contents($item->children);
- if (!empty($item->preceedwithhr) && $item->preceedwithhr===true) {
- $content = html_writer::empty_tag('hr') . $content;
- }
- $content = html_writer::tag('li', $content, $liattr);
- $lis[] = $content;
- }
- return html_writer::tag('ul', implode("\n", $lis), $attrs);
- }
- /**
- * Returns a search box.
- *
- * @param string $id The search box wrapper div id, defaults to an autogenerated one.
- * @return string HTML with the search form hidden by default.
- */
- public function search_box($id = false) {
- global $CFG;
- // Accessing $CFG directly as using \core_search::is_global_search_enabled would
- // result in an extra included file for each site, even the ones where global search
- // is disabled.
- if (empty($CFG->enableglobalsearch) || !has_capability('moodle/search:query', context_system::instance())) {
- return '';
- }
- $data = [
- 'action' => new moodle_url('/search/index.php'),
- 'hiddenfields' => (object) ['name' => 'context', 'value' => $this->page->context->id],
- 'inputname' => 'q',
- 'searchstring' => get_string('search'),
- ];
- return $this->render_from_template('core/search_input_navbar', $data);
- }
- /**
- * Allow plugins to provide some content to be rendered in the navbar.
- * The plugin must define a PLUGIN_render_navbar_output function that returns
- * the HTML they wish to add to the navbar.
- *
- * @return string HTML for the navbar
- */
- public function navbar_plugin_output() {
- $output = '';
- // Give subsystems an opportunity to inject extra html content. The callback
- // must always return a string containing valid html.
- foreach (\core_component::get_core_subsystems() as $name => $path) {
- if ($path) {
- $output .= component_callback($name, 'render_navbar_output', [$this], '');
- }
- }
- if ($pluginsfunction = get_plugins_with_function('render_navbar_output')) {
- foreach ($pluginsfunction as $plugintype => $plugins) {
- foreach ($plugins as $pluginfunction) {
- $output .= $pluginfunction($this);
- }
- }
- }
- return $output;
- }
- /**
- * Construct a user menu, returning HTML that can be echoed out by a
- * layout file.
- *
- * @param stdClass $user A user object, usually $USER.
- * @param bool $withlinks true if a dropdown should be built.
- * @return string HTML fragment.
- */
- public function user_menu($user = null, $withlinks = null) {
- global $USER, $CFG;
- require_once($CFG->dirroot . '/user/lib.php');
- if (is_null($user)) {
- $user = $USER;
- }
- // Note: this behaviour is intended to match that of core_renderer::login_info,
- // but should not be considered to be good practice; layout options are
- // intended to be theme-specific. Please don't copy this snippet anywhere else.
- if (is_null($withlinks)) {
- $withlinks = empty($this->page->layout_options['nologinlinks']);
- }
- // Add a class for when $withlinks is false.
- $usermenuclasses = 'usermenu';
- if (!$withlinks) {
- $usermenuclasses .= ' withoutlinks';
- }
- $returnstr = "";
- // If during initial install, return the empty return string.
- if (during_initial_install()) {
- return $returnstr;
- }
- $loginpage = $this->is_login_page();
- $loginurl = get_login_url();
- // Get some navigation opts.
- $opts = user_get_user_navigation_info($user, $this->page);
- if (!empty($opts->unauthenticateduser)) {
- $returnstr = get_string($opts->unauthenticateduser['content'], 'moodle');
- // If not logged in, show the typical not-logged-in string.
- if (!$loginpage && (!$opts->unauthenticateduser['guest'] || $withlinks)) {
- $returnstr .= " (<a href=\"$loginurl\">" . get_string('login') . '</a>)';
- }
- return html_writer::div(
- html_writer::span(
- $returnstr,
- 'login nav-link'
- ),
- $usermenuclasses
- );
- }
- $avatarclasses = "avatars";
- $avatarcontents = html_writer::span($opts->metadata['useravatar'], 'avatar current');
- $usertextcontents = $opts->metadata['userfullname'];
- // Other user.
- if (!empty($opts->metadata['asotheruser'])) {
- $avatarcontents .= html_writer::span(
- $opts->metadata['realuseravatar'],
- 'avatar realuser'
- );
- $usertextcontents = $opts->metadata['realuserfullname'];
- $usertextcontents .= html_writer::tag(
- 'span',
- get_string(
- 'loggedinas',
- 'moodle',
- html_writer::span(
- $opts->metadata['userfullname'],
- 'value'
- )
- ),
- array('class' => 'meta viewingas')
- );
- }
- // Role.
- if (!empty($opts->metadata['asotherrole'])) {
- $role = core_text::strtolower(preg_replace('#[ ]+#', '-', trim($opts->metadata['rolename'])));
- $usertextcontents .= html_writer::span(
- $opts->metadata['rolename'],
- 'meta role role-' . $role
- );
- }
- // User login failures.
- if (!empty($opts->metadata['userloginfail'])) {
- $usertextcontents .= html_writer::span(
- $opts->metadata['userloginfail'],
- 'meta loginfailures'
- );
- }
- // MNet.
- if (!empty($opts->metadata['asmnetuser'])) {
- $mnet = strtolower(preg_replace('#[ ]+#', '-', trim($opts->metadata['mnetidprovidername'])));
- $usertextcontents .= html_writer::span(
- $opts->metadata['mnetidprovidername'],
- 'meta mnet mnet-' . $mnet
- );
- }
- $returnstr .= html_writer::span(
- html_writer::span($usertextcontents, 'usertext mr-1') .
- html_writer::span($avatarcontents, $avatarclasses),
- 'userbutton'
- );
- // Create a divider (well, a filler).
- $divider = new action_menu_filler();
- $divider->primary = false;
- $am = new action_menu();
- $am->set_menu_trigger(
- $returnstr,
- 'nav-link'
- );
- $am->set_action_label(get_string('usermenu'));
- $am->set_alignment(action_menu::TR, action_menu::BR);
- $am->set_nowrap_on_items();
- if ($withlinks) {
- $navitemcount = count($opts->navitems);
- $idx = 0;
- foreach ($opts->navitems as $key => $value) {
- switch ($value->itemtype) {
- case 'divider':
- // If the nav item is a divider, add one and skip link processing.
- $am->add($divider);
- break;
- case 'invalid':
- // Silently skip invalid entries (should we post a notification?).
- break;
- case 'link':
- // Process this as a link item.
- $pix = null;
- if (isset($value->pix) && !empty($value->pix)) {
- $pix = new pix_icon($value->pix, '', null, array('class' => 'iconsmall'));
- } else if (isset($value->imgsrc) && !empty($value->imgsrc)) {
- $value->title = html_writer::img(
- $value->imgsrc,
- $value->title,
- array('class' => 'iconsmall')
- ) . $value->title;
- }
- $al = new action_menu_link_secondary(
- $value->url,
- $pix,
- $value->title,
- array('class' => 'icon')
- );
- if (!empty($value->titleidentifier)) {
- $al->attributes['data-title'] = $value->titleidentifier;
- }
- $am->add($al);
- break;
- }
- $idx++;
- // Add dividers after the first item and before the last item.
- if ($idx == 1 || $idx == $navitemcount - 1) {
- $am->add($divider);
- }
- }
- }
- return html_writer::div(
- $this->render($am),
- $usermenuclasses
- );
- }
- /**
- * Secure layout login info.
- *
- * @return string
- */
- public function secure_layout_login_info() {
- if (get_config('core', 'logininfoinsecurelayout')) {
- return $this->login_info(false);
- } else {
- return '';
- }
- }
- /**
- * Returns the language menu in the secure layout.
- *
- * No custom menu items are passed though, such that it will render only the language selection.
- *
- * @return string
- */
- public function secure_layout_language_menu() {
- if (get_config('core', 'langmenuinsecurelayout')) {
- $custommenu = new custom_menu('', current_language());
- return $this->render_custom_menu($custommenu);
- } else {
- return '';
- }
- }
- /**
- * This renders the navbar.
- * Uses bootstrap compatible html.
- */
- public function navbar() {
- return $this->render_from_template('core/navbar', $this->page->navbar);
- }
- /**
- * Renders a breadcrumb navigation node object.
- *
- * @param breadcrumb_navigation_node $item The navigation node to render.
- * @return string HTML fragment
- */
- protected function render_breadcrumb_navigation_node(breadcrumb_navigation_node $item) {
- if ($item->action instanceof moodle_url) {
- $content = $item->get_content();
- $title = $item->get_title();
- $attributes = array();
- $attributes['itemprop'] = 'url';
- if ($title !== '') {
- $attributes['title'] = $title;
- }
- if ($item->hidden) {
- $attributes['class'] = 'dimmed_text';
- }
- if ($item->is_last()) {
- $attributes['aria-current'] = 'page';
- }
- $content = html_writer::tag('span', $content, array('itemprop' => 'title'));
- $content = html_writer::link($item->action, $content, $attributes);
- $attributes = array();
- $attributes['itemscope'] = '';
- $attributes['itemtype'] = 'http://data-vocabulary.org/Breadcrumb';
- $content = html_writer::tag('span', $content, $attributes);
- } else {
- $content = $this->render_navigation_node($item);
- }
- return $content;
- }
- /**
- * Renders a navigation node object.
- *
- * @param navigation_node $item The navigation node to render.
- * @return string HTML fragment
- */
- protected function render_navigation_node(navigation_node $item) {
- $content = $item->get_content();
- $title = $item->get_title();
- if ($item->icon instanceof renderable && !$item->hideicon) {
- $icon = $this->render($item->icon);
- $content = $icon.$content; // use CSS for spacing of icons
- }
- if ($item->helpbutton !== null) {
- $content = trim($item->helpbutton).html_writer::tag('span', $content, array('class'=>'clearhelpbutton', 'tabindex'=>'0'));
- }
- if ($content === '') {
- return '';
- }
- if ($item->action instanceof action_link) {
- $link = $item->action;
- if ($item->hidden) {
- $link->add_class('dimmed');
- }
- if (!empty($content)) {
- // Providing there is content we will use that for the link content.
- $link->text = $content;
- }
- $content = $this->render($link);
- } else if ($item->action instanceof moodle_url) {
- $attributes = array();
- if ($title !== '') {
- $attributes['title'] = $title;
- }
- if ($item->hidden) {
- $attributes['class'] = 'dimmed_text';
- }
- $content = html_writer::link($item->action, $content, $attributes);
- } else if (is_string($item->action) || empty($item->action)) {
- $attributes = array('tabindex'=>'0'); //add tab support to span but still maintain character stream sequence.
- if ($title !== '') {
- $attributes['title'] = $title;
- }
- if ($item->hidden) {
- $attributes['class'] = 'dimmed_text';
- }
- $content = html_writer::tag('span', $content, $attributes);
- }
- return $content;
- }
- /**
- * Accessibility: Right arrow-like character is
- * used in the breadcrumb trail, course navigation menu
- * (previous/next activity), calendar, and search forum block.
- * If the theme does not set characters, appropriate defaults
- * are set automatically. Please DO NOT
- * use < > » - these are confusing for blind users.
- *
- * @return string
- */
- public function rarrow() {
- return $this->page->theme->rarrow;
- }
- /**
- * Accessibility: Left arrow-like character is
- * used in the breadcrumb trail, course navigation menu
- * (previous/next activity), calendar, and search forum block.
- * If the theme does not set characters, appropriate defaults
- * are set automatically. Please DO NOT
- * use < > » - these are confusing for blind users.
- *
- * @return string
- */
- public function larrow() {
- return $this->page->theme->larrow;
- }
- /**
- * Accessibility: Up arrow-like character is used in
- * the book heirarchical navigation.
- * If the theme does not set characters, appropriate defaults
- * are set automatically. Please DO NOT
- * use ^ - this is confusing for blind users.
- *
- * @return string
- */
- public function uarrow() {
- return $this->page->theme->uarrow;
- }
- /**
- * Accessibility: Down arrow-like character.
- * If the theme does not set characters, appropriate defaults
- * are set automatically.
- *
- * @return string
- */
- public function darrow() {
- return $this->page->theme->darrow;
- }
- /**
- * Returns the custom menu if one has been set
- *
- * A custom menu can be configured by browsing to
- * Settings: Administration > Appearance > Themes > Theme settings
- * and then configuring the custommenu config setting as described.
- *
- * Theme developers: DO NOT OVERRIDE! Please override function
- * {@link core_renderer::render_custom_menu()} instead.
- *
- * @param string $custommenuitems - custom menuitems set by theme instead of global theme settings
- * @return string
- */
- public function custom_menu($custommenuitems = '') {
- global $CFG;
- if (empty($custommenuitems) && !empty($CFG->custommenuitems)) {
- $custommenuitems = $CFG->custommenuitems;
- }
- $custommenu = new custom_menu($custommenuitems, current_language());
- return $this->render_custom_menu($custommenu);
- }
- /**
- * We want to show the custom menus as a list of links in the footer on small screens.
- * Just return the menu object exported so we can render it differently.
- */
- public function custom_menu_flat() {
- global $CFG;
- $custommenuitems = '';
- if (empty($custommenuitems) && !empty($CFG->custommenuitems)) {
- $custommenuitems = $CFG->custommenuitems;
- }
- $custommenu = new custom_menu($custommenuitems, current_language());
- $langs = get_string_manager()->get_list_of_translations();
- $haslangmenu = $this->lang_menu() != '';
- if ($haslangmenu) {
- $strlang = get_string('language');
- $currentlang = current_language();
- if (isset($langs[$currentlang])) {
- $currentlang = $langs[$currentlang];
- } else {
- $currentlang = $strlang;
- }
- $this->language = $custommenu->add($currentlang, new moodle_url('#'), $strlang, 10000);
- foreach ($langs as $langtype => $langname) {
- $this->language->add($langname, new moodle_url($this->page->url, array('lang' => $langtype)), $langname);
- }
- }
- return $custommenu->export_for_template($this);
- }
- /**
- * Renders a custom menu object (located in outputcomponents.php)
- *
- * The custom menu this method produces makes use of the YUI3 menunav widget
- * and requires very specific html elements and classes.
- *
- * @staticvar int $menucount
- * @param custom_menu $menu
- * @return string
- */
- protected function render_custom_menu(custom_menu $menu) {
- global $CFG;
- $langs = get_string_manager()->get_list_of_translations();
- $haslangmenu = $this->lang_menu() != '';
- if (!$menu->has_children() && !$haslangmenu) {
- return '';
- }
- if ($haslangmenu) {
- $strlang = get_string('language');
- $currentlang = current_language();
- if (isset($langs[$currentlang])) {
- $currentlang = $langs[$currentlang];
- } else {
- $currentlang = $strlang;
- }
- $this->language = $menu->add($currentlang, new moodle_url('#'), $strlang, 10000);
- foreach ($langs as $langtype => $langname) {
- $this->language->add($langname, new moodle_url($this->page->url, array('lang' => $langtype)), $langname);
- }
- }
- $content = '';
- foreach ($menu->get_children() as $item) {
- $context = $item->export_for_template($this);
- $content .= $this->render_from_template('core/custom_menu_item', $context);
- }
- return $content;
- }
- /**
- * Renders a custom menu node as part of a submenu
- *
- * The custom menu this method produces makes use of the YUI3 menunav widget
- * and requires very specific html elements and classes.
- *
- * @see core:renderer::render_custom_menu()
- *
- * @staticvar int $submenucount
- * @param custom_menu_item $menunode
- * @return string
- */
- protected function render_custom_menu_item(custom_menu_item $menunode) {
- // Required to ensure we get unique trackable id's
- static $submenucount = 0;
- if ($menunode->has_children()) {
- // If the child has menus render it as a sub menu
- $submenucount++;
- $content = html_writer::start_tag('li');
- if ($menunode->get_url() !== null) {
- $url = $menunode->get_url();
- } else {
- $url = '#cm_submenu_'.$submenucount;
- }
- $content .= html_writer::link($url, $menunode->get_text(), array('class'=>'yui3-menu-label', 'title'=>$menunode->get_title()));
- $content .= html_writer::start_tag('div', array('id'=>'cm_submenu_'.$submenucount, 'class'=>'yui3-menu custom_menu_submenu'));
- $content .= html_writer::start_tag('div', array('class'=>'yui3-menu-content'));
- $content .= html_writer::start_tag('ul');
- foreach ($menunode->get_children() as $menunode) {
- $content .= $this->render_custom_menu_item($menunode);
- }
- $content .= html_writer::end_tag('ul');
- $content .= html_writer::end_tag('div');
- $content .= html_writer::end_tag('div');
- $content .= html_writer::end_tag('li');
- } else {
- // The node doesn't have children so produce a final menuitem.
- // Also, if the node's text matches '####', add a class so we can treat it as a divider.
- $content = '';
- if (preg_match("/^#+$/", $menunode->get_text())) {
- // This is a divider.
- $content = html_writer::start_tag('li', array('class' => 'yui3-menuitem divider'));
- } else {
- $content = html_writer::start_tag(
- 'li',
- array(
- 'class' => 'yui3-menuitem'
- )
- );
- if ($menunode->get_url() !== null) {
- $url = $menunode->get_url();
- } else {
- $url = '#';
- }
- $content .= html_writer::link(
- $url,
- $menunode->get_text(),
- array('class' => 'yui3-menuitem-content', 'title' => $menunode->get_title())
- );
- }
- $content .= html_writer::end_tag('li');
- }
- // Return the sub menu
- return $content;
- }
- /**
- * Renders theme links for switching between default and other themes.
- *
- * @return string
- */
- protected function theme_switch_links() {
- $actualdevice = core_useragent::get_device_type();
- $currentdevice = $this->page->devicetypeinuse;
- $switched = ($actualdevice != $currentdevice);
- if (!$switched && $currentdevice == 'default' && $actualdevice == 'default') {
- // The user is using the a default device and hasn't switched so don't shown the switch
- // device links.
- return '';
- }
- if ($switched) {
- $linktext = get_string('switchdevicerecommended');
- $devicetype = $actualdevice;
- } else {
- $linktext = get_string('switchdevicedefault');
- $devicetype = 'default';
- }
- $linkurl = new moodle_url('/theme/switchdevice.php', array('url' => $this->page->url, 'device' => $devicetype, 'sesskey' => sesskey()));
- $content = html_writer::start_tag('div', array('id' => 'theme_switch_link'));
- $content .= html_writer::link($linkurl, $linktext, array('rel' => 'nofollow'));
- $content .= html_writer::end_tag('div');
- return $content;
- }
- /**
- * Renders tabs
- *
- * This function replaces print_tabs() used before Moodle 2.5 but with slightly different arguments
- *
- * Theme developers: In order to change how tabs are displayed please override functions
- * {@link core_renderer::render_tabtree()} and/or {@link core_renderer::render_tabobject()}
- *
- * @param array $tabs array of tabs, each of them may have it's own ->subtree
- * @param string|null $selected which tab to mark as selected, all parent tabs will
- * automatically be marked as activated
- * @param array|string|null $inactive list of ids of inactive tabs, regardless of
- * their level. Note that you can as weel specify tabobject::$inactive for separate instances
- * @return string
- */
- public final function tabtree($tabs, $selected = null, $inactive = null) {
- return $this->render(new tabtree($tabs, $selected, $inactive));
- }
- /**
- * Renders tabtree
- *
- * @param tabtree $tabtree
- * @return string
- */
- protected function render_tabtree(tabtree $tabtree) {
- if (empty($tabtree->subtree)) {
- return '';
- }
- $data = $tabtree->export_for_template($this);
- return $this->render_from_template('core/tabtree', $data);
- }
- /**
- * Renders tabobject (part of tabtree)
- *
- * This function is called from {@link core_renderer::render_tabtree()}
- * and also it calls itself when printing the $tabobject subtree recursively.
- *
- * Property $tabobject->level indicates the number of row of tabs.
- *
- * @param tabobject $tabobject
- * @return string HTML fragment
- */
- protected function render_tabobject(tabobject $tabobject) {
- $str = '';
- // Print name of the current tab.
- if ($tabobject instanceof tabtree) {
- // No name for tabtree root.
- } else if ($tabobject->inactive || $tabobject->activated || ($tabobject->selected && !$tabobject->linkedwhenselected)) {
- // Tab name without a link. The <a> tag is used for styling.
- $str .= html_writer::tag('a', html_writer::span($tabobject->text), array('class' => 'nolink moodle-has-zindex'));
- } else {
- // Tab name with a link.
- if (!($tabobject->link instanceof moodle_url)) {
- // backward compartibility when link was passed as quoted string
- $str .= "<a href=\"$tabobject->link\" title=\"$tabobject->title\"><span>$tabobject->text</span></a>";
- } else {
- $str .= html_writer::link($tabobject->link, html_writer::span($tabobject->text), array('title' => $tabobject->title));
- }
- }
- if (empty($tabobject->subtree)) {
- if ($tabobject->selected) {
- $str .= html_writer::tag('div', ' ', array('class' => 'tabrow'. ($tabobject->level + 1). ' empty'));
- }
- return $str;
- }
- // Print subtree.
- if ($tabobject->level == 0 || $tabobject->selected || $tabobject->activated) {
- $str .= html_writer::start_tag('ul', array('class' => 'tabrow'. $tabobject->level));
- $cnt = 0;
- foreach ($tabobject->subtree as $tab) {
- $liclass = '';
- if (!$cnt) {
- $liclass .= ' first';
- }
- if ($cnt == count($tabobject->subtree) - 1) {
- $liclass .= ' last';
- }
- if ((empty($tab->subtree)) && (!empty($tab->selected))) {
- $liclass .= ' onerow';
- }
- if ($tab->selected) {
- $liclass .= ' here selected';
- } else if ($tab->activated) {
- $liclass .= ' here active';
- }
- // This will recursively call function render_tabobject() for each item in subtree.
- $str .= html_writer::tag('li', $this->render($tab), array('class' => trim($liclass)));
- $cnt++;
- }
- $str .= html_writer::end_tag('ul');
- }
- return $str;
- }
- /**
- * Get the HTML for blocks in the given region.
- *
- * @since Moodle 2.5.1 2.6
- * @param string $region The region to get HTML for.
- * @param array $classes Wrapping tag classes.
- * @param string $tag Wrapping tag.
- * @param boolean $fakeblocksonly Include fake blocks only.
- * @return string HTML.
- */
- public function blocks($region, $classes = array(), $tag = 'aside', $fakeblocksonly = false) {
- $displayregion = $this->page->apply_theme_region_manipulations($region);
- $classes = (array)$classes;
- $classes[] = 'block-region';
- $attributes = array(
- 'id' => 'block-region-'.preg_replace('#[^a-zA-Z0-9_\-]+#', '-', $displayregion),
- 'class' => join(' ', $classes),
- 'data-blockregion' => $displayregion,
- 'data-droptarget' => '1'
- );
- if ($this->page->blocks->region_has_content($displayregion, $this)) {
- $content = $this->blocks_for_region($displayregion, $fakeblocksonly);
- } else {
- $content = '';
- }
- return html_writer::tag($tag, $content, $attributes);
- }
- /**
- * Renders a custom block region.
- *
- * Use this method if you want to add an additional block region to the content of the page.
- * Please note this should only be used in special situations.
- * We want to leave the theme is control where ever possible!
- *
- * This method must use the same method that the theme uses within its layout file.
- * As such it asks the theme what method it is using.
- * It can be one of two values, blocks or blocks_for_region (deprecated).
- *
- * @param string $regionname The name of the custom region to add.
- * @return string HTML for the block region.
- */
- public function custom_block_region($regionname) {
- if ($this->page->theme->get_block_render_method() === 'blocks') {
- return $this->blocks($regionname);
- } else {
- return $this->blocks_for_region($regionname);
- }
- }
- /**
- * Returns the CSS classes to apply to the body tag.
- *
- * @since Moodle 2.5.1 2.6
- * @param array $additionalclasses Any additional classes to apply.
- * @return string
- */
- public function body_css_classes(array $additionalclasses = array()) {
- return $this->page->bodyclasses . ' ' . implode(' ', $additionalclasses);
- }
- /**
- * The ID attribute to apply to the body tag.
- *
- * @since Moodle 2.5.1 2.6
- * @return string
- */
- public function body_id() {
- return $this->page->bodyid;
- }
- /**
- * Returns HTML attributes to use within the body tag. This includes an ID and classes.
- *
- * @since Moodle 2.5.1 2.6
- * @param string|array $additionalclasses Any additional classes to give the body tag,
- * @return string
- */
- public function body_attributes($additionalclasses = array()) {
- if (!is_array($additionalclasses)) {
- $additionalclasses = explode(' ', $additionalclasses);
- }
- return ' id="'. $this->body_id().'" class="'.$this->body_css_classes($additionalclasses).'"';
- }
- /**
- * Gets HTML for the page heading.
- *
- * @since Moodle 2.5.1 2.6
- * @param string $tag The tag to encase the heading in. h1 by default.
- * @return string HTML.
- */
- public function page_heading($tag = 'h1') {
- return html_writer::tag($tag, $this->page->heading);
- }
- /**
- * Gets the HTML for the page heading button.
- *
- * @since Moodle 2.5.1 2.6
- * @return string HTML.
- */
- public function page_heading_button() {
- return $this->page->button;
- }
- /**
- * Returns the Moodle docs link to use for this page.
- *
- * @since Moodle 2.5.1 2.6
- * @param string $text
- * @return string
- */
- public function page_doc_link($text = null) {
- if ($text === null) {
- $text = get_string('moodledocslink');
- }
- $path = page_get_doc_link_path($this->page);
- if (!$path) {
- return '';
- }
- return $this->doc_link($path, $text);
- }
- /**
- * Returns the page heading menu.
- *
- * @since Moodle 2.5.1 2.6
- * @return string HTML.
- */
- public function page_heading_menu() {
- return $this->page->headingmenu;
- }
- /**
- * Returns the title to use on the page.
- *
- * @since Moodle 2.5.1 2.6
- * @return string
- */
- public function page_title() {
- return $this->page->title;
- }
- /**
- * Returns the moodle_url for the favicon.
- *
- * @since Moodle 2.5.1 2.6
- * @return moodle_url The moodle_url for the favicon
- */
- public function favicon() {
- return $this->image_url('favicon', 'theme');
- }
- /**
- * Renders preferences groups.
- *
- * @param preferences_groups $renderable The renderable
- * @return string The output.
- */
- public function render_preferences_groups(preferences_groups $renderable) {
- return $this->render_from_template('core/preferences_groups', $renderable);
- }
- /**
- * Renders preferences group.
- *
- * @param preferences_group $renderable The renderable
- * @return string The output.
- */
- public function render_preferences_group(preferences_group $renderable) {
- $html = '';
- $html .= html_writer::start_tag('div', array('class' => 'col-sm-4 preferences-group'));
- $html .= $this->heading($renderable->title, 3);
- $html .= html_writer::start_tag('ul');
- foreach ($renderable->nodes as $node) {
- if ($node->has_children()) {
- debugging('Preferences nodes do not support children', DEBUG_DEVELOPER);
- }
- $html .= html_writer::tag('li', $this->render($node));
- }
- $html .= html_writer::end_tag('ul');
- $html .= html_writer::end_tag('div');
- return $html;
- }
- public function context_header($headerinfo = null, $headinglevel = 1) {
- global $DB, $USER, $CFG, $SITE;
- require_once($CFG->dirroot . '/user/lib.php');
- $context = $this->page->context;
- $heading = null;
- $imagedata = null;
- $subheader = null;
- $userbuttons = null;
- // Make sure to use the heading if it has been set.
- if (isset($headerinfo['heading'])) {
- $heading = $headerinfo['heading'];
- } else {
- $heading = $this->page->heading;
- }
- // The user context currently has images and buttons. Other contexts may follow.
- if (isset($headerinfo['user']) || $context->contextlevel == CONTEXT_USER) {
- if (isset($headerinfo['user'])) {
- $user = $headerinfo['user'];
- } else {
- // Look up the user information if it is not supplied.
- $user = $DB->get_record('user', array('id' => $context->instanceid));
- }
- // If the user context is set, then use that for capability checks.
- if (isset($headerinfo['usercontext'])) {
- $context = $headerinfo['usercontext'];
- }
- // Only provide user information if the user is the current user, or a user which the current user can view.
- // When checking user_can_view_profile(), either:
- // If the page context is course, check the course context (from the page object) or;
- // If page context is NOT course, then check across all courses.
- $course = ($this->page->context->contextlevel == CONTEXT_COURSE) ? $this->page->course : null;
- if (user_can_view_profile($user, $course)) {
- // Use the user's full name if the heading isn't set.
- if (empty($heading)) {
- $heading = fullname($user);
- }
- $imagedata = $this->user_picture($user, array('size' => 100));
- // Check to see if we should be displaying a message button.
- if (!empty($CFG->messaging) && has_capability('moodle/site:sendmessage', $context)) {
- $userbuttons = array(
- 'messages' => array(
- 'buttontype' => 'message',
- 'title' => get_string('message', 'message'),
- 'url' => new moodle_url('/message/index.php', array('id' => $user->id)),
- 'image' => 'message',
- 'linkattributes' => \core_message\helper::messageuser_link_params($user->id),
- 'page' => $this->page
- )
- );
- if ($USER->id != $user->id) {
- $iscontact = \core_message\api::is_contact($USER->id, $user->id);
- $contacttitle = $iscontact ? 'removefromyourcontacts' : 'addtoyourcontacts';
- $contacturlaction = $iscontact ? 'removecontact' : 'addcontact';
- $contactimage = $iscontact ? 'removecontact' : 'addcontact';
- $userbuttons['togglecontact'] = array(
- 'buttontype' => 'togglecontact',
- 'title' => get_string($contacttitle, 'message'),
- 'url' => new moodle_url('/message/index.php', array(
- 'user1' => $USER->id,
- 'user2' => $user->id,
- $contacturlaction => $user->id,
- 'sesskey' => sesskey())
- ),
- 'image' => $contactimage,
- 'linkattributes' => \core_message\helper::togglecontact_link_params($user, $iscontact),
- 'page' => $this->page
- );
- }
- }
- } else {
- $heading = null;
- }
- }
- if ($this->should_display_main_logo($headinglevel)) {
- $sitename = format_string($SITE->fullname, true, ['context' => context_course::instance(SITEID)]);
- // Logo.
- $html = html_writer::div(
- html_writer::empty_tag('img', [
- 'src' => $this->get_logo_url(null, 150),
- 'alt' => get_string('logoof', '', $sitename),
- 'class' => 'img-fluid'
- ]),
- 'logo'
- );
- // Heading.
- if (!isset($heading)) {
- $html .= $this->heading($this->page->heading, $headinglevel, 'sr-only');
- } else {
- $html .= $this->heading($heading, $headinglevel, 'sr-only');
- }
- return $html;
- }
- $contextheader = new context_header($heading, $headinglevel, $imagedata, $userbuttons);
- return $this->render_context_header($contextheader);
- }
- /**
- * Renders the skip links for the page.
- *
- * @param array $links List of skip links.
- * @return string HTML for the skip links.
- */
- public function render_skip_links($links) {
- $context = [ 'links' => []];
- foreach ($links as $url => $text) {
- $context['links'][] = [ 'url' => $url, 'text' => $text];
- }
- return $this->render_from_template('core/skip_links', $context);
- }
- /**
- * Renders the header bar.
- *
- * @param context_header $contextheader Header bar object.
- * @return string HTML for the header bar.
- */
- protected function render_context_header(context_header $contextheader) {
- // Generate the heading first and before everything else as we might have to do an early return.
- if (!isset($contextheader->heading)) {
- $heading = $this->heading($this->page->heading, $contextheader->headinglevel);
- } else {
- $heading = $this->heading($contextheader->heading, $contextheader->headinglevel);
- }
- $showheader = empty($this->page->layout_options['nocontextheader']);
- if (!$showheader) {
- // Return the heading wrapped in an sr-only element so it is only visible to screen-readers.
- return html_writer::div($heading, 'sr-only');
- }
- // All the html stuff goes here.
- $html = html_writer::start_div('page-context-header');
- // Image data.
- if (isset($contextheader->imagedata)) {
- // Header specific image.
- $html .= html_writer::div($contextheader->imagedata, 'page-header-image icon-size-7');
- }
- // Headings.
- if (isset($contextheader->prefix)) {
- $prefix = html_writer::div($contextheader->prefix, 'text-muted');
- $heading = $prefix . $heading;
- }
- $html .= html_writer::tag('div', $heading, array('class' => 'page-header-headings'));
- // Buttons.
- if (isset($contextheader->additionalbuttons)) {
- $html .= html_writer::start_div('btn-group header-button-group');
- foreach ($contextheader->additionalbuttons as $button) {
- if (!isset($button->page)) {
- // Include js for messaging.
- if ($button['buttontype'] === 'togglecontact') {
- \core_message\helper::togglecontact_requirejs();
- }
- if ($button['buttontype'] === 'message') {
- \core_message\helper::messageuser_requirejs();
- }
- $image = $this->pix_icon($button['formattedimage'], $button['title'], 'moodle', array(
- 'class' => 'iconsmall',
- 'role' => 'presentation'
- ));
- $image .= html_writer::span($button['title'], 'header-button-title');
- } else {
- $image = html_writer::empty_tag('img', array(
- 'src' => $button['formattedimage'],
- 'role' => 'presentation'
- ));
- }
- $html .= html_writer::link($button['url'], html_writer::tag('span', $image), $button['linkattributes']);
- }
- $html .= html_writer::end_div();
- }
- $html .= html_writer::end_div();
- return $html;
- }
- /**
- * Wrapper for header elements.
- *
- * @return string HTML to display the main header.
- */
- public function full_header() {
- $pagetype = $this->page->pagetype;
- $homepage = get_home_page();
- $homepagetype = null;
- // Add a special case since /my/courses is a part of the /my subsystem.
- if ($homepage == HOMEPAGE_MY && $this->page->title !== get_string('mycourses')) {
- $homepagetype = 'my-index';
- } else if ($homepage == HOMEPAGE_SITE) {
- $homepagetype = 'site-index';
- }
- if ($this->page->include_region_main_settings_in_header_actions() &&
- !$this->page->blocks->is_block_present('settings')) {
- // Only include the region main settings if the page has requested it and it doesn't already have
- // the settings block on it. The region main settings are included in the settings block and
- // duplicating the content causes behat failures.
- $this->page->add_header_action(html_writer::div(
- $this->region_main_settings_menu(),
- 'd-print-none',
- ['id' => 'region-main-settings-menu']
- ));
- }
- $header = new stdClass();
- $header->settingsmenu = $this->context_header_settings_menu();
- $header->contextheader = $this->context_header();
- $header->hasnavbar = empty($this->page->layout_options['nonavbar']);
- $header->navbar = $this->navbar();
- $header->pageheadingbutton = $this->page_heading_button();
- $header->courseheader = $this->course_header();
- $header->headeractions = $this->page->get_header_actions();
- if (!empty($pagetype) && !empty($homepagetype) && $pagetype == $homepagetype) {
- $header->welcomemessage = \core_user::welcome_message();
- }
- return $this->render_from_template('core/full_header', $header);
- }
- /**
- * This is an optional menu that can be added to a layout by a theme. It contains the
- * menu for the course administration, only on the course main page.
- *
- * @return string
- */
- public function context_header_settings_menu() {
- $context = $this->page->context;
- $menu = new action_menu();
- $items = $this->page->navbar->get_items();
- $currentnode = end($items);
- $showcoursemenu = false;
- $showfrontpagemenu = false;
- $showusermenu = false;
- // We are on the course home page.
- if (($context->contextlevel == CONTEXT_COURSE) &&
- !empty($currentnode) &&
- ($currentnode->type == navigation_node::TYPE_COURSE || $currentnode->type == navigation_node::TYPE_SECTION)) {
- $showcoursemenu = true;
- }
- $courseformat = course_get_format($this->page->course);
- // This is a single activity course format, always show the course menu on the activity main page.
- if ($context->contextlevel == CONTEXT_MODULE &&
- !$courseformat->has_view_page()) {
- $this->page->navigation->initialise();
- $activenode = $this->page->navigation->find_active_node();
- // If the settings menu has been forced then show the menu.
- if ($this->page->is_settings_menu_forced()) {
- $showcoursemenu = true;
- } else if (!empty($activenode) && ($activenode->type == navigation_node::TYPE_ACTIVITY ||
- $activenode->type == navigation_node::TYPE_RESOURCE)) {
- // We only want to show the menu on the first page of the activity. This means
- // the breadcrumb has no additional nodes.
- if ($currentnode && ($currentnode->key == $activenode->key && $currentnode->type == $activenode->type)) {
- $showcoursemenu = true;
- }
- }
- }
- // This is the site front page.
- if ($context->contextlevel == CONTEXT_COURSE &&
- !empty($currentnode) &&
- $currentnode->key === 'home') {
- $showfrontpagemenu = true;
- }
- // This is the user profile page.
- if ($context->contextlevel == CONTEXT_USER &&
- !empty($currentnode) &&
- ($currentnode->key === 'myprofile')) {
- $showusermenu = true;
- }
- if ($showfrontpagemenu) {
- $settingsnode = $this->page->settingsnav->find('frontpage', navigation_node::TYPE_SETTING);
- if ($settingsnode) {
- // Build an action menu based on the visible nodes from this navigation tree.
- $skipped = $this->build_action_menu_from_navigation($menu, $settingsnode, false, true);
- // We only add a list to the full settings menu if we didn't include every node in the short menu.
- if ($skipped) {
- $text = get_string('morenavigationlinks');
- $url = new moodle_url('/course/admin.php', array('courseid' => $this->page->course->id));
- $link = new action_link($url, $text, null, null, new pix_icon('t/edit', $text));
- $menu->add_secondary_action($link);
- }
- }
- } else if ($showcoursemenu) {
- $settingsnode = $this->page->settingsnav->find('courseadmin', navigation_node::TYPE_COURSE);
- if ($settingsnode) {
- // Build an action menu based on the visible nodes from this navigation tree.
- $skipped = $this->build_action_menu_from_navigation($menu, $settingsnode, false, true);
- // We only add a list to the full settings menu if we didn't include every node in the short menu.
- if ($skipped) {
- $text = get_string('morenavigationlinks');
- $url = new moodle_url('/course/admin.php', array('courseid' => $this->page->course->id));
- $link = new action_link($url, $text, null, null, new pix_icon('t/edit', $text));
- $menu->add_secondary_action($link);
- }
- }
- } else if ($showusermenu) {
- // Get the course admin node from the settings navigation.
- $settingsnode = $this->page->settingsnav->find('useraccount', navigation_node::TYPE_CONTAINER);
- if ($settingsnode) {
- // Build an action menu based on the visible nodes from this navigation tree.
- $this->build_action_menu_from_navigation($menu, $settingsnode);
- }
- }
- return $this->render($menu);
- }
- /**
- * Take a node in the nav tree and make an action menu out of it.
- * The links are injected in the action menu.
- *
- * @param action_menu $menu
- * @param navigation_node $node
- * @param boolean $indent
- * @param boolean $onlytopleafnodes
- * @return boolean nodesskipped - True if nodes were skipped in building the menu
- */
- protected function build_action_menu_from_navigation(action_menu $menu,
- navigation_node $node,
- $indent = false,
- $onlytopleafnodes = false) {
- $skipped = false;
- // Build an action menu based on the visible nodes from this navigation tree.
- foreach ($node->children as $menuitem) {
- if ($menuitem->display) {
- if ($onlytopleafnodes && $menuitem->children->count()) {
- $skipped = true;
- continue;
- }
- if ($menuitem->action) {
- if ($menuitem->action instanceof action_link) {
- $link = $menuitem->action;
- // Give preference to setting icon over action icon.
- if (!empty($menuitem->icon)) {
- $link->icon = $menuitem->icon;
- }
- } else {
- $link = new action_link($menuitem->action, $menuitem->text, null, null, $menuitem->icon);
- }
- } else {
- if ($onlytopleafnodes) {
- $skipped = true;
- continue;
- }
- $link = new action_link(new moodle_url('#'), $menuitem->text, null, ['disabled' => true], $menuitem->icon);
- }
- if ($indent) {
- $link->add_class('ml-4');
- }
- if (!empty($menuitem->classes)) {
- $link->add_class(implode(" ", $menuitem->classes));
- }
- $menu->add_secondary_action($link);
- $skipped = $skipped || $this->build_action_menu_from_navigation($menu, $menuitem, true);
- }
- }
- return $skipped;
- }
- /**
- * This is an optional menu that can be added to a layout by a theme. It contains the
- * menu for the most specific thing from the settings block. E.g. Module administration.
- *
- * @return string
- */
- public function region_main_settings_menu() {
- $context = $this->page->context;
- $menu = new action_menu();
- if ($context->contextlevel == CONTEXT_MODULE) {
- $this->page->navigation->initialise();
- $node = $this->page->navigation->find_active_node();
- $buildmenu = false;
- // If the settings menu has been forced then show the menu.
- if ($this->page->is_settings_menu_forced()) {
- $buildmenu = true;
- } else if (!empty($node) && ($node->type == navigation_node::TYPE_ACTIVITY ||
- $node->type == navigation_node::TYPE_RESOURCE)) {
- $items = $this->page->navbar->get_items();
- $navbarnode = end($items);
- // We only want to show the menu on the first page of the activity. This means
- // the breadcrumb has no additional nodes.
- if ($navbarnode && ($navbarnode->key === $node->key && $navbarnode->type == $node->type)) {
- $buildmenu = true;
- }
- }
- if ($buildmenu) {
- // Get the course admin node from the settings navigation.
- $node = $this->page->settingsnav->find('modulesettings', navigation_node::TYPE_SETTING);
- if ($node) {
- // Build an action menu based on the visible nodes from this navigation tree.
- $this->build_action_menu_from_navigation($menu, $node);
- }
- }
- } else if ($context->contextlevel == CONTEXT_COURSECAT) {
- // For course category context, show category settings menu, if we're on the course category page.
- if ($this->page->pagetype === 'course-index-category') {
- $node = $this->page->settingsnav->find('categorysettings', navigation_node::TYPE_CONTAINER);
- if ($node) {
- // Build an action menu based on the visible nodes from this navigation tree.
- $this->build_action_menu_from_navigation($menu, $node);
- }
- }
- } else {
- $items = $this->page->navbar->get_items();
- $navbarnode = end($items);
- if ($navbarnode && ($navbarnode->key === 'participants')) {
- $node = $this->page->settingsnav->find('users', navigation_node::TYPE_CONTAINER);
- if ($node) {
- // Build an action menu based on the visible nodes from this navigation tree.
- $this->build_action_menu_from_navigation($menu, $node);
- }
- }
- }
- return $this->render($menu);
- }
- /**
- * Displays the list of tags associated with an entry
- *
- * @param array $tags list of instances of core_tag or stdClass
- * @param string $label label to display in front, by default 'Tags' (get_string('tags')), set to null
- * to use default, set to '' (empty string) to omit the label completely
- * @param string $classes additional classes for the enclosing div element
- * @param int $limit limit the number of tags to display, if size of $tags is more than this limit the "more" link
- * will be appended to the end, JS will toggle the rest of the tags
- * @param context $pagecontext specify if needed to overwrite the current page context for the view tag link
- * @param bool $accesshidelabel if true, the label should have class="accesshide" added.
- * @return string
- */
- public function tag_list($tags, $label = null, $classes = '', $limit = 10,
- $pagecontext = null, $accesshidelabel = false) {
- $list = new \core_tag\output\taglist($tags, $label, $classes, $limit, $pagecontext, $accesshidelabel);
- return $this->render_from_template('core_tag/taglist', $list->export_for_template($this));
- }
- /**
- * Renders element for inline editing of any value
- *
- * @param \core\output\inplace_editable $element
- * @return string
- */
- public function render_inplace_editable(\core\output\inplace_editable $element) {
- return $this->render_from_template('core/inplace_editable', $element->export_for_template($this));
- }
- /**
- * Renders a bar chart.
- *
- * @param \core\chart_bar $chart The chart.
- * @return string.
- */
- public function render_chart_bar(\core\chart_bar $chart) {
- return $this->render_chart($chart);
- }
- /**
- * Renders a line chart.
- *
- * @param \core\chart_line $chart The chart.
- * @return string.
- */
- public function render_chart_line(\core\chart_line $chart) {
- return $this->render_chart($chart);
- }
- /**
- * Renders a pie chart.
- *
- * @param \core\chart_pie $chart The chart.
- * @return string.
- */
- public function render_chart_pie(\core\chart_pie $chart) {
- return $this->render_chart($chart);
- }
- /**
- * Renders a chart.
- *
- * @param \core\chart_base $chart The chart.
- * @param bool $withtable Whether to include a data table with the chart.
- * @return string.
- */
- public function render_chart(\core\chart_base $chart, $withtable = true) {
- $chartdata = json_encode($chart);
- return $this->render_from_template('core/chart', (object) [
- 'chartdata' => $chartdata,
- 'withtable' => $withtable
- ]);
- }
- /**
- * Renders the login form.
- *
- * @param \core_auth\output\login $form The renderable.
- * @return string
- */
- public function render_login(\core_auth\output\login $form) {
- global $CFG, $SITE;
- $context = $form->export_for_template($this);
- // Override because rendering is not supported in template yet.
- if ($CFG->rememberusername == 0) {
- $context->cookieshelpiconformatted = $this->help_icon('cookiesenabledonlysession');
- } else {
- $context->cookieshelpiconformatted = $this->help_icon('cookiesenabled');
- }
- $context->errorformatted = $this->error_text($context->error);
- $url = $this->get_logo_url();
- if ($url) {
- $url = $url->out(false);
- }
- $context->logourl = $url;
- $context->sitename = format_string($SITE->fullname, true,
- ['context' => context_course::instance(SITEID), "escape" => false]);
- return $this->render_from_template('core/loginform', $context);
- }
- /**
- * Renders an mform element from a template.
- *
- * @param HTML_QuickForm_element $element element
- * @param bool $required if input is required field
- * @param bool $advanced if input is an advanced field
- * @param string $error error message to display
- * @param bool $ingroup True if this element is rendered as part of a group
- * @return mixed string|bool
- */
- public function mform_element($element, $required, $advanced, $error, $ingroup) {
- $templatename = 'core_form/element-' . $element->getType();
- if ($ingroup) {
- $templatename .= "-inline";
- }
- try {
- // We call this to generate a file not found exception if there is no template.
- // We don't want to call export_for_template if there is no template.
- core\output\mustache_template_finder::get_template_filepath($templatename);
- if ($element instanceof templatable) {
- $elementcontext = $element->export_for_template($this);
- $helpbutton = '';
- if (method_exists($element, 'getHelpButton')) {
- $helpbutton = $element->getHelpButton();
- }
- $label = $element->getLabel();
- $text = '';
- if (method_exists($element, 'getText')) {
- // There currently exists code that adds a form element with an empty label.
- // If this is the case then set the label to the description.
- if (empty($label)) {
- $label = $element->getText();
- } else {
- $text = $element->getText();
- }
- }
- // Generate the form element wrapper ids and names to pass to the template.
- // This differs between group and non-group elements.
- if ($element->getType() === 'group') {
- // Group element.
- // The id will be something like 'fgroup_id_NAME'. E.g. fgroup_id_mygroup.
- $elementcontext['wrapperid'] = $elementcontext['id'];
- // Ensure group elements pass through the group name as the element name.
- $elementcontext['name'] = $elementcontext['groupname'];
- } else {
- // Non grouped element.
- // Creates an id like 'fitem_id_NAME'. E.g. fitem_id_mytextelement.
- $elementcontext['wrapperid'] = 'fitem_' . $elementcontext['id'];
- }
- $context = array(
- 'element' => $elementcontext,
- 'label' => $label,
- 'text' => $text,
- 'required' => $required,
- 'advanced' => $advanced,
- 'helpbutton' => $helpbutton,
- 'error' => $error
- );
- return $this->render_from_template($templatename, $context);
- }
- } catch (Exception $e) {
- // No template for this element.
- return false;
- }
- }
- /**
- * Render the login signup form into a nice template for the theme.
- *
- * @param mform $form
- * @return string
- */
- public function render_login_signup_form($form) {
- global $SITE;
- $context = $form->export_for_template($this);
- $url = $this->get_logo_url();
- if ($url) {
- $url = $url->out(false);
- }
- $context['logourl'] = $url;
- $context['sitename'] = format_string($SITE->fullname, true,
- ['context' => context_course::instance(SITEID), "escape" => false]);
- return $this->render_from_template('core/signup_form_layout', $context);
- }
- /**
- * Render the verify age and location page into a nice template for the theme.
- *
- * @param \core_auth\output\verify_age_location_page $page The renderable
- * @return string
- */
- protected function render_verify_age_location_page($page) {
- $context = $page->export_for_template($this);
- return $this->render_from_template('core/auth_verify_age_location_page', $context);
- }
- /**
- * Render the digital minor contact information page into a nice template for the theme.
- *
- * @param \core_auth\output\digital_minor_page $page The renderable
- * @return string
- */
- protected function render_digital_minor_page($page) {
- $context = $page->export_for_template($this);
- return $this->render_from_template('core/auth_digital_minor_page', $context);
- }
- /**
- * Renders a progress bar.
- *
- * Do not use $OUTPUT->render($bar), instead use progress_bar::create().
- *
- * @param progress_bar $bar The bar.
- * @return string HTML fragment
- */
- public function render_progress_bar(progress_bar $bar) {
- $data = $bar->export_for_template($this);
- return $this->render_from_template('core/progress_bar', $data);
- }
- /**
- * Renders an update to a progress bar.
- *
- * Note: This does not cleanly map to a renderable class and should
- * never be used directly.
- *
- * @param string $id
- * @param float $percent
- * @param string $msg Message
- * @param string $estimate time remaining message
- * @return string ascii fragment
- */
- public function render_progress_bar_update(string $id, float $percent, string $msg, string $estimate) : string {
- return html_writer::script(js_writer::function_call('updateProgressBar', [$id, $percent, $msg, $estimate]));
- }
- /**
- * Renders element for a toggle-all checkbox.
- *
- * @param \core\output\checkbox_toggleall $element
- * @return string
- */
- public function render_checkbox_toggleall(\core\output\checkbox_toggleall $element) {
- return $this->render_from_template($element->get_template(), $element->export_for_template($this));
- }
- /**
- * Renders release information in the footer popup
- * @return string Moodle release info.
- */
- public function moodle_release() {
- global $CFG;
- if (!during_initial_install() && is_siteadmin()) {
- return $CFG->release;
- }
- }
- }
- /**
- * A renderer that generates output for command-line scripts.
- *
- * The implementation of this renderer is probably incomplete.
- *
- * @copyright 2009 Tim Hunt
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- * @since Moodle 2.0
- * @package core
- * @category output
- */
- class core_renderer_cli extends core_renderer {
- /**
- * @var array $progressmaximums stores the largest percentage for a progress bar.
- * @return string ascii fragment
- */
- private $progressmaximums = [];
- /**
- * Returns the page header.
- *
- * @return string HTML fragment
- */
- public function header() {
- return $this->page->heading . "\n";
- }
- /**
- * Renders a Check API result
- *
- * To aid in CLI consistency this status is NOT translated and the visual
- * width is always exactly 10 chars.
- *
- * @param core\check\result $result
- * @return string HTML fragment
- */
- protected function render_check_result(core\check\result $result) {
- $status = $result->get_status();
- $labels = [
- core\check\result::NA => ' ' . cli_ansi_format('<colour:darkGray>' ) . ' NA ',
- core\check\result::OK => ' ' . cli_ansi_format('<colour:green>') . ' OK ',
- core\check\result::INFO => ' ' . cli_ansi_format('<colour:blue>' ) . ' INFO ',
- core\check\result::UNKNOWN => ' ' . cli_ansi_format('<colour:darkGray>' ) . ' UNKNOWN ',
- core\check\result::WARNING => ' ' . cli_ansi_format('<colour:black><bgcolour:yellow>') . ' WARNING ',
- core\check\result::ERROR => ' ' . cli_ansi_format('<bgcolour:red>') . ' ERROR ',
- core\check\result::CRITICAL => '' . cli_ansi_format('<bgcolour:red>') . ' CRITICAL ',
- ];
- $string = $labels[$status] . cli_ansi_format('<colour:normal>');
- return $string;
- }
- /**
- * Renders a Check API result
- *
- * @param result $result
- * @return string fragment
- */
- public function check_result(core\check\result $result) {
- return $this->render_check_result($result);
- }
- /**
- * Renders a progress bar.
- *
- * Do not use $OUTPUT->render($bar), instead use progress_bar::create().
- *
- * @param progress_bar $bar The bar.
- * @return string ascii fragment
- */
- public function render_progress_bar(progress_bar $bar) {
- global $CFG;
- $size = 55; // The width of the progress bar in chars.
- $ascii = "\n";
- if (stream_isatty(STDOUT)) {
- require_once($CFG->libdir.'/clilib.php');
- $ascii .= "[" . str_repeat(' ', $size) . "] 0% \n";
- return cli_ansi_format($ascii);
- }
- $this->progressmaximums[$bar->get_id()] = 0;
- $ascii .= '[';
- return $ascii;
- }
- /**
- * Renders an update to a progress bar.
- *
- * Note: This does not cleanly map to a renderable class and should
- * never be used directly.
- *
- * @param string $id
- * @param float $percent
- * @param string $msg Message
- * @param string $estimate time remaining message
- * @return string ascii fragment
- */
- public function render_progress_bar_update(string $id, float $percent, string $msg, string $estimate) : string {
- $size = 55; // The width of the progress bar in chars.
- $ascii = '';
- // If we are rendering to a terminal then we can safely use ansii codes
- // to move the cursor and redraw the complete progress bar each time
- // it is updated.
- if (stream_isatty(STDOUT)) {
- $colour = $percent == 100 ? 'green' : 'blue';
- $done = $percent * $size * 0.01;
- $whole = floor($done);
- $bar = "<colour:$colour>";
- $bar .= str_repeat('█', $whole);
- if ($whole < $size) {
- // By using unicode chars for partial blocks we can have higher
- // precision progress bar.
- $fraction = floor(($done - $whole) * 8);
- $bar .= core_text::substr(' ▏▎▍▌▋▊▉', $fraction, 1);
- // Fill the rest of the empty bar.
- $bar .= str_repeat(' ', $size - $whole - 1);
- }
- $bar .= '<colour:normal>';
- if ($estimate) {
- $estimate = "- $estimate";
- }
- $ascii .= '<cursor:up>';
- $ascii .= '<cursor:up>';
- $ascii .= sprintf("[$bar] %3.1f%% %-22s\n", $percent, $estimate);
- $ascii .= sprintf("%-80s\n", $msg);
- return cli_ansi_format($ascii);
- }
- // If we are not rendering to a tty, ie when piped to another command
- // or on windows we need to progressively render the progress bar
- // which can only ever go forwards.
- $done = round($percent * $size * 0.01);
- $delta = max(0, $done - $this->progressmaximums[$id]);
- $ascii .= str_repeat('#', $delta);
- if ($percent >= 100 && $delta > 0) {
- $ascii .= sprintf("] %3.1f%%\n$msg\n", $percent);
- }
- $this->progressmaximums[$id] += $delta;
- return $ascii;
- }
- /**
- * Returns a template fragment representing a Heading.
- *
- * @param string $text The text of the heading
- * @param int $level The level of importance of the heading
- * @param string $classes A space-separated list of CSS classes
- * @param string $id An optional ID
- * @return string A template fragment for a heading
- */
- public function heading($text, $level = 2, $classes = 'main', $id = null) {
- $text .= "\n";
- switch ($level) {
- case 1:
- return '=>' . $text;
- case 2:
- return '-->' . $text;
- default:
- return $text;
- }
- }
- /**
- * Returns a template fragment representing a fatal error.
- *
- * @param string $message The message to output
- * @param string $moreinfourl URL where more info can be found about the error
- * @param string $link Link for the Continue button
- * @param array $backtrace The execution backtrace
- * @param string $debuginfo Debugging information
- * @return string A template fragment for a fatal error
- */
- public function fatal_error($message, $moreinfourl, $link, $backtrace, $debuginfo = null, $errorcode = "") {
- global $CFG;
- $output = "!!! $message !!!\n";
- if ($CFG->debugdeveloper) {
- if (!empty($debuginfo)) {
- $output .= $this->notification($debuginfo, 'notifytiny');
- }
- if (!empty($backtrace)) {
- $output .= $this->notification('Stack trace: ' . format_backtrace($backtrace, true), 'notifytiny');
- }
- }
- return $output;
- }
- /**
- * Returns a template fragment representing a notification.
- *
- * @param string $message The message to print out.
- * @param string $type The type of notification. See constants on \core\output\notification.
- * @param bool $closebutton Whether to show a close icon to remove the notification (default true).
- * @return string A template fragment for a notification
- */
- public function notification($message, $type = null, $closebutton = true) {
- $message = clean_text($message);
- if ($type === 'notifysuccess' || $type === 'success') {
- return "++ $message ++\n";
- }
- return "!! $message !!\n";
- }
- /**
- * There is no footer for a cli request, however we must override the
- * footer method to prevent the default footer.
- */
- public function footer() {}
- /**
- * Render a notification (that is, a status message about something that has
- * just happened).
- *
- * @param \core\output\notification $notification the notification to print out
- * @return string plain text output
- */
- public function render_notification(\core\output\notification $notification) {
- return $this->notification($notification->get_message(), $notification->get_message_type());
- }
- }
- /**
- * A renderer that generates output for ajax scripts.
- *
- * This renderer prevents accidental sends back only json
- * encoded error messages, all other output is ignored.
- *
- * @copyright 2010 Petr Skoda
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- * @since Moodle 2.0
- * @package core
- * @category output
- */
- class core_renderer_ajax extends core_renderer {
- /**
- * Returns a template fragment representing a fatal error.
- *
- * @param string $message The message to output
- * @param string $moreinfourl URL where more info can be found about the error
- * @param string $link Link for the Continue button
- * @param array $backtrace The execution backtrace
- * @param string $debuginfo Debugging information
- * @return string A template fragment for a fatal error
- */
- public function fatal_error($message, $moreinfourl, $link, $backtrace, $debuginfo = null, $errorcode = "") {
- global $CFG;
- $this->page->set_context(null); // ugly hack - make sure page context is set to something, we do not want bogus warnings here
- $e = new stdClass();
- $e->error = $message;
- $e->errorcode = $errorcode;
- $e->stacktrace = NULL;
- $e->debuginfo = NULL;
- $e->reproductionlink = NULL;
- if (!empty($CFG->debug) and $CFG->debug >= DEBUG_DEVELOPER) {
- $link = (string) $link;
- if ($link) {
- $e->reproductionlink = $link;
- }
- if (!empty($debuginfo)) {
- $e->debuginfo = $debuginfo;
- }
- if (!empty($backtrace)) {
- $e->stacktrace = format_backtrace($backtrace, true);
- }
- }
- $this->header();
- return json_encode($e);
- }
- /**
- * Used to display a notification.
- * For the AJAX notifications are discarded.
- *
- * @param string $message The message to print out.
- * @param string $type The type of notification. See constants on \core\output\notification.
- * @param bool $closebutton Whether to show a close icon to remove the notification (default true).
- */
- public function notification($message, $type = null, $closebutton = true) {
- }
- /**
- * Used to display a redirection message.
- * AJAX redirections should not occur and as such redirection messages
- * are discarded.
- *
- * @param moodle_url|string $encodedurl
- * @param string $message
- * @param int $delay
- * @param bool $debugdisableredirect
- * @param string $messagetype The type of notification to show the message in.
- * See constants on \core\output\notification.
- */
- public function redirect_message($encodedurl, $message, $delay, $debugdisableredirect,
- $messagetype = \core\output\notification::NOTIFY_INFO) {}
- /**
- * Prepares the start of an AJAX output.
- */
- public function header() {
- // unfortunately YUI iframe upload does not support application/json
- if (!empty($_FILES)) {
- @header('Content-type: text/plain; charset=utf-8');
- if (!core_useragent::supports_json_contenttype()) {
- @header('X-Content-Type-Options: nosniff');
- }
- } else if (!core_useragent::supports_json_contenttype()) {
- @header('Content-type: text/plain; charset=utf-8');
- @header('X-Content-Type-Options: nosniff');
- } else {
- @header('Content-type: application/json; charset=utf-8');
- }
- // Headers to make it not cacheable and json
- @header('Cache-Control: no-store, no-cache, must-revalidate');
- @header('Cache-Control: post-check=0, pre-check=0', false);
- @header('Pragma: no-cache');
- @header('Expires: Mon, 20 Aug 1969 09:23:00 GMT');
- @header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
- @header('Accept-Ranges: none');
- }
- /**
- * There is no footer for an AJAX request, however we must override the
- * footer method to prevent the default footer.
- */
- public function footer() {}
- /**
- * No need for headers in an AJAX request... this should never happen.
- * @param string $text
- * @param int $level
- * @param string $classes
- * @param string $id
- */
- public function heading($text, $level = 2, $classes = 'main', $id = null) {}
- }
- /**
- * The maintenance renderer.
- *
- * The purpose of this renderer is to block out the core renderer methods that are not usable when the site
- * is running a maintenance related task.
- * It must always extend the core_renderer as we switch from the core_renderer to this renderer in a couple of places.
- *
- * @since Moodle 2.6
- * @package core
- * @category output
- * @copyright 2013 Sam Hemelryk
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
- class core_renderer_maintenance extends core_renderer {
- /**
- * Initialises the renderer instance.
- *
- * @param moodle_page $page
- * @param string $target
- * @throws coding_exception
- */
- public function __construct(moodle_page $page, $target) {
- if ($target !== RENDERER_TARGET_MAINTENANCE || $page->pagelayout !== 'maintenance') {
- throw new coding_exception('Invalid request for the maintenance renderer.');
- }
- parent::__construct($page, $target);
- }
- /**
- * Does nothing. The maintenance renderer cannot produce blocks.
- *
- * @param block_contents $bc
- * @param string $region
- * @return string
- */
- public function block(block_contents $bc, $region) {
- return '';
- }
- /**
- * Does nothing. The maintenance renderer cannot produce blocks.
- *
- * @param string $region
- * @param array $classes
- * @param string $tag
- * @param boolean $fakeblocksonly
- * @return string
- */
- public function blocks($region, $classes = array(), $tag = 'aside', $fakeblocksonly = false) {
- return '';
- }
- /**
- * Does nothing. The maintenance renderer cannot produce blocks.
- *
- * @param string $region
- * @param boolean $fakeblocksonly Output fake block only.
- * @return string
- */
- public function blocks_for_region($region, $fakeblocksonly = false) {
- return '';
- }
- /**
- * Does nothing. The maintenance renderer cannot produce a course content header.
- *
- * @param bool $onlyifnotcalledbefore
- * @return string
- */
- public function course_content_header($onlyifnotcalledbefore = false) {
- return '';
- }
- /**
- * Does nothing. The maintenance renderer cannot produce a course content footer.
- *
- * @param bool $onlyifnotcalledbefore
- * @return string
- */
- public function course_content_footer($onlyifnotcalledbefore = false) {
- return '';
- }
- /**
- * Does nothing. The maintenance renderer cannot produce a course header.
- *
- * @return string
- */
- public function course_header() {
- return '';
- }
- /**
- * Does nothing. The maintenance renderer cannot produce a course footer.
- *
- * @return string
- */
- public function course_footer() {
- return '';
- }
- /**
- * Does nothing. The maintenance renderer cannot produce a custom menu.
- *
- * @param string $custommenuitems
- * @return string
- */
- public function custom_menu($custommenuitems = '') {
- return '';
- }
- /**
- * Does nothing. The maintenance renderer cannot produce a file picker.
- *
- * @param array $options
- * @return string
- */
- public function file_picker($options) {
- return '';
- }
- /**
- * Does nothing. The maintenance renderer cannot produce and HTML file tree.
- *
- * @param array $dir
- * @return string
- */
- public function htmllize_file_tree($dir) {
- return '';
- }
- /**
- * Overridden confirm message for upgrades.
- *
- * @param string $message The question to ask the user
- * @param single_button|moodle_url|string $continue The single_button component representing the Continue answer.
- * @param single_button|moodle_url|string $cancel The single_button component representing the Cancel answer.
- * @return string HTML fragment
- */
- public function confirm($message, $continue, $cancel) {
- // We need plain styling of confirm boxes on upgrade because we don't know which stylesheet we have (it could be
- // from any previous version of Moodle).
- if ($continue instanceof single_button) {
- $continue->primary = true;
- } else if (is_string($continue)) {
- $continue = new single_button(new moodle_url($continue), get_string('continue'), 'post', true);
- } else if ($continue instanceof moodle_url) {
- $continue = new single_button($continue, get_string('continue'), 'post', true);
- } else {
- throw new coding_exception('The continue param to $OUTPUT->confirm() must be either a URL' .
- ' (string/moodle_url) or a single_button instance.');
- }
- if ($cancel instanceof single_button) {
- $output = '';
- } else if (is_string($cancel)) {
- $cancel = new single_button(new moodle_url($cancel), get_string('cancel'), 'get');
- } else if ($cancel instanceof moodle_url) {
- $cancel = new single_button($cancel, get_string('cancel'), 'get');
- } else {
- throw new coding_exception('The cancel param to $OUTPUT->confirm() must be either a URL' .
- ' (string/moodle_url) or a single_button instance.');
- }
- $output = $this->box_start('generalbox', 'notice');
- $output .= html_writer::tag('h4', get_string('confirm'));
- $output .= html_writer::tag('p', $message);
- $output .= html_writer::tag('div', $this->render($continue) . $this->render($cancel), array('class' => 'buttons'));
- $output .= $this->box_end();
- return $output;
- }
- /**
- * Does nothing. The maintenance renderer does not support JS.
- *
- * @param block_contents $bc
- */
- public function init_block_hider_js(block_contents $bc) {
- // Does nothing.
- }
- /**
- * Does nothing. The maintenance renderer cannot produce language menus.
- *
- * @return string
- */
- public function lang_menu() {
- return '';
- }
- /**
- * Does nothing. The maintenance renderer has no need for login information.
- *
- * @param null $withlinks
- * @return string
- */
- public function login_info($withlinks = null) {
- return '';
- }
- /**
- * Secure login info.
- *
- * @return string
- */
- public function secure_login_info() {
- return $this->login_info(false);
- }
- /**
- * Does nothing. The maintenance renderer cannot produce user pictures.
- *
- * @param stdClass $user
- * @param array $options
- * @return string
- */
- public function user_picture(stdClass $user, array $options = null) {
- return '';
- }
- }