filter-mediaplugin MDL-16706 Replaced inline JS with new PAGE methods and created...
[moodle.git] / lib / outputlib.php
CommitLineData
571fa828 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/**
20 * Functions for generating the HTML that Moodle should output.
21 *
22 * Please see http://docs.moodle.org/en/Developement:How_Moodle_outputs_HTML
23 * for an overview.
24 *
25 * @package moodlecore
26 * @copyright 2009 Tim Hunt
27 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later (5)
28 */
29
30
c84a2dbe 31function initialise_theme_and_output() {
32 global $CFG, $OUTPUT, $PAGE, $THEME;
33 if (!($OUTPUT instanceof bootstrap_renderer)) {
34 return; // Already done.
35 }
36 if (!isset($CFG->theme) || empty($PAGE)) {
37 // Too soon to do anything.
38 return;
39 }
40 theme_setup();
34a2777c 41 if (CLI_SCRIPT) {
c84a2dbe 42 $rendererfactory = new cli_renderer_factory($THEME, $PAGE);
34a2777c 43 } else {
c84a2dbe 44 $classname = $THEME->rendererfactory;
45 $rendererfactory = new $classname($THEME, $PAGE);
34a2777c 46 }
c84a2dbe 47 $OUTPUT = $rendererfactory->get_renderer('core');
34a2777c 48}
49
50
571fa828 51/**
52 * A renderer factory is just responsible for creating an appropriate renderer
53 * for any given part of Moodle.
54 *
55 * Which renderer factory to use is chose by the current theme, and an instance
56 * if created automatically when the theme is set up.
57 *
58 * A renderer factory must also have a constructor that takes a theme object and
59 * a moodle_page object. (See {@link renderer_factory_base::__construct} for an example.)
60 *
61 * @copyright 2009 Tim Hunt
62 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
63 * @since Moodle 2.0
64 */
65interface renderer_factory {
66 /**
67 * Return the renderer for a particular part of Moodle.
68 *
69 * The renderer interfaces are defined by classes called moodle_..._renderer
70 * where ... is the name of the module, which, will be defined in this file
71 * for core parts of Moodle, and in a file called renderer.php for plugins.
72 *
73 * There is no separate interface definintion for renderers. Instead we
74 * take advantage of PHP being a dynamic languages. The renderer returned
75 * does not need to be a subclass of the moodle_..._renderer base class, it
76 * just needs to impmenent the same interface. This is sometimes called
77 * 'Duck typing'. For a tricky example, see {@link template_renderer} below.
78 * renderer ob
79 *
80 * @param $module the name of part of moodle. E.g. 'core', 'quiz', 'qtype_multichoice'.
81 * @return object an object implementing the requested renderer interface.
82 */
83 public function get_renderer($module);
84}
85
86
87/**
88 * This is a base class to help you implement the renderer_factory interface.
89 *
90 * It keeps a cache of renderers that have been constructed, so you only need
91 * to construct each one once in you subclass.
92 *
93 * It also has a method to get the name of, and include the renderer.php with
94 * the definition of, the standard renderer class for a given module.
95 *
96 * @copyright 2009 Tim Hunt
97 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
98 * @since Moodle 2.0
99 */
100abstract class renderer_factory_base implements renderer_factory {
101 /** The theme we are rendering for. */
102 protected $theme;
103
104 /** The page we are doing output for. */
105 protected $page;
106
107 /** Used to cache renderers as they are created. */
108 protected $renderers = array();
109
34a2777c 110 protected $opencontainers;
111
571fa828 112 /**
113 * Constructor.
114 * @param object $theme the theme we are rendering for.
115 * @param moodle_page $page the page we are doing output for.
116 */
117 public function __construct($theme, $page) {
118 $this->theme = $theme;
119 $this->page = $page;
34a2777c 120 $this->opencontainers = new xhtml_container_stack();
571fa828 121 }
122
123 /* Implement the interface method. */
124 public function get_renderer($module) {
125 // Cache the renderers by module name, and delegate the actual
126 // construction to the create_renderer method.
127 if (!array_key_exists($module, $this->renderers)) {
128 $this->renderers[$module] = $this->create_renderer($module);
129 }
130
131 return $this->renderers[$module];
132 }
133
134 /**
135 * Subclasses should override this method to actually create an instance of
136 * the appropriate renderer class, based on the module name. That is,
137 * this method should implement the same contract as
138 * {@link renderer_factory::get_renderer}.
139 *
140 * @param $module the name of part of moodle. E.g. 'core', 'quiz', 'qtype_multichoice'.
141 * @return object an object implementing the requested renderer interface.
142 */
143 abstract public function create_renderer($module);
144
145 /**
146 * For a given module name, return the name of the standard renderer class
147 * that defines the renderer interface for that module.
148 *
149 * Also, if it exists, include the renderer.php file for that module, so
150 * the class definition of the default renderer has been loaded.
151 *
152 * @param string $module the name of part of moodle. E.g. 'core', 'quiz', 'qtype_multichoice'.
153 * @return string the name of the standard renderer class for that module.
154 */
155 protected function standard_renderer_class_for_module($module) {
156 $pluginrenderer = get_plugin_dir($module) . '/renderer.php';
157 if (file_exists($pluginrenderer)) {
158 include_once($pluginrenderer);
159 }
160 $class = 'moodle_' . $module . '_renderer';
161 if (!class_exists($class)) {
162 throw new coding_exception('Request for an unknown renderer class ' . $class);
163 }
164 return $class;
165 }
166}
167
168
169/**
170 * This is the default renderer factory for Moodle. It simply returns an instance
171 * of the appropriate standard renderer class.
172 *
173 * @copyright 2009 Tim Hunt
174 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
175 * @since Moodle 2.0
176 */
177class standard_renderer_factory extends renderer_factory_base {
178 /**
179 * Constructor.
180 * @param object $theme the theme we are rendering for.
181 * @param moodle_page $page the page we are doing output for.
182 */
183 public function __construct($theme, $page) {
184 parent::__construct($theme, $page);
185 }
186
187 /* Implement the subclass method. */
188 public function create_renderer($module) {
189 if ($module == 'core') {
c84a2dbe 190 return new moodle_core_renderer($this->opencontainers, $this->page, $this);
571fa828 191 } else {
192 $class = $this->standard_renderer_class_for_module($module);
34a2777c 193 return new $class($this->opencontainers, $this->get_renderer('core'), $this->page);
571fa828 194 }
195 }
196}
197
198
199/**
200 * This is a slight variatoin on the standard_renderer_factory that uses
201 * custom_corners_core_renderer instead of moodle_core_renderer.
202 *
203 * This generates the slightly different HTML that the custom_corners theme expects.
204 *
205 * @copyright 2009 Tim Hunt
206 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
207 * @since Moodle 2.0
208 */
209class custom_corners_renderer_factory extends standard_renderer_factory {
210 /**
211 * Constructor.
212 * @param object $theme the theme we are rendering for.
213 * @param moodle_page $page the page we are doing output for.
214 */
215 public function __construct($theme, $page) {
216 parent::__construct($theme, $page);
c84a2dbe 217 $this->renderers = array('core' => new custom_corners_core_renderer($this->opencontainers, $this->page, $this));
218 }
219}
220
221
222/**
223 * This is a slight variation on the standard_renderer_factory used by CLI scripts.
224 *
225 * @copyright 2009 Tim Hunt
226 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
227 * @since Moodle 2.0
228 */
229class cli_renderer_factory extends standard_renderer_factory {
230 /**
231 * Constructor.
232 * @param object $theme the theme we are rendering for.
233 * @param moodle_page $page the page we are doing output for.
234 */
235 public function __construct($theme, $page) {
236 parent::__construct($theme, $page);
237 $this->renderers = array('core' => new cli_core_renderer($this->opencontainers, $this->page, $this));
571fa828 238 }
239}
240
241
242/**
243 * This is renderer factory allows themes to override the standard renderers using
244 * php code.
245 *
246 * It will load any code from theme/mytheme/renderers.php and
247 * theme/parenttheme/renderers.php, if then exist. Then whenever you ask for
248 * a renderer for 'component', it will create a mytheme_component_renderer or a
249 * parenttheme_component_renderer, instead of a moodle_component_renderer,
250 * if either of those classes exist.
251 *
252 * This generates the slightly different HTML that the custom_corners theme expects.
253 *
254 * @copyright 2009 Tim Hunt
255 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
256 * @since Moodle 2.0
257 */
258class theme_overridden_renderer_factory extends standard_renderer_factory {
259 protected $prefixes = array();
260
261 /**
262 * Constructor.
263 * @param object $theme the theme we are rendering for.
264 * @param moodle_page $page the page we are doing output for.
265 */
266 public function __construct($theme, $page) {
267 global $CFG;
268 parent::__construct($theme, $page);
269
270 // Initialise $this->prefixes.
271 $renderersfile = $theme->dir . '/renderers.php';
272 if (is_readable($renderersfile)) {
273 include_once($renderersfile);
274 $this->prefixes[] = $theme->name . '_';
275 }
276 if (!empty($theme->parent)) {
277 $renderersfile = $CFG->themedir .'/'. $theme->parent . '/renderers.php';
278 if (is_readable($renderersfile)) {
279 include_once($renderersfile);
280 $this->prefixes[] = $theme->parent . '_';
281 }
282 }
283 }
284
285 /* Implement the subclass method. */
286 public function create_renderer($module) {
287 foreach ($this->prefixes as $prefix) {
288 $classname = $prefix . $module . '_renderer';
289 if (class_exists($classname)) {
290 if ($module == 'core') {
c84a2dbe 291 return new $classname($this->opencontainers, $this->page, $this);
571fa828 292 } else {
34a2777c 293 return new $classname($this->opencontainers, $this->get_renderer('core'), $this->page);
571fa828 294 }
295 }
296 }
297 return parent::create_renderer($module);
298 }
299}
300
301
302/**
303 * This is renderer factory that allows you to create templated themes.
304 *
305 * This should be considered an experimental proof of concept. In particular,
306 * the performance is probably not very good. Do not try to use in on a busy site
307 * without doing careful load testing first!
308 *
309 * This renderer factory returns instances of {@link template_renderer} class
310 * which which implement the corresponding renderer interface in terms of
311 * templates. To use this your theme must have a templates folder inside it.
312 * Then suppose the method moodle_core_renderer::greeting($name = 'world');
313 * exists. Then, a call to $OUTPUT->greeting() will cause the template
314 * /theme/yourtheme/templates/core/greeting.php to be rendered, with the variable
315 * $name available. The greeting.php template might contain
316 *
317 * <pre>
318 * <h1>Hello <?php echo $name ?>!</h1>
319 * </pre>
320 *
321 * @copyright 2009 Tim Hunt
322 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
323 * @since Moodle 2.0
324 */
325class template_renderer_factory extends renderer_factory_base {
326 /**
327 * An array of paths of where to search for templates. Normally this theme,
328 * the parent theme then the standardtemplate theme. (If some of these do
329 * not exist, or are the same as each other, then the list will be shorter.
330 */
331 protected $searchpaths = array();
332
333 /**
334 * Constructor.
335 * @param object $theme the theme we are rendering for.
336 * @param moodle_page $page the page we are doing output for.
337 */
338 public function __construct($theme, $page) {
339 global $CFG;
340 parent::__construct($theme, $page);
341
342 // Initialise $this->searchpaths.
343 if ($theme->name != 'standardtemplate') {
344 $templatesdir = $theme->dir . '/templates';
345 if (is_dir($templatesdir)) {
346 $this->searchpaths[] = $templatesdir;
347 }
348 }
349 if (!empty($theme->parent)) {
350 $templatesdir = $CFG->themedir .'/'. $theme->parent . '/templates';
351 if (is_dir($templatesdir)) {
352 $this->searchpaths[] = $templatesdir;
353 }
354 }
355 $this->searchpaths[] = $CFG->themedir .'/standardtemplate/templates';
356 }
357
358 /* Implement the subclass method. */
359 public function create_renderer($module) {
360 // Refine the list of search paths for this module.
361 $searchpaths = array();
362 foreach ($this->searchpaths as $rootpath) {
363 $path = $rootpath . '/' . $module;
364 if (is_dir($path)) {
365 $searchpaths[] = $path;
366 }
367 }
368
369 // Create a template_renderer that copies the API of the standard renderer.
370 $copiedclass = $this->standard_renderer_class_for_module($module);
c84a2dbe 371 return new template_renderer($copiedclass, $searchpaths, $this->opencontainers, $this->page, $this);
571fa828 372 }
373}
374
375
376/**
377 * Simple base class for Moodle renderers.
378 *
379 * Tracks the xhtml_container_stack to use, which is passed in in the constructor.
380 *
8954245a 381 * Also has methods to facilitate generating HTML output.
382 *
571fa828 383 * @copyright 2009 Tim Hunt
384 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
385 * @since Moodle 2.0
386 */
387class moodle_renderer_base {
388 /** @var xhtml_container_stack the xhtml_container_stack to use. */
34a2777c 389 protected $opencontainers;
390 /** @var moodle_page the page we are rendering for. */
391 protected $page;
571fa828 392
393 /**
394 * Constructor
34a2777c 395 * @param $opencontainers the xhtml_container_stack to use.
396 * @param moodle_page $page the page we are doing output for.
571fa828 397 */
34a2777c 398 public function __construct($opencontainers, $page) {
399 $this->opencontainers = $opencontainers;
400 $this->page = $page;
571fa828 401 }
8954245a 402
403 protected function output_tag($tagname, $attributes, $contents) {
404 return $this->output_start_tag($tagname, $attributes) . $contents .
405 $this->output_end_tag($tagname);
406 }
407 protected function output_start_tag($tagname, $attributes) {
408 return '<' . $tagname . $this->output_attributes($attributes) . '>';
409 }
410 protected function output_end_tag($tagname) {
411 return '</' . $tagname . '>';
412 }
413 protected function output_empty_tag($tagname, $attributes) {
414 return '<' . $tagname . $this->output_attributes($attributes) . ' />';
415 }
416
417 protected function output_attribute($name, $value) {
34a2777c 418 $value = trim($value);
8954245a 419 if ($value || is_numeric($value)) { // We want 0 to be output.
420 return ' ' . $name . '="' . $value . '"';
421 }
422 }
423 protected function output_attributes($attributes) {
424 $output = '';
425 foreach ($attributes as $name => $value) {
426 $output .= $this->output_attribute($name, $value);
427 }
428 return $output;
429 }
34a2777c 430 public static function prepare_classes($classes) {
431 if (is_array($classes)) {
432 return implode(' ', array_unique($classes));
433 }
434 return $classes;
8954245a 435 }
571fa828 436}
437
438
439/**
440 * This is the templated renderer which copies the API of another class, replacing
441 * all methods calls with instantiation of a template.
442 *
443 * When the method method_name is called, this class will search for a template
444 * called method_name.php in the folders in $searchpaths, taking the first one
445 * that it finds. Then it will set up variables for each of the arguments of that
446 * method, and render the template. This is implemented in the {@link __call()}
447 * PHP magic method.
448 *
449 * Methods like print_box_start and print_box_end are handles specially, and
450 * implemented in terms of the print_box.php method.
451 *
452 * @copyright 2009 Tim Hunt
453 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
454 * @since Moodle 2.0
455 */
456class template_renderer extends moodle_renderer_base {
457 /** @var ReflectionClass information about the class whose API we are copying. */
458 protected $copiedclass;
459 /** @var array of places to search for templates. */
460 protected $searchpaths;
c84a2dbe 461 protected $rendererfactory;
571fa828 462
463 /**
464 * Magic word used when breaking apart container templates to implement
465 * _start and _end methods.
466 */
467 const contentstoken = '-@#-Contents-go-here-#@-';
468
469 /**
470 * Constructor
471 * @param string $copiedclass the name of a class whose API we should be copying.
472 * @param $searchpaths a list of folders to search for templates in.
34a2777c 473 * @param $opencontainers the xhtml_container_stack to use.
474 * @param moodle_page $page the page we are doing output for.
c84a2dbe 475 * @param renderer_factory $rendererfactory the renderer factory that created us.
571fa828 476 */
c84a2dbe 477 public function __construct($copiedclass, $searchpaths, $opencontainers, $page, $rendererfactory) {
34a2777c 478 parent::__construct($opencontainers, $page);
571fa828 479 $this->copiedclass = new ReflectionClass($copiedclass);
480 $this->searchpaths = $searchpaths;
c84a2dbe 481 $this->rendererfactory = $rendererfactory;
482 }
483
484 /**
485 * Get a renderer for another part of Moodle.
486 * @param $module the name of part of moodle. E.g. 'core', 'quiz', 'qtype_multichoice'.
487 * @return object an object implementing the requested renderer interface.
488 */
489 public function get_other_renderer($module) {
490 $this->rendererfactory->get_renderer($module);
571fa828 491 }
492
493 /* PHP magic method implementation. */
494 public function __call($method, $arguments) {
495 if (substr($method, -6) == '_start') {
496 return $this->process_start(substr($method, 0, -6), $arguments);
497 } else if (substr($method, -4) == '_end') {
498 return $this->process_end(substr($method, 0, -4), $arguments);
499 } else {
500 return $this->process_template($method, $arguments);
501 }
502 }
503
504 /**
505 * Render the template for a given method of the renderer class we are copying,
506 * using the arguments passed.
507 * @param string $method the method that was called.
508 * @param array $arguments the arguments that were passed to it.
509 * @return string the HTML to be output.
510 */
511 protected function process_template($method, $arguments) {
512 if (!$this->copiedclass->hasMethod($method) ||
513 !$this->copiedclass->getMethod($method)->isPublic()) {
514 throw new coding_exception('Unknown method ' . $method);
515 }
516
517 // Find the template file for this method.
518 $template = $this->find_template($method);
519
520 // Use the reflection API to find out what variable names the arguments
521 // should be stored in, and fill in any missing ones with the defaults.
522 $namedarguments = array();
523 $expectedparams = $this->copiedclass->getMethod($method)->getParameters();
524 foreach ($expectedparams as $param) {
525 $paramname = $param->getName();
526 if (!empty($arguments)) {
527 $namedarguments[$paramname] = array_shift($arguments);
528 } else if ($param->isDefaultValueAvailable()) {
529 $namedarguments[$paramname] = $param->getDefaultValue();
530 } else {
531 throw new coding_exception('Missing required argument ' . $paramname);
532 }
533 }
534
535 // Actually render the template.
536 return $this->render_template($template, $namedarguments);
537 }
538
539 /**
540 * Actually do the work of rendering the template.
541 * @param $_template the full path to the template file.
542 * @param $_namedarguments an array variable name => value, the variables
543 * that should be available to the template.
544 * @return string the HTML to be output.
545 */
546 protected function render_template($_template, $_namedarguments) {
547 // Note, we intentionally break the coding guidelines with regards to
548 // local variable names used in this function, so that they do not clash
549 // with the names of any variables being passed to the template.
550
6e37fa50 551 global $CFG, $SITE, $THEME, $USER;
552 // The next lines are a bit tricky. The point is, here we are in a method
553 // of a renderer class, and this object may, or may not, be the the same as
554 // the global $OUTPUT object. When rendering the template, we want to use
555 // this object. However, people writing Moodle code expect the current
556 // rederer to be called $OUTPUT, not $this, so define a variable called
557 // $OUTPUT pointing at $this. The same comment applies to $PAGE and $COURSE.
34a2777c 558 $OUTPUT = $this;
6e37fa50 559 $PAGE = $this->page;
34a2777c 560 $COURSE = $this->page->course;
571fa828 561
562 // And the parameters from the function call.
563 extract($_namedarguments);
564
565 // Include the template, capturing the output.
566 ob_start();
567 include($_template);
568 $_result = ob_get_contents();
569 ob_end_clean();
570
571 return $_result;
572 }
573
574 /**
575 * Searches the folders in {@link $searchpaths} to try to find a template for
576 * this method name. Throws an exception if one cannot be found.
577 * @param string $method the method name.
578 * @return string the full path of the template to use.
579 */
580 protected function find_template($method) {
581 foreach ($this->searchpaths as $path) {
582 $filename = $path . '/' . $method . '.php';
583 if (file_exists($filename)) {
584 return $filename;
585 }
586 }
587 throw new coding_exception('Cannot find template for ' . $this->copiedclass->getName() . '::' . $method);
588 }
589
590 /**
591 * Handle methods like print_box_start by using the print_box template,
592 * splitting the result, pusing the end onto the stack, then returning the start.
593 * @param string $method the method that was called, with _start stripped off.
594 * @param array $arguments the arguments that were passed to it.
595 * @return string the HTML to be output.
596 */
597 protected function process_start($template, $arguments) {
598 array_unshift($arguments, self::contentstoken);
599 $html = $this->process_template($template, $arguments);
600 list($start, $end) = explode(self::contentstoken, $html, 2);
34a2777c 601 $this->opencontainers->push($template, $end);
571fa828 602 return $start;
603 }
604
605 /**
606 * Handle methods like print_box_end, we just need to pop the end HTML from
607 * the stack.
608 * @param string $method the method that was called, with _end stripped off.
609 * @param array $arguments not used. Assumed to be irrelevant.
610 * @return string the HTML to be output.
611 */
612 protected function process_end($template, $arguments) {
34a2777c 613 return $this->opencontainers->pop($template);
571fa828 614 }
615
616 /**
617 * @return array the list of paths where this class searches for templates.
618 */
619 public function get_search_paths() {
620 return $this->searchpaths;
621 }
622
623 /**
624 * @return string the name of the class whose API we are copying.
625 */
626 public function get_copied_class() {
627 return $this->copiedclass->getName();
628 }
629}
630
631
632/**
633 * This class keeps track of which HTML tags are currently open.
634 *
635 * This makes it much easier to always generate well formed XHTML output, even
636 * if execution terminates abruptly. Any time you output some opening HTML
637 * without the matching closing HTML, you should push the neccessary close tags
638 * onto the stack.
639 *
640 * @copyright 2009 Tim Hunt
641 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
642 * @since Moodle 2.0
643 */
644class xhtml_container_stack {
645 /** @var array stores the list of open containers. */
34a2777c 646 protected $opencontainers = array();
571fa828 647
648 /**
649 * Push the close HTML for a recently opened container onto the stack.
650 * @param string $type The type of container. This is checked when {@link pop()}
651 * is called and must match, otherwise a developer debug warning is output.
652 * @param string $closehtml The HTML required to close the container.
653 */
654 public function push($type, $closehtml) {
655 $container = new stdClass;
656 $container->type = $type;
657 $container->closehtml = $closehtml;
34a2777c 658 array_push($this->opencontainers, $container);
571fa828 659 }
660
661 /**
662 * Pop the HTML for the next closing container from the stack. The $type
663 * must match the type passed when the container was opened, otherwise a
664 * warning will be output.
665 * @param string $type The type of container.
666 * @return string the HTML requried to close the container.
667 */
668 public function pop($type) {
34a2777c 669 if (empty($this->opencontainers)) {
571fa828 670 debugging('There are no more open containers. This suggests there is a nesting problem.', DEBUG_DEVELOPER);
671 return;
672 }
673
34a2777c 674 $container = array_pop($this->opencontainers);
571fa828 675 if ($container->type != $type) {
676 debugging('The type of container to be closed (' . $container->type .
677 ') does not match the type of the next open container (' . $type .
678 '). This suggests there is a nesting problem.', DEBUG_DEVELOPER);
679 }
680 return $container->closehtml;
681 }
682
34a2777c 683 /**
684 * Return how many containers are currently open.
685 * @return integer how many containers are currently open.
686 */
687 public function count() {
688 return count($this->opencontainers);
689 }
690
571fa828 691 /**
692 * Close all but the last open container. This is useful in places like error
693 * handling, where you want to close all the open containers (apart from <body>)
694 * before outputting the error message.
695 * @return string the HTML requried to close any open containers inside <body>.
696 */
697 public function pop_all_but_last() {
698 $output = '';
34a2777c 699 while (count($this->opencontainers) > 1) {
700 $container = array_pop($this->opencontainers);
571fa828 701 $output .= $container->closehtml;
702 }
703 return $output;
704 }
705
706 /**
707 * You can call this function if you want to throw away an instance of this
708 * class without properly emptying the stack (for example, in a unit test).
709 * Calling this method stops the destruct method from outputting a developer
710 * debug warning. After calling this method, the instance can no longer be used.
711 */
712 public function discard() {
34a2777c 713 $this->opencontainers = null;
571fa828 714 }
715
716 /**
717 * Emergency fallback. If we get to the end of processing and not all
718 * containers have been closed, output the rest with a developer debug warning.
719 */
720 public function __destruct() {
34a2777c 721 if (empty($this->opencontainers)) {
571fa828 722 return;
723 }
724
725 debugging('Some containers were left open. This suggests there is a nesting problem.', DEBUG_DEVELOPER);
726 echo $this->pop_all_but_last();
34a2777c 727 $container = array_pop($this->opencontainers);
571fa828 728 echo $container->closehtml;
729 }
730}
731
732
733/**
734 * The standard implementation of the moodle_core_renderer interface.
735 *
736 * @copyright 2009 Tim Hunt
737 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
738 * @since Moodle 2.0
739 */
740class moodle_core_renderer extends moodle_renderer_base {
34a2777c 741 const PERFORMANCE_INFO_TOKEN = '%%PERFORMANCEINFO%%';
742 const END_HTML_TOKEN = '%%ENDHTML%%';
743 const MAIN_CONTENT_TOKEN = '[MAIN CONTENT GOES HERE]';
744 protected $contenttype;
c84a2dbe 745 protected $rendererfactory;
e8775320 746 protected $metarefreshtag = '';
c84a2dbe 747 /**
748 * Constructor
749 * @param $opencontainers the xhtml_container_stack to use.
750 * @param moodle_page $page the page we are doing output for.
751 * @param renderer_factory $rendererfactory the renderer factory that created us.
752 */
753 public function __construct($opencontainers, $page, $rendererfactory) {
754 parent::__construct($opencontainers, $page);
755 $this->rendererfactory = $rendererfactory;
756 }
757
758 /**
759 * Get a renderer for another part of Moodle.
760 * @param $module the name of part of moodle. E.g. 'core', 'quiz', 'qtype_multichoice'.
761 * @return object an object implementing the requested renderer interface.
762 */
763 public function get_other_renderer($module) {
764 $this->rendererfactory->get_renderer($module);
765 }
34a2777c 766
767 public function doctype() {
768 global $CFG;
769
770 $doctype = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">' . "\n";
771 $this->contenttype = 'text/html; charset=utf-8';
772
773 if (empty($CFG->xmlstrictheaders)) {
774 return $doctype;
775 }
776
777 // We want to serve the page with an XML content type, to force well-formedness errors to be reported.
778 $prolog = '<?xml version="1.0" encoding="utf-8"?>' . "\n";
779 if (isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'application/xhtml+xml') !== false) {
780 // Firefox and other browsers that can cope natively with XHTML.
781 $this->contenttype = 'application/xhtml+xml; charset=utf-8';
782
783 } else if (preg_match('/MSIE.*Windows NT/', $_SERVER['HTTP_USER_AGENT'])) {
784 // IE can't cope with application/xhtml+xml, but it will cope if we send application/xml with an XSL stylesheet.
785 $this->contenttype = 'application/xml; charset=utf-8';
786 $prolog .= '<?xml-stylesheet type="text/xsl" href="' . $CFG->httpswwwroot . '/lib/xhtml.xsl"?>' . "\n";
787
788 } else {
789 $prolog = '';
790 }
791
792 return $prolog . $doctype;
793 }
794
795 public function htmlattributes() {
796 return get_html_lang(true) . ' xmlns="http://www.w3.org/1999/xhtml"';
797 }
798
799 public function standard_head_html() {
800 global $CFG, $THEME;
801 $output = '';
802 $output .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />' . "\n";
803 $output .= '<meta name="keywords" content="moodle, ' . $this->page->title . '" />' . "\n";
804 if (!$this->page->cacheable) {
805 $output .= '<meta http-equiv="pragma" content="no-cache" />' . "\n";
806 $output .= '<meta http-equiv="expires" content="0" />' . "\n";
807 }
e8775320 808 // This is only set by the {@link redirect()} method
809 $output .= $this->metarefreshtag;
34a2777c 810 ob_start();
811 include($CFG->javascript);
812 $output .= ob_get_contents();
813 ob_end_clean();
814 $output .= $this->page->requires->get_head_code();
815
816 foreach ($this->page->alternateversions as $type => $alt) {
817 $output .= $this->output_empty_tag('link', array('rel' => 'alternate',
818 'type' => $type, 'title' => $alt->title, 'href' => $alt->url));
819 }
820
821 // Add the meta page from the themes if any were requested
822 // TODO kill this.
823 $PAGE = $this->page;
824 $metapage = '';
825 if (!isset($THEME->standardmetainclude) || $THEME->standardmetainclude) {
826 ob_start();
827 include_once($CFG->dirroot.'/theme/standard/meta.php');
828 $output .= ob_get_contents();
829 ob_end_clean();
830 }
831 if ($THEME->parent && (!isset($THEME->parentmetainclude) || $THEME->parentmetainclude)) {
832 if (file_exists($CFG->dirroot.'/theme/'.$THEME->parent.'/meta.php')) {
833 ob_start();
834 include_once($CFG->dirroot.'/theme/'.$THEME->parent.'/meta.php');
835 $output .= ob_get_contents();
836 ob_end_clean();
837 }
838 }
839 if (!isset($THEME->metainclude) || $THEME->metainclude) {
840 if (file_exists($CFG->dirroot.'/theme/'.current_theme().'/meta.php')) {
841 ob_start();
842 include_once($CFG->dirroot.'/theme/'.current_theme().'/meta.php');
843 $output .= ob_get_contents();
844 ob_end_clean();
845 }
846 }
847
848 return $output;
849 }
850
851 public function standard_top_of_body_html() {
852 return $this->page->requires->get_top_of_body_code();
853 }
854
855 public function standard_footer_html() {
856 $output = self::PERFORMANCE_INFO_TOKEN;
857 if (debugging()) {
858 $output .= '<div class="validators"><ul>
859 <li><a href="http://validator.w3.org/check?verbose=1&amp;ss=1&amp;uri=' . urlencode(qualified_me()) . '">Validate HTML</a></li>
860 <li><a href="http://www.contentquality.com/mynewtester/cynthia.exe?rptmode=-1&amp;url1=' . urlencode(qualified_me()) . '">Section 508 Check</a></li>
861 <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>
862 </ul></div>';
863 }
864 return $output;
865 }
866
867 public function standard_end_of_body_html() {
868 echo self::END_HTML_TOKEN;
869 }
870
871 public function login_info() {
872 global $USER;
873 return user_login_string($this->page->course, $USER);
874 }
875
876 public function home_link() {
877 global $CFG, $SITE;
878
879 if ($this->page->pagetype == 'site-index') {
880 // Special case for site home page - please do not remove
881 return '<div class="sitelink">' .
882 '<a title="Moodle ' . $CFG->release . '" href="http://moodle.org/">' .
883 '<img style="width:100px;height:30px" src="' . $CFG->httpswwwroot . '/pix/moodlelogo.gif" alt="moodlelogo" /></a></div>';
884
885 } else if (!empty($CFG->target_release) && $CFG->target_release != $CFG->release) {
886 // Special case for during install/upgrade.
887 return '<div class="sitelink">'.
888 '<a title="Moodle ' . $CFG->target_release . '" href="http://docs.moodle.org/en/Administrator_documentation" onclick="this.target=\'_blank\'">' .
889 '<img style="width:100px;height:30px" src="' . $CFG->httpswwwroot . '/pix/moodlelogo.gif" alt="moodlelogo" /></a></div>';
890
891 } else if ($this->page->course->id == $SITE->id || strpos($this->page->pagetype, 'course-view') === 0) {
892 return '<div class="homelink"><a href="' . $CFG->wwwroot . '/">' .
893 get_string('home') . '</a></div>';
894
895 } else {
896 return '<div class="homelink"><a href="' . $CFG->wwwroot . '/course/view.php?id=' . $this->page->course->id . '">' .
897 format_string($this->page->course->shortname) . '</a></div>';
898 }
899 }
900
e8775320 901 public function has_started() {
902 if ($this->page->stated >= moodle_page::STATE_IN_BODY) {
903 return true;
904 }
905 return false;
906 }
907
908 public function redirect($encodedurl, $message, $delay, $messageclass='notifyproblem') {
909 global $CFG;
910 $url = str_replace('&amp;', '&', $encodedurl);
911
912 $disableredirect = false;
913
914 if ($delay!=0) {
915 /// At developer debug level. Don't redirect if errors have been printed on screen.
916 /// Currenly only works in PHP 5.2+; we do not want strict PHP5 errors
917 $lasterror = error_get_last();
918 $error = defined('DEBUGGING_PRINTED') or (!empty($lasterror) && ($lasterror['type'] & DEBUG_DEVELOPER));
919 $errorprinted = debugging('', DEBUG_ALL) && $CFG->debugdisplay && $error;
920 if ($errorprinted) {
921 $disableredirect= true;
922 $message = "<strong>Error output, so disabling automatic redirect.</strong></p><p>" . $message;
923 }
924 }
925
926 switch ($this->page->state) {
927 case moodle_page::STATE_BEFORE_HEADER :
928 // No output yet it is safe to delivery the full arsenol of redirect methods
929 if (!$disableredirect) {
930 @header($_SERVER['SERVER_PROTOCOL'] . ' 303 See Other'); //302 might not work for POST requests, 303 is ignored by obsolete clients
931 @header('Location: '.$url);
932 $this->metarefreshtag = '<meta http-equiv="refresh" content="'. $delay .'; url='. $encodedurl .'" />'."\n";
933 $this->page->requires->js_function_call('document.location.replace', array($url))->after_delay($delay+3);
934 }
935 $output = $this->header();
936 $output .= $this->notification($message, $messageclass);
937 $output .= $this->footer();
938 break;
939 case moodle_page::STATE_PRINTING_HEADER :
940 // We should hopefully never get here
941 throw new coding_exception('You cannot redirect while printing the page header');
942 break;
943 case moodle_page::STATE_IN_BODY :
944 // We really shouldn't be here but we can deal with this
945 debugging("You should really redirect before you start page output");
946 if (!$disableredirect) {
947 $this->page->requires->js_function_call('document.location.replace', array($url))->after_delay($delay+3);
948 }
949 $output = $this->opencontainers->pop_all_but_last();
950 $output .= $this->notification($message, $messageclass);
951 $output .= $this->footer();
952 break;
953 case moodle_page::STATE_DONE :
954 // Too late to be calling redirect now
955 throw new coding_exception('You cannot redirect after the entire page has been generated');
956 break;
957 }
958 return $output;
959 }
960
34a2777c 961 // TODO remove $navigation and $menu arguments - replace with $PAGE->navigation
962 public function header($navigation = '', $menu='') {
963 global $USER, $CFG;
964
965 output_starting_hook();
966 $this->page->set_state(moodle_page::STATE_PRINTING_HEADER);
967
968 // Add any stylesheets required using the horrible legacy mechanism. TODO kill this.
969 foreach ($CFG->stylesheets as $stylesheet) {
970 $this->page->requires->css($stylesheet, true);
971 }
972
973 // Find the appropriate page template, based on $this->page->generaltype.
974 $templatefile = $this->find_page_template();
975 if ($templatefile) {
976 // Render the template.
977 $template = $this->render_page_template($templatefile, $menu, $navigation);
978 } else {
979 // New style template not found, fall back to using header.html and footer.html.
980 $template = $this->handle_legacy_theme($navigation, $menu);
981 }
982
983 // Slice the template output into header and footer.
984 $cutpos = strpos($template, self::MAIN_CONTENT_TOKEN);
985 if ($cutpos === false) {
986 throw new coding_exception('Layout template ' . $templatefile .
987 ' does not contain the string "' . self::MAIN_CONTENT_TOKEN . '".');
988 }
989 $header = substr($template, 0, $cutpos);
990 $footer = substr($template, $cutpos + strlen(self::MAIN_CONTENT_TOKEN));
991
992 send_headers($this->contenttype, $this->page->cacheable);
993 $this->opencontainers->push('header/footer', $footer);
994 $this->page->set_state(moodle_page::STATE_IN_BODY);
995 return $header . $this->skip_link_target();
996 }
997
998 protected function find_page_template() {
999 global $THEME;
1000
1001 // If this is a particular page type, look for a specific template.
1002 $type = $this->page->generaltype;
1003 if ($type != 'normal') {
1004 $templatefile = $THEME->dir . '/layout-' . $type . '.php';
1005 if (is_readable($templatefile)) {
1006 return $templatefile;
1007 }
1008 }
1009
1010 // Otherwise look for the general template.
1011 $templatefile = $THEME->dir . '/layout.php';
1012 if (is_readable($templatefile)) {
1013 return $templatefile;
1014 }
1015
1016 return false;
1017 }
1018
1019 protected function render_page_template($templatefile, $menu, $navigation) {
1020 global $CFG, $SITE, $THEME, $USER;
6e37fa50 1021 // The next lines are a bit tricky. The point is, here we are in a method
1022 // of a renderer class, and this object may, or may not, be the the same as
1023 // the global $OUTPUT object. When rendering the template, we want to use
1024 // this object. However, people writing Moodle code expect the current
1025 // rederer to be called $OUTPUT, not $this, so define a variable called
1026 // $OUTPUT pointing at $this. The same comment applies to $PAGE and $COURSE.
34a2777c 1027 $OUTPUT = $this;
1028 $PAGE = $this->page;
1029 $COURSE = $this->page->course;
1030
1031 ob_start();
1032 include($templatefile);
1033 $template = ob_get_contents();
1034 ob_end_clean();
1035 return $template;
1036 }
1037
1038 protected function handle_legacy_theme($navigation, $menu) {
1039 global $CFG, $SITE, $THEME, $USER;
1040 // Set a pretend global from the properties of this class.
6e37fa50 1041 // See the comment in render_page_template for a fuller explanation.
34a2777c 1042 $COURSE = $this->page->course;
1043
1044 // Set up local variables that header.html expects.
1045 $direction = $this->htmlattributes();
1046 $title = $this->page->title;
1047 $heading = $this->page->heading;
1048 $focus = $this->page->focuscontrol;
1049 $button = $this->page->button;
1050 $pageid = $this->page->pagetype;
1051 $pageclass = $this->page->bodyclasses;
1052 $bodytags = ' class="' . $pageclass . '" id="' . $pageid . '"';
1053 $home = $this->page->generaltype == 'home';
1054
1055 $meta = $this->standard_head_html();
1056 // The next line is a nasty hack. having set $meta to standard_head_html, we have already
1057 // got the contents of include($CFG->javascript). However, legacy themes are going to
1058 // include($CFG->javascript) again. We want to make sure that when they do, nothing is output.
1059 $CFG->javascript = $CFG->libdir . '/emptyfile.php';
1060
1061 // Set up local variables that footer.html expects.
1062 $homelink = $this->home_link();
1063 $loggedinas = $this->login_info();
1064 $course = $this->page->course;
1065 $performanceinfo = self::PERFORMANCE_INFO_TOKEN;
1066
1067 if (!$menu && $navigation) {
1068 $menu = $loggedinas;
1069 }
1070
1071 ob_start();
1072 include($THEME->dir . '/header.html');
1073 $this->page->requires->get_top_of_body_code();
1074 echo self::MAIN_CONTENT_TOKEN;
1075
1076 $menu = str_replace('navmenu', 'navmenufooter', $menu);
1077 include($THEME->dir . '/footer.html');
1078
1079 $output = ob_get_contents();
1080 ob_end_clean();
1081
1082 $output = str_replace('</body>', self::END_HTML_TOKEN . '</body>', $output);
1083
1084 return $output;
1085 }
1086
1087 public function footer() {
1088 $output = '';
1089 if ($this->opencontainers->count() != 1) {
1090 debugging('Some HTML tags were opened in the body of the page but not closed.', DEBUG_DEVELOPER);
1091 $output .= $this->opencontainers->pop_all_but_last();
1092 }
1093
1094 $footer = $this->opencontainers->pop('header/footer');
1095
1096 // Provide some performance info if required
1097 $performanceinfo = '';
1098 if (defined('MDL_PERF') || (!empty($CFG->perfdebug) and $CFG->perfdebug > 7)) {
1099 $perf = get_performance_info();
1100 if (defined('MDL_PERFTOLOG') && !function_exists('register_shutdown_function')) {
1101 error_log("PERF: " . $perf['txt']);
1102 }
1103 if (defined('MDL_PERFTOFOOT') || debugging() || $CFG->perfdebug > 7) {
1104 $performanceinfo = $perf['html'];
1105 }
1106 }
1107 $footer = str_replace(self::PERFORMANCE_INFO_TOKEN, $performanceinfo, $footer);
1108
1109 $footer = str_replace(self::END_HTML_TOKEN, $this->page->requires->get_end_code(), $footer);
1110
1111 $this->page->set_state(moodle_page::STATE_DONE);
1112
1113 return $output . $footer;
1114 }
1115
8954245a 1116 public function link_to_popup_window() {
1117
1118 }
571fa828 1119
8954245a 1120 public function button_to_popup_window() {
1121
1122 }
1123
1124 public function close_window_button($buttontext = null, $reloadopener = false) {
1125 if (empty($buttontext)) {
1126 $buttontext = get_string('closewindow');
1127 }
1128 // TODO
1129 }
1130
1131 public function close_window($delay = 0, $reloadopener = false) {
1132 // TODO
1133 }
1134
1135 /**
1136 * Output a <select> menu.
1137 *
1138 * You can either call this function with a single moodle_select_menu argument
1139 * or, with a list of parameters, in which case those parameters are sent to
1140 * the moodle_select_menu constructor.
1141 *
1142 * @param moodle_select_menu $selectmenu a moodle_select_menu that describes
1143 * the select menu you want output.
1144 * @return string the HTML for the <select>
1145 */
1146 public function select_menu($selectmenu) {
1147 $selectmenu = clone($selectmenu);
1148 $selectmenu->prepare();
1149
1150 if ($selectmenu->nothinglabel) {
1151 $selectmenu->options = array($selectmenu->nothingvalue => $selectmenu->nothinglabel) +
1152 $selectmenu->options;
1153 }
1154
1155 if (empty($selectmenu->id)) {
1156 $selectmenu->id = 'menu' . str_replace(array('[', ']'), '', $selectmenu->name);
1157 }
1158
1159 $attributes = array(
1160 'name' => $selectmenu->name,
1161 'id' => $selectmenu->id,
1162 'class' => $selectmenu->get_classes_string(),
1163 'onchange' => $selectmenu->script,
1164 );
1165 if ($selectmenu->disabled) {
1166 $attributes['disabled'] = 'disabled';
1167 }
1168 if ($selectmenu->tabindex) {
1169 $attributes['tabindex'] = $tabindex;
1170 }
1171
1172 if ($selectmenu->listbox) {
1173 if (is_integer($selectmenu->listbox)) {
1174 $size = $selectmenu->listbox;
1175 } else {
1176 $size = min($selectmenu->maxautosize, count($selectmenu->options));
1177 }
1178 $attributes['size'] = $size;
1179 if ($selectmenu->multiple) {
1180 $attributes['multiple'] = 'multiple';
1181 }
1182 }
1183
1184 $html = $this->output_start_tag('select', $attributes) . "\n";
1185 foreach ($selectmenu->options as $value => $label) {
1186 $attributes = array('value' => $value);
1187 if ((string)$value == (string)$selectmenu->selectedvalue ||
1188 (is_array($selectmenu->selectedvalue) && in_array($value, $selectmenu->selectedvalue))) {
1189 $attributes['selected'] = 'selected';
1190 }
1191 $html .= ' ' . $this->output_tag('option', $attributes, s($label)) . "\n";
1192 }
1193 $html .= $this->output_end_tag('select') . "\n";
1194
1195 return $html;
1196 }
1197
1198 // TODO choose_from_menu_nested
1199
1200 // TODO choose_from_radio
1201
1202 /**
1203 * Output an error message. By default wraps the error message in <span class="error">.
1204 * If the error message is blank, nothing is output.
1205 * @param $message the error message.
1206 * @return string the HTML to output.
1207 */
1208 public function error_text($message) {
1209 if (empty($message)) {
1210 return '';
1211 }
1212 return $this->output_tag('span', array('class' => 'error'), $message);
1213 }
34a2777c 1214
1215 /**
1216 * Do not call this function directly.
1217 *
1218 * To terminate the current script with a fatal error, call the {@link print_error}
1219 * function, or throw an exception. Doing either of those things will then call this
1220 * funciton to display the error, before terminating the exection.
1221 *
1222 * @param string $message
1223 * @param string $moreinfourl
1224 * @param string $link
1225 * @param array $backtrace
1226 * @param string $debuginfo
1227 * @param bool $showerrordebugwarning
1228 * @return string the HTML to output.
1229 */
1230 public function fatal_error($message, $moreinfourl, $link, $backtrace,
1231 $debuginfo = null, $showerrordebugwarning = false) {
1232
1233 $output = '';
1234
e8775320 1235 if ($this->has_started()) {
1236 $output .= $this->opencontainers->pop_all_but_last();
1237 } else {
34a2777c 1238 // Header not yet printed
1239 @header('HTTP/1.0 404 Not Found');
c84a2dbe 1240 $this->page->set_title(get_string('error'));
1241 $output .= $this->header();
34a2777c 1242 }
1243
1244 $message = '<p class="errormessage">' . $message . '</p>'.
1245 '<p class="errorcode"><a href="' . $moreinfourl . '">' .
1246 get_string('moreinformation') . '</a></p>';
1247 $output .= $this->box($message, 'errorbox');
1248
1249 if (debugging('', DEBUG_DEVELOPER)) {
1250 if ($showerrordebugwarning) {
1251 $output .= $this->notification('error() is a deprecated function. ' .
1252 'Please call print_error() instead of error()', 'notifytiny');
1253 }
1254 if (!empty($debuginfo)) {
1255 $output .= $this->notification($debuginfo, 'notifytiny');
1256 }
1257 if (!empty($backtrace)) {
1258 $output .= $this->notification('Stack trace: ' .
1259 format_backtrace($backtrace, true), 'notifytiny');
1260 }
1261 }
1262
1263 if (!empty($link)) {
1264 $output .= $this->continue_button($link);
1265 }
1266
c84a2dbe 1267 $output .= $this->footer();
34a2777c 1268
1269 // Padding to encourage IE to display our error page, rather than its own.
1270 $output .= str_repeat(' ', 512);
1271
1272 return $output;
1273 }
1274
1275 /**
1276 * Output a notification (that is, a status message about something that has
1277 * just happened).
1278 *
1279 * @param string $message the message to print out
1280 * @param string $classes normally 'notifyproblem' or 'notifysuccess'.
1281 * @return string the HTML to output.
1282 */
1283 public function notification($message, $classes = 'notifyproblem') {
1284 return $this->output_tag('div', array('class' =>
1285 moodle_renderer_base::prepare_classes($classes)), clean_text($message));
1286 }
1287
1288 /**
1289 * Print a continue button that goes to a particular URL.
1290 *
1291 * @param string|moodle_url $link The url the button goes to.
1292 * @return string the HTML to output.
1293 */
1294 public function continue_button($link) {
1295 if (!is_a($link, 'moodle_url')) {
1296 $link = new moodle_url($link);
1297 }
1298 return $this->output_tag('div', array('class' => 'continuebutton'),
1299 print_single_button($link->out(true), $link->params(), get_string('continue'), 'get', '', true));
1300 }
1301
1302 /**
1303 * Output the place a skip link goes to.
1304 * @param $id The target name from the corresponding $PAGE->requires->skip_link_to($target) call.
1305 * @return string the HTML to output.
1306 */
1307 public function skip_link_target($id = 'maincontent') {
1308 return $this->output_tag('span', array('id' => $id), '');
1309 }
1310
1311 public function heading($text, $level, $classes = 'main', $id = '') {
1312 $level = (integer) $level;
1313 if ($level < 1 or $level > 6) {
1314 throw new coding_exception('Heading level must be an integer between 1 and 6.');
1315 }
1316 return $this->output_tag('h' . $level,
1317 array('id' => $id, 'class' => moodle_renderer_base::prepare_classes($classes)), $text);
1318 }
1319
1320 public function box($contents, $classes = 'generalbox', $id = '') {
1321 return $this->box_start($classes, $id) . $contents . $this->box_end();
1322 }
1323
1324 public function box_start($classes = 'generalbox', $id = '') {
1325 $this->opencontainers->push('box', $this->output_end_tag('div'));
1326 return $this->output_start_tag('div', array('id' => $id,
1327 'class' => 'box ' . moodle_renderer_base::prepare_classes($classes)));
1328 }
1329
1330 public function box_end() {
1331 return $this->opencontainers->pop('box');
1332 }
1333
1334 public function container($contents, $classes = '', $id = '') {
1335 return $this->container_start($classes, $id) . $contents . $this->container_end();
1336 }
1337
1338 public function container_start($classes = '', $id = '') {
1339 $this->opencontainers->push('container', $this->output_end_tag('div'));
1340 return $this->output_start_tag('div', array('id' => $id,
1341 'class' => moodle_renderer_base::prepare_classes($classes)));
1342 }
1343
1344 public function container_end() {
1345 return $this->opencontainers->pop('container');
1346 }
8954245a 1347}
1348
1349
1350/**
1351 * Base class for classes representing HTML elements, like moodle_select_menu.
1352 *
1353 * Handles the id and class attribues.
1354 *
1355 * @copyright 2009 Tim Hunt
1356 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1357 * @since Moodle 2.0
1358 */
1359class moodle_html_component {
1360 /**
1361 * @var string value to use for the id attribute of this HTML tag.
1362 */
1363 public $id = '';
1364 /**
1365 * @var array class names to add to this HTML element.
1366 */
1367 public $classes = array();
1368
1369 /**
1370 * Ensure some class names are an array.
1371 * @param mixed $classes either an array of class names or a space-separated
1372 * string containing class names.
1373 * @return array the class names as an array.
1374 */
1375 public static function clean_clases($classes) {
1376 if (is_array($classes)) {
1377 return $classes;
1378 } else {
1379 return explode(' '. trim($classes));
1380 }
1381 }
1382
1383 /**
1384 * Set the class name array.
1385 * @param mixed $classes either an array of class names or a space-separated
1386 * string containing class names.
1387 */
1388 public function set_classes($classes) {
1389 $this->classes = self::clean_clases($classes);
1390 }
1391
1392 /**
1393 * Add a class name to the class names array.
1394 * @param string $class the new class name to add.
1395 */
1396 public function add_class($class) {
1397 $this->classes[] = $class;
1398 }
1399
1400 /**
1401 * Add a whole lot of class names to the class names array.
1402 * @param mixed $classes either an array of class names or a space-separated
1403 * string containing class names.
1404 */
1405 public function add_classes($classes) {
1406 $this->classes += self::clean_clases($classes);
1407 }
1408
1409 /**
1410 * Get the class names as a string.
1411 * @return string the class names as a space-separated string. Ready to be put in the class="" attribute.
1412 */
1413 public function get_classes_string() {
1414 return implode(' ', $this->classes);
1415 }
1416
1417 /**
1418 * Perform any cleanup or final processing that should be done before an
1419 * instance of this class is output.
1420 */
1421 public function prepare() {
1422 $this->classes = array_unique(self::clean_clases($this->classes));
1423 }
1424}
1425
1426
1427/**
1428 * This class hold all the information required to describe a <select> menu that
34a2777c 1429 * will be printed by {@link moodle_core_renderer::select_menu()}. (Or by an overridden
8954245a 1430 * version of that method in a subclass.)
1431 *
1432 * All the fields that are not set by the constructor have sensible defaults, so
1433 * you only need to set the properties where you want non-default behaviour.
1434 *
1435 * @copyright 2009 Tim Hunt
1436 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1437 * @since Moodle 2.0
1438 */
1439class moodle_select_menu extends moodle_html_component {
1440 /**
1441 * @var array the choices to show in the menu. An array $value => $label.
1442 */
1443 public $options;
1444 /**
1445 * @var string the name of this form control. That is, the name of the GET/POST
1446 * variable that will be set if this select is submmitted as part of a form.
1447 */
1448 public $name;
1449 /**
1450 * @var string the option to select initially. Should match one
1451 * of the $options array keys. Default none.
1452 */
1453 public $selectedvalue;
1454 /**
1455 * @var string The label for the 'nothing is selected' option.
1456 * Defaults to get_string('choosedots').
1457 * Set this to '' if you do not want a 'nothing is selected' option.
1458 */
1459 public $nothinglabel = null;
1460 /**
1461 * @var string The value returned by the 'nothing is selected' option. Defaults to 0.
1462 */
1463 public $nothingvalue = 0;
1464 /**
1465 * @var boolean set this to true if you want the control to appear disabled.
1466 */
1467 public $disabled = false;
1468 /**
1469 * @var integer if non-zero, sets the tabindex attribute on the <select> element. Default 0.
1470 */
1471 public $tabindex = 0;
1472 /**
1473 * @var mixed Defaults to false, which means display the select as a dropdown menu.
1474 * If true, display this select as a list box whose size is chosen automatically.
1475 * If an integer, display as list box of that size.
1476 */
1477 public $listbox = false;
1478 /**
1479 * @var integer if you are using $listbox === true to get an automatically
1480 * sized list box, the size of the list box will be the number of options,
1481 * or this number, whichever is smaller.
1482 */
1483 public $maxautosize = 10;
1484 /**
1485 * @var boolean if true, allow multiple selection. Only used if $listbox is true.
1486 */
1487 public $multiple = false;
1488 /**
1489 * @deprecated
1490 * @var string JavaScript to add as an onchange attribute. Do not use this.
1491 * Use the YUI even library instead.
1492 */
1493 public $script = '';
1494
1495 /* @see lib/moodle_html_component#prepare() */
1496 public function prepare() {
1497 if (empty($this->id)) {
1498 $this->id = 'menu' . str_replace(array('[', ']'), '', $this->name);
1499 }
1500 if (empty($this->classes)) {
1501 $this->set_classes(array('menu' . str_replace(array('[', ']'), '', $this->name)));
1502 }
1503 $this->add_class('select');
1504 parent::prepare();
1505 }
1506
1507 /**
1508 * This is a shortcut for making a simple select menu. It lets you specify
1509 * the options, name and selected option in one line of code.
1510 * @param array $options used to initialise {@link $options}.
1511 * @param string $name used to initialise {@link $name}.
1512 * @param string $selected used to initialise {@link $selected}.
1513 * @return moodle_select_menu A moodle_select_menu object with the three common fields initialised.
1514 */
1515 public static function make($options, $name, $selected = '') {
1516 $menu = new moodle_select_menu();
1517 $menu->options = $options;
1518 $menu->name = $name;
1519 $menu->selectedvalue = $selected;
1520 return $menu;
1521 }
1522
1523 /**
1524 * This is a shortcut for making a yes/no select menu.
1525 * @param string $name used to initialise {@link $name}.
1526 * @param string $selected used to initialise {@link $selected}.
1527 * @return moodle_select_menu A menu initialised with yes/no options.
1528 */
1529 public static function make_yes_no($name, $selected) {
1530 return self::make(array(0 => get_string('no'), 1 => get_string('yes')), $name, $selected);
1531 }
571fa828 1532}
1533
1534
34a2777c 1535/**
1536 * A renderer that generates output for commandlines scripts.
1537 *
1538 * The implementation of this renderer is probably incomplete.
1539 *
1540 * @copyright 2009 Tim Hunt
1541 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1542 * @since Moodle 2.0
1543 */
1544class cli_core_renderer extends moodle_core_renderer {
1545 public function header() {
1546 output_starting_hook();
1547 return $this->page->heading . "\n";
1548 }
1549
1550 public function heading($text, $level, $classes = 'main', $id = '') {
1551 $text .= "\n";
1552 switch ($level) {
1553 case 1:
1554 return '=>' . $text;
1555 case 2:
1556 return '-->' . $text;
1557 default:
1558 return $text;
1559 }
1560 }
1561
1562 public function fatal_error($errorcode, $module, $a, $link, $backtrace,
1563 $debuginfo = null, $showerrordebugwarning = false) {
1564 $output = "!!! $message !!!\n";
1565
1566 if (debugging('', DEBUG_DEVELOPER)) {
1567 if (!empty($debuginfo)) {
1568 $this->notification($debuginfo, 'notifytiny');
1569 }
1570 if (!empty($backtrace)) {
1571 $this->notification('Stack trace: ' . format_backtrace($backtrace, true), 'notifytiny');
1572 }
1573 }
1574 }
1575
1576 public function notification($message, $classes = 'notifyproblem') {
1577 $message = clean_text($message);
1578 if ($style === 'notifysuccess') {
1579 return "++ $message ++\n";
1580 }
1581 return "!! $message !!\n";
1582 }
1583}
1584
1585
571fa828 1586/**
1587 * A renderer for the custom corner theme, and other themes based on it.
1588 *
1589 * Generates the slightly different HTML that the custom corners theme wants.
1590 *
1591 * @copyright 2009 Tim Hunt
1592 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1593 * @since Moodle 2.0
1594 */
1595class custom_corners_core_renderer extends moodle_core_renderer {
c84a2dbe 1596 protected $wraplevel = 1;
1597
34a2777c 1598 protected function custom_corners_divs($classes = '', $idbase = '') {
1599 if (strpos($classes, 'clearfix') !== false) {
1600 $clearfix = ' clearfix';
1601 $classes = trim(str_replace('clearfix', '', $classes));
1602 } else {
1603 $clearfix = '';
1604 }
571fa828 1605
34a2777c 1606 // Analise if we want ids for the custom corner elements
1607 $id = '';
1608 $idbt = '';
1609 $idi1 = '';
1610 $idi2 = '';
1611 $idi3 = '';
1612 $idbb = '';
1613 if ($idbase) {
1614 $id = $idbase;
1615 $idbt = $idbase . '-bt';
1616 $idi1 = $idbase . '-i1';
1617 $idi2 = $idbase . '-i2';
1618 $idi3 = $idbase . '-i3';
1619 $idbb = $idbase . '-bb';
1620 }
1621
34a2777c 1622 // Create start tags.
c84a2dbe 1623 $start = $this->output_start_tag('div', array('id' => $id, 'class' => "wrap wraplevel{$this->wraplevel} $classes")) . "\n";
34a2777c 1624 $start .= $this->output_tag('div', array('id' => $idbt, 'class' => 'bt'), '<div>&nbsp;</div>') . "\n";
1625 $start .= $this->output_start_tag('div', array('id' => $idi1, 'class' => 'i1'));
1626 $start .= $this->output_start_tag('div', array('id' => $idi2, 'class' => 'i2'));
1627 $start .= $this->output_start_tag('div', array('id' => $idi3, 'class' => "i3$clearfix"));
1628
1629 // Create end tags.
1630 $end = $this->output_end_tag('div');
1631 $end .= $this->output_end_tag('div');
1632 $end .= $this->output_end_tag('div');
1633 $end .= $this->output_tag('div', array('id' => $idbb, 'class' => 'bb'), '<div>&nbsp;</div>') . "\n";
1634 $end .= $this->output_end_tag('div');
1635
1636 return array($start, $end);
1637 }
571fa828 1638
34a2777c 1639 public function box_start($classes = 'generalbox', $id = '') {
1640 list($start, $end) = $this->custom_corners_divs('ccbox box ' . moodle_renderer_base::prepare_classes($classes), $id);
1641 $this->opencontainers->push('box', $end);
c84a2dbe 1642 $this->wraplevel += 1;
34a2777c 1643 return $start;
1644 }
1645
c84a2dbe 1646 public function box_end() {
1647 $this->wraplevel -= 1;
1648 return parent::box_end();
1649 }
1650
34a2777c 1651 public function container_start($classes = '', $id = '') {
1652 list($start, $end) = $this->custom_corners_divs(moodle_renderer_base::prepare_classes($classes), $id);
1653 $this->opencontainers->push('container', $end);
c84a2dbe 1654 $this->wraplevel += 1;
34a2777c 1655 return $start;
1656 }
c84a2dbe 1657
1658 public function container_end() {
1659 $this->wraplevel -= 1;
1660 return parent::container_end();
1661 }
34a2777c 1662}