MDL-19077 Split outputlib.php into smaller logical files.
[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();
497 $this->page->requires->js('lib/cookies.js')->in_head();
498 $this->page->requires->js_function_call('setTimeout', Array('fix_column_widths()', 20));
499
500 $focus = $this->page->focuscontrol;
501 if (!empty($focus)) {
502 if (preg_match("#forms\['([a-zA-Z0-9]+)'\].elements\['([a-zA-Z0-9]+)'\]#", $focus, $matches)) {
503 // This is a horrifically bad way to handle focus but it is passed in
504 // through messy formslib::moodleform
505 $this->page->requires->js_function_call('old_onload_focus', Array($matches[1], $matches[2]));
506 } else if (strpos($focus, '.')!==false) {
507 // Old style of focus, bad way to do it
508 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);
509 $this->page->requires->js_function_call('old_onload_focus', explode('.', $focus, 2));
510 } else {
511 // Focus element with given id
512 $this->page->requires->js_function_call('focuscontrol', Array($focus));
513 }
514 }
515
516 // Add the meta tags from the themes if any were requested.
517 $output .= $this->page->theme->get_meta_tags($this->page);
518
519 // Get any HTML from the page_requirements_manager.
520 $output .= $this->page->requires->get_head_code();
521
522 // List alternate versions.
523 foreach ($this->page->alternateversions as $type => $alt) {
524 $output .= $this->output_empty_tag('link', array('rel' => 'alternate',
525 'type' => $type, 'title' => $alt->title, 'href' => $alt->url));
526 }
527
528 return $output;
529 }
530
531 /**
532 * The standard tags (typically skip links) that should be output just inside
533 * the start of the <body> tag. Designed to be called in theme layout.php files.
534 * @return string HTML fragment.
535 */
536 public function standard_top_of_body_html() {
537 return $this->page->requires->get_top_of_body_code();
538 }
539
540 /**
541 * The standard tags (typically performance information and validation links,
542 * if we are in developer debug mode) that should be output in the footer area
543 * of the page. Designed to be called in theme layout.php files.
544 * @return string HTML fragment.
545 */
546 public function standard_footer_html() {
547 global $CFG;
548
549 // This function is normally called from a layout.php file in {@link header()}
550 // but some of the content won't be known until later, so we return a placeholder
551 // for now. This will be replaced with the real content in {@link footer()}.
552 $output = self::PERFORMANCE_INFO_TOKEN;
553 if (!empty($CFG->debugpageinfo)) {
554 $output .= '<div class="performanceinfo">This page is: ' . $this->page->debug_summary() . '</div>';
555 }
556 if (!empty($CFG->debugvalidators)) {
557 $output .= '<div class="validators"><ul>
558 <li><a href="http://validator.w3.org/check?verbose=1&amp;ss=1&amp;uri=' . urlencode(qualified_me()) . '">Validate HTML</a></li>
559 <li><a href="http://www.contentquality.com/mynewtester/cynthia.exe?rptmode=-1&amp;url1=' . urlencode(qualified_me()) . '">Section 508 Check</a></li>
560 <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>
561 </ul></div>';
562 }
563 return $output;
564 }
565
566 /**
567 * The standard tags (typically script tags that are not needed earlier) that
568 * should be output after everything else, . Designed to be called in theme layout.php files.
569 * @return string HTML fragment.
570 */
571 public function standard_end_of_body_html() {
572 // This function is normally called from a layout.php file in {@link header()}
573 // but some of the content won't be known until later, so we return a placeholder
574 // for now. This will be replaced with the real content in {@link footer()}.
575 echo self::END_HTML_TOKEN;
576 }
577
578 /**
579 * Return the standard string that says whether you are logged in (and switched
580 * roles/logged in as another user).
581 * @return string HTML fragment.
582 */
583 public function login_info() {
584 global $USER;
585 return user_login_string($this->page->course, $USER);
586 }
587
588 /**
589 * Return the 'back' link that normally appears in the footer.
590 * @return string HTML fragment.
591 */
592 public function home_link() {
593 global $CFG, $SITE;
594
595 if ($this->page->pagetype == 'site-index') {
596 // Special case for site home page - please do not remove
597 return '<div class="sitelink">' .
598 '<a title="Moodle ' . $CFG->release . '" href="http://moodle.org/">' .
599 '<img style="width:100px;height:30px" src="' . $CFG->httpswwwroot . '/pix/moodlelogo.gif" alt="moodlelogo" /></a></div>';
600
601 } else if (!empty($CFG->target_release) && $CFG->target_release != $CFG->release) {
602 // Special case for during install/upgrade.
603 return '<div class="sitelink">'.
604 '<a title="Moodle ' . $CFG->target_release . '" href="http://docs.moodle.org/en/Administrator_documentation" onclick="this.target=\'_blank\'">' .
605 '<img style="width:100px;height:30px" src="' . $CFG->httpswwwroot . '/pix/moodlelogo.gif" alt="moodlelogo" /></a></div>';
606
607 } else if ($this->page->course->id == $SITE->id || strpos($this->page->pagetype, 'course-view') === 0) {
608 return '<div class="homelink"><a href="' . $CFG->wwwroot . '/">' .
609 get_string('home') . '</a></div>';
610
611 } else {
612 return '<div class="homelink"><a href="' . $CFG->wwwroot . '/course/view.php?id=' . $this->page->course->id . '">' .
613 format_string($this->page->course->shortname) . '</a></div>';
614 }
615 }
616
617 /**
618 * Redirects the user by any means possible given the current state
619 *
620 * This function should not be called directly, it should always be called using
621 * the redirect function in lib/weblib.php
622 *
623 * The redirect function should really only be called before page output has started
624 * however it will allow itself to be called during the state STATE_IN_BODY
625 *
626 * @param string $encodedurl The URL to send to encoded if required
627 * @param string $message The message to display to the user if any
628 * @param int $delay The delay before redirecting a user, if $message has been
629 * set this is a requirement and defaults to 3, set to 0 no delay
630 * @param boolean $debugdisableredirect this redirect has been disabled for
631 * debugging purposes. Display a message that explains, and don't
632 * trigger the redirect.
633 * @return string The HTML to display to the user before dying, may contain
634 * meta refresh, javascript refresh, and may have set header redirects
635 */
636 public function redirect_message($encodedurl, $message, $delay, $debugdisableredirect) {
637 global $CFG;
638 $url = str_replace('&amp;', '&', $encodedurl);
639
640 switch ($this->page->state) {
641 case moodle_page::STATE_BEFORE_HEADER :
642 // No output yet it is safe to delivery the full arsenal of redirect methods
643 if (!$debugdisableredirect) {
644 // Don't use exactly the same time here, it can cause problems when both redirects fire at the same time.
645 $this->metarefreshtag = '<meta http-equiv="refresh" content="'. $delay .'; url='. $encodedurl .'" />'."\n";
646 $this->page->requires->js_function_call('document.location.replace', array($url))->after_delay($delay + 3);
647 }
648 $output = $this->header();
649 break;
650 case moodle_page::STATE_PRINTING_HEADER :
651 // We should hopefully never get here
652 throw new coding_exception('You cannot redirect while printing the page header');
653 break;
654 case moodle_page::STATE_IN_BODY :
655 // We really shouldn't be here but we can deal with this
656 debugging("You should really redirect before you start page output");
657 if (!$debugdisableredirect) {
658 $this->page->requires->js_function_call('document.location.replace', array($url))->after_delay($delay);
659 }
660 $output = $this->opencontainers->pop_all_but_last();
661 break;
662 case moodle_page::STATE_DONE :
663 // Too late to be calling redirect now
664 throw new coding_exception('You cannot redirect after the entire page has been generated');
665 break;
666 }
667 $output .= $this->notification($message, 'redirectmessage');
668 $output .= '<a href="'. $encodedurl .'">'. get_string('continue') .'</a>';
669 if ($debugdisableredirect) {
670 $output .= '<p><strong>Error output, so disabling automatic redirect.</strong></p>';
671 }
672 $output .= $this->footer();
673 return $output;
674 }
675
676 /**
677 * Start output by sending the HTTP headers, and printing the HTML <head>
678 * and the start of the <body>.
679 *
680 * To control what is printed, you should set properties on $PAGE. If you
681 * are familiar with the old {@link print_header()} function from Moodle 1.9
682 * you will find that there are properties on $PAGE that correspond to most
683 * of the old parameters to could be passed to print_header.
684 *
685 * Not that, in due course, the remaining $navigation, $menu parameters here
686 * will be replaced by more properties of $PAGE, but that is still to do.
687 *
688 * @param string $navigation legacy, like the old parameter to print_header. Will be
689 * removed when there is a $PAGE->... replacement.
690 * @param string $menu legacy, like the old parameter to print_header. Will be
691 * removed when there is a $PAGE->... replacement.
692 * @return string HTML that you must output this, preferably immediately.
693 */
694 public function header($navigation = '', $menu='') {
695 // TODO remove $navigation and $menu arguments - replace with $PAGE->navigation
696 global $USER, $CFG;
697
698 $this->page->set_state(moodle_page::STATE_PRINTING_HEADER);
699
700 // Find the appropriate page template, based on $this->page->generaltype.
701 $templatefile = $this->page->theme->template_for_page($this->page->generaltype);
702 if ($templatefile) {
703 // Render the template.
704 $template = $this->render_page_template($templatefile, $menu, $navigation);
705 } else {
706 // New style template not found, fall back to using header.html and footer.html.
707 $template = $this->handle_legacy_theme($navigation, $menu);
708 }
709
710 // Slice the template output into header and footer.
711 $cutpos = strpos($template, self::MAIN_CONTENT_TOKEN);
712 if ($cutpos === false) {
713 throw new coding_exception('Layout template ' . $templatefile .
714 ' does not contain the string "' . self::MAIN_CONTENT_TOKEN . '".');
715 }
716 $header = substr($template, 0, $cutpos);
717 $footer = substr($template, $cutpos + strlen(self::MAIN_CONTENT_TOKEN));
718
719 if (empty($this->contenttype)) {
720 debugging('The layout template did not call $OUTPUT->doctype()');
721 $this->doctype();
722 }
723
724 send_headers($this->contenttype, $this->page->cacheable);
725 $this->opencontainers->push('header/footer', $footer);
726 $this->page->set_state(moodle_page::STATE_IN_BODY);
727 return $header . $this->skip_link_target();
728 }
729
730 /**
731 * Renders and outputs the page template.
732 * @param string $templatefile The name of the template's file
733 * @param array $menu The menu that will be used in the included file
734 * @param array $navigation The navigation that will be used in the included file
735 * @return string HTML code
736 */
737 protected function render_page_template($templatefile, $menu, $navigation) {
738 global $CFG, $SITE, $THEME, $USER;
739 // The next lines are a bit tricky. The point is, here we are in a method
740 // of a renderer class, and this object may, or may not, be the same as
741 // the global $OUTPUT object. When rendering the template, we want to use
742 // this object. However, people writing Moodle code expect the current
743 // renderer to be called $OUTPUT, not $this, so define a variable called
744 // $OUTPUT pointing at $this. The same comment applies to $PAGE and $COURSE.
745 $OUTPUT = $this;
746 $PAGE = $this->page;
747 $COURSE = $this->page->course;
748
749 ob_start();
750 include($templatefile);
751 $template = ob_get_contents();
752 ob_end_clean();
753 return $template;
754 }
755
756 /**
757 * Renders and outputs a legacy template.
758 * @param array $navigation The navigation that will be used in the included file
759 * @param array $menu The menu that will be used in the included file
760 * @return string HTML code
761 */
762 protected function handle_legacy_theme($navigation, $menu) {
763 global $CFG, $SITE, $USER;
764 // Set a pretend global from the properties of this class.
765 // See the comment in render_page_template for a fuller explanation.
766 $COURSE = $this->page->course;
767 $THEME = $this->page->theme;
768
769 // Set up local variables that header.html expects.
770 $direction = $this->htmlattributes();
771 $title = $this->page->title;
772 $heading = $this->page->heading;
773 $focus = $this->page->focuscontrol;
774 $button = $this->page->button;
775 $pageid = $this->page->pagetype;
776 $pageclass = $this->page->bodyclasses;
777 $bodytags = ' class="' . $pageclass . '" id="' . $pageid . '"';
778 $home = $this->page->generaltype == 'home';
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
792 if (!$menu && $navigation) {
793 $menu = $loggedinas;
794 }
795
796 if (!empty($this->page->theme->layouttable)) {
797 $lt = $this->page->theme->layouttable;
798 } else {
799 $lt = array('left', 'middle', 'right');
800 }
801
802 if (!empty($this->page->theme->block_l_max_width)) {
803 $preferredwidthleft = $this->page->theme->block_l_max_width;
804 } else {
805 $preferredwidthleft = 210;
806 }
807 if (!empty($this->page->theme->block_r_max_width)) {
808 $preferredwidthright = $this->page->theme->block_r_max_width;
809 } else {
810 $preferredwidthright = 210;
811 }
812
813 ob_start();
814 include($this->page->theme->dir . '/header.html');
815
816 echo '<table id="layout-table"><tr>';
817 foreach ($lt as $column) {
818 if ($column == 'left' && $this->page->blocks->region_has_content(BLOCK_POS_LEFT, $this)) {
819 echo '<td id="left-column" class="block-region" style="width: ' . $preferredwidthright . 'px; vertical-align: top;">';
820 echo $this->container_start();
821 echo $this->blocks_for_region(BLOCK_POS_LEFT);
822 echo $this->container_end();
823 echo '</td>';
824
825 } else if ($column == 'middle') {
826 echo '<td id="middle-column" style="vertical-align: top;">';
827 echo $this->container_start();
828 echo $this->skip_link_target();
829 echo self::MAIN_CONTENT_TOKEN;
830 echo $this->container_end();
831 echo '</td>';
832
833 } else if ($column == 'right' && $this->page->blocks->region_has_content(BLOCK_POS_RIGHT, $this)) {
834 echo '<td id="right-column" class="block-region" style="width: ' . $preferredwidthright . 'px; vertical-align: top;">';
835 echo $this->container_start();
836 echo $this->blocks_for_region(BLOCK_POS_RIGHT);
837 echo $this->container_end();
838 echo '</td>';
839 }
840 }
841 echo '</tr></table>';
842
843 $menu = str_replace('navmenu', 'navmenufooter', $menu);
844 include($THEME->dir . '/footer.html');
845
846 $output = ob_get_contents();
847 ob_end_clean();
848
849 // Put in the start of body code. Bit of a hack, put it in before the first
850 // <div or <table.
851 $divpos = strpos($output, '<div');
852 $tablepos = strpos($output, '<table');
853 if ($divpos === false || ($tablepos !== false && $tablepos < $divpos)) {
854 $pos = $tablepos;
855 } else {
856 $pos = $divpos;
857 }
858 $output = substr($output, 0, $divpos) . $this->standard_top_of_body_html() .
859 substr($output, $divpos);
860
861 // Put in the end token before the end of body.
862 $output = str_replace('</body>', self::END_HTML_TOKEN . '</body>', $output);
863
864 // Make sure we use the correct doctype.
865 $output = preg_replace('/(<!DOCTYPE.+?>)/s', $this->doctype(), $output);
866
867 return $output;
868 }
869
870 /**
871 * Outputs the page's footer
872 * @return string HTML fragment
873 */
874 public function footer() {
875 $output = $this->opencontainers->pop_all_but_last(true);
876
877 $footer = $this->opencontainers->pop('header/footer');
878
879 // Provide some performance info if required
880 $performanceinfo = '';
881 if (defined('MDL_PERF') || (!empty($CFG->perfdebug) and $CFG->perfdebug > 7)) {
882 $perf = get_performance_info();
883 if (defined('MDL_PERFTOLOG') && !function_exists('register_shutdown_function')) {
884 error_log("PERF: " . $perf['txt']);
885 }
886 if (defined('MDL_PERFTOFOOT') || debugging() || $CFG->perfdebug > 7) {
887 $performanceinfo = $perf['html'];
888 }
889 }
890 $footer = str_replace(self::PERFORMANCE_INFO_TOKEN, $performanceinfo, $footer);
891
892 $footer = str_replace(self::END_HTML_TOKEN, $this->page->requires->get_end_code(), $footer);
893
894 $this->page->set_state(moodle_page::STATE_DONE);
895
896
897 return $output . $footer;
898 }
899
900 /**
901 * Output the row of editing icons for a block, as defined by the controls array.
902 * @param array $controls an array like {@link block_contents::$controls}.
903 * @return HTML fragment.
904 */
905 public function block_controls($controls) {
906 if (empty($controls)) {
907 return '';
908 }
909 $controlshtml = array();
910 foreach ($controls as $control) {
911 $controlshtml[] = $this->output_tag('a', array('class' => 'icon',
912 'title' => $control['caption'], 'href' => $control['url']),
913 $this->output_empty_tag('img', array('src' => $this->old_icon_url($control['icon']),
914 'alt' => $control['caption'])));
915 }
916 return $this->output_tag('div', array('class' => 'commands'), implode('', $controlshtml));
917 }
918
919 /**
920 * Prints a nice side block with an optional header.
921 *
922 * The content is described
923 * by a {@link block_contents} object.
924 *
925 * @param block_contents $bc HTML for the content
926 * @param string $region the region the block is appearing in.
927 * @return string the HTML to be output.
928 */
929 function block($bc, $region) {
930 $bc = clone($bc); // Avoid messing up the object passed in.
931 $bc->prepare();
932
933 $skiptitle = strip_tags($bc->title);
934 if (empty($skiptitle)) {
935 $output = '';
936 $skipdest = '';
937 } else {
938 $output = $this->output_tag('a', array('href' => '#sb-' . $bc->skipid, 'class' => 'skip-block'),
939 get_string('skipa', 'access', $skiptitle));
940 $skipdest = $this->output_tag('span', array('id' => 'sb-' . $bc->skipid, 'class' => 'skip-block-to'), '');
941 }
942
943 $bc->attributes['id'] = $bc->id;
944 $bc->attributes['class'] = $bc->get_classes_string();
945 $output .= $this->output_start_tag('div', $bc->attributes);
946
947 $controlshtml = $this->block_controls($bc->controls);
948
949 $title = '';
950 if ($bc->title) {
951 $title = $this->output_tag('h2', null, $bc->title);
952 }
953
954 if ($title || $controlshtml) {
955 $output .= $this->output_tag('div', array('class' => 'header'),
956 $this->output_tag('div', array('class' => 'title'),
957 $title . $controlshtml));
958 }
959
960 $output .= $this->output_start_tag('div', array('class' => 'content'));
961 $output .= $bc->content;
962
963 if ($bc->footer) {
964 $output .= $this->output_tag('div', array('class' => 'footer'), $bc->footer);
965 }
966
967 $output .= $this->output_end_tag('div');
968 $output .= $this->output_end_tag('div');
969
970 if ($bc->annotation) {
971 $output .= $this->output_tag('div', array('class' => 'blockannotation'), $bc->annotation);
972 }
973 $output .= $skipdest;
974
975 $this->init_block_hider_js($bc);
976 return $output;
977 }
978
979 /**
980 * Calls the JS require function to hide a block.
981 * @param block_contents $bc A block_contents object
982 * @return void
983 */
984 protected function init_block_hider_js($bc) {
985 if ($bc->collapsible != block_contents::NOT_HIDEABLE) {
986 $userpref = 'block' . $bc->blockinstanceid . 'hidden';
987 user_preference_allow_ajax_update($userpref, PARAM_BOOL);
988 $this->page->requires->yui_lib('dom');
989 $this->page->requires->yui_lib('event');
990 $plaintitle = strip_tags($bc->title);
991 $this->page->requires->js_function_call('new block_hider', array($bc->id, $userpref,
992 get_string('hideblocka', 'access', $plaintitle), get_string('showblocka', 'access', $plaintitle),
993 $this->old_icon_url('t/switch_minus'), $this->old_icon_url('t/switch_plus')));
994 }
995 }
996
997 /**
998 * Render the contents of a block_list.
999 * @param array $icons the icon for each item.
1000 * @param array $items the content of each item.
1001 * @return string HTML
1002 */
1003 public function list_block_contents($icons, $items) {
1004 $row = 0;
1005 $lis = array();
1006 foreach ($items as $key => $string) {
1007 $item = $this->output_start_tag('li', array('class' => 'r' . $row));
1008 if ($icons) {
1009 $item .= $this->output_tag('div', array('class' => 'icon column c0'), $icons[$key]);
1010 }
1011 $item .= $this->output_tag('div', array('class' => 'column c1'), $string);
1012 $item .= $this->output_end_tag('li');
1013 $lis[] = $item;
1014 $row = 1 - $row; // Flip even/odd.
1015 }
1016 return $this->output_tag('ul', array('class' => 'list'), implode("\n", $lis));
1017 }
1018
1019 /**
1020 * Output all the blocks in a particular region.
1021 * @param string $region the name of a region on this page.
1022 * @return string the HTML to be output.
1023 */
1024 public function blocks_for_region($region) {
1025 $blockcontents = $this->page->blocks->get_content_for_region($region, $this);
1026
1027 $output = '';
1028 foreach ($blockcontents as $bc) {
1029 if ($bc instanceof block_contents) {
1030 $output .= $this->block($bc, $region);
1031 } else if ($bc instanceof block_move_target) {
1032 $output .= $this->block_move_target($bc);
1033 } else {
1034 throw new coding_exception('Unexpected type of thing (' . get_class($bc) . ') found in list of block contents.');
1035 }
1036 }
1037 return $output;
1038 }
1039
1040 /**
1041 * Output a place where the block that is currently being moved can be dropped.
1042 * @param block_move_target $target with the necessary details.
1043 * @return string the HTML to be output.
1044 */
1045 public function block_move_target($target) {
1046 return $this->output_tag('a', array('href' => $target->url, 'class' => 'blockmovetarget'),
1047 $this->output_tag('span', array('class' => 'accesshide'), $target->text));
1048 }
1049
1050 /**
1051 * Given a html_link object, outputs an <a> tag that uses the object's attributes.
1052 *
1053 * @param mixed $link A html_link object or a string URL (text param required in second case)
1054 * @param string $text A descriptive text for the link. If $link is a html_link, this is not required.
1055 * @return string HTML fragment
1056 */
1057 public function link($link, $text=null) {
1058 $attributes = array();
1059
1060 if (is_a($link, 'html_link')) {
1061 $link = clone($link);
1062 $link->prepare();
1063 $this->prepare_event_handlers($link);
1064 $attributes['href'] = prepare_url($link->url);
1065 $attributes['class'] = $link->get_classes_string();
1066 $attributes['title'] = $link->title;
1067 $attributes['id'] = $link->id;
1068
1069 $text = $link->text;
1070
1071 } else if (empty($text)) {
1072 throw new coding_exception('$OUTPUT->link() must have a string as second parameter if the first param ($link) is a string');
1073
1074 } else {
1075 $attributes['href'] = prepare_url($link);
1076 }
1077
1078 return $this->output_tag('a', $attributes, $text);
1079 }
1080
1081 /**
1082 * Print a message along with button choices for Continue/Cancel. Labels default to Yes(Continue)/No(Cancel).
1083 * If a string or moodle_url is given instead of a html_button, method defaults to post and text to Yes/No
1084 * @param string $message The question to ask the user
1085 * @param mixed $continue The html_form component representing the Continue answer. Can also be a moodle_url or string URL
1086 * @param mixed $cancel The html_form component representing the Cancel answer. Can also be a moodle_url or string URL
1087 * @return string HTML fragment
1088 */
1089 public function confirm($message, $continue, $cancel) {
1090 if ($continue instanceof html_form) {
1091 $continue = clone($continue);
1092 } else if (is_string($continue)) {
1093 $continueform = new html_form();
1094 $continueform->url = new moodle_url($continue);
1095 $continue = $continueform;
1096 } else if ($continue instanceof moodle_url) {
1097 $continueform = new html_form();
1098 $continueform->url = $continue;
1099 $continue = $continueform;
1100 } else {
1101 throw new coding_exception('The continue param to $OUTPUT->confirm must be either a URL (string/moodle_url) or a html_form object.');
1102 }
1103
1104 if ($cancel instanceof html_form) {
1105 $cancel = clone($cancel);
1106 } else if (is_string($cancel)) {
1107 $cancelform = new html_form();
1108 $cancelform->url = new moodle_url($cancel);
1109 $cancel = $cancelform;
1110 } else if ($cancel instanceof moodle_url) {
1111 $cancelform = new html_form();
1112 $cancelform->url = $cancel;
1113 $cancel = $cancelform;
1114 } else {
1115 throw new coding_exception('The cancel param to $OUTPUT->confirm must be either a URL (string/moodle_url) or a html_form object.');
1116 }
1117
1118 if (empty($continue->button->text)) {
1119 $continue->button->text = get_string('yes');
1120 }
1121 if (empty($cancel->button->text)) {
1122 $cancel->button->text = get_string('no');
1123 }
1124
1125 $output = $this->box_start('generalbox', 'notice');
1126 $output .= $this->output_tag('p', array(), $message);
1127 $output .= $this->output_tag('div', array('class' => 'buttons'), $this->button($continue) . $this->button($cancel));
1128 $output .= $this->box_end();
1129 return $output;
1130 }
1131
1132 /**
1133 * Given a html_form object, outputs an <input> tag within a form that uses the object's attributes.
1134 *
1135 * @param html_form $form A html_form object
1136 * @return string HTML fragment
1137 */
1138 public function button($form) {
1139 if (empty($form->button) or !($form->button instanceof html_button)) {
1140 throw new coding_exception('$OUTPUT->button($form) requires $form to have a button (html_button) value');
1141 }
1142 $form = clone($form);
1143 $form->button->prepare();
1144
1145 $this->prepare_event_handlers($form->button);
1146
1147 $buttonattributes = array('class' => $form->button->get_classes_string(),
1148 'type' => 'submit',
1149 'value' => $form->button->text,
1150 'disabled' => $form->button->disabled,
1151 'id' => $form->button->id);
1152
1153 $buttonoutput = $this->output_empty_tag('input', $buttonattributes);
1154
1155 // Removing the button so it doesn't get output again
1156 unset($form->button);
1157
1158 return $this->form($form, $buttonoutput);
1159 }
1160
1161 /**
1162 * Given a html_form component and an optional rendered submit button,
1163 * outputs a HTML form with correct divs and inputs and a single submit button.
1164 * This doesn't render any other visible inputs. Use moodleforms for these.
1165 * @param html_form $form A html_form instance
1166 * @param string $contents HTML fragment to put inside the form. If given, must contain at least the submit button.
1167 * @return string HTML fragment
1168 */
1169 public function form($form, $contents=null) {
1170 $form = clone($form);
1171 $form->prepare();
1172 $this->prepare_event_handlers($form);
1173 $buttonoutput = null;
1174
1175 if (empty($contents) && !empty($form->button)) {
1176 debugging("You probably want to use \$OUTPUT->button(\$form), please read that function's documentation", DEBUG_DEVELOPER);
1177 } else if (empty($contents)) {
1178 $contents = $this->output_empty_tag('input', array('type' => 'submit', 'value' => get_string('ok')));
1179 } else if (!empty($form->button)) {
1180 $form->button->prepare();
1181 $buttonoutput = $this->output_start_tag('div', array('id' => "noscript$form->id"));
1182 $this->prepare_event_handlers($form->button);
1183
1184 $buttonattributes = array('class' => $form->button->get_classes_string(),
1185 'type' => 'submit',
1186 'value' => $form->button->text,
1187 'disabled' => $form->button->disabled,
1188 'id' => $form->button->id);
1189
1190 $buttonoutput .= $this->output_empty_tag('input', $buttonattributes);
1191 $buttonoutput .= $this->output_end_tag('div');
1192 $this->page->requires->js_function_call('hide_item', array("noscript$form->id"));
1193
1194 }
1195
1196 $hiddenoutput = '';
1197
1198 foreach ($form->url->params() as $var => $val) {
1199 $hiddenoutput .= $this->output_empty_tag('input', array('type' => 'hidden', 'name' => $var, 'value' => $val));
1200 }
1201
1202 $formattributes = array(
1203 'method' => $form->method,
1204 'action' => prepare_url($form->url, true),
1205 'id' => $form->id,
1206 'class' => $form->get_classes_string());
1207
1208 $divoutput = $this->output_tag('div', array(), $hiddenoutput . $contents . $buttonoutput);
1209 $formoutput = $this->output_tag('form', $formattributes, $divoutput);
1210 $output = $this->output_tag('div', array('class' => 'singlebutton'), $formoutput);
1211
1212 return $output;
1213 }
1214
1215 /**
1216 * Returns a string containing a link to the user documentation.
1217 * Also contains an icon by default. Shown to teachers and admin only.
1218 * @param string $path The page link after doc root and language, no leading slash.
1219 * @param string $text The text to be displayed for the link
1220 * @param string $iconpath The path to the icon to be displayed
1221 */
1222 public function doc_link($path, $text=false, $iconpath=false) {
1223 global $CFG, $OUTPUT;
1224 $icon = new action_icon();
1225 $icon->linktext = $text;
1226 $icon->image->alt = $text;
1227 $icon->image->add_class('iconhelp');
1228 $icon->link->url = new moodle_url(get_docs_url($path));
1229
1230 if (!empty($iconpath)) {
1231 $icon->image->src = $iconpath;
1232 } else {
1233 $icon->image->src = $this->old_icon_url('docs');
1234 }
1235
1236 if (!empty($CFG->doctonewwindow)) {
1237 $icon->actions[] = new popup_action('click', $icon->link->url);
1238 }
1239
1240 return $this->action_icon($icon);
1241
1242 }
1243
1244 /**
1245 * Given a action_icon object, outputs an image linking to an action (URL or AJAX).
1246 *
1247 * @param action_icon $icon An action_icon object
1248 * @return string HTML fragment
1249 */
1250 public function action_icon($icon) {
1251 $icon = clone($icon);
1252 $icon->prepare();
1253 $imageoutput = $this->image($icon->image);
1254
1255 if ($icon->linktext) {
1256 $imageoutput .= $icon->linktext;
1257 }
1258 $icon->link->text = $imageoutput;
1259
1260 return $this->link($icon->link);
1261 }
1262
1263 /*
1264 * Centered heading with attached help button (same title text)
1265 * and optional icon attached
1266 * @param help_icon $helpicon A help_icon object
1267 * @param mixed $image An image URL or a html_image object
1268 * @return string HTML fragment
1269 */
1270 public function heading_with_help($helpicon, $image=false) {
1271 if (!($image instanceof html_image) && !empty($image)) {
1272 $htmlimage = new html_image();
1273 $htmlimage->src = $image;
1274 $image = $htmlimage;
1275 }
1276 return $this->container($this->image($image) . $this->heading($helpicon->text, 2, 'main help') . $this->help_icon($helpicon), 'heading-with-help');
1277 }
1278
1279 /**
1280 * Print a help icon.
1281 *
1282 * @param help_icon $helpicon A help_icon object, subclass of html_link
1283 *
1284 * @return string HTML fragment
1285 */
1286 public function help_icon($icon) {
1287 global $COURSE;
1288 $icon = clone($icon);
1289 $icon->prepare();
1290
1291 $popup = new popup_action('click', $icon->link->url);
1292 $icon->link->add_action($popup);
1293
1294 $image = null;
1295
1296 if (!empty($icon->image)) {
1297 $image = $icon->image;
1298 $image->add_class('iconhelp');
1299 }
1300
1301 return $this->output_tag('span', array('class' => 'helplink'), $this->link_to_popup($icon->link, $image));
1302 }
1303
1304 /**
1305 * Creates and returns a button to a popup window
1306 *
1307 * @param html_link $link Subclass of moodle_html_component
1308 * @param moodle_popup $popup A moodle_popup object
1309 * @param html_image $image An optional image replacing the link text
1310 *
1311 * @return string HTML fragment
1312 */
1313 public function link_to_popup($link, $image=null) {
1314 $link = clone($link);
1315 $link->prepare();
1316
1317 $this->prepare_event_handlers($link);
1318
1319 if (empty($link->url)) {
1320 throw new coding_exception('Called $OUTPUT->link_to_popup($link) method without $link->url set.');
1321 }
1322
1323 $linkurl = prepare_url($link->url);
1324
1325 $tagoptions = array(
1326 'title' => $link->title,
1327 'id' => $link->id,
1328 'href' => ($linkurl) ? $linkurl : prepare_url($popup->url),
1329 'class' => $link->get_classes_string());
1330
1331 // Use image if one is given
1332 if (!empty($image) && $image instanceof html_image) {
1333
1334 if (empty($image->alt)) {
1335 $image->alt = $link->text;
1336 }
1337
1338 $link->text = $this->image($image);
1339
1340 if (!empty($link->linktext)) {
1341 $link->text = "$link->title &nbsp; $link->text";
1342 }
1343 }
1344
1345 return $this->output_tag('a', $tagoptions, $link->text);
1346 }
1347
1348 /**
1349 * Creates and returns a spacer image with optional line break.
1350 *
1351 * @param html_image $image Subclass of moodle_html_component
1352 *
1353 * @return string HTML fragment
1354 */
1355 public function spacer($image) {
1356 $image = clone($image);
1357 $image->prepare();
1358 $image->add_class('spacer');
1359
1360 if (empty($image->src)) {
1361 $image->src = $this->old_icon_url('spacer');
1362 }
1363
1364 $output = $this->image($image);
1365
1366 return $output;
1367 }
1368
1369 /**
1370 * Creates and returns an image.
1371 *
1372 * @param html_image $image Subclass of moodle_html_component
1373 *
1374 * @return string HTML fragment
1375 */
1376 public function image($image) {
1377 if ($image === false) {
1378 return false;
1379 }
1380
1381 $image = clone($image);
1382 $image->prepare();
1383
1384 $this->prepare_event_handlers($image);
1385
1386 $attributes = array('class' => $image->get_classes_string(),
1387 'src' => prepare_url($image->src),
1388 'alt' => $image->alt,
1389 'style' => $image->style,
1390 'title' => $image->title,
1391 'id' => $image->id);
1392
1393 if (!empty($image->height) || !empty($image->width)) {
1394 $attributes['style'] .= $this->prepare_legacy_width_and_height($image);
1395 }
1396 return $this->output_empty_tag('img', $attributes);
1397 }
1398
1399 /**
1400 * Print the specified user's avatar.
1401 *
1402 * This method can be used in two ways:
1403 * <pre>
1404 * // Option 1:
1405 * $userpic = new user_picture();
1406 * // Set properties of $userpic
1407 * $OUTPUT->user_picture($userpic);
1408 *
1409 * // Option 2: (shortcut for simple cases)
1410 * // $user has come from the DB and has fields id, picture, imagealt, firstname and lastname
1411 * $OUTPUT->user_picture($user, $COURSE->id);
1412 * </pre>
1413 *
1414 * @param object $userpic Object with at least fields id, picture, imagealt, firstname, lastname
1415 * If any of these are missing, or if a userid is passed, the database is queried. Avoid this
1416 * if at all possible, particularly for reports. It is very bad for performance.
1417 * A user_picture object is a better parameter.
1418 * @param int $courseid courseid Used when constructing the link to the user's profile. Required if $userpic
1419 * is not a user_picture object
1420 * @return string HTML fragment
1421 */
1422 public function user_picture($userpic, $courseid=null) {
1423 // Instantiate a user_picture object if $user is not already one
1424 if (!($userpic instanceof user_picture)) {
1425 if (empty($courseid)) {
1426 throw new coding_exception('Called $OUTPUT->user_picture with a $user object but no $courseid.');
1427 }
1428
1429 $user = $userpic;
1430 $userpic = new user_picture();
1431 $userpic->user = $user;
1432 $userpic->courseid = $courseid;
1433 } else {
1434 $userpic = clone($userpic);
1435 }
1436
1437 $userpic->prepare();
1438
1439 $output = $this->image($userpic->image);
1440
1441 if (!empty($userpic->url)) {
1442 $actions = $userpic->get_actions();
1443 if ($userpic->popup && !empty($actions)) {
1444 $link = new html_link();
1445 $link->url = $userpic->url;
1446 $link->text = fullname($userpic->user);
1447 $link->title = fullname($userpic->user);
1448
1449 foreach ($actions as $action) {
1450 $link->add_action($action);
1451 }
1452 $output = $this->link_to_popup($link, $userpic->image);
1453 } else {
1454 $output = $this->link(prepare_url($userpic->url), $output);
1455 }
1456 }
1457
1458 return $output;
1459 }
1460
1461 /**
1462 * Prints the 'Update this Modulename' button that appears on module pages.
1463 *
1464 * @param string $cmid the course_module id.
1465 * @param string $modulename the module name, eg. "forum", "quiz" or "workshop"
1466 * @return string the HTML for the button, if this user has permission to edit it, else an empty string.
1467 */
1468 public function update_module_button($cmid, $modulename) {
1469 global $CFG;
1470 if (has_capability('moodle/course:manageactivities', get_context_instance(CONTEXT_MODULE, $cmid))) {
1471 $modulename = get_string('modulename', $modulename);
1472 $string = get_string('updatethis', '', $modulename);
1473
1474 $form = new html_form();
1475 $form->url = new moodle_url("$CFG->wwwroot/course/mod.php", array('update' => $cmid, 'return' => true, 'sesskey' => sesskey()));
1476 $form->button->text = $string;
1477 return $this->button($form);
1478 } else {
1479 return '';
1480 }
1481 }
1482
1483 /**
1484 * Prints a "Turn editing on/off" button in a form.
1485 * @param moodle_url $url The URL + params to send through when clicking the button
1486 * @return string HTML the button
1487 */
1488 public function edit_button(moodle_url $url) {
1489 global $USER;
1490 if (!empty($USER->editing)) {
1491 $string = get_string('turneditingoff');
1492 $edit = '0';
1493 } else {
1494 $string = get_string('turneditingon');
1495 $edit = '1';
1496 }
1497
1498 $form = new html_form();
1499 $form->url = $url;
1500 $form->url->param('edit', $edit);
1501 $form->button->text = $string;
1502
1503 return $this->button($form);
1504 }
1505
1506 /**
1507 * Outputs a HTML nested list
1508 *
1509 * @param html_list $list A html_list object
1510 * @return string HTML structure
1511 */
1512 public function htmllist($list) {
1513 $list = clone($list);
1514 $list->prepare();
1515
1516 $this->prepare_event_handlers($list);
1517
1518 if ($list->type == 'ordered') {
1519 $tag = 'ol';
1520 } else if ($list->type == 'unordered') {
1521 $tag = 'ul';
1522 }
1523
1524 $output = $this->output_start_tag($tag, array('class' => $list->get_classes_string()));
1525
1526 foreach ($list->items as $listitem) {
1527 if ($listitem instanceof html_list) {
1528 $output .= $this->output_start_tag('li', array());
1529 $output .= $this->htmllist($listitem);
1530 $output .= $this->output_end_tag('li');
1531 } else if ($listitem instanceof html_list_item) {
1532 $listitem->prepare();
1533 $this->prepare_event_handlers($listitem);
1534 $output .= $this->output_tag('li', array('class' => $listitem->get_classes_string()), $listitem->value);
1535 }
1536 }
1537
1538 return $output . $this->output_end_tag($tag);
1539 }
1540
1541 /**
1542 * Prints a simple button to close a window
1543 *
1544 * @global objec)t
1545 * @param string $text The lang string for the button's label (already output from get_string())
1546 * @return string|void if $return is true, void otherwise
1547 */
1548 public function close_window_button($text) {
1549 if (empty($text)) {
1550 $text = get_string('closewindow');
1551 }
1552 $closeform = new html_form();
1553 $closeform->url = '#';
1554 $closeform->button->text = $text;
1555 $closeform->button->add_action('click', 'close_window');
1556 $closeform->button->prepare();
1557 return $this->container($this->button($closeform), 'closewindow');
1558 }
1559
1560 /**
1561 * Outputs a <select> menu or a list of radio/checkbox inputs.
1562 *
1563 * This method is extremely versatile, and can be used to output yes/no menus,
1564 * form-enclosed menus with automatic redirects when an option is selected,
1565 * descriptive labels and help icons. By default it just outputs a select
1566 * menu.
1567 *
1568 * To add a descriptive label, use moodle_select::set_label($text, $for) or
1569 * moodle_select::set_label($label) passing a html_label object
1570 *
1571 * To add a help icon, use moodle_select::set_help($page, $text, $linktext) or
1572 * moodle_select::set_help($helpicon) passing a help_icon object
1573 *
1574 * If you moodle_select::$rendertype to "radio", it will render radio buttons
1575 * instead of a <select> menu, unless $multiple is true, in which case it
1576 * will render checkboxes.
1577 *
1578 * To surround the menu with a form, simply set moodle_select->form as a
1579 * valid html_form object. Note that this function will NOT automatically
1580 * add a form for non-JS browsers. If you do not set one up, it assumes
1581 * that you are providing your own form in some other way.
1582 *
1583 * You can either call this function with a single moodle_select argument
1584 * or, with a list of parameters, in which case those parameters are sent to
1585 * the moodle_select constructor.
1586 *
1587 * @param moodle_select $select a moodle_select that describes
1588 * the select menu you want output.
1589 * @return string the HTML for the <select>
1590 */
1591 public function select($select) {
1592 $select = clone($select);
1593 $select->prepare();
1594
1595 $this->prepare_event_handlers($select);
1596
1597 if (empty($select->id)) {
1598 $select->id = 'menu' . str_replace(array('[', ']'), '', $select->name);
1599 }
1600
1601 $attributes = array(
1602 'name' => $select->name,
1603 'id' => $select->id,
1604 'class' => $select->get_classes_string()
1605 );
1606 if ($select->disabled) {
1607 $attributes['disabled'] = 'disabled';
1608 }
1609 if ($select->tabindex) {
1610 $attributes['tabindex'] = $tabindex;
1611 }
1612
1613 if ($select->rendertype == 'menu' && $select->listbox) {
1614 if (is_integer($select->listbox)) {
1615 $size = $select->listbox;
1616 } else {
1617 $size = min($select->maxautosize, count($select->options));
1618 }
1619 $attributes['size'] = $size;
1620 if ($select->multiple) {
1621 $attributes['multiple'] = 'multiple';
1622 }
1623 }
1624
1625 $html = '';
1626
1627 if (!empty($select->label)) {
1628 $html .= $this->label($select->label);
1629 }
1630
1631 if (!empty($select->helpicon) && $select->helpicon instanceof help_icon) {
1632 $html .= $this->help_icon($select->helpicon);
1633 }
1634
1635 if ($select->rendertype == 'menu') {
1636 $html .= $this->output_start_tag('select', $attributes) . "\n";
1637
1638 foreach ($select->options as $option) {
1639 // $OUTPUT->select_option detects if $option is an option or an optgroup
1640 $html .= $this->select_option($option);
1641 }
1642
1643 $html .= $this->output_end_tag('select') . "\n";
1644 } else if ($select->rendertype == 'radio') {
1645 $currentradio = 0;
1646 foreach ($select->options as $option) {
1647 $html .= $this->radio($option, $select->name);
1648 $currentradio++;
1649 }
1650 } else if ($select->rendertype == 'checkbox') {
1651 $currentcheckbox = 0;
1652 foreach ($select->options as $option) {
1653 $html .= $this->checkbox($option, $select->name);
1654 $currentcheckbox++;
1655 }
1656 }
1657
1658 if (!empty($select->form) && $select->form instanceof html_form) {
1659 $html = $this->form($select->form, $html);
1660 }
1661
1662 return $html;
1663 }
1664
1665 /**
1666 * Outputs a <input type="radio" /> element. Optgroups are ignored, so do not
1667 * pass a html_select_optgroup as a param to this function.
1668 *
1669 * @param html_select_option $option a html_select_option
1670 * @return string the HTML for the <input type="radio">
1671 */
1672 public function radio($option, $name='unnamed') {
1673 if ($option instanceof html_select_optgroup) {
1674 throw new coding_exception('$OUTPUT->radio($option) does not support a html_select_optgroup object as param.');
1675 } else if (!($option instanceof html_select_option)) {
1676 throw new coding_exception('$OUTPUT->radio($option) only accepts a html_select_option object as param.');
1677 }
1678 $option = clone($option);
1679 $option->prepare();
1680 $option->label->for = $option->id;
1681 $this->prepare_event_handlers($option);
1682
1683 $output = $this->output_start_tag('span', array('class' => "radiogroup $select->name rb$currentradio")) . "\n";
1684 $output .= $this->label($option->label);
1685
1686 if ($option->selected == 'selected') {
1687 $option->selected = 'checked';
1688 }
1689
1690 $output .= $this->output_empty_tag('input', array(
1691 'type' => 'radio',
1692 'value' => $option->value,
1693 'name' => $name,
1694 'alt' => $option->alt,
1695 'id' => $option->id,
1696 'class' => $option->get_classes_string(),
1697 'checked' => $option->selected));
1698
1699 $output .= $this->output_end_tag('span');
1700
1701 return $output;
1702 }
1703
1704 /**
1705 * Outputs a <input type="checkbox" /> element. Optgroups are ignored, so do not
1706 * pass a html_select_optgroup as a param to this function.
1707 *
1708 * @param html_select_option $option a html_select_option
1709 * @return string the HTML for the <input type="checkbox">
1710 */
1711 public function checkbox($option, $name='unnamed') {
1712 if ($option instanceof html_select_optgroup) {
1713 throw new coding_exception('$OUTPUT->checkbox($option) does not support a html_select_optgroup object as param.');
1714 } else if (!($option instanceof html_select_option)) {
1715 throw new coding_exception('$OUTPUT->checkbox($option) only accepts a html_select_option object as param.');
1716 }
1717 $option = clone($option);
1718 $option->prepare();
1719
1720 $option->label->for = $option->id;
1721 $this->prepare_event_handlers($option);
1722
1723 $output = $this->output_start_tag('span', array('class' => "checkbox $name")) . "\n";
1724
1725 if ($option->selected == 'selected') {
1726 $option->selected = 'checked';
1727 }
1728
1729 $output .= $this->output_empty_tag('input', array(
1730 'type' => 'checkbox',
1731 'value' => $option->value,
1732 'name' => $name,
1733 'id' => $option->id,
1734 'alt' => $option->alt,
1735 'class' => $option->get_classes_string(),
1736 'checked' => $option->selected));
1737 $output .= $this->label($option->label);
1738
1739 $output .= $this->output_end_tag('span');
1740
1741 return $output;
1742 }
1743
1744 /**
1745 * Output an <option> or <optgroup> element. If an optgroup element is detected,
1746 * this will recursively output its options as well.
1747 *
1748 * @param mixed $option a html_select_option or moodle_select_optgroup
1749 * @return string the HTML for the <option> or <optgroup>
1750 */
1751 public function select_option($option) {
1752 $option = clone($option);
1753 $option->prepare();
1754 $this->prepare_event_handlers($option);
1755
1756 if ($option instanceof html_select_option) {
1757 return $this->output_tag('option', array(
1758 'value' => $option->value,
1759 'class' => $option->get_classes_string(),
1760 'selected' => $option->selected), $option->text);
1761 } else if ($option instanceof html_select_optgroup) {
1762 $output = $this->output_start_tag('optgroup', array('label' => $option->text, 'class' => $option->get_classes_string()));
1763 foreach ($option->options as $selectoption) {
1764 $output .= $this->select_option($selectoption);
1765 }
1766 $output .= $this->output_end_tag('optgroup');
1767 return $output;
1768 }
1769 }
1770
1771 /**
1772 * Output an <input type="text"> element
1773 *
1774 * @param html_field $field a html_field object
1775 * @return string the HTML for the <input>
1776 */
1777 public function textfield($field) {
1778 $field = clone($field);
1779 $field->prepare();
1780 $this->prepare_event_handlers($field);
1781 $output = $this->output_start_tag('span', array('class' => "textfield $field->name"));
1782 $output .= $this->output_empty_tag('input', array(
1783 'type' => 'text',
1784 'name' => $field->name,
1785 'id' => $field->id,
1786 'value' => $field->value,
1787 'style' => $field->style,
1788 'alt' => $field->alt,
1789 'maxlength' => $field->maxlength));
1790 $output .= $this->output_end_tag('span');
1791 return $output;
1792 }
1793
1794 /**
1795 * Outputs a <label> element.
1796 * @param html_label $label A html_label object
1797 * @return HTML fragment
1798 */
1799 public function label($label) {
1800 $label = clone($label);
1801 $label->prepare();
1802 $this->prepare_event_handlers($label);
1803 return $this->output_tag('label', array('for' => $label->for, 'class' => $label->get_classes_string()), $label->text);
1804 }
1805
1806 /**
1807 * Output an error message. By default wraps the error message in <span class="error">.
1808 * If the error message is blank, nothing is output.
1809 * @param string $message the error message.
1810 * @return string the HTML to output.
1811 */
1812 public function error_text($message) {
1813 if (empty($message)) {
1814 return '';
1815 }
1816 return $this->output_tag('span', array('class' => 'error'), $message);
1817 }
1818
1819 /**
1820 * Do not call this function directly.
1821 *
1822 * To terminate the current script with a fatal error, call the {@link print_error}
1823 * function, or throw an exception. Doing either of those things will then call this
1824 * function to display the error, before terminating the execution.
1825 *
1826 * @param string $message The message to output
1827 * @param string $moreinfourl URL where more info can be found about the error
1828 * @param string $link Link for the Continue button
1829 * @param array $backtrace The execution backtrace
1830 * @param string $debuginfo Debugging information
1831 * @param bool $showerrordebugwarning Whether or not to show a debugging warning
1832 * @return string the HTML to output.
1833 */
1834 public function fatal_error($message, $moreinfourl, $link, $backtrace,
1835 $debuginfo = null, $showerrordebugwarning = false) {
1836
1837 $output = '';
1838
1839 if ($this->has_started()) {
1840 $output .= $this->opencontainers->pop_all_but_last();
1841 } else {
1842 // Header not yet printed
1843 @header('HTTP/1.0 404 Not Found');
1844 $this->page->set_title(get_string('error'));
1845 $output .= $this->header();
1846 }
1847
1848 $message = '<p class="errormessage">' . $message . '</p>'.
1849 '<p class="errorcode"><a href="' . $moreinfourl . '">' .
1850 get_string('moreinformation') . '</a></p>';
1851 $output .= $this->box($message, 'errorbox');
1852
1853 if (debugging('', DEBUG_DEVELOPER)) {
1854 if ($showerrordebugwarning) {
1855 $output .= $this->notification('error() is a deprecated function. ' .
1856 'Please call print_error() instead of error()', 'notifytiny');
1857 }
1858 if (!empty($debuginfo)) {
1859 $output .= $this->notification($debuginfo, 'notifytiny');
1860 }
1861 if (!empty($backtrace)) {
1862 $output .= $this->notification('Stack trace: ' .
1863 format_backtrace($backtrace), 'notifytiny');
1864 }
1865 }
1866
1867 if (!empty($link)) {
1868 $output .= $this->continue_button($link);
1869 }
1870
1871 $output .= $this->footer();
1872
1873 // Padding to encourage IE to display our error page, rather than its own.
1874 $output .= str_repeat(' ', 512);
1875
1876 return $output;
1877 }
1878
1879 /**
1880 * Output a notification (that is, a status message about something that has
1881 * just happened).
1882 *
1883 * @param string $message the message to print out
1884 * @param string $classes normally 'notifyproblem' or 'notifysuccess'.
1885 * @return string the HTML to output.
1886 */
1887 public function notification($message, $classes = 'notifyproblem') {
1888 return $this->output_tag('div', array('class' =>
1889 moodle_renderer_base::prepare_classes($classes)), clean_text($message));
1890 }
1891
1892 /**
1893 * Print a continue button that goes to a particular URL.
1894 *
1895 * @param string|moodle_url $link The url the button goes to.
1896 * @return string the HTML to output.
1897 */
1898 public function continue_button($link) {
1899 if (!is_a($link, 'moodle_url')) {
1900 $link = new moodle_url($link);
1901 }
1902 $form = new html_form();
1903 $form->url = $link;
1904 $form->values = $link->params();
1905 $form->button->text = get_string('continue');
1906 $form->method = 'get';
1907
1908 return $this->output_tag('div', array('class' => 'continuebutton') , $this->button($form));
1909 }
1910
1911 /**
1912 * Prints a single paging bar to provide access to other pages (usually in a search)
1913 *
1914 * @param string|moodle_url $link The url the button goes to.
1915 * @return string the HTML to output.
1916 */
1917 public function paging_bar($pagingbar) {
1918 $output = '';
1919 $pagingbar = clone($pagingbar);
1920 $pagingbar->prepare();
1921
1922 if ($pagingbar->totalcount > $pagingbar->perpage) {
1923 $output .= get_string('page') . ':';
1924
1925 if (!empty($pagingbar->previouslink)) {
1926 $output .= '&nbsp;(' . $this->link($pagingbar->previouslink) . ')&nbsp;';
1927 }
1928
1929 if (!empty($pagingbar->firstlink)) {
1930 $output .= '&nbsp;' . $this->link($pagingbar->firstlink) . '&nbsp;...';
1931 }
1932
1933 foreach ($pagingbar->pagelinks as $link) {
1934 if ($link instanceof html_link) {
1935 $output .= '&nbsp;&nbsp;' . $this->link($link);
1936 } else {
1937 $output .= "&nbsp;&nbsp;$link";
1938 }
1939 }
1940
1941 if (!empty($pagingbar->lastlink)) {
1942 $output .= '&nbsp;...' . $this->link($pagingbar->lastlink) . '&nbsp;';
1943 }
1944
1945 if (!empty($pagingbar->nextlink)) {
1946 $output .= '&nbsp;&nbsp;(' . $this->link($pagingbar->nextlink) . ')';
1947 }
1948 }
1949
1950 return $this->output_tag('div', array('class' => 'paging'), $output);
1951 }
1952
1953 /**
1954 * Render a HTML table
1955 *
1956 * @param object $table {@link html_table} instance containing all the information needed
1957 * @return string the HTML to output.
1958 */
1959 public function table(html_table $table) {
1960 $table = clone($table);
1961 $table->prepare();
1962 $attributes = array(
1963 'id' => $table->id,
1964 'width' => $table->width,
1965 'summary' => $table->summary,
1966 'cellpadding' => $table->cellpadding,
1967 'cellspacing' => $table->cellspacing,
1968 'class' => $table->get_classes_string());
1969 $output = $this->output_start_tag('table', $attributes) . "\n";
1970
1971 $countcols = 0;
1972
1973 if (!empty($table->head)) {
1974 $countcols = count($table->head);
1975 $output .= $this->output_start_tag('thead', array()) . "\n";
1976 $output .= $this->output_start_tag('tr', array()) . "\n";
1977 $keys = array_keys($table->head);
1978 $lastkey = end($keys);
1979 foreach ($table->head as $key => $heading) {
1980 $classes = array('header', 'c' . $key);
1981 if (isset($table->headspan[$key]) && $table->headspan[$key] > 1) {
1982 $colspan = $table->headspan[$key];
1983 $countcols += $table->headspan[$key] - 1;
1984 } else {
1985 $colspan = '';
1986 }
1987 if ($key == $lastkey) {
1988 $classes[] = 'lastcol';
1989 }
1990 if (isset($table->colclasses[$key])) {
1991 $classes[] = $table->colclasses[$key];
1992 }
1993 if ($table->rotateheaders) {
1994 // we need to wrap the heading content
1995 $heading = $this->output_tag('span', '', $heading);
1996 }
1997 $attributes = array(
1998 'style' => $table->align[$key] . $table->size[$key] . 'white-space:nowrap;',
1999 'class' => moodle_renderer_base::prepare_classes($classes),
2000 'scope' => 'col',
2001 'colspan' => $colspan);
2002 $output .= $this->output_tag('th', $attributes, $heading) . "\n";
2003 }
2004 $output .= $this->output_end_tag('tr') . "\n";
2005 $output .= $this->output_end_tag('thead') . "\n";
2006 }
2007
2008 if (!empty($table->data)) {
2009 $oddeven = 1;
2010 $keys = array_keys($table->data);
2011 $lastrowkey = end($keys);
2012 $output .= $this->output_start_tag('tbody', array()) . "\n";
2013
2014 foreach ($table->data as $key => $row) {
2015 if (($row === 'hr') && ($countcols)) {
2016 $output .= $this->output_tag('td', array('colspan' => $countcols),
2017 $this->output_tag('div', array('class' => 'tabledivider'), '')) . "\n";
2018 } else {
2019 // Convert array rows to html_table_rows and cell strings to html_table_cell objects
2020 if (!($row instanceof html_table_row)) {
2021 $newrow = new html_table_row();
2022
2023 foreach ($row as $unused => $item) {
2024 $cell = new html_table_cell();
2025 $cell->text = $item;
2026 $newrow->cells[] = $cell;
2027 }
2028 $row = $newrow;
2029 }
2030
2031 $oddeven = $oddeven ? 0 : 1;
2032 if (isset($table->rowclasses[$key])) {
2033 $row->add_classes(array_unique(moodle_html_component::clean_classes($table->rowclasses[$key])));
2034 }
2035
2036 $row->add_class('r' . $oddeven);
2037 if ($key == $lastrowkey) {
2038 $row->add_class('lastrow');
2039 }
2040
2041 $output .= $this->output_start_tag('tr', array('class' => $row->get_classes_string(), 'style' => $row->style, 'id' => $row->id)) . "\n";
2042 $keys2 = array_keys($row->cells);
2043 $lastkey = end($keys2);
2044
2045 foreach ($row->cells as $key => $cell) {
2046 if (isset($table->colclasses[$key])) {
2047 $cell->add_classes(array_unique(moodle_html_component::clean_classes($table->colclasses[$key])));
2048 }
2049
2050 $cell->add_classes('cell');
2051 $cell->add_classes('c' . $key);
2052 if ($key == $lastkey) {
2053 $cell->add_classes('lastcol');
2054 }
2055 $tdstyle = '';
2056 $tdstyle .= isset($table->align[$key]) ? $table->align[$key] : '';
2057 $tdstyle .= isset($table->size[$key]) ? $table->size[$key] : '';
2058 $tdstyle .= isset($table->wrap[$key]) ? $table->wrap[$key] : '';
2059 $tdattributes = array(
2060 'style' => $tdstyle . $cell->style,
2061 'colspan' => $cell->colspan,
2062 'rowspan' => $cell->rowspan,
2063 'id' => $cell->id,
2064 'class' => $cell->get_classes_string(),
2065 'abbr' => $cell->abbr,
2066 'scope' => $cell->scope);
2067
2068 $output .= $this->output_tag('td', $tdattributes, $cell->text) . "\n";
2069 }
2070 }
2071 $output .= $this->output_end_tag('tr') . "\n";
2072 }
2073 $output .= $this->output_end_tag('tbody') . "\n";
2074 }
2075 $output .= $this->output_end_tag('table') . "\n";
2076
2077 if ($table->rotateheaders && can_use_rotated_text()) {
2078 $this->page->requires->yui_lib('event');
2079 $this->page->requires->js('course/report/progress/textrotate.js');
2080 }
2081
2082 return $output;
2083 }
2084
2085 /**
2086 * Output the place a skip link goes to.
2087 * @param string $id The target name from the corresponding $PAGE->requires->skip_link_to($target) call.
2088 * @return string the HTML to output.
2089 */
2090 public function skip_link_target($id = '') {
2091 return $this->output_tag('span', array('id' => $id), '');
2092 }
2093
2094 /**
2095 * Outputs a heading
2096 * @param string $text The text of the heading
2097 * @param int $level The level of importance of the heading. Defaulting to 2
2098 * @param string $classes A space-separated list of CSS classes
2099 * @param string $id An optional ID
2100 * @return string the HTML to output.
2101 */
2102 public function heading($text, $level = 2, $classes = 'main', $id = '') {
2103 $level = (integer) $level;
2104 if ($level < 1 or $level > 6) {
2105 throw new coding_exception('Heading level must be an integer between 1 and 6.');
2106 }
2107 return $this->output_tag('h' . $level,
2108 array('id' => $id, 'class' => moodle_renderer_base::prepare_classes($classes)), $text);
2109 }
2110
2111 /**
2112 * Outputs a box.
2113 * @param string $contents The contents of the box
2114 * @param string $classes A space-separated list of CSS classes
2115 * @param string $id An optional ID
2116 * @return string the HTML to output.
2117 */
2118 public function box($contents, $classes = 'generalbox', $id = '') {
2119 return $this->box_start($classes, $id) . $contents . $this->box_end();
2120 }
2121
2122 /**
2123 * Outputs the opening section of a box.
2124 * @param string $classes A space-separated list of CSS classes
2125 * @param string $id An optional ID
2126 * @return string the HTML to output.
2127 */
2128 public function box_start($classes = 'generalbox', $id = '') {
2129 $this->opencontainers->push('box', $this->output_end_tag('div'));
2130 return $this->output_start_tag('div', array('id' => $id,
2131 'class' => 'box ' . moodle_renderer_base::prepare_classes($classes)));
2132 }
2133
2134 /**
2135 * Outputs the closing section of a box.
2136 * @return string the HTML to output.
2137 */
2138 public function box_end() {
2139 return $this->opencontainers->pop('box');
2140 }
2141
2142 /**
2143 * Outputs a container.
2144 * @param string $contents The contents of the box
2145 * @param string $classes A space-separated list of CSS classes
2146 * @param string $id An optional ID
2147 * @return string the HTML to output.
2148 */
2149 public function container($contents, $classes = '', $id = '') {
2150 return $this->container_start($classes, $id) . $contents . $this->container_end();
2151 }
2152
2153 /**
2154 * Outputs the opening section of a container.
2155 * @param string $classes A space-separated list of CSS classes
2156 * @param string $id An optional ID
2157 * @return string the HTML to output.
2158 */
2159 public function container_start($classes = '', $id = '') {
2160 $this->opencontainers->push('container', $this->output_end_tag('div'));
2161 return $this->output_start_tag('div', array('id' => $id,
2162 'class' => moodle_renderer_base::prepare_classes($classes)));
2163 }
2164
2165 /**
2166 * Outputs the closing section of a container.
2167 * @return string the HTML to output.
2168 */
2169 public function container_end() {
2170 return $this->opencontainers->pop('container');
2171 }
2172}
2173
2174
2175/// RENDERERS
2176
2177/**
2178 * A renderer that generates output for command-line scripts.
2179 *
2180 * The implementation of this renderer is probably incomplete.
2181 *
2182 * @copyright 2009 Tim Hunt
2183 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2184 * @since Moodle 2.0
2185 */
2186class cli_core_renderer extends moodle_core_renderer {
2187 /**
2188 * Returns the page header.
2189 * @return string HTML fragment
2190 */
2191 public function header() {
2192 output_starting_hook();
2193 return $this->page->heading . "\n";
2194 }
2195
2196 /**
2197 * Returns a template fragment representing a Heading.
2198 * @param string $text The text of the heading
2199 * @param int $level The level of importance of the heading
2200 * @param string $classes A space-separated list of CSS classes
2201 * @param string $id An optional ID
2202 * @return string A template fragment for a heading
2203 */
2204 public function heading($text, $level, $classes = 'main', $id = '') {
2205 $text .= "\n";
2206 switch ($level) {
2207 case 1:
2208 return '=>' . $text;
2209 case 2:
2210 return '-->' . $text;
2211 default:
2212 return $text;
2213 }
2214 }
2215
2216 /**
2217 * Returns a template fragment representing a fatal error.
2218 * @param string $message The message to output
2219 * @param string $moreinfourl URL where more info can be found about the error
2220 * @param string $link Link for the Continue button
2221 * @param array $backtrace The execution backtrace
2222 * @param string $debuginfo Debugging information
2223 * @param bool $showerrordebugwarning Whether or not to show a debugging warning
2224 * @return string A template fragment for a fatal error
2225 */
2226 public function fatal_error($message, $moreinfourl, $link, $backtrace,
2227 $debuginfo = null, $showerrordebugwarning = false) {
2228 $output = "!!! $message !!!\n";
2229
2230 if (debugging('', DEBUG_DEVELOPER)) {
2231 if (!empty($debuginfo)) {
2232 $this->notification($debuginfo, 'notifytiny');
2233 }
2234 if (!empty($backtrace)) {
2235 $this->notification('Stack trace: ' . format_backtrace($backtrace, true), 'notifytiny');
2236 }
2237 }
2238 }
2239
2240 /**
2241 * Returns a template fragment representing a notification.
2242 * @param string $message The message to include
2243 * @param string $classes A space-separated list of CSS classes
2244 * @return string A template fragment for a notification
2245 */
2246 public function notification($message, $classes = 'notifyproblem') {
2247 $message = clean_text($message);
2248 if ($classes === 'notifysuccess') {
2249 return "++ $message ++\n";
2250 }
2251 return "!! $message !!\n";
2252 }
2253}
2254