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