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