[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/ -> outputrenderers.php (source)

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * Classes for rendering HTML output for Moodle.
  19   *
  20   * Please see {@link http://docs.moodle.org/en/Developement:How_Moodle_outputs_HTML}
  21   * for an overview.
  22   *
  23   * Included in this file are the primary renderer classes:
  24   *     - renderer_base:         The renderer outline class that all renderers
  25   *                              should inherit from.
  26   *     - core_renderer:         The standard HTML renderer.
  27   *     - core_renderer_cli:     An adaption of the standard renderer for CLI scripts.
  28   *     - core_renderer_ajax:    An adaption of the standard renderer for AJAX scripts.
  29   *     - plugin_renderer_base:  A renderer class that should be extended by all
  30   *                              plugin renderers.
  31   *
  32   * @package core
  33   * @category output
  34   * @copyright  2009 Tim Hunt
  35   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  36   */
  37  
  38  defined('MOODLE_INTERNAL') || die();
  39  
  40  /**
  41   * Simple base class for Moodle renderers.
  42   *
  43   * Tracks the xhtml_container_stack to use, which is passed in in the constructor.
  44   *
  45   * Also has methods to facilitate generating HTML output.
  46   *
  47   * @copyright 2009 Tim Hunt
  48   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  49   * @since Moodle 2.0
  50   * @package core
  51   * @category output
  52   */
  53  class renderer_base {
  54      /**
  55       * @var xhtml_container_stack The xhtml_container_stack to use.
  56       */
  57      protected $opencontainers;
  58  
  59      /**
  60       * @var moodle_page The Moodle page the renderer has been created to assist with.
  61       */
  62      protected $page;
  63  
  64      /**
  65       * @var string The requested rendering target.
  66       */
  67      protected $target;
  68  
  69      /**
  70       * @var Mustache_Engine $mustache The mustache template compiler
  71       */
  72      private $mustache;
  73  
  74      /**
  75       * Return an instance of the mustache class.
  76       *
  77       * @since 2.9
  78       * @return Mustache_Engine
  79       */
  80      protected function get_mustache() {
  81          global $CFG;
  82  
  83          if ($this->mustache === null) {
  84              require_once($CFG->dirroot . '/lib/mustache/src/Mustache/Autoloader.php');
  85              Mustache_Autoloader::register();
  86  
  87              $themename = $this->page->theme->name;
  88              $themerev = theme_get_revision();
  89  
  90              $cachedir = make_localcache_directory("mustache/$themerev/$themename");
  91  
  92              $loader = new \core\output\mustache_filesystem_loader();
  93              $stringhelper = new \core\output\mustache_string_helper();
  94              $quotehelper = new \core\output\mustache_quote_helper();
  95              $jshelper = new \core\output\mustache_javascript_helper($this->page->requires);
  96              $pixhelper = new \core\output\mustache_pix_helper($this);
  97  
  98              // We only expose the variables that are exposed to JS templates.
  99              $safeconfig = $this->page->requires->get_config_for_javascript($this->page, $this);
 100  
 101              $helpers = array('config' => $safeconfig,
 102                               'str' => array($stringhelper, 'str'),
 103                               'quote' => array($quotehelper, 'quote'),
 104                               'js' => array($jshelper, 'help'),
 105                               'pix' => array($pixhelper, 'pix'));
 106  
 107              $this->mustache = new Mustache_Engine(array(
 108                  'cache' => $cachedir,
 109                  'escape' => 's',
 110                  'loader' => $loader,
 111                  'helpers' => $helpers,
 112                  'pragmas' => [Mustache_Engine::PRAGMA_BLOCKS]));
 113  
 114          }
 115  
 116          return $this->mustache;
 117      }
 118  
 119  
 120      /**
 121       * Constructor
 122       *
 123       * The constructor takes two arguments. The first is the page that the renderer
 124       * has been created to assist with, and the second is the target.
 125       * The target is an additional identifier that can be used to load different
 126       * renderers for different options.
 127       *
 128       * @param moodle_page $page the page we are doing output for.
 129       * @param string $target one of rendering target constants
 130       */
 131      public function __construct(moodle_page $page, $target) {
 132          $this->opencontainers = $page->opencontainers;
 133          $this->page = $page;
 134          $this->target = $target;
 135      }
 136  
 137      /**
 138       * Renders a template by name with the given context.
 139       *
 140       * The provided data needs to be array/stdClass made up of only simple types.
 141       * Simple types are array,stdClass,bool,int,float,string
 142       *
 143       * @since 2.9
 144       * @param array|stdClass $context Context containing data for the template.
 145       * @return string|boolean
 146       */
 147      public function render_from_template($templatename, $context) {
 148          static $templatecache = array();
 149          $mustache = $this->get_mustache();
 150  
 151          // Provide 1 random value that will not change within a template
 152          // but will be different from template to template. This is useful for
 153          // e.g. aria attributes that only work with id attributes and must be
 154          // unique in a page.
 155          $mustache->addHelper('uniqid', new \core\output\mustache_uniqid_helper());
 156          if (isset($templatecache[$templatename])) {
 157              $template = $templatecache[$templatename];
 158          } else {
 159              try {
 160                  $template = $mustache->loadTemplate($templatename);
 161                  $templatecache[$templatename] = $template;
 162              } catch (Mustache_Exception_UnknownTemplateException $e) {
 163                  throw new moodle_exception('Unknown template: ' . $templatename);
 164              }
 165          }
 166          return trim($template->render($context));
 167      }
 168  
 169  
 170      /**
 171       * Returns rendered widget.
 172       *
 173       * The provided widget needs to be an object that extends the renderable
 174       * interface.
 175       * If will then be rendered by a method based upon the classname for the widget.
 176       * For instance a widget of class `crazywidget` will be rendered by a protected
 177       * render_crazywidget method of this renderer.
 178       *
 179       * @param renderable $widget instance with renderable interface
 180       * @return string
 181       */
 182      public function render(renderable $widget) {
 183          $classname = get_class($widget);
 184          // Strip namespaces.
 185          $classname = preg_replace('/^.*\\\/', '', $classname);
 186          // Remove _renderable suffixes
 187          $classname = preg_replace('/_renderable$/', '', $classname);
 188  
 189          $rendermethod = 'render_'.$classname;
 190          if (method_exists($this, $rendermethod)) {
 191              return $this->$rendermethod($widget);
 192          }
 193          throw new coding_exception('Can not render widget, renderer method ('.$rendermethod.') not found.');
 194      }
 195  
 196      /**
 197       * Adds a JS action for the element with the provided id.
 198       *
 199       * This method adds a JS event for the provided component action to the page
 200       * and then returns the id that the event has been attached to.
 201       * If no id has been provided then a new ID is generated by {@link html_writer::random_id()}
 202       *
 203       * @param component_action $action
 204       * @param string $id
 205       * @return string id of element, either original submitted or random new if not supplied
 206       */
 207      public function add_action_handler(component_action $action, $id = null) {
 208          if (!$id) {
 209              $id = html_writer::random_id($action->event);
 210          }
 211          $this->page->requires->event_handler("#$id", $action->event, $action->jsfunction, $action->jsfunctionargs);
 212          return $id;
 213      }
 214  
 215      /**
 216       * Returns true is output has already started, and false if not.
 217       *
 218       * @return boolean true if the header has been printed.
 219       */
 220      public function has_started() {
 221          return $this->page->state >= moodle_page::STATE_IN_BODY;
 222      }
 223  
 224      /**
 225       * Given an array or space-separated list of classes, prepares and returns the HTML class attribute value
 226       *
 227       * @param mixed $classes Space-separated string or array of classes
 228       * @return string HTML class attribute value
 229       */
 230      public static function prepare_classes($classes) {
 231          if (is_array($classes)) {
 232              return implode(' ', array_unique($classes));
 233          }
 234          return $classes;
 235      }
 236  
 237      /**
 238       * Return the moodle_url for an image.
 239       *
 240       * The exact image location and extension is determined
 241       * automatically by searching for gif|png|jpg|jpeg, please
 242       * note there can not be diferent images with the different
 243       * extension. The imagename is for historical reasons
 244       * a relative path name, it may be changed later for core
 245       * images. It is recommended to not use subdirectories
 246       * in plugin and theme pix directories.
 247       *
 248       * There are three types of images:
 249       * 1/ theme images  - stored in theme/mytheme/pix/,
 250       *                    use component 'theme'
 251       * 2/ core images   - stored in /pix/,
 252       *                    overridden via theme/mytheme/pix_core/
 253       * 3/ plugin images - stored in mod/mymodule/pix,
 254       *                    overridden via theme/mytheme/pix_plugins/mod/mymodule/,
 255       *                    example: pix_url('comment', 'mod_glossary')
 256       *
 257       * @param string $imagename the pathname of the image
 258       * @param string $component full plugin name (aka component) or 'theme'
 259       * @return moodle_url
 260       */
 261      public function pix_url($imagename, $component = 'moodle') {
 262          return $this->page->theme->pix_url($imagename, $component);
 263      }
 264  }
 265  
 266  
 267  /**
 268   * Basis for all plugin renderers.
 269   *
 270   * @copyright Petr Skoda (skodak)
 271   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 272   * @since Moodle 2.0
 273   * @package core
 274   * @category output
 275   */
 276  class plugin_renderer_base extends renderer_base {
 277  
 278      /**
 279       * @var renderer_base|core_renderer A reference to the current renderer.
 280       * The renderer provided here will be determined by the page but will in 90%
 281       * of cases by the {@link core_renderer}
 282       */
 283      protected $output;
 284  
 285      /**
 286       * Constructor method, calls the parent constructor
 287       *
 288       * @param moodle_page $page
 289       * @param string $target one of rendering target constants
 290       */
 291      public function __construct(moodle_page $page, $target) {
 292          if (empty($target) && $page->pagelayout === 'maintenance') {
 293              // If the page is using the maintenance layout then we're going to force the target to maintenance.
 294              // This way we'll get a special maintenance renderer that is designed to block access to API's that are likely
 295              // unavailable for this page layout.
 296              $target = RENDERER_TARGET_MAINTENANCE;
 297          }
 298          $this->output = $page->get_renderer('core', null, $target);
 299          parent::__construct($page, $target);
 300      }
 301  
 302      /**
 303       * Renders the provided widget and returns the HTML to display it.
 304       *
 305       * @param renderable $widget instance with renderable interface
 306       * @return string
 307       */
 308      public function render(renderable $widget) {
 309          $classname = get_class($widget);
 310          // Strip namespaces.
 311          $classname = preg_replace('/^.*\\\/', '', $classname);
 312          // Keep a copy at this point, we may need to look for a deprecated method.
 313          $deprecatedmethod = 'render_'.$classname;
 314          // Remove _renderable suffixes
 315          $classname = preg_replace('/_renderable$/', '', $classname);
 316  
 317          $rendermethod = 'render_'.$classname;
 318          if (method_exists($this, $rendermethod)) {
 319              return $this->$rendermethod($widget);
 320          }
 321          if ($rendermethod !== $deprecatedmethod && method_exists($this, $deprecatedmethod)) {
 322              // This is exactly where we don't want to be.
 323              // If you have arrived here you have a renderable component within your plugin that has the name
 324              // blah_renderable, and you have a render method render_blah_renderable on your plugin.
 325              // In 2.8 we revamped output, as part of this change we changed slightly how renderables got rendered
 326              // and the _renderable suffix now gets removed when looking for a render method.
 327              // You need to change your renderers render_blah_renderable to render_blah.
 328              // Until you do this it will not be possible for a theme to override the renderer to override your method.
 329              // Please do it ASAP.
 330              static $debugged = array();
 331              if (!isset($debugged[$deprecatedmethod])) {
 332                  debugging(sprintf('Deprecated call. Please rename your renderables render method from %s to %s.',
 333                      $deprecatedmethod, $rendermethod), DEBUG_DEVELOPER);
 334                  $debugged[$deprecatedmethod] = true;
 335              }
 336              return $this->$deprecatedmethod($widget);
 337          }
 338          // pass to core renderer if method not found here
 339          return $this->output->render($widget);
 340      }
 341  
 342      /**
 343       * Magic method used to pass calls otherwise meant for the standard renderer
 344       * to it to ensure we don't go causing unnecessary grief.
 345       *
 346       * @param string $method
 347       * @param array $arguments
 348       * @return mixed
 349       */
 350      public function __call($method, $arguments) {
 351          if (method_exists('renderer_base', $method)) {
 352              throw new coding_exception('Protected method called against '.get_class($this).' :: '.$method);
 353          }
 354          if (method_exists($this->output, $method)) {
 355              return call_user_func_array(array($this->output, $method), $arguments);
 356          } else {
 357              throw new coding_exception('Unknown method called against '.get_class($this).' :: '.$method);
 358          }
 359      }
 360  }
 361  
 362  
 363  /**
 364   * The standard implementation of the core_renderer interface.
 365   *
 366   * @copyright 2009 Tim Hunt
 367   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 368   * @since Moodle 2.0
 369   * @package core
 370   * @category output
 371   */
 372  class core_renderer extends renderer_base {
 373      /**
 374       * Do NOT use, please use <?php echo $OUTPUT->main_content() ?>
 375       * in layout files instead.
 376       * @deprecated
 377       * @var string used in {@link core_renderer::header()}.
 378       */
 379      const MAIN_CONTENT_TOKEN = '[MAIN CONTENT GOES HERE]';
 380  
 381      /**
 382       * @var string Used to pass information from {@link core_renderer::doctype()} to
 383       * {@link core_renderer::standard_head_html()}.
 384       */
 385      protected $contenttype;
 386  
 387      /**
 388       * @var string Used by {@link core_renderer::redirect_message()} method to communicate
 389       * with {@link core_renderer::header()}.
 390       */
 391      protected $metarefreshtag = '';
 392  
 393      /**
 394       * @var string Unique token for the closing HTML
 395       */
 396      protected $unique_end_html_token;
 397  
 398      /**
 399       * @var string Unique token for performance information
 400       */
 401      protected $unique_performance_info_token;
 402  
 403      /**
 404       * @var string Unique token for the main content.
 405       */
 406      protected $unique_main_content_token;
 407  
 408      /**
 409       * Constructor
 410       *
 411       * @param moodle_page $page the page we are doing output for.
 412       * @param string $target one of rendering target constants
 413       */
 414      public function __construct(moodle_page $page, $target) {
 415          $this->opencontainers = $page->opencontainers;
 416          $this->page = $page;
 417          $this->target = $target;
 418  
 419          $this->unique_end_html_token = '%%ENDHTML-'.sesskey().'%%';
 420          $this->unique_performance_info_token = '%%PERFORMANCEINFO-'.sesskey().'%%';
 421          $this->unique_main_content_token = '[MAIN CONTENT GOES HERE - '.sesskey().']';
 422      }
 423  
 424      /**
 425       * Get the DOCTYPE declaration that should be used with this page. Designed to
 426       * be called in theme layout.php files.
 427       *
 428       * @return string the DOCTYPE declaration that should be used.
 429       */
 430      public function doctype() {
 431          if ($this->page->theme->doctype === 'html5') {
 432              $this->contenttype = 'text/html; charset=utf-8';
 433              return "<!DOCTYPE html>\n";
 434  
 435          } else if ($this->page->theme->doctype === 'xhtml5') {
 436              $this->contenttype = 'application/xhtml+xml; charset=utf-8';
 437              return "<!DOCTYPE html>\n";
 438  
 439          } else {
 440              // legacy xhtml 1.0
 441              $this->contenttype = 'text/html; charset=utf-8';
 442              return ('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">' . "\n");
 443          }
 444      }
 445  
 446      /**
 447       * The attributes that should be added to the <html> tag. Designed to
 448       * be called in theme layout.php files.
 449       *
 450       * @return string HTML fragment.
 451       */
 452      public function htmlattributes() {
 453          $return = get_html_lang(true);
 454          if ($this->page->theme->doctype !== 'html5') {
 455              $return .= ' xmlns="http://www.w3.org/1999/xhtml"';
 456          }
 457          return $return;
 458      }
 459  
 460      /**
 461       * The standard tags (meta tags, links to stylesheets and JavaScript, etc.)
 462       * that should be included in the <head> tag. Designed to be called in theme
 463       * layout.php files.
 464       *
 465       * @return string HTML fragment.
 466       */
 467      public function standard_head_html() {
 468          global $CFG, $SESSION;
 469  
 470          // Before we output any content, we need to ensure that certain
 471          // page components are set up.
 472  
 473          // Blocks must be set up early as they may require javascript which
 474          // has to be included in the page header before output is created.
 475          foreach ($this->page->blocks->get_regions() as $region) {
 476              $this->page->blocks->ensure_content_created($region, $this);
 477          }
 478  
 479          $output = '';
 480  
 481          // Allow a url_rewrite plugin to setup any dynamic head content.
 482          if (isset($CFG->urlrewriteclass) && !isset($CFG->upgraderunning)) {
 483              $class = $CFG->urlrewriteclass;
 484              $output .= $class::html_head_setup();
 485          }
 486  
 487          $output .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />' . "\n";
 488          $output .= '<meta name="keywords" content="moodle, ' . $this->page->title . '" />' . "\n";
 489          // This is only set by the {@link redirect()} method
 490          $output .= $this->metarefreshtag;
 491  
 492          // Check if a periodic refresh delay has been set and make sure we arn't
 493          // already meta refreshing
 494          if ($this->metarefreshtag=='' && $this->page->periodicrefreshdelay!==null) {
 495              $output .= '<meta http-equiv="refresh" content="'.$this->page->periodicrefreshdelay.';url='.$this->page->url->out().'" />';
 496          }
 497  
 498          // flow player embedding support
 499          $this->page->requires->js_function_call('M.util.load_flowplayer');
 500  
 501          // Set up help link popups for all links with the helptooltip class
 502          $this->page->requires->js_init_call('M.util.help_popups.setup');
 503  
 504          // Setup help icon overlays.
 505          $this->page->requires->yui_module('moodle-core-popuphelp', 'M.core.init_popuphelp');
 506          $this->page->requires->strings_for_js(array(
 507              'morehelp',
 508              'loadinghelp',
 509          ), 'moodle');
 510  
 511          $this->page->requires->js_function_call('setTimeout', array('fix_column_widths()', 20));
 512  
 513          $focus = $this->page->focuscontrol;
 514          if (!empty($focus)) {
 515              if (preg_match("#forms\['([a-zA-Z0-9]+)'\].elements\['([a-zA-Z0-9]+)'\]#", $focus, $matches)) {
 516                  // This is a horrifically bad way to handle focus but it is passed in
 517                  // through messy formslib::moodleform
 518                  $this->page->requires->js_function_call('old_onload_focus', array($matches[1], $matches[2]));
 519              } else if (strpos($focus, '.')!==false) {
 520                  // Old style of focus, bad way to do it
 521                  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);
 522                  $this->page->requires->js_function_call('old_onload_focus', explode('.', $focus, 2));
 523              } else {
 524                  // Focus element with given id
 525                  $this->page->requires->js_function_call('focuscontrol', array($focus));
 526              }
 527          }
 528  
 529          // Get the theme stylesheet - this has to be always first CSS, this loads also styles.css from all plugins;
 530          // any other custom CSS can not be overridden via themes and is highly discouraged
 531          $urls = $this->page->theme->css_urls($this->page);
 532          foreach ($urls as $url) {
 533              $this->page->requires->css_theme($url);
 534          }
 535  
 536          // Get the theme javascript head and footer
 537          if ($jsurl = $this->page->theme->javascript_url(true)) {
 538              $this->page->requires->js($jsurl, true);
 539          }
 540          if ($jsurl = $this->page->theme->javascript_url(false)) {
 541              $this->page->requires->js($jsurl);
 542          }
 543  
 544          // Get any HTML from the page_requirements_manager.
 545          $output .= $this->page->requires->get_head_code($this->page, $this);
 546  
 547          // List alternate versions.
 548          foreach ($this->page->alternateversions as $type => $alt) {
 549              $output .= html_writer::empty_tag('link', array('rel' => 'alternate',
 550                      'type' => $type, 'title' => $alt->title, 'href' => $alt->url));
 551          }
 552  
 553          if (!empty($CFG->additionalhtmlhead)) {
 554              $output .= "\n".$CFG->additionalhtmlhead;
 555          }
 556  
 557          return $output;
 558      }
 559  
 560      /**
 561       * The standard tags (typically skip links) that should be output just inside
 562       * the start of the <body> tag. Designed to be called in theme layout.php files.
 563       *
 564       * @return string HTML fragment.
 565       */
 566      public function standard_top_of_body_html() {
 567          global $CFG;
 568          $output = $this->page->requires->get_top_of_body_code();
 569          if ($this->page->pagelayout !== 'embedded' && !empty($CFG->additionalhtmltopofbody)) {
 570              $output .= "\n".$CFG->additionalhtmltopofbody;
 571          }
 572          $output .= $this->maintenance_warning();
 573          return $output;
 574      }
 575  
 576      /**
 577       * Scheduled maintenance warning message.
 578       *
 579       * Note: This is a nasty hack to display maintenance notice, this should be moved
 580       *       to some general notification area once we have it.
 581       *
 582       * @return string
 583       */
 584      public function maintenance_warning() {
 585          global $CFG;
 586  
 587          $output = '';
 588          if (isset($CFG->maintenance_later) and $CFG->maintenance_later > time()) {
 589              $timeleft = $CFG->maintenance_later - time();
 590              // If timeleft less than 30 sec, set the class on block to error to highlight.
 591              $errorclass = ($timeleft < 30) ? 'error' : 'warning';
 592              $output .= $this->box_start($errorclass . ' moodle-has-zindex maintenancewarning');
 593              $a = new stdClass();
 594              $a->min = (int)($timeleft/60);
 595              $a->sec = (int)($timeleft % 60);
 596              $output .= get_string('maintenancemodeisscheduled', 'admin', $a) ;
 597              $output .= $this->box_end();
 598              $this->page->requires->yui_module('moodle-core-maintenancemodetimer', 'M.core.maintenancemodetimer',
 599                      array(array('timeleftinsec' => $timeleft)));
 600              $this->page->requires->strings_for_js(
 601                      array('maintenancemodeisscheduled', 'sitemaintenance'),
 602                      'admin');
 603          }
 604          return $output;
 605      }
 606  
 607      /**
 608       * The standard tags (typically performance information and validation links,
 609       * if we are in developer debug mode) that should be output in the footer area
 610       * of the page. Designed to be called in theme layout.php files.
 611       *
 612       * @return string HTML fragment.
 613       */
 614      public function standard_footer_html() {
 615          global $CFG, $SCRIPT;
 616  
 617          if (during_initial_install()) {
 618              // Debugging info can not work before install is finished,
 619              // in any case we do not want any links during installation!
 620              return '';
 621          }
 622  
 623          // This function is normally called from a layout.php file in {@link core_renderer::header()}
 624          // but some of the content won't be known until later, so we return a placeholder
 625          // for now. This will be replaced with the real content in {@link core_renderer::footer()}.
 626          $output = $this->unique_performance_info_token;
 627          if ($this->page->devicetypeinuse == 'legacy') {
 628              // The legacy theme is in use print the notification
 629              $output .= html_writer::tag('div', get_string('legacythemeinuse'), array('class'=>'legacythemeinuse'));
 630          }
 631  
 632          // Get links to switch device types (only shown for users not on a default device)
 633          $output .= $this->theme_switch_links();
 634  
 635          if (!empty($CFG->debugpageinfo)) {
 636              $output .= '<div class="performanceinfo pageinfo">This page is: ' . $this->page->debug_summary() . '</div>';
 637          }
 638          if (debugging(null, DEBUG_DEVELOPER) and has_capability('moodle/site:config', context_system::instance())) {  // Only in developer mode
 639              // Add link to profiling report if necessary
 640              if (function_exists('profiling_is_running') && profiling_is_running()) {
 641                  $txt = get_string('profiledscript', 'admin');
 642                  $title = get_string('profiledscriptview', 'admin');
 643                  $url = $CFG->wwwroot . '/admin/tool/profiling/index.php?script=' . urlencode($SCRIPT);
 644                  $link= '<a title="' . $title . '" href="' . $url . '">' . $txt . '</a>';
 645                  $output .= '<div class="profilingfooter">' . $link . '</div>';
 646              }
 647              $purgeurl = new moodle_url('/admin/purgecaches.php', array('confirm' => 1,
 648                  'sesskey' => sesskey(), 'returnurl' => $this->page->url->out_as_local_url(false)));
 649              $output .= '<div class="purgecaches">' .
 650                      html_writer::link($purgeurl, get_string('purgecaches', 'admin')) . '</div>';
 651          }
 652          if (!empty($CFG->debugvalidators)) {
 653              // NOTE: this is not a nice hack, $PAGE->url is not always accurate and $FULLME neither, it is not a bug if it fails. --skodak
 654              $output .= '<div class="validators"><ul>
 655                <li><a href="http://validator.w3.org/check?verbose=1&amp;ss=1&amp;uri=' . urlencode(qualified_me()) . '">Validate HTML</a></li>
 656                <li><a href="http://www.contentquality.com/mynewtester/cynthia.exe?rptmode=-1&amp;url1=' . urlencode(qualified_me()) . '">Section 508 Check</a></li>
 657                <li><a href="http://www.contentquality.com/mynewtester/cynthia.exe?rptmode=0&amp;warnp2n3e=1&amp;url1=' . urlencode(qualified_me()) . '">WCAG 1 (2,3) Check</a></li>
 658              </ul></div>';
 659          }
 660          return $output;
 661      }
 662  
 663      /**
 664       * Returns standard main content placeholder.
 665       * Designed to be called in theme layout.php files.
 666       *
 667       * @return string HTML fragment.
 668       */
 669      public function main_content() {
 670          // This is here because it is the only place we can inject the "main" role over the entire main content area
 671          // without requiring all theme's to manually do it, and without creating yet another thing people need to
 672          // remember in the theme.
 673          // This is an unfortunate hack. DO NO EVER add anything more here.
 674          // DO NOT add classes.
 675          // DO NOT add an id.
 676          return '<div role="main">'.$this->unique_main_content_token.'</div>';
 677      }
 678  
 679      /**
 680       * The standard tags (typically script tags that are not needed earlier) that
 681       * should be output after everything else. Designed to be called in theme layout.php files.
 682       *
 683       * @return string HTML fragment.
 684       */
 685      public function standard_end_of_body_html() {
 686          global $CFG;
 687  
 688          // This function is normally called from a layout.php file in {@link core_renderer::header()}
 689          // but some of the content won't be known until later, so we return a placeholder
 690          // for now. This will be replaced with the real content in {@link core_renderer::footer()}.
 691          $output = '';
 692          if ($this->page->pagelayout !== 'embedded' && !empty($CFG->additionalhtmlfooter)) {
 693              $output .= "\n".$CFG->additionalhtmlfooter;
 694          }
 695          $output .= $this->unique_end_html_token;
 696          return $output;
 697      }
 698  
 699      /**
 700       * Return the standard string that says whether you are logged in (and switched
 701       * roles/logged in as another user).
 702       * @param bool $withlinks if false, then don't include any links in the HTML produced.
 703       * If not set, the default is the nologinlinks option from the theme config.php file,
 704       * and if that is not set, then links are included.
 705       * @return string HTML fragment.
 706       */
 707      public function login_info($withlinks = null) {
 708          global $USER, $CFG, $DB, $SESSION;
 709  
 710          if (during_initial_install()) {
 711              return '';
 712          }
 713  
 714          if (is_null($withlinks)) {
 715              $withlinks = empty($this->page->layout_options['nologinlinks']);
 716          }
 717  
 718          $course = $this->page->course;
 719          if (\core\session\manager::is_loggedinas()) {
 720              $realuser = \core\session\manager::get_realuser();
 721              $fullname = fullname($realuser, true);
 722              if ($withlinks) {
 723                  $loginastitle = get_string('loginas');
 724                  $realuserinfo = " [<a href=\"$CFG->wwwroot/course/loginas.php?id=$course->id&amp;sesskey=".sesskey()."\"";
 725                  $realuserinfo .= "title =\"".$loginastitle."\">$fullname</a>] ";
 726              } else {
 727                  $realuserinfo = " [$fullname] ";
 728              }
 729          } else {
 730              $realuserinfo = '';
 731          }
 732  
 733          $loginpage = $this->is_login_page();
 734          $loginurl = get_login_url();
 735  
 736          if (empty($course->id)) {
 737              // $course->id is not defined during installation
 738              return '';
 739          } else if (isloggedin()) {
 740              $context = context_course::instance($course->id);
 741  
 742              $fullname = fullname($USER, true);
 743              // Since Moodle 2.0 this link always goes to the public profile page (not the course profile page)
 744              if ($withlinks) {
 745                  $linktitle = get_string('viewprofile');
 746                  $username = "<a href=\"$CFG->wwwroot/user/profile.php?id=$USER->id\" title=\"$linktitle\">$fullname</a>";
 747              } else {
 748                  $username = $fullname;
 749              }
 750              if (is_mnet_remote_user($USER) and $idprovider = $DB->get_record('mnet_host', array('id'=>$USER->mnethostid))) {
 751                  if ($withlinks) {
 752                      $username .= " from <a href=\"{$idprovider->wwwroot}\">{$idprovider->name}</a>";
 753                  } else {
 754                      $username .= " from {$idprovider->name}";
 755                  }
 756              }
 757              if (isguestuser()) {
 758                  $loggedinas = $realuserinfo.get_string('loggedinasguest');
 759                  if (!$loginpage && $withlinks) {
 760                      $loggedinas .= " (<a href=\"$loginurl\">".get_string('login').'</a>)';
 761                  }
 762              } else if (is_role_switched($course->id)) { // Has switched roles
 763                  $rolename = '';
 764                  if ($role = $DB->get_record('role', array('id'=>$USER->access['rsw'][$context->path]))) {
 765                      $rolename = ': '.role_get_name($role, $context);
 766                  }
 767                  $loggedinas = get_string('loggedinas', 'moodle', $username).$rolename;
 768                  if ($withlinks) {
 769                      $url = new moodle_url('/course/switchrole.php', array('id'=>$course->id,'sesskey'=>sesskey(), 'switchrole'=>0, 'returnurl'=>$this->page->url->out_as_local_url(false)));
 770                      $loggedinas .= ' ('.html_writer::tag('a', get_string('switchrolereturn'), array('href' => $url)).')';
 771                  }
 772              } else {
 773                  $loggedinas = $realuserinfo.get_string('loggedinas', 'moodle', $username);
 774                  if ($withlinks) {
 775                      $loggedinas .= " (<a href=\"$CFG->wwwroot/login/logout.php?sesskey=".sesskey()."\">".get_string('logout').'</a>)';
 776                  }
 777              }
 778          } else {
 779              $loggedinas = get_string('loggedinnot', 'moodle');
 780              if (!$loginpage && $withlinks) {
 781                  $loggedinas .= " (<a href=\"$loginurl\">".get_string('login').'</a>)';
 782              }
 783          }
 784  
 785          $loggedinas = '<div class="logininfo">'.$loggedinas.'</div>';
 786  
 787          if (isset($SESSION->justloggedin)) {
 788              unset($SESSION->justloggedin);
 789              if (!empty($CFG->displayloginfailures)) {
 790                  if (!isguestuser()) {
 791                      // Include this file only when required.
 792                      require_once($CFG->dirroot . '/user/lib.php');
 793                      if ($count = user_count_login_failures($USER)) {
 794                          $loggedinas .= '<div class="loginfailures">';
 795                          $a = new stdClass();
 796                          $a->attempts = $count;
 797                          $loggedinas .= get_string('failedloginattempts', '', $a);
 798                          if (file_exists("$CFG->dirroot/report/log/index.php") and has_capability('report/log:view', context_system::instance())) {
 799                              $loggedinas .= ' ('.html_writer::link(new moodle_url('/report/log/index.php', array('chooselog' => 1,
 800                                      'id' => 0 , 'modid' => 'site_errors')), get_string('logs')).')';
 801                          }
 802                          $loggedinas .= '</div>';
 803                      }
 804                  }
 805              }
 806          }
 807  
 808          return $loggedinas;
 809      }
 810  
 811      /**
 812       * Check whether the current page is a login page.
 813       *
 814       * @since Moodle 2.9
 815       * @return bool
 816       */
 817      protected function is_login_page() {
 818          // This is a real bit of a hack, but its a rarety that we need to do something like this.
 819          // In fact the login pages should be only these two pages and as exposing this as an option for all pages
 820          // could lead to abuse (or at least unneedingly complex code) the hack is the way to go.
 821          return in_array(
 822              $this->page->url->out_as_local_url(false, array()),
 823              array(
 824                  '/login/index.php',
 825                  '/login/forgot_password.php',
 826              )
 827          );
 828      }
 829  
 830      /**
 831       * Return the 'back' link that normally appears in the footer.
 832       *
 833       * @return string HTML fragment.
 834       */
 835      public function home_link() {
 836          global $CFG, $SITE;
 837  
 838          if ($this->page->pagetype == 'site-index') {
 839              // Special case for site home page - please do not remove
 840              return '<div class="sitelink">' .
 841                     '<a title="Moodle" href="http://moodle.org/">' .
 842                     '<img src="' . $this->pix_url('moodlelogo') . '" alt="'.get_string('moodlelogo').'" /></a></div>';
 843  
 844          } else if (!empty($CFG->target_release) && $CFG->target_release != $CFG->release) {
 845              // Special case for during install/upgrade.
 846              return '<div class="sitelink">'.
 847                     '<a title="Moodle" href="http://docs.moodle.org/en/Administrator_documentation" onclick="this.target=\'_blank\'">' .
 848                     '<img src="' . $this->pix_url('moodlelogo') . '" alt="'.get_string('moodlelogo').'" /></a></div>';
 849  
 850          } else if ($this->page->course->id == $SITE->id || strpos($this->page->pagetype, 'course-view') === 0) {
 851              return '<div class="homelink"><a href="' . $CFG->wwwroot . '/">' .
 852                      get_string('home') . '</a></div>';
 853  
 854          } else {
 855              return '<div class="homelink"><a href="' . $CFG->wwwroot . '/course/view.php?id=' . $this->page->course->id . '">' .
 856                      format_string($this->page->course->shortname, true, array('context' => $this->page->context)) . '</a></div>';
 857          }
 858      }
 859  
 860      /**
 861       * Redirects the user by any means possible given the current state
 862       *
 863       * This function should not be called directly, it should always be called using
 864       * the redirect function in lib/weblib.php
 865       *
 866       * The redirect function should really only be called before page output has started
 867       * however it will allow itself to be called during the state STATE_IN_BODY
 868       *
 869       * @param string $encodedurl The URL to send to encoded if required
 870       * @param string $message The message to display to the user if any
 871       * @param int $delay The delay before redirecting a user, if $message has been
 872       *         set this is a requirement and defaults to 3, set to 0 no delay
 873       * @param boolean $debugdisableredirect this redirect has been disabled for
 874       *         debugging purposes. Display a message that explains, and don't
 875       *         trigger the redirect.
 876       * @param string $messagetype The type of notification to show the message in.
 877       *         See constants on \core\output\notification.
 878       * @return string The HTML to display to the user before dying, may contain
 879       *         meta refresh, javascript refresh, and may have set header redirects
 880       */
 881      public function redirect_message($encodedurl, $message, $delay, $debugdisableredirect,
 882                                       $messagetype = \core\output\notification::NOTIFY_INFO) {
 883          global $CFG;
 884          $url = str_replace('&amp;', '&', $encodedurl);
 885  
 886          switch ($this->page->state) {
 887              case moodle_page::STATE_BEFORE_HEADER :
 888                  // No output yet it is safe to delivery the full arsenal of redirect methods
 889                  if (!$debugdisableredirect) {
 890                      // Don't use exactly the same time here, it can cause problems when both redirects fire at the same time.
 891                      $this->metarefreshtag = '<meta http-equiv="refresh" content="'. $delay .'; url='. $encodedurl .'" />'."\n";
 892                      $this->page->requires->js_function_call('document.location.replace', array($url), false, ($delay + 3));
 893                  }
 894                  $output = $this->header();
 895                  break;
 896              case moodle_page::STATE_PRINTING_HEADER :
 897                  // We should hopefully never get here
 898                  throw new coding_exception('You cannot redirect while printing the page header');
 899                  break;
 900              case moodle_page::STATE_IN_BODY :
 901                  // We really shouldn't be here but we can deal with this
 902                  debugging("You should really redirect before you start page output");
 903                  if (!$debugdisableredirect) {
 904                      $this->page->requires->js_function_call('document.location.replace', array($url), false, $delay);
 905                  }
 906                  $output = $this->opencontainers->pop_all_but_last();
 907                  break;
 908              case moodle_page::STATE_DONE :
 909                  // Too late to be calling redirect now
 910                  throw new coding_exception('You cannot redirect after the entire page has been generated');
 911                  break;
 912          }
 913          $output .= $this->notification($message, $messagetype);
 914          $output .= '<div class="continuebutton">(<a href="'. $encodedurl .'">'. get_string('continue') .'</a>)</div>';
 915          if ($debugdisableredirect) {
 916              $output .= '<p><strong>'.get_string('erroroutput', 'error').'</strong></p>';
 917          }
 918          $output .= $this->footer();
 919          return $output;
 920      }
 921  
 922      /**
 923       * Start output by sending the HTTP headers, and printing the HTML <head>
 924       * and the start of the <body>.
 925       *
 926       * To control what is printed, you should set properties on $PAGE. If you
 927       * are familiar with the old {@link print_header()} function from Moodle 1.9
 928       * you will find that there are properties on $PAGE that correspond to most
 929       * of the old parameters to could be passed to print_header.
 930       *
 931       * Not that, in due course, the remaining $navigation, $menu parameters here
 932       * will be replaced by more properties of $PAGE, but that is still to do.
 933       *
 934       * @return string HTML that you must output this, preferably immediately.
 935       */
 936      public function header() {
 937          global $USER, $CFG;
 938  
 939          if (\core\session\manager::is_loggedinas()) {
 940              $this->page->add_body_class('userloggedinas');
 941          }
 942  
 943          // If the user is logged in, and we're not in initial install,
 944          // check to see if the user is role-switched and add the appropriate
 945          // CSS class to the body element.
 946          if (!during_initial_install() && isloggedin() && is_role_switched($this->page->course->id)) {
 947              $this->page->add_body_class('userswitchedrole');
 948          }
 949  
 950          // Give themes a chance to init/alter the page object.
 951          $this->page->theme->init_page($this->page);
 952  
 953          $this->page->set_state(moodle_page::STATE_PRINTING_HEADER);
 954  
 955          // Find the appropriate page layout file, based on $this->page->pagelayout.
 956          $layoutfile = $this->page->theme->layout_file($this->page->pagelayout);
 957          // Render the layout using the layout file.
 958          $rendered = $this->render_page_layout($layoutfile);
 959  
 960          // Slice the rendered output into header and footer.
 961          $cutpos = strpos($rendered, $this->unique_main_content_token);
 962          if ($cutpos === false) {
 963              $cutpos = strpos($rendered, self::MAIN_CONTENT_TOKEN);
 964              $token = self::MAIN_CONTENT_TOKEN;
 965          } else {
 966              $token = $this->unique_main_content_token;
 967          }
 968  
 969          if ($cutpos === false) {
 970              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.');
 971          }
 972          $header = substr($rendered, 0, $cutpos);
 973          $footer = substr($rendered, $cutpos + strlen($token));
 974  
 975          if (empty($this->contenttype)) {
 976              debugging('The page layout file did not call $OUTPUT->doctype()');
 977              $header = $this->doctype() . $header;
 978          }
 979  
 980          // If this theme version is below 2.4 release and this is a course view page
 981          if ((!isset($this->page->theme->settings->version) || $this->page->theme->settings->version < 2012101500) &&
 982                  $this->page->pagelayout === 'course' && $this->page->url->compare(new moodle_url('/course/view.php'), URL_MATCH_BASE)) {
 983              // check if course content header/footer have not been output during render of theme layout
 984              $coursecontentheader = $this->course_content_header(true);
 985              $coursecontentfooter = $this->course_content_footer(true);
 986              if (!empty($coursecontentheader)) {
 987                  // display debug message and add header and footer right above and below main content
 988                  // Please note that course header and footer (to be displayed above and below the whole page)
 989                  // are not displayed in this case at all.
 990                  // Besides the content header and footer are not displayed on any other course page
 991                  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);
 992                  $header .= $coursecontentheader;
 993                  $footer = $coursecontentfooter. $footer;
 994              }
 995          }
 996  
 997          send_headers($this->contenttype, $this->page->cacheable);
 998  
 999          $this->opencontainers->push('header/footer', $footer);
1000          $this->page->set_state(moodle_page::STATE_IN_BODY);
1001  
1002          return $header . $this->skip_link_target('maincontent');
1003      }
1004  
1005      /**
1006       * Renders and outputs the page layout file.
1007       *
1008       * This is done by preparing the normal globals available to a script, and
1009       * then including the layout file provided by the current theme for the
1010       * requested layout.
1011       *
1012       * @param string $layoutfile The name of the layout file
1013       * @return string HTML code
1014       */
1015      protected function render_page_layout($layoutfile) {
1016          global $CFG, $SITE, $USER;
1017          // The next lines are a bit tricky. The point is, here we are in a method
1018          // of a renderer class, and this object may, or may not, be the same as
1019          // the global $OUTPUT object. When rendering the page layout file, we want to use
1020          // this object. However, people writing Moodle code expect the current
1021          // renderer to be called $OUTPUT, not $this, so define a variable called
1022          // $OUTPUT pointing at $this. The same comment applies to $PAGE and $COURSE.
1023          $OUTPUT = $this;
1024          $PAGE = $this->page;
1025          $COURSE = $this->page->course;
1026  
1027          ob_start();
1028          include($layoutfile);
1029          $rendered = ob_get_contents();
1030          ob_end_clean();
1031          return $rendered;
1032      }
1033  
1034      /**
1035       * Outputs the page's footer
1036       *
1037       * @return string HTML fragment
1038       */
1039      public function footer() {
1040          global $CFG, $DB, $PAGE;
1041  
1042          $output = $this->container_end_all(true);
1043  
1044          $footer = $this->opencontainers->pop('header/footer');
1045  
1046          if (debugging() and $DB and $DB->is_transaction_started()) {
1047              // TODO: MDL-20625 print warning - transaction will be rolled back
1048          }
1049  
1050          // Provide some performance info if required
1051          $performanceinfo = '';
1052          if (defined('MDL_PERF') || (!empty($CFG->perfdebug) and $CFG->perfdebug > 7)) {
1053              $perf = get_performance_info();
1054              if (defined('MDL_PERFTOFOOT') || debugging() || $CFG->perfdebug > 7) {
1055                  $performanceinfo = $perf['html'];
1056              }
1057          }
1058  
1059          // We always want performance data when running a performance test, even if the user is redirected to another page.
1060          if (MDL_PERF_TEST && strpos($footer, $this->unique_performance_info_token) === false) {
1061              $footer = $this->unique_performance_info_token . $footer;
1062          }
1063          $footer = str_replace($this->unique_performance_info_token, $performanceinfo, $footer);
1064  
1065          // Only show notifications when we have a $PAGE context id.
1066          if (!empty($PAGE->context->id)) {
1067              $this->page->requires->js_call_amd('core/notification', 'init', array(
1068                  $PAGE->context->id,
1069                  \core\notification::fetch_as_array($this)
1070              ));
1071          }
1072          $footer = str_replace($this->unique_end_html_token, $this->page->requires->get_end_code(), $footer);
1073  
1074          $this->page->set_state(moodle_page::STATE_DONE);
1075  
1076          return $output . $footer;
1077      }
1078  
1079      /**
1080       * Close all but the last open container. This is useful in places like error
1081       * handling, where you want to close all the open containers (apart from <body>)
1082       * before outputting the error message.
1083       *
1084       * @param bool $shouldbenone assert that the stack should be empty now - causes a
1085       *      developer debug warning if it isn't.
1086       * @return string the HTML required to close any open containers inside <body>.
1087       */
1088      public function container_end_all($shouldbenone = false) {
1089          return $this->opencontainers->pop_all_but_last($shouldbenone);
1090      }
1091  
1092      /**
1093       * Returns course-specific information to be output immediately above content on any course page
1094       * (for the current course)
1095       *
1096       * @param bool $onlyifnotcalledbefore output content only if it has not been output before
1097       * @return string
1098       */
1099      public function course_content_header($onlyifnotcalledbefore = false) {
1100          global $CFG;
1101          static $functioncalled = false;
1102          if ($functioncalled && $onlyifnotcalledbefore) {
1103              // we have already output the content header
1104              return '';
1105          }
1106  
1107          // Output any session notification.
1108          $notifications = \core\notification::fetch();
1109  
1110          $bodynotifications = '';
1111          foreach ($notifications as $notification) {
1112              $bodynotifications .= $this->render_from_template(
1113                      $notification->get_template_name(),
1114                      $notification->export_for_template($this)
1115                  );
1116          }
1117  
1118          $output = html_writer::span($bodynotifications, 'notifications', array('id' => 'user-notifications'));
1119  
1120          if ($this->page->course->id == SITEID) {
1121              // return immediately and do not include /course/lib.php if not necessary
1122              return $output;
1123          }
1124  
1125          require_once($CFG->dirroot.'/course/lib.php');
1126          $functioncalled = true;
1127          $courseformat = course_get_format($this->page->course);
1128          if (($obj = $courseformat->course_content_header()) !== null) {
1129              $output .= html_writer::div($courseformat->get_renderer($this->page)->render($obj), 'course-content-header');
1130          }
1131          return $output;
1132      }
1133  
1134      /**
1135       * Returns course-specific information to be output immediately below content on any course page
1136       * (for the current course)
1137       *
1138       * @param bool $onlyifnotcalledbefore output content only if it has not been output before
1139       * @return string
1140       */
1141      public function course_content_footer($onlyifnotcalledbefore = false) {
1142          global $CFG;
1143          if ($this->page->course->id == SITEID) {
1144              // return immediately and do not include /course/lib.php if not necessary
1145              return '';
1146          }
1147          static $functioncalled = false;
1148          if ($functioncalled && $onlyifnotcalledbefore) {
1149              // we have already output the content footer
1150              return '';
1151          }
1152          $functioncalled = true;
1153          require_once($CFG->dirroot.'/course/lib.php');
1154          $courseformat = course_get_format($this->page->course);
1155          if (($obj = $courseformat->course_content_footer()) !== null) {
1156              return html_writer::div($courseformat->get_renderer($this->page)->render($obj), 'course-content-footer');
1157          }
1158          return '';
1159      }
1160  
1161      /**
1162       * Returns course-specific information to be output on any course page in the header area
1163       * (for the current course)
1164       *
1165       * @return string
1166       */
1167      public function course_header() {
1168          global $CFG;
1169          if ($this->page->course->id == SITEID) {
1170              // return immediately and do not include /course/lib.php if not necessary
1171              return '';
1172          }
1173          require_once($CFG->dirroot.'/course/lib.php');
1174          $courseformat = course_get_format($this->page->course);
1175          if (($obj = $courseformat->course_header()) !== null) {
1176              return $courseformat->get_renderer($this->page)->render($obj);
1177          }
1178          return '';
1179      }
1180  
1181      /**
1182       * Returns course-specific information to be output on any course page in the footer area
1183       * (for the current course)
1184       *
1185       * @return string
1186       */
1187      public function course_footer() {
1188          global $CFG;
1189          if ($this->page->course->id == SITEID) {
1190              // return immediately and do not include /course/lib.php if not necessary
1191              return '';
1192          }
1193          require_once($CFG->dirroot.'/course/lib.php');
1194          $courseformat = course_get_format($this->page->course);
1195          if (($obj = $courseformat->course_footer()) !== null) {
1196              return $courseformat->get_renderer($this->page)->render($obj);
1197          }
1198          return '';
1199      }
1200  
1201      /**
1202       * Returns lang menu or '', this method also checks forcing of languages in courses.
1203       *
1204       * This function calls {@link core_renderer::render_single_select()} to actually display the language menu.
1205       *
1206       * @return string The lang menu HTML or empty string
1207       */
1208      public function lang_menu() {
1209          global $CFG;
1210  
1211          if (empty($CFG->langmenu)) {
1212              return '';
1213          }
1214  
1215          if ($this->page->course != SITEID and !empty($this->page->course->lang)) {
1216              // do not show lang menu if language forced
1217              return '';
1218          }
1219  
1220          $currlang = current_language();
1221          $langs = get_string_manager()->get_list_of_translations();
1222  
1223          if (count($langs) < 2) {
1224              return '';
1225          }
1226  
1227          $s = new single_select($this->page->url, 'lang', $langs, $currlang, null);
1228          $s->label = get_accesshide(get_string('language'));
1229          $s->class = 'langmenu';
1230          return $this->render($s);
1231      }
1232  
1233      /**
1234       * Output the row of editing icons for a block, as defined by the controls array.
1235       *
1236       * @param array $controls an array like {@link block_contents::$controls}.
1237       * @param string $blockid The ID given to the block.
1238       * @return string HTML fragment.
1239       */
1240      public function block_controls($actions, $blockid = null) {
1241          global $CFG;
1242          if (empty($actions)) {
1243              return '';
1244          }
1245          $menu = new action_menu($actions);
1246          if ($blockid !== null) {
1247              $menu->set_owner_selector('#'.$blockid);
1248          }
1249          $menu->set_constraint('.block-region');
1250          $menu->attributes['class'] .= ' block-control-actions commands';
1251          if (isset($CFG->blockeditingmenu) && !$CFG->blockeditingmenu) {
1252              $menu->do_not_enhance();
1253          }
1254          return $this->render($menu);
1255      }
1256  
1257      /**
1258       * Renders an action menu component.
1259       *
1260       * ARIA references:
1261       *   - http://www.w3.org/WAI/GL/wiki/Using_ARIA_menus
1262       *   - http://stackoverflow.com/questions/12279113/recommended-wai-aria-implementation-for-navigation-bar-menu
1263       *
1264       * @param action_menu $menu
1265       * @return string HTML
1266       */
1267      public function render_action_menu(action_menu $menu) {
1268          $menu->initialise_js($this->page);
1269  
1270          $output = html_writer::start_tag('div', $menu->attributes);
1271          $output .= html_writer::start_tag('ul', $menu->attributesprimary);
1272          foreach ($menu->get_primary_actions($this) as $action) {
1273              if ($action instanceof renderable) {
1274                  $content = $this->render($action);
1275              } else {
1276                  $content = $action;
1277              }
1278              $output .= html_writer::tag('li', $content, array('role' => 'presentation'));
1279          }
1280          $output .= html_writer::end_tag('ul');
1281          $output .= html_writer::start_tag('ul', $menu->attributessecondary);
1282          foreach ($menu->get_secondary_actions() as $action) {
1283              if ($action instanceof renderable) {
1284                  $content = $this->render($action);
1285              } else {
1286                  $content = $action;
1287              }
1288              $output .= html_writer::tag('li', $content, array('role' => 'presentation'));
1289          }
1290          $output .= html_writer::end_tag('ul');
1291          $output .= html_writer::end_tag('div');
1292          return $output;
1293      }
1294  
1295      /**
1296       * Renders an action_menu_link item.
1297       *
1298       * @param action_menu_link $action
1299       * @return string HTML fragment
1300       */
1301      protected function render_action_menu_link(action_menu_link $action) {
1302          static $actioncount = 0;
1303          $actioncount++;
1304  
1305          $comparetoalt = '';
1306          $text = '';
1307          if (!$action->icon || $action->primary === false) {
1308              $text .= html_writer::start_tag('span', array('class'=>'menu-action-text', 'id' => 'actionmenuaction-'.$actioncount));
1309              if ($action->text instanceof renderable) {
1310                  $text .= $this->render($action->text);
1311              } else {
1312                  $text .= $action->text;
1313                  $comparetoalt = (string)$action->text;
1314              }
1315              $text .= html_writer::end_tag('span');
1316          }
1317  
1318          $icon = '';
1319          if ($action->icon) {
1320              $icon = $action->icon;
1321              if ($action->primary || !$action->actionmenu->will_be_enhanced()) {
1322                  $action->attributes['title'] = $action->text;
1323              }
1324              if (!$action->primary && $action->actionmenu->will_be_enhanced()) {
1325                  if ((string)$icon->attributes['alt'] === $comparetoalt) {
1326                      $icon->attributes['alt'] = '';
1327                  }
1328                  if (isset($icon->attributes['title']) && (string)$icon->attributes['title'] === $comparetoalt) {
1329                      unset($icon->attributes['title']);
1330                  }
1331              }
1332              $icon = $this->render($icon);
1333          }
1334  
1335          // A disabled link is rendered as formatted text.
1336          if (!empty($action->attributes['disabled'])) {
1337              // Do not use div here due to nesting restriction in xhtml strict.
1338              return html_writer::tag('span', $icon.$text, array('class'=>'currentlink', 'role' => 'menuitem'));
1339          }
1340  
1341          $attributes = $action->attributes;
1342          unset($action->attributes['disabled']);
1343          $attributes['href'] = $action->url;
1344          if ($text !== '') {
1345              $attributes['aria-labelledby'] = 'actionmenuaction-'.$actioncount;
1346          }
1347  
1348          return html_writer::tag('a', $icon.$text, $attributes);
1349      }
1350  
1351      /**
1352       * Renders a primary action_menu_filler item.
1353       *
1354       * @param action_menu_link_filler $action
1355       * @return string HTML fragment
1356       */
1357      protected function render_action_menu_filler(action_menu_filler $action) {
1358          return html_writer::span('&nbsp;', 'filler');
1359      }
1360  
1361      /**
1362       * Renders a primary action_menu_link item.
1363       *
1364       * @param action_menu_link_primary $action
1365       * @return string HTML fragment
1366       */
1367      protected function render_action_menu_link_primary(action_menu_link_primary $action) {
1368          return $this->render_action_menu_link($action);
1369      }
1370  
1371      /**
1372       * Renders a secondary action_menu_link item.
1373       *
1374       * @param action_menu_link_secondary $action
1375       * @return string HTML fragment
1376       */
1377      protected function render_action_menu_link_secondary(action_menu_link_secondary $action) {
1378          return $this->render_action_menu_link($action);
1379      }
1380  
1381      /**
1382       * Prints a nice side block with an optional header.
1383       *
1384       * The content is described
1385       * by a {@link core_renderer::block_contents} object.
1386       *
1387       * <div id="inst{$instanceid}" class="block_{$blockname} block">
1388       *      <div class="header"></div>
1389       *      <div class="content">
1390       *          ...CONTENT...
1391       *          <div class="footer">
1392       *          </div>
1393       *      </div>
1394       *      <div class="annotation">
1395       *      </div>
1396       * </div>
1397       *
1398       * @param block_contents $bc HTML for the content
1399       * @param string $region the region the block is appearing in.
1400       * @return string the HTML to be output.
1401       */
1402      public function block(block_contents $bc, $region) {
1403          $bc = clone($bc); // Avoid messing up the object passed in.
1404          if (empty($bc->blockinstanceid) || !strip_tags($bc->title)) {
1405              $bc->collapsible = block_contents::NOT_HIDEABLE;
1406          }
1407          if (!empty($bc->blockinstanceid)) {
1408              $bc->attributes['data-instanceid'] = $bc->blockinstanceid;
1409          }
1410          $skiptitle = strip_tags($bc->title);
1411          if ($bc->blockinstanceid && !empty($skiptitle)) {
1412              $bc->attributes['aria-labelledby'] = 'instance-'.$bc->blockinstanceid.'-header';
1413          } else if (!empty($bc->arialabel)) {
1414              $bc->attributes['aria-label'] = $bc->arialabel;
1415          }
1416          if ($bc->dockable) {
1417              $bc->attributes['data-dockable'] = 1;
1418          }
1419          if ($bc->collapsible == block_contents::HIDDEN) {
1420              $bc->add_class('hidden');
1421          }
1422          if (!empty($bc->controls)) {
1423              $bc->add_class('block_with_controls');
1424          }
1425  
1426  
1427          if (empty($skiptitle)) {
1428              $output = '';
1429              $skipdest = '';
1430          } else {
1431              $output = html_writer::link('#sb-'.$bc->skipid, get_string('skipa', 'access', $skiptitle),
1432                        array('class' => 'skip skip-block', 'id' => 'fsb-' . $bc->skipid));
1433              $skipdest = html_writer::span('', 'skip-block-to',
1434                        array('id' => 'sb-' . $bc->skipid));
1435          }
1436  
1437          $output .= html_writer::start_tag('div', $bc->attributes);
1438  
1439          $output .= $this->block_header($bc);
1440          $output .= $this->block_content($bc);
1441  
1442          $output .= html_writer::end_tag('div');
1443  
1444          $output .= $this->block_annotation($bc);
1445  
1446          $output .= $skipdest;
1447  
1448          $this->init_block_hider_js($bc);
1449          return $output;
1450      }
1451  
1452      /**
1453       * Produces a header for a block
1454       *
1455       * @param block_contents $bc
1456       * @return string
1457       */
1458      protected function block_header(block_contents $bc) {
1459  
1460          $title = '';
1461          if ($bc->title) {
1462              $attributes = array();
1463              if ($bc->blockinstanceid) {
1464                  $attributes['id'] = 'instance-'.$bc->blockinstanceid.'-header';
1465              }
1466              $title = html_writer::tag('h2', $bc->title, $attributes);
1467          }
1468  
1469          $blockid = null;
1470          if (isset($bc->attributes['id'])) {
1471              $blockid = $bc->attributes['id'];
1472          }
1473          $controlshtml = $this->block_controls($bc->controls, $blockid);
1474  
1475          $output = '';
1476          if ($title || $controlshtml) {
1477              $output .= html_writer::tag('div', html_writer::tag('div', html_writer::tag('div', '', array('class'=>'block_action')). $title . $controlshtml, array('class' => 'title')), array('class' => 'header'));
1478          }
1479          return $output;
1480      }
1481  
1482      /**
1483       * Produces the content area for a block
1484       *
1485       * @param block_contents $bc
1486       * @return string
1487       */
1488      protected function block_content(block_contents $bc) {
1489          $output = html_writer::start_tag('div', array('class' => 'content'));
1490          if (!$bc->title && !$this->block_controls($bc->controls)) {
1491              $output .= html_writer::tag('div', '', array('class'=>'block_action notitle'));
1492          }
1493          $output .= $bc->content;
1494          $output .= $this->block_footer($bc);
1495          $output .= html_writer::end_tag('div');
1496  
1497          return $output;
1498      }
1499  
1500      /**
1501       * Produces the footer for a block
1502       *
1503       * @param block_contents $bc
1504       * @return string
1505       */
1506      protected function block_footer(block_contents $bc) {
1507          $output = '';
1508          if ($bc->footer) {
1509              $output .= html_writer::tag('div', $bc->footer, array('class' => 'footer'));
1510          }
1511          return $output;
1512      }
1513  
1514      /**
1515       * Produces the annotation for a block
1516       *
1517       * @param block_contents $bc
1518       * @return string
1519       */
1520      protected function block_annotation(block_contents $bc) {
1521          $output = '';
1522          if ($bc->annotation) {
1523              $output .= html_writer::tag('div', $bc->annotation, array('class' => 'blockannotation'));
1524          }
1525          return $output;
1526      }
1527  
1528      /**
1529       * Calls the JS require function to hide a block.
1530       *
1531       * @param block_contents $bc A block_contents object
1532       */
1533      protected function init_block_hider_js(block_contents $bc) {
1534          if (!empty($bc->attributes['id']) and $bc->collapsible != block_contents::NOT_HIDEABLE) {
1535              $config = new stdClass;
1536              $config->id = $bc->attributes['id'];
1537              $config->title = strip_tags($bc->title);
1538              $config->preference = 'block' . $bc->blockinstanceid . 'hidden';
1539              $config->tooltipVisible = get_string('hideblocka', 'access', $config->title);
1540              $config->tooltipHidden = get_string('showblocka', 'access', $config->title);
1541  
1542              $this->page->requires->js_init_call('M.util.init_block_hider', array($config));
1543              user_preference_allow_ajax_update($config->preference, PARAM_BOOL);
1544          }
1545      }
1546  
1547      /**
1548       * Render the contents of a block_list.
1549       *
1550       * @param array $icons the icon for each item.
1551       * @param array $items the content of each item.
1552       * @return string HTML
1553       */
1554      public function list_block_contents($icons, $items) {
1555          $row = 0;
1556          $lis = array();
1557          foreach ($items as $key => $string) {
1558              $item = html_writer::start_tag('li', array('class' => 'r' . $row));
1559              if (!empty($icons[$key])) { //test if the content has an assigned icon
1560                  $item .= html_writer::tag('div', $icons[$key], array('class' => 'icon column c0'));
1561              }
1562              $item .= html_writer::tag('div', $string, array('class' => 'column c1'));
1563              $item .= html_writer::end_tag('li');
1564              $lis[] = $item;
1565              $row = 1 - $row; // Flip even/odd.
1566          }
1567          return html_writer::tag('ul', implode("\n", $lis), array('class' => 'unlist'));
1568      }
1569  
1570      /**
1571       * Output all the blocks in a particular region.
1572       *
1573       * @param string $region the name of a region on this page.
1574       * @return string the HTML to be output.
1575       */
1576      public function blocks_for_region($region) {
1577          $blockcontents = $this->page->blocks->get_content_for_region($region, $this);
1578          $blocks = $this->page->blocks->get_blocks_for_region($region);
1579          $lastblock = null;
1580          $zones = array();
1581          foreach ($blocks as $block) {
1582              $zones[] = $block->title;
1583          }
1584          $output = '';
1585  
1586          foreach ($blockcontents as $bc) {
1587              if ($bc instanceof block_contents) {
1588                  $output .= $this->block($bc, $region);
1589                  $lastblock = $bc->title;
1590              } else if ($bc instanceof block_move_target) {
1591                  $output .= $this->block_move_target($bc, $zones, $lastblock, $region);
1592              } else {
1593                  throw new coding_exception('Unexpected type of thing (' . get_class($bc) . ') found in list of block contents.');
1594              }
1595          }
1596          return $output;
1597      }
1598  
1599      /**
1600       * Output a place where the block that is currently being moved can be dropped.
1601       *
1602       * @param block_move_target $target with the necessary details.
1603       * @param array $zones array of areas where the block can be moved to
1604       * @param string $previous the block located before the area currently being rendered.
1605       * @param string $region the name of the region
1606       * @return string the HTML to be output.
1607       */
1608      public function block_move_target($target, $zones, $previous, $region) {
1609          if ($previous == null) {
1610              if (empty($zones)) {
1611                  // There are no zones, probably because there are no blocks.
1612                  $regions = $this->page->theme->get_all_block_regions();
1613                  $position = get_string('moveblockinregion', 'block', $regions[$region]);
1614              } else {
1615                  $position = get_string('moveblockbefore', 'block', $zones[0]);
1616              }
1617          } else {
1618              $position = get_string('moveblockafter', 'block', $previous);
1619          }
1620          return html_writer::tag('a', html_writer::tag('span', $position, array('class' => 'accesshide')), array('href' => $target->url, 'class' => 'blockmovetarget'));
1621      }
1622  
1623      /**
1624       * Renders a special html link with attached action
1625       *
1626       * Theme developers: DO NOT OVERRIDE! Please override function
1627       * {@link core_renderer::render_action_link()} instead.
1628       *
1629       * @param string|moodle_url $url
1630       * @param string $text HTML fragment
1631       * @param component_action $action
1632       * @param array $attributes associative array of html link attributes + disabled
1633       * @param pix_icon optional pix icon to render with the link
1634       * @return string HTML fragment
1635       */
1636      public function action_link($url, $text, component_action $action = null, array $attributes = null, $icon = null) {
1637          if (!($url instanceof moodle_url)) {
1638              $url = new moodle_url($url);
1639          }
1640          $link = new action_link($url, $text, $action, $attributes, $icon);
1641  
1642          return $this->render($link);
1643      }
1644  
1645      /**
1646       * Renders an action_link object.
1647       *
1648       * The provided link is renderer and the HTML returned. At the same time the
1649       * associated actions are setup in JS by {@link core_renderer::add_action_handler()}
1650       *
1651       * @param action_link $link
1652       * @return string HTML fragment
1653       */
1654      protected function render_action_link(action_link $link) {
1655          global $CFG;
1656  
1657          $text = '';
1658          if ($link->icon) {
1659              $text .= $this->render($link->icon);
1660          }
1661  
1662          if ($link->text instanceof renderable) {
1663              $text .= $this->render($link->text);
1664          } else {
1665              $text .= $link->text;
1666          }
1667  
1668          // A disabled link is rendered as formatted text
1669          if (!empty($link->attributes['disabled'])) {
1670              // do not use div here due to nesting restriction in xhtml strict
1671              return html_writer::tag('span', $text, array('class'=>'currentlink'));
1672          }
1673  
1674          $attributes = $link->attributes;
1675          unset($link->attributes['disabled']);
1676          $attributes['href'] = $link->url;
1677  
1678          if ($link->actions) {
1679              if (empty($attributes['id'])) {
1680                  $id = html_writer::random_id('action_link');
1681                  $attributes['id'] = $id;
1682              } else {
1683                  $id = $attributes['id'];
1684              }
1685              foreach ($link->actions as $action) {
1686                  $this->add_action_handler($action, $id);
1687              }
1688          }
1689  
1690          return html_writer::tag('a', $text, $attributes);
1691      }
1692  
1693  
1694      /**
1695       * Renders an action_icon.
1696       *
1697       * This function uses the {@link core_renderer::action_link()} method for the
1698       * most part. What it does different is prepare the icon as HTML and use it
1699       * as the link text.
1700       *
1701       * Theme developers: If you want to change how action links and/or icons are rendered,
1702       * consider overriding function {@link core_renderer::render_action_link()} and
1703       * {@link core_renderer::render_pix_icon()}.
1704       *
1705       * @param string|moodle_url $url A string URL or moodel_url
1706       * @param pix_icon $pixicon
1707       * @param component_action $action
1708       * @param array $attributes associative array of html link attributes + disabled
1709       * @param bool $linktext show title next to image in link
1710       * @return string HTML fragment
1711       */
1712      public function action_icon($url, pix_icon $pixicon, component_action $action = null, array $attributes = null, $linktext=false) {
1713          if (!($url instanceof moodle_url)) {
1714              $url = new moodle_url($url);
1715          }
1716          $attributes = (array)$attributes;
1717  
1718          if (empty($attributes['class'])) {
1719              // let ppl override the class via $options
1720              $attributes['class'] = 'action-icon';
1721          }
1722  
1723          $icon = $this->render($pixicon);
1724  
1725          if ($linktext) {
1726              $text = $pixicon->attributes['alt'];
1727          } else {
1728              $text = '';
1729          }
1730  
1731          return $this->action_link($url, $text.$icon, $action, $attributes);
1732      }
1733  
1734     /**
1735      * Print a message along with button choices for Continue/Cancel
1736      *
1737      * If a string or moodle_url is given instead of a single_button, method defaults to post.
1738      *
1739      * @param string $message The question to ask the user
1740      * @param single_button|moodle_url|string $continue The single_button component representing the Continue answer. Can also be a moodle_url or string URL
1741      * @param single_button|moodle_url|string $cancel The single_button component representing the Cancel answer. Can also be a moodle_url or string URL
1742      * @return string HTML fragment
1743      */
1744      public function confirm($message, $continue, $cancel) {
1745          if ($continue instanceof single_button) {
1746              // ok
1747          } else if (is_string($continue)) {
1748              $continue = new single_button(new moodle_url($continue), get_string('continue'), 'post');
1749          } else if ($continue instanceof moodle_url) {
1750              $continue = new single_button($continue, get_string('continue'), 'post');
1751          } else {
1752              throw new coding_exception('The continue param to $OUTPUT->confirm() must be either a URL (string/moodle_url) or a single_button instance.');
1753          }
1754  
1755          if ($cancel instanceof single_button) {
1756              // ok
1757          } else if (is_string($cancel)) {
1758              $cancel = new single_button(new moodle_url($cancel), get_string('cancel'), 'get');
1759          } else if ($cancel instanceof moodle_url) {
1760              $cancel = new single_button($cancel, get_string('cancel'), 'get');
1761          } else {
1762              throw new coding_exception('The cancel param to $OUTPUT->confirm() must be either a URL (string/moodle_url) or a single_button instance.');
1763          }
1764  
1765          $output = $this->box_start('generalbox', 'notice');
1766          $output .= html_writer::tag('p', $message);
1767          $output .= html_writer::tag('div', $this->render($continue) . $this->render($cancel), array('class' => 'buttons'));
1768          $output .= $this->box_end();
1769          return $output;
1770      }
1771  
1772      /**
1773       * Returns a form with a single button.
1774       *
1775       * Theme developers: DO NOT OVERRIDE! Please override function
1776       * {@link core_renderer::render_single_button()} instead.
1777       *
1778       * @param string|moodle_url $url
1779       * @param string $label button text
1780       * @param string $method get or post submit method
1781       * @param array $options associative array {disabled, title, etc.}
1782       * @return string HTML fragment
1783       */
1784      public function single_button($url, $label, $method='post', array $options=null) {
1785          if (!($url instanceof moodle_url)) {
1786              $url = new moodle_url($url);
1787          }
1788          $button = new single_button($url, $label, $method);
1789  
1790          foreach ((array)$options as $key=>$value) {
1791              if (array_key_exists($key, $button)) {
1792                  $button->$key = $value;
1793              }
1794          }
1795  
1796          return $this->render($button);
1797      }
1798  
1799      /**
1800       * Renders a single button widget.
1801       *
1802       * This will return HTML to display a form containing a single button.
1803       *
1804       * @param single_button $button
1805       * @return string HTML fragment
1806       */
1807      protected function render_single_button(single_button $button) {
1808          $attributes = array('type'     => 'submit',
1809                              'value'    => $button->label,
1810                              'disabled' => $button->disabled ? 'disabled' : null,
1811                              'title'    => $button->tooltip);
1812  
1813          if ($button->actions) {
1814              $id = html_writer::random_id('single_button');
1815              $attributes['id'] = $id;
1816              foreach ($button->actions as $action) {
1817                  $this->add_action_handler($action, $id);
1818              }
1819          }
1820  
1821          // first the input element
1822          $output = html_writer::empty_tag('input', $attributes);
1823  
1824          // then hidden fields
1825          $params = $button->url->params();
1826          if ($button->method === 'post') {
1827              $params['sesskey'] = sesskey();
1828          }
1829          foreach ($params as $var => $val) {
1830              $output .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => $var, 'value' => $val));
1831          }
1832  
1833          // then div wrapper for xhtml strictness
1834          $output = html_writer::tag('div', $output);
1835  
1836          // now the form itself around it
1837          if ($button->method === 'get') {
1838              $url = $button->url->out_omit_querystring(true); // url without params, the anchor part allowed
1839          } else {
1840              $url = $button->url->out_omit_querystring();     // url without params, the anchor part not allowed
1841          }
1842          if ($url === '') {
1843              $url = '#'; // there has to be always some action
1844          }
1845          $attributes = array('method' => $button->method,
1846                              'action' => $url,
1847                              'id'     => $button->formid);
1848          $output = html_writer::tag('form', $output, $attributes);
1849  
1850          // and finally one more wrapper with class
1851          return html_writer::tag('div', $output, array('class' => $button->class));
1852      }
1853  
1854      /**
1855       * Returns a form with a single select widget.
1856       *
1857       * Theme developers: DO NOT OVERRIDE! Please override function
1858       * {@link core_renderer::render_single_select()} instead.
1859       *
1860       * @param moodle_url $url form action target, includes hidden fields
1861       * @param string $name name of selection field - the changing parameter in url
1862       * @param array $options list of options
1863       * @param string $selected selected element
1864       * @param array $nothing
1865       * @param string $formid
1866       * @param array $attributes other attributes for the single select
1867       * @return string HTML fragment
1868       */
1869      public function single_select($url, $name, array $options, $selected = '',
1870                                  $nothing = array('' => 'choosedots'), $formid = null, $attributes = array()) {
1871          if (!($url instanceof moodle_url)) {
1872              $url = new moodle_url($url);
1873          }
1874          $select = new single_select($url, $name, $options, $selected, $nothing, $formid);
1875  
1876          if (array_key_exists('label', $attributes)) {
1877              $select->set_label($attributes['label']);
1878              unset($attributes['label']);
1879          }
1880          $select->attributes = $attributes;
1881  
1882          return $this->render($select);
1883      }
1884  
1885      /**
1886       * Returns a dataformat selection and download form
1887       *
1888       * @param string $label A text label
1889       * @param moodle_url|string $base The download page url
1890       * @param string $name The query param which will hold the type of the download
1891       * @param array $params Extra params sent to the download page
1892       * @return string HTML fragment
1893       */
1894      public function download_dataformat_selector($label, $base, $name = 'dataformat', $params = array()) {
1895  
1896          $formats = core_plugin_manager::instance()->get_plugins_of_type('dataformat');
1897          $options = array();
1898          foreach ($formats as $format) {
1899              if ($format->is_enabled()) {
1900                  $options[] = array(
1901                      'value' => $format->name,
1902                      'label' => get_string('dataformat', $format->component),
1903                  );
1904              }
1905          }
1906          $hiddenparams = array();
1907          foreach ($params as $key => $value) {
1908              $hiddenparams[] = array(
1909                  'name' => $key,
1910                  'value' => $value,
1911              );
1912          }
1913          $data = array(
1914              'label' => $label,
1915              'base' => $base,
1916              'name' => $name,
1917              'params' => $hiddenparams,
1918              'options' => $options,
1919              'sesskey' => sesskey(),
1920              'submit' => get_string('download'),
1921          );
1922  
1923          return $this->render_from_template('core/dataformat_selector', $data);
1924      }
1925  
1926  
1927      /**
1928       * Internal implementation of single_select rendering
1929       *
1930       * @param single_select $select
1931       * @return string HTML fragment
1932       */
1933      protected function render_single_select(single_select $select) {
1934          $select = clone($select);
1935          if (empty($select->formid)) {
1936              $select->formid = html_writer::random_id('single_select_f');
1937          }
1938  
1939          $output = '';
1940          $params = $select->url->params();
1941          if ($select->method === 'post') {
1942              $params['sesskey'] = sesskey();
1943          }
1944          foreach ($params as $name=>$value) {
1945              $output .= html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>$name, 'value'=>$value));
1946          }
1947  
1948          if (empty($select->attributes['id'])) {
1949              $select->attributes['id'] = html_writer::random_id('single_select');
1950          }
1951  
1952          if ($select->disabled) {
1953              $select->attributes['disabled'] = 'disabled';
1954          }
1955  
1956          if ($select->tooltip) {
1957              $select->attributes['title'] = $select->tooltip;
1958          }
1959  
1960          $select->attributes['class'] = 'autosubmit';
1961          if ($select->class) {
1962              $select->attributes['class'] .= ' ' . $select->class;
1963          }
1964  
1965          if ($select->label) {
1966              $output .= html_writer::label($select->label, $select->attributes['id'], false, $select->labelattributes);
1967          }
1968  
1969          if ($select->helpicon instanceof help_icon) {
1970              $output .= $this->render($select->helpicon);
1971          }
1972          $output .= html_writer::select($select->options, $select->name, $select->selected, $select->nothing, $select->attributes);
1973  
1974          $go = html_writer::empty_tag('input', array('type'=>'submit', 'value'=>get_string('go')));
1975          $output .= html_writer::tag('noscript', html_writer::tag('div', $go), array('class' => 'inline'));
1976  
1977          $nothing = empty($select->nothing) ? false : key($select->nothing);
1978          $this->page->requires->yui_module('moodle-core-formautosubmit',
1979              'M.core.init_formautosubmit',
1980              array(array('selectid' => $select->attributes['id'], 'nothing' => $nothing))
1981          );
1982  
1983          // then div wrapper for xhtml strictness
1984          $output = html_writer::tag('div', $output);
1985  
1986          // now the form itself around it
1987          if ($select->method === 'get') {
1988              $url = $select->url->out_omit_querystring(true); // url without params, the anchor part allowed
1989          } else {
1990              $url = $select->url->out_omit_querystring();     // url without params, the anchor part not allowed
1991          }
1992          $formattributes = array('method' => $select->method,
1993                                  'action' => $url,
1994                                  'id'     => $select->formid);
1995          $output = html_writer::tag('form', $output, $formattributes);
1996  
1997          // and finally one more wrapper with class
1998          return html_writer::tag('div', $output, array('class' => $select->class));
1999      }
2000  
2001      /**
2002       * Returns a form with a url select widget.
2003       *
2004       * Theme developers: DO NOT OVERRIDE! Please override function
2005       * {@link core_renderer::render_url_select()} instead.
2006       *
2007       * @param array $urls list of urls - array('/course/view.php?id=1'=>'Frontpage', ....)
2008       * @param string $selected selected element
2009       * @param array $nothing
2010       * @param string $formid
2011       * @return string HTML fragment
2012       */
2013      public function url_select(array $urls, $selected, $nothing = array('' => 'choosedots'), $formid = null) {
2014          $select = new url_select($urls, $selected, $nothing, $formid);
2015          return $this->render($select);
2016      }
2017  
2018      /**
2019       * Internal implementation of url_select rendering
2020       *
2021       * @param url_select $select
2022       * @return string HTML fragment
2023       */
2024      protected function render_url_select(url_select $select) {
2025          global $CFG;
2026  
2027          $select = clone($select);
2028          if (empty($select->formid)) {
2029              $select->formid = html_writer::random_id('url_select_f');
2030          }
2031  
2032          if (empty($select->attributes['id'])) {
2033              $select->attributes['id'] = html_writer::random_id('url_select');
2034          }
2035  
2036          if ($select->disabled) {
2037              $select->attributes['disabled'] = 'disabled';
2038          }
2039  
2040          if ($select->tooltip) {
2041              $select->attributes['title'] = $select->tooltip;
2042          }
2043  
2044          $output = '';
2045  
2046          if ($select->label) {
2047              $output .= html_writer::label($select->label, $select->attributes['id'], false, $select->labelattributes);
2048          }
2049  
2050          $classes = array();
2051          if (!$select->showbutton) {
2052              $classes[] = 'autosubmit';
2053          }
2054          if ($select->class) {
2055              $classes[] = $select->class;
2056          }
2057          if (count($classes)) {
2058              $select->attributes['class'] = implode(' ', $classes);
2059          }
2060  
2061          if ($select->helpicon instanceof help_icon) {
2062              $output .= $this->render($select->helpicon);
2063          }
2064  
2065          // For security reasons, the script course/jumpto.php requires URL starting with '/'. To keep
2066          // backward compatibility, we are removing heading $CFG->wwwroot from URLs here.
2067          $urls = array();
2068          foreach ($select->urls as $k=>$v) {
2069              if (is_array($v)) {
2070                  // optgroup structure
2071                  foreach ($v as $optgrouptitle => $optgroupoptions) {
2072                      foreach ($optgroupoptions as $optionurl => $optiontitle) {
2073                          if (empty($optionurl)) {
2074                              $safeoptionurl = '';
2075                          } else if (strpos($optionurl, $CFG->wwwroot.'/') === 0) {
2076                              // debugging('URLs passed to url_select should be in local relative form - please fix the code.', DEBUG_DEVELOPER);
2077                              $safeoptionurl = str_replace($CFG->wwwroot, '', $optionurl);
2078                          } else if (strpos($optionurl, '/') !== 0) {
2079                              debugging("Invalid url_select urls parameter inside optgroup: url '$optionurl' is not local relative url!");
2080                              continue;
2081                          } else {
2082                              $safeoptionurl = $optionurl;
2083                          }
2084                          $urls[$k][$optgrouptitle][$safeoptionurl] = $optiontitle;
2085                      }
2086                  }
2087              } else {
2088                  // plain list structure
2089                  if (empty($k)) {
2090                      // nothing selected option
2091                  } else if (strpos($k, $CFG->wwwroot.'/') === 0) {
2092                      $k = str_replace($CFG->wwwroot, '', $k);
2093                  } else if (strpos($k, '/') !== 0) {
2094                      debugging("Invalid url_select urls parameter: url '$k' is not local relative url!");
2095                      continue;
2096                  }
2097                  $urls[$k] = $v;
2098              }
2099          }
2100          $selected = $select->selected;
2101          if (!empty($selected)) {
2102              if (strpos($select->selected, $CFG->wwwroot.'/') === 0) {
2103                  $selected = str_replace($CFG->wwwroot, '', $selected);
2104              } else if (strpos($selected, '/') !== 0) {
2105                  debugging("Invalid value of parameter 'selected': url '$selected' is not local relative url!");
2106              }
2107          }
2108  
2109          $output .= html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>'sesskey', 'value'=>sesskey()));
2110          $output .= html_writer::select($urls, 'jump', $selected, $select->nothing, $select->attributes);
2111  
2112          if (!$select->showbutton) {
2113              $go = html_writer::empty_tag('input', array('type'=>'submit', 'value'=>get_string('go')));
2114              $output .= html_writer::tag('noscript', html_writer::tag('div', $go), array('class' => 'inline'));
2115              $nothing = empty($select->nothing) ? false : key($select->nothing);
2116              $this->page->requires->yui_module('moodle-core-formautosubmit',
2117                  'M.core.init_formautosubmit',
2118                  array(array('selectid' => $select->attributes['id'], 'nothing' => $nothing))
2119              );
2120          } else {
2121              $output .= html_writer::empty_tag('input', array('type'=>'submit', 'value'=>$select->showbutton));
2122          }
2123  
2124          // then div wrapper for xhtml strictness
2125          $output = html_writer::tag('div', $output);
2126  
2127          // now the form itself around it
2128          $formattributes = array('method' => 'post',
2129                                  'action' => new moodle_url('/course/jumpto.php'),
2130                                  'id'     => $select->formid);
2131          $output = html_writer::tag('form', $output, $formattributes);
2132  
2133          // and finally one more wrapper with class
2134          return html_writer::tag('div', $output, array('class' => $select->class));
2135      }
2136  
2137      /**
2138       * Returns a string containing a link to the user documentation.
2139       * Also contains an icon by default. Shown to teachers and admin only.
2140       *
2141       * @param string $path The page link after doc root and language, no leading slash.
2142       * @param string $text The text to be displayed for the link
2143       * @param boolean $forcepopup Whether to force a popup regardless of the value of $CFG->doctonewwindow
2144       * @return string
2145       */
2146      public function doc_link($path, $text = '', $forcepopup = false) {
2147          global $CFG;
2148  
2149          $icon = $this->pix_icon('docs', '', 'moodle', array('class'=>'iconhelp icon-pre', 'role'=>'presentation'));
2150  
2151          $url = new moodle_url(get_docs_url($path));
2152  
2153          $attributes = array('href'=>$url);
2154          if (!empty($CFG->doctonewwindow) || $forcepopup) {
2155              $attributes['class'] = 'helplinkpopup';
2156          }
2157  
2158          return html_writer::tag('a', $icon.$text, $attributes);
2159      }
2160  
2161      /**
2162       * Return HTML for a pix_icon.
2163       *
2164       * Theme developers: DO NOT OVERRIDE! Please override function
2165       * {@link core_renderer::render_pix_icon()} instead.
2166       *
2167       * @param string $pix short pix name
2168       * @param string $alt mandatory alt attribute
2169       * @param string $component standard compoennt name like 'moodle', 'mod_forum', etc.
2170       * @param array $attributes htm lattributes
2171       * @return string HTML fragment
2172       */
2173      public function pix_icon($pix, $alt, $component='moodle', array $attributes = null) {
2174          $icon = new pix_icon($pix, $alt, $component, $attributes);
2175          return $this->render($icon);
2176      }
2177  
2178      /**
2179       * Renders a pix_icon widget and returns the HTML to display it.
2180       *
2181       * @param pix_icon $icon
2182       * @return string HTML fragment
2183       */
2184      protected function render_pix_icon(pix_icon $icon) {
2185          $data = $icon->export_for_template($this);
2186          return $this->render_from_template('core/pix_icon', $data);
2187      }
2188  
2189      /**
2190       * Return HTML to display an emoticon icon.
2191       *
2192       * @param pix_emoticon $emoticon
2193       * @return string HTML fragment
2194       */
2195      protected function render_pix_emoticon(pix_emoticon $emoticon) {
2196          $attributes = $emoticon->attributes;
2197          $attributes['src'] = $this->pix_url($emoticon->pix, $emoticon->component);
2198          return html_writer::empty_tag('img', $attributes);
2199      }
2200  
2201      /**
2202       * Produces the html that represents this rating in the UI
2203       *
2204       * @param rating $rating the page object on which this rating will appear
2205       * @return string
2206       */
2207      function render_rating(rating $rating) {
2208          global $CFG, $USER;
2209  
2210          if ($rating->settings->aggregationmethod == RATING_AGGREGATE_NONE) {
2211              return null;//ratings are turned off
2212          }
2213  
2214          $ratingmanager = new rating_manager();
2215          // Initialise the JavaScript so ratings can be done by AJAX.
2216          $ratingmanager->initialise_rating_javascript($this->page);
2217  
2218          $strrate = get_string("rate", "rating");
2219          $ratinghtml = ''; //the string we'll return
2220  
2221          // permissions check - can they view the aggregate?
2222          if ($rating->user_can_view_aggregate()) {
2223  
2224              $aggregatelabel = $ratingmanager->get_aggregate_label($rating->settings->aggregationmethod);
2225              $aggregatestr   = $rating->get_aggregate_string();
2226  
2227              $aggregatehtml  = html_writer::tag('span', $aggregatestr, array('id' => 'ratingaggregate'.$rating->itemid, 'class' => 'ratingaggregate')).' ';
2228              if ($rating->count > 0) {
2229                  $countstr = "({$rating->count})";
2230              } else {
2231                  $countstr = '-';
2232              }
2233              $aggregatehtml .= html_writer::tag('span', $countstr, array('id'=>"ratingcount{$rating->itemid}", 'class' => 'ratingcount')).' ';
2234  
2235              $ratinghtml .= html_writer::tag('span', $aggregatelabel, array('class'=>'rating-aggregate-label'));
2236              if ($rating->settings->permissions->viewall && $rating->settings->pluginpermissions->viewall) {
2237  
2238                  $nonpopuplink = $rating->get_view_ratings_url();
2239                  $popuplink = $rating->get_view_ratings_url(true);
2240  
2241                  $action = new popup_action('click', $popuplink, 'ratings', array('height' => 400, 'width' => 600));
2242                  $ratinghtml .= $this->action_link($nonpopuplink, $aggregatehtml, $action);
2243              } else {
2244                  $ratinghtml .= $aggregatehtml;
2245              }
2246          }
2247  
2248          $formstart = null;
2249          // if the item doesn't belong to the current user, the user has permission to rate
2250          // and we're within the assessable period
2251          if ($rating->user_can_rate()) {
2252  
2253              $rateurl = $rating->get_rate_url();
2254              $inputs = $rateurl->params();
2255  
2256              //start the rating form
2257              $formattrs = array(
2258                  'id'     => "postrating{$rating->itemid}",
2259                  'class'  => 'postratingform',
2260                  'method' => 'post',
2261                  'action' => $rateurl->out_omit_querystring()
2262              );
2263              $formstart  = html_writer::start_tag('form', $formattrs);
2264              $formstart .= html_writer::start_tag('div', array('class' => 'ratingform'));
2265  
2266              // add the hidden inputs
2267              foreach ($inputs as $name => $value) {
2268                  $attributes = array('type' => 'hidden', 'class' => 'ratinginput', 'name' => $name, 'value' => $value);
2269                  $formstart .= html_writer::empty_tag('input', $attributes);
2270              }
2271  
2272              if (empty($ratinghtml)) {
2273                  $ratinghtml .= $strrate.': ';
2274              }
2275              $ratinghtml = $formstart.$ratinghtml;
2276  
2277              $scalearray = array(RATING_UNSET_RATING => $strrate.'...') + $rating->settings->scale->scaleitems;
2278              $scaleattrs = array('class'=>'postratingmenu ratinginput','id'=>'menurating'.$rating->itemid);
2279              $ratinghtml .= html_writer::label($rating->rating, 'menurating'.$rating->itemid, false, array('class' => 'accesshide'));
2280              $ratinghtml .= html_writer::select($scalearray, 'rating', $rating->rating, false, $scaleattrs);
2281  
2282              //output submit button
2283              $ratinghtml .= html_writer::start_tag('span', array('class'=>"ratingsubmit"));
2284  
2285              $attributes = array('type' => 'submit', 'class' => 'postratingmenusubmit', 'id' => 'postratingsubmit'.$rating->itemid, 'value' => s(get_string('rate', 'rating')));
2286              $ratinghtml .= html_writer::empty_tag('input', $attributes);
2287  
2288              if (!$rating->settings->scale->isnumeric) {
2289                  // If a global scale, try to find current course ID from the context
2290                  if (empty($rating->settings->scale->courseid) and $coursecontext = $rating->context->get_course_context(false)) {
2291                      $courseid = $coursecontext->instanceid;
2292                  } else {
2293                      $courseid = $rating->settings->scale->courseid;
2294                  }
2295                  $ratinghtml .= $this->help_icon_scale($courseid, $rating->settings->scale);
2296              }
2297              $ratinghtml .= html_writer::end_tag('span');
2298              $ratinghtml .= html_writer::end_tag('div');
2299              $ratinghtml .= html_writer::end_tag('form');
2300          }
2301  
2302          return $ratinghtml;
2303      }
2304  
2305      /**
2306       * Centered heading with attached help button (same title text)
2307       * and optional icon attached.
2308       *
2309       * @param string $text A heading text
2310       * @param string $helpidentifier The keyword that defines a help page
2311       * @param string $component component name
2312       * @param string|moodle_url $icon
2313       * @param string $iconalt icon alt text
2314       * @param int $level The level of importance of the heading. Defaulting to 2
2315       * @param string $classnames A space-separated list of CSS classes. Defaulting to null
2316       * @return string HTML fragment
2317       */
2318      public function heading_with_help($text, $helpidentifier, $component = 'moodle', $icon = '', $iconalt = '', $level = 2, $classnames = null) {
2319          $image = '';
2320          if ($icon) {
2321              $image = $this->pix_icon($icon, $iconalt, $component, array('class'=>'icon iconlarge'));
2322          }
2323  
2324          $help = '';
2325          if ($helpidentifier) {
2326              $help = $this->help_icon($helpidentifier, $component);
2327          }
2328  
2329          return $this->heading($image.$text.$help, $level, $classnames);
2330      }
2331  
2332      /**
2333       * Returns HTML to display a help icon.
2334       *
2335       * @deprecated since Moodle 2.0
2336       */
2337      public function old_help_icon($helpidentifier, $title, $component = 'moodle', $linktext = '') {
2338          throw new coding_exception('old_help_icon() can not be used any more, please see help_icon().');
2339      }
2340  
2341      /**
2342       * Returns HTML to display a help icon.
2343       *
2344       * Theme developers: DO NOT OVERRIDE! Please override function
2345       * {@link core_renderer::render_help_icon()} instead.
2346       *
2347       * @param string $identifier The keyword that defines a help page
2348       * @param string $component component name
2349       * @param string|bool $linktext true means use $title as link text, string means link text value
2350       * @return string HTML fragment
2351       */
2352      public function help_icon($identifier, $component = 'moodle', $linktext = '') {
2353          $icon = new help_icon($identifier, $component);
2354          $icon->diag_strings();
2355          if ($linktext === true) {
2356              $icon->linktext = get_string($icon->identifier, $icon->component);
2357          } else if (!empty($linktext)) {
2358              $icon->linktext = $linktext;
2359          }
2360          return $this->render($icon);
2361      }
2362  
2363      /**
2364       * Implementation of user image rendering.
2365       *
2366       * @param help_icon $helpicon A help icon instance
2367       * @return string HTML fragment
2368       */
2369      protected function render_help_icon(help_icon $helpicon) {
2370          global $CFG;
2371  
2372          // first get the help image icon
2373          $src = $this->pix_url('help');
2374  
2375          $title = get_string($helpicon->identifier, $helpicon->component);
2376  
2377          if (empty($helpicon->linktext)) {
2378              $alt = get_string('helpprefix2', '', trim($title, ". \t"));
2379          } else {
2380              $alt = get_string('helpwiththis');
2381          }
2382  
2383          $attributes = array('src'=>$src, 'alt'=>$alt, 'class'=>'iconhelp');
2384          $output = html_writer::empty_tag('img', $attributes);
2385  
2386          // add the link text if given
2387          if (!empty($helpicon->linktext)) {
2388              // the spacing has to be done through CSS
2389              $output .= $helpicon->linktext;
2390          }
2391  
2392          // now create the link around it - we need https on loginhttps pages
2393          $url = new moodle_url($CFG->httpswwwroot.'/help.php', array('component' => $helpicon->component, 'identifier' => $helpicon->identifier, 'lang'=>current_language()));
2394  
2395          // note: this title is displayed only if JS is disabled, otherwise the link will have the new ajax tooltip
2396          $title = get_string('helpprefix2', '', trim($title, ". \t"));
2397  
2398          $attributes = array('href' => $url, 'title' => $title, 'aria-haspopup' => 'true', 'target'=>'_blank');
2399          $output = html_writer::tag('a', $output, $attributes);
2400  
2401          // and finally span
2402          return html_writer::tag('span', $output, array('class' => 'helptooltip'));
2403      }
2404  
2405      /**
2406       * Returns HTML to display a scale help icon.
2407       *
2408       * @param int $courseid
2409       * @param stdClass $scale instance
2410       * @return string HTML fragment
2411       */
2412      public function help_icon_scale($courseid, stdClass $scale) {
2413          global $CFG;
2414  
2415          $title = get_string('helpprefix2', '', $scale->name) .' ('.get_string('newwindow').')';
2416  
2417          $icon = $this->pix_icon('help', get_string('scales'), 'moodle', array('class'=>'iconhelp'));
2418  
2419          $scaleid = abs($scale->id);
2420  
2421          $link = new moodle_url('/course/scales.php', array('id' => $courseid, 'list' => true, 'scaleid' => $scaleid));
2422          $action = new popup_action('click', $link, 'ratingscale');
2423  
2424          return html_writer::tag('span', $this->action_link($link, $icon, $action), array('class' => 'helplink'));
2425      }
2426  
2427      /**
2428       * Creates and returns a spacer image with optional line break.
2429       *
2430       * @param array $attributes Any HTML attributes to add to the spaced.
2431       * @param bool $br Include a BR after the spacer.... DON'T USE THIS. Don't be
2432       *     laxy do it with CSS which is a much better solution.
2433       * @return string HTML fragment
2434       */
2435      public function spacer(array $attributes = null, $br = false) {
2436          $attributes = (array)$attributes;
2437          if (empty($attributes['width'])) {
2438              $attributes['width'] = 1;
2439          }
2440          if (empty($attributes['height'])) {
2441              $attributes['height'] = 1;
2442          }
2443          $attributes['class'] = 'spacer';
2444  
2445          $output = $this->pix_icon('spacer', '', 'moodle', $attributes);
2446  
2447          if (!empty($br)) {
2448              $output .= '<br />';
2449          }
2450  
2451          return $output;
2452      }
2453  
2454      /**
2455       * Returns HTML to display the specified user's avatar.
2456       *
2457       * User avatar may be obtained in two ways:
2458       * <pre>
2459       * // Option 1: (shortcut for simple cases, preferred way)
2460       * // $user has come from the DB and has fields id, picture, imagealt, firstname and lastname
2461       * $OUTPUT->user_picture($user, array('popup'=>true));
2462       *
2463       * // Option 2:
2464       * $userpic = new user_picture($user);
2465       * // Set properties of $userpic
2466       * $userpic->popup = true;
2467       * $OUTPUT->render($userpic);
2468       * </pre>
2469       *
2470       * Theme developers: DO NOT OVERRIDE! Please override function
2471       * {@link core_renderer::render_user_picture()} instead.
2472       *
2473       * @param stdClass $user Object with at least fields id, picture, imagealt, firstname, lastname
2474       *     If any of these are missing, the database is queried. Avoid this
2475       *     if at all possible, particularly for reports. It is very bad for performance.
2476       * @param array $options associative array with user picture options, used only if not a user_picture object,
2477       *     options are:
2478       *     - courseid=$this->page->course->id (course id of user profile in link)
2479       *     - size=35 (size of image)
2480       *     - link=true (make image clickable - the link leads to user profile)
2481       *     - popup=false (open in popup)
2482       *     - alttext=true (add image alt attribute)
2483       *     - class = image class attribute (default 'userpicture')
2484       *     - visibletoscreenreaders=true (whether to be visible to screen readers)
2485       * @return string HTML fragment
2486       */
2487      public function user_picture(stdClass $user, array $options = null) {
2488          $userpicture = new user_picture($user);
2489          foreach ((array)$options as $key=>$value) {
2490              if (array_key_exists($key, $userpicture)) {
2491                  $userpicture->$key = $value;
2492              }
2493          }
2494          return $this->render($userpicture);
2495      }
2496  
2497      /**
2498       * Internal implementation of user image rendering.
2499       *
2500       * @param user_picture $userpicture
2501       * @return string
2502       */
2503      protected function render_user_picture(user_picture $userpicture) {
2504          global $CFG, $DB;
2505  
2506          $user = $userpicture->user;
2507  
2508          if ($userpicture->alttext) {
2509              if (!empty($user->imagealt)) {
2510                  $alt = $user->imagealt;
2511              } else {
2512                  $alt = get_string('pictureof', '', fullname($user));
2513              }
2514          } else {
2515              $alt = '';
2516          }
2517  
2518          if (empty($userpicture->size)) {
2519              $size = 35;
2520          } else if ($userpicture->size === true or $userpicture->size == 1) {
2521              $size = 100;
2522          } else {
2523              $size = $userpicture->size;
2524          }
2525  
2526          $class = $userpicture->class;
2527  
2528          if ($user->picture == 0) {
2529              $class .= ' defaultuserpic';
2530          }
2531  
2532          $src = $userpicture->get_url($this->page, $this);
2533  
2534          $attributes = array('src'=>$src, 'alt'=>$alt, 'title'=>$alt, 'class'=>$class, 'width'=>$size, 'height'=>$size);
2535          if (!$userpicture->visibletoscreenreaders) {
2536              $attributes['role'] = 'presentation';
2537          }
2538  
2539          // get the image html output fisrt
2540          $output = html_writer::empty_tag('img', $attributes);
2541  
2542          // then wrap it in link if needed
2543          if (!$userpicture->link) {
2544              return $output;
2545          }
2546  
2547          if (empty($userpicture->courseid)) {
2548              $courseid = $this->page->course->id;
2549          } else {
2550              $courseid = $userpicture->courseid;
2551          }
2552  
2553          if ($courseid == SITEID) {
2554              $url = new moodle_url('/user/profile.php', array('id' => $user->id));
2555          } else {
2556              $url = new moodle_url('/user/view.php', array('id' => $user->id, 'course' => $courseid));
2557          }
2558  
2559          $attributes = array('href'=>$url);
2560          if (!$userpicture->visibletoscreenreaders) {
2561              $attributes['tabindex'] = '-1';
2562              $attributes['aria-hidden'] = 'true';
2563          }
2564  
2565          if ($userpicture->popup) {
2566              $id = html_writer::random_id('userpicture');
2567              $attributes['id'] = $id;
2568              $this->add_action_handler(new popup_action('click', $url), $id);
2569          }
2570  
2571          return html_writer::tag('a', $output, $attributes);
2572      }
2573  
2574      /**
2575       * Internal implementation of file tree viewer items rendering.
2576       *
2577       * @param array $dir
2578       * @return string
2579       */
2580      public function htmllize_file_tree($dir) {
2581          if (empty($dir['subdirs']) and empty($dir['files'])) {
2582              return '';
2583          }
2584          $result = '<ul>';
2585          foreach ($dir['subdirs'] as $subdir) {
2586              $result .= '<li>'.s($subdir['dirname']).' '.$this->htmllize_file_tree($subdir).'</li>';
2587          }
2588          foreach ($dir['files'] as $file) {
2589              $filename = $file->get_filename();
2590              $result .= '<li><span>'.html_writer::link($file->fileurl, $filename).'</span></li>';
2591          }
2592          $result .= '</ul>';
2593  
2594          return $result;
2595      }
2596  
2597      /**
2598       * Returns HTML to display the file picker
2599       *
2600       * <pre>
2601       * $OUTPUT->file_picker($options);
2602       * </pre>
2603       *
2604       * Theme developers: DO NOT OVERRIDE! Please override function
2605       * {@link core_renderer::render_file_picker()} instead.
2606       *
2607       * @param array $options associative array with file manager options
2608       *   options are:
2609       *       maxbytes=>-1,
2610       *       itemid=>0,
2611       *       client_id=>uniqid(),
2612       *       acepted_types=>'*',
2613       *       return_types=>FILE_INTERNAL,
2614       *       context=>$PAGE->context
2615       * @return string HTML fragment
2616       */
2617      public function file_picker($options) {
2618          $fp = new file_picker($options);
2619          return $this->render($fp);
2620      }
2621  
2622      /**
2623       * Internal implementation of file picker rendering.
2624       *
2625       * @param file_picker $fp
2626       * @return string
2627       */
2628      public function render_file_picker(file_picker $fp) {
2629          global $CFG, $OUTPUT, $USER;
2630          $options = $fp->options;
2631          $client_id = $options->client_id;
2632          $strsaved = get_string('filesaved', 'repository');
2633          $straddfile = get_string('openpicker', 'repository');
2634          $strloading  = get_string('loading', 'repository');
2635          $strdndenabled = get_string('dndenabled_inbox', 'moodle');
2636          $strdroptoupload = get_string('droptoupload', 'moodle');
2637          $icon_progress = $OUTPUT->pix_icon('i/loading_small', $strloading).'';
2638  
2639          $currentfile = $options->currentfile;
2640          if (empty($currentfile)) {
2641              $currentfile = '';
2642          } else {
2643              $currentfile .= ' - ';
2644          }
2645          if ($options->maxbytes) {
2646              $size = $options->maxbytes;
2647          } else {
2648              $size = get_max_upload_file_size();
2649          }
2650          if ($size == -1) {
2651              $maxsize = '';
2652          } else {
2653              $maxsize = get_string('maxfilesize', 'moodle', display_size($size));
2654          }
2655          if ($options->buttonname) {
2656              $buttonname = ' name="' . $options->buttonname . '"';
2657          } else {
2658              $buttonname = '';
2659          }
2660          $html = <<<EOD
2661  <div class="filemanager-loading mdl-align" id='filepicker-loading-{$client_id}'>
2662  $icon_progress
2663  </div>
2664  <div id="filepicker-wrapper-{$client_id}" class="mdl-left" style="display:none">
2665      <div>
2666          <input type="button" class="fp-btn-choose" id="filepicker-button-{$client_id}" value="{$straddfile}"{$buttonname}/>
2667          <span> $maxsize </span>
2668      </div>
2669  EOD;
2670          if ($options->env != 'url') {
2671              $html .= <<<EOD
2672      <div id="file_info_{$client_id}" class="mdl-left filepicker-filelist" style="position: relative">
2673      <div class="filepicker-filename">
2674          <div class="filepicker-container">$currentfile<div class="dndupload-message">$strdndenabled <br/><div class="dndupload-arrow"></div></div></div>
2675          <div class="dndupload-progressbars"></div>
2676      </div>
2677      <div><div class="dndupload-target">{$strdroptoupload}<br/><div class="dndupload-arrow"></div></div></div>
2678      </div>
2679  EOD;
2680          }
2681          $html .= '</div>';
2682          return $html;
2683      }
2684  
2685      /**
2686       * Returns HTML to display the 'Update this Modulename' button that appears on module pages.
2687       *
2688       * @param string $cmid the course_module id.
2689       * @param string $modulename the module name, eg. "forum", "quiz" or "workshop"
2690       * @return string the HTML for the button, if this user has permission to edit it, else an empty string.
2691       */
2692      public function update_module_button($cmid, $modulename) {
2693          global $CFG;
2694          if (has_capability('moodle/course:manageactivities', context_module::instance($cmid))) {
2695              $modulename = get_string('modulename', $modulename);
2696              $string = get_string('updatethis', '', $modulename);
2697              $url = new moodle_url("$CFG->wwwroot/course/mod.php", array('update' => $cmid, 'return' => true, 'sesskey' => sesskey()));
2698              return $this->single_button($url, $string);
2699          } else {
2700              return '';
2701          }
2702      }
2703  
2704      /**
2705       * Returns HTML to display a "Turn editing on/off" button in a form.
2706       *
2707       * @param moodle_url $url The URL + params to send through when clicking the button
2708       * @return string HTML the button
2709       */
2710      public function edit_button(moodle_url $url) {
2711  
2712          $url->param('sesskey', sesskey());
2713          if ($this->page->user_is_editing()) {
2714              $url->param('edit', 'off');
2715              $editstring = get_string('turneditingoff');
2716          } else {
2717              $url->param('edit', 'on');
2718              $editstring = get_string('turneditingon');
2719          }
2720  
2721          return $this->single_button($url, $editstring);
2722      }
2723  
2724      /**
2725       * Returns HTML to display a simple button to close a window
2726       *
2727       * @param string $text The lang string for the button's label (already output from get_string())
2728       * @return string html fragment
2729       */
2730      public function close_window_button($text='') {
2731          if (empty($text)) {
2732              $text = get_string('closewindow');
2733          }
2734          $button = new single_button(new moodle_url('#'), $text, 'get');
2735          $button->add_action(new component_action('click', 'close_window'));
2736  
2737          return $this->container($this->render($button), 'closewindow');
2738      }
2739  
2740      /**
2741       * Output an error message. By default wraps the error message in <span class="error">.
2742       * If the error message is blank, nothing is output.
2743       *
2744       * @param string $message the error message.
2745       * @return string the HTML to output.
2746       */
2747      public function error_text($message) {
2748          if (empty($message)) {
2749              return '';
2750          }
2751          $message = $this->pix_icon('i/warning', get_string('error'), '', array('class' => 'icon icon-pre', 'title'=>'')) . $message;
2752          return html_writer::tag('span', $message, array('class' => 'error'));
2753      }
2754  
2755      /**
2756       * Do not call this function directly.
2757       *
2758       * To terminate the current script with a fatal error, call the {@link print_error}
2759       * function, or throw an exception. Doing either of those things will then call this
2760       * function to display the error, before terminating the execution.
2761       *
2762       * @param string $message The message to output
2763       * @param string $moreinfourl URL where more info can be found about the error
2764       * @param string $link Link for the Continue button
2765       * @param array $backtrace The execution backtrace
2766       * @param string $debuginfo Debugging information
2767       * @return string the HTML to output.
2768       */
2769      public function fatal_error($message, $moreinfourl, $link, $backtrace, $debuginfo = null) {
2770          global $CFG;
2771  
2772          $output = '';
2773          $obbuffer = '';
2774  
2775          if ($this->has_started()) {
2776              // we can not always recover properly here, we have problems with output buffering,
2777              // html tables, etc.
2778              $output .= $this->opencontainers->pop_all_but_last();
2779  
2780          } else {
2781              // It is really bad if library code throws exception when output buffering is on,
2782              // because the buffered text would be printed before our start of page.
2783              // NOTE: this hack might be behave unexpectedly in case output buffering is enabled in PHP.ini
2784              error_reporting(0); // disable notices from gzip compression, etc.
2785              while (ob_get_level() > 0) {
2786                  $buff = ob_get_clean();
2787                  if ($buff === false) {
2788                      break;
2789                  }
2790                  $obbuffer .= $buff;
2791              }
2792              error_reporting($CFG->debug);
2793  
2794              // Output not yet started.
2795              $protocol = (isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0');
2796              if (empty($_SERVER['HTTP_RANGE'])) {
2797                  @header($protocol . ' 404 Not Found');
2798              } else {
2799                  // Must stop byteserving attempts somehow,
2800                  // this is weird but Chrome PDF viewer can be stopped only with 407!
2801                  @header($protocol . ' 407 Proxy Authentication Required');
2802              }
2803  
2804              $this->page->set_context(null); // ugly hack - make sure page context is set to something, we do not want bogus warnings here
2805              $this->page->set_url('/'); // no url
2806              //$this->page->set_pagelayout('base'); //TODO: MDL-20676 blocks on error pages are weird, unfortunately it somehow detect the pagelayout from URL :-(
2807              $this->page->set_title(get_string('error'));
2808              $this->page->set_heading($this->page->course->fullname);
2809              $output .= $this->header();
2810          }
2811  
2812          $message = '<p class="errormessage">' . $message . '</p>'.
2813                  '<p class="errorcode"><a href="' . $moreinfourl . '">' .
2814                  get_string('moreinformation') . '</a></p>';
2815          if (empty($CFG->rolesactive)) {
2816              $message .= '<p class="errormessage">' . get_string('installproblem', 'error') . '</p>';
2817              //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.
2818          }
2819          $output .= $this->box($message, 'errorbox', null, array('data-rel' => 'fatalerror'));
2820  
2821          if ($CFG->debugdeveloper) {
2822              if (!empty($debuginfo)) {
2823                  $debuginfo = s($debuginfo); // removes all nasty JS
2824                  $debuginfo = str_replace("\n", '<br />', $debuginfo); // keep newlines
2825                  $output .= $this->notification('<strong>Debug info:</strong> '.$debuginfo, 'notifytiny');
2826              }
2827              if (!empty($backtrace)) {
2828                  $output .= $this->notification('<strong>Stack trace:</strong> '.format_backtrace($backtrace), 'notifytiny');
2829              }
2830              if ($obbuffer !== '' ) {
2831                  $output .= $this->notification('<strong>Output buffer:</strong> '.s($obbuffer), 'notifytiny');
2832              }
2833          }
2834  
2835          if (empty($CFG->rolesactive)) {
2836              // continue does not make much sense if moodle is not installed yet because error is most probably not recoverable
2837          } else if (!empty($link)) {
2838              $output .= $this->continue_button($link);
2839          }
2840  
2841          $output .= $this->footer();
2842  
2843          // Padding to encourage IE to display our error page, rather than its own.
2844          $output .= str_repeat(' ', 512);
2845  
2846          return $output;
2847      }
2848  
2849      /**
2850       * Output a notification (that is, a status message about something that has just happened).
2851       *
2852       * Note: \core\notification::add() may be more suitable for your usage.
2853       *
2854       * @param string $message The message to print out.
2855       * @param string $type    The type of notification. See constants on \core\output\notification.
2856       * @return string the HTML to output.
2857       */
2858      public function notification($message, $type = null) {
2859          $typemappings = [
2860              // Valid types.
2861              'success'           => \core\output\notification::NOTIFY_SUCCESS,
2862              'info'              => \core\output\notification::NOTIFY_INFO,
2863              'warning'           => \core\output\notification::NOTIFY_WARNING,
2864              'error'             => \core\output\notification::NOTIFY_ERROR,
2865  
2866              // Legacy types mapped to current types.
2867              'notifyproblem'     => \core\output\notification::NOTIFY_ERROR,
2868              'notifytiny'        => \core\output\notification::NOTIFY_ERROR,
2869              'notifyerror'       => \core\output\notification::NOTIFY_ERROR,
2870              'notifysuccess'     => \core\output\notification::NOTIFY_SUCCESS,
2871              'notifymessage'     => \core\output\notification::NOTIFY_INFO,
2872              'notifyredirect'    => \core\output\notification::NOTIFY_INFO,
2873              'redirectmessage'   => \core\output\notification::NOTIFY_INFO,
2874          ];
2875  
2876          $extraclasses = [];
2877  
2878          if ($type) {
2879              if (strpos($type, ' ') === false) {
2880                  // No spaces in the list of classes, therefore no need to loop over and determine the class.
2881                  if (isset($typemappings[$type])) {
2882                      $type = $typemappings[$type];
2883                  } else {
2884                      // The value provided did not match a known type. It must be an extra class.
2885                      $extraclasses = [$type];
2886                  }
2887              } else {
2888                  // Identify what type of notification this is.
2889                  $classarray = explode(' ', self::prepare_classes($type));
2890  
2891                  // Separate out the type of notification from the extra classes.
2892                  foreach ($classarray as $class) {
2893                      if (isset($typemappings[$class])) {
2894                          $type = $typemappings[$class];
2895                      } else {
2896                          $extraclasses[] = $class;
2897                      }
2898                  }
2899              }
2900          }
2901  
2902          $notification = new \core\output\notification($message, $type);
2903          if (count($extraclasses)) {
2904              $notification->set_extra_classes($extraclasses);
2905          }
2906  
2907          // Return the rendered template.
2908          return $this->render_from_template($notification->get_template_name(), $notification->export_for_template($this));
2909      }
2910  
2911      /**
2912       * Output a notification at a particular level - in this case, NOTIFY_PROBLEM.
2913       *
2914       * @param string $message the message to print out
2915       * @return string HTML fragment.
2916       * @deprecated since Moodle 3.1 MDL-30811 - please do not use this function any more.
2917       * @todo MDL-53113 This will be removed in Moodle 3.5.
2918       * @see \core\output\notification
2919       */
2920      public function notify_problem($message) {
2921          debugging(__FUNCTION__ . ' is deprecated.' .
2922              'Please use \core\notification::add, or \core\output\notification as required',
2923              DEBUG_DEVELOPER);
2924          $n = new \core\output\notification($message, \core\output\notification::NOTIFY_ERROR);
2925          return $this->render($n);
2926      }
2927  
2928      /**
2929       * Output a notification at a particular level - in this case, NOTIFY_SUCCESS.
2930       *
2931       * @param string $message the message to print out
2932       * @return string HTML fragment.
2933       * @deprecated since Moodle 3.1 MDL-30811 - please do not use this function any more.
2934       * @todo MDL-53113 This will be removed in Moodle 3.5.
2935       * @see \core\output\notification
2936       */
2937      public function notify_success($message) {
2938          debugging(__FUNCTION__ . ' is deprecated.' .
2939              'Please use \core\notification::add, or \core\output\notification as required',
2940              DEBUG_DEVELOPER);
2941          $n = new \core\output\notification($message, \core\output\notification::NOTIFY_SUCCESS);
2942          return $this->render($n);
2943      }
2944  
2945      /**
2946       * Output a notification at a particular level - in this case, NOTIFY_MESSAGE.
2947       *
2948       * @param string $message the message to print out
2949       * @return string HTML fragment.
2950       * @deprecated since Moodle 3.1 MDL-30811 - please do not use this function any more.
2951       * @todo MDL-53113 This will be removed in Moodle 3.5.
2952       * @see \core\output\notification
2953       */
2954      public function notify_message($message) {
2955          debugging(__FUNCTION__ . ' is deprecated.' .
2956              'Please use \core\notification::add, or \core\output\notification as required',
2957              DEBUG_DEVELOPER);
2958          $n = new \core\output\notification($message, \core\output\notification::NOTIFY_INFO);
2959          return $this->render($n);
2960      }
2961  
2962      /**
2963       * Output a notification at a particular level - in this case, NOTIFY_REDIRECT.
2964       *
2965       * @param string $message the message to print out
2966       * @return string HTML fragment.
2967       * @deprecated since Moodle 3.1 MDL-30811 - please do not use this function any more.
2968       * @todo MDL-53113 This will be removed in Moodle 3.5.
2969       * @see \core\output\notification
2970       */
2971      public function notify_redirect($message) {
2972          debugging(__FUNCTION__ . ' is deprecated.' .
2973              'Please use \core\notification::add, or \core\output\notification as required',
2974              DEBUG_DEVELOPER);
2975          $n = new \core\output\notification($message, \core\output\notification::NOTIFY_INFO);
2976          return $this->render($n);
2977      }
2978  
2979      /**
2980       * Render a notification (that is, a status message about something that has
2981       * just happened).
2982       *
2983       * @param \core\output\notification $notification the notification to print out
2984       * @return string the HTML to output.
2985       */
2986      protected function render_notification(\core\output\notification $notification) {
2987          return $this->render_from_template($notification->get_template_name(), $notification->export_for_template($this));
2988      }
2989  
2990      /**
2991       * Returns HTML to display a continue button that goes to a particular URL.
2992       *
2993       * @param string|moodle_url $url The url the button goes to.
2994       * @return string the HTML to output.
2995       */
2996      public function continue_button($url) {
2997          if (!($url instanceof moodle_url)) {
2998              $url = new moodle_url($url);
2999          }
3000          $button = new single_button($url, get_string('continue'), 'get');
3001          $button->class = 'continuebutton';
3002  
3003          return $this->render($button);
3004      }
3005  
3006      /**
3007       * Returns HTML to display a single paging bar to provide access to other pages  (usually in a search)
3008       *
3009       * Theme developers: DO NOT OVERRIDE! Please override function
3010       * {@link core_renderer::render_paging_bar()} instead.
3011       *
3012       * @param int $totalcount The total number of entries available to be paged through
3013       * @param int $page The page you are currently viewing
3014       * @param int $perpage The number of entries that should be shown per page
3015       * @param string|moodle_url $baseurl url of the current page, the $pagevar parameter is added
3016       * @param string $pagevar name of page parameter that holds the page number
3017       * @return string the HTML to output.
3018       */
3019      public function paging_bar($totalcount, $page, $perpage, $baseurl, $pagevar = 'page') {
3020          $pb = new paging_bar($totalcount, $page, $perpage, $baseurl, $pagevar);
3021          return $this->render($pb);
3022      }
3023  
3024      /**
3025       * Internal implementation of paging bar rendering.
3026       *
3027       * @param paging_bar $pagingbar
3028       * @return string
3029       */
3030      protected function render_paging_bar(paging_bar $pagingbar) {
3031          $output = '';
3032          $pagingbar = clone($pagingbar);
3033          $pagingbar->prepare($this, $this->page, $this->target);
3034  
3035          if ($pagingbar->totalcount > $pagingbar->perpage) {
3036              $output .= get_string('page') . ':';
3037  
3038              if (!empty($pagingbar->previouslink)) {
3039                  $output .= ' (' . $pagingbar->previouslink . ') ';
3040              }
3041  
3042              if (!empty($pagingbar->firstlink)) {
3043                  $output .= ' ' . $pagingbar->firstlink . ' ...';
3044              }
3045  
3046              foreach ($pagingbar->pagelinks as $link) {
3047                  $output .= "  $link";
3048              }
3049  
3050              if (!empty($pagingbar->lastlink)) {
3051                  $output .= ' ... ' . $pagingbar->lastlink . ' ';
3052              }
3053  
3054              if (!empty($pagingbar->nextlink)) {
3055                  $output .= '  (' . $pagingbar->nextlink . ')';
3056              }
3057          }
3058  
3059          return html_writer::tag('div', $output, array('class' => 'paging'));
3060      }
3061  
3062      /**
3063       * Output the place a skip link goes to.
3064       *
3065       * @param string $id The target name from the corresponding $PAGE->requires->skip_link_to($target) call.
3066       * @return string the HTML to output.
3067       */
3068      public function skip_link_target($id = null) {
3069          return html_writer::span('', '', array('id' => $id));
3070      }
3071  
3072      /**
3073       * Outputs a heading
3074       *
3075       * @param string $text The text of the heading
3076       * @param int $level The level of importance of the heading. Defaulting to 2
3077       * @param string $classes A space-separated list of CSS classes. Defaulting to null
3078       * @param string $id An optional ID
3079       * @return string the HTML to output.
3080       */
3081      public function heading($text, $level = 2, $classes = null, $id = null) {
3082          $level = (integer) $level;
3083          if ($level < 1 or $level > 6) {
3084              throw new coding_exception('Heading level must be an integer between 1 and 6.');
3085          }
3086          return html_writer::tag('h' . $level, $text, array('id' => $id, 'class' => renderer_base::prepare_classes($classes)));
3087      }
3088  
3089      /**
3090       * Outputs a box.
3091       *
3092       * @param string $contents The contents of the box
3093       * @param string $classes A space-separated list of CSS classes
3094       * @param string $id An optional ID
3095       * @param array $attributes An array of other attributes to give the box.
3096       * @return string the HTML to output.
3097       */
3098      public function box($contents, $classes = 'generalbox', $id = null, $attributes = array()) {
3099          return $this->box_start($classes, $id, $attributes) . $contents . $this->box_end();
3100      }
3101  
3102      /**
3103       * Outputs the opening section of a box.
3104       *
3105       * @param string $classes A space-separated list of CSS classes
3106       * @param string $id An optional ID
3107       * @param array $attributes An array of other attributes to give the box.
3108       * @return string the HTML to output.
3109       */
3110      public function box_start($classes = 'generalbox', $id = null, $attributes = array()) {
3111          $this->opencontainers->push('box', html_writer::end_tag('div'));
3112          $attributes['id'] = $id;
3113          $attributes['class'] = 'box ' . renderer_base::prepare_classes($classes);
3114          return html_writer::start_tag('div', $attributes);
3115      }
3116  
3117      /**
3118       * Outputs the closing section of a box.
3119       *
3120       * @return string the HTML to output.
3121       */
3122      public function box_end() {
3123          return $this->opencontainers->pop('box');
3124      }
3125  
3126      /**
3127       * Outputs a container.
3128       *
3129       * @param string $contents The contents of the box
3130       * @param string $classes A space-separated list of CSS classes
3131       * @param string $id An optional ID
3132       * @return string the HTML to output.
3133       */
3134      public function container($contents, $classes = null, $id = null) {
3135          return $this->container_start($classes, $id) . $contents . $this->container_end();
3136      }
3137  
3138      /**
3139       * Outputs the opening section of a container.
3140       *
3141       * @param string $classes A space-separated list of CSS classes
3142       * @param string $id An optional ID
3143       * @return string the HTML to output.
3144       */
3145      public function container_start($classes = null, $id = null) {
3146          $this->opencontainers->push('container', html_writer::end_tag('div'));
3147          return html_writer::start_tag('div', array('id' => $id,
3148                  'class' => renderer_base::prepare_classes($classes)));
3149      }
3150  
3151      /**
3152       * Outputs the closing section of a container.
3153       *
3154       * @return string the HTML to output.
3155       */
3156      public function container_end() {
3157          return $this->opencontainers->pop('container');
3158      }
3159  
3160      /**
3161       * Make nested HTML lists out of the items
3162       *
3163       * The resulting list will look something like this:
3164       *
3165       * <pre>
3166       * <<ul>>
3167       * <<li>><div class='tree_item parent'>(item contents)</div>
3168       *      <<ul>
3169       *      <<li>><div class='tree_item'>(item contents)</div><</li>>
3170       *      <</ul>>
3171       * <</li>>
3172       * <</ul>>
3173       * </pre>
3174       *
3175       * @param array $items
3176       * @param array $attrs html attributes passed to the top ofs the list
3177       * @return string HTML
3178       */
3179      public function tree_block_contents($items, $attrs = array()) {
3180          // exit if empty, we don't want an empty ul element
3181          if (empty($items)) {
3182              return '';
3183          }
3184          // array of nested li elements
3185          $lis = array();
3186          foreach ($items as $item) {
3187              // this applies to the li item which contains all child lists too
3188              $content = $item->content($this);
3189              $liclasses = array($item->get_css_type());
3190              if (!$item->forceopen || (!$item->forceopen && $item->collapse) || ($item->children->count()==0  && $item->nodetype==navigation_node::NODETYPE_BRANCH)) {
3191                  $liclasses[] = 'collapsed';
3192              }
3193              if ($item->isactive === true) {
3194                  $liclasses[] = 'current_branch';
3195              }
3196              $liattr = array('class'=>join(' ',$liclasses));
3197              // class attribute on the div item which only contains the item content
3198              $divclasses = array('tree_item');
3199              if ($item->children->count()>0  || $item->nodetype==navigation_node::NODETYPE_BRANCH) {
3200                  $divclasses[] = 'branch';
3201              } else {
3202                  $divclasses[] = 'leaf';
3203              }
3204              if (!empty($item->classes) && count($item->classes)>0) {
3205                  $divclasses[] = join(' ', $item->classes);
3206              }
3207              $divattr = array('class'=>join(' ', $divclasses));
3208              if (!empty($item->id)) {
3209                  $divattr['id'] = $item->id;
3210              }
3211              $content = html_writer::tag('p', $content, $divattr) . $this->tree_block_contents($item->children);
3212              if (!empty($item->preceedwithhr) && $item->preceedwithhr===true) {
3213                  $content = html_writer::empty_tag('hr') . $content;
3214              }
3215              $content = html_writer::tag('li', $content, $liattr);
3216              $lis[] = $content;
3217          }
3218          return html_writer::tag('ul', implode("\n", $lis), $attrs);
3219      }
3220  
3221      /**
3222       * Returns a search box.
3223       *
3224       * @param  string $id     The search box wrapper div id, defaults to an autogenerated one.
3225       * @return string         HTML with the search form hidden by default.
3226       */
3227      public function search_box($id = false) {
3228          global $CFG;
3229  
3230          // Accessing $CFG directly as using \core_search::is_global_search_enabled would
3231          // result in an extra included file for each site, even the ones where global search
3232          // is disabled.
3233          if (empty($CFG->enableglobalsearch) || !has_capability('moodle/search:query', context_system::instance())) {
3234              return '';
3235          }
3236  
3237          if ($id == false) {
3238              $id = uniqid();
3239          } else {
3240              // Needs to be cleaned, we use it for the input id.
3241              $id = clean_param($id, PARAM_ALPHANUMEXT);
3242          }
3243  
3244          // JS to animate the form.
3245          $this->page->requires->js_call_amd('core/search-input', 'init', array($id));
3246  
3247          $searchicon = html_writer::tag('div', $this->pix_icon('a/search', get_string('search', 'search'), 'moodle'),
3248              array('role' => 'button', 'tabindex' => 0));
3249          $formattrs = array('class' => 'search-input-form', 'action' => $CFG->wwwroot . '/search/index.php');
3250          $inputattrs = array('type' => 'text', 'name' => 'q', 'placeholder' => get_string('search', 'search'),
3251              'size' => 13, 'tabindex' => -1, 'id' => 'id_q_' . $id);
3252  
3253          $contents = html_writer::tag('label', get_string('enteryoursearchquery', 'search'),
3254              array('for' => 'id_q_' . $id, 'class' => 'accesshide')) . html_writer::tag('input', '', $inputattrs);
3255          $searchinput = html_writer::tag('form', $contents, $formattrs);
3256  
3257          return html_writer::tag('div', $searchicon . $searchinput, array('class' => 'search-input-wrapper', 'id' => $id));
3258      }
3259  
3260      /**
3261       * Construct a user menu, returning HTML that can be echoed out by a
3262       * layout file.
3263       *
3264       * @param stdClass $user A user object, usually $USER.
3265       * @param bool $withlinks true if a dropdown should be built.
3266       * @return string HTML fragment.
3267       */
3268      public function user_menu($user = null, $withlinks = null) {
3269          global $USER, $CFG;
3270          require_once($CFG->dirroot . '/user/lib.php');
3271  
3272          if (is_null($user)) {
3273              $user = $USER;
3274          }
3275  
3276          // Note: this behaviour is intended to match that of core_renderer::login_info,
3277          // but should not be considered to be good practice; layout options are
3278          // intended to be theme-specific. Please don't copy this snippet anywhere else.
3279          if (is_null($withlinks)) {
3280              $withlinks = empty($this->page->layout_options['nologinlinks']);
3281          }
3282  
3283          // Add a class for when $withlinks is false.
3284          $usermenuclasses = 'usermenu';
3285          if (!$withlinks) {
3286              $usermenuclasses .= ' withoutlinks';
3287          }
3288  
3289          $returnstr = "";
3290  
3291          // If during initial install, return the empty return string.
3292          if (during_initial_install()) {
3293              return $returnstr;
3294          }
3295  
3296          $loginpage = $this->is_login_page();
3297          $loginurl = get_login_url();
3298          // If not logged in, show the typical not-logged-in string.
3299          if (!isloggedin()) {
3300              $returnstr = get_string('loggedinnot', 'moodle');
3301              if (!$loginpage) {
3302                  $returnstr .= " (<a href=\"$loginurl\">" . get_string('login') . '</a>)';
3303              }
3304              return html_writer::div(
3305                  html_writer::span(
3306                      $returnstr,
3307                      'login'
3308                  ),
3309                  $usermenuclasses
3310              );
3311  
3312          }
3313  
3314          // If logged in as a guest user, show a string to that effect.
3315          if (isguestuser()) {
3316              $returnstr = get_string('loggedinasguest');
3317              if (!$loginpage && $withlinks) {
3318                  $returnstr .= " (<a href=\"$loginurl\">".get_string('login').'</a>)';
3319              }
3320  
3321              return html_writer::div(
3322                  html_writer::span(
3323                      $returnstr,
3324                      'login'
3325                  ),
3326                  $usermenuclasses
3327              );
3328          }
3329  
3330          // Get some navigation opts.
3331          $opts = user_get_user_navigation_info($user, $this->page);
3332  
3333          $avatarclasses = "avatars";
3334          $avatarcontents = html_writer::span($opts->metadata['useravatar'], 'avatar current');
3335          $usertextcontents = $opts->metadata['userfullname'];
3336  
3337          // Other user.
3338          if (!empty($opts->metadata['asotheruser'])) {
3339              $avatarcontents .= html_writer::span(
3340                  $opts->metadata['realuseravatar'],
3341                  'avatar realuser'
3342              );
3343              $usertextcontents = $opts->metadata['realuserfullname'];
3344              $usertextcontents .= html_writer::tag(
3345                  'span',
3346                  get_string(
3347                      'loggedinas',
3348                      'moodle',
3349                      html_writer::span(
3350                          $opts->metadata['userfullname'],
3351                          'value'
3352                      )
3353                  ),
3354                  array('class' => 'meta viewingas')
3355              );
3356          }
3357  
3358          // Role.
3359          if (!empty($opts->metadata['asotherrole'])) {
3360              $role = core_text::strtolower(preg_replace('#[ ]+#', '-', trim($opts->metadata['rolename'])));
3361              $usertextcontents .= html_writer::span(
3362                  $opts->metadata['rolename'],
3363                  'meta role role-' . $role
3364              );
3365          }
3366  
3367          // User login failures.
3368          if (!empty($opts->metadata['userloginfail'])) {
3369              $usertextcontents .= html_writer::span(
3370                  $opts->metadata['userloginfail'],
3371                  'meta loginfailures'
3372              );
3373          }
3374  
3375          // MNet.
3376          if (!empty($opts->metadata['asmnetuser'])) {
3377              $mnet = strtolower(preg_replace('#[ ]+#', '-', trim($opts->metadata['mnetidprovidername'])));
3378              $usertextcontents .= html_writer::span(
3379                  $opts->metadata['mnetidprovidername'],
3380                  'meta mnet mnet-' . $mnet
3381              );
3382          }
3383  
3384          $returnstr .= html_writer::span(
3385              html_writer::span($usertextcontents, 'usertext') .
3386              html_writer::span($avatarcontents, $avatarclasses),
3387              'userbutton'
3388          );
3389  
3390          // Create a divider (well, a filler).
3391          $divider = new action_menu_filler();
3392          $divider->primary = false;
3393  
3394          $am = new action_menu();
3395          $am->initialise_js($this->page);
3396          $am->set_menu_trigger(
3397              $returnstr
3398          );
3399          $am->set_alignment(action_menu::TR, action_menu::BR);
3400          $am->set_nowrap_on_items();
3401          if ($withlinks) {
3402              $navitemcount = count($opts->navitems);
3403              $idx = 0;
3404              foreach ($opts->navitems as $key => $value) {
3405  
3406                  switch ($value->itemtype) {
3407                      case 'divider':
3408                          // If the nav item is a divider, add one and skip link processing.
3409                          $am->add($divider);
3410                          break;
3411  
3412                      case 'invalid':
3413                          // Silently skip invalid entries (should we post a notification?).
3414                          break;
3415  
3416                      case 'link':
3417                          // Process this as a link item.
3418                          $pix = null;
3419                          if (isset($value->pix) && !empty($value->pix)) {
3420                              $pix = new pix_icon($value->pix, $value->title, null, array('class' => 'iconsmall'));
3421                          } else if (isset($value->imgsrc) && !empty($value->imgsrc)) {
3422                              $value->title = html_writer::img(
3423                                  $value->imgsrc,
3424                                  $value->title,
3425                                  array('class' => 'iconsmall')
3426                              ) . $value->title;
3427                          }
3428  
3429                          $al = new action_menu_link_secondary(
3430                              $value->url,
3431                              $pix,
3432                              $value->title,
3433                              array('class' => 'icon')
3434                          );
3435                          if (!empty($value->titleidentifier)) {
3436                              $al->attributes['data-title'] = $value->titleidentifier;
3437                          }
3438                          $am->add($al);
3439                          break;
3440                  }
3441  
3442                  $idx++;
3443  
3444                  // Add dividers after the first item and before the last item.
3445                  if ($idx == 1 || $idx == $navitemcount - 1) {
3446                      $am->add($divider);
3447                  }
3448              }
3449          }
3450  
3451          return html_writer::div(
3452              $this->render($am),
3453              $usermenuclasses
3454          );
3455      }
3456  
3457      /**
3458       * Return the navbar content so that it can be echoed out by the layout
3459       *
3460       * @return string XHTML navbar
3461       */
3462      public function navbar() {
3463          $items = $this->page->navbar->get_items();
3464          $itemcount = count($items);
3465          if ($itemcount === 0) {
3466              return '';
3467          }
3468  
3469          $htmlblocks = array();
3470          // Iterate the navarray and display each node
3471          $separator = get_separator();
3472          for ($i=0;$i < $itemcount;$i++) {
3473              $item = $items[$i];
3474              $item->hideicon = true;
3475              if ($i===0) {
3476                  $content = html_writer::tag('li', $this->render($item));
3477              } else {
3478                  $content = html_writer::tag('li', $separator.$this->render($item));
3479              }
3480              $htmlblocks[] = $content;
3481          }
3482  
3483          //accessibility: heading for navbar list  (MDL-20446)
3484          $navbarcontent = html_writer::tag('span', get_string('pagepath'),
3485                  array('class' => 'accesshide', 'id' => 'navbar-label'));
3486          $navbarcontent .= html_writer::tag('nav',
3487                  html_writer::tag('ul', join('', $htmlblocks)),
3488                  array('aria-labelledby' => 'navbar-label'));
3489          // XHTML
3490          return $navbarcontent;
3491      }
3492  
3493      /**
3494       * Renders a breadcrumb navigation node object.
3495       *
3496       * @param breadcrumb_navigation_node $item The navigation node to render.
3497       * @return string HTML fragment
3498       */
3499      protected function render_breadcrumb_navigation_node(breadcrumb_navigation_node $item) {
3500  
3501          if ($item->action instanceof moodle_url) {
3502              $content = $item->get_content();
3503              $title = $item->get_title();
3504              $attributes = array();
3505              $attributes['itemprop'] = 'url';
3506              if ($title !== '') {
3507                  $attributes['title'] = $title;
3508              }
3509              if ($item->hidden) {
3510                  $attributes['class'] = 'dimmed_text';
3511              }
3512              $content = html_writer::tag('span', $content, array('itemprop' => 'title'));
3513              $content = html_writer::link($item->action, $content, $attributes);
3514  
3515              $attributes = array();
3516              $attributes['itemscope'] = '';
3517              $attributes['itemtype'] = 'http://data-vocabulary.org/Breadcrumb';
3518              $content = html_writer::tag('span', $content, $attributes);
3519  
3520          } else {
3521              $content = $this->render_navigation_node($item);
3522          }
3523          return $content;
3524      }
3525  
3526      /**
3527       * Renders a navigation node object.
3528       *
3529       * @param navigation_node $item The navigation node to render.
3530       * @return string HTML fragment
3531       */
3532      protected function render_navigation_node(navigation_node $item) {
3533          $content = $item->get_content();
3534          $title = $item->get_title();
3535          if ($item->icon instanceof renderable && !$item->hideicon) {
3536              $icon = $this->render($item->icon);
3537              $content = $icon.$content; // use CSS for spacing of icons
3538          }
3539          if ($item->helpbutton !== null) {
3540              $content = trim($item->helpbutton).html_writer::tag('span', $content, array('class'=>'clearhelpbutton', 'tabindex'=>'0'));
3541          }
3542          if ($content === '') {
3543              return '';
3544          }
3545          if ($item->action instanceof action_link) {
3546              $link = $item->action;
3547              if ($item->hidden) {
3548                  $link->add_class('dimmed');
3549              }
3550              if (!empty($content)) {
3551                  // Providing there is content we will use that for the link content.
3552                  $link->text = $content;
3553              }
3554              $content = $this->render($link);
3555          } else if ($item->action instanceof moodle_url) {
3556              $attributes = array();
3557              if ($title !== '') {
3558                  $attributes['title'] = $title;
3559              }
3560              if ($item->hidden) {
3561                  $attributes['class'] = 'dimmed_text';
3562              }
3563              $content = html_writer::link($item->action, $content, $attributes);
3564  
3565          } else if (is_string($item->action) || empty($item->action)) {
3566              $attributes = array('tabindex'=>'0'); //add tab support to span but still maintain character stream sequence.
3567              if ($title !== '') {
3568                  $attributes['title'] = $title;
3569              }
3570              if ($item->hidden) {
3571                  $attributes['class'] = 'dimmed_text';
3572              }
3573              $content = html_writer::tag('span', $content, $attributes);
3574          }
3575          return $content;
3576      }
3577  
3578      /**
3579       * Accessibility: Right arrow-like character is
3580       * used in the breadcrumb trail, course navigation menu
3581       * (previous/next activity), calendar, and search forum block.
3582       * If the theme does not set characters, appropriate defaults
3583       * are set automatically. Please DO NOT
3584       * use &lt; &gt; &raquo; - these are confusing for blind users.
3585       *
3586       * @return string
3587       */
3588      public function rarrow() {
3589          return $this->page->theme->rarrow;
3590      }
3591  
3592      /**
3593       * Accessibility: Left arrow-like character is
3594       * used in the breadcrumb trail, course navigation menu
3595       * (previous/next activity), calendar, and search forum block.
3596       * If the theme does not set characters, appropriate defaults
3597       * are set automatically. Please DO NOT
3598       * use &lt; &gt; &raquo; - these are confusing for blind users.
3599       *
3600       * @return string
3601       */
3602      public function larrow() {
3603          return $this->page->theme->larrow;
3604      }
3605  
3606      /**
3607       * Accessibility: Up arrow-like character is used in
3608       * the book heirarchical navigation.
3609       * If the theme does not set characters, appropriate defaults
3610       * are set automatically. Please DO NOT
3611       * use ^ - this is confusing for blind users.
3612       *
3613       * @return string
3614       */
3615      public function uarrow() {
3616          return $this->page->theme->uarrow;
3617      }
3618  
3619      /**
3620       * Accessibility: Down arrow-like character.
3621       * If the theme does not set characters, appropriate defaults
3622       * are set automatically.
3623       *
3624       * @return string
3625       */
3626      public function darrow() {
3627          return $this->page->theme->darrow;
3628      }
3629  
3630      /**
3631       * Returns the custom menu if one has been set
3632       *
3633       * A custom menu can be configured by browsing to
3634       *    Settings: Administration > Appearance > Themes > Theme settings
3635       * and then configuring the custommenu config setting as described.
3636       *
3637       * Theme developers: DO NOT OVERRIDE! Please override function
3638       * {@link core_renderer::render_custom_menu()} instead.
3639       *
3640       * @param string $custommenuitems - custom menuitems set by theme instead of global theme settings
3641       * @return string
3642       */
3643      public function custom_menu($custommenuitems = '') {
3644          global $CFG;
3645          if (empty($custommenuitems) && !empty($CFG->custommenuitems)) {
3646              $custommenuitems = $CFG->custommenuitems;
3647          }
3648          if (empty($custommenuitems)) {
3649              return '';
3650          }
3651          $custommenu = new custom_menu($custommenuitems, current_language());
3652          return $this->render($custommenu);
3653      }
3654  
3655      /**
3656       * Renders a custom menu object (located in outputcomponents.php)
3657       *
3658       * The custom menu this method produces makes use of the YUI3 menunav widget
3659       * and requires very specific html elements and classes.
3660       *
3661       * @staticvar int $menucount
3662       * @param custom_menu $menu
3663       * @return string
3664       */
3665      protected function render_custom_menu(custom_menu $menu) {
3666          static $menucount = 0;
3667          // If the menu has no children return an empty string
3668          if (!$menu->has_children()) {
3669              return '';
3670          }
3671          // Increment the menu count. This is used for ID's that get worked with
3672          // in JavaScript as is essential
3673          $menucount++;
3674          // Initialise this custom menu (the custom menu object is contained in javascript-static
3675          $jscode = js_writer::function_call_with_Y('M.core_custom_menu.init', array('custom_menu_'.$menucount));
3676          $jscode = "(function(){{$jscode}})";
3677          $this->page->requires->yui_module('node-menunav', $jscode);
3678          // Build the root nodes as required by YUI
3679          $content = html_writer::start_tag('div', array('id'=>'custom_menu_'.$menucount, 'class'=>'yui3-menu yui3-menu-horizontal javascript-disabled custom-menu'));
3680          $content .= html_writer::start_tag('div', array('class'=>'yui3-menu-content'));
3681          $content .= html_writer::start_tag('ul');
3682          // Render each child
3683          foreach ($menu->get_children() as $item) {
3684              $content .= $this->render_custom_menu_item($item);
3685          }
3686          // Close the open tags
3687          $content .= html_writer::end_tag('ul');
3688          $content .= html_writer::end_tag('div');
3689          $content .= html_writer::end_tag('div');
3690          // Return the custom menu
3691          return $content;
3692      }
3693  
3694      /**
3695       * Renders a custom menu node as part of a submenu
3696       *
3697       * The custom menu this method produces makes use of the YUI3 menunav widget
3698       * and requires very specific html elements and classes.
3699       *
3700       * @see core:renderer::render_custom_menu()
3701       *
3702       * @staticvar int $submenucount
3703       * @param custom_menu_item $menunode
3704       * @return string
3705       */
3706      protected function render_custom_menu_item(custom_menu_item $menunode) {
3707          // Required to ensure we get unique trackable id's
3708          static $submenucount = 0;
3709          if ($menunode->has_children()) {
3710              // If the child has menus render it as a sub menu
3711              $submenucount++;
3712              $content = html_writer::start_tag('li');
3713              if ($menunode->get_url() !== null) {
3714                  $url = $menunode->get_url();
3715              } else {
3716                  $url = '#cm_submenu_'.$submenucount;
3717              }
3718              $content .= html_writer::link($url, $menunode->get_text(), array('class'=>'yui3-menu-label', 'title'=>$menunode->get_title()));
3719              $content .= html_writer::start_tag('div', array('id'=>'cm_submenu_'.$submenucount, 'class'=>'yui3-menu custom_menu_submenu'));
3720              $content .= html_writer::start_tag('div', array('class'=>'yui3-menu-content'));
3721              $content .= html_writer::start_tag('ul');
3722              foreach ($menunode->get_children() as $menunode) {
3723                  $content .= $this->render_custom_menu_item($menunode);
3724              }
3725              $content .= html_writer::end_tag('ul');
3726              $content .= html_writer::end_tag('div');
3727              $content .= html_writer::end_tag('div');
3728              $content .= html_writer::end_tag('li');
3729          } else {
3730              // The node doesn't have children so produce a final menuitem.
3731              // Also, if the node's text matches '####', add a class so we can treat it as a divider.
3732              $content = '';
3733              if (preg_match("/^#+$/", $menunode->get_text())) {
3734  
3735                  // This is a divider.
3736                  $content = html_writer::start_tag('li', array('class' => 'yui3-menuitem divider'));
3737              } else {
3738                  $content = html_writer::start_tag(
3739                      'li',
3740                      array(
3741                          'class' => 'yui3-menuitem'
3742                      )
3743                  );
3744                  if ($menunode->get_url() !== null) {
3745                      $url = $menunode->get_url();
3746                  } else {
3747                      $url = '#';
3748                  }
3749                  $content .= html_writer::link(
3750                      $url,
3751                      $menunode->get_text(),
3752                      array('class' => 'yui3-menuitem-content', 'title' => $menunode->get_title())
3753                  );
3754              }
3755              $content .= html_writer::end_tag('li');
3756          }
3757          // Return the sub menu
3758          return $content;
3759      }
3760  
3761      /**
3762       * Renders theme links for switching between default and other themes.
3763       *
3764       * @return string
3765       */
3766      protected function theme_switch_links() {
3767  
3768          $actualdevice = core_useragent::get_device_type();
3769          $currentdevice = $this->page->devicetypeinuse;
3770          $switched = ($actualdevice != $currentdevice);
3771  
3772          if (!$switched && $currentdevice == 'default' && $actualdevice == 'default') {
3773              // The user is using the a default device and hasn't switched so don't shown the switch
3774              // device links.
3775              return '';
3776          }
3777  
3778          if ($switched) {
3779              $linktext = get_string('switchdevicerecommended');
3780              $devicetype = $actualdevice;
3781          } else {
3782              $linktext = get_string('switchdevicedefault');
3783              $devicetype = 'default';
3784          }
3785          $linkurl = new moodle_url('/theme/switchdevice.php', array('url' => $this->page->url, 'device' => $devicetype, 'sesskey' => sesskey()));
3786  
3787          $content  = html_writer::start_tag('div', array('id' => 'theme_switch_link'));
3788          $content .= html_writer::link($linkurl, $linktext, array('rel' => 'nofollow'));
3789          $content .= html_writer::end_tag('div');
3790  
3791          return $content;
3792      }
3793  
3794      /**
3795       * Renders tabs
3796       *
3797       * This function replaces print_tabs() used before Moodle 2.5 but with slightly different arguments
3798       *
3799       * Theme developers: In order to change how tabs are displayed please override functions
3800       * {@link core_renderer::render_tabtree()} and/or {@link core_renderer::render_tabobject()}
3801       *
3802       * @param array $tabs array of tabs, each of them may have it's own ->subtree
3803       * @param string|null $selected which tab to mark as selected, all parent tabs will
3804       *     automatically be marked as activated
3805       * @param array|string|null $inactive list of ids of inactive tabs, regardless of
3806       *     their level. Note that you can as weel specify tabobject::$inactive for separate instances
3807       * @return string
3808       */
3809      public final function tabtree($tabs, $selected = null, $inactive = null) {
3810          return $this->render(new tabtree($tabs, $selected, $inactive));
3811      }
3812  
3813      /**
3814       * Renders tabtree
3815       *
3816       * @param tabtree $tabtree
3817       * @return string
3818       */
3819      protected function render_tabtree(tabtree $tabtree) {
3820          if (empty($tabtree->subtree)) {
3821              return '';
3822          }
3823          $str = '';
3824          $str .= html_writer::start_tag('div', array('class' => 'tabtree'));
3825          $str .= $this->render_tabobject($tabtree);
3826          $str .= html_writer::end_tag('div').
3827                  html_writer::tag('div', ' ', array('class' => 'clearer'));
3828          return $str;
3829      }
3830  
3831      /**
3832       * Renders tabobject (part of tabtree)
3833       *
3834       * This function is called from {@link core_renderer::render_tabtree()}
3835       * and also it calls itself when printing the $tabobject subtree recursively.
3836       *
3837       * Property $tabobject->level indicates the number of row of tabs.
3838       *
3839       * @param tabobject $tabobject
3840       * @return string HTML fragment
3841       */
3842      protected function render_tabobject(tabobject $tabobject) {
3843          $str = '';
3844  
3845          // Print name of the current tab.
3846          if ($tabobject instanceof tabtree) {
3847              // No name for tabtree root.
3848          } else if ($tabobject->inactive || $tabobject->activated || ($tabobject->selected && !$tabobject->linkedwhenselected)) {
3849              // Tab name without a link. The <a> tag is used for styling.
3850              $str .= html_writer::tag('a', html_writer::span($tabobject->text), array('class' => 'nolink moodle-has-zindex'));
3851          } else {
3852              // Tab name with a link.
3853              if (!($tabobject->link instanceof moodle_url)) {
3854                  // backward compartibility when link was passed as quoted string
3855                  $str .= "<a href=\"$tabobject->link\" title=\"$tabobject->title\"><span>$tabobject->text</span></a>";
3856              } else {
3857                  $str .= html_writer::link($tabobject->link, html_writer::span($tabobject->text), array('title' => $tabobject->title));
3858              }
3859          }
3860  
3861          if (empty($tabobject->subtree)) {
3862              if ($tabobject->selected) {
3863                  $str .= html_writer::tag('div', '&nbsp;', array('class' => 'tabrow'. ($tabobject->level + 1). ' empty'));
3864              }
3865              return $str;
3866          }
3867  
3868          // Print subtree.
3869          if ($tabobject->level == 0 || $tabobject->selected || $tabobject->activated) {
3870              $str .= html_writer::start_tag('ul', array('class' => 'tabrow'. $tabobject->level));
3871              $cnt = 0;
3872              foreach ($tabobject->subtree as $tab) {
3873                  $liclass = '';
3874                  if (!$cnt) {
3875                      $liclass .= ' first';
3876                  }
3877                  if ($cnt == count($tabobject->subtree) - 1) {
3878                      $liclass .= ' last';
3879                  }
3880                  if ((empty($tab->subtree)) && (!empty($tab->selected))) {
3881                      $liclass .= ' onerow';
3882                  }
3883  
3884                  if ($tab->selected) {
3885                      $liclass .= ' here selected';
3886                  } else if ($tab->activated) {
3887                      $liclass .= ' here active';
3888                  }
3889  
3890                  // This will recursively call function render_tabobject() for each item in subtree.
3891                  $str .= html_writer::tag('li', $this->render($tab), array('class' => trim($liclass)));
3892                  $cnt++;
3893              }
3894              $str .= html_writer::end_tag('ul');
3895          }
3896  
3897          return $str;
3898      }
3899  
3900      /**
3901       * Get the HTML for blocks in the given region.
3902       *
3903       * @since Moodle 2.5.1 2.6
3904       * @param string $region The region to get HTML for.
3905       * @return string HTML.
3906       */
3907      public function blocks($region, $classes = array(), $tag = 'aside') {
3908          $displayregion = $this->page->apply_theme_region_manipulations($region);
3909          $classes = (array)$classes;
3910          $classes[] = 'block-region';
3911          $attributes = array(
3912              'id' => 'block-region-'.preg_replace('#[^a-zA-Z0-9_\-]+#', '-', $displayregion),
3913              'class' => join(' ', $classes),
3914              'data-blockregion' => $displayregion,
3915              'data-droptarget' => '1'
3916          );
3917          if ($this->page->blocks->region_has_content($displayregion, $this)) {
3918              $content = $this->blocks_for_region($displayregion);
3919          } else {
3920              $content = '';
3921          }
3922          return html_writer::tag($tag, $content, $attributes);
3923      }
3924  
3925      /**
3926       * Renders a custom block region.
3927       *
3928       * Use this method if you want to add an additional block region to the content of the page.
3929       * Please note this should only be used in special situations.
3930       * We want to leave the theme is control where ever possible!
3931       *
3932       * This method must use the same method that the theme uses within its layout file.
3933       * As such it asks the theme what method it is using.
3934       * It can be one of two values, blocks or blocks_for_region (deprecated).
3935       *
3936       * @param string $regionname The name of the custom region to add.
3937       * @return string HTML for the block region.
3938       */
3939      public function custom_block_region($regionname) {
3940          if ($this->page->theme->get_block_render_method() === 'blocks') {
3941              return $this->blocks($regionname);
3942          } else {
3943              return $this->blocks_for_region($regionname);
3944          }
3945      }
3946  
3947      /**
3948       * Returns the CSS classes to apply to the body tag.
3949       *
3950       * @since Moodle 2.5.1 2.6
3951       * @param array $additionalclasses Any additional classes to apply.
3952       * @return string
3953       */
3954      public function body_css_classes(array $additionalclasses = array()) {
3955          // Add a class for each block region on the page.
3956          // We use the block manager here because the theme object makes get_string calls.
3957          $usedregions = array();
3958          foreach ($this->page->blocks->get_regions() as $region) {
3959              $additionalclasses[] = 'has-region-'.$region;
3960              if ($this->page->blocks->region_has_content($region, $this)) {
3961                  $additionalclasses[] = 'used-region-'.$region;
3962                  $usedregions[] = $region;
3963              } else {
3964                  $additionalclasses[] = 'empty-region-'.$region;
3965              }
3966              if ($this->page->blocks->region_completely_docked($region, $this)) {
3967                  $additionalclasses[] = 'docked-region-'.$region;
3968              }
3969          }
3970          if (!$usedregions) {
3971              // No regions means there is only content, add 'content-only' class.
3972              $additionalclasses[] = 'content-only';
3973          } else if (count($usedregions) === 1) {
3974              // Add the -only class for the only used region.
3975              $region = array_shift($usedregions);
3976              $additionalclasses[] = $region . '-only';
3977          }
3978          foreach ($this->page->layout_options as $option => $value) {
3979              if ($value) {
3980                  $additionalclasses[] = 'layout-option-'.$option;
3981              }
3982          }
3983          $css = $this->page->bodyclasses .' '. join(' ', $additionalclasses);
3984          return $css;
3985      }
3986  
3987      /**
3988       * The ID attribute to apply to the body tag.
3989       *
3990       * @since Moodle 2.5.1 2.6
3991       * @return string
3992       */
3993      public function body_id() {
3994          return $this->page->bodyid;
3995      }
3996  
3997      /**
3998       * Returns HTML attributes to use within the body tag. This includes an ID and classes.
3999       *
4000       * @since Moodle 2.5.1 2.6
4001       * @param string|array $additionalclasses Any additional classes to give the body tag,
4002       * @return string
4003       */
4004      public function body_attributes($additionalclasses = array()) {
4005          if (!is_array($additionalclasses)) {
4006              $additionalclasses = explode(' ', $additionalclasses);
4007          }
4008          return ' id="'. $this->body_id().'" class="'.$this->body_css_classes($additionalclasses).'"';
4009      }
4010  
4011      /**
4012       * Gets HTML for the page heading.
4013       *
4014       * @since Moodle 2.5.1 2.6
4015       * @param string $tag The tag to encase the heading in. h1 by default.
4016       * @return string HTML.
4017       */
4018      public function page_heading($tag = 'h1') {
4019          return html_writer::tag($tag, $this->page->heading);
4020      }
4021  
4022      /**
4023       * Gets the HTML for the page heading button.
4024       *
4025       * @since Moodle 2.5.1 2.6
4026       * @return string HTML.
4027       */
4028      public function page_heading_button() {
4029          return $this->page->button;
4030      }
4031  
4032      /**
4033       * Returns the Moodle docs link to use for this page.
4034       *
4035       * @since Moodle 2.5.1 2.6
4036       * @param string $text
4037       * @return string
4038       */
4039      public function page_doc_link($text = null) {
4040          if ($text === null) {
4041              $text = get_string('moodledocslink');
4042          }
4043          $path = page_get_doc_link_path($this->page);
4044          if (!$path) {
4045              return '';
4046          }
4047          return $this->doc_link($path, $text);
4048      }
4049  
4050      /**
4051       * Returns the page heading menu.
4052       *
4053       * @since Moodle 2.5.1 2.6
4054       * @return string HTML.
4055       */
4056      public function page_heading_menu() {
4057          return $this->page->headingmenu;
4058      }
4059  
4060      /**
4061       * Returns the title to use on the page.
4062       *
4063       * @since Moodle 2.5.1 2.6
4064       * @return string
4065       */
4066      public function page_title() {
4067          return $this->page->title;
4068      }
4069  
4070      /**
4071       * Returns the URL for the favicon.
4072       *
4073       * @since Moodle 2.5.1 2.6
4074       * @return string The favicon URL
4075       */
4076      public function favicon() {
4077          return $this->pix_url('favicon', 'theme');
4078      }
4079  
4080      /**
4081       * Renders preferences groups.
4082       *
4083       * @param  preferences_groups $renderable The renderable
4084       * @return string The output.
4085       */
4086      public function render_preferences_groups(preferences_groups $renderable) {
4087          $html = '';
4088          $html .= html_writer::start_div('row-fluid');
4089          $html .= html_writer::start_tag('div', array('class' => 'span12 preferences-groups'));
4090          $i = 0;
4091          $open = false;
4092          foreach ($renderable->groups as $group) {
4093              if ($i == 0 || $i % 3 == 0) {
4094                  if ($open) {
4095                      $html .= html_writer::end_tag('div');
4096                  }
4097                  $html .= html_writer::start_tag('div', array('class' => 'row-fluid'));
4098                  $open = true;
4099              }
4100              $html .= $this->render($group);
4101              $i++;
4102          }
4103  
4104          $html .= html_writer::end_tag('div');
4105  
4106          $html .= html_writer::end_tag('ul');
4107          $html .= html_writer::end_tag('div');
4108          $html .= html_writer::end_div();
4109          return $html;
4110      }
4111  
4112      /**
4113       * Renders preferences group.
4114       *
4115       * @param  preferences_group $renderable The renderable
4116       * @return string The output.
4117       */
4118      public function render_preferences_group(preferences_group $renderable) {
4119          $html = '';
4120          $html .= html_writer::start_tag('div', array('class' => 'span4 preferences-group'));
4121          $html .= $this->heading($renderable->title, 3);
4122          $html .= html_writer::start_tag('ul');
4123          foreach ($renderable->nodes as $node) {
4124              if ($node->has_children()) {
4125                  debugging('Preferences nodes do not support children', DEBUG_DEVELOPER);
4126              }
4127              $html .= html_writer::tag('li', $this->render($node));
4128          }
4129          $html .= html_writer::end_tag('ul');
4130          $html .= html_writer::end_tag('div');
4131          return $html;
4132      }
4133  
4134      /**
4135       * Returns the header bar.
4136       *
4137       * @since Moodle 2.9
4138       * @param array $headerinfo An array of header information, dependant on what type of header is being displayed. The following
4139       *                          array example is user specific.
4140       *                          heading => Override the page heading.
4141       *                          user => User object.
4142       *                          usercontext => user context.
4143       * @param int $headinglevel What level the 'h' tag will be.
4144       * @return string HTML for the header bar.
4145       */
4146      public function context_header($headerinfo = null, $headinglevel = 1) {
4147          global $DB, $USER, $CFG;
4148          $context = $this->page->context;
4149          // Make sure to use the heading if it has been set.
4150          if (isset($headerinfo['heading'])) {
4151              $heading = $headerinfo['heading'];
4152          } else {
4153              $heading = null;
4154          }
4155          $imagedata = null;
4156          $subheader = null;
4157          $userbuttons = null;
4158          // The user context currently has images and buttons. Other contexts may follow.
4159          if (isset($headerinfo['user']) || $context->contextlevel == CONTEXT_USER) {
4160              if (isset($headerinfo['user'])) {
4161                  $user = $headerinfo['user'];
4162              } else {
4163                  // Look up the user information if it is not supplied.
4164                  $user = $DB->get_record('user', array('id' => $context->instanceid));
4165              }
4166              // If the user context is set, then use that for capability checks.
4167              if (isset($headerinfo['usercontext'])) {
4168                  $context = $headerinfo['usercontext'];
4169              }
4170              // Use the user's full name if the heading isn't set.
4171              if (!isset($heading)) {
4172                  $heading = fullname($user);
4173              }
4174  
4175              $imagedata = $this->user_picture($user, array('size' => 100));
4176              // Check to see if we should be displaying a message button.
4177              if (!empty($CFG->messaging) && $USER->id != $user->id && has_capability('moodle/site:sendmessage', $context)) {
4178                  $userbuttons = array(
4179                      'messages' => array(
4180                          'buttontype' => 'message',
4181                          'title' => get_string('message', 'message'),
4182                          'url' => new moodle_url('/message/index.php', array('id' => $user->id)),
4183                          'image' => 'message',
4184                          'linkattributes' => message_messenger_sendmessage_link_params($user),
4185                          'page' => $this->page
4186                      )
4187                  );
4188                  $this->page->requires->string_for_js('changesmadereallygoaway', 'moodle');
4189              }
4190          }
4191  
4192          $contextheader = new context_header($heading, $headinglevel, $imagedata, $userbuttons);
4193          return $this->render_context_header($contextheader);
4194      }
4195  
4196       /**
4197        * Renders the header bar.
4198        *
4199        * @param context_header $contextheader Header bar object.
4200        * @return string HTML for the header bar.
4201        */
4202      protected function render_context_header(context_header $contextheader) {
4203  
4204          // All the html stuff goes here.
4205          $html = html_writer::start_div('page-context-header');
4206  
4207          // Image data.
4208          if (isset($contextheader->imagedata)) {
4209              // Header specific image.
4210              $html .= html_writer::div($contextheader->imagedata, 'page-header-image');
4211          }
4212  
4213          // Headings.
4214          if (!isset($contextheader->heading)) {
4215              $headings = $this->heading($this->page->heading, $contextheader->headinglevel);
4216          } else {
4217              $headings = $this->heading($contextheader->heading, $contextheader->headinglevel);
4218          }
4219  
4220          $html .= html_writer::tag('div', $headings, array('class' => 'page-header-headings'));
4221  
4222          // Buttons.
4223          if (isset($contextheader->additionalbuttons)) {
4224              $html .= html_writer::start_div('btn-group header-button-group');
4225              foreach ($contextheader->additionalbuttons as $button) {
4226                  if (!isset($button->page)) {
4227                      // Include js for messaging.
4228                      if ($button['buttontype'] === 'message') {
4229                          message_messenger_requirejs();
4230                      }
4231                      $image = $this->pix_icon($button['formattedimage'], $button['title'], 'moodle', array(
4232                          'class' => 'iconsmall',
4233                          'role' => 'presentation'
4234                      ));
4235                      $image .= html_writer::span($button['title'], 'header-button-title');
4236                  } else {
4237                      $image = html_writer::empty_tag('img', array(
4238                          'src' => $button['formattedimage'],
4239                          'role' => 'presentation'
4240                      ));
4241                  }
4242                  $html .= html_writer::link($button['url'], html_writer::tag('span', $image), $button['linkattributes']);
4243              }
4244              $html .= html_writer::end_div();
4245          }
4246          $html .= html_writer::end_div();
4247  
4248          return $html;
4249      }
4250  
4251      /**
4252       * Wrapper for header elements.
4253       *
4254       * @return string HTML to display the main header.
4255       */
4256      public function full_header() {
4257          $html = html_writer::start_tag('header', array('id' => 'page-header', 'class' => 'clearfix'));
4258          $html .= $this->context_header();
4259          $html .= html_writer::start_div('clearfix', array('id' => 'page-navbar'));
4260          $html .= html_writer::tag('div', $this->navbar(), array('class' => 'breadcrumb-nav'));
4261          $html .= html_writer::div($this->page_heading_button(), 'breadcrumb-button');
4262          $html .= html_writer::end_div();
4263          $html .= html_writer::tag('div', $this->course_header(), array('id' => 'course-header'));
4264          $html .= html_writer::end_tag('header');
4265          return $html;
4266      }
4267  
4268      /**
4269       * Displays the list of tags associated with an entry
4270       *
4271       * @param array $tags list of instances of core_tag or stdClass
4272       * @param string $label label to display in front, by default 'Tags' (get_string('tags')), set to null
4273       *               to use default, set to '' (empty string) to omit the label completely
4274       * @param string $classes additional classes for the enclosing div element
4275       * @param int $limit limit the number of tags to display, if size of $tags is more than this limit the "more" link
4276       *               will be appended to the end, JS will toggle the rest of the tags
4277       * @param context $pagecontext specify if needed to overwrite the current page context for the view tag link
4278       * @return string
4279       */
4280      public function tag_list($tags, $label = null, $classes = '', $limit = 10, $pagecontext = null) {
4281          $list = new \core_tag\output\taglist($tags, $label, $classes, $limit, $pagecontext);
4282          return $this->render_from_template('core_tag/taglist', $list->export_for_template($this));
4283      }
4284  
4285      /**
4286       * Renders element for inline editing of any value
4287       *
4288       * @param \core\output\inplace_editable $element
4289       * @return string
4290       */
4291      public function render_inplace_editable(\core\output\inplace_editable $element) {
4292          return $this->render_from_template('core/inplace_editable', $element->export_for_template($this));
4293      }
4294  
4295      /**
4296       * Renders a bar chart.
4297       *
4298       * @param \core\chart_bar $chart The chart.
4299       * @return string.
4300       */
4301      public function render_chart_bar(\core\chart_bar $chart) {
4302          return $this->render_chart($chart);
4303      }
4304  
4305      /**
4306       * Renders a line chart.
4307       *
4308       * @param \core\chart_line $chart The chart.
4309       * @return string.
4310       */
4311      public function render_chart_line(\core\chart_line $chart) {
4312          return $this->render_chart($chart);
4313      }
4314  
4315      /**
4316       * Renders a pie chart.
4317       *
4318       * @param \core\chart_pie $chart The chart.
4319       * @return string.
4320       */
4321      public function render_chart_pie(\core\chart_pie $chart) {
4322          return $this->render_chart($chart);
4323      }
4324  
4325      /**
4326       * Renders a chart.
4327       *
4328       * @param \core\chart_base $chart The chart.
4329       * @param bool $withtable Whether to include a data table with the chart.
4330       * @return string.
4331       */
4332      public function render_chart(\core\chart_base $chart, $withtable = true) {
4333          $chartdata = json_encode($chart);
4334          return $this->render_from_template('core/chart', (object) [
4335              'chartdata' => $chartdata,
4336              'withtable' => $withtable
4337          ]);
4338      }
4339  
4340  }
4341  
4342  /**
4343   * A renderer that generates output for command-line scripts.
4344   *
4345   * The implementation of this renderer is probably incomplete.
4346   *
4347   * @copyright 2009 Tim Hunt
4348   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4349   * @since Moodle 2.0
4350   * @package core
4351   * @category output
4352   */
4353  class core_renderer_cli extends core_renderer {
4354  
4355      /**
4356       * Returns the page header.
4357       *
4358       * @return string HTML fragment
4359       */
4360      public function header() {
4361          return $this->page->heading . "\n";
4362      }
4363  
4364      /**
4365       * Returns a template fragment representing a Heading.
4366       *
4367       * @param string $text The text of the heading
4368       * @param int $level The level of importance of the heading
4369       * @param string $classes A space-separated list of CSS classes
4370       * @param string $id An optional ID
4371       * @return string A template fragment for a heading
4372       */
4373      public function heading($text, $level = 2, $classes = 'main', $id = null) {
4374          $text .= "\n";
4375          switch ($level) {
4376              case 1:
4377                  return '=>' . $text;
4378              case 2:
4379                  return '-->' . $text;
4380              default:
4381                  return $text;
4382          }
4383      }
4384  
4385      /**
4386       * Returns a template fragment representing a fatal error.
4387       *
4388       * @param string $message The message to output
4389       * @param string $moreinfourl URL where more info can be found about the error
4390       * @param string $link Link for the Continue button
4391       * @param array $backtrace The execution backtrace
4392       * @param string $debuginfo Debugging information
4393       * @return string A template fragment for a fatal error
4394       */
4395      public function fatal_error($message, $moreinfourl, $link, $backtrace, $debuginfo = null) {
4396          global $CFG;
4397  
4398          $output = "!!! $message !!!\n";
4399  
4400          if ($CFG->debugdeveloper) {
4401              if (!empty($debuginfo)) {
4402                  $output .= $this->notification($debuginfo, 'notifytiny');
4403              }
4404              if (!empty($backtrace)) {
4405                  $output .= $this->notification('Stack trace: ' . format_backtrace($backtrace, true), 'notifytiny');
4406              }
4407          }
4408  
4409          return $output;
4410      }
4411  
4412      /**
4413       * Returns a template fragment representing a notification.
4414       *
4415       * @param string $message The message to print out.
4416       * @param string $type    The type of notification. See constants on \core\output\notification.
4417       * @return string A template fragment for a notification
4418       */
4419      public function notification($message, $type = null) {
4420          $message = clean_text($message);
4421          if ($type === 'notifysuccess' || $type === 'success') {
4422              return "++ $message ++\n";
4423          }
4424          return "!! $message !!\n";
4425      }
4426  
4427      /**
4428       * There is no footer for a cli request, however we must override the
4429       * footer method to prevent the default footer.
4430       */
4431      public function footer() {}
4432  
4433      /**
4434       * Render a notification (that is, a status message about something that has
4435       * just happened).
4436       *
4437       * @param \core\output\notification $notification the notification to print out
4438       * @return string plain text output
4439       */
4440      public function render_notification(\core\output\notification $notification) {
4441          return $this->notification($notification->get_message(), $notification->get_message_type());
4442      }
4443  }
4444  
4445  
4446  /**
4447   * A renderer that generates output for ajax scripts.
4448   *
4449   * This renderer prevents accidental sends back only json
4450   * encoded error messages, all other output is ignored.
4451   *
4452   * @copyright 2010 Petr Skoda
4453   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4454   * @since Moodle 2.0
4455   * @package core
4456   * @category output
4457   */
4458  class core_renderer_ajax extends core_renderer {
4459  
4460      /**
4461       * Returns a template fragment representing a fatal error.
4462       *
4463       * @param string $message The message to output
4464       * @param string $moreinfourl URL where more info can be found about the error
4465       * @param string $link Link for the Continue button
4466       * @param array $backtrace The execution backtrace
4467       * @param string $debuginfo Debugging information
4468       * @return string A template fragment for a fatal error
4469       */
4470      public function fatal_error($message, $moreinfourl, $link, $backtrace, $debuginfo = null) {
4471          global $CFG;
4472  
4473          $this->page->set_context(null); // ugly hack - make sure page context is set to something, we do not want bogus warnings here
4474  
4475          $e = new stdClass();
4476          $e->error      = $message;
4477          $e->stacktrace = NULL;
4478          $e->debuginfo  = NULL;
4479          $e->reproductionlink = NULL;
4480          if (!empty($CFG->debug) and $CFG->debug >= DEBUG_DEVELOPER) {
4481              $link = (string) $link;
4482              if ($link) {
4483                  $e->reproductionlink = $link;
4484              }
4485              if (!empty($debuginfo)) {
4486                  $e->debuginfo = $debuginfo;
4487              }
4488              if (!empty($backtrace)) {
4489                  $e->stacktrace = format_backtrace($backtrace, true);
4490              }
4491          }
4492          $this->header();
4493          return json_encode($e);
4494      }
4495  
4496      /**
4497       * Used to display a notification.
4498       * For the AJAX notifications are discarded.
4499       *
4500       * @param string $message The message to print out.
4501       * @param string $type    The type of notification. See constants on \core\output\notification.
4502       */
4503      public function notification($message, $type = null) {}
4504  
4505      /**
4506       * Used to display a redirection message.
4507       * AJAX redirections should not occur and as such redirection messages
4508       * are discarded.
4509       *
4510       * @param moodle_url|string $encodedurl
4511       * @param string $message
4512       * @param int $delay
4513       * @param bool $debugdisableredirect
4514       * @param string $messagetype The type of notification to show the message in.
4515       *         See constants on \core\output\notification.
4516       */
4517      public function redirect_message($encodedurl, $message, $delay, $debugdisableredirect,
4518                                       $messagetype = \core\output\notification::NOTIFY_INFO) {}
4519  
4520      /**
4521       * Prepares the start of an AJAX output.
4522       */
4523      public function header() {
4524          // unfortunately YUI iframe upload does not support application/json
4525          if (!empty($_FILES)) {
4526              @header('Content-type: text/plain; charset=utf-8');
4527              if (!core_useragent::supports_json_contenttype()) {
4528                  @header('X-Content-Type-Options: nosniff');
4529              }
4530          } else if (!core_useragent::supports_json_contenttype()) {
4531              @header('Content-type: text/plain; charset=utf-8');
4532              @header('X-Content-Type-Options: nosniff');
4533          } else {
4534              @header('Content-type: application/json; charset=utf-8');
4535          }
4536  
4537          // Headers to make it not cacheable and json
4538          @header('Cache-Control: no-store, no-cache, must-revalidate');
4539          @header('Cache-Control: post-check=0, pre-check=0', false);
4540          @header('Pragma: no-cache');
4541          @header('Expires: Mon, 20 Aug 1969 09:23:00 GMT');
4542          @header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
4543          @header('Accept-Ranges: none');
4544      }
4545  
4546      /**
4547       * There is no footer for an AJAX request, however we must override the
4548       * footer method to prevent the default footer.
4549       */
4550      public function footer() {}
4551  
4552      /**
4553       * No need for headers in an AJAX request... this should never happen.
4554       * @param string $text
4555       * @param int $level
4556       * @param string $classes
4557       * @param string $id
4558       */
4559      public function heading($text, $level = 2, $classes = 'main', $id = null) {}
4560  }
4561  
4562  
4563  /**
4564   * Renderer for media files.
4565   *
4566   * Used in file resources, media filter, and any other places that need to
4567   * output embedded media.
4568   *
4569   * @copyright 2011 The Open University
4570   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4571   */
4572  class core_media_renderer extends plugin_renderer_base {
4573      /** @var array Array of available 'player' objects */
4574      private $players;
4575      /** @var string Regex pattern for links which may contain embeddable content */
4576      private $embeddablemarkers;
4577  
4578      /**
4579       * Constructor requires medialib.php.
4580       *
4581       * This is needed in the constructor (not later) so that you can use the
4582       * constants and static functions that are defined in core_media class
4583       * before you call renderer functions.
4584       */
4585      public function __construct() {
4586          global $CFG;
4587          require_once($CFG->libdir . '/medialib.php');
4588      }
4589  
4590      /**
4591       * Obtains the list of core_media_player objects currently in use to render
4592       * items.
4593       *
4594       * The list is in rank order (highest first) and does not include players
4595       * which are disabled.
4596       *
4597       * @return array Array of core_media_player objects in rank order
4598       */
4599      protected function get_players() {
4600          global $CFG;
4601  
4602          // Save time by only building the list once.
4603          if (!$this->players) {
4604              // Get raw list of players.
4605              $players = $this->get_players_raw();
4606  
4607              // Chuck all the ones that are disabled.
4608              foreach ($players as $key => $player) {
4609                  if (!$player->is_enabled()) {
4610                      unset($players[$key]);
4611                  }
4612              }
4613  
4614              // Sort in rank order (highest first).
4615              usort($players, array('core_media_player', 'compare_by_rank'));
4616              $this->players = $players;
4617          }
4618          return $this->players;
4619      }
4620  
4621      /**
4622       * Obtains a raw list of player objects that includes objects regardless
4623       * of whether they are disabled or not, and without sorting.
4624       *
4625       * You can override this in a subclass if you need to add additional
4626       * players.
4627       *
4628       * The return array is be indexed by player name to make it easier to
4629       * remove players in a subclass.
4630       *
4631       * @return array $players Array of core_media_player objects in any order
4632       */
4633      protected function get_players_raw() {
4634          return array(
4635              'vimeo' => new core_media_player_vimeo(),
4636              'youtube' => new core_media_player_youtube(),
4637              'youtube_playlist' => new core_media_player_youtube_playlist(),
4638              'html5video' => new core_media_player_html5video(),
4639              'html5audio' => new core_media_player_html5audio(),
4640              'mp3' => new core_media_player_mp3(),
4641              'flv' => new core_media_player_flv(),
4642              'wmp' => new core_media_player_wmp(),
4643              'qt' => new core_media_player_qt(),
4644              'rm' => new core_media_player_rm(),
4645              'swf' => new core_media_player_swf(),
4646              'link' => new core_media_player_link(),
4647          );
4648      }
4649  
4650      /**
4651       * Renders a media file (audio or video) using suitable embedded player.
4652       *
4653       * See embed_alternatives function for full description of parameters.
4654       * This function calls through to that one.
4655       *
4656       * When using this function you can also specify width and height in the
4657       * URL by including ?d=100x100 at the end. If specified in the URL, this
4658       * will override the $width and $height parameters.
4659       *
4660       * @param moodle_url $url Full URL of media file
4661       * @param string $name Optional user-readable name to display in download link
4662       * @param int $width Width in pixels (optional)
4663       * @param int $height Height in pixels (optional)
4664       * @param array $options Array of key/value pairs
4665       * @return string HTML content of embed
4666       */
4667      public function embed_url(moodle_url $url, $name = '', $width = 0, $height = 0,
4668              $options = array()) {
4669  
4670          // Get width and height from URL if specified (overrides parameters in
4671          // function call).
4672          $rawurl = $url->out(false);
4673          if (preg_match('/[?#]d=([\d]{1,4}%?)x([\d]{1,4}%?)/', $rawurl, $matches)) {
4674              $width = $matches[1];
4675              $height = $matches[2];
4676              $url = new moodle_url(str_replace($matches[0], '', $rawurl));
4677          }
4678  
4679          // Defer to array version of function.
4680          return $this->embed_alternatives(array($url), $name, $width, $height, $options);
4681      }
4682  
4683      /**
4684       * Renders media files (audio or video) using suitable embedded player.
4685       * The list of URLs should be alternative versions of the same content in
4686       * multiple formats. If there is only one format it should have a single
4687       * entry.
4688       *
4689       * If the media files are not in a supported format, this will give students
4690       * a download link to each format. The download link uses the filename
4691       * unless you supply the optional name parameter.
4692       *
4693       * Width and height are optional. If specified, these are suggested sizes
4694       * and should be the exact values supplied by the user, if they come from
4695       * user input. These will be treated as relating to the size of the video
4696       * content, not including any player control bar.
4697       *
4698       * For audio files, height will be ignored. For video files, a few formats
4699       * work if you specify only width, but in general if you specify width
4700       * you must specify height as well.
4701       *
4702       * The $options array is passed through to the core_media_player classes
4703       * that render the object tag. The keys can contain values from
4704       * core_media::OPTION_xx.
4705       *
4706       * @param array $alternatives Array of moodle_url to media files
4707       * @param string $name Optional user-readable name to display in download link
4708       * @param int $width Width in pixels (optional)
4709       * @param int $height Height in pixels (optional)
4710       * @param array $options Array of key/value pairs
4711       * @return string HTML content of embed
4712       */
4713      public function embed_alternatives($alternatives, $name = '', $width = 0, $height = 0,
4714              $options = array()) {
4715  
4716          // Get list of player plugins (will also require the library).
4717          $players = $this->get_players();
4718  
4719          // Set up initial text which will be replaced by first player that
4720          // supports any of the formats.
4721          $out = core_media_player::PLACEHOLDER;
4722  
4723          // Loop through all players that support any of these URLs.
4724          foreach ($players as $player) {
4725              // Option: When no other player matched, don't do the default link player.
4726              if (!empty($options[core_media::OPTION_FALLBACK_TO_BLANK]) &&
4727                      $player->get_rank() === 0 && $out === core_media_player::PLACEHOLDER) {
4728                  continue;
4729              }
4730  
4731              $supported = $player->list_supported_urls($alternatives, $options);
4732              if ($supported) {
4733                  // Embed.
4734                  $text = $player->embed($supported, $name, $width, $height, $options);
4735  
4736                  // Put this in place of the 'fallback' slot in the previous text.
4737                  $out = str_replace(core_media_player::PLACEHOLDER, $text, $out);
4738              }
4739          }
4740  
4741          // Remove 'fallback' slot from final version and return it.
4742          $out = str_replace(core_media_player::PLACEHOLDER, '', $out);
4743          if (!empty($options[core_media::OPTION_BLOCK]) && $out !== '') {
4744              $out = html_writer::tag('div', $out, array('class' => 'resourcecontent'));
4745          }
4746          return $out;
4747      }
4748  
4749      /**
4750       * Checks whether a file can be embedded. If this returns true you will get
4751       * an embedded player; if this returns false, you will just get a download
4752       * link.
4753       *
4754       * This is a wrapper for can_embed_urls.
4755       *
4756       * @param moodle_url $url URL of media file
4757       * @param array $options Options (same as when embedding)
4758       * @return bool True if file can be embedded
4759       */
4760      public function can_embed_url(moodle_url $url, $options = array()) {
4761          return $this->can_embed_urls(array($url), $options);
4762      }
4763  
4764      /**
4765       * Checks whether a file can be embedded. If this returns true you will get
4766       * an embedded player; if this returns false, you will just get a download
4767       * link.
4768       *
4769       * @param array $urls URL of media file and any alternatives (moodle_url)
4770       * @param array $options Options (same as when embedding)
4771       * @return bool True if file can be embedded
4772       */
4773      public function can_embed_urls(array $urls, $options = array()) {
4774          // Check all players to see if any of them support it.
4775          foreach ($this->get_players() as $player) {
4776              // Link player (always last on list) doesn't count!
4777              if ($player->get_rank() <= 0) {
4778                  break;
4779              }
4780              // First player that supports it, return true.
4781              if ($player->list_supported_urls($urls, $options)) {
4782                  return true;
4783              }
4784          }
4785          return false;
4786      }
4787  
4788      /**
4789       * Obtains a list of markers that can be used in a regular expression when
4790       * searching for URLs that can be embedded by any player type.
4791       *
4792       * This string is used to improve peformance of regex matching by ensuring
4793       * that the (presumably C) regex code can do a quick keyword check on the
4794       * URL part of a link to see if it matches one of these, rather than having
4795       * to go into PHP code for every single link to see if it can be embedded.
4796       *
4797       * @return string String suitable for use in regex such as '(\.mp4|\.flv)'
4798       */
4799      public function get_embeddable_markers() {
4800          if (empty($this->embeddablemarkers)) {
4801              $markers = '';
4802              foreach ($this->get_players() as $player) {
4803                  foreach ($player->get_embeddable_markers() as $marker) {
4804                      if ($markers !== '') {
4805                          $markers .= '|';
4806                      }
4807                      $markers .= preg_quote($marker);
4808                  }
4809              }
4810              $this->embeddablemarkers = $markers;
4811          }
4812          return $this->embeddablemarkers;
4813      }
4814  }
4815  
4816  /**
4817   * The maintenance renderer.
4818   *
4819   * The purpose of this renderer is to block out the core renderer methods that are not usable when the site
4820   * is running a maintenance related task.
4821   * It must always extend the core_renderer as we switch from the core_renderer to this renderer in a couple of places.
4822   *
4823   * @since Moodle 2.6
4824   * @package core
4825   * @category output
4826   * @copyright 2013 Sam Hemelryk
4827   * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4828   */
4829  class core_renderer_maintenance extends core_renderer {
4830  
4831      /**
4832       * Initialises the renderer instance.
4833       * @param moodle_page $page
4834       * @param string $target
4835       * @throws coding_exception
4836       */
4837      public function __construct(moodle_page $page, $target) {
4838          if ($target !== RENDERER_TARGET_MAINTENANCE || $page->pagelayout !== 'maintenance') {
4839              throw new coding_exception('Invalid request for the maintenance renderer.');
4840          }
4841          parent::__construct($page, $target);
4842      }
4843  
4844      /**
4845       * Does nothing. The maintenance renderer cannot produce blocks.
4846       *
4847       * @param block_contents $bc
4848       * @param string $region
4849       * @return string
4850       */
4851      public function block(block_contents $bc, $region) {
4852          // Computer says no blocks.
4853          // debugging('Please do not use $OUTPUT->'.__FUNCTION__.'() when performing maintenance tasks.', DEBUG_DEVELOPER);
4854          return '';
4855      }
4856  
4857      /**
4858       * Does nothing. The maintenance renderer cannot produce blocks.
4859       *
4860       * @param string $region
4861       * @param array $classes
4862       * @param string $tag
4863       * @return string
4864       */
4865      public function blocks($region, $classes = array(), $tag = 'aside') {
4866          // Computer says no blocks.
4867          // debugging('Please do not use $OUTPUT->'.__FUNCTION__.'() when performing maintenance tasks.', DEBUG_DEVELOPER);
4868          return '';
4869      }
4870  
4871      /**
4872       * Does nothing. The maintenance renderer cannot produce blocks.
4873       *
4874       * @param string $region
4875       * @return string
4876       */
4877      public function blocks_for_region($region) {
4878          // Computer says no blocks for region.
4879          // debugging('Please do not use $OUTPUT->'.__FUNCTION__.'() when performing maintenance tasks.', DEBUG_DEVELOPER);
4880          return '';
4881      }
4882  
4883      /**
4884       * Does nothing. The maintenance renderer cannot produce a course content header.
4885       *
4886       * @param bool $onlyifnotcalledbefore
4887       * @return string
4888       */
4889      public function course_content_header($onlyifnotcalledbefore = false) {
4890          // Computer says no course content header.
4891          // debugging('Please do not use $OUTPUT->'.__FUNCTION__.'() when performing maintenance tasks.', DEBUG_DEVELOPER);
4892          return '';
4893      }
4894  
4895      /**
4896       * Does nothing. The maintenance renderer cannot produce a course content footer.
4897       *
4898       * @param bool $onlyifnotcalledbefore
4899       * @return string
4900       */
4901      public function course_content_footer($onlyifnotcalledbefore = false) {
4902          // Computer says no course content footer.
4903          // debugging('Please do not use $OUTPUT->'.__FUNCTION__.'() when performing maintenance tasks.', DEBUG_DEVELOPER);
4904          return '';
4905      }
4906  
4907      /**
4908       * Does nothing. The maintenance renderer cannot produce a course header.
4909       *
4910       * @return string
4911       */
4912      public function course_header() {
4913          // Computer says no course header.
4914          // debugging('Please do not use $OUTPUT->'.__FUNCTION__.'() when performing maintenance tasks.', DEBUG_DEVELOPER);
4915          return '';
4916      }
4917  
4918      /**
4919       * Does nothing. The maintenance renderer cannot produce a course footer.
4920       *
4921       * @return string
4922       */
4923      public function course_footer() {
4924          // Computer says no course footer.
4925          // debugging('Please do not use $OUTPUT->'.__FUNCTION__.'() when performing maintenance tasks.', DEBUG_DEVELOPER);
4926          return '';
4927      }
4928  
4929      /**
4930       * Does nothing. The maintenance renderer cannot produce a custom menu.
4931       *
4932       * @param string $custommenuitems
4933       * @return string
4934       */
4935      public function custom_menu($custommenuitems = '') {
4936          // Computer says no custom menu.
4937          // debugging('Please do not use $OUTPUT->'.__FUNCTION__.'() when performing maintenance tasks.', DEBUG_DEVELOPER);
4938          return '';
4939      }
4940  
4941      /**
4942       * Does nothing. The maintenance renderer cannot produce a file picker.
4943       *
4944       * @param array $options
4945       * @return string
4946       */
4947      public function file_picker($options) {
4948          // Computer says no file picker.
4949          // debugging('Please do not use $OUTPUT->'.__FUNCTION__.'() when performing maintenance tasks.', DEBUG_DEVELOPER);
4950          return '';
4951      }
4952  
4953      /**
4954       * Does nothing. The maintenance renderer cannot produce and HTML file tree.
4955       *
4956       * @param array $dir
4957       * @return string
4958       */
4959      public function htmllize_file_tree($dir) {
4960          // Hell no we don't want no htmllized file tree.
4961          // Also why on earth is this function on the core renderer???
4962          // debugging('Please do not use $OUTPUT->'.__FUNCTION__.'() when performing maintenance tasks.', DEBUG_DEVELOPER);
4963          return '';
4964  
4965      }
4966  
4967      /**
4968       * Does nothing. The maintenance renderer does not support JS.
4969       *
4970       * @param block_contents $bc
4971       */
4972      public function init_block_hider_js(block_contents $bc) {
4973          // Computer says no JavaScript.
4974          // Do nothing, ridiculous method.
4975      }
4976  
4977      /**
4978       * Does nothing. The maintenance renderer cannot produce language menus.
4979       *
4980       * @return string
4981       */
4982      public function lang_menu() {
4983          // Computer says no lang menu.
4984          // debugging('Please do not use $OUTPUT->'.__FUNCTION__.'() when performing maintenance tasks.', DEBUG_DEVELOPER);
4985          return '';
4986      }
4987  
4988      /**
4989       * Does nothing. The maintenance renderer has no need for login information.
4990       *
4991       * @param null $withlinks
4992       * @return string
4993       */
4994      public function login_info($withlinks = null) {
4995          // Computer says no login info.
4996          // debugging('Please do not use $OUTPUT->'.__FUNCTION__.'() when performing maintenance tasks.', DEBUG_DEVELOPER);
4997          return '';
4998      }
4999  
5000      /**
5001       * Does nothing. The maintenance renderer cannot produce user pictures.
5002       *
5003       * @param stdClass $user
5004       * @param array $options
5005       * @return string
5006       */
5007      public function user_picture(stdClass $user, array $options = null) {
5008          // Computer says no user pictures.
5009          // debugging('Please do not use $OUTPUT->'.__FUNCTION__.'() when performing maintenance tasks.', DEBUG_DEVELOPER);
5010          return '';
5011      }
5012  }


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1