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