MDL-20204 fixed some alt image typos
[moodle.git] / lib / outputrenderers.php
CommitLineData
d9c8f425 1<?php
2
3// This file is part of Moodle - http://moodle.org/
4//
5// Moodle is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// Moodle is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17
18/**
19 * Classes for rendering HTML output for Moodle.
20 *
21 * Please see http://docs.moodle.org/en/Developement:How_Moodle_outputs_HTML
22 * for an overview.
23 *
24 * @package moodlecore
25 * @copyright 2009 Tim Hunt
26 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
27 */
28
29/**
30 * Simple base class for Moodle renderers.
31 *
32 * Tracks the xhtml_container_stack to use, which is passed in in the constructor.
33 *
34 * Also has methods to facilitate generating HTML output.
35 *
36 * @copyright 2009 Tim Hunt
37 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
38 * @since Moodle 2.0
39 */
78946b9b 40class renderer_base {
d9c8f425 41 /** @var xhtml_container_stack the xhtml_container_stack to use. */
42 protected $opencontainers;
43 /** @var moodle_page the page we are rendering for. */
44 protected $page;
c927e35c
PS
45 /** @var requested rendering target conatnt */
46 protected $target;
d9c8f425 47
48 /**
49 * Constructor
50 * @param moodle_page $page the page we are doing output for.
c927e35c 51 * @param string $target one of rendering target constants
d9c8f425 52 */
c927e35c 53 public function __construct(moodle_page $page, $target) {
d9c8f425 54 $this->opencontainers = $page->opencontainers;
55 $this->page = $page;
c927e35c 56 $this->target = $target;
d9c8f425 57 }
58
59 /**
60 * Have we started output yet?
61 * @return boolean true if the header has been printed.
62 */
63 public function has_started() {
64 return $this->page->state >= moodle_page::STATE_IN_BODY;
65 }
66
67 /**
68 * Outputs a tag with attributes and contents
69 * @param string $tagname The name of tag ('a', 'img', 'span' etc.)
70 * @param array $attributes The tag attributes (array('src' => $url, 'class' => 'class1') etc.)
71 * @param string $contents What goes between the opening and closing tags
72 * @return string HTML fragment
73 */
a309d7db 74 protected function output_tag($tagname, array $attributes = null, $contents) {
d9c8f425 75 return $this->output_start_tag($tagname, $attributes) . $contents .
76 $this->output_end_tag($tagname);
77 }
78
79 /**
80 * Outputs an opening tag with attributes
81 * @param string $tagname The name of tag ('a', 'img', 'span' etc.)
82 * @param array $attributes The tag attributes (array('src' => $url, 'class' => 'class1') etc.)
83 * @return string HTML fragment
84 */
a309d7db 85 protected function output_start_tag($tagname, array $attributes = null) {
d9c8f425 86 return '<' . $tagname . $this->output_attributes($attributes) . '>';
87 }
88
89 /**
90 * Outputs a closing tag
91 * @param string $tagname The name of tag ('a', 'img', 'span' etc.)
92 * @return string HTML fragment
93 */
94 protected function output_end_tag($tagname) {
95 return '</' . $tagname . '>';
96 }
97
98 /**
99 * Outputs an empty tag with attributes
100 * @param string $tagname The name of tag ('input', 'img', 'br' etc.)
101 * @param array $attributes The tag attributes (array('src' => $url, 'class' => 'class1') etc.)
102 * @return string HTML fragment
103 */
a309d7db 104 protected function output_empty_tag($tagname, array $attributes = null) {
d9c8f425 105 return '<' . $tagname . $this->output_attributes($attributes) . ' />';
106 }
107
108 /**
109 * Outputs a HTML attribute and value
110 * @param string $name The name of the attribute ('src', 'href', 'class' etc.)
111 * @param string $value The value of the attribute. The value will be escaped with {@link s()}
112 * @return string HTML fragment
113 */
114 protected function output_attribute($name, $value) {
115 if (is_array($value)) {
116 debugging("Passed an array for the HTML attribute $name", DEBUG_DEVELOPER);
117 }
118
119 $value = trim($value);
120 if ($value == HTML_ATTR_EMPTY) {
121 return ' ' . $name . '=""';
122 } else if ($value || is_numeric($value)) { // We want 0 to be output.
123 return ' ' . $name . '="' . s($value) . '"';
124 }
125 }
126
127 /**
128 * Outputs a list of HTML attributes and values
129 * @param array $attributes The tag attributes (array('src' => $url, 'class' => 'class1') etc.)
130 * The values will be escaped with {@link s()}
131 * @return string HTML fragment
132 */
a309d7db
PS
133 protected function output_attributes(array $attributes = null) {
134 $attributes = (array)$attributes;
d9c8f425 135 $output = '';
136 foreach ($attributes as $name => $value) {
137 $output .= $this->output_attribute($name, $value);
138 }
139 return $output;
140 }
141
142 /**
143 * Given an array or space-separated list of classes, prepares and returns the HTML class attribute value
144 * @param mixed $classes Space-separated string or array of classes
145 * @return string HTML class attribute value
146 */
147 public static function prepare_classes($classes) {
148 if (is_array($classes)) {
149 return implode(' ', array_unique($classes));
150 }
151 return $classes;
152 }
153
d9c8f425 154 /**
78946b9b 155 * Return the moodle_url for an image
d9c8f425 156 *
78946b9b
PS
157 * @param string $imagename the name of the image
158 * @param string $component full plugin name
159 * @return moodle_url
d9c8f425 160 */
c927e35c 161 public function pix_url($imagename, $component = 'moodle') {
c39e5ba2 162 return $this->page->theme->pix_url($imagename, $component);
d9c8f425 163 }
164
165 /**
6dd7d7f0 166 * A helper function that takes a html_component subclass as param.
d9c8f425 167 * If that component has an id attribute and an array of valid component_action objects,
168 * it sets up the appropriate event handlers.
169 *
6dd7d7f0 170 * @param html_component $component
d9c8f425 171 * @return void;
172 */
c2c04c69 173 protected function prepare_event_handlers(html_component $component) {
d9c8f425 174 $actions = $component->get_actions();
175 if (!empty($actions) && is_array($actions) && $actions[0] instanceof component_action) {
176 foreach ($actions as $action) {
177 if (!empty($action->jsfunction)) {
178 $this->page->requires->event_handler($component->id, $action->event, $action->jsfunction, $action->jsfunctionargs);
179 }
180 }
181 }
182 }
183
184 /**
6dd7d7f0 185 * Given a html_component with height and/or width set, translates them
d9c8f425 186 * to appropriate CSS rules.
187 *
6dd7d7f0 188 * @param html_component $component
d9c8f425 189 * @return string CSS rules
190 */
a309d7db 191 protected function prepare_legacy_width_and_height(html_component $component) {
d9c8f425 192 $output = '';
193 if (!empty($component->height)) {
194 // We need a more intelligent way to handle these warnings. If $component->height have come from
195 // somewhere in deprecatedlib.php, then there is no point outputting a warning here.
6dd7d7f0 196 // debugging('Explicit height given to html_component leads to inline css. Use a proper CSS class instead.', DEBUG_DEVELOPER);
d9c8f425 197 $output .= "height: {$component->height}px;";
198 }
199 if (!empty($component->width)) {
6dd7d7f0 200 // debugging('Explicit width given to html_component leads to inline css. Use a proper CSS class instead.', DEBUG_DEVELOPER);
d9c8f425 201 $output .= "width: {$component->width}px;";
202 }
203 return $output;
204 }
1ba862ec
PS
205
206 /**
207 * Helper function for applying of html_component options
208 * @param html_component $component
209 * @param array $options
210 * @return void
211 */
8fa16366 212 public static function apply_component_options(html_component $component, array $options = null) {
1ba862ec
PS
213 $options = (array)$options;
214 foreach ($options as $key => $value) {
215 if ($key === 'class' or $key === 'classes') {
216 $component->add_classes($value);
217 } else if (array_key_exists($key, $component)) {
218 $component->$key = $value;
219 }
220 }
221 }
d9c8f425 222}
223
c927e35c 224
75590935
PS
225/**
226 * Basis for all plugin renderers.
227 *
c927e35c
PS
228 * @author Petr Skoda (skodak)
229 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
230 * @since Moodle 2.0
75590935
PS
231 */
232class plugin_renderer_base extends renderer_base {
233 /**
234 * A reference to the current general renderer probably {@see core_renderer}
235 * @var renderer_base
236 */
237 protected $output;
238
239 /**
240 * Contructor method, calls the parent constructor
241 * @param moodle_page $page
c927e35c 242 * @param string $target one of rendering target constants
75590935 243 */
c927e35c
PS
244 public function __construct(moodle_page $page, $target) {
245 $this->output = $page->get_renderer('core', null, $target);
246 parent::__construct($page, $target);
75590935 247 }
ff5265c6
PS
248
249 /**
250 * Magic method used to pass calls otherwise meant for the standard renderer
251 * to it to ensure we don't go causing unnessecary greif.
252 *
253 * @param string $method
254 * @param array $arguments
255 * @return mixed
256 */
257 public function __call($method, $arguments) {
37b5b18e
PS
258 if (method_exists('renderer_base', $method)) {
259 throw new coding_exception('Protected method called against '.__CLASS__.' :: '.$method);
260 }
ff5265c6
PS
261 if (method_exists($this->output, $method)) {
262 return call_user_func_array(array($this->output, $method), $arguments);
263 } else {
264 throw new coding_exception('Unknown method called against '.__CLASS__.' :: '.$method);
265 }
266 }
75590935 267}
d9c8f425 268
c927e35c 269
d9c8f425 270/**
78946b9b 271 * The standard implementation of the core_renderer interface.
d9c8f425 272 *
273 * @copyright 2009 Tim Hunt
274 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
275 * @since Moodle 2.0
276 */
78946b9b 277class core_renderer extends renderer_base {
d9c8f425 278 /** @var string used in {@link header()}. */
279 const PERFORMANCE_INFO_TOKEN = '%%PERFORMANCEINFO%%';
280 /** @var string used in {@link header()}. */
281 const END_HTML_TOKEN = '%%ENDHTML%%';
282 /** @var string used in {@link header()}. */
283 const MAIN_CONTENT_TOKEN = '[MAIN CONTENT GOES HERE]';
284 /** @var string used to pass information from {@link doctype()} to {@link standard_head_html()}. */
285 protected $contenttype;
286 /** @var string used by {@link redirect_message()} method to communicate with {@link header()}. */
287 protected $metarefreshtag = '';
288
289 /**
290 * Get the DOCTYPE declaration that should be used with this page. Designed to
291 * be called in theme layout.php files.
292 * @return string the DOCTYPE declaration (and any XML prologue) that should be used.
293 */
294 public function doctype() {
295 global $CFG;
296
297 $doctype = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">' . "\n";
298 $this->contenttype = 'text/html; charset=utf-8';
299
300 if (empty($CFG->xmlstrictheaders)) {
301 return $doctype;
302 }
303
304 // We want to serve the page with an XML content type, to force well-formedness errors to be reported.
305 $prolog = '<?xml version="1.0" encoding="utf-8"?>' . "\n";
306 if (isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'application/xhtml+xml') !== false) {
307 // Firefox and other browsers that can cope natively with XHTML.
308 $this->contenttype = 'application/xhtml+xml; charset=utf-8';
309
310 } else if (preg_match('/MSIE.*Windows NT/', $_SERVER['HTTP_USER_AGENT'])) {
311 // IE can't cope with application/xhtml+xml, but it will cope if we send application/xml with an XSL stylesheet.
312 $this->contenttype = 'application/xml; charset=utf-8';
313 $prolog .= '<?xml-stylesheet type="text/xsl" href="' . $CFG->httpswwwroot . '/lib/xhtml.xsl"?>' . "\n";
314
315 } else {
316 $prolog = '';
317 }
318
319 return $prolog . $doctype;
320 }
321
322 /**
323 * The attributes that should be added to the <html> tag. Designed to
324 * be called in theme layout.php files.
325 * @return string HTML fragment.
326 */
327 public function htmlattributes() {
328 return get_html_lang(true) . ' xmlns="http://www.w3.org/1999/xhtml"';
329 }
330
331 /**
332 * The standard tags (meta tags, links to stylesheets and JavaScript, etc.)
333 * that should be included in the <head> tag. Designed to be called in theme
334 * layout.php files.
335 * @return string HTML fragment.
336 */
337 public function standard_head_html() {
b5bbeaf0 338 global $CFG, $SESSION;
d9c8f425 339 $output = '';
340 $output .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />' . "\n";
341 $output .= '<meta name="keywords" content="moodle, ' . $this->page->title . '" />' . "\n";
342 if (!$this->page->cacheable) {
343 $output .= '<meta http-equiv="pragma" content="no-cache" />' . "\n";
344 $output .= '<meta http-equiv="expires" content="0" />' . "\n";
345 }
346 // This is only set by the {@link redirect()} method
347 $output .= $this->metarefreshtag;
348
349 // Check if a periodic refresh delay has been set and make sure we arn't
350 // already meta refreshing
351 if ($this->metarefreshtag=='' && $this->page->periodicrefreshdelay!==null) {
352 $output .= '<meta http-equiv="refresh" content="'.$this->page->periodicrefreshdelay.';url='.$this->page->url->out().'" />';
353 }
354
355 $this->page->requires->js('lib/javascript-static.js')->in_head();
356 $this->page->requires->js('lib/javascript-deprecated.js')->in_head();
357 $this->page->requires->js('lib/javascript-mod.php')->in_head();
7d2a0492 358 $this->page->requires->js_function_call('setTimeout', array('fix_column_widths()', 20));
d9c8f425 359
360 $focus = $this->page->focuscontrol;
361 if (!empty($focus)) {
362 if (preg_match("#forms\['([a-zA-Z0-9]+)'\].elements\['([a-zA-Z0-9]+)'\]#", $focus, $matches)) {
363 // This is a horrifically bad way to handle focus but it is passed in
364 // through messy formslib::moodleform
7d2a0492 365 $this->page->requires->js_function_call('old_onload_focus', array($matches[1], $matches[2]));
d9c8f425 366 } else if (strpos($focus, '.')!==false) {
367 // Old style of focus, bad way to do it
368 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);
369 $this->page->requires->js_function_call('old_onload_focus', explode('.', $focus, 2));
370 } else {
371 // Focus element with given id
7d2a0492 372 $this->page->requires->js_function_call('focuscontrol', array($focus));
d9c8f425 373 }
374 }
375
78946b9b
PS
376 // Get the theme stylesheet - this has to be always first CSS, this loads also styles.css from all plugins;
377 // any other custom CSS can not be overridden via themes and is highly discouraged
378 $urls = $this->page->theme->css_urls();
379 foreach ($urls as $url) {
9b2735f9 380 $this->page->requires->css($url->out(), true);
78946b9b
PS
381 }
382
38aafea2
PS
383 // Get the theme javascript
384 $jsurl = $this->page->theme->javascript_url();
e68c5f36 385 $this->page->requires->js($jsurl->out(), true)->in_head();
38aafea2 386
78946b9b 387 // Perform a browser environment check for the flash version. Should only run once per login session.
b5bbeaf0 388 if (isloggedin() && !empty($CFG->excludeoldflashclients) && empty($SESSION->flashversion)) {
985d1d1d
PS
389 $this->page->requires->yui2_lib('event');
390 $this->page->requires->yui2_lib('connection');
b5bbeaf0 391 $this->page->requires->js('lib/swfobject/swfobject.js')->in_head();
392 $this->page->requires->js('lib/flashdetect/flashdetect.js')->in_head();
393 $this->page->requires->js_function_call('setflashversiontosession', array($CFG->wwwroot, sesskey()));
394 }
395
d9c8f425 396 // Get any HTML from the page_requirements_manager.
945f19f7 397 $output .= $this->page->requires->get_head_code($this->page, $this);
d9c8f425 398
399 // List alternate versions.
400 foreach ($this->page->alternateversions as $type => $alt) {
401 $output .= $this->output_empty_tag('link', array('rel' => 'alternate',
402 'type' => $type, 'title' => $alt->title, 'href' => $alt->url));
403 }
404
405 return $output;
406 }
407
408 /**
409 * The standard tags (typically skip links) that should be output just inside
410 * the start of the <body> tag. Designed to be called in theme layout.php files.
411 * @return string HTML fragment.
412 */
413 public function standard_top_of_body_html() {
414 return $this->page->requires->get_top_of_body_code();
415 }
416
417 /**
418 * The standard tags (typically performance information and validation links,
419 * if we are in developer debug mode) that should be output in the footer area
420 * of the page. Designed to be called in theme layout.php files.
421 * @return string HTML fragment.
422 */
423 public function standard_footer_html() {
424 global $CFG;
425
426 // This function is normally called from a layout.php file in {@link header()}
427 // but some of the content won't be known until later, so we return a placeholder
428 // for now. This will be replaced with the real content in {@link footer()}.
429 $output = self::PERFORMANCE_INFO_TOKEN;
430 if (!empty($CFG->debugpageinfo)) {
431 $output .= '<div class="performanceinfo">This page is: ' . $this->page->debug_summary() . '</div>';
432 }
433 if (!empty($CFG->debugvalidators)) {
434 $output .= '<div class="validators"><ul>
435 <li><a href="http://validator.w3.org/check?verbose=1&amp;ss=1&amp;uri=' . urlencode(qualified_me()) . '">Validate HTML</a></li>
436 <li><a href="http://www.contentquality.com/mynewtester/cynthia.exe?rptmode=-1&amp;url1=' . urlencode(qualified_me()) . '">Section 508 Check</a></li>
437 <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>
438 </ul></div>';
439 }
440 return $output;
441 }
442
443 /**
444 * The standard tags (typically script tags that are not needed earlier) that
445 * should be output after everything else, . Designed to be called in theme layout.php files.
446 * @return string HTML fragment.
447 */
448 public function standard_end_of_body_html() {
449 // This function is normally called from a layout.php file in {@link header()}
450 // but some of the content won't be known until later, so we return a placeholder
451 // for now. This will be replaced with the real content in {@link footer()}.
452 echo self::END_HTML_TOKEN;
453 }
454
455 /**
456 * Return the standard string that says whether you are logged in (and switched
457 * roles/logged in as another user).
458 * @return string HTML fragment.
459 */
460 public function login_info() {
461 global $USER;
462 return user_login_string($this->page->course, $USER);
463 }
464
465 /**
466 * Return the 'back' link that normally appears in the footer.
467 * @return string HTML fragment.
468 */
469 public function home_link() {
470 global $CFG, $SITE;
471
472 if ($this->page->pagetype == 'site-index') {
473 // Special case for site home page - please do not remove
474 return '<div class="sitelink">' .
34dff6aa 475 '<a title="Moodle" href="http://moodle.org/">' .
d9c8f425 476 '<img style="width:100px;height:30px" src="' . $CFG->httpswwwroot . '/pix/moodlelogo.gif" alt="moodlelogo" /></a></div>';
477
478 } else if (!empty($CFG->target_release) && $CFG->target_release != $CFG->release) {
479 // Special case for during install/upgrade.
480 return '<div class="sitelink">'.
34dff6aa 481 '<a title="Moodle" href="http://docs.moodle.org/en/Administrator_documentation" onclick="this.target=\'_blank\'">' .
d9c8f425 482 '<img style="width:100px;height:30px" src="' . $CFG->httpswwwroot . '/pix/moodlelogo.gif" alt="moodlelogo" /></a></div>';
483
484 } else if ($this->page->course->id == $SITE->id || strpos($this->page->pagetype, 'course-view') === 0) {
485 return '<div class="homelink"><a href="' . $CFG->wwwroot . '/">' .
486 get_string('home') . '</a></div>';
487
488 } else {
489 return '<div class="homelink"><a href="' . $CFG->wwwroot . '/course/view.php?id=' . $this->page->course->id . '">' .
490 format_string($this->page->course->shortname) . '</a></div>';
491 }
492 }
493
494 /**
495 * Redirects the user by any means possible given the current state
496 *
497 * This function should not be called directly, it should always be called using
498 * the redirect function in lib/weblib.php
499 *
500 * The redirect function should really only be called before page output has started
501 * however it will allow itself to be called during the state STATE_IN_BODY
502 *
503 * @param string $encodedurl The URL to send to encoded if required
504 * @param string $message The message to display to the user if any
505 * @param int $delay The delay before redirecting a user, if $message has been
506 * set this is a requirement and defaults to 3, set to 0 no delay
507 * @param boolean $debugdisableredirect this redirect has been disabled for
508 * debugging purposes. Display a message that explains, and don't
509 * trigger the redirect.
510 * @return string The HTML to display to the user before dying, may contain
511 * meta refresh, javascript refresh, and may have set header redirects
512 */
513 public function redirect_message($encodedurl, $message, $delay, $debugdisableredirect) {
514 global $CFG;
515 $url = str_replace('&amp;', '&', $encodedurl);
516
517 switch ($this->page->state) {
518 case moodle_page::STATE_BEFORE_HEADER :
519 // No output yet it is safe to delivery the full arsenal of redirect methods
520 if (!$debugdisableredirect) {
521 // Don't use exactly the same time here, it can cause problems when both redirects fire at the same time.
522 $this->metarefreshtag = '<meta http-equiv="refresh" content="'. $delay .'; url='. $encodedurl .'" />'."\n";
523 $this->page->requires->js_function_call('document.location.replace', array($url))->after_delay($delay + 3);
524 }
525 $output = $this->header();
526 break;
527 case moodle_page::STATE_PRINTING_HEADER :
528 // We should hopefully never get here
529 throw new coding_exception('You cannot redirect while printing the page header');
530 break;
531 case moodle_page::STATE_IN_BODY :
532 // We really shouldn't be here but we can deal with this
533 debugging("You should really redirect before you start page output");
534 if (!$debugdisableredirect) {
535 $this->page->requires->js_function_call('document.location.replace', array($url))->after_delay($delay);
536 }
537 $output = $this->opencontainers->pop_all_but_last();
538 break;
539 case moodle_page::STATE_DONE :
540 // Too late to be calling redirect now
541 throw new coding_exception('You cannot redirect after the entire page has been generated');
542 break;
543 }
544 $output .= $this->notification($message, 'redirectmessage');
545 $output .= '<a href="'. $encodedurl .'">'. get_string('continue') .'</a>';
546 if ($debugdisableredirect) {
547 $output .= '<p><strong>Error output, so disabling automatic redirect.</strong></p>';
548 }
549 $output .= $this->footer();
550 return $output;
551 }
552
553 /**
554 * Start output by sending the HTTP headers, and printing the HTML <head>
555 * and the start of the <body>.
556 *
557 * To control what is printed, you should set properties on $PAGE. If you
558 * are familiar with the old {@link print_header()} function from Moodle 1.9
559 * you will find that there are properties on $PAGE that correspond to most
560 * of the old parameters to could be passed to print_header.
561 *
562 * Not that, in due course, the remaining $navigation, $menu parameters here
563 * will be replaced by more properties of $PAGE, but that is still to do.
564 *
d9c8f425 565 * @return string HTML that you must output this, preferably immediately.
566 */
e120c61d 567 public function header() {
d9c8f425 568 global $USER, $CFG;
569
570 $this->page->set_state(moodle_page::STATE_PRINTING_HEADER);
571
78946b9b
PS
572 // Find the appropriate page layout file, based on $this->page->pagelayout.
573 $layoutfile = $this->page->theme->layout_file($this->page->pagelayout);
574 // Render the layout using the layout file.
575 $rendered = $this->render_page_layout($layoutfile);
67e84a7f 576
78946b9b
PS
577 // Slice the rendered output into header and footer.
578 $cutpos = strpos($rendered, self::MAIN_CONTENT_TOKEN);
d9c8f425 579 if ($cutpos === false) {
78946b9b 580 throw new coding_exception('page layout file ' . $layoutfile .
d9c8f425 581 ' does not contain the string "' . self::MAIN_CONTENT_TOKEN . '".');
582 }
78946b9b
PS
583 $header = substr($rendered, 0, $cutpos);
584 $footer = substr($rendered, $cutpos + strlen(self::MAIN_CONTENT_TOKEN));
d9c8f425 585
586 if (empty($this->contenttype)) {
78946b9b 587 debugging('The page layout file did not call $OUTPUT->doctype()');
67e84a7f 588 $header = $this->doctype() . $header;
d9c8f425 589 }
590
591 send_headers($this->contenttype, $this->page->cacheable);
67e84a7f 592
d9c8f425 593 $this->opencontainers->push('header/footer', $footer);
594 $this->page->set_state(moodle_page::STATE_IN_BODY);
67e84a7f 595
d9c8f425 596 return $header . $this->skip_link_target();
597 }
598
599 /**
78946b9b
PS
600 * Renders and outputs the page layout file.
601 * @param string $layoutfile The name of the layout file
d9c8f425 602 * @return string HTML code
603 */
78946b9b 604 protected function render_page_layout($layoutfile) {
92e01ab7 605 global $CFG, $SITE, $USER;
d9c8f425 606 // The next lines are a bit tricky. The point is, here we are in a method
607 // of a renderer class, and this object may, or may not, be the same as
78946b9b 608 // the global $OUTPUT object. When rendering the page layout file, we want to use
d9c8f425 609 // this object. However, people writing Moodle code expect the current
610 // renderer to be called $OUTPUT, not $this, so define a variable called
611 // $OUTPUT pointing at $this. The same comment applies to $PAGE and $COURSE.
612 $OUTPUT = $this;
613 $PAGE = $this->page;
614 $COURSE = $this->page->course;
615
d9c8f425 616 ob_start();
78946b9b
PS
617 include($layoutfile);
618 $rendered = ob_get_contents();
d9c8f425 619 ob_end_clean();
78946b9b 620 return $rendered;
d9c8f425 621 }
622
623 /**
624 * Outputs the page's footer
625 * @return string HTML fragment
626 */
627 public function footer() {
d5a8d9aa 628 global $CFG, $DB;
0f0801f4 629
f6794ace 630 $output = $this->container_end_all(true);
d9c8f425 631
632 $footer = $this->opencontainers->pop('header/footer');
633
d5a8d9aa 634 if (debugging() and $DB and $DB->is_transaction_started()) {
03221650 635 // TODO: MDL-20625 print warning - transaction will be rolled back
d5a8d9aa
PS
636 }
637
d9c8f425 638 // Provide some performance info if required
639 $performanceinfo = '';
640 if (defined('MDL_PERF') || (!empty($CFG->perfdebug) and $CFG->perfdebug > 7)) {
641 $perf = get_performance_info();
642 if (defined('MDL_PERFTOLOG') && !function_exists('register_shutdown_function')) {
643 error_log("PERF: " . $perf['txt']);
644 }
645 if (defined('MDL_PERFTOFOOT') || debugging() || $CFG->perfdebug > 7) {
646 $performanceinfo = $perf['html'];
647 }
648 }
649 $footer = str_replace(self::PERFORMANCE_INFO_TOKEN, $performanceinfo, $footer);
650
651 $footer = str_replace(self::END_HTML_TOKEN, $this->page->requires->get_end_code(), $footer);
652
653 $this->page->set_state(moodle_page::STATE_DONE);
654
655
656 return $output . $footer;
657 }
658
f6794ace
PS
659 /**
660 * Close all but the last open container. This is useful in places like error
661 * handling, where you want to close all the open containers (apart from <body>)
662 * before outputting the error message.
663 * @param bool $shouldbenone assert that the stack should be empty now - causes a
664 * developer debug warning if it isn't.
665 * @return string the HTML required to close any open containers inside <body>.
666 */
667 public function container_end_all($shouldbenone = false) {
668 return $this->opencontainers->pop_all_but_last($shouldbenone);
669 }
670
d9c8f425 671 /**
672 * Output the row of editing icons for a block, as defined by the controls array.
673 * @param array $controls an array like {@link block_contents::$controls}.
674 * @return HTML fragment.
675 */
676 public function block_controls($controls) {
677 if (empty($controls)) {
678 return '';
679 }
680 $controlshtml = array();
681 foreach ($controls as $control) {
682 $controlshtml[] = $this->output_tag('a', array('class' => 'icon',
683 'title' => $control['caption'], 'href' => $control['url']),
c39e5ba2 684 $this->output_empty_tag('img', array('src' => $this->pix_url($control['icon'])->out(false, array(), false),
d9c8f425 685 'alt' => $control['caption'])));
686 }
687 return $this->output_tag('div', array('class' => 'commands'), implode('', $controlshtml));
688 }
689
690 /**
691 * Prints a nice side block with an optional header.
692 *
693 * The content is described
694 * by a {@link block_contents} object.
695 *
696 * @param block_contents $bc HTML for the content
697 * @param string $region the region the block is appearing in.
698 * @return string the HTML to be output.
699 */
700 function block($bc, $region) {
701 $bc = clone($bc); // Avoid messing up the object passed in.
34059565 702 $bc->prepare($this, $this->page, $this->target);
d9c8f425 703
704 $skiptitle = strip_tags($bc->title);
705 if (empty($skiptitle)) {
706 $output = '';
707 $skipdest = '';
708 } else {
709 $output = $this->output_tag('a', array('href' => '#sb-' . $bc->skipid, 'class' => 'skip-block'),
710 get_string('skipa', 'access', $skiptitle));
711 $skipdest = $this->output_tag('span', array('id' => 'sb-' . $bc->skipid, 'class' => 'skip-block-to'), '');
712 }
713
714 $bc->attributes['id'] = $bc->id;
715 $bc->attributes['class'] = $bc->get_classes_string();
716 $output .= $this->output_start_tag('div', $bc->attributes);
717
718 $controlshtml = $this->block_controls($bc->controls);
719
720 $title = '';
721 if ($bc->title) {
722 $title = $this->output_tag('h2', null, $bc->title);
723 }
724
725 if ($title || $controlshtml) {
726 $output .= $this->output_tag('div', array('class' => 'header'),
727 $this->output_tag('div', array('class' => 'title'),
728 $title . $controlshtml));
729 }
730
731 $output .= $this->output_start_tag('div', array('class' => 'content'));
732 $output .= $bc->content;
733
734 if ($bc->footer) {
735 $output .= $this->output_tag('div', array('class' => 'footer'), $bc->footer);
736 }
737
738 $output .= $this->output_end_tag('div');
739 $output .= $this->output_end_tag('div');
740
741 if ($bc->annotation) {
742 $output .= $this->output_tag('div', array('class' => 'blockannotation'), $bc->annotation);
743 }
744 $output .= $skipdest;
745
746 $this->init_block_hider_js($bc);
747 return $output;
748 }
749
750 /**
751 * Calls the JS require function to hide a block.
752 * @param block_contents $bc A block_contents object
753 * @return void
754 */
755 protected function init_block_hider_js($bc) {
756 if ($bc->collapsible != block_contents::NOT_HIDEABLE) {
757 $userpref = 'block' . $bc->blockinstanceid . 'hidden';
758 user_preference_allow_ajax_update($userpref, PARAM_BOOL);
f44b10ed
PS
759 $this->page->requires->yui2_lib('dom');
760 $this->page->requires->yui2_lib('event');
d9c8f425 761 $plaintitle = strip_tags($bc->title);
762 $this->page->requires->js_function_call('new block_hider', array($bc->id, $userpref,
763 get_string('hideblocka', 'access', $plaintitle), get_string('showblocka', 'access', $plaintitle),
c39e5ba2 764 $this->pix_url('t/switch_minus')->out(false, array(), false), $this->pix_url('t/switch_plus')->out(false, array(), false)));
d9c8f425 765 }
766 }
767
768 /**
769 * Render the contents of a block_list.
770 * @param array $icons the icon for each item.
771 * @param array $items the content of each item.
772 * @return string HTML
773 */
774 public function list_block_contents($icons, $items) {
775 $row = 0;
776 $lis = array();
777 foreach ($items as $key => $string) {
778 $item = $this->output_start_tag('li', array('class' => 'r' . $row));
2c5ec833 779 if (!empty($icons[$key])) { //test if the content has an assigned icon
780 $item .= $this->output_tag('div', array('class' => 'icon column c0'), $icons[$key]);
d9c8f425 781 }
782 $item .= $this->output_tag('div', array('class' => 'column c1'), $string);
783 $item .= $this->output_end_tag('li');
784 $lis[] = $item;
785 $row = 1 - $row; // Flip even/odd.
786 }
787 return $this->output_tag('ul', array('class' => 'list'), implode("\n", $lis));
788 }
789
790 /**
791 * Output all the blocks in a particular region.
792 * @param string $region the name of a region on this page.
793 * @return string the HTML to be output.
794 */
795 public function blocks_for_region($region) {
796 $blockcontents = $this->page->blocks->get_content_for_region($region, $this);
797
798 $output = '';
799 foreach ($blockcontents as $bc) {
800 if ($bc instanceof block_contents) {
801 $output .= $this->block($bc, $region);
802 } else if ($bc instanceof block_move_target) {
803 $output .= $this->block_move_target($bc);
804 } else {
805 throw new coding_exception('Unexpected type of thing (' . get_class($bc) . ') found in list of block contents.');
806 }
807 }
808 return $output;
809 }
810
811 /**
812 * Output a place where the block that is currently being moved can be dropped.
813 * @param block_move_target $target with the necessary details.
814 * @return string the HTML to be output.
815 */
816 public function block_move_target($target) {
817 return $this->output_tag('a', array('href' => $target->url, 'class' => 'blockmovetarget'),
818 $this->output_tag('span', array('class' => 'accesshide'), $target->text));
819 }
820
821 /**
822 * Given a html_link object, outputs an <a> tag that uses the object's attributes.
823 *
824 * @param mixed $link A html_link object or a string URL (text param required in second case)
5d77fc1d
PS
825 * @param string $text A descriptive text for the link. If $link is a html_link, this is ignored.
826 * @param array $options a tag attributes and link otpions. If $link is a html_link, this is ignored.
d9c8f425 827 * @return string HTML fragment
828 */
5d77fc1d 829 public function link($link_or_url, $text = null, array $options = null) {
3468eb2a 830 global $CFG;
6f8f4d83 831
5d77fc1d
PS
832 if ($link_or_url instanceof html_link) {
833 $link = clone($link_or_url);
834 } else {
835 $link = new html_link($link_or_url, $text, $options);
836 }
a0ead5eb 837
5d77fc1d 838 $link->prepare($this, $this->page, $this->target);
d9c8f425 839
5d77fc1d
PS
840 // A disabled link is rendered as formatted text
841 if ($link->disabled) {
842 return $this->container($link->text, 'currentlink');
843 }
d9c8f425 844
5d77fc1d 845 $this->prepare_event_handlers($link);
d9c8f425 846
5d77fc1d
PS
847 $attributes = array('href' => prepare_url($link->url),
848 'class' => $link->get_classes_string(),
849 'title' => $link->title,
850 'style' => $link->style,
851 'id' => $link->id);
d9c8f425 852
3468eb2a 853 if (!empty($CFG->frametarget)) {
5d77fc1d 854 //TODO: this seems wrong, we have to use onclick hack in order to be xhtml strict...
3468eb2a 855 $attributes['target'] = $CFG->framename;
856 }
857
5d77fc1d 858 return $this->output_tag('a', $attributes, $link->text);
d9c8f425 859 }
860
861 /**
0b634d75 862 * Print a message along with button choices for Continue/Cancel
863 *
864 * If a string or moodle_url is given instead of a html_button, method defaults to post.
865 *
d9c8f425 866 * @param string $message The question to ask the user
867 * @param mixed $continue The html_form component representing the Continue answer. Can also be a moodle_url or string URL
868 * @param mixed $cancel The html_form component representing the Cancel answer. Can also be a moodle_url or string URL
869 * @return string HTML fragment
870 */
871 public function confirm($message, $continue, $cancel) {
872 if ($continue instanceof html_form) {
873 $continue = clone($continue);
874 } else if (is_string($continue)) {
875 $continueform = new html_form();
0b634d75 876 $continueform->button->text = get_string('continue');
d9c8f425 877 $continueform->url = new moodle_url($continue);
878 $continue = $continueform;
879 } else if ($continue instanceof moodle_url) {
880 $continueform = new html_form();
0b634d75 881 $continueform->button->text = get_string('continue');
d9c8f425 882 $continueform->url = $continue;
883 $continue = $continueform;
884 } else {
885 throw new coding_exception('The continue param to $OUTPUT->confirm must be either a URL (string/moodle_url) or a html_form object.');
886 }
887
888 if ($cancel instanceof html_form) {
889 $cancel = clone($cancel);
890 } else if (is_string($cancel)) {
891 $cancelform = new html_form();
0b634d75 892 $cancelform->button->text = get_string('cancel');
d9c8f425 893 $cancelform->url = new moodle_url($cancel);
894 $cancel = $cancelform;
895 } else if ($cancel instanceof moodle_url) {
896 $cancelform = new html_form();
1f1aa445 897 $cancelform->button->text = get_string('cancel');
d9c8f425 898 $cancelform->url = $cancel;
899 $cancel = $cancelform;
900 } else {
901 throw new coding_exception('The cancel param to $OUTPUT->confirm must be either a URL (string/moodle_url) or a html_form object.');
902 }
903
d9c8f425 904 $output = $this->box_start('generalbox', 'notice');
905 $output .= $this->output_tag('p', array(), $message);
906 $output .= $this->output_tag('div', array('class' => 'buttons'), $this->button($continue) . $this->button($cancel));
907 $output .= $this->box_end();
908 return $output;
909 }
910
911 /**
912 * Given a html_form object, outputs an <input> tag within a form that uses the object's attributes.
913 *
914 * @param html_form $form A html_form object
915 * @return string HTML fragment
916 */
917 public function button($form) {
918 if (empty($form->button) or !($form->button instanceof html_button)) {
919 throw new coding_exception('$OUTPUT->button($form) requires $form to have a button (html_button) value');
920 }
921 $form = clone($form);
34059565 922 $form->button->prepare($this, $this->page, $this->target);
d9c8f425 923
924 $this->prepare_event_handlers($form->button);
925
926 $buttonattributes = array('class' => $form->button->get_classes_string(),
927 'type' => 'submit',
928 'value' => $form->button->text,
929 'disabled' => $form->button->disabled,
930 'id' => $form->button->id);
931
932 $buttonoutput = $this->output_empty_tag('input', $buttonattributes);
933
934 // Removing the button so it doesn't get output again
935 unset($form->button);
936
7a5c78e0 937 return $this->output_tag('div', array('class' => 'singlebutton'), $this->form($form, $buttonoutput));
d9c8f425 938 }
939
940 /**
941 * Given a html_form component and an optional rendered submit button,
942 * outputs a HTML form with correct divs and inputs and a single submit button.
943 * This doesn't render any other visible inputs. Use moodleforms for these.
944 * @param html_form $form A html_form instance
945 * @param string $contents HTML fragment to put inside the form. If given, must contain at least the submit button.
946 * @return string HTML fragment
947 */
948 public function form($form, $contents=null) {
949 $form = clone($form);
34059565 950 $form->prepare($this, $this->page, $this->target);
d9c8f425 951 $this->prepare_event_handlers($form);
952 $buttonoutput = null;
953
954 if (empty($contents) && !empty($form->button)) {
955 debugging("You probably want to use \$OUTPUT->button(\$form), please read that function's documentation", DEBUG_DEVELOPER);
956 } else if (empty($contents)) {
957 $contents = $this->output_empty_tag('input', array('type' => 'submit', 'value' => get_string('ok')));
958 } else if (!empty($form->button)) {
34059565 959 $form->button->prepare($this, $this->page, $this->target);
d9c8f425 960 $this->prepare_event_handlers($form->button);
961
962 $buttonattributes = array('class' => $form->button->get_classes_string(),
963 'type' => 'submit',
964 'value' => $form->button->text,
965 'disabled' => $form->button->disabled,
966 'id' => $form->button->id);
967
b65bfc3e 968 $buttonoutput = $this->output_empty_tag('input', $buttonattributes);
969
970 // Hide the submit button if the button has a JS submit action
971 if ($form->jssubmitaction) {
972 $buttonoutput = $this->output_start_tag('div', array('id' => "noscript$form->id")) . $buttonoutput . $this->output_end_tag('div');
973 $this->page->requires->js_function_call('hide_item', array("noscript$form->id"));
974 }
d9c8f425 975
976 }
977
978 $hiddenoutput = '';
979
980 foreach ($form->url->params() as $var => $val) {
981 $hiddenoutput .= $this->output_empty_tag('input', array('type' => 'hidden', 'name' => $var, 'value' => $val));
982 }
983
984 $formattributes = array(
985 'method' => $form->method,
986 'action' => prepare_url($form->url, true),
987 'id' => $form->id,
988 'class' => $form->get_classes_string());
989
990 $divoutput = $this->output_tag('div', array(), $hiddenoutput . $contents . $buttonoutput);
7a5c78e0 991 $output = $this->output_tag('form', $formattributes, $divoutput);
d9c8f425 992
993 return $output;
994 }
995
996 /**
997 * Returns a string containing a link to the user documentation.
998 * Also contains an icon by default. Shown to teachers and admin only.
999 * @param string $path The page link after doc root and language, no leading slash.
1000 * @param string $text The text to be displayed for the link
1001 * @param string $iconpath The path to the icon to be displayed
1002 */
1003 public function doc_link($path, $text=false, $iconpath=false) {
1004 global $CFG, $OUTPUT;
beb56299 1005 $icon = new moodle_action_icon();
d9c8f425 1006 $icon->linktext = $text;
1007 $icon->image->alt = $text;
1008 $icon->image->add_class('iconhelp');
1009 $icon->link->url = new moodle_url(get_docs_url($path));
1010
1011 if (!empty($iconpath)) {
1012 $icon->image->src = $iconpath;
1013 } else {
c39e5ba2 1014 $icon->image->src = $this->pix_url('docs')->out(false, array(), false);
d9c8f425 1015 }
1016
1017 if (!empty($CFG->doctonewwindow)) {
62a27b69 1018 $icon->add_action(new popup_action('click', $icon->link->url));
d9c8f425 1019 }
1020
1021 return $this->action_icon($icon);
1022
1023 }
1024
1025 /**
beb56299 1026 * Given a moodle_action_icon object, outputs an image linking to an action (URL or AJAX).
d9c8f425 1027 *
beb56299 1028 * @param moodle_action_icon $icon A moodle_action_icon object
d9c8f425 1029 * @return string HTML fragment
1030 */
1031 public function action_icon($icon) {
1032 $icon = clone($icon);
34059565 1033 $icon->prepare($this, $this->page, $this->target);
d9c8f425 1034 $imageoutput = $this->image($icon->image);
1035
1036 if ($icon->linktext) {
1037 $imageoutput .= $icon->linktext;
1038 }
1039 $icon->link->text = $imageoutput;
1040
1041 return $this->link($icon->link);
1042 }
1043
1044 /*
1045 * Centered heading with attached help button (same title text)
1046 * and optional icon attached
94056d9d 1047 * @param moodle_help_icon $helpicon A moodle_help_icon object
d9c8f425 1048 * @param mixed $image An image URL or a html_image object
1049 * @return string HTML fragment
1050 */
1051 public function heading_with_help($helpicon, $image=false) {
1052 if (!($image instanceof html_image) && !empty($image)) {
1053 $htmlimage = new html_image();
1054 $htmlimage->src = $image;
1055 $image = $htmlimage;
1056 }
1057 return $this->container($this->image($image) . $this->heading($helpicon->text, 2, 'main help') . $this->help_icon($helpicon), 'heading-with-help');
1058 }
1059
1060 /**
1061 * Print a help icon.
1062 *
94056d9d 1063 * @param moodle_help_icon $helpicon A moodle_help_icon object, subclass of html_link
d9c8f425 1064 *
1065 * @return string HTML fragment
1066 */
1067 public function help_icon($icon) {
1068 global $COURSE;
1069 $icon = clone($icon);
34059565 1070 $icon->prepare($this, $this->page, $this->target);
d9c8f425 1071
1072 $popup = new popup_action('click', $icon->link->url);
1073 $icon->link->add_action($popup);
1074
1075 $image = null;
1076
1077 if (!empty($icon->image)) {
1078 $image = $icon->image;
1079 $image->add_class('iconhelp');
1080 }
1081
1082 return $this->output_tag('span', array('class' => 'helplink'), $this->link_to_popup($icon->link, $image));
1083 }
1084
1085 /**
1086 * Creates and returns a button to a popup window
1087 *
6dd7d7f0 1088 * @param html_link $link Subclass of html_component
d9c8f425 1089 * @param moodle_popup $popup A moodle_popup object
1090 * @param html_image $image An optional image replacing the link text
1091 *
1092 * @return string HTML fragment
1093 */
1094 public function link_to_popup($link, $image=null) {
5d77fc1d
PS
1095 //TODO: decide if this should be removed completely, because link() already handles this
1096 // we could also add html_link::make_popup() factory method
d9c8f425 1097 $link = clone($link);
d9c8f425 1098
1099 // Use image if one is given
1100 if (!empty($image) && $image instanceof html_image) {
1101
b65bfc3e 1102 if (empty($image->alt) || $image->alt == HTML_ATTR_EMPTY) {
d9c8f425 1103 $image->alt = $link->text;
b65bfc3e 1104 $image->title = $link->text;
d9c8f425 1105 }
1106
1107 $link->text = $this->image($image);
1108
5d77fc1d 1109 if (!empty($link->linktext)) { // TODO: this is weird!
46aa52bf 1110 $link->text = "$link->title &#160; $link->text";
d9c8f425 1111 }
1112 }
1113
34059565 1114 $link->prepare($this, $this->page, $this->target);
02f64f97
TH
1115 $this->prepare_event_handlers($link);
1116
1117 if (empty($link->url)) {
1118 throw new coding_exception('Called $OUTPUT->link_to_popup($link) method without $link->url set.');
1119 }
1120
1121 $linkurl = prepare_url($link->url);
1122
1123 $tagoptions = array(
1124 'title' => $link->title,
1125 'id' => $link->id,
1126 'href' => ($linkurl) ? $linkurl : prepare_url($popup->url),
1127 'class' => $link->get_classes_string());
1128
d9c8f425 1129 return $this->output_tag('a', $tagoptions, $link->text);
1130 }
1131
1132 /**
1133 * Creates and returns a spacer image with optional line break.
1134 *
1ba862ec
PS
1135 * @param array $options id, alt, width=1, height=1, etc.
1136 * special options br=false (break after spacer)
d9c8f425 1137 * @return string HTML fragment
1138 */
1ba862ec
PS
1139 public function spacer(array $options = null) {
1140 $options = (array)$options;
1141 if (empty($options['width'])) {
1142 $options['width'] = 1;
1143 }
1144 if (empty($options['height'])) {
1145 $options['height'] = 1;
d9c8f425 1146 }
1ba862ec 1147 $options['class'] = 'spacer';
d9c8f425 1148
1ba862ec 1149 $output = $this->image($this->pix_url('spacer'), $options);
b65bfc3e 1150
1ba862ec
PS
1151 if (!empty($options['br'])) {
1152 $output .= '<br />';
1153 }
d9c8f425 1154
1155 return $output;
1156 }
1157
1158 /**
1159 * Creates and returns an image.
1160 *
1ba862ec
PS
1161 * @param html_image|moodle_url|string $image_or_url image or url of the image
1162 * @param array $options image attributes such as title, id, alt, widht, height
d9c8f425 1163 *
1164 * @return string HTML fragment
1165 */
1ba862ec
PS
1166 public function image($image_or_url, array $options = null) {
1167 if ($image_or_url === false) {
d9c8f425 1168 return false;
8fa16366 1169 }
1ba862ec 1170
8fa16366 1171 if ($image_or_url instanceof html_image) {
1ba862ec 1172 $image = clone($image_or_url);
1ba862ec 1173 } else {
8fa16366 1174 $image = new html_image($image_or_url, $options);
d9c8f425 1175 }
1176
34059565 1177 $image->prepare($this, $this->page, $this->target);
d9c8f425 1178
1179 $this->prepare_event_handlers($image);
1180
1181 $attributes = array('class' => $image->get_classes_string(),
1182 'src' => prepare_url($image->src),
1183 'alt' => $image->alt,
1184 'style' => $image->style,
1185 'title' => $image->title,
1186 'id' => $image->id);
1187
1ba862ec
PS
1188 // do not use prepare_legacy_width_and_height() here,
1189 // xhtml strict allows width&height and inline styles break theming too!
1190 if (!empty($image->height)) {
1191 $attributes['height'] = $image->height;
1192 }
1193 if (!empty($image->width)) {
1194 $attributes['width'] = $image->width;
d9c8f425 1195 }
1ba862ec 1196
d9c8f425 1197 return $this->output_empty_tag('img', $attributes);
1198 }
1199
1200 /**
1201 * Print the specified user's avatar.
1202 *
1203 * This method can be used in two ways:
1204 * <pre>
812dbaf7
PS
1205 * // Option 1: (shortcut for simple cases, preferred way)
1206 * // $user has come from the DB and has fields id, picture, imagealt, firstname and lastname
1207 * $OUTPUT->user_picture($user, array('popup'=>true));
1208 *
1209 * // Option 2: (not recommended)
1210 * $userpic = new user_picture();
d9c8f425 1211 * // Set properties of $userpic
812dbaf7
PS
1212 * $userpic->user = $user;
1213 * $userpic->popup = true;
d9c8f425 1214 * $OUTPUT->user_picture($userpic);
d9c8f425 1215 * </pre>
1216 *
1ba862ec 1217 * @param object|user_picture $user_or_userpicture Object with at least fields id, picture, imagealt, firstname, lastname
812dbaf7 1218 * If any of these are missing, the database is queried. Avoid this
d9c8f425 1219 * if at all possible, particularly for reports. It is very bad for performance.
812dbaf7
PS
1220 * @param array $options associative array with user picture options, used only if not a user_picture object,
1221 * options are:
1222 * - courseid=$this->page->course->id (course id of user profile in link)
1223 * - size=35 (size of image)
1224 * - link=true (make image clickable - the link leads to user profile)
1225 * - popup=false (open in popup)
1226 * - alttext=true (add image alt attribute)
1ba862ec 1227 * - etc.
d9c8f425 1228 * @return string HTML fragment
1229 */
812dbaf7
PS
1230 public function user_picture(stdClass $user_or_userpicture, array $options = null) {
1231
1232 if ($user_or_userpicture instanceof user_picture) {
1233 // we need a clone because prepare() should not be called repeatedly
1234 $userpic = clone($user_or_userpicture);
d9c8f425 1235 } else {
8fa16366 1236 $userpic = new user_picture($user_or_userpicture, $options);
d9c8f425 1237 }
1238
34059565 1239 $userpic->prepare($this, $this->page, $this->target);
d9c8f425 1240
812dbaf7
PS
1241 // get the image html output fisrt, then wrap it in link if needed
1242 $output = $this->image($userpic);
d9c8f425 1243
812dbaf7
PS
1244 if ($userpic->link) {
1245 $link = new html_link();
1246 $link->url = $userpic->url;
1247 $link->text = $output;
1248 if ($userpic->popup) {
1249 $link->add_action(new popup_action('click', $userpic->url));
d9c8f425 1250 }
812dbaf7 1251 $output = $this->link($link);
d9c8f425 1252 }
1253
1254 return $output;
1255 }
1256
1257 /**
1258 * Prints the 'Update this Modulename' button that appears on module pages.
1259 *
1260 * @param string $cmid the course_module id.
1261 * @param string $modulename the module name, eg. "forum", "quiz" or "workshop"
1262 * @return string the HTML for the button, if this user has permission to edit it, else an empty string.
1263 */
1264 public function update_module_button($cmid, $modulename) {
1265 global $CFG;
1266 if (has_capability('moodle/course:manageactivities', get_context_instance(CONTEXT_MODULE, $cmid))) {
1267 $modulename = get_string('modulename', $modulename);
1268 $string = get_string('updatethis', '', $modulename);
1269
1270 $form = new html_form();
1271 $form->url = new moodle_url("$CFG->wwwroot/course/mod.php", array('update' => $cmid, 'return' => true, 'sesskey' => sesskey()));
1272 $form->button->text = $string;
1273 return $this->button($form);
1274 } else {
1275 return '';
1276 }
1277 }
1278
1279 /**
1280 * Prints a "Turn editing on/off" button in a form.
1281 * @param moodle_url $url The URL + params to send through when clicking the button
1282 * @return string HTML the button
1283 */
1284 public function edit_button(moodle_url $url) {
1285 global $USER;
1286 if (!empty($USER->editing)) {
1287 $string = get_string('turneditingoff');
1288 $edit = '0';
1289 } else {
1290 $string = get_string('turneditingon');
1291 $edit = '1';
1292 }
1293
1294 $form = new html_form();
1295 $form->url = $url;
1296 $form->url->param('edit', $edit);
1297 $form->button->text = $string;
1298
1299 return $this->button($form);
1300 }
1301
1302 /**
1303 * Outputs a HTML nested list
1304 *
1305 * @param html_list $list A html_list object
1306 * @return string HTML structure
1307 */
1308 public function htmllist($list) {
1309 $list = clone($list);
34059565 1310 $list->prepare($this, $this->page, $this->target);
d9c8f425 1311
1312 $this->prepare_event_handlers($list);
1313
1314 if ($list->type == 'ordered') {
1315 $tag = 'ol';
1316 } else if ($list->type == 'unordered') {
1317 $tag = 'ul';
1318 }
1319
1320 $output = $this->output_start_tag($tag, array('class' => $list->get_classes_string()));
1321
1322 foreach ($list->items as $listitem) {
1323 if ($listitem instanceof html_list) {
b65bfc3e 1324 $output .= $this->output_start_tag('li', array()) . "\n";
1325 $output .= $this->htmllist($listitem) . "\n";
1326 $output .= $this->output_end_tag('li') . "\n";
d9c8f425 1327 } else if ($listitem instanceof html_list_item) {
34059565 1328 $listitem->prepare($this, $this->page, $this->target);
d9c8f425 1329 $this->prepare_event_handlers($listitem);
b65bfc3e 1330 $output .= $this->output_tag('li', array('class' => $listitem->get_classes_string()), $listitem->value) . "\n";
a0ead5eb 1331 } else {
1332 $output .= $this->output_tag('li', array(), $listitem) . "\n";
d9c8f425 1333 }
1334 }
1335
b65bfc3e 1336 if ($list->text) {
1337 $output = $list->text . $output;
1338 }
1339
d9c8f425 1340 return $output . $this->output_end_tag($tag);
1341 }
21237187 1342
54a007e8 1343 /**
1344 * Prints an inline span element with optional text contents.
1345 *
319770d7 1346 * @param mixed $span A html_span object or some string content to wrap in a span
1347 * @param mixed $classes A space-separated list or an array of classes. Only used if $span is a string
54a007e8 1348 * @return string A HTML fragment
1349 */
319770d7 1350 public function span($span, $classes='') {
1351 if (!($span instanceof html_span)) {
1352 $text = $span;
1353 $span = new html_span();
1354 $span->contents = $text;
1355 $span->add_classes($classes);
1356 }
1357
54a007e8 1358 $span = clone($span);
34059565 1359 $span->prepare($this, $this->page, $this->target);
54a007e8 1360 $this->prepare_event_handlers($span);
1361 $attributes = array('class' => $span->get_classes_string(),
1362 'alt' => $span->alt,
1363 'style' => $span->style,
1364 'title' => $span->title,
1365 'id' => $span->id);
1366 return $this->output_tag('span', $attributes, $span->contents);
1367 }
d9c8f425 1368
1369 /**
1370 * Prints a simple button to close a window
1371 *
1372 * @global objec)t
1373 * @param string $text The lang string for the button's label (already output from get_string())
1374 * @return string|void if $return is true, void otherwise
1375 */
7a5c78e0 1376 public function close_window_button($text='') {
d9c8f425 1377 if (empty($text)) {
1378 $text = get_string('closewindow');
1379 }
1380 $closeform = new html_form();
1381 $closeform->url = '#';
7a5c78e0 1382 $closeform->method = 'get';
d9c8f425 1383 $closeform->button->text = $text;
1384 $closeform->button->add_action('click', 'close_window');
34059565 1385 $closeform->button->prepare($this, $this->page, $this->target);
d9c8f425 1386 return $this->container($this->button($closeform), 'closewindow');
1387 }
1388
1389 /**
1390 * Outputs a <select> menu or a list of radio/checkbox inputs.
1391 *
1392 * This method is extremely versatile, and can be used to output yes/no menus,
1393 * form-enclosed menus with automatic redirects when an option is selected,
1394 * descriptive labels and help icons. By default it just outputs a select
1395 * menu.
1396 *
7b1f2c82 1397 * To add a descriptive label, use html_select::set_label($text, $for) or
1398 * html_select::set_label($label) passing a html_label object
d9c8f425 1399 *
7b1f2c82 1400 * To add a help icon, use html_select::set_help($page, $text, $linktext) or
1401 * html_select::set_help($helpicon) passing a moodle_help_icon object
d9c8f425 1402 *
7b1f2c82 1403 * If you html_select::$rendertype to "radio", it will render radio buttons
d9c8f425 1404 * instead of a <select> menu, unless $multiple is true, in which case it
1405 * will render checkboxes.
1406 *
7b1f2c82 1407 * To surround the menu with a form, simply set html_select->form as a
d9c8f425 1408 * valid html_form object. Note that this function will NOT automatically
1409 * add a form for non-JS browsers. If you do not set one up, it assumes
1410 * that you are providing your own form in some other way.
1411 *
7b1f2c82 1412 * You can either call this function with a single html_select argument
d9c8f425 1413 * or, with a list of parameters, in which case those parameters are sent to
7b1f2c82 1414 * the html_select constructor.
d9c8f425 1415 *
7b1f2c82 1416 * @param html_select $select a html_select that describes
d9c8f425 1417 * the select menu you want output.
1418 * @return string the HTML for the <select>
1419 */
1420 public function select($select) {
1421 $select = clone($select);
34059565 1422 $select->prepare($this, $this->page, $this->target);
d9c8f425 1423
1424 $this->prepare_event_handlers($select);
1425
1426 if (empty($select->id)) {
1427 $select->id = 'menu' . str_replace(array('[', ']'), '', $select->name);
1428 }
1429
1430 $attributes = array(
1431 'name' => $select->name,
1432 'id' => $select->id,
1433 'class' => $select->get_classes_string()
1434 );
1435 if ($select->disabled) {
1436 $attributes['disabled'] = 'disabled';
1437 }
1438 if ($select->tabindex) {
93b026ee 1439 $attributes['tabindex'] = $select->tabindex;
d9c8f425 1440 }
1441
1442 if ($select->rendertype == 'menu' && $select->listbox) {
1443 if (is_integer($select->listbox)) {
1444 $size = $select->listbox;
1445 } else {
1446 $size = min($select->maxautosize, count($select->options));
1447 }
1448 $attributes['size'] = $size;
1449 if ($select->multiple) {
1450 $attributes['multiple'] = 'multiple';
1451 }
1452 }
1453
1454 $html = '';
1455
1456 if (!empty($select->label)) {
1457 $html .= $this->label($select->label);
1458 }
1459
94056d9d 1460 if (!empty($select->helpicon) && $select->helpicon instanceof moodle_help_icon) {
d9c8f425 1461 $html .= $this->help_icon($select->helpicon);
1462 }
1463
1464 if ($select->rendertype == 'menu') {
1465 $html .= $this->output_start_tag('select', $attributes) . "\n";
1466
1467 foreach ($select->options as $option) {
1468 // $OUTPUT->select_option detects if $option is an option or an optgroup
1469 $html .= $this->select_option($option);
1470 }
1471
1472 $html .= $this->output_end_tag('select') . "\n";
1473 } else if ($select->rendertype == 'radio') {
1474 $currentradio = 0;
1475 foreach ($select->options as $option) {
1476 $html .= $this->radio($option, $select->name);
1477 $currentradio++;
1478 }
1479 } else if ($select->rendertype == 'checkbox') {
1480 $currentcheckbox = 0;
1ae3767a 1481 // If only two choices are available, suggest using the checkbox method instead
1482 if (count($select->options) < 3 && !$select->multiple) {
1483 debugging('You are using $OUTPUT->select() to render two mutually exclusive choices using checkboxes. Please use $OUTPUT->checkbox(html_select_option) instead.', DEBUG_DEVELOPER);
1484 } else {
1485 foreach ($select->options as $option) {
1486 $html .= $this->checkbox($option, $select->name);
1487 $currentcheckbox++;
1488 }
d9c8f425 1489 }
1490 }
1491
1492 if (!empty($select->form) && $select->form instanceof html_form) {
1493 $html = $this->form($select->form, $html);
1494 }
1495
1496 return $html;
1497 }
1498
1499 /**
1500 * Outputs a <input type="radio" /> element. Optgroups are ignored, so do not
1501 * pass a html_select_optgroup as a param to this function.
1502 *
1503 * @param html_select_option $option a html_select_option
1504 * @return string the HTML for the <input type="radio">
1505 */
1506 public function radio($option, $name='unnamed') {
1ae3767a 1507 static $currentradio = array();
e57c283d 1508
1ae3767a 1509 if (empty($currentradio[$name])) {
1510 $currentradio[$name] = 0;
1511 }
1512
d9c8f425 1513 if ($option instanceof html_select_optgroup) {
1514 throw new coding_exception('$OUTPUT->radio($option) does not support a html_select_optgroup object as param.');
1515 } else if (!($option instanceof html_select_option)) {
1516 throw new coding_exception('$OUTPUT->radio($option) only accepts a html_select_option object as param.');
1517 }
1518 $option = clone($option);
34059565 1519 $option->prepare($this, $this->page, $this->target);
d9c8f425 1520 $option->label->for = $option->id;
1521 $this->prepare_event_handlers($option);
1522
1ae3767a 1523 $output = $this->output_start_tag('span', array('class' => "radiogroup $name rb{$currentradio[$name]}")) . "\n";
d9c8f425 1524 $output .= $this->label($option->label);
1525
1526 if ($option->selected == 'selected') {
1527 $option->selected = 'checked';
1528 }
1529
1530 $output .= $this->output_empty_tag('input', array(
1531 'type' => 'radio',
1532 'value' => $option->value,
1533 'name' => $name,
1534 'alt' => $option->alt,
1535 'id' => $option->id,
1536 'class' => $option->get_classes_string(),
1537 'checked' => $option->selected));
1538
1539 $output .= $this->output_end_tag('span');
1ae3767a 1540 $currentradio[$name]++;
d9c8f425 1541 return $output;
1542 }
1543
1544 /**
1545 * Outputs a <input type="checkbox" /> element. Optgroups are ignored, so do not
1546 * pass a html_select_optgroup as a param to this function.
1547 *
1548 * @param html_select_option $option a html_select_option
1549 * @return string the HTML for the <input type="checkbox">
1550 */
1551 public function checkbox($option, $name='unnamed') {
1552 if ($option instanceof html_select_optgroup) {
1553 throw new coding_exception('$OUTPUT->checkbox($option) does not support a html_select_optgroup object as param.');
1554 } else if (!($option instanceof html_select_option)) {
1555 throw new coding_exception('$OUTPUT->checkbox($option) only accepts a html_select_option object as param.');
1556 }
1557 $option = clone($option);
34059565 1558 $option->prepare($this, $this->page, $this->target);
d9c8f425 1559
1560 $option->label->for = $option->id;
1561 $this->prepare_event_handlers($option);
1562
1563 $output = $this->output_start_tag('span', array('class' => "checkbox $name")) . "\n";
1564
a8744ef1 1565 if ($option->selected) {
d9c8f425 1566 $option->selected = 'checked';
a8744ef1 1567 } else {
1568 $option->selected = '';
d9c8f425 1569 }
1570
1571 $output .= $this->output_empty_tag('input', array(
1572 'type' => 'checkbox',
1573 'value' => $option->value,
1574 'name' => $name,
1575 'id' => $option->id,
1576 'alt' => $option->alt,
a4998d01 1577 'disabled' => $option->disabled,
d9c8f425 1578 'class' => $option->get_classes_string(),
1579 'checked' => $option->selected));
1580 $output .= $this->label($option->label);
1581
1582 $output .= $this->output_end_tag('span');
1583
1584 return $output;
1585 }
1586
1587 /**
1588 * Output an <option> or <optgroup> element. If an optgroup element is detected,
1589 * this will recursively output its options as well.
1590 *
7b1f2c82 1591 * @param mixed $option a html_select_option or html_select_optgroup
d9c8f425 1592 * @return string the HTML for the <option> or <optgroup>
1593 */
1594 public function select_option($option) {
1595 $option = clone($option);
34059565 1596 $option->prepare($this, $this->page, $this->target);
d9c8f425 1597 $this->prepare_event_handlers($option);
1598
1599 if ($option instanceof html_select_option) {
1600 return $this->output_tag('option', array(
1601 'value' => $option->value,
a4998d01 1602 'disabled' => $option->disabled,
d9c8f425 1603 'class' => $option->get_classes_string(),
1604 'selected' => $option->selected), $option->text);
1605 } else if ($option instanceof html_select_optgroup) {
1606 $output = $this->output_start_tag('optgroup', array('label' => $option->text, 'class' => $option->get_classes_string()));
1607 foreach ($option->options as $selectoption) {
1608 $output .= $this->select_option($selectoption);
1609 }
1610 $output .= $this->output_end_tag('optgroup');
1611 return $output;
1612 }
1613 }
1614
1615 /**
1616 * Output an <input type="text"> element
1617 *
1618 * @param html_field $field a html_field object
1619 * @return string the HTML for the <input>
1620 */
1621 public function textfield($field) {
1c1f64a2 1622 return $this->output_tag('span', array('class' => "textfield $field->name"), $this->field($field));
1623 }
1624
1625 /**
1626 * Output an <input/> element
1627 *
1628 * @param html_field $field a html_field object
1629 * @return string the HTML for the <input>
1630 */
1631 public function field($field) {
d9c8f425 1632 $field = clone($field);
34059565 1633 $field->prepare($this, $this->page, $this->target);
d9c8f425 1634 $this->prepare_event_handlers($field);
a019627a 1635 $label = '';
1c1f64a2 1636 if (!empty($field->label->text)) {
a019627a 1637 $label = $this->label($field->label);
3cc457db 1638 }
a019627a 1639 return $label . $this->output_empty_tag('input', array(
1c1f64a2 1640 'type' => $field->type,
d9c8f425 1641 'name' => $field->name,
1642 'id' => $field->id,
1643 'value' => $field->value,
5fc6d585 1644 'disabled' => $field->disabled,
d9c8f425 1645 'style' => $field->style,
1646 'alt' => $field->alt,
1c1f64a2 1647 'title' => $field->title,
d9c8f425 1648 'maxlength' => $field->maxlength));
d9c8f425 1649 }
1650
1651 /**
1652 * Outputs a <label> element.
1653 * @param html_label $label A html_label object
1654 * @return HTML fragment
1655 */
1656 public function label($label) {
1657 $label = clone($label);
34059565 1658 $label->prepare($this, $this->page, $this->target);
d9c8f425 1659 $this->prepare_event_handlers($label);
1660 return $this->output_tag('label', array('for' => $label->for, 'class' => $label->get_classes_string()), $label->text);
1661 }
1662
1663 /**
1664 * Output an error message. By default wraps the error message in <span class="error">.
1665 * If the error message is blank, nothing is output.
1666 * @param string $message the error message.
1667 * @return string the HTML to output.
1668 */
1669 public function error_text($message) {
1670 if (empty($message)) {
1671 return '';
1672 }
1673 return $this->output_tag('span', array('class' => 'error'), $message);
1674 }
1675
1676 /**
1677 * Do not call this function directly.
1678 *
1679 * To terminate the current script with a fatal error, call the {@link print_error}
1680 * function, or throw an exception. Doing either of those things will then call this
1681 * function to display the error, before terminating the execution.
1682 *
1683 * @param string $message The message to output
1684 * @param string $moreinfourl URL where more info can be found about the error
1685 * @param string $link Link for the Continue button
1686 * @param array $backtrace The execution backtrace
1687 * @param string $debuginfo Debugging information
d9c8f425 1688 * @return string the HTML to output.
1689 */
83267ec0 1690 public function fatal_error($message, $moreinfourl, $link, $backtrace, $debuginfo = null) {
d9c8f425 1691
1692 $output = '';
6f8f4d83 1693 $obbuffer = '';
e57c283d 1694
d9c8f425 1695 if ($this->has_started()) {
50764d37
PS
1696 // we can not always recover properly here, we have problems with output buffering,
1697 // html tables, etc.
d9c8f425 1698 $output .= $this->opencontainers->pop_all_but_last();
50764d37 1699
d9c8f425 1700 } else {
50764d37
PS
1701 // It is really bad if library code throws exception when output buffering is on,
1702 // because the buffered text would be printed before our start of page.
1703 // NOTE: this hack might be behave unexpectedly in case output buffering is enabled in PHP.ini
1704 while (ob_get_level() > 0) {
6f8f4d83 1705 $obbuffer .= ob_get_clean();
50764d37 1706 }
6f8f4d83 1707
d9c8f425 1708 // Header not yet printed
85309744 1709 if (isset($_SERVER['SERVER_PROTOCOL'])) {
78946b9b
PS
1710 // server protocol should be always present, because this render
1711 // can not be used from command line or when outputting custom XML
85309744
PS
1712 @header($_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found');
1713 }
6d92adcb 1714 $this->page->set_url(''); // no url
191b267b 1715 //$this->page->set_pagelayout('base'); //TODO: MDL-20676 blocks on error pages are weird, unfortunately it somehow detect the pagelayout from URL :-(
d9c8f425 1716 $this->page->set_title(get_string('error'));
1717 $output .= $this->header();
1718 }
1719
1720 $message = '<p class="errormessage">' . $message . '</p>'.
1721 '<p class="errorcode"><a href="' . $moreinfourl . '">' .
1722 get_string('moreinformation') . '</a></p>';
1723 $output .= $this->box($message, 'errorbox');
1724
6f8f4d83
PS
1725 if (debugging('', DEBUG_DEVELOPER)) {
1726 if (!empty($debuginfo)) {
1727 $output .= $this->notification('<strong>Debug info:</strong> '.s($debuginfo), 'notifytiny');
1728 }
1729 if (!empty($backtrace)) {
1730 $output .= $this->notification('<strong>Stack trace:</strong> '.format_backtrace($backtrace), 'notifytiny');
1731 }
1732 if ($obbuffer !== '' ) {
1733 $output .= $this->notification('<strong>Output buffer:</strong> '.s($obbuffer), 'notifytiny');
1734 }
d9c8f425 1735 }
1736
1737 if (!empty($link)) {
1738 $output .= $this->continue_button($link);
1739 }
1740
1741 $output .= $this->footer();
1742
1743 // Padding to encourage IE to display our error page, rather than its own.
1744 $output .= str_repeat(' ', 512);
1745
1746 return $output;
1747 }
1748
1749 /**
1750 * Output a notification (that is, a status message about something that has
1751 * just happened).
1752 *
1753 * @param string $message the message to print out
1754 * @param string $classes normally 'notifyproblem' or 'notifysuccess'.
1755 * @return string the HTML to output.
1756 */
1757 public function notification($message, $classes = 'notifyproblem') {
1758 return $this->output_tag('div', array('class' =>
78946b9b 1759 renderer_base::prepare_classes($classes)), clean_text($message));
d9c8f425 1760 }
1761
1762 /**
1763 * Print a continue button that goes to a particular URL.
1764 *
1765 * @param string|moodle_url $link The url the button goes to.
1766 * @return string the HTML to output.
1767 */
1768 public function continue_button($link) {
1769 if (!is_a($link, 'moodle_url')) {
1770 $link = new moodle_url($link);
1771 }
1772 $form = new html_form();
1773 $form->url = $link;
1774 $form->values = $link->params();
1775 $form->button->text = get_string('continue');
1776 $form->method = 'get';
1777
1778 return $this->output_tag('div', array('class' => 'continuebutton') , $this->button($form));
1779 }
1780
1781 /**
1782 * Prints a single paging bar to provide access to other pages (usually in a search)
1783 *
1784 * @param string|moodle_url $link The url the button goes to.
1785 * @return string the HTML to output.
1786 */
1787 public function paging_bar($pagingbar) {
1788 $output = '';
1789 $pagingbar = clone($pagingbar);
34059565 1790 $pagingbar->prepare($this, $this->page, $this->target);
d9c8f425 1791
1792 if ($pagingbar->totalcount > $pagingbar->perpage) {
1793 $output .= get_string('page') . ':';
1794
1795 if (!empty($pagingbar->previouslink)) {
46aa52bf 1796 $output .= '&#160;(' . $this->link($pagingbar->previouslink) . ')&#160;';
d9c8f425 1797 }
1798
1799 if (!empty($pagingbar->firstlink)) {
46aa52bf 1800 $output .= '&#160;' . $this->link($pagingbar->firstlink) . '&#160;...';
d9c8f425 1801 }
1802
1803 foreach ($pagingbar->pagelinks as $link) {
1804 if ($link instanceof html_link) {
46aa52bf 1805 $output .= '&#160;&#160;' . $this->link($link);
d9c8f425 1806 } else {
46aa52bf 1807 $output .= "&#160;&#160;$link";
d9c8f425 1808 }
1809 }
1810
1811 if (!empty($pagingbar->lastlink)) {
46aa52bf 1812 $output .= '&#160;...' . $this->link($pagingbar->lastlink) . '&#160;';
d9c8f425 1813 }
1814
1815 if (!empty($pagingbar->nextlink)) {
46aa52bf 1816 $output .= '&#160;&#160;(' . $this->link($pagingbar->nextlink) . ')';
d9c8f425 1817 }
1818 }
1819
1820 return $this->output_tag('div', array('class' => 'paging'), $output);
1821 }
1822
1823 /**
1824 * Render a HTML table
1825 *
1826 * @param object $table {@link html_table} instance containing all the information needed
1827 * @return string the HTML to output.
1828 */
1829 public function table(html_table $table) {
1830 $table = clone($table);
34059565 1831 $table->prepare($this, $this->page, $this->target);
d9c8f425 1832 $attributes = array(
1833 'id' => $table->id,
1834 'width' => $table->width,
1835 'summary' => $table->summary,
1836 'cellpadding' => $table->cellpadding,
1837 'cellspacing' => $table->cellspacing,
1838 'class' => $table->get_classes_string());
1839 $output = $this->output_start_tag('table', $attributes) . "\n";
1840
1841 $countcols = 0;
1842
1843 if (!empty($table->head)) {
1844 $countcols = count($table->head);
319770d7 1845 $output .= $this->output_start_tag('thead', $table->headclasses) . "\n";
d9c8f425 1846 $output .= $this->output_start_tag('tr', array()) . "\n";
1847 $keys = array_keys($table->head);
1848 $lastkey = end($keys);
54a007e8 1849
d9c8f425 1850 foreach ($table->head as $key => $heading) {
54a007e8 1851 // Convert plain string headings into html_table_cell objects
1852 if (!($heading instanceof html_table_cell)) {
1853 $headingtext = $heading;
1854 $heading = new html_table_cell();
1855 $heading->text = $headingtext;
1856 $heading->header = true;
1857 }
f2a51402 1858
a4998d01 1859 if ($heading->header !== false) {
1860 $heading->header = true;
1861 }
54a007e8 1862
1863 $this->prepare_event_handlers($heading);
1864
1865 $heading->add_classes(array('header', 'c' . $key));
d9c8f425 1866 if (isset($table->headspan[$key]) && $table->headspan[$key] > 1) {
54a007e8 1867 $heading->colspan = $table->headspan[$key];
d9c8f425 1868 $countcols += $table->headspan[$key] - 1;
21237187 1869 }
54a007e8 1870
d9c8f425 1871 if ($key == $lastkey) {
54a007e8 1872 $heading->add_class('lastcol');
d9c8f425 1873 }
1874 if (isset($table->colclasses[$key])) {
54a007e8 1875 $heading->add_class($table->colclasses[$key]);
d9c8f425 1876 }
1877 if ($table->rotateheaders) {
1878 // we need to wrap the heading content
54a007e8 1879 $heading->text = $this->output_tag('span', '', $heading->text);
d9c8f425 1880 }
54a007e8 1881
d9c8f425 1882 $attributes = array(
54a007e8 1883 'style' => $table->align[$key] . $table->size[$key] . $heading->style,
1884 'class' => $heading->get_classes_string(),
1885 'scope' => $heading->scope,
1886 'colspan' => $heading->colspan);
21237187 1887
a4998d01 1888 $tagtype = 'td';
1889 if ($heading->header === true) {
1890 $tagtype = 'th';
1891 }
1892 $output .= $this->output_tag($tagtype, $attributes, $heading->text) . "\n";
d9c8f425 1893 }
1894 $output .= $this->output_end_tag('tr') . "\n";
1895 $output .= $this->output_end_tag('thead') . "\n";
1896 }
1897
1898 if (!empty($table->data)) {
1899 $oddeven = 1;
1900 $keys = array_keys($table->data);
1901 $lastrowkey = end($keys);
78946b9b 1902 $output .= $this->output_start_tag('tbody', array('class' => renderer_base::prepare_classes($table->bodyclasses))) . "\n";
d9c8f425 1903
1904 foreach ($table->data as $key => $row) {
1905 if (($row === 'hr') && ($countcols)) {
1906 $output .= $this->output_tag('td', array('colspan' => $countcols),
1907 $this->output_tag('div', array('class' => 'tabledivider'), '')) . "\n";
1908 } else {
1909 // Convert array rows to html_table_rows and cell strings to html_table_cell objects
1910 if (!($row instanceof html_table_row)) {
1911 $newrow = new html_table_row();
1912
1913 foreach ($row as $unused => $item) {
1914 $cell = new html_table_cell();
1915 $cell->text = $item;
54a007e8 1916 $this->prepare_event_handlers($cell);
d9c8f425 1917 $newrow->cells[] = $cell;
1918 }
1919 $row = $newrow;
1920 }
21237187 1921
54a007e8 1922 $this->prepare_event_handlers($row);
d9c8f425 1923
1924 $oddeven = $oddeven ? 0 : 1;
1925 if (isset($table->rowclasses[$key])) {
6dd7d7f0 1926 $row->add_classes(array_unique(html_component::clean_classes($table->rowclasses[$key])));
d9c8f425 1927 }
1928
1929 $row->add_class('r' . $oddeven);
1930 if ($key == $lastrowkey) {
1931 $row->add_class('lastrow');
1932 }
1933
1934 $output .= $this->output_start_tag('tr', array('class' => $row->get_classes_string(), 'style' => $row->style, 'id' => $row->id)) . "\n";
1935 $keys2 = array_keys($row->cells);
1936 $lastkey = end($keys2);
1937
1938 foreach ($row->cells as $key => $cell) {
54a007e8 1939 if (!($cell instanceof html_table_cell)) {
1940 $mycell = new html_table_cell();
1941 $mycell->text = $cell;
1942 $this->prepare_event_handlers($mycell);
1943 $cell = $mycell;
1944 }
1945
d9c8f425 1946 if (isset($table->colclasses[$key])) {
6dd7d7f0 1947 $cell->add_classes(array_unique(html_component::clean_classes($table->colclasses[$key])));
d9c8f425 1948 }
1949
1950 $cell->add_classes('cell');
1951 $cell->add_classes('c' . $key);
1952 if ($key == $lastkey) {
1953 $cell->add_classes('lastcol');
1954 }
1955 $tdstyle = '';
1956 $tdstyle .= isset($table->align[$key]) ? $table->align[$key] : '';
1957 $tdstyle .= isset($table->size[$key]) ? $table->size[$key] : '';
1958 $tdstyle .= isset($table->wrap[$key]) ? $table->wrap[$key] : '';
1959 $tdattributes = array(
1960 'style' => $tdstyle . $cell->style,
1961 'colspan' => $cell->colspan,
1962 'rowspan' => $cell->rowspan,
1963 'id' => $cell->id,
1964 'class' => $cell->get_classes_string(),
1965 'abbr' => $cell->abbr,
e09e9d55 1966 'scope' => $cell->scope,
a2431800 1967 'title' => $cell->title);
1ae3767a 1968 $tagtype = 'td';
a4998d01 1969 if ($cell->header === true) {
1ae3767a 1970 $tagtype = 'th';
1971 }
1972 $output .= $this->output_tag($tagtype, $tdattributes, $cell->text) . "\n";
d9c8f425 1973 }
1974 }
1975 $output .= $this->output_end_tag('tr') . "\n";
1976 }
1977 $output .= $this->output_end_tag('tbody') . "\n";
1978 }
1979 $output .= $this->output_end_tag('table') . "\n";
1980
1981 if ($table->rotateheaders && can_use_rotated_text()) {
f44b10ed 1982 $this->page->requires->yui2_lib('event');
d9c8f425 1983 $this->page->requires->js('course/report/progress/textrotate.js');
1984 }
1985
1986 return $output;
1987 }
1988
1989 /**
1990 * Output the place a skip link goes to.
1991 * @param string $id The target name from the corresponding $PAGE->requires->skip_link_to($target) call.
1992 * @return string the HTML to output.
1993 */
1994 public function skip_link_target($id = '') {
1995 return $this->output_tag('span', array('id' => $id), '');
1996 }
1997
1998 /**
1999 * Outputs a heading
2000 * @param string $text The text of the heading
2001 * @param int $level The level of importance of the heading. Defaulting to 2
2002 * @param string $classes A space-separated list of CSS classes
2003 * @param string $id An optional ID
2004 * @return string the HTML to output.
2005 */
2006 public function heading($text, $level = 2, $classes = 'main', $id = '') {
2007 $level = (integer) $level;
2008 if ($level < 1 or $level > 6) {
2009 throw new coding_exception('Heading level must be an integer between 1 and 6.');
2010 }
2011 return $this->output_tag('h' . $level,
78946b9b 2012 array('id' => $id, 'class' => renderer_base::prepare_classes($classes)), $text);
d9c8f425 2013 }
2014
2015 /**
2016 * Outputs a box.
2017 * @param string $contents The contents of the box
2018 * @param string $classes A space-separated list of CSS classes
2019 * @param string $id An optional ID
2020 * @return string the HTML to output.
2021 */
2022 public function box($contents, $classes = 'generalbox', $id = '') {
2023 return $this->box_start($classes, $id) . $contents . $this->box_end();
2024 }
2025
2026 /**
2027 * Outputs the opening section of a box.
2028 * @param string $classes A space-separated list of CSS classes
2029 * @param string $id An optional ID
2030 * @return string the HTML to output.
2031 */
2032 public function box_start($classes = 'generalbox', $id = '') {
2033 $this->opencontainers->push('box', $this->output_end_tag('div'));
2034 return $this->output_start_tag('div', array('id' => $id,
78946b9b 2035 'class' => 'box ' . renderer_base::prepare_classes($classes)));
d9c8f425 2036 }
2037
2038 /**
2039 * Outputs the closing section of a box.
2040 * @return string the HTML to output.
2041 */
2042 public function box_end() {
2043 return $this->opencontainers->pop('box');
2044 }
2045
2046 /**
2047 * Outputs a container.
2048 * @param string $contents The contents of the box
2049 * @param string $classes A space-separated list of CSS classes
2050 * @param string $id An optional ID
2051 * @return string the HTML to output.
2052 */
2053 public function container($contents, $classes = '', $id = '') {
2054 return $this->container_start($classes, $id) . $contents . $this->container_end();
2055 }
2056
2057 /**
2058 * Outputs the opening section of a container.
2059 * @param string $classes A space-separated list of CSS classes
2060 * @param string $id An optional ID
2061 * @return string the HTML to output.
2062 */
2063 public function container_start($classes = '', $id = '') {
2064 $this->opencontainers->push('container', $this->output_end_tag('div'));
2065 return $this->output_start_tag('div', array('id' => $id,
78946b9b 2066 'class' => renderer_base::prepare_classes($classes)));
d9c8f425 2067 }
2068
2069 /**
2070 * Outputs the closing section of a container.
2071 * @return string the HTML to output.
2072 */
2073 public function container_end() {
2074 return $this->opencontainers->pop('container');
2075 }
7d2a0492 2076
2077 /**
2078 * Make nested HTML lists out of the items
2079 *
2080 * The resulting list will look something like this:
2081 *
2082 * <pre>
2083 * <<ul>>
2084 * <<li>><div class='tree_item parent'>(item contents)</div>
2085 * <<ul>
2086 * <<li>><div class='tree_item'>(item contents)</div><</li>>
2087 * <</ul>>
2088 * <</li>>
2089 * <</ul>>
2090 * </pre>
2091 *
2092 * @param array[]tree_item $items
2093 * @param array[string]string $attrs html attributes passed to the top of
2094 * the list
2095 * @return string HTML
2096 */
2097 function tree_block_contents($items, $attrs=array()) {
2098 // exit if empty, we don't want an empty ul element
2099 if (empty($items)) {
2100 return '';
2101 }
2102 // array of nested li elements
2103 $lis = array();
2104 foreach ($items as $item) {
2105 // this applies to the li item which contains all child lists too
2106 $content = $item->content($this);
2107 $liclasses = array($item->get_css_type());
2108 if (!$item->forceopen || (!$item->forceopen && $item->collapse) || (count($item->children)==0 && $item->nodetype==navigation_node::NODETYPE_BRANCH)) {
2109 $liclasses[] = 'collapsed';
2110 }
2111 if ($item->isactive === true) {
2112 $liclasses[] = 'current_branch';
2113 }
2114 $liattr = array('class'=>join(' ',$liclasses));
2115 // class attribute on the div item which only contains the item content
2116 $divclasses = array('tree_item');
2117 if (!empty($item->children) || $item->nodetype==navigation_node::NODETYPE_BRANCH) {
2118 $divclasses[] = 'branch';
2119 } else {
2120 $divclasses[] = 'leaf';
2121 }
2122 if (!empty($item->classes) && count($item->classes)>0) {
2123 $divclasses[] = join(' ', $item->classes);
2124 }
2125 $divattr = array('class'=>join(' ', $divclasses));
2126 if (!empty($item->id)) {
2127 $divattr['id'] = $item->id;
2128 }
2129 $content = $this->output_tag('p', $divattr, $content) . $this->tree_block_contents($item->children);
2130 if (!empty($item->preceedwithhr) && $item->preceedwithhr===true) {
2131 $content = $this->output_tag('hr', array(), null).$content;
2132 }
2133 $content = $this->output_tag('li', $liattr, $content);
2134 $lis[] = $content;
2135 }
2136 return $this->output_tag('ul', $attrs, implode("\n", $lis));
2137 }
2138
2139 /**
2140 * Return the navbar content so that it can be echoed out by the layout
2141 * @return string XHTML navbar
2142 */
2143 public function navbar() {
2144 return $this->page->navbar->content();
2145 }
92e01ab7
PS
2146
2147 /**
2148 * Accessibility: Right arrow-like character is
2149 * used in the breadcrumb trail, course navigation menu
2150 * (previous/next activity), calendar, and search forum block.
2151 * If the theme does not set characters, appropriate defaults
2152 * are set automatically. Please DO NOT
2153 * use &lt; &gt; &raquo; - these are confusing for blind users.
2154 * @return string
2155 */
2156 public function rarrow() {
2157 return $this->page->theme->rarrow;
2158 }
2159
2160 /**
2161 * Accessibility: Right arrow-like character is
2162 * used in the breadcrumb trail, course navigation menu
2163 * (previous/next activity), calendar, and search forum block.
2164 * If the theme does not set characters, appropriate defaults
2165 * are set automatically. Please DO NOT
2166 * use &lt; &gt; &raquo; - these are confusing for blind users.
2167 * @return string
2168 */
2169 public function larrow() {
2170 return $this->page->theme->larrow;
2171 }
088ccb43
PS
2172
2173 /**
2174 * Returns the colours of the small MP3 player
2175 * @return string
2176 */
2177 public function filter_mediaplugin_colors() {
2178 return $this->page->theme->filter_mediaplugin_colors;
2179 }
2180
2181 /**
2182 * Returns the colours of the big MP3 player
2183 * @return string
2184 */
2185 public function resource_mp3player_colors() {
2186 return $this->page->theme->resource_mp3player_colors;
2187 }
78946b9b 2188}
d9c8f425 2189
2190
2191/// RENDERERS
2192
2193/**
2194 * A renderer that generates output for command-line scripts.
2195 *
2196 * The implementation of this renderer is probably incomplete.
2197 *
2198 * @copyright 2009 Tim Hunt
2199 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2200 * @since Moodle 2.0
2201 */
56cbc53b 2202class core_renderer_cli extends core_renderer {
d9c8f425 2203 /**
2204 * Returns the page header.
2205 * @return string HTML fragment
2206 */
2207 public function header() {
2208 output_starting_hook();
2209 return $this->page->heading . "\n";
2210 }
2211
2212 /**
2213 * Returns a template fragment representing a Heading.
2214 * @param string $text The text of the heading
2215 * @param int $level The level of importance of the heading
2216 * @param string $classes A space-separated list of CSS classes
2217 * @param string $id An optional ID
2218 * @return string A template fragment for a heading
2219 */
2220 public function heading($text, $level, $classes = 'main', $id = '') {
2221 $text .= "\n";
2222 switch ($level) {
2223 case 1:
2224 return '=>' . $text;
2225 case 2:
2226 return '-->' . $text;
2227 default:
2228 return $text;
2229 }
2230 }
2231
2232 /**
2233 * Returns a template fragment representing a fatal error.
2234 * @param string $message The message to output
2235 * @param string $moreinfourl URL where more info can be found about the error
2236 * @param string $link Link for the Continue button
2237 * @param array $backtrace The execution backtrace
2238 * @param string $debuginfo Debugging information
d9c8f425 2239 * @return string A template fragment for a fatal error
2240 */
83267ec0 2241 public function fatal_error($message, $moreinfourl, $link, $backtrace, $debuginfo = null) {
d9c8f425 2242 $output = "!!! $message !!!\n";
2243
2244 if (debugging('', DEBUG_DEVELOPER)) {
2245 if (!empty($debuginfo)) {
2246 $this->notification($debuginfo, 'notifytiny');
2247 }
2248 if (!empty($backtrace)) {
2249 $this->notification('Stack trace: ' . format_backtrace($backtrace, true), 'notifytiny');
2250 }
2251 }
2252 }
2253
2254 /**
2255 * Returns a template fragment representing a notification.
2256 * @param string $message The message to include
2257 * @param string $classes A space-separated list of CSS classes
2258 * @return string A template fragment for a notification
2259 */
2260 public function notification($message, $classes = 'notifyproblem') {
2261 $message = clean_text($message);
2262 if ($classes === 'notifysuccess') {
2263 return "++ $message ++\n";
2264 }
2265 return "!! $message !!\n";
2266 }
2267}
2268