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