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'); |
33 | ||
571fa828 | 34 | /** |
78946b9b PS |
35 | * Invalidate all server and client side caches. |
36 | * @return void | |
37 | */ | |
38 | function theme_reset_all_caches() { | |
39 | global $CFG; | |
40 | require_once("$CFG->libdir/filelib.php"); | |
41 | ||
42 | set_config('themerev', empty($CFG->themerev) ? 1 : $CFG->themerev+1); | |
43 | fulldelete("$CFG->dataroot/cache/theme"); | |
44 | } | |
45 | ||
46 | /** | |
47 | * Enable or disable theme designer mode. | |
48 | * @param bool $state | |
49 | * @return void | |
50 | */ | |
51 | function theme_set_designer_mod($state) { | |
52 | theme_reset_all_caches(); | |
53 | set_config('themedesignermode', (int)!empty($state)); | |
54 | } | |
55 | ||
56 | /** | |
57 | * Returns current theme revision number. | |
58 | * @return int | |
571fa828 | 59 | */ |
78946b9b PS |
60 | function theme_get_revision() { |
61 | global $CFG; | |
62 | ||
63 | if (empty($CFG->themedesignermode)) { | |
64 | if (empty($CFG->themerev)) { | |
65 | return -1; | |
66 | } else { | |
67 | return $CFG->themerev; | |
68 | } | |
69 | ||
70 | } else { | |
71 | return -1; | |
72 | } | |
73 | } | |
74 | ||
571fa828 | 75 | |
571fa828 | 76 | /** |
fdeb7fa1 | 77 | * This class represents the configuration variables of a Moodle theme. |
78 | * | |
79 | * All the variables with access: public below (with a few exceptions that are marked) | |
80 | * are the properties you can set in your theme's config.php file. | |
81 | * | |
82 | * There are also some methods and protected variables that are part of the inner | |
83 | * workings of Moodle's themes system. If you are just editing a theme's config.php | |
fa1afe32 | 84 | * file, you can just ignore those, and the following information for developers. |
ebebf55c | 85 | * |
86 | * Normally, to create an instance of this class, you should use the | |
87 | * {@link theme_config::load()} factory method to load a themes config.php file. | |
fa1afe32 | 88 | * However, normally you don't need to bother, because moodle_page (that is, $PAGE) |
fdeb7fa1 | 89 | * will create one for you, accessible as $PAGE->theme. |
571fa828 | 90 | * |
91 | * @copyright 2009 Tim Hunt | |
92 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
93 | * @since Moodle 2.0 | |
94 | */ | |
ebebf55c | 95 | class theme_config { |
5f0baa43 PS |
96 | /** |
97 | * @var string default theme, used when requested theme not found | |
98 | * TODO: MDL-21149 replace with some new default theme when implemented | |
99 | */ | |
fef27578 | 100 | const DEFAULT_THEME = 'standardold'; |
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'; |
78946b9b | 636 | foreach ($css['plugins'] as $plugin=>$unused) { |
c724f835 | 637 | $urls[] = new moodle_url($baseurl, array('theme'=>$this->name,'type'=>'plugin', 'subtype'=>$plugin)); |
78946b9b PS |
638 | } |
639 | foreach ($css['parents'] as $parent=>$sheets) { | |
640 | foreach ($sheets as $sheet=>$unused2) { | |
c71c725e | 641 | $urls[] = new moodle_url($baseurl, array('theme'=>$this->name,'type'=>'parent', 'subtype'=>$parent, 'sheet'=>$sheet)); |
78946b9b PS |
642 | } |
643 | } | |
644 | foreach ($css['theme'] as $sheet=>$unused) { | |
c724f835 | 645 | $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 | 646 | } |
78946b9b | 647 | } |
efaa4c08 PS |
648 | |
649 | return $urls; | |
571fa828 | 650 | } |
34a2777c | 651 | |
ebebf55c | 652 | /** |
78946b9b PS |
653 | * Returns the content of the one huge CSS merged from all style sheets. |
654 | * @return string | |
ebebf55c | 655 | */ |
78946b9b | 656 | public function css_content() { |
34a2777c | 657 | global $CFG; |
658 | ||
c724f835 | 659 | $css = array('plugins'=>array(), 'parents'=>array(), 'theme'=>array()); |
78946b9b | 660 | |
8c96c3cb | 661 | // get all plugin sheets |
78946b9b PS |
662 | $excludes = null; |
663 | if (is_array($this->plugins_exclude_sheets) or $this->plugins_exclude_sheets === true) { | |
664 | $excludes = $this->plugins_exclude_sheets; | |
665 | } else { | |
666 | foreach ($this->parent_configs as $parent_config) { // the immediate parent first, base last | |
667 | if (!isset($parent_config->plugins_exclude_sheets)) { | |
668 | continue; | |
669 | } | |
670 | if (is_array($parent_config->plugins_exclude_sheets) or $parent_config->plugins_exclude_sheets === true) { | |
671 | $excludes = $parent_config->plugins_exclude_sheets; | |
672 | break; | |
673 | } | |
674 | } | |
ebebf55c | 675 | } |
78946b9b PS |
676 | if ($excludes !== true) { |
677 | foreach (get_plugin_types() as $type=>$unused) { | |
678 | if ($type === 'theme') { | |
679 | continue; | |
680 | } | |
681 | if (!empty($excludes[$type]) and $excludes[$type] === true) { | |
682 | continue; | |
683 | } | |
684 | $plugins = get_plugin_list($type); | |
685 | foreach ($plugins as $plugin=>$fulldir) { | |
686 | if (!empty($excludes[$type]) and is_array($excludes[$type]) | |
687 | and in_array($plugin, $excludes[$type])) { | |
688 | continue; | |
689 | } | |
690 | $sheetfile = "$fulldir/styles.css"; | |
691 | if (is_readable($sheetfile)) { | |
692 | $css['plugins'][$type.'_'.$plugin] = $this->post_process("/*** Standard plugin $type/$plugin ***/\n\n" . file_get_contents($sheetfile)); | |
693 | } | |
694 | } | |
695 | } | |
34a2777c | 696 | } |
697 | ||
78946b9b PS |
698 | // find out wanted parent sheets |
699 | $excludes = null; | |
700 | if (is_array($this->parents_exclude_sheets) or $this->parents_exclude_sheets === true) { | |
701 | $excludes = $this->parents_exclude_sheets; | |
702 | } else { | |
703 | foreach ($this->parent_configs as $parent_config) { // the immediate parent first, base last | |
704 | if (!isset($parent_config->parents_exclude_sheets)) { | |
705 | continue; | |
706 | } | |
707 | if (is_array($parent_config->parents_exclude_sheets) or $parent_config->parents_exclude_sheets === true) { | |
708 | $excludes = $parent_config->parents_exclude_sheets; | |
709 | break; | |
710 | } | |
711 | } | |
712 | } | |
713 | if ($excludes !== true) { | |
714 | foreach (array_reverse($this->parent_configs) as $parent_config) { // base first, the immediate parent last | |
715 | $parent = $parent_config->name; | |
716 | if (empty($parent_config->sheets)) { | |
717 | continue; | |
718 | } | |
719 | if (!empty($excludes[$parent]) and $excludes[$parent] === true) { | |
720 | continue; | |
721 | } | |
722 | foreach ($parent_config->sheets as $sheet) { | |
723 | if (!empty($excludes[$parent]) and is_array($excludes[$parent]) | |
724 | and in_array($sheet, $excludes[$parent])) { | |
725 | continue; | |
726 | } | |
727 | $sheetfile = "$parent_config->dir/style/$sheet.css"; | |
728 | if (is_readable($sheetfile)) { | |
358c13cb | 729 | $css['parents'][$parent][$sheet] = $this->post_process("/*** Parent theme $parent/style/$sheet.css ***/\n\n" . file_get_contents($sheetfile)); |
78946b9b PS |
730 | } |
731 | } | |
732 | } | |
733 | } | |
34a2777c | 734 | |
78946b9b PS |
735 | // current theme sheets |
736 | if (is_array($this->sheets)) { | |
737 | foreach ($this->sheets as $sheet) { | |
738 | $sheetfile = "$this->dir/style/$sheet.css"; | |
739 | if (is_readable($sheetfile)) { | |
358c13cb | 740 | $css['theme'][$sheet] = $this->post_process("/*** This theme $this->name/style/$sheet ***/\n\n" . file_get_contents($sheetfile)); |
78946b9b | 741 | } |
ebebf55c | 742 | } |
78946b9b PS |
743 | } |
744 | ||
745 | return $css; | |
746 | } | |
34a2777c | 747 | |
e68c5f36 PS |
748 | |
749 | /** | |
750 | * Get the javascript URL of this theme | |
04c01408 | 751 | * @param bool $footer true measn footer url, false means head |
e68c5f36 PS |
752 | * @return moodle_url |
753 | */ | |
04c01408 | 754 | public function javascript_url($footer=false) { |
e68c5f36 PS |
755 | global $CFG; |
756 | ||
757 | $rev = theme_get_revision(); | |
758 | ||
759 | $params = array('theme'=>$this->name,'rev'=>$rev); | |
04c01408 PS |
760 | if ($footer) { |
761 | $params['type'] = 'footer'; | |
762 | } | |
e68c5f36 PS |
763 | return new moodle_url($CFG->httpswwwroot.'/theme/javascripts.php', $params); |
764 | } | |
765 | ||
766 | /** | |
767 | * Returns the content of the one huge javascript file merged from all theme javascript files. | |
04c01408 | 768 | * @param bool $footer true measn footer url, false means head |
e68c5f36 PS |
769 | * @return string |
770 | */ | |
04c01408 PS |
771 | public function javascript_content($footer=false) { |
772 | $type = $footer ? 'javascripts_footer' : 'javascripts'; | |
773 | ||
358c13cb PS |
774 | $js = array(); |
775 | // find out wanted parent javascripts | |
776 | $excludes = null; | |
777 | if (is_array($this->parents_exclude_javascripts) or $this->parents_exclude_javascripts === true) { | |
778 | $excludes = $this->parents_exclude_javascripts; | |
779 | } else { | |
780 | foreach ($this->parent_configs as $parent_config) { // the immediate parent first, base last | |
781 | if (!isset($parent_config->parents_exclude_javascripts)) { | |
782 | continue; | |
783 | } | |
784 | if (is_array($parent_config->parents_exclude_javascripts) or $parent_config->parents_exclude_javascripts === true) { | |
785 | $excludes = $parent_config->parents_exclude_javascripts; | |
786 | break; | |
787 | } | |
788 | } | |
789 | } | |
790 | if ($excludes !== true) { | |
791 | foreach (array_reverse($this->parent_configs) as $parent_config) { // base first, the immediate parent last | |
792 | $parent = $parent_config->name; | |
04c01408 | 793 | if (empty($parent_config->$type)) { |
358c13cb PS |
794 | continue; |
795 | } | |
796 | if (!empty($excludes[$parent]) and $excludes[$parent] === true) { | |
797 | continue; | |
798 | } | |
04c01408 | 799 | foreach ($parent_config->$type as $javascript) { |
358c13cb PS |
800 | if (!empty($excludes[$parent]) and is_array($excludes[$parent]) |
801 | and in_array($javascript, $excludes[$parent])) { | |
802 | continue; | |
803 | } | |
804 | $javascriptfile = "$parent_config->dir/javascript/$javascript.js"; | |
805 | if (is_readable($javascriptfile)) { | |
806 | $js[] = "/*** Parent theme $parent/javascript/$javascript.js ***/\n\n" . file_get_contents($javascriptfile); | |
807 | } | |
808 | } | |
809 | } | |
810 | } | |
811 | ||
812 | // current theme javascripts | |
04c01408 PS |
813 | if (is_array($this->$type)) { |
814 | foreach ($this->$type as $javascript) { | |
358c13cb PS |
815 | $javascriptfile = "$this->dir/javascript/$javascript.js"; |
816 | if (is_readable($javascriptfile)) { | |
817 | $js[] = "/*** This theme $this->name/javascript/$javascript.js ***/\n\n" . file_get_contents($javascriptfile); | |
818 | } | |
819 | } | |
820 | } | |
821 | ||
822 | return implode("\n\n", $js); | |
e68c5f36 PS |
823 | } |
824 | ||
78946b9b | 825 | protected function post_process($css) { |
df06c1c4 PS |
826 | global $CFG; |
827 | ||
78946b9b PS |
828 | // now resolve all image locations |
829 | if (preg_match_all('/\[\[pix:([a-z_]+\|)?([^\]]+)\]\]/', $css, $matches, PREG_SET_ORDER)) { | |
830 | $replaced = array(); | |
831 | foreach ($matches as $match) { | |
832 | if (isset($replaced[$match[0]])) { | |
833 | continue; | |
834 | } | |
835 | $replaced[$match[0]] = true; | |
836 | $imagename = $match[2]; | |
837 | $component = rtrim($match[1], '|'); | |
b9bc2019 | 838 | $imageurl = $this->pix_url($imagename, $component)->out(false); |
df06c1c4 PS |
839 | // we do not need full url because the image.php is always in the same dir |
840 | $imageurl = str_replace("$CFG->httpswwwroot/theme/", '', $imageurl); | |
841 | $css = str_replace($match[0], $imageurl, $css); | |
ebebf55c | 842 | } |
34a2777c | 843 | } |
17a6649b | 844 | |
78946b9b PS |
845 | // now resolve all theme settings or do any other postprocessing |
846 | $csspostprocess = $this->csspostprocess; | |
847 | if (function_exists($csspostprocess)) { | |
848 | $css = $csspostprocess($css, $this); | |
fdeb7fa1 | 849 | } |
850 | ||
78946b9b | 851 | return $css; |
ebebf55c | 852 | } |
17a6649b | 853 | |
ebebf55c | 854 | /** |
78946b9b PS |
855 | * Return the URL for an image |
856 | * | |
857 | * @param string $imagename the name of the icon. | |
858 | * @param string $component, specification of one plugin like in get_string() | |
859 | * @return moodle_url | |
fdeb7fa1 | 860 | */ |
c39e5ba2 | 861 | public function pix_url($imagename, $component) { |
fdeb7fa1 | 862 | global $CFG; |
78946b9b PS |
863 | |
864 | $params = array('theme'=>$this->name, 'image'=>$imagename); | |
865 | ||
866 | $rev = theme_get_revision(); | |
867 | if ($rev != -1) { | |
868 | $params['rev'] = $rev; | |
869 | } | |
870 | if (!empty($component) and $component !== 'moodle'and $component !== 'core') { | |
871 | $params['component'] = $component; | |
fdeb7fa1 | 872 | } |
78946b9b PS |
873 | |
874 | return new moodle_url("$CFG->httpswwwroot/theme/image.php", $params); | |
fdeb7fa1 | 875 | } |
876 | ||
877 | /** | |
78946b9b PS |
878 | * Resolves the real image location. |
879 | * @param string $image name of image, may contain relative path | |
880 | * @param string $component | |
881 | * @return string full file path | |
fdeb7fa1 | 882 | */ |
78946b9b PS |
883 | public function resolve_image_location($image, $component) { |
884 | global $CFG; | |
885 | ||
886 | if ($component === 'moodle' or $component === 'core' or empty($component)) { | |
887 | if ($imagefile = $this->image_exists("$this->dir/pix_core/$image")) { | |
888 | return $imagefile; | |
889 | } | |
890 | foreach (array_reverse($this->parent_configs) as $parent_config) { // base first, the immediate parent last | |
891 | if ($imagefile = $this->image_exists("$parent_config->dir/pix_core/$image")) { | |
892 | return $imagefile; | |
893 | } | |
894 | } | |
895 | if ($imagefile = $this->image_exists("$CFG->dirroot/pix/$image")) { | |
896 | return $imagefile; | |
897 | } | |
898 | return null; | |
899 | ||
900 | } else if ($component === 'theme') { //exception | |
901 | if ($image === 'favicon') { | |
902 | return "$this->dir/pix/favicon.ico"; | |
903 | } | |
904 | if ($imagefile = $this->image_exists("$this->dir/pix/$image")) { | |
905 | return $imagefile; | |
906 | } | |
907 | foreach (array_reverse($this->parent_configs) as $parent_config) { // base first, the immediate parent last | |
908 | if ($imagefile = $this->image_exists("$parent_config->dir/pix/$image")) { | |
909 | return $imagefile; | |
910 | } | |
911 | } | |
912 | return null; | |
913 | ||
78946b9b PS |
914 | } else { |
915 | if (strpos($component, '_') === false) { | |
916 | $component = 'mod_'.$component; | |
917 | } | |
918 | list($type, $plugin) = explode('_', $component, 2); | |
919 | ||
920 | if ($imagefile = $this->image_exists("$this->dir/pix_plugins/$type/$plugin/$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_plugins/$type/$plugin/$image")) { | |
925 | return $imagefile; | |
926 | } | |
927 | } | |
928 | $dir = get_plugin_directory($type, $plugin); | |
929 | if ($imagefile = $this->image_exists("$dir/pix/$image")) { | |
930 | return $imagefile; | |
931 | } | |
932 | return null; | |
fdeb7fa1 | 933 | } |
fdeb7fa1 | 934 | } |
935 | ||
d4a03c00 | 936 | /** |
78946b9b PS |
937 | * Checks if file with any image extension exists. |
938 | * @param string $filepath | |
939 | * @return string image name with extension | |
d4a03c00 | 940 | */ |
78946b9b PS |
941 | private static function image_exists($filepath) { |
942 | if (file_exists("$filepath.gif")) { | |
943 | return "$filepath.gif"; | |
944 | } else if (file_exists("$filepath.png")) { | |
945 | return "$filepath.png"; | |
946 | } else if (file_exists("$filepath.jpg")) { | |
947 | return "$filepath.jpg"; | |
948 | } else if (file_exists("$filepath.jpeg")) { | |
949 | return "$filepath.jpeg"; | |
d4a03c00 | 950 | } else { |
78946b9b | 951 | return false; |
d4a03c00 | 952 | } |
953 | } | |
954 | ||
fdeb7fa1 | 955 | /** |
78946b9b PS |
956 | * Loads the theme config from config.php file. |
957 | * @param string $themename | |
958 | * @param object $settings from config_plugins table | |
959 | * @return object | |
fdeb7fa1 | 960 | */ |
78946b9b PS |
961 | private static function find_theme_config($themename, $settings) { |
962 | // We have to use the variable name $THEME (upper case) because that | |
963 | // is what is used in theme config.php files. | |
fdeb7fa1 | 964 | |
78946b9b PS |
965 | if (!$dir = theme_config::find_theme_location($themename)) { |
966 | return null; | |
fdeb7fa1 | 967 | } |
2f67a9b3 | 968 | |
78946b9b PS |
969 | $THEME = new object(); |
970 | $THEME->name = $themename; | |
971 | $THEME->dir = $dir; | |
972 | $THEME->settings = $settings; | |
973 | ||
974 | global $CFG; // just in case somebody tries to use $CFG in theme config | |
975 | include("$THEME->dir/config.php"); | |
976 | ||
977 | // verify the theme configuration is OK | |
978 | if (!is_array($THEME->parents)) { | |
979 | // parents option is mandatory now | |
980 | return null; | |
fdeb7fa1 | 981 | } |
982 | ||
78946b9b | 983 | return $THEME; |
fdeb7fa1 | 984 | } |
985 | ||
d4a03c00 | 986 | /** |
78946b9b PS |
987 | * Finds the theme location and verifies the theme has all needed files |
988 | * and is not obsoleted. | |
989 | * @param string $themename | |
990 | * @return string full dir path or null if not found | |
d4a03c00 | 991 | */ |
78946b9b PS |
992 | private static function find_theme_location($themename) { |
993 | global $CFG; | |
994 | ||
995 | if (file_exists("$CFG->dirroot/theme/$themename/config.php")) { | |
996 | $dir = "$CFG->dirroot/theme/$themename"; | |
d4a03c00 | 997 | |
7d875874 | 998 | } else { |
78946b9b | 999 | return null; |
d4a03c00 | 1000 | } |
78946b9b PS |
1001 | |
1002 | if (file_exists("$dir/styles.php")) { | |
1003 | //legacy theme - needs to be upgraded - upgrade info is displayed on the admin settings page | |
1004 | return null; | |
1005 | } | |
2f67a9b3 | 1006 | |
78946b9b | 1007 | return $dir; |
d4a03c00 | 1008 | } |
1009 | ||
1d13c75c | 1010 | /** |
78946b9b | 1011 | * Get the renderer for a part of Moodle for this theme. |
78946b9b | 1012 | * @param moodle_page $page the page we are rendering |
56cbc53b | 1013 | * @param string $module the name of part of moodle. E.g. 'core', 'quiz', 'qtype_multichoice'. |
78946b9b | 1014 | * @param string $subtype optional subtype such as 'news' resulting to 'mod_forum_news' |
c927e35c | 1015 | * @param string $target one of rendering target constants |
78946b9b | 1016 | * @return renderer_base the requested renderer. |
1d13c75c | 1017 | */ |
c927e35c | 1018 | public function get_renderer(moodle_page $page, $component, $subtype = null, $target = null) { |
78946b9b | 1019 | if (is_null($this->rf)) { |
c927e35c | 1020 | $classname = $this->rendererfactory; |
78946b9b | 1021 | $this->rf = new $classname($this); |
1d13c75c | 1022 | } |
78946b9b | 1023 | |
c927e35c | 1024 | return $this->rf->get_renderer($page, $component, $subtype, $target); |
1d13c75c | 1025 | } |
1026 | ||
fdeb7fa1 | 1027 | /** |
78946b9b PS |
1028 | * Get the information from {@link $layouts} for this type of page. |
1029 | * @param string $pagelayout the the page layout name. | |
1030 | * @return array the appropriate part of {@link $layouts}. | |
fdeb7fa1 | 1031 | */ |
78946b9b PS |
1032 | protected function layout_info_for_page($pagelayout) { |
1033 | if (array_key_exists($pagelayout, $this->layouts)) { | |
1034 | return $this->layouts[$pagelayout]; | |
1035 | } else { | |
191b267b PS |
1036 | debugging('Invalid page layout specified: ' . $pagelayout); |
1037 | return $this->layouts['standard']; | |
fdeb7fa1 | 1038 | } |
1039 | } | |
1040 | ||
1041 | /** | |
78946b9b PS |
1042 | * Given the settings of this theme, and the page pagelayout, return the |
1043 | * full path of the page layout file to use. | |
1044 | * | |
1045 | * Used by {@link core_renderer::header()}. | |
1046 | * | |
1047 | * @param string $pagelayout the the page layout name. | |
1048 | * @return string Full path to the lyout file to use | |
ebebf55c | 1049 | */ |
78946b9b | 1050 | public function layout_file($pagelayout) { |
ebebf55c | 1051 | global $CFG; |
fdeb7fa1 | 1052 | |
78946b9b PS |
1053 | $layoutinfo = $this->layout_info_for_page($pagelayout); |
1054 | $layoutfile = $layoutinfo['file']; | |
1055 | $theme = $layoutinfo['theme']; | |
fdeb7fa1 | 1056 | |
78946b9b PS |
1057 | if ($dir = $this->find_theme_location($theme)) { |
1058 | $path = "$dir/layout/$layoutfile"; | |
fdeb7fa1 | 1059 | |
78946b9b PS |
1060 | // Check the template exists, return general base theme template if not. |
1061 | if (is_readable($path)) { | |
1062 | return $path; | |
1063 | } | |
34a2777c | 1064 | } |
1065 | ||
191b267b | 1066 | debugging('Can not find layout file for: ' . $pagelayout); |
78946b9b PS |
1067 | // fallback to standard normal layout |
1068 | return "$CFG->dirroot/theme/base/layout/general.php"; | |
1069 | } | |
ebebf55c | 1070 | |
78946b9b PS |
1071 | /** |
1072 | * Returns auxiliary page layout options specified in layout configuration array. | |
1073 | * @param string $pagelayout | |
1074 | * @return array | |
1075 | */ | |
1076 | public function pagelayout_options($pagelayout) { | |
1077 | $info = $this->layout_info_for_page($pagelayout); | |
1078 | if (!empty($info['options'])) { | |
1079 | return $info['options']; | |
34a2777c | 1080 | } |
78946b9b PS |
1081 | return array(); |
1082 | } | |
34a2777c | 1083 | |
78946b9b PS |
1084 | /** |
1085 | * Inform a block_manager about the block regions this theme wants on this | |
1086 | * page layout. | |
1087 | * @param string $pagelayout the general type of the page. | |
1088 | * @param block_manager $blockmanager the block_manger to set up. | |
1089 | * @return void | |
1090 | */ | |
1091 | public function setup_blocks($pagelayout, $blockmanager) { | |
1092 | $layoutinfo = $this->layout_info_for_page($pagelayout); | |
1093 | if (!empty($layoutinfo['regions'])) { | |
1094 | $blockmanager->add_regions($layoutinfo['regions']); | |
1095 | $blockmanager->set_default_region($layoutinfo['defaultregion']); | |
ebebf55c | 1096 | } |
34a2777c | 1097 | } |
1098 | ||
ebebf55c | 1099 | /** |
78946b9b PS |
1100 | * Get the list of all block regions known to this theme in all templates. |
1101 | * @return array internal region name => human readable name. | |
ebebf55c | 1102 | */ |
78946b9b PS |
1103 | public function get_all_block_regions() { |
1104 | $regions = array(); | |
78946b9b PS |
1105 | foreach ($this->layouts as $layoutinfo) { |
1106 | foreach ($layoutinfo['regions'] as $region) { | |
a546e528 MD |
1107 | $regionstring = get_string('region-' . $region, 'theme_' . $layoutinfo['theme']); |
1108 | if (substr($regionstring, 0, 1) != '[') { // A name exists in this theme, so use it | |
1109 | $regions[$region] = $regionstring; | |
1110 | } else { // Otherwise, try to find one elsewhere | |
1111 | // Check parents, if any | |
1112 | if (!empty($this->parents)) { | |
1113 | foreach ($this->parents as $parentthemename) { | |
1114 | $regionstring = get_string('region-' . $region, 'theme_'.$parentthemename); | |
1115 | if (substr($regionstring, 0, 1) != '[') { // A name exists in this theme, so use it | |
1116 | $regions[$region] = $regionstring; | |
1117 | } | |
1118 | } | |
1119 | } | |
1120 | if (empty($regions[$region])) { // Last resort, try the base theme for names | |
1121 | $regions[$region] = get_string('region-' . $region, 'theme_base'); | |
1122 | } | |
1123 | } | |
ebebf55c | 1124 | } |
34a2777c | 1125 | } |
78946b9b | 1126 | return $regions; |
34a2777c | 1127 | } |
ebebf55c | 1128 | } |
34a2777c | 1129 | |
78946b9b | 1130 | |
ebebf55c | 1131 | /** |
d9c8f425 | 1132 | * This class keeps track of which HTML tags are currently open. |
1133 | * | |
1134 | * This makes it much easier to always generate well formed XHTML output, even | |
1135 | * if execution terminates abruptly. Any time you output some opening HTML | |
1136 | * without the matching closing HTML, you should push the necessary close tags | |
1137 | * onto the stack. | |
ebebf55c | 1138 | * |
1139 | * @copyright 2009 Tim Hunt | |
1140 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
1141 | * @since Moodle 2.0 | |
1142 | */ | |
d9c8f425 | 1143 | class xhtml_container_stack { |
1144 | /** @var array stores the list of open containers. */ | |
1145 | protected $opencontainers = array(); | |
fa1afe32 | 1146 | /** |
d9c8f425 | 1147 | * @var array in developer debug mode, stores a stack trace of all opens and |
1148 | * closes, so we can output helpful error messages when there is a mismatch. | |
fa1afe32 | 1149 | */ |
d9c8f425 | 1150 | protected $log = array(); |
fa1afe32 | 1151 | /** |
d9c8f425 | 1152 | * Store whether we are developer debug mode. We need this in several places |
1153 | * including in the destructor where we may no thave access to $CFG. | |
1154 | * @var boolean | |
fa1afe32 | 1155 | */ |
d9c8f425 | 1156 | protected $isdebugging; |
34a2777c | 1157 | |
d9c8f425 | 1158 | public function __construct() { |
1159 | $this->isdebugging = debugging('', DEBUG_DEVELOPER); | |
ebebf55c | 1160 | } |
34a2777c | 1161 | |
fa1afe32 | 1162 | /** |
d9c8f425 | 1163 | * Push the close HTML for a recently opened container onto the stack. |
1164 | * @param string $type The type of container. This is checked when {@link pop()} | |
1165 | * is called and must match, otherwise a developer debug warning is output. | |
1166 | * @param string $closehtml The HTML required to close the container. | |
1167 | * @return void | |
fa1afe32 | 1168 | */ |
d9c8f425 | 1169 | public function push($type, $closehtml) { |
1170 | $container = new stdClass; | |
1171 | $container->type = $type; | |
1172 | $container->closehtml = $closehtml; | |
1173 | if ($this->isdebugging) { | |
1174 | $this->log('Open', $type); | |
3aaa27f4 | 1175 | } |
d9c8f425 | 1176 | array_push($this->opencontainers, $container); |
ebebf55c | 1177 | } |
34a2777c | 1178 | |
fa1afe32 | 1179 | /** |
d9c8f425 | 1180 | * Pop the HTML for the next closing container from the stack. The $type |
1181 | * must match the type passed when the container was opened, otherwise a | |
1182 | * warning will be output. | |
1183 | * @param string $type The type of container. | |
1184 | * @return string the HTML required to close the container. | |
fa1afe32 | 1185 | */ |
d9c8f425 | 1186 | public function pop($type) { |
1187 | if (empty($this->opencontainers)) { | |
1188 | debugging('<p>There are no more open containers. This suggests there is a nesting problem.</p>' . | |
1189 | $this->output_log(), DEBUG_DEVELOPER); | |
1190 | return; | |
3aaa27f4 | 1191 | } |
ebebf55c | 1192 | |
d9c8f425 | 1193 | $container = array_pop($this->opencontainers); |
1194 | if ($container->type != $type) { | |
1195 | debugging('<p>The type of container to be closed (' . $container->type . | |
1196 | ') does not match the type of the next open container (' . $type . | |
1197 | '). This suggests there is a nesting problem.</p>' . | |
1198 | $this->output_log(), DEBUG_DEVELOPER); | |
ebebf55c | 1199 | } |
d9c8f425 | 1200 | if ($this->isdebugging) { |
1201 | $this->log('Close', $type); | |
e8775320 | 1202 | } |
d9c8f425 | 1203 | return $container->closehtml; |
ebebf55c | 1204 | } |
e8775320 | 1205 | |
fa1afe32 | 1206 | /** |
d9c8f425 | 1207 | * Close all but the last open container. This is useful in places like error |
1208 | * handling, where you want to close all the open containers (apart from <body>) | |
1209 | * before outputting the error message. | |
1210 | * @param bool $shouldbenone assert that the stack should be empty now - causes a | |
1211 | * developer debug warning if it isn't. | |
1212 | * @return string the HTML required to close any open containers inside <body>. | |
fa1afe32 | 1213 | */ |
d9c8f425 | 1214 | public function pop_all_but_last($shouldbenone = false) { |
1215 | if ($shouldbenone && count($this->opencontainers) != 1) { | |
1216 | debugging('<p>Some HTML tags were opened in the body of the page but not closed.</p>' . | |
1217 | $this->output_log(), DEBUG_DEVELOPER); | |
1218 | } | |
1219 | $output = ''; | |
1220 | while (count($this->opencontainers) > 1) { | |
1221 | $container = array_pop($this->opencontainers); | |
1222 | $output .= $container->closehtml; | |
e8775320 | 1223 | } |
d9c8f425 | 1224 | return $output; |
e8775320 | 1225 | } |
34a2777c | 1226 | |
ebebf55c | 1227 | /** |
d9c8f425 | 1228 | * You can call this function if you want to throw away an instance of this |
1229 | * class without properly emptying the stack (for example, in a unit test). | |
1230 | * Calling this method stops the destruct method from outputting a developer | |
1231 | * debug warning. After calling this method, the instance can no longer be used. | |
1232 | * @return void | |
ebebf55c | 1233 | */ |
d9c8f425 | 1234 | public function discard() { |
1235 | $this->opencontainers = null; | |
ebebf55c | 1236 | } |
d9c8f425 | 1237 | |
fa1afe32 | 1238 | /** |
d9c8f425 | 1239 | * Adds an entry to the log. |
1240 | * @param string $action The name of the action | |
1241 | * @param string $type The type of action | |
1242 | * @return void | |
fa1afe32 | 1243 | */ |
d9c8f425 | 1244 | protected function log($action, $type) { |
1245 | $this->log[] = '<li>' . $action . ' ' . $type . ' at:' . | |
1246 | format_backtrace(debug_backtrace()) . '</li>'; | |
ebebf55c | 1247 | } |
34a2777c | 1248 | |
fa1afe32 | 1249 | /** |
d9c8f425 | 1250 | * Outputs the log's contents as a HTML list. |
1251 | * @return string HTML list of the log | |
fa1afe32 | 1252 | */ |
d9c8f425 | 1253 | protected function output_log() { |
1254 | return '<ul>' . implode("\n", $this->log) . '</ul>'; | |
34a2777c | 1255 | } |
1256 | } |