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