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