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