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