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