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