a3e9e09081de4b7e50ba42c186a59e93564ced2d
[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 moodle_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;
46     /**
47      * Constructor
48      * @param moodle_page $page the page we are doing output for.
49      */
50     public function __construct($page) {
51         $this->opencontainers = $page->opencontainers;
52         $this->page = $page;
53     }
55     /**
56      * Have we started output yet?
57      * @return boolean true if the header has been printed.
58      */
59     public function has_started() {
60         return $this->page->state >= moodle_page::STATE_IN_BODY;
61     }
63     /**
64      * Outputs a tag with attributes and contents
65      * @param string $tagname The name of tag ('a', 'img', 'span' etc.)
66      * @param array $attributes The tag attributes (array('src' => $url, 'class' => 'class1') etc.)
67      * @param string $contents What goes between the opening and closing tags
68      * @return string HTML fragment
69      */
70     protected function output_tag($tagname, $attributes, $contents) {
71         return $this->output_start_tag($tagname, $attributes) . $contents .
72                 $this->output_end_tag($tagname);
73     }
75     /**
76      * Outputs an opening tag with attributes
77      * @param string $tagname The name of tag ('a', 'img', 'span' etc.)
78      * @param array $attributes The tag attributes (array('src' => $url, 'class' => 'class1') etc.)
79      * @return string HTML fragment
80      */
81     protected function output_start_tag($tagname, $attributes) {
82         return '<' . $tagname . $this->output_attributes($attributes) . '>';
83     }
85     /**
86      * Outputs a closing tag
87      * @param string $tagname The name of tag ('a', 'img', 'span' etc.)
88      * @return string HTML fragment
89      */
90     protected function output_end_tag($tagname) {
91         return '</' . $tagname . '>';
92     }
94     /**
95      * Outputs an empty tag with attributes
96      * @param string $tagname The name of tag ('input', 'img', 'br' etc.)
97      * @param array $attributes The tag attributes (array('src' => $url, 'class' => 'class1') etc.)
98      * @return string HTML fragment
99      */
100     protected function output_empty_tag($tagname, $attributes) {
101         return '<' . $tagname . $this->output_attributes($attributes) . ' />';
102     }
104     /**
105      * Outputs a HTML attribute and value
106      * @param string $name The name of the attribute ('src', 'href', 'class' etc.)
107      * @param string $value The value of the attribute. The value will be escaped with {@link s()}
108      * @return string HTML fragment
109      */
110     protected function output_attribute($name, $value) {
111         if (is_array($value)) {
112             debugging("Passed an array for the HTML attribute $name", DEBUG_DEVELOPER);
113         }
115         $value = trim($value);
116         if ($value == HTML_ATTR_EMPTY) {
117             return ' ' . $name . '=""';
118         } else if ($value || is_numeric($value)) { // We want 0 to be output.
119             return ' ' . $name . '="' . s($value) . '"';
120         }
121     }
123     /**
124      * Outputs a list of HTML attributes and values
125      * @param array $attributes The tag attributes (array('src' => $url, 'class' => 'class1') etc.)
126      *       The values will be escaped with {@link s()}
127      * @return string HTML fragment
128      */
129     protected function output_attributes($attributes) {
130         if (empty($attributes)) {
131             $attributes = array();
132         }
133         $output = '';
134         foreach ($attributes as $name => $value) {
135             $output .= $this->output_attribute($name, $value);
136         }
137         return $output;
138     }
140     /**
141      * Given an array or space-separated list of classes, prepares and returns the HTML class attribute value
142      * @param mixed $classes Space-separated string or array of classes
143      * @return string HTML class attribute value
144      */
145     public static function prepare_classes($classes) {
146         if (is_array($classes)) {
147             return implode(' ', array_unique($classes));
148         }
149         return $classes;
150     }
152     /**
153      * Return the URL for an icon identified as in pre-Moodle 2.0 code.
154      *
155      * Suppose you have old code like $url = "$CFG->pixpath/i/course.gif";
156      * then old_icon_url('i/course'); will return the equivalent URL that is correct now.
157      *
158      * @param string $iconname the name of the icon.
159      * @return string the URL for that icon.
160      */
161     public function old_icon_url($iconname) {
162         return $this->page->theme->old_icon_url($iconname);
163     }
165     /**
166      * Return the URL for an icon identified as in pre-Moodle 2.0 code.
167      *
168      * Suppose you have old code like $url = "$CFG->modpixpath/$mod/icon.gif";
169      * then mod_icon_url('icon', $mod); will return the equivalent URL that is correct now.
170      *
171      * @param string $iconname the name of the icon.
172      * @param string $module the module the icon belongs to.
173      * @return string the URL for that icon.
174      */
175     public function mod_icon_url($iconname, $module) {
176         return $this->page->theme->mod_icon_url($iconname, $module);
177     }
179     /**
180      * A helper function that takes a moodle_html_component subclass as param.
181      * If that component has an id attribute and an array of valid component_action objects,
182      * it sets up the appropriate event handlers.
183      *
184      * @param moodle_html_component $component
185      * @return void;
186      */
187     protected function prepare_event_handlers(&$component) {
188         $actions = $component->get_actions();
189         if (!empty($actions) && is_array($actions) && $actions[0] instanceof component_action) {
190             foreach ($actions as $action) {
191                 if (!empty($action->jsfunction)) {
192                     $this->page->requires->event_handler($component->id, $action->event, $action->jsfunction, $action->jsfunctionargs);
193                 }
194             }
195         }
196     }
198     /**
199      * Given a moodle_html_component with height and/or width set, translates them
200      * to appropriate CSS rules.
201      *
202      * @param moodle_html_component $component
203      * @return string CSS rules
204      */
205     protected function prepare_legacy_width_and_height($component) {
206         $output = '';
207         if (!empty($component->height)) {
208             // We need a more intelligent way to handle these warnings. If $component->height have come from
209             // somewhere in deprecatedlib.php, then there is no point outputting a warning here.
210             // debugging('Explicit height given to moodle_html_component leads to inline css. Use a proper CSS class instead.', DEBUG_DEVELOPER);
211             $output .= "height: {$component->height}px;";
212         }
213         if (!empty($component->width)) {
214             // debugging('Explicit width given to moodle_html_component leads to inline css. Use a proper CSS class instead.', DEBUG_DEVELOPER);
215             $output .= "width: {$component->width}px;";
216         }
217         return $output;
218     }
222 /**
223  * This is the templated renderer which copies the API of another class, replacing
224  * all methods calls with instantiation of a template.
225  *
226  * When the method method_name is called, this class will search for a template
227  * called method_name.php in the folders in $searchpaths, taking the first one
228  * that it finds. Then it will set up variables for each of the arguments of that
229  * method, and render the template. This is implemented in the {@link __call()}
230  * PHP magic method.
231  *
232  * Methods like print_box_start and print_box_end are handles specially, and
233  * implemented in terms of the print_box.php method.
234  *
235  * @copyright 2009 Tim Hunt
236  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
237  * @since     Moodle 2.0
238  */
239 class template_renderer extends moodle_renderer_base {
240     /** @var ReflectionClass information about the class whose API we are copying. */
241     protected $copiedclass;
242     /** @var array of places to search for templates. */
243     protected $searchpaths;
244     protected $rendererfactory;
246     /**
247      * Magic word used when breaking apart container templates to implement
248      * _start and _end methods.
249      */
250     const CONTENTSTOKEN = '-@#-Contents-go-here-#@-';
252     /**
253      * Constructor
254      * @param string $copiedclass the name of a class whose API we should be copying.
255      * @param array $searchpaths a list of folders to search for templates in.
256      * @param moodle_page $page the page we are doing output for.
257      */
258     public function __construct($copiedclass, $searchpaths, $page) {
259         parent::__construct($page);
260         $this->copiedclass = new ReflectionClass($copiedclass);
261         $this->searchpaths = $searchpaths;
262     }
264     /**
265      * PHP magic method implementation. Do not use this method directly.
266      * @param string $method The method to call
267      * @param array $arguments The arguments to pass to the method
268      * @return mixed The return value of the called method
269      */
270     public function __call($method, $arguments) {
271         if (substr($method, -6) == '_start') {
272             return $this->process_start(substr($method, 0, -6), $arguments);
273         } else if (substr($method, -4) == '_end') {
274             return $this->process_end(substr($method, 0, -4), $arguments);
275         } else {
276             return $this->process_template($method, $arguments);
277         }
278     }
280     /**
281      * Render the template for a given method of the renderer class we are copying,
282      * using the arguments passed.
283      * @param string $method the method that was called.
284      * @param array $arguments the arguments that were passed to it.
285      * @return string the HTML to be output.
286      */
287     protected function process_template($method, $arguments) {
288         if (!$this->copiedclass->hasMethod($method) ||
289                 !$this->copiedclass->getMethod($method)->isPublic()) {
290             throw new coding_exception('Unknown method ' . $method);
291         }
293         // Find the template file for this method.
294         $template = $this->find_template($method);
296         // Use the reflection API to find out what variable names the arguments
297         // should be stored in, and fill in any missing ones with the defaults.
298         $namedarguments = array();
299         $expectedparams = $this->copiedclass->getMethod($method)->getParameters();
300         foreach ($expectedparams as $param) {
301             $paramname = $param->getName();
302             if (!empty($arguments)) {
303                 $namedarguments[$paramname] = array_shift($arguments);
304             } else if ($param->isDefaultValueAvailable()) {
305                 $namedarguments[$paramname] = $param->getDefaultValue();
306             } else {
307                 throw new coding_exception('Missing required argument ' . $paramname);
308             }
309         }
311         // Actually render the template.
312         return $this->render_template($template, $namedarguments);
313     }
315     /**
316      * Actually do the work of rendering the template.
317      * @param string $_template the full path to the template file.
318      * @param array $_namedarguments an array variable name => value, the variables
319      *      that should be available to the template.
320      * @return string the HTML to be output.
321      */
322     protected function render_template($_template, $_namedarguments) {
323         // Note, we intentionally break the coding guidelines with regards to
324         // local variable names used in this function, so that they do not clash
325         // with the names of any variables being passed to the template.
327         global $CFG, $SITE, $THEME, $USER;
328         // The next lines are a bit tricky. The point is, here we are in a method
329         // of a renderer class, and this object may, or may not, be the same as
330         // the global $OUTPUT object. When rendering the template, we want to use
331         // this object. However, people writing Moodle code expect the current
332         // renderer to be called $OUTPUT, not $this, so define a variable called
333         // $OUTPUT pointing at $this. The same comment applies to $PAGE and $COURSE.
334         $OUTPUT = $this;
335         $PAGE = $this->page;
336         $COURSE = $this->page->course;
338         // And the parameters from the function call.
339         extract($_namedarguments);
341         // Include the template, capturing the output.
342         ob_start();
343         include($_template);
344         $_result = ob_get_contents();
345         ob_end_clean();
347         return $_result;
348     }
350     /**
351      * Searches the folders in {@link $searchpaths} to try to find a template for
352      * this method name. Throws an exception if one cannot be found.
353      * @param string $method the method name.
354      * @return string the full path of the template to use.
355      */
356     protected function find_template($method) {
357         foreach ($this->searchpaths as $path) {
358             $filename = $path . '/' . $method . '.php';
359             if (file_exists($filename)) {
360                 return $filename;
361             }
362         }
363         throw new coding_exception('Cannot find template for ' . $this->copiedclass->getName() . '::' . $method);
364     }
366     /**
367      * Handle methods like print_box_start by using the print_box template,
368      * splitting the result, pushing the end onto the stack, then returning the start.
369      * @param string $method the method that was called, with _start stripped off.
370      * @param array $arguments the arguments that were passed to it.
371      * @return string the HTML to be output.
372      */
373     protected function process_start($method, $arguments) {
374         array_unshift($arguments, self::CONTENTSTOKEN);
375         $html = $this->process_template($method, $arguments);
376         list($start, $end) = explode(self::CONTENTSTOKEN, $html, 2);
377         $this->opencontainers->push($method, $end);
378         return $start;
379     }
381     /**
382      * Handle methods like print_box_end, we just need to pop the end HTML from
383      * the stack.
384      * @param string $method the method that was called, with _end stripped off.
385      * @param array $arguments not used. Assumed to be irrelevant.
386      * @return string the HTML to be output.
387      */
388     protected function process_end($method, $arguments) {
389         return $this->opencontainers->pop($method);
390     }
392     /**
393      * @return array the list of paths where this class searches for templates.
394      */
395     public function get_search_paths() {
396         return $this->searchpaths;
397     }
399     /**
400      * @return string the name of the class whose API we are copying.
401      */
402     public function get_copied_class() {
403         return $this->copiedclass->getName();
404     }
407 /**
408  * The standard implementation of the moodle_core_renderer interface.
409  *
410  * @copyright 2009 Tim Hunt
411  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
412  * @since     Moodle 2.0
413  */
414 class moodle_core_renderer extends moodle_renderer_base {
415     /** @var string used in {@link header()}. */
416     const PERFORMANCE_INFO_TOKEN = '%%PERFORMANCEINFO%%';
417     /** @var string used in {@link header()}. */
418     const END_HTML_TOKEN = '%%ENDHTML%%';
419     /** @var string used in {@link header()}. */
420     const MAIN_CONTENT_TOKEN = '[MAIN CONTENT GOES HERE]';
421     /** @var string used to pass information from {@link doctype()} to {@link standard_head_html()}. */
422     protected $contenttype;
423     /** @var string used by {@link redirect_message()} method to communicate with {@link header()}. */
424     protected $metarefreshtag = '';
426     /**
427      * Get the DOCTYPE declaration that should be used with this page. Designed to
428      * be called in theme layout.php files.
429      * @return string the DOCTYPE declaration (and any XML prologue) that should be used.
430      */
431     public function doctype() {
432         global $CFG;
434         $doctype = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">' . "\n";
435         $this->contenttype = 'text/html; charset=utf-8';
437         if (empty($CFG->xmlstrictheaders)) {
438             return $doctype;
439         }
441         // We want to serve the page with an XML content type, to force well-formedness errors to be reported.
442         $prolog = '<?xml version="1.0" encoding="utf-8"?>' . "\n";
443         if (isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'application/xhtml+xml') !== false) {
444             // Firefox and other browsers that can cope natively with XHTML.
445             $this->contenttype = 'application/xhtml+xml; charset=utf-8';
447         } else if (preg_match('/MSIE.*Windows NT/', $_SERVER['HTTP_USER_AGENT'])) {
448             // IE can't cope with application/xhtml+xml, but it will cope if we send application/xml with an XSL stylesheet.
449             $this->contenttype = 'application/xml; charset=utf-8';
450             $prolog .= '<?xml-stylesheet type="text/xsl" href="' . $CFG->httpswwwroot . '/lib/xhtml.xsl"?>' . "\n";
452         } else {
453             $prolog = '';
454         }
456         return $prolog . $doctype;
457     }
459     /**
460      * The attributes that should be added to the <html> tag. Designed to
461      * be called in theme layout.php files.
462      * @return string HTML fragment.
463      */
464     public function htmlattributes() {
465         return get_html_lang(true) . ' xmlns="http://www.w3.org/1999/xhtml"';
466     }
468     /**
469      * The standard tags (meta tags, links to stylesheets and JavaScript, etc.)
470      * that should be included in the <head> tag. Designed to be called in theme
471      * layout.php files.
472      * @return string HTML fragment.
473      */
474     public function standard_head_html() {
475         global $CFG;
476         $output = '';
477         $output .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />' . "\n";
478         $output .= '<meta name="keywords" content="moodle, ' . $this->page->title . '" />' . "\n";
479         if (!$this->page->cacheable) {
480             $output .= '<meta http-equiv="pragma" content="no-cache" />' . "\n";
481             $output .= '<meta http-equiv="expires" content="0" />' . "\n";
482         }
483         // This is only set by the {@link redirect()} method
484         $output .= $this->metarefreshtag;
486         // Check if a periodic refresh delay has been set and make sure we arn't
487         // already meta refreshing
488         if ($this->metarefreshtag=='' && $this->page->periodicrefreshdelay!==null) {
489             $output .= '<meta http-equiv="refresh" content="'.$this->page->periodicrefreshdelay.';url='.$this->page->url->out().'" />';
490         }
492         $this->page->requires->js('lib/javascript-static.js')->in_head();
493         $this->page->requires->js('lib/javascript-deprecated.js')->in_head();
494         $this->page->requires->js('lib/javascript-mod.php')->in_head();
495         $this->page->requires->js('lib/overlib/overlib.js')->in_head();
496         $this->page->requires->js('lib/overlib/overlib_cssstyle.js')->in_head();
497         $this->page->requires->js('lib/cookies.js')->in_head();
498         $this->page->requires->js_function_call('setTimeout', array('fix_column_widths()', 20));
500         $focus = $this->page->focuscontrol;
501         if (!empty($focus)) {
502             if (preg_match("#forms\['([a-zA-Z0-9]+)'\].elements\['([a-zA-Z0-9]+)'\]#", $focus, $matches)) {
503                 // This is a horrifically bad way to handle focus but it is passed in
504                 // through messy formslib::moodleform
505                 $this->page->requires->js_function_call('old_onload_focus', array($matches[1], $matches[2]));
506             } else if (strpos($focus, '.')!==false) {
507                 // Old style of focus, bad way to do it
508                 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);
509                 $this->page->requires->js_function_call('old_onload_focus', explode('.', $focus, 2));
510             } else {
511                 // Focus element with given id
512                 $this->page->requires->js_function_call('focuscontrol', array($focus));
513             }
514         }
516         // Add the meta tags from the themes if any were requested.
517         $output .= $this->page->theme->get_meta_tags($this->page);
519         // Get any HTML from the page_requirements_manager.
520         $output .= $this->page->requires->get_head_code();
522         // List alternate versions.
523         foreach ($this->page->alternateversions as $type => $alt) {
524             $output .= $this->output_empty_tag('link', array('rel' => 'alternate',
525                     'type' => $type, 'title' => $alt->title, 'href' => $alt->url));
526         }
528         return $output;
529     }
531     /**
532      * The standard tags (typically skip links) that should be output just inside
533      * the start of the <body> tag. Designed to be called in theme layout.php files.
534      * @return string HTML fragment.
535      */
536     public function standard_top_of_body_html() {
537         return  $this->page->requires->get_top_of_body_code();
538     }
540     /**
541      * The standard tags (typically performance information and validation links,
542      * if we are in developer debug mode) that should be output in the footer area
543      * of the page. Designed to be called in theme layout.php files.
544      * @return string HTML fragment.
545      */
546     public function standard_footer_html() {
547         global $CFG;
549         // This function is normally called from a layout.php file in {@link header()}
550         // but some of the content won't be known until later, so we return a placeholder
551         // for now. This will be replaced with the real content in {@link footer()}.
552         $output = self::PERFORMANCE_INFO_TOKEN;
553         if (!empty($CFG->debugpageinfo)) {
554             $output .= '<div class="performanceinfo">This page is: ' . $this->page->debug_summary() . '</div>';
555         }
556         if (!empty($CFG->debugvalidators)) {
557             $output .= '<div class="validators"><ul>
558               <li><a href="http://validator.w3.org/check?verbose=1&amp;ss=1&amp;uri=' . urlencode(qualified_me()) . '">Validate HTML</a></li>
559               <li><a href="http://www.contentquality.com/mynewtester/cynthia.exe?rptmode=-1&amp;url1=' . urlencode(qualified_me()) . '">Section 508 Check</a></li>
560               <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>
561             </ul></div>';
562         }
563         return $output;
564     }
566     /**
567      * The standard tags (typically script tags that are not needed earlier) that
568      * should be output after everything else, . Designed to be called in theme layout.php files.
569      * @return string HTML fragment.
570      */
571     public function standard_end_of_body_html() {
572         // This function is normally called from a layout.php file in {@link header()}
573         // but some of the content won't be known until later, so we return a placeholder
574         // for now. This will be replaced with the real content in {@link footer()}.
575         echo self::END_HTML_TOKEN;
576     }
578     /**
579      * Return the standard string that says whether you are logged in (and switched
580      * roles/logged in as another user).
581      * @return string HTML fragment.
582      */
583     public function login_info() {
584         global $USER;
585         return user_login_string($this->page->course, $USER);
586     }
588     /**
589      * Return the 'back' link that normally appears in the footer.
590      * @return string HTML fragment.
591      */
592     public function home_link() {
593         global $CFG, $SITE;
595         if ($this->page->pagetype == 'site-index') {
596             // Special case for site home page - please do not remove
597             return '<div class="sitelink">' .
598                    '<a title="Moodle ' . $CFG->release . '" href="http://moodle.org/">' .
599                    '<img style="width:100px;height:30px" src="' . $CFG->httpswwwroot . '/pix/moodlelogo.gif" alt="moodlelogo" /></a></div>';
601         } else if (!empty($CFG->target_release) && $CFG->target_release != $CFG->release) {
602             // Special case for during install/upgrade.
603             return '<div class="sitelink">'.
604                    '<a title="Moodle ' . $CFG->target_release . '" href="http://docs.moodle.org/en/Administrator_documentation" onclick="this.target=\'_blank\'">' .
605                    '<img style="width:100px;height:30px" src="' . $CFG->httpswwwroot . '/pix/moodlelogo.gif" alt="moodlelogo" /></a></div>';
607         } else if ($this->page->course->id == $SITE->id || strpos($this->page->pagetype, 'course-view') === 0) {
608             return '<div class="homelink"><a href="' . $CFG->wwwroot . '/">' .
609                     get_string('home') . '</a></div>';
611         } else {
612             return '<div class="homelink"><a href="' . $CFG->wwwroot . '/course/view.php?id=' . $this->page->course->id . '">' .
613                     format_string($this->page->course->shortname) . '</a></div>';
614         }
615     }
617     /**
618      * Redirects the user by any means possible given the current state
619      *
620      * This function should not be called directly, it should always be called using
621      * the redirect function in lib/weblib.php
622      *
623      * The redirect function should really only be called before page output has started
624      * however it will allow itself to be called during the state STATE_IN_BODY
625      *
626      * @param string $encodedurl The URL to send to encoded if required
627      * @param string $message The message to display to the user if any
628      * @param int $delay The delay before redirecting a user, if $message has been
629      *         set this is a requirement and defaults to 3, set to 0 no delay
630      * @param boolean $debugdisableredirect this redirect has been disabled for
631      *         debugging purposes. Display a message that explains, and don't
632      *         trigger the redirect.
633      * @return string The HTML to display to the user before dying, may contain
634      *         meta refresh, javascript refresh, and may have set header redirects
635      */
636     public function redirect_message($encodedurl, $message, $delay, $debugdisableredirect) {
637         global $CFG;
638         $url = str_replace('&amp;', '&', $encodedurl);
640         switch ($this->page->state) {
641             case moodle_page::STATE_BEFORE_HEADER :
642                 // No output yet it is safe to delivery the full arsenal of redirect methods
643                 if (!$debugdisableredirect) {
644                     // Don't use exactly the same time here, it can cause problems when both redirects fire at the same time.
645                     $this->metarefreshtag = '<meta http-equiv="refresh" content="'. $delay .'; url='. $encodedurl .'" />'."\n";
646                     $this->page->requires->js_function_call('document.location.replace', array($url))->after_delay($delay + 3);
647                 }
648                 $output = $this->header();
649                 break;
650             case moodle_page::STATE_PRINTING_HEADER :
651                 // We should hopefully never get here
652                 throw new coding_exception('You cannot redirect while printing the page header');
653                 break;
654             case moodle_page::STATE_IN_BODY :
655                 // We really shouldn't be here but we can deal with this
656                 debugging("You should really redirect before you start page output");
657                 if (!$debugdisableredirect) {
658                     $this->page->requires->js_function_call('document.location.replace', array($url))->after_delay($delay);
659                 }
660                 $output = $this->opencontainers->pop_all_but_last();
661                 break;
662             case moodle_page::STATE_DONE :
663                 // Too late to be calling redirect now
664                 throw new coding_exception('You cannot redirect after the entire page has been generated');
665                 break;
666         }
667         $output .= $this->notification($message, 'redirectmessage');
668         $output .= '<a href="'. $encodedurl .'">'. get_string('continue') .'</a>';
669         if ($debugdisableredirect) {
670             $output .= '<p><strong>Error output, so disabling automatic redirect.</strong></p>';
671         }
672         $output .= $this->footer();
673         return $output;
674     }
676     /**
677      * Start output by sending the HTTP headers, and printing the HTML <head>
678      * and the start of the <body>.
679      *
680      * To control what is printed, you should set properties on $PAGE. If you
681      * are familiar with the old {@link print_header()} function from Moodle 1.9
682      * you will find that there are properties on $PAGE that correspond to most
683      * of the old parameters to could be passed to print_header.
684      *
685      * Not that, in due course, the remaining $navigation, $menu parameters here
686      * will be replaced by more properties of $PAGE, but that is still to do.
687      *
688      * @return string HTML that you must output this, preferably immediately.
689      */
690     public function header() {
691         // TODO remove $navigation and $menu arguments - replace with $PAGE->navigation
692         global $USER, $CFG;
694         $this->page->set_state(moodle_page::STATE_PRINTING_HEADER);
696         // Find the appropriate page template, based on $this->page->generaltype.
697         $templatefile = $this->page->theme->template_for_page($this->page->generaltype);
698         if ($templatefile) {
699             // Render the template.
700             $template = $this->render_page_template($templatefile);
701         } else {
702             // New style template not found, fall back to using header.html and footer.html.
703             $template = $this->handle_legacy_theme();
704         }
706         // Slice the template output into header and footer.
707         $cutpos = strpos($template, self::MAIN_CONTENT_TOKEN);
708         if ($cutpos === false) {
709             throw new coding_exception('Layout template ' . $templatefile .
710                     ' does not contain the string "' . self::MAIN_CONTENT_TOKEN . '".');
711         }
712         $header = substr($template, 0, $cutpos);
713         $footer = substr($template, $cutpos + strlen(self::MAIN_CONTENT_TOKEN));
715         if (empty($this->contenttype)) {
716             debugging('The layout template did not call $OUTPUT->doctype()');
717             $this->doctype();
718         }
720         send_headers($this->contenttype, $this->page->cacheable);
721         $this->opencontainers->push('header/footer', $footer);
722         $this->page->set_state(moodle_page::STATE_IN_BODY);
723         return $header . $this->skip_link_target();
724     }
726     /**
727      * Renders and outputs the page template.
728      * @param string $templatefile The name of the template's file
729      * @param array $menu The menu that will be used in the included file
730      * @param array $navigation The navigation that will be used in the included file
731      * @return string HTML code
732      */
733     protected function render_page_template($templatefile) {
734         global $CFG, $SITE, $THEME, $USER;
735         // The next lines are a bit tricky. The point is, here we are in a method
736         // of a renderer class, and this object may, or may not, be the same as
737         // the global $OUTPUT object. When rendering the template, we want to use
738         // this object. However, people writing Moodle code expect the current
739         // renderer to be called $OUTPUT, not $this, so define a variable called
740         // $OUTPUT pointing at $this. The same comment applies to $PAGE and $COURSE.
741         $OUTPUT = $this;
742         $PAGE = $this->page;
743         $COURSE = $this->page->course;
745         // Required for legacy template uses
746         $navigation = $this->has_navbar();
748         ob_start();
749         include($templatefile);
750         $template = ob_get_contents();
751         ob_end_clean();
752         return $template;
753     }
755     /**
756      * Renders and outputs a legacy template.
757      * @param array $navigation The navigation that will be used in the included file
758      * @param array $menu The menu that will be used in the included file
759      * @return string HTML code
760      */
761     protected function handle_legacy_theme() {
762         global $CFG, $SITE, $USER;
763         // Set a pretend global from the properties of this class.
764         // See the comment in render_page_template for a fuller explanation.
765         $COURSE = $this->page->course;
766         $THEME = $this->page->theme;
767         $OUTPUT = $this;
769         // Set up local variables that header.html expects.
770         $direction = $this->htmlattributes();
771         $title = $this->page->title;
772         $heading = $this->page->heading;
773         $focus = $this->page->focuscontrol;
774         $button = $this->page->button;
775         $pageid = $this->page->pagetype;
776         $pageclass = $this->page->bodyclasses;
777         $bodytags = ' class="' . $pageclass . '" id="' . $pageid . '"';
778         $home = $this->page->generaltype == 'home';
779         $menu = $this->page->headingmenu;
781         $meta = $this->standard_head_html();
782         // The next line is a nasty hack. having set $meta to standard_head_html, we have already
783         // got the contents of include($CFG->javascript). However, legacy themes are going to
784         // include($CFG->javascript) again. We want to make sure that when they do, nothing is output.
785         $CFG->javascript = $CFG->libdir . '/emptyfile.php';
787         // Set up local variables that footer.html expects.
788         $homelink = $this->home_link();
789         $loggedinas = $this->login_info();
790         $course = $this->page->course;
791         $performanceinfo = self::PERFORMANCE_INFO_TOKEN;
793         $navigation = $this->has_navbar();
794         if (!$menu && $navigation) {
795             $menu = $loggedinas;
796         }
798         if (!empty($this->page->theme->layouttable)) {
799             $lt = $this->page->theme->layouttable;
800         } else {
801             $lt = array('left', 'middle', 'right');
802         }
804         if (!empty($this->page->theme->block_l_max_width)) {
805             $preferredwidthleft = $this->page->theme->block_l_max_width;
806         } else {
807             $preferredwidthleft = 210;
808         }
809         if (!empty($this->page->theme->block_r_max_width)) {
810             $preferredwidthright = $this->page->theme->block_r_max_width;
811         } else {
812             $preferredwidthright = 210;
813         }
815         ob_start();
816         include($this->page->theme->dir . '/header.html');
818         echo '<table id="layout-table"><tr>';
819         foreach ($lt as $column) {
820             if ($column == 'left' && $this->page->blocks->region_has_content(BLOCK_POS_LEFT, $this)) {
821                 echo '<td id="left-column" class="block-region" style="width: ' . $preferredwidthright . 'px; vertical-align: top;">';
822                 echo $this->container_start();
823                 echo $this->blocks_for_region(BLOCK_POS_LEFT);
824                 echo $this->container_end();
825                 echo '</td>';
827             } else if ($column == 'middle') {
828                 echo '<td id="middle-column" style="vertical-align: top;">';
829                 echo $this->container_start();
830                 echo $this->skip_link_target();
831                 echo self::MAIN_CONTENT_TOKEN;
832                 echo $this->container_end();
833                 echo '</td>';
835             } else if ($column == 'right' && $this->page->blocks->region_has_content(BLOCK_POS_RIGHT, $this)) {
836                 echo '<td id="right-column" class="block-region" style="width: ' . $preferredwidthright . 'px; vertical-align: top;">';
837                 echo $this->container_start();
838                 echo $this->blocks_for_region(BLOCK_POS_RIGHT);
839                 echo $this->container_end();
840                 echo '</td>';
841             }
842         }
843         echo '</tr></table>';
845         $menu = str_replace('navmenu', 'navmenufooter', $menu);
846         include($THEME->dir . '/footer.html');
848         $output = ob_get_contents();
849         ob_end_clean();
851         // Put in the start of body code. Bit of a hack, put it in before the first
852         // <div or <table.
853         $divpos = strpos($output, '<div');
854         $tablepos = strpos($output, '<table');
855         if ($divpos === false || ($tablepos !== false && $tablepos < $divpos)) {
856             $pos = $tablepos;
857         } else {
858             $pos = $divpos;
859         }
860         $output = substr($output, 0, $divpos) . $this->standard_top_of_body_html() .
861                 substr($output, $divpos);
863         // Put in the end token before the end of body.
864         $output = str_replace('</body>', self::END_HTML_TOKEN . '</body>', $output);
866         // Make sure we use the correct doctype.
867         $output = preg_replace('/(<!DOCTYPE.+?>)/s', $this->doctype(), $output);
869         return $output;
870     }
872     /**
873      * Outputs the page's footer
874      * @return string HTML fragment
875      */
876     public function footer() {
877         $output = $this->opencontainers->pop_all_but_last(true);
879         $footer = $this->opencontainers->pop('header/footer');
881         // Provide some performance info if required
882         $performanceinfo = '';
883         if (defined('MDL_PERF') || (!empty($CFG->perfdebug) and $CFG->perfdebug > 7)) {
884             $perf = get_performance_info();
885             if (defined('MDL_PERFTOLOG') && !function_exists('register_shutdown_function')) {
886                 error_log("PERF: " . $perf['txt']);
887             }
888             if (defined('MDL_PERFTOFOOT') || debugging() || $CFG->perfdebug > 7) {
889                 $performanceinfo = $perf['html'];
890             }
891         }
892         $footer = str_replace(self::PERFORMANCE_INFO_TOKEN, $performanceinfo, $footer);
894         $footer = str_replace(self::END_HTML_TOKEN, $this->page->requires->get_end_code(), $footer);
896         $this->page->set_state(moodle_page::STATE_DONE);
899         return $output . $footer;
900     }
902     /**
903      * Output the row of editing icons for a block, as defined by the controls array.
904      * @param array $controls an array like {@link block_contents::$controls}.
905      * @return HTML fragment.
906      */
907     public function block_controls($controls) {
908         if (empty($controls)) {
909             return '';
910         }
911         $controlshtml = array();
912         foreach ($controls as $control) {
913             $controlshtml[] = $this->output_tag('a', array('class' => 'icon',
914                     'title' => $control['caption'], 'href' => $control['url']),
915                     $this->output_empty_tag('img',  array('src' => $this->old_icon_url($control['icon']),
916                     'alt' => $control['caption'])));
917         }
918         return $this->output_tag('div', array('class' => 'commands'), implode('', $controlshtml));
919     }
921     /**
922      * Prints a nice side block with an optional header.
923      *
924      * The content is described
925      * by a {@link block_contents} object.
926      *
927      * @param block_contents $bc HTML for the content
928      * @param string $region the region the block is appearing in.
929      * @return string the HTML to be output.
930      */
931     function block($bc, $region) {
932         $bc = clone($bc); // Avoid messing up the object passed in.
933         $bc->prepare();
935         $skiptitle = strip_tags($bc->title);
936         if (empty($skiptitle)) {
937             $output = '';
938             $skipdest = '';
939         } else {
940             $output = $this->output_tag('a', array('href' => '#sb-' . $bc->skipid, 'class' => 'skip-block'),
941                     get_string('skipa', 'access', $skiptitle));
942             $skipdest = $this->output_tag('span', array('id' => 'sb-' . $bc->skipid, 'class' => 'skip-block-to'), '');
943         }
945         $bc->attributes['id'] = $bc->id;
946         $bc->attributes['class'] = $bc->get_classes_string();
947         $output .= $this->output_start_tag('div', $bc->attributes);
949         $controlshtml = $this->block_controls($bc->controls);
951         $title = '';
952         if ($bc->title) {
953             $title = $this->output_tag('h2', null, $bc->title);
954         }
956         if ($title || $controlshtml) {
957             $output .= $this->output_tag('div', array('class' => 'header'),
958                     $this->output_tag('div', array('class' => 'title'),
959                     $title . $controlshtml));
960         }
962         $output .= $this->output_start_tag('div', array('class' => 'content'));
963         $output .= $bc->content;
965         if ($bc->footer) {
966             $output .= $this->output_tag('div', array('class' => 'footer'), $bc->footer);
967         }
969         $output .= $this->output_end_tag('div');
970         $output .= $this->output_end_tag('div');
972         if ($bc->annotation) {
973             $output .= $this->output_tag('div', array('class' => 'blockannotation'), $bc->annotation);
974         }
975         $output .= $skipdest;
977         $this->init_block_hider_js($bc);
978         return $output;
979     }
981     /**
982      * Calls the JS require function to hide a block.
983      * @param block_contents $bc A block_contents object
984      * @return void
985      */
986     protected function init_block_hider_js($bc) {
987         if ($bc->collapsible != block_contents::NOT_HIDEABLE) {
988             $userpref = 'block' . $bc->blockinstanceid . 'hidden';
989             user_preference_allow_ajax_update($userpref, PARAM_BOOL);
990             $this->page->requires->yui_lib('dom');
991             $this->page->requires->yui_lib('event');
992             $plaintitle = strip_tags($bc->title);
993             $this->page->requires->js_function_call('new block_hider', array($bc->id, $userpref,
994                     get_string('hideblocka', 'access', $plaintitle), get_string('showblocka', 'access', $plaintitle),
995                     $this->old_icon_url('t/switch_minus'), $this->old_icon_url('t/switch_plus')));
996         }
997     }
999     /**
1000      * Render the contents of a block_list.
1001      * @param array $icons the icon for each item.
1002      * @param array $items the content of each item.
1003      * @return string HTML
1004      */
1005     public function list_block_contents($icons, $items) {
1006         $row = 0;
1007         $lis = array();
1008         foreach ($items as $key => $string) {
1009             $item = $this->output_start_tag('li', array('class' => 'r' . $row));
1010             if (!empty($icons[$key])) { //test if the content has an assigned icon
1011                 $item .= $this->output_tag('div', array('class' => 'icon column c0'), $icons[$key]);
1012             }
1013             $item .= $this->output_tag('div', array('class' => 'column c1'), $string);
1014             $item .= $this->output_end_tag('li');
1015             $lis[] = $item;
1016             $row = 1 - $row; // Flip even/odd.
1017         }
1018         return $this->output_tag('ul', array('class' => 'list'), implode("\n", $lis));
1019     }
1021     /**
1022      * Output all the blocks in a particular region.
1023      * @param string $region the name of a region on this page.
1024      * @return string the HTML to be output.
1025      */
1026     public function blocks_for_region($region) {
1027         $blockcontents = $this->page->blocks->get_content_for_region($region, $this);
1029         $output = '';
1030         foreach ($blockcontents as $bc) {
1031             if ($bc instanceof block_contents) {
1032                 $output .= $this->block($bc, $region);
1033             } else if ($bc instanceof block_move_target) {
1034                 $output .= $this->block_move_target($bc);
1035             } else {
1036                 throw new coding_exception('Unexpected type of thing (' . get_class($bc) . ') found in list of block contents.');
1037             }
1038         }
1039         return $output;
1040     }
1042     /**
1043      * Output a place where the block that is currently being moved can be dropped.
1044      * @param block_move_target $target with the necessary details.
1045      * @return string the HTML to be output.
1046      */
1047     public function block_move_target($target) {
1048         return $this->output_tag('a', array('href' => $target->url, 'class' => 'blockmovetarget'),
1049                 $this->output_tag('span', array('class' => 'accesshide'), $target->text));
1050     }
1052     /**
1053      * Given a html_link object, outputs an <a> tag that uses the object's attributes.
1054      *
1055      * @param mixed $link A html_link object or a string URL (text param required in second case)
1056      * @param string $text A descriptive text for the link. If $link is a html_link, this is not required.
1057      * @return string HTML fragment
1058      */
1059     public function link($link, $text=null) {
1060         $attributes = array();
1062         if (is_a($link, 'html_link')) {
1063             $link = clone($link);
1065             if ($link->has_action('popup_action')) {
1066                 return $this->link_to_popup($link);
1067             }
1069             $link->prepare();
1070             $this->prepare_event_handlers($link);
1072             // A disabled link is rendered as formatted text
1073             if ($link->disabled) {
1074                 return $this->container($link->text, 'currentlink');
1075             }
1077             $attributes['href'] = prepare_url($link->url);
1078             $attributes['class'] = $link->get_classes_string();
1079             $attributes['title'] = $link->title;
1080             $attributes['id'] = $link->id;
1082             $text = $link->text;
1084         } else if (empty($text)) {
1085             throw new coding_exception('$OUTPUT->link() must have a string as second parameter if the first param ($link) is a string');
1087         } else {
1088             $attributes['href'] = prepare_url($link);
1089         }
1091         return $this->output_tag('a', $attributes, $text);
1092     }
1094    /**
1095     * Print a message along with button choices for Continue/Cancel
1096     *
1097     * If a string or moodle_url is given instead of a html_button, method defaults to post.
1098     *
1099     * @param string $message The question to ask the user
1100     * @param mixed $continue The html_form component representing the Continue answer. Can also be a moodle_url or string URL
1101     * @param mixed $cancel The html_form component representing the Cancel answer. Can also be a moodle_url or string URL
1102     * @return string HTML fragment
1103     */
1104     public function confirm($message, $continue, $cancel) {
1105         if ($continue instanceof html_form) {
1106             $continue = clone($continue);
1107         } else if (is_string($continue)) {
1108             $continueform = new html_form();
1109             $continueform->button->text = get_string('continue');
1110             $continueform->url = new moodle_url($continue);
1111             $continue = $continueform;
1112         } else if ($continue instanceof moodle_url) {
1113             $continueform = new html_form();
1114             $continueform->button->text = get_string('continue');
1115             $continueform->url = $continue;
1116             $continue = $continueform;
1117         } else {
1118             throw new coding_exception('The continue param to $OUTPUT->confirm must be either a URL (string/moodle_url) or a html_form object.');
1119         }
1121         if ($cancel instanceof html_form) {
1122             $cancel = clone($cancel);
1123         } else if (is_string($cancel)) {
1124             $cancelform = new html_form();
1125             $cancelform->button->text = get_string('cancel');
1126             $cancelform->url = new moodle_url($cancel);
1127             $cancel = $cancelform;
1128         } else if ($cancel instanceof moodle_url) {
1129             $cancelform = new html_form();
1130             $cancelform->button->text = get_string('cancel');
1131             $cancelform->url = $cancel;
1132             $cancel = $cancelform;
1133         } else {
1134             throw new coding_exception('The cancel param to $OUTPUT->confirm must be either a URL (string/moodle_url) or a html_form object.');
1135         }
1137         $output = $this->box_start('generalbox', 'notice');
1138         $output .= $this->output_tag('p', array(), $message);
1139         $output .= $this->output_tag('div', array('class' => 'buttons'), $this->button($continue) . $this->button($cancel));
1140         $output .= $this->box_end();
1141         return $output;
1142     }
1144     /**
1145      * Given a html_form object, outputs an <input> tag within a form that uses the object's attributes.
1146      *
1147      * @param html_form $form A html_form object
1148      * @return string HTML fragment
1149      */
1150     public function button($form) {
1151         if (empty($form->button) or !($form->button instanceof html_button)) {
1152             throw new coding_exception('$OUTPUT->button($form) requires $form to have a button (html_button) value');
1153         }
1154         $form = clone($form);
1155         $form->button->prepare();
1157         $this->prepare_event_handlers($form->button);
1159         $buttonattributes = array('class' => $form->button->get_classes_string(),
1160                                   'type' => 'submit',
1161                                   'value' => $form->button->text,
1162                                   'disabled' => $form->button->disabled,
1163                                   'id' => $form->button->id);
1165         $buttonoutput = $this->output_empty_tag('input', $buttonattributes);
1167         // Removing the button so it doesn't get output again
1168         unset($form->button);
1170         return $this->output_tag('div', array('class' => 'singlebutton'), $this->form($form, $buttonoutput));
1171     }
1173     /**
1174      * Given a html_form component and an optional rendered submit button,
1175      * outputs a HTML form with correct divs and inputs and a single submit button.
1176      * This doesn't render any other visible inputs. Use moodleforms for these.
1177      * @param html_form $form A html_form instance
1178      * @param string $contents HTML fragment to put inside the form. If given, must contain at least the submit button.
1179      * @return string HTML fragment
1180      */
1181     public function form($form, $contents=null) {
1182         $form = clone($form);
1183         $form->prepare();
1184         $this->prepare_event_handlers($form);
1185         $buttonoutput = null;
1187         if (empty($contents) && !empty($form->button)) {
1188             debugging("You probably want to use \$OUTPUT->button(\$form), please read that function's documentation", DEBUG_DEVELOPER);
1189         } else if (empty($contents)) {
1190             $contents = $this->output_empty_tag('input', array('type' => 'submit', 'value' => get_string('ok')));
1191         } else if (!empty($form->button)) {
1192             $form->button->prepare();
1193             $this->prepare_event_handlers($form->button);
1195             $buttonattributes = array('class' => $form->button->get_classes_string(),
1196                                       'type' => 'submit',
1197                                       'value' => $form->button->text,
1198                                       'disabled' => $form->button->disabled,
1199                                       'id' => $form->button->id);
1201             $buttonoutput = $this->output_empty_tag('input', $buttonattributes);
1203             // Hide the submit button if the button has a JS submit action
1204             if ($form->jssubmitaction) {
1205                 $buttonoutput = $this->output_start_tag('div', array('id' => "noscript$form->id")) . $buttonoutput . $this->output_end_tag('div');
1206                 $this->page->requires->js_function_call('hide_item', array("noscript$form->id"));
1207             }
1209         }
1211         $hiddenoutput = '';
1213         foreach ($form->url->params() as $var => $val) {
1214             $hiddenoutput .= $this->output_empty_tag('input', array('type' => 'hidden', 'name' => $var, 'value' => $val));
1215         }
1217         $formattributes = array(
1218                 'method' => $form->method,
1219                 'action' => prepare_url($form->url, true),
1220                 'id' => $form->id,
1221                 'class' => $form->get_classes_string());
1223         $divoutput = $this->output_tag('div', array(), $hiddenoutput . $contents . $buttonoutput);
1224         $output = $this->output_tag('form', $formattributes, $divoutput);
1226         return $output;
1227     }
1229     /**
1230      * Returns a string containing a link to the user documentation.
1231      * Also contains an icon by default. Shown to teachers and admin only.
1232      * @param string $path The page link after doc root and language, no leading slash.
1233      * @param string $text The text to be displayed for the link
1234      * @param string $iconpath The path to the icon to be displayed
1235      */
1236     public function doc_link($path, $text=false, $iconpath=false) {
1237         global $CFG, $OUTPUT;
1238         $icon = new moodle_action_icon();
1239         $icon->linktext = $text;
1240         $icon->image->alt = $text;
1241         $icon->image->add_class('iconhelp');
1242         $icon->link->url = new moodle_url(get_docs_url($path));
1244         if (!empty($iconpath)) {
1245             $icon->image->src = $iconpath;
1246         } else {
1247             $icon->image->src = $this->old_icon_url('docs');
1248         }
1250         if (!empty($CFG->doctonewwindow)) {
1251             $icon->actions[] = new popup_action('click', $icon->link->url);
1252         }
1254         return $this->action_icon($icon);
1256     }
1258     /**
1259      * Given a moodle_action_icon object, outputs an image linking to an action (URL or AJAX).
1260      *
1261      * @param moodle_action_icon $icon A moodle_action_icon object
1262      * @return string HTML fragment
1263      */
1264     public function action_icon($icon) {
1265         $icon = clone($icon);
1266         $icon->prepare();
1267         $imageoutput = $this->image($icon->image);
1269         if ($icon->linktext) {
1270             $imageoutput .= $icon->linktext;
1271         }
1272         $icon->link->text = $imageoutput;
1274         return $this->link($icon->link);
1275     }
1277     /*
1278      * Centered heading with attached help button (same title text)
1279      * and optional icon attached
1280      * @param moodle_help_icon $helpicon A moodle_help_icon object
1281      * @param mixed $image An image URL or a html_image object
1282      * @return string HTML fragment
1283      */
1284     public function heading_with_help($helpicon, $image=false) {
1285         if (!($image instanceof html_image) && !empty($image)) {
1286             $htmlimage = new html_image();
1287             $htmlimage->src = $image;
1288             $image = $htmlimage;
1289         }
1290         return $this->container($this->image($image) . $this->heading($helpicon->text, 2, 'main help') . $this->help_icon($helpicon), 'heading-with-help');
1291     }
1293     /**
1294      * Print a help icon.
1295      *
1296      * @param moodle_help_icon $helpicon A moodle_help_icon object, subclass of html_link
1297      *
1298      * @return string  HTML fragment
1299      */
1300     public function help_icon($icon) {
1301         global $COURSE;
1302         $icon = clone($icon);
1303         $icon->prepare();
1305         $popup = new popup_action('click', $icon->link->url);
1306         $icon->link->add_action($popup);
1308         $image = null;
1310         if (!empty($icon->image)) {
1311             $image = $icon->image;
1312             $image->add_class('iconhelp');
1313         }
1315         return $this->output_tag('span', array('class' => 'helplink'), $this->link_to_popup($icon->link, $image));
1316     }
1318     /**
1319      * Creates and returns a button to a popup window
1320      *
1321      * @param html_link $link Subclass of moodle_html_component
1322      * @param moodle_popup $popup A moodle_popup object
1323      * @param html_image $image An optional image replacing the link text
1324      *
1325      * @return string HTML fragment
1326      */
1327     public function link_to_popup($link, $image=null) {
1328         $link = clone($link);
1329         $link->prepare();
1331         $this->prepare_event_handlers($link);
1333         if (empty($link->url)) {
1334             throw new coding_exception('Called $OUTPUT->link_to_popup($link) method without $link->url set.');
1335         }
1337         $linkurl = prepare_url($link->url);
1339         $tagoptions = array(
1340                 'title' => $link->title,
1341                 'id' => $link->id,
1342                 'href' => ($linkurl) ? $linkurl : prepare_url($popup->url),
1343                 'class' => $link->get_classes_string());
1345         // Use image if one is given
1346         if (!empty($image) && $image instanceof html_image) {
1348             if (empty($image->alt) || $image->alt == HTML_ATTR_EMPTY) {
1349                 $image->alt = $link->text;
1350                 $image->title = $link->text;
1351             }
1353             $link->text = $this->image($image);
1355             if (!empty($link->linktext)) {
1356                 $link->text = "$link->title &nbsp; $link->text";
1357             }
1358         }
1360         return $this->output_tag('a', $tagoptions, $link->text);
1361     }
1363     /**
1364      * Creates and returns a spacer image with optional line break.
1365      *
1366      * @param html_image $image Subclass of moodle_html_component
1367      *
1368      * @return string HTML fragment
1369      */
1370     public function spacer($image) {
1371         $image = clone($image);
1373         if (empty($image->src)) {
1374             $image->src = $this->old_icon_url('spacer');
1375         }
1377         $image->prepare();
1378         $image->add_class('spacer');
1380         $output = $this->image($image);
1382         return $output;
1383     }
1385     /**
1386      * Creates and returns an image.
1387      *
1388      * @param html_image $image Subclass of moodle_html_component
1389      *
1390      * @return string HTML fragment
1391      */
1392     public function image($image) {
1393         if ($image === false) {
1394             return false;
1395         }
1397         $image = clone($image);
1398         $image->prepare();
1400         $this->prepare_event_handlers($image);
1402         $attributes = array('class' => $image->get_classes_string(),
1403                             'src' => prepare_url($image->src),
1404                             'alt' => $image->alt,
1405                             'style' => $image->style,
1406                             'title' => $image->title,
1407                             'id' => $image->id);
1409         if (!empty($image->height) || !empty($image->width)) {
1410             $attributes['style'] .= $this->prepare_legacy_width_and_height($image);
1411         }
1412         return $this->output_empty_tag('img', $attributes);
1413     }
1415     /**
1416      * Print the specified user's avatar.
1417      *
1418      * This method can be used in two ways:
1419      * <pre>
1420      * // Option 1:
1421      * $userpic = new moodle_user_picture();
1422      * // Set properties of $userpic
1423      * $OUTPUT->user_picture($userpic);
1424      *
1425      * // Option 2: (shortcut for simple cases)
1426      * // $user has come from the DB and has fields id, picture, imagealt, firstname and lastname
1427      * $OUTPUT->user_picture($user, $COURSE->id);
1428      * </pre>
1429      *
1430      * @param object $userpic Object with at least fields id, picture, imagealt, firstname, lastname
1431      *     If any of these are missing, or if a userid is passed, the database is queried. Avoid this
1432      *     if at all possible, particularly for reports. It is very bad for performance.
1433      *     A moodle_user_picture object is a better parameter.
1434      * @param int $courseid courseid Used when constructing the link to the user's profile. Required if $userpic
1435      *     is not a moodle_user_picture object
1436      * @return string HTML fragment
1437      */
1438     public function user_picture($userpic, $courseid=null) {
1439         // Instantiate a moodle_user_picture object if $user is not already one
1440         if (!($userpic instanceof moodle_user_picture)) {
1441             if (empty($courseid)) {
1442                 throw new coding_exception('Called $OUTPUT->user_picture with a $user object but no $courseid.');
1443             }
1445             $user = $userpic;
1446             $userpic = new moodle_user_picture();
1447             $userpic->user = $user;
1448             $userpic->courseid = $courseid;
1449         } else {
1450             $userpic = clone($userpic);
1451         }
1453         $userpic->prepare();
1455         $output = $this->image($userpic->image);
1457         if (!empty($userpic->url)) {
1458             $actions = $userpic->get_actions();
1459             if ($userpic->popup && !empty($actions)) {
1460                 $link = new html_link();
1461                 $link->url = $userpic->url;
1462                 $link->text = fullname($userpic->user);
1463                 $link->title = fullname($userpic->user);
1465                 foreach ($actions as $action) {
1466                     $link->add_action($action);
1467                 }
1468                 $output = $this->link_to_popup($link, $userpic->image);
1469             } else {
1470                 $output = $this->link(prepare_url($userpic->url), $output);
1471             }
1472         }
1474         return $output;
1475     }
1477     /**
1478      * Prints the 'Update this Modulename' button that appears on module pages.
1479      *
1480      * @param string $cmid the course_module id.
1481      * @param string $modulename the module name, eg. "forum", "quiz" or "workshop"
1482      * @return string the HTML for the button, if this user has permission to edit it, else an empty string.
1483      */
1484     public function update_module_button($cmid, $modulename) {
1485         global $CFG;
1486         if (has_capability('moodle/course:manageactivities', get_context_instance(CONTEXT_MODULE, $cmid))) {
1487             $modulename = get_string('modulename', $modulename);
1488             $string = get_string('updatethis', '', $modulename);
1490             $form = new html_form();
1491             $form->url = new moodle_url("$CFG->wwwroot/course/mod.php", array('update' => $cmid, 'return' => true, 'sesskey' => sesskey()));
1492             $form->button->text = $string;
1493             return $this->button($form);
1494         } else {
1495             return '';
1496         }
1497     }
1499     /**
1500      * Prints a "Turn editing on/off" button in a form.
1501      * @param moodle_url $url The URL + params to send through when clicking the button
1502      * @return string HTML the button
1503      */
1504     public function edit_button(moodle_url $url) {
1505         global $USER;
1506         if (!empty($USER->editing)) {
1507             $string = get_string('turneditingoff');
1508             $edit = '0';
1509         } else {
1510             $string = get_string('turneditingon');
1511             $edit = '1';
1512         }
1514         $form = new html_form();
1515         $form->url = $url;
1516         $form->url->param('edit', $edit);
1517         $form->button->text = $string;
1519         return $this->button($form);
1520     }
1522     /**
1523      * Outputs a HTML nested list
1524      *
1525      * @param html_list $list A html_list object
1526      * @return string HTML structure
1527      */
1528     public function htmllist($list) {
1529         $list = clone($list);
1530         $list->prepare();
1532         $this->prepare_event_handlers($list);
1534         if ($list->type == 'ordered') {
1535             $tag = 'ol';
1536         } else if ($list->type == 'unordered') {
1537             $tag = 'ul';
1538         }
1540         $output = $this->output_start_tag($tag, array('class' => $list->get_classes_string()));
1542         foreach ($list->items as $listitem) {
1543             if ($listitem instanceof html_list) {
1544                 $output .= $this->output_start_tag('li', array()) . "\n";
1545                 $output .= $this->htmllist($listitem) . "\n";
1546                 $output .= $this->output_end_tag('li') . "\n";
1547             } else if ($listitem instanceof html_list_item) {
1548                 $listitem->prepare();
1549                 $this->prepare_event_handlers($listitem);
1550                 $output .= $this->output_tag('li', array('class' => $listitem->get_classes_string()), $listitem->value) . "\n";
1551             } else {
1552                 $output .= $this->output_tag('li', array(), $listitem) . "\n";
1553             }
1554         }
1556         if ($list->text) {
1557             $output = $list->text . $output;
1558         }
1560         return $output . $this->output_end_tag($tag);
1561     }
1563     /**
1564      * Prints an inline span element with optional text contents.
1565      *
1566      * @param mixed $span A html_span object or some string content to wrap in a span
1567      * @param mixed $classes A space-separated list or an array of classes. Only used if $span is a string
1568      * @return string A HTML fragment
1569      */
1570     public function span($span, $classes='') {
1571         if (!($span instanceof html_span)) {
1572             $text = $span;
1573             $span = new html_span();
1574             $span->contents = $text;
1575             $span->add_classes($classes);
1576         }
1578         $span = clone($span);
1579         $span->prepare();
1580         $this->prepare_event_handlers($span);
1581         $attributes = array('class' => $span->get_classes_string(),
1582                             'alt' => $span->alt,
1583                             'style' => $span->style,
1584                             'title' => $span->title,
1585                             'id' => $span->id);
1586         return $this->output_tag('span', $attributes, $span->contents);
1587     }
1589     /**
1590      * Prints a simple button to close a window
1591      *
1592      * @global objec)t
1593      * @param string $text The lang string for the button's label (already output from get_string())
1594      * @return string|void if $return is true, void otherwise
1595      */
1596     public function close_window_button($text='') {
1597         if (empty($text)) {
1598             $text = get_string('closewindow');
1599         }
1600         $closeform = new html_form();
1601         $closeform->url = '#';
1602         $closeform->method = 'get';
1603         $closeform->button->text = $text;
1604         $closeform->button->add_action('click', 'close_window');
1605         $closeform->button->prepare();
1606         return $this->container($this->button($closeform), 'closewindow');
1607     }
1609     /**
1610      * Outputs a <select> menu or a list of radio/checkbox inputs.
1611      *
1612      * This method is extremely versatile, and can be used to output yes/no menus,
1613      * form-enclosed menus with automatic redirects when an option is selected,
1614      * descriptive labels and help icons. By default it just outputs a select
1615      * menu.
1616      *
1617      * To add a descriptive label, use html_select::set_label($text, $for) or
1618      * html_select::set_label($label) passing a html_label object
1619      *
1620      * To add a help icon, use html_select::set_help($page, $text, $linktext) or
1621      * html_select::set_help($helpicon) passing a moodle_help_icon object
1622      *
1623      * If you html_select::$rendertype to "radio", it will render radio buttons
1624      * instead of a <select> menu, unless $multiple is true, in which case it
1625      * will render checkboxes.
1626      *
1627      * To surround the menu with a form, simply set html_select->form as a
1628      * valid html_form object. Note that this function will NOT automatically
1629      * add a form for non-JS browsers. If you do not set one up, it assumes
1630      * that you are providing your own form in some other way.
1631      *
1632      * You can either call this function with a single html_select argument
1633      * or, with a list of parameters, in which case those parameters are sent to
1634      * the html_select constructor.
1635      *
1636      * @param html_select $select a html_select that describes
1637      *      the select menu you want output.
1638      * @return string the HTML for the <select>
1639      */
1640     public function select($select) {
1641         $select = clone($select);
1642         $select->prepare();
1644         $this->prepare_event_handlers($select);
1646         if (empty($select->id)) {
1647             $select->id = 'menu' . str_replace(array('[', ']'), '', $select->name);
1648         }
1650         $attributes = array(
1651             'name' => $select->name,
1652             'id' => $select->id,
1653             'class' => $select->get_classes_string()
1654         );
1655         if ($select->disabled) {
1656             $attributes['disabled'] = 'disabled';
1657         }
1658         if ($select->tabindex) {
1659             $attributes['tabindex'] = $tabindex;
1660         }
1662         if ($select->rendertype == 'menu' && $select->listbox) {
1663             if (is_integer($select->listbox)) {
1664                 $size = $select->listbox;
1665             } else {
1666                 $size = min($select->maxautosize, count($select->options));
1667             }
1668             $attributes['size'] = $size;
1669             if ($select->multiple) {
1670                 $attributes['multiple'] = 'multiple';
1671             }
1672         }
1674         $html = '';
1676         if (!empty($select->label)) {
1677             $html .= $this->label($select->label);
1678         }
1680         if (!empty($select->helpicon) && $select->helpicon instanceof moodle_help_icon) {
1681             $html .= $this->help_icon($select->helpicon);
1682         }
1684         if ($select->rendertype == 'menu') {
1685             $html .= $this->output_start_tag('select', $attributes) . "\n";
1687             foreach ($select->options as $option) {
1688                 // $OUTPUT->select_option detects if $option is an option or an optgroup
1689                 $html .= $this->select_option($option);
1690             }
1692             $html .= $this->output_end_tag('select') . "\n";
1693         } else if ($select->rendertype == 'radio') {
1694             $currentradio = 0;
1695             foreach ($select->options as $option) {
1696                 $html .= $this->radio($option, $select->name);
1697                 $currentradio++;
1698             }
1699         } else if ($select->rendertype == 'checkbox') {
1700             $currentcheckbox = 0;
1701             // If only two choices are available, suggest using the checkbox method instead
1702             if (count($select->options) < 3 && !$select->multiple) {
1703                 debugging('You are using $OUTPUT->select() to render two mutually exclusive choices using checkboxes. Please use $OUTPUT->checkbox(html_select_option) instead.', DEBUG_DEVELOPER);
1704             } else {
1705                 foreach ($select->options as $option) {
1706                     $html .= $this->checkbox($option, $select->name);
1707                     $currentcheckbox++;
1708                 }
1709             }
1710         }
1712         if (!empty($select->form) && $select->form instanceof html_form) {
1713             $html = $this->form($select->form, $html);
1714         }
1716         return $html;
1717     }
1719     /**
1720      * Outputs a <input type="radio" /> element. Optgroups are ignored, so do not
1721      * pass a html_select_optgroup as a param to this function.
1722      *
1723      * @param html_select_option $option a html_select_option
1724      * @return string the HTML for the <input type="radio">
1725      */
1726     public function radio($option, $name='unnamed') {
1727         static $currentradio = array();
1729         if (empty($currentradio[$name])) {
1730             $currentradio[$name] = 0;
1731         }
1733         if ($option instanceof html_select_optgroup) {
1734             throw new coding_exception('$OUTPUT->radio($option) does not support a html_select_optgroup object as param.');
1735         } else if (!($option instanceof html_select_option)) {
1736             throw new coding_exception('$OUTPUT->radio($option) only accepts a html_select_option object as param.');
1737         }
1738         $option = clone($option);
1739         $option->prepare();
1740         $option->label->for = $option->id;
1741         $this->prepare_event_handlers($option);
1743         $output = $this->output_start_tag('span', array('class' => "radiogroup $name rb{$currentradio[$name]}")) . "\n";
1744         $output .= $this->label($option->label);
1746         if ($option->selected == 'selected') {
1747             $option->selected = 'checked';
1748         }
1750         $output .= $this->output_empty_tag('input', array(
1751                 'type' => 'radio',
1752                 'value' => $option->value,
1753                 'name' => $name,
1754                 'alt' => $option->alt,
1755                 'id' => $option->id,
1756                 'class' => $option->get_classes_string(),
1757                 'checked' => $option->selected));
1759         $output .= $this->output_end_tag('span');
1760         $currentradio[$name]++;
1761         return $output;
1762     }
1764     /**
1765      * Outputs a <input type="checkbox" /> element. Optgroups are ignored, so do not
1766      * pass a html_select_optgroup as a param to this function.
1767      *
1768      * @param html_select_option $option a html_select_option
1769      * @return string the HTML for the <input type="checkbox">
1770      */
1771     public function checkbox($option, $name='unnamed') {
1772         if ($option instanceof html_select_optgroup) {
1773             throw new coding_exception('$OUTPUT->checkbox($option) does not support a html_select_optgroup object as param.');
1774         } else if (!($option instanceof html_select_option)) {
1775             throw new coding_exception('$OUTPUT->checkbox($option) only accepts a html_select_option object as param.');
1776         }
1777         $option = clone($option);
1778         $option->prepare();
1780         $option->label->for = $option->id;
1781         $this->prepare_event_handlers($option);
1783         $output = $this->output_start_tag('span', array('class' => "checkbox $name")) . "\n";
1785         if ($option->selected) {
1786             $option->selected = 'checked';
1787         } else {
1788             $option->selected = '';
1789         }
1791         $output .= $this->output_empty_tag('input', array(
1792                 'type' => 'checkbox',
1793                 'value' => $option->value,
1794                 'name' => $name,
1795                 'id' => $option->id,
1796                 'alt' => $option->alt,
1797                 'disabled' => $option->disabled,
1798                 'class' => $option->get_classes_string(),
1799                 'checked' => $option->selected));
1800         $output .= $this->label($option->label);
1802         $output .= $this->output_end_tag('span');
1804         return $output;
1805     }
1807     /**
1808      * Output an <option> or <optgroup> element. If an optgroup element is detected,
1809      * this will recursively output its options as well.
1810      *
1811      * @param mixed $option a html_select_option or html_select_optgroup
1812      * @return string the HTML for the <option> or <optgroup>
1813      */
1814     public function select_option($option) {
1815         $option = clone($option);
1816         $option->prepare();
1817         $this->prepare_event_handlers($option);
1819         if ($option instanceof html_select_option) {
1820             return $this->output_tag('option', array(
1821                     'value' => $option->value,
1822                     'disabled' => $option->disabled,
1823                     'class' => $option->get_classes_string(),
1824                     'selected' => $option->selected), $option->text);
1825         } else if ($option instanceof html_select_optgroup) {
1826             $output = $this->output_start_tag('optgroup', array('label' => $option->text, 'class' => $option->get_classes_string()));
1827             foreach ($option->options as $selectoption) {
1828                 $output .= $this->select_option($selectoption);
1829             }
1830             $output .= $this->output_end_tag('optgroup');
1831             return $output;
1832         }
1833     }
1835     /**
1836      * Output an <input type="text"> element
1837      *
1838      * @param html_field $field a html_field object
1839      * @return string the HTML for the <input>
1840      */
1841     public function textfield($field) {
1842         return $this->output_tag('span', array('class' => "textfield $field->name"), $this->field($field));
1843     }
1845     /**
1846      * Output an <input/> element
1847      *
1848      * @param html_field $field a html_field object
1849      * @return string the HTML for the <input>
1850      */
1851     public function field($field) {
1852         $field = clone($field);
1853         $field->prepare();
1854         $this->prepare_event_handlers($field);
1855         $label = '';
1856         if (!empty($field->label->text)) {
1857             $label = $this->label($field->label);
1858         }
1859         return $label . $this->output_empty_tag('input', array(
1860                 'type' => $field->type,
1861                 'name' => $field->name,
1862                 'id' => $field->id,
1863                 'value' => $field->value,
1864                 'disabled' => $field->disabled,
1865                 'style' => $field->style,
1866                 'alt' => $field->alt,
1867                 'title' => $field->title,
1868                 'maxlength' => $field->maxlength));
1869     }
1871     /**
1872      * Outputs a <label> element.
1873      * @param html_label $label A html_label object
1874      * @return HTML fragment
1875      */
1876     public function label($label) {
1877         $label = clone($label);
1878         $label->prepare();
1879         $this->prepare_event_handlers($label);
1880         return $this->output_tag('label', array('for' => $label->for, 'class' => $label->get_classes_string()), $label->text);
1881     }
1883     /**
1884      * Output an error message. By default wraps the error message in <span class="error">.
1885      * If the error message is blank, nothing is output.
1886      * @param string $message the error message.
1887      * @return string the HTML to output.
1888      */
1889     public function error_text($message) {
1890         if (empty($message)) {
1891             return '';
1892         }
1893         return $this->output_tag('span', array('class' => 'error'), $message);
1894     }
1896     /**
1897      * Do not call this function directly.
1898      *
1899      * To terminate the current script with a fatal error, call the {@link print_error}
1900      * function, or throw an exception. Doing either of those things will then call this
1901      * function to display the error, before terminating the execution.
1902      *
1903      * @param string $message The message to output
1904      * @param string $moreinfourl URL where more info can be found about the error
1905      * @param string $link Link for the Continue button
1906      * @param array $backtrace The execution backtrace
1907      * @param string $debuginfo Debugging information
1908      * @param bool $showerrordebugwarning Whether or not to show a debugging warning
1909      * @return string the HTML to output.
1910      */
1911     public function fatal_error($message, $moreinfourl, $link, $backtrace,
1912                 $debuginfo = null, $showerrordebugwarning = false) {
1914         $output = '';
1916         if (!debugging('', DEBUG_DEVELOPER)) {
1917             return false;
1918         }
1920         if ($this->has_started()) {
1921             $output .= $this->opencontainers->pop_all_but_last();
1922         } else {
1923             // Header not yet printed
1924             @header('HTTP/1.0 404 Not Found');
1925             $this->page->set_title(get_string('error'));
1926             $output .= $this->header();
1927         }
1929         $message = '<p class="errormessage">' . $message . '</p>'.
1930                 '<p class="errorcode"><a href="' . $moreinfourl . '">' .
1931                 get_string('moreinformation') . '</a></p>';
1932         $output .= $this->box($message, 'errorbox');
1934         if ($showerrordebugwarning) {
1935             $output .= $this->notification('error() is a deprecated function. ' .
1936                     'Please call print_error() instead of error()', 'notifytiny');
1937         }
1938         if (!empty($debuginfo)) {
1939             $output .= $this->notification($debuginfo, 'notifytiny');
1940         }
1941         if (!empty($backtrace)) {
1942             $output .= $this->notification('Stack trace: ' .
1943                     format_backtrace($backtrace), 'notifytiny');
1944         }
1946         if (!empty($link)) {
1947             $output .= $this->continue_button($link);
1948         }
1950         $output .= $this->footer();
1952         // Padding to encourage IE to display our error page, rather than its own.
1953         $output .= str_repeat(' ', 512);
1955         return $output;
1956     }
1958     /**
1959      * Output a notification (that is, a status message about something that has
1960      * just happened).
1961      *
1962      * @param string $message the message to print out
1963      * @param string $classes normally 'notifyproblem' or 'notifysuccess'.
1964      * @return string the HTML to output.
1965      */
1966     public function notification($message, $classes = 'notifyproblem') {
1967         return $this->output_tag('div', array('class' =>
1968                 moodle_renderer_base::prepare_classes($classes)), clean_text($message));
1969     }
1971     /**
1972      * Print a continue button that goes to a particular URL.
1973      *
1974      * @param string|moodle_url $link The url the button goes to.
1975      * @return string the HTML to output.
1976      */
1977     public function continue_button($link) {
1978         if (!is_a($link, 'moodle_url')) {
1979             $link = new moodle_url($link);
1980         }
1981         $form = new html_form();
1982         $form->url = $link;
1983         $form->values = $link->params();
1984         $form->button->text = get_string('continue');
1985         $form->method = 'get';
1987         return $this->output_tag('div', array('class' => 'continuebutton') , $this->button($form));
1988     }
1990     /**
1991      * Prints a single paging bar to provide access to other pages  (usually in a search)
1992      *
1993      * @param string|moodle_url $link The url the button goes to.
1994      * @return string the HTML to output.
1995      */
1996     public function paging_bar($pagingbar) {
1997         $output = '';
1998         $pagingbar = clone($pagingbar);
1999         $pagingbar->prepare();
2001         if ($pagingbar->totalcount > $pagingbar->perpage) {
2002             $output .= get_string('page') . ':';
2004             if (!empty($pagingbar->previouslink)) {
2005                 $output .= '&nbsp;(' . $this->link($pagingbar->previouslink) . ')&nbsp;';
2006             }
2008             if (!empty($pagingbar->firstlink)) {
2009                 $output .= '&nbsp;' . $this->link($pagingbar->firstlink) . '&nbsp;...';
2010             }
2012             foreach ($pagingbar->pagelinks as $link) {
2013                 if ($link instanceof html_link) {
2014                     $output .= '&nbsp;&nbsp;' . $this->link($link);
2015                 } else {
2016                     $output .= "&nbsp;&nbsp;$link";
2017                 }
2018             }
2020             if (!empty($pagingbar->lastlink)) {
2021                 $output .= '&nbsp;...' . $this->link($pagingbar->lastlink) . '&nbsp;';
2022             }
2024             if (!empty($pagingbar->nextlink)) {
2025                 $output .= '&nbsp;&nbsp;(' . $this->link($pagingbar->nextlink) . ')';
2026             }
2027         }
2029         return $this->output_tag('div', array('class' => 'paging'), $output);
2030     }
2032     /**
2033      * Render a HTML table
2034      *
2035      * @param object $table {@link html_table} instance containing all the information needed
2036      * @return string the HTML to output.
2037      */
2038     public function table(html_table $table) {
2039         $table = clone($table);
2040         $table->prepare();
2041         $attributes = array(
2042                 'id'            => $table->id,
2043                 'width'         => $table->width,
2044                 'summary'       => $table->summary,
2045                 'cellpadding'   => $table->cellpadding,
2046                 'cellspacing'   => $table->cellspacing,
2047                 'class'         => $table->get_classes_string());
2048         $output = $this->output_start_tag('table', $attributes) . "\n";
2050         $countcols = 0;
2052         if (!empty($table->head)) {
2053             $countcols = count($table->head);
2054             $output .= $this->output_start_tag('thead', $table->headclasses) . "\n";
2055             $output .= $this->output_start_tag('tr', array()) . "\n";
2056             $keys = array_keys($table->head);
2057             $lastkey = end($keys);
2059             foreach ($table->head as $key => $heading) {
2060                 // Convert plain string headings into html_table_cell objects
2061                 if (!($heading instanceof html_table_cell)) {
2062                     $headingtext = $heading;
2063                     $heading = new html_table_cell();
2064                     $heading->text = $headingtext;
2065                     $heading->header = true;
2066                 }
2068                 if ($heading->header !== false) {
2069                     $heading->header = true;
2070                 }
2072                 $this->prepare_event_handlers($heading);
2074                 $heading->add_classes(array('header', 'c' . $key));
2075                 if (isset($table->headspan[$key]) && $table->headspan[$key] > 1) {
2076                     $heading->colspan = $table->headspan[$key];
2077                     $countcols += $table->headspan[$key] - 1;
2078                 }
2080                 if ($key == $lastkey) {
2081                     $heading->add_class('lastcol');
2082                 }
2083                 if (isset($table->colclasses[$key])) {
2084                     $heading->add_class($table->colclasses[$key]);
2085                 }
2086                 if ($table->rotateheaders) {
2087                     // we need to wrap the heading content
2088                     $heading->text = $this->output_tag('span', '', $heading->text);
2089                 }
2091                 $attributes = array(
2092                         'style'     => $table->align[$key] . $table->size[$key] . $heading->style,
2093                         'class'     => $heading->get_classes_string(),
2094                         'scope'     => $heading->scope,
2095                         'colspan'   => $heading->colspan);
2097                 $tagtype = 'td';
2098                 if ($heading->header === true) {
2099                     $tagtype = 'th';
2100                 }
2101                 $output .= $this->output_tag($tagtype, $attributes, $heading->text) . "\n";
2102             }
2103             $output .= $this->output_end_tag('tr') . "\n";
2104             $output .= $this->output_end_tag('thead') . "\n";
2105         }
2107         if (!empty($table->data)) {
2108             $oddeven    = 1;
2109             $keys       = array_keys($table->data);
2110             $lastrowkey = end($keys);
2111             $output .= $this->output_start_tag('tbody', array('class' => moodle_renderer_base::prepare_classes($table->bodyclasses))) . "\n";
2113             foreach ($table->data as $key => $row) {
2114                 if (($row === 'hr') && ($countcols)) {
2115                     $output .= $this->output_tag('td', array('colspan' => $countcols),
2116                                                  $this->output_tag('div', array('class' => 'tabledivider'), '')) . "\n";
2117                 } else {
2118                     // Convert array rows to html_table_rows and cell strings to html_table_cell objects
2119                     if (!($row instanceof html_table_row)) {
2120                         $newrow = new html_table_row();
2122                         foreach ($row as $unused => $item) {
2123                             $cell = new html_table_cell();
2124                             $cell->text = $item;
2125                             $this->prepare_event_handlers($cell);
2126                             $newrow->cells[] = $cell;
2127                         }
2128                         $row = $newrow;
2129                     }
2131                     $this->prepare_event_handlers($row);
2133                     $oddeven = $oddeven ? 0 : 1;
2134                     if (isset($table->rowclasses[$key])) {
2135                         $row->add_classes(array_unique(moodle_html_component::clean_classes($table->rowclasses[$key])));
2136                     }
2138                     $row->add_class('r' . $oddeven);
2139                     if ($key == $lastrowkey) {
2140                         $row->add_class('lastrow');
2141                     }
2143                     $output .= $this->output_start_tag('tr', array('class' => $row->get_classes_string(), 'style' => $row->style, 'id' => $row->id)) . "\n";
2144                     $keys2 = array_keys($row->cells);
2145                     $lastkey = end($keys2);
2147                     foreach ($row->cells as $key => $cell) {
2148                         if (!($cell instanceof html_table_cell)) {
2149                             $mycell = new html_table_cell();
2150                             $mycell->text = $cell;
2151                             $this->prepare_event_handlers($mycell);
2152                             $cell = $mycell;
2153                         }
2155                         if (isset($table->colclasses[$key])) {
2156                             $cell->add_classes(array_unique(moodle_html_component::clean_classes($table->colclasses[$key])));
2157                         }
2159                         $cell->add_classes('cell');
2160                         $cell->add_classes('c' . $key);
2161                         if ($key == $lastkey) {
2162                             $cell->add_classes('lastcol');
2163                         }
2164                         $tdstyle = '';
2165                         $tdstyle .= isset($table->align[$key]) ? $table->align[$key] : '';
2166                         $tdstyle .= isset($table->size[$key]) ? $table->size[$key] : '';
2167                         $tdstyle .= isset($table->wrap[$key]) ? $table->wrap[$key] : '';
2168                         $tdattributes = array(
2169                                 'style' => $tdstyle . $cell->style,
2170                                 'colspan' => $cell->colspan,
2171                                 'rowspan' => $cell->rowspan,
2172                                 'id' => $cell->id,
2173                                 'class' => $cell->get_classes_string(),
2174                                 'abbr' => $cell->abbr,
2175                                 'scope' => $cell->scope);
2176                         $tagtype = 'td';
2177                         if ($cell->header === true) {
2178                             $tagtype = 'th';
2179                         }
2180                         $output .= $this->output_tag($tagtype, $tdattributes, $cell->text) . "\n";
2181                     }
2182                 }
2183                 $output .= $this->output_end_tag('tr') . "\n";
2184             }
2185             $output .= $this->output_end_tag('tbody') . "\n";
2186         }
2187         $output .= $this->output_end_tag('table') . "\n";
2189         if ($table->rotateheaders && can_use_rotated_text()) {
2190             $this->page->requires->yui_lib('event');
2191             $this->page->requires->js('course/report/progress/textrotate.js');
2192         }
2194         return $output;
2195     }
2197     /**
2198      * Output the place a skip link goes to.
2199      * @param string $id The target name from the corresponding $PAGE->requires->skip_link_to($target) call.
2200      * @return string the HTML to output.
2201      */
2202     public function skip_link_target($id = '') {
2203         return $this->output_tag('span', array('id' => $id), '');
2204     }
2206     /**
2207      * Outputs a heading
2208      * @param string $text The text of the heading
2209      * @param int $level The level of importance of the heading. Defaulting to 2
2210      * @param string $classes A space-separated list of CSS classes
2211      * @param string $id An optional ID
2212      * @return string the HTML to output.
2213      */
2214     public function heading($text, $level = 2, $classes = 'main', $id = '') {
2215         $level = (integer) $level;
2216         if ($level < 1 or $level > 6) {
2217             throw new coding_exception('Heading level must be an integer between 1 and 6.');
2218         }
2219         return $this->output_tag('h' . $level,
2220                 array('id' => $id, 'class' => moodle_renderer_base::prepare_classes($classes)), $text);
2221     }
2223     /**
2224      * Outputs a box.
2225      * @param string $contents The contents of the box
2226      * @param string $classes A space-separated list of CSS classes
2227      * @param string $id An optional ID
2228      * @return string the HTML to output.
2229      */
2230     public function box($contents, $classes = 'generalbox', $id = '') {
2231         return $this->box_start($classes, $id) . $contents . $this->box_end();
2232     }
2234     /**
2235      * Outputs the opening section of a box.
2236      * @param string $classes A space-separated list of CSS classes
2237      * @param string $id An optional ID
2238      * @return string the HTML to output.
2239      */
2240     public function box_start($classes = 'generalbox', $id = '') {
2241         $this->opencontainers->push('box', $this->output_end_tag('div'));
2242         return $this->output_start_tag('div', array('id' => $id,
2243                 'class' => 'box ' . moodle_renderer_base::prepare_classes($classes)));
2244     }
2246     /**
2247      * Outputs the closing section of a box.
2248      * @return string the HTML to output.
2249      */
2250     public function box_end() {
2251         return $this->opencontainers->pop('box');
2252     }
2254     /**
2255      * Outputs a container.
2256      * @param string $contents The contents of the box
2257      * @param string $classes A space-separated list of CSS classes
2258      * @param string $id An optional ID
2259      * @return string the HTML to output.
2260      */
2261     public function container($contents, $classes = '', $id = '') {
2262         return $this->container_start($classes, $id) . $contents . $this->container_end();
2263     }
2265     /**
2266      * Outputs the opening section of a container.
2267      * @param string $classes A space-separated list of CSS classes
2268      * @param string $id An optional ID
2269      * @return string the HTML to output.
2270      */
2271     public function container_start($classes = '', $id = '') {
2272         $this->opencontainers->push('container', $this->output_end_tag('div'));
2273         return $this->output_start_tag('div', array('id' => $id,
2274                 'class' => moodle_renderer_base::prepare_classes($classes)));
2275     }
2277     /**
2278      * Outputs the closing section of a container.
2279      * @return string the HTML to output.
2280      */
2281     public function container_end() {
2282         return $this->opencontainers->pop('container');
2283     }
2285    /**
2286      * Make nested HTML lists out of the items
2287      *
2288      * The resulting list will look something like this:
2289      *
2290      * <pre>
2291      * <<ul>>
2292      * <<li>><div class='tree_item parent'>(item contents)</div>
2293      *      <<ul>
2294      *      <<li>><div class='tree_item'>(item contents)</div><</li>>
2295      *      <</ul>>
2296      * <</li>>
2297      * <</ul>>
2298      * </pre>
2299      *
2300      * @param array[]tree_item $items
2301      * @param array[string]string $attrs html attributes passed to the top of
2302      * the list
2303      * @return string HTML
2304      */
2305     function tree_block_contents($items, $attrs=array()) {
2306         // exit if empty, we don't want an empty ul element
2307         if (empty($items)) {
2308             return '';
2309         }
2310         // array of nested li elements
2311         $lis = array();
2312         foreach ($items as $item) {
2313             // this applies to the li item which contains all child lists too
2314             $content = $item->content($this);
2315             $liclasses = array($item->get_css_type());
2316             if (!$item->forceopen || (!$item->forceopen && $item->collapse) || (count($item->children)==0  && $item->nodetype==navigation_node::NODETYPE_BRANCH)) {
2317                 $liclasses[] = 'collapsed';
2318             }
2319             if ($item->isactive === true) {
2320                 $liclasses[] = 'current_branch';
2321             }
2322             $liattr = array('class'=>join(' ',$liclasses));
2323             // class attribute on the div item which only contains the item content
2324             $divclasses = array('tree_item');
2325             if (!empty($item->children)  || $item->nodetype==navigation_node::NODETYPE_BRANCH) {
2326                 $divclasses[] = 'branch';
2327             } else {
2328                 $divclasses[] = 'leaf';
2329             }
2330             if (!empty($item->classes) && count($item->classes)>0) {
2331                 $divclasses[] = join(' ', $item->classes);
2332             }
2333             $divattr = array('class'=>join(' ', $divclasses));
2334             if (!empty($item->id)) {
2335                 $divattr['id'] = $item->id;
2336             }
2337             $content = $this->output_tag('p', $divattr, $content) . $this->tree_block_contents($item->children);
2338             if (!empty($item->preceedwithhr) && $item->preceedwithhr===true) {
2339                 $content = $this->output_tag('hr', array(), null).$content;
2340             }
2341             $content = $this->output_tag('li', $liattr, $content);
2342             $lis[] = $content;
2343         }
2344         return $this->output_tag('ul', $attrs, implode("\n", $lis));
2345     }
2347     /**
2348      * Return the navbar content so that it can be echoed out by the layout
2349      * @return string XHTML navbar
2350      */
2351     public function navbar() {
2352         return $this->page->navbar->content();
2353     }
2355     /**
2356      * Checks to see if there are any items on the navbar object
2357      * @return bool true if there are, false if not
2358      */
2359     public function has_navbar() {
2360         return $this->page->navbar->has_items();
2361     }
2365 /// RENDERERS
2367 /**
2368  * A renderer that generates output for command-line scripts.
2369  *
2370  * The implementation of this renderer is probably incomplete.
2371  *
2372  * @copyright 2009 Tim Hunt
2373  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2374  * @since     Moodle 2.0
2375  */
2376 class cli_core_renderer extends moodle_core_renderer {
2377     /**
2378      * Returns the page header.
2379      * @return string HTML fragment
2380      */
2381     public function header() {
2382         output_starting_hook();
2383         return $this->page->heading . "\n";
2384     }
2386     /**
2387      * Returns a template fragment representing a Heading.
2388      * @param string $text The text of the heading
2389      * @param int $level The level of importance of the heading
2390      * @param string $classes A space-separated list of CSS classes
2391      * @param string $id An optional ID
2392      * @return string A template fragment for a heading
2393      */
2394     public function heading($text, $level, $classes = 'main', $id = '') {
2395         $text .= "\n";
2396         switch ($level) {
2397             case 1:
2398                 return '=>' . $text;
2399             case 2:
2400                 return '-->' . $text;
2401             default:
2402                 return $text;
2403         }
2404     }
2406     /**
2407      * Returns a template fragment representing a fatal error.
2408      * @param string $message The message to output
2409      * @param string $moreinfourl URL where more info can be found about the error
2410      * @param string $link Link for the Continue button
2411      * @param array $backtrace The execution backtrace
2412      * @param string $debuginfo Debugging information
2413      * @param bool $showerrordebugwarning Whether or not to show a debugging warning
2414      * @return string A template fragment for a fatal error
2415      */
2416     public function fatal_error($message, $moreinfourl, $link, $backtrace,
2417                 $debuginfo = null, $showerrordebugwarning = false) {
2418         $output = "!!! $message !!!\n";
2420         if (debugging('', DEBUG_DEVELOPER)) {
2421             if (!empty($debuginfo)) {
2422                 $this->notification($debuginfo, 'notifytiny');
2423             }
2424             if (!empty($backtrace)) {
2425                 $this->notification('Stack trace: ' . format_backtrace($backtrace, true), 'notifytiny');
2426             }
2427         }
2428     }
2430     /**
2431      * Returns a template fragment representing a notification.
2432      * @param string $message The message to include
2433      * @param string $classes A space-separated list of CSS classes
2434      * @return string A template fragment for a notification
2435      */
2436     public function notification($message, $classes = 'notifyproblem') {
2437         $message = clean_text($message);
2438         if ($classes === 'notifysuccess') {
2439             return "++ $message ++\n";
2440         }
2441         return "!! $message !!\n";
2442     }