webservice MDL-12886 refactor web service description + implement clean_function_para...
[moodle.git] / lib / outputrenderers.php
CommitLineData
d9c8f425 1<?php
2
3// This file is part of Moodle - http://moodle.org/
4//
5// Moodle is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// Moodle is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17
18/**
19 * Classes for rendering HTML output for Moodle.
20 *
21 * Please see http://docs.moodle.org/en/Developement:How_Moodle_outputs_HTML
22 * for an overview.
23 *
24 * @package moodlecore
25 * @copyright 2009 Tim Hunt
26 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
27 */
28
29/**
30 * Simple base class for Moodle renderers.
31 *
32 * Tracks the xhtml_container_stack to use, which is passed in in the constructor.
33 *
34 * Also has methods to facilitate generating HTML output.
35 *
36 * @copyright 2009 Tim Hunt
37 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
38 * @since Moodle 2.0
39 */
40class 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;
45
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 }
54
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 }
62
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 }
74
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 }
84
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 }
93
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 }
103
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 }
114
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 }
122
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 }
139
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 }
151
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 }
164
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 }
178
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 }
197
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 }
219}
220
221
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 */
239class 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;
245
246 /**
247 * Magic word used when breaking apart container templates to implement
248 * _start and _end methods.
249 */
250 const CONTENTSTOKEN = '-@#-Contents-go-here-#@-';
251
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 }
263
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 }
279
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 }
292
293 // Find the template file for this method.
294 $template = $this->find_template($method);
295
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 }
310
311 // Actually render the template.
312 return $this->render_template($template, $namedarguments);
313 }
314
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.
326
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;
337
338 // And the parameters from the function call.
339 extract($_namedarguments);
340
341 // Include the template, capturing the output.
342 ob_start();
343 include($_template);
344 $_result = ob_get_contents();
345 ob_end_clean();
346
347 return $_result;
348 }
349
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 }
365
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 }
380
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 }
391
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 }
398
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 }
405}
406
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 */
414class 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 = '';
425
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;
433
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';
436
437 if (empty($CFG->xmlstrictheaders)) {
438 return $doctype;
439 }
440
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';
446
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";
451
452 } else {
453 $prolog = '';
454 }
455
456 return $prolog . $doctype;
457 }
458
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 }
467
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;
485
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 }
491
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));
499
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 }
515
516 // Add the meta tags from the themes if any were requested.
517 $output .= $this->page->theme->get_meta_tags($this->page);
518
519 // Get any HTML from the page_requirements_manager.
520 $output .= $this->page->requires->get_head_code();
521
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 }
527
528 return $output;
529 }
530
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 }
539
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;
548
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 }
565
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 }
577
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 }
587
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;
594
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>';
600
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>';
606
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>';
610
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 }
616
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);
639
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 }
675
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 * @param string $navigation legacy, like the old parameter to print_header. Will be
689 * removed when there is a $PAGE->... replacement.
690 * @param string $menu legacy, like the old parameter to print_header. Will be
691 * removed when there is a $PAGE->... replacement.
692 * @return string HTML that you must output this, preferably immediately.
693 */
694 public function header($navigation = '', $menu='') {
695 // TODO remove $navigation and $menu arguments - replace with $PAGE->navigation
696 global $USER, $CFG;
697
698 $this->page->set_state(moodle_page::STATE_PRINTING_HEADER);
699
700 // Find the appropriate page template, based on $this->page->generaltype.
701 $templatefile = $this->page->theme->template_for_page($this->page->generaltype);
702 if ($templatefile) {
703 // Render the template.
704 $template = $this->render_page_template($templatefile, $menu, $navigation);
705 } else {
706 // New style template not found, fall back to using header.html and footer.html.
707 $template = $this->handle_legacy_theme($navigation, $menu);
708 }
709
710 // Slice the template output into header and footer.
711 $cutpos = strpos($template, self::MAIN_CONTENT_TOKEN);
712 if ($cutpos === false) {
713 throw new coding_exception('Layout template ' . $templatefile .
714 ' does not contain the string "' . self::MAIN_CONTENT_TOKEN . '".');
715 }
716 $header = substr($template, 0, $cutpos);
717 $footer = substr($template, $cutpos + strlen(self::MAIN_CONTENT_TOKEN));
718
719 if (empty($this->contenttype)) {
720 debugging('The layout template did not call $OUTPUT->doctype()');
721 $this->doctype();
722 }
723
724 send_headers($this->contenttype, $this->page->cacheable);
725 $this->opencontainers->push('header/footer', $footer);
726 $this->page->set_state(moodle_page::STATE_IN_BODY);
727 return $header . $this->skip_link_target();
728 }
729
730 /**
731 * Renders and outputs the page template.
732 * @param string $templatefile The name of the template's file
733 * @param array $menu The menu that will be used in the included file
734 * @param array $navigation The navigation that will be used in the included file
735 * @return string HTML code
736 */
737 protected function render_page_template($templatefile, $menu, $navigation) {
738 global $CFG, $SITE, $THEME, $USER;
739 // The next lines are a bit tricky. The point is, here we are in a method
740 // of a renderer class, and this object may, or may not, be the same as
741 // the global $OUTPUT object. When rendering the template, we want to use
742 // this object. However, people writing Moodle code expect the current
743 // renderer to be called $OUTPUT, not $this, so define a variable called
744 // $OUTPUT pointing at $this. The same comment applies to $PAGE and $COURSE.
745 $OUTPUT = $this;
746 $PAGE = $this->page;
747 $COURSE = $this->page->course;
748
749 ob_start();
750 include($templatefile);
751 $template = ob_get_contents();
752 ob_end_clean();
753 return $template;
754 }
755
756 /**
757 * Renders and outputs a legacy template.
758 * @param array $navigation The navigation that will be used in the included file
759 * @param array $menu The menu that will be used in the included file
760 * @return string HTML code
761 */
762 protected function handle_legacy_theme($navigation, $menu) {
763 global $CFG, $SITE, $USER;
764 // Set a pretend global from the properties of this class.
765 // See the comment in render_page_template for a fuller explanation.
766 $COURSE = $this->page->course;
767 $THEME = $this->page->theme;
768
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
780 $meta = $this->standard_head_html();
781 // The next line is a nasty hack. having set $meta to standard_head_html, we have already
782 // got the contents of include($CFG->javascript). However, legacy themes are going to
783 // include($CFG->javascript) again. We want to make sure that when they do, nothing is output.
784 $CFG->javascript = $CFG->libdir . '/emptyfile.php';
785
786 // Set up local variables that footer.html expects.
787 $homelink = $this->home_link();
788 $loggedinas = $this->login_info();
789 $course = $this->page->course;
790 $performanceinfo = self::PERFORMANCE_INFO_TOKEN;
791
792 if (!$menu && $navigation) {
793 $menu = $loggedinas;
794 }
795
796 if (!empty($this->page->theme->layouttable)) {
797 $lt = $this->page->theme->layouttable;
798 } else {
799 $lt = array('left', 'middle', 'right');
800 }
801
802 if (!empty($this->page->theme->block_l_max_width)) {
803 $preferredwidthleft = $this->page->theme->block_l_max_width;
804 } else {
805 $preferredwidthleft = 210;
806 }
807 if (!empty($this->page->theme->block_r_max_width)) {
808 $preferredwidthright = $this->page->theme->block_r_max_width;
809 } else {
810 $preferredwidthright = 210;
811 }
812
813 ob_start();
814 include($this->page->theme->dir . '/header.html');
815
816 echo '<table id="layout-table"><tr>';
817 foreach ($lt as $column) {
818 if ($column == 'left' && $this->page->blocks->region_has_content(BLOCK_POS_LEFT, $this)) {
819 echo '<td id="left-column" class="block-region" style="width: ' . $preferredwidthright . 'px; vertical-align: top;">';
820 echo $this->container_start();
821 echo $this->blocks_for_region(BLOCK_POS_LEFT);
822 echo $this->container_end();
823 echo '</td>';
824
825 } else if ($column == 'middle') {
826 echo '<td id="middle-column" style="vertical-align: top;">';
827 echo $this->container_start();
828 echo $this->skip_link_target();
829 echo self::MAIN_CONTENT_TOKEN;
830 echo $this->container_end();
831 echo '</td>';
832
833 } else if ($column == 'right' && $this->page->blocks->region_has_content(BLOCK_POS_RIGHT, $this)) {
834 echo '<td id="right-column" class="block-region" style="width: ' . $preferredwidthright . 'px; vertical-align: top;">';
835 echo $this->container_start();
836 echo $this->blocks_for_region(BLOCK_POS_RIGHT);
837 echo $this->container_end();
838 echo '</td>';
839 }
840 }
841 echo '</tr></table>';
842
843 $menu = str_replace('navmenu', 'navmenufooter', $menu);
844 include($THEME->dir . '/footer.html');
845
846 $output = ob_get_contents();
847 ob_end_clean();
848
849 // Put in the start of body code. Bit of a hack, put it in before the first
850 // <div or <table.
851 $divpos = strpos($output, '<div');
852 $tablepos = strpos($output, '<table');
853 if ($divpos === false || ($tablepos !== false && $tablepos < $divpos)) {
854 $pos = $tablepos;
855 } else {
856 $pos = $divpos;
857 }
858 $output = substr($output, 0, $divpos) . $this->standard_top_of_body_html() .
859 substr($output, $divpos);
860
861 // Put in the end token before the end of body.
862 $output = str_replace('</body>', self::END_HTML_TOKEN . '</body>', $output);
863
864 // Make sure we use the correct doctype.
865 $output = preg_replace('/(<!DOCTYPE.+?>)/s', $this->doctype(), $output);
866
867 return $output;
868 }
869
870 /**
871 * Outputs the page's footer
872 * @return string HTML fragment
873 */
874 public function footer() {
875 $output = $this->opencontainers->pop_all_but_last(true);
876
877 $footer = $this->opencontainers->pop('header/footer');
878
879 // Provide some performance info if required
880 $performanceinfo = '';
881 if (defined('MDL_PERF') || (!empty($CFG->perfdebug) and $CFG->perfdebug > 7)) {
882 $perf = get_performance_info();
883 if (defined('MDL_PERFTOLOG') && !function_exists('register_shutdown_function')) {
884 error_log("PERF: " . $perf['txt']);
885 }
886 if (defined('MDL_PERFTOFOOT') || debugging() || $CFG->perfdebug > 7) {
887 $performanceinfo = $perf['html'];
888 }
889 }
890 $footer = str_replace(self::PERFORMANCE_INFO_TOKEN, $performanceinfo, $footer);
891
892 $footer = str_replace(self::END_HTML_TOKEN, $this->page->requires->get_end_code(), $footer);
893
894 $this->page->set_state(moodle_page::STATE_DONE);
895
896
897 return $output . $footer;
898 }
899
900 /**
901 * Output the row of editing icons for a block, as defined by the controls array.
902 * @param array $controls an array like {@link block_contents::$controls}.
903 * @return HTML fragment.
904 */
905 public function block_controls($controls) {
906 if (empty($controls)) {
907 return '';
908 }
909 $controlshtml = array();
910 foreach ($controls as $control) {
911 $controlshtml[] = $this->output_tag('a', array('class' => 'icon',
912 'title' => $control['caption'], 'href' => $control['url']),
913 $this->output_empty_tag('img', array('src' => $this->old_icon_url($control['icon']),
914 'alt' => $control['caption'])));
915 }
916 return $this->output_tag('div', array('class' => 'commands'), implode('', $controlshtml));
917 }
918
919 /**
920 * Prints a nice side block with an optional header.
921 *
922 * The content is described
923 * by a {@link block_contents} object.
924 *
925 * @param block_contents $bc HTML for the content
926 * @param string $region the region the block is appearing in.
927 * @return string the HTML to be output.
928 */
929 function block($bc, $region) {
930 $bc = clone($bc); // Avoid messing up the object passed in.
931 $bc->prepare();
932
933 $skiptitle = strip_tags($bc->title);
934 if (empty($skiptitle)) {
935 $output = '';
936 $skipdest = '';
937 } else {
938 $output = $this->output_tag('a', array('href' => '#sb-' . $bc->skipid, 'class' => 'skip-block'),
939 get_string('skipa', 'access', $skiptitle));
940 $skipdest = $this->output_tag('span', array('id' => 'sb-' . $bc->skipid, 'class' => 'skip-block-to'), '');
941 }
942
943 $bc->attributes['id'] = $bc->id;
944 $bc->attributes['class'] = $bc->get_classes_string();
945 $output .= $this->output_start_tag('div', $bc->attributes);
946
947 $controlshtml = $this->block_controls($bc->controls);
948
949 $title = '';
950 if ($bc->title) {
951 $title = $this->output_tag('h2', null, $bc->title);
952 }
953
954 if ($title || $controlshtml) {
955 $output .= $this->output_tag('div', array('class' => 'header'),
956 $this->output_tag('div', array('class' => 'title'),
957 $title . $controlshtml));
958 }
959
960 $output .= $this->output_start_tag('div', array('class' => 'content'));
961 $output .= $bc->content;
962
963 if ($bc->footer) {
964 $output .= $this->output_tag('div', array('class' => 'footer'), $bc->footer);
965 }
966
967 $output .= $this->output_end_tag('div');
968 $output .= $this->output_end_tag('div');
969
970 if ($bc->annotation) {
971 $output .= $this->output_tag('div', array('class' => 'blockannotation'), $bc->annotation);
972 }
973 $output .= $skipdest;
974
975 $this->init_block_hider_js($bc);
976 return $output;
977 }
978
979 /**
980 * Calls the JS require function to hide a block.
981 * @param block_contents $bc A block_contents object
982 * @return void
983 */
984 protected function init_block_hider_js($bc) {
985 if ($bc->collapsible != block_contents::NOT_HIDEABLE) {
986 $userpref = 'block' . $bc->blockinstanceid . 'hidden';
987 user_preference_allow_ajax_update($userpref, PARAM_BOOL);
988 $this->page->requires->yui_lib('dom');
989 $this->page->requires->yui_lib('event');
990 $plaintitle = strip_tags($bc->title);
991 $this->page->requires->js_function_call('new block_hider', array($bc->id, $userpref,
992 get_string('hideblocka', 'access', $plaintitle), get_string('showblocka', 'access', $plaintitle),
993 $this->old_icon_url('t/switch_minus'), $this->old_icon_url('t/switch_plus')));
994 }
995 }
996
997 /**
998 * Render the contents of a block_list.
999 * @param array $icons the icon for each item.
1000 * @param array $items the content of each item.
1001 * @return string HTML
1002 */
1003 public function list_block_contents($icons, $items) {
1004 $row = 0;
1005 $lis = array();
1006 foreach ($items as $key => $string) {
1007 $item = $this->output_start_tag('li', array('class' => 'r' . $row));
1008 if ($icons) {
d0b7a11f 1009 if (key_exists($key, $icons)) { //test if the content has an assigned icon
1010 $item .= $this->output_tag('div', array('class' => 'icon column c0'), $icons[$key]);
1011 }
d9c8f425 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 }
1020
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);
1028
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 }
1041
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 }
1051
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();
1061
1062 if (is_a($link, 'html_link')) {
1063 $link = clone($link);
db49be13 1064
1065 if ($link->has_action('popup_action')) {
1066 return $this->link_to_popup($link);
1067 }
1068
d9c8f425 1069 $link->prepare();
1070 $this->prepare_event_handlers($link);
1071 $attributes['href'] = prepare_url($link->url);
1072 $attributes['class'] = $link->get_classes_string();
1073 $attributes['title'] = $link->title;
1074 $attributes['id'] = $link->id;
1075
1076 $text = $link->text;
1077
1078 } else if (empty($text)) {
1079 throw new coding_exception('$OUTPUT->link() must have a string as second parameter if the first param ($link) is a string');
1080
1081 } else {
1082 $attributes['href'] = prepare_url($link);
1083 }
1084
1085 return $this->output_tag('a', $attributes, $text);
1086 }
1087
1088 /**
1089 * Print a message along with button choices for Continue/Cancel. Labels default to Yes(Continue)/No(Cancel).
1090 * If a string or moodle_url is given instead of a html_button, method defaults to post and text to Yes/No
1091 * @param string $message The question to ask the user
1092 * @param mixed $continue The html_form component representing the Continue answer. Can also be a moodle_url or string URL
1093 * @param mixed $cancel The html_form component representing the Cancel answer. Can also be a moodle_url or string URL
1094 * @return string HTML fragment
1095 */
1096 public function confirm($message, $continue, $cancel) {
1097 if ($continue instanceof html_form) {
1098 $continue = clone($continue);
1099 } else if (is_string($continue)) {
1100 $continueform = new html_form();
1101 $continueform->url = new moodle_url($continue);
1102 $continue = $continueform;
1103 } else if ($continue instanceof moodle_url) {
1104 $continueform = new html_form();
1105 $continueform->url = $continue;
1106 $continue = $continueform;
1107 } else {
1108 throw new coding_exception('The continue param to $OUTPUT->confirm must be either a URL (string/moodle_url) or a html_form object.');
1109 }
1110
1111 if ($cancel instanceof html_form) {
1112 $cancel = clone($cancel);
1113 } else if (is_string($cancel)) {
1114 $cancelform = new html_form();
1115 $cancelform->url = new moodle_url($cancel);
1116 $cancel = $cancelform;
1117 } else if ($cancel instanceof moodle_url) {
1118 $cancelform = new html_form();
1119 $cancelform->url = $cancel;
1120 $cancel = $cancelform;
1121 } else {
1122 throw new coding_exception('The cancel param to $OUTPUT->confirm must be either a URL (string/moodle_url) or a html_form object.');
1123 }
1124
1125 if (empty($continue->button->text)) {
1126 $continue->button->text = get_string('yes');
1127 }
1128 if (empty($cancel->button->text)) {
1129 $cancel->button->text = get_string('no');
1130 }
1131
1132 $output = $this->box_start('generalbox', 'notice');
1133 $output .= $this->output_tag('p', array(), $message);
1134 $output .= $this->output_tag('div', array('class' => 'buttons'), $this->button($continue) . $this->button($cancel));
1135 $output .= $this->box_end();
1136 return $output;
1137 }
1138
1139 /**
1140 * Given a html_form object, outputs an <input> tag within a form that uses the object's attributes.
1141 *
1142 * @param html_form $form A html_form object
1143 * @return string HTML fragment
1144 */
1145 public function button($form) {
1146 if (empty($form->button) or !($form->button instanceof html_button)) {
1147 throw new coding_exception('$OUTPUT->button($form) requires $form to have a button (html_button) value');
1148 }
1149 $form = clone($form);
1150 $form->button->prepare();
1151
1152 $this->prepare_event_handlers($form->button);
1153
1154 $buttonattributes = array('class' => $form->button->get_classes_string(),
1155 'type' => 'submit',
1156 'value' => $form->button->text,
1157 'disabled' => $form->button->disabled,
1158 'id' => $form->button->id);
1159
1160 $buttonoutput = $this->output_empty_tag('input', $buttonattributes);
1161
1162 // Removing the button so it doesn't get output again
1163 unset($form->button);
1164
7a5c78e0 1165 return $this->output_tag('div', array('class' => 'singlebutton'), $this->form($form, $buttonoutput));
d9c8f425 1166 }
1167
1168 /**
1169 * Given a html_form component and an optional rendered submit button,
1170 * outputs a HTML form with correct divs and inputs and a single submit button.
1171 * This doesn't render any other visible inputs. Use moodleforms for these.
1172 * @param html_form $form A html_form instance
1173 * @param string $contents HTML fragment to put inside the form. If given, must contain at least the submit button.
1174 * @return string HTML fragment
1175 */
1176 public function form($form, $contents=null) {
1177 $form = clone($form);
1178 $form->prepare();
1179 $this->prepare_event_handlers($form);
1180 $buttonoutput = null;
1181
1182 if (empty($contents) && !empty($form->button)) {
1183 debugging("You probably want to use \$OUTPUT->button(\$form), please read that function's documentation", DEBUG_DEVELOPER);
1184 } else if (empty($contents)) {
1185 $contents = $this->output_empty_tag('input', array('type' => 'submit', 'value' => get_string('ok')));
1186 } else if (!empty($form->button)) {
1187 $form->button->prepare();
d9c8f425 1188 $this->prepare_event_handlers($form->button);
1189
1190 $buttonattributes = array('class' => $form->button->get_classes_string(),
1191 'type' => 'submit',
1192 'value' => $form->button->text,
1193 'disabled' => $form->button->disabled,
1194 'id' => $form->button->id);
1195
b65bfc3e 1196 $buttonoutput = $this->output_empty_tag('input', $buttonattributes);
1197
1198 // Hide the submit button if the button has a JS submit action
1199 if ($form->jssubmitaction) {
1200 $buttonoutput = $this->output_start_tag('div', array('id' => "noscript$form->id")) . $buttonoutput . $this->output_end_tag('div');
1201 $this->page->requires->js_function_call('hide_item', array("noscript$form->id"));
1202 }
d9c8f425 1203
1204 }
1205
1206 $hiddenoutput = '';
1207
1208 foreach ($form->url->params() as $var => $val) {
1209 $hiddenoutput .= $this->output_empty_tag('input', array('type' => 'hidden', 'name' => $var, 'value' => $val));
1210 }
1211
1212 $formattributes = array(
1213 'method' => $form->method,
1214 'action' => prepare_url($form->url, true),
1215 'id' => $form->id,
1216 'class' => $form->get_classes_string());
1217
1218 $divoutput = $this->output_tag('div', array(), $hiddenoutput . $contents . $buttonoutput);
7a5c78e0 1219 $output = $this->output_tag('form', $formattributes, $divoutput);
d9c8f425 1220
1221 return $output;
1222 }
1223
1224 /**
1225 * Returns a string containing a link to the user documentation.
1226 * Also contains an icon by default. Shown to teachers and admin only.
1227 * @param string $path The page link after doc root and language, no leading slash.
1228 * @param string $text The text to be displayed for the link
1229 * @param string $iconpath The path to the icon to be displayed
1230 */
1231 public function doc_link($path, $text=false, $iconpath=false) {
1232 global $CFG, $OUTPUT;
beb56299 1233 $icon = new moodle_action_icon();
d9c8f425 1234 $icon->linktext = $text;
1235 $icon->image->alt = $text;
1236 $icon->image->add_class('iconhelp');
1237 $icon->link->url = new moodle_url(get_docs_url($path));
1238
1239 if (!empty($iconpath)) {
1240 $icon->image->src = $iconpath;
1241 } else {
1242 $icon->image->src = $this->old_icon_url('docs');
1243 }
1244
1245 if (!empty($CFG->doctonewwindow)) {
1246 $icon->actions[] = new popup_action('click', $icon->link->url);
1247 }
1248
1249 return $this->action_icon($icon);
1250
1251 }
1252
1253 /**
beb56299 1254 * Given a moodle_action_icon object, outputs an image linking to an action (URL or AJAX).
d9c8f425 1255 *
beb56299 1256 * @param moodle_action_icon $icon A moodle_action_icon object
d9c8f425 1257 * @return string HTML fragment
1258 */
1259 public function action_icon($icon) {
1260 $icon = clone($icon);
1261 $icon->prepare();
1262 $imageoutput = $this->image($icon->image);
1263
1264 if ($icon->linktext) {
1265 $imageoutput .= $icon->linktext;
1266 }
1267 $icon->link->text = $imageoutput;
1268
1269 return $this->link($icon->link);
1270 }
1271
1272 /*
1273 * Centered heading with attached help button (same title text)
1274 * and optional icon attached
94056d9d 1275 * @param moodle_help_icon $helpicon A moodle_help_icon object
d9c8f425 1276 * @param mixed $image An image URL or a html_image object
1277 * @return string HTML fragment
1278 */
1279 public function heading_with_help($helpicon, $image=false) {
1280 if (!($image instanceof html_image) && !empty($image)) {
1281 $htmlimage = new html_image();
1282 $htmlimage->src = $image;
1283 $image = $htmlimage;
1284 }
1285 return $this->container($this->image($image) . $this->heading($helpicon->text, 2, 'main help') . $this->help_icon($helpicon), 'heading-with-help');
1286 }
1287
1288 /**
1289 * Print a help icon.
1290 *
94056d9d 1291 * @param moodle_help_icon $helpicon A moodle_help_icon object, subclass of html_link
d9c8f425 1292 *
1293 * @return string HTML fragment
1294 */
1295 public function help_icon($icon) {
1296 global $COURSE;
1297 $icon = clone($icon);
1298 $icon->prepare();
1299
1300 $popup = new popup_action('click', $icon->link->url);
1301 $icon->link->add_action($popup);
1302
1303 $image = null;
1304
1305 if (!empty($icon->image)) {
1306 $image = $icon->image;
1307 $image->add_class('iconhelp');
1308 }
1309
1310 return $this->output_tag('span', array('class' => 'helplink'), $this->link_to_popup($icon->link, $image));
1311 }
1312
1313 /**
1314 * Creates and returns a button to a popup window
1315 *
1316 * @param html_link $link Subclass of moodle_html_component
1317 * @param moodle_popup $popup A moodle_popup object
1318 * @param html_image $image An optional image replacing the link text
1319 *
1320 * @return string HTML fragment
1321 */
1322 public function link_to_popup($link, $image=null) {
1323 $link = clone($link);
1324 $link->prepare();
1325
1326 $this->prepare_event_handlers($link);
1327
1328 if (empty($link->url)) {
1329 throw new coding_exception('Called $OUTPUT->link_to_popup($link) method without $link->url set.');
1330 }
1331
1332 $linkurl = prepare_url($link->url);
1333
1334 $tagoptions = array(
1335 'title' => $link->title,
1336 'id' => $link->id,
1337 'href' => ($linkurl) ? $linkurl : prepare_url($popup->url),
1338 'class' => $link->get_classes_string());
1339
1340 // Use image if one is given
1341 if (!empty($image) && $image instanceof html_image) {
1342
b65bfc3e 1343 if (empty($image->alt) || $image->alt == HTML_ATTR_EMPTY) {
d9c8f425 1344 $image->alt = $link->text;
b65bfc3e 1345 $image->title = $link->text;
d9c8f425 1346 }
1347
1348 $link->text = $this->image($image);
1349
1350 if (!empty($link->linktext)) {
1351 $link->text = "$link->title &nbsp; $link->text";
1352 }
1353 }
1354
1355 return $this->output_tag('a', $tagoptions, $link->text);
1356 }
1357
1358 /**
1359 * Creates and returns a spacer image with optional line break.
1360 *
1361 * @param html_image $image Subclass of moodle_html_component
1362 *
1363 * @return string HTML fragment
1364 */
1365 public function spacer($image) {
1366 $image = clone($image);
d9c8f425 1367
1368 if (empty($image->src)) {
1369 $image->src = $this->old_icon_url('spacer');
1370 }
1371
b65bfc3e 1372 $image->prepare();
1373 $image->add_class('spacer');
1374
d9c8f425 1375 $output = $this->image($image);
1376
1377 return $output;
1378 }
1379
1380 /**
1381 * Creates and returns an image.
1382 *
1383 * @param html_image $image Subclass of moodle_html_component
1384 *
1385 * @return string HTML fragment
1386 */
1387 public function image($image) {
1388 if ($image === false) {
1389 return false;
1390 }
1391
1392 $image = clone($image);
1393 $image->prepare();
1394
1395 $this->prepare_event_handlers($image);
1396
1397 $attributes = array('class' => $image->get_classes_string(),
1398 'src' => prepare_url($image->src),
1399 'alt' => $image->alt,
1400 'style' => $image->style,
1401 'title' => $image->title,
1402 'id' => $image->id);
1403
1404 if (!empty($image->height) || !empty($image->width)) {
1405 $attributes['style'] .= $this->prepare_legacy_width_and_height($image);
1406 }
1407 return $this->output_empty_tag('img', $attributes);
1408 }
1409
1410 /**
1411 * Print the specified user's avatar.
1412 *
1413 * This method can be used in two ways:
1414 * <pre>
1415 * // Option 1:
beb56299 1416 * $userpic = new moodle_user_picture();
d9c8f425 1417 * // Set properties of $userpic
1418 * $OUTPUT->user_picture($userpic);
1419 *
1420 * // Option 2: (shortcut for simple cases)
1421 * // $user has come from the DB and has fields id, picture, imagealt, firstname and lastname
1422 * $OUTPUT->user_picture($user, $COURSE->id);
1423 * </pre>
1424 *
1425 * @param object $userpic Object with at least fields id, picture, imagealt, firstname, lastname
1426 * If any of these are missing, or if a userid is passed, the database is queried. Avoid this
1427 * if at all possible, particularly for reports. It is very bad for performance.
beb56299 1428 * A moodle_user_picture object is a better parameter.
d9c8f425 1429 * @param int $courseid courseid Used when constructing the link to the user's profile. Required if $userpic
beb56299 1430 * is not a moodle_user_picture object
d9c8f425 1431 * @return string HTML fragment
1432 */
1433 public function user_picture($userpic, $courseid=null) {
beb56299 1434 // Instantiate a moodle_user_picture object if $user is not already one
1435 if (!($userpic instanceof moodle_user_picture)) {
d9c8f425 1436 if (empty($courseid)) {
1437 throw new coding_exception('Called $OUTPUT->user_picture with a $user object but no $courseid.');
1438 }
1439
1440 $user = $userpic;
beb56299 1441 $userpic = new moodle_user_picture();
d9c8f425 1442 $userpic->user = $user;
1443 $userpic->courseid = $courseid;
1444 } else {
1445 $userpic = clone($userpic);
1446 }
1447
1448 $userpic->prepare();
1449
1450 $output = $this->image($userpic->image);
1451
1452 if (!empty($userpic->url)) {
1453 $actions = $userpic->get_actions();
1454 if ($userpic->popup && !empty($actions)) {
1455 $link = new html_link();
1456 $link->url = $userpic->url;
1457 $link->text = fullname($userpic->user);
1458 $link->title = fullname($userpic->user);
1459
1460 foreach ($actions as $action) {
1461 $link->add_action($action);
1462 }
1463 $output = $this->link_to_popup($link, $userpic->image);
1464 } else {
1465 $output = $this->link(prepare_url($userpic->url), $output);
1466 }
1467 }
1468
1469 return $output;
1470 }
1471
1472 /**
1473 * Prints the 'Update this Modulename' button that appears on module pages.
1474 *
1475 * @param string $cmid the course_module id.
1476 * @param string $modulename the module name, eg. "forum", "quiz" or "workshop"
1477 * @return string the HTML for the button, if this user has permission to edit it, else an empty string.
1478 */
1479 public function update_module_button($cmid, $modulename) {
1480 global $CFG;
1481 if (has_capability('moodle/course:manageactivities', get_context_instance(CONTEXT_MODULE, $cmid))) {
1482 $modulename = get_string('modulename', $modulename);
1483 $string = get_string('updatethis', '', $modulename);
1484
1485 $form = new html_form();
1486 $form->url = new moodle_url("$CFG->wwwroot/course/mod.php", array('update' => $cmid, 'return' => true, 'sesskey' => sesskey()));
1487 $form->button->text = $string;
1488 return $this->button($form);
1489 } else {
1490 return '';
1491 }
1492 }
1493
1494 /**
1495 * Prints a "Turn editing on/off" button in a form.
1496 * @param moodle_url $url The URL + params to send through when clicking the button
1497 * @return string HTML the button
1498 */
1499 public function edit_button(moodle_url $url) {
1500 global $USER;
1501 if (!empty($USER->editing)) {
1502 $string = get_string('turneditingoff');
1503 $edit = '0';
1504 } else {
1505 $string = get_string('turneditingon');
1506 $edit = '1';
1507 }
1508
1509 $form = new html_form();
1510 $form->url = $url;
1511 $form->url->param('edit', $edit);
1512 $form->button->text = $string;
1513
1514 return $this->button($form);
1515 }
1516
1517 /**
1518 * Outputs a HTML nested list
1519 *
1520 * @param html_list $list A html_list object
1521 * @return string HTML structure
1522 */
1523 public function htmllist($list) {
1524 $list = clone($list);
1525 $list->prepare();
1526
1527 $this->prepare_event_handlers($list);
1528
1529 if ($list->type == 'ordered') {
1530 $tag = 'ol';
1531 } else if ($list->type == 'unordered') {
1532 $tag = 'ul';
1533 }
1534
1535 $output = $this->output_start_tag($tag, array('class' => $list->get_classes_string()));
1536
1537 foreach ($list->items as $listitem) {
1538 if ($listitem instanceof html_list) {
b65bfc3e 1539 $output .= $this->output_start_tag('li', array()) . "\n";
1540 $output .= $this->htmllist($listitem) . "\n";
1541 $output .= $this->output_end_tag('li') . "\n";
d9c8f425 1542 } else if ($listitem instanceof html_list_item) {
1543 $listitem->prepare();
1544 $this->prepare_event_handlers($listitem);
b65bfc3e 1545 $output .= $this->output_tag('li', array('class' => $listitem->get_classes_string()), $listitem->value) . "\n";
d9c8f425 1546 }
1547 }
1548
b65bfc3e 1549 if ($list->text) {
1550 $output = $list->text . $output;
1551 }
1552
d9c8f425 1553 return $output . $this->output_end_tag($tag);
1554 }
1555
1556 /**
1557 * Prints a simple button to close a window
1558 *
1559 * @global objec)t
1560 * @param string $text The lang string for the button's label (already output from get_string())
1561 * @return string|void if $return is true, void otherwise
1562 */
7a5c78e0 1563 public function close_window_button($text='') {
d9c8f425 1564 if (empty($text)) {
1565 $text = get_string('closewindow');
1566 }
1567 $closeform = new html_form();
1568 $closeform->url = '#';
7a5c78e0 1569 $closeform->method = 'get';
d9c8f425 1570 $closeform->button->text = $text;
1571 $closeform->button->add_action('click', 'close_window');
1572 $closeform->button->prepare();
1573 return $this->container($this->button($closeform), 'closewindow');
1574 }
1575
1576 /**
1577 * Outputs a <select> menu or a list of radio/checkbox inputs.
1578 *
1579 * This method is extremely versatile, and can be used to output yes/no menus,
1580 * form-enclosed menus with automatic redirects when an option is selected,
1581 * descriptive labels and help icons. By default it just outputs a select
1582 * menu.
1583 *
7b1f2c82 1584 * To add a descriptive label, use html_select::set_label($text, $for) or
1585 * html_select::set_label($label) passing a html_label object
d9c8f425 1586 *
7b1f2c82 1587 * To add a help icon, use html_select::set_help($page, $text, $linktext) or
1588 * html_select::set_help($helpicon) passing a moodle_help_icon object
d9c8f425 1589 *
7b1f2c82 1590 * If you html_select::$rendertype to "radio", it will render radio buttons
d9c8f425 1591 * instead of a <select> menu, unless $multiple is true, in which case it
1592 * will render checkboxes.
1593 *
7b1f2c82 1594 * To surround the menu with a form, simply set html_select->form as a
d9c8f425 1595 * valid html_form object. Note that this function will NOT automatically
1596 * add a form for non-JS browsers. If you do not set one up, it assumes
1597 * that you are providing your own form in some other way.
1598 *
7b1f2c82 1599 * You can either call this function with a single html_select argument
d9c8f425 1600 * or, with a list of parameters, in which case those parameters are sent to
7b1f2c82 1601 * the html_select constructor.
d9c8f425 1602 *
7b1f2c82 1603 * @param html_select $select a html_select that describes
d9c8f425 1604 * the select menu you want output.
1605 * @return string the HTML for the <select>
1606 */
1607 public function select($select) {
1608 $select = clone($select);
1609 $select->prepare();
1610
1611 $this->prepare_event_handlers($select);
1612
1613 if (empty($select->id)) {
1614 $select->id = 'menu' . str_replace(array('[', ']'), '', $select->name);
1615 }
1616
1617 $attributes = array(
1618 'name' => $select->name,
1619 'id' => $select->id,
1620 'class' => $select->get_classes_string()
1621 );
1622 if ($select->disabled) {
1623 $attributes['disabled'] = 'disabled';
1624 }
1625 if ($select->tabindex) {
1626 $attributes['tabindex'] = $tabindex;
1627 }
1628
1629 if ($select->rendertype == 'menu' && $select->listbox) {
1630 if (is_integer($select->listbox)) {
1631 $size = $select->listbox;
1632 } else {
1633 $size = min($select->maxautosize, count($select->options));
1634 }
1635 $attributes['size'] = $size;
1636 if ($select->multiple) {
1637 $attributes['multiple'] = 'multiple';
1638 }
1639 }
1640
1641 $html = '';
1642
1643 if (!empty($select->label)) {
1644 $html .= $this->label($select->label);
1645 }
1646
94056d9d 1647 if (!empty($select->helpicon) && $select->helpicon instanceof moodle_help_icon) {
d9c8f425 1648 $html .= $this->help_icon($select->helpicon);
1649 }
1650
1651 if ($select->rendertype == 'menu') {
1652 $html .= $this->output_start_tag('select', $attributes) . "\n";
1653
1654 foreach ($select->options as $option) {
1655 // $OUTPUT->select_option detects if $option is an option or an optgroup
1656 $html .= $this->select_option($option);
1657 }
1658
1659 $html .= $this->output_end_tag('select') . "\n";
1660 } else if ($select->rendertype == 'radio') {
1661 $currentradio = 0;
1662 foreach ($select->options as $option) {
1663 $html .= $this->radio($option, $select->name);
1664 $currentradio++;
1665 }
1666 } else if ($select->rendertype == 'checkbox') {
1667 $currentcheckbox = 0;
1ae3767a 1668 // If only two choices are available, suggest using the checkbox method instead
1669 if (count($select->options) < 3 && !$select->multiple) {
1670 debugging('You are using $OUTPUT->select() to render two mutually exclusive choices using checkboxes. Please use $OUTPUT->checkbox(html_select_option) instead.', DEBUG_DEVELOPER);
1671 } else {
1672 foreach ($select->options as $option) {
1673 $html .= $this->checkbox($option, $select->name);
1674 $currentcheckbox++;
1675 }
d9c8f425 1676 }
1677 }
1678
1679 if (!empty($select->form) && $select->form instanceof html_form) {
1680 $html = $this->form($select->form, $html);
1681 }
1682
1683 return $html;
1684 }
1685
1686 /**
1687 * Outputs a <input type="radio" /> element. Optgroups are ignored, so do not
1688 * pass a html_select_optgroup as a param to this function.
1689 *
1690 * @param html_select_option $option a html_select_option
1691 * @return string the HTML for the <input type="radio">
1692 */
1693 public function radio($option, $name='unnamed') {
1ae3767a 1694 static $currentradio = array();
e57c283d 1695
1ae3767a 1696 if (empty($currentradio[$name])) {
1697 $currentradio[$name] = 0;
1698 }
1699
d9c8f425 1700 if ($option instanceof html_select_optgroup) {
1701 throw new coding_exception('$OUTPUT->radio($option) does not support a html_select_optgroup object as param.');
1702 } else if (!($option instanceof html_select_option)) {
1703 throw new coding_exception('$OUTPUT->radio($option) only accepts a html_select_option object as param.');
1704 }
1705 $option = clone($option);
1706 $option->prepare();
1707 $option->label->for = $option->id;
1708 $this->prepare_event_handlers($option);
1709
1ae3767a 1710 $output = $this->output_start_tag('span', array('class' => "radiogroup $name rb{$currentradio[$name]}")) . "\n";
d9c8f425 1711 $output .= $this->label($option->label);
1712
1713 if ($option->selected == 'selected') {
1714 $option->selected = 'checked';
1715 }
1716
1717 $output .= $this->output_empty_tag('input', array(
1718 'type' => 'radio',
1719 'value' => $option->value,
1720 'name' => $name,
1721 'alt' => $option->alt,
1722 'id' => $option->id,
1723 'class' => $option->get_classes_string(),
1724 'checked' => $option->selected));
1725
1726 $output .= $this->output_end_tag('span');
1ae3767a 1727 $currentradio[$name]++;
d9c8f425 1728 return $output;
1729 }
1730
1731 /**
1732 * Outputs a <input type="checkbox" /> element. Optgroups are ignored, so do not
1733 * pass a html_select_optgroup as a param to this function.
1734 *
1735 * @param html_select_option $option a html_select_option
1736 * @return string the HTML for the <input type="checkbox">
1737 */
1738 public function checkbox($option, $name='unnamed') {
1739 if ($option instanceof html_select_optgroup) {
1740 throw new coding_exception('$OUTPUT->checkbox($option) does not support a html_select_optgroup object as param.');
1741 } else if (!($option instanceof html_select_option)) {
1742 throw new coding_exception('$OUTPUT->checkbox($option) only accepts a html_select_option object as param.');
1743 }
1744 $option = clone($option);
1745 $option->prepare();
1746
1747 $option->label->for = $option->id;
1748 $this->prepare_event_handlers($option);
1749
1750 $output = $this->output_start_tag('span', array('class' => "checkbox $name")) . "\n";
1751
1752 if ($option->selected == 'selected') {
1753 $option->selected = 'checked';
1754 }
1755
1756 $output .= $this->output_empty_tag('input', array(
1757 'type' => 'checkbox',
1758 'value' => $option->value,
1759 'name' => $name,
1760 'id' => $option->id,
1761 'alt' => $option->alt,
1762 'class' => $option->get_classes_string(),
1763 'checked' => $option->selected));
1764 $output .= $this->label($option->label);
1765
1766 $output .= $this->output_end_tag('span');
1767
1768 return $output;
1769 }
1770
1771 /**
1772 * Output an <option> or <optgroup> element. If an optgroup element is detected,
1773 * this will recursively output its options as well.
1774 *
7b1f2c82 1775 * @param mixed $option a html_select_option or html_select_optgroup
d9c8f425 1776 * @return string the HTML for the <option> or <optgroup>
1777 */
1778 public function select_option($option) {
1779 $option = clone($option);
1780 $option->prepare();
1781 $this->prepare_event_handlers($option);
1782
1783 if ($option instanceof html_select_option) {
1784 return $this->output_tag('option', array(
1785 'value' => $option->value,
1786 'class' => $option->get_classes_string(),
1787 'selected' => $option->selected), $option->text);
1788 } else if ($option instanceof html_select_optgroup) {
1789 $output = $this->output_start_tag('optgroup', array('label' => $option->text, 'class' => $option->get_classes_string()));
1790 foreach ($option->options as $selectoption) {
1791 $output .= $this->select_option($selectoption);
1792 }
1793 $output .= $this->output_end_tag('optgroup');
1794 return $output;
1795 }
1796 }
1797
1798 /**
1799 * Output an <input type="text"> element
1800 *
1801 * @param html_field $field a html_field object
1802 * @return string the HTML for the <input>
1803 */
1804 public function textfield($field) {
1c1f64a2 1805 return $this->output_tag('span', array('class' => "textfield $field->name"), $this->field($field));
1806 }
1807
1808 /**
1809 * Output an <input/> element
1810 *
1811 * @param html_field $field a html_field object
1812 * @return string the HTML for the <input>
1813 */
1814 public function field($field) {
d9c8f425 1815 $field = clone($field);
1816 $field->prepare();
1817 $this->prepare_event_handlers($field);
1c1f64a2 1818 if (!empty($field->label->text)) {
3cc457db 1819 $output .= $this->label($field->label);
1820 }
1c1f64a2 1821 return $this->output_empty_tag('input', array(
1822 'type' => $field->type,
d9c8f425 1823 'name' => $field->name,
1824 'id' => $field->id,
1825 'value' => $field->value,
1826 'style' => $field->style,
1827 'alt' => $field->alt,
1c1f64a2 1828 'title' => $field->title,
d9c8f425 1829 'maxlength' => $field->maxlength));
d9c8f425 1830 }
1831
1832 /**
1833 * Outputs a <label> element.
1834 * @param html_label $label A html_label object
1835 * @return HTML fragment
1836 */
1837 public function label($label) {
1838 $label = clone($label);
1839 $label->prepare();
1840 $this->prepare_event_handlers($label);
1841 return $this->output_tag('label', array('for' => $label->for, 'class' => $label->get_classes_string()), $label->text);
1842 }
1843
1844 /**
1845 * Output an error message. By default wraps the error message in <span class="error">.
1846 * If the error message is blank, nothing is output.
1847 * @param string $message the error message.
1848 * @return string the HTML to output.
1849 */
1850 public function error_text($message) {
1851 if (empty($message)) {
1852 return '';
1853 }
1854 return $this->output_tag('span', array('class' => 'error'), $message);
1855 }
1856
1857 /**
1858 * Do not call this function directly.
1859 *
1860 * To terminate the current script with a fatal error, call the {@link print_error}
1861 * function, or throw an exception. Doing either of those things will then call this
1862 * function to display the error, before terminating the execution.
1863 *
1864 * @param string $message The message to output
1865 * @param string $moreinfourl URL where more info can be found about the error
1866 * @param string $link Link for the Continue button
1867 * @param array $backtrace The execution backtrace
1868 * @param string $debuginfo Debugging information
1869 * @param bool $showerrordebugwarning Whether or not to show a debugging warning
1870 * @return string the HTML to output.
1871 */
1872 public function fatal_error($message, $moreinfourl, $link, $backtrace,
1873 $debuginfo = null, $showerrordebugwarning = false) {
1874
1875 $output = '';
1876
e57c283d 1877 if (!debugging('', DEBUG_DEVELOPER)) {
1878 return false;
1879 }
1880
d9c8f425 1881 if ($this->has_started()) {
1882 $output .= $this->opencontainers->pop_all_but_last();
1883 } else {
1884 // Header not yet printed
1885 @header('HTTP/1.0 404 Not Found');
1886 $this->page->set_title(get_string('error'));
1887 $output .= $this->header();
1888 }
1889
1890 $message = '<p class="errormessage">' . $message . '</p>'.
1891 '<p class="errorcode"><a href="' . $moreinfourl . '">' .
1892 get_string('moreinformation') . '</a></p>';
1893 $output .= $this->box($message, 'errorbox');
1894
e57c283d 1895 if ($showerrordebugwarning) {
1896 $output .= $this->notification('error() is a deprecated function. ' .
1897 'Please call print_error() instead of error()', 'notifytiny');
1898 }
1899 if (!empty($debuginfo)) {
1900 $output .= $this->notification($debuginfo, 'notifytiny');
1901 }
1902 if (!empty($backtrace)) {
1903 $output .= $this->notification('Stack trace: ' .
1904 format_backtrace($backtrace), 'notifytiny');
d9c8f425 1905 }
1906
1907 if (!empty($link)) {
1908 $output .= $this->continue_button($link);
1909 }
1910
1911 $output .= $this->footer();
1912
1913 // Padding to encourage IE to display our error page, rather than its own.
1914 $output .= str_repeat(' ', 512);
1915
1916 return $output;
1917 }
1918
1919 /**
1920 * Output a notification (that is, a status message about something that has
1921 * just happened).
1922 *
1923 * @param string $message the message to print out
1924 * @param string $classes normally 'notifyproblem' or 'notifysuccess'.
1925 * @return string the HTML to output.
1926 */
1927 public function notification($message, $classes = 'notifyproblem') {
1928 return $this->output_tag('div', array('class' =>
1929 moodle_renderer_base::prepare_classes($classes)), clean_text($message));
1930 }
1931
1932 /**
1933 * Print a continue button that goes to a particular URL.
1934 *
1935 * @param string|moodle_url $link The url the button goes to.
1936 * @return string the HTML to output.
1937 */
1938 public function continue_button($link) {
1939 if (!is_a($link, 'moodle_url')) {
1940 $link = new moodle_url($link);
1941 }
1942 $form = new html_form();
1943 $form->url = $link;
1944 $form->values = $link->params();
1945 $form->button->text = get_string('continue');
1946 $form->method = 'get';
1947
1948 return $this->output_tag('div', array('class' => 'continuebutton') , $this->button($form));
1949 }
1950
1951 /**
1952 * Prints a single paging bar to provide access to other pages (usually in a search)
1953 *
1954 * @param string|moodle_url $link The url the button goes to.
1955 * @return string the HTML to output.
1956 */
1957 public function paging_bar($pagingbar) {
1958 $output = '';
1959 $pagingbar = clone($pagingbar);
1960 $pagingbar->prepare();
1961
1962 if ($pagingbar->totalcount > $pagingbar->perpage) {
1963 $output .= get_string('page') . ':';
1964
1965 if (!empty($pagingbar->previouslink)) {
1966 $output .= '&nbsp;(' . $this->link($pagingbar->previouslink) . ')&nbsp;';
1967 }
1968
1969 if (!empty($pagingbar->firstlink)) {
1970 $output .= '&nbsp;' . $this->link($pagingbar->firstlink) . '&nbsp;...';
1971 }
1972
1973 foreach ($pagingbar->pagelinks as $link) {
1974 if ($link instanceof html_link) {
1975 $output .= '&nbsp;&nbsp;' . $this->link($link);
1976 } else {
1977 $output .= "&nbsp;&nbsp;$link";
1978 }
1979 }
1980
1981 if (!empty($pagingbar->lastlink)) {
1982 $output .= '&nbsp;...' . $this->link($pagingbar->lastlink) . '&nbsp;';
1983 }
1984
1985 if (!empty($pagingbar->nextlink)) {
1986 $output .= '&nbsp;&nbsp;(' . $this->link($pagingbar->nextlink) . ')';
1987 }
1988 }
1989
1990 return $this->output_tag('div', array('class' => 'paging'), $output);
1991 }
1992
1993 /**
1994 * Render a HTML table
1995 *
1996 * @param object $table {@link html_table} instance containing all the information needed
1997 * @return string the HTML to output.
1998 */
1999 public function table(html_table $table) {
2000 $table = clone($table);
2001 $table->prepare();
2002 $attributes = array(
2003 'id' => $table->id,
2004 'width' => $table->width,
2005 'summary' => $table->summary,
2006 'cellpadding' => $table->cellpadding,
2007 'cellspacing' => $table->cellspacing,
2008 'class' => $table->get_classes_string());
2009 $output = $this->output_start_tag('table', $attributes) . "\n";
2010
2011 $countcols = 0;
2012
2013 if (!empty($table->head)) {
2014 $countcols = count($table->head);
2015 $output .= $this->output_start_tag('thead', array()) . "\n";
2016 $output .= $this->output_start_tag('tr', array()) . "\n";
2017 $keys = array_keys($table->head);
2018 $lastkey = end($keys);
2019 foreach ($table->head as $key => $heading) {
2020 $classes = array('header', 'c' . $key);
2021 if (isset($table->headspan[$key]) && $table->headspan[$key] > 1) {
2022 $colspan = $table->headspan[$key];
2023 $countcols += $table->headspan[$key] - 1;
2024 } else {
2025 $colspan = '';
2026 }
2027 if ($key == $lastkey) {
2028 $classes[] = 'lastcol';
2029 }
2030 if (isset($table->colclasses[$key])) {
2031 $classes[] = $table->colclasses[$key];
2032 }
2033 if ($table->rotateheaders) {
2034 // we need to wrap the heading content
2035 $heading = $this->output_tag('span', '', $heading);
2036 }
2037 $attributes = array(
2038 'style' => $table->align[$key] . $table->size[$key] . 'white-space:nowrap;',
2039 'class' => moodle_renderer_base::prepare_classes($classes),
2040 'scope' => 'col',
2041 'colspan' => $colspan);
2042 $output .= $this->output_tag('th', $attributes, $heading) . "\n";
2043 }
2044 $output .= $this->output_end_tag('tr') . "\n";
2045 $output .= $this->output_end_tag('thead') . "\n";
2046 }
2047
2048 if (!empty($table->data)) {
2049 $oddeven = 1;
2050 $keys = array_keys($table->data);
2051 $lastrowkey = end($keys);
2052 $output .= $this->output_start_tag('tbody', array()) . "\n";
2053
2054 foreach ($table->data as $key => $row) {
2055 if (($row === 'hr') && ($countcols)) {
2056 $output .= $this->output_tag('td', array('colspan' => $countcols),
2057 $this->output_tag('div', array('class' => 'tabledivider'), '')) . "\n";
2058 } else {
2059 // Convert array rows to html_table_rows and cell strings to html_table_cell objects
2060 if (!($row instanceof html_table_row)) {
2061 $newrow = new html_table_row();
2062
2063 foreach ($row as $unused => $item) {
2064 $cell = new html_table_cell();
2065 $cell->text = $item;
2066 $newrow->cells[] = $cell;
2067 }
2068 $row = $newrow;
2069 }
2070
2071 $oddeven = $oddeven ? 0 : 1;
2072 if (isset($table->rowclasses[$key])) {
2073 $row->add_classes(array_unique(moodle_html_component::clean_classes($table->rowclasses[$key])));
2074 }
2075
2076 $row->add_class('r' . $oddeven);
2077 if ($key == $lastrowkey) {
2078 $row->add_class('lastrow');
2079 }
2080
2081 $output .= $this->output_start_tag('tr', array('class' => $row->get_classes_string(), 'style' => $row->style, 'id' => $row->id)) . "\n";
2082 $keys2 = array_keys($row->cells);
2083 $lastkey = end($keys2);
2084
2085 foreach ($row->cells as $key => $cell) {
2086 if (isset($table->colclasses[$key])) {
2087 $cell->add_classes(array_unique(moodle_html_component::clean_classes($table->colclasses[$key])));
2088 }
2089
2090 $cell->add_classes('cell');
2091 $cell->add_classes('c' . $key);
2092 if ($key == $lastkey) {
2093 $cell->add_classes('lastcol');
2094 }
2095 $tdstyle = '';
2096 $tdstyle .= isset($table->align[$key]) ? $table->align[$key] : '';
2097 $tdstyle .= isset($table->size[$key]) ? $table->size[$key] : '';
2098 $tdstyle .= isset($table->wrap[$key]) ? $table->wrap[$key] : '';
2099 $tdattributes = array(
2100 'style' => $tdstyle . $cell->style,
2101 'colspan' => $cell->colspan,
2102 'rowspan' => $cell->rowspan,
2103 'id' => $cell->id,
2104 'class' => $cell->get_classes_string(),
2105 'abbr' => $cell->abbr,
2106 'scope' => $cell->scope);
1ae3767a 2107 $tagtype = 'td';
2108 if ($cell->header) {
2109 $tagtype = 'th';
2110 }
2111 $output .= $this->output_tag($tagtype, $tdattributes, $cell->text) . "\n";
d9c8f425 2112 }
2113 }
2114 $output .= $this->output_end_tag('tr') . "\n";
2115 }
2116 $output .= $this->output_end_tag('tbody') . "\n";
2117 }
2118 $output .= $this->output_end_tag('table') . "\n";
2119
2120 if ($table->rotateheaders && can_use_rotated_text()) {
2121 $this->page->requires->yui_lib('event');
2122 $this->page->requires->js('course/report/progress/textrotate.js');
2123 }
2124
2125 return $output;
2126 }
2127
2128 /**
2129 * Output the place a skip link goes to.
2130 * @param string $id The target name from the corresponding $PAGE->requires->skip_link_to($target) call.
2131 * @return string the HTML to output.
2132 */
2133 public function skip_link_target($id = '') {
2134 return $this->output_tag('span', array('id' => $id), '');
2135 }
2136
2137 /**
2138 * Outputs a heading
2139 * @param string $text The text of the heading
2140 * @param int $level The level of importance of the heading. Defaulting to 2
2141 * @param string $classes A space-separated list of CSS classes
2142 * @param string $id An optional ID
2143 * @return string the HTML to output.
2144 */
2145 public function heading($text, $level = 2, $classes = 'main', $id = '') {
2146 $level = (integer) $level;
2147 if ($level < 1 or $level > 6) {
2148 throw new coding_exception('Heading level must be an integer between 1 and 6.');
2149 }
2150 return $this->output_tag('h' . $level,
2151 array('id' => $id, 'class' => moodle_renderer_base::prepare_classes($classes)), $text);
2152 }
2153
2154 /**
2155 * Outputs a box.
2156 * @param string $contents The contents of the box
2157 * @param string $classes A space-separated list of CSS classes
2158 * @param string $id An optional ID
2159 * @return string the HTML to output.
2160 */
2161 public function box($contents, $classes = 'generalbox', $id = '') {
2162 return $this->box_start($classes, $id) . $contents . $this->box_end();
2163 }
2164
2165 /**
2166 * Outputs the opening section of a box.
2167 * @param string $classes A space-separated list of CSS classes
2168 * @param string $id An optional ID
2169 * @return string the HTML to output.
2170 */
2171 public function box_start($classes = 'generalbox', $id = '') {
2172 $this->opencontainers->push('box', $this->output_end_tag('div'));
2173 return $this->output_start_tag('div', array('id' => $id,
2174 'class' => 'box ' . moodle_renderer_base::prepare_classes($classes)));
2175 }
2176
2177 /**
2178 * Outputs the closing section of a box.
2179 * @return string the HTML to output.
2180 */
2181 public function box_end() {
2182 return $this->opencontainers->pop('box');
2183 }
2184
2185 /**
2186 * Outputs a container.
2187 * @param string $contents The contents of the box
2188 * @param string $classes A space-separated list of CSS classes
2189 * @param string $id An optional ID
2190 * @return string the HTML to output.
2191 */
2192 public function container($contents, $classes = '', $id = '') {
2193 return $this->container_start($classes, $id) . $contents . $this->container_end();
2194 }
2195
2196 /**
2197 * Outputs the opening section of a container.
2198 * @param string $classes A space-separated list of CSS classes
2199 * @param string $id An optional ID
2200 * @return string the HTML to output.
2201 */
2202 public function container_start($classes = '', $id = '') {
2203 $this->opencontainers->push('container', $this->output_end_tag('div'));
2204 return $this->output_start_tag('div', array('id' => $id,
2205 'class' => moodle_renderer_base::prepare_classes($classes)));
2206 }
2207
2208 /**
2209 * Outputs the closing section of a container.
2210 * @return string the HTML to output.
2211 */
2212 public function container_end() {
2213 return $this->opencontainers->pop('container');
2214 }
2215}
2216
2217
2218/// RENDERERS
2219
2220/**
2221 * A renderer that generates output for command-line scripts.
2222 *
2223 * The implementation of this renderer is probably incomplete.
2224 *
2225 * @copyright 2009 Tim Hunt
2226 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2227 * @since Moodle 2.0
2228 */
2229class cli_core_renderer extends moodle_core_renderer {
2230 /**
2231 * Returns the page header.
2232 * @return string HTML fragment
2233 */
2234 public function header() {
2235 output_starting_hook();
2236 return $this->page->heading . "\n";
2237 }
2238
2239 /**
2240 * Returns a template fragment representing a Heading.
2241 * @param string $text The text of the heading
2242 * @param int $level The level of importance of the heading
2243 * @param string $classes A space-separated list of CSS classes
2244 * @param string $id An optional ID
2245 * @return string A template fragment for a heading
2246 */
2247 public function heading($text, $level, $classes = 'main', $id = '') {
2248 $text .= "\n";
2249 switch ($level) {
2250 case 1:
2251 return '=>' . $text;
2252 case 2:
2253 return '-->' . $text;
2254 default:
2255 return $text;
2256 }
2257 }
2258
2259 /**
2260 * Returns a template fragment representing a fatal error.
2261 * @param string $message The message to output
2262 * @param string $moreinfourl URL where more info can be found about the error
2263 * @param string $link Link for the Continue button
2264 * @param array $backtrace The execution backtrace
2265 * @param string $debuginfo Debugging information
2266 * @param bool $showerrordebugwarning Whether or not to show a debugging warning
2267 * @return string A template fragment for a fatal error
2268 */
2269 public function fatal_error($message, $moreinfourl, $link, $backtrace,
2270 $debuginfo = null, $showerrordebugwarning = false) {
2271 $output = "!!! $message !!!\n";
2272
2273 if (debugging('', DEBUG_DEVELOPER)) {
2274 if (!empty($debuginfo)) {
2275 $this->notification($debuginfo, 'notifytiny');
2276 }
2277 if (!empty($backtrace)) {
2278 $this->notification('Stack trace: ' . format_backtrace($backtrace, true), 'notifytiny');
2279 }
2280 }
2281 }
2282
2283 /**
2284 * Returns a template fragment representing a notification.
2285 * @param string $message The message to include
2286 * @param string $classes A space-separated list of CSS classes
2287 * @return string A template fragment for a notification
2288 */
2289 public function notification($message, $classes = 'notifyproblem') {
2290 $message = clean_text($message);
2291 if ($classes === 'notifysuccess') {
2292 return "++ $message ++\n";
2293 }
2294 return "!! $message !!\n";
2295 }
2296}
2297