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