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