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