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