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