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