MDL-40759 icons: Refactor to allow theme icon systems
[moodle.git] / lib / outputrenderers.php
CommitLineData
d9c8f425 1<?php
d9c8f425 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 *
7a3c215b 20 * Please see {@link http://docs.moodle.org/en/Developement:How_Moodle_outputs_HTML}
d9c8f425 21 * for an overview.
22 *
7a3c215b
SH
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 *
f8129210 32 * @package core
76be40cc 33 * @category output
78bfb562
PS
34 * @copyright 2009 Tim Hunt
35 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
d9c8f425 36 */
37
78bfb562
PS
38defined('MOODLE_INTERNAL') || die();
39
d9c8f425 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
7a3c215b
SH
48 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
49 * @since Moodle 2.0
f8129210 50 * @package core
76be40cc 51 * @category output
d9c8f425 52 */
78946b9b 53class renderer_base {
7a3c215b 54 /**
76be40cc 55 * @var xhtml_container_stack The xhtml_container_stack to use.
7a3c215b 56 */
d9c8f425 57 protected $opencontainers;
7a3c215b
SH
58
59 /**
76be40cc 60 * @var moodle_page The Moodle page the renderer has been created to assist with.
7a3c215b 61 */
d9c8f425 62 protected $page;
7a3c215b
SH
63
64 /**
76be40cc 65 * @var string The requested rendering target.
7a3c215b 66 */
c927e35c 67 protected $target;
d9c8f425 68
9bdcf579
DW
69 /**
70 * @var Mustache_Engine $mustache The mustache template compiler
71 */
72 private $mustache;
73
9bdcf579
DW
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) {
9bdcf579
DW
84 $themename = $this->page->theme->name;
85 $themerev = theme_get_revision();
9bdcf579 86
fcc383db
DW
87 $cachedir = make_localcache_directory("mustache/$themerev/$themename");
88
89 $loader = new \core\output\mustache_filesystem_loader();
9bdcf579 90 $stringhelper = new \core\output\mustache_string_helper();
0b4bff8c 91 $quotehelper = new \core\output\mustache_quote_helper();
9bdcf579
DW
92 $jshelper = new \core\output\mustache_javascript_helper($this->page->requires);
93 $pixhelper = new \core\output\mustache_pix_helper($this);
75378ded 94 $shortentexthelper = new \core\output\mustache_shorten_text_helper();
8fa8675d 95 $userdatehelper = new \core\output\mustache_user_date_helper();
9bdcf579
DW
96
97 // We only expose the variables that are exposed to JS templates.
98 $safeconfig = $this->page->requires->get_config_for_javascript($this->page, $this);
99
100 $helpers = array('config' => $safeconfig,
101 'str' => array($stringhelper, 'str'),
0b4bff8c 102 'quote' => array($quotehelper, 'quote'),
9bdcf579 103 'js' => array($jshelper, 'help'),
75378ded 104 'pix' => array($pixhelper, 'pix'),
e2310e43
AN
105 'shortentext' => array($shortentexthelper, 'shorten'),
106 'userdate' => array($userdatehelper, 'transform'),
107 );
9bdcf579
DW
108
109 $this->mustache = new Mustache_Engine(array(
110 'cache' => $cachedir,
111 'escape' => 's',
112 'loader' => $loader,
85fa6a93
DW
113 'helpers' => $helpers,
114 'pragmas' => [Mustache_Engine::PRAGMA_BLOCKS]));
9bdcf579
DW
115
116 }
117
118 return $this->mustache;
119 }
120
121
d9c8f425 122 /**
123 * Constructor
7a3c215b 124 *
3d3fae72 125 * The constructor takes two arguments. The first is the page that the renderer
7a3c215b
SH
126 * has been created to assist with, and the second is the target.
127 * The target is an additional identifier that can be used to load different
128 * renderers for different options.
129 *
d9c8f425 130 * @param moodle_page $page the page we are doing output for.
c927e35c 131 * @param string $target one of rendering target constants
d9c8f425 132 */
c927e35c 133 public function __construct(moodle_page $page, $target) {
d9c8f425 134 $this->opencontainers = $page->opencontainers;
135 $this->page = $page;
c927e35c 136 $this->target = $target;
d9c8f425 137 }
138
9bdcf579
DW
139 /**
140 * Renders a template by name with the given context.
141 *
142 * The provided data needs to be array/stdClass made up of only simple types.
143 * Simple types are array,stdClass,bool,int,float,string
144 *
145 * @since 2.9
146 * @param array|stdClass $context Context containing data for the template.
147 * @return string|boolean
148 */
149 public function render_from_template($templatename, $context) {
150 static $templatecache = array();
151 $mustache = $this->get_mustache();
152
a0e358a6
RW
153 try {
154 // Grab a copy of the existing helper to be restored later.
7b55aaa1 155 $uniqidhelper = $mustache->getHelper('uniqid');
a0e358a6
RW
156 } catch (Mustache_Exception_UnknownHelperException $e) {
157 // Helper doesn't exist.
7b55aaa1 158 $uniqidhelper = null;
a0e358a6
RW
159 }
160
9bdcf579
DW
161 // Provide 1 random value that will not change within a template
162 // but will be different from template to template. This is useful for
163 // e.g. aria attributes that only work with id attributes and must be
164 // unique in a page.
165 $mustache->addHelper('uniqid', new \core\output\mustache_uniqid_helper());
166 if (isset($templatecache[$templatename])) {
167 $template = $templatecache[$templatename];
168 } else {
169 try {
170 $template = $mustache->loadTemplate($templatename);
171 $templatecache[$templatename] = $template;
172 } catch (Mustache_Exception_UnknownTemplateException $e) {
173 throw new moodle_exception('Unknown template: ' . $templatename);
174 }
175 }
3274d5ca 176
7b55aaa1 177 $renderedtemplate = trim($template->render($context));
3274d5ca
RW
178
179 // If we had an existing uniqid helper then we need to restore it to allow
180 // handle nested calls of render_from_template.
7b55aaa1
MN
181 if ($uniqidhelper) {
182 $mustache->addHelper('uniqid', $uniqidhelper);
3274d5ca
RW
183 }
184
7b55aaa1 185 return $renderedtemplate;
9bdcf579
DW
186 }
187
188
d9c8f425 189 /**
5d0c95a5 190 * Returns rendered widget.
7a3c215b
SH
191 *
192 * The provided widget needs to be an object that extends the renderable
193 * interface.
3d3fae72 194 * If will then be rendered by a method based upon the classname for the widget.
7a3c215b
SH
195 * For instance a widget of class `crazywidget` will be rendered by a protected
196 * render_crazywidget method of this renderer.
197 *
996b1e0c 198 * @param renderable $widget instance with renderable interface
5d0c95a5 199 * @return string
d9c8f425 200 */
5d0c95a5 201 public function render(renderable $widget) {
d2bba1ee
DW
202 $classname = get_class($widget);
203 // Strip namespaces.
204 $classname = preg_replace('/^.*\\\/', '', $classname);
205 // Remove _renderable suffixes
206 $classname = preg_replace('/_renderable$/', '', $classname);
207
208 $rendermethod = 'render_'.$classname;
5d0c95a5
PS
209 if (method_exists($this, $rendermethod)) {
210 return $this->$rendermethod($widget);
211 }
212 throw new coding_exception('Can not render widget, renderer method ('.$rendermethod.') not found.');
d9c8f425 213 }
214
215 /**
7a3c215b
SH
216 * Adds a JS action for the element with the provided id.
217 *
218 * This method adds a JS event for the provided component action to the page
219 * and then returns the id that the event has been attached to.
f8129210 220 * If no id has been provided then a new ID is generated by {@link html_writer::random_id()}
7a3c215b 221 *
f8129210 222 * @param component_action $action
c80877aa
PS
223 * @param string $id
224 * @return string id of element, either original submitted or random new if not supplied
d9c8f425 225 */
7a3c215b 226 public function add_action_handler(component_action $action, $id = null) {
c80877aa
PS
227 if (!$id) {
228 $id = html_writer::random_id($action->event);
229 }
d96d8f03 230 $this->page->requires->event_handler("#$id", $action->event, $action->jsfunction, $action->jsfunctionargs);
c80877aa 231 return $id;
d9c8f425 232 }
233
234 /**
7a3c215b
SH
235 * Returns true is output has already started, and false if not.
236 *
5d0c95a5 237 * @return boolean true if the header has been printed.
d9c8f425 238 */
5d0c95a5
PS
239 public function has_started() {
240 return $this->page->state >= moodle_page::STATE_IN_BODY;
d9c8f425 241 }
242
243 /**
244 * Given an array or space-separated list of classes, prepares and returns the HTML class attribute value
7a3c215b 245 *
d9c8f425 246 * @param mixed $classes Space-separated string or array of classes
247 * @return string HTML class attribute value
248 */
249 public static function prepare_classes($classes) {
250 if (is_array($classes)) {
251 return implode(' ', array_unique($classes));
252 }
253 return $classes;
254 }
255
95b06c13
DW
256 public function pix_url($imagename, $component = 'moodle') {
257 debugging('pix_url is deprecated. Use image_url for images and pix_icon for icons.');
258 return $this->page->theme->image_url($imagename, $component);
259 }
260
d9c8f425 261 /**
897e902b 262 * Return the moodle_url for an image.
7a3c215b 263 *
897e902b
PS
264 * The exact image location and extension is determined
265 * automatically by searching for gif|png|jpg|jpeg, please
266 * note there can not be diferent images with the different
267 * extension. The imagename is for historical reasons
268 * a relative path name, it may be changed later for core
269 * images. It is recommended to not use subdirectories
270 * in plugin and theme pix directories.
d9c8f425 271 *
897e902b
PS
272 * There are three types of images:
273 * 1/ theme images - stored in theme/mytheme/pix/,
274 * use component 'theme'
275 * 2/ core images - stored in /pix/,
276 * overridden via theme/mytheme/pix_core/
277 * 3/ plugin images - stored in mod/mymodule/pix,
278 * overridden via theme/mytheme/pix_plugins/mod/mymodule/,
95b06c13 279 * example: image_url('comment', 'mod_glossary')
897e902b
PS
280 *
281 * @param string $imagename the pathname of the image
282 * @param string $component full plugin name (aka component) or 'theme'
78946b9b 283 * @return moodle_url
d9c8f425 284 */
95b06c13
DW
285 public function image_url($imagename, $component = 'moodle') {
286 return $this->page->theme->image_url($imagename, $component);
d9c8f425 287 }
2258b4dc
FM
288
289 /**
290 * Return the site's logo URL, if any.
291 *
292 * @param int $maxwidth The maximum width, or null when the maximum width does not matter.
293 * @param int $maxheight The maximum height, or null when the maximum height does not matter.
294 * @return moodle_url|false
295 */
7ad1d2da 296 public function get_logo_url($maxwidth = null, $maxheight = 200) {
2258b4dc
FM
297 global $CFG;
298 $logo = get_config('core_admin', 'logo');
299 if (empty($logo)) {
300 return false;
301 }
302
7ad1d2da
DW
303 // 200px high is the default image size which should be displayed at 100px in the page to account for retina displays.
304 // It's not worth the overhead of detecting and serving 2 different images based on the device.
305
2258b4dc
FM
306 // Hide the requested size in the file path.
307 $filepath = ((int) $maxwidth . 'x' . (int) $maxheight) . '/';
308
309 // Use $CFG->themerev to prevent browser caching when the file changes.
310 return moodle_url::make_pluginfile_url(context_system::instance()->id, 'core_admin', 'logo', $filepath,
311 theme_get_revision(), $logo);
312 }
313
314 /**
315 * Return the site's compact logo URL, if any.
316 *
317 * @param int $maxwidth The maximum width, or null when the maximum width does not matter.
318 * @param int $maxheight The maximum height, or null when the maximum height does not matter.
319 * @return moodle_url|false
320 */
321 public function get_compact_logo_url($maxwidth = 100, $maxheight = 100) {
322 global $CFG;
323 $logo = get_config('core_admin', 'logocompact');
324 if (empty($logo)) {
325 return false;
326 }
327
328 // Hide the requested size in the file path.
329 $filepath = ((int) $maxwidth . 'x' . (int) $maxheight) . '/';
330
331 // Use $CFG->themerev to prevent browser caching when the file changes.
332 return moodle_url::make_pluginfile_url(context_system::instance()->id, 'core_admin', 'logocompact', $filepath,
333 theme_get_revision(), $logo);
334 }
335
d9c8f425 336}
337
c927e35c 338
75590935
PS
339/**
340 * Basis for all plugin renderers.
341 *
f8129210
SH
342 * @copyright Petr Skoda (skodak)
343 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
344 * @since Moodle 2.0
345 * @package core
76be40cc 346 * @category output
75590935
PS
347 */
348class plugin_renderer_base extends renderer_base {
7a3c215b 349
75590935 350 /**
3d3fae72
SH
351 * @var renderer_base|core_renderer A reference to the current renderer.
352 * The renderer provided here will be determined by the page but will in 90%
f8129210 353 * of cases by the {@link core_renderer}
75590935
PS
354 */
355 protected $output;
356
357 /**
996b1e0c 358 * Constructor method, calls the parent constructor
7a3c215b 359 *
75590935 360 * @param moodle_page $page
c927e35c 361 * @param string $target one of rendering target constants
75590935 362 */
c927e35c 363 public function __construct(moodle_page $page, $target) {
166ac0a3
SH
364 if (empty($target) && $page->pagelayout === 'maintenance') {
365 // If the page is using the maintenance layout then we're going to force the target to maintenance.
366 // This way we'll get a special maintenance renderer that is designed to block access to API's that are likely
367 // unavailable for this page layout.
368 $target = RENDERER_TARGET_MAINTENANCE;
369 }
c927e35c
PS
370 $this->output = $page->get_renderer('core', null, $target);
371 parent::__construct($page, $target);
75590935 372 }
ff5265c6 373
5d0c95a5 374 /**
7a3c215b
SH
375 * Renders the provided widget and returns the HTML to display it.
376 *
71c03ac1 377 * @param renderable $widget instance with renderable interface
5d0c95a5
PS
378 * @return string
379 */
380 public function render(renderable $widget) {
d2bba1ee
DW
381 $classname = get_class($widget);
382 // Strip namespaces.
383 $classname = preg_replace('/^.*\\\/', '', $classname);
ebdcb292
SH
384 // Keep a copy at this point, we may need to look for a deprecated method.
385 $deprecatedmethod = 'render_'.$classname;
d2bba1ee 386 // Remove _renderable suffixes
ebdcb292 387 $classname = preg_replace('/_renderable$/', '', $classname);
d2bba1ee
DW
388
389 $rendermethod = 'render_'.$classname;
5d0c95a5
PS
390 if (method_exists($this, $rendermethod)) {
391 return $this->$rendermethod($widget);
392 }
ebdcb292
SH
393 if ($rendermethod !== $deprecatedmethod && method_exists($this, $deprecatedmethod)) {
394 // This is exactly where we don't want to be.
395 // If you have arrived here you have a renderable component within your plugin that has the name
396 // blah_renderable, and you have a render method render_blah_renderable on your plugin.
397 // In 2.8 we revamped output, as part of this change we changed slightly how renderables got rendered
398 // and the _renderable suffix now gets removed when looking for a render method.
399 // You need to change your renderers render_blah_renderable to render_blah.
400 // Until you do this it will not be possible for a theme to override the renderer to override your method.
401 // Please do it ASAP.
402 static $debugged = array();
403 if (!isset($debugged[$deprecatedmethod])) {
404 debugging(sprintf('Deprecated call. Please rename your renderables render method from %s to %s.',
405 $deprecatedmethod, $rendermethod), DEBUG_DEVELOPER);
406 $debugged[$deprecatedmethod] = true;
407 }
408 return $this->$deprecatedmethod($widget);
409 }
5d0c95a5 410 // pass to core renderer if method not found here
469bf7a4 411 return $this->output->render($widget);
5d0c95a5
PS
412 }
413
ff5265c6
PS
414 /**
415 * Magic method used to pass calls otherwise meant for the standard renderer
996b1e0c 416 * to it to ensure we don't go causing unnecessary grief.
ff5265c6
PS
417 *
418 * @param string $method
419 * @param array $arguments
420 * @return mixed
421 */
422 public function __call($method, $arguments) {
37b5b18e 423 if (method_exists('renderer_base', $method)) {
fede0be5 424 throw new coding_exception('Protected method called against '.get_class($this).' :: '.$method);
37b5b18e 425 }
ff5265c6
PS
426 if (method_exists($this->output, $method)) {
427 return call_user_func_array(array($this->output, $method), $arguments);
428 } else {
fede0be5 429 throw new coding_exception('Unknown method called against '.get_class($this).' :: '.$method);
ff5265c6
PS
430 }
431 }
75590935 432}
d9c8f425 433
c927e35c 434
d9c8f425 435/**
78946b9b 436 * The standard implementation of the core_renderer interface.
d9c8f425 437 *
438 * @copyright 2009 Tim Hunt
7a3c215b
SH
439 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
440 * @since Moodle 2.0
f8129210 441 * @package core
76be40cc 442 * @category output
d9c8f425 443 */
78946b9b 444class core_renderer extends renderer_base {
72009b87
PS
445 /**
446 * Do NOT use, please use <?php echo $OUTPUT->main_content() ?>
447 * in layout files instead.
72009b87 448 * @deprecated
f8129210 449 * @var string used in {@link core_renderer::header()}.
72009b87 450 */
d9c8f425 451 const MAIN_CONTENT_TOKEN = '[MAIN CONTENT GOES HERE]';
7a3c215b
SH
452
453 /**
f8129210
SH
454 * @var string Used to pass information from {@link core_renderer::doctype()} to
455 * {@link core_renderer::standard_head_html()}.
7a3c215b 456 */
d9c8f425 457 protected $contenttype;
7a3c215b
SH
458
459 /**
f8129210
SH
460 * @var string Used by {@link core_renderer::redirect_message()} method to communicate
461 * with {@link core_renderer::header()}.
7a3c215b 462 */
d9c8f425 463 protected $metarefreshtag = '';
7a3c215b
SH
464
465 /**
76be40cc 466 * @var string Unique token for the closing HTML
7a3c215b 467 */
72009b87 468 protected $unique_end_html_token;
7a3c215b
SH
469
470 /**
76be40cc 471 * @var string Unique token for performance information
7a3c215b 472 */
72009b87 473 protected $unique_performance_info_token;
7a3c215b
SH
474
475 /**
76be40cc 476 * @var string Unique token for the main content.
7a3c215b 477 */
72009b87
PS
478 protected $unique_main_content_token;
479
480 /**
481 * Constructor
7a3c215b 482 *
72009b87
PS
483 * @param moodle_page $page the page we are doing output for.
484 * @param string $target one of rendering target constants
485 */
486 public function __construct(moodle_page $page, $target) {
487 $this->opencontainers = $page->opencontainers;
488 $this->page = $page;
489 $this->target = $target;
490
491 $this->unique_end_html_token = '%%ENDHTML-'.sesskey().'%%';
492 $this->unique_performance_info_token = '%%PERFORMANCEINFO-'.sesskey().'%%';
493 $this->unique_main_content_token = '[MAIN CONTENT GOES HERE - '.sesskey().']';
494 }
d9c8f425 495
496 /**
497 * Get the DOCTYPE declaration that should be used with this page. Designed to
498 * be called in theme layout.php files.
7a3c215b 499 *
13725b37 500 * @return string the DOCTYPE declaration that should be used.
d9c8f425 501 */
502 public function doctype() {
13725b37
PS
503 if ($this->page->theme->doctype === 'html5') {
504 $this->contenttype = 'text/html; charset=utf-8';
505 return "<!DOCTYPE html>\n";
d9c8f425 506
13725b37 507 } else if ($this->page->theme->doctype === 'xhtml5') {
d9c8f425 508 $this->contenttype = 'application/xhtml+xml; charset=utf-8';
13725b37 509 return "<!DOCTYPE html>\n";
d9c8f425 510
511 } else {
13725b37
PS
512 // legacy xhtml 1.0
513 $this->contenttype = 'text/html; charset=utf-8';
514 return ('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">' . "\n");
d9c8f425 515 }
d9c8f425 516 }
517
518 /**
519 * The attributes that should be added to the <html> tag. Designed to
520 * be called in theme layout.php files.
7a3c215b 521 *
d9c8f425 522 * @return string HTML fragment.
523 */
524 public function htmlattributes() {
13725b37 525 $return = get_html_lang(true);
5ebd1fb9 526 $attributes = array();
13725b37 527 if ($this->page->theme->doctype !== 'html5') {
5ebd1fb9 528 $attributes['xmlns'] = 'http://www.w3.org/1999/xhtml';
13725b37 529 }
5ebd1fb9
BH
530
531 // Give plugins an opportunity to add things like xml namespaces to the html element.
532 // This function should return an array of html attribute names => values.
533 $pluginswithfunction = get_plugins_with_function('add_htmlattributes', 'lib.php');
534 foreach ($pluginswithfunction as $plugins) {
535 foreach ($plugins as $function) {
536 $newattrs = $function();
537 unset($newattrs['dir']);
538 unset($newattrs['lang']);
539 unset($newattrs['xmlns']);
540 unset($newattrs['xml:lang']);
541 $attributes += $newattrs;
542 }
543 }
544
545 foreach ($attributes as $key => $val) {
546 $val = s($val);
547 $return .= " $key=\"$val\"";
548 }
549
13725b37 550 return $return;
d9c8f425 551 }
552
553 /**
554 * The standard tags (meta tags, links to stylesheets and JavaScript, etc.)
555 * that should be included in the <head> tag. Designed to be called in theme
556 * layout.php files.
7a3c215b 557 *
d9c8f425 558 * @return string HTML fragment.
559 */
560 public function standard_head_html() {
b5bbeaf0 561 global $CFG, $SESSION;
59849f79
AN
562
563 // Before we output any content, we need to ensure that certain
564 // page components are set up.
565
566 // Blocks must be set up early as they may require javascript which
567 // has to be included in the page header before output is created.
568 foreach ($this->page->blocks->get_regions() as $region) {
569 $this->page->blocks->ensure_content_created($region, $this);
570 }
571
d9c8f425 572 $output = '';
2ab797c9 573
5ebd1fb9
BH
574 // Give plugins an opportunity to add any head elements. The callback
575 // must always return a string containing valid html head content.
576 $pluginswithfunction = get_plugins_with_function('before_standard_html_head', 'lib.php');
577 foreach ($pluginswithfunction as $plugins) {
578 foreach ($plugins as $function) {
579 $output .= $function();
580 }
581 }
582
2ab797c9
BH
583 // Allow a url_rewrite plugin to setup any dynamic head content.
584 if (isset($CFG->urlrewriteclass) && !isset($CFG->upgraderunning)) {
585 $class = $CFG->urlrewriteclass;
586 $output .= $class::html_head_setup();
587 }
588
d9c8f425 589 $output .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />' . "\n";
590 $output .= '<meta name="keywords" content="moodle, ' . $this->page->title . '" />' . "\n";
f8129210 591 // This is only set by the {@link redirect()} method
d9c8f425 592 $output .= $this->metarefreshtag;
593
594 // Check if a periodic refresh delay has been set and make sure we arn't
595 // already meta refreshing
596 if ($this->metarefreshtag=='' && $this->page->periodicrefreshdelay!==null) {
597 $output .= '<meta http-equiv="refresh" content="'.$this->page->periodicrefreshdelay.';url='.$this->page->url->out().'" />';
598 }
599
5c6daf13 600 // Smart App Banners meta tag is only displayed if mobile services are enabled and configured.
038dc234 601 if (!empty($CFG->enablemobilewebservice)) {
5c6daf13 602 $mobilesettings = get_config('tool_mobile');
038dc234 603 if (!empty($mobilesettings->enablesmartappbanners) and !empty($mobilesettings->iosappid)) {
5c6daf13
JL
604 $output .= '<meta name="apple-itunes-app" content="app-id=' . s($mobilesettings->iosappid) . ', ';
605 $output .= 'app-argument=' . $this->page->url->out() . '"/>';
606 }
607 }
608
238b8bc9 609 // Set up help link popups for all links with the helptooltip class
afe3566c
ARN
610 $this->page->requires->js_init_call('M.util.help_popups.setup');
611
d9c8f425 612 $focus = $this->page->focuscontrol;
613 if (!empty($focus)) {
614 if (preg_match("#forms\['([a-zA-Z0-9]+)'\].elements\['([a-zA-Z0-9]+)'\]#", $focus, $matches)) {
615 // This is a horrifically bad way to handle focus but it is passed in
616 // through messy formslib::moodleform
7d2a0492 617 $this->page->requires->js_function_call('old_onload_focus', array($matches[1], $matches[2]));
d9c8f425 618 } else if (strpos($focus, '.')!==false) {
619 // Old style of focus, bad way to do it
620 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);
621 $this->page->requires->js_function_call('old_onload_focus', explode('.', $focus, 2));
622 } else {
623 // Focus element with given id
7d2a0492 624 $this->page->requires->js_function_call('focuscontrol', array($focus));
d9c8f425 625 }
626 }
627
78946b9b
PS
628 // Get the theme stylesheet - this has to be always first CSS, this loads also styles.css from all plugins;
629 // any other custom CSS can not be overridden via themes and is highly discouraged
efaa4c08 630 $urls = $this->page->theme->css_urls($this->page);
78946b9b 631 foreach ($urls as $url) {
c0467479 632 $this->page->requires->css_theme($url);
78946b9b
PS
633 }
634
04c01408 635 // Get the theme javascript head and footer
d7656956
ARN
636 if ($jsurl = $this->page->theme->javascript_url(true)) {
637 $this->page->requires->js($jsurl, true);
638 }
639 if ($jsurl = $this->page->theme->javascript_url(false)) {
640 $this->page->requires->js($jsurl);
641 }
5d0c95a5 642
d9c8f425 643 // Get any HTML from the page_requirements_manager.
945f19f7 644 $output .= $this->page->requires->get_head_code($this->page, $this);
d9c8f425 645
646 // List alternate versions.
647 foreach ($this->page->alternateversions as $type => $alt) {
5d0c95a5 648 $output .= html_writer::empty_tag('link', array('rel' => 'alternate',
d9c8f425 649 'type' => $type, 'title' => $alt->title, 'href' => $alt->url));
650 }
8a7703ce 651
90e920c7
SH
652 if (!empty($CFG->additionalhtmlhead)) {
653 $output .= "\n".$CFG->additionalhtmlhead;
654 }
d9c8f425 655
656 return $output;
657 }
658
659 /**
660 * The standard tags (typically skip links) that should be output just inside
661 * the start of the <body> tag. Designed to be called in theme layout.php files.
7a3c215b 662 *
d9c8f425 663 * @return string HTML fragment.
664 */
665 public function standard_top_of_body_html() {
90e920c7 666 global $CFG;
536f0460 667 $output = $this->page->requires->get_top_of_body_code($this);
5d01400a 668 if ($this->page->pagelayout !== 'embedded' && !empty($CFG->additionalhtmltopofbody)) {
90e920c7
SH
669 $output .= "\n".$CFG->additionalhtmltopofbody;
670 }
5ebd1fb9
BH
671
672 // Give plugins an opportunity to inject extra html content. The callback
673 // must always return a string containing valid html.
674 $pluginswithfunction = get_plugins_with_function('before_standard_top_of_body_html', 'lib.php');
675 foreach ($pluginswithfunction as $plugins) {
676 foreach ($plugins as $function) {
677 $output .= $function();
678 }
679 }
680
48e114a5 681 $output .= $this->maintenance_warning();
5ebd1fb9 682
48e114a5
PS
683 return $output;
684 }
685
686 /**
687 * Scheduled maintenance warning message.
688 *
689 * Note: This is a nasty hack to display maintenance notice, this should be moved
690 * to some general notification area once we have it.
691 *
692 * @return string
693 */
694 public function maintenance_warning() {
695 global $CFG;
696
697 $output = '';
698 if (isset($CFG->maintenance_later) and $CFG->maintenance_later > time()) {
f487a8f8
RT
699 $timeleft = $CFG->maintenance_later - time();
700 // If timeleft less than 30 sec, set the class on block to error to highlight.
bff9fd89
DW
701 $errorclass = ($timeleft < 30) ? 'alert-error alert-danger' : 'alert-warning';
702 $output .= $this->box_start($errorclass . ' moodle-has-zindex maintenancewarning m-a-1 alert');
f487a8f8 703 $a = new stdClass();
f2510387
DL
704 $a->hour = (int)($timeleft / 3600);
705 $a->min = (int)(($timeleft / 60) % 60);
f487a8f8 706 $a->sec = (int)($timeleft % 60);
f2510387
DL
707 if ($a->hour > 0) {
708 $output .= get_string('maintenancemodeisscheduledlong', 'admin', $a);
709 } else {
710 $output .= get_string('maintenancemodeisscheduled', 'admin', $a);
711 }
712
48e114a5 713 $output .= $this->box_end();
f487a8f8
RT
714 $this->page->requires->yui_module('moodle-core-maintenancemodetimer', 'M.core.maintenancemodetimer',
715 array(array('timeleftinsec' => $timeleft)));
716 $this->page->requires->strings_for_js(
f2510387 717 array('maintenancemodeisscheduled', 'maintenancemodeisscheduledlong', 'sitemaintenance'),
f487a8f8 718 'admin');
48e114a5 719 }
90e920c7 720 return $output;
d9c8f425 721 }
722
723 /**
724 * The standard tags (typically performance information and validation links,
725 * if we are in developer debug mode) that should be output in the footer area
726 * of the page. Designed to be called in theme layout.php files.
7a3c215b 727 *
d9c8f425 728 * @return string HTML fragment.
729 */
730 public function standard_footer_html() {
6af80cae 731 global $CFG, $SCRIPT;
d9c8f425 732
ec3ce3a9
PS
733 if (during_initial_install()) {
734 // Debugging info can not work before install is finished,
735 // in any case we do not want any links during installation!
736 return '';
737 }
738
f8129210 739 // This function is normally called from a layout.php file in {@link core_renderer::header()}
d9c8f425 740 // but some of the content won't be known until later, so we return a placeholder
f8129210 741 // for now. This will be replaced with the real content in {@link core_renderer::footer()}.
72009b87 742 $output = $this->unique_performance_info_token;
e5824bb9 743 if ($this->page->devicetypeinuse == 'legacy') {
ee8df661
SH
744 // The legacy theme is in use print the notification
745 $output .= html_writer::tag('div', get_string('legacythemeinuse'), array('class'=>'legacythemeinuse'));
746 }
37959dd4 747
e5824bb9 748 // Get links to switch device types (only shown for users not on a default device)
37959dd4
AF
749 $output .= $this->theme_switch_links();
750
d9c8f425 751 if (!empty($CFG->debugpageinfo)) {
d4c3f025 752 $output .= '<div class="performanceinfo pageinfo">This page is: ' . $this->page->debug_summary() . '</div>';
d9c8f425 753 }
b0c6dc1c 754 if (debugging(null, DEBUG_DEVELOPER) and has_capability('moodle/site:config', context_system::instance())) { // Only in developer mode
6af80cae
EL
755 // Add link to profiling report if necessary
756 if (function_exists('profiling_is_running') && profiling_is_running()) {
757 $txt = get_string('profiledscript', 'admin');
758 $title = get_string('profiledscriptview', 'admin');
4ac92d2a 759 $url = $CFG->wwwroot . '/admin/tool/profiling/index.php?script=' . urlencode($SCRIPT);
6af80cae
EL
760 $link= '<a title="' . $title . '" href="' . $url . '">' . $txt . '</a>';
761 $output .= '<div class="profilingfooter">' . $link . '</div>';
762 }
2d22f3d9
TH
763 $purgeurl = new moodle_url('/admin/purgecaches.php', array('confirm' => 1,
764 'sesskey' => sesskey(), 'returnurl' => $this->page->url->out_as_local_url(false)));
765 $output .= '<div class="purgecaches">' .
766 html_writer::link($purgeurl, get_string('purgecaches', 'admin')) . '</div>';
ba6c97ee 767 }
d9c8f425 768 if (!empty($CFG->debugvalidators)) {
f0202ae9 769 // 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
8819e01d 770 $output .= '<div class="validators"><ul class="list-unstyled m-l-1">
d9c8f425 771 <li><a href="http://validator.w3.org/check?verbose=1&amp;ss=1&amp;uri=' . urlencode(qualified_me()) . '">Validate HTML</a></li>
772 <li><a href="http://www.contentquality.com/mynewtester/cynthia.exe?rptmode=-1&amp;url1=' . urlencode(qualified_me()) . '">Section 508 Check</a></li>
773 <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>
774 </ul></div>';
775 }
776 return $output;
777 }
778
72009b87
PS
779 /**
780 * Returns standard main content placeholder.
781 * Designed to be called in theme layout.php files.
7a3c215b 782 *
72009b87
PS
783 * @return string HTML fragment.
784 */
785 public function main_content() {
537ba512
SH
786 // This is here because it is the only place we can inject the "main" role over the entire main content area
787 // without requiring all theme's to manually do it, and without creating yet another thing people need to
788 // remember in the theme.
789 // This is an unfortunate hack. DO NO EVER add anything more here.
790 // DO NOT add classes.
791 // DO NOT add an id.
792 return '<div role="main">'.$this->unique_main_content_token.'</div>';
72009b87
PS
793 }
794
d9c8f425 795 /**
796 * The standard tags (typically script tags that are not needed earlier) that
391edc51 797 * should be output after everything else. Designed to be called in theme layout.php files.
7a3c215b 798 *
d9c8f425 799 * @return string HTML fragment.
800 */
801 public function standard_end_of_body_html() {
391edc51
TH
802 global $CFG;
803
f8129210 804 // This function is normally called from a layout.php file in {@link core_renderer::header()}
d9c8f425 805 // but some of the content won't be known until later, so we return a placeholder
f8129210 806 // for now. This will be replaced with the real content in {@link core_renderer::footer()}.
391edc51 807 $output = '';
5d01400a 808 if ($this->page->pagelayout !== 'embedded' && !empty($CFG->additionalhtmlfooter)) {
391edc51
TH
809 $output .= "\n".$CFG->additionalhtmlfooter;
810 }
811 $output .= $this->unique_end_html_token;
812 return $output;
d9c8f425 813 }
814
815 /**
816 * Return the standard string that says whether you are logged in (and switched
817 * roles/logged in as another user).
2d0e682d
MV
818 * @param bool $withlinks if false, then don't include any links in the HTML produced.
819 * If not set, the default is the nologinlinks option from the theme config.php file,
820 * and if that is not set, then links are included.
d9c8f425 821 * @return string HTML fragment.
822 */
2d0e682d 823 public function login_info($withlinks = null) {
8f0fe0b8 824 global $USER, $CFG, $DB, $SESSION;
4bcc5118 825
244a32c6
PS
826 if (during_initial_install()) {
827 return '';
828 }
4bcc5118 829
2d0e682d
MV
830 if (is_null($withlinks)) {
831 $withlinks = empty($this->page->layout_options['nologinlinks']);
832 }
833
244a32c6 834 $course = $this->page->course;
d79d5ac2
PS
835 if (\core\session\manager::is_loggedinas()) {
836 $realuser = \core\session\manager::get_realuser();
244a32c6 837 $fullname = fullname($realuser, true);
2d0e682d 838 if ($withlinks) {
fa84b901
RT
839 $loginastitle = get_string('loginas');
840 $realuserinfo = " [<a href=\"$CFG->wwwroot/course/loginas.php?id=$course->id&amp;sesskey=".sesskey()."\"";
841 $realuserinfo .= "title =\"".$loginastitle."\">$fullname</a>] ";
2d0e682d
MV
842 } else {
843 $realuserinfo = " [$fullname] ";
844 }
244a32c6
PS
845 } else {
846 $realuserinfo = '';
847 }
4bcc5118 848
0e61dba3 849 $loginpage = $this->is_login_page();
244a32c6 850 $loginurl = get_login_url();
4bcc5118 851
244a32c6
PS
852 if (empty($course->id)) {
853 // $course->id is not defined during installation
854 return '';
4f0c2d00 855 } else if (isloggedin()) {
b0c6dc1c 856 $context = context_course::instance($course->id);
4bcc5118 857
244a32c6 858 $fullname = fullname($USER, true);
03d9401e 859 // Since Moodle 2.0 this link always goes to the public profile page (not the course profile page)
2d0e682d 860 if ($withlinks) {
c7fe9f81
RT
861 $linktitle = get_string('viewprofile');
862 $username = "<a href=\"$CFG->wwwroot/user/profile.php?id=$USER->id\" title=\"$linktitle\">$fullname</a>";
2d0e682d
MV
863 } else {
864 $username = $fullname;
865 }
244a32c6 866 if (is_mnet_remote_user($USER) and $idprovider = $DB->get_record('mnet_host', array('id'=>$USER->mnethostid))) {
2d0e682d
MV
867 if ($withlinks) {
868 $username .= " from <a href=\"{$idprovider->wwwroot}\">{$idprovider->name}</a>";
869 } else {
870 $username .= " from {$idprovider->name}";
871 }
244a32c6 872 }
b3df1764 873 if (isguestuser()) {
2778744b 874 $loggedinas = $realuserinfo.get_string('loggedinasguest');
2d0e682d 875 if (!$loginpage && $withlinks) {
2778744b
PS
876 $loggedinas .= " (<a href=\"$loginurl\">".get_string('login').'</a>)';
877 }
f5c1e621 878 } else if (is_role_switched($course->id)) { // Has switched roles
244a32c6
PS
879 $rolename = '';
880 if ($role = $DB->get_record('role', array('id'=>$USER->access['rsw'][$context->path]))) {
f2cf0f84 881 $rolename = ': '.role_get_name($role, $context);
244a32c6 882 }
2d0e682d
MV
883 $loggedinas = get_string('loggedinas', 'moodle', $username).$rolename;
884 if ($withlinks) {
aae028d9 885 $url = new moodle_url('/course/switchrole.php', array('id'=>$course->id,'sesskey'=>sesskey(), 'switchrole'=>0, 'returnurl'=>$this->page->url->out_as_local_url(false)));
8bf91fb8 886 $loggedinas .= ' ('.html_writer::tag('a', get_string('switchrolereturn'), array('href' => $url)).')';
2d0e682d 887 }
244a32c6 888 } else {
2d0e682d
MV
889 $loggedinas = $realuserinfo.get_string('loggedinas', 'moodle', $username);
890 if ($withlinks) {
891 $loggedinas .= " (<a href=\"$CFG->wwwroot/login/logout.php?sesskey=".sesskey()."\">".get_string('logout').'</a>)';
892 }
244a32c6
PS
893 }
894 } else {
2778744b 895 $loggedinas = get_string('loggedinnot', 'moodle');
2d0e682d 896 if (!$loginpage && $withlinks) {
2778744b
PS
897 $loggedinas .= " (<a href=\"$loginurl\">".get_string('login').'</a>)';
898 }
244a32c6 899 }
4bcc5118 900
244a32c6 901 $loggedinas = '<div class="logininfo">'.$loggedinas.'</div>';
4bcc5118 902
244a32c6
PS
903 if (isset($SESSION->justloggedin)) {
904 unset($SESSION->justloggedin);
905 if (!empty($CFG->displayloginfailures)) {
b3df1764 906 if (!isguestuser()) {
52dc1de7
AA
907 // Include this file only when required.
908 require_once($CFG->dirroot . '/user/lib.php');
909 if ($count = user_count_login_failures($USER)) {
2b0c88e2 910 $loggedinas .= '<div class="loginfailures">';
52dc1de7
AA
911 $a = new stdClass();
912 $a->attempts = $count;
913 $loggedinas .= get_string('failedloginattempts', '', $a);
b0c6dc1c 914 if (file_exists("$CFG->dirroot/report/log/index.php") and has_capability('report/log:view', context_system::instance())) {
46fa0628
DP
915 $loggedinas .= ' ('.html_writer::link(new moodle_url('/report/log/index.php', array('chooselog' => 1,
916 'id' => 0 , 'modid' => 'site_errors')), get_string('logs')).')';
244a32c6
PS
917 }
918 $loggedinas .= '</div>';
919 }
920 }
921 }
922 }
4bcc5118 923
244a32c6 924 return $loggedinas;
d9c8f425 925 }
926
0e61dba3
AN
927 /**
928 * Check whether the current page is a login page.
929 *
930 * @since Moodle 2.9
931 * @return bool
932 */
933 protected function is_login_page() {
934 // This is a real bit of a hack, but its a rarety that we need to do something like this.
935 // In fact the login pages should be only these two pages and as exposing this as an option for all pages
936 // could lead to abuse (or at least unneedingly complex code) the hack is the way to go.
937 return in_array(
938 $this->page->url->out_as_local_url(false, array()),
939 array(
940 '/login/index.php',
941 '/login/forgot_password.php',
942 )
943 );
944 }
945
d9c8f425 946 /**
947 * Return the 'back' link that normally appears in the footer.
7a3c215b 948 *
d9c8f425 949 * @return string HTML fragment.
950 */
951 public function home_link() {
952 global $CFG, $SITE;
953
954 if ($this->page->pagetype == 'site-index') {
955 // Special case for site home page - please do not remove
956 return '<div class="sitelink">' .
34dff6aa 957 '<a title="Moodle" href="http://moodle.org/">' .
663640f5 958 '<img src="' . $this->image_url('moodlelogo') . '" alt="'.get_string('moodlelogo').'" /></a></div>';
d9c8f425 959
960 } else if (!empty($CFG->target_release) && $CFG->target_release != $CFG->release) {
961 // Special case for during install/upgrade.
962 return '<div class="sitelink">'.
34dff6aa 963 '<a title="Moodle" href="http://docs.moodle.org/en/Administrator_documentation" onclick="this.target=\'_blank\'">' .
663640f5 964 '<img src="' . $this->image_url('moodlelogo') . '" alt="'.get_string('moodlelogo').'" /></a></div>';
d9c8f425 965
966 } else if ($this->page->course->id == $SITE->id || strpos($this->page->pagetype, 'course-view') === 0) {
967 return '<div class="homelink"><a href="' . $CFG->wwwroot . '/">' .
968 get_string('home') . '</a></div>';
969
970 } else {
971 return '<div class="homelink"><a href="' . $CFG->wwwroot . '/course/view.php?id=' . $this->page->course->id . '">' .
8ebbb06a 972 format_string($this->page->course->shortname, true, array('context' => $this->page->context)) . '</a></div>';
d9c8f425 973 }
974 }
975
976 /**
977 * Redirects the user by any means possible given the current state
978 *
979 * This function should not be called directly, it should always be called using
980 * the redirect function in lib/weblib.php
981 *
982 * The redirect function should really only be called before page output has started
983 * however it will allow itself to be called during the state STATE_IN_BODY
984 *
985 * @param string $encodedurl The URL to send to encoded if required
986 * @param string $message The message to display to the user if any
987 * @param int $delay The delay before redirecting a user, if $message has been
988 * set this is a requirement and defaults to 3, set to 0 no delay
989 * @param boolean $debugdisableredirect this redirect has been disabled for
990 * debugging purposes. Display a message that explains, and don't
991 * trigger the redirect.
3ad96419
AN
992 * @param string $messagetype The type of notification to show the message in.
993 * See constants on \core\output\notification.
d9c8f425 994 * @return string The HTML to display to the user before dying, may contain
995 * meta refresh, javascript refresh, and may have set header redirects
996 */
3ad96419
AN
997 public function redirect_message($encodedurl, $message, $delay, $debugdisableredirect,
998 $messagetype = \core\output\notification::NOTIFY_INFO) {
d9c8f425 999 global $CFG;
1000 $url = str_replace('&amp;', '&', $encodedurl);
1001
1002 switch ($this->page->state) {
1003 case moodle_page::STATE_BEFORE_HEADER :
1004 // No output yet it is safe to delivery the full arsenal of redirect methods
1005 if (!$debugdisableredirect) {
1006 // Don't use exactly the same time here, it can cause problems when both redirects fire at the same time.
1007 $this->metarefreshtag = '<meta http-equiv="refresh" content="'. $delay .'; url='. $encodedurl .'" />'."\n";
593f9b87 1008 $this->page->requires->js_function_call('document.location.replace', array($url), false, ($delay + 3));
d9c8f425 1009 }
1010 $output = $this->header();
1011 break;
1012 case moodle_page::STATE_PRINTING_HEADER :
1013 // We should hopefully never get here
1014 throw new coding_exception('You cannot redirect while printing the page header');
1015 break;
1016 case moodle_page::STATE_IN_BODY :
1017 // We really shouldn't be here but we can deal with this
1018 debugging("You should really redirect before you start page output");
1019 if (!$debugdisableredirect) {
593f9b87 1020 $this->page->requires->js_function_call('document.location.replace', array($url), false, $delay);
d9c8f425 1021 }
1022 $output = $this->opencontainers->pop_all_but_last();
1023 break;
1024 case moodle_page::STATE_DONE :
1025 // Too late to be calling redirect now
1026 throw new coding_exception('You cannot redirect after the entire page has been generated');
1027 break;
1028 }
3ad96419 1029 $output .= $this->notification($message, $messagetype);
3ab2e357 1030 $output .= '<div class="continuebutton">(<a href="'. $encodedurl .'">'. get_string('continue') .'</a>)</div>';
d9c8f425 1031 if ($debugdisableredirect) {
05da2850 1032 $output .= '<p><strong>'.get_string('erroroutput', 'error').'</strong></p>';
d9c8f425 1033 }
1034 $output .= $this->footer();
1035 return $output;
1036 }
1037
1038 /**
1039 * Start output by sending the HTTP headers, and printing the HTML <head>
1040 * and the start of the <body>.
1041 *
1042 * To control what is printed, you should set properties on $PAGE. If you
f8129210 1043 * are familiar with the old {@link print_header()} function from Moodle 1.9
d9c8f425 1044 * you will find that there are properties on $PAGE that correspond to most
1045 * of the old parameters to could be passed to print_header.
1046 *
1047 * Not that, in due course, the remaining $navigation, $menu parameters here
1048 * will be replaced by more properties of $PAGE, but that is still to do.
1049 *
d9c8f425 1050 * @return string HTML that you must output this, preferably immediately.
1051 */
e120c61d 1052 public function header() {
45cd101f 1053 global $USER, $CFG, $SESSION;
d9c8f425 1054
5ebd1fb9
BH
1055 // Give plugins an opportunity touch things before the http headers are sent
1056 // such as adding additional headers. The return value is ignored.
1057 $pluginswithfunction = get_plugins_with_function('before_http_headers', 'lib.php');
1058 foreach ($pluginswithfunction as $plugins) {
1059 foreach ($plugins as $function) {
1060 $function();
1061 }
1062 }
1063
d79d5ac2 1064 if (\core\session\manager::is_loggedinas()) {
e884f63a
PS
1065 $this->page->add_body_class('userloggedinas');
1066 }
1067
45cd101f
SL
1068 if (isset($SESSION->justloggedin) && !empty($CFG->displayloginfailures)) {
1069 require_once($CFG->dirroot . '/user/lib.php');
1070 // Set second parameter to false as we do not want reset the counter, the same message appears on footer.
1071 if ($count = user_count_login_failures($USER, false)) {
1072 $this->page->add_body_class('loginfailures');
1073 }
1074 }
1075
46629116
JC
1076 // If the user is logged in, and we're not in initial install,
1077 // check to see if the user is role-switched and add the appropriate
1078 // CSS class to the body element.
1079 if (!during_initial_install() && isloggedin() && is_role_switched($this->page->course->id)) {
1080 $this->page->add_body_class('userswitchedrole');
1081 }
1082
63c88397
PS
1083 // Give themes a chance to init/alter the page object.
1084 $this->page->theme->init_page($this->page);
1085
d9c8f425 1086 $this->page->set_state(moodle_page::STATE_PRINTING_HEADER);
1087
78946b9b
PS
1088 // Find the appropriate page layout file, based on $this->page->pagelayout.
1089 $layoutfile = $this->page->theme->layout_file($this->page->pagelayout);
1090 // Render the layout using the layout file.
1091 $rendered = $this->render_page_layout($layoutfile);
67e84a7f 1092
78946b9b 1093 // Slice the rendered output into header and footer.
72009b87
PS
1094 $cutpos = strpos($rendered, $this->unique_main_content_token);
1095 if ($cutpos === false) {
1096 $cutpos = strpos($rendered, self::MAIN_CONTENT_TOKEN);
1097 $token = self::MAIN_CONTENT_TOKEN;
1098 } else {
1099 $token = $this->unique_main_content_token;
1100 }
1101
d9c8f425 1102 if ($cutpos === false) {
72009b87 1103 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.');
d9c8f425 1104 }
78946b9b 1105 $header = substr($rendered, 0, $cutpos);
72009b87 1106 $footer = substr($rendered, $cutpos + strlen($token));
d9c8f425 1107
1108 if (empty($this->contenttype)) {
78946b9b 1109 debugging('The page layout file did not call $OUTPUT->doctype()');
67e84a7f 1110 $header = $this->doctype() . $header;
d9c8f425 1111 }
1112
fdd4b9a5
MG
1113 // If this theme version is below 2.4 release and this is a course view page
1114 if ((!isset($this->page->theme->settings->version) || $this->page->theme->settings->version < 2012101500) &&
1115 $this->page->pagelayout === 'course' && $this->page->url->compare(new moodle_url('/course/view.php'), URL_MATCH_BASE)) {
1116 // check if course content header/footer have not been output during render of theme layout
1117 $coursecontentheader = $this->course_content_header(true);
1118 $coursecontentfooter = $this->course_content_footer(true);
1119 if (!empty($coursecontentheader)) {
1120 // display debug message and add header and footer right above and below main content
1121 // Please note that course header and footer (to be displayed above and below the whole page)
1122 // are not displayed in this case at all.
1123 // Besides the content header and footer are not displayed on any other course page
1124 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);
1125 $header .= $coursecontentheader;
1126 $footer = $coursecontentfooter. $footer;
1127 }
1128 }
1129
d9c8f425 1130 send_headers($this->contenttype, $this->page->cacheable);
67e84a7f 1131
d9c8f425 1132 $this->opencontainers->push('header/footer', $footer);
1133 $this->page->set_state(moodle_page::STATE_IN_BODY);
67e84a7f 1134
29ba64e5 1135 return $header . $this->skip_link_target('maincontent');
d9c8f425 1136 }
1137
1138 /**
78946b9b 1139 * Renders and outputs the page layout file.
7a3c215b
SH
1140 *
1141 * This is done by preparing the normal globals available to a script, and
1142 * then including the layout file provided by the current theme for the
1143 * requested layout.
1144 *
78946b9b 1145 * @param string $layoutfile The name of the layout file
d9c8f425 1146 * @return string HTML code
1147 */
78946b9b 1148 protected function render_page_layout($layoutfile) {
92e01ab7 1149 global $CFG, $SITE, $USER;
d9c8f425 1150 // The next lines are a bit tricky. The point is, here we are in a method
1151 // of a renderer class, and this object may, or may not, be the same as
78946b9b 1152 // the global $OUTPUT object. When rendering the page layout file, we want to use
d9c8f425 1153 // this object. However, people writing Moodle code expect the current
1154 // renderer to be called $OUTPUT, not $this, so define a variable called
1155 // $OUTPUT pointing at $this. The same comment applies to $PAGE and $COURSE.
1156 $OUTPUT = $this;
1157 $PAGE = $this->page;
1158 $COURSE = $this->page->course;
1159
d9c8f425 1160 ob_start();
78946b9b
PS
1161 include($layoutfile);
1162 $rendered = ob_get_contents();
d9c8f425 1163 ob_end_clean();
78946b9b 1164 return $rendered;
d9c8f425 1165 }
1166
1167 /**
1168 * Outputs the page's footer
7a3c215b 1169 *
d9c8f425 1170 * @return string HTML fragment
1171 */
1172 public function footer() {
0346323c 1173 global $CFG, $DB, $PAGE;
0f0801f4 1174
5ebd1fb9
BH
1175 // Give plugins an opportunity to touch the page before JS is finalized.
1176 $pluginswithfunction = get_plugins_with_function('before_footer', 'lib.php');
1177 foreach ($pluginswithfunction as $plugins) {
1178 foreach ($plugins as $function) {
1179 $function();
1180 }
1181 }
1182
f6794ace 1183 $output = $this->container_end_all(true);
4d2ee4c2 1184
d9c8f425 1185 $footer = $this->opencontainers->pop('header/footer');
1186
d5a8d9aa 1187 if (debugging() and $DB and $DB->is_transaction_started()) {
03221650 1188 // TODO: MDL-20625 print warning - transaction will be rolled back
d5a8d9aa
PS
1189 }
1190
d9c8f425 1191 // Provide some performance info if required
1192 $performanceinfo = '';
1193 if (defined('MDL_PERF') || (!empty($CFG->perfdebug) and $CFG->perfdebug > 7)) {
1194 $perf = get_performance_info();
d9c8f425 1195 if (defined('MDL_PERFTOFOOT') || debugging() || $CFG->perfdebug > 7) {
1196 $performanceinfo = $perf['html'];
1197 }
1198 }
58a3a34e
DM
1199
1200 // We always want performance data when running a performance test, even if the user is redirected to another page.
1201 if (MDL_PERF_TEST && strpos($footer, $this->unique_performance_info_token) === false) {
1202 $footer = $this->unique_performance_info_token . $footer;
1203 }
72009b87 1204 $footer = str_replace($this->unique_performance_info_token, $performanceinfo, $footer);
d9c8f425 1205
38870d60
MN
1206 // Only show notifications when we have a $PAGE context id.
1207 if (!empty($PAGE->context->id)) {
1208 $this->page->requires->js_call_amd('core/notification', 'init', array(
1209 $PAGE->context->id,
1210 \core\notification::fetch_as_array($this)
1211 ));
1212 }
72009b87 1213 $footer = str_replace($this->unique_end_html_token, $this->page->requires->get_end_code(), $footer);
d9c8f425 1214
1215 $this->page->set_state(moodle_page::STATE_DONE);
d9c8f425 1216
1217 return $output . $footer;
1218 }
1219
f6794ace
PS
1220 /**
1221 * Close all but the last open container. This is useful in places like error
1222 * handling, where you want to close all the open containers (apart from <body>)
1223 * before outputting the error message.
7a3c215b 1224 *
f6794ace
PS
1225 * @param bool $shouldbenone assert that the stack should be empty now - causes a
1226 * developer debug warning if it isn't.
1227 * @return string the HTML required to close any open containers inside <body>.
1228 */
1229 public function container_end_all($shouldbenone = false) {
1230 return $this->opencontainers->pop_all_but_last($shouldbenone);
1231 }
1232
fdd4b9a5
MG
1233 /**
1234 * Returns course-specific information to be output immediately above content on any course page
1235 * (for the current course)
1236 *
1237 * @param bool $onlyifnotcalledbefore output content only if it has not been output before
1238 * @return string
1239 */
1240 public function course_content_header($onlyifnotcalledbefore = false) {
1241 global $CFG;
fdd4b9a5
MG
1242 static $functioncalled = false;
1243 if ($functioncalled && $onlyifnotcalledbefore) {
1244 // we have already output the content header
1245 return '';
1246 }
0346323c
AN
1247
1248 // Output any session notification.
1249 $notifications = \core\notification::fetch();
1250
1251 $bodynotifications = '';
1252 foreach ($notifications as $notification) {
1253 $bodynotifications .= $this->render_from_template(
1254 $notification->get_template_name(),
1255 $notification->export_for_template($this)
1256 );
1257 }
1258
1259 $output = html_writer::span($bodynotifications, 'notifications', array('id' => 'user-notifications'));
1260
1261 if ($this->page->course->id == SITEID) {
1262 // return immediately and do not include /course/lib.php if not necessary
1263 return $output;
1264 }
1265
fdd4b9a5
MG
1266 require_once($CFG->dirroot.'/course/lib.php');
1267 $functioncalled = true;
1268 $courseformat = course_get_format($this->page->course);
1269 if (($obj = $courseformat->course_content_header()) !== null) {
0346323c 1270 $output .= html_writer::div($courseformat->get_renderer($this->page)->render($obj), 'course-content-header');
fdd4b9a5 1271 }
0346323c 1272 return $output;
fdd4b9a5
MG
1273 }
1274
1275 /**
1276 * Returns course-specific information to be output immediately below content on any course page
1277 * (for the current course)
1278 *
1279 * @param bool $onlyifnotcalledbefore output content only if it has not been output before
1280 * @return string
1281 */
1282 public function course_content_footer($onlyifnotcalledbefore = false) {
1283 global $CFG;
1284 if ($this->page->course->id == SITEID) {
1285 // return immediately and do not include /course/lib.php if not necessary
1286 return '';
1287 }
1288 static $functioncalled = false;
1289 if ($functioncalled && $onlyifnotcalledbefore) {
1290 // we have already output the content footer
1291 return '';
1292 }
1293 $functioncalled = true;
1294 require_once($CFG->dirroot.'/course/lib.php');
1295 $courseformat = course_get_format($this->page->course);
1296 if (($obj = $courseformat->course_content_footer()) !== null) {
06a72e01 1297 return html_writer::div($courseformat->get_renderer($this->page)->render($obj), 'course-content-footer');
fdd4b9a5
MG
1298 }
1299 return '';
1300 }
1301
1302 /**
1303 * Returns course-specific information to be output on any course page in the header area
1304 * (for the current course)
1305 *
1306 * @return string
1307 */
1308 public function course_header() {
1309 global $CFG;
1310 if ($this->page->course->id == SITEID) {
1311 // return immediately and do not include /course/lib.php if not necessary
1312 return '';
1313 }
1314 require_once($CFG->dirroot.'/course/lib.php');
1315 $courseformat = course_get_format($this->page->course);
1316 if (($obj = $courseformat->course_header()) !== null) {
1317 return $courseformat->get_renderer($this->page)->render($obj);
1318 }
1319 return '';
1320 }
1321
1322 /**
1323 * Returns course-specific information to be output on any course page in the footer area
1324 * (for the current course)
1325 *
1326 * @return string
1327 */
1328 public function course_footer() {
1329 global $CFG;
1330 if ($this->page->course->id == SITEID) {
1331 // return immediately and do not include /course/lib.php if not necessary
1332 return '';
1333 }
1334 require_once($CFG->dirroot.'/course/lib.php');
1335 $courseformat = course_get_format($this->page->course);
1336 if (($obj = $courseformat->course_footer()) !== null) {
1337 return $courseformat->get_renderer($this->page)->render($obj);
1338 }
1339 return '';
1340 }
1341
244a32c6
PS
1342 /**
1343 * Returns lang menu or '', this method also checks forcing of languages in courses.
7a3c215b 1344 *
2fada290
MG
1345 * This function calls {@link core_renderer::render_single_select()} to actually display the language menu.
1346 *
7a3c215b 1347 * @return string The lang menu HTML or empty string
244a32c6
PS
1348 */
1349 public function lang_menu() {
1350 global $CFG;
1351
1352 if (empty($CFG->langmenu)) {
1353 return '';
1354 }
1355
1356 if ($this->page->course != SITEID and !empty($this->page->course->lang)) {
1357 // do not show lang menu if language forced
1358 return '';
1359 }
1360
1361 $currlang = current_language();
1f96e907 1362 $langs = get_string_manager()->get_list_of_translations();
4bcc5118 1363
244a32c6
PS
1364 if (count($langs) < 2) {
1365 return '';
1366 }
1367
a9967cf5
PS
1368 $s = new single_select($this->page->url, 'lang', $langs, $currlang, null);
1369 $s->label = get_accesshide(get_string('language'));
1370 $s->class = 'langmenu';
1371 return $this->render($s);
244a32c6
PS
1372 }
1373
d9c8f425 1374 /**
1375 * Output the row of editing icons for a block, as defined by the controls array.
7a3c215b 1376 *
f8129210 1377 * @param array $controls an array like {@link block_contents::$controls}.
b59f2e3b 1378 * @param string $blockid The ID given to the block.
7a3c215b 1379 * @return string HTML fragment.
d9c8f425 1380 */
b59f2e3b 1381 public function block_controls($actions, $blockid = null) {
10fc1569 1382 global $CFG;
b59f2e3b
SH
1383 if (empty($actions)) {
1384 return '';
1385 }
cf69a00a 1386 $menu = new action_menu($actions);
b59f2e3b
SH
1387 if ($blockid !== null) {
1388 $menu->set_owner_selector('#'.$blockid);
1389 }
ae3fd8eb 1390 $menu->set_constraint('.block-region');
b59f2e3b
SH
1391 $menu->attributes['class'] .= ' block-control-actions commands';
1392 return $this->render($menu);
1393 }
1394
1395 /**
1396 * Renders an action menu component.
1397 *
3665af78
SH
1398 * ARIA references:
1399 * - http://www.w3.org/WAI/GL/wiki/Using_ARIA_menus
1400 * - http://stackoverflow.com/questions/12279113/recommended-wai-aria-implementation-for-navigation-bar-menu
1401 *
b59f2e3b
SH
1402 * @param action_menu $menu
1403 * @return string HTML
1404 */
1405 public function render_action_menu(action_menu $menu) {
f15024c1
FM
1406 $context = $menu->export_for_template($this);
1407 return $this->render_from_template('core/action_menu', $context);
d9c8f425 1408 }
1409
cf69a00a 1410 /**
3665af78 1411 * Renders an action_menu_link item.
cf69a00a 1412 *
3665af78 1413 * @param action_menu_link $action
cf69a00a
SH
1414 * @return string HTML fragment
1415 */
3665af78 1416 protected function render_action_menu_link(action_menu_link $action) {
f15024c1 1417 return $this->render_from_template('core/action_menu_link', $action->export_for_template($this));
cf69a00a
SH
1418 }
1419
9ac099a1
AN
1420 /**
1421 * Renders a primary action_menu_filler item.
1422 *
1423 * @param action_menu_link_filler $action
1424 * @return string HTML fragment
1425 */
1426 protected function render_action_menu_filler(action_menu_filler $action) {
1427 return html_writer::span('&nbsp;', 'filler');
1428 }
1429
cf69a00a 1430 /**
3665af78 1431 * Renders a primary action_menu_link item.
cf69a00a 1432 *
3665af78 1433 * @param action_menu_link_primary $action
cf69a00a
SH
1434 * @return string HTML fragment
1435 */
3665af78
SH
1436 protected function render_action_menu_link_primary(action_menu_link_primary $action) {
1437 return $this->render_action_menu_link($action);
cf69a00a
SH
1438 }
1439
1440 /**
3665af78 1441 * Renders a secondary action_menu_link item.
cf69a00a 1442 *
3665af78 1443 * @param action_menu_link_secondary $action
cf69a00a
SH
1444 * @return string HTML fragment
1445 */
3665af78
SH
1446 protected function render_action_menu_link_secondary(action_menu_link_secondary $action) {
1447 return $this->render_action_menu_link($action);
cf69a00a
SH
1448 }
1449
d9c8f425 1450 /**
1451 * Prints a nice side block with an optional header.
1452 *
1453 * The content is described
f8129210 1454 * by a {@link core_renderer::block_contents} object.
d9c8f425 1455 *
cbb54cce
SH
1456 * <div id="inst{$instanceid}" class="block_{$blockname} block">
1457 * <div class="header"></div>
1458 * <div class="content">
1459 * ...CONTENT...
1460 * <div class="footer">
1461 * </div>
1462 * </div>
1463 * <div class="annotation">
1464 * </div>
1465 * </div>
1466 *
d9c8f425 1467 * @param block_contents $bc HTML for the content
1468 * @param string $region the region the block is appearing in.
1469 * @return string the HTML to be output.
1470 */
7a3c215b 1471 public function block(block_contents $bc, $region) {
d9c8f425 1472 $bc = clone($bc); // Avoid messing up the object passed in.
dd72b308
PS
1473 if (empty($bc->blockinstanceid) || !strip_tags($bc->title)) {
1474 $bc->collapsible = block_contents::NOT_HIDEABLE;
1475 }
84192d78
SH
1476 if (!empty($bc->blockinstanceid)) {
1477 $bc->attributes['data-instanceid'] = $bc->blockinstanceid;
1478 }
91d941c3
SH
1479 $skiptitle = strip_tags($bc->title);
1480 if ($bc->blockinstanceid && !empty($skiptitle)) {
1481 $bc->attributes['aria-labelledby'] = 'instance-'.$bc->blockinstanceid.'-header';
1482 } else if (!empty($bc->arialabel)) {
1483 $bc->attributes['aria-label'] = $bc->arialabel;
1484 }
84192d78
SH
1485 if ($bc->dockable) {
1486 $bc->attributes['data-dockable'] = 1;
1487 }
dd72b308
PS
1488 if ($bc->collapsible == block_contents::HIDDEN) {
1489 $bc->add_class('hidden');
1490 }
1491 if (!empty($bc->controls)) {
1492 $bc->add_class('block_with_controls');
1493 }
d9c8f425 1494
91d941c3 1495
d9c8f425 1496 if (empty($skiptitle)) {
1497 $output = '';
1498 $skipdest = '';
1499 } else {
9e8d0842
AG
1500 $output = html_writer::link('#sb-'.$bc->skipid, get_string('skipa', 'access', $skiptitle),
1501 array('class' => 'skip skip-block', 'id' => 'fsb-' . $bc->skipid));
0b7856ef 1502 $skipdest = html_writer::span('', 'skip-block-to',
9e8d0842 1503 array('id' => 'sb-' . $bc->skipid));
d9c8f425 1504 }
4d2ee4c2 1505
5d0c95a5 1506 $output .= html_writer::start_tag('div', $bc->attributes);
d9c8f425 1507
9f5c39b5
SH
1508 $output .= $this->block_header($bc);
1509 $output .= $this->block_content($bc);
1510
1511 $output .= html_writer::end_tag('div');
1512
1513 $output .= $this->block_annotation($bc);
1514
1515 $output .= $skipdest;
1516
1517 $this->init_block_hider_js($bc);
1518 return $output;
1519 }
1520
1521 /**
1522 * Produces a header for a block
fa7f2a45 1523 *
9f5c39b5
SH
1524 * @param block_contents $bc
1525 * @return string
1526 */
1527 protected function block_header(block_contents $bc) {
d9c8f425 1528
1529 $title = '';
1530 if ($bc->title) {
91d941c3
SH
1531 $attributes = array();
1532 if ($bc->blockinstanceid) {
1533 $attributes['id'] = 'instance-'.$bc->blockinstanceid.'-header';
1534 }
1535 $title = html_writer::tag('h2', $bc->title, $attributes);
d9c8f425 1536 }
1537
b59f2e3b
SH
1538 $blockid = null;
1539 if (isset($bc->attributes['id'])) {
1540 $blockid = $bc->attributes['id'];
1541 }
1542 $controlshtml = $this->block_controls($bc->controls, $blockid);
9f5c39b5
SH
1543
1544 $output = '';
d9c8f425 1545 if ($title || $controlshtml) {
46de77b6 1546 $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'));
d9c8f425 1547 }
9f5c39b5
SH
1548 return $output;
1549 }
d9c8f425 1550
9f5c39b5
SH
1551 /**
1552 * Produces the content area for a block
1553 *
1554 * @param block_contents $bc
1555 * @return string
1556 */
1557 protected function block_content(block_contents $bc) {
1558 $output = html_writer::start_tag('div', array('class' => 'content'));
1559 if (!$bc->title && !$this->block_controls($bc->controls)) {
46de77b6
SH
1560 $output .= html_writer::tag('div', '', array('class'=>'block_action notitle'));
1561 }
d9c8f425 1562 $output .= $bc->content;
9f5c39b5
SH
1563 $output .= $this->block_footer($bc);
1564 $output .= html_writer::end_tag('div');
fa7f2a45 1565
9f5c39b5
SH
1566 return $output;
1567 }
d9c8f425 1568
9f5c39b5
SH
1569 /**
1570 * Produces the footer for a block
1571 *
1572 * @param block_contents $bc
1573 * @return string
1574 */
1575 protected function block_footer(block_contents $bc) {
1576 $output = '';
d9c8f425 1577 if ($bc->footer) {
26acc814 1578 $output .= html_writer::tag('div', $bc->footer, array('class' => 'footer'));
d9c8f425 1579 }
9f5c39b5
SH
1580 return $output;
1581 }
d9c8f425 1582
9f5c39b5
SH
1583 /**
1584 * Produces the annotation for a block
1585 *
1586 * @param block_contents $bc
1587 * @return string
1588 */
1589 protected function block_annotation(block_contents $bc) {
1590 $output = '';
d9c8f425 1591 if ($bc->annotation) {
26acc814 1592 $output .= html_writer::tag('div', $bc->annotation, array('class' => 'blockannotation'));
d9c8f425 1593 }
d9c8f425 1594 return $output;
1595 }
1596
1597 /**
1598 * Calls the JS require function to hide a block.
7a3c215b 1599 *
d9c8f425 1600 * @param block_contents $bc A block_contents object
d9c8f425 1601 */
dd72b308
PS
1602 protected function init_block_hider_js(block_contents $bc) {
1603 if (!empty($bc->attributes['id']) and $bc->collapsible != block_contents::NOT_HIDEABLE) {
cbb54cce
SH
1604 $config = new stdClass;
1605 $config->id = $bc->attributes['id'];
1606 $config->title = strip_tags($bc->title);
1607 $config->preference = 'block' . $bc->blockinstanceid . 'hidden';
1608 $config->tooltipVisible = get_string('hideblocka', 'access', $config->title);
1609 $config->tooltipHidden = get_string('showblocka', 'access', $config->title);
1610
1611 $this->page->requires->js_init_call('M.util.init_block_hider', array($config));
1612 user_preference_allow_ajax_update($config->preference, PARAM_BOOL);
d9c8f425 1613 }
1614 }
1615
1616 /**
1617 * Render the contents of a block_list.
7a3c215b 1618 *
d9c8f425 1619 * @param array $icons the icon for each item.
1620 * @param array $items the content of each item.
1621 * @return string HTML
1622 */
1623 public function list_block_contents($icons, $items) {
1624 $row = 0;
1625 $lis = array();
1626 foreach ($items as $key => $string) {
5d0c95a5 1627 $item = html_writer::start_tag('li', array('class' => 'r' . $row));
2c5ec833 1628 if (!empty($icons[$key])) { //test if the content has an assigned icon
26acc814 1629 $item .= html_writer::tag('div', $icons[$key], array('class' => 'icon column c0'));
d9c8f425 1630 }
26acc814 1631 $item .= html_writer::tag('div', $string, array('class' => 'column c1'));
5d0c95a5 1632 $item .= html_writer::end_tag('li');
d9c8f425 1633 $lis[] = $item;
1634 $row = 1 - $row; // Flip even/odd.
1635 }
f2a04fc1 1636 return html_writer::tag('ul', implode("\n", $lis), array('class' => 'unlist'));
d9c8f425 1637 }
1638
1639 /**
1640 * Output all the blocks in a particular region.
7a3c215b 1641 *
d9c8f425 1642 * @param string $region the name of a region on this page.
1643 * @return string the HTML to be output.
1644 */
1645 public function blocks_for_region($region) {
1646 $blockcontents = $this->page->blocks->get_content_for_region($region, $this);
6671fa73
JF
1647 $blocks = $this->page->blocks->get_blocks_for_region($region);
1648 $lastblock = null;
1649 $zones = array();
1650 foreach ($blocks as $block) {
1651 $zones[] = $block->title;
1652 }
d9c8f425 1653 $output = '';
6671fa73 1654
d9c8f425 1655 foreach ($blockcontents as $bc) {
1656 if ($bc instanceof block_contents) {
1657 $output .= $this->block($bc, $region);
6671fa73 1658 $lastblock = $bc->title;
d9c8f425 1659 } else if ($bc instanceof block_move_target) {
686e3b3a 1660 $output .= $this->block_move_target($bc, $zones, $lastblock, $region);
d9c8f425 1661 } else {
1662 throw new coding_exception('Unexpected type of thing (' . get_class($bc) . ') found in list of block contents.');
1663 }
1664 }
1665 return $output;
1666 }
1667
1668 /**
1669 * Output a place where the block that is currently being moved can be dropped.
7a3c215b 1670 *
d9c8f425 1671 * @param block_move_target $target with the necessary details.
6671fa73
JF
1672 * @param array $zones array of areas where the block can be moved to
1673 * @param string $previous the block located before the area currently being rendered.
686e3b3a 1674 * @param string $region the name of the region
d9c8f425 1675 * @return string the HTML to be output.
1676 */
686e3b3a 1677 public function block_move_target($target, $zones, $previous, $region) {
0e2ca62e 1678 if ($previous == null) {
686e3b3a
FM
1679 if (empty($zones)) {
1680 // There are no zones, probably because there are no blocks.
1681 $regions = $this->page->theme->get_all_block_regions();
1682 $position = get_string('moveblockinregion', 'block', $regions[$region]);
1683 } else {
1684 $position = get_string('moveblockbefore', 'block', $zones[0]);
1685 }
6671fa73
JF
1686 } else {
1687 $position = get_string('moveblockafter', 'block', $previous);
1688 }
1689 return html_writer::tag('a', html_writer::tag('span', $position, array('class' => 'accesshide')), array('href' => $target->url, 'class' => 'blockmovetarget'));
d9c8f425 1690 }
1691
574fbea4 1692 /**
996b1e0c 1693 * Renders a special html link with attached action
574fbea4 1694 *
2fada290
MG
1695 * Theme developers: DO NOT OVERRIDE! Please override function
1696 * {@link core_renderer::render_action_link()} instead.
1697 *
574fbea4
PS
1698 * @param string|moodle_url $url
1699 * @param string $text HTML fragment
1700 * @param component_action $action
11820bac 1701 * @param array $attributes associative array of html link attributes + disabled
e282c679 1702 * @param pix_icon optional pix icon to render with the link
7a3c215b 1703 * @return string HTML fragment
574fbea4 1704 */
e282c679 1705 public function action_link($url, $text, component_action $action = null, array $attributes = null, $icon = null) {
574fbea4
PS
1706 if (!($url instanceof moodle_url)) {
1707 $url = new moodle_url($url);
1708 }
e282c679 1709 $link = new action_link($url, $text, $action, $attributes, $icon);
574fbea4 1710
f14b641b 1711 return $this->render($link);
574fbea4
PS
1712 }
1713
1714 /**
7a3c215b
SH
1715 * Renders an action_link object.
1716 *
1717 * The provided link is renderer and the HTML returned. At the same time the
f8129210 1718 * associated actions are setup in JS by {@link core_renderer::add_action_handler()}
7a3c215b 1719 *
574fbea4
PS
1720 * @param action_link $link
1721 * @return string HTML fragment
1722 */
1723 protected function render_action_link(action_link $link) {
11ba3fd4 1724 return $this->render_from_template('core/action_link', $link->export_for_template($this));
574fbea4
PS
1725 }
1726
c63923bd 1727 /**
7a3c215b
SH
1728 * Renders an action_icon.
1729 *
f8129210 1730 * This function uses the {@link core_renderer::action_link()} method for the
7a3c215b
SH
1731 * most part. What it does different is prepare the icon as HTML and use it
1732 * as the link text.
c63923bd 1733 *
2fada290
MG
1734 * Theme developers: If you want to change how action links and/or icons are rendered,
1735 * consider overriding function {@link core_renderer::render_action_link()} and
1736 * {@link core_renderer::render_pix_icon()}.
1737 *
c63923bd
PS
1738 * @param string|moodle_url $url A string URL or moodel_url
1739 * @param pix_icon $pixicon
1740 * @param component_action $action
1741 * @param array $attributes associative array of html link attributes + disabled
1742 * @param bool $linktext show title next to image in link
1743 * @return string HTML fragment
1744 */
1745 public function action_icon($url, pix_icon $pixicon, component_action $action = null, array $attributes = null, $linktext=false) {
1746 if (!($url instanceof moodle_url)) {
1747 $url = new moodle_url($url);
1748 }
1749 $attributes = (array)$attributes;
1750
524645e7 1751 if (empty($attributes['class'])) {
c63923bd
PS
1752 // let ppl override the class via $options
1753 $attributes['class'] = 'action-icon';
1754 }
1755
1756 $icon = $this->render($pixicon);
1757
1758 if ($linktext) {
1759 $text = $pixicon->attributes['alt'];
1760 } else {
1761 $text = '';
1762 }
1763
1764 return $this->action_link($url, $text.$icon, $action, $attributes);
1765 }
1766
d9c8f425 1767 /**
0b634d75 1768 * Print a message along with button choices for Continue/Cancel
1769 *
4ed85790 1770 * If a string or moodle_url is given instead of a single_button, method defaults to post.
0b634d75 1771 *
d9c8f425 1772 * @param string $message The question to ask the user
3ba60ee1
PS
1773 * @param single_button|moodle_url|string $continue The single_button component representing the Continue answer. Can also be a moodle_url or string URL
1774 * @param single_button|moodle_url|string $cancel The single_button component representing the Cancel answer. Can also be a moodle_url or string URL
d9c8f425 1775 * @return string HTML fragment
1776 */
1777 public function confirm($message, $continue, $cancel) {
4871a238 1778 if ($continue instanceof single_button) {
11820bac 1779 // ok
f2405a5f 1780 $continue->primary = true;
4871a238 1781 } else if (is_string($continue)) {
f2405a5f 1782 $continue = new single_button(new moodle_url($continue), get_string('continue'), 'post', true);
4871a238 1783 } else if ($continue instanceof moodle_url) {
f2405a5f 1784 $continue = new single_button($continue, get_string('continue'), 'post', true);
d9c8f425 1785 } else {
4ed85790 1786 throw new coding_exception('The continue param to $OUTPUT->confirm() must be either a URL (string/moodle_url) or a single_button instance.');
d9c8f425 1787 }
1788
4871a238 1789 if ($cancel instanceof single_button) {
11820bac 1790 // ok
4871a238
PS
1791 } else if (is_string($cancel)) {
1792 $cancel = new single_button(new moodle_url($cancel), get_string('cancel'), 'get');
1793 } else if ($cancel instanceof moodle_url) {
26eab8d4 1794 $cancel = new single_button($cancel, get_string('cancel'), 'get');
d9c8f425 1795 } else {
4ed85790 1796 throw new coding_exception('The cancel param to $OUTPUT->confirm() must be either a URL (string/moodle_url) or a single_button instance.');
d9c8f425 1797 }
1798
f2405a5f
DW
1799 $output = $this->box_start('generalbox modal modal-dialog modal-in-page show', 'notice');
1800 $output .= $this->box_start('modal-content', 'modal-content');
1801 $output .= $this->box_start('modal-header', 'modal-header');
1802 $output .= html_writer::tag('h4', get_string('confirm'));
1803 $output .= $this->box_end();
1804 $output .= $this->box_start('modal-body', 'modal-body');
26acc814 1805 $output .= html_writer::tag('p', $message);
f2405a5f
DW
1806 $output .= $this->box_end();
1807 $output .= $this->box_start('modal-footer', 'modal-footer');
26acc814 1808 $output .= html_writer::tag('div', $this->render($continue) . $this->render($cancel), array('class' => 'buttons'));
d9c8f425 1809 $output .= $this->box_end();
f2405a5f
DW
1810 $output .= $this->box_end();
1811 $output .= $this->box_end();
d9c8f425 1812 return $output;
1813 }
1814
3cd5305f 1815 /**
3ba60ee1 1816 * Returns a form with a single button.
3cd5305f 1817 *
2fada290
MG
1818 * Theme developers: DO NOT OVERRIDE! Please override function
1819 * {@link core_renderer::render_single_button()} instead.
1820 *
3ba60ee1 1821 * @param string|moodle_url $url
3cd5305f
PS
1822 * @param string $label button text
1823 * @param string $method get or post submit method
3ba60ee1 1824 * @param array $options associative array {disabled, title, etc.}
3cd5305f
PS
1825 * @return string HTML fragment
1826 */
3ba60ee1 1827 public function single_button($url, $label, $method='post', array $options=null) {
574fbea4
PS
1828 if (!($url instanceof moodle_url)) {
1829 $url = new moodle_url($url);
3ba60ee1 1830 }
574fbea4
PS
1831 $button = new single_button($url, $label, $method);
1832
3ba60ee1
PS
1833 foreach ((array)$options as $key=>$value) {
1834 if (array_key_exists($key, $button)) {
1835 $button->$key = $value;
1836 }
3cd5305f
PS
1837 }
1838
3ba60ee1 1839 return $this->render($button);
3cd5305f
PS
1840 }
1841
d9c8f425 1842 /**
7a3c215b
SH
1843 * Renders a single button widget.
1844 *
1845 * This will return HTML to display a form containing a single button.
1846 *
3ba60ee1 1847 * @param single_button $button
d9c8f425 1848 * @return string HTML fragment
1849 */
3ba60ee1
PS
1850 protected function render_single_button(single_button $button) {
1851 $attributes = array('type' => 'submit',
1852 'value' => $button->label,
db09524d 1853 'disabled' => $button->disabled ? 'disabled' : null,
3ba60ee1
PS
1854 'title' => $button->tooltip);
1855
1856 if ($button->actions) {
1857 $id = html_writer::random_id('single_button');
1858 $attributes['id'] = $id;
1859 foreach ($button->actions as $action) {
c80877aa 1860 $this->add_action_handler($action, $id);
3ba60ee1 1861 }
d9c8f425 1862 }
d9c8f425 1863
3ba60ee1
PS
1864 // first the input element
1865 $output = html_writer::empty_tag('input', $attributes);
d9c8f425 1866
3ba60ee1
PS
1867 // then hidden fields
1868 $params = $button->url->params();
1869 if ($button->method === 'post') {
1870 $params['sesskey'] = sesskey();
1871 }
1872 foreach ($params as $var => $val) {
1873 $output .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => $var, 'value' => $val));
1874 }
d9c8f425 1875
3ba60ee1 1876 // then div wrapper for xhtml strictness
26acc814 1877 $output = html_writer::tag('div', $output);
d9c8f425 1878
3ba60ee1 1879 // now the form itself around it
a12cd69c
DM
1880 if ($button->method === 'get') {
1881 $url = $button->url->out_omit_querystring(true); // url without params, the anchor part allowed
1882 } else {
1883 $url = $button->url->out_omit_querystring(); // url without params, the anchor part not allowed
1884 }
a6855934
PS
1885 if ($url === '') {
1886 $url = '#'; // there has to be always some action
1887 }
3ba60ee1 1888 $attributes = array('method' => $button->method,
a6855934 1889 'action' => $url,
3ba60ee1 1890 'id' => $button->formid);
26acc814 1891 $output = html_writer::tag('form', $output, $attributes);
d9c8f425 1892
3ba60ee1 1893 // and finally one more wrapper with class
26acc814 1894 return html_writer::tag('div', $output, array('class' => $button->class));
d9c8f425 1895 }
1896
a9967cf5 1897 /**
ab08be98 1898 * Returns a form with a single select widget.
7a3c215b 1899 *
2fada290
MG
1900 * Theme developers: DO NOT OVERRIDE! Please override function
1901 * {@link core_renderer::render_single_select()} instead.
1902 *
a9967cf5
PS
1903 * @param moodle_url $url form action target, includes hidden fields
1904 * @param string $name name of selection field - the changing parameter in url
1905 * @param array $options list of options
1906 * @param string $selected selected element
1907 * @param array $nothing
f8dab966 1908 * @param string $formid
32bd11cb 1909 * @param array $attributes other attributes for the single select
a9967cf5
PS
1910 * @return string HTML fragment
1911 */
32bd11cb
BB
1912 public function single_select($url, $name, array $options, $selected = '',
1913 $nothing = array('' => 'choosedots'), $formid = null, $attributes = array()) {
a9967cf5
PS
1914 if (!($url instanceof moodle_url)) {
1915 $url = new moodle_url($url);
1916 }
f8dab966 1917 $select = new single_select($url, $name, $options, $selected, $nothing, $formid);
a9967cf5 1918
32bd11cb
BB
1919 if (array_key_exists('label', $attributes)) {
1920 $select->set_label($attributes['label']);
1921 unset($attributes['label']);
1922 }
1923 $select->attributes = $attributes;
1924
a9967cf5
PS
1925 return $this->render($select);
1926 }
1927
bff1edbe
BH
1928 /**
1929 * Returns a dataformat selection and download form
1930 *
1931 * @param string $label A text label
1932 * @param moodle_url|string $base The download page url
1933 * @param string $name The query param which will hold the type of the download
1934 * @param array $params Extra params sent to the download page
1935 * @return string HTML fragment
1936 */
1937 public function download_dataformat_selector($label, $base, $name = 'dataformat', $params = array()) {
1938
1939 $formats = core_plugin_manager::instance()->get_plugins_of_type('dataformat');
1940 $options = array();
1941 foreach ($formats as $format) {
1942 if ($format->is_enabled()) {
1943 $options[] = array(
1944 'value' => $format->name,
fef11147 1945 'label' => get_string('dataformat', $format->component),
bff1edbe
BH
1946 );
1947 }
1948 }
1949 $hiddenparams = array();
1950 foreach ($params as $key => $value) {
1951 $hiddenparams[] = array(
1952 'name' => $key,
1953 'value' => $value,
1954 );
1955 }
1956 $data = array(
1957 'label' => $label,
1958 'base' => $base,
1959 'name' => $name,
1960 'params' => $hiddenparams,
1961 'options' => $options,
1962 'sesskey' => sesskey(),
1963 'submit' => get_string('download'),
1964 );
1965
1966 return $this->render_from_template('core/dataformat_selector', $data);
1967 }
1968
1969
a9967cf5
PS
1970 /**
1971 * Internal implementation of single_select rendering
7a3c215b 1972 *
a9967cf5
PS
1973 * @param single_select $select
1974 * @return string HTML fragment
1975 */
1976 protected function render_single_select(single_select $select) {
c67e93b2 1977 return $this->render_from_template('core/single_select', $select->export_for_template($this));
4d10e579
PS
1978 }
1979
1980 /**
ab08be98 1981 * Returns a form with a url select widget.
7a3c215b 1982 *
2fada290
MG
1983 * Theme developers: DO NOT OVERRIDE! Please override function
1984 * {@link core_renderer::render_url_select()} instead.
1985 *
4d10e579
PS
1986 * @param array $urls list of urls - array('/course/view.php?id=1'=>'Frontpage', ....)
1987 * @param string $selected selected element
1988 * @param array $nothing
1989 * @param string $formid
1990 * @return string HTML fragment
1991 */
7a3c215b 1992 public function url_select(array $urls, $selected, $nothing = array('' => 'choosedots'), $formid = null) {
4d10e579
PS
1993 $select = new url_select($urls, $selected, $nothing, $formid);
1994 return $this->render($select);
1995 }
1996
1997 /**
ab08be98 1998 * Internal implementation of url_select rendering
7a3c215b
SH
1999 *
2000 * @param url_select $select
4d10e579
PS
2001 * @return string HTML fragment
2002 */
2003 protected function render_url_select(url_select $select) {
c67e93b2 2004 return $this->render_from_template('core/url_select', $select->export_for_template($this));
a9967cf5
PS
2005 }
2006
d9c8f425 2007 /**
2008 * Returns a string containing a link to the user documentation.
2009 * Also contains an icon by default. Shown to teachers and admin only.
7a3c215b 2010 *
d9c8f425 2011 * @param string $path The page link after doc root and language, no leading slash.
2012 * @param string $text The text to be displayed for the link
afe3566c 2013 * @param boolean $forcepopup Whether to force a popup regardless of the value of $CFG->doctonewwindow
996b1e0c 2014 * @return string
d9c8f425 2015 */
afe3566c 2016 public function doc_link($path, $text = '', $forcepopup = false) {
8ae8bf8a
PS
2017 global $CFG;
2018
ae4086bd 2019 $icon = $this->pix_icon('docs', '', 'moodle', array('class'=>'iconhelp icon-pre', 'role'=>'presentation'));
8ae8bf8a 2020
000c278c 2021 $url = new moodle_url(get_docs_url($path));
8ae8bf8a 2022
c80877aa 2023 $attributes = array('href'=>$url);
afe3566c
ARN
2024 if (!empty($CFG->doctonewwindow) || $forcepopup) {
2025 $attributes['class'] = 'helplinkpopup';
d9c8f425 2026 }
1adaa404 2027
26acc814 2028 return html_writer::tag('a', $icon.$text, $attributes);
d9c8f425 2029 }
2030
c2dde7ee
DW
2031 /**
2032 * Return HTML for an activity_icon.
2033 *
2034 * Theme developers: DO NOT OVERRIDE! Please override function
2035 * {@link core_renderer::render_activity_icon()} instead.
2036 *
2037 * @param string $pix short pix name
2038 * @param string $alt mandatory alt attribute
2039 * @param string $component standard compoennt name like 'moodle', 'mod_forum', etc.
2040 * @param array $attributes htm lattributes
2041 * @return string HTML fragment
2042 */
2043 public function activity_icon($pix, $alt, $component='moodle', array $attributes = null) {
2044 $icon = new activity_icon($pix, $alt, $component, $attributes);
2045 return $this->render($icon);
2046 }
2047
2048 /**
2049 * Renders a pix_icon widget and returns the HTML to display it.
2050 *
2051 * @param pix_icon $icon
2052 * @return string HTML fragment
2053 */
2054 protected function render_activity_icon(activity_icon $icon) {
2055 global $PAGE;
2056
e330b1c2 2057 $system = \core\output\icon_system::instance(\core\output\icon_system::STANDARD);
c2dde7ee
DW
2058 return $system->render_pix_icon($this, $icon);
2059 }
2060
000c278c 2061 /**
7a3c215b
SH
2062 * Return HTML for a pix_icon.
2063 *
2fada290
MG
2064 * Theme developers: DO NOT OVERRIDE! Please override function
2065 * {@link core_renderer::render_pix_icon()} instead.
2066 *
000c278c
PS
2067 * @param string $pix short pix name
2068 * @param string $alt mandatory alt attribute
eb557002 2069 * @param string $component standard compoennt name like 'moodle', 'mod_forum', etc.
000c278c
PS
2070 * @param array $attributes htm lattributes
2071 * @return string HTML fragment
2072 */
2073 public function pix_icon($pix, $alt, $component='moodle', array $attributes = null) {
2074 $icon = new pix_icon($pix, $alt, $component, $attributes);
2075 return $this->render($icon);
2076 }
2077
2078 /**
7a3c215b
SH
2079 * Renders a pix_icon widget and returns the HTML to display it.
2080 *
000c278c
PS
2081 * @param pix_icon $icon
2082 * @return string HTML fragment
2083 */
ce0110bf 2084 protected function render_pix_icon(pix_icon $icon) {
95b06c13
DW
2085 global $PAGE;
2086
2087 $system = \core\output\icon_system::instance();
2088 return $system->render_pix_icon($this, $icon);
000c278c
PS
2089 }
2090
d63c5073 2091 /**
7a3c215b
SH
2092 * Return HTML to display an emoticon icon.
2093 *
d63c5073
DM
2094 * @param pix_emoticon $emoticon
2095 * @return string HTML fragment
2096 */
2097 protected function render_pix_emoticon(pix_emoticon $emoticon) {
663640f5 2098 return $this->render_pix_icon($emoticon);
d63c5073
DM
2099 }
2100
a09aeee4 2101 /**
7a3c215b
SH
2102 * Produces the html that represents this rating in the UI
2103 *
2104 * @param rating $rating the page object on which this rating will appear
2105 * @return string
2106 */
a09aeee4 2107 function render_rating(rating $rating) {
7ac928a7 2108 global $CFG, $USER;
a09aeee4 2109
2b04c41c 2110 if ($rating->settings->aggregationmethod == RATING_AGGREGATE_NONE) {
63e87951
AD
2111 return null;//ratings are turned off
2112 }
2113
2b04c41c
SH
2114 $ratingmanager = new rating_manager();
2115 // Initialise the JavaScript so ratings can be done by AJAX.
2116 $ratingmanager->initialise_rating_javascript($this->page);
a09aeee4 2117
63e87951
AD
2118 $strrate = get_string("rate", "rating");
2119 $ratinghtml = ''; //the string we'll return
2120
2b04c41c
SH
2121 // permissions check - can they view the aggregate?
2122 if ($rating->user_can_view_aggregate()) {
a09aeee4 2123
2b04c41c
SH
2124 $aggregatelabel = $ratingmanager->get_aggregate_label($rating->settings->aggregationmethod);
2125 $aggregatestr = $rating->get_aggregate_string();
a09aeee4 2126
6278ce45 2127 $aggregatehtml = html_writer::tag('span', $aggregatestr, array('id' => 'ratingaggregate'.$rating->itemid, 'class' => 'ratingaggregate')).' ';
2b04c41c 2128 if ($rating->count > 0) {
6278ce45 2129 $countstr = "({$rating->count})";
d251b259 2130 } else {
6278ce45 2131 $countstr = '-';
d251b259 2132 }
6278ce45 2133 $aggregatehtml .= html_writer::tag('span', $countstr, array('id'=>"ratingcount{$rating->itemid}", 'class' => 'ratingcount')).' ';
63e87951 2134
c6de9cef 2135 $ratinghtml .= html_writer::tag('span', $aggregatelabel, array('class'=>'rating-aggregate-label'));
d251b259 2136 if ($rating->settings->permissions->viewall && $rating->settings->pluginpermissions->viewall) {
2b04c41c
SH
2137
2138 $nonpopuplink = $rating->get_view_ratings_url();
2139 $popuplink = $rating->get_view_ratings_url(true);
a09aeee4 2140
d251b259 2141 $action = new popup_action('click', $popuplink, 'ratings', array('height' => 400, 'width' => 600));
c6de9cef 2142 $ratinghtml .= $this->action_link($nonpopuplink, $aggregatehtml, $action);
d251b259 2143 } else {
c6de9cef 2144 $ratinghtml .= $aggregatehtml;
a09aeee4 2145 }
d251b259 2146 }
a09aeee4 2147
d251b259 2148 $formstart = null;
2b04c41c
SH
2149 // if the item doesn't belong to the current user, the user has permission to rate
2150 // and we're within the assessable period
2151 if ($rating->user_can_rate()) {
771b3fbe 2152
2b04c41c
SH
2153 $rateurl = $rating->get_rate_url();
2154 $inputs = $rateurl->params();
771b3fbe 2155
2b04c41c
SH
2156 //start the rating form
2157 $formattrs = array(
2158 'id' => "postrating{$rating->itemid}",
2159 'class' => 'postratingform',
2160 'method' => 'post',
2161 'action' => $rateurl->out_omit_querystring()
2162 );
2163 $formstart = html_writer::start_tag('form', $formattrs);
2164 $formstart .= html_writer::start_tag('div', array('class' => 'ratingform'));
2165
2166 // add the hidden inputs
2167 foreach ($inputs as $name => $value) {
2168 $attributes = array('type' => 'hidden', 'class' => 'ratinginput', 'name' => $name, 'value' => $value);
2169 $formstart .= html_writer::empty_tag('input', $attributes);
2170 }
3180bc2c 2171
d251b259
AD
2172 if (empty($ratinghtml)) {
2173 $ratinghtml .= $strrate.': ';
2174 }
d251b259 2175 $ratinghtml = $formstart.$ratinghtml;
63e87951 2176
2b04c41c
SH
2177 $scalearray = array(RATING_UNSET_RATING => $strrate.'...') + $rating->settings->scale->scaleitems;
2178 $scaleattrs = array('class'=>'postratingmenu ratinginput','id'=>'menurating'.$rating->itemid);
ecc5cc31 2179 $ratinghtml .= html_writer::label($rating->rating, 'menurating'.$rating->itemid, false, array('class' => 'accesshide'));
2b04c41c 2180 $ratinghtml .= html_writer::select($scalearray, 'rating', $rating->rating, false, $scaleattrs);
a09aeee4 2181
d251b259 2182 //output submit button
771b3fbe
AD
2183 $ratinghtml .= html_writer::start_tag('span', array('class'=>"ratingsubmit"));
2184
2b04c41c 2185 $attributes = array('type' => 'submit', 'class' => 'postratingmenusubmit', 'id' => 'postratingsubmit'.$rating->itemid, 'value' => s(get_string('rate', 'rating')));
771b3fbe 2186 $ratinghtml .= html_writer::empty_tag('input', $attributes);
a09aeee4 2187
2b04c41c 2188 if (!$rating->settings->scale->isnumeric) {
eaf52ff0
MN
2189 // If a global scale, try to find current course ID from the context
2190 if (empty($rating->settings->scale->courseid) and $coursecontext = $rating->context->get_course_context(false)) {
2191 $courseid = $coursecontext->instanceid;
2192 } else {
2193 $courseid = $rating->settings->scale->courseid;
2194 }
2195 $ratinghtml .= $this->help_icon_scale($courseid, $rating->settings->scale);
a09aeee4 2196 }
771b3fbe
AD
2197 $ratinghtml .= html_writer::end_tag('span');
2198 $ratinghtml .= html_writer::end_tag('div');
2199 $ratinghtml .= html_writer::end_tag('form');
a09aeee4
AD
2200 }
2201
63e87951 2202 return $ratinghtml;
a09aeee4
AD
2203 }
2204
7a3c215b 2205 /**
d9c8f425 2206 * Centered heading with attached help button (same title text)
7a3c215b
SH
2207 * and optional icon attached.
2208 *
4bcc5118 2209 * @param string $text A heading text
53a78cef 2210 * @param string $helpidentifier The keyword that defines a help page
4bcc5118
PS
2211 * @param string $component component name
2212 * @param string|moodle_url $icon
2213 * @param string $iconalt icon alt text
699e2fd0
RW
2214 * @param int $level The level of importance of the heading. Defaulting to 2
2215 * @param string $classnames A space-separated list of CSS classes. Defaulting to null
d9c8f425 2216 * @return string HTML fragment
2217 */
699e2fd0 2218 public function heading_with_help($text, $helpidentifier, $component = 'moodle', $icon = '', $iconalt = '', $level = 2, $classnames = null) {
4bcc5118
PS
2219 $image = '';
2220 if ($icon) {
8ef1aa40 2221 $image = $this->pix_icon($icon, $iconalt, $component, array('class'=>'icon iconlarge'));
d9c8f425 2222 }
4bcc5118 2223
259c165d
PS
2224 $help = '';
2225 if ($helpidentifier) {
2226 $help = $this->help_icon($helpidentifier, $component);
2227 }
4bcc5118 2228
699e2fd0 2229 return $this->heading($image.$text.$help, $level, $classnames);
d9c8f425 2230 }
2231
2232 /**
7a3c215b 2233 * Returns HTML to display a help icon.
d9c8f425 2234 *
cb616be8 2235 * @deprecated since Moodle 2.0
bf11293a 2236 */
596509e4 2237 public function old_help_icon($helpidentifier, $title, $component = 'moodle', $linktext = '') {
a6d81a73 2238 throw new coding_exception('old_help_icon() can not be used any more, please see help_icon().');
d9c8f425 2239 }
2240
259c165d 2241 /**
7a3c215b 2242 * Returns HTML to display a help icon.
259c165d 2243 *
2fada290
MG
2244 * Theme developers: DO NOT OVERRIDE! Please override function
2245 * {@link core_renderer::render_help_icon()} instead.
2246 *
259c165d
PS
2247 * @param string $identifier The keyword that defines a help page
2248 * @param string $component component name
2249 * @param string|bool $linktext true means use $title as link text, string means link text value
2250 * @return string HTML fragment
2251 */
2252 public function help_icon($identifier, $component = 'moodle', $linktext = '') {
2cf81209 2253 $icon = new help_icon($identifier, $component);
259c165d
PS
2254 $icon->diag_strings();
2255 if ($linktext === true) {
2256 $icon->linktext = get_string($icon->identifier, $icon->component);
2257 } else if (!empty($linktext)) {
2258 $icon->linktext = $linktext;
2259 }
2260 return $this->render($icon);
2261 }
2262
2263 /**
2264 * Implementation of user image rendering.
7a3c215b 2265 *
3d3fae72 2266 * @param help_icon $helpicon A help icon instance
259c165d
PS
2267 * @return string HTML fragment
2268 */
2269 protected function render_help_icon(help_icon $helpicon) {
a84eadb8 2270 return $this->render_from_template('core/help_icon', $helpicon->export_for_template($this));
259c165d
PS
2271 }
2272
d9c8f425 2273 /**
7a3c215b 2274 * Returns HTML to display a scale help icon.
d9c8f425 2275 *
4bcc5118 2276 * @param int $courseid
7a3c215b
SH
2277 * @param stdClass $scale instance
2278 * @return string HTML fragment
d9c8f425 2279 */
4bcc5118
PS
2280 public function help_icon_scale($courseid, stdClass $scale) {
2281 global $CFG;
02f64f97 2282
4bcc5118 2283 $title = get_string('helpprefix2', '', $scale->name) .' ('.get_string('newwindow').')';
02f64f97 2284
0029a917 2285 $icon = $this->pix_icon('help', get_string('scales'), 'moodle', array('class'=>'iconhelp'));
02f64f97 2286
68bf577b
AD
2287 $scaleid = abs($scale->id);
2288
2289 $link = new moodle_url('/course/scales.php', array('id' => $courseid, 'list' => true, 'scaleid' => $scaleid));
230ec401 2290 $action = new popup_action('click', $link, 'ratingscale');
02f64f97 2291
26acc814 2292 return html_writer::tag('span', $this->action_link($link, $icon, $action), array('class' => 'helplink'));
d9c8f425 2293 }
2294
2295 /**
2296 * Creates and returns a spacer image with optional line break.
2297 *
3d3fae72
SH
2298 * @param array $attributes Any HTML attributes to add to the spaced.
2299 * @param bool $br Include a BR after the spacer.... DON'T USE THIS. Don't be
2300 * laxy do it with CSS which is a much better solution.
d9c8f425 2301 * @return string HTML fragment
2302 */
0029a917
PS
2303 public function spacer(array $attributes = null, $br = false) {
2304 $attributes = (array)$attributes;
2305 if (empty($attributes['width'])) {
2306 $attributes['width'] = 1;
1ba862ec 2307 }
e1a5a9cc 2308 if (empty($attributes['height'])) {
0029a917 2309 $attributes['height'] = 1;
d9c8f425 2310 }
0029a917 2311 $attributes['class'] = 'spacer';
d9c8f425 2312
0029a917 2313 $output = $this->pix_icon('spacer', '', 'moodle', $attributes);
b65bfc3e 2314
0029a917 2315 if (!empty($br)) {
1ba862ec
PS
2316 $output .= '<br />';
2317 }
d9c8f425 2318
2319 return $output;
2320 }
2321
d9c8f425 2322 /**
7a3c215b 2323 * Returns HTML to display the specified user's avatar.
d9c8f425 2324 *
5d0c95a5 2325 * User avatar may be obtained in two ways:
d9c8f425 2326 * <pre>
812dbaf7
PS
2327 * // Option 1: (shortcut for simple cases, preferred way)
2328 * // $user has come from the DB and has fields id, picture, imagealt, firstname and lastname
2329 * $OUTPUT->user_picture($user, array('popup'=>true));
2330 *
5d0c95a5
PS
2331 * // Option 2:
2332 * $userpic = new user_picture($user);
d9c8f425 2333 * // Set properties of $userpic
812dbaf7 2334 * $userpic->popup = true;
5d0c95a5 2335 * $OUTPUT->render($userpic);
d9c8f425 2336 * </pre>
2337 *
2fada290
MG
2338 * Theme developers: DO NOT OVERRIDE! Please override function
2339 * {@link core_renderer::render_user_picture()} instead.
2340 *
7a3c215b 2341 * @param stdClass $user Object with at least fields id, picture, imagealt, firstname, lastname
812dbaf7 2342 * If any of these are missing, the database is queried. Avoid this
d9c8f425 2343 * if at all possible, particularly for reports. It is very bad for performance.
812dbaf7
PS
2344 * @param array $options associative array with user picture options, used only if not a user_picture object,
2345 * options are:
2346 * - courseid=$this->page->course->id (course id of user profile in link)
2347 * - size=35 (size of image)
2348 * - link=true (make image clickable - the link leads to user profile)
2349 * - popup=false (open in popup)
2350 * - alttext=true (add image alt attribute)
5d0c95a5 2351 * - class = image class attribute (default 'userpicture')
e4a1efcb 2352 * - visibletoscreenreaders=true (whether to be visible to screen readers)
d9c8f425 2353 * @return string HTML fragment
2354 */
5d0c95a5
PS
2355 public function user_picture(stdClass $user, array $options = null) {
2356 $userpicture = new user_picture($user);
2357 foreach ((array)$options as $key=>$value) {
2358 if (array_key_exists($key, $userpicture)) {
2359 $userpicture->$key = $value;
2360 }
2361 }
2362 return $this->render($userpicture);
2363 }
2364
2365 /**
2366 * Internal implementation of user image rendering.
7a3c215b 2367 *
5d0c95a5
PS
2368 * @param user_picture $userpicture
2369 * @return string
2370 */
2371 protected function render_user_picture(user_picture $userpicture) {
2372 global $CFG, $DB;
812dbaf7 2373
5d0c95a5
PS
2374 $user = $userpicture->user;
2375
2376 if ($userpicture->alttext) {
2377 if (!empty($user->imagealt)) {
2378 $alt = $user->imagealt;
2379 } else {
2380 $alt = get_string('pictureof', '', fullname($user));
2381 }
d9c8f425 2382 } else {
97c10099 2383 $alt = '';
5d0c95a5
PS
2384 }
2385
2386 if (empty($userpicture->size)) {
5d0c95a5
PS
2387 $size = 35;
2388 } else if ($userpicture->size === true or $userpicture->size == 1) {
5d0c95a5 2389 $size = 100;
5d0c95a5 2390 } else {
5d0c95a5 2391 $size = $userpicture->size;
d9c8f425 2392 }
2393
5d0c95a5 2394 $class = $userpicture->class;
d9c8f425 2395
4d254790 2396 if ($user->picture == 0) {
5d0c95a5 2397 $class .= ' defaultuserpic';
5d0c95a5 2398 }
d9c8f425 2399
871a3ec5
SH
2400 $src = $userpicture->get_url($this->page, $this);
2401
29cf6631 2402 $attributes = array('src'=>$src, 'alt'=>$alt, 'title'=>$alt, 'class'=>$class, 'width'=>$size, 'height'=>$size);
e4a1efcb
JC
2403 if (!$userpicture->visibletoscreenreaders) {
2404 $attributes['role'] = 'presentation';
2405 }
2406
5d0c95a5 2407 // get the image html output fisrt
0e35ba6f 2408 $output = html_writer::empty_tag('img', $attributes);
5d0c95a5
PS
2409
2410 // then wrap it in link if needed
2411 if (!$userpicture->link) {
2412 return $output;
d9c8f425 2413 }
2414
5d0c95a5
PS
2415 if (empty($userpicture->courseid)) {
2416 $courseid = $this->page->course->id;
2417 } else {
2418 $courseid = $userpicture->courseid;
2419 }
2420
03d9401e
MD
2421 if ($courseid == SITEID) {
2422 $url = new moodle_url('/user/profile.php', array('id' => $user->id));
2423 } else {
2424 $url = new moodle_url('/user/view.php', array('id' => $user->id, 'course' => $courseid));
2425 }
5d0c95a5
PS
2426
2427 $attributes = array('href'=>$url);
e4a1efcb 2428 if (!$userpicture->visibletoscreenreaders) {
e4a1efcb
JC
2429 $attributes['tabindex'] = '-1';
2430 $attributes['aria-hidden'] = 'true';
2431 }
5d0c95a5
PS
2432
2433 if ($userpicture->popup) {
2434 $id = html_writer::random_id('userpicture');
2435 $attributes['id'] = $id;
c80877aa 2436 $this->add_action_handler(new popup_action('click', $url), $id);
5d0c95a5
PS
2437 }
2438
26acc814 2439 return html_writer::tag('a', $output, $attributes);
d9c8f425 2440 }
b80ef420 2441
b80ef420
DC
2442 /**
2443 * Internal implementation of file tree viewer items rendering.
7a3c215b 2444 *
b80ef420
DC
2445 * @param array $dir
2446 * @return string
2447 */
2448 public function htmllize_file_tree($dir) {
2449 if (empty($dir['subdirs']) and empty($dir['files'])) {
2450 return '';
2451 }
2452 $result = '<ul>';
2453 foreach ($dir['subdirs'] as $subdir) {
2454 $result .= '<li>'.s($subdir['dirname']).' '.$this->htmllize_file_tree($subdir).'</li>';
2455 }
2456 foreach ($dir['files'] as $file) {
2457 $filename = $file->get_filename();
2458 $result .= '<li><span>'.html_writer::link($file->fileurl, $filename).'</span></li>';
2459 }
2460 $result .= '</ul>';
2461
2462 return $result;
2463 }
7a3c215b 2464
bb496de7 2465 /**
7a3c215b 2466 * Returns HTML to display the file picker
bb496de7
DC
2467 *
2468 * <pre>
2469 * $OUTPUT->file_picker($options);
2470 * </pre>
2471 *
2fada290
MG
2472 * Theme developers: DO NOT OVERRIDE! Please override function
2473 * {@link core_renderer::render_file_picker()} instead.
2474 *
bb496de7
DC
2475 * @param array $options associative array with file manager options
2476 * options are:
2477 * maxbytes=>-1,
2478 * itemid=>0,
2479 * client_id=>uniqid(),
2480 * acepted_types=>'*',
2481 * return_types=>FILE_INTERNAL,
2482 * context=>$PAGE->context
2483 * @return string HTML fragment
2484 */
2485 public function file_picker($options) {
2486 $fp = new file_picker($options);
2487 return $this->render($fp);
2488 }
7a3c215b 2489
b80ef420
DC
2490 /**
2491 * Internal implementation of file picker rendering.
7a3c215b 2492 *
b80ef420
DC
2493 * @param file_picker $fp
2494 * @return string
2495 */
bb496de7
DC
2496 public function render_file_picker(file_picker $fp) {
2497 global $CFG, $OUTPUT, $USER;
2498 $options = $fp->options;
2499 $client_id = $options->client_id;
2500 $strsaved = get_string('filesaved', 'repository');
2501 $straddfile = get_string('openpicker', 'repository');
2502 $strloading = get_string('loading', 'repository');
adce0230 2503 $strdndenabled = get_string('dndenabled_inbox', 'moodle');
906e7d89 2504 $strdroptoupload = get_string('droptoupload', 'moodle');
bb496de7
DC
2505 $icon_progress = $OUTPUT->pix_icon('i/loading_small', $strloading).'';
2506
2507 $currentfile = $options->currentfile;
2508 if (empty($currentfile)) {
322945e9
FM
2509 $currentfile = '';
2510 } else {
2511 $currentfile .= ' - ';
bb496de7 2512 }
b817205b
DC
2513 if ($options->maxbytes) {
2514 $size = $options->maxbytes;
2515 } else {
2516 $size = get_max_upload_file_size();
2517 }
513aed3c 2518 if ($size == -1) {
831399c4 2519 $maxsize = '';
513aed3c
DC
2520 } else {
2521 $maxsize = get_string('maxfilesize', 'moodle', display_size($size));
2522 }
f50a61fb 2523 if ($options->buttonname) {
4b72f9eb
AW
2524 $buttonname = ' name="' . $options->buttonname . '"';
2525 } else {
2526 $buttonname = '';
2527 }
bb496de7
DC
2528 $html = <<<EOD
2529<div class="filemanager-loading mdl-align" id='filepicker-loading-{$client_id}'>
2530$icon_progress
2531</div>
2532<div id="filepicker-wrapper-{$client_id}" class="mdl-left" style="display:none">
2533 <div>
f6dabb31 2534 <input type="button" class="btn btn-secondary fp-btn-choose" id="filepicker-button-{$client_id}" value="{$straddfile}"{$buttonname}/>
fa7f2a45 2535 <span> $maxsize </span>
bb496de7
DC
2536 </div>
2537EOD;
2538 if ($options->env != 'url') {
2539 $html .= <<<EOD
50597880 2540 <div id="file_info_{$client_id}" class="mdl-left filepicker-filelist" style="position: relative">
a9352f1f 2541 <div class="filepicker-filename">
08a6a19d 2542 <div class="filepicker-container">$currentfile<div class="dndupload-message">$strdndenabled <br/><div class="dndupload-arrow"></div></div></div>
0f94289c 2543 <div class="dndupload-progressbars"></div>
a9352f1f 2544 </div>
08a6a19d 2545 <div><div class="dndupload-target">{$strdroptoupload}<br/><div class="dndupload-arrow"></div></div></div>
f08fac7c 2546 </div>
bb496de7
DC
2547EOD;
2548 }
2549 $html .= '</div>';
2550 return $html;
2551 }
d9c8f425 2552
2553 /**
7a3c215b 2554 * Returns HTML to display the 'Update this Modulename' button that appears on module pages.
d9c8f425 2555 *
d3932d2b
JP
2556 * @deprecated since Moodle 3.2
2557 *
d9c8f425 2558 * @param string $cmid the course_module id.
2559 * @param string $modulename the module name, eg. "forum", "quiz" or "workshop"
2560 * @return string the HTML for the button, if this user has permission to edit it, else an empty string.
2561 */
2562 public function update_module_button($cmid, $modulename) {
2563 global $CFG;
d3932d2b
JP
2564
2565 debugging('core_renderer::update_module_button() has been deprecated and should not be used anymore. Activity modules ' .
2566 'should not add the edit module button, the link is already available in the Administration block. Themes can choose ' .
2567 'to display the link in the buttons row consistently for all module types.', DEBUG_DEVELOPER);
2568
b0c6dc1c 2569 if (has_capability('moodle/course:manageactivities', context_module::instance($cmid))) {
d9c8f425 2570 $modulename = get_string('modulename', $modulename);
2571 $string = get_string('updatethis', '', $modulename);
3ba60ee1
PS
2572 $url = new moodle_url("$CFG->wwwroot/course/mod.php", array('update' => $cmid, 'return' => true, 'sesskey' => sesskey()));
2573 return $this->single_button($url, $string);
d9c8f425 2574 } else {
2575 return '';
2576 }
2577 }
2578
2579 /**
7a3c215b
SH
2580 * Returns HTML to display a "Turn editing on/off" button in a form.
2581 *
d9c8f425 2582 * @param moodle_url $url The URL + params to send through when clicking the button
2583 * @return string HTML the button
2584 */
2585 public function edit_button(moodle_url $url) {
3362dfdc
EL
2586
2587 $url->param('sesskey', sesskey());
2588 if ($this->page->user_is_editing()) {
2589 $url->param('edit', 'off');
2590 $editstring = get_string('turneditingoff');
d9c8f425 2591 } else {
3362dfdc
EL
2592 $url->param('edit', 'on');
2593 $editstring = get_string('turneditingon');
d9c8f425 2594 }
2595
3362dfdc 2596 return $this->single_button($url, $editstring);
d9c8f425 2597 }
2598
d9c8f425 2599 /**
7a3c215b 2600 * Returns HTML to display a simple button to close a window
d9c8f425 2601 *
d9c8f425 2602 * @param string $text The lang string for the button's label (already output from get_string())
3ba60ee1 2603 * @return string html fragment
d9c8f425 2604 */
7a5c78e0 2605 public function close_window_button($text='') {
d9c8f425 2606 if (empty($text)) {
2607 $text = get_string('closewindow');
2608 }
a6855934
PS
2609 $button = new single_button(new moodle_url('#'), $text, 'get');
2610 $button->add_action(new component_action('click', 'close_window'));
3ba60ee1
PS
2611
2612 return $this->container($this->render($button), 'closewindow');
d9c8f425 2613 }
2614
d9c8f425 2615 /**
2616 * Output an error message. By default wraps the error message in <span class="error">.
2617 * If the error message is blank, nothing is output.
7a3c215b 2618 *
d9c8f425 2619 * @param string $message the error message.
2620 * @return string the HTML to output.
2621 */
2622 public function error_text($message) {
2623 if (empty($message)) {
2624 return '';
2625 }
3246648b 2626 $message = $this->pix_icon('i/warning', get_string('error'), '', array('class' => 'icon icon-pre', 'title'=>'')) . $message;
26acc814 2627 return html_writer::tag('span', $message, array('class' => 'error'));
d9c8f425 2628 }
2629
2630 /**
2631 * Do not call this function directly.
2632 *
f8129210 2633 * To terminate the current script with a fatal error, call the {@link print_error}
d9c8f425 2634 * function, or throw an exception. Doing either of those things will then call this
2635 * function to display the error, before terminating the execution.
2636 *
2637 * @param string $message The message to output
2638 * @param string $moreinfourl URL where more info can be found about the error
2639 * @param string $link Link for the Continue button
2640 * @param array $backtrace The execution backtrace
2641 * @param string $debuginfo Debugging information
d9c8f425 2642 * @return string the HTML to output.
2643 */
9201a00a 2644 public function fatal_error($message, $moreinfourl, $link, $backtrace, $debuginfo = null, $errorcode = "") {
6bd8d7e7 2645 global $CFG;
d9c8f425 2646
2647 $output = '';
6f8f4d83 2648 $obbuffer = '';
e57c283d 2649
d9c8f425 2650 if ($this->has_started()) {
50764d37
PS
2651 // we can not always recover properly here, we have problems with output buffering,
2652 // html tables, etc.
d9c8f425 2653 $output .= $this->opencontainers->pop_all_but_last();
50764d37 2654
d9c8f425 2655 } else {
50764d37
PS
2656 // It is really bad if library code throws exception when output buffering is on,
2657 // because the buffered text would be printed before our start of page.
2658 // NOTE: this hack might be behave unexpectedly in case output buffering is enabled in PHP.ini
6bd8d7e7 2659 error_reporting(0); // disable notices from gzip compression, etc.
50764d37 2660 while (ob_get_level() > 0) {
2cadd443
PS
2661 $buff = ob_get_clean();
2662 if ($buff === false) {
2663 break;
2664 }
2665 $obbuffer .= $buff;
50764d37 2666 }
6bd8d7e7 2667 error_reporting($CFG->debug);
6f8f4d83 2668
f22f1caf
PS
2669 // Output not yet started.
2670 $protocol = (isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0');
2671 if (empty($_SERVER['HTTP_RANGE'])) {
2672 @header($protocol . ' 404 Not Found');
6b0d1723
JF
2673 } else if (core_useragent::check_safari_ios_version(602) && !empty($_SERVER['HTTP_X_PLAYBACK_SESSION_ID'])) {
2674 // Coax iOS 10 into sending the session cookie.
2675 @header($protocol . ' 403 Forbidden');
f22f1caf
PS
2676 } else {
2677 // Must stop byteserving attempts somehow,
2678 // this is weird but Chrome PDF viewer can be stopped only with 407!
2679 @header($protocol . ' 407 Proxy Authentication Required');
85309744 2680 }
f22f1caf 2681
eb5bdb35 2682 $this->page->set_context(null); // ugly hack - make sure page context is set to something, we do not want bogus warnings here
7fde1e4b 2683 $this->page->set_url('/'); // no url
191b267b 2684 //$this->page->set_pagelayout('base'); //TODO: MDL-20676 blocks on error pages are weird, unfortunately it somehow detect the pagelayout from URL :-(
dcfb9b78 2685 $this->page->set_title(get_string('error'));
8093188f 2686 $this->page->set_heading($this->page->course->fullname);
d9c8f425 2687 $output .= $this->header();
2688 }
2689
2690 $message = '<p class="errormessage">' . $message . '</p>'.
2691 '<p class="errorcode"><a href="' . $moreinfourl . '">' .
2692 get_string('moreinformation') . '</a></p>';
1ad8143a
PS
2693 if (empty($CFG->rolesactive)) {
2694 $message .= '<p class="errormessage">' . get_string('installproblem', 'error') . '</p>';
2695 //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.
2696 }
4c2892c6 2697 $output .= $this->box($message, 'errorbox', null, array('data-rel' => 'fatalerror'));
d9c8f425 2698
96f81ea3 2699 if ($CFG->debugdeveloper) {
6f8f4d83 2700 if (!empty($debuginfo)) {
c5d18164
PS
2701 $debuginfo = s($debuginfo); // removes all nasty JS
2702 $debuginfo = str_replace("\n", '<br />', $debuginfo); // keep newlines
2703 $output .= $this->notification('<strong>Debug info:</strong> '.$debuginfo, 'notifytiny');
6f8f4d83
PS
2704 }
2705 if (!empty($backtrace)) {
2706 $output .= $this->notification('<strong>Stack trace:</strong> '.format_backtrace($backtrace), 'notifytiny');
2707 }
2708 if ($obbuffer !== '' ) {
2709 $output .= $this->notification('<strong>Output buffer:</strong> '.s($obbuffer), 'notifytiny');
2710 }
d9c8f425 2711 }
2712
3efe6bbb
PS
2713 if (empty($CFG->rolesactive)) {
2714 // continue does not make much sense if moodle is not installed yet because error is most probably not recoverable
2715 } else if (!empty($link)) {
d9c8f425 2716 $output .= $this->continue_button($link);
2717 }
2718
2719 $output .= $this->footer();
2720
2721 // Padding to encourage IE to display our error page, rather than its own.
2722 $output .= str_repeat(' ', 512);
2723
2724 return $output;
2725 }
2726
2727 /**
24346803 2728 * Output a notification (that is, a status message about something that has just happened).
d9c8f425 2729 *
0346323c
AN
2730 * Note: \core\notification::add() may be more suitable for your usage.
2731 *
24346803
AN
2732 * @param string $message The message to print out.
2733 * @param string $type The type of notification. See constants on \core\output\notification.
d9c8f425 2734 * @return string the HTML to output.
2735 */
24346803
AN
2736 public function notification($message, $type = null) {
2737 $typemappings = [
2738 // Valid types.
2739 'success' => \core\output\notification::NOTIFY_SUCCESS,
2740 'info' => \core\output\notification::NOTIFY_INFO,
2741 'warning' => \core\output\notification::NOTIFY_WARNING,
2742 'error' => \core\output\notification::NOTIFY_ERROR,
2743
2744 // Legacy types mapped to current types.
2745 'notifyproblem' => \core\output\notification::NOTIFY_ERROR,
2746 'notifytiny' => \core\output\notification::NOTIFY_ERROR,
2747 'notifyerror' => \core\output\notification::NOTIFY_ERROR,
2748 'notifysuccess' => \core\output\notification::NOTIFY_SUCCESS,
2749 'notifymessage' => \core\output\notification::NOTIFY_INFO,
2750 'notifyredirect' => \core\output\notification::NOTIFY_INFO,
2751 'redirectmessage' => \core\output\notification::NOTIFY_INFO,
2752 ];
2753
2754 $extraclasses = [];
2755
2756 if ($type) {
2757 if (strpos($type, ' ') === false) {
2758 // No spaces in the list of classes, therefore no need to loop over and determine the class.
2759 if (isset($typemappings[$type])) {
2760 $type = $typemappings[$type];
2761 } else {
2762 // The value provided did not match a known type. It must be an extra class.
2763 $extraclasses = [$type];
2764 }
2765 } else {
2766 // Identify what type of notification this is.
2767 $classarray = explode(' ', self::prepare_classes($type));
2768
2769 // Separate out the type of notification from the extra classes.
2770 foreach ($classarray as $class) {
2771 if (isset($typemappings[$class])) {
2772 $type = $typemappings[$class];
2773 } else {
2774 $extraclasses[] = $class;
2775 }
263fb9d1
JC
2776 }
2777 }
2778 }
2779
24346803
AN
2780 $notification = new \core\output\notification($message, $type);
2781 if (count($extraclasses)) {
2782 $notification->set_extra_classes($extraclasses);
2783 }
263fb9d1 2784
24346803
AN
2785 // Return the rendered template.
2786 return $this->render_from_template($notification->get_template_name(), $notification->export_for_template($this));
263fb9d1
JC
2787 }
2788
2789 /**
2790 * Output a notification at a particular level - in this case, NOTIFY_PROBLEM.
2791 *
2792 * @param string $message the message to print out
2793 * @return string HTML fragment.
24346803
AN
2794 * @deprecated since Moodle 3.1 MDL-30811 - please do not use this function any more.
2795 * @todo MDL-53113 This will be removed in Moodle 3.5.
2796 * @see \core\output\notification
263fb9d1
JC
2797 */
2798 public function notify_problem($message) {
24346803 2799 debugging(__FUNCTION__ . ' is deprecated.' .
0346323c 2800 'Please use \core\notification::add, or \core\output\notification as required',
24346803
AN
2801 DEBUG_DEVELOPER);
2802 $n = new \core\output\notification($message, \core\output\notification::NOTIFY_ERROR);
263fb9d1
JC
2803 return $this->render($n);
2804 }
2805
2806 /**
2807 * Output a notification at a particular level - in this case, NOTIFY_SUCCESS.
2808 *
2809 * @param string $message the message to print out
2810 * @return string HTML fragment.
24346803
AN
2811 * @deprecated since Moodle 3.1 MDL-30811 - please do not use this function any more.
2812 * @todo MDL-53113 This will be removed in Moodle 3.5.
2813 * @see \core\output\notification
263fb9d1
JC
2814 */
2815 public function notify_success($message) {
24346803 2816 debugging(__FUNCTION__ . ' is deprecated.' .
0346323c 2817 'Please use \core\notification::add, or \core\output\notification as required',
24346803 2818 DEBUG_DEVELOPER);
263fb9d1
JC
2819 $n = new \core\output\notification($message, \core\output\notification::NOTIFY_SUCCESS);
2820 return $this->render($n);
2821 }
2822
2823 /**
2824 * Output a notification at a particular level - in this case, NOTIFY_MESSAGE.
2825 *
2826 * @param string $message the message to print out
2827 * @return string HTML fragment.
24346803
AN
2828 * @deprecated since Moodle 3.1 MDL-30811 - please do not use this function any more.
2829 * @todo MDL-53113 This will be removed in Moodle 3.5.
2830 * @see \core\output\notification
263fb9d1
JC
2831 */
2832 public function notify_message($message) {
24346803 2833 debugging(__FUNCTION__ . ' is deprecated.' .
0346323c 2834 'Please use \core\notification::add, or \core\output\notification as required',
24346803
AN
2835 DEBUG_DEVELOPER);
2836 $n = new \core\output\notification($message, \core\output\notification::NOTIFY_INFO);
263fb9d1
JC
2837 return $this->render($n);
2838 }
2839
2840 /**
2841 * Output a notification at a particular level - in this case, NOTIFY_REDIRECT.
2842 *
2843 * @param string $message the message to print out
2844 * @return string HTML fragment.
24346803
AN
2845 * @deprecated since Moodle 3.1 MDL-30811 - please do not use this function any more.
2846 * @todo MDL-53113 This will be removed in Moodle 3.5.
2847 * @see \core\output\notification
263fb9d1
JC
2848 */
2849 public function notify_redirect($message) {
24346803 2850 debugging(__FUNCTION__ . ' is deprecated.' .
0346323c 2851 'Please use \core\notification::add, or \core\output\notification as required',
24346803
AN
2852 DEBUG_DEVELOPER);
2853 $n = new \core\output\notification($message, \core\output\notification::NOTIFY_INFO);
263fb9d1
JC
2854 return $this->render($n);
2855 }
2856
2857 /**
2858 * Render a notification (that is, a status message about something that has
2859 * just happened).
2860 *
2861 * @param \core\output\notification $notification the notification to print out
2862 * @return string the HTML to output.
2863 */
2864 protected function render_notification(\core\output\notification $notification) {
24346803 2865 return $this->render_from_template($notification->get_template_name(), $notification->export_for_template($this));
d9c8f425 2866 }
2867
2868 /**
7a3c215b 2869 * Returns HTML to display a continue button that goes to a particular URL.
d9c8f425 2870 *
3ba60ee1 2871 * @param string|moodle_url $url The url the button goes to.
d9c8f425 2872 * @return string the HTML to output.
2873 */
3ba60ee1
PS
2874 public function continue_button($url) {
2875 if (!($url instanceof moodle_url)) {
2876 $url = new moodle_url($url);
d9c8f425 2877 }
5c3e1eed 2878 $button = new single_button($url, get_string('continue'), 'get', true);
3ba60ee1 2879 $button->class = 'continuebutton';
d9c8f425 2880
3ba60ee1 2881 return $this->render($button);
d9c8f425 2882 }
2883
2884 /**
7a3c215b 2885 * Returns HTML to display a single paging bar to provide access to other pages (usually in a search)
d9c8f425 2886 *
2fada290
MG
2887 * Theme developers: DO NOT OVERRIDE! Please override function
2888 * {@link core_renderer::render_paging_bar()} instead.
2889 *
71c03ac1 2890 * @param int $totalcount The total number of entries available to be paged through
929d7a83
PS
2891 * @param int $page The page you are currently viewing
2892 * @param int $perpage The number of entries that should be shown per page
2893 * @param string|moodle_url $baseurl url of the current page, the $pagevar parameter is added
2894 * @param string $pagevar name of page parameter that holds the page number
d9c8f425 2895 * @return string the HTML to output.
2896 */
929d7a83
PS
2897 public function paging_bar($totalcount, $page, $perpage, $baseurl, $pagevar = 'page') {
2898 $pb = new paging_bar($totalcount, $page, $perpage, $baseurl, $pagevar);
2899 return $this->render($pb);
2900 }
2901
2902 /**
2903 * Internal implementation of paging bar rendering.
7a3c215b 2904 *
929d7a83
PS
2905 * @param paging_bar $pagingbar
2906 * @return string
2907 */
2908 protected function render_paging_bar(paging_bar $pagingbar) {
d9c8f425 2909 $output = '';
2910 $pagingbar = clone($pagingbar);
34059565 2911 $pagingbar->prepare($this, $this->page, $this->target);
d9c8f425 2912
2913 if ($pagingbar->totalcount > $pagingbar->perpage) {
2914 $output .= get_string('page') . ':';
2915
2916 if (!empty($pagingbar->previouslink)) {
b146b6a8 2917 $output .= ' (' . $pagingbar->previouslink . ') ';
d9c8f425 2918 }
2919
2920 if (!empty($pagingbar->firstlink)) {
b146b6a8 2921 $output .= ' ' . $pagingbar->firstlink . ' ...';
d9c8f425 2922 }
2923
2924 foreach ($pagingbar->pagelinks as $link) {
b146b6a8 2925 $output .= " $link";
d9c8f425 2926 }
2927
2928 if (!empty($pagingbar->lastlink)) {
7ca2c19b 2929 $output .= ' ... ' . $pagingbar->lastlink . ' ';
d9c8f425 2930 }
2931
2932 if (!empty($pagingbar->nextlink)) {
b146b6a8 2933 $output .= ' (' . $pagingbar->nextlink . ')';
d9c8f425 2934 }
2935 }
2936
26acc814 2937 return html_writer::tag('div', $output, array('class' => 'paging'));
d9c8f425 2938 }
2939
d9c8f425 2940 /**
2941 * Output the place a skip link goes to.
7a3c215b 2942 *
d9c8f425 2943 * @param string $id The target name from the corresponding $PAGE->requires->skip_link_to($target) call.
2944 * @return string the HTML to output.
2945 */
fe213365 2946 public function skip_link_target($id = null) {
9e8d0842 2947 return html_writer::span('', '', array('id' => $id));
d9c8f425 2948 }
2949
2950 /**
2951 * Outputs a heading
7a3c215b 2952 *
d9c8f425 2953 * @param string $text The text of the heading
2954 * @param int $level The level of importance of the heading. Defaulting to 2
699e2fd0 2955 * @param string $classes A space-separated list of CSS classes. Defaulting to null
d9c8f425 2956 * @param string $id An optional ID
2957 * @return string the HTML to output.
2958 */
699e2fd0 2959 public function heading($text, $level = 2, $classes = null, $id = null) {
d9c8f425 2960 $level = (integer) $level;
2961 if ($level < 1 or $level > 6) {
2962 throw new coding_exception('Heading level must be an integer between 1 and 6.');
2963 }
26acc814 2964 return html_writer::tag('h' . $level, $text, array('id' => $id, 'class' => renderer_base::prepare_classes($classes)));
d9c8f425 2965 }
2966
2967 /**
2968 * Outputs a box.
7a3c215b 2969 *
d9c8f425 2970 * @param string $contents The contents of the box
2971 * @param string $classes A space-separated list of CSS classes
2972 * @param string $id An optional ID
3e76c7fa 2973 * @param array $attributes An array of other attributes to give the box.
d9c8f425 2974 * @return string the HTML to output.
2975 */
3e76c7fa
SH
2976 public function box($contents, $classes = 'generalbox', $id = null, $attributes = array()) {
2977 return $this->box_start($classes, $id, $attributes) . $contents . $this->box_end();
d9c8f425 2978 }
2979
2980 /**
3d3fae72 2981 * Outputs the opening section of a box.
7a3c215b 2982 *
d9c8f425 2983 * @param string $classes A space-separated list of CSS classes
2984 * @param string $id An optional ID
3e76c7fa 2985 * @param array $attributes An array of other attributes to give the box.
d9c8f425 2986 * @return string the HTML to output.
2987 */
3e76c7fa 2988 public function box_start($classes = 'generalbox', $id = null, $attributes = array()) {
5d0c95a5 2989 $this->opencontainers->push('box', html_writer::end_tag('div'));
3e76c7fa
SH
2990 $attributes['id'] = $id;
2991 $attributes['class'] = 'box ' . renderer_base::prepare_classes($classes);
2992 return html_writer::start_tag('div', $attributes);
d9c8f425 2993 }
2994
2995 /**
2996 * Outputs the closing section of a box.
7a3c215b 2997 *
d9c8f425 2998 * @return string the HTML to output.
2999 */
3000 public function box_end() {
3001 return $this->opencontainers->pop('box');
3002 }
3003
3004 /**
3005 * Outputs a container.
7a3c215b 3006 *
d9c8f425 3007 * @param string $contents The contents of the box
3008 * @param string $classes A space-separated list of CSS classes
3009 * @param string $id An optional ID
3010 * @return string the HTML to output.
3011 */
fe213365 3012 public function container($contents, $classes = null, $id = null) {
d9c8f425 3013 return $this->container_start($classes, $id) . $contents . $this->container_end();
3014 }
3015
3016 /**
3017 * Outputs the opening section of a container.
7a3c215b 3018 *
d9c8f425 3019 * @param string $classes A space-separated list of CSS classes
3020 * @param string $id An optional ID
3021 * @return string the HTML to output.
3022 */
fe213365 3023 public function container_start($classes = null, $id = null) {
5d0c95a5
PS
3024 $this->opencontainers->push('container', html_writer::end_tag('div'));
3025 return html_writer::start_tag('div', array('id' => $id,
78946b9b 3026 'class' => renderer_base::prepare_classes($classes)));
d9c8f425 3027 }
3028
3029 /**
3030 * Outputs the closing section of a container.
7a3c215b 3031 *
d9c8f425 3032 * @return string the HTML to output.
3033 */
3034 public function container_end() {
3035 return $this->opencontainers->pop('container');
3036 }
7d2a0492 3037
3406acde 3038 /**
7d2a0492 3039 * Make nested HTML lists out of the items
3040 *
3041 * The resulting list will look something like this:
3042 *
3043 * <pre>
3044 * <<ul>>
3045 * <<li>><div class='tree_item parent'>(item contents)</div>
3046 * <<ul>
3047 * <<li>><div class='tree_item'>(item contents)</div><</li>>
3048 * <</ul>>
3049 * <</li>>
3050 * <</ul>>
3051 * </pre>
3052 *
7a3c215b
SH
3053 * @param array $items
3054 * @param array $attrs html attributes passed to the top ofs the list
7d2a0492 3055 * @return string HTML
3056 */
7a3c215b 3057 public function tree_block_contents($items, $attrs = array()) {
7d2a0492 3058 // exit if empty, we don't want an empty ul element
3059 if (empty($items)) {
3060 return '';
3061 }
3062 // array of nested li elements
3063 $lis = array();
3064 foreach ($items as $item) {
3065 // this applies to the li item which contains all child lists too
3066 $content = $item->content($this);
3067 $liclasses = array($item->get_css_type());
3406acde 3068 if (!$item->forceopen || (!$item->forceopen && $item->collapse) || ($item->children->count()==0 && $item->nodetype==navigation_node::NODETYPE_BRANCH)) {
7d2a0492 3069 $liclasses[] = 'collapsed';
3070 }
3071 if ($item->isactive === true) {
3072 $liclasses[] = 'current_branch';
3073 }
3074 $liattr = array('class'=>join(' ',$liclasses));