formslib: use abstract, rather than a print_error, now we have PHP5.
[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.
897b5c82 62 * @param string $subtype optional subtype such as 'news' resulting to 'mod_forum_news'
571fa828 63 * @return object an object implementing the requested renderer interface.
64 */
897b5c82 65 public function get_renderer($component, $page, $subtype=null);
571fa828 66}
67
68
69/**
ebebf55c 70 * An icon finder is responsible for working out the correct URL for an icon.
571fa828 71 *
ebebf55c 72 * A icon finder must also have a constructor that takes a theme object.
73 * (See {@link standard_icon_finder::__construct} for an example.)
571fa828 74 *
ebebf55c 75 * Note that we are planning to change the Moodle icon naming convention before
76 * the Moodle 2.0 relase. Therefore, this API will probably change.
571fa828 77 *
78 * @copyright 2009 Tim Hunt
79 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
80 * @since Moodle 2.0
81 */
ebebf55c 82interface icon_finder {
571fa828 83 /**
ebebf55c 84 * Return the URL for an icon indentifed as in pre-Moodle 2.0 code.
85 *
86 * Suppose you have old code like $url = "$CFG->pixpath/i/course.gif";
87 * then old_icon_url('i/course'); will return the equivalent URL that is correct now.
88 *
89 * @param $iconname the name of the icon.
90 * @return string the URL for that icon.
571fa828 91 */
ebebf55c 92 public function old_icon_url($iconname);
93
571fa828 94 /**
ebebf55c 95 * Return the URL for an icon indentifed as in pre-Moodle 2.0 code.
571fa828 96 *
ebebf55c 97 * Suppose you have old code like $url = "$CFG->modpixpath/$mod/icon.gif";
98 * then mod_icon_url('icon', $mod); will return the equivalent URL that is correct now.
571fa828 99 *
ebebf55c 100 * @param $iconname the name of the icon.
101 * @param $module the module the icon belongs to.
102 * @return string the URL for that icon.
571fa828 103 */
ebebf55c 104 public function mod_icon_url($iconname, $module);
571fa828 105}
106
107
108/**
fdeb7fa1 109 * This class represents the configuration variables of a Moodle theme.
110 *
111 * All the variables with access: public below (with a few exceptions that are marked)
112 * are the properties you can set in your theme's config.php file.
113 *
114 * There are also some methods and protected variables that are part of the inner
115 * workings of Moodle's themes system. If you are just editing a theme's config.php
116 * file, you cac just ingore those, and the following information for developers.
ebebf55c 117 *
118 * Normally, to create an instance of this class, you should use the
119 * {@link theme_config::load()} factory method to load a themes config.php file.
fdeb7fa1 120 * However, normally you don't need to bother, becuase moodle_page (that is, $PAGE)
121 * will create one for you, accessible as $PAGE->theme.
571fa828 122 *
123 * @copyright 2009 Tim Hunt
124 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
125 * @since Moodle 2.0
126 */
ebebf55c 127class theme_config {
128 /**
fdeb7fa1 129 * @var string the name of this theme. Set automatically when this theme is
130 * loaded. Please do not try to set this in your theme's config.php file.
ebebf55c 131 */
fdeb7fa1 132 public $name;
571fa828 133
fdeb7fa1 134 /**
135 * @var string the folder where this themes files are stored. This is set
136 * automatically when the theme is loaded to $CFG->themedir . '/' . $this->name.
137 * Please do not try to set this in your theme's config.php file.
138 */
139 public $dir;
8954245a 140
fdeb7fa1 141 /**
142 * @var array The names of all the stylesheets from this theme that you would
143 * like included, in order. Give the names of the files without .css.
144 */
145 public $sheets = array('styles_layout', 'styles_fonts', 'styles_color');
b7009474 146
fdeb7fa1 147 /**
148 * You can base your theme on another theme by linking to the other theme as
149 * a parent. This lets you use the CSS from the other theme
d4a03c00 150 * (see {@link $parentsheets}), or layout templates (see {@link $layouts}).
fdeb7fa1 151 * That makes it easy to create a new theme that is similar to another one
152 * but with a few changes. In this theme's CSS you only need to override
153 * those rules you want to change.
154 */
155 public $parent = null;
8954245a 156
fdeb7fa1 157 /**
158 * @var boolean|array Whether and which stylesheets from the parent theme
159 * to use in this theme. (Ignored if parent is null)
160 *
161 * Possbile values are:
162 * false - no parent theme CSS.
163 * true - include all the normal parent theme CSS. Currently this means
164 * array('styles_layout', 'styles_fonts', 'styles_color').
165 * array - include just the listed stylesheets. Give the files names
166 * without the .css, as in the above example.
167 */
168 public $parentsheets = false;
571fa828 169
fdeb7fa1 170 /**
171 * @var boolean|array Whether and which stylesheets from the standard theme
172 * to use in this theme.
173 *
174 * The advantages of using the standard stylesheets in your theme is that
175 * they give you a good basic layout, and when the Moodle core code is
176 * updated with new features, the standard theme CSS will be updated to match
177 * the changes in the code. Therefore, your theme is less likely to break
178 * when you upgrade Moodle.
179 *
180 * Possbile values are:
181 * false - no standard theme CSS.
182 * true - include all the main standard theme CSS. Currently this means
183 * array('styles_layout', 'styles_fonts', 'styles_color').
184 * array - include just the listed stylesheets. Give the files names
185 * without the .css, as in the above example.
186 */
187 public $standardsheets = true;
571fa828 188
fdeb7fa1 189 /**
190 * @var array use the CSS fragments from these types of plugins.
191 *
192 * All the plugins of the given types will be searched for a file called
193 * styles.php and, if found, these will be included with the CSS for this theme.
194 *
195 * This allows modules to provide some basic CSS so they work out of the box.
196 * You are strongly advised to leave this enabled, otherwise you will have to
197 * provide styling in your theme for every installed block, activity, course
198 * format, ... in your Moodle site.
199 *
200 * This setting is an array of plugin types, as in the {@link get_plugin_types()}
201 * function. The default value has been chosen to be the same as Moodle 1.9.
202 * This is not necessarily the best choice.
203 *
204 * The plugin CSS is included first, before any theme CSS. To be precise,
205 * if $standardsheets is true, the the plugin CSS is included with the
206 * standard theme's CSS, otherwise if $parentsheets is true, the plugin CSS
207 * will be included with the parent theme's CSS, otherwise the plugin CSS
208 * will be include with this theme's CSS.
209 */
210 public $pluginsheets = array('mod', 'block', 'format', 'gradereport');
571fa828 211
fdeb7fa1 212 /**
213 * @var boolean When this is true then Moodle will try to include a file
214 * meta.php from this theme into the <head></head> part of the page.
215 */
ebebf55c 216 public $metainclude = false;
571fa828 217
fdeb7fa1 218 /**
219 * @var boolean When this is true, and when this theme has a parent, then
220 * Moodle will try to include a file meta.php from the parent theme into the
221 * <head></head> part of the page.
222 */
ebebf55c 223 public $parentmetainclude = false;
571fa828 224
fdeb7fa1 225 /**
226 * @var boolean When this is true then Moodle will try to include the file
227 * meta.php from the standard theme into the <head></head> part of the page.
228 */
229 public $standardmetainclude = true;
571fa828 230
fdeb7fa1 231 /**
232 * If true, then this theme must have a "pix" subdirectory that contains
233 * copies of all files from the moodle/pix directory, plus a "pix/mod"
234 * directory containing all the icons for all the activity modules.
235 *
236 * @var boolean
237 */
238 public $custompix = false;
571fa828 239
fdeb7fa1 240 /**
241 * Which template to use for each general type of page.
242 *
d4a03c00 243 * This is an array of arrays. The keys of the outer array are the different
244 * types of page. Pages in Moodle are categorised into one of a short list of
245 * types like 'normal', 'home', 'popup', 'form', .... The most reliable way
246 * to get a complete list is to look at
247 * {@link http://cvs.moodle.org/moodle/theme/standard/config.php?view=markup the standard theme config.php file}.
248 * That file also has a good example of how to set this setting.
fdeb7fa1 249 *
d4a03c00 250 * If Moodle encouters a general type of page that is not listed in your theme,
251 * then it will use the first layout. Therefore, should probably put 'normal'
252 * first in this array.
fdeb7fa1 253 *
d4a03c00 254 * For each page type, the value in the outer array is an array that describes
255 * how you want that type of page to look. For example
256 * <pre>
257 * $THEME->layouts = array(
258 * // Most pages. Put this first, so if we encounter an unknown page type, this is used.
259 * 'normal' => array(
260 * 'layout' => 'parent:layout.php',
261 * 'regions' => array('side-pre', 'side-post'),
262 * 'defaultregion' => 'side-post'
263 * ),
264 * // The site home page.
265 * 'home' => array(
266 * 'layout' => 'layout-home.php',
267 * 'regions' => array('side-pre', 'side-post'),
268 * 'defaultregion' => 'side-post'
269 * ),
270 * // ...
271 * );
272 * </pre>
fdeb7fa1 273 *
d4a03c00 274 * 'layout' is the layout template to use for this type of page. You can
275 * specify this in one of three ways:
fdeb7fa1 276 * <ol>
d4a03c00 277 * <li><b>filename</b> for example 'layout-home.php' as above. Use that file from this theme.</li>
278 * <li><b>parent:filename</b> for example 'parent:layout.php' as above. Use the
fdeb7fa1 279 * specified file from the parent theme. (Obviously, you can only do this
280 * if this theme has a parent!)</li>
281 * <li><b>standard:filename</b> for example 'standard:layout-popup.php'. Use
282 * the specified file from the standard theme.</li>
283 * </ol>
d4a03c00 284 * To promote conisitency, you are encouraged to call your layout files
285 * layout.php or layout-something.php.
fdeb7fa1 286 *
d4a03c00 287 * 'regions' This lists the regions on the page where blocks may appear. For
288 * each region you list here, your layout file must include a call to
289 * <pre>
290 * echo $OUTPUT->blocks_for_region($regionname);
291 * </pre>
292 * or equivalent so that the blocks are actually visible.
fdeb7fa1 293 *
d4a03c00 294 * 'defaultregion' If the list of regions is non-empty, then you must pick
295 * one of the one of them as 'default'. This has two meanings. First, this is
296 * where new blocks are added. Second, if there are any blocks associated with
297 * the page, but in non-existant regions, they appear here. (Imaging, for example,
298 * that someone added blocks using a different theme that used different region
299 * names, and then switched to this theme.)
fdeb7fa1 300 *
301 * @var array
302 */
d4a03c00 303 public $layouts = array();
571fa828 304
fdeb7fa1 305 /**
306 * With this you can control the colours of the big MP3 player
307 * that is used for MP3 resources.
308 *
309 * @var string
310 */
ebebf55c 311 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 312
fdeb7fa1 313 /**
314 * With this you can control the colours of the small MP3 player
315 * that is used elsewhere
316 *.
317 * @var string
318 */
ebebf55c 319 public $filter_mediaplugin_colors = 'bgColour=000000&btnColour=ffffff&btnBorderColour=cccccc&iconColour=000000&iconOverColour=00cc00&trackColour=cccccc&handleColour=ffffff&loaderColour=ffffff&waitForPlay=yes';
320
fdeb7fa1 321 /**
322 *$THEME->rarrow = '&#x25BA;' //OR '&rarr;';
323 *$THEME->larrow = '&#x25C4;' //OR '&larr;';
324 *$CFG->block_search_button = link_arrow_right(get_string('search'), $url='', $accesshide=true);
325 *
326 * Accessibility: Right and left arrow-like characters are
327 * used in the breadcrumb trail, course navigation menu
328 * (previous/next activity), calendar, and search forum block.
329 *
330 * If the theme does not set characters, appropriate defaults
331 * are set by (lib/weblib.php:check_theme_arrows). The suggestions
332 * above are 'silent' in a screen-reader like JAWS. Please DO NOT
333 * use &lt; &gt; &raquo; - these are confusing for blind users.
334 */
ebebf55c 335
fdeb7fa1 336 /**
337 * Name of the renderer factory class to use.
338 *
339 * This is an advanced feature. Moodle output is generated by 'renderers',
340 * you can customise the HTML that is output by writing custom renderers,
341 * and then you need to specify 'renderer factory' so that Moodle can find
342 * your renderers.
343 *
344 * There are some renderer factories supplied with Moodle. Please follow these
345 * links to see what they do.
346 * <ul>
347 * <li>{@link standard_renderer_factory} - the default.</li>
348 * <li>{@link theme_overridden_renderer_factory} - use this if you want to write
349 * your own custom renderers in a renderers.php file in this theme (or the parent theme).</li>
350 * <li>{@link template_renderer_factory} - highly experimental! Do not use (yet).</li>
351 * </ul>
352 *
353 * @var string name of a class implementing the {@link renderer_factory} interface.
354 */
ebebf55c 355 public $rendererfactory = 'standard_renderer_factory';
ebebf55c 356
fdeb7fa1 357 /**
358 * Name of the icon finder class to use.
359 *
360 * This is an advanced feature. controls how Moodle converts from the icon
361 * names used in the code to URLs to embed in the HTML. You should not ever
362 * need to change this.
363 *
364 * @var string name of a class implementing the {@link icon_finder} interface.
365 */
ebebf55c 366 public $iconfinder = 'pix_icon_finder';
571fa828 367
368 /**
fdeb7fa1 369 * Function to do custom CSS processing.
370 *
371 * This is an advanced feature. If you want to do custom processing on the
372 * CSS before it is output (for example, to replace certain variable names
373 * with particular values) you can give the name of a function here.
ebebf55c 374 *
375 * There are two functions avaiable that you may wish to use (defined in lib/outputlib.php):
fdeb7fa1 376 * <ul>
377 * <li>{@link output_css_replacing_constants}</li>
378 * <li>{@link output_css_for_css_edit}</li>
379 * </ul>
380 *
381 * If you wish to write your own function, look at those two as examples,
382 * and it should be clear what you have to do.
ebebf55c 383 *
384 * @var string the name of a function.
571fa828 385 */
ebebf55c 386 public $customcssoutputfunction = null;
571fa828 387
fdeb7fa1 388 /**
389 * You can use this to control the cutoff point for strings
390 * in the navmenus (list of activities in popup menu etc)
391 * Default is 50 characters wide.
392 */
393 public $navmenuwidth = 50;
394
395 /**
396 * By setting this to true, then you will have access to a
397 * new variable in your header.html and footer.html called
398 * $navmenulist ... this contains a simple XHTML menu of
399 * all activities in the current course, mostly useful for
400 * creating popup navigation menus and so on.
401 */
402 public $makenavmenulist = false;
403
404 /**
405 * @var renderer_factory Instance of the renderer_factory implementation
406 * we are using. Implementation detail.
407 */
408 protected $rf = null;
409
410 /**
411 * @var renderer_factory Instance of the icon_finder implementation we are
412 * using. Implementation detail.
413 */
414 protected $if = null;
415
571fa828 416 /**
ebebf55c 417 * Load the config.php file for a particular theme, and return an instance
418 * of this class. (That is, this is a factory method.)
419 *
420 * @param string $themename the name of the theme.
421 * @return theme_config an instance of this class.
571fa828 422 */
ebebf55c 423 public static function load($themename) {
424 global $CFG;
571fa828 425
ebebf55c 426 // We have to use the variable name $THEME (upper case) becuase that
427 // is what is used in theme config.php files.
428
429 // Set some other standard properties of the theme.
430 $THEME = new theme_config;
431 $THEME->name = $themename;
432 $THEME->dir = $CFG->themedir . '/' . $themename;
433
434 // Load up the theme config
435 $configfile = $THEME->dir . '/config.php';
436 if (!is_readable($configfile)) {
437 throw new coding_exception('Cannot use theme ' . $themename .
438 '. The file ' . $configfile . ' does not exist or is not readable.');
571fa828 439 }
ebebf55c 440 include($configfile);
441
442 $THEME->update_legacy_information();
443
444 return $THEME;
571fa828 445 }
446
34a2777c 447 /**
ebebf55c 448 * Get the renderer for a part of Moodle for this theme.
449 * @param string $module the name of part of moodle. E.g. 'core', 'quiz', 'qtype_multichoice'.
450 * @param moodle_page $page the page we are rendering
451 * @return moodle_renderer_base the requested renderer.
897b5c82 452 * @param string $subtype optional subtype such as 'news' resulting to 'mod_forum_news'
34a2777c 453 */
897b5c82 454 public function get_renderer($module, $page, $subtype=null) {
ebebf55c 455 if (is_null($this->rf)) {
456 if (CLI_SCRIPT) {
457 $classname = 'cli_renderer_factory';
458 } else {
459 $classname = $this->rendererfactory;
460 }
461 $this->rf = new $classname($this);
462 }
463
897b5c82 464 return $this->rf->get_renderer($module, $page, $subtype);
34a2777c 465 }
466
571fa828 467 /**
ebebf55c 468 * Get the renderer for a part of Moodle for this theme.
469 * @return moodle_renderer_base the requested renderer.
571fa828 470 */
ebebf55c 471 protected function get_icon_finder() {
472 if (is_null($this->if)) {
473 $classname = $this->iconfinder;
474 $this->if = new $classname($this);
571fa828 475 }
ebebf55c 476 return $this->if;
571fa828 477 }
478
479 /**
ebebf55c 480 * Return the URL for an icon indentifed as in pre-Moodle 2.0 code.
481 *
482 * Suppose you have old code like $url = "$CFG->pixpath/i/course.gif";
483 * then old_icon_url('i/course'); will return the equivalent URL that is correct now.
484 *
485 * @param $iconname the name of the icon.
486 * @return string the URL for that icon.
571fa828 487 */
ebebf55c 488 public function old_icon_url($iconname) {
4096752d 489 return $this->get_icon_finder()->old_icon_url($iconname);
571fa828 490 }
491
492 /**
ebebf55c 493 * Return the URL for an icon indentifed as in pre-Moodle 2.0 code.
494 *
495 * Suppose you have old code like $url = "$CFG->modpixpath/$mod/icon.gif";
496 * then mod_icon_url('icon', $mod); will return the equivalent URL that is correct now.
497 *
498 * @param $iconname the name of the icon.
499 * @param $module the module the icon belongs to.
500 * @return string the URL for that icon.
571fa828 501 */
ebebf55c 502 public function mod_icon_url($iconname, $module) {
4096752d 503 return $this->get_icon_finder()->mod_icon_url($iconname, $module);
571fa828 504 }
34a2777c 505
ebebf55c 506 /**
507 * Get the list of stylesheet URLs that need to go in the header for this theme.
508 * @return array of URLs.
509 */
510 public function get_stylesheet_urls() {
34a2777c 511 global $CFG;
512
fdeb7fa1 513 // We need to tell the CSS that is being included (for example the standard
514 // theme CSS) which theme it is being included for. Prepare the necessary param.
515 $param = '?for=' . $this->name;
34a2777c 516
ebebf55c 517 // Stylesheets, in order (standard, parent, this - some of which may be the same).
518 $stylesheets = array();
519 if ($this->name != 'standard' && $this->standardsheets) {
fdeb7fa1 520 $stylesheets[] = $CFG->httpsthemewww . '/standard/styles.php' . $param;
ebebf55c 521 }
522 if (!empty($this->parent)) {
fdeb7fa1 523 $stylesheets[] = $CFG->httpsthemewww . '/' . $this->parent . '/styles.php' . $param;
34a2777c 524 }
fdeb7fa1 525 $stylesheets[] = $CFG->httpsthemewww . '/' . $this->name . '/styles.php' . $param;
34a2777c 526
fdeb7fa1 527 // Additional styles for right-to-left languages, if applicable.
ebebf55c 528 if (right_to_left()) {
529 $stylesheets[] = $CFG->httpsthemewww . '/standard/rtl.css';
34a2777c 530
ebebf55c 531 if (!empty($this->parent) && file_exists($CFG->themedir . '/' . $this->parent . '/rtl.css')) {
532 $stylesheets[] = $CFG->httpsthemewww . '/' . $this->parent . '/rtl.css';
533 }
34a2777c 534
ebebf55c 535 if (file_exists($this->dir . '/rtl.css')) {
536 $stylesheets[] = $CFG->httpsthemewww . '/' . $this->name . '/rtl.css';
537 }
34a2777c 538 }
17a6649b 539
fdeb7fa1 540 // If the theme wants pluginsheets, get them included in the first (most
541 // general) stylesheet we are including. That is, process them with the
542 // standard CSS if we are using that, else with the parent CSS, else with
543 // our own CSS.
544 if (!empty($this->pluginsheets)) {
545 $stylesheets[0] .= '&amp;pluginsheets=1';
546 }
547
ebebf55c 548 return $stylesheets;
549 }
17a6649b 550
ebebf55c 551 /**
fdeb7fa1 552 * Get the meta tags from one theme to got in the <head> of the HTML.
553 * @param $themename the name of the theme to get meta tags from.
554 * @param $page that page whose <head> is being output.
555 * @return string HTML code.
556 */
557 protected function get_theme_meta_tags($themename, $page) {
558 global $CFG;
559 // At least one theme's meta.php expects to have $PAGE visible.
560 $PAGE = $page;
561 $filename = $CFG->themedir . '/' . $themename . '/meta.php';
562 if (file_exists($filename)) {
563 ob_start();
564 include_once($filename);
f77fcb5a 565 $metatags = ob_get_contents();
fdeb7fa1 566 ob_end_clean();
567 }
568 return $metatags;
569 }
570
571 /**
572 * Get all the meta tags (from this theme, standard, parent) that this theme
573 * wants in the <head> of the HTML.
574 *
575 * @param $page that page whose <head> is being output.
576 * @return string HTML code.
577 */
578 public function get_meta_tags($page) {
579 $metatags = '';
580 if ($this->standardmetainclude) {
581 $metatags .= $this->get_theme_meta_tags('standard', $page);
582 }
583 if ($this->parent && $this->parentmetainclude) {
584 $metatags .= $this->get_theme_meta_tags($this->parent, $page);
585 }
586 if ($this->metainclude) {
587 $metatags .= $this->get_theme_meta_tags($this->name, $page);
588 }
589 return $metatags;
590 }
591
d4a03c00 592 /**
593 * Get the information from {@link $layouts} for this type of page.
594 * @param string $generaltype the general type of the page.
595 * @return array the appropriate part of {@link $layouts}.
596 */
597 protected function layout_info_for_page($generaltype) {
598 if (array_key_exists($generaltype, $this->layouts)) {
599 return $this->layouts[$generaltype];
600 } else {
601 return reset($this->layouts);
602 }
603 }
604
fdeb7fa1 605 /**
606 * Given the settings of this theme, and the page generaltype, return the
607 * full path of the page layout template to use.
608 *
609 * Used by {@link moodle_core_renderer::header()}. If an appropriate new-style
610 * template cannot be found, returns false to signal that the old-style
611 * header.html and footer.html files should be used.
612 *
d4a03c00 613 * @param string $generaltype the general type of the page.
fdeb7fa1 614 * @return string Full path to the template to use, or false if a new-style
615 * template cannot be found.
616 */
d4a03c00 617 public function template_for_page($generaltype) {
fdeb7fa1 618 global $CFG;
619
620 // Legacy fallback.
d4a03c00 621 if (empty($this->layouts)) {
fdeb7fa1 622 return false;
623 }
624
d4a03c00 625 $layoutinfo = $this->layout_info_for_page($generaltype);
626 $templatefile = $layoutinfo['layout'];
fdeb7fa1 627
628 // Parse the name that was found.
d4a03c00 629 if (strpos($templatefile, 'standard:') === 0) {
fdeb7fa1 630 $templatepath = $CFG->themedir . '/standard/' . substr($templatefile, 9);
d4a03c00 631 } else if (strpos($templatefile, 'parent:') === 0) {
fdeb7fa1 632 if (empty($this->parent)) {
633 throw new coding_exception('This theme (' . $this->name .
634 ') does not have a parent. You cannot specify a layout template like ' .
635 $templatefile);
636 }
637 $templatepath = $CFG->themedir . '/' . $this->parent . '/' . substr($templatefile, 7);
638 } else {
639 $templatepath = $this->dir . '/' . $templatefile;
640 }
641
642 // Check the the template exists.
643 if (!is_readable($templatepath)) {
644 throw new coding_exception('The template ' . $templatefile . ' (' . $templatepath .
645 ') for page type ' . $generaltype . ' cannot be found in this theme (' .
646 $this->name . ')');
647 }
648
649 return $templatepath;
650 }
651
d4a03c00 652 /**
653 * Inform a block_manager about the block regions this theme wants on this
654 * type of page.
655 * @param string $generaltype the general type of the page.
656 * @param block_manager $blockmanager the block_manger to set up.
657 */
658 public function setup_blocks($generaltype, $blockmanager) {
659 // Legacy fallback.
660 if (empty($this->layouts)) {
661 if (!in_array($generaltype, array('form', 'popup', 'maintenance'))) {
662 $blockmanager->add_regions(array(BLOCK_POS_LEFT, BLOCK_POS_RIGHT));
663 $blockmanager->set_default_region(BLOCK_POS_RIGHT);
664 }
665 return;
666 }
667
668 $layoutinfo = $this->layout_info_for_page($generaltype);
669 if (!empty($layoutinfo['regions'])) {
670 $blockmanager->add_regions($layoutinfo['regions']);
671 $blockmanager->set_default_region($layoutinfo['defaultregion']);
672 }
673 }
674
fdeb7fa1 675 /**
676 * Helper method used by {@link update_legacy_information()}. Update one entry
677 * in the $this->pluginsheets array, based on the legacy $property propery.
678 * @param $plugintype e.g. 'mod'.
679 * @param $property e.g. 'modsheets'.
680 */
681 protected function update_legacy_plugin_sheets($plugintype, $property) {
d4a03c00 682 // In Moodle 1.9, modsheets etc. were ignored if standardsheets was false.
683 if (!empty($this->standardsheets) && property_exists($this, $property)) {
fdeb7fa1 684 debugging('$THEME->' . $property . ' is deprecated. Please use the new $THEME->pluginsheets instead.', DEBUG_DEVELOPER);
685 if (!empty($this->$property) && !in_array($plugintype, $this->pluginsheets)) {
686 $this->pluginsheets[] = $plugintype;
687 } else if (empty($this->$property) && in_array($plugintype, $this->pluginsheets)) {
688 unset($this->pluginsheets[array_search($plugintype, $this->pluginsheets)]);
689 }
690 }
691 }
692
693 /**
694 * This method looks a the settings that have been loaded, to see whether
ebebf55c 695 * any legacy things are being used, and outputs warning and tries to update
696 * things to use equivalent newer settings.
697 */
698 protected function update_legacy_information() {
699 global $CFG;
fdeb7fa1 700
701 $this->update_legacy_plugin_sheets('mod', 'modsheets');
702 $this->update_legacy_plugin_sheets('block', 'blocksheets');
703 $this->update_legacy_plugin_sheets('format', 'formatsheets');
704 $this->update_legacy_plugin_sheets('gradereport', 'gradereportsheets');
705
fdeb7fa1 706 if (!empty($this->langsheets)) {
707 debugging('$THEME->langsheets is no longer supported. No languages were ' .
708 'using it for anything, and it did not seem to serve any purpose.', DEBUG_DEVELOPER);
709 }
710
ebebf55c 711 if (!empty($this->customcorners)) {
712 // $THEME->customcorners is deprecated but we provide support for it via the
713 // custom_corners_renderer_factory class in lib/deprecatedlib.php
714 debugging('$THEME->customcorners is deprecated. Please use the new $THEME->rendererfactory ' .
715 'to control HTML generation. Please use $this->rendererfactory = \'custom_corners_renderer_factory\'; ' .
716 'in your config.php file instead.', DEBUG_DEVELOPER);
717 $this->rendererfactory = 'custom_corners_renderer_factory';
34a2777c 718 }
719
ebebf55c 720 if (!empty($this->cssconstants)) {
721 debugging('$THEME->cssconstants is deprecated. Please use ' .
722 '$THEME->customcssoutputfunction = \'output_css_replacing_constants\'; ' .
723 'in your config.php file instead.', DEBUG_DEVELOPER);
724 $this->customcssoutputfunction = 'output_css_replacing_constants';
34a2777c 725 }
ebebf55c 726
727 if (!empty($this->CSSEdit)) {
728 debugging('$THEME->CSSEdit is deprecated. Please use ' .
729 '$THEME->customcssoutputfunction = \'output_css_for_css_edit\'; ' .
730 'in your config.php file instead.', DEBUG_DEVELOPER);
731 $this->customcssoutputfunction = 'output_css_for_css_edit';
34a2777c 732 }
733
ae96b517 734 if (!empty($CFG->smartpix)) {
ebebf55c 735 $this->iconfinder = 'smartpix_icon_finder';
736 } else if ($this->custompix) {
737 $this->iconfinder = 'theme_icon_finder';
738 }
34a2777c 739 }
740
ebebf55c 741 /**
742 * Set the variable $CFG->pixpath and $CFG->modpixpath to be the right
d436d197 743 * ones for this theme. These should no longer be used, but legacy code
744 * might still rely on them.
ebebf55c 745 */
d436d197 746 public function setup_legacy_pix_paths() {
ebebf55c 747 global $CFG;
748 if (!empty($CFG->smartpix)) {
749 if ($CFG->slasharguments) {
750 // Use this method if possible for better caching
751 $extra = '';
752 } else {
753 $extra = '?file=';
754 }
755 $CFG->pixpath = $CFG->httpswwwroot . '/pix/smartpix.php' . $extra . '/' . $this->name;
756 $CFG->modpixpath = $CFG->httpswwwroot . '/pix/smartpix.php' . $extra . '/' . $this->name . '/mod';
34a2777c 757
ebebf55c 758 } else if (empty($THEME->custompix)) {
759 $CFG->pixpath = $CFG->httpswwwroot . '/pix';
760 $CFG->modpixpath = $CFG->httpswwwroot . '/mod';
761
762 } else {
763 $CFG->pixpath = $CFG->httpsthemewww . '/' . $this->name . '/pix';
764 $CFG->modpixpath = $CFG->httpsthemewww . '/' . $this->name . '/pix/mod';
34a2777c 765 }
34a2777c 766 }
ebebf55c 767}
34a2777c 768
ebebf55c 769
770/**
771 * This icon finder implements the old scheme that was used when themes that had
772 * $THEME->custompix = false.
773 *
774 * @copyright 2009 Tim Hunt
775 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
776 * @since Moodle 2.0
777 */
778class pix_icon_finder implements icon_finder {
779 /**
780 * Constructor
781 * @param theme_config $theme the theme we are finding icons for (which is irrelevant).
782 */
783 public function __construct($theme) {
34a2777c 784 }
785
ebebf55c 786 /* Implement interface method. */
787 public function old_icon_url($iconname) {
788 global $CFG;
3aaa27f4 789 if (file_exists($CFG->dirroot . '/pix/' . $iconname . '.png')) {
790 return $CFG->httpswwwroot . '/pix/' . $iconname . '.png';
791 } else {
792 return $CFG->httpswwwroot . '/pix/' . $iconname . '.gif';
793 }
34a2777c 794 }
795
ebebf55c 796 /* Implement interface method. */
797 public function mod_icon_url($iconname, $module) {
798 global $CFG;
3aaa27f4 799 if (file_exists($CFG->dirroot . '/mod/' . $module . '/' . $iconname . '.png')) {
800 return $CFG->httpswwwroot . '/mod/' . $module . '/' . $iconname . '.png';
801 } else {
802 return $CFG->httpswwwroot . '/mod/' . $module . '/' . $iconname . '.gif';
803 }
ebebf55c 804 }
805}
34a2777c 806
34a2777c 807
ebebf55c 808/**
809 * This icon finder implements the old scheme that was used for themes that had
810 * $THEME->custompix = true.
811 *
812 * @copyright 2009 Tim Hunt
813 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
814 * @since Moodle 2.0
815 */
816class theme_icon_finder implements icon_finder {
817 protected $themename;
818 /**
819 * Constructor
820 * @param theme_config $theme the theme we are finding icons for.
821 */
822 public function __construct($theme) {
823 $this->themename = $theme->name;
824 }
34a2777c 825
ebebf55c 826 /* Implement interface method. */
827 public function old_icon_url($iconname) {
828 global $CFG;
3aaa27f4 829 if (file_exists($CFG->themedir . '/' . $this->themename . '/pix/' . $iconname . '.png')) {
830 return $CFG->httpsthemewww . '/' . $this->themename . '/pix/' . $iconname . '.png';
831 } else {
832 return $CFG->httpsthemewww . '/' . $this->themename . '/pix/' . $iconname . '.gif';
833 }
ebebf55c 834 }
34a2777c 835
ebebf55c 836 /* Implement interface method. */
837 public function mod_icon_url($iconname, $module) {
838 global $CFG;
3aaa27f4 839 if (file_exists($CFG->themedir . '/' . $this->themename . '/pix/mod/' . $module . '/' . $iconname . '.png')) {
840 return $CFG->httpsthemewww . '/' . $this->themename . '/pix/mod/' . $module . '/' . $iconname . '.png';
841 } else {
842 return $CFG->httpsthemewww . '/' . $this->themename . '/pix/mod/' . $module . '/' . $iconname . '.gif';
843 }
34a2777c 844 }
ebebf55c 845}
846
847
848/**
849 * This icon finder implements the algorithm in pix/smartpix.php.
850 *
851 * @copyright 2009 Tim Hunt
852 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
853 * @since Moodle 2.0
854 */
855class smartpix_icon_finder extends pix_icon_finder {
856 protected $places = array();
34a2777c 857
afa2dcad 858 /**
ebebf55c 859 * Constructor
860 * @param theme_config $theme the theme we are finding icons for.
afa2dcad 861 */
ebebf55c 862 public function __construct($theme) {
e8775320 863 global $CFG;
ebebf55c 864 $this->places[$CFG->themedir . '/' . $theme->name . '/pix/'] =
865 $CFG->httpsthemewww . '/' . $theme->name . '/pix/';
866 if (!empty($theme->parent)) {
867 $this->places[$CFG->themedir . '/' . $theme->parent . '/pix/'] =
868 $CFG->httpsthemewww . '/' . $theme->parent . '/pix/';
869 }
870 }
e8775320 871
ebebf55c 872 /* Implement interface method. */
873 public function old_icon_url($iconname) {
874 foreach ($this->places as $dirroot => $urlroot) {
3aaa27f4 875 if (file_exists($dirroot . $iconname . '.png')) {
876 return $dirroot . $iconname . '.png';
877 } else if (file_exists($dirroot . $iconname . '.gif')) {
878 return $dirroot . $iconname . '.gif';
e8775320 879 }
880 }
ebebf55c 881 return parent::old_icon_url($iconname);
882 }
e8775320 883
ebebf55c 884 /* Implement interface method. */
885 public function mod_icon_url($iconname, $module) {
886 foreach ($this->places as $dirroot => $urlroot) {
3aaa27f4 887 if (file_exists($dirroot . 'mod/' . $iconname . '.png')) {
888 return $dirroot . 'mod/' . $iconname . '.png';
889 } else if (file_exists($dirroot . 'mod/' . $iconname . '.gif')) {
890 return $dirroot . 'mod/' . $iconname . '.gif';
ebebf55c 891 }
e8775320 892 }
3aaa27f4 893 return parent::old_icon_url($iconname, $module);
e8775320 894 }
ebebf55c 895}
e8775320 896
34a2777c 897
ebebf55c 898/**
899 * This is a base class to help you implement the renderer_factory interface.
900 *
901 * It keeps a cache of renderers that have been constructed, so you only need
902 * to construct each one once in you subclass.
903 *
904 * It also has a method to get the name of, and include the renderer.php with
905 * the definition of, the standard renderer class for a given module.
906 *
907 * @copyright 2009 Tim Hunt
908 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
909 * @since Moodle 2.0
910 */
911abstract class renderer_factory_base implements renderer_factory {
912 /** @var theme_config the theme we belong to. */
913 protected $theme;
34a2777c 914
ebebf55c 915 /**
916 * Constructor.
917 * @param theme_config $theme the theme we belong to.
918 */
919 public function __construct($theme) {
920 $this->theme = $theme;
921 }
922 /**
923 * For a given module name, return the name of the standard renderer class
924 * that defines the renderer interface for that module.
925 *
926 * Also, if it exists, include the renderer.php file for that module, so
927 * the class definition of the default renderer has been loaded.
928 *
929 * @param string $component name such as 'core', 'mod_forum' or 'qtype_multichoice'.
897b5c82 930 * @param string $subtype optional subtype such as 'news' resulting to 'mod_forum_news'
ebebf55c 931 * @return string the name of the standard renderer class for that module.
932 */
897b5c82 933 protected function standard_renderer_class_for_module($component, $subtype=null) {
ebebf55c 934 if ($component != 'core') {
935 $pluginrenderer = get_component_directory($component) . '/renderer.php';
936 if (file_exists($pluginrenderer)) {
937 include_once($pluginrenderer);
938 }
34a2777c 939 }
897b5c82 940 if (is_null($subtype)) {
941 $class = 'moodle_' . $component . '_renderer';
942 } else {
943 $class = 'moodle_' . $component . '_' . $subtype . '_renderer';
944 }
ebebf55c 945 if (!class_exists($class)) {
946 throw new coding_exception('Request for an unknown renderer class ' . $class);
34a2777c 947 }
ebebf55c 948 return $class;
34a2777c 949 }
ebebf55c 950}
34a2777c 951
34a2777c 952
ebebf55c 953/**
954 * This is the default renderer factory for Moodle. It simply returns an instance
955 * of the appropriate standard renderer class.
956 *
957 * @copyright 2009 Tim Hunt
958 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
959 * @since Moodle 2.0
960 */
961class standard_renderer_factory extends renderer_factory_base {
962 /* Implement the subclass method. */
897b5c82 963 public function get_renderer($module, $page, $subtype=null) {
ebebf55c 964 if ($module == 'core') {
965 return new moodle_core_renderer($page);
966 } else {
897b5c82 967 $class = $this->standard_renderer_class_for_module($module, $subtype);
ebebf55c 968 return new $class($page, $this->get_renderer('core', $page));
34a2777c 969 }
ebebf55c 970 }
971}
34a2777c 972
34a2777c 973
ebebf55c 974/**
975 * This is a slight variation on the standard_renderer_factory used by CLI scripts.
976 *
977 * @copyright 2009 Tim Hunt
978 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
979 * @since Moodle 2.0
980 */
981class cli_renderer_factory extends standard_renderer_factory {
982 /* Implement the subclass method. */
897b5c82 983 public function get_renderer($module, $page, $subtype=null) {
ebebf55c 984 if ($module == 'core') {
985 return new cli_core_renderer($page);
986 } else {
897b5c82 987 parent::get_renderer($module, $page, $subtype);
ebebf55c 988 }
34a2777c 989 }
ebebf55c 990}
34a2777c 991
34a2777c 992
ebebf55c 993/**
994 * This is renderer factory allows themes to override the standard renderers using
995 * php code.
996 *
997 * It will load any code from theme/mytheme/renderers.php and
998 * theme/parenttheme/renderers.php, if then exist. Then whenever you ask for
999 * a renderer for 'component', it will create a mytheme_component_renderer or a
1000 * parenttheme_component_renderer, instead of a moodle_component_renderer,
1001 * if either of those classes exist.
1002 *
1003 * This generates the slightly different HTML that the custom_corners theme expects.
1004 *
1005 * @copyright 2009 Tim Hunt
1006 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1007 * @since Moodle 2.0
1008 */
1009class theme_overridden_renderer_factory extends standard_renderer_factory {
1010 protected $prefixes = array();
1011
1012 /**
1013 * Constructor.
1014 * @param object $theme the theme we are rendering for.
1015 * @param moodle_page $page the page we are doing output for.
1016 */
1017 public function __construct($theme) {
1018 global $CFG;
1019 parent::__construct($theme);
1020
1021 // Initialise $this->prefixes.
1022 $renderersfile = $theme->dir . '/renderers.php';
1023 if (is_readable($renderersfile)) {
1024 include_once($renderersfile);
1025 $this->prefixes[] = $theme->name . '_';
1026 }
1027 if (!empty($theme->parent)) {
1028 $renderersfile = $CFG->themedir .'/'. $theme->parent . '/renderers.php';
1029 if (is_readable($renderersfile)) {
1030 include_once($renderersfile);
1031 $this->prefixes[] = $theme->parent . '_';
1032 }
1033 }
34a2777c 1034 }
1035
d4a03c00 1036 /* Implement the interface method. */
897b5c82 1037 public function get_renderer($module, $page, $subtype=null) {
ebebf55c 1038 foreach ($this->prefixes as $prefix) {
897b5c82 1039 if (is_null($subtype)) {
1040 $classname = $prefix . $module . '_renderer';
1041 } else {
1042 $classname = $prefix . $module . '_' . $subtype . '_renderer';
1043 }
ebebf55c 1044 if (class_exists($classname)) {
1045 if ($module == 'core') {
1046 return new $classname($page);
1047 } else {
1048 return new $classname($page, $this->get_renderer('core', $page));
1049 }
1050 }
1051 }
897b5c82 1052 return parent::get_renderer($module, $page, $subtype);
ebebf55c 1053 }
1054}
34a2777c 1055
34a2777c 1056
ebebf55c 1057/**
1058 * This is renderer factory that allows you to create templated themes.
1059 *
1060 * This should be considered an experimental proof of concept. In particular,
1061 * the performance is probably not very good. Do not try to use in on a busy site
1062 * without doing careful load testing first!
1063 *
1064 * This renderer factory returns instances of {@link template_renderer} class
1065 * which which implement the corresponding renderer interface in terms of
1066 * templates. To use this your theme must have a templates folder inside it.
1067 * Then suppose the method moodle_core_renderer::greeting($name = 'world');
1068 * exists. Then, a call to $OUTPUT->greeting() will cause the template
1069 * /theme/yourtheme/templates/core/greeting.php to be rendered, with the variable
1070 * $name available. The greeting.php template might contain
1071 *
1072 * <pre>
1073 * <h1>Hello <?php echo $name ?>!</h1>
1074 * </pre>
1075 *
1076 * @copyright 2009 Tim Hunt
1077 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1078 * @since Moodle 2.0
1079 */
1080class template_renderer_factory extends renderer_factory_base {
1081 /**
1082 * An array of paths of where to search for templates. Normally this theme,
1083 * the parent theme then the standardtemplate theme. (If some of these do
1084 * not exist, or are the same as each other, then the list will be shorter.
1085 */
1086 protected $searchpaths = array();
34a2777c 1087
ebebf55c 1088 /**
1089 * Constructor.
1090 * @param object $theme the theme we are rendering for.
1091 * @param moodle_page $page the page we are doing output for.
1092 */
1093 public function __construct($theme) {
1094 global $CFG;
1095 parent::__construct($theme);
34a2777c 1096
ebebf55c 1097 // Initialise $this->searchpaths.
1098 if ($theme->name != 'standardtemplate') {
1099 $templatesdir = $theme->dir . '/templates';
1100 if (is_dir($templatesdir)) {
1101 $this->searchpaths[] = $templatesdir;
1102 }
34a2777c 1103 }
ebebf55c 1104 if (!empty($theme->parent)) {
1105 $templatesdir = $CFG->themedir .'/'. $theme->parent . '/templates';
1106 if (is_dir($templatesdir)) {
1107 $this->searchpaths[] = $templatesdir;
1108 }
1109 }
1110 $this->searchpaths[] = $CFG->themedir .'/standardtemplate/templates';
1111 }
34a2777c 1112
ebebf55c 1113 /* Implement the subclass method. */
897b5c82 1114 public function get_renderer($module, $page, $subtype=null) {
ebebf55c 1115 // Refine the list of search paths for this module.
1116 $searchpaths = array();
1117 foreach ($this->searchpaths as $rootpath) {
1118 $path = $rootpath . '/' . $module;
d4a03c00 1119 if (!is_null($subtype)) {
1120 $path .= '/' . $subtype;
897b5c82 1121 }
ebebf55c 1122 if (is_dir($path)) {
1123 $searchpaths[] = $path;
1124 }
1125 }
34a2777c 1126
ebebf55c 1127 // Create a template_renderer that copies the API of the standard renderer.
897b5c82 1128 $copiedclass = $this->standard_renderer_class_for_module($module, $subtype);
ebebf55c 1129 return new template_renderer($copiedclass, $searchpaths, $page);
1130 }
1131}
34a2777c 1132
34a2777c 1133
ebebf55c 1134/**
1135 * Simple base class for Moodle renderers.
1136 *
1137 * Tracks the xhtml_container_stack to use, which is passed in in the constructor.
1138 *
1139 * Also has methods to facilitate generating HTML output.
1140 *
1141 * @copyright 2009 Tim Hunt
1142 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1143 * @since Moodle 2.0
1144 */
1145class moodle_renderer_base {
1146 /** @var xhtml_container_stack the xhtml_container_stack to use. */
1147 protected $opencontainers;
1148 /** @var moodle_page the page we are rendering for. */
1149 protected $page;
34a2777c 1150
ebebf55c 1151 /**
1152 * Constructor
1153 * @param $opencontainers the xhtml_container_stack to use.
1154 * @param moodle_page $page the page we are doing output for.
1155 */
1156 public function __construct($page) {
1157 $this->opencontainers = $page->opencontainers;
1158 $this->page = $page;
34a2777c 1159 }
1160
ebebf55c 1161 /**
1162 * Have we started output yet?
1163 * @return boolean true if the header has been printed.
1164 */
1165 public function has_started() {
1166 return $this->page->state >= moodle_page::STATE_IN_BODY;
1167 }
1168
1169 protected function output_tag($tagname, $attributes, $contents) {
1170 return $this->output_start_tag($tagname, $attributes) . $contents .
1171 $this->output_end_tag($tagname);
1172 }
1173 protected function output_start_tag($tagname, $attributes) {
1174 return '<' . $tagname . $this->output_attributes($attributes) . '>';
1175 }
1176 protected function output_end_tag($tagname) {
1177 return '</' . $tagname . '>';
1178 }
1179 protected function output_empty_tag($tagname, $attributes) {
1180 return '<' . $tagname . $this->output_attributes($attributes) . ' />';
1181 }
1182
1183 protected function output_attribute($name, $value) {
1184 $value = trim($value);
1185 if ($value || is_numeric($value)) { // We want 0 to be output.
1186 return ' ' . $name . '="' . $value . '"';
1187 }
1188 }
1189 protected function output_attributes($attributes) {
1190 if (empty($attributes)) {
1191 $attributes = array();
1192 }
34a2777c 1193 $output = '';
ebebf55c 1194 foreach ($attributes as $name => $value) {
1195 $output .= $this->output_attribute($name, $value);
34a2777c 1196 }
ebebf55c 1197 return $output;
1198 }
1199 public static function prepare_classes($classes) {
1200 if (is_array($classes)) {
1201 return implode(' ', array_unique($classes));
1202 }
1203 return $classes;
1204 }
34a2777c 1205
ebebf55c 1206 /**
1207 * Return the URL for an icon indentifed as in pre-Moodle 2.0 code.
1208 *
1209 * Suppose you have old code like $url = "$CFG->pixpath/i/course.gif";
1210 * then old_icon_url('i/course'); will return the equivalent URL that is correct now.
1211 *
1212 * @param $iconname the name of the icon.
1213 * @return string the URL for that icon.
1214 */
1215 public function old_icon_url($iconname) {
1216 return $this->page->theme->old_icon_url($iconname);
1217 }
34a2777c 1218
ebebf55c 1219 /**
1220 * Return the URL for an icon indentifed as in pre-Moodle 2.0 code.
1221 *
1222 * Suppose you have old code like $url = "$CFG->modpixpath/$mod/icon.gif";
1223 * then mod_icon_url('icon', $mod); will return the equivalent URL that is correct now.
1224 *
1225 * @param $iconname the name of the icon.
1226 * @param $module the module the icon belongs to.
1227 * @return string the URL for that icon.
1228 */
1229 public function mod_icon_url($iconname, $module) {
1230 return $this->page->theme->mod_icon_url($iconname, $module);
1231 }
1232}
34a2777c 1233
34a2777c 1234
ebebf55c 1235/**
1236 * This is the templated renderer which copies the API of another class, replacing
1237 * all methods calls with instantiation of a template.
1238 *
1239 * When the method method_name is called, this class will search for a template
1240 * called method_name.php in the folders in $searchpaths, taking the first one
1241 * that it finds. Then it will set up variables for each of the arguments of that
1242 * method, and render the template. This is implemented in the {@link __call()}
1243 * PHP magic method.
1244 *
1245 * Methods like print_box_start and print_box_end are handles specially, and
1246 * implemented in terms of the print_box.php method.
1247 *
1248 * @copyright 2009 Tim Hunt
1249 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1250 * @since Moodle 2.0
1251 */
1252class template_renderer extends moodle_renderer_base {
1253 /** @var ReflectionClass information about the class whose API we are copying. */
1254 protected $copiedclass;
1255 /** @var array of places to search for templates. */
1256 protected $searchpaths;
1257 protected $rendererfactory;
34a2777c 1258
ebebf55c 1259 /**
1260 * Magic word used when breaking apart container templates to implement
1261 * _start and _end methods.
1262 */
1263 const contentstoken = '-@#-Contents-go-here-#@-';
1264
1265 /**
1266 * Constructor
1267 * @param string $copiedclass the name of a class whose API we should be copying.
1268 * @param $searchpaths a list of folders to search for templates in.
1269 * @param $opencontainers the xhtml_container_stack to use.
1270 * @param moodle_page $page the page we are doing output for.
1271 */
1272 public function __construct($copiedclass, $searchpaths, $page) {
1273 parent::__construct($page);
1274 $this->copiedclass = new ReflectionClass($copiedclass);
1275 $this->searchpaths = $searchpaths;
1276 }
1277
1278 /* PHP magic method implementation. */
1279 public function __call($method, $arguments) {
1280 if (substr($method, -6) == '_start') {
1281 return $this->process_start(substr($method, 0, -6), $arguments);
1282 } else if (substr($method, -4) == '_end') {
1283 return $this->process_end(substr($method, 0, -4), $arguments);
1284 } else {
1285 return $this->process_template($method, $arguments);
1286 }
34a2777c 1287 }
1288
a5cb8d69 1289 /**
ebebf55c 1290 * Render the template for a given method of the renderer class we are copying,
1291 * using the arguments passed.
1292 * @param string $method the method that was called.
1293 * @param array $arguments the arguments that were passed to it.
a5cb8d69 1294 * @return string the HTML to be output.
1295 */
ebebf55c 1296 protected function process_template($method, $arguments) {
1297 if (!$this->copiedclass->hasMethod($method) ||
1298 !$this->copiedclass->getMethod($method)->isPublic()) {
1299 throw new coding_exception('Unknown method ' . $method);
a5cb8d69 1300 }
1301
ebebf55c 1302 // Find the template file for this method.
1303 $template = $this->find_template($method);
a5cb8d69 1304
ebebf55c 1305 // Use the reflection API to find out what variable names the arguments
1306 // should be stored in, and fill in any missing ones with the defaults.
1307 $namedarguments = array();
1308 $expectedparams = $this->copiedclass->getMethod($method)->getParameters();
1309 foreach ($expectedparams as $param) {
1310 $paramname = $param->getName();
1311 if (!empty($arguments)) {
1312 $namedarguments[$paramname] = array_shift($arguments);
1313 } else if ($param->isDefaultValueAvailable()) {
1314 $namedarguments[$paramname] = $param->getDefaultValue();
1315 } else {
1316 throw new coding_exception('Missing required argument ' . $paramname);
a5cb8d69 1317 }
a5cb8d69 1318 }
1319
ebebf55c 1320 // Actually render the template.
1321 return $this->render_template($template, $namedarguments);
1322 }
a5cb8d69 1323
ebebf55c 1324 /**
1325 * Actually do the work of rendering the template.
1326 * @param $_template the full path to the template file.
1327 * @param $_namedarguments an array variable name => value, the variables
1328 * that should be available to the template.
1329 * @return string the HTML to be output.
1330 */
1331 protected function render_template($_template, $_namedarguments) {
1332 // Note, we intentionally break the coding guidelines with regards to
1333 // local variable names used in this function, so that they do not clash
1334 // with the names of any variables being passed to the template.
a5cb8d69 1335
ebebf55c 1336 global $CFG, $SITE, $THEME, $USER;
1337 // The next lines are a bit tricky. The point is, here we are in a method
1338 // of a renderer class, and this object may, or may not, be the the same as
1339 // the global $OUTPUT object. When rendering the template, we want to use
1340 // this object. However, people writing Moodle code expect the current
1341 // rederer to be called $OUTPUT, not $this, so define a variable called
1342 // $OUTPUT pointing at $this. The same comment applies to $PAGE and $COURSE.
1343 $OUTPUT = $this;
1344 $PAGE = $this->page;
1345 $COURSE = $this->page->course;
a5cb8d69 1346
ebebf55c 1347 // And the parameters from the function call.
1348 extract($_namedarguments);
a5cb8d69 1349
ebebf55c 1350 // Include the template, capturing the output.
1351 ob_start();
1352 include($_template);
1353 $_result = ob_get_contents();
1354 ob_end_clean();
a5cb8d69 1355
ebebf55c 1356 return $_result;
a5cb8d69 1357 }
1358
ebebf55c 1359 /**
1360 * Searches the folders in {@link $searchpaths} to try to find a template for
1361 * this method name. Throws an exception if one cannot be found.
1362 * @param string $method the method name.
1363 * @return string the full path of the template to use.
1364 */
1365 protected function find_template($method) {
1366 foreach ($this->searchpaths as $path) {
1367 $filename = $path . '/' . $method . '.php';
1368 if (file_exists($filename)) {
1369 return $filename;
1370 }
1371 }
1372 throw new coding_exception('Cannot find template for ' . $this->copiedclass->getName() . '::' . $method);
8954245a 1373 }
571fa828 1374
ebebf55c 1375 /**
1376 * Handle methods like print_box_start by using the print_box template,
1377 * splitting the result, pusing the end onto the stack, then returning the start.
1378 * @param string $method the method that was called, with _start stripped off.
1379 * @param array $arguments the arguments that were passed to it.
1380 * @return string the HTML to be output.
1381 */
1382 protected function process_start($template, $arguments) {
1383 array_unshift($arguments, self::contentstoken);
1384 $html = $this->process_template($template, $arguments);
1385 list($start, $end) = explode(self::contentstoken, $html, 2);
1386 $this->opencontainers->push($template, $end);
1387 return $start;
8954245a 1388 }
1389
ebebf55c 1390 /**
1391 * Handle methods like print_box_end, we just need to pop the end HTML from
1392 * the stack.
1393 * @param string $method the method that was called, with _end stripped off.
1394 * @param array $arguments not used. Assumed to be irrelevant.
1395 * @return string the HTML to be output.
1396 */
1397 protected function process_end($template, $arguments) {
1398 return $this->opencontainers->pop($template);
8954245a 1399 }
1400
ebebf55c 1401 /**
1402 * @return array the list of paths where this class searches for templates.
1403 */
1404 public function get_search_paths() {
1405 return $this->searchpaths;
8954245a 1406 }
1407
1408 /**
ebebf55c 1409 * @return string the name of the class whose API we are copying.
8954245a 1410 */
ebebf55c 1411 public function get_copied_class() {
1412 return $this->copiedclass->getName();
1413 }
1414}
8954245a 1415
8954245a 1416
ebebf55c 1417/**
1418 * This class keeps track of which HTML tags are currently open.
1419 *
1420 * This makes it much easier to always generate well formed XHTML output, even
1421 * if execution terminates abruptly. Any time you output some opening HTML
1422 * without the matching closing HTML, you should push the neccessary close tags
1423 * onto the stack.
1424 *
1425 * @copyright 2009 Tim Hunt
1426 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1427 * @since Moodle 2.0
1428 */
1429class xhtml_container_stack {
1430 /** @var array stores the list of open containers. */
1431 protected $opencontainers = array();
4af1e3b0 1432 /**
1433 * @var array in developer debug mode, stores a stack trace of all opens and
1434 * closes, so we can output helpful error messages when there is a mismatch.
1435 */
1436 protected $log = array();
8954245a 1437
ebebf55c 1438 /**
1439 * Push the close HTML for a recently opened container onto the stack.
1440 * @param string $type The type of container. This is checked when {@link pop()}
1441 * is called and must match, otherwise a developer debug warning is output.
1442 * @param string $closehtml The HTML required to close the container.
1443 */
1444 public function push($type, $closehtml) {
1445 $container = new stdClass;
1446 $container->type = $type;
1447 $container->closehtml = $closehtml;
4af1e3b0 1448 if (debugging('', DEBUG_DEVELOPER)) {
1449 $this->log('Open', $type);
1450 }
ebebf55c 1451 array_push($this->opencontainers, $container);
1452 }
8954245a 1453
ebebf55c 1454 /**
1455 * Pop the HTML for the next closing container from the stack. The $type
1456 * must match the type passed when the container was opened, otherwise a
1457 * warning will be output.
1458 * @param string $type The type of container.
1459 * @return string the HTML requried to close the container.
1460 */
1461 public function pop($type) {
1462 if (empty($this->opencontainers)) {
4af1e3b0 1463 debugging('<p>There are no more open containers. This suggests there is a nesting problem.</p>' .
1464 $this->output_log(), DEBUG_DEVELOPER);
ebebf55c 1465 return;
8954245a 1466 }
1467
ebebf55c 1468 $container = array_pop($this->opencontainers);
1469 if ($container->type != $type) {
4af1e3b0 1470 debugging('<p>The type of container to be closed (' . $container->type .
ebebf55c 1471 ') does not match the type of the next open container (' . $type .
4af1e3b0 1472 '). This suggests there is a nesting problem.</p>' .
1473 $this->output_log(), DEBUG_DEVELOPER);
1474 }
1475 if (debugging('', DEBUG_DEVELOPER)) {
1476 $this->log('Close', $type);
8954245a 1477 }
ebebf55c 1478 return $container->closehtml;
8954245a 1479 }
1480
8954245a 1481 /**
ebebf55c 1482 * Close all but the last open container. This is useful in places like error
1483 * handling, where you want to close all the open containers (apart from <body>)
1484 * before outputting the error message.
4af1e3b0 1485 * @param $shouldbenone assert that the stack shold be empty now - causes a
1486 * developer debug warning if it isn't.
ebebf55c 1487 * @return string the HTML requried to close any open containers inside <body>.
8954245a 1488 */
4af1e3b0 1489 public function pop_all_but_last($shouldbenone = false) {
1490 if ($shouldbenone && count($this->opencontainers) != 1) {
1491 debugging('<p>Some HTML tags were opened in the body of the page but not closed.</p>' .
1492 $this->output_log(), DEBUG_DEVELOPER);
1493 }
ebebf55c 1494 $output = '';
1495 while (count($this->opencontainers) > 1) {
1496 $container = array_pop($this->opencontainers);
1497 $output .= $container->closehtml;
8954245a 1498 }
ebebf55c 1499 return $output;
8954245a 1500 }
34a2777c 1501
1502 /**
ebebf55c 1503 * You can call this function if you want to throw away an instance of this
1504 * class without properly emptying the stack (for example, in a unit test).
1505 * Calling this method stops the destruct method from outputting a developer
1506 * debug warning. After calling this method, the instance can no longer be used.
34a2777c 1507 */
ebebf55c 1508 public function discard() {
1509 $this->opencontainers = null;
1510 }
34a2777c 1511
ebebf55c 1512 /**
1513 * Emergency fallback. If we get to the end of processing and not all
1514 * containers have been closed, output the rest with a developer debug warning.
1515 */
1516 public function __destruct() {
1517 if (empty($this->opencontainers)) {
1518 return;
34a2777c 1519 }
1520
4af1e3b0 1521 debugging('<p>Some containers were left open. This suggests there is a nesting problem.</p>' .
1522 $this->output_log(), DEBUG_DEVELOPER);
ebebf55c 1523 echo $this->pop_all_but_last();
1524 $container = array_pop($this->opencontainers);
1525 echo $container->closehtml;
1526 }
4af1e3b0 1527
1528 protected function log($action, $type) {
1529 $this->log[] = '<li>' . $action . ' ' . $type . ' at:' .
fdeb7fa1 1530 format_backtrace(debug_backtrace()) . '</li>';
4af1e3b0 1531 }
1532
1533 protected function output_log() {
1534 return '<ul>' . implode("\n", $this->log) . '</ul>';
1535 }
ebebf55c 1536}
34a2777c 1537
34a2777c 1538
ebebf55c 1539/**
1540 * The standard implementation of the moodle_core_renderer interface.
1541 *
1542 * @copyright 2009 Tim Hunt
1543 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1544 * @since Moodle 2.0
1545 */
1546class moodle_core_renderer extends moodle_renderer_base {
fdeb7fa1 1547 /** @var string used in {@link header()}. */
ebebf55c 1548 const PERFORMANCE_INFO_TOKEN = '%%PERFORMANCEINFO%%';
fdeb7fa1 1549 /** @var string used in {@link header()}. */
ebebf55c 1550 const END_HTML_TOKEN = '%%ENDHTML%%';
fdeb7fa1 1551 /** @var string used in {@link header()}. */
ebebf55c 1552 const MAIN_CONTENT_TOKEN = '[MAIN CONTENT GOES HERE]';
fdeb7fa1 1553 /** @var string used to pass information from {@link doctype()} to {@link standard_head_html()}. */
ebebf55c 1554 protected $contenttype;
fdeb7fa1 1555 /** @var string used by {@link redirect_message()} method to communicate with {@link header()}. */
ebebf55c 1556 protected $metarefreshtag = '';
1557
fdeb7fa1 1558 /**
1559 * Get the DOCTYPE declaration that should be used with this page. Designed to
1560 * be called in theme layout.php files.
1561 * @return string the DOCTYPE declaration (and any XML prologue) that should be used.
1562 */
ebebf55c 1563 public function doctype() {
1564 global $CFG;
1565
1566 $doctype = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">' . "\n";
1567 $this->contenttype = 'text/html; charset=utf-8';
1568
1569 if (empty($CFG->xmlstrictheaders)) {
1570 return $doctype;
34a2777c 1571 }
1572
ebebf55c 1573 // We want to serve the page with an XML content type, to force well-formedness errors to be reported.
1574 $prolog = '<?xml version="1.0" encoding="utf-8"?>' . "\n";
1575 if (isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'application/xhtml+xml') !== false) {
1576 // Firefox and other browsers that can cope natively with XHTML.
1577 $this->contenttype = 'application/xhtml+xml; charset=utf-8';
34a2777c 1578
ebebf55c 1579 } else if (preg_match('/MSIE.*Windows NT/', $_SERVER['HTTP_USER_AGENT'])) {
1580 // IE can't cope with application/xhtml+xml, but it will cope if we send application/xml with an XSL stylesheet.
1581 $this->contenttype = 'application/xml; charset=utf-8';
1582 $prolog .= '<?xml-stylesheet type="text/xsl" href="' . $CFG->httpswwwroot . '/lib/xhtml.xsl"?>' . "\n";
34a2777c 1583
ebebf55c 1584 } else {
1585 $prolog = '';
1586 }
1587
1588 return $prolog . $doctype;
34a2777c 1589 }
1590
fdeb7fa1 1591 /**
1592 * The attributes that should be added to the <html> tag. Designed to
1593 * be called in theme layout.php files.
1594 * @return string HTML fragment.
1595 */
ebebf55c 1596 public function htmlattributes() {
1597 return get_html_lang(true) . ' xmlns="http://www.w3.org/1999/xhtml"';
34a2777c 1598 }
1599
fdeb7fa1 1600 /**
1601 * The standard tags (meta tags, links to stylesheets and JavaScript, etc.)
1602 * that should be included in the <head> tag. Designed to be called in theme
1603 * layout.php files.
1604 * @return string HTML fragment.
1605 */
ebebf55c 1606 public function standard_head_html() {
fdeb7fa1 1607 global $CFG;
ebebf55c 1608 $output = '';
1609 $output .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />' . "\n";
1610 $output .= '<meta name="keywords" content="moodle, ' . $this->page->title . '" />' . "\n";
1611 if (!$this->page->cacheable) {
1612 $output .= '<meta http-equiv="pragma" content="no-cache" />' . "\n";
1613 $output .= '<meta http-equiv="expires" content="0" />' . "\n";
34a2777c 1614 }
ebebf55c 1615 // This is only set by the {@link redirect()} method
1616 $output .= $this->metarefreshtag;
1617
1618 // Check if a periodic refresh delay has been set and make sure we arn't
1619 // already meta refreshing
1620 if ($this->metarefreshtag=='' && $this->page->periodicrefreshdelay!==null) {
e11a8328 1621 $output .= '<meta http-equiv="refresh" content="'.$this->page->periodicrefreshdelay.';url='.$this->page->url->out().'" />';
ebebf55c 1622 }
1623
e11a8328 1624 $this->page->requires->js('lib/javascript-static.js')->in_head();
1625 $this->page->requires->js('lib/javascript-mod.php')->in_head();
1626 $this->page->requires->js('lib/overlib/overlib.js')->in_head();
1627 $this->page->requires->js('lib/overlib/overlib_cssstyle.js')->in_head();
1628 $this->page->requires->js('lib/cookies.js')->in_head();
e11a8328 1629 $this->page->requires->js_function_call('setTimeout', Array('fix_column_widths()', 20));
1630
1631 $focus = $this->page->focuscontrol;
1632 if (!empty($focus)) {
428acddb 1633 if(preg_match("#forms\['([a-zA-Z0-9]+)'\].elements\['([a-zA-Z0-9]+)'\]#", $focus, $matches)) {
1634 // This is a horrifically bad way to handle focus but it is passed in
1635 // through messy formslib::moodleform
1636 $this->page->requires->js_function_call('old_onload_focus', Array($matches[1], $matches[2]));
1637 } else if (strpos($focus, '.')!==false) {
e11a8328 1638 // Old style of focus, bad way to do it
428acddb 1639 debugging('This code is using the old style focus event, Please update this code to focus on an element id or the moodleform focus method.', DEBUG_DEVELOPER);
1640 $this->page->requires->js_function_call('old_onload_focus', explode('.', $focus, 2));
e11a8328 1641 } else {
1642 // Focus element with given id
1643 $this->page->requires->js_function_call('focuscontrol', Array($focus));
1644 }
1645 }
fdeb7fa1 1646
1647 // Add the meta tags from the themes if any were requested.
1648 $output .= $this->page->theme->get_meta_tags($this->page);
1649
1650 // Get any HTML from the page_requirements_manager.
ebebf55c 1651 $output .= $this->page->requires->get_head_code();
1652
1653 // List alternate versions.
1654 foreach ($this->page->alternateversions as $type => $alt) {
1655 $output .= $this->output_empty_tag('link', array('rel' => 'alternate',
1656 'type' => $type, 'title' => $alt->title, 'href' => $alt->url));
1657 }
1658
ebebf55c 1659 return $output;
34a2777c 1660 }
1661
fdeb7fa1 1662 /**
1663 * The standard tags (typically skip links) that should be output just inside
1664 * the start of the <body> tag. Designed to be called in theme layout.php files.
1665 * @return string HTML fragment.
1666 */
ebebf55c 1667 public function standard_top_of_body_html() {
1668 return $this->page->requires->get_top_of_body_code();
34a2777c 1669 }
1670
fdeb7fa1 1671 /**
1672 * The standard tags (typically performance information and validation links,
1673 * if we are indevelope debug mode) that should be output in the footer area
1674 * of the page. Designed to be called in theme layout.php files.
1675 * @return string HTML fragment.
1676 */
ebebf55c 1677 public function standard_footer_html() {
fdeb7fa1 1678 // This function is normally called from a layout.php file in {@link header()}
1679 // but some of the content won't be known until later, so we return a placeholder
1680 // for now. This will be replaced with the real content in {@link footer()}.
ebebf55c 1681 $output = self::PERFORMANCE_INFO_TOKEN;
1682 if (debugging()) {
1683 $output .= '<div class="validators"><ul>
1684 <li><a href="http://validator.w3.org/check?verbose=1&amp;ss=1&amp;uri=' . urlencode(qualified_me()) . '">Validate HTML</a></li>
1685 <li><a href="http://www.contentquality.com/mynewtester/cynthia.exe?rptmode=-1&amp;url1=' . urlencode(qualified_me()) . '">Section 508 Check</a></li>
1686 <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>
1687 </ul></div>';
34a2777c 1688 }
ebebf55c 1689 return $output;
34a2777c 1690 }
1691
fdeb7fa1 1692 /**
1693 * The standard tags (typically script tags that are not needed earlier) that
1694 * should be output after everything else, . Designed to be called in theme layout.php files.
1695 * @return string HTML fragment.
1696 */
ebebf55c 1697 public function standard_end_of_body_html() {
fdeb7fa1 1698 // This function is normally called from a layout.php file in {@link header()}
1699 // but some of the content won't be known until later, so we return a placeholder
1700 // for now. This will be replaced with the real content in {@link footer()}.
ebebf55c 1701 echo self::END_HTML_TOKEN;
34a2777c 1702 }
1703
fdeb7fa1 1704 /**
1705 * Return the standard string that says whether you are logged in (and switched
1706 * roles/logged in as another user).
1707 * @return string HTML fragment.
1708 */
ebebf55c 1709 public function login_info() {
1710 global $USER;
1711 return user_login_string($this->page->course, $USER);
34a2777c 1712 }
1713
fdeb7fa1 1714 /**
1715 * Return the 'back' link that normally appears in the footer.
1716 * @return string HTML fragment.
1717 */
ebebf55c 1718 public function home_link() {
1719 global $CFG, $SITE;
34a2777c 1720
ebebf55c 1721 if ($this->page->pagetype == 'site-index') {
1722 // Special case for site home page - please do not remove
1723 return '<div class="sitelink">' .
1724 '<a title="Moodle ' . $CFG->release . '" href="http://moodle.org/">' .
1725 '<img style="width:100px;height:30px" src="' . $CFG->httpswwwroot . '/pix/moodlelogo.gif" alt="moodlelogo" /></a></div>';
34a2777c 1726
ebebf55c 1727 } else if (!empty($CFG->target_release) && $CFG->target_release != $CFG->release) {
1728 // Special case for during install/upgrade.
1729 return '<div class="sitelink">'.
1730 '<a title="Moodle ' . $CFG->target_release . '" href="http://docs.moodle.org/en/Administrator_documentation" onclick="this.target=\'_blank\'">' .
1731 '<img style="width:100px;height:30px" src="' . $CFG->httpswwwroot . '/pix/moodlelogo.gif" alt="moodlelogo" /></a></div>';
34a2777c 1732
ebebf55c 1733 } else if ($this->page->course->id == $SITE->id || strpos($this->page->pagetype, 'course-view') === 0) {
1734 return '<div class="homelink"><a href="' . $CFG->wwwroot . '/">' .
1735 get_string('home') . '</a></div>';
1736
1737 } else {
1738 return '<div class="homelink"><a href="' . $CFG->wwwroot . '/course/view.php?id=' . $this->page->course->id . '">' .
1739 format_string($this->page->course->shortname) . '</a></div>';
1740 }
34a2777c 1741 }
a5cb8d69 1742
1743 /**
ebebf55c 1744 * Redirects the user by any means possible given the current state
1745 *
1746 * This function should not be called directly, it should always be called using
1747 * the redirect function in lib/weblib.php
1748 *
1749 * The redirect function should really only be called before page output has started
1750 * however it will allow itself to be called during the state STATE_IN_BODY
1751 *
1752 * @param string $encodedurl The URL to send to encoded if required
1753 * @param string $message The message to display to the user if any
1754 * @param int $delay The delay before redirecting a user, if $message has been
1755 * set this is a requirement and defaults to 3, set to 0 no delay
1756 * @param string $messageclass The css class to put on the message that is
1757 * being displayed to the user
ae96b517 1758 * @param boolean $debugdisableredirect this redirect has been disabled for
1759 * debugging purposes. Display a message that explains, and don't
1760 * trigger the redirect.
ebebf55c 1761 * @return string The HTML to display to the user before dying, may contain
1762 * meta refresh, javascript refresh, and may have set header redirects
a5cb8d69 1763 */
ae96b517 1764 public function redirect_message($encodedurl, $message, $delay, $debugdisableredirect) {
ebebf55c 1765 global $CFG;
1766 $url = str_replace('&amp;', '&', $encodedurl);
8954245a 1767
ebebf55c 1768 switch ($this->page->state) {
1769 case moodle_page::STATE_BEFORE_HEADER :
1770 // No output yet it is safe to delivery the full arsenol of redirect methods
ae96b517 1771 if (!$debugdisableredirect) {
1772 // Don't use exactly the same time here, it can cause problems when both redirects fire at the same time.
ebebf55c 1773 $this->metarefreshtag = '<meta http-equiv="refresh" content="'. $delay .'; url='. $encodedurl .'" />'."\n";
ae96b517 1774 $this->page->requires->js_function_call('document.location.replace', array($url))->after_delay($delay + 3);
ebebf55c 1775 }
1776 $output = $this->header();
ebebf55c 1777 break;
1778 case moodle_page::STATE_PRINTING_HEADER :
1779 // We should hopefully never get here
1780 throw new coding_exception('You cannot redirect while printing the page header');
1781 break;
1782 case moodle_page::STATE_IN_BODY :
1783 // We really shouldn't be here but we can deal with this
1784 debugging("You should really redirect before you start page output");
728f96c7 1785 if (!$debugdisableredirect) {
ae96b517 1786 $this->page->requires->js_function_call('document.location.replace', array($url))->after_delay($delay);
ebebf55c 1787 }
1788 $output = $this->opencontainers->pop_all_but_last();
ebebf55c 1789 break;
1790 case moodle_page::STATE_DONE :
1791 // Too late to be calling redirect now
1792 throw new coding_exception('You cannot redirect after the entire page has been generated');
1793 break;
1794 }
ae96b517 1795 $output .= $this->notification($message, 'redirectmessage');
1796 $output .= '<a href="'. $encodedurl .'">'. get_string('continue') .'</a>';
1797 if ($debugdisableredirect) {
1798 $output .= '<p><strong>Error output, so disabling automatic redirect.</strong></p>';
1799 }
1800 $output .= $this->footer();
ebebf55c 1801 return $output;
1802 }
b7009474 1803
fdeb7fa1 1804 /**
1805 * Start output by sending the HTTP headers, and printing the HTML <head>
1806 * and the start of the <body>.
1807 *
1808 * To control what is printed, you should set properies on $PAGE. If you
1809 * are familiar with the old {@link print_header()} function from Moodle 1.9
1810 * you will find that there are properties on $PAGE that correspond to most
1811 * of the old paramters to could be passed to print_header.
1812 *
1813 * Not that, in due course, the remaining $navigation, $menu paramters here
1814 * will be replaced by more properties of $PAGE, but that is still to do.
1815 *
1816 * @param $navigation legacy, like the old paramter to print_header. Will be
1817 * removed when there is a $PAGE->... replacement.
1818 * @param $menu legacy, like the old paramter to print_header. Will be
1819 * removed when there is a $PAGE->... replacement.
1820 * @return string HTML that you must output this, preferably immediately.
1821 */
ebebf55c 1822 public function header($navigation = '', $menu='') {
fdeb7fa1 1823 // TODO remove $navigation and $menu arguments - replace with $PAGE->navigation
ebebf55c 1824 global $USER, $CFG;
b7009474 1825
ebebf55c 1826 $this->page->set_state(moodle_page::STATE_PRINTING_HEADER);
b7009474 1827
ebebf55c 1828 // Find the appropriate page template, based on $this->page->generaltype.
d4a03c00 1829 $templatefile = $this->page->theme->template_for_page($this->page->generaltype);
ebebf55c 1830 if ($templatefile) {
1831 // Render the template.
1832 $template = $this->render_page_template($templatefile, $menu, $navigation);
1833 } else {
1834 // New style template not found, fall back to using header.html and footer.html.
1835 $template = $this->handle_legacy_theme($navigation, $menu);
1836 }
b7009474 1837
ebebf55c 1838 // Slice the template output into header and footer.
1839 $cutpos = strpos($template, self::MAIN_CONTENT_TOKEN);
1840 if ($cutpos === false) {
1841 throw new coding_exception('Layout template ' . $templatefile .
1842 ' does not contain the string "' . self::MAIN_CONTENT_TOKEN . '".');
1843 }
1844 $header = substr($template, 0, $cutpos);
1845 $footer = substr($template, $cutpos + strlen(self::MAIN_CONTENT_TOKEN));
b7009474 1846
60a5b188 1847 if (empty($this->contenttype)) {
1848 debugging('The layout template did not call $OUTPUT->doctype()');
1849 $this->doctype();
1850 }
1851
ebebf55c 1852 send_headers($this->contenttype, $this->page->cacheable);
1853 $this->opencontainers->push('header/footer', $footer);
1854 $this->page->set_state(moodle_page::STATE_IN_BODY);
1855 return $header . $this->skip_link_target();
1856 }
b7009474 1857
ebebf55c 1858 protected function render_page_template($templatefile, $menu, $navigation) {
1859 global $CFG, $SITE, $THEME, $USER;
1860 // The next lines are a bit tricky. The point is, here we are in a method
1861 // of a renderer class, and this object may, or may not, be the the same as
1862 // the global $OUTPUT object. When rendering the template, we want to use
1863 // this object. However, people writing Moodle code expect the current
1864 // rederer to be called $OUTPUT, not $this, so define a variable called
1865 // $OUTPUT pointing at $this. The same comment applies to $PAGE and $COURSE.
1866 $OUTPUT = $this;
1867 $PAGE = $this->page;
1868 $COURSE = $this->page->course;
b7009474 1869
ebebf55c 1870 ob_start();
1871 include($templatefile);
1872 $template = ob_get_contents();
1873 ob_end_clean();
1874 return $template;
1875 }
b7009474 1876
ebebf55c 1877 protected function handle_legacy_theme($navigation, $menu) {
d4a03c00 1878 global $CFG, $SITE, $USER;
ebebf55c 1879 // Set a pretend global from the properties of this class.
1880 // See the comment in render_page_template for a fuller explanation.
1881 $COURSE = $this->page->course;
d4a03c00 1882 $THEME = $this->page->theme;
ebebf55c 1883
1884 // Set up local variables that header.html expects.
1885 $direction = $this->htmlattributes();
1886 $title = $this->page->title;
1887 $heading = $this->page->heading;
1888 $focus = $this->page->focuscontrol;
1889 $button = $this->page->button;
1890 $pageid = $this->page->pagetype;
1891 $pageclass = $this->page->bodyclasses;
1892 $bodytags = ' class="' . $pageclass . '" id="' . $pageid . '"';
1893 $home = $this->page->generaltype == 'home';
1894
1895 $meta = $this->standard_head_html();
1896 // The next line is a nasty hack. having set $meta to standard_head_html, we have already
1897 // got the contents of include($CFG->javascript). However, legacy themes are going to
1898 // include($CFG->javascript) again. We want to make sure that when they do, nothing is output.
1899 $CFG->javascript = $CFG->libdir . '/emptyfile.php';
b7009474 1900
ebebf55c 1901 // Set up local variables that footer.html expects.
1902 $homelink = $this->home_link();
1903 $loggedinas = $this->login_info();
1904 $course = $this->page->course;
1905 $performanceinfo = self::PERFORMANCE_INFO_TOKEN;
b7009474 1906
ebebf55c 1907 if (!$menu && $navigation) {
1908 $menu = $loggedinas;
1909 }
b7009474 1910
d4a03c00 1911 if (!empty($this->page->theme->layouttable)) {
1912 $lt = $this->page->theme->layouttable;
1913 } else {
1914 $lt = array('left', 'middle', 'right');
1915 }
1916
1917 if (!empty($this->page->theme->block_l_max_width)) {
1918 $preferredwidthleft = $this->page->theme->block_l_max_width;
1919 } else {
1920 $preferredwidthleft = 210;
1921 }
1922 if (!empty($this->page->theme->block_r_max_width)) {
1923 $preferredwidthright = $this->page->theme->block_r_max_width;
1924 } else {
1925 $preferredwidthright = 210;
1926 }
1927
ebebf55c 1928 ob_start();
d4a03c00 1929 include($this->page->theme->dir . '/header.html');
ebebf55c 1930 $this->page->requires->get_top_of_body_code();
d4a03c00 1931
1932 echo '<table id="layout-table"><tr>';
1933 foreach ($lt as $column) {
1934 if ($column == 'left' && $this->page->blocks->region_has_content(BLOCK_POS_LEFT)) {
ae42ff6f 1935 echo '<td id="left-column" class="block-region" style="width: ' . $preferredwidthright . 'px; vertical-align: top;">';
d4a03c00 1936 echo $this->container_start();
1937 echo $this->blocks_for_region(BLOCK_POS_LEFT);
1938 echo $this->container_end();
1939 echo '</td>';
1940
1941 } else if ($column == 'middle') {
1942 echo '<td id="middle-column" style="vertical-align: top;">';
1943 echo $this->container_start();
1944 echo $this->skip_link_target();
1945 echo self::MAIN_CONTENT_TOKEN;
1946 echo $this->container_end();
1947 echo '</td>';
1948
1949 } else if ($column == 'right' && $this->page->blocks->region_has_content(BLOCK_POS_RIGHT)) {
ae42ff6f 1950 echo '<td id="right-column" class="block-region" style="width: ' . $preferredwidthright . 'px; vertical-align: top;">';
d4a03c00 1951 echo $this->container_start();
1952 echo $this->blocks_for_region(BLOCK_POS_RIGHT);
1953 echo $this->container_end();
1954 echo '</td>';
1955 }
1956 }
1957 echo '</tr></table>';
b7009474 1958
ebebf55c 1959 $menu = str_replace('navmenu', 'navmenufooter', $menu);
1960 include($THEME->dir . '/footer.html');
b7009474 1961
ebebf55c 1962 $output = ob_get_contents();
1963 ob_end_clean();
b7009474 1964
ebebf55c 1965 $output = str_replace('</body>', self::END_HTML_TOKEN . '</body>', $output);
60a5b188 1966 // Make sure we use the correct doctype.
1967 $output = preg_replace('/(<!DOCTYPE.+?>)/s', $this->doctype(), $output);
b7009474 1968
ebebf55c 1969 return $output;
1970 }
b7009474 1971
ebebf55c 1972 public function footer() {
4af1e3b0 1973 $output = $this->opencontainers->pop_all_but_last(true);
b7009474 1974
ebebf55c 1975 $footer = $this->opencontainers->pop('header/footer');
b7009474 1976
ebebf55c 1977 // Provide some performance info if required
1978 $performanceinfo = '';
1979 if (defined('MDL_PERF') || (!empty($CFG->perfdebug) and $CFG->perfdebug > 7)) {
1980 $perf = get_performance_info();
1981 if (defined('MDL_PERFTOLOG') && !function_exists('register_shutdown_function')) {
1982 error_log("PERF: " . $perf['txt']);
1983 }
1984 if (defined('MDL_PERFTOFOOT') || debugging() || $CFG->perfdebug > 7) {
1985 $performanceinfo = $perf['html'];
1986 }
1987 }
1988 $footer = str_replace(self::PERFORMANCE_INFO_TOKEN, $performanceinfo, $footer);
b7009474 1989
ebebf55c 1990 $footer = str_replace(self::END_HTML_TOKEN, $this->page->requires->get_end_code(), $footer);
b7009474 1991
ebebf55c 1992 $this->page->set_state(moodle_page::STATE_DONE);
b7009474 1993
ebebf55c 1994 return $output . $footer;
1995 }
b7009474 1996
d4a03c00 1997 /**
1998 * Output the row of editing icons for a block, as defined by the controls array.
1999 * @param $controls an array like {@link block_contents::$controls}.
2000 * @return HTML fragment.
2001 */
2002 public function block_controls($controls) {
2003 if (empty($controls)) {
2004 return '';
2005 }
2006 $controlshtml = array();
2007 foreach ($controls as $control) {
2008 $controlshtml[] = $this->output_tag('a', array('class' => 'icon',
2009 'title' => $control['caption'], 'href' => $control['url']),
1936f20b 2010 $this->output_empty_tag('img', array('src' => $this->old_icon_url($control['icon']),
d4a03c00 2011 'alt' => $control['caption'])));
2012 }
2013 return $this->output_tag('div', array('class' => 'commands'), implode('', $controlshtml));
2014 }
2015
ebebf55c 2016 /**
2017 * Prints a nice side block with an optional header.
2018 *
2019 * The content is described
2020 * by a {@link block_contents} object.
2021 *
d4a03c00 2022 * @param block_contents $bc HTML for the content
ebebf55c 2023 * @return string the HTML to be output.
2024 */
2025 function block($bc) {
d4a03c00 2026 $bc = clone($bc); // Avoid messing up the object passed in.
ebebf55c 2027 $bc->prepare();
b7009474 2028
d4a03c00 2029 $skiptitle = strip_tags($bc->title);
2030 if (empty($skiptitle)) {
ebebf55c 2031 $output = '';
2032 $skipdest = '';
2033 } else {
2034 $output = $this->output_tag('a', array('href' => '#sb-' . $bc->skipid, 'class' => 'skip-block'),
d4a03c00 2035 get_string('skipa', 'access', $skiptitle));
ebebf55c 2036 $skipdest = $this->output_tag('span', array('id' => 'sb-' . $bc->skipid, 'class' => 'skip-block-to'), '');
2037 }
b7009474 2038
ebebf55c 2039 $bc->attributes['id'] = $bc->id;
2040 $bc->attributes['class'] = $bc->get_classes_string();
2041 $output .= $this->output_start_tag('div', $bc->attributes);
b7009474 2042
d4a03c00 2043 $controlshtml = $this->block_controls($bc->controls);
b7009474 2044
d4a03c00 2045 $title = '';
2046 if ($bc->title) {
2047 $title = $this->output_tag('h2', null, $bc->title);
ebebf55c 2048 }
b7009474 2049
d4a03c00 2050 if ($title || $controlshtml) {
2051 $output .= $this->output_tag('div', array('class' => 'header'),
2052 $this->output_tag('div', array('class' => 'title'),
2053 $title . $controlshtml));
ebebf55c 2054 }
b7009474 2055
d4a03c00 2056 $output .= $this->output_start_tag('div', array('class' => 'content'));
2057 $output .= $bc->content;
2058
ebebf55c 2059 if ($bc->footer) {
2060 $output .= $this->output_tag('div', array('class' => 'footer'), $bc->footer);
2061 }
b7009474 2062
ebebf55c 2063 $output .= $this->output_end_tag('div');
2064 $output .= $this->output_end_tag('div');
d4a03c00 2065 if ($bc->annotation) {
2066 $output .= $this->output_tag('div', array('class' => 'blockannotation'), $bc->annotation);
2067 }
ebebf55c 2068 $output .= $skipdest;
b7009474 2069
d4a03c00 2070 $this->init_block_hider_js($bc);
2071 return $output;
2072 }
2073
2074 protected function init_block_hider_js($bc) {
2075 if ($bc->collapsible != block_contents::NOT_HIDEABLE) {
2076 $userpref = 'block' . $bc->blockinstanceid . 'hidden';
2077 user_preference_allow_ajax_update($userpref, PARAM_BOOL);
2078 $this->page->requires->yui_lib('dom');
2079 $this->page->requires->yui_lib('event');
2080 $plaintitle = strip_tags($bc->title);
2081 $this->page->requires->js_function_call('new block_hider', array($bc->id, $userpref,
2082 get_string('hideblocka', 'access', $plaintitle), get_string('showblocka', 'access', $plaintitle),
2083 $this->old_icon_url('t/switch_minus'), $this->old_icon_url('t/switch_plus')));
ebebf55c 2084 }
d4a03c00 2085 }
2086
2087 /**
2088 * Render the contents of a block_list.
2089 * @param array $icons the icon for each item.
2090 * @param array $items the content of each item.
2091 * @return string HTML
2092 */
2093 public function list_block_contents($icons, $items) {
2094 $row = 0;
2095 $lis = array();
2096 foreach ($items as $key => $string) {
2097 $item = $this->output_start_tag('li', array('class' => 'r' . $row));
2098 if ($icons) {
2099 $item .= $this->output_tag('div', array('class' => 'icon column c0'), $icons[$key]);
2100 }
2101 $item .= $this->output_tag('div', array('class' => 'column c1'), $string);
2102 $item .= $this->output_end_tag('li');
2103 $lis[] = $item;
2104 $row = 1 - $row; // Flip even/odd.
2105 }
2106 return $this->output_tag('ul', array('class' => 'list'), implode("\n", $lis));
2107 }
b7009474 2108
d4a03c00 2109 /**
2110 * Output all the blocks in a particular region.
2111 * @param $region the name of a region on this page.
2112 * @return string the HTML to be output.
2113 */
2114 public function blocks_for_region($region) {
2115 $blockcontents = $this->page->blocks->get_content_for_region($region, $this);
2116 $output = '';
2117 foreach ($blockcontents as $bc) {
2118 $output .= $this->block($bc);
2119 }
ebebf55c 2120 return $output;
2121 }
b7009474 2122
ebebf55c 2123 public function link_to_popup_window() {
b7009474 2124
ebebf55c 2125 }
b7009474 2126
ebebf55c 2127 public function button_to_popup_window() {
b7009474 2128
ebebf55c 2129 }
b7009474 2130
ebebf55c 2131 public function close_window_button($buttontext = null, $reloadopener = false) {
2132 if (empty($buttontext)) {
2133 $buttontext = get_string('closewindow');
2134 }
2135 // TODO
2136 }
b7009474 2137
ebebf55c 2138 public function close_window($delay = 0, $reloadopener = false) {
2139 // TODO
2140 }
b7009474 2141
ebebf55c 2142 /**
2143 * Output a <select> menu.
2144 *
2145 * You can either call this function with a single moodle_select_menu argument
2146 * or, with a list of parameters, in which case those parameters are sent to
2147 * the moodle_select_menu constructor.
2148 *
2149 * @param moodle_select_menu $selectmenu a moodle_select_menu that describes
2150 * the select menu you want output.
2151 * @return string the HTML for the <select>
2152 */
2153 public function select_menu($selectmenu) {
2154 $selectmenu = clone($selectmenu);
2155 $selectmenu->prepare();
b7009474 2156
ebebf55c 2157 if ($selectmenu->nothinglabel) {
2158 $selectmenu->options = array($selectmenu->nothingvalue => $selectmenu->nothinglabel) +
2159 $selectmenu->options;
2160 }
b7009474 2161
ebebf55c 2162 if (empty($selectmenu->id)) {
2163 $selectmenu->id = 'menu' . str_replace(array('[', ']'), '', $selectmenu->name);
2164 }
b7009474 2165
ebebf55c 2166 $attributes = array(
2167 'name' => $selectmenu->name,
2168 'id' => $selectmenu->id,
2169 'class' => $selectmenu->get_classes_string(),
2170 'onchange' => $selectmenu->script,
2171 );
2172 if ($selectmenu->disabled) {
2173 $attributes['disabled'] = 'disabled';
2174 }
2175 if ($selectmenu->tabindex) {
2176 $attributes['tabindex'] = $tabindex;
2177 }
b7009474 2178
ebebf55c 2179 if ($selectmenu->listbox) {
2180 if (is_integer($selectmenu->listbox)) {
2181 $size = $selectmenu->listbox;
2182 } else {
2183 $size = min($selectmenu->maxautosize, count($selectmenu->options));
2184 }
2185 $attributes['size'] = $size;
2186 if ($selectmenu->multiple) {
2187 $attributes['multiple'] = 'multiple';
2188 }
2189 }
b7009474 2190
ebebf55c 2191 $html = $this->output_start_tag('select', $attributes) . "\n";
2192 foreach ($selectmenu->options as $value => $label) {
2193 $attributes = array('value' => $value);
2194 if ((string)$value == (string)$selectmenu->selectedvalue ||
2195 (is_array($selectmenu->selectedvalue) && in_array($value, $selectmenu->selectedvalue))) {
2196 $attributes['selected'] = 'selected';
2197 }
2198 $html .= ' ' . $this->output_tag('option', $attributes, s($label)) . "\n";
2199 }
2200 $html .= $this->output_end_tag('select') . "\n";
b7009474 2201
ebebf55c 2202 return $html;
2203 }
b7009474 2204
ebebf55c 2205 // TODO choose_from_menu_nested
2206
2207 // TODO choose_from_radio
b7009474 2208
2209 /**
ebebf55c 2210 * Output an error message. By default wraps the error message in <span class="error">.
2211 * If the error message is blank, nothing is output.
2212 * @param $message the error message.
2213 * @return string the HTML to output.
b7009474 2214 */
ebebf55c 2215 public function error_text($message) {
2216 if (empty($message)) {
2217 return '';
2218 }
2219 return $this->output_tag('span', array('class' => 'error'), $message);
2220 }
b7009474 2221
2222 /**
ebebf55c 2223 * Do not call this function directly.
b7009474 2224 *
ebebf55c 2225 * To terminate the current script with a fatal error, call the {@link print_error}
2226 * function, or throw an exception. Doing either of those things will then call this
2227 * funciton to display the error, before terminating the exection.
2228 *
2229 * @param string $message
2230 * @param string $moreinfourl
2231 * @param string $link
2232 * @param array $backtrace
2233 * @param string $debuginfo
2234 * @param bool $showerrordebugwarning
2235 * @return string the HTML to output.
b7009474 2236 */
ebebf55c 2237 public function fatal_error($message, $moreinfourl, $link, $backtrace,
2238 $debuginfo = null, $showerrordebugwarning = false) {
b7009474 2239
ebebf55c 2240 $output = '';
b7009474 2241
ebebf55c 2242 if ($this->has_started()) {
2243 $output .= $this->opencontainers->pop_all_but_last();
2244 } else {
2245 // Header not yet printed
2246 @header('HTTP/1.0 404 Not Found');
2247 $this->page->set_title(get_string('error'));
2248 $output .= $this->header();
2249 }
b7009474 2250
ebebf55c 2251 $message = '<p class="errormessage">' . $message . '</p>'.
2252 '<p class="errorcode"><a href="' . $moreinfourl . '">' .
2253 get_string('moreinformation') . '</a></p>';
2254 $output .= $this->box($message, 'errorbox');
2255
2256 if (debugging('', DEBUG_DEVELOPER)) {
2257 if ($showerrordebugwarning) {
2258 $output .= $this->notification('error() is a deprecated function. ' .
2259 'Please call print_error() instead of error()', 'notifytiny');
2260 }
2261 if (!empty($debuginfo)) {
2262 $output .= $this->notification($debuginfo, 'notifytiny');
2263 }
2264 if (!empty($backtrace)) {
2265 $output .= $this->notification('Stack trace: ' .
2266 format_backtrace($backtrace), 'notifytiny');
2267 }
b7009474 2268 }
b7009474 2269
ebebf55c 2270 if (!empty($link)) {
2271 $output .= $this->continue_button($link);
2272 }
b7009474 2273
ebebf55c 2274 $output .= $this->footer();
2275
2276 // Padding to encourage IE to display our error page, rather than its own.
2277 $output .= str_repeat(' ', 512);
2278
2279 return $output;
b7009474 2280 }
2281
db8d89d8 2282 /**
ebebf55c 2283 * Output a notification (that is, a status message about something that has
2284 * just happened).
2285 *
2286 * @param string $message the message to print out
2287 * @param string $classes normally 'notifyproblem' or 'notifysuccess'.
2288 * @return string the HTML to output.
db8d89d8 2289 */
ebebf55c 2290 public function notification($message, $classes = 'notifyproblem') {
2291 return $this->output_tag('div', array('class' =>
2292 moodle_renderer_base::prepare_classes($classes)), clean_text($message));
db8d89d8 2293 }
2294
b7009474 2295 /**
ebebf55c 2296 * Print a continue button that goes to a particular URL.
2297 *
2298 * @param string|moodle_url $link The url the button goes to.
2299 * @return string the HTML to output.
b7009474 2300 */
ebebf55c 2301 public function continue_button($link) {
2302 if (!is_a($link, 'moodle_url')) {
2303 $link = new moodle_url($link);
b7009474 2304 }
ebebf55c 2305 return $this->output_tag('div', array('class' => 'continuebutton'),
2306 print_single_button($link->out(true), $link->params(), get_string('continue'), 'get', '', true));
b7009474 2307 }
2308
480b0720 2309 /**
2310 * Render a HTML table
2311 *
2312 * @param object $table {@link html_table} instance containing all the information needed
2313 * @return string the HTML to output.
2314 */
2315 public function table(html_table $table) {
2316 $table->prepare();
2317 $attributes = array(
2318 'id' => $table->id,
2319 'width' => $table->width,
2320 'summary' => $table->summary,
2321 'cellpadding' => $table->cellpadding,
2322 'cellspacing' => $table->cellspacing,
2323 'class' => $table->get_classes_string());
2324 $output = $this->output_start_tag('table', $attributes) . "\n";
2325
2326 $countcols = 0;
2327
2328 if (!empty($table->head)) {
2329 $countcols = count($table->head);
2330 $output .= $this->output_start_tag('thead', array()) . "\n";
2331 $output .= $this->output_start_tag('tr', array()) . "\n";
2332 $keys = array_keys($table->head);
2333 $lastkey = end($keys);
2334 foreach ($table->head as $key => $heading) {
2335 $classes = array('header', 'c' . $key);
2336 if (isset($table->headspan[$key]) && $table->headspan[$key] > 1) {
2337 $colspan = $table->headspan[$key];
2338 $countcols += $table->headspan[$key] - 1;
2339 } else {
2340 $colspan = '';
2341 }
2342 if ($key == $lastkey) {
2343 $classes[] = 'lastcol';
2344 }
2345 if (isset($table->colclasses[$key])) {
2346 $classes[] = $table->colclasses[$key];
2347 }
2348 if ($table->rotateheaders) {
2349 // we need to wrap the heading content
2350 $heading = $this->output_tag('span', '', $heading);
2351 }
2352 $attributes = array(
2353 'style' => $table->align[$key] . $table->size[$key] . 'white-space:nowrap;',
2354 'class' => moodle_renderer_base::prepare_classes($classes),
2355 'scope' => 'col',
2356 'colspan' => $colspan);
2357 $output .= $this->output_tag('th', $attributes, $heading) . "\n";
2358 }
2359 $output .= $this->output_end_tag('tr') . "\n";
2360 $output .= $this->output_end_tag('thead') . "\n";
2361 }
2362
2363 if (!empty($table->data)) {
2364 $oddeven = 1;
2365 $keys = array_keys($table->data);
2366 $lastrowkey = end($keys);
2367 $output .= $this->output_start_tag('tbody', array()) . "\n";
2368 foreach ($table->data as $key => $row) {
2369 $oddeven = $oddeven ? 0 : 1;
2370 if (isset($table->rowclasses[$key])) {
2371 $classes = array_unique(moodle_html_component::clean_classes($table->rowclasses[$key]));
2372 } else {
2373 $classes = array();
2374 }
2375 $classes[] = 'r' . $oddeven;
2376 if ($key == $lastrowkey) {
2377 $classes[] = 'lastrow';
2378 }
2379 $output .= $this->output_start_tag('tr', array('class' => moodle_renderer_base::prepare_classes($classes))) . "\n";
2380 if (($row === 'hr') && ($countcols)) {
2381 $output .= $this->output_tag('td', array('colspan' => $countcols),
2382 $this->output_tag('div', array('class' => 'tabledivider'), '')) . "\n";
2383 } else { /// it's a normal row of data
2384 $keys2 = array_keys($row);
2385 $lastkey = end($keys2);
2386 foreach ($row as $key => $item) {
2387 if (isset($table->colclasses[$key])) {
2388 $classes = array_unique(moodle_html_component::clean_classes($table->colclasses[$key]));
2389 } else {
2390 $classes = array();
2391 }
2392 $classes[] = 'cell';
2393 $classes[] = 'c' . $key;
2394 if ($key == $lastkey) {
2395 $classes[] = 'lastcol';
2396 }
2397 $tdstyle = '';
2398 $tdstyle .= isset($table->align[$key]) ? $table->align[$key] : '';
2399 $tdstyle .= isset($table->size[$key]) ? $table->size[$key] : '';
2400 $tdstyle .= isset($table->wrap[$key]) ? $table->wrap[$key] : '';
2401 $output .= $this->output_tag('td',
2402 array('style' => $tdstyle,
2403 'class' => moodle_renderer_base::prepare_classes($classes)),
2404 $item) . "\n";
2405 }
2406 }
2407 $output .= $this->output_end_tag('tr') . "\n";
2408 }
2409 $output .= $this->output_end_tag('tbody') . "\n";
2410 }
2411 $output .= $this->output_end_tag('table') . "\n";
2412
2413 if ($table->rotateheaders && can_use_rotated_text()) {
2414 $this->page->requires->yui_lib('event');
2415 $this->page->requires->js('course/report/progress/textrotate.js');
2416 }
2417
2418 return $output;
2419 }
2420
b7009474 2421 /**
ebebf55c 2422 * Output the place a skip link goes to.
2423 * @param $id The target name from the corresponding $PAGE->requires->skip_link_to($target) call.
2424 * @return string the HTML to output.
b7009474 2425 */
d4a03c00 2426 public function skip_link_target($id = '') {
ebebf55c 2427 return $this->output_tag('span', array('id' => $id), '');
2428 }
b7009474 2429
ebebf55c 2430 public function heading($text, $level, $classes = 'main', $id = '') {
2431 $level = (integer) $level;
2432 if ($level < 1 or $level > 6) {
2433 throw new coding_exception('Heading level must be an integer between 1 and 6.');
b7009474 2434 }
ebebf55c 2435 return $this->output_tag('h' . $level,
2436 array('id' => $id, 'class' => moodle_renderer_base::prepare_classes($classes)), $text);
2437 }
b7009474 2438
ebebf55c 2439 public function box($contents, $classes = 'generalbox', $id = '') {
2440 return $this->box_start($classes, $id) . $contents . $this->box_end();
2441 }
b7009474 2442
ebebf55c 2443 public function box_start($classes = 'generalbox', $id = '') {
2444 $this->opencontainers->push('box', $this->output_end_tag('div'));
2445 return $this->output_start_tag('div', array('id' => $id,
2446 'class' => 'box ' . moodle_renderer_base::prepare_classes($classes)));
2447 }
b7009474 2448
ebebf55c 2449 public function box_end() {
2450 return $this->opencontainers->pop('box');
2451 }
b7009474 2452
ebebf55c 2453 public function container($contents, $classes = '', $id = '') {
2454 return $this->container_start($classes, $id) . $contents . $this->container_end();
2455 }
b7009474 2456
ebebf55c 2457 public function container_start($classes = '', $id = '') {
2458 $this->opencontainers->push('container', $this->output_end_tag('div'));
2459 return $this->output_start_tag('div', array('id' => $id,
2460 'class' => moodle_renderer_base::prepare_classes($classes)));
2461 }
2462
2463 public function container_end() {
2464 return $this->opencontainers->pop('container');
b7009474 2465 }
b7009474 2466}
2467
2468
8954245a 2469/**
2470 * Base class for classes representing HTML elements, like moodle_select_menu.
2471 *
2472 * Handles the id and class attribues.
2473 *
2474 * @copyright 2009 Tim Hunt
2475 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2476 * @since Moodle 2.0
2477 */
2478class moodle_html_component {
2479 /**
2480 * @var string value to use for the id attribute of this HTML tag.
2481 */
2482 public $id = '';
2483 /**
2484 * @var array class names to add to this HTML element.
2485 */
2486 public $classes = array();
2487
2488 /**
2489 * Ensure some class names are an array.
2490 * @param mixed $classes either an array of class names or a space-separated
2491 * string containing class names.
2492 * @return array the class names as an array.
2493 */
d4a03c00 2494 public static function clean_classes($classes) {
8954245a 2495 if (is_array($classes)) {
2496 return $classes;
2497 } else {
a5cb8d69 2498 return explode(' ', trim($classes));
8954245a 2499 }
2500 }
2501
2502 /**
2503 * Set the class name array.
2504 * @param mixed $classes either an array of class names or a space-separated
2505 * string containing class names.
2506 */
2507 public function set_classes($classes) {
d4a03c00 2508 $this->classes = self::clean_classes($classes);
8954245a 2509 }
2510
2511 /**
2512 * Add a class name to the class names array.
2513 * @param string $class the new class name to add.
2514 */
2515 public function add_class($class) {
2516 $this->classes[] = $class;
2517 }
2518
2519 /**
2520 * Add a whole lot of class names to the class names array.
2521 * @param mixed $classes either an array of class names or a space-separated
2522 * string containing class names.
2523 */
2524 public function add_classes($classes) {
d4a03c00 2525 $this->classes += self::clean_classes($classes);
8954245a 2526 }
2527
2528 /**
2529 * Get the class names as a string.
2530 * @return string the class names as a space-separated string. Ready to be put in the class="" attribute.
2531 */
2532 public function get_classes_string() {
2533 return implode(' ', $this->classes);
2534 }
2535
2536 /**
2537 * Perform any cleanup or final processing that should be done before an
2538 * instance of this class is output.
2539 */
2540 public function prepare() {
d4a03c00 2541 $this->classes = array_unique(self::clean_classes($this->classes));
8954245a 2542 }
480b0720 2543
2544 /**
2545 * This checks developer do not try to assign a property directly
2546 * if we have a setter for it. Otherwise, the property is set as expected.
2547 */
2548 public function __set($name, $value) {
2549 if ($name == 'class') {
2550 debugging('this way of setting css class has been deprecated. use set_classes() method instead.');
2551 $this->set_classes($value);
2552 } else {
2553 $this->{$name} = $value;
2554 }
2555 }
2556
8954245a 2557}
2558
2559
2560/**
2561 * This class hold all the information required to describe a <select> menu that
34a2777c 2562 * will be printed by {@link moodle_core_renderer::select_menu()}. (Or by an overridden
8954245a 2563 * version of that method in a subclass.)
2564 *
2565 * All the fields that are not set by the constructor have sensible defaults, so
2566 * you only need to set the properties where you want non-default behaviour.
2567 *
2568 * @copyright 2009 Tim Hunt
2569 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2570 * @since Moodle 2.0
2571 */
2572class moodle_select_menu extends moodle_html_component {
2573 /**
2574 * @var array the choices to show in the menu. An array $value => $label.
2575 */
2576 public $options;
2577 /**
2578 * @var string the name of this form control. That is, the name of the GET/POST
2579 * variable that will be set if this select is submmitted as part of a form.
2580 */
2581 public $name;
2582 /**
2583 * @var string the option to select initially. Should match one
2584 * of the $options array keys. Default none.
2585 */
2586 public $selectedvalue;
2587 /**
2588 * @var string The label for the 'nothing is selected' option.
2589 * Defaults to get_string('choosedots').
2590 * Set this to '' if you do not want a 'nothing is selected' option.
2591 */
2592 public $nothinglabel = null;
2593 /**
2594 * @var string The value returned by the 'nothing is selected' option. Defaults to 0.
2595 */
2596 public $nothingvalue = 0;
2597 /**
2598 * @var boolean set this to true if you want the control to appear disabled.
2599 */
2600 public $disabled = false;
2601 /**
2602 * @var integer if non-zero, sets the tabindex attribute on the <select> element. Default 0.
2603 */
2604 public $tabindex = 0;
2605 /**
2606 * @var mixed Defaults to false, which means display the select as a dropdown menu.
2607 * If true, display this select as a list box whose size is chosen automatically.
2608 * If an integer, display as list box of that size.
2609 */
2610 public $listbox = false;
2611 /**
2612 * @var integer if you are using $listbox === true to get an automatically
2613 * sized list box, the size of the list box will be the number of options,
2614 * or this number, whichever is smaller.
2615 */
2616 public $maxautosize = 10;
2617 /**
2618 * @var boolean if true, allow multiple selection. Only used if $listbox is true.
2619 */
2620 public $multiple = false;
2621 /**
2622 * @deprecated
2623 * @var string JavaScript to add as an onchange attribute. Do not use this.
2624 * Use the YUI even library instead.
2625 */
2626 public $script = '';
2627
2628 /* @see lib/moodle_html_component#prepare() */
2629 public function prepare() {
2630 if (empty($this->id)) {
2631 $this->id = 'menu' . str_replace(array('[', ']'), '', $this->name);
2632 }
2633 if (empty($this->classes)) {
2634 $this->set_classes(array('menu' . str_replace(array('[', ']'), '', $this->name)));
2635 }
2636 $this->add_class('select');
2637 parent::prepare();
2638 }
2639
2640 /**
2641 * This is a shortcut for making a simple select menu. It lets you specify
2642 * the options, name and selected option in one line of code.
2643 * @param array $options used to initialise {@link $options}.
2644 * @param string $name used to initialise {@link $name}.
2645 * @param string $selected used to initialise {@link $selected}.
2646 * @return moodle_select_menu A moodle_select_menu object with the three common fields initialised.
2647 */
2648 public static function make($options, $name, $selected = '') {
2649 $menu = new moodle_select_menu();
2650 $menu->options = $options;
2651 $menu->name = $name;
2652 $menu->selectedvalue = $selected;
2653 return $menu;
2654 }
2655
2656 /**
2657 * This is a shortcut for making a yes/no select menu.
2658 * @param string $name used to initialise {@link $name}.
2659 * @param string $selected used to initialise {@link $selected}.
2660 * @return moodle_select_menu A menu initialised with yes/no options.
2661 */
2662 public static function make_yes_no($name, $selected) {
2663 return self::make(array(0 => get_string('no'), 1 => get_string('yes')), $name, $selected);
2664 }
571fa828 2665}
2666
2667
a5cb8d69 2668/**
d4a03c00 2669 * This class represents how a block appears on a page.
2670 *
2671 * During output, each block instance is asked to return a block_contents object,
2672 * those are then passed to the $OUTPUT->block function for display.
2673 *
2674 * {@link $contents} should probably be generated using a moodle_block_..._renderer.
a5cb8d69 2675 *
d4a03c00 2676 * Other block-like things that need to appear on the page, for example the
2677 * add new block UI, are also represented as block_contents objects.
a5cb8d69 2678 *
2679 * @copyright 2009 Tim Hunt
2680 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2681 * @since Moodle 2.0
2682 */
2683class block_contents extends moodle_html_component {
d4a03c00 2684 /** @var int used to set $skipid. */
a5cb8d69 2685 protected static $idcounter = 1;
d4a03c00 2686
2687 const NOT_HIDEABLE = 0;
2688 const VISIBLE = 1;
2689 const HIDDEN = 2;
2690
a5cb8d69 2691 /**
d4a03c00 2692 * @param integer $skipid All the blocks (or things that look like blocks)
2693 * printed on a page are given a unique number that can be used to construct
2694 * id="" attributes. This is set automatically be the {@link prepare()} method.
2695 * Do not try to set it manually.
a5cb8d69 2696 */
d4a03c00 2697 public $skipid;
2698
2699 /**
2700 * @var integer If this is the contents of a real block, this should be set to
2701 * the block_instance.id. Otherwise this should be set to 0.
2702 */
2703 public $blockinstanceid = 0;
2704
a5cb8d69 2705 /**
d4a03c00 2706 * @var integer if this is a real block instance, and there is a corresponding
2707 * block_position.id for the block on this page, this should be set to that id.
2708 * Otherwise it should be 0.
2709 */
2710 public $blockpositionid = 0;
2711
2712 /**
2713 * @param array $attributes an array of attribute => value pairs that are put on the
2714 * outer div of this block. {@link $id} and {@link $classes} attributes should be set separately.
2715 */
2716 public $attributes = array();
2717
2718 /**
2719 * @param string $title The title of this block. If this came from user input,
2720 * it should already have had format_string() processing done on it. This will
2721 * be output inside <h2> tags. Please do not cause invalid XHTML.
a5cb8d69 2722 */
2723 public $title = '';
d4a03c00 2724
a5cb8d69 2725 /**
2726 * @param string $content HTML for the content
2727 */
2728 public $content = '';
d4a03c00 2729
a5cb8d69 2730 /**
2731 * @param array $list an alternative to $content, it you want a list of things with optional icons.
2732 */
d4a03c00 2733 public $footer = '';
2734
a5cb8d69 2735 /**
d4a03c00 2736 * Any small print that should appear under the block to explain to the
2737 * teacher about the block, for example 'This is a sticky block that was
2738 * added in the system context.'
2739 * @var string
a5cb8d69 2740 */
d4a03c00 2741 public $annotation = '';
2742
a5cb8d69 2743 /**
d4a03c00 2744 * @var integer one of the constants NOT_HIDEABLE, VISIBLE, HIDDEN. Whether
2745 * the user can toggle whether this block is visible.
a5cb8d69 2746 */
d4a03c00 2747 public $collapsible = self::NOT_HIDEABLE;
2748
a5cb8d69 2749 /**
d4a03c00 2750 * A (possibly empty) array of editing controls. Each element of this array
1936f20b 2751 * should be an array('url' => $url, 'icon' => $icon, 'caption' => $caption).
2752 * $icon is the icon name. Fed to $OUTPUT->old_icon_url.
d4a03c00 2753 * @var array
a5cb8d69 2754 */
d4a03c00 2755 public $controls = array();
a5cb8d69 2756
2757 /* @see lib/moodle_html_component#prepare() */
2758 public function prepare() {
2759 $this->skipid = self::$idcounter;
2760 self::$idcounter += 1;
2761 $this->add_class('sideblock');
d4a03c00 2762 if (empty($this->blockinstanceid) || !strip_tags($this->title)) {
2763 $this->collapsible = self::NOT_HIDEABLE;
2764 }
2765 if ($this->collapsible == self::HIDDEN) {
2766 $this->add_class('hidden');
2767 }
2768 if (!empty($this->controls)) {
2769 $this->add_class('block_with_controls');
2770 }
a5cb8d69 2771 parent::prepare();
2772 }
2773}
2774
2775
480b0720 2776/**
2777 * Holds all the information required to render a <table> by
2778 * {@see moodle_core_renderer::table()} or by an overridden version of that
2779 * method in a subclass.
2780 *
2781 * Example of usage:
2782 * $t = new html_table();
2783 * ... // set various properties of the object $t as described below
2784 * echo $OUTPUT->table($t);
2785 *
2786 * @copyright 2009 David Mudrak <david.mudrak@gmail.com>
2787 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2788 * @since Moodle 2.0
2789 */
2790class html_table extends moodle_html_component {
2791 /**
2792 * @var array of headings. The n-th array item is used as a heading of the n-th column.
2793 *
2794 * Example of usage:
2795 * $t->head = array('Student', 'Grade');
2796 */
2797 public $head;
2798 /**
2799 * @var array can be used to make a heading span multiple columns
2800 *
2801 * Example of usage:
2802 * $t->headspan = array(2,1);
2803 *
2804 * In this example, {@see html_table:$data} is supposed to have three columns. For the first two columns,
2805 * the same heading is used. Therefore, {@see html_table::$head} should consist of two items.
2806 */
2807 public $headspan;
2808 /**
2809 * @var array of column alignments. The value is used as CSS 'text-align' property. Therefore, possible
2810 * values are 'left', 'right', 'center' and 'justify'. Specify 'right' or 'left' from the perspective
2811 * of a left-to-right (LTR) language. For RTL, the values are flipped automatically.
2812 *
2813 * Examples of usage:
2814 * $t->align = array(null, 'right');
2815 * or
2816 * $t->align[1] = 'right';
2817 *
2818 */
2819 public $align;
2820 /**
2821 * @var array of column sizes. The value is used as CSS 'size' property.
2822 *
2823 * Examples of usage:
2824 * $t->size = array('50%', '50%');
2825 * or
2826 * $t->size[1] = '120px';
2827 */
2828 public $size;
2829 /**
2830 * @var array of wrapping information. The only possible value is 'nowrap' that sets the
2831 * CSS property 'white-space' to the value 'nowrap' in the given column.
2832 *
2833 * Example of usage:
2834 * $t->wrap = array(null, 'nowrap');
2835 */
2836 public $wrap;
2837 /**
2838 * @var array of arrays containing the data. Alternatively, if you have
2839 * $head specified, the string 'hr' (for horizontal ruler) can be used
2840 * instead of an array of cells data resulting in a divider rendered.
2841 *
2842 * Example if usage:
2843 * $row1 = array('Harry Potter', '76 %');
2844 * $row2 = array('Hermione Granger', '100 %');
2845 * $t->data = array($row1, $row2);
2846 */
2847 public $data;
2848 /**
2849 * @var string width of the table, percentage of the page prefered. Defaults to 80% of the page width.
2850 */
2851 public $width = '80%';
2852 /**
2853 * @var string alignment the whole table. Can be 'right', 'left' or 'center' (default).
2854 */
2855 public $tablealign = 'center';
2856 /**
2857 * @var int padding on each cell, in pixels
2858 */
2859 public $cellpadding = 5;
2860 /**
2861 * @var int spacing between cells, in pixels
2862 */
2863 public $cellspacing = 1;
2864 /**
2865 * @var array classes to add to particular rows, space-separated string.
2866 * Classes 'r0' or 'r1' are added automatically for every odd or even row,
2867 * respectively. Class 'lastrow' is added automatically for the last row
2868 * in the table.
2869 *
2870 * Example of usage:
2871 * $t->rowclasses[9] = 'tenth'
2872 */
2873 public $rowclasses;
2874 /**
2875 * @var array classes to add to every cell in a particular colummn,
2876 * space-separated string. Class 'cell' is added automatically by the renderer.
2877 * Classes 'c0' or 'c1' are added automatically for every odd or even column,
2878 * respectively. Class 'lastcol' is added automatically for all last cells
2879 * in a row.
2880 *
2881 * Example of usage:
2882 * $t->colclasses = array(null, 'grade');
2883 */
2884 public $colclasses;
2885 /**
2886 * @var string description of the contents for screen readers.
2887 */
2888 public $summary;
2889 /**
2890 * @var bool true causes the contents of the heading cells to be rotated 90 degrees.
2891 */
2892 public $rotateheaders = false;
2893
2894 /**
2895 * @see moodle_html_component::prepare()
2896 */
2897 public function prepare() {
2898 if (!empty($this->align)) {
2899 foreach ($this->align as $key => $aa) {
2900 if ($aa) {
2901 $this->align[$key] = 'text-align:'. fix_align_rtl($aa) .';'; // Fix for RTL languages
2902 } else {
2903 $this->align[$key] = '';
2904 }
2905 }
2906 }
2907 if (!empty($this->size)) {
2908 foreach ($this->size as $key => $ss) {
2909 if ($ss) {
2910 $this->size[$key] = 'width:'. $ss .';';
2911 } else {
2912 $this->size[$key] = '';
2913 }
2914 }
2915 }
2916 if (!empty($this->wrap)) {
2917 foreach ($this->wrap as $key => $ww) {
2918 if ($ww) {
2919 $this->wrap[$key] = 'white-space:nowrap;';
2920 } else {
2921 $this->wrap[$key] = '';
2922 }
2923 }
2924 }
2925 if (!empty($this->head)) {
2926 foreach ($this->head as $key => $val) {
2927 if (!isset($this->align[$key])) {
2928 $this->align[$key] = '';
2929 }
2930 if (!isset($this->size[$key])) {
2931 $this->size[$key] = '';
2932 }
2933 if (!isset($this->wrap[$key])) {
2934 $this->wrap[$key] = '';
2935 }
2936
2937 }
2938 }
75904dfa 2939 if (empty($this->classes)) { // must be done before align
2940 $this->set_classes(array('generaltable'));
2941 }
480b0720 2942 if (!empty($this->tablealign)) {
2943 $this->add_class('boxalign' . $this->tablealign);
2944 }
2945 if (!empty($this->rotateheaders)) {
2946 $this->add_class('rotateheaders');
2947 } else {
2948 $this->rotateheaders = false; // Makes life easier later.
2949 }
480b0720 2950 parent::prepare();
2951 }
2952
2953 public function __set($name, $value) {
2954 if ($name == 'rowclass') {
2955 debugging('rowclass[] has been deprecated for html_table ' .
2956 'and should be replaced with rowclasses[]. please fix the code.');
2957 $this->rowclasses = $value;
2958 } else {
2959 parent::__set($name, $value);
2960 }
2961 }
2962}
2963
2964
34a2777c 2965/**
2966 * A renderer that generates output for commandlines scripts.
2967 *
2968 * The implementation of this renderer is probably incomplete.
2969 *
2970 * @copyright 2009 Tim Hunt
2971 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2972 * @since Moodle 2.0
2973 */
2974class cli_core_renderer extends moodle_core_renderer {
2975 public function header() {
2976 output_starting_hook();
2977 return $this->page->heading . "\n";
2978 }
2979
2980 public function heading($text, $level, $classes = 'main', $id = '') {
2981 $text .= "\n";
2982 switch ($level) {
2983 case 1:
2984 return '=>' . $text;
2985 case 2:
2986 return '-->' . $text;
2987 default:
2988 return $text;
2989 }
2990 }
2991
60a5b188 2992 public function fatal_error($message, $moreinfourl, $link, $backtrace,
34a2777c 2993 $debuginfo = null, $showerrordebugwarning = false) {
2994 $output = "!!! $message !!!\n";
2995
2996 if (debugging('', DEBUG_DEVELOPER)) {
2997 if (!empty($debuginfo)) {
2998 $this->notification($debuginfo, 'notifytiny');
2999 }
3000 if (!empty($backtrace)) {
3001 $this->notification('Stack trace: ' . format_backtrace($backtrace, true), 'notifytiny');
3002 }
3003 }
3004 }
3005
3006 public function notification($message, $classes = 'notifyproblem') {
3007 $message = clean_text($message);
60a5b188 3008 if ($classes === 'notifysuccess') {
34a2777c 3009 return "++ $message ++\n";
3010 }
3011 return "!! $message !!\n";
3012 }
3013}
3014
b7009474 3015
3016/**
3017 * Output CSS while replacing constants/variables. See MDL-6798 for details
3018 *
3019 * Information from Urs Hunkler:
3020 *
3021 * This is an adaptation of Shaun Inman's "CSS Server-side Constants" for Moodle.
3022 * http://www.shauninman.com/post/heap/2005/08/09/css_constants
3023 *
3024 * To use, specify $THEME->customcssoutputfunction = 'output_css_replacing_constants';
3025 * in your theme's config.php file.
3026 *
3027 * The constant definitions are written into a separate CSS file named like
3028 * constants.css and loaded first in config.php. You can use constants for any
3029 * CSS properties. The constant definition looks like:
3030 * <code>
3031 * \@server constants {
3032 * fontColor: #3a2830;
3033 * aLink: #116699;
3034 * aVisited: #AA2200;
3035 * aHover: #779911;
3036 * pageBackground: #FFFFFF;
3037 * backgroundColor: #EEEEEE;
3038 * backgroundSideblockHeader: #a8a4e9;
3039 * fontcolorSideblockHeader: #222222;
3040 * color1: #98818b;
3041 * color2: #bd807b;
3042 * color3: #f9d1d7;
3043 * color4: #e8d4d8;
3044 * }
3045 * </code>
3046 *
3047 * The lines in the CSS files using CSS constants look like:
3048 * <code>
3049 * body {
3050 * font-size: 100%;
3051 * background-color: pageBackground;
3052 * color: fontColor;
3053 * font-family: 'Bitstream Vera Serif', georgia, times, serif;
3054 * margin: 0;
3055 * padding: 0;
3056 * }
3057 * div#page {
3058 * margin: 0 10px;
3059 * padding-top: 5px;
3060 * border-top-width: 10px;
3061 * border-top-style: solid;
3062 * border-top-color: color3;
3063 * }
3064 * div.clearer {
3065 * clear: both;
3066 * }
3067 * a:link {
3068 * color: aLink;
fdeb7fa1 3069 * }
b7009474 3070 * </code>
3071 *
3072 * @param array $files an arry of the CSS fiels that need to be output.
fdeb7fa1 3073 * @param array $toreplace for convenience. If you are going to output the names
3074 * of the css files, for debugging purposes, then you should output
3075 * str_replace($toreplace, '', $file); becuase it looks prettier.
b7009474 3076 */
fdeb7fa1 3077function output_css_replacing_constants($files, $toreplace) {
b7009474 3078 // Get all the CSS.
b7009474 3079 ob_start();
3080 foreach ($files as $file) {
3081 $shortname = str_replace($toreplace, '', $file);
3082 echo '/******* ' . $shortname . " start *******/\n\n";
3083 @include_once($file);
3084 echo '/******* ' . $shortname . " end *******/\n\n";
3085 }
3086 $css = ob_get_contents();
3087 ob_end_clean();
3088
3089 if (preg_match_all("/@server\s+(?:variables|constants)\s*\{\s*([^\}]+)\s*\}\s*/i", $css, $matches)) {
3090 $variables = array();
3091 foreach ($matches[0] as $key => $server) {
3092 $css = str_replace($server, '', $css);
3093 preg_match_all("/([^:\}\s]+)\s*:\s*([^;\}]+);/", $matches[1][$key], $vars);
3094 foreach ($vars[1] as $var => $value) {
3095 $variables[$value] = $vars[2][$var];
3096 }
3097 }
3098 $css = str_replace(array_keys($variables), array_values($variables), $css);
3099 }
3100 echo $css;
3101}
3102
3103/**
3104 * This CSS output function will link to CSS files rather than including them
3105 * inline.
3106 *
3107 * The single CSS files can then be edited and saved with interactive
3108 * CSS editors like CSSEdit. Any files that have a .php extension are still included
3109 * inline.
3110 *
3111 * @param array $files an arry of the CSS fiels that need to be output.
fdeb7fa1 3112 * @param array $toreplace for convenience. If you are going to output the names
3113 * of the css files, for debugging purposes, then you should output
3114 * str_replace($toreplace, '', $file); becuase it looks prettier.
b7009474 3115 */
fdeb7fa1 3116function output_css_for_css_edit($files, $toreplace) {
b7009474 3117 foreach ($files as $file) {
3118 $shortname = str_replace($toreplace, '', $file);
3119 echo '/* @group ' . $shortname . " */\n\n";
3120 if (strpos($file, '.css') !== false) {
3121 echo '@import url("' . $file . '");'."\n\n";
3122 } else {
3123 @include_once($file);
3124 }
3125 echo "/* @end */\n\n";
3126 }
f77fcb5a 3127}