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