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