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