MDL-19077 - change how $OUTPUT is initialised.
[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;
746
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 }
808 ob_start();
809 include($CFG->javascript);
810 $output .= ob_get_contents();
811 ob_end_clean();
812 $output .= $this->page->requires->get_head_code();
813
814 foreach ($this->page->alternateversions as $type => $alt) {
815 $output .= $this->output_empty_tag('link', array('rel' => 'alternate',
816 'type' => $type, 'title' => $alt->title, 'href' => $alt->url));
817 }
818
819 // Add the meta page from the themes if any were requested
820 // TODO kill this.
821 $PAGE = $this->page;
822 $metapage = '';
823 if (!isset($THEME->standardmetainclude) || $THEME->standardmetainclude) {
824 ob_start();
825 include_once($CFG->dirroot.'/theme/standard/meta.php');
826 $output .= ob_get_contents();
827 ob_end_clean();
828 }
829 if ($THEME->parent && (!isset($THEME->parentmetainclude) || $THEME->parentmetainclude)) {
830 if (file_exists($CFG->dirroot.'/theme/'.$THEME->parent.'/meta.php')) {
831 ob_start();
832 include_once($CFG->dirroot.'/theme/'.$THEME->parent.'/meta.php');
833 $output .= ob_get_contents();
834 ob_end_clean();
835 }
836 }
837 if (!isset($THEME->metainclude) || $THEME->metainclude) {
838 if (file_exists($CFG->dirroot.'/theme/'.current_theme().'/meta.php')) {
839 ob_start();
840 include_once($CFG->dirroot.'/theme/'.current_theme().'/meta.php');
841 $output .= ob_get_contents();
842 ob_end_clean();
843 }
844 }
845
846 return $output;
847 }
848
849 public function standard_top_of_body_html() {
850 return $this->page->requires->get_top_of_body_code();
851 }
852
853 public function standard_footer_html() {
854 $output = self::PERFORMANCE_INFO_TOKEN;
855 if (debugging()) {
856 $output .= '<div class="validators"><ul>
857 <li><a href="http://validator.w3.org/check?verbose=1&amp;ss=1&amp;uri=' . urlencode(qualified_me()) . '">Validate HTML</a></li>
858 <li><a href="http://www.contentquality.com/mynewtester/cynthia.exe?rptmode=-1&amp;url1=' . urlencode(qualified_me()) . '">Section 508 Check</a></li>
859 <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>
860 </ul></div>';
861 }
862 return $output;
863 }
864
865 public function standard_end_of_body_html() {
866 echo self::END_HTML_TOKEN;
867 }
868
869 public function login_info() {
870 global $USER;
871 return user_login_string($this->page->course, $USER);
872 }
873
874 public function home_link() {
875 global $CFG, $SITE;
876
877 if ($this->page->pagetype == 'site-index') {
878 // Special case for site home page - please do not remove
879 return '<div class="sitelink">' .
880 '<a title="Moodle ' . $CFG->release . '" href="http://moodle.org/">' .
881 '<img style="width:100px;height:30px" src="' . $CFG->httpswwwroot . '/pix/moodlelogo.gif" alt="moodlelogo" /></a></div>';
882
883 } else if (!empty($CFG->target_release) && $CFG->target_release != $CFG->release) {
884 // Special case for during install/upgrade.
885 return '<div class="sitelink">'.
886 '<a title="Moodle ' . $CFG->target_release . '" href="http://docs.moodle.org/en/Administrator_documentation" onclick="this.target=\'_blank\'">' .
887 '<img style="width:100px;height:30px" src="' . $CFG->httpswwwroot . '/pix/moodlelogo.gif" alt="moodlelogo" /></a></div>';
888
889 } else if ($this->page->course->id == $SITE->id || strpos($this->page->pagetype, 'course-view') === 0) {
890 return '<div class="homelink"><a href="' . $CFG->wwwroot . '/">' .
891 get_string('home') . '</a></div>';
892
893 } else {
894 return '<div class="homelink"><a href="' . $CFG->wwwroot . '/course/view.php?id=' . $this->page->course->id . '">' .
895 format_string($this->page->course->shortname) . '</a></div>';
896 }
897 }
898
899 // TODO remove $navigation and $menu arguments - replace with $PAGE->navigation
900 public function header($navigation = '', $menu='') {
901 global $USER, $CFG;
902
903 output_starting_hook();
904 $this->page->set_state(moodle_page::STATE_PRINTING_HEADER);
905
906 // Add any stylesheets required using the horrible legacy mechanism. TODO kill this.
907 foreach ($CFG->stylesheets as $stylesheet) {
908 $this->page->requires->css($stylesheet, true);
909 }
910
911 // Find the appropriate page template, based on $this->page->generaltype.
912 $templatefile = $this->find_page_template();
913 if ($templatefile) {
914 // Render the template.
915 $template = $this->render_page_template($templatefile, $menu, $navigation);
916 } else {
917 // New style template not found, fall back to using header.html and footer.html.
918 $template = $this->handle_legacy_theme($navigation, $menu);
919 }
920
921 // Slice the template output into header and footer.
922 $cutpos = strpos($template, self::MAIN_CONTENT_TOKEN);
923 if ($cutpos === false) {
924 throw new coding_exception('Layout template ' . $templatefile .
925 ' does not contain the string "' . self::MAIN_CONTENT_TOKEN . '".');
926 }
927 $header = substr($template, 0, $cutpos);
928 $footer = substr($template, $cutpos + strlen(self::MAIN_CONTENT_TOKEN));
929
930 send_headers($this->contenttype, $this->page->cacheable);
931 $this->opencontainers->push('header/footer', $footer);
932 $this->page->set_state(moodle_page::STATE_IN_BODY);
933 return $header . $this->skip_link_target();
934 }
935
936 protected function find_page_template() {
937 global $THEME;
938
939 // If this is a particular page type, look for a specific template.
940 $type = $this->page->generaltype;
941 if ($type != 'normal') {
942 $templatefile = $THEME->dir . '/layout-' . $type . '.php';
943 if (is_readable($templatefile)) {
944 return $templatefile;
945 }
946 }
947
948 // Otherwise look for the general template.
949 $templatefile = $THEME->dir . '/layout.php';
950 if (is_readable($templatefile)) {
951 return $templatefile;
952 }
953
954 return false;
955 }
956
957 protected function render_page_template($templatefile, $menu, $navigation) {
958 global $CFG, $SITE, $THEME, $USER;
6e37fa50 959 // The next lines are a bit tricky. The point is, here we are in a method
960 // of a renderer class, and this object may, or may not, be the the same as
961 // the global $OUTPUT object. When rendering the template, we want to use
962 // this object. However, people writing Moodle code expect the current
963 // rederer to be called $OUTPUT, not $this, so define a variable called
964 // $OUTPUT pointing at $this. The same comment applies to $PAGE and $COURSE.
34a2777c 965 $OUTPUT = $this;
966 $PAGE = $this->page;
967 $COURSE = $this->page->course;
968
969 ob_start();
970 include($templatefile);
971 $template = ob_get_contents();
972 ob_end_clean();
973 return $template;
974 }
975
976 protected function handle_legacy_theme($navigation, $menu) {
977 global $CFG, $SITE, $THEME, $USER;
978 // Set a pretend global from the properties of this class.
6e37fa50 979 // See the comment in render_page_template for a fuller explanation.
34a2777c 980 $COURSE = $this->page->course;
981
982 // Set up local variables that header.html expects.
983 $direction = $this->htmlattributes();
984 $title = $this->page->title;
985 $heading = $this->page->heading;
986 $focus = $this->page->focuscontrol;
987 $button = $this->page->button;
988 $pageid = $this->page->pagetype;
989 $pageclass = $this->page->bodyclasses;
990 $bodytags = ' class="' . $pageclass . '" id="' . $pageid . '"';
991 $home = $this->page->generaltype == 'home';
992
993 $meta = $this->standard_head_html();
994 // The next line is a nasty hack. having set $meta to standard_head_html, we have already
995 // got the contents of include($CFG->javascript). However, legacy themes are going to
996 // include($CFG->javascript) again. We want to make sure that when they do, nothing is output.
997 $CFG->javascript = $CFG->libdir . '/emptyfile.php';
998
999 // Set up local variables that footer.html expects.
1000 $homelink = $this->home_link();
1001 $loggedinas = $this->login_info();
1002 $course = $this->page->course;
1003 $performanceinfo = self::PERFORMANCE_INFO_TOKEN;
1004
1005 if (!$menu && $navigation) {
1006 $menu = $loggedinas;
1007 }
1008
1009 ob_start();
1010 include($THEME->dir . '/header.html');
1011 $this->page->requires->get_top_of_body_code();
1012 echo self::MAIN_CONTENT_TOKEN;
1013
1014 $menu = str_replace('navmenu', 'navmenufooter', $menu);
1015 include($THEME->dir . '/footer.html');
1016
1017 $output = ob_get_contents();
1018 ob_end_clean();
1019
1020 $output = str_replace('</body>', self::END_HTML_TOKEN . '</body>', $output);
1021
1022 return $output;
1023 }
1024
1025 public function footer() {
1026 $output = '';
1027 if ($this->opencontainers->count() != 1) {
1028 debugging('Some HTML tags were opened in the body of the page but not closed.', DEBUG_DEVELOPER);
1029 $output .= $this->opencontainers->pop_all_but_last();
1030 }
1031
1032 $footer = $this->opencontainers->pop('header/footer');
1033
1034 // Provide some performance info if required
1035 $performanceinfo = '';
1036 if (defined('MDL_PERF') || (!empty($CFG->perfdebug) and $CFG->perfdebug > 7)) {
1037 $perf = get_performance_info();
1038 if (defined('MDL_PERFTOLOG') && !function_exists('register_shutdown_function')) {
1039 error_log("PERF: " . $perf['txt']);
1040 }
1041 if (defined('MDL_PERFTOFOOT') || debugging() || $CFG->perfdebug > 7) {
1042 $performanceinfo = $perf['html'];
1043 }
1044 }
1045 $footer = str_replace(self::PERFORMANCE_INFO_TOKEN, $performanceinfo, $footer);
1046
1047 $footer = str_replace(self::END_HTML_TOKEN, $this->page->requires->get_end_code(), $footer);
1048
1049 $this->page->set_state(moodle_page::STATE_DONE);
1050
1051 return $output . $footer;
1052 }
1053
8954245a 1054 public function link_to_popup_window() {
1055
1056 }
571fa828 1057
8954245a 1058 public function button_to_popup_window() {
1059
1060 }
1061
1062 public function close_window_button($buttontext = null, $reloadopener = false) {
1063 if (empty($buttontext)) {
1064 $buttontext = get_string('closewindow');
1065 }
1066 // TODO
1067 }
1068
1069 public function close_window($delay = 0, $reloadopener = false) {
1070 // TODO
1071 }
1072
1073 /**
1074 * Output a <select> menu.
1075 *
1076 * You can either call this function with a single moodle_select_menu argument
1077 * or, with a list of parameters, in which case those parameters are sent to
1078 * the moodle_select_menu constructor.
1079 *
1080 * @param moodle_select_menu $selectmenu a moodle_select_menu that describes
1081 * the select menu you want output.
1082 * @return string the HTML for the <select>
1083 */
1084 public function select_menu($selectmenu) {
1085 $selectmenu = clone($selectmenu);
1086 $selectmenu->prepare();
1087
1088 if ($selectmenu->nothinglabel) {
1089 $selectmenu->options = array($selectmenu->nothingvalue => $selectmenu->nothinglabel) +
1090 $selectmenu->options;
1091 }
1092
1093 if (empty($selectmenu->id)) {
1094 $selectmenu->id = 'menu' . str_replace(array('[', ']'), '', $selectmenu->name);
1095 }
1096
1097 $attributes = array(
1098 'name' => $selectmenu->name,
1099 'id' => $selectmenu->id,
1100 'class' => $selectmenu->get_classes_string(),
1101 'onchange' => $selectmenu->script,
1102 );
1103 if ($selectmenu->disabled) {
1104 $attributes['disabled'] = 'disabled';
1105 }
1106 if ($selectmenu->tabindex) {
1107 $attributes['tabindex'] = $tabindex;
1108 }
1109
1110 if ($selectmenu->listbox) {
1111 if (is_integer($selectmenu->listbox)) {
1112 $size = $selectmenu->listbox;
1113 } else {
1114 $size = min($selectmenu->maxautosize, count($selectmenu->options));
1115 }
1116 $attributes['size'] = $size;
1117 if ($selectmenu->multiple) {
1118 $attributes['multiple'] = 'multiple';
1119 }
1120 }
1121
1122 $html = $this->output_start_tag('select', $attributes) . "\n";
1123 foreach ($selectmenu->options as $value => $label) {
1124 $attributes = array('value' => $value);
1125 if ((string)$value == (string)$selectmenu->selectedvalue ||
1126 (is_array($selectmenu->selectedvalue) && in_array($value, $selectmenu->selectedvalue))) {
1127 $attributes['selected'] = 'selected';
1128 }
1129 $html .= ' ' . $this->output_tag('option', $attributes, s($label)) . "\n";
1130 }
1131 $html .= $this->output_end_tag('select') . "\n";
1132
1133 return $html;
1134 }
1135
1136 // TODO choose_from_menu_nested
1137
1138 // TODO choose_from_radio
1139
1140 /**
1141 * Output an error message. By default wraps the error message in <span class="error">.
1142 * If the error message is blank, nothing is output.
1143 * @param $message the error message.
1144 * @return string the HTML to output.
1145 */
1146 public function error_text($message) {
1147 if (empty($message)) {
1148 return '';
1149 }
1150 return $this->output_tag('span', array('class' => 'error'), $message);
1151 }
34a2777c 1152
1153 /**
1154 * Do not call this function directly.
1155 *
1156 * To terminate the current script with a fatal error, call the {@link print_error}
1157 * function, or throw an exception. Doing either of those things will then call this
1158 * funciton to display the error, before terminating the exection.
1159 *
1160 * @param string $message
1161 * @param string $moreinfourl
1162 * @param string $link
1163 * @param array $backtrace
1164 * @param string $debuginfo
1165 * @param bool $showerrordebugwarning
1166 * @return string the HTML to output.
1167 */
1168 public function fatal_error($message, $moreinfourl, $link, $backtrace,
1169 $debuginfo = null, $showerrordebugwarning = false) {
1170
1171 $output = '';
1172
1173 if ($this->opencontainers->count() == 0) {
1174 // Header not yet printed
1175 @header('HTTP/1.0 404 Not Found');
c84a2dbe 1176 $this->page->set_title(get_string('error'));
1177 $output .= $this->header();
34a2777c 1178 } else {
1179 $output .= $this->opencontainers->pop_all_but_last();
1180 }
1181
1182 $message = '<p class="errormessage">' . $message . '</p>'.
1183 '<p class="errorcode"><a href="' . $moreinfourl . '">' .
1184 get_string('moreinformation') . '</a></p>';
1185 $output .= $this->box($message, 'errorbox');
1186
1187 if (debugging('', DEBUG_DEVELOPER)) {
1188 if ($showerrordebugwarning) {
1189 $output .= $this->notification('error() is a deprecated function. ' .
1190 'Please call print_error() instead of error()', 'notifytiny');
1191 }
1192 if (!empty($debuginfo)) {
1193 $output .= $this->notification($debuginfo, 'notifytiny');
1194 }
1195 if (!empty($backtrace)) {
1196 $output .= $this->notification('Stack trace: ' .
1197 format_backtrace($backtrace, true), 'notifytiny');
1198 }
1199 }
1200
1201 if (!empty($link)) {
1202 $output .= $this->continue_button($link);
1203 }
1204
c84a2dbe 1205 $output .= $this->footer();
34a2777c 1206
1207 // Padding to encourage IE to display our error page, rather than its own.
1208 $output .= str_repeat(' ', 512);
1209
1210 return $output;
1211 }
1212
1213 /**
1214 * Output a notification (that is, a status message about something that has
1215 * just happened).
1216 *
1217 * @param string $message the message to print out
1218 * @param string $classes normally 'notifyproblem' or 'notifysuccess'.
1219 * @return string the HTML to output.
1220 */
1221 public function notification($message, $classes = 'notifyproblem') {
1222 return $this->output_tag('div', array('class' =>
1223 moodle_renderer_base::prepare_classes($classes)), clean_text($message));
1224 }
1225
1226 /**
1227 * Print a continue button that goes to a particular URL.
1228 *
1229 * @param string|moodle_url $link The url the button goes to.
1230 * @return string the HTML to output.
1231 */
1232 public function continue_button($link) {
1233 if (!is_a($link, 'moodle_url')) {
1234 $link = new moodle_url($link);
1235 }
1236 return $this->output_tag('div', array('class' => 'continuebutton'),
1237 print_single_button($link->out(true), $link->params(), get_string('continue'), 'get', '', true));
1238 }
1239
1240 /**
1241 * Output the place a skip link goes to.
1242 * @param $id The target name from the corresponding $PAGE->requires->skip_link_to($target) call.
1243 * @return string the HTML to output.
1244 */
1245 public function skip_link_target($id = 'maincontent') {
1246 return $this->output_tag('span', array('id' => $id), '');
1247 }
1248
1249 public function heading($text, $level, $classes = 'main', $id = '') {
1250 $level = (integer) $level;
1251 if ($level < 1 or $level > 6) {
1252 throw new coding_exception('Heading level must be an integer between 1 and 6.');
1253 }
1254 return $this->output_tag('h' . $level,
1255 array('id' => $id, 'class' => moodle_renderer_base::prepare_classes($classes)), $text);
1256 }
1257
1258 public function box($contents, $classes = 'generalbox', $id = '') {
1259 return $this->box_start($classes, $id) . $contents . $this->box_end();
1260 }
1261
1262 public function box_start($classes = 'generalbox', $id = '') {
1263 $this->opencontainers->push('box', $this->output_end_tag('div'));
1264 return $this->output_start_tag('div', array('id' => $id,
1265 'class' => 'box ' . moodle_renderer_base::prepare_classes($classes)));
1266 }
1267
1268 public function box_end() {
1269 return $this->opencontainers->pop('box');
1270 }
1271
1272 public function container($contents, $classes = '', $id = '') {
1273 return $this->container_start($classes, $id) . $contents . $this->container_end();
1274 }
1275
1276 public function container_start($classes = '', $id = '') {
1277 $this->opencontainers->push('container', $this->output_end_tag('div'));
1278 return $this->output_start_tag('div', array('id' => $id,
1279 'class' => moodle_renderer_base::prepare_classes($classes)));
1280 }
1281
1282 public function container_end() {
1283 return $this->opencontainers->pop('container');
1284 }
8954245a 1285}
1286
1287
1288/**
1289 * Base class for classes representing HTML elements, like moodle_select_menu.
1290 *
1291 * Handles the id and class attribues.
1292 *
1293 * @copyright 2009 Tim Hunt
1294 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1295 * @since Moodle 2.0
1296 */
1297class moodle_html_component {
1298 /**
1299 * @var string value to use for the id attribute of this HTML tag.
1300 */
1301 public $id = '';
1302 /**
1303 * @var array class names to add to this HTML element.
1304 */
1305 public $classes = array();
1306
1307 /**
1308 * Ensure some class names are an array.
1309 * @param mixed $classes either an array of class names or a space-separated
1310 * string containing class names.
1311 * @return array the class names as an array.
1312 */
1313 public static function clean_clases($classes) {
1314 if (is_array($classes)) {
1315 return $classes;
1316 } else {
1317 return explode(' '. trim($classes));
1318 }
1319 }
1320
1321 /**
1322 * Set the class name array.
1323 * @param mixed $classes either an array of class names or a space-separated
1324 * string containing class names.
1325 */
1326 public function set_classes($classes) {
1327 $this->classes = self::clean_clases($classes);
1328 }
1329
1330 /**
1331 * Add a class name to the class names array.
1332 * @param string $class the new class name to add.
1333 */
1334 public function add_class($class) {
1335 $this->classes[] = $class;
1336 }
1337
1338 /**
1339 * Add a whole lot of class names to the class names array.
1340 * @param mixed $classes either an array of class names or a space-separated
1341 * string containing class names.
1342 */
1343 public function add_classes($classes) {
1344 $this->classes += self::clean_clases($classes);
1345 }
1346
1347 /**
1348 * Get the class names as a string.
1349 * @return string the class names as a space-separated string. Ready to be put in the class="" attribute.
1350 */
1351 public function get_classes_string() {
1352 return implode(' ', $this->classes);
1353 }
1354
1355 /**
1356 * Perform any cleanup or final processing that should be done before an
1357 * instance of this class is output.
1358 */
1359 public function prepare() {
1360 $this->classes = array_unique(self::clean_clases($this->classes));
1361 }
1362}
1363
1364
1365/**
1366 * This class hold all the information required to describe a <select> menu that
34a2777c 1367 * will be printed by {@link moodle_core_renderer::select_menu()}. (Or by an overridden
8954245a 1368 * version of that method in a subclass.)
1369 *
1370 * All the fields that are not set by the constructor have sensible defaults, so
1371 * you only need to set the properties where you want non-default behaviour.
1372 *
1373 * @copyright 2009 Tim Hunt
1374 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1375 * @since Moodle 2.0
1376 */
1377class moodle_select_menu extends moodle_html_component {
1378 /**
1379 * @var array the choices to show in the menu. An array $value => $label.
1380 */
1381 public $options;
1382 /**
1383 * @var string the name of this form control. That is, the name of the GET/POST
1384 * variable that will be set if this select is submmitted as part of a form.
1385 */
1386 public $name;
1387 /**
1388 * @var string the option to select initially. Should match one
1389 * of the $options array keys. Default none.
1390 */
1391 public $selectedvalue;
1392 /**
1393 * @var string The label for the 'nothing is selected' option.
1394 * Defaults to get_string('choosedots').
1395 * Set this to '' if you do not want a 'nothing is selected' option.
1396 */
1397 public $nothinglabel = null;
1398 /**
1399 * @var string The value returned by the 'nothing is selected' option. Defaults to 0.
1400 */
1401 public $nothingvalue = 0;
1402 /**
1403 * @var boolean set this to true if you want the control to appear disabled.
1404 */
1405 public $disabled = false;
1406 /**
1407 * @var integer if non-zero, sets the tabindex attribute on the <select> element. Default 0.
1408 */
1409 public $tabindex = 0;
1410 /**
1411 * @var mixed Defaults to false, which means display the select as a dropdown menu.
1412 * If true, display this select as a list box whose size is chosen automatically.
1413 * If an integer, display as list box of that size.
1414 */
1415 public $listbox = false;
1416 /**
1417 * @var integer if you are using $listbox === true to get an automatically
1418 * sized list box, the size of the list box will be the number of options,
1419 * or this number, whichever is smaller.
1420 */
1421 public $maxautosize = 10;
1422 /**
1423 * @var boolean if true, allow multiple selection. Only used if $listbox is true.
1424 */
1425 public $multiple = false;
1426 /**
1427 * @deprecated
1428 * @var string JavaScript to add as an onchange attribute. Do not use this.
1429 * Use the YUI even library instead.
1430 */
1431 public $script = '';
1432
1433 /* @see lib/moodle_html_component#prepare() */
1434 public function prepare() {
1435 if (empty($this->id)) {
1436 $this->id = 'menu' . str_replace(array('[', ']'), '', $this->name);
1437 }
1438 if (empty($this->classes)) {
1439 $this->set_classes(array('menu' . str_replace(array('[', ']'), '', $this->name)));
1440 }
1441 $this->add_class('select');
1442 parent::prepare();
1443 }
1444
1445 /**
1446 * This is a shortcut for making a simple select menu. It lets you specify
1447 * the options, name and selected option in one line of code.
1448 * @param array $options used to initialise {@link $options}.
1449 * @param string $name used to initialise {@link $name}.
1450 * @param string $selected used to initialise {@link $selected}.
1451 * @return moodle_select_menu A moodle_select_menu object with the three common fields initialised.
1452 */
1453 public static function make($options, $name, $selected = '') {
1454 $menu = new moodle_select_menu();
1455 $menu->options = $options;
1456 $menu->name = $name;
1457 $menu->selectedvalue = $selected;
1458 return $menu;
1459 }
1460
1461 /**
1462 * This is a shortcut for making a yes/no select menu.
1463 * @param string $name used to initialise {@link $name}.
1464 * @param string $selected used to initialise {@link $selected}.
1465 * @return moodle_select_menu A menu initialised with yes/no options.
1466 */
1467 public static function make_yes_no($name, $selected) {
1468 return self::make(array(0 => get_string('no'), 1 => get_string('yes')), $name, $selected);
1469 }
571fa828 1470}
1471
1472
34a2777c 1473/**
1474 * A renderer that generates output for commandlines scripts.
1475 *
1476 * The implementation of this renderer is probably incomplete.
1477 *
1478 * @copyright 2009 Tim Hunt
1479 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1480 * @since Moodle 2.0
1481 */
1482class cli_core_renderer extends moodle_core_renderer {
1483 public function header() {
1484 output_starting_hook();
1485 return $this->page->heading . "\n";
1486 }
1487
1488 public function heading($text, $level, $classes = 'main', $id = '') {
1489 $text .= "\n";
1490 switch ($level) {
1491 case 1:
1492 return '=>' . $text;
1493 case 2:
1494 return '-->' . $text;
1495 default:
1496 return $text;
1497 }
1498 }
1499
1500 public function fatal_error($errorcode, $module, $a, $link, $backtrace,
1501 $debuginfo = null, $showerrordebugwarning = false) {
1502 $output = "!!! $message !!!\n";
1503
1504 if (debugging('', DEBUG_DEVELOPER)) {
1505 if (!empty($debuginfo)) {
1506 $this->notification($debuginfo, 'notifytiny');
1507 }
1508 if (!empty($backtrace)) {
1509 $this->notification('Stack trace: ' . format_backtrace($backtrace, true), 'notifytiny');
1510 }
1511 }
1512 }
1513
1514 public function notification($message, $classes = 'notifyproblem') {
1515 $message = clean_text($message);
1516 if ($style === 'notifysuccess') {
1517 return "++ $message ++\n";
1518 }
1519 return "!! $message !!\n";
1520 }
1521}
1522
1523
571fa828 1524/**
1525 * A renderer for the custom corner theme, and other themes based on it.
1526 *
1527 * Generates the slightly different HTML that the custom corners theme wants.
1528 *
1529 * @copyright 2009 Tim Hunt
1530 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1531 * @since Moodle 2.0
1532 */
1533class custom_corners_core_renderer extends moodle_core_renderer {
c84a2dbe 1534 protected $wraplevel = 1;
1535
34a2777c 1536 protected function custom_corners_divs($classes = '', $idbase = '') {
1537 if (strpos($classes, 'clearfix') !== false) {
1538 $clearfix = ' clearfix';
1539 $classes = trim(str_replace('clearfix', '', $classes));
1540 } else {
1541 $clearfix = '';
1542 }
571fa828 1543
34a2777c 1544 // Analise if we want ids for the custom corner elements
1545 $id = '';
1546 $idbt = '';
1547 $idi1 = '';
1548 $idi2 = '';
1549 $idi3 = '';
1550 $idbb = '';
1551 if ($idbase) {
1552 $id = $idbase;
1553 $idbt = $idbase . '-bt';
1554 $idi1 = $idbase . '-i1';
1555 $idi2 = $idbase . '-i2';
1556 $idi3 = $idbase . '-i3';
1557 $idbb = $idbase . '-bb';
1558 }
1559
34a2777c 1560 // Create start tags.
c84a2dbe 1561 $start = $this->output_start_tag('div', array('id' => $id, 'class' => "wrap wraplevel{$this->wraplevel} $classes")) . "\n";
34a2777c 1562 $start .= $this->output_tag('div', array('id' => $idbt, 'class' => 'bt'), '<div>&nbsp;</div>') . "\n";
1563 $start .= $this->output_start_tag('div', array('id' => $idi1, 'class' => 'i1'));
1564 $start .= $this->output_start_tag('div', array('id' => $idi2, 'class' => 'i2'));
1565 $start .= $this->output_start_tag('div', array('id' => $idi3, 'class' => "i3$clearfix"));
1566
1567 // Create end tags.
1568 $end = $this->output_end_tag('div');
1569 $end .= $this->output_end_tag('div');
1570 $end .= $this->output_end_tag('div');
1571 $end .= $this->output_tag('div', array('id' => $idbb, 'class' => 'bb'), '<div>&nbsp;</div>') . "\n";
1572 $end .= $this->output_end_tag('div');
1573
1574 return array($start, $end);
1575 }
571fa828 1576
34a2777c 1577 public function box_start($classes = 'generalbox', $id = '') {
1578 list($start, $end) = $this->custom_corners_divs('ccbox box ' . moodle_renderer_base::prepare_classes($classes), $id);
1579 $this->opencontainers->push('box', $end);
c84a2dbe 1580 $this->wraplevel += 1;
34a2777c 1581 return $start;
1582 }
1583
c84a2dbe 1584 public function box_end() {
1585 $this->wraplevel -= 1;
1586 return parent::box_end();
1587 }
1588
34a2777c 1589 public function container_start($classes = '', $id = '') {
1590 list($start, $end) = $this->custom_corners_divs(moodle_renderer_base::prepare_classes($classes), $id);
1591 $this->opencontainers->push('container', $end);
c84a2dbe 1592 $this->wraplevel += 1;
34a2777c 1593 return $start;
1594 }
c84a2dbe 1595
1596 public function container_end() {
1597 $this->wraplevel -= 1;
1598 return parent::container_end();
1599 }
34a2777c 1600}