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