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