HotPot module: differentiate between "guesses-correct" (JCloze) and "guess-correct...
[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));
2c5ec833 1008 if (!empty($icons[$key])) { //test if the content has an assigned icon
1009 $item .= $this->output_tag('div', array('class' => 'icon column c0'), $icons[$key]);
d9c8f425 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);
db49be13 1062
1063 if ($link->has_action('popup_action')) {
1064 return $this->link_to_popup($link);
1065 }
1066
d9c8f425 1067 $link->prepare();
1068 $this->prepare_event_handlers($link);
1069 $attributes['href'] = prepare_url($link->url);
1070 $attributes['class'] = $link->get_classes_string();
1071 $attributes['title'] = $link->title;
1072 $attributes['id'] = $link->id;
1073
1074 $text = $link->text;
1075
1076 } else if (empty($text)) {
1077 throw new coding_exception('$OUTPUT->link() must have a string as second parameter if the first param ($link) is a string');
1078
1079 } else {
1080 $attributes['href'] = prepare_url($link);
1081 }
1082
1083 return $this->output_tag('a', $attributes, $text);
1084 }
1085
1086 /**
1087 * Print a message along with button choices for Continue/Cancel. Labels default to Yes(Continue)/No(Cancel).
1088 * If a string or moodle_url is given instead of a html_button, method defaults to post and text to Yes/No
1089 * @param string $message The question to ask the user
1090 * @param mixed $continue The html_form component representing the Continue answer. Can also be a moodle_url or string URL
1091 * @param mixed $cancel The html_form component representing the Cancel answer. Can also be a moodle_url or string URL
1092 * @return string HTML fragment
1093 */
1094 public function confirm($message, $continue, $cancel) {
1095 if ($continue instanceof html_form) {
1096 $continue = clone($continue);
1097 } else if (is_string($continue)) {
1098 $continueform = new html_form();
1099 $continueform->url = new moodle_url($continue);
1100 $continue = $continueform;
1101 } else if ($continue instanceof moodle_url) {
1102 $continueform = new html_form();
1103 $continueform->url = $continue;
1104 $continue = $continueform;
1105 } else {
1106 throw new coding_exception('The continue param to $OUTPUT->confirm must be either a URL (string/moodle_url) or a html_form object.');
1107 }
1108
1109 if ($cancel instanceof html_form) {
1110 $cancel = clone($cancel);
1111 } else if (is_string($cancel)) {
1112 $cancelform = new html_form();
1113 $cancelform->url = new moodle_url($cancel);
1114 $cancel = $cancelform;
1115 } else if ($cancel instanceof moodle_url) {
1116 $cancelform = new html_form();
1f1aa445 1117 $cancelform->button->text = get_string('cancel');
d9c8f425 1118 $cancelform->url = $cancel;
1119 $cancel = $cancelform;
1120 } else {
1121 throw new coding_exception('The cancel param to $OUTPUT->confirm must be either a URL (string/moodle_url) or a html_form object.');
1122 }
1123
1124 if (empty($continue->button->text)) {
1125 $continue->button->text = get_string('yes');
1126 }
1127 if (empty($cancel->button->text)) {
1128 $cancel->button->text = get_string('no');
1129 }
1130
1131 $output = $this->box_start('generalbox', 'notice');
1132 $output .= $this->output_tag('p', array(), $message);
1133 $output .= $this->output_tag('div', array('class' => 'buttons'), $this->button($continue) . $this->button($cancel));
1134 $output .= $this->box_end();
1135 return $output;
1136 }
1137
1138 /**
1139 * Given a html_form object, outputs an <input> tag within a form that uses the object's attributes.
1140 *
1141 * @param html_form $form A html_form object
1142 * @return string HTML fragment
1143 */
1144 public function button($form) {
1145 if (empty($form->button) or !($form->button instanceof html_button)) {
1146 throw new coding_exception('$OUTPUT->button($form) requires $form to have a button (html_button) value');
1147 }
1148 $form = clone($form);
1149 $form->button->prepare();
1150
1151 $this->prepare_event_handlers($form->button);
1152
1153 $buttonattributes = array('class' => $form->button->get_classes_string(),
1154 'type' => 'submit',
1155 'value' => $form->button->text,
1156 'disabled' => $form->button->disabled,
1157 'id' => $form->button->id);
1158
1159 $buttonoutput = $this->output_empty_tag('input', $buttonattributes);
1160
1161 // Removing the button so it doesn't get output again
1162 unset($form->button);
1163
7a5c78e0 1164 return $this->output_tag('div', array('class' => 'singlebutton'), $this->form($form, $buttonoutput));
d9c8f425 1165 }
1166
1167 /**
1168 * Given a html_form component and an optional rendered submit button,
1169 * outputs a HTML form with correct divs and inputs and a single submit button.
1170 * This doesn't render any other visible inputs. Use moodleforms for these.
1171 * @param html_form $form A html_form instance
1172 * @param string $contents HTML fragment to put inside the form. If given, must contain at least the submit button.
1173 * @return string HTML fragment
1174 */
1175 public function form($form, $contents=null) {
1176 $form = clone($form);
1177 $form->prepare();
1178 $this->prepare_event_handlers($form);
1179 $buttonoutput = null;
1180
1181 if (empty($contents) && !empty($form->button)) {
1182 debugging("You probably want to use \$OUTPUT->button(\$form), please read that function's documentation", DEBUG_DEVELOPER);
1183 } else if (empty($contents)) {
1184 $contents = $this->output_empty_tag('input', array('type' => 'submit', 'value' => get_string('ok')));
1185 } else if (!empty($form->button)) {
1186 $form->button->prepare();
d9c8f425 1187 $this->prepare_event_handlers($form->button);
1188
1189 $buttonattributes = array('class' => $form->button->get_classes_string(),
1190 'type' => 'submit',
1191 'value' => $form->button->text,
1192 'disabled' => $form->button->disabled,
1193 'id' => $form->button->id);
1194
b65bfc3e 1195 $buttonoutput = $this->output_empty_tag('input', $buttonattributes);
1196
1197 // Hide the submit button if the button has a JS submit action
1198 if ($form->jssubmitaction) {
1199 $buttonoutput = $this->output_start_tag('div', array('id' => "noscript$form->id")) . $buttonoutput . $this->output_end_tag('div');
1200 $this->page->requires->js_function_call('hide_item', array("noscript$form->id"));
1201 }
d9c8f425 1202
1203 }
1204
1205 $hiddenoutput = '';
1206
1207 foreach ($form->url->params() as $var => $val) {
1208 $hiddenoutput .= $this->output_empty_tag('input', array('type' => 'hidden', 'name' => $var, 'value' => $val));
1209 }
1210
1211 $formattributes = array(
1212 'method' => $form->method,
1213 'action' => prepare_url($form->url, true),
1214 'id' => $form->id,
1215 'class' => $form->get_classes_string());
1216
1217 $divoutput = $this->output_tag('div', array(), $hiddenoutput . $contents . $buttonoutput);
7a5c78e0 1218 $output = $this->output_tag('form', $formattributes, $divoutput);
d9c8f425 1219
1220 return $output;
1221 }
1222
1223 /**
1224 * Returns a string containing a link to the user documentation.
1225 * Also contains an icon by default. Shown to teachers and admin only.
1226 * @param string $path The page link after doc root and language, no leading slash.
1227 * @param string $text The text to be displayed for the link
1228 * @param string $iconpath The path to the icon to be displayed
1229 */
1230 public function doc_link($path, $text=false, $iconpath=false) {
1231 global $CFG, $OUTPUT;
beb56299 1232 $icon = new moodle_action_icon();
d9c8f425 1233 $icon->linktext = $text;
1234 $icon->image->alt = $text;
1235 $icon->image->add_class('iconhelp');
1236 $icon->link->url = new moodle_url(get_docs_url($path));
1237
1238 if (!empty($iconpath)) {
1239 $icon->image->src = $iconpath;
1240 } else {
1241 $icon->image->src = $this->old_icon_url('docs');
1242 }
1243
1244 if (!empty($CFG->doctonewwindow)) {
1245 $icon->actions[] = new popup_action('click', $icon->link->url);
1246 }
1247
1248 return $this->action_icon($icon);
1249
1250 }
1251
1252 /**
beb56299 1253 * Given a moodle_action_icon object, outputs an image linking to an action (URL or AJAX).
d9c8f425 1254 *
beb56299 1255 * @param moodle_action_icon $icon A moodle_action_icon object
d9c8f425 1256 * @return string HTML fragment
1257 */
1258 public function action_icon($icon) {
1259 $icon = clone($icon);
1260 $icon->prepare();
1261 $imageoutput = $this->image($icon->image);
1262
1263 if ($icon->linktext) {
1264 $imageoutput .= $icon->linktext;
1265 }
1266 $icon->link->text = $imageoutput;
1267
1268 return $this->link($icon->link);
1269 }
1270
1271 /*
1272 * Centered heading with attached help button (same title text)
1273 * and optional icon attached
94056d9d 1274 * @param moodle_help_icon $helpicon A moodle_help_icon object
d9c8f425 1275 * @param mixed $image An image URL or a html_image object
1276 * @return string HTML fragment
1277 */
1278 public function heading_with_help($helpicon, $image=false) {
1279 if (!($image instanceof html_image) && !empty($image)) {
1280 $htmlimage = new html_image();
1281 $htmlimage->src = $image;
1282 $image = $htmlimage;
1283 }
1284 return $this->container($this->image($image) . $this->heading($helpicon->text, 2, 'main help') . $this->help_icon($helpicon), 'heading-with-help');
1285 }
1286
1287 /**
1288 * Print a help icon.
1289 *
94056d9d 1290 * @param moodle_help_icon $helpicon A moodle_help_icon object, subclass of html_link
d9c8f425 1291 *
1292 * @return string HTML fragment
1293 */
1294 public function help_icon($icon) {
1295 global $COURSE;
1296 $icon = clone($icon);
1297 $icon->prepare();
1298
1299 $popup = new popup_action('click', $icon->link->url);
1300 $icon->link->add_action($popup);
1301
1302 $image = null;
1303
1304 if (!empty($icon->image)) {
1305 $image = $icon->image;
1306 $image->add_class('iconhelp');
1307 }
1308
1309 return $this->output_tag('span', array('class' => 'helplink'), $this->link_to_popup($icon->link, $image));
1310 }
1311
1312 /**
1313 * Creates and returns a button to a popup window
1314 *
1315 * @param html_link $link Subclass of moodle_html_component
1316 * @param moodle_popup $popup A moodle_popup object
1317 * @param html_image $image An optional image replacing the link text
1318 *
1319 * @return string HTML fragment
1320 */
1321 public function link_to_popup($link, $image=null) {
1322 $link = clone($link);
1323 $link->prepare();
1324
1325 $this->prepare_event_handlers($link);
1326
1327 if (empty($link->url)) {
1328 throw new coding_exception('Called $OUTPUT->link_to_popup($link) method without $link->url set.');
1329 }
1330
1331 $linkurl = prepare_url($link->url);
1332
1333 $tagoptions = array(
1334 'title' => $link->title,
1335 'id' => $link->id,
1336 'href' => ($linkurl) ? $linkurl : prepare_url($popup->url),
1337 'class' => $link->get_classes_string());
1338
1339 // Use image if one is given
1340 if (!empty($image) && $image instanceof html_image) {
1341
b65bfc3e 1342 if (empty($image->alt) || $image->alt == HTML_ATTR_EMPTY) {
d9c8f425 1343 $image->alt = $link->text;
b65bfc3e 1344 $image->title = $link->text;
d9c8f425 1345 }
1346
1347 $link->text = $this->image($image);
1348
1349 if (!empty($link->linktext)) {
1350 $link->text = "$link->title &nbsp; $link->text";
1351 }
1352 }
1353
1354 return $this->output_tag('a', $tagoptions, $link->text);
1355 }
1356
1357 /**
1358 * Creates and returns a spacer image with optional line break.
1359 *
1360 * @param html_image $image Subclass of moodle_html_component
1361 *
1362 * @return string HTML fragment
1363 */
1364 public function spacer($image) {
1365 $image = clone($image);
d9c8f425 1366
1367 if (empty($image->src)) {
1368 $image->src = $this->old_icon_url('spacer');
1369 }
1370
b65bfc3e 1371 $image->prepare();
1372 $image->add_class('spacer');
1373
d9c8f425 1374 $output = $this->image($image);
1375
1376 return $output;
1377 }
1378
1379 /**
1380 * Creates and returns an image.
1381 *
1382 * @param html_image $image Subclass of moodle_html_component
1383 *
1384 * @return string HTML fragment
1385 */
1386 public function image($image) {
1387 if ($image === false) {
1388 return false;
1389 }
1390
1391 $image = clone($image);
1392 $image->prepare();
1393
1394 $this->prepare_event_handlers($image);
1395
1396 $attributes = array('class' => $image->get_classes_string(),
1397 'src' => prepare_url($image->src),
1398 'alt' => $image->alt,
1399 'style' => $image->style,
1400 'title' => $image->title,
1401 'id' => $image->id);
1402
1403 if (!empty($image->height) || !empty($image->width)) {
1404 $attributes['style'] .= $this->prepare_legacy_width_and_height($image);
1405 }
1406 return $this->output_empty_tag('img', $attributes);
1407 }
1408
1409 /**
1410 * Print the specified user's avatar.
1411 *
1412 * This method can be used in two ways:
1413 * <pre>
1414 * // Option 1:
beb56299 1415 * $userpic = new moodle_user_picture();
d9c8f425 1416 * // Set properties of $userpic
1417 * $OUTPUT->user_picture($userpic);
1418 *
1419 * // Option 2: (shortcut for simple cases)
1420 * // $user has come from the DB and has fields id, picture, imagealt, firstname and lastname
1421 * $OUTPUT->user_picture($user, $COURSE->id);
1422 * </pre>
1423 *
1424 * @param object $userpic Object with at least fields id, picture, imagealt, firstname, lastname
1425 * If any of these are missing, or if a userid is passed, the database is queried. Avoid this
1426 * if at all possible, particularly for reports. It is very bad for performance.
beb56299 1427 * A moodle_user_picture object is a better parameter.
d9c8f425 1428 * @param int $courseid courseid Used when constructing the link to the user's profile. Required if $userpic
beb56299 1429 * is not a moodle_user_picture object
d9c8f425 1430 * @return string HTML fragment
1431 */
1432 public function user_picture($userpic, $courseid=null) {
beb56299 1433 // Instantiate a moodle_user_picture object if $user is not already one
1434 if (!($userpic instanceof moodle_user_picture)) {
d9c8f425 1435 if (empty($courseid)) {
1436 throw new coding_exception('Called $OUTPUT->user_picture with a $user object but no $courseid.');
1437 }
1438
1439 $user = $userpic;
beb56299 1440 $userpic = new moodle_user_picture();
d9c8f425 1441 $userpic->user = $user;
1442 $userpic->courseid = $courseid;
1443 } else {
1444 $userpic = clone($userpic);
1445 }
1446
1447 $userpic->prepare();
1448
1449 $output = $this->image($userpic->image);
1450
1451 if (!empty($userpic->url)) {
1452 $actions = $userpic->get_actions();
1453 if ($userpic->popup && !empty($actions)) {
1454 $link = new html_link();
1455 $link->url = $userpic->url;
1456 $link->text = fullname($userpic->user);
1457 $link->title = fullname($userpic->user);
1458
1459 foreach ($actions as $action) {
1460 $link->add_action($action);
1461 }
1462 $output = $this->link_to_popup($link, $userpic->image);
1463 } else {
1464 $output = $this->link(prepare_url($userpic->url), $output);
1465 }
1466 }
1467
1468 return $output;
1469 }
1470
1471 /**
1472 * Prints the 'Update this Modulename' button that appears on module pages.
1473 *
1474 * @param string $cmid the course_module id.
1475 * @param string $modulename the module name, eg. "forum", "quiz" or "workshop"
1476 * @return string the HTML for the button, if this user has permission to edit it, else an empty string.
1477 */
1478 public function update_module_button($cmid, $modulename) {
1479 global $CFG;
1480 if (has_capability('moodle/course:manageactivities', get_context_instance(CONTEXT_MODULE, $cmid))) {
1481 $modulename = get_string('modulename', $modulename);
1482 $string = get_string('updatethis', '', $modulename);
1483
1484 $form = new html_form();
1485 $form->url = new moodle_url("$CFG->wwwroot/course/mod.php", array('update' => $cmid, 'return' => true, 'sesskey' => sesskey()));
1486 $form->button->text = $string;
1487 return $this->button($form);
1488 } else {
1489 return '';
1490 }
1491 }
1492
1493 /**
1494 * Prints a "Turn editing on/off" button in a form.
1495 * @param moodle_url $url The URL + params to send through when clicking the button
1496 * @return string HTML the button
1497 */
1498 public function edit_button(moodle_url $url) {
1499 global $USER;
1500 if (!empty($USER->editing)) {
1501 $string = get_string('turneditingoff');
1502 $edit = '0';
1503 } else {
1504 $string = get_string('turneditingon');
1505 $edit = '1';
1506 }
1507
1508 $form = new html_form();
1509 $form->url = $url;
1510 $form->url->param('edit', $edit);
1511 $form->button->text = $string;
1512
1513 return $this->button($form);
1514 }
1515
1516 /**
1517 * Outputs a HTML nested list
1518 *
1519 * @param html_list $list A html_list object
1520 * @return string HTML structure
1521 */
1522 public function htmllist($list) {
1523 $list = clone($list);
1524 $list->prepare();
1525
1526 $this->prepare_event_handlers($list);
1527
1528 if ($list->type == 'ordered') {
1529 $tag = 'ol';
1530 } else if ($list->type == 'unordered') {
1531 $tag = 'ul';
1532 }
1533
1534 $output = $this->output_start_tag($tag, array('class' => $list->get_classes_string()));
1535
1536 foreach ($list->items as $listitem) {
1537 if ($listitem instanceof html_list) {
b65bfc3e 1538 $output .= $this->output_start_tag('li', array()) . "\n";
1539 $output .= $this->htmllist($listitem) . "\n";
1540 $output .= $this->output_end_tag('li') . "\n";
d9c8f425 1541 } else if ($listitem instanceof html_list_item) {
1542 $listitem->prepare();
1543 $this->prepare_event_handlers($listitem);
b65bfc3e 1544 $output .= $this->output_tag('li', array('class' => $listitem->get_classes_string()), $listitem->value) . "\n";
d9c8f425 1545 }
1546 }
1547
b65bfc3e 1548 if ($list->text) {
1549 $output = $list->text . $output;
1550 }
1551
d9c8f425 1552 return $output . $this->output_end_tag($tag);
1553 }
54a007e8 1554
1555 /**
1556 * Prints an inline span element with optional text contents.
1557 *
319770d7 1558 * @param mixed $span A html_span object or some string content to wrap in a span
1559 * @param mixed $classes A space-separated list or an array of classes. Only used if $span is a string
54a007e8 1560 * @return string A HTML fragment
1561 */
319770d7 1562 public function span($span, $classes='') {
1563 if (!($span instanceof html_span)) {
1564 $text = $span;
1565 $span = new html_span();
1566 $span->contents = $text;
1567 $span->add_classes($classes);
1568 }
1569
54a007e8 1570 $span = clone($span);
1571 $span->prepare();
1572 $this->prepare_event_handlers($span);
1573 $attributes = array('class' => $span->get_classes_string(),
1574 'alt' => $span->alt,
1575 'style' => $span->style,
1576 'title' => $span->title,
1577 'id' => $span->id);
1578 return $this->output_tag('span', $attributes, $span->contents);
1579 }
d9c8f425 1580
1581 /**
1582 * Prints a simple button to close a window
1583 *
1584 * @global objec)t
1585 * @param string $text The lang string for the button's label (already output from get_string())
1586 * @return string|void if $return is true, void otherwise
1587 */
7a5c78e0 1588 public function close_window_button($text='') {
d9c8f425 1589 if (empty($text)) {
1590 $text = get_string('closewindow');
1591 }
1592 $closeform = new html_form();
1593 $closeform->url = '#';
7a5c78e0 1594 $closeform->method = 'get';
d9c8f425 1595 $closeform->button->text = $text;
1596 $closeform->button->add_action('click', 'close_window');
1597 $closeform->button->prepare();
1598 return $this->container($this->button($closeform), 'closewindow');
1599 }
1600
1601 /**
1602 * Outputs a <select> menu or a list of radio/checkbox inputs.
1603 *
1604 * This method is extremely versatile, and can be used to output yes/no menus,
1605 * form-enclosed menus with automatic redirects when an option is selected,
1606 * descriptive labels and help icons. By default it just outputs a select
1607 * menu.
1608 *
7b1f2c82 1609 * To add a descriptive label, use html_select::set_label($text, $for) or
1610 * html_select::set_label($label) passing a html_label object
d9c8f425 1611 *
7b1f2c82 1612 * To add a help icon, use html_select::set_help($page, $text, $linktext) or
1613 * html_select::set_help($helpicon) passing a moodle_help_icon object
d9c8f425 1614 *
7b1f2c82 1615 * If you html_select::$rendertype to "radio", it will render radio buttons
d9c8f425 1616 * instead of a <select> menu, unless $multiple is true, in which case it
1617 * will render checkboxes.
1618 *
7b1f2c82 1619 * To surround the menu with a form, simply set html_select->form as a
d9c8f425 1620 * valid html_form object. Note that this function will NOT automatically
1621 * add a form for non-JS browsers. If you do not set one up, it assumes
1622 * that you are providing your own form in some other way.
1623 *
7b1f2c82 1624 * You can either call this function with a single html_select argument
d9c8f425 1625 * or, with a list of parameters, in which case those parameters are sent to
7b1f2c82 1626 * the html_select constructor.
d9c8f425 1627 *
7b1f2c82 1628 * @param html_select $select a html_select that describes
d9c8f425 1629 * the select menu you want output.
1630 * @return string the HTML for the <select>
1631 */
1632 public function select($select) {
1633 $select = clone($select);
1634 $select->prepare();
1635
1636 $this->prepare_event_handlers($select);
1637
1638 if (empty($select->id)) {
1639 $select->id = 'menu' . str_replace(array('[', ']'), '', $select->name);
1640 }
1641
1642 $attributes = array(
1643 'name' => $select->name,
1644 'id' => $select->id,
1645 'class' => $select->get_classes_string()
1646 );
1647 if ($select->disabled) {
1648 $attributes['disabled'] = 'disabled';
1649 }
1650 if ($select->tabindex) {
1651 $attributes['tabindex'] = $tabindex;
1652 }
1653
1654 if ($select->rendertype == 'menu' && $select->listbox) {
1655 if (is_integer($select->listbox)) {
1656 $size = $select->listbox;
1657 } else {
1658 $size = min($select->maxautosize, count($select->options));
1659 }
1660 $attributes['size'] = $size;
1661 if ($select->multiple) {
1662 $attributes['multiple'] = 'multiple';
1663 }
1664 }
1665
1666 $html = '';
1667
1668 if (!empty($select->label)) {
1669 $html .= $this->label($select->label);
1670 }
1671
94056d9d 1672 if (!empty($select->helpicon) && $select->helpicon instanceof moodle_help_icon) {
d9c8f425 1673 $html .= $this->help_icon($select->helpicon);
1674 }
1675
1676 if ($select->rendertype == 'menu') {
1677 $html .= $this->output_start_tag('select', $attributes) . "\n";
1678
1679 foreach ($select->options as $option) {
1680 // $OUTPUT->select_option detects if $option is an option or an optgroup
1681 $html .= $this->select_option($option);
1682 }
1683
1684 $html .= $this->output_end_tag('select') . "\n";
1685 } else if ($select->rendertype == 'radio') {
1686 $currentradio = 0;
1687 foreach ($select->options as $option) {
1688 $html .= $this->radio($option, $select->name);
1689 $currentradio++;
1690 }
1691 } else if ($select->rendertype == 'checkbox') {
1692 $currentcheckbox = 0;
1ae3767a 1693 // If only two choices are available, suggest using the checkbox method instead
1694 if (count($select->options) < 3 && !$select->multiple) {
1695 debugging('You are using $OUTPUT->select() to render two mutually exclusive choices using checkboxes. Please use $OUTPUT->checkbox(html_select_option) instead.', DEBUG_DEVELOPER);
1696 } else {
1697 foreach ($select->options as $option) {
1698 $html .= $this->checkbox($option, $select->name);
1699 $currentcheckbox++;
1700 }
d9c8f425 1701 }
1702 }
1703
1704 if (!empty($select->form) && $select->form instanceof html_form) {
1705 $html = $this->form($select->form, $html);
1706 }
1707
1708 return $html;
1709 }
1710
1711 /**
1712 * Outputs a <input type="radio" /> element. Optgroups are ignored, so do not
1713 * pass a html_select_optgroup as a param to this function.
1714 *
1715 * @param html_select_option $option a html_select_option
1716 * @return string the HTML for the <input type="radio">
1717 */
1718 public function radio($option, $name='unnamed') {
1ae3767a 1719 static $currentradio = array();
e57c283d 1720
1ae3767a 1721 if (empty($currentradio[$name])) {
1722 $currentradio[$name] = 0;
1723 }
1724
d9c8f425 1725 if ($option instanceof html_select_optgroup) {
1726 throw new coding_exception('$OUTPUT->radio($option) does not support a html_select_optgroup object as param.');
1727 } else if (!($option instanceof html_select_option)) {
1728 throw new coding_exception('$OUTPUT->radio($option) only accepts a html_select_option object as param.');
1729 }
1730 $option = clone($option);
1731 $option->prepare();
1732 $option->label->for = $option->id;
1733 $this->prepare_event_handlers($option);
1734
1ae3767a 1735 $output = $this->output_start_tag('span', array('class' => "radiogroup $name rb{$currentradio[$name]}")) . "\n";
d9c8f425 1736 $output .= $this->label($option->label);
1737
1738 if ($option->selected == 'selected') {
1739 $option->selected = 'checked';
1740 }
1741
1742 $output .= $this->output_empty_tag('input', array(
1743 'type' => 'radio',
1744 'value' => $option->value,
1745 'name' => $name,
1746 'alt' => $option->alt,
1747 'id' => $option->id,
1748 'class' => $option->get_classes_string(),
1749 'checked' => $option->selected));
1750
1751 $output .= $this->output_end_tag('span');
1ae3767a 1752 $currentradio[$name]++;
d9c8f425 1753 return $output;
1754 }
1755
1756 /**
1757 * Outputs a <input type="checkbox" /> element. Optgroups are ignored, so do not
1758 * pass a html_select_optgroup as a param to this function.
1759 *
1760 * @param html_select_option $option a html_select_option
1761 * @return string the HTML for the <input type="checkbox">
1762 */
1763 public function checkbox($option, $name='unnamed') {
1764 if ($option instanceof html_select_optgroup) {
1765 throw new coding_exception('$OUTPUT->checkbox($option) does not support a html_select_optgroup object as param.');
1766 } else if (!($option instanceof html_select_option)) {
1767 throw new coding_exception('$OUTPUT->checkbox($option) only accepts a html_select_option object as param.');
1768 }
1769 $option = clone($option);
1770 $option->prepare();
1771
1772 $option->label->for = $option->id;
1773 $this->prepare_event_handlers($option);
1774
1775 $output = $this->output_start_tag('span', array('class' => "checkbox $name")) . "\n";
1776
1777 if ($option->selected == 'selected') {
1778 $option->selected = 'checked';
1779 }
1780
1781 $output .= $this->output_empty_tag('input', array(
1782 'type' => 'checkbox',
1783 'value' => $option->value,
1784 'name' => $name,
1785 'id' => $option->id,
1786 'alt' => $option->alt,
1787 'class' => $option->get_classes_string(),
1788 'checked' => $option->selected));
1789 $output .= $this->label($option->label);
1790
1791 $output .= $this->output_end_tag('span');
1792
1793 return $output;
1794 }
1795
1796 /**
1797 * Output an <option> or <optgroup> element. If an optgroup element is detected,
1798 * this will recursively output its options as well.
1799 *
7b1f2c82 1800 * @param mixed $option a html_select_option or html_select_optgroup
d9c8f425 1801 * @return string the HTML for the <option> or <optgroup>
1802 */
1803 public function select_option($option) {
1804 $option = clone($option);
1805 $option->prepare();
1806 $this->prepare_event_handlers($option);
1807
1808 if ($option instanceof html_select_option) {
1809 return $this->output_tag('option', array(
1810 'value' => $option->value,
1811 'class' => $option->get_classes_string(),
1812 'selected' => $option->selected), $option->text);
1813 } else if ($option instanceof html_select_optgroup) {
1814 $output = $this->output_start_tag('optgroup', array('label' => $option->text, 'class' => $option->get_classes_string()));
1815 foreach ($option->options as $selectoption) {
1816 $output .= $this->select_option($selectoption);
1817 }
1818 $output .= $this->output_end_tag('optgroup');
1819 return $output;
1820 }
1821 }
1822
1823 /**
1824 * Output an <input type="text"> element
1825 *
1826 * @param html_field $field a html_field object
1827 * @return string the HTML for the <input>
1828 */
1829 public function textfield($field) {
1c1f64a2 1830 return $this->output_tag('span', array('class' => "textfield $field->name"), $this->field($field));
1831 }
1832
1833 /**
1834 * Output an <input/> element
1835 *
1836 * @param html_field $field a html_field object
1837 * @return string the HTML for the <input>
1838 */
1839 public function field($field) {
d9c8f425 1840 $field = clone($field);
1841 $field->prepare();
1842 $this->prepare_event_handlers($field);
1c1f64a2 1843 if (!empty($field->label->text)) {
3cc457db 1844 $output .= $this->label($field->label);
1845 }
1c1f64a2 1846 return $this->output_empty_tag('input', array(
1847 'type' => $field->type,
d9c8f425 1848 'name' => $field->name,
1849 'id' => $field->id,
1850 'value' => $field->value,
1851 'style' => $field->style,
1852 'alt' => $field->alt,
1c1f64a2 1853 'title' => $field->title,
d9c8f425 1854 'maxlength' => $field->maxlength));
d9c8f425 1855 }
1856
1857 /**
1858 * Outputs a <label> element.
1859 * @param html_label $label A html_label object
1860 * @return HTML fragment
1861 */
1862 public function label($label) {
1863 $label = clone($label);
1864 $label->prepare();
1865 $this->prepare_event_handlers($label);
1866 return $this->output_tag('label', array('for' => $label->for, 'class' => $label->get_classes_string()), $label->text);
1867 }
1868
1869 /**
1870 * Output an error message. By default wraps the error message in <span class="error">.
1871 * If the error message is blank, nothing is output.
1872 * @param string $message the error message.
1873 * @return string the HTML to output.
1874 */
1875 public function error_text($message) {
1876 if (empty($message)) {
1877 return '';
1878 }
1879 return $this->output_tag('span', array('class' => 'error'), $message);
1880 }
1881
1882 /**
1883 * Do not call this function directly.
1884 *
1885 * To terminate the current script with a fatal error, call the {@link print_error}
1886 * function, or throw an exception. Doing either of those things will then call this
1887 * function to display the error, before terminating the execution.
1888 *
1889 * @param string $message The message to output
1890 * @param string $moreinfourl URL where more info can be found about the error
1891 * @param string $link Link for the Continue button
1892 * @param array $backtrace The execution backtrace
1893 * @param string $debuginfo Debugging information
1894 * @param bool $showerrordebugwarning Whether or not to show a debugging warning
1895 * @return string the HTML to output.
1896 */
1897 public function fatal_error($message, $moreinfourl, $link, $backtrace,
1898 $debuginfo = null, $showerrordebugwarning = false) {
1899
1900 $output = '';
1901
e57c283d 1902 if (!debugging('', DEBUG_DEVELOPER)) {
1903 return false;
1904 }
1905
d9c8f425 1906 if ($this->has_started()) {
1907 $output .= $this->opencontainers->pop_all_but_last();
1908 } else {
1909 // Header not yet printed
1910 @header('HTTP/1.0 404 Not Found');
1911 $this->page->set_title(get_string('error'));
1912 $output .= $this->header();
1913 }
1914
1915 $message = '<p class="errormessage">' . $message . '</p>'.
1916 '<p class="errorcode"><a href="' . $moreinfourl . '">' .
1917 get_string('moreinformation') . '</a></p>';
1918 $output .= $this->box($message, 'errorbox');
1919
e57c283d 1920 if ($showerrordebugwarning) {
1921 $output .= $this->notification('error() is a deprecated function. ' .
1922 'Please call print_error() instead of error()', 'notifytiny');
1923 }
1924 if (!empty($debuginfo)) {
1925 $output .= $this->notification($debuginfo, 'notifytiny');
1926 }
1927 if (!empty($backtrace)) {
1928 $output .= $this->notification('Stack trace: ' .
1929 format_backtrace($backtrace), 'notifytiny');
d9c8f425 1930 }
1931
1932 if (!empty($link)) {
1933 $output .= $this->continue_button($link);
1934 }
1935
1936 $output .= $this->footer();
1937
1938 // Padding to encourage IE to display our error page, rather than its own.
1939 $output .= str_repeat(' ', 512);
1940
1941 return $output;
1942 }
1943
1944 /**
1945 * Output a notification (that is, a status message about something that has
1946 * just happened).
1947 *
1948 * @param string $message the message to print out
1949 * @param string $classes normally 'notifyproblem' or 'notifysuccess'.
1950 * @return string the HTML to output.
1951 */
1952 public function notification($message, $classes = 'notifyproblem') {
1953 return $this->output_tag('div', array('class' =>
1954 moodle_renderer_base::prepare_classes($classes)), clean_text($message));
1955 }
1956
1957 /**
1958 * Print a continue button that goes to a particular URL.
1959 *
1960 * @param string|moodle_url $link The url the button goes to.
1961 * @return string the HTML to output.
1962 */
1963 public function continue_button($link) {
1964 if (!is_a($link, 'moodle_url')) {
1965 $link = new moodle_url($link);
1966 }
1967 $form = new html_form();
1968 $form->url = $link;
1969 $form->values = $link->params();
1970 $form->button->text = get_string('continue');
1971 $form->method = 'get';
1972
1973 return $this->output_tag('div', array('class' => 'continuebutton') , $this->button($form));
1974 }
1975
1976 /**
1977 * Prints a single paging bar to provide access to other pages (usually in a search)
1978 *
1979 * @param string|moodle_url $link The url the button goes to.
1980 * @return string the HTML to output.
1981 */
1982 public function paging_bar($pagingbar) {
1983 $output = '';
1984 $pagingbar = clone($pagingbar);
1985 $pagingbar->prepare();
1986
1987 if ($pagingbar->totalcount > $pagingbar->perpage) {
1988 $output .= get_string('page') . ':';
1989
1990 if (!empty($pagingbar->previouslink)) {
1991 $output .= '&nbsp;(' . $this->link($pagingbar->previouslink) . ')&nbsp;';
1992 }
1993
1994 if (!empty($pagingbar->firstlink)) {
1995 $output .= '&nbsp;' . $this->link($pagingbar->firstlink) . '&nbsp;...';
1996 }
1997
1998 foreach ($pagingbar->pagelinks as $link) {
1999 if ($link instanceof html_link) {
2000 $output .= '&nbsp;&nbsp;' . $this->link($link);
2001 } else {
2002 $output .= "&nbsp;&nbsp;$link";
2003 }
2004 }
2005
2006 if (!empty($pagingbar->lastlink)) {
2007 $output .= '&nbsp;...' . $this->link($pagingbar->lastlink) . '&nbsp;';
2008 }
2009
2010 if (!empty($pagingbar->nextlink)) {
2011 $output .= '&nbsp;&nbsp;(' . $this->link($pagingbar->nextlink) . ')';
2012 }
2013 }
2014
2015 return $this->output_tag('div', array('class' => 'paging'), $output);
2016 }
2017
2018 /**
2019 * Render a HTML table
2020 *
2021 * @param object $table {@link html_table} instance containing all the information needed
2022 * @return string the HTML to output.
2023 */
2024 public function table(html_table $table) {
2025 $table = clone($table);
2026 $table->prepare();
2027 $attributes = array(
2028 'id' => $table->id,
2029 'width' => $table->width,
2030 'summary' => $table->summary,
2031 'cellpadding' => $table->cellpadding,
2032 'cellspacing' => $table->cellspacing,
2033 'class' => $table->get_classes_string());
2034 $output = $this->output_start_tag('table', $attributes) . "\n";
2035
2036 $countcols = 0;
2037
2038 if (!empty($table->head)) {
2039 $countcols = count($table->head);
319770d7 2040 $output .= $this->output_start_tag('thead', $table->headclasses) . "\n";
d9c8f425 2041 $output .= $this->output_start_tag('tr', array()) . "\n";
2042 $keys = array_keys($table->head);
2043 $lastkey = end($keys);
54a007e8 2044
d9c8f425 2045 foreach ($table->head as $key => $heading) {
54a007e8 2046 // Convert plain string headings into html_table_cell objects
2047 if (!($heading instanceof html_table_cell)) {
2048 $headingtext = $heading;
2049 $heading = new html_table_cell();
2050 $heading->text = $headingtext;
2051 $heading->header = true;
2052 }
2053
2054 $this->prepare_event_handlers($heading);
2055
2056 $heading->add_classes(array('header', 'c' . $key));
d9c8f425 2057 if (isset($table->headspan[$key]) && $table->headspan[$key] > 1) {
54a007e8 2058 $heading->colspan = $table->headspan[$key];
d9c8f425 2059 $countcols += $table->headspan[$key] - 1;
54a007e8 2060 }
2061
d9c8f425 2062 if ($key == $lastkey) {
54a007e8 2063 $heading->add_class('lastcol');
d9c8f425 2064 }
2065 if (isset($table->colclasses[$key])) {
54a007e8 2066 $heading->add_class($table->colclasses[$key]);
d9c8f425 2067 }
2068 if ($table->rotateheaders) {
2069 // we need to wrap the heading content
54a007e8 2070 $heading->text = $this->output_tag('span', '', $heading->text);
d9c8f425 2071 }
54a007e8 2072
d9c8f425 2073 $attributes = array(
54a007e8 2074 'style' => $table->align[$key] . $table->size[$key] . $heading->style,
2075 'class' => $heading->get_classes_string(),
2076 'scope' => $heading->scope,
2077 'colspan' => $heading->colspan);
2078
2079 $output .= $this->output_tag('th', $attributes, $heading->text) . "\n";
d9c8f425 2080 }
2081 $output .= $this->output_end_tag('tr') . "\n";
2082 $output .= $this->output_end_tag('thead') . "\n";
2083 }
2084
2085 if (!empty($table->data)) {
2086 $oddeven = 1;
2087 $keys = array_keys($table->data);
2088 $lastrowkey = end($keys);
319770d7 2089 $output .= $this->output_start_tag('tbody', $table->bodyclasses) . "\n";
d9c8f425 2090
2091 foreach ($table->data as $key => $row) {
2092 if (($row === 'hr') && ($countcols)) {
2093 $output .= $this->output_tag('td', array('colspan' => $countcols),
2094 $this->output_tag('div', array('class' => 'tabledivider'), '')) . "\n";
2095 } else {
2096 // Convert array rows to html_table_rows and cell strings to html_table_cell objects
2097 if (!($row instanceof html_table_row)) {
2098 $newrow = new html_table_row();
2099
2100 foreach ($row as $unused => $item) {
2101 $cell = new html_table_cell();
2102 $cell->text = $item;
54a007e8 2103 $this->prepare_event_handlers($cell);
d9c8f425 2104 $newrow->cells[] = $cell;
2105 }
2106 $row = $newrow;
2107 }
54a007e8 2108
2109 $this->prepare_event_handlers($row);
d9c8f425 2110
2111 $oddeven = $oddeven ? 0 : 1;
2112 if (isset($table->rowclasses[$key])) {
2113 $row->add_classes(array_unique(moodle_html_component::clean_classes($table->rowclasses[$key])));
2114 }
2115
2116 $row->add_class('r' . $oddeven);
2117 if ($key == $lastrowkey) {
2118 $row->add_class('lastrow');
2119 }
2120
2121 $output .= $this->output_start_tag('tr', array('class' => $row->get_classes_string(), 'style' => $row->style, 'id' => $row->id)) . "\n";
2122 $keys2 = array_keys($row->cells);
2123 $lastkey = end($keys2);
2124
2125 foreach ($row->cells as $key => $cell) {
54a007e8 2126 if (!($cell instanceof html_table_cell)) {
2127 $mycell = new html_table_cell();
2128 $mycell->text = $cell;
2129 $this->prepare_event_handlers($mycell);
2130 $cell = $mycell;
2131 }
2132
d9c8f425 2133 if (isset($table->colclasses[$key])) {
2134 $cell->add_classes(array_unique(moodle_html_component::clean_classes($table->colclasses[$key])));
2135 }
2136
2137 $cell->add_classes('cell');
2138 $cell->add_classes('c' . $key);
2139 if ($key == $lastkey) {
2140 $cell->add_classes('lastcol');
2141 }
2142 $tdstyle = '';
2143 $tdstyle .= isset($table->align[$key]) ? $table->align[$key] : '';
2144 $tdstyle .= isset($table->size[$key]) ? $table->size[$key] : '';
2145 $tdstyle .= isset($table->wrap[$key]) ? $table->wrap[$key] : '';
2146 $tdattributes = array(
2147 'style' => $tdstyle . $cell->style,
2148 'colspan' => $cell->colspan,
2149 'rowspan' => $cell->rowspan,
2150 'id' => $cell->id,
2151 'class' => $cell->get_classes_string(),
2152 'abbr' => $cell->abbr,
2153 'scope' => $cell->scope);
1ae3767a 2154 $tagtype = 'td';
2155 if ($cell->header) {
2156 $tagtype = 'th';
2157 }
2158 $output .= $this->output_tag($tagtype, $tdattributes, $cell->text) . "\n";
d9c8f425 2159 }
2160 }
2161 $output .= $this->output_end_tag('tr') . "\n";
2162 }
2163 $output .= $this->output_end_tag('tbody') . "\n";
2164 }
2165 $output .= $this->output_end_tag('table') . "\n";
2166
2167 if ($table->rotateheaders && can_use_rotated_text()) {
2168 $this->page->requires->yui_lib('event');
2169 $this->page->requires->js('course/report/progress/textrotate.js');
2170 }
2171
2172 return $output;
2173 }
2174
2175 /**
2176 * Output the place a skip link goes to.
2177 * @param string $id The target name from the corresponding $PAGE->requires->skip_link_to($target) call.
2178 * @return string the HTML to output.
2179 */
2180 public function skip_link_target($id = '') {
2181 return $this->output_tag('span', array('id' => $id), '');
2182 }
2183
2184 /**
2185 * Outputs a heading
2186 * @param string $text The text of the heading
2187 * @param int $level The level of importance of the heading. Defaulting to 2
2188 * @param string $classes A space-separated list of CSS classes
2189 * @param string $id An optional ID
2190 * @return string the HTML to output.
2191 */
2192 public function heading($text, $level = 2, $classes = 'main', $id = '') {
2193 $level = (integer) $level;
2194 if ($level < 1 or $level > 6) {
2195 throw new coding_exception('Heading level must be an integer between 1 and 6.');
2196 }
2197 return $this->output_tag('h' . $level,
2198 array('id' => $id, 'class' => moodle_renderer_base::prepare_classes($classes)), $text);
2199 }
2200
2201 /**
2202 * Outputs a box.
2203 * @param string $contents The contents of the box
2204 * @param string $classes A space-separated list of CSS classes
2205 * @param string $id An optional ID
2206 * @return string the HTML to output.
2207 */
2208 public function box($contents, $classes = 'generalbox', $id = '') {
2209 return $this->box_start($classes, $id) . $contents . $this->box_end();
2210 }
2211
2212 /**
2213 * Outputs the opening section of a box.
2214 * @param string $classes A space-separated list of CSS classes
2215 * @param string $id An optional ID
2216 * @return string the HTML to output.
2217 */
2218 public function box_start($classes = 'generalbox', $id = '') {
2219 $this->opencontainers->push('box', $this->output_end_tag('div'));
2220 return $this->output_start_tag('div', array('id' => $id,
2221 'class' => 'box ' . moodle_renderer_base::prepare_classes($classes)));
2222 }
2223
2224 /**
2225 * Outputs the closing section of a box.
2226 * @return string the HTML to output.
2227 */
2228 public function box_end() {
2229 return $this->opencontainers->pop('box');
2230 }
2231
2232 /**
2233 * Outputs a container.
2234 * @param string $contents The contents of the 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 container($contents, $classes = '', $id = '') {
2240 return $this->container_start($classes, $id) . $contents . $this->container_end();
2241 }
2242
2243 /**
2244 * Outputs the opening section of a container.
2245 * @param string $classes A space-separated list of CSS classes
2246 * @param string $id An optional ID
2247 * @return string the HTML to output.
2248 */
2249 public function container_start($classes = '', $id = '') {
2250 $this->opencontainers->push('container', $this->output_end_tag('div'));
2251 return $this->output_start_tag('div', array('id' => $id,
2252 'class' => moodle_renderer_base::prepare_classes($classes)));
2253 }
2254
2255 /**
2256 * Outputs the closing section of a container.
2257 * @return string the HTML to output.
2258 */
2259 public function container_end() {
2260 return $this->opencontainers->pop('container');
2261 }
2262}
2263
2264
2265/// RENDERERS
2266
2267/**
2268 * A renderer that generates output for command-line scripts.
2269 *
2270 * The implementation of this renderer is probably incomplete.
2271 *
2272 * @copyright 2009 Tim Hunt
2273 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2274 * @since Moodle 2.0
2275 */
2276class cli_core_renderer extends moodle_core_renderer {
2277 /**
2278 * Returns the page header.
2279 * @return string HTML fragment
2280 */
2281 public function header() {
2282 output_starting_hook();
2283 return $this->page->heading . "\n";
2284 }
2285
2286 /**
2287 * Returns a template fragment representing a Heading.
2288 * @param string $text The text of the heading
2289 * @param int $level The level of importance of the heading
2290 * @param string $classes A space-separated list of CSS classes
2291 * @param string $id An optional ID
2292 * @return string A template fragment for a heading
2293 */
2294 public function heading($text, $level, $classes = 'main', $id = '') {
2295 $text .= "\n";
2296 switch ($level) {
2297 case 1:
2298 return '=>' . $text;
2299 case 2:
2300 return '-->' . $text;
2301 default:
2302 return $text;
2303 }
2304 }
2305
2306 /**
2307 * Returns a template fragment representing a fatal error.
2308 * @param string $message The message to output
2309 * @param string $moreinfourl URL where more info can be found about the error
2310 * @param string $link Link for the Continue button
2311 * @param array $backtrace The execution backtrace
2312 * @param string $debuginfo Debugging information
2313 * @param bool $showerrordebugwarning Whether or not to show a debugging warning
2314 * @return string A template fragment for a fatal error
2315 */
2316 public function fatal_error($message, $moreinfourl, $link, $backtrace,
2317 $debuginfo = null, $showerrordebugwarning = false) {
2318 $output = "!!! $message !!!\n";
2319
2320 if (debugging('', DEBUG_DEVELOPER)) {
2321 if (!empty($debuginfo)) {
2322 $this->notification($debuginfo, 'notifytiny');
2323 }
2324 if (!empty($backtrace)) {
2325 $this->notification('Stack trace: ' . format_backtrace($backtrace, true), 'notifytiny');
2326 }
2327 }
2328 }
2329
2330 /**
2331 * Returns a template fragment representing a notification.
2332 * @param string $message The message to include
2333 * @param string $classes A space-separated list of CSS classes
2334 * @return string A template fragment for a notification
2335 */
2336 public function notification($message, $classes = 'notifyproblem') {
2337 $message = clean_text($message);
2338 if ($classes === 'notifysuccess') {
2339 return "++ $message ++\n";
2340 }
2341 return "!! $message !!\n";
2342 }
2343}
2344