Catch another potential type of infinite recursion output initialisation.
[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
b7009474 27 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
571fa828 28 */
29
30
31/**
32 * A renderer factory is just responsible for creating an appropriate renderer
33 * for any given part of Moodle.
34 *
35 * Which renderer factory to use is chose by the current theme, and an instance
36 * if created automatically when the theme is set up.
37 *
ebebf55c 38 * A renderer factory must also have a constructor that takes a theme_config object.
39 * (See {@link renderer_factory_base::__construct} for an example.)
571fa828 40 *
41 * @copyright 2009 Tim Hunt
42 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
43 * @since Moodle 2.0
44 */
45interface renderer_factory {
46 /**
47 * Return the renderer for a particular part of Moodle.
48 *
49 * The renderer interfaces are defined by classes called moodle_..._renderer
50 * where ... is the name of the module, which, will be defined in this file
51 * for core parts of Moodle, and in a file called renderer.php for plugins.
52 *
53 * There is no separate interface definintion for renderers. Instead we
54 * take advantage of PHP being a dynamic languages. The renderer returned
55 * does not need to be a subclass of the moodle_..._renderer base class, it
56 * just needs to impmenent the same interface. This is sometimes called
57 * 'Duck typing'. For a tricky example, see {@link template_renderer} below.
58 * renderer ob
59 *
ebebf55c 60 * @param string $component name such as 'core', 'mod_forum' or 'qtype_multichoice'.
61 * @param moodle_page $page the page the renderer is outputting content for.
571fa828 62 * @return object an object implementing the requested renderer interface.
63 */
ebebf55c 64 public function get_renderer($component, $page);
571fa828 65}
66
67
68/**
ebebf55c 69 * An icon finder is responsible for working out the correct URL for an icon.
571fa828 70 *
ebebf55c 71 * A icon finder must also have a constructor that takes a theme object.
72 * (See {@link standard_icon_finder::__construct} for an example.)
571fa828 73 *
ebebf55c 74 * Note that we are planning to change the Moodle icon naming convention before
75 * the Moodle 2.0 relase. Therefore, this API will probably change.
571fa828 76 *
77 * @copyright 2009 Tim Hunt
78 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
79 * @since Moodle 2.0
80 */
ebebf55c 81interface icon_finder {
571fa828 82 /**
ebebf55c 83 * Return the URL for an icon indentifed as in pre-Moodle 2.0 code.
84 *
85 * Suppose you have old code like $url = "$CFG->pixpath/i/course.gif";
86 * then old_icon_url('i/course'); will return the equivalent URL that is correct now.
87 *
88 * @param $iconname the name of the icon.
89 * @return string the URL for that icon.
571fa828 90 */
ebebf55c 91 public function old_icon_url($iconname);
92
571fa828 93 /**
ebebf55c 94 * Return the URL for an icon indentifed as in pre-Moodle 2.0 code.
571fa828 95 *
ebebf55c 96 * Suppose you have old code like $url = "$CFG->modpixpath/$mod/icon.gif";
97 * then mod_icon_url('icon', $mod); will return the equivalent URL that is correct now.
571fa828 98 *
ebebf55c 99 * @param $iconname the name of the icon.
100 * @param $module the module the icon belongs to.
101 * @return string the URL for that icon.
571fa828 102 */
ebebf55c 103 public function mod_icon_url($iconname, $module);
571fa828 104}
105
106
107/**
ebebf55c 108 *This class represents the configuration variables of a Moodle theme.
109 *
110 * Normally, to create an instance of this class, you should use the
111 * {@link theme_config::load()} factory method to load a themes config.php file.
571fa828 112 *
113 * @copyright 2009 Tim Hunt
114 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
115 * @since Moodle 2.0
116 */
ebebf55c 117class theme_config {
118 /**
119 * @var array The names of all the stylesheets from this theme that you would
120 * like included, in order.
121 */
122 public $sheets = array('styles_layout', 'styles_fonts', 'styles_color');
571fa828 123
ebebf55c 124 public $standardsheets = true;
571fa828 125
ebebf55c 126/// This variable can be set to an array containing
127/// filenames from the *STANDARD* theme. If the
128/// array exists, it will be used to choose the
129/// files to include in the standard style sheet.
130/// When false, then no files are used.
131/// When true or NON-EXISTENT, then ALL standard files are used.
132/// This parameter can be used, for example, to prevent
133/// having to override too many classes.
134/// Note that the trailing .css should not be included
135/// eg $THEME->standardsheets = array('styles_layout','styles_fonts','styles_color');
136////////////////////////////////////////////////////////////////////////////////
571fa828 137
138
ebebf55c 139 public $parent = null;
571fa828 140
ebebf55c 141/// This variable can be set to the name of a parent theme
142/// which you want to have included before the current theme.
143/// This can make it easy to make modifications to another
144/// theme without having to actually change the files
145/// If this variable is empty or false then a parent theme
146/// is not used.
147////////////////////////////////////////////////////////////////////////////////
571fa828 148
571fa828 149
ebebf55c 150 public $parentsheets = false;
571fa828 151
ebebf55c 152/// This variable can be set to an array containing
153/// filenames from a chosen *PARENT* theme. If the
154/// array exists, it will be used to choose the
155/// files to include in the standard style sheet.
156/// When false, then no files are used.
157/// When true or NON-EXISTENT, then ALL standard files are used.
158/// This parameter can be used, for example, to prevent
159/// having to override too many classes.
160/// Note that the trailing .css should not be included
161/// eg $THEME->parentsheets = array('styles_layout','styles_fonts','styles_color');
162////////////////////////////////////////////////////////////////////////////////
571fa828 163
571fa828 164
ebebf55c 165 public $modsheets = true;
571fa828 166
ebebf55c 167/// When this is enabled, then this theme will search for
168/// files named "styles.php" inside all Activity modules and
169/// include them. This allows modules to provide some basic
170/// layouts so they work out of the box.
171/// It is HIGHLY recommended to leave this enabled.
571fa828 172
571fa828 173
ebebf55c 174 public $blocksheets = true;
571fa828 175
ebebf55c 176/// When this is enabled, then this theme will search for
177/// files named "styles.php" inside all Block modules and
178/// include them. This allows Blocks to provide some basic
179/// layouts so they work out of the box.
180/// It is HIGHLY recommended to leave this enabled.
571fa828 181
571fa828 182
ebebf55c 183 public $langsheets = false;
8954245a 184
ebebf55c 185/// By setting this to true, then this theme will search for
186/// a file named "styles.php" inside the current language
187/// directory. This allows different languages to provide
188/// different styles.
b7009474 189
8954245a 190
ebebf55c 191 public $courseformatsheets = true;
571fa828 192
ebebf55c 193/// When this is enabled, this theme will search for files
194/// named "styles.php" inside all course formats and
195/// include them. This allows course formats to provide
196/// their own default styles.
571fa828 197
571fa828 198
ebebf55c 199 public $metainclude = false;
571fa828 200
ebebf55c 201/// When this is enabled (or not set!) then Moodle will try
202/// to include a file meta.php from this theme into the
203/// <head></head> part of the page.
571fa828 204
571fa828 205
ebebf55c 206 public $standardmetainclude = true;
571fa828 207
571fa828 208
ebebf55c 209/// When this is enabled (or not set!) then Moodle will try
210/// to include a file meta.php from the standard theme into the
211/// <head></head> part of the page.
571fa828 212
571fa828 213
ebebf55c 214 public $parentmetainclude = false;
571fa828 215
ebebf55c 216/// When this is enabled (or not set!) then Moodle will try
217/// to include a file meta.php from the parent theme into the
218/// <head></head> part of the page.
571fa828 219
571fa828 220
ebebf55c 221 public $navmenuwidth = 50;
571fa828 222
ebebf55c 223/// You can use this to control the cutoff point for strings
224/// in the navmenus (list of activities in popup menu etc)
225/// Default is 50 characters wide.
571fa828 226
571fa828 227
ebebf55c 228 public $makenavmenulist = false;
571fa828 229
ebebf55c 230/// By setting this to true, then you will have access to a
231/// new variable in your header.html and footer.html called
232/// $navmenulist ... this contains a simple XHTML menu of
233/// all activities in the current course, mostly useful for
234/// creating popup navigation menus and so on.
571fa828 235
571fa828 236
571fa828 237
ebebf55c 238 public $resource_mp3player_colors = 'bgColour=000000&btnColour=ffffff&btnBorderColour=cccccc&iconColour=000000&iconOverColour=00cc00&trackColour=cccccc&handleColour=ffffff&loaderColour=ffffff&font=Arial&fontColour=3333FF&buffer=10&waitForPlay=no&autoPlay=yes';
571fa828 239
ebebf55c 240/// With this you can control the colours of the "big" MP3 player
241/// that is used for MP3 resources.
242
243
244 public $filter_mediaplugin_colors = 'bgColour=000000&btnColour=ffffff&btnBorderColour=cccccc&iconColour=000000&iconOverColour=00cc00&trackColour=cccccc&handleColour=ffffff&loaderColour=ffffff&waitForPlay=yes';
245
246/// ...And this controls the small embedded player
247
248
249 public $custompix = false;
250
251/// If true, then this theme must have a "pix"
252/// subdirectory that contains copies of all
253/// files from the moodle/pix directory, plus a
254/// "pix/mod" directory containing all the icons
255/// for all the activity modules.
256
257
258///$THEME->rarrow = '&#x25BA;' //OR '&rarr;';
259///$THEME->larrow = '&#x25C4;' //OR '&larr;';
260///$CFG->block_search_button = link_arrow_right(get_string('search'), $url='', $accesshide=true);
261///
262/// Accessibility: Right and left arrow-like characters are
263/// used in the breadcrumb trail, course navigation menu
264/// (previous/next activity), calendar, and search forum block.
265///
266/// If the theme does not set characters, appropriate defaults
267/// are set by (lib/weblib.php:check_theme_arrows). The suggestions
268/// above are 'silent' in a screen-reader like JAWS. Please DO NOT
269/// use &lt; &gt; &raquo; - these are confusing for blind users.
270////////////////////////////////////////////////////////////////////////////////
271
272
273 public $blockregions = array('side-pre', 'side-post');
274 public $defaultblockregion = 'side-post';
275/// Areas where blocks may appear on any page that uses this theme. For each
276/// region you list in $THEME->blockregions you must call blocks_print_group
277/// with that region id somewhere in header.html or footer.html.
278/// defaultblockregion is the region where new blocks will be added, and
279/// where any blocks in unrecognised regions will be shown. (Suppose someone
280/// added a block when anther theme was selected).
281////////////////////////////////////////////////////////////////////////////////
282
283 /** @var string the name of this theme. Set automatically. */
284 public $name;
285 /** @var string the folder where this themes fiels are stored. $CFG->themedir . '/' . $this->name */
286 public $dir;
287
288 /** @var string Name of the renderer factory class to use. */
289 public $rendererfactory = 'standard_renderer_factory';
290 /** @var renderer_factory Instance of the renderer_factory class. */
291 protected $rf = null;
292
293 /** @var string Name of the icon finder class to use. */
294 public $iconfinder = 'pix_icon_finder';
295 /** @var renderer_factory Instance of the renderer_factory class. */
296 protected $if = null;
571fa828 297
298 /**
ebebf55c 299 * If you want to do custom processing on the CSS before it is output (for
300 * example, to replace certain variable names with particular values) you can
301 * give the name of a function here.
302 *
303 * There are two functions avaiable that you may wish to use (defined in lib/outputlib.php):
304 * output_css_replacing_constants
305 * output_css_for_css_edit
306 * If you wish to write your own function, use those two as examples, and it
307 * should be clear what you have to do.
308 *
309 * @var string the name of a function.
571fa828 310 */
ebebf55c 311 public $customcssoutputfunction = null;
571fa828 312
313 /**
ebebf55c 314 * Load the config.php file for a particular theme, and return an instance
315 * of this class. (That is, this is a factory method.)
316 *
317 * @param string $themename the name of the theme.
318 * @return theme_config an instance of this class.
571fa828 319 */
ebebf55c 320 public static function load($themename) {
321 global $CFG;
571fa828 322
ebebf55c 323 // We have to use the variable name $THEME (upper case) becuase that
324 // is what is used in theme config.php files.
325
326 // Set some other standard properties of the theme.
327 $THEME = new theme_config;
328 $THEME->name = $themename;
329 $THEME->dir = $CFG->themedir . '/' . $themename;
330
331 // Load up the theme config
332 $configfile = $THEME->dir . '/config.php';
333 if (!is_readable($configfile)) {
334 throw new coding_exception('Cannot use theme ' . $themename .
335 '. The file ' . $configfile . ' does not exist or is not readable.');
571fa828 336 }
ebebf55c 337 include($configfile);
338
339 $THEME->update_legacy_information();
340
341 return $THEME;
571fa828 342 }
343
34a2777c 344 /**
ebebf55c 345 * Get the renderer for a part of Moodle for this theme.
346 * @param string $module the name of part of moodle. E.g. 'core', 'quiz', 'qtype_multichoice'.
347 * @param moodle_page $page the page we are rendering
348 * @return moodle_renderer_base the requested renderer.
34a2777c 349 */
ebebf55c 350 public function get_renderer($module, $page) {
351 if (is_null($this->rf)) {
352 if (CLI_SCRIPT) {
353 $classname = 'cli_renderer_factory';
354 } else {
355 $classname = $this->rendererfactory;
356 }
357 $this->rf = new $classname($this);
358 }
359
360 return $this->rf->get_renderer($module, $page);
34a2777c 361 }
362
571fa828 363 /**
ebebf55c 364 * Get the renderer for a part of Moodle for this theme.
365 * @return moodle_renderer_base the requested renderer.
571fa828 366 */
ebebf55c 367 protected function get_icon_finder() {
368 if (is_null($this->if)) {
369 $classname = $this->iconfinder;
370 $this->if = new $classname($this);
571fa828 371 }
ebebf55c 372 return $this->if;
571fa828 373 }
374
375 /**
ebebf55c 376 * Return the URL for an icon indentifed as in pre-Moodle 2.0 code.
377 *
378 * Suppose you have old code like $url = "$CFG->pixpath/i/course.gif";
379 * then old_icon_url('i/course'); will return the equivalent URL that is correct now.
380 *
381 * @param $iconname the name of the icon.
382 * @return string the URL for that icon.
571fa828 383 */
ebebf55c 384 public function old_icon_url($iconname) {
4096752d 385 return $this->get_icon_finder()->old_icon_url($iconname);
571fa828 386 }
387
388 /**
ebebf55c 389 * Return the URL for an icon indentifed as in pre-Moodle 2.0 code.
390 *
391 * Suppose you have old code like $url = "$CFG->modpixpath/$mod/icon.gif";
392 * then mod_icon_url('icon', $mod); will return the equivalent URL that is correct now.
393 *
394 * @param $iconname the name of the icon.
395 * @param $module the module the icon belongs to.
396 * @return string the URL for that icon.
571fa828 397 */
ebebf55c 398 public function mod_icon_url($iconname, $module) {
4096752d 399 return $this->get_icon_finder()->mod_icon_url($iconname, $module);
571fa828 400 }
34a2777c 401
ebebf55c 402 /**
403 * Get the list of stylesheet URLs that need to go in the header for this theme.
404 * @return array of URLs.
405 */
406 public function get_stylesheet_urls() {
34a2777c 407 global $CFG;
408
ebebf55c 409 // Put together the parameters
410 $params = '?for=' . $this->name;
34a2777c 411
ebebf55c 412 // Stylesheets, in order (standard, parent, this - some of which may be the same).
413 $stylesheets = array();
414 if ($this->name != 'standard' && $this->standardsheets) {
415 $stylesheets[] = $CFG->httpsthemewww . '/standard/styles.php' . $params;
416 }
417 if (!empty($this->parent)) {
418 $stylesheets[] = $CFG->httpsthemewww . '/' . $this->parent . '/styles.php' . $params;
34a2777c 419 }
420
ebebf55c 421 // Pass on the current language, if it will be needed.
422 if (!empty($this->langsheets)) {
423 $params .= '&lang=' . current_language();
34a2777c 424 }
ebebf55c 425 $stylesheets[] = $CFG->httpsthemewww . '/' . $this->name . '/styles.php' . $params;
34a2777c 426
ebebf55c 427 // Additional styles for right-to-left languages.
428 if (right_to_left()) {
429 $stylesheets[] = $CFG->httpsthemewww . '/standard/rtl.css';
34a2777c 430
ebebf55c 431 if (!empty($this->parent) && file_exists($CFG->themedir . '/' . $this->parent . '/rtl.css')) {
432 $stylesheets[] = $CFG->httpsthemewww . '/' . $this->parent . '/rtl.css';
433 }
34a2777c 434
ebebf55c 435 if (file_exists($this->dir . '/rtl.css')) {
436 $stylesheets[] = $CFG->httpsthemewww . '/' . $this->name . '/rtl.css';
437 }
34a2777c 438 }
17a6649b 439
ebebf55c 440 return $stylesheets;
441 }
17a6649b 442
ebebf55c 443 /**
444 * This methon looks a the settings that have been loaded, to see whether
445 * any legacy things are being used, and outputs warning and tries to update
446 * things to use equivalent newer settings.
447 */
448 protected function update_legacy_information() {
449 global $CFG;
450 if (!empty($this->customcorners)) {
451 // $THEME->customcorners is deprecated but we provide support for it via the
452 // custom_corners_renderer_factory class in lib/deprecatedlib.php
453 debugging('$THEME->customcorners is deprecated. Please use the new $THEME->rendererfactory ' .
454 'to control HTML generation. Please use $this->rendererfactory = \'custom_corners_renderer_factory\'; ' .
455 'in your config.php file instead.', DEBUG_DEVELOPER);
456 $this->rendererfactory = 'custom_corners_renderer_factory';
34a2777c 457 }
458
ebebf55c 459 if (!empty($this->cssconstants)) {
460 debugging('$THEME->cssconstants is deprecated. Please use ' .
461 '$THEME->customcssoutputfunction = \'output_css_replacing_constants\'; ' .
462 'in your config.php file instead.', DEBUG_DEVELOPER);
463 $this->customcssoutputfunction = 'output_css_replacing_constants';
34a2777c 464 }
ebebf55c 465
466 if (!empty($this->CSSEdit)) {
467 debugging('$THEME->CSSEdit is deprecated. Please use ' .
468 '$THEME->customcssoutputfunction = \'output_css_for_css_edit\'; ' .
469 'in your config.php file instead.', DEBUG_DEVELOPER);
470 $this->customcssoutputfunction = 'output_css_for_css_edit';
34a2777c 471 }
472
ae96b517 473 if (!empty($CFG->smartpix)) {
ebebf55c 474 $this->iconfinder = 'smartpix_icon_finder';
475 } else if ($this->custompix) {
476 $this->iconfinder = 'theme_icon_finder';
477 }
34a2777c 478 }
479
ebebf55c 480 /**
481 * Set the variable $CFG->pixpath and $CFG->modpixpath to be the right
d436d197 482 * ones for this theme. These should no longer be used, but legacy code
483 * might still rely on them.
ebebf55c 484 */
d436d197 485 public function setup_legacy_pix_paths() {
ebebf55c 486 global $CFG;
487 if (!empty($CFG->smartpix)) {
488 if ($CFG->slasharguments) {
489 // Use this method if possible for better caching
490 $extra = '';
491 } else {
492 $extra = '?file=';
493 }
494 $CFG->pixpath = $CFG->httpswwwroot . '/pix/smartpix.php' . $extra . '/' . $this->name;
495 $CFG->modpixpath = $CFG->httpswwwroot . '/pix/smartpix.php' . $extra . '/' . $this->name . '/mod';
34a2777c 496
ebebf55c 497 } else if (empty($THEME->custompix)) {
498 $CFG->pixpath = $CFG->httpswwwroot . '/pix';
499 $CFG->modpixpath = $CFG->httpswwwroot . '/mod';
500
501 } else {
502 $CFG->pixpath = $CFG->httpsthemewww . '/' . $this->name . '/pix';
503 $CFG->modpixpath = $CFG->httpsthemewww . '/' . $this->name . '/pix/mod';
34a2777c 504 }
34a2777c 505 }
ebebf55c 506}
34a2777c 507
ebebf55c 508
509/**
510 * This icon finder implements the old scheme that was used when themes that had
511 * $THEME->custompix = false.
512 *
513 * @copyright 2009 Tim Hunt
514 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
515 * @since Moodle 2.0
516 */
517class pix_icon_finder implements icon_finder {
518 /**
519 * Constructor
520 * @param theme_config $theme the theme we are finding icons for (which is irrelevant).
521 */
522 public function __construct($theme) {
34a2777c 523 }
524
ebebf55c 525 /* Implement interface method. */
526 public function old_icon_url($iconname) {
527 global $CFG;
3aaa27f4 528 if (file_exists($CFG->dirroot . '/pix/' . $iconname . '.png')) {
529 return $CFG->httpswwwroot . '/pix/' . $iconname . '.png';
530 } else {
531 return $CFG->httpswwwroot . '/pix/' . $iconname . '.gif';
532 }
34a2777c 533 }
534
ebebf55c 535 /* Implement interface method. */
536 public function mod_icon_url($iconname, $module) {
537 global $CFG;
3aaa27f4 538 if (file_exists($CFG->dirroot . '/mod/' . $module . '/' . $iconname . '.png')) {
539 return $CFG->httpswwwroot . '/mod/' . $module . '/' . $iconname . '.png';
540 } else {
541 return $CFG->httpswwwroot . '/mod/' . $module . '/' . $iconname . '.gif';
542 }
ebebf55c 543 }
544}
34a2777c 545
34a2777c 546
ebebf55c 547/**
548 * This icon finder implements the old scheme that was used for themes that had
549 * $THEME->custompix = true.
550 *
551 * @copyright 2009 Tim Hunt
552 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
553 * @since Moodle 2.0
554 */
555class theme_icon_finder implements icon_finder {
556 protected $themename;
557 /**
558 * Constructor
559 * @param theme_config $theme the theme we are finding icons for.
560 */
561 public function __construct($theme) {
562 $this->themename = $theme->name;
563 }
34a2777c 564
ebebf55c 565 /* Implement interface method. */
566 public function old_icon_url($iconname) {
567 global $CFG;
3aaa27f4 568 if (file_exists($CFG->themedir . '/' . $this->themename . '/pix/' . $iconname . '.png')) {
569 return $CFG->httpsthemewww . '/' . $this->themename . '/pix/' . $iconname . '.png';
570 } else {
571 return $CFG->httpsthemewww . '/' . $this->themename . '/pix/' . $iconname . '.gif';
572 }
ebebf55c 573 }
34a2777c 574
ebebf55c 575 /* Implement interface method. */
576 public function mod_icon_url($iconname, $module) {
577 global $CFG;
3aaa27f4 578 if (file_exists($CFG->themedir . '/' . $this->themename . '/pix/mod/' . $module . '/' . $iconname . '.png')) {
579 return $CFG->httpsthemewww . '/' . $this->themename . '/pix/mod/' . $module . '/' . $iconname . '.png';
580 } else {
581 return $CFG->httpsthemewww . '/' . $this->themename . '/pix/mod/' . $module . '/' . $iconname . '.gif';
582 }
34a2777c 583 }
ebebf55c 584}
585
586
587/**
588 * This icon finder implements the algorithm in pix/smartpix.php.
589 *
590 * @copyright 2009 Tim Hunt
591 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
592 * @since Moodle 2.0
593 */
594class smartpix_icon_finder extends pix_icon_finder {
595 protected $places = array();
34a2777c 596
afa2dcad 597 /**
ebebf55c 598 * Constructor
599 * @param theme_config $theme the theme we are finding icons for.
afa2dcad 600 */
ebebf55c 601 public function __construct($theme) {
e8775320 602 global $CFG;
ebebf55c 603 $this->places[$CFG->themedir . '/' . $theme->name . '/pix/'] =
604 $CFG->httpsthemewww . '/' . $theme->name . '/pix/';
605 if (!empty($theme->parent)) {
606 $this->places[$CFG->themedir . '/' . $theme->parent . '/pix/'] =
607 $CFG->httpsthemewww . '/' . $theme->parent . '/pix/';
608 }
609 }
e8775320 610
ebebf55c 611 /* Implement interface method. */
612 public function old_icon_url($iconname) {
613 foreach ($this->places as $dirroot => $urlroot) {
3aaa27f4 614 if (file_exists($dirroot . $iconname . '.png')) {
615 return $dirroot . $iconname . '.png';
616 } else if (file_exists($dirroot . $iconname . '.gif')) {
617 return $dirroot . $iconname . '.gif';
e8775320 618 }
619 }
ebebf55c 620 return parent::old_icon_url($iconname);
621 }
e8775320 622
ebebf55c 623 /* Implement interface method. */
624 public function mod_icon_url($iconname, $module) {
625 foreach ($this->places as $dirroot => $urlroot) {
3aaa27f4 626 if (file_exists($dirroot . 'mod/' . $iconname . '.png')) {
627 return $dirroot . 'mod/' . $iconname . '.png';
628 } else if (file_exists($dirroot . 'mod/' . $iconname . '.gif')) {
629 return $dirroot . 'mod/' . $iconname . '.gif';
ebebf55c 630 }
e8775320 631 }
3aaa27f4 632 return parent::old_icon_url($iconname, $module);
e8775320 633 }
ebebf55c 634}
e8775320 635
34a2777c 636
ebebf55c 637/**
638 * This is a base class to help you implement the renderer_factory interface.
639 *
640 * It keeps a cache of renderers that have been constructed, so you only need
641 * to construct each one once in you subclass.
642 *
643 * It also has a method to get the name of, and include the renderer.php with
644 * the definition of, the standard renderer class for a given module.
645 *
646 * @copyright 2009 Tim Hunt
647 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
648 * @since Moodle 2.0
649 */
650abstract class renderer_factory_base implements renderer_factory {
651 /** @var theme_config the theme we belong to. */
652 protected $theme;
34a2777c 653
ebebf55c 654 /**
655 * Constructor.
656 * @param theme_config $theme the theme we belong to.
657 */
658 public function __construct($theme) {
659 $this->theme = $theme;
660 }
661 /**
662 * For a given module name, return the name of the standard renderer class
663 * that defines the renderer interface for that module.
664 *
665 * Also, if it exists, include the renderer.php file for that module, so
666 * the class definition of the default renderer has been loaded.
667 *
668 * @param string $component name such as 'core', 'mod_forum' or 'qtype_multichoice'.
669 * @return string the name of the standard renderer class for that module.
670 */
671 protected function standard_renderer_class_for_module($component) {
672 if ($component != 'core') {
673 $pluginrenderer = get_component_directory($component) . '/renderer.php';
674 if (file_exists($pluginrenderer)) {
675 include_once($pluginrenderer);
676 }
34a2777c 677 }
ebebf55c 678 $class = 'moodle_' . $component . '_renderer';
679 if (!class_exists($class)) {
680 throw new coding_exception('Request for an unknown renderer class ' . $class);
34a2777c 681 }
ebebf55c 682 return $class;
34a2777c 683 }
ebebf55c 684}
34a2777c 685
34a2777c 686
ebebf55c 687/**
688 * This is the default renderer factory for Moodle. It simply returns an instance
689 * of the appropriate standard renderer class.
690 *
691 * @copyright 2009 Tim Hunt
692 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
693 * @since Moodle 2.0
694 */
695class standard_renderer_factory extends renderer_factory_base {
696 /* Implement the subclass method. */
697 public function get_renderer($module, $page) {
698 if ($module == 'core') {
699 return new moodle_core_renderer($page);
700 } else {
701 $class = $this->standard_renderer_class_for_module($module);
702 return new $class($page, $this->get_renderer('core', $page));
34a2777c 703 }
ebebf55c 704 }
705}
34a2777c 706
34a2777c 707
ebebf55c 708/**
709 * This is a slight variation on the standard_renderer_factory used by CLI scripts.
710 *
711 * @copyright 2009 Tim Hunt
712 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
713 * @since Moodle 2.0
714 */
715class cli_renderer_factory extends standard_renderer_factory {
716 /* Implement the subclass method. */
717 public function get_renderer($module, $page) {
718 if ($module == 'core') {
719 return new cli_core_renderer($page);
720 } else {
721 parent::get_renderer($module, $page);
722 }
34a2777c 723 }
ebebf55c 724}
34a2777c 725
34a2777c 726
ebebf55c 727/**
728 * This is renderer factory allows themes to override the standard renderers using
729 * php code.
730 *
731 * It will load any code from theme/mytheme/renderers.php and
732 * theme/parenttheme/renderers.php, if then exist. Then whenever you ask for
733 * a renderer for 'component', it will create a mytheme_component_renderer or a
734 * parenttheme_component_renderer, instead of a moodle_component_renderer,
735 * if either of those classes exist.
736 *
737 * This generates the slightly different HTML that the custom_corners theme expects.
738 *
739 * @copyright 2009 Tim Hunt
740 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
741 * @since Moodle 2.0
742 */
743class theme_overridden_renderer_factory extends standard_renderer_factory {
744 protected $prefixes = array();
745
746 /**
747 * Constructor.
748 * @param object $theme the theme we are rendering for.
749 * @param moodle_page $page the page we are doing output for.
750 */
751 public function __construct($theme) {
752 global $CFG;
753 parent::__construct($theme);
754
755 // Initialise $this->prefixes.
756 $renderersfile = $theme->dir . '/renderers.php';
757 if (is_readable($renderersfile)) {
758 include_once($renderersfile);
759 $this->prefixes[] = $theme->name . '_';
760 }
761 if (!empty($theme->parent)) {
762 $renderersfile = $CFG->themedir .'/'. $theme->parent . '/renderers.php';
763 if (is_readable($renderersfile)) {
764 include_once($renderersfile);
765 $this->prefixes[] = $theme->parent . '_';
766 }
767 }
34a2777c 768 }
769
ebebf55c 770 /* Implement the subclass method. */
771 public function get_renderer($module, $page) {
772 foreach ($this->prefixes as $prefix) {
773 $classname = $prefix . $module . '_renderer';
774 if (class_exists($classname)) {
775 if ($module == 'core') {
776 return new $classname($page);
777 } else {
778 return new $classname($page, $this->get_renderer('core', $page));
779 }
780 }
781 }
782 return parent::get_renderer($module, $page);
783 }
784}
34a2777c 785
34a2777c 786
ebebf55c 787/**
788 * This is renderer factory that allows you to create templated themes.
789 *
790 * This should be considered an experimental proof of concept. In particular,
791 * the performance is probably not very good. Do not try to use in on a busy site
792 * without doing careful load testing first!
793 *
794 * This renderer factory returns instances of {@link template_renderer} class
795 * which which implement the corresponding renderer interface in terms of
796 * templates. To use this your theme must have a templates folder inside it.
797 * Then suppose the method moodle_core_renderer::greeting($name = 'world');
798 * exists. Then, a call to $OUTPUT->greeting() will cause the template
799 * /theme/yourtheme/templates/core/greeting.php to be rendered, with the variable
800 * $name available. The greeting.php template might contain
801 *
802 * <pre>
803 * <h1>Hello <?php echo $name ?>!</h1>
804 * </pre>
805 *
806 * @copyright 2009 Tim Hunt
807 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
808 * @since Moodle 2.0
809 */
810class template_renderer_factory extends renderer_factory_base {
811 /**
812 * An array of paths of where to search for templates. Normally this theme,
813 * the parent theme then the standardtemplate theme. (If some of these do
814 * not exist, or are the same as each other, then the list will be shorter.
815 */
816 protected $searchpaths = array();
34a2777c 817
ebebf55c 818 /**
819 * Constructor.
820 * @param object $theme the theme we are rendering for.
821 * @param moodle_page $page the page we are doing output for.
822 */
823 public function __construct($theme) {
824 global $CFG;
825 parent::__construct($theme);
34a2777c 826
ebebf55c 827 // Initialise $this->searchpaths.
828 if ($theme->name != 'standardtemplate') {
829 $templatesdir = $theme->dir . '/templates';
830 if (is_dir($templatesdir)) {
831 $this->searchpaths[] = $templatesdir;
832 }
34a2777c 833 }
ebebf55c 834 if (!empty($theme->parent)) {
835 $templatesdir = $CFG->themedir .'/'. $theme->parent . '/templates';
836 if (is_dir($templatesdir)) {
837 $this->searchpaths[] = $templatesdir;
838 }
839 }
840 $this->searchpaths[] = $CFG->themedir .'/standardtemplate/templates';
841 }
34a2777c 842
ebebf55c 843 /* Implement the subclass method. */
844 public function get_renderer($module, $page) {
845 // Refine the list of search paths for this module.
846 $searchpaths = array();
847 foreach ($this->searchpaths as $rootpath) {
848 $path = $rootpath . '/' . $module;
849 if (is_dir($path)) {
850 $searchpaths[] = $path;
851 }
852 }
34a2777c 853
ebebf55c 854 // Create a template_renderer that copies the API of the standard renderer.
855 $copiedclass = $this->standard_renderer_class_for_module($module);
856 return new template_renderer($copiedclass, $searchpaths, $page);
857 }
858}
34a2777c 859
34a2777c 860
ebebf55c 861/**
862 * Simple base class for Moodle renderers.
863 *
864 * Tracks the xhtml_container_stack to use, which is passed in in the constructor.
865 *
866 * Also has methods to facilitate generating HTML output.
867 *
868 * @copyright 2009 Tim Hunt
869 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
870 * @since Moodle 2.0
871 */
872class moodle_renderer_base {
873 /** @var xhtml_container_stack the xhtml_container_stack to use. */
874 protected $opencontainers;
875 /** @var moodle_page the page we are rendering for. */
876 protected $page;
34a2777c 877
ebebf55c 878 /**
879 * Constructor
880 * @param $opencontainers the xhtml_container_stack to use.
881 * @param moodle_page $page the page we are doing output for.
882 */
883 public function __construct($page) {
884 $this->opencontainers = $page->opencontainers;
885 $this->page = $page;
34a2777c 886 }
887
ebebf55c 888 /**
889 * Have we started output yet?
890 * @return boolean true if the header has been printed.
891 */
892 public function has_started() {
893 return $this->page->state >= moodle_page::STATE_IN_BODY;
894 }
895
896 protected function output_tag($tagname, $attributes, $contents) {
897 return $this->output_start_tag($tagname, $attributes) . $contents .
898 $this->output_end_tag($tagname);
899 }
900 protected function output_start_tag($tagname, $attributes) {
901 return '<' . $tagname . $this->output_attributes($attributes) . '>';
902 }
903 protected function output_end_tag($tagname) {
904 return '</' . $tagname . '>';
905 }
906 protected function output_empty_tag($tagname, $attributes) {
907 return '<' . $tagname . $this->output_attributes($attributes) . ' />';
908 }
909
910 protected function output_attribute($name, $value) {
911 $value = trim($value);
912 if ($value || is_numeric($value)) { // We want 0 to be output.
913 return ' ' . $name . '="' . $value . '"';
914 }
915 }
916 protected function output_attributes($attributes) {
917 if (empty($attributes)) {
918 $attributes = array();
919 }
34a2777c 920 $output = '';
ebebf55c 921 foreach ($attributes as $name => $value) {
922 $output .= $this->output_attribute($name, $value);
34a2777c 923 }
ebebf55c 924 return $output;
925 }
926 public static function prepare_classes($classes) {
927 if (is_array($classes)) {
928 return implode(' ', array_unique($classes));
929 }
930 return $classes;
931 }
34a2777c 932
ebebf55c 933 /**
934 * Return the URL for an icon indentifed as in pre-Moodle 2.0 code.
935 *
936 * Suppose you have old code like $url = "$CFG->pixpath/i/course.gif";
937 * then old_icon_url('i/course'); will return the equivalent URL that is correct now.
938 *
939 * @param $iconname the name of the icon.
940 * @return string the URL for that icon.
941 */
942 public function old_icon_url($iconname) {
943 return $this->page->theme->old_icon_url($iconname);
944 }
34a2777c 945
ebebf55c 946 /**
947 * Return the URL for an icon indentifed as in pre-Moodle 2.0 code.
948 *
949 * Suppose you have old code like $url = "$CFG->modpixpath/$mod/icon.gif";
950 * then mod_icon_url('icon', $mod); will return the equivalent URL that is correct now.
951 *
952 * @param $iconname the name of the icon.
953 * @param $module the module the icon belongs to.
954 * @return string the URL for that icon.
955 */
956 public function mod_icon_url($iconname, $module) {
957 return $this->page->theme->mod_icon_url($iconname, $module);
958 }
959}
34a2777c 960
34a2777c 961
ebebf55c 962/**
963 * This is the templated renderer which copies the API of another class, replacing
964 * all methods calls with instantiation of a template.
965 *
966 * When the method method_name is called, this class will search for a template
967 * called method_name.php in the folders in $searchpaths, taking the first one
968 * that it finds. Then it will set up variables for each of the arguments of that
969 * method, and render the template. This is implemented in the {@link __call()}
970 * PHP magic method.
971 *
972 * Methods like print_box_start and print_box_end are handles specially, and
973 * implemented in terms of the print_box.php method.
974 *
975 * @copyright 2009 Tim Hunt
976 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
977 * @since Moodle 2.0
978 */
979class template_renderer extends moodle_renderer_base {
980 /** @var ReflectionClass information about the class whose API we are copying. */
981 protected $copiedclass;
982 /** @var array of places to search for templates. */
983 protected $searchpaths;
984 protected $rendererfactory;
34a2777c 985
ebebf55c 986 /**
987 * Magic word used when breaking apart container templates to implement
988 * _start and _end methods.
989 */
990 const contentstoken = '-@#-Contents-go-here-#@-';
991
992 /**
993 * Constructor
994 * @param string $copiedclass the name of a class whose API we should be copying.
995 * @param $searchpaths a list of folders to search for templates in.
996 * @param $opencontainers the xhtml_container_stack to use.
997 * @param moodle_page $page the page we are doing output for.
998 */
999 public function __construct($copiedclass, $searchpaths, $page) {
1000 parent::__construct($page);
1001 $this->copiedclass = new ReflectionClass($copiedclass);
1002 $this->searchpaths = $searchpaths;
1003 }
1004
1005 /* PHP magic method implementation. */
1006 public function __call($method, $arguments) {
1007 if (substr($method, -6) == '_start') {
1008 return $this->process_start(substr($method, 0, -6), $arguments);
1009 } else if (substr($method, -4) == '_end') {
1010 return $this->process_end(substr($method, 0, -4), $arguments);
1011 } else {
1012 return $this->process_template($method, $arguments);
1013 }
34a2777c 1014 }
1015
a5cb8d69 1016 /**
ebebf55c 1017 * Render the template for a given method of the renderer class we are copying,
1018 * using the arguments passed.
1019 * @param string $method the method that was called.
1020 * @param array $arguments the arguments that were passed to it.
a5cb8d69 1021 * @return string the HTML to be output.
1022 */
ebebf55c 1023 protected function process_template($method, $arguments) {
1024 if (!$this->copiedclass->hasMethod($method) ||
1025 !$this->copiedclass->getMethod($method)->isPublic()) {
1026 throw new coding_exception('Unknown method ' . $method);
a5cb8d69 1027 }
1028
ebebf55c 1029 // Find the template file for this method.
1030 $template = $this->find_template($method);
a5cb8d69 1031
ebebf55c 1032 // Use the reflection API to find out what variable names the arguments
1033 // should be stored in, and fill in any missing ones with the defaults.
1034 $namedarguments = array();
1035 $expectedparams = $this->copiedclass->getMethod($method)->getParameters();
1036 foreach ($expectedparams as $param) {
1037 $paramname = $param->getName();
1038 if (!empty($arguments)) {
1039 $namedarguments[$paramname] = array_shift($arguments);
1040 } else if ($param->isDefaultValueAvailable()) {
1041 $namedarguments[$paramname] = $param->getDefaultValue();
1042 } else {
1043 throw new coding_exception('Missing required argument ' . $paramname);
a5cb8d69 1044 }
a5cb8d69 1045 }
1046
ebebf55c 1047 // Actually render the template.
1048 return $this->render_template($template, $namedarguments);
1049 }
a5cb8d69 1050
ebebf55c 1051 /**
1052 * Actually do the work of rendering the template.
1053 * @param $_template the full path to the template file.
1054 * @param $_namedarguments an array variable name => value, the variables
1055 * that should be available to the template.
1056 * @return string the HTML to be output.
1057 */
1058 protected function render_template($_template, $_namedarguments) {
1059 // Note, we intentionally break the coding guidelines with regards to
1060 // local variable names used in this function, so that they do not clash
1061 // with the names of any variables being passed to the template.
a5cb8d69 1062
ebebf55c 1063 global $CFG, $SITE, $THEME, $USER;
1064 // The next lines are a bit tricky. The point is, here we are in a method
1065 // of a renderer class, and this object may, or may not, be the the same as
1066 // the global $OUTPUT object. When rendering the template, we want to use
1067 // this object. However, people writing Moodle code expect the current
1068 // rederer to be called $OUTPUT, not $this, so define a variable called
1069 // $OUTPUT pointing at $this. The same comment applies to $PAGE and $COURSE.
1070 $OUTPUT = $this;
1071 $PAGE = $this->page;
1072 $COURSE = $this->page->course;
a5cb8d69 1073
ebebf55c 1074 // And the parameters from the function call.
1075 extract($_namedarguments);
a5cb8d69 1076
ebebf55c 1077 // Include the template, capturing the output.
1078 ob_start();
1079 include($_template);
1080 $_result = ob_get_contents();
1081 ob_end_clean();
a5cb8d69 1082
ebebf55c 1083 return $_result;
a5cb8d69 1084 }
1085
ebebf55c 1086 /**
1087 * Searches the folders in {@link $searchpaths} to try to find a template for
1088 * this method name. Throws an exception if one cannot be found.
1089 * @param string $method the method name.
1090 * @return string the full path of the template to use.
1091 */
1092 protected function find_template($method) {
1093 foreach ($this->searchpaths as $path) {
1094 $filename = $path . '/' . $method . '.php';
1095 if (file_exists($filename)) {
1096 return $filename;
1097 }
1098 }
1099 throw new coding_exception('Cannot find template for ' . $this->copiedclass->getName() . '::' . $method);
8954245a 1100 }
571fa828 1101
ebebf55c 1102 /**
1103 * Handle methods like print_box_start by using the print_box template,
1104 * splitting the result, pusing the end onto the stack, then returning the start.
1105 * @param string $method the method that was called, with _start stripped off.
1106 * @param array $arguments the arguments that were passed to it.
1107 * @return string the HTML to be output.
1108 */
1109 protected function process_start($template, $arguments) {
1110 array_unshift($arguments, self::contentstoken);
1111 $html = $this->process_template($template, $arguments);
1112 list($start, $end) = explode(self::contentstoken, $html, 2);
1113 $this->opencontainers->push($template, $end);
1114 return $start;
8954245a 1115 }
1116
ebebf55c 1117 /**
1118 * Handle methods like print_box_end, we just need to pop the end HTML from
1119 * the stack.
1120 * @param string $method the method that was called, with _end stripped off.
1121 * @param array $arguments not used. Assumed to be irrelevant.
1122 * @return string the HTML to be output.
1123 */
1124 protected function process_end($template, $arguments) {
1125 return $this->opencontainers->pop($template);
8954245a 1126 }
1127
ebebf55c 1128 /**
1129 * @return array the list of paths where this class searches for templates.
1130 */
1131 public function get_search_paths() {
1132 return $this->searchpaths;
8954245a 1133 }
1134
1135 /**
ebebf55c 1136 * @return string the name of the class whose API we are copying.
8954245a 1137 */
ebebf55c 1138 public function get_copied_class() {
1139 return $this->copiedclass->getName();
1140 }
1141}
8954245a 1142
8954245a 1143
ebebf55c 1144/**
1145 * This class keeps track of which HTML tags are currently open.
1146 *
1147 * This makes it much easier to always generate well formed XHTML output, even
1148 * if execution terminates abruptly. Any time you output some opening HTML
1149 * without the matching closing HTML, you should push the neccessary close tags
1150 * onto the stack.
1151 *
1152 * @copyright 2009 Tim Hunt
1153 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1154 * @since Moodle 2.0
1155 */
1156class xhtml_container_stack {
1157 /** @var array stores the list of open containers. */
1158 protected $opencontainers = array();
4af1e3b0 1159 /**
1160 * @var array in developer debug mode, stores a stack trace of all opens and
1161 * closes, so we can output helpful error messages when there is a mismatch.
1162 */
1163 protected $log = array();
8954245a 1164
ebebf55c 1165 /**
1166 * Push the close HTML for a recently opened container onto the stack.
1167 * @param string $type The type of container. This is checked when {@link pop()}
1168 * is called and must match, otherwise a developer debug warning is output.
1169 * @param string $closehtml The HTML required to close the container.
1170 */
1171 public function push($type, $closehtml) {
1172 $container = new stdClass;
1173 $container->type = $type;
1174 $container->closehtml = $closehtml;
4af1e3b0 1175 if (debugging('', DEBUG_DEVELOPER)) {
1176 $this->log('Open', $type);
1177 }
ebebf55c 1178 array_push($this->opencontainers, $container);
1179 }
8954245a 1180
ebebf55c 1181 /**
1182 * Pop the HTML for the next closing container from the stack. The $type
1183 * must match the type passed when the container was opened, otherwise a
1184 * warning will be output.
1185 * @param string $type The type of container.
1186 * @return string the HTML requried to close the container.
1187 */
1188 public function pop($type) {
1189 if (empty($this->opencontainers)) {
4af1e3b0 1190 debugging('<p>There are no more open containers. This suggests there is a nesting problem.</p>' .
1191 $this->output_log(), DEBUG_DEVELOPER);
ebebf55c 1192 return;
8954245a 1193 }
1194
ebebf55c 1195 $container = array_pop($this->opencontainers);
1196 if ($container->type != $type) {
4af1e3b0 1197 debugging('<p>The type of container to be closed (' . $container->type .
ebebf55c 1198 ') does not match the type of the next open container (' . $type .
4af1e3b0 1199 '). This suggests there is a nesting problem.</p>' .
1200 $this->output_log(), DEBUG_DEVELOPER);
1201 }
1202 if (debugging('', DEBUG_DEVELOPER)) {
1203 $this->log('Close', $type);
8954245a 1204 }
ebebf55c 1205 return $container->closehtml;
8954245a 1206 }
1207
8954245a 1208 /**
ebebf55c 1209 * Close all but the last open container. This is useful in places like error
1210 * handling, where you want to close all the open containers (apart from <body>)
1211 * before outputting the error message.
4af1e3b0 1212 * @param $shouldbenone assert that the stack shold be empty now - causes a
1213 * developer debug warning if it isn't.
ebebf55c 1214 * @return string the HTML requried to close any open containers inside <body>.
8954245a 1215 */
4af1e3b0 1216 public function pop_all_but_last($shouldbenone = false) {
1217 if ($shouldbenone && count($this->opencontainers) != 1) {
1218 debugging('<p>Some HTML tags were opened in the body of the page but not closed.</p>' .
1219 $this->output_log(), DEBUG_DEVELOPER);
1220 }
ebebf55c 1221 $output = '';
1222 while (count($this->opencontainers) > 1) {
1223 $container = array_pop($this->opencontainers);
1224 $output .= $container->closehtml;
8954245a 1225 }
ebebf55c 1226 return $output;
8954245a 1227 }
34a2777c 1228
1229 /**
ebebf55c 1230 * You can call this function if you want to throw away an instance of this
1231 * class without properly emptying the stack (for example, in a unit test).
1232 * Calling this method stops the destruct method from outputting a developer
1233 * debug warning. After calling this method, the instance can no longer be used.
34a2777c 1234 */
ebebf55c 1235 public function discard() {
1236 $this->opencontainers = null;
1237 }
34a2777c 1238
ebebf55c 1239 /**
1240 * Emergency fallback. If we get to the end of processing and not all
1241 * containers have been closed, output the rest with a developer debug warning.
1242 */
1243 public function __destruct() {
1244 if (empty($this->opencontainers)) {
1245 return;
34a2777c 1246 }
1247
4af1e3b0 1248 debugging('<p>Some containers were left open. This suggests there is a nesting problem.</p>' .
1249 $this->output_log(), DEBUG_DEVELOPER);
ebebf55c 1250 echo $this->pop_all_but_last();
1251 $container = array_pop($this->opencontainers);
1252 echo $container->closehtml;
1253 }
4af1e3b0 1254
1255 protected function log($action, $type) {
1256 $this->log[] = '<li>' . $action . ' ' . $type . ' at:' .
1257 format_backtrace(debug_backtrace()) . '</li>';
1258 }
1259
1260 protected function output_log() {
1261 return '<ul>' . implode("\n", $this->log) . '</ul>';
1262 }
ebebf55c 1263}
34a2777c 1264
34a2777c 1265
ebebf55c 1266/**
1267 * The standard implementation of the moodle_core_renderer interface.
1268 *
1269 * @copyright 2009 Tim Hunt
1270 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1271 * @since Moodle 2.0
1272 */
1273class moodle_core_renderer extends moodle_renderer_base {
1274 const PERFORMANCE_INFO_TOKEN = '%%PERFORMANCEINFO%%';
1275 const END_HTML_TOKEN = '%%ENDHTML%%';
1276 const MAIN_CONTENT_TOKEN = '[MAIN CONTENT GOES HERE]';
1277 protected $contenttype;
1278 protected $metarefreshtag = '';
1279
1280 public function doctype() {
1281 global $CFG;
1282
1283 $doctype = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">' . "\n";
1284 $this->contenttype = 'text/html; charset=utf-8';
1285
1286 if (empty($CFG->xmlstrictheaders)) {
1287 return $doctype;
34a2777c 1288 }
1289
ebebf55c 1290 // We want to serve the page with an XML content type, to force well-formedness errors to be reported.
1291 $prolog = '<?xml version="1.0" encoding="utf-8"?>' . "\n";
1292 if (isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'application/xhtml+xml') !== false) {
1293 // Firefox and other browsers that can cope natively with XHTML.
1294 $this->contenttype = 'application/xhtml+xml; charset=utf-8';
34a2777c 1295
ebebf55c 1296 } else if (preg_match('/MSIE.*Windows NT/', $_SERVER['HTTP_USER_AGENT'])) {
1297 // IE can't cope with application/xhtml+xml, but it will cope if we send application/xml with an XSL stylesheet.
1298 $this->contenttype = 'application/xml; charset=utf-8';
1299 $prolog .= '<?xml-stylesheet type="text/xsl" href="' . $CFG->httpswwwroot . '/lib/xhtml.xsl"?>' . "\n";
34a2777c 1300
ebebf55c 1301 } else {
1302 $prolog = '';
1303 }
1304
1305 return $prolog . $doctype;
34a2777c 1306 }
1307
ebebf55c 1308 public function htmlattributes() {
1309 return get_html_lang(true) . ' xmlns="http://www.w3.org/1999/xhtml"';
34a2777c 1310 }
1311
ebebf55c 1312 public function standard_head_html() {
1313 global $CFG, $THEME;
1314 $output = '';
1315 $output .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />' . "\n";
1316 $output .= '<meta name="keywords" content="moodle, ' . $this->page->title . '" />' . "\n";
1317 if (!$this->page->cacheable) {
1318 $output .= '<meta http-equiv="pragma" content="no-cache" />' . "\n";
1319 $output .= '<meta http-equiv="expires" content="0" />' . "\n";
34a2777c 1320 }
ebebf55c 1321 // This is only set by the {@link redirect()} method
1322 $output .= $this->metarefreshtag;
1323
1324 // Check if a periodic refresh delay has been set and make sure we arn't
1325 // already meta refreshing
1326 if ($this->metarefreshtag=='' && $this->page->periodicrefreshdelay!==null) {
1327 $metarefesh = '<meta http-equiv="refresh" content="%d;url=%s" />';
1328 $output .= sprintf($metarefesh, $this->page->periodicrefreshdelay, $this->page->url->out());
1329 }
1330
1331 // TODO get rid of $CFG->javascript. We should be able to do everything
1332 // with $PAGE->requires.
1333 ob_start();
1334 include($CFG->javascript);
1335 $output .= ob_get_contents();
1336 ob_end_clean();
1337 $output .= $this->page->requires->get_head_code();
1338
1339 // List alternate versions.
1340 foreach ($this->page->alternateversions as $type => $alt) {
1341 $output .= $this->output_empty_tag('link', array('rel' => 'alternate',
1342 'type' => $type, 'title' => $alt->title, 'href' => $alt->url));
1343 }
1344
1345 // Add the meta page from the themes if any were requested
1346 // TODO See if we can get rid of this.
1347 $PAGE = $this->page;
1348 $metapage = '';
1349 if (!isset($THEME->standardmetainclude) || $THEME->standardmetainclude) {
1350 ob_start();
1351 include_once($CFG->dirroot.'/theme/standard/meta.php');
1352 $output .= ob_get_contents();
1353 ob_end_clean();
1354 }
1355 if ($THEME->parent && (!isset($THEME->parentmetainclude) || $THEME->parentmetainclude)) {
1356 if (file_exists($CFG->dirroot.'/theme/'.$THEME->parent.'/meta.php')) {
1357 ob_start();
1358 include_once($CFG->dirroot.'/theme/'.$THEME->parent.'/meta.php');
1359 $output .= ob_get_contents();
1360 ob_end_clean();
1361 }
1362 }
1363 if (!isset($THEME->metainclude) || $THEME->metainclude) {
1364 if (file_exists($CFG->dirroot.'/theme/'.current_theme().'/meta.php')) {
1365 ob_start();
1366 include_once($CFG->dirroot.'/theme/'.current_theme().'/meta.php');
1367 $output .= ob_get_contents();
1368 ob_end_clean();
1369 }
1370 }
1371
1372 return $output;
34a2777c 1373 }
1374
ebebf55c 1375 public function standard_top_of_body_html() {
1376 return $this->page->requires->get_top_of_body_code();
34a2777c 1377 }
1378
ebebf55c 1379 public function standard_footer_html() {
1380 $output = self::PERFORMANCE_INFO_TOKEN;
1381 if (debugging()) {
1382 $output .= '<div class="validators"><ul>
1383 <li><a href="http://validator.w3.org/check?verbose=1&amp;ss=1&amp;uri=' . urlencode(qualified_me()) . '">Validate HTML</a></li>
1384 <li><a href="http://www.contentquality.com/mynewtester/cynthia.exe?rptmode=-1&amp;url1=' . urlencode(qualified_me()) . '">Section 508 Check</a></li>
1385 <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>
1386 </ul></div>';
34a2777c 1387 }
ebebf55c 1388 return $output;
34a2777c 1389 }
1390
ebebf55c 1391 public function standard_end_of_body_html() {
1392 echo self::END_HTML_TOKEN;
34a2777c 1393 }
1394
ebebf55c 1395 public function login_info() {
1396 global $USER;
1397 return user_login_string($this->page->course, $USER);
34a2777c 1398 }
1399
ebebf55c 1400 public function home_link() {
1401 global $CFG, $SITE;
34a2777c 1402
ebebf55c 1403 if ($this->page->pagetype == 'site-index') {
1404 // Special case for site home page - please do not remove
1405 return '<div class="sitelink">' .
1406 '<a title="Moodle ' . $CFG->release . '" href="http://moodle.org/">' .
1407 '<img style="width:100px;height:30px" src="' . $CFG->httpswwwroot . '/pix/moodlelogo.gif" alt="moodlelogo" /></a></div>';
34a2777c 1408
ebebf55c 1409 } else if (!empty($CFG->target_release) && $CFG->target_release != $CFG->release) {
1410 // Special case for during install/upgrade.
1411 return '<div class="sitelink">'.
1412 '<a title="Moodle ' . $CFG->target_release . '" href="http://docs.moodle.org/en/Administrator_documentation" onclick="this.target=\'_blank\'">' .
1413 '<img style="width:100px;height:30px" src="' . $CFG->httpswwwroot . '/pix/moodlelogo.gif" alt="moodlelogo" /></a></div>';
34a2777c 1414
ebebf55c 1415 } else if ($this->page->course->id == $SITE->id || strpos($this->page->pagetype, 'course-view') === 0) {
1416 return '<div class="homelink"><a href="' . $CFG->wwwroot . '/">' .
1417 get_string('home') . '</a></div>';
1418
1419 } else {
1420 return '<div class="homelink"><a href="' . $CFG->wwwroot . '/course/view.php?id=' . $this->page->course->id . '">' .
1421 format_string($this->page->course->shortname) . '</a></div>';
1422 }
34a2777c 1423 }
a5cb8d69 1424
1425 /**
ebebf55c 1426 * Redirects the user by any means possible given the current state
1427 *
1428 * This function should not be called directly, it should always be called using
1429 * the redirect function in lib/weblib.php
1430 *
1431 * The redirect function should really only be called before page output has started
1432 * however it will allow itself to be called during the state STATE_IN_BODY
1433 *
1434 * @param string $encodedurl The URL to send to encoded if required
1435 * @param string $message The message to display to the user if any
1436 * @param int $delay The delay before redirecting a user, if $message has been
1437 * set this is a requirement and defaults to 3, set to 0 no delay
1438 * @param string $messageclass The css class to put on the message that is
1439 * being displayed to the user
ae96b517 1440 * @param boolean $debugdisableredirect this redirect has been disabled for
1441 * debugging purposes. Display a message that explains, and don't
1442 * trigger the redirect.
ebebf55c 1443 * @return string The HTML to display to the user before dying, may contain
1444 * meta refresh, javascript refresh, and may have set header redirects
a5cb8d69 1445 */
ae96b517 1446 public function redirect_message($encodedurl, $message, $delay, $debugdisableredirect) {
ebebf55c 1447 global $CFG;
1448 $url = str_replace('&amp;', '&', $encodedurl);
8954245a 1449
ebebf55c 1450 switch ($this->page->state) {
1451 case moodle_page::STATE_BEFORE_HEADER :
1452 // No output yet it is safe to delivery the full arsenol of redirect methods
ae96b517 1453 if (!$debugdisableredirect) {
1454 // Don't use exactly the same time here, it can cause problems when both redirects fire at the same time.
ebebf55c 1455 $this->metarefreshtag = '<meta http-equiv="refresh" content="'. $delay .'; url='. $encodedurl .'" />'."\n";
ae96b517 1456 $this->page->requires->js_function_call('document.location.replace', array($url))->after_delay($delay + 3);
ebebf55c 1457 }
1458 $output = $this->header();
ebebf55c 1459 break;
1460 case moodle_page::STATE_PRINTING_HEADER :
1461 // We should hopefully never get here
1462 throw new coding_exception('You cannot redirect while printing the page header');
1463 break;
1464 case moodle_page::STATE_IN_BODY :
1465 // We really shouldn't be here but we can deal with this
1466 debugging("You should really redirect before you start page output");
1467 if (!$disableredirect) {
ae96b517 1468 $this->page->requires->js_function_call('document.location.replace', array($url))->after_delay($delay);
ebebf55c 1469 }
1470 $output = $this->opencontainers->pop_all_but_last();
ebebf55c 1471 break;
1472 case moodle_page::STATE_DONE :
1473 // Too late to be calling redirect now
1474 throw new coding_exception('You cannot redirect after the entire page has been generated');
1475 break;
1476 }
ae96b517 1477 $output .= $this->notification($message, 'redirectmessage');
1478 $output .= '<a href="'. $encodedurl .'">'. get_string('continue') .'</a>';
1479 if ($debugdisableredirect) {
1480 $output .= '<p><strong>Error output, so disabling automatic redirect.</strong></p>';
1481 }
1482 $output .= $this->footer();
ebebf55c 1483 return $output;
1484 }
b7009474 1485
ebebf55c 1486 // TODO remove $navigation and $menu arguments - replace with $PAGE->navigation
1487 public function header($navigation = '', $menu='') {
1488 global $USER, $CFG;
b7009474 1489
ebebf55c 1490 output_starting_hook();
1491 $this->page->set_state(moodle_page::STATE_PRINTING_HEADER);
b7009474 1492
ebebf55c 1493 // Find the appropriate page template, based on $this->page->generaltype.
1494 $templatefile = $this->find_page_template();
1495 if ($templatefile) {
1496 // Render the template.
1497 $template = $this->render_page_template($templatefile, $menu, $navigation);
1498 } else {
1499 // New style template not found, fall back to using header.html and footer.html.
1500 $template = $this->handle_legacy_theme($navigation, $menu);
1501 }
b7009474 1502
ebebf55c 1503 // Slice the template output into header and footer.
1504 $cutpos = strpos($template, self::MAIN_CONTENT_TOKEN);
1505 if ($cutpos === false) {
1506 throw new coding_exception('Layout template ' . $templatefile .
1507 ' does not contain the string "' . self::MAIN_CONTENT_TOKEN . '".');
1508 }
1509 $header = substr($template, 0, $cutpos);
1510 $footer = substr($template, $cutpos + strlen(self::MAIN_CONTENT_TOKEN));
b7009474 1511
ebebf55c 1512 send_headers($this->contenttype, $this->page->cacheable);
1513 $this->opencontainers->push('header/footer', $footer);
1514 $this->page->set_state(moodle_page::STATE_IN_BODY);
1515 return $header . $this->skip_link_target();
1516 }
b7009474 1517
ebebf55c 1518 protected function find_page_template() {
1519 global $THEME;
b7009474 1520
ebebf55c 1521 // If this is a particular page type, look for a specific template.
1522 $type = $this->page->generaltype;
1523 if ($type != 'normal') {
1524 $templatefile = $THEME->dir . '/layout-' . $type . '.php';
1525 if (is_readable($templatefile)) {
1526 return $templatefile;
1527 }
1528 }
b7009474 1529
ebebf55c 1530 // Otherwise look for the general template.
1531 $templatefile = $THEME->dir . '/layout.php';
1532 if (is_readable($templatefile)) {
1533 return $templatefile;
1534 }
b7009474 1535
ebebf55c 1536 return false;
1537 }
b7009474 1538
ebebf55c 1539 protected function render_page_template($templatefile, $menu, $navigation) {
1540 global $CFG, $SITE, $THEME, $USER;
1541 // The next lines are a bit tricky. The point is, here we are in a method
1542 // of a renderer class, and this object may, or may not, be the the same as
1543 // the global $OUTPUT object. When rendering the template, we want to use
1544 // this object. However, people writing Moodle code expect the current
1545 // rederer to be called $OUTPUT, not $this, so define a variable called
1546 // $OUTPUT pointing at $this. The same comment applies to $PAGE and $COURSE.
1547 $OUTPUT = $this;
1548 $PAGE = $this->page;
1549 $COURSE = $this->page->course;
b7009474 1550
ebebf55c 1551 ob_start();
1552 include($templatefile);
1553 $template = ob_get_contents();
1554 ob_end_clean();
1555 return $template;
1556 }
b7009474 1557
ebebf55c 1558 protected function handle_legacy_theme($navigation, $menu) {
1559 global $CFG, $SITE, $THEME, $USER;
1560 // Set a pretend global from the properties of this class.
1561 // See the comment in render_page_template for a fuller explanation.
1562 $COURSE = $this->page->course;
1563
1564 // Set up local variables that header.html expects.
1565 $direction = $this->htmlattributes();
1566 $title = $this->page->title;
1567 $heading = $this->page->heading;
1568 $focus = $this->page->focuscontrol;
1569 $button = $this->page->button;
1570 $pageid = $this->page->pagetype;
1571 $pageclass = $this->page->bodyclasses;
1572 $bodytags = ' class="' . $pageclass . '" id="' . $pageid . '"';
1573 $home = $this->page->generaltype == 'home';
1574
1575 $meta = $this->standard_head_html();
1576 // The next line is a nasty hack. having set $meta to standard_head_html, we have already
1577 // got the contents of include($CFG->javascript). However, legacy themes are going to
1578 // include($CFG->javascript) again. We want to make sure that when they do, nothing is output.
1579 $CFG->javascript = $CFG->libdir . '/emptyfile.php';
b7009474 1580
ebebf55c 1581 // Set up local variables that footer.html expects.
1582 $homelink = $this->home_link();
1583 $loggedinas = $this->login_info();
1584 $course = $this->page->course;
1585 $performanceinfo = self::PERFORMANCE_INFO_TOKEN;
b7009474 1586
ebebf55c 1587 if (!$menu && $navigation) {
1588 $menu = $loggedinas;
1589 }
b7009474 1590
ebebf55c 1591 ob_start();
1592 include($THEME->dir . '/header.html');
1593 $this->page->requires->get_top_of_body_code();
1594 echo self::MAIN_CONTENT_TOKEN;
b7009474 1595
ebebf55c 1596 $menu = str_replace('navmenu', 'navmenufooter', $menu);
1597 include($THEME->dir . '/footer.html');
b7009474 1598
ebebf55c 1599 $output = ob_get_contents();
1600 ob_end_clean();
b7009474 1601
ebebf55c 1602 $output = str_replace('</body>', self::END_HTML_TOKEN . '</body>', $output);
b7009474 1603
ebebf55c 1604 return $output;
1605 }
b7009474 1606
ebebf55c 1607 public function footer() {
4af1e3b0 1608 $output = $this->opencontainers->pop_all_but_last(true);
b7009474 1609
ebebf55c 1610 $footer = $this->opencontainers->pop('header/footer');
b7009474 1611
ebebf55c 1612 // Provide some performance info if required
1613 $performanceinfo = '';
1614 if (defined('MDL_PERF') || (!empty($CFG->perfdebug) and $CFG->perfdebug > 7)) {
1615 $perf = get_performance_info();
1616 if (defined('MDL_PERFTOLOG') && !function_exists('register_shutdown_function')) {
1617 error_log("PERF: " . $perf['txt']);
1618 }
1619 if (defined('MDL_PERFTOFOOT') || debugging() || $CFG->perfdebug > 7) {
1620 $performanceinfo = $perf['html'];
1621 }
1622 }
1623 $footer = str_replace(self::PERFORMANCE_INFO_TOKEN, $performanceinfo, $footer);
b7009474 1624
ebebf55c 1625 $footer = str_replace(self::END_HTML_TOKEN, $this->page->requires->get_end_code(), $footer);
b7009474 1626
ebebf55c 1627 $this->page->set_state(moodle_page::STATE_DONE);
b7009474 1628
ebebf55c 1629 return $output . $footer;
1630 }
b7009474 1631
ebebf55c 1632 /**
1633 * Prints a nice side block with an optional header.
1634 *
1635 * The content is described
1636 * by a {@link block_contents} object.
1637 *
1638 * @param block $content HTML for the content
1639 * @return string the HTML to be output.
1640 */
1641 function block($bc) {
1642 $bc = clone($bc);
1643 $bc->prepare();
b7009474 1644
ebebf55c 1645 $title = strip_tags($bc->title);
1646 if (empty($title)) {
1647 $output = '';
1648 $skipdest = '';
1649 } else {
1650 $output = $this->output_tag('a', array('href' => '#sb-' . $bc->skipid, 'class' => 'skip-block'),
1651 get_string('skipa', 'access', $title));
1652 $skipdest = $this->output_tag('span', array('id' => 'sb-' . $bc->skipid, 'class' => 'skip-block-to'), '');
1653 }
b7009474 1654
ebebf55c 1655 $bc->attributes['id'] = $bc->id;
1656 $bc->attributes['class'] = $bc->get_classes_string();
1657 $output .= $this->output_start_tag('div', $bc->attributes);
b7009474 1658
ebebf55c 1659 if ($bc->heading) {
1660 // Some callers pass in complete html for the heading, which may include
1661 // complicated things such as the 'hide block' button; some just pass in
1662 // text. If they only pass in plain text i.e. it doesn't include a
1663 // <div>, then we add in standard tags that make it look like a normal
1664 // page block including the h2 for accessibility
1665 if (strpos($bc->heading, '</div>') === false) {
1666 $bc->heading = $this->output_tag('div', array('class' => 'title'),
1667 $this->output_tag('h2', null, $bc->heading));
1668 }
b7009474 1669
ebebf55c 1670 $output .= $this->output_tag('div', array('class' => 'header'), $bc->heading);
1671 }
b7009474 1672
ebebf55c 1673 $output .= $this->output_start_tag('div', array('class' => 'content'));
b7009474 1674
ebebf55c 1675 if ($bc->content) {
1676 $output .= $bc->content;
b7009474 1677
ebebf55c 1678 } else if ($bc->list) {
1679 $row = 0;
1680 $items = array();
1681 foreach ($bc->list as $key => $string) {
1682 $item = $this->output_start_tag('li', array('class' => 'r' . $row));
1683 if ($bc->icons) {
1684 $item .= $this->output_tag('div', array('class' => 'icon column c0'), $bc->icons[$key]);
1685 }
1686 $item .= $this->output_tag('div', array('class' => 'column c1'), $string);
1687 $item .= $this->output_end_tag('li');
1688 $items[] = $item;
1689 $row = 1 - $row; // Flip even/odd.
1690 }
1691 $output .= $this->output_tag('ul', array('class' => 'list'), implode("\n", $items));
1692 }
b7009474 1693
ebebf55c 1694 if ($bc->footer) {
1695 $output .= $this->output_tag('div', array('class' => 'footer'), $bc->footer);
1696 }
b7009474 1697
ebebf55c 1698 $output .= $this->output_end_tag('div');
1699 $output .= $this->output_end_tag('div');
1700 $output .= $skipdest;
b7009474 1701
ebebf55c 1702 if (!empty($CFG->allowuserblockhiding) && isset($attributes['id'])) {
1703 $strshow = addslashes_js(get_string('showblocka', 'access', $title));
1704 $strhide = addslashes_js(get_string('hideblocka', 'access', $title));
1705 $output .= $this->page->requires->js_function_call('elementCookieHide', array(
1706 $bc->id, $strshow, $strhide))->asap();
1707 }
b7009474 1708
ebebf55c 1709 return $output;
1710 }
b7009474 1711
ebebf55c 1712 public function link_to_popup_window() {
b7009474 1713
ebebf55c 1714 }
b7009474 1715
ebebf55c 1716 public function button_to_popup_window() {
b7009474 1717
ebebf55c 1718 }
b7009474 1719
ebebf55c 1720 public function close_window_button($buttontext = null, $reloadopener = false) {
1721 if (empty($buttontext)) {
1722 $buttontext = get_string('closewindow');
1723 }
1724 // TODO
1725 }
b7009474 1726
ebebf55c 1727 public function close_window($delay = 0, $reloadopener = false) {
1728 // TODO
1729 }
b7009474 1730
ebebf55c 1731 /**
1732 * Output a <select> menu.
1733 *
1734 * You can either call this function with a single moodle_select_menu argument
1735 * or, with a list of parameters, in which case those parameters are sent to
1736 * the moodle_select_menu constructor.
1737 *
1738 * @param moodle_select_menu $selectmenu a moodle_select_menu that describes
1739 * the select menu you want output.
1740 * @return string the HTML for the <select>
1741 */
1742 public function select_menu($selectmenu) {
1743 $selectmenu = clone($selectmenu);
1744 $selectmenu->prepare();
b7009474 1745
ebebf55c 1746 if ($selectmenu->nothinglabel) {
1747 $selectmenu->options = array($selectmenu->nothingvalue => $selectmenu->nothinglabel) +
1748 $selectmenu->options;
1749 }
b7009474 1750
ebebf55c 1751 if (empty($selectmenu->id)) {
1752 $selectmenu->id = 'menu' . str_replace(array('[', ']'), '', $selectmenu->name);
1753 }
b7009474 1754
ebebf55c 1755 $attributes = array(
1756 'name' => $selectmenu->name,
1757 'id' => $selectmenu->id,
1758 'class' => $selectmenu->get_classes_string(),
1759 'onchange' => $selectmenu->script,
1760 );
1761 if ($selectmenu->disabled) {
1762 $attributes['disabled'] = 'disabled';
1763 }
1764 if ($selectmenu->tabindex) {
1765 $attributes['tabindex'] = $tabindex;
1766 }
b7009474 1767
ebebf55c 1768 if ($selectmenu->listbox) {
1769 if (is_integer($selectmenu->listbox)) {
1770 $size = $selectmenu->listbox;
1771 } else {
1772 $size = min($selectmenu->maxautosize, count($selectmenu->options));
1773 }
1774 $attributes['size'] = $size;
1775 if ($selectmenu->multiple) {
1776 $attributes['multiple'] = 'multiple';
1777 }
1778 }
b7009474 1779
ebebf55c 1780 $html = $this->output_start_tag('select', $attributes) . "\n";
1781 foreach ($selectmenu->options as $value => $label) {
1782 $attributes = array('value' => $value);
1783 if ((string)$value == (string)$selectmenu->selectedvalue ||
1784 (is_array($selectmenu->selectedvalue) && in_array($value, $selectmenu->selectedvalue))) {
1785 $attributes['selected'] = 'selected';
1786 }
1787 $html .= ' ' . $this->output_tag('option', $attributes, s($label)) . "\n";
1788 }
1789 $html .= $this->output_end_tag('select') . "\n";
b7009474 1790
ebebf55c 1791 return $html;
1792 }
b7009474 1793
ebebf55c 1794 // TODO choose_from_menu_nested
1795
1796 // TODO choose_from_radio
b7009474 1797
1798 /**
ebebf55c 1799 * Output an error message. By default wraps the error message in <span class="error">.
1800 * If the error message is blank, nothing is output.
1801 * @param $message the error message.
1802 * @return string the HTML to output.
b7009474 1803 */
ebebf55c 1804 public function error_text($message) {
1805 if (empty($message)) {
1806 return '';
1807 }
1808 return $this->output_tag('span', array('class' => 'error'), $message);
1809 }
b7009474 1810
1811 /**
ebebf55c 1812 * Do not call this function directly.
b7009474 1813 *
ebebf55c 1814 * To terminate the current script with a fatal error, call the {@link print_error}
1815 * function, or throw an exception. Doing either of those things will then call this
1816 * funciton to display the error, before terminating the exection.
1817 *
1818 * @param string $message
1819 * @param string $moreinfourl
1820 * @param string $link
1821 * @param array $backtrace
1822 * @param string $debuginfo
1823 * @param bool $showerrordebugwarning
1824 * @return string the HTML to output.
b7009474 1825 */
ebebf55c 1826 public function fatal_error($message, $moreinfourl, $link, $backtrace,
1827 $debuginfo = null, $showerrordebugwarning = false) {
b7009474 1828
ebebf55c 1829 $output = '';
b7009474 1830
ebebf55c 1831 if ($this->has_started()) {
1832 $output .= $this->opencontainers->pop_all_but_last();
1833 } else {
1834 // Header not yet printed
1835 @header('HTTP/1.0 404 Not Found');
1836 $this->page->set_title(get_string('error'));
1837 $output .= $this->header();
1838 }
b7009474 1839
ebebf55c 1840 $message = '<p class="errormessage">' . $message . '</p>'.
1841 '<p class="errorcode"><a href="' . $moreinfourl . '">' .
1842 get_string('moreinformation') . '</a></p>';
1843 $output .= $this->box($message, 'errorbox');
1844
1845 if (debugging('', DEBUG_DEVELOPER)) {
1846 if ($showerrordebugwarning) {
1847 $output .= $this->notification('error() is a deprecated function. ' .
1848 'Please call print_error() instead of error()', 'notifytiny');
1849 }
1850 if (!empty($debuginfo)) {
1851 $output .= $this->notification($debuginfo, 'notifytiny');
1852 }
1853 if (!empty($backtrace)) {
1854 $output .= $this->notification('Stack trace: ' .
1855 format_backtrace($backtrace), 'notifytiny');
1856 }
b7009474 1857 }
b7009474 1858
ebebf55c 1859 if (!empty($link)) {
1860 $output .= $this->continue_button($link);
1861 }
b7009474 1862
ebebf55c 1863 $output .= $this->footer();
1864
1865 // Padding to encourage IE to display our error page, rather than its own.
1866 $output .= str_repeat(' ', 512);
1867
1868 return $output;
b7009474 1869 }
1870
db8d89d8 1871 /**
ebebf55c 1872 * Output a notification (that is, a status message about something that has
1873 * just happened).
1874 *
1875 * @param string $message the message to print out
1876 * @param string $classes normally 'notifyproblem' or 'notifysuccess'.
1877 * @return string the HTML to output.
db8d89d8 1878 */
ebebf55c 1879 public function notification($message, $classes = 'notifyproblem') {
1880 return $this->output_tag('div', array('class' =>
1881 moodle_renderer_base::prepare_classes($classes)), clean_text($message));
db8d89d8 1882 }
1883
b7009474 1884 /**
ebebf55c 1885 * Print a continue button that goes to a particular URL.
1886 *
1887 * @param string|moodle_url $link The url the button goes to.
1888 * @return string the HTML to output.
b7009474 1889 */
ebebf55c 1890 public function continue_button($link) {
1891 if (!is_a($link, 'moodle_url')) {
1892 $link = new moodle_url($link);
b7009474 1893 }
ebebf55c 1894 return $this->output_tag('div', array('class' => 'continuebutton'),
1895 print_single_button($link->out(true), $link->params(), get_string('continue'), 'get', '', true));
b7009474 1896 }
1897
1898 /**
ebebf55c 1899 * Output the place a skip link goes to.
1900 * @param $id The target name from the corresponding $PAGE->requires->skip_link_to($target) call.
1901 * @return string the HTML to output.
b7009474 1902 */
ebebf55c 1903 public function skip_link_target($id = 'maincontent') {
1904 return $this->output_tag('span', array('id' => $id), '');
1905 }
b7009474 1906
ebebf55c 1907 public function heading($text, $level, $classes = 'main', $id = '') {
1908 $level = (integer) $level;
1909 if ($level < 1 or $level > 6) {
1910 throw new coding_exception('Heading level must be an integer between 1 and 6.');
b7009474 1911 }
ebebf55c 1912 return $this->output_tag('h' . $level,
1913 array('id' => $id, 'class' => moodle_renderer_base::prepare_classes($classes)), $text);
1914 }
b7009474 1915
ebebf55c 1916 public function box($contents, $classes = 'generalbox', $id = '') {
1917 return $this->box_start($classes, $id) . $contents . $this->box_end();
1918 }
b7009474 1919
ebebf55c 1920 public function box_start($classes = 'generalbox', $id = '') {
1921 $this->opencontainers->push('box', $this->output_end_tag('div'));
1922 return $this->output_start_tag('div', array('id' => $id,
1923 'class' => 'box ' . moodle_renderer_base::prepare_classes($classes)));
1924 }
b7009474 1925
ebebf55c 1926 public function box_end() {
1927 return $this->opencontainers->pop('box');
1928 }
b7009474 1929
ebebf55c 1930 public function container($contents, $classes = '', $id = '') {
1931 return $this->container_start($classes, $id) . $contents . $this->container_end();
1932 }
b7009474 1933
ebebf55c 1934 public function container_start($classes = '', $id = '') {
1935 $this->opencontainers->push('container', $this->output_end_tag('div'));
1936 return $this->output_start_tag('div', array('id' => $id,
1937 'class' => moodle_renderer_base::prepare_classes($classes)));
1938 }
1939
1940 public function container_end() {
1941 return $this->opencontainers->pop('container');
b7009474 1942 }
b7009474 1943}
1944
1945
8954245a 1946/**
1947 * Base class for classes representing HTML elements, like moodle_select_menu.
1948 *
1949 * Handles the id and class attribues.
1950 *
1951 * @copyright 2009 Tim Hunt
1952 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1953 * @since Moodle 2.0
1954 */
1955class moodle_html_component {
1956 /**
1957 * @var string value to use for the id attribute of this HTML tag.
1958 */
1959 public $id = '';
1960 /**
1961 * @var array class names to add to this HTML element.
1962 */
1963 public $classes = array();
1964
1965 /**
1966 * Ensure some class names are an array.
1967 * @param mixed $classes either an array of class names or a space-separated
1968 * string containing class names.
1969 * @return array the class names as an array.
1970 */
1971 public static function clean_clases($classes) {
1972 if (is_array($classes)) {
1973 return $classes;
1974 } else {
a5cb8d69 1975 return explode(' ', trim($classes));
8954245a 1976 }
1977 }
1978
1979 /**
1980 * Set the class name array.
1981 * @param mixed $classes either an array of class names or a space-separated
1982 * string containing class names.
1983 */
1984 public function set_classes($classes) {
1985 $this->classes = self::clean_clases($classes);
1986 }
1987
1988 /**
1989 * Add a class name to the class names array.
1990 * @param string $class the new class name to add.
1991 */
1992 public function add_class($class) {
1993 $this->classes[] = $class;
1994 }
1995
1996 /**
1997 * Add a whole lot of class names to the class names array.
1998 * @param mixed $classes either an array of class names or a space-separated
1999 * string containing class names.
2000 */
2001 public function add_classes($classes) {
2002 $this->classes += self::clean_clases($classes);
2003 }
2004
2005 /**
2006 * Get the class names as a string.
2007 * @return string the class names as a space-separated string. Ready to be put in the class="" attribute.
2008 */
2009 public function get_classes_string() {
2010 return implode(' ', $this->classes);
2011 }
2012
2013 /**
2014 * Perform any cleanup or final processing that should be done before an
2015 * instance of this class is output.
2016 */
2017 public function prepare() {
2018 $this->classes = array_unique(self::clean_clases($this->classes));
2019 }
2020}
2021
2022
2023/**
2024 * This class hold all the information required to describe a <select> menu that
34a2777c 2025 * will be printed by {@link moodle_core_renderer::select_menu()}. (Or by an overridden
8954245a 2026 * version of that method in a subclass.)
2027 *
2028 * All the fields that are not set by the constructor have sensible defaults, so
2029 * you only need to set the properties where you want non-default behaviour.
2030 *
2031 * @copyright 2009 Tim Hunt
2032 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2033 * @since Moodle 2.0
2034 */
2035class moodle_select_menu extends moodle_html_component {
2036 /**
2037 * @var array the choices to show in the menu. An array $value => $label.
2038 */
2039 public $options;
2040 /**
2041 * @var string the name of this form control. That is, the name of the GET/POST
2042 * variable that will be set if this select is submmitted as part of a form.
2043 */
2044 public $name;
2045 /**
2046 * @var string the option to select initially. Should match one
2047 * of the $options array keys. Default none.
2048 */
2049 public $selectedvalue;
2050 /**
2051 * @var string The label for the 'nothing is selected' option.
2052 * Defaults to get_string('choosedots').
2053 * Set this to '' if you do not want a 'nothing is selected' option.
2054 */
2055 public $nothinglabel = null;
2056 /**
2057 * @var string The value returned by the 'nothing is selected' option. Defaults to 0.
2058 */
2059 public $nothingvalue = 0;
2060 /**
2061 * @var boolean set this to true if you want the control to appear disabled.
2062 */
2063 public $disabled = false;
2064 /**
2065 * @var integer if non-zero, sets the tabindex attribute on the <select> element. Default 0.
2066 */
2067 public $tabindex = 0;
2068 /**
2069 * @var mixed Defaults to false, which means display the select as a dropdown menu.
2070 * If true, display this select as a list box whose size is chosen automatically.
2071 * If an integer, display as list box of that size.
2072 */
2073 public $listbox = false;
2074 /**
2075 * @var integer if you are using $listbox === true to get an automatically
2076 * sized list box, the size of the list box will be the number of options,
2077 * or this number, whichever is smaller.
2078 */
2079 public $maxautosize = 10;
2080 /**
2081 * @var boolean if true, allow multiple selection. Only used if $listbox is true.
2082 */
2083 public $multiple = false;
2084 /**
2085 * @deprecated
2086 * @var string JavaScript to add as an onchange attribute. Do not use this.
2087 * Use the YUI even library instead.
2088 */
2089 public $script = '';
2090
2091 /* @see lib/moodle_html_component#prepare() */
2092 public function prepare() {
2093 if (empty($this->id)) {
2094 $this->id = 'menu' . str_replace(array('[', ']'), '', $this->name);
2095 }
2096 if (empty($this->classes)) {
2097 $this->set_classes(array('menu' . str_replace(array('[', ']'), '', $this->name)));
2098 }
2099 $this->add_class('select');
2100 parent::prepare();
2101 }
2102
2103 /**
2104 * This is a shortcut for making a simple select menu. It lets you specify
2105 * the options, name and selected option in one line of code.
2106 * @param array $options used to initialise {@link $options}.
2107 * @param string $name used to initialise {@link $name}.
2108 * @param string $selected used to initialise {@link $selected}.
2109 * @return moodle_select_menu A moodle_select_menu object with the three common fields initialised.
2110 */
2111 public static function make($options, $name, $selected = '') {
2112 $menu = new moodle_select_menu();
2113 $menu->options = $options;
2114 $menu->name = $name;
2115 $menu->selectedvalue = $selected;
2116 return $menu;
2117 }
2118
2119 /**
2120 * This is a shortcut for making a yes/no select menu.
2121 * @param string $name used to initialise {@link $name}.
2122 * @param string $selected used to initialise {@link $selected}.
2123 * @return moodle_select_menu A menu initialised with yes/no options.
2124 */
2125 public static function make_yes_no($name, $selected) {
2126 return self::make(array(0 => get_string('no'), 1 => get_string('yes')), $name, $selected);
2127 }
571fa828 2128}
2129
2130
a5cb8d69 2131/**
2132 * This class hold all the information required to describe a Moodle block.
2133 *
2134 * That is, it holds all the different bits of HTML content that need to be put into the block.
2135 *
2136 * @copyright 2009 Tim Hunt
2137 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2138 * @since Moodle 2.0
2139 */
2140class block_contents extends moodle_html_component {
2141 protected static $idcounter = 1;
2142 /**
2143 * @param string $heading HTML for the heading. Can include full HTML or just
2144 * plain text - plain text will automatically be enclosed in the appropriate
2145 * heading tags.
2146 */
2147 public $heading = '';
2148 /**
2149 * @param string $title Plain text title, as embedded in the $heading.
2150 */
2151 public $title = '';
2152 /**
2153 * @param string $content HTML for the content
2154 */
2155 public $content = '';
2156 /**
2157 * @param array $list an alternative to $content, it you want a list of things with optional icons.
2158 */
2159 public $list = array();
2160 /**
2161 * @param array $icons optional icons for the things in $list.
2162 */
2163 public $icons = array();
2164 /**
2165 * @param string $footer Extra HTML content that gets output at the end, inside a &lt;div class="footer">
2166 */
2167 public $footer = '';
2168 /**
2169 * @param array $attributes an array of attribute => value pairs that are put on the
2170 * outer div of this block. {@link $id} and {@link $classes} attributes should be set separately.
2171 */
2172 public $attributes = array();
2173 /**
2174 * @param integer $skipid do not set this manually. It is set automatically be the {@link prepare()} method.
2175 */
2176 public $skipid;
2177
2178 /* @see lib/moodle_html_component#prepare() */
2179 public function prepare() {
2180 $this->skipid = self::$idcounter;
2181 self::$idcounter += 1;
2182 $this->add_class('sideblock');
2183 parent::prepare();
2184 }
2185}
2186
2187
34a2777c 2188/**
2189 * A renderer that generates output for commandlines scripts.
2190 *
2191 * The implementation of this renderer is probably incomplete.
2192 *
2193 * @copyright 2009 Tim Hunt
2194 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2195 * @since Moodle 2.0
2196 */
2197class cli_core_renderer extends moodle_core_renderer {
2198 public function header() {
2199 output_starting_hook();
2200 return $this->page->heading . "\n";
2201 }
2202
2203 public function heading($text, $level, $classes = 'main', $id = '') {
2204 $text .= "\n";
2205 switch ($level) {
2206 case 1:
2207 return '=>' . $text;
2208 case 2:
2209 return '-->' . $text;
2210 default:
2211 return $text;
2212 }
2213 }
2214
2215 public function fatal_error($errorcode, $module, $a, $link, $backtrace,
2216 $debuginfo = null, $showerrordebugwarning = false) {
2217 $output = "!!! $message !!!\n";
2218
2219 if (debugging('', DEBUG_DEVELOPER)) {
2220 if (!empty($debuginfo)) {
2221 $this->notification($debuginfo, 'notifytiny');
2222 }
2223 if (!empty($backtrace)) {
2224 $this->notification('Stack trace: ' . format_backtrace($backtrace, true), 'notifytiny');
2225 }
2226 }
2227 }
2228
2229 public function notification($message, $classes = 'notifyproblem') {
2230 $message = clean_text($message);
2231 if ($style === 'notifysuccess') {
2232 return "++ $message ++\n";
2233 }
2234 return "!! $message !!\n";
2235 }
2236}
2237
b7009474 2238
2239/**
2240 * Output CSS while replacing constants/variables. See MDL-6798 for details
2241 *
2242 * Information from Urs Hunkler:
2243 *
2244 * This is an adaptation of Shaun Inman's "CSS Server-side Constants" for Moodle.
2245 * http://www.shauninman.com/post/heap/2005/08/09/css_constants
2246 *
2247 * To use, specify $THEME->customcssoutputfunction = 'output_css_replacing_constants';
2248 * in your theme's config.php file.
2249 *
2250 * The constant definitions are written into a separate CSS file named like
2251 * constants.css and loaded first in config.php. You can use constants for any
2252 * CSS properties. The constant definition looks like:
2253 * <code>
2254 * \@server constants {
2255 * fontColor: #3a2830;
2256 * aLink: #116699;
2257 * aVisited: #AA2200;
2258 * aHover: #779911;
2259 * pageBackground: #FFFFFF;
2260 * backgroundColor: #EEEEEE;
2261 * backgroundSideblockHeader: #a8a4e9;
2262 * fontcolorSideblockHeader: #222222;
2263 * color1: #98818b;
2264 * color2: #bd807b;
2265 * color3: #f9d1d7;
2266 * color4: #e8d4d8;
2267 * }
2268 * </code>
2269 *
2270 * The lines in the CSS files using CSS constants look like:
2271 * <code>
2272 * body {
2273 * font-size: 100%;
2274 * background-color: pageBackground;
2275 * color: fontColor;
2276 * font-family: 'Bitstream Vera Serif', georgia, times, serif;
2277 * margin: 0;
2278 * padding: 0;
2279 * }
2280 * div#page {
2281 * margin: 0 10px;
2282 * padding-top: 5px;
2283 * border-top-width: 10px;
2284 * border-top-style: solid;
2285 * border-top-color: color3;
2286 * }
2287 * div.clearer {
2288 * clear: both;
2289 * }
2290 * a:link {
2291 * color: aLink;
2292 * }
2293 * </code>
2294 *
2295 * @param array $files an arry of the CSS fiels that need to be output.
2296 */
2297function output_css_replacing_constants($files) {
2298 global $CFG;
2299 // Get all the CSS.
2300 $toreplace = array($CFG->dirroot, $CFG->themedir);
2301 ob_start();
2302 foreach ($files as $file) {
2303 $shortname = str_replace($toreplace, '', $file);
2304 echo '/******* ' . $shortname . " start *******/\n\n";
2305 @include_once($file);
2306 echo '/******* ' . $shortname . " end *******/\n\n";
2307 }
2308 $css = ob_get_contents();
2309 ob_end_clean();
2310
2311 if (preg_match_all("/@server\s+(?:variables|constants)\s*\{\s*([^\}]+)\s*\}\s*/i", $css, $matches)) {
2312 $variables = array();
2313 foreach ($matches[0] as $key => $server) {
2314 $css = str_replace($server, '', $css);
2315 preg_match_all("/([^:\}\s]+)\s*:\s*([^;\}]+);/", $matches[1][$key], $vars);
2316 foreach ($vars[1] as $var => $value) {
2317 $variables[$value] = $vars[2][$var];
2318 }
2319 }
2320 $css = str_replace(array_keys($variables), array_values($variables), $css);
2321 }
2322 echo $css;
2323}
2324
2325/**
2326 * This CSS output function will link to CSS files rather than including them
2327 * inline.
2328 *
2329 * The single CSS files can then be edited and saved with interactive
2330 * CSS editors like CSSEdit. Any files that have a .php extension are still included
2331 * inline.
2332 *
2333 * @param array $files an arry of the CSS fiels that need to be output.
2334 */
2335function output_css_for_css_edit($files) {
2336 global $CFG;
2337 $toreplace = array($CFG->dirroot, $CFG->themedir);
2338 foreach ($files as $file) {
2339 $shortname = str_replace($toreplace, '', $file);
2340 echo '/* @group ' . $shortname . " */\n\n";
2341 if (strpos($file, '.css') !== false) {
2342 echo '@import url("' . $file . '");'."\n\n";
2343 } else {
2344 @include_once($file);
2345 }
2346 echo "/* @end */\n\n";
2347 }
2348}