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(); | |
4399b9d5 | 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 | ||
4d56ee95 SH |
326 | /** |
327 | * If set to true then this theme will not be shown in the theme selector unless | |
328 | * theme designer mode is turned on. | |
329 | * @var bool | |
330 | */ | |
331 | public $hidefromselector = false; | |
332 | ||
78946b9b PS |
333 | /** |
334 | * Instance of the renderer_factory implementation | |
fdeb7fa1 | 335 | * we are using. Implementation detail. |
78946b9b | 336 | * @var renderer_factory |
fdeb7fa1 | 337 | */ |
338 | protected $rf = null; | |
339 | ||
340 | /** | |
78946b9b PS |
341 | * List of parent config objects. |
342 | * @var array list of parent configs | |
343 | **/ | |
344 | protected $parent_configs = array(); | |
fdeb7fa1 | 345 | |
571fa828 | 346 | /** |
ebebf55c | 347 | * Load the config.php file for a particular theme, and return an instance |
348 | * of this class. (That is, this is a factory method.) | |
349 | * | |
350 | * @param string $themename the name of the theme. | |
351 | * @return theme_config an instance of this class. | |
571fa828 | 352 | */ |
ebebf55c | 353 | public static function load($themename) { |
354 | global $CFG; | |
571fa828 | 355 | |
78946b9b PS |
356 | // load theme settings from db |
357 | try { | |
358 | $settings = get_config('theme_'.$themename); | |
359 | } catch (dml_exception $e) { | |
da0c0e25 | 360 | // most probably moodle tables not created yet |
78946b9b PS |
361 | $settings = new object(); |
362 | } | |
ebebf55c | 363 | |
78946b9b PS |
364 | if ($config = theme_config::find_theme_config($themename, $settings)) { |
365 | return new theme_config($config); | |
5f0baa43 PS |
366 | |
367 | } else if ($themename == theme_config::DEFAULT_THEME) { | |
368 | throw new coding_exception('Default theme '.theme_config::DEFAULT_THEME.' not available or broken!'); | |
369 | ||
78946b9b PS |
370 | } else { |
371 | // bad luck, the requested theme has some problems - admin see details in theme config | |
5f0baa43 | 372 | return new theme_config(theme_config::find_theme_config(theme_config::DEFAULT_THEME, $settings)); |
78946b9b PS |
373 | } |
374 | } | |
ebebf55c | 375 | |
78946b9b PS |
376 | /** |
377 | * Theme diagnostic code. It is very problematic to send debug output | |
378 | * to the actual CSS file, instead this functions is supposed to | |
ae5b6801 | 379 | * diagnose given theme and highlights all potential problems. |
78946b9b PS |
380 | * This information should be available from the theme selection page |
381 | * or some other debug page for theme designers. | |
382 | * | |
383 | * @param string $themename | |
384 | * @return array description of problems | |
385 | */ | |
386 | public static function diagnose($themename) { | |
5ef719e7 | 387 | //TODO: MDL-21108 |
78946b9b PS |
388 | return array(); |
389 | } | |
390 | ||
391 | /** | |
392 | * Private constructor, can be called only from the factory method. | |
393 | * @param stdClass $config | |
394 | */ | |
395 | private function __construct($config) { | |
396 | global $CFG; //needed for included lib.php files | |
397 | ||
398 | $this->settings = $config->settings; | |
399 | $this->name = $config->name; | |
400 | $this->dir = $config->dir; | |
401 | ||
402 | if ($this->name != 'base') { | |
403 | $baseconfig = theme_config::find_theme_config('base', $this->settings); | |
404 | } else { | |
405 | $baseconfig = $config; | |
571fa828 | 406 | } |
ebebf55c | 407 | |
fef27578 | 408 | $configurable = array('parents', 'sheets', 'parents_exclude_sheets', 'plugins_exclude_sheets', 'javascripts', 'javascripts_footer', |
d2c394f3 | 409 | 'parents_exclude_javascripts', 'layouts', 'resource_mp3player_colors', 'enable_dock', |
4d56ee95 | 410 | 'filter_mediaplugin_colors', 'rendererfactory', 'csspostprocess', 'editor_sheets', 'rarrow', 'larrow', 'hidefromselector'); |
ebebf55c | 411 | |
78946b9b PS |
412 | foreach ($config as $key=>$value) { |
413 | if (in_array($key, $configurable)) { | |
414 | $this->$key = $value; | |
415 | } | |
416 | } | |
417 | ||
418 | // verify all parents and load configs and renderers | |
419 | foreach ($this->parents as $parent) { | |
420 | if ($parent == 'base') { | |
421 | $parent_config = $baseconfig; | |
422 | } else if (!$parent_config = theme_config::find_theme_config($parent, $this->settings)) { | |
423 | // this is not good - better exclude faulty parents | |
424 | continue; | |
425 | } | |
426 | $libfile = $parent_config->dir.'/lib.php'; | |
427 | if (is_readable($libfile)) { | |
428 | // theme may store various function here | |
429 | include_once($libfile); | |
430 | } | |
431 | $renderersfile = $parent_config->dir.'/renderers.php'; | |
432 | if (is_readable($renderersfile)) { | |
433 | // may contain core and plugin renderers and renderer factory | |
434 | include_once($renderersfile); | |
435 | } | |
436 | $this->parent_configs[$parent] = $parent_config; | |
437 | $rendererfile = $parent_config->dir.'/renderers.php'; | |
438 | if (is_readable($rendererfile)) { | |
439 | // may contain core and plugin renderers and renderer factory | |
440 | include_once($rendererfile); | |
441 | } | |
442 | } | |
443 | $libfile = $this->dir.'/lib.php'; | |
444 | if (is_readable($libfile)) { | |
445 | // theme may store various function here | |
446 | include_once($libfile); | |
447 | } | |
448 | $rendererfile = $this->dir.'/renderers.php'; | |
449 | if (is_readable($rendererfile)) { | |
450 | // may contain core and plugin renderers and renderer factory | |
451 | include_once($rendererfile); | |
452 | } | |
4399b9d5 | 453 | |
78946b9b PS |
454 | // cascade all layouts properly |
455 | foreach ($baseconfig->layouts as $layout=>$value) { | |
456 | if (!isset($this->layouts[$layout])) { | |
457 | foreach ($this->parent_configs as $parent_config) { | |
458 | if (isset($parent_config->layouts[$layout])) { | |
459 | $this->layouts[$layout] = $parent_config->layouts[$layout]; | |
460 | continue 2; | |
461 | } | |
462 | } | |
463 | $this->layouts[$layout] = $value; | |
464 | } | |
465 | } | |
466 | ||
467 | //fix arrows if needed | |
468 | $this->check_theme_arrows(); | |
571fa828 | 469 | } |
470 | ||
78946b9b PS |
471 | /* |
472 | * Checks if arrows $THEME->rarrow, $THEME->larrow have been set (theme/-/config.php). | |
473 | * If not it applies sensible defaults. | |
474 | * | |
475 | * Accessibility: right and left arrow Unicode characters for breadcrumb, calendar, | |
476 | * search forum block, etc. Important: these are 'silent' in a screen-reader | |
477 | * (unlike > »), and must be accompanied by text. | |
34a2777c | 478 | */ |
78946b9b PS |
479 | private function check_theme_arrows() { |
480 | if (!isset($this->rarrow) and !isset($this->larrow)) { | |
481 | // Default, looks good in Win XP/IE 6, Win/Firefox 1.5, Win/Netscape 8... | |
482 | // Also OK in Win 9x/2K/IE 5.x | |
483 | $this->rarrow = '►'; | |
484 | $this->larrow = '◄'; | |
485 | if (empty($_SERVER['HTTP_USER_AGENT'])) { | |
486 | $uagent = ''; | |
ebebf55c | 487 | } else { |
78946b9b PS |
488 | $uagent = $_SERVER['HTTP_USER_AGENT']; |
489 | } | |
490 | if (false !== strpos($uagent, 'Opera') | |
491 | || false !== strpos($uagent, 'Mac')) { | |
492 | // Looks good in Win XP/Mac/Opera 8/9, Mac/Firefox 2, Camino, Safari. | |
493 | // Not broken in Mac/IE 5, Mac/Netscape 7 (?). | |
494 | $this->rarrow = '▶'; | |
495 | $this->larrow = '◀'; | |
496 | } | |
497 | elseif (false !== strpos($uagent, 'Konqueror')) { | |
498 | $this->rarrow = '→'; | |
499 | $this->larrow = '←'; | |
500 | } | |
501 | elseif (isset($_SERVER['HTTP_ACCEPT_CHARSET']) | |
502 | && false === stripos($_SERVER['HTTP_ACCEPT_CHARSET'], 'utf-8')) { | |
503 | // (Win/IE 5 doesn't set ACCEPT_CHARSET, but handles Unicode.) | |
504 | // To be safe, non-Unicode browsers! | |
505 | $this->rarrow = '>'; | |
506 | $this->larrow = '<'; | |
507 | } | |
508 | ||
509 | /// RTL support - in RTL languages, swap r and l arrows | |
510 | if (right_to_left()) { | |
511 | $t = $this->rarrow; | |
512 | $this->rarrow = $this->larrow; | |
513 | $this->larrow = $t; | |
ebebf55c | 514 | } |
ebebf55c | 515 | } |
78946b9b | 516 | } |
ebebf55c | 517 | |
78946b9b PS |
518 | /** |
519 | * Returns output renderer prefixes, these are used when looking | |
520 | * for the overriden renderers in themes. | |
521 | * @return array | |
522 | */ | |
523 | public function renderer_prefixes() { | |
524 | global $CFG; // just in case the included files need it | |
525 | ||
596af93a | 526 | $prefixes = array('theme_'.$this->name); |
78946b9b PS |
527 | |
528 | foreach ($this->parent_configs as $parent) { | |
529 | $prefixes[] = 'theme_'.$parent->name; | |
530 | } | |
531 | ||
532 | return $prefixes; | |
34a2777c | 533 | } |
534 | ||
571fa828 | 535 | /** |
78946b9b PS |
536 | * Returns the stylesheet URL of this editor content |
537 | * @param bool $encoded false means use & and true use & in URLs | |
538 | * @return string | |
571fa828 | 539 | */ |
78946b9b PS |
540 | public function editor_css_url($encoded=true) { |
541 | global $CFG; | |
542 | ||
543 | $rev = theme_get_revision(); | |
544 | ||
545 | if ($rev > -1) { | |
546 | $params = array('theme'=>$this->name,'rev'=>$rev, 'type'=>'editor'); | |
547 | return new moodle_url($CFG->httpswwwroot.'/theme/styles.php', $params); | |
548 | } else { | |
549 | $params = array('theme'=>$this->name, 'type'=>'editor'); | |
550 | return new moodle_url($CFG->httpswwwroot.'/theme/styles_debug.php', $params); | |
571fa828 | 551 | } |
571fa828 | 552 | } |
553 | ||
554 | /** | |
78946b9b PS |
555 | * Returns the content of the CSS to be used in editor content |
556 | * @return string | |
571fa828 | 557 | */ |
78946b9b PS |
558 | public function editor_css_content() { |
559 | global $CFG; | |
560 | ||
561 | $css = ''; | |
562 | ||
563 | // first editor plugins | |
564 | $plugins = get_plugin_list('editor'); | |
565 | foreach ($plugins as $plugin=>$fulldir) { | |
566 | $sheetfile = "$fulldir/editor_styles.css"; | |
567 | if (is_readable($sheetfile)) { | |
568 | $css .= "/*** Editor $plugin content CSS ***/\n\n" . file_get_contents($sheetfile) . "\n\n"; | |
569 | } | |
570 | } | |
571 | // then parent themes | |
572 | foreach (array_reverse($this->parent_configs) as $parent_config) { // base first, the immediate parent last | |
573 | if (empty($parent_config->editor_sheets)) { | |
574 | continue; | |
575 | } | |
576 | foreach ($parent_config->editor_sheets as $sheet) { | |
577 | $sheetfile = "$parent_config->dir/$sheet.css"; | |
578 | if (is_readable($sheetfile)) { | |
579 | $css .= "/*** Parent theme $parent/$sheet ***/\n\n" . file_get_contents($sheetfile) . "\n\n"; | |
580 | } | |
581 | } | |
582 | } | |
583 | // finally this theme | |
584 | if (!empty($this->editor_sheets)) { | |
585 | foreach ($this->editor_sheets as $sheet) { | |
586 | $sheetfile = "$this->dir/$sheet.css"; | |
587 | if (is_readable($sheetfile)) { | |
588 | $css .= "/*** Theme $sheet ***/\n\n" . file_get_contents($sheetfile) . "\n\n"; | |
589 | } | |
590 | } | |
591 | } | |
592 | ||
593 | return $this->post_process($css); | |
571fa828 | 594 | } |
595 | ||
596 | /** | |
78946b9b PS |
597 | * Get the stylesheet URL of this theme |
598 | * @param bool $encoded false means use & and true use & in URLs | |
38aafea2 | 599 | * @return array of moodle_url |
571fa828 | 600 | */ |
efaa4c08 | 601 | public function css_urls(moodle_page $page) { |
78946b9b PS |
602 | global $CFG; |
603 | ||
604 | $rev = theme_get_revision(); | |
605 | ||
efaa4c08 | 606 | $urls = array(); |
efaa4c08 | 607 | |
78946b9b | 608 | if ($rev > -1) { |
78946b9b | 609 | if (check_browser_version('MSIE', 5) and !check_browser_version('MSIE', 8)) { |
537740cb SH |
610 | // We need to split the CSS files for IE |
611 | $urls[] = new moodle_url($CFG->httpswwwroot.'/theme/styles.php', array('theme'=>$this->name,'rev'=>$rev, 'type'=>'plugins')); | |
612 | $urls[] = new moodle_url($CFG->httpswwwroot.'/theme/styles.php', array('theme'=>$this->name,'rev'=>$rev, 'type'=>'parents')); | |
613 | $urls[] = new moodle_url($CFG->httpswwwroot.'/theme/styles.php', array('theme'=>$this->name,'rev'=>$rev, 'type'=>'theme')); | |
614 | } else { | |
615 | $urls[] = new moodle_url($CFG->httpswwwroot.'/theme/styles.php', array('theme'=>$this->name,'rev'=>$rev)); | |
78946b9b | 616 | } |
78946b9b | 617 | } else { |
7829cf79 PS |
618 | // find out the current CSS and cache it now for 5 seconds |
619 | // the point is to construct the CSS only once and pass it through the | |
620 | // dataroot to the script that actually serves the sheets | |
d6b64d15 PS |
621 | if (!defined('THEME_DESIGNER_CACHE_LIFETIME')) { |
622 | define('THEME_DESIGNER_CACHE_LIFETIME', 4); // this can be also set in config.php | |
623 | } | |
7829cf79 PS |
624 | $candidatesheet = "$CFG->dataroot/cache/theme/$this->name/designer.ser"; |
625 | if (!file_exists($candidatesheet)) { | |
626 | $css = $this->css_content(); | |
627 | check_dir_exists(dirname($candidatesheet), true, true); | |
628 | file_put_contents($candidatesheet, serialize($css)); | |
629 | ||
d6b64d15 | 630 | } else if (filemtime($candidatesheet) > time() - THEME_DESIGNER_CACHE_LIFETIME) { |
7829cf79 PS |
631 | if ($css = file_get_contents($candidatesheet)) { |
632 | $css = unserialize($css); | |
633 | } else { | |
634 | unlink($candidatesheet); | |
635 | $css = $this->css_content(); | |
636 | } | |
637 | ||
638 | } else { | |
639 | unlink($candidatesheet); | |
640 | $css = $this->css_content(); | |
641 | file_put_contents($candidatesheet, serialize($css)); | |
642 | } | |
643 | ||
c724f835 | 644 | $baseurl = $CFG->httpswwwroot.'/theme/styles_debug.php'; |
c09e1a2c PS |
645 | |
646 | if (check_browser_version('MSIE', 5)) { | |
647 | // lalala, IE does not allow more than 31 linked CSS files from main document | |
4399b9d5 PS |
648 | $urls[] = new moodle_url($CFG->httpswwwroot.'/theme/styles_debug.php', array('theme'=>$this->name, 'type'=>'ie', 'subtype'=>'plugins')); |
649 | $urls[] = new moodle_url($CFG->httpswwwroot.'/theme/styles_debug.php', array('theme'=>$this->name, 'type'=>'ie', 'subtype'=>'parents')); | |
650 | $urls[] = new moodle_url($CFG->httpswwwroot.'/theme/styles_debug.php', array('theme'=>$this->name, 'type'=>'ie', 'subtype'=>'theme')); | |
c09e1a2c PS |
651 | |
652 | } else { | |
653 | foreach ($css['plugins'] as $plugin=>$unused) { | |
654 | $urls[] = new moodle_url($baseurl, array('theme'=>$this->name,'type'=>'plugin', 'subtype'=>$plugin)); | |
655 | } | |
656 | foreach ($css['parents'] as $parent=>$sheets) { | |
657 | foreach ($sheets as $sheet=>$unused2) { | |
658 | $urls[] = new moodle_url($baseurl, array('theme'=>$this->name,'type'=>'parent', 'subtype'=>$parent, 'sheet'=>$sheet)); | |
659 | } | |
660 | } | |
661 | foreach ($css['theme'] as $sheet=>$unused) { | |
662 | $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 | 663 | } |
78946b9b | 664 | } |
78946b9b | 665 | } |
efaa4c08 PS |
666 | |
667 | return $urls; | |
571fa828 | 668 | } |
34a2777c | 669 | |
ebebf55c | 670 | /** |
78946b9b PS |
671 | * Returns the content of the one huge CSS merged from all style sheets. |
672 | * @return string | |
ebebf55c | 673 | */ |
78946b9b | 674 | public function css_content() { |
34a2777c | 675 | global $CFG; |
676 | ||
c724f835 | 677 | $css = array('plugins'=>array(), 'parents'=>array(), 'theme'=>array()); |
78946b9b | 678 | |
8c96c3cb | 679 | // get all plugin sheets |
78946b9b PS |
680 | $excludes = null; |
681 | if (is_array($this->plugins_exclude_sheets) or $this->plugins_exclude_sheets === true) { | |
682 | $excludes = $this->plugins_exclude_sheets; | |
683 | } else { | |
684 | foreach ($this->parent_configs as $parent_config) { // the immediate parent first, base last | |
685 | if (!isset($parent_config->plugins_exclude_sheets)) { | |
686 | continue; | |
687 | } | |
688 | if (is_array($parent_config->plugins_exclude_sheets) or $parent_config->plugins_exclude_sheets === true) { | |
689 | $excludes = $parent_config->plugins_exclude_sheets; | |
690 | break; | |
691 | } | |
692 | } | |
ebebf55c | 693 | } |
78946b9b PS |
694 | if ($excludes !== true) { |
695 | foreach (get_plugin_types() as $type=>$unused) { | |
696 | if ($type === 'theme') { | |
697 | continue; | |
698 | } | |
699 | if (!empty($excludes[$type]) and $excludes[$type] === true) { | |
700 | continue; | |
701 | } | |
702 | $plugins = get_plugin_list($type); | |
703 | foreach ($plugins as $plugin=>$fulldir) { | |
704 | if (!empty($excludes[$type]) and is_array($excludes[$type]) | |
705 | and in_array($plugin, $excludes[$type])) { | |
706 | continue; | |
707 | } | |
f8bb9666 SH |
708 | |
709 | $plugincontent = ''; | |
78946b9b PS |
710 | $sheetfile = "$fulldir/styles.css"; |
711 | if (is_readable($sheetfile)) { | |
f8bb9666 SH |
712 | $plugincontent .= "/*** Standard plugin $type/$plugin ***/\n\n"; |
713 | $plugincontent .= file_get_contents($sheetfile); | |
714 | } | |
715 | $sheetthemefile = "$fulldir/styles_{$this->name}.css"; | |
716 | if (is_readable($sheetthemefile)) { | |
717 | $plugincontent .= "\n/*** Standard plugin $type/$plugin for the {$this->name} theme ***/\n\n"; | |
718 | $plugincontent .= file_get_contents($sheetthemefile); | |
719 | } | |
720 | if (!empty($plugincontent)) { | |
721 | $css['plugins'][$type.'_'.$plugin] = $this->post_process($plugincontent); | |
78946b9b PS |
722 | } |
723 | } | |
724 | } | |
34a2777c | 725 | } |
726 | ||
78946b9b PS |
727 | // find out wanted parent sheets |
728 | $excludes = null; | |
729 | if (is_array($this->parents_exclude_sheets) or $this->parents_exclude_sheets === true) { | |
730 | $excludes = $this->parents_exclude_sheets; | |
731 | } else { | |
732 | foreach ($this->parent_configs as $parent_config) { // the immediate parent first, base last | |
733 | if (!isset($parent_config->parents_exclude_sheets)) { | |
734 | continue; | |
735 | } | |
736 | if (is_array($parent_config->parents_exclude_sheets) or $parent_config->parents_exclude_sheets === true) { | |
737 | $excludes = $parent_config->parents_exclude_sheets; | |
738 | break; | |
739 | } | |
740 | } | |
741 | } | |
742 | if ($excludes !== true) { | |
743 | foreach (array_reverse($this->parent_configs) as $parent_config) { // base first, the immediate parent last | |
744 | $parent = $parent_config->name; | |
745 | if (empty($parent_config->sheets)) { | |
746 | continue; | |
747 | } | |
748 | if (!empty($excludes[$parent]) and $excludes[$parent] === true) { | |
749 | continue; | |
750 | } | |
751 | foreach ($parent_config->sheets as $sheet) { | |
752 | if (!empty($excludes[$parent]) and is_array($excludes[$parent]) | |
753 | and in_array($sheet, $excludes[$parent])) { | |
754 | continue; | |
755 | } | |
756 | $sheetfile = "$parent_config->dir/style/$sheet.css"; | |
757 | if (is_readable($sheetfile)) { | |
358c13cb | 758 | $css['parents'][$parent][$sheet] = $this->post_process("/*** Parent theme $parent/style/$sheet.css ***/\n\n" . file_get_contents($sheetfile)); |
78946b9b PS |
759 | } |
760 | } | |
761 | } | |
762 | } | |
34a2777c | 763 | |
78946b9b PS |
764 | // current theme sheets |
765 | if (is_array($this->sheets)) { | |
766 | foreach ($this->sheets as $sheet) { | |
767 | $sheetfile = "$this->dir/style/$sheet.css"; | |
768 | if (is_readable($sheetfile)) { | |
358c13cb | 769 | $css['theme'][$sheet] = $this->post_process("/*** This theme $this->name/style/$sheet ***/\n\n" . file_get_contents($sheetfile)); |
78946b9b | 770 | } |
ebebf55c | 771 | } |
78946b9b PS |
772 | } |
773 | ||
774 | return $css; | |
775 | } | |
34a2777c | 776 | |
e68c5f36 PS |
777 | |
778 | /** | |
779 | * Get the javascript URL of this theme | |
baeb20bb | 780 | * @param bool $inhead true means haed url, false means footer |
e68c5f36 PS |
781 | * @return moodle_url |
782 | */ | |
baeb20bb | 783 | public function javascript_url($inhead) { |
e68c5f36 PS |
784 | global $CFG; |
785 | ||
786 | $rev = theme_get_revision(); | |
e68c5f36 | 787 | $params = array('theme'=>$this->name,'rev'=>$rev); |
baeb20bb PS |
788 | $params['type'] = $inhead ? 'head' : 'footer'; |
789 | ||
790 | return new moodle_url($CFG->httpswwwroot.'/theme/javascript.php', $params); | |
e68c5f36 PS |
791 | } |
792 | ||
793 | /** | |
794 | * Returns the content of the one huge javascript file merged from all theme javascript files. | |
baeb20bb | 795 | * @param bool $inhead |
e68c5f36 PS |
796 | * @return string |
797 | */ | |
baeb20bb PS |
798 | public function javascript_content($type) { |
799 | $type = ($type === 'footer') ? 'javascripts_footer' : 'javascripts'; | |
04c01408 | 800 | |
358c13cb PS |
801 | $js = array(); |
802 | // find out wanted parent javascripts | |
803 | $excludes = null; | |
804 | if (is_array($this->parents_exclude_javascripts) or $this->parents_exclude_javascripts === true) { | |
805 | $excludes = $this->parents_exclude_javascripts; | |
806 | } else { | |
807 | foreach ($this->parent_configs as $parent_config) { // the immediate parent first, base last | |
808 | if (!isset($parent_config->parents_exclude_javascripts)) { | |
809 | continue; | |
810 | } | |
811 | if (is_array($parent_config->parents_exclude_javascripts) or $parent_config->parents_exclude_javascripts === true) { | |
812 | $excludes = $parent_config->parents_exclude_javascripts; | |
813 | break; | |
814 | } | |
815 | } | |
816 | } | |
817 | if ($excludes !== true) { | |
818 | foreach (array_reverse($this->parent_configs) as $parent_config) { // base first, the immediate parent last | |
819 | $parent = $parent_config->name; | |
04c01408 | 820 | if (empty($parent_config->$type)) { |
358c13cb PS |
821 | continue; |
822 | } | |
823 | if (!empty($excludes[$parent]) and $excludes[$parent] === true) { | |
824 | continue; | |
825 | } | |
04c01408 | 826 | foreach ($parent_config->$type as $javascript) { |
358c13cb PS |
827 | if (!empty($excludes[$parent]) and is_array($excludes[$parent]) |
828 | and in_array($javascript, $excludes[$parent])) { | |
829 | continue; | |
830 | } | |
831 | $javascriptfile = "$parent_config->dir/javascript/$javascript.js"; | |
832 | if (is_readable($javascriptfile)) { | |
833 | $js[] = "/*** Parent theme $parent/javascript/$javascript.js ***/\n\n" . file_get_contents($javascriptfile); | |
834 | } | |
835 | } | |
836 | } | |
837 | } | |
838 | ||
839 | // current theme javascripts | |
04c01408 PS |
840 | if (is_array($this->$type)) { |
841 | foreach ($this->$type as $javascript) { | |
358c13cb PS |
842 | $javascriptfile = "$this->dir/javascript/$javascript.js"; |
843 | if (is_readable($javascriptfile)) { | |
844 | $js[] = "/*** This theme $this->name/javascript/$javascript.js ***/\n\n" . file_get_contents($javascriptfile); | |
845 | } | |
846 | } | |
847 | } | |
848 | ||
849 | return implode("\n\n", $js); | |
e68c5f36 PS |
850 | } |
851 | ||
78946b9b | 852 | protected function post_process($css) { |
df06c1c4 PS |
853 | global $CFG; |
854 | ||
78946b9b PS |
855 | // now resolve all image locations |
856 | if (preg_match_all('/\[\[pix:([a-z_]+\|)?([^\]]+)\]\]/', $css, $matches, PREG_SET_ORDER)) { | |
857 | $replaced = array(); | |
858 | foreach ($matches as $match) { | |
859 | if (isset($replaced[$match[0]])) { | |
860 | continue; | |
861 | } | |
862 | $replaced[$match[0]] = true; | |
863 | $imagename = $match[2]; | |
864 | $component = rtrim($match[1], '|'); | |
b9bc2019 | 865 | $imageurl = $this->pix_url($imagename, $component)->out(false); |
df06c1c4 PS |
866 | // we do not need full url because the image.php is always in the same dir |
867 | $imageurl = str_replace("$CFG->httpswwwroot/theme/", '', $imageurl); | |
868 | $css = str_replace($match[0], $imageurl, $css); | |
ebebf55c | 869 | } |
34a2777c | 870 | } |
17a6649b | 871 | |
78946b9b PS |
872 | // now resolve all theme settings or do any other postprocessing |
873 | $csspostprocess = $this->csspostprocess; | |
874 | if (function_exists($csspostprocess)) { | |
875 | $css = $csspostprocess($css, $this); | |
fdeb7fa1 | 876 | } |
877 | ||
78946b9b | 878 | return $css; |
ebebf55c | 879 | } |
17a6649b | 880 | |
ebebf55c | 881 | /** |
78946b9b PS |
882 | * Return the URL for an image |
883 | * | |
884 | * @param string $imagename the name of the icon. | |
885 | * @param string $component, specification of one plugin like in get_string() | |
886 | * @return moodle_url | |
fdeb7fa1 | 887 | */ |
c39e5ba2 | 888 | public function pix_url($imagename, $component) { |
fdeb7fa1 | 889 | global $CFG; |
78946b9b PS |
890 | |
891 | $params = array('theme'=>$this->name, 'image'=>$imagename); | |
892 | ||
893 | $rev = theme_get_revision(); | |
894 | if ($rev != -1) { | |
895 | $params['rev'] = $rev; | |
896 | } | |
897 | if (!empty($component) and $component !== 'moodle'and $component !== 'core') { | |
898 | $params['component'] = $component; | |
fdeb7fa1 | 899 | } |
78946b9b PS |
900 | |
901 | return new moodle_url("$CFG->httpswwwroot/theme/image.php", $params); | |
fdeb7fa1 | 902 | } |
903 | ||
904 | /** | |
78946b9b PS |
905 | * Resolves the real image location. |
906 | * @param string $image name of image, may contain relative path | |
907 | * @param string $component | |
908 | * @return string full file path | |
fdeb7fa1 | 909 | */ |
78946b9b PS |
910 | public function resolve_image_location($image, $component) { |
911 | global $CFG; | |
912 | ||
913 | if ($component === 'moodle' or $component === 'core' or empty($component)) { | |
914 | if ($imagefile = $this->image_exists("$this->dir/pix_core/$image")) { | |
915 | return $imagefile; | |
916 | } | |
917 | foreach (array_reverse($this->parent_configs) as $parent_config) { // base first, the immediate parent last | |
918 | if ($imagefile = $this->image_exists("$parent_config->dir/pix_core/$image")) { | |
919 | return $imagefile; | |
920 | } | |
921 | } | |
922 | if ($imagefile = $this->image_exists("$CFG->dirroot/pix/$image")) { | |
923 | return $imagefile; | |
924 | } | |
925 | return null; | |
926 | ||
927 | } else if ($component === 'theme') { //exception | |
928 | if ($image === 'favicon') { | |
929 | return "$this->dir/pix/favicon.ico"; | |
930 | } | |
931 | if ($imagefile = $this->image_exists("$this->dir/pix/$image")) { | |
932 | return $imagefile; | |
933 | } | |
934 | foreach (array_reverse($this->parent_configs) as $parent_config) { // base first, the immediate parent last | |
935 | if ($imagefile = $this->image_exists("$parent_config->dir/pix/$image")) { | |
936 | return $imagefile; | |
937 | } | |
938 | } | |
939 | return null; | |
940 | ||
78946b9b PS |
941 | } else { |
942 | if (strpos($component, '_') === false) { | |
943 | $component = 'mod_'.$component; | |
944 | } | |
945 | list($type, $plugin) = explode('_', $component, 2); | |
946 | ||
947 | if ($imagefile = $this->image_exists("$this->dir/pix_plugins/$type/$plugin/$image")) { | |
948 | return $imagefile; | |
949 | } | |
950 | foreach (array_reverse($this->parent_configs) as $parent_config) { // base first, the immediate parent last | |
951 | if ($imagefile = $this->image_exists("$parent_config->dir/pix_plugins/$type/$plugin/$image")) { | |
952 | return $imagefile; | |
953 | } | |
954 | } | |
955 | $dir = get_plugin_directory($type, $plugin); | |
956 | if ($imagefile = $this->image_exists("$dir/pix/$image")) { | |
957 | return $imagefile; | |
958 | } | |
959 | return null; | |
fdeb7fa1 | 960 | } |
fdeb7fa1 | 961 | } |
962 | ||
d4a03c00 | 963 | /** |
78946b9b PS |
964 | * Checks if file with any image extension exists. |
965 | * @param string $filepath | |
966 | * @return string image name with extension | |
d4a03c00 | 967 | */ |
78946b9b PS |
968 | private static function image_exists($filepath) { |
969 | if (file_exists("$filepath.gif")) { | |
970 | return "$filepath.gif"; | |
971 | } else if (file_exists("$filepath.png")) { | |
972 | return "$filepath.png"; | |
973 | } else if (file_exists("$filepath.jpg")) { | |
974 | return "$filepath.jpg"; | |
975 | } else if (file_exists("$filepath.jpeg")) { | |
976 | return "$filepath.jpeg"; | |
d4a03c00 | 977 | } else { |
78946b9b | 978 | return false; |
d4a03c00 | 979 | } |
980 | } | |
981 | ||
fdeb7fa1 | 982 | /** |
78946b9b PS |
983 | * Loads the theme config from config.php file. |
984 | * @param string $themename | |
985 | * @param object $settings from config_plugins table | |
986 | * @return object | |
fdeb7fa1 | 987 | */ |
78946b9b PS |
988 | private static function find_theme_config($themename, $settings) { |
989 | // We have to use the variable name $THEME (upper case) because that | |
990 | // is what is used in theme config.php files. | |
fdeb7fa1 | 991 | |
78946b9b PS |
992 | if (!$dir = theme_config::find_theme_location($themename)) { |
993 | return null; | |
fdeb7fa1 | 994 | } |
2f67a9b3 | 995 | |
78946b9b PS |
996 | $THEME = new object(); |
997 | $THEME->name = $themename; | |
998 | $THEME->dir = $dir; | |
999 | $THEME->settings = $settings; | |
1000 | ||
1001 | global $CFG; // just in case somebody tries to use $CFG in theme config | |
1002 | include("$THEME->dir/config.php"); | |
1003 | ||
1004 | // verify the theme configuration is OK | |
1005 | if (!is_array($THEME->parents)) { | |
1006 | // parents option is mandatory now | |
1007 | return null; | |
fdeb7fa1 | 1008 | } |
1009 | ||
78946b9b | 1010 | return $THEME; |
fdeb7fa1 | 1011 | } |
1012 | ||
d4a03c00 | 1013 | /** |
78946b9b PS |
1014 | * Finds the theme location and verifies the theme has all needed files |
1015 | * and is not obsoleted. | |
1016 | * @param string $themename | |
1017 | * @return string full dir path or null if not found | |
d4a03c00 | 1018 | */ |
78946b9b PS |
1019 | private static function find_theme_location($themename) { |
1020 | global $CFG; | |
1021 | ||
1022 | if (file_exists("$CFG->dirroot/theme/$themename/config.php")) { | |
1023 | $dir = "$CFG->dirroot/theme/$themename"; | |
d4a03c00 | 1024 | |
7d875874 | 1025 | } else { |
78946b9b | 1026 | return null; |
d4a03c00 | 1027 | } |
78946b9b PS |
1028 | |
1029 | if (file_exists("$dir/styles.php")) { | |
1030 | //legacy theme - needs to be upgraded - upgrade info is displayed on the admin settings page | |
1031 | return null; | |
1032 | } | |
2f67a9b3 | 1033 | |
78946b9b | 1034 | return $dir; |
d4a03c00 | 1035 | } |
1036 | ||
1d13c75c | 1037 | /** |
78946b9b | 1038 | * Get the renderer for a part of Moodle for this theme. |
78946b9b | 1039 | * @param moodle_page $page the page we are rendering |
56cbc53b | 1040 | * @param string $module the name of part of moodle. E.g. 'core', 'quiz', 'qtype_multichoice'. |
78946b9b | 1041 | * @param string $subtype optional subtype such as 'news' resulting to 'mod_forum_news' |
c927e35c | 1042 | * @param string $target one of rendering target constants |
78946b9b | 1043 | * @return renderer_base the requested renderer. |
1d13c75c | 1044 | */ |
c927e35c | 1045 | public function get_renderer(moodle_page $page, $component, $subtype = null, $target = null) { |
78946b9b | 1046 | if (is_null($this->rf)) { |
c927e35c | 1047 | $classname = $this->rendererfactory; |
78946b9b | 1048 | $this->rf = new $classname($this); |
1d13c75c | 1049 | } |
78946b9b | 1050 | |
c927e35c | 1051 | return $this->rf->get_renderer($page, $component, $subtype, $target); |
1d13c75c | 1052 | } |
1053 | ||
fdeb7fa1 | 1054 | /** |
78946b9b PS |
1055 | * Get the information from {@link $layouts} for this type of page. |
1056 | * @param string $pagelayout the the page layout name. | |
1057 | * @return array the appropriate part of {@link $layouts}. | |
fdeb7fa1 | 1058 | */ |
78946b9b PS |
1059 | protected function layout_info_for_page($pagelayout) { |
1060 | if (array_key_exists($pagelayout, $this->layouts)) { | |
1061 | return $this->layouts[$pagelayout]; | |
1062 | } else { | |
191b267b PS |
1063 | debugging('Invalid page layout specified: ' . $pagelayout); |
1064 | return $this->layouts['standard']; | |
fdeb7fa1 | 1065 | } |
1066 | } | |
1067 | ||
1068 | /** | |
78946b9b PS |
1069 | * Given the settings of this theme, and the page pagelayout, return the |
1070 | * full path of the page layout file to use. | |
1071 | * | |
1072 | * Used by {@link core_renderer::header()}. | |
1073 | * | |
1074 | * @param string $pagelayout the the page layout name. | |
1075 | * @return string Full path to the lyout file to use | |
ebebf55c | 1076 | */ |
78946b9b | 1077 | public function layout_file($pagelayout) { |
ebebf55c | 1078 | global $CFG; |
fdeb7fa1 | 1079 | |
78946b9b PS |
1080 | $layoutinfo = $this->layout_info_for_page($pagelayout); |
1081 | $layoutfile = $layoutinfo['file']; | |
fdeb7fa1 | 1082 | |
84db3ea2 SH |
1083 | if (array_key_exists('theme', $layoutinfo)) { |
1084 | $themes = array($layoutinfo['theme']); | |
1085 | } else { | |
1086 | $themes = array_merge(array($this->name),$this->parents); | |
1087 | } | |
4399b9d5 | 1088 | |
84db3ea2 SH |
1089 | foreach ($themes as $theme) { |
1090 | if ($dir = $this->find_theme_location($theme)) { | |
1091 | $path = "$dir/layout/$layoutfile"; | |
1092 | ||
1093 | // Check the template exists, return general base theme template if not. | |
1094 | if (is_readable($path)) { | |
1095 | return $path; | |
1096 | } | |
78946b9b | 1097 | } |
34a2777c | 1098 | } |
1099 | ||
191b267b | 1100 | debugging('Can not find layout file for: ' . $pagelayout); |
78946b9b PS |
1101 | // fallback to standard normal layout |
1102 | return "$CFG->dirroot/theme/base/layout/general.php"; | |
1103 | } | |
ebebf55c | 1104 | |
78946b9b PS |
1105 | /** |
1106 | * Returns auxiliary page layout options specified in layout configuration array. | |
1107 | * @param string $pagelayout | |
1108 | * @return array | |
1109 | */ | |
1110 | public function pagelayout_options($pagelayout) { | |
1111 | $info = $this->layout_info_for_page($pagelayout); | |
1112 | if (!empty($info['options'])) { | |
1113 | return $info['options']; | |
34a2777c | 1114 | } |
78946b9b PS |
1115 | return array(); |
1116 | } | |
34a2777c | 1117 | |
78946b9b PS |
1118 | /** |
1119 | * Inform a block_manager about the block regions this theme wants on this | |
1120 | * page layout. | |
1121 | * @param string $pagelayout the general type of the page. | |
1122 | * @param block_manager $blockmanager the block_manger to set up. | |
1123 | * @return void | |
1124 | */ | |
1125 | public function setup_blocks($pagelayout, $blockmanager) { | |
1126 | $layoutinfo = $this->layout_info_for_page($pagelayout); | |
1127 | if (!empty($layoutinfo['regions'])) { | |
1128 | $blockmanager->add_regions($layoutinfo['regions']); | |
1129 | $blockmanager->set_default_region($layoutinfo['defaultregion']); | |
ebebf55c | 1130 | } |
34a2777c | 1131 | } |
1132 | ||
cadc0d28 TH |
1133 | protected function get_region_name($region, $theme) { |
1134 | $regionstring = get_string('region-' . $region, 'theme_' . $theme); | |
1135 | // A name exists in this theme, so use it | |
1136 | if (substr($regionstring, 0, 1) != '[') { | |
1137 | return $regionstring; | |
1138 | } | |
1139 | ||
1140 | // Otherwise, try to find one elsewhere | |
1141 | // Check parents, if any | |
1142 | foreach ($this->parents as $parentthemename) { | |
1143 | $regionstring = get_string('region-' . $region, 'theme_' . $parentthemename); | |
1144 | if (substr($regionstring, 0, 1) != '[') { | |
1145 | return $regionstring; | |
1146 | } | |
1147 | } | |
1148 | ||
1149 | // Last resort, try the base theme for names | |
1150 | return get_string('region-' . $region, 'theme_base'); | |
1151 | } | |
1152 | ||
ebebf55c | 1153 | /** |
78946b9b PS |
1154 | * Get the list of all block regions known to this theme in all templates. |
1155 | * @return array internal region name => human readable name. | |
ebebf55c | 1156 | */ |
78946b9b PS |
1157 | public function get_all_block_regions() { |
1158 | $regions = array(); | |
78946b9b PS |
1159 | foreach ($this->layouts as $layoutinfo) { |
1160 | foreach ($layoutinfo['regions'] as $region) { | |
a9535f79 | 1161 | $regions[$region] = $this->get_region_name($region, $this->name); |
ebebf55c | 1162 | } |
34a2777c | 1163 | } |
78946b9b | 1164 | return $regions; |
34a2777c | 1165 | } |
ebebf55c | 1166 | } |
34a2777c | 1167 | |
78946b9b | 1168 | |
ebebf55c | 1169 | /** |
d9c8f425 | 1170 | * This class keeps track of which HTML tags are currently open. |
1171 | * | |
1172 | * This makes it much easier to always generate well formed XHTML output, even | |
1173 | * if execution terminates abruptly. Any time you output some opening HTML | |
1174 | * without the matching closing HTML, you should push the necessary close tags | |
1175 | * onto the stack. | |
ebebf55c | 1176 | * |
1177 | * @copyright 2009 Tim Hunt | |
1178 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
1179 | * @since Moodle 2.0 | |
1180 | */ | |
d9c8f425 | 1181 | class xhtml_container_stack { |
1182 | /** @var array stores the list of open containers. */ | |
1183 | protected $opencontainers = array(); | |
fa1afe32 | 1184 | /** |
d9c8f425 | 1185 | * @var array in developer debug mode, stores a stack trace of all opens and |
1186 | * closes, so we can output helpful error messages when there is a mismatch. | |
fa1afe32 | 1187 | */ |
d9c8f425 | 1188 | protected $log = array(); |
fa1afe32 | 1189 | /** |
d9c8f425 | 1190 | * Store whether we are developer debug mode. We need this in several places |
1191 | * including in the destructor where we may no thave access to $CFG. | |
1192 | * @var boolean | |
fa1afe32 | 1193 | */ |
d9c8f425 | 1194 | protected $isdebugging; |
34a2777c | 1195 | |
d9c8f425 | 1196 | public function __construct() { |
1197 | $this->isdebugging = debugging('', DEBUG_DEVELOPER); | |
ebebf55c | 1198 | } |
34a2777c | 1199 | |
fa1afe32 | 1200 | /** |
d9c8f425 | 1201 | * Push the close HTML for a recently opened container onto the stack. |
1202 | * @param string $type The type of container. This is checked when {@link pop()} | |
1203 | * is called and must match, otherwise a developer debug warning is output. | |
1204 | * @param string $closehtml The HTML required to close the container. | |
1205 | * @return void | |
fa1afe32 | 1206 | */ |
d9c8f425 | 1207 | public function push($type, $closehtml) { |
1208 | $container = new stdClass; | |
1209 | $container->type = $type; | |
1210 | $container->closehtml = $closehtml; | |
1211 | if ($this->isdebugging) { | |
1212 | $this->log('Open', $type); | |
3aaa27f4 | 1213 | } |
d9c8f425 | 1214 | array_push($this->opencontainers, $container); |
ebebf55c | 1215 | } |
34a2777c | 1216 | |
fa1afe32 | 1217 | /** |
d9c8f425 | 1218 | * Pop the HTML for the next closing container from the stack. The $type |
1219 | * must match the type passed when the container was opened, otherwise a | |
1220 | * warning will be output. | |
1221 | * @param string $type The type of container. | |
1222 | * @return string the HTML required to close the container. | |
fa1afe32 | 1223 | */ |
d9c8f425 | 1224 | public function pop($type) { |
1225 | if (empty($this->opencontainers)) { | |
1226 | debugging('<p>There are no more open containers. This suggests there is a nesting problem.</p>' . | |
1227 | $this->output_log(), DEBUG_DEVELOPER); | |
1228 | return; | |
3aaa27f4 | 1229 | } |
ebebf55c | 1230 | |
d9c8f425 | 1231 | $container = array_pop($this->opencontainers); |
1232 | if ($container->type != $type) { | |
1233 | debugging('<p>The type of container to be closed (' . $container->type . | |
1234 | ') does not match the type of the next open container (' . $type . | |
1235 | '). This suggests there is a nesting problem.</p>' . | |
1236 | $this->output_log(), DEBUG_DEVELOPER); | |
ebebf55c | 1237 | } |
d9c8f425 | 1238 | if ($this->isdebugging) { |
1239 | $this->log('Close', $type); | |
e8775320 | 1240 | } |
d9c8f425 | 1241 | return $container->closehtml; |
ebebf55c | 1242 | } |
e8775320 | 1243 | |
fa1afe32 | 1244 | /** |
d9c8f425 | 1245 | * Close all but the last open container. This is useful in places like error |
1246 | * handling, where you want to close all the open containers (apart from <body>) | |
1247 | * before outputting the error message. | |
1248 | * @param bool $shouldbenone assert that the stack should be empty now - causes a | |
1249 | * developer debug warning if it isn't. | |
1250 | * @return string the HTML required to close any open containers inside <body>. | |
fa1afe32 | 1251 | */ |
d9c8f425 | 1252 | public function pop_all_but_last($shouldbenone = false) { |
1253 | if ($shouldbenone && count($this->opencontainers) != 1) { | |
1254 | debugging('<p>Some HTML tags were opened in the body of the page but not closed.</p>' . | |
1255 | $this->output_log(), DEBUG_DEVELOPER); | |
1256 | } | |
1257 | $output = ''; | |
1258 | while (count($this->opencontainers) > 1) { | |
1259 | $container = array_pop($this->opencontainers); | |
1260 | $output .= $container->closehtml; | |
e8775320 | 1261 | } |
d9c8f425 | 1262 | return $output; |
e8775320 | 1263 | } |
34a2777c | 1264 | |
ebebf55c | 1265 | /** |
d9c8f425 | 1266 | * You can call this function if you want to throw away an instance of this |
1267 | * class without properly emptying the stack (for example, in a unit test). | |
1268 | * Calling this method stops the destruct method from outputting a developer | |
1269 | * debug warning. After calling this method, the instance can no longer be used. | |
1270 | * @return void | |
ebebf55c | 1271 | */ |
d9c8f425 | 1272 | public function discard() { |
1273 | $this->opencontainers = null; | |
ebebf55c | 1274 | } |
d9c8f425 | 1275 | |
fa1afe32 | 1276 | /** |
d9c8f425 | 1277 | * Adds an entry to the log. |
1278 | * @param string $action The name of the action | |
1279 | * @param string $type The type of action | |
1280 | * @return void | |
fa1afe32 | 1281 | */ |
d9c8f425 | 1282 | protected function log($action, $type) { |
1283 | $this->log[] = '<li>' . $action . ' ' . $type . ' at:' . | |
1284 | format_backtrace(debug_backtrace()) . '</li>'; | |
ebebf55c | 1285 | } |
34a2777c | 1286 | |
fa1afe32 | 1287 | /** |
d9c8f425 | 1288 | * Outputs the log's contents as a HTML list. |
1289 | * @return string HTML list of the log | |
fa1afe32 | 1290 | */ |
d9c8f425 | 1291 | protected function output_log() { |
1292 | return '<ul>' . implode("\n", $this->log) . '</ul>'; | |
34a2777c | 1293 | } |
1294 | } |