Commit | Line | Data |
---|---|---|
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 | ||
78946b9b PS |
18 | /** |
19 | * Functions for generating the HTML that Moodle should output. | |
20 | * | |
21 | * Please see http://docs.moodle.org/en/Developement:How_Moodle_outputs_HTML | |
22 | * for an overview. | |
23 | * | |
24 | * @package moodlecore | |
25 | * @copyright 2009 Tim Hunt | |
26 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
27 | */ | |
28 | ||
d9c8f425 | 29 | require_once($CFG->libdir.'/outputcomponents.php'); |
30 | require_once($CFG->libdir.'/outputactions.php'); | |
31 | require_once($CFG->libdir.'/outputfactories.php'); | |
d9c8f425 | 32 | require_once($CFG->libdir.'/outputrenderers.php'); |
0bb38e8c | 33 | require_once($CFG->libdir.'/outputrequirementslib.php'); |
d9c8f425 | 34 | |
571fa828 | 35 | /** |
78946b9b PS |
36 | * Invalidate all server and client side caches. |
37 | * @return void | |
38 | */ | |
39 | function theme_reset_all_caches() { | |
40 | global $CFG; | |
41 | require_once("$CFG->libdir/filelib.php"); | |
42 | ||
43 | set_config('themerev', empty($CFG->themerev) ? 1 : $CFG->themerev+1); | |
44 | fulldelete("$CFG->dataroot/cache/theme"); | |
45 | } | |
46 | ||
47 | /** | |
48 | * Enable or disable theme designer mode. | |
49 | * @param bool $state | |
50 | * @return void | |
51 | */ | |
52 | function theme_set_designer_mod($state) { | |
53 | theme_reset_all_caches(); | |
54 | set_config('themedesignermode', (int)!empty($state)); | |
55 | } | |
56 | ||
57 | /** | |
58 | * Returns current theme revision number. | |
59 | * @return int | |
571fa828 | 60 | */ |
78946b9b PS |
61 | function theme_get_revision() { |
62 | global $CFG; | |
63 | ||
64 | if (empty($CFG->themedesignermode)) { | |
65 | if (empty($CFG->themerev)) { | |
66 | return -1; | |
67 | } else { | |
68 | return $CFG->themerev; | |
69 | } | |
70 | ||
71 | } else { | |
72 | return -1; | |
73 | } | |
74 | } | |
75 | ||
571fa828 | 76 | |
571fa828 | 77 | /** |
fdeb7fa1 | 78 | * This class represents the configuration variables of a Moodle theme. |
79 | * | |
80 | * All the variables with access: public below (with a few exceptions that are marked) | |
81 | * are the properties you can set in your theme's config.php file. | |
82 | * | |
83 | * There are also some methods and protected variables that are part of the inner | |
84 | * workings of Moodle's themes system. If you are just editing a theme's config.php | |
fa1afe32 | 85 | * file, you can just ignore those, and the following information for developers. |
ebebf55c | 86 | * |
87 | * Normally, to create an instance of this class, you should use the | |
88 | * {@link theme_config::load()} factory method to load a themes config.php file. | |
fa1afe32 | 89 | * However, normally you don't need to bother, because moodle_page (that is, $PAGE) |
fdeb7fa1 | 90 | * will create one for you, accessible as $PAGE->theme. |
571fa828 | 91 | * |
92 | * @copyright 2009 Tim Hunt | |
93 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
94 | * @since Moodle 2.0 | |
95 | */ | |
ebebf55c | 96 | class theme_config { |
5f0baa43 PS |
97 | /** |
98 | * @var string default theme, used when requested theme not found | |
5f0baa43 | 99 | */ |
90723839 | 100 | const DEFAULT_THEME = 'standard'; |
5f0baa43 | 101 | |
ebebf55c | 102 | /** |
78946b9b PS |
103 | * You can base your theme on other themes by linking to the other theme as |
104 | * parents. This lets you use the CSS and layouts from the other themes | |
105 | * (see {@link $layouts}). | |
fdeb7fa1 | 106 | * That makes it easy to create a new theme that is similar to another one |
107 | * but with a few changes. In this theme's CSS you only need to override | |
108 | * those rules you want to change. | |
fdeb7fa1 | 109 | * |
78946b9b | 110 | * @var array |
fdeb7fa1 | 111 | */ |
78946b9b | 112 | public $parents; |
571fa828 | 113 | |
fdeb7fa1 | 114 | /** |
78946b9b PS |
115 | * The names of all the stylesheets from this theme that you would |
116 | * like included, in order. Give the names of the files without .css. | |
fdeb7fa1 | 117 | * |
78946b9b | 118 | * @var array |
fdeb7fa1 | 119 | */ |
78946b9b | 120 | public $sheets = array(); |
571fa828 | 121 | |
fdeb7fa1 | 122 | /** |
2e7dbbd7 | 123 | * The names of all the stylesheets from parents that should be excluded. |
78946b9b PS |
124 | * true value may be used to specify all parents or all themes from one parent. |
125 | * If no value specified value from parent theme used. | |
fdeb7fa1 | 126 | * |
78946b9b | 127 | * @var array or arrays, true means all, null means use value from parent |
fdeb7fa1 | 128 | */ |
78946b9b | 129 | public $parents_exclude_sheets = null; |
571fa828 | 130 | |
fdeb7fa1 | 131 | /** |
78946b9b PS |
132 | * List of plugin sheets to be excluded. |
133 | * If no value specified value from parent theme used. | |
134 | * | |
135 | * @var array of full plugin names, null means use value from parent | |
fdeb7fa1 | 136 | */ |
78946b9b | 137 | public $plugins_exclude_sheets = null; |
571fa828 | 138 | |
fdeb7fa1 | 139 | /** |
78946b9b PS |
140 | * List of style sheets that are included in the text editor bodies. |
141 | * Sheets from parent themes are used automatically and can not be excluded. | |
fdeb7fa1 | 142 | * |
78946b9b | 143 | * @var array |
fdeb7fa1 | 144 | */ |
78946b9b | 145 | public $editor_sheets = array(); |
571fa828 | 146 | |
38aafea2 PS |
147 | /** |
148 | * The names of all the javascript files this theme that you would | |
04c01408 | 149 | * like included from head, in order. Give the names of the files without .js. |
38aafea2 PS |
150 | * |
151 | * @var array | |
152 | */ | |
153 | public $javascripts = array(); | |
154 | ||
04c01408 PS |
155 | /** |
156 | * The names of all the javascript files this theme that you would | |
157 | * like included from footer, in order. Give the names of the files without .js. | |
158 | * | |
159 | * @var array | |
160 | */ | |
161 | public $javascripts_footer = array(); | |
162 | ||
38aafea2 | 163 | /** |
b2ae9661 | 164 | * The names of all the javascript files from parents that should be excluded. |
38aafea2 PS |
165 | * true value may be used to specify all parents or all themes from one parent. |
166 | * If no value specified value from parent theme used. | |
167 | * | |
168 | * @var array or arrays, true means all, null means use value from parent | |
169 | */ | |
170 | public $parents_exclude_javascripts = null; | |
171 | ||
fdeb7fa1 | 172 | /** |
78946b9b | 173 | * Which file to use for each page layout. |
fdeb7fa1 | 174 | * |
78946b9b PS |
175 | * This is an array of arrays. The keys of the outer array are the different layouts. |
176 | * Pages in Moodle are using several different layouts like 'normal', 'course', 'home', | |
177 | * 'popup', 'form', .... The most reliable way to get a complete list is to look at | |
178 | * {@link http://cvs.moodle.org/moodle/theme/base/config.php?view=markup the base theme config.php file}. | |
d4a03c00 | 179 | * That file also has a good example of how to set this setting. |
fdeb7fa1 | 180 | * |
78946b9b | 181 | * For each layout, the value in the outer array is an array that describes |
d4a03c00 | 182 | * how you want that type of page to look. For example |
183 | * <pre> | |
184 | * $THEME->layouts = array( | |
39df78bf | 185 | * // Most pages - if we encounter an unknown or a missing page type, this one is used. |
191b267b | 186 | * 'standard' => array( |
78946b9b PS |
187 | * 'theme' = 'mytheme', |
188 | * 'file' => 'normal.php', | |
d4a03c00 | 189 | * 'regions' => array('side-pre', 'side-post'), |
190 | * 'defaultregion' => 'side-post' | |
191 | * ), | |
192 | * // The site home page. | |
193 | * 'home' => array( | |
78946b9b PS |
194 | * 'theme' = 'mytheme', |
195 | * 'file' => 'home.php', | |
d4a03c00 | 196 | * 'regions' => array('side-pre', 'side-post'), |
197 | * 'defaultregion' => 'side-post' | |
198 | * ), | |
199 | * // ... | |
200 | * ); | |
201 | * </pre> | |
fdeb7fa1 | 202 | * |
78946b9b PS |
203 | * 'theme' name of the theme where is the layout located |
204 | * 'file' is the layout file to use for this type of page. | |
205 | * layout files are stored in layout subfolder | |
d4a03c00 | 206 | * 'regions' This lists the regions on the page where blocks may appear. For |
207 | * each region you list here, your layout file must include a call to | |
208 | * <pre> | |
209 | * echo $OUTPUT->blocks_for_region($regionname); | |
210 | * </pre> | |
211 | * or equivalent so that the blocks are actually visible. | |
fdeb7fa1 | 212 | * |
d4a03c00 | 213 | * 'defaultregion' If the list of regions is non-empty, then you must pick |
214 | * one of the one of them as 'default'. This has two meanings. First, this is | |
215 | * where new blocks are added. Second, if there are any blocks associated with | |
fa1afe32 | 216 | * the page, but in non-existent regions, they appear here. (Imaging, for example, |
d4a03c00 | 217 | * that someone added blocks using a different theme that used different region |
218 | * names, and then switched to this theme.) | |
fdeb7fa1 | 219 | * |
220 | * @var array | |
221 | */ | |
d4a03c00 | 222 | public $layouts = array(); |
571fa828 | 223 | |
fdeb7fa1 | 224 | /** |
225 | * With this you can control the colours of the big MP3 player | |
226 | * that is used for MP3 resources. | |
227 | * | |
228 | * @var string | |
229 | */ | |
ebebf55c | 230 | 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 | 231 | |
fdeb7fa1 | 232 | /** |
78946b9b PS |
233 | * With this you can control the colours of the small MP3 player |
234 | * that is used elsewhere. | |
235 | * | |
fdeb7fa1 | 236 | * @var string |
237 | */ | |
ebebf55c | 238 | public $filter_mediaplugin_colors = 'bgColour=000000&btnColour=ffffff&btnBorderColour=cccccc&iconColour=000000&iconOverColour=00cc00&trackColour=cccccc&handleColour=ffffff&loaderColour=ffffff&waitForPlay=yes'; |
239 | ||
fdeb7fa1 | 240 | /** |
241 | * Name of the renderer factory class to use. | |
242 | * | |
243 | * This is an advanced feature. Moodle output is generated by 'renderers', | |
244 | * you can customise the HTML that is output by writing custom renderers, | |
245 | * and then you need to specify 'renderer factory' so that Moodle can find | |
246 | * your renderers. | |
247 | * | |
248 | * There are some renderer factories supplied with Moodle. Please follow these | |
249 | * links to see what they do. | |
250 | * <ul> | |
251 | * <li>{@link standard_renderer_factory} - the default.</li> | |
252 | * <li>{@link theme_overridden_renderer_factory} - use this if you want to write | |
78946b9b | 253 | * your own custom renderers in a lib.php file in this theme (or the parent theme).</li> |
fdeb7fa1 | 254 | * </ul> |
255 | * | |
256 | * @var string name of a class implementing the {@link renderer_factory} interface. | |
257 | */ | |
ebebf55c | 258 | public $rendererfactory = 'standard_renderer_factory'; |
ebebf55c | 259 | |
fdeb7fa1 | 260 | /** |
78946b9b | 261 | * Function to do custom CSS post-processing. |
fdeb7fa1 | 262 | * |
78946b9b PS |
263 | * This is an advanced feature. If you want to do custom post-processing on the |
264 | * CSS before it is output (for example, to replace certain variable names | |
265 | * with particular values) you can give the name of a function here. | |
fdeb7fa1 | 266 | * |
78946b9b | 267 | * @var string the name of a function. |
fdeb7fa1 | 268 | */ |
78946b9b | 269 | public $csspostprocess = null; |
571fa828 | 270 | |
271 | /** | |
78946b9b PS |
272 | * Accessibility: Right arrow-like character is |
273 | * used in the breadcrumb trail, course navigation menu | |
274 | * (previous/next activity), calendar, and search forum block. | |
275 | * If the theme does not set characters, appropriate defaults | |
276 | * are set automatically. Please DO NOT | |
277 | * use < > » - these are confusing for blind users. | |
fdeb7fa1 | 278 | * |
78946b9b PS |
279 | * @var string |
280 | */ | |
281 | public $rarrow = null; | |
282 | ||
283 | /** | |
284 | * Accessibility: Right arrow-like character is | |
285 | * used in the breadcrumb trail, course navigation menu | |
286 | * (previous/next activity), calendar, and search forum block. | |
287 | * If the theme does not set characters, appropriate defaults | |
288 | * are set automatically. Please DO NOT | |
289 | * use < > » - these are confusing for blind users. | |
ebebf55c | 290 | * |
78946b9b | 291 | * @var string |
571fa828 | 292 | */ |
78946b9b PS |
293 | public $larrow = null; |
294 | ||
295 | ||
296 | //==Following properties are not configurable from theme config.php== | |
571fa828 | 297 | |
fdeb7fa1 | 298 | /** |
78946b9b PS |
299 | * The name of this theme. Set automatically when this theme is |
300 | * loaded. This can not be set in theme config.php | |
301 | * @var string | |
fdeb7fa1 | 302 | */ |
78946b9b | 303 | public $name; |
fdeb7fa1 | 304 | |
305 | /** | |
78946b9b PS |
306 | * the folder where this themes files are stored. This is set |
307 | * automatically. This can not be set in theme config.php | |
308 | * @var string | |
fdeb7fa1 | 309 | */ |
78946b9b | 310 | public $dir; |
fdeb7fa1 | 311 | |
312 | /** | |
78946b9b PS |
313 | * Theme settings stored in config_plugins table. |
314 | * This can not be set in theme config.php | |
315 | * @var object | |
316 | */ | |
317 | public $setting = null; | |
318 | ||
d2c394f3 SH |
319 | /** |
320 | * If set to true and the theme enables the dock then blocks will be able | |
321 | * to be moved to the special dock | |
322 | * @var bool | |
323 | */ | |
324 | public $enable_dock = false; | |
325 | ||
78946b9b PS |
326 | /** |
327 | * Instance of the renderer_factory implementation | |
fdeb7fa1 | 328 | * we are using. Implementation detail. |
78946b9b | 329 | * @var renderer_factory |
fdeb7fa1 | 330 | */ |
331 | protected $rf = null; | |
332 | ||
333 | /** | |
78946b9b PS |
334 | * List of parent config objects. |
335 | * @var array list of parent configs | |
336 | **/ | |
337 | protected $parent_configs = array(); | |
fdeb7fa1 | 338 | |
571fa828 | 339 | /** |
ebebf55c | 340 | * Load the config.php file for a particular theme, and return an instance |
341 | * of this class. (That is, this is a factory method.) | |
342 | * | |
343 | * @param string $themename the name of the theme. | |
344 | * @return theme_config an instance of this class. | |
571fa828 | 345 | */ |
ebebf55c | 346 | public static function load($themename) { |
347 | global $CFG; | |
571fa828 | 348 | |
78946b9b PS |
349 | // load theme settings from db |
350 | try { | |
351 | $settings = get_config('theme_'.$themename); | |
352 | } catch (dml_exception $e) { | |
da0c0e25 | 353 | // most probably moodle tables not created yet |
78946b9b PS |
354 | $settings = new object(); |
355 | } | |
ebebf55c | 356 | |
78946b9b PS |
357 | if ($config = theme_config::find_theme_config($themename, $settings)) { |
358 | return new theme_config($config); | |
5f0baa43 PS |
359 | |
360 | } else if ($themename == theme_config::DEFAULT_THEME) { | |
361 | throw new coding_exception('Default theme '.theme_config::DEFAULT_THEME.' not available or broken!'); | |
362 | ||
78946b9b PS |
363 | } else { |
364 | // bad luck, the requested theme has some problems - admin see details in theme config | |
5f0baa43 | 365 | return new theme_config(theme_config::find_theme_config(theme_config::DEFAULT_THEME, $settings)); |
78946b9b PS |
366 | } |
367 | } | |
ebebf55c | 368 | |
78946b9b PS |
369 | /** |
370 | * Theme diagnostic code. It is very problematic to send debug output | |
371 | * to the actual CSS file, instead this functions is supposed to | |
ae5b6801 | 372 | * diagnose given theme and highlights all potential problems. |
78946b9b PS |
373 | * This information should be available from the theme selection page |
374 | * or some other debug page for theme designers. | |
375 | * | |
376 | * @param string $themename | |
377 | * @return array description of problems | |
378 | */ | |
379 | public static function diagnose($themename) { | |
5ef719e7 | 380 | //TODO: MDL-21108 |
78946b9b PS |
381 | return array(); |
382 | } | |
383 | ||
384 | /** | |
385 | * Private constructor, can be called only from the factory method. | |
386 | * @param stdClass $config | |
387 | */ | |
388 | private function __construct($config) { | |
389 | global $CFG; //needed for included lib.php files | |
390 | ||
391 | $this->settings = $config->settings; | |
392 | $this->name = $config->name; | |
393 | $this->dir = $config->dir; | |
394 | ||
395 | if ($this->name != 'base') { | |
396 | $baseconfig = theme_config::find_theme_config('base', $this->settings); | |
397 | } else { | |
398 | $baseconfig = $config; | |
571fa828 | 399 | } |
ebebf55c | 400 | |
fef27578 | 401 | $configurable = array('parents', 'sheets', 'parents_exclude_sheets', 'plugins_exclude_sheets', 'javascripts', 'javascripts_footer', |
d2c394f3 | 402 | 'parents_exclude_javascripts', 'layouts', 'resource_mp3player_colors', 'enable_dock', |
78946b9b | 403 | 'filter_mediaplugin_colors', 'rendererfactory', 'csspostprocess', 'editor_sheets', 'rarrow', 'larrow'); |
ebebf55c | 404 | |
78946b9b PS |
405 | foreach ($config as $key=>$value) { |
406 | if (in_array($key, $configurable)) { | |
407 | $this->$key = $value; | |
408 | } | |
409 | } | |
410 | ||
411 | // verify all parents and load configs and renderers | |
412 | foreach ($this->parents as $parent) { | |
413 | if ($parent == 'base') { | |
414 | $parent_config = $baseconfig; | |
415 | } else if (!$parent_config = theme_config::find_theme_config($parent, $this->settings)) { | |
416 | // this is not good - better exclude faulty parents | |
417 | continue; | |
418 | } | |
419 | $libfile = $parent_config->dir.'/lib.php'; | |
420 | if (is_readable($libfile)) { | |
421 | // theme may store various function here | |
422 | include_once($libfile); | |
423 | } | |
424 | $renderersfile = $parent_config->dir.'/renderers.php'; | |
425 | if (is_readable($renderersfile)) { | |
426 | // may contain core and plugin renderers and renderer factory | |
427 | include_once($renderersfile); | |
428 | } | |
429 | $this->parent_configs[$parent] = $parent_config; | |
430 | $rendererfile = $parent_config->dir.'/renderers.php'; | |
431 | if (is_readable($rendererfile)) { | |
432 | // may contain core and plugin renderers and renderer factory | |
433 | include_once($rendererfile); | |
434 | } | |
435 | } | |
436 | $libfile = $this->dir.'/lib.php'; | |
437 | if (is_readable($libfile)) { | |
438 | // theme may store various function here | |
439 | include_once($libfile); | |
440 | } | |
441 | $rendererfile = $this->dir.'/renderers.php'; | |
442 | if (is_readable($rendererfile)) { | |
443 | // may contain core and plugin renderers and renderer factory | |
444 | include_once($rendererfile); | |
445 | } | |
446 | ||
447 | // cascade all layouts properly | |
448 | foreach ($baseconfig->layouts as $layout=>$value) { | |
449 | if (!isset($this->layouts[$layout])) { | |
450 | foreach ($this->parent_configs as $parent_config) { | |
451 | if (isset($parent_config->layouts[$layout])) { | |
452 | $this->layouts[$layout] = $parent_config->layouts[$layout]; | |
453 | continue 2; | |
454 | } | |
455 | } | |
456 | $this->layouts[$layout] = $value; | |
457 | } | |
458 | } | |
459 | ||
460 | //fix arrows if needed | |
461 | $this->check_theme_arrows(); | |
571fa828 | 462 | } |
463 | ||
78946b9b PS |
464 | /* |
465 | * Checks if arrows $THEME->rarrow, $THEME->larrow have been set (theme/-/config.php). | |
466 | * If not it applies sensible defaults. | |
467 | * | |
468 | * Accessibility: right and left arrow Unicode characters for breadcrumb, calendar, | |
469 | * search forum block, etc. Important: these are 'silent' in a screen-reader | |
470 | * (unlike > »), and must be accompanied by text. | |
34a2777c | 471 | */ |
78946b9b PS |
472 | private function check_theme_arrows() { |
473 | if (!isset($this->rarrow) and !isset($this->larrow)) { | |
474 | // Default, looks good in Win XP/IE 6, Win/Firefox 1.5, Win/Netscape 8... | |
475 | // Also OK in Win 9x/2K/IE 5.x | |
476 | $this->rarrow = '►'; | |
477 | $this->larrow = '◄'; | |
478 | if (empty($_SERVER['HTTP_USER_AGENT'])) { | |
479 | $uagent = ''; | |
ebebf55c | 480 | } else { |
78946b9b PS |
481 | $uagent = $_SERVER['HTTP_USER_AGENT']; |
482 | } | |
483 | if (false !== strpos($uagent, 'Opera') | |
484 | || false !== strpos($uagent, 'Mac')) { | |
485 | // Looks good in Win XP/Mac/Opera 8/9, Mac/Firefox 2, Camino, Safari. | |
486 | // Not broken in Mac/IE 5, Mac/Netscape 7 (?). | |
487 | $this->rarrow = '▶'; | |
488 | $this->larrow = '◀'; | |
489 | } | |
490 | elseif (false !== strpos($uagent, 'Konqueror')) { | |
491 | $this->rarrow = '→'; | |
492 | $this->larrow = '←'; | |
493 | } | |
494 | elseif (isset($_SERVER['HTTP_ACCEPT_CHARSET']) | |
495 | && false === stripos($_SERVER['HTTP_ACCEPT_CHARSET'], 'utf-8')) { | |
496 | // (Win/IE 5 doesn't set ACCEPT_CHARSET, but handles Unicode.) | |
497 | // To be safe, non-Unicode browsers! | |
498 | $this->rarrow = '>'; | |
499 | $this->larrow = '<'; | |
500 | } | |
501 | ||
502 | /// RTL support - in RTL languages, swap r and l arrows | |
503 | if (right_to_left()) { | |
504 | $t = $this->rarrow; | |
505 | $this->rarrow = $this->larrow; | |
506 | $this->larrow = $t; | |
ebebf55c | 507 | } |
ebebf55c | 508 | } |
78946b9b | 509 | } |
ebebf55c | 510 | |
78946b9b PS |
511 | /** |
512 | * Returns output renderer prefixes, these are used when looking | |
513 | * for the overriden renderers in themes. | |
514 | * @return array | |
515 | */ | |
516 | public function renderer_prefixes() { | |
517 | global $CFG; // just in case the included files need it | |
518 | ||
596af93a | 519 | $prefixes = array('theme_'.$this->name); |
78946b9b PS |
520 | |
521 | foreach ($this->parent_configs as $parent) { | |
522 | $prefixes[] = 'theme_'.$parent->name; | |
523 | } | |
524 | ||
525 | return $prefixes; | |
34a2777c | 526 | } |
527 | ||
571fa828 | 528 | /** |
78946b9b PS |
529 | * Returns the stylesheet URL of this editor content |
530 | * @param bool $encoded false means use & and true use & in URLs | |
531 | * @return string | |
571fa828 | 532 | */ |
78946b9b PS |
533 | public function editor_css_url($encoded=true) { |
534 | global $CFG; | |
535 | ||
536 | $rev = theme_get_revision(); | |
537 | ||
538 | if ($rev > -1) { | |
539 | $params = array('theme'=>$this->name,'rev'=>$rev, 'type'=>'editor'); | |
540 | return new moodle_url($CFG->httpswwwroot.'/theme/styles.php', $params); | |
541 | } else { | |
542 | $params = array('theme'=>$this->name, 'type'=>'editor'); | |
543 | return new moodle_url($CFG->httpswwwroot.'/theme/styles_debug.php', $params); | |
571fa828 | 544 | } |
571fa828 | 545 | } |
546 | ||
547 | /** | |
78946b9b PS |
548 | * Returns the content of the CSS to be used in editor content |
549 | * @return string | |
571fa828 | 550 | */ |
78946b9b PS |
551 | public function editor_css_content() { |
552 | global $CFG; | |
553 | ||
554 | $css = ''; | |
555 | ||
556 | // first editor plugins | |
557 | $plugins = get_plugin_list('editor'); | |
558 | foreach ($plugins as $plugin=>$fulldir) { | |
559 | $sheetfile = "$fulldir/editor_styles.css"; | |
560 | if (is_readable($sheetfile)) { | |
561 | $css .= "/*** Editor $plugin content CSS ***/\n\n" . file_get_contents($sheetfile) . "\n\n"; | |
562 | } | |
563 | } | |
564 | // then parent themes | |
565 | foreach (array_reverse($this->parent_configs) as $parent_config) { // base first, the immediate parent last | |
566 | if (empty($parent_config->editor_sheets)) { | |
567 | continue; | |
568 | } | |
569 | foreach ($parent_config->editor_sheets as $sheet) { | |
570 | $sheetfile = "$parent_config->dir/$sheet.css"; | |
571 | if (is_readable($sheetfile)) { | |
572 | $css .= "/*** Parent theme $parent/$sheet ***/\n\n" . file_get_contents($sheetfile) . "\n\n"; | |
573 | } | |
574 | } | |
575 | } | |
576 | // finally this theme | |
577 | if (!empty($this->editor_sheets)) { | |
578 | foreach ($this->editor_sheets as $sheet) { | |
579 | $sheetfile = "$this->dir/$sheet.css"; | |
580 | if (is_readable($sheetfile)) { | |
581 | $css .= "/*** Theme $sheet ***/\n\n" . file_get_contents($sheetfile) . "\n\n"; | |
582 | } | |
583 | } | |
584 | } | |
585 | ||
586 | return $this->post_process($css); | |
571fa828 | 587 | } |
588 | ||
589 | /** | |
78946b9b PS |
590 | * Get the stylesheet URL of this theme |
591 | * @param bool $encoded false means use & and true use & in URLs | |
38aafea2 | 592 | * @return array of moodle_url |
571fa828 | 593 | */ |
efaa4c08 | 594 | public function css_urls(moodle_page $page) { |
78946b9b PS |
595 | global $CFG; |
596 | ||
597 | $rev = theme_get_revision(); | |
598 | ||
efaa4c08 | 599 | $urls = array(); |
efaa4c08 | 600 | |
78946b9b PS |
601 | if ($rev > -1) { |
602 | $params = array('theme'=>$this->name,'rev'=>$rev); | |
603 | if (check_browser_version('MSIE', 5) and !check_browser_version('MSIE', 8)) { | |
604 | $params['type'] = 'ie'; | |
605 | } | |
efaa4c08 | 606 | $urls[] = new moodle_url($CFG->httpswwwroot.'/theme/styles.php', $params); |
78946b9b PS |
607 | |
608 | } else { | |
7829cf79 PS |
609 | // find out the current CSS and cache it now for 5 seconds |
610 | // the point is to construct the CSS only once and pass it through the | |
611 | // dataroot to the script that actually serves the sheets | |
d6b64d15 PS |
612 | if (!defined('THEME_DESIGNER_CACHE_LIFETIME')) { |
613 | define('THEME_DESIGNER_CACHE_LIFETIME', 4); // this can be also set in config.php | |
614 | } | |
7829cf79 PS |
615 | $candidatesheet = "$CFG->dataroot/cache/theme/$this->name/designer.ser"; |
616 | if (!file_exists($candidatesheet)) { | |
617 | $css = $this->css_content(); | |
618 | check_dir_exists(dirname($candidatesheet), true, true); | |
619 | file_put_contents($candidatesheet, serialize($css)); | |
620 | ||
d6b64d15 | 621 | } else if (filemtime($candidatesheet) > time() - THEME_DESIGNER_CACHE_LIFETIME) { |
7829cf79 PS |
622 | if ($css = file_get_contents($candidatesheet)) { |
623 | $css = unserialize($css); | |
624 | } else { | |
625 | unlink($candidatesheet); | |
626 | $css = $this->css_content(); | |
627 | } | |
628 | ||
629 | } else { | |
630 | unlink($candidatesheet); | |
631 | $css = $this->css_content(); | |
632 | file_put_contents($candidatesheet, serialize($css)); | |
633 | } | |
634 | ||
c724f835 | 635 | $baseurl = $CFG->httpswwwroot.'/theme/styles_debug.php'; |
c09e1a2c PS |
636 | |
637 | if (check_browser_version('MSIE', 5)) { | |
638 | // lalala, IE does not allow more than 31 linked CSS files from main document | |
639 | $urls[] = new moodle_url($CFG->httpswwwroot.'/theme/styles_debug.php', array('theme'=>$this->name, 'type'=>'ie')); | |
640 | ||
641 | } else { | |
642 | foreach ($css['plugins'] as $plugin=>$unused) { | |
643 | $urls[] = new moodle_url($baseurl, array('theme'=>$this->name,'type'=>'plugin', 'subtype'=>$plugin)); | |
644 | } | |
645 | foreach ($css['parents'] as $parent=>$sheets) { | |
646 | foreach ($sheets as $sheet=>$unused2) { | |
647 | $urls[] = new moodle_url($baseurl, array('theme'=>$this->name,'type'=>'parent', 'subtype'=>$parent, 'sheet'=>$sheet)); | |
648 | } | |
649 | } | |
650 | foreach ($css['theme'] as $sheet=>$unused) { | |
651 | $urls[] = new moodle_url($baseurl, array('sheet'=>$sheet, 'theme'=>$this->name, 'type'=>'theme')); // sheet first in order to make long urls easier to read | |
78946b9b | 652 | } |
78946b9b | 653 | } |
78946b9b | 654 | } |
efaa4c08 PS |
655 | |
656 | return $urls; | |
571fa828 | 657 | } |
34a2777c | 658 | |
ebebf55c | 659 | /** |
78946b9b PS |
660 | * Returns the content of the one huge CSS merged from all style sheets. |
661 | * @return string | |
ebebf55c | 662 | */ |
78946b9b | 663 | public function css_content() { |
34a2777c | 664 | global $CFG; |
665 | ||
c724f835 | 666 | $css = array('plugins'=>array(), 'parents'=>array(), 'theme'=>array()); |
78946b9b | 667 | |
8c96c3cb | 668 | // get all plugin sheets |
78946b9b PS |
669 | $excludes = null; |
670 | if (is_array($this->plugins_exclude_sheets) or $this->plugins_exclude_sheets === true) { | |
671 | $excludes = $this->plugins_exclude_sheets; | |
672 | } else { | |
673 | foreach ($this->parent_configs as $parent_config) { // the immediate parent first, base last | |
674 | if (!isset($parent_config->plugins_exclude_sheets)) { | |
675 | continue; | |
676 | } | |
677 | if (is_array($parent_config->plugins_exclude_sheets) or $parent_config->plugins_exclude_sheets === true) { | |
678 | $excludes = $parent_config->plugins_exclude_sheets; | |
679 | break; | |
680 | } | |
681 | } | |
ebebf55c | 682 | } |
78946b9b PS |
683 | if ($excludes !== true) { |
684 | foreach (get_plugin_types() as $type=>$unused) { | |
685 | if ($type === 'theme') { | |
686 | continue; | |
687 | } | |
688 | if (!empty($excludes[$type]) and $excludes[$type] === true) { | |
689 | continue; | |
690 | } | |
691 | $plugins = get_plugin_list($type); | |
692 | foreach ($plugins as $plugin=>$fulldir) { | |
693 | if (!empty($excludes[$type]) and is_array($excludes[$type]) | |
694 | and in_array($plugin, $excludes[$type])) { | |
695 | continue; | |
696 | } | |
f8bb9666 SH |
697 | |
698 | $plugincontent = ''; | |
78946b9b PS |
699 | $sheetfile = "$fulldir/styles.css"; |
700 | if (is_readable($sheetfile)) { | |
f8bb9666 SH |
701 | $plugincontent .= "/*** Standard plugin $type/$plugin ***/\n\n"; |
702 | $plugincontent .= file_get_contents($sheetfile); | |
703 | } | |
704 | $sheetthemefile = "$fulldir/styles_{$this->name}.css"; | |
705 | if (is_readable($sheetthemefile)) { | |
706 | $plugincontent .= "\n/*** Standard plugin $type/$plugin for the {$this->name} theme ***/\n\n"; | |
707 | $plugincontent .= file_get_contents($sheetthemefile); | |
708 | } | |
709 | if (!empty($plugincontent)) { | |
710 | $css['plugins'][$type.'_'.$plugin] = $this->post_process($plugincontent); | |
78946b9b PS |
711 | } |
712 | } | |
713 | } | |
34a2777c | 714 | } |
715 | ||
78946b9b PS |
716 | // find out wanted parent sheets |
717 | $excludes = null; | |
718 | if (is_array($this->parents_exclude_sheets) or $this->parents_exclude_sheets === true) { | |
719 | $excludes = $this->parents_exclude_sheets; | |
720 | } else { | |
721 | foreach ($this->parent_configs as $parent_config) { // the immediate parent first, base last | |
722 | if (!isset($parent_config->parents_exclude_sheets)) { | |
723 | continue; | |
724 | } | |
725 | if (is_array($parent_config->parents_exclude_sheets) or $parent_config->parents_exclude_sheets === true) { | |
726 | $excludes = $parent_config->parents_exclude_sheets; | |
727 | break; | |
728 | } | |
729 | } | |
730 | } | |
731 | if ($excludes !== true) { | |
732 | foreach (array_reverse($this->parent_configs) as $parent_config) { // base first, the immediate parent last | |
733 | $parent = $parent_config->name; | |
734 | if (empty($parent_config->sheets)) { | |
735 | continue; | |
736 | } | |
737 | if (!empty($excludes[$parent]) and $excludes[$parent] === true) { | |
738 | continue; | |
739 | } | |
740 | foreach ($parent_config->sheets as $sheet) { | |
741 | if (!empty($excludes[$parent]) and is_array($excludes[$parent]) | |
742 | and in_array($sheet, $excludes[$parent])) { | |
743 | continue; | |
744 | } | |
745 | $sheetfile = "$parent_config->dir/style/$sheet.css"; | |
746 | if (is_readable($sheetfile)) { | |
358c13cb | 747 | $css['parents'][$parent][$sheet] = $this->post_process("/*** Parent theme $parent/style/$sheet.css ***/\n\n" . file_get_contents($sheetfile)); |
78946b9b PS |
748 | } |
749 | } | |
750 | } | |
751 | } | |
34a2777c | 752 | |
78946b9b PS |
753 | // current theme sheets |
754 | if (is_array($this->sheets)) { | |
755 | foreach ($this->sheets as $sheet) { | |
756 | $sheetfile = "$this->dir/style/$sheet.css"; | |
757 | if (is_readable($sheetfile)) { | |
358c13cb | 758 | $css['theme'][$sheet] = $this->post_process("/*** This theme $this->name/style/$sheet ***/\n\n" . file_get_contents($sheetfile)); |
78946b9b | 759 | } |
ebebf55c | 760 | } |
78946b9b PS |
761 | } |
762 | ||
763 | return $css; | |
764 | } | |
34a2777c | 765 | |
e68c5f36 PS |
766 | |
767 | /** | |
768 | * Get the javascript URL of this theme | |
baeb20bb | 769 | * @param bool $inhead true means haed url, false means footer |
e68c5f36 PS |
770 | * @return moodle_url |
771 | */ | |
baeb20bb | 772 | public function javascript_url($inhead) { |
e68c5f36 PS |
773 | global $CFG; |
774 | ||
775 | $rev = theme_get_revision(); | |
e68c5f36 | 776 | $params = array('theme'=>$this->name,'rev'=>$rev); |
baeb20bb PS |
777 | $params['type'] = $inhead ? 'head' : 'footer'; |
778 | ||
779 | return new moodle_url($CFG->httpswwwroot.'/theme/javascript.php', $params); | |
e68c5f36 PS |
780 | } |
781 | ||
782 | /** | |
783 | * Returns the content of the one huge javascript file merged from all theme javascript files. | |
baeb20bb | 784 | * @param bool $inhead |
e68c5f36 PS |
785 | * @return string |
786 | */ | |
baeb20bb PS |
787 | public function javascript_content($type) { |
788 | $type = ($type === 'footer') ? 'javascripts_footer' : 'javascripts'; | |
04c01408 | 789 | |
358c13cb PS |
790 | $js = array(); |
791 | // find out wanted parent javascripts | |
792 | $excludes = null; | |
793 | if (is_array($this->parents_exclude_javascripts) or $this->parents_exclude_javascripts === true) { | |
794 | $excludes = $this->parents_exclude_javascripts; | |
795 | } else { | |
796 | foreach ($this->parent_configs as $parent_config) { // the immediate parent first, base last | |
797 | if (!isset($parent_config->parents_exclude_javascripts)) { | |
798 | continue; | |
799 | } | |
800 | if (is_array($parent_config->parents_exclude_javascripts) or $parent_config->parents_exclude_javascripts === true) { | |
801 | $excludes = $parent_config->parents_exclude_javascripts; | |
802 | break; | |
803 | } | |
804 | } | |
805 | } | |
806 | if ($excludes !== true) { | |
807 | foreach (array_reverse($this->parent_configs) as $parent_config) { // base first, the immediate parent last | |
808 | $parent = $parent_config->name; | |
04c01408 | 809 | if (empty($parent_config->$type)) { |
358c13cb PS |
810 | continue; |
811 | } | |
812 | if (!empty($excludes[$parent]) and $excludes[$parent] === true) { | |
813 | continue; | |
814 | } | |
04c01408 | 815 | foreach ($parent_config->$type as $javascript) { |
358c13cb PS |
816 | if (!empty($excludes[$parent]) and is_array($excludes[$parent]) |
817 | and in_array($javascript, $excludes[$parent])) { | |
818 | continue; | |
819 | } | |
820 | $javascriptfile = "$parent_config->dir/javascript/$javascript.js"; | |
821 | if (is_readable($javascriptfile)) { | |
822 | $js[] = "/*** Parent theme $parent/javascript/$javascript.js ***/\n\n" . file_get_contents($javascriptfile); | |
823 | } | |
824 | } | |
825 | } | |
826 | } | |
827 | ||
828 | // current theme javascripts | |
04c01408 PS |
829 | if (is_array($this->$type)) { |
830 | foreach ($this->$type as $javascript) { | |
358c13cb PS |
831 | $javascriptfile = "$this->dir/javascript/$javascript.js"; |
832 | if (is_readable($javascriptfile)) { | |
833 | $js[] = "/*** This theme $this->name/javascript/$javascript.js ***/\n\n" . file_get_contents($javascriptfile); | |
834 | } | |
835 | } | |
836 | } | |
837 | ||
838 | return implode("\n\n", $js); | |
e68c5f36 PS |
839 | } |
840 | ||
78946b9b | 841 | protected function post_process($css) { |
df06c1c4 PS |
842 | global $CFG; |
843 | ||
78946b9b PS |
844 | // now resolve all image locations |
845 | if (preg_match_all('/\[\[pix:([a-z_]+\|)?([^\]]+)\]\]/', $css, $matches, PREG_SET_ORDER)) { | |
846 | $replaced = array(); | |
847 | foreach ($matches as $match) { | |
848 | if (isset($replaced[$match[0]])) { | |
849 | continue; | |
850 | } | |
851 | $replaced[$match[0]] = true; | |
852 | $imagename = $match[2]; | |
853 | $component = rtrim($match[1], '|'); | |
b9bc2019 | 854 | $imageurl = $this->pix_url($imagename, $component)->out(false); |
df06c1c4 PS |
855 | // we do not need full url because the image.php is always in the same dir |
856 | $imageurl = str_replace("$CFG->httpswwwroot/theme/", '', $imageurl); | |
857 | $css = str_replace($match[0], $imageurl, $css); | |
ebebf55c | 858 | } |
34a2777c | 859 | } |
17a6649b | 860 | |
78946b9b PS |
861 | // now resolve all theme settings or do any other postprocessing |
862 | $csspostprocess = $this->csspostprocess; | |
863 | if (function_exists($csspostprocess)) { | |
864 | $css = $csspostprocess($css, $this); | |
fdeb7fa1 | 865 | } |
866 | ||
78946b9b | 867 | return $css; |
ebebf55c | 868 | } |
17a6649b | 869 | |
ebebf55c | 870 | /** |
78946b9b PS |
871 | * Return the URL for an image |
872 | * | |
873 | * @param string $imagename the name of the icon. | |
874 | * @param string $component, specification of one plugin like in get_string() | |
875 | * @return moodle_url | |
fdeb7fa1 | 876 | */ |
c39e5ba2 | 877 | public function pix_url($imagename, $component) { |
fdeb7fa1 | 878 | global $CFG; |
78946b9b PS |
879 | |
880 | $params = array('theme'=>$this->name, 'image'=>$imagename); | |
881 | ||
882 | $rev = theme_get_revision(); | |
883 | if ($rev != -1) { | |
884 | $params['rev'] = $rev; | |
885 | } | |
886 | if (!empty($component) and $component !== 'moodle'and $component !== 'core') { | |
887 | $params['component'] = $component; | |
fdeb7fa1 | 888 | } |
78946b9b PS |
889 | |
890 | return new moodle_url("$CFG->httpswwwroot/theme/image.php", $params); | |
fdeb7fa1 | 891 | } |
892 | ||
893 | /** | |
78946b9b PS |
894 | * Resolves the real image location. |
895 | * @param string $image name of image, may contain relative path | |
896 | * @param string $component | |
897 | * @return string full file path | |
fdeb7fa1 | 898 | */ |
78946b9b PS |
899 | public function resolve_image_location($image, $component) { |
900 | global $CFG; | |
901 | ||
902 | if ($component === 'moodle' or $component === 'core' or empty($component)) { | |
903 | if ($imagefile = $this->image_exists("$this->dir/pix_core/$image")) { | |
904 | return $imagefile; | |
905 | } | |
906 | foreach (array_reverse($this->parent_configs) as $parent_config) { // base first, the immediate parent last | |
907 | if ($imagefile = $this->image_exists("$parent_config->dir/pix_core/$image")) { | |
908 | return $imagefile; | |
909 | } | |
910 | } | |
911 | if ($imagefile = $this->image_exists("$CFG->dirroot/pix/$image")) { | |
912 | return $imagefile; | |
913 | } | |
914 | return null; | |
915 | ||
916 | } else if ($component === 'theme') { //exception | |
917 | if ($image === 'favicon') { | |
918 | return "$this->dir/pix/favicon.ico"; | |
919 | } | |
920 | if ($imagefile = $this->image_exists("$this->dir/pix/$image")) { | |
921 | return $imagefile; | |
922 | } | |
923 | foreach (array_reverse($this->parent_configs) as $parent_config) { // base first, the immediate parent last | |
924 | if ($imagefile = $this->image_exists("$parent_config->dir/pix/$image")) { | |
925 | return $imagefile; | |
926 | } | |
927 | } | |
928 | return null; | |
929 | ||
78946b9b PS |
930 | } else { |
931 | if (strpos($component, '_') === false) { | |
932 | $component = 'mod_'.$component; | |
933 | } | |
934 | list($type, $plugin) = explode('_', $component, 2); | |
935 | ||
936 | if ($imagefile = $this->image_exists("$this->dir/pix_plugins/$type/$plugin/$image")) { | |
937 | return $imagefile; | |
938 | } | |
939 | foreach (array_reverse($this->parent_configs) as $parent_config) { // base first, the immediate parent last | |
940 | if ($imagefile = $this->image_exists("$parent_config->dir/pix_plugins/$type/$plugin/$image")) { | |
941 | return $imagefile; | |
942 | } | |
943 | } | |
944 | $dir = get_plugin_directory($type, $plugin); | |
945 | if ($imagefile = $this->image_exists("$dir/pix/$image")) { | |
946 | return $imagefile; | |
947 | } | |
948 | return null; | |
fdeb7fa1 | 949 | } |
fdeb7fa1 | 950 | } |
951 | ||
d4a03c00 | 952 | /** |
78946b9b PS |
953 | * Checks if file with any image extension exists. |
954 | * @param string $filepath | |
955 | * @return string image name with extension | |
d4a03c00 | 956 | */ |
78946b9b PS |
957 | private static function image_exists($filepath) { |
958 | if (file_exists("$filepath.gif")) { | |
959 | return "$filepath.gif"; | |
960 | } else if (file_exists("$filepath.png")) { | |
961 | return "$filepath.png"; | |
962 | } else if (file_exists("$filepath.jpg")) { | |
963 | return "$filepath.jpg"; | |
964 | } else if (file_exists("$filepath.jpeg")) { | |
965 | return "$filepath.jpeg"; | |
d4a03c00 | 966 | } else { |
78946b9b | 967 | return false; |
d4a03c00 | 968 | } |
969 | } | |
970 | ||
fdeb7fa1 | 971 | /** |
78946b9b PS |
972 | * Loads the theme config from config.php file. |
973 | * @param string $themename | |
974 | * @param object $settings from config_plugins table | |
975 | * @return object | |
fdeb7fa1 | 976 | */ |
78946b9b PS |
977 | private static function find_theme_config($themename, $settings) { |
978 | // We have to use the variable name $THEME (upper case) because that | |
979 | // is what is used in theme config.php files. | |
fdeb7fa1 | 980 | |
78946b9b PS |
981 | if (!$dir = theme_config::find_theme_location($themename)) { |
982 | return null; | |
fdeb7fa1 | 983 | } |
2f67a9b3 | 984 | |
78946b9b PS |
985 | $THEME = new object(); |
986 | $THEME->name = $themename; | |
987 | $THEME->dir = $dir; | |
988 | $THEME->settings = $settings; | |
989 | ||
990 | global $CFG; // just in case somebody tries to use $CFG in theme config | |
991 | include("$THEME->dir/config.php"); | |
992 | ||
993 | // verify the theme configuration is OK | |
994 | if (!is_array($THEME->parents)) { | |
995 | // parents option is mandatory now | |
996 | return null; | |
fdeb7fa1 | 997 | } |
998 | ||
78946b9b | 999 | return $THEME; |
fdeb7fa1 | 1000 | } |
1001 | ||
d4a03c00 | 1002 | /** |
78946b9b PS |
1003 | * Finds the theme location and verifies the theme has all needed files |
1004 | * and is not obsoleted. | |
1005 | * @param string $themename | |
1006 | * @return string full dir path or null if not found | |
d4a03c00 | 1007 | */ |
78946b9b PS |
1008 | private static function find_theme_location($themename) { |
1009 | global $CFG; | |
1010 | ||
1011 | if (file_exists("$CFG->dirroot/theme/$themename/config.php")) { | |
1012 | $dir = "$CFG->dirroot/theme/$themename"; | |
d4a03c00 | 1013 | |
7d875874 | 1014 | } else { |
78946b9b | 1015 | return null; |
d4a03c00 | 1016 | } |
78946b9b PS |
1017 | |
1018 | if (file_exists("$dir/styles.php")) { | |
1019 | //legacy theme - needs to be upgraded - upgrade info is displayed on the admin settings page | |
1020 | return null; | |
1021 | } | |
2f67a9b3 | 1022 | |
78946b9b | 1023 | return $dir; |
d4a03c00 | 1024 | } |
1025 | ||
1d13c75c | 1026 | /** |
78946b9b | 1027 | * Get the renderer for a part of Moodle for this theme. |
78946b9b | 1028 | * @param moodle_page $page the page we are rendering |
56cbc53b | 1029 | * @param string $module the name of part of moodle. E.g. 'core', 'quiz', 'qtype_multichoice'. |
78946b9b | 1030 | * @param string $subtype optional subtype such as 'news' resulting to 'mod_forum_news' |
c927e35c | 1031 | * @param string $target one of rendering target constants |
78946b9b | 1032 | * @return renderer_base the requested renderer. |
1d13c75c | 1033 | */ |
c927e35c | 1034 | public function get_renderer(moodle_page $page, $component, $subtype = null, $target = null) { |
78946b9b | 1035 | if (is_null($this->rf)) { |
c927e35c | 1036 | $classname = $this->rendererfactory; |
78946b9b | 1037 | $this->rf = new $classname($this); |
1d13c75c | 1038 | } |
78946b9b | 1039 | |
c927e35c | 1040 | return $this->rf->get_renderer($page, $component, $subtype, $target); |
1d13c75c | 1041 | } |
1042 | ||
fdeb7fa1 | 1043 | /** |
78946b9b PS |
1044 | * Get the information from {@link $layouts} for this type of page. |
1045 | * @param string $pagelayout the the page layout name. | |
1046 | * @return array the appropriate part of {@link $layouts}. | |
fdeb7fa1 | 1047 | */ |
78946b9b PS |
1048 | protected function layout_info_for_page($pagelayout) { |
1049 | if (array_key_exists($pagelayout, $this->layouts)) { | |
1050 | return $this->layouts[$pagelayout]; | |
1051 | } else { | |
191b267b PS |
1052 | debugging('Invalid page layout specified: ' . $pagelayout); |
1053 | return $this->layouts['standard']; | |
fdeb7fa1 | 1054 | } |
1055 | } | |
1056 | ||
1057 | /** | |
78946b9b PS |
1058 | * Given the settings of this theme, and the page pagelayout, return the |
1059 | * full path of the page layout file to use. | |
1060 | * | |
1061 | * Used by {@link core_renderer::header()}. | |
1062 | * | |
1063 | * @param string $pagelayout the the page layout name. | |
1064 | * @return string Full path to the lyout file to use | |
ebebf55c | 1065 | */ |
78946b9b | 1066 | public function layout_file($pagelayout) { |
ebebf55c | 1067 | global $CFG; |
fdeb7fa1 | 1068 | |
78946b9b PS |
1069 | $layoutinfo = $this->layout_info_for_page($pagelayout); |
1070 | $layoutfile = $layoutinfo['file']; | |
1071 | $theme = $layoutinfo['theme']; | |
fdeb7fa1 | 1072 | |
78946b9b PS |
1073 | if ($dir = $this->find_theme_location($theme)) { |
1074 | $path = "$dir/layout/$layoutfile"; | |
fdeb7fa1 | 1075 | |
78946b9b PS |
1076 | // Check the template exists, return general base theme template if not. |
1077 | if (is_readable($path)) { | |
1078 | return $path; | |
1079 | } | |
34a2777c | 1080 | } |
1081 | ||
191b267b | 1082 | debugging('Can not find layout file for: ' . $pagelayout); |
78946b9b PS |
1083 | // fallback to standard normal layout |
1084 | return "$CFG->dirroot/theme/base/layout/general.php"; | |
1085 | } | |
ebebf55c | 1086 | |
78946b9b PS |
1087 | /** |
1088 | * Returns auxiliary page layout options specified in layout configuration array. | |
1089 | * @param string $pagelayout | |
1090 | * @return array | |
1091 | */ | |
1092 | public function pagelayout_options($pagelayout) { | |
1093 | $info = $this->layout_info_for_page($pagelayout); | |
1094 | if (!empty($info['options'])) { | |
1095 | return $info['options']; | |
34a2777c | 1096 | } |
78946b9b PS |
1097 | return array(); |
1098 | } | |
34a2777c | 1099 | |
78946b9b PS |
1100 | /** |
1101 | * Inform a block_manager about the block regions this theme wants on this | |
1102 | * page layout. | |
1103 | * @param string $pagelayout the general type of the page. | |
1104 | * @param block_manager $blockmanager the block_manger to set up. | |
1105 | * @return void | |
1106 | */ | |
1107 | public function setup_blocks($pagelayout, $blockmanager) { | |
1108 | $layoutinfo = $this->layout_info_for_page($pagelayout); | |
1109 | if (!empty($layoutinfo['regions'])) { | |
1110 | $blockmanager->add_regions($layoutinfo['regions']); | |
1111 | $blockmanager->set_default_region($layoutinfo['defaultregion']); | |
ebebf55c | 1112 | } |
34a2777c | 1113 | } |
1114 | ||
cadc0d28 TH |
1115 | protected function get_region_name($region, $theme) { |
1116 | $regionstring = get_string('region-' . $region, 'theme_' . $theme); | |
1117 | // A name exists in this theme, so use it | |
1118 | if (substr($regionstring, 0, 1) != '[') { | |
1119 | return $regionstring; | |
1120 | } | |
1121 | ||
1122 | // Otherwise, try to find one elsewhere | |
1123 | // Check parents, if any | |
1124 | foreach ($this->parents as $parentthemename) { | |
1125 | $regionstring = get_string('region-' . $region, 'theme_' . $parentthemename); | |
1126 | if (substr($regionstring, 0, 1) != '[') { | |
1127 | return $regionstring; | |
1128 | } | |
1129 | } | |
1130 | ||
1131 | // Last resort, try the base theme for names | |
1132 | return get_string('region-' . $region, 'theme_base'); | |
1133 | } | |
1134 | ||
ebebf55c | 1135 | /** |
78946b9b PS |
1136 | * Get the list of all block regions known to this theme in all templates. |
1137 | * @return array internal region name => human readable name. | |
ebebf55c | 1138 | */ |
78946b9b PS |
1139 | public function get_all_block_regions() { |
1140 | $regions = array(); | |
78946b9b PS |
1141 | foreach ($this->layouts as $layoutinfo) { |
1142 | foreach ($layoutinfo['regions'] as $region) { | |
cadc0d28 | 1143 | $regions[$region] = $this->get_region_name($region, $layoutinfo['theme']); |
ebebf55c | 1144 | } |
34a2777c | 1145 | } |
78946b9b | 1146 | return $regions; |
34a2777c | 1147 | } |
ebebf55c | 1148 | } |
34a2777c | 1149 | |
78946b9b | 1150 | |
ebebf55c | 1151 | /** |
d9c8f425 | 1152 | * This class keeps track of which HTML tags are currently open. |
1153 | * | |
1154 | * This makes it much easier to always generate well formed XHTML output, even | |
1155 | * if execution terminates abruptly. Any time you output some opening HTML | |
1156 | * without the matching closing HTML, you should push the necessary close tags | |
1157 | * onto the stack. | |
ebebf55c | 1158 | * |
1159 | * @copyright 2009 Tim Hunt | |
1160 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
1161 | * @since Moodle 2.0 | |
1162 | */ | |
d9c8f425 | 1163 | class xhtml_container_stack { |
1164 | /** @var array stores the list of open containers. */ | |
1165 | protected $opencontainers = array(); | |
fa1afe32 | 1166 | /** |
d9c8f425 | 1167 | * @var array in developer debug mode, stores a stack trace of all opens and |
1168 | * closes, so we can output helpful error messages when there is a mismatch. | |
fa1afe32 | 1169 | */ |
d9c8f425 | 1170 | protected $log = array(); |
fa1afe32 | 1171 | /** |
d9c8f425 | 1172 | * Store whether we are developer debug mode. We need this in several places |
1173 | * including in the destructor where we may no thave access to $CFG. | |
1174 | * @var boolean | |
fa1afe32 | 1175 | */ |
d9c8f425 | 1176 | protected $isdebugging; |
34a2777c | 1177 | |
d9c8f425 | 1178 | public function __construct() { |
1179 | $this->isdebugging = debugging('', DEBUG_DEVELOPER); | |
ebebf55c | 1180 | } |
34a2777c | 1181 | |
fa1afe32 | 1182 | /** |
d9c8f425 | 1183 | * Push the close HTML for a recently opened container onto the stack. |
1184 | * @param string $type The type of container. This is checked when {@link pop()} | |
1185 | * is called and must match, otherwise a developer debug warning is output. | |
1186 | * @param string $closehtml The HTML required to close the container. | |
1187 | * @return void | |
fa1afe32 | 1188 | */ |
d9c8f425 | 1189 | public function push($type, $closehtml) { |
1190 | $container = new stdClass; | |
1191 | $container->type = $type; | |
1192 | $container->closehtml = $closehtml; | |
1193 | if ($this->isdebugging) { | |
1194 | $this->log('Open', $type); | |
3aaa27f4 | 1195 | } |
d9c8f425 | 1196 | array_push($this->opencontainers, $container); |
ebebf55c | 1197 | } |
34a2777c | 1198 | |
fa1afe32 | 1199 | /** |
d9c8f425 | 1200 | * Pop the HTML for the next closing container from the stack. The $type |
1201 | * must match the type passed when the container was opened, otherwise a | |
1202 | * warning will be output. | |
1203 | * @param string $type The type of container. | |
1204 | * @return string the HTML required to close the container. | |
fa1afe32 | 1205 | */ |
d9c8f425 | 1206 | public function pop($type) { |
1207 | if (empty($this->opencontainers)) { | |
1208 | debugging('<p>There are no more open containers. This suggests there is a nesting problem.</p>' . | |
1209 | $this->output_log(), DEBUG_DEVELOPER); | |
1210 | return; | |
3aaa27f4 | 1211 | } |
ebebf55c | 1212 | |
d9c8f425 | 1213 | $container = array_pop($this->opencontainers); |
1214 | if ($container->type != $type) { | |
1215 | debugging('<p>The type of container to be closed (' . $container->type . | |
1216 | ') does not match the type of the next open container (' . $type . | |
1217 | '). This suggests there is a nesting problem.</p>' . | |
1218 | $this->output_log(), DEBUG_DEVELOPER); | |
ebebf55c | 1219 | } |
d9c8f425 | 1220 | if ($this->isdebugging) { |
1221 | $this->log('Close', $type); | |
e8775320 | 1222 | } |
d9c8f425 | 1223 | return $container->closehtml; |
ebebf55c | 1224 | } |
e8775320 | 1225 | |
fa1afe32 | 1226 | /** |
d9c8f425 | 1227 | * Close all but the last open container. This is useful in places like error |
1228 | * handling, where you want to close all the open containers (apart from <body>) | |
1229 | * before outputting the error message. | |
1230 | * @param bool $shouldbenone assert that the stack should be empty now - causes a | |
1231 | * developer debug warning if it isn't. | |
1232 | * @return string the HTML required to close any open containers inside <body>. | |
fa1afe32 | 1233 | */ |
d9c8f425 | 1234 | public function pop_all_but_last($shouldbenone = false) { |
1235 | if ($shouldbenone && count($this->opencontainers) != 1) { | |
1236 | debugging('<p>Some HTML tags were opened in the body of the page but not closed.</p>' . | |
1237 | $this->output_log(), DEBUG_DEVELOPER); | |
1238 | } | |
1239 | $output = ''; | |
1240 | while (count($this->opencontainers) > 1) { | |
1241 | $container = array_pop($this->opencontainers); | |
1242 | $output .= $container->closehtml; | |
e8775320 | 1243 | } |
d9c8f425 | 1244 | return $output; |
e8775320 | 1245 | } |
34a2777c | 1246 | |
ebebf55c | 1247 | /** |
d9c8f425 | 1248 | * You can call this function if you want to throw away an instance of this |
1249 | * class without properly emptying the stack (for example, in a unit test). | |
1250 | * Calling this method stops the destruct method from outputting a developer | |
1251 | * debug warning. After calling this method, the instance can no longer be used. | |
1252 | * @return void | |
ebebf55c | 1253 | */ |
d9c8f425 | 1254 | public function discard() { |
1255 | $this->opencontainers = null; | |
ebebf55c | 1256 | } |
d9c8f425 | 1257 | |
fa1afe32 | 1258 | /** |
d9c8f425 | 1259 | * Adds an entry to the log. |
1260 | * @param string $action The name of the action | |
1261 | * @param string $type The type of action | |
1262 | * @return void | |
fa1afe32 | 1263 | */ |
d9c8f425 | 1264 | protected function log($action, $type) { |
1265 | $this->log[] = '<li>' . $action . ' ' . $type . ' at:' . | |
1266 | format_backtrace(debug_backtrace()) . '</li>'; | |
ebebf55c | 1267 | } |
34a2777c | 1268 | |
fa1afe32 | 1269 | /** |
d9c8f425 | 1270 | * Outputs the log's contents as a HTML list. |
1271 | * @return string HTML list of the log | |
fa1afe32 | 1272 | */ |
d9c8f425 | 1273 | protected function output_log() { |
1274 | return '<ul>' . implode("\n", $this->log) . '</ul>'; | |
34a2777c | 1275 | } |
1276 | } |