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