authentication plugins: MDL-21343 Add missing $OUTPUT global variables used in plugin...
[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
52368876
PS
926 * @param html_form|moodle_url|string $continue The html_form component representing the Continue answer. Can also be a moodle_url or string URL
927 * @param html_form|moodle_url|string $cancel The html_form component representing the Cancel answer. Can also be a moodle_url or string URL
d9c8f425 928 * @return string HTML fragment
929 */
930 public function confirm($message, $continue, $cancel) {
26eab8d4 931 if ($continue instanceof html_form) { //TODO: change to single_button
d9c8f425 932 $continue = clone($continue);
52368876 933 } else if (is_string($continue) or $continue instanceof moodle_url) {
26eab8d4 934 $continue = new single_button($continue, get_string('continue'), 'post');
d9c8f425 935 } else {
26eab8d4 936 throw new coding_exception('The continue param to $OUTPUT->confirm() must be either a URL (string/moodle_url) or a html_form instance.');
d9c8f425 937 }
938
26eab8d4 939 if ($cancel instanceof html_form) { //TODO: change to single_button
d9c8f425 940 $cancel = clone($cancel);
52368876 941 } else if (is_string($cancel) or $cancel instanceof moodle_url) {
26eab8d4 942 $cancel = new single_button($cancel, get_string('cancel'), 'get');
d9c8f425 943 } else {
26eab8d4 944 throw new coding_exception('The cancel param to $OUTPUT->confirm() must be either a URL (string/moodle_url) or a html_form instance.');
d9c8f425 945 }
946
d9c8f425 947 $output = $this->box_start('generalbox', 'notice');
5d0c95a5
PS
948 $output .= html_writer::tag('p', array(), $message);
949 $output .= html_writer::tag('div', array('class' => 'buttons'), $this->button($continue) . $this->button($cancel));
d9c8f425 950 $output .= $this->box_end();
951 return $output;
952 }
953
3cd5305f
PS
954 /**
955 * Returns a form with single button.
5c2ed7e2 956 * If first parameter is html_form instance all other parameters are ignored.
3cd5305f 957 *
26eab8d4 958 * @param string|moodle_url|single_button $url_or_singlebutton
3cd5305f
PS
959 * @param string $label button text
960 * @param string $method get or post submit method
5c2ed7e2 961 * @param array $options associative array {disabled, title}
3cd5305f
PS
962 * @return string HTML fragment
963 */
26eab8d4
PS
964 public function single_button($url_or_singlebutton, $label=null, $method='post', array $options=null) {
965 if ($url_or_singlebutton instanceof single_button) {
966 $button = $url_or_singlebutton;
5c2ed7e2
PS
967 if (func_num_args() > 1) {
968 debugging('html_form instance used as first parameter of $OUTPUT->single_button(), all other parameters are ignored.');
3cd5305f 969 }
169a5b54 970 } else if ($url_or_singlebutton instanceof moodle_url or is_string($url_or_singlebutton)) {
26eab8d4 971 $button = new single_button($url_or_singlebutton, $label, $method, $options);
3cd5305f 972 } else {
26eab8d4 973 throw new coding_exception('The $$url_or_singlebutton param to $OUTPUT->single_button() must be either a URL (string/moodle_url) or a single_button instance.');
3cd5305f
PS
974 }
975
d894edd4 976 return $this->button($button);
3cd5305f
PS
977 }
978
d9c8f425 979 /**
980 * Given a html_form object, outputs an <input> tag within a form that uses the object's attributes.
981 *
982 * @param html_form $form A html_form object
983 * @return string HTML fragment
984 */
3cd5305f 985 public function button(html_form $form) {
d9c8f425 986 if (empty($form->button) or !($form->button instanceof html_button)) {
987 throw new coding_exception('$OUTPUT->button($form) requires $form to have a button (html_button) value');
988 }
989 $form = clone($form);
34059565 990 $form->button->prepare($this, $this->page, $this->target);
d9c8f425 991
992 $this->prepare_event_handlers($form->button);
993
994 $buttonattributes = array('class' => $form->button->get_classes_string(),
995 'type' => 'submit',
996 'value' => $form->button->text,
997 'disabled' => $form->button->disabled,
998 'id' => $form->button->id);
999
5d0c95a5 1000 $buttonoutput = html_writer::empty_tag('input', $buttonattributes);
d9c8f425 1001
1002 // Removing the button so it doesn't get output again
1003 unset($form->button);
1004
5d0c95a5 1005 return html_writer::tag('div', array('class' => 'singlebutton'), $this->form($form, $buttonoutput));
d9c8f425 1006 }
1007
1008 /**
1009 * Given a html_form component and an optional rendered submit button,
1010 * outputs a HTML form with correct divs and inputs and a single submit button.
1011 * This doesn't render any other visible inputs. Use moodleforms for these.
1012 * @param html_form $form A html_form instance
1013 * @param string $contents HTML fragment to put inside the form. If given, must contain at least the submit button.
1014 * @return string HTML fragment
1015 */
3cd5305f 1016 public function form(html_form $form, $contents=null) {
d9c8f425 1017 $form = clone($form);
34059565 1018 $form->prepare($this, $this->page, $this->target);
d9c8f425 1019 $this->prepare_event_handlers($form);
1020 $buttonoutput = null;
1021
1022 if (empty($contents) && !empty($form->button)) {
5c2ed7e2 1023 debugging("You probably want to use \$OUTPUT->single_button(\$form), please read that function's documentation", DEBUG_DEVELOPER);
d9c8f425 1024 } else if (empty($contents)) {
5d0c95a5 1025 $contents = html_writer::empty_tag('input', array('type' => 'submit', 'value' => get_string('ok')));
d9c8f425 1026 } else if (!empty($form->button)) {
34059565 1027 $form->button->prepare($this, $this->page, $this->target);
d9c8f425 1028 $this->prepare_event_handlers($form->button);
1029
1030 $buttonattributes = array('class' => $form->button->get_classes_string(),
1031 'type' => 'submit',
1032 'value' => $form->button->text,
1033 'disabled' => $form->button->disabled,
1034 'id' => $form->button->id);
1035
5d0c95a5 1036 $buttonoutput = html_writer::empty_tag('input', $buttonattributes);
b65bfc3e 1037
1038 // Hide the submit button if the button has a JS submit action
1039 if ($form->jssubmitaction) {
5d0c95a5 1040 $buttonoutput = html_writer::start_tag('div', array('id' => "noscript$form->id")) . $buttonoutput . html_writer::end_tag('div');
b65bfc3e 1041 $this->page->requires->js_function_call('hide_item', array("noscript$form->id"));
1042 }
d9c8f425 1043
1044 }
1045
1046 $hiddenoutput = '';
1047
1048 foreach ($form->url->params() as $var => $val) {
5d0c95a5 1049 $hiddenoutput .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => $var, 'value' => $val));
d9c8f425 1050 }
1051
1052 $formattributes = array(
1053 'method' => $form->method,
1054 'action' => prepare_url($form->url, true),
1055 'id' => $form->id,
1056 'class' => $form->get_classes_string());
1057
5d0c95a5
PS
1058 $divoutput = html_writer::tag('div', array(), $hiddenoutput . $contents . $buttonoutput);
1059 $output = html_writer::tag('form', $formattributes, $divoutput);
d9c8f425 1060
1061 return $output;
1062 }
1063
1064 /**
1065 * Returns a string containing a link to the user documentation.
1066 * Also contains an icon by default. Shown to teachers and admin only.
1067 * @param string $path The page link after doc root and language, no leading slash.
1068 * @param string $text The text to be displayed for the link
8ae8bf8a 1069 * @retrun string
d9c8f425 1070 */
8ae8bf8a
PS
1071 public function doc_link($path, $text) {
1072 global $CFG;
1073
1074 $options = array('class'=>'iconhelp', 'alt'=>$text);
1075 $url = new moodle_url(get_docs_url($path));
1076
bc2587bc 1077 $icon = $this->image('docs', $options);
8ae8bf8a
PS
1078
1079 $link = new html_link($url, $icon.$text);
d9c8f425 1080
1081 if (!empty($CFG->doctonewwindow)) {
8ae8bf8a 1082 $link->add_action(new popup_action('click', $url));
d9c8f425 1083 }
1084
8ae8bf8a 1085 return $this->link($link);
d9c8f425 1086 }
1087
1088 /**
beb56299 1089 * Given a moodle_action_icon object, outputs an image linking to an action (URL or AJAX).
d9c8f425 1090 *
1df1adaa 1091 * @param mixed $url_or_link A html_link object or a string URL (text param required in second case)
8ae8bf8a
PS
1092 * @param string $title link title and also image alt if no alt specified in $options
1093 * @param html_image|moodle_url|string $image_or_url image or url of the image,
1094 * it is also possible to use short pix name for core images
1095 * @param array $options image attributes such as title, id, alt, widht, height
1096 * @param bool $linktext show title next to image in link
d9c8f425 1097 * @return string HTML fragment
1098 */
8ae8bf8a
PS
1099 public function action_icon($url_or_link, $title, $image_or_url, array $options = null, $linktext=false) {
1100 $options = (array)$options;
1101 if (empty($options['class'])) {
1102 // let ppl override the class via $options
1103 $options['class'] = 'action-icon';
1104 }
1105
1106 if (empty($title)) {
1107 debugging('$title should not be empty in action_icon() call');
1108 }
1109
1110 if (!$linktext) {
1c11dcb6 1111 $options['alt'] = $title;
8ae8bf8a 1112 }
d9c8f425 1113
1c11dcb6 1114 $icon = $this->image($image_or_url, $options);
8ae8bf8a
PS
1115
1116 if ($linktext) {
a7b575b1 1117 $icon = $icon . $title;
8ae8bf8a
PS
1118 }
1119
1120 if ($url_or_link instanceof html_link) {
1121 $link = clone($url_or_link);
1122 $link->text = ($icon);
8ae8bf8a 1123 } else {
1df1adaa 1124 $link = new html_link($url_or_link, $icon);
d9c8f425 1125 }
1df1adaa 1126 $url = $link->url;
d9c8f425 1127
8ae8bf8a 1128 return $this->link($link);
d9c8f425 1129 }
1130
1131 /*
1132 * Centered heading with attached help button (same title text)
1133 * and optional icon attached
4bcc5118
PS
1134 * @param string $text A heading text
1135 * @param string $page The keyword that defines a help page
1136 * @param string $component component name
1137 * @param string|moodle_url $icon
1138 * @param string $iconalt icon alt text
d9c8f425 1139 * @return string HTML fragment
1140 */
4bcc5118
PS
1141 public function heading_with_help($text, $helppage, $component='moodle', $icon='', $iconalt='') {
1142 $image = '';
1143 if ($icon) {
1144 if ($icon instanceof moodle_url) {
1145 $image = $this->image($icon, array('class'=>'icon', 'alt'=>$iconalt));
1146 } else {
1147 $image = $this->image($this->pix_url($icon, $component), array('class'=>'icon', 'alt'=>$iconalt));
1148 }
d9c8f425 1149 }
4bcc5118
PS
1150
1151 $help = $this->help_icon($helppage, $text, $component);
1152
1153 return $this->heading($image.$text.$help, 2, 'main help');
d9c8f425 1154 }
1155
1156 /**
1157 * Print a help icon.
1158 *
4bcc5118 1159 * @param string $page The keyword that defines a help page
bf11293a 1160 * @param string $title A descriptive text for accessibility only
4bcc5118 1161 * @param string $component component name
bf11293a
PS
1162 * @param string|bool $linktext true means use $title as link text, string means link text value
1163 * @return string HTML fragment
1164 */
1165 public function help_icon($helppage, $title, $component = 'moodle', $linktext='') {
1166 $icon = new help_icon($helppage, $title, $component);
1167 if ($linktext === true) {
1168 $icon->linktext = $title;
1169 } else if (!empty($linktext)) {
1170 $icon->linktext = $linktext;
1171 }
1172 return $this->render($icon);
1173 }
4bcc5118 1174
bf11293a
PS
1175 /**
1176 * Implementation of user image rendering.
1177 * @param help_icon $helpicon
1178 * @return string HTML fragment
d9c8f425 1179 */
bf11293a
PS
1180 protected function render_help_icon(help_icon $helpicon) {
1181 global $CFG;
d9c8f425 1182
bf11293a
PS
1183 // first get the help image icon
1184 $src = $this->pix_url('help');
d9c8f425 1185
bf11293a
PS
1186 if (empty($helpicon->linktext)) {
1187 $alt = $helpicon->title;
1188 } else {
1189 $alt = get_string('helpwiththis');
1190 }
1191
1192 $attributes = array('src'=>$src, 'alt'=>$alt, 'class'=>'iconhelp');
1193 $output = html_writer::empty_tag('img', $attributes);
1194
1195 // add the link text if given
1196 if (!empty($helpicon->linktext)) {
1197 // the spacing has to be done through CSS
1198 $output .= $helpicon->linktext;
d9c8f425 1199 }
1200
bf11293a
PS
1201 // now create the link around it - TODO: this will be changed during the big lang cleanup in 2.0
1202 $url = new moodle_url($CFG->wwwroot.'/help.php', array('module' => $helpicon->component, 'file' => $helpicon->helppage .'.html'));
1203
1204 // note: this title is displayed only if JS is disabled, otherwise the link will have the new ajax tooltip
1205 $title = get_string('helpprefix2', '', trim($helpicon->title, ". \t"));
1206
1207 $attributes = array('href'=>$url, 'title'=>$title);
1208 $id = html_writer::random_id('helpicon');
1209 $attributes['id'] = $id;
1210 $this->add_action_handler($id, new popup_action('click', $url));
1211 $output = html_writer::tag('a', $attributes, $output);
1212
1213 // and finally span
1214 return html_writer::tag('span', array('class' => 'helplink'), $output);
d9c8f425 1215 }
1216
1217 /**
4bcc5118 1218 * Print scale help icon.
d9c8f425 1219 *
4bcc5118
PS
1220 * @param int $courseid
1221 * @param object $scale instance
1222 * @return string HTML fragment
d9c8f425 1223 */
4bcc5118
PS
1224 public function help_icon_scale($courseid, stdClass $scale) {
1225 global $CFG;
02f64f97 1226
4bcc5118 1227 $title = get_string('helpprefix2', '', $scale->name) .' ('.get_string('newwindow').')';
02f64f97 1228
4bcc5118 1229 $icon = $this->image($this->pix_url('help'), array('class'=>'iconhelp', 'alt'=>get_string('scales')));
02f64f97 1230
4bcc5118
PS
1231 $link = new html_link(new moodle_url($CFG->wwwroot.'/course/scales.php', array('id' => $courseid, 'list' => true, 'scaleid' => $scale->id)), $icon);
1232 $popupaction = new popup_action('click', $link->url, 'ratingscale');
1233 $link->add_action($popupaction);
02f64f97 1234
5d0c95a5 1235 return html_writer::tag('span', array('class' => 'helplink'), $this->link($link));
d9c8f425 1236 }
1237
1238 /**
1239 * Creates and returns a spacer image with optional line break.
1240 *
1ba862ec
PS
1241 * @param array $options id, alt, width=1, height=1, etc.
1242 * special options br=false (break after spacer)
d9c8f425 1243 * @return string HTML fragment
1244 */
1ba862ec
PS
1245 public function spacer(array $options = null) {
1246 $options = (array)$options;
1247 if (empty($options['width'])) {
1248 $options['width'] = 1;
1249 }
1250 if (empty($options['height'])) {
1251 $options['height'] = 1;
d9c8f425 1252 }
1ba862ec 1253 $options['class'] = 'spacer';
d9c8f425 1254
1ba862ec 1255 $output = $this->image($this->pix_url('spacer'), $options);
b65bfc3e 1256
1ba862ec
PS
1257 if (!empty($options['br'])) {
1258 $output .= '<br />';
1259 }
d9c8f425 1260
1261 return $output;
1262 }
1263
1264 /**
1265 * Creates and returns an image.
1266 *
b5270630
PS
1267 * @param html_image|moodle_url|string $image_or_url image or url of the image,
1268 * it is also possible to use short pix name for core images
1ba862ec 1269 * @param array $options image attributes such as title, id, alt, widht, height
d9c8f425 1270 *
1271 * @return string HTML fragment
1272 */
1ba862ec 1273 public function image($image_or_url, array $options = null) {
b5270630
PS
1274 if (empty($image_or_url)) {
1275 throw new coding_exception('Empty $image_or_url value in $OUTPTU->image()');
8fa16366 1276 }
1ba862ec 1277
8fa16366 1278 if ($image_or_url instanceof html_image) {
1ba862ec 1279 $image = clone($image_or_url);
1ba862ec 1280 } else {
ada38fad 1281 if ($image_or_url instanceof moodle_url) {
801db076 1282 $url = $image_or_url;
ada38fad 1283 } else if (strpos($image_or_url, 'http')) {
b5270630
PS
1284 $url = new moodle_url($image_or_url);
1285 } else {
1286 $url = $this->pix_url($image_or_url, 'moodle');
1287 }
1288 $image = new html_image($url, $options);
d9c8f425 1289 }
1290
34059565 1291 $image->prepare($this, $this->page, $this->target);
d9c8f425 1292
1293 $this->prepare_event_handlers($image);
1294
1295 $attributes = array('class' => $image->get_classes_string(),
b5270630
PS
1296 'src' => prepare_url($image->src),
1297 'alt' => $image->alt,
d9c8f425 1298 'style' => $image->style,
1299 'title' => $image->title,
b5270630 1300 'id' => $image->id);
d9c8f425 1301
1ba862ec
PS
1302 // do not use prepare_legacy_width_and_height() here,
1303 // xhtml strict allows width&height and inline styles break theming too!
1304 if (!empty($image->height)) {
1305 $attributes['height'] = $image->height;
1306 }
1307 if (!empty($image->width)) {
1308 $attributes['width'] = $image->width;
d9c8f425 1309 }
1ba862ec 1310
5d0c95a5 1311 return html_writer::empty_tag('img', $attributes);
d9c8f425 1312 }
1313
1314 /**
1315 * Print the specified user's avatar.
1316 *
5d0c95a5 1317 * User avatar may be obtained in two ways:
d9c8f425 1318 * <pre>
812dbaf7
PS
1319 * // Option 1: (shortcut for simple cases, preferred way)
1320 * // $user has come from the DB and has fields id, picture, imagealt, firstname and lastname
1321 * $OUTPUT->user_picture($user, array('popup'=>true));
1322 *
5d0c95a5
PS
1323 * // Option 2:
1324 * $userpic = new user_picture($user);
d9c8f425 1325 * // Set properties of $userpic
812dbaf7 1326 * $userpic->popup = true;
5d0c95a5 1327 * $OUTPUT->render($userpic);
d9c8f425 1328 * </pre>
1329 *
5d0c95a5 1330 * @param object Object with at least fields id, picture, imagealt, firstname, lastname
812dbaf7 1331 * If any of these are missing, the database is queried. Avoid this
d9c8f425 1332 * if at all possible, particularly for reports. It is very bad for performance.
812dbaf7
PS
1333 * @param array $options associative array with user picture options, used only if not a user_picture object,
1334 * options are:
1335 * - courseid=$this->page->course->id (course id of user profile in link)
1336 * - size=35 (size of image)
1337 * - link=true (make image clickable - the link leads to user profile)
1338 * - popup=false (open in popup)
1339 * - alttext=true (add image alt attribute)
5d0c95a5 1340 * - class = image class attribute (default 'userpicture')
d9c8f425 1341 * @return string HTML fragment
1342 */
5d0c95a5
PS
1343 public function user_picture(stdClass $user, array $options = null) {
1344 $userpicture = new user_picture($user);
1345 foreach ((array)$options as $key=>$value) {
1346 if (array_key_exists($key, $userpicture)) {
1347 $userpicture->$key = $value;
1348 }
1349 }
1350 return $this->render($userpicture);
1351 }
1352
1353 /**
1354 * Internal implementation of user image rendering.
1355 * @param user_picture $userpicture
1356 * @return string
1357 */
1358 protected function render_user_picture(user_picture $userpicture) {
1359 global $CFG, $DB;
812dbaf7 1360
5d0c95a5
PS
1361 $user = $userpicture->user;
1362
1363 if ($userpicture->alttext) {
1364 if (!empty($user->imagealt)) {
1365 $alt = $user->imagealt;
1366 } else {
1367 $alt = get_string('pictureof', '', fullname($user));
1368 }
d9c8f425 1369 } else {
5d0c95a5
PS
1370 $alt = HTML_ATTR_EMPTY;
1371 }
1372
1373 if (empty($userpicture->size)) {
1374 $file = 'f2';
1375 $size = 35;
1376 } else if ($userpicture->size === true or $userpicture->size == 1) {
1377 $file = 'f1';
1378 $size = 100;
1379 } else if ($userpicture->size >= 50) {
1380 $file = 'f1';
1381 $size = $userpicture->size;
1382 } else {
1383 $file = 'f2';
1384 $size = $userpicture->size;
d9c8f425 1385 }
1386
5d0c95a5 1387 $class = $userpicture->class;
d9c8f425 1388
5d0c95a5
PS
1389 if (!empty($user->picture)) {
1390 require_once($CFG->libdir.'/filelib.php');
1391 $src = new moodle_url(get_file_url($user->id.'/'.$file.'.jpg', null, 'user'));
1392 } else { // Print default user pictures (use theme version if available)
1393 $class .= ' defaultuserpic';
1394 $src = $this->pix_url('u/' . $file);
1395 }
d9c8f425 1396
5d0c95a5
PS
1397 $attributes = array('src'=>$src, 'alt'=>$alt, 'class'=>$class, 'width'=>$size, 'height'=>$size);
1398
1399 // get the image html output fisrt
1400 $output = html_writer::empty_tag('img', $attributes);;
1401
1402 // then wrap it in link if needed
1403 if (!$userpicture->link) {
1404 return $output;
d9c8f425 1405 }
1406
5d0c95a5
PS
1407 if (empty($userpicture->courseid)) {
1408 $courseid = $this->page->course->id;
1409 } else {
1410 $courseid = $userpicture->courseid;
1411 }
1412
1413 $url = new moodle_url($CFG->wwwroot.'/user/view.php', array('id' => $user->id, 'course' => $courseid));
1414
1415 $attributes = array('href'=>$url);
1416
1417 if ($userpicture->popup) {
1418 $id = html_writer::random_id('userpicture');
1419 $attributes['id'] = $id;
1420 $this->add_action_handler($id, new popup_action('click', $url));
1421 }
1422
1423 return html_writer::tag('a', $attributes, $output);
d9c8f425 1424 }
1425
1426 /**
1427 * Prints the 'Update this Modulename' button that appears on module pages.
1428 *
1429 * @param string $cmid the course_module id.
1430 * @param string $modulename the module name, eg. "forum", "quiz" or "workshop"
1431 * @return string the HTML for the button, if this user has permission to edit it, else an empty string.
1432 */
1433 public function update_module_button($cmid, $modulename) {
1434 global $CFG;
1435 if (has_capability('moodle/course:manageactivities', get_context_instance(CONTEXT_MODULE, $cmid))) {
1436 $modulename = get_string('modulename', $modulename);
1437 $string = get_string('updatethis', '', $modulename);
1438
1439 $form = new html_form();
1440 $form->url = new moodle_url("$CFG->wwwroot/course/mod.php", array('update' => $cmid, 'return' => true, 'sesskey' => sesskey()));
1441 $form->button->text = $string;
1442 return $this->button($form);
1443 } else {
1444 return '';
1445 }
1446 }
1447
1448 /**
1449 * Prints a "Turn editing on/off" button in a form.
1450 * @param moodle_url $url The URL + params to send through when clicking the button
1451 * @return string HTML the button
1452 */
1453 public function edit_button(moodle_url $url) {
1454 global $USER;
1455 if (!empty($USER->editing)) {
1456 $string = get_string('turneditingoff');
1457 $edit = '0';
1458 } else {
1459 $string = get_string('turneditingon');
1460 $edit = '1';
1461 }
1462
1463 $form = new html_form();
1464 $form->url = $url;
1465 $form->url->param('edit', $edit);
1466 $form->button->text = $string;
1467
1468 return $this->button($form);
1469 }
1470
1471 /**
1472 * Outputs a HTML nested list
1473 *
1474 * @param html_list $list A html_list object
1475 * @return string HTML structure
1476 */
1477 public function htmllist($list) {
1478 $list = clone($list);
34059565 1479 $list->prepare($this, $this->page, $this->target);
d9c8f425 1480
1481 $this->prepare_event_handlers($list);
1482
1483 if ($list->type == 'ordered') {
1484 $tag = 'ol';
1485 } else if ($list->type == 'unordered') {
1486 $tag = 'ul';
1487 }
1488
5d0c95a5 1489 $output = html_writer::start_tag($tag, array('class' => $list->get_classes_string()));
d9c8f425 1490
1491 foreach ($list->items as $listitem) {
1492 if ($listitem instanceof html_list) {
5d0c95a5 1493 $output .= html_writer::start_tag('li', array()) . "\n";
b65bfc3e 1494 $output .= $this->htmllist($listitem) . "\n";
5d0c95a5 1495 $output .= html_writer::end_tag('li') . "\n";
d9c8f425 1496 } else if ($listitem instanceof html_list_item) {
34059565 1497 $listitem->prepare($this, $this->page, $this->target);
d9c8f425 1498 $this->prepare_event_handlers($listitem);
5d0c95a5 1499 $output .= html_writer::tag('li', array('class' => $listitem->get_classes_string()), $listitem->value) . "\n";
a0ead5eb 1500 } else {
5d0c95a5 1501 $output .= html_writer::tag('li', array(), $listitem) . "\n";
d9c8f425 1502 }
1503 }
1504
b65bfc3e 1505 if ($list->text) {
1506 $output = $list->text . $output;
1507 }
1508
5d0c95a5 1509 return $output . html_writer::end_tag($tag);
d9c8f425 1510 }
21237187 1511
54a007e8 1512 /**
1513 * Prints an inline span element with optional text contents.
1514 *
319770d7 1515 * @param mixed $span A html_span object or some string content to wrap in a span
1516 * @param mixed $classes A space-separated list or an array of classes. Only used if $span is a string
54a007e8 1517 * @return string A HTML fragment
1518 */
319770d7 1519 public function span($span, $classes='') {
1520 if (!($span instanceof html_span)) {
1521 $text = $span;
1522 $span = new html_span();
1523 $span->contents = $text;
1524 $span->add_classes($classes);
1525 }
1526
54a007e8 1527 $span = clone($span);
34059565 1528 $span->prepare($this, $this->page, $this->target);
54a007e8 1529 $this->prepare_event_handlers($span);
1530 $attributes = array('class' => $span->get_classes_string(),
1531 'alt' => $span->alt,
1532 'style' => $span->style,
1533 'title' => $span->title,
1534 'id' => $span->id);
5d0c95a5 1535 return html_writer::tag('span', $attributes, $span->contents);
54a007e8 1536 }
d9c8f425 1537
1538 /**
1539 * Prints a simple button to close a window
1540 *
1541 * @global objec)t
1542 * @param string $text The lang string for the button's label (already output from get_string())
1543 * @return string|void if $return is true, void otherwise
1544 */
7a5c78e0 1545 public function close_window_button($text='') {
d9c8f425 1546 if (empty($text)) {
1547 $text = get_string('closewindow');
1548 }
1549 $closeform = new html_form();
1550 $closeform->url = '#';
7a5c78e0 1551 $closeform->method = 'get';
d9c8f425 1552 $closeform->button->text = $text;
1553 $closeform->button->add_action('click', 'close_window');
34059565 1554 $closeform->button->prepare($this, $this->page, $this->target);
d9c8f425 1555 return $this->container($this->button($closeform), 'closewindow');
1556 }
1557
1558 /**
1559 * Outputs a <select> menu or a list of radio/checkbox inputs.
1560 *
1561 * This method is extremely versatile, and can be used to output yes/no menus,
1562 * form-enclosed menus with automatic redirects when an option is selected,
1563 * descriptive labels and help icons. By default it just outputs a select
1564 * menu.
1565 *
7b1f2c82 1566 * To add a descriptive label, use html_select::set_label($text, $for) or
1567 * html_select::set_label($label) passing a html_label object
d9c8f425 1568 *
7b1f2c82 1569 * To add a help icon, use html_select::set_help($page, $text, $linktext) or
4bcc5118 1570 * html_select::set_help($helpicon) passing a help_icon object
d9c8f425 1571 *
7b1f2c82 1572 * If you html_select::$rendertype to "radio", it will render radio buttons
d9c8f425 1573 * instead of a <select> menu, unless $multiple is true, in which case it
1574 * will render checkboxes.
1575 *
7b1f2c82 1576 * To surround the menu with a form, simply set html_select->form as a
d9c8f425 1577 * valid html_form object. Note that this function will NOT automatically
1578 * add a form for non-JS browsers. If you do not set one up, it assumes
1579 * that you are providing your own form in some other way.
1580 *
7b1f2c82 1581 * You can either call this function with a single html_select argument
d9c8f425 1582 * or, with a list of parameters, in which case those parameters are sent to
7b1f2c82 1583 * the html_select constructor.
d9c8f425 1584 *
7b1f2c82 1585 * @param html_select $select a html_select that describes
d9c8f425 1586 * the select menu you want output.
1587 * @return string the HTML for the <select>
1588 */
1589 public function select($select) {
1590 $select = clone($select);
34059565 1591 $select->prepare($this, $this->page, $this->target);
d9c8f425 1592
1593 $this->prepare_event_handlers($select);
1594
1595 if (empty($select->id)) {
1596 $select->id = 'menu' . str_replace(array('[', ']'), '', $select->name);
1597 }
1598
1599 $attributes = array(
1600 'name' => $select->name,
1601 'id' => $select->id,
1602 'class' => $select->get_classes_string()
1603 );
1604 if ($select->disabled) {
1605 $attributes['disabled'] = 'disabled';
1606 }
1607 if ($select->tabindex) {
93b026ee 1608 $attributes['tabindex'] = $select->tabindex;
d9c8f425 1609 }
1610
1611 if ($select->rendertype == 'menu' && $select->listbox) {
1612 if (is_integer($select->listbox)) {
1613 $size = $select->listbox;
1614 } else {
1615 $size = min($select->maxautosize, count($select->options));
1616 }
1617 $attributes['size'] = $size;
1618 if ($select->multiple) {
1619 $attributes['multiple'] = 'multiple';
1620 }
1621 }
1622
1623 $html = '';
1624
1625 if (!empty($select->label)) {
1626 $html .= $this->label($select->label);
1627 }
1628
4bcc5118
PS
1629 if ($select->helpicon) {
1630 $html .= $this->help_icon($select->helpicon['helppage'], $select->helpicon['text'], $select->helpicon['component']);
d9c8f425 1631 }
1632
1633 if ($select->rendertype == 'menu') {
5d0c95a5 1634 $html .= html_writer::start_tag('select', $attributes) . "\n";
d9c8f425 1635
1636 foreach ($select->options as $option) {
1637 // $OUTPUT->select_option detects if $option is an option or an optgroup
1638 $html .= $this->select_option($option);
1639 }
1640
5d0c95a5 1641 $html .= html_writer::end_tag('select') . "\n";
d9c8f425 1642 } else if ($select->rendertype == 'radio') {
1643 $currentradio = 0;
1644 foreach ($select->options as $option) {
1645 $html .= $this->radio($option, $select->name);
1646 $currentradio++;
1647 }
1648 } else if ($select->rendertype == 'checkbox') {
1649 $currentcheckbox = 0;
1ae3767a 1650 // If only two choices are available, suggest using the checkbox method instead
1651 if (count($select->options) < 3 && !$select->multiple) {
1652 debugging('You are using $OUTPUT->select() to render two mutually exclusive choices using checkboxes. Please use $OUTPUT->checkbox(html_select_option) instead.', DEBUG_DEVELOPER);
1653 } else {
1654 foreach ($select->options as $option) {
1655 $html .= $this->checkbox($option, $select->name);
1656 $currentcheckbox++;
1657 }
d9c8f425 1658 }
1659 }
1660
1661 if (!empty($select->form) && $select->form instanceof html_form) {
1662 $html = $this->form($select->form, $html);
1663 }
1664
1665 return $html;
1666 }
1667
1668 /**
1669 * Outputs a <input type="radio" /> element. Optgroups are ignored, so do not
1670 * pass a html_select_optgroup as a param to this function.
1671 *
1672 * @param html_select_option $option a html_select_option
1673 * @return string the HTML for the <input type="radio">
1674 */
1675 public function radio($option, $name='unnamed') {
1ae3767a 1676 static $currentradio = array();
e57c283d 1677
1ae3767a 1678 if (empty($currentradio[$name])) {
1679 $currentradio[$name] = 0;
1680 }
1681
d9c8f425 1682 if ($option instanceof html_select_optgroup) {
1683 throw new coding_exception('$OUTPUT->radio($option) does not support a html_select_optgroup object as param.');
1684 } else if (!($option instanceof html_select_option)) {
1685 throw new coding_exception('$OUTPUT->radio($option) only accepts a html_select_option object as param.');
1686 }
1687 $option = clone($option);
34059565 1688 $option->prepare($this, $this->page, $this->target);
d9c8f425 1689 $option->label->for = $option->id;
1690 $this->prepare_event_handlers($option);
1691
5d0c95a5 1692 $output = html_writer::start_tag('span', array('class' => "radiogroup $name rb{$currentradio[$name]}")) . "\n";
d9c8f425 1693 $output .= $this->label($option->label);
1694
1695 if ($option->selected == 'selected') {
1696 $option->selected = 'checked';
1697 }
1698
5d0c95a5 1699 $output .= html_writer::empty_tag('input', array(
d9c8f425 1700 'type' => 'radio',
1701 'value' => $option->value,
1702 'name' => $name,
1703 'alt' => $option->alt,
1704 'id' => $option->id,
1705 'class' => $option->get_classes_string(),
1706 'checked' => $option->selected));
1707
5d0c95a5 1708 $output .= html_writer::end_tag('span');
1ae3767a 1709 $currentradio[$name]++;
d9c8f425 1710 return $output;
1711 }
1712
1713 /**
1714 * Outputs a <input type="checkbox" /> element. Optgroups are ignored, so do not
1715 * pass a html_select_optgroup as a param to this function.
1716 *
1717 * @param html_select_option $option a html_select_option
1718 * @return string the HTML for the <input type="checkbox">
1719 */
1720 public function checkbox($option, $name='unnamed') {
1721 if ($option instanceof html_select_optgroup) {
1722 throw new coding_exception('$OUTPUT->checkbox($option) does not support a html_select_optgroup object as param.');
1723 } else if (!($option instanceof html_select_option)) {
1724 throw new coding_exception('$OUTPUT->checkbox($option) only accepts a html_select_option object as param.');
1725 }
1726 $option = clone($option);
34059565 1727 $option->prepare($this, $this->page, $this->target);
d9c8f425 1728
1729 $option->label->for = $option->id;
1730 $this->prepare_event_handlers($option);
1731
5d0c95a5 1732 $output = html_writer::start_tag('span', array('class' => "checkbox $name")) . "\n";
d9c8f425 1733
a8744ef1 1734 if ($option->selected) {
d9c8f425 1735 $option->selected = 'checked';
a8744ef1 1736 } else {
1737 $option->selected = '';
d9c8f425 1738 }
1739
5d0c95a5 1740 $output .= html_writer::empty_tag('input', array(
d9c8f425 1741 'type' => 'checkbox',
1742 'value' => $option->value,
1743 'name' => $name,
1744 'id' => $option->id,
1745 'alt' => $option->alt,
a4998d01 1746 'disabled' => $option->disabled,
d9c8f425 1747 'class' => $option->get_classes_string(),
1748 'checked' => $option->selected));
1749 $output .= $this->label($option->label);
1750
5d0c95a5 1751 $output .= html_writer::end_tag('span');
d9c8f425 1752
1753 return $output;
1754 }
1755
1756 /**
1757 * Output an <option> or <optgroup> element. If an optgroup element is detected,
1758 * this will recursively output its options as well.
1759 *
7b1f2c82 1760 * @param mixed $option a html_select_option or html_select_optgroup
d9c8f425 1761 * @return string the HTML for the <option> or <optgroup>
1762 */
1763 public function select_option($option) {
1764 $option = clone($option);
34059565 1765 $option->prepare($this, $this->page, $this->target);
d9c8f425 1766 $this->prepare_event_handlers($option);
1767
1768 if ($option instanceof html_select_option) {
5d0c95a5 1769 return html_writer::tag('option', array(
d9c8f425 1770 'value' => $option->value,
a4998d01 1771 'disabled' => $option->disabled,
d9c8f425 1772 'class' => $option->get_classes_string(),
1773 'selected' => $option->selected), $option->text);
1774 } else if ($option instanceof html_select_optgroup) {
5d0c95a5 1775 $output = html_writer::start_tag('optgroup', array('label' => $option->text, 'class' => $option->get_classes_string()));
d9c8f425 1776 foreach ($option->options as $selectoption) {
1777 $output .= $this->select_option($selectoption);
1778 }
5d0c95a5 1779 $output .= html_writer::end_tag('optgroup');
d9c8f425 1780 return $output;
1781 }
1782 }
1783
1784 /**
1785 * Output an <input type="text"> element
1786 *
1787 * @param html_field $field a html_field object
1788 * @return string the HTML for the <input>
1789 */
1790 public function textfield($field) {
5d0c95a5 1791 return html_writer::tag('span', array('class' => "textfield $field->name"), $this->field($field));
1c1f64a2 1792 }
1793
1794 /**
1795 * Output an <input/> element
1796 *
1797 * @param html_field $field a html_field object
1798 * @return string the HTML for the <input>
1799 */
1800 public function field($field) {
d9c8f425 1801 $field = clone($field);
34059565 1802 $field->prepare($this, $this->page, $this->target);
d9c8f425 1803 $this->prepare_event_handlers($field);
a019627a 1804 $label = '';
1c1f64a2 1805 if (!empty($field->label->text)) {
a019627a 1806 $label = $this->label($field->label);
3cc457db 1807 }
5d0c95a5 1808 return $label . html_writer::empty_tag('input', array(
1c1f64a2 1809 'type' => $field->type,
d9c8f425 1810 'name' => $field->name,
1811 'id' => $field->id,
1812 'value' => $field->value,
5fc6d585 1813 'disabled' => $field->disabled,
d9c8f425 1814 'style' => $field->style,
1815 'alt' => $field->alt,
1c1f64a2 1816 'title' => $field->title,
d9c8f425 1817 'maxlength' => $field->maxlength));
d9c8f425 1818 }
1819
1820 /**
1821 * Outputs a <label> element.
1822 * @param html_label $label A html_label object
1823 * @return HTML fragment
1824 */
1825 public function label($label) {
1826 $label = clone($label);
34059565 1827 $label->prepare($this, $this->page, $this->target);
d9c8f425 1828 $this->prepare_event_handlers($label);
5d0c95a5 1829 return html_writer::tag('label', array('for' => $label->for, 'class' => $label->get_classes_string()), $label->text);
d9c8f425 1830 }
1831
1832 /**
1833 * Output an error message. By default wraps the error message in <span class="error">.
1834 * If the error message is blank, nothing is output.
1835 * @param string $message the error message.
1836 * @return string the HTML to output.
1837 */
1838 public function error_text($message) {
1839 if (empty($message)) {
1840 return '';
1841 }
5d0c95a5 1842 return html_writer::tag('span', array('class' => 'error'), $message);
d9c8f425 1843 }
1844
1845 /**
1846 * Do not call this function directly.
1847 *
1848 * To terminate the current script with a fatal error, call the {@link print_error}
1849 * function, or throw an exception. Doing either of those things will then call this
1850 * function to display the error, before terminating the execution.
1851 *
1852 * @param string $message The message to output
1853 * @param string $moreinfourl URL where more info can be found about the error
1854 * @param string $link Link for the Continue button
1855 * @param array $backtrace The execution backtrace
1856 * @param string $debuginfo Debugging information
d9c8f425 1857 * @return string the HTML to output.
1858 */
83267ec0 1859 public function fatal_error($message, $moreinfourl, $link, $backtrace, $debuginfo = null) {
d9c8f425 1860
1861 $output = '';
6f8f4d83 1862 $obbuffer = '';
e57c283d 1863
d9c8f425 1864 if ($this->has_started()) {
50764d37
PS
1865 // we can not always recover properly here, we have problems with output buffering,
1866 // html tables, etc.
d9c8f425 1867 $output .= $this->opencontainers->pop_all_but_last();
50764d37 1868
d9c8f425 1869 } else {
50764d37
PS
1870 // It is really bad if library code throws exception when output buffering is on,
1871 // because the buffered text would be printed before our start of page.
1872 // NOTE: this hack might be behave unexpectedly in case output buffering is enabled in PHP.ini
1873 while (ob_get_level() > 0) {
6f8f4d83 1874 $obbuffer .= ob_get_clean();
50764d37 1875 }
6f8f4d83 1876
d9c8f425 1877 // Header not yet printed
85309744 1878 if (isset($_SERVER['SERVER_PROTOCOL'])) {
78946b9b
PS
1879 // server protocol should be always present, because this render
1880 // can not be used from command line or when outputting custom XML
85309744
PS
1881 @header($_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found');
1882 }
6d92adcb 1883 $this->page->set_url(''); // no url
191b267b 1884 //$this->page->set_pagelayout('base'); //TODO: MDL-20676 blocks on error pages are weird, unfortunately it somehow detect the pagelayout from URL :-(
d9c8f425 1885 $this->page->set_title(get_string('error'));
1886 $output .= $this->header();
1887 }
1888
1889 $message = '<p class="errormessage">' . $message . '</p>'.
1890 '<p class="errorcode"><a href="' . $moreinfourl . '">' .
1891 get_string('moreinformation') . '</a></p>';
1892 $output .= $this->box($message, 'errorbox');
1893
6f8f4d83
PS
1894 if (debugging('', DEBUG_DEVELOPER)) {
1895 if (!empty($debuginfo)) {
1896 $output .= $this->notification('<strong>Debug info:</strong> '.s($debuginfo), 'notifytiny');
1897 }
1898 if (!empty($backtrace)) {
1899 $output .= $this->notification('<strong>Stack trace:</strong> '.format_backtrace($backtrace), 'notifytiny');
1900 }
1901 if ($obbuffer !== '' ) {
1902 $output .= $this->notification('<strong>Output buffer:</strong> '.s($obbuffer), 'notifytiny');
1903 }
d9c8f425 1904 }
1905
1906 if (!empty($link)) {
1907 $output .= $this->continue_button($link);
1908 }
1909
1910 $output .= $this->footer();
1911
1912 // Padding to encourage IE to display our error page, rather than its own.
1913 $output .= str_repeat(' ', 512);
1914
1915 return $output;
1916 }
1917
1918 /**
1919 * Output a notification (that is, a status message about something that has
1920 * just happened).
1921 *
1922 * @param string $message the message to print out
1923 * @param string $classes normally 'notifyproblem' or 'notifysuccess'.
1924 * @return string the HTML to output.
1925 */
1926 public function notification($message, $classes = 'notifyproblem') {
5d0c95a5 1927 return html_writer::tag('div', array('class' =>
78946b9b 1928 renderer_base::prepare_classes($classes)), clean_text($message));
d9c8f425 1929 }
1930
1931 /**
1932 * Print a continue button that goes to a particular URL.
1933 *
1934 * @param string|moodle_url $link The url the button goes to.
1935 * @return string the HTML to output.
1936 */
1937 public function continue_button($link) {
1938 if (!is_a($link, 'moodle_url')) {
1939 $link = new moodle_url($link);
1940 }
1941 $form = new html_form();
1942 $form->url = $link;
1943 $form->values = $link->params();
1944 $form->button->text = get_string('continue');
1945 $form->method = 'get';
1946
5d0c95a5 1947 return html_writer::tag('div', array('class' => 'continuebutton') , $this->button($form));
d9c8f425 1948 }
1949
1950 /**
1951 * Prints a single paging bar to provide access to other pages (usually in a search)
1952 *
1953 * @param string|moodle_url $link The url the button goes to.
1954 * @return string the HTML to output.
1955 */
1956 public function paging_bar($pagingbar) {
1957 $output = '';
1958 $pagingbar = clone($pagingbar);
34059565 1959 $pagingbar->prepare($this, $this->page, $this->target);
d9c8f425 1960
1961 if ($pagingbar->totalcount > $pagingbar->perpage) {
1962 $output .= get_string('page') . ':';
1963
1964 if (!empty($pagingbar->previouslink)) {
46aa52bf 1965 $output .= '&#160;(' . $this->link($pagingbar->previouslink) . ')&#160;';
d9c8f425 1966 }
1967
1968 if (!empty($pagingbar->firstlink)) {
46aa52bf 1969 $output .= '&#160;' . $this->link($pagingbar->firstlink) . '&#160;...';
d9c8f425 1970 }
1971
1972 foreach ($pagingbar->pagelinks as $link) {
1973 if ($link instanceof html_link) {
46aa52bf 1974 $output .= '&#160;&#160;' . $this->link($link);
d9c8f425 1975 } else {
46aa52bf 1976 $output .= "&#160;&#160;$link";
d9c8f425 1977 }
1978 }
1979
1980 if (!empty($pagingbar->lastlink)) {
46aa52bf 1981 $output .= '&#160;...' . $this->link($pagingbar->lastlink) . '&#160;';
d9c8f425 1982 }
1983
1984 if (!empty($pagingbar->nextlink)) {
46aa52bf 1985 $output .= '&#160;&#160;(' . $this->link($pagingbar->nextlink) . ')';
d9c8f425 1986 }
1987 }
1988
5d0c95a5 1989 return html_writer::tag('div', array('class' => 'paging'), $output);
d9c8f425 1990 }
1991
1992 /**
1993 * Render a HTML table
1994 *
1995 * @param object $table {@link html_table} instance containing all the information needed
1996 * @return string the HTML to output.
1997 */
1998 public function table(html_table $table) {
1999 $table = clone($table);
34059565 2000 $table->prepare($this, $this->page, $this->target);
d9c8f425 2001 $attributes = array(
2002 'id' => $table->id,
2003 'width' => $table->width,
2004 'summary' => $table->summary,
2005 'cellpadding' => $table->cellpadding,
2006 'cellspacing' => $table->cellspacing,
2007 'class' => $table->get_classes_string());
5d0c95a5 2008 $output = html_writer::start_tag('table', $attributes) . "\n";
d9c8f425 2009
2010 $countcols = 0;
2011
2012 if (!empty($table->head)) {
2013 $countcols = count($table->head);
5d0c95a5
PS
2014 $output .= html_writer::start_tag('thead', $table->headclasses) . "\n";
2015 $output .= html_writer::start_tag('tr', array()) . "\n";
d9c8f425 2016 $keys = array_keys($table->head);
2017 $lastkey = end($keys);
54a007e8 2018
d9c8f425 2019 foreach ($table->head as $key => $heading) {
54a007e8 2020 // Convert plain string headings into html_table_cell objects
2021 if (!($heading instanceof html_table_cell)) {
2022 $headingtext = $heading;
2023 $heading = new html_table_cell();
2024 $heading->text = $headingtext;
2025 $heading->header = true;
2026 }
f2a51402 2027
a4998d01 2028 if ($heading->header !== false) {
2029 $heading->header = true;
2030 }
54a007e8 2031
2032 $this->prepare_event_handlers($heading);
2033
2034 $heading->add_classes(array('header', 'c' . $key));
d9c8f425 2035 if (isset($table->headspan[$key]) && $table->headspan[$key] > 1) {
54a007e8 2036 $heading->colspan = $table->headspan[$key];
d9c8f425 2037 $countcols += $table->headspan[$key] - 1;
21237187 2038 }
54a007e8 2039
d9c8f425 2040 if ($key == $lastkey) {
54a007e8 2041 $heading->add_class('lastcol');
d9c8f425 2042 }
2043 if (isset($table->colclasses[$key])) {
54a007e8 2044 $heading->add_class($table->colclasses[$key]);
d9c8f425 2045 }
2046 if ($table->rotateheaders) {
2047 // we need to wrap the heading content
5d0c95a5 2048 $heading->text = html_writer::tag('span', null, $heading->text);
d9c8f425 2049 }
54a007e8 2050
d9c8f425 2051 $attributes = array(
54a007e8 2052 'style' => $table->align[$key] . $table->size[$key] . $heading->style,
2053 'class' => $heading->get_classes_string(),
2054 'scope' => $heading->scope,
2055 'colspan' => $heading->colspan);
21237187 2056
a4998d01 2057 $tagtype = 'td';
2058 if ($heading->header === true) {
2059 $tagtype = 'th';
2060 }
5d0c95a5 2061 $output .= html_writer::tag($tagtype, $attributes, $heading->text) . "\n";
d9c8f425 2062 }
5d0c95a5
PS
2063 $output .= html_writer::end_tag('tr') . "\n";
2064 $output .= html_writer::end_tag('thead') . "\n";
d9c8f425 2065 }
2066
2067 if (!empty($table->data)) {
2068 $oddeven = 1;
2069 $keys = array_keys($table->data);
2070 $lastrowkey = end($keys);
5d0c95a5 2071 $output .= html_writer::start_tag('tbody', array('class' => renderer_base::prepare_classes($table->bodyclasses))) . "\n";
d9c8f425 2072
2073 foreach ($table->data as $key => $row) {
2074 if (($row === 'hr') && ($countcols)) {
5d0c95a5
PS
2075 $output .= html_writer::tag('td', array('colspan' => $countcols),
2076 html_writer::tag('div', array('class' => 'tabledivider'), '')) . "\n";
d9c8f425 2077 } else {
2078 // Convert array rows to html_table_rows and cell strings to html_table_cell objects
2079 if (!($row instanceof html_table_row)) {
2080 $newrow = new html_table_row();
2081
2082 foreach ($row as $unused => $item) {
2083 $cell = new html_table_cell();
2084 $cell->text = $item;
54a007e8 2085 $this->prepare_event_handlers($cell);
d9c8f425 2086 $newrow->cells[] = $cell;
2087 }
2088 $row = $newrow;
2089 }
21237187 2090
54a007e8 2091 $this->prepare_event_handlers($row);
d9c8f425 2092
2093 $oddeven = $oddeven ? 0 : 1;
2094 if (isset($table->rowclasses[$key])) {
6dd7d7f0 2095 $row->add_classes(array_unique(html_component::clean_classes($table->rowclasses[$key])));
d9c8f425 2096 }
2097
2098 $row->add_class('r' . $oddeven);
2099 if ($key == $lastrowkey) {
2100 $row->add_class('lastrow');
2101 }
2102
5d0c95a5 2103 $output .= html_writer::start_tag('tr', array('class' => $row->get_classes_string(), 'style' => $row->style, 'id' => $row->id)) . "\n";
d9c8f425 2104 $keys2 = array_keys($row->cells);
2105 $lastkey = end($keys2);
2106
2107 foreach ($row->cells as $key => $cell) {
54a007e8 2108 if (!($cell instanceof html_table_cell)) {
2109 $mycell = new html_table_cell();
2110 $mycell->text = $cell;
2111 $this->prepare_event_handlers($mycell);
2112 $cell = $mycell;
2113 }
2114
d9c8f425 2115 if (isset($table->colclasses[$key])) {
6dd7d7f0 2116 $cell->add_classes(array_unique(html_component::clean_classes($table->colclasses[$key])));
d9c8f425 2117 }
2118
2119 $cell->add_classes('cell');
2120 $cell->add_classes('c' . $key);
2121 if ($key == $lastkey) {
2122 $cell->add_classes('lastcol');
2123 }
2124 $tdstyle = '';
2125 $tdstyle .= isset($table->align[$key]) ? $table->align[$key] : '';
2126 $tdstyle .= isset($table->size[$key]) ? $table->size[$key] : '';
2127 $tdstyle .= isset($table->wrap[$key]) ? $table->wrap[$key] : '';
2128 $tdattributes = array(
2129 'style' => $tdstyle . $cell->style,
2130 'colspan' => $cell->colspan,
2131 'rowspan' => $cell->rowspan,
2132 'id' => $cell->id,
2133 'class' => $cell->get_classes_string(),
2134 'abbr' => $cell->abbr,
e09e9d55 2135 'scope' => $cell->scope,
a2431800 2136 'title' => $cell->title);
1ae3767a 2137 $tagtype = 'td';
a4998d01 2138 if ($cell->header === true) {
1ae3767a 2139 $tagtype = 'th';
2140 }
5d0c95a5 2141 $output .= html_writer::tag($tagtype, $tdattributes, $cell->text) . "\n";
d9c8f425 2142 }
2143 }
5d0c95a5 2144 $output .= html_writer::end_tag('tr') . "\n";
d9c8f425 2145 }
5d0c95a5 2146 $output .= html_writer::end_tag('tbody') . "\n";
d9c8f425 2147 }
5d0c95a5 2148 $output .= html_writer::end_tag('table') . "\n";
d9c8f425 2149
2150 if ($table->rotateheaders && can_use_rotated_text()) {
f44b10ed 2151 $this->page->requires->yui2_lib('event');
d9c8f425 2152 $this->page->requires->js('course/report/progress/textrotate.js');
2153 }
2154
2155 return $output;
2156 }
2157
2158 /**
2159 * Output the place a skip link goes to.
2160 * @param string $id The target name from the corresponding $PAGE->requires->skip_link_to($target) call.
2161 * @return string the HTML to output.
2162 */
2163 public function skip_link_target($id = '') {
5d0c95a5 2164 return html_writer::tag('span', array('id' => $id), '');
d9c8f425 2165 }
2166
2167 /**
2168 * Outputs a heading
2169 * @param string $text The text of the heading
2170 * @param int $level The level of importance of the heading. Defaulting to 2
2171 * @param string $classes A space-separated list of CSS classes
2172 * @param string $id An optional ID
2173 * @return string the HTML to output.
2174 */
2175 public function heading($text, $level = 2, $classes = 'main', $id = '') {
2176 $level = (integer) $level;
2177 if ($level < 1 or $level > 6) {
2178 throw new coding_exception('Heading level must be an integer between 1 and 6.');
2179 }
5d0c95a5 2180 return html_writer::tag('h' . $level,
78946b9b 2181 array('id' => $id, 'class' => renderer_base::prepare_classes($classes)), $text);
d9c8f425 2182 }
2183
2184 /**
2185 * Outputs a box.
2186 * @param string $contents The contents of the box
2187 * @param string $classes A space-separated list of CSS classes
2188 * @param string $id An optional ID
2189 * @return string the HTML to output.
2190 */
2191 public function box($contents, $classes = 'generalbox', $id = '') {
2192 return $this->box_start($classes, $id) . $contents . $this->box_end();
2193 }
2194
2195 /**
2196 * Outputs the opening section of a box.
2197 * @param string $classes A space-separated list of CSS classes
2198 * @param string $id An optional ID
2199 * @return string the HTML to output.
2200 */
2201 public function box_start($classes = 'generalbox', $id = '') {
5d0c95a5
PS
2202 $this->opencontainers->push('box', html_writer::end_tag('div'));
2203 return html_writer::start_tag('div', array('id' => $id,
78946b9b 2204 'class' => 'box ' . renderer_base::prepare_classes($classes)));
d9c8f425 2205 }
2206
2207 /**
2208 * Outputs the closing section of a box.
2209 * @return string the HTML to output.
2210 */
2211 public function box_end() {
2212 return $this->opencontainers->pop('box');
2213 }
2214
2215 /**
2216 * Outputs a container.
2217 * @param string $contents The contents of the box
2218 * @param string $classes A space-separated list of CSS classes
2219 * @param string $id An optional ID
2220 * @return string the HTML to output.
2221 */
2222 public function container($contents, $classes = '', $id = '') {
2223 return $this->container_start($classes, $id) . $contents . $this->container_end();
2224 }
2225
2226 /**
2227 * Outputs the opening section of a container.
2228 * @param string $classes A space-separated list of CSS classes
2229 * @param string $id An optional ID
2230 * @return string the HTML to output.
2231 */
2232 public function container_start($classes = '', $id = '') {
5d0c95a5
PS
2233 $this->opencontainers->push('container', html_writer::end_tag('div'));
2234 return html_writer::start_tag('div', array('id' => $id,
78946b9b 2235 'class' => renderer_base::prepare_classes($classes)));
d9c8f425 2236 }
2237
2238 /**
2239 * Outputs the closing section of a container.
2240 * @return string the HTML to output.
2241 */
2242 public function container_end() {
2243 return $this->opencontainers->pop('container');
2244 }
7d2a0492 2245
2246 /**
2247 * Make nested HTML lists out of the items
2248 *
2249 * The resulting list will look something like this:
2250 *
2251 * <pre>
2252 * <<ul>>
2253 * <<li>><div class='tree_item parent'>(item contents)</div>
2254 * <<ul>
2255 * <<li>><div class='tree_item'>(item contents)</div><</li>>
2256 * <</ul>>
2257 * <</li>>
2258 * <</ul>>
2259 * </pre>
2260 *
2261 * @param array[]tree_item $items
2262 * @param array[string]string $attrs html attributes passed to the top of
2263 * the list
2264 * @return string HTML
2265 */
2266 function tree_block_contents($items, $attrs=array()) {
2267 // exit if empty, we don't want an empty ul element
2268 if (empty($items)) {
2269 return '';
2270 }
2271 // array of nested li elements
2272 $lis = array();
2273 foreach ($items as $item) {
2274 // this applies to the li item which contains all child lists too
2275 $content = $item->content($this);
2276 $liclasses = array($item->get_css_type());
2277 if (!$item->forceopen || (!$item->forceopen && $item->collapse) || (count($item->children)==0 && $item->nodetype==navigation_node::NODETYPE_BRANCH)) {
2278 $liclasses[] = 'collapsed';
2279 }
2280 if ($item->isactive === true) {
2281 $liclasses[] = 'current_branch';
2282 }
2283 $liattr = array('class'=>join(' ',$liclasses));
2284 // class attribute on the div item which only contains the item content
2285 $divclasses = array('tree_item');
2286 if (!empty($item->children) || $item->nodetype==navigation_node::NODETYPE_BRANCH) {
2287 $divclasses[] = 'branch';
2288 } else {
2289 $divclasses[] = 'leaf';
2290 }
2291 if (!empty($item->classes) && count($item->classes)>0) {
2292 $divclasses[] = join(' ', $item->classes);
2293 }
2294 $divattr = array('class'=>join(' ', $divclasses));
2295 if (!empty($item->id)) {
2296 $divattr['id'] = $item->id;
2297 }
5d0c95a5 2298 $content = html_writer::tag('p', $divattr, $content) . $this->tree_block_contents($item->children);
7d2a0492 2299 if (!empty($item->preceedwithhr) && $item->preceedwithhr===true) {
5d0c95a5 2300 $content = html_writer::tag('hr', array(), null).$content;
7d2a0492 2301 }
5d0c95a5 2302 $content = html_writer::tag('li', $liattr, $content);
7d2a0492 2303 $lis[] = $content;
2304 }
5d0c95a5 2305 return html_writer::tag('ul', $attrs, implode("\n", $lis));
7d2a0492 2306 }
2307
2308 /**
2309 * Return the navbar content so that it can be echoed out by the layout
2310 * @return string XHTML navbar
2311 */
2312 public function navbar() {
2313 return $this->page->navbar->content();
2314 }
92e01ab7
PS
2315
2316 /**
2317 * Accessibility: Right arrow-like character is
2318 * used in the breadcrumb trail, course navigation menu
2319 * (previous/next activity), calendar, and search forum block.
2320 * If the theme does not set characters, appropriate defaults
2321 * are set automatically. Please DO NOT
2322 * use &lt; &gt; &raquo; - these are confusing for blind users.
2323 * @return string
2324 */
2325 public function rarrow() {
2326 return $this->page->theme->rarrow;
2327 }
2328
2329 /**
2330 * Accessibility: Right arrow-like character is
2331 * used in the breadcrumb trail, course navigation menu
2332 * (previous/next activity), calendar, and search forum block.
2333 * If the theme does not set characters, appropriate defaults
2334 * are set automatically. Please DO NOT
2335 * use &lt; &gt; &raquo; - these are confusing for blind users.
2336 * @return string
2337 */
2338 public function larrow() {
2339 return $this->page->theme->larrow;
2340 }
088ccb43
PS
2341
2342 /**
2343 * Returns the colours of the small MP3 player
2344 * @return string
2345 */
2346 public function filter_mediaplugin_colors() {
2347 return $this->page->theme->filter_mediaplugin_colors;
2348 }
2349
2350 /**
2351 * Returns the colours of the big MP3 player
2352 * @return string
2353 */
2354 public function resource_mp3player_colors() {
2355 return $this->page->theme->resource_mp3player_colors;
2356 }
78946b9b 2357}
d9c8f425 2358
2359
2360/// RENDERERS
2361
2362/**
2363 * A renderer that generates output for command-line scripts.
2364 *
2365 * The implementation of this renderer is probably incomplete.
2366 *
2367 * @copyright 2009 Tim Hunt
2368 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2369 * @since Moodle 2.0
2370 */
56cbc53b 2371class core_renderer_cli extends core_renderer {
d9c8f425 2372 /**
2373 * Returns the page header.
2374 * @return string HTML fragment
2375 */
2376 public function header() {
2377 output_starting_hook();
2378 return $this->page->heading . "\n";
2379 }
2380
2381 /**
2382 * Returns a template fragment representing a Heading.
2383 * @param string $text The text of the heading
2384 * @param int $level The level of importance of the heading
2385 * @param string $classes A space-separated list of CSS classes
2386 * @param string $id An optional ID
2387 * @return string A template fragment for a heading
2388 */
2389 public function heading($text, $level, $classes = 'main', $id = '') {
2390 $text .= "\n";
2391 switch ($level) {
2392 case 1:
2393 return '=>' . $text;
2394 case 2:
2395 return '-->' . $text;
2396 default:
2397 return $text;
2398 }
2399 }
2400
2401 /**
2402 * Returns a template fragment representing a fatal error.
2403 * @param string $message The message to output
2404 * @param string $moreinfourl URL where more info can be found about the error
2405 * @param string $link Link for the Continue button
2406 * @param array $backtrace The execution backtrace
2407 * @param string $debuginfo Debugging information
d9c8f425 2408 * @return string A template fragment for a fatal error
2409 */
83267ec0 2410 public function fatal_error($message, $moreinfourl, $link, $backtrace, $debuginfo = null) {
d9c8f425 2411 $output = "!!! $message !!!\n";
2412
2413 if (debugging('', DEBUG_DEVELOPER)) {
2414 if (!empty($debuginfo)) {
2415 $this->notification($debuginfo, 'notifytiny');
2416 }
2417 if (!empty($backtrace)) {
2418 $this->notification('Stack trace: ' . format_backtrace($backtrace, true), 'notifytiny');
2419 }
2420 }
2421 }
2422
2423 /**
2424 * Returns a template fragment representing a notification.
2425 * @param string $message The message to include
2426 * @param string $classes A space-separated list of CSS classes
2427 * @return string A template fragment for a notification
2428 */
2429 public function notification($message, $classes = 'notifyproblem') {
2430 $message = clean_text($message);
2431 if ($classes === 'notifysuccess') {
2432 return "++ $message ++\n";
2433 }
2434 return "!! $message !!\n";
2435 }
2436}
2437