|
Moodle
2.2.1
http://www.collinsharper.com
|
00001 <?php 00002 00003 // This file is part of Moodle - http://moodle.org/ 00004 // 00005 // Moodle is free software: you can redistribute it and/or modify 00006 // it under the terms of the GNU General Public License as published by 00007 // the Free Software Foundation, either version 3 of the License, or 00008 // (at your option) any later version. 00009 // 00010 // Moodle is distributed in the hope that it will be useful, 00011 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00013 // GNU General Public License for more details. 00014 // 00015 // You should have received a copy of the GNU General Public License 00016 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 00017 00030 defined('MOODLE_INTERNAL') || die(); 00031 00032 require_once($CFG->libdir.'/outputcomponents.php'); 00033 require_once($CFG->libdir.'/outputactions.php'); 00034 require_once($CFG->libdir.'/outputfactories.php'); 00035 require_once($CFG->libdir.'/outputrenderers.php'); 00036 require_once($CFG->libdir.'/outputrequirementslib.php'); 00037 00042 function theme_reset_all_caches() { 00043 global $CFG; 00044 require_once("$CFG->libdir/filelib.php"); 00045 00046 set_config('themerev', empty($CFG->themerev) ? 1 : $CFG->themerev+1); 00047 fulldelete("$CFG->cachedir/theme"); 00048 } 00049 00055 function theme_set_designer_mod($state) { 00056 theme_reset_all_caches(); 00057 set_config('themedesignermode', (int)!empty($state)); 00058 } 00059 00064 function theme_get_revision() { 00065 global $CFG; 00066 00067 if (empty($CFG->themedesignermode)) { 00068 if (empty($CFG->themerev)) { 00069 return -1; 00070 } else { 00071 return $CFG->themerev; 00072 } 00073 00074 } else { 00075 return -1; 00076 } 00077 } 00078 00079 00099 class theme_config { 00103 const DEFAULT_THEME = 'standard'; 00104 00115 public $parents; 00116 00123 public $sheets = array(); 00124 00132 public $parents_exclude_sheets = null; 00133 00140 public $plugins_exclude_sheets = null; 00141 00148 public $editor_sheets = array(); 00149 00156 public $javascripts = array(); 00157 00164 public $javascripts_footer = array(); 00165 00173 public $parents_exclude_javascripts = null; 00174 00225 public $layouts = array(); 00226 00245 public $rendererfactory = 'standard_renderer_factory'; 00246 00256 public $csspostprocess = null; 00257 00268 public $rarrow = null; 00269 00280 public $larrow = null; 00281 00286 public $enablecourseajax = true; 00287 00288 //==Following properties are not configurable from theme config.php== 00289 00295 public $name; 00296 00302 public $dir; 00303 00309 public $setting = null; 00310 00316 public $enable_dock = false; 00317 00323 public $hidefromselector = false; 00324 00330 protected $rf = null; 00331 00336 protected $parent_configs = array(); 00337 00345 public static function load($themename) { 00346 global $CFG; 00347 00348 // load theme settings from db 00349 try { 00350 $settings = get_config('theme_'.$themename); 00351 } catch (dml_exception $e) { 00352 // most probably moodle tables not created yet 00353 $settings = new stdClass(); 00354 } 00355 00356 if ($config = theme_config::find_theme_config($themename, $settings)) { 00357 return new theme_config($config); 00358 00359 } else if ($themename == theme_config::DEFAULT_THEME) { 00360 throw new coding_exception('Default theme '.theme_config::DEFAULT_THEME.' not available or broken!'); 00361 00362 } else { 00363 // bad luck, the requested theme has some problems - admin see details in theme config 00364 return new theme_config(theme_config::find_theme_config(theme_config::DEFAULT_THEME, $settings)); 00365 } 00366 } 00367 00378 public static function diagnose($themename) { 00379 //TODO: MDL-21108 00380 return array(); 00381 } 00382 00387 private function __construct($config) { 00388 global $CFG; //needed for included lib.php files 00389 00390 $this->settings = $config->settings; 00391 $this->name = $config->name; 00392 $this->dir = $config->dir; 00393 00394 if ($this->name != 'base') { 00395 $baseconfig = theme_config::find_theme_config('base', $this->settings); 00396 } else { 00397 $baseconfig = $config; 00398 } 00399 00400 $configurable = array('parents', 'sheets', 'parents_exclude_sheets', 'plugins_exclude_sheets', 'javascripts', 'javascripts_footer', 00401 'parents_exclude_javascripts', 'layouts', 'enable_dock', 'enablecourseajax', 00402 'rendererfactory', 'csspostprocess', 'editor_sheets', 'rarrow', 'larrow', 'hidefromselector'); 00403 00404 foreach ($config as $key=>$value) { 00405 if (in_array($key, $configurable)) { 00406 $this->$key = $value; 00407 } 00408 } 00409 00410 // verify all parents and load configs and renderers 00411 foreach ($this->parents as $parent) { 00412 if ($parent == 'base') { 00413 $parent_config = $baseconfig; 00414 } else if (!$parent_config = theme_config::find_theme_config($parent, $this->settings)) { 00415 // this is not good - better exclude faulty parents 00416 continue; 00417 } 00418 $libfile = $parent_config->dir.'/lib.php'; 00419 if (is_readable($libfile)) { 00420 // theme may store various function here 00421 include_once($libfile); 00422 } 00423 $renderersfile = $parent_config->dir.'/renderers.php'; 00424 if (is_readable($renderersfile)) { 00425 // may contain core and plugin renderers and renderer factory 00426 include_once($renderersfile); 00427 } 00428 $this->parent_configs[$parent] = $parent_config; 00429 $rendererfile = $parent_config->dir.'/renderers.php'; 00430 if (is_readable($rendererfile)) { 00431 // may contain core and plugin renderers and renderer factory 00432 include_once($rendererfile); 00433 } 00434 } 00435 $libfile = $this->dir.'/lib.php'; 00436 if (is_readable($libfile)) { 00437 // theme may store various function here 00438 include_once($libfile); 00439 } 00440 $rendererfile = $this->dir.'/renderers.php'; 00441 if (is_readable($rendererfile)) { 00442 // may contain core and plugin renderers and renderer factory 00443 include_once($rendererfile); 00444 } 00445 00446 // cascade all layouts properly 00447 foreach ($baseconfig->layouts as $layout=>$value) { 00448 if (!isset($this->layouts[$layout])) { 00449 foreach ($this->parent_configs as $parent_config) { 00450 if (isset($parent_config->layouts[$layout])) { 00451 $this->layouts[$layout] = $parent_config->layouts[$layout]; 00452 continue 2; 00453 } 00454 } 00455 $this->layouts[$layout] = $value; 00456 } 00457 } 00458 00459 //fix arrows if needed 00460 $this->check_theme_arrows(); 00461 } 00462 00463 /* 00464 * Checks if arrows $THEME->rarrow, $THEME->larrow have been set (theme/-/config.php). 00465 * If not it applies sensible defaults. 00466 * 00467 * Accessibility: right and left arrow Unicode characters for breadcrumb, calendar, 00468 * search forum block, etc. Important: these are 'silent' in a screen-reader 00469 * (unlike > »), and must be accompanied by text. 00470 */ 00471 private function check_theme_arrows() { 00472 if (!isset($this->rarrow) and !isset($this->larrow)) { 00473 // Default, looks good in Win XP/IE 6, Win/Firefox 1.5, Win/Netscape 8... 00474 // Also OK in Win 9x/2K/IE 5.x 00475 $this->rarrow = '►'; 00476 $this->larrow = '◄'; 00477 if (empty($_SERVER['HTTP_USER_AGENT'])) { 00478 $uagent = ''; 00479 } else { 00480 $uagent = $_SERVER['HTTP_USER_AGENT']; 00481 } 00482 if (false !== strpos($uagent, 'Opera') 00483 || false !== strpos($uagent, 'Mac')) { 00484 // Looks good in Win XP/Mac/Opera 8/9, Mac/Firefox 2, Camino, Safari. 00485 // Not broken in Mac/IE 5, Mac/Netscape 7 (?). 00486 $this->rarrow = '▶'; 00487 $this->larrow = '◀'; 00488 } 00489 elseif (false !== strpos($uagent, 'Konqueror')) { 00490 $this->rarrow = '→'; 00491 $this->larrow = '←'; 00492 } 00493 elseif (isset($_SERVER['HTTP_ACCEPT_CHARSET']) 00494 && false === stripos($_SERVER['HTTP_ACCEPT_CHARSET'], 'utf-8')) { 00495 // (Win/IE 5 doesn't set ACCEPT_CHARSET, but handles Unicode.) 00496 // To be safe, non-Unicode browsers! 00497 $this->rarrow = '>'; 00498 $this->larrow = '<'; 00499 } 00500 00502 if (right_to_left()) { 00503 $t = $this->rarrow; 00504 $this->rarrow = $this->larrow; 00505 $this->larrow = $t; 00506 } 00507 } 00508 } 00509 00515 public function renderer_prefixes() { 00516 global $CFG; // just in case the included files need it 00517 00518 $prefixes = array('theme_'.$this->name); 00519 00520 foreach ($this->parent_configs as $parent) { 00521 $prefixes[] = 'theme_'.$parent->name; 00522 } 00523 00524 return $prefixes; 00525 } 00526 00532 public function editor_css_url($encoded=true) { 00533 global $CFG; 00534 00535 $rev = theme_get_revision(); 00536 00537 if ($rev > -1) { 00538 $params = array('theme'=>$this->name,'rev'=>$rev, 'type'=>'editor'); 00539 return new moodle_url($CFG->httpswwwroot.'/theme/styles.php', $params); 00540 } else { 00541 $params = array('theme'=>$this->name, 'type'=>'editor'); 00542 return new moodle_url($CFG->httpswwwroot.'/theme/styles_debug.php', $params); 00543 } 00544 } 00545 00550 public function editor_css_files() { 00551 global $CFG; 00552 00553 $files = array(); 00554 00555 // first editor plugins 00556 $plugins = get_plugin_list('editor'); 00557 foreach ($plugins as $plugin=>$fulldir) { 00558 $sheetfile = "$fulldir/editor_styles.css"; 00559 if (is_readable($sheetfile)) { 00560 $files['plugin_'.$plugin] = $sheetfile; 00561 } 00562 } 00563 // then parent themes 00564 foreach (array_reverse($this->parent_configs) as $parent_config) { // base first, the immediate parent last 00565 if (empty($parent_config->editor_sheets)) { 00566 continue; 00567 } 00568 foreach ($parent_config->editor_sheets as $sheet) { 00569 $sheetfile = "$parent_config->dir/style/$sheet.css"; 00570 if (is_readable($sheetfile)) { 00571 $files['parent_'.$parent_config->name.'_'.$sheet] = $sheetfile; 00572 } 00573 } 00574 } 00575 // finally this theme 00576 if (!empty($this->editor_sheets)) { 00577 foreach ($this->editor_sheets as $sheet) { 00578 $sheetfile = "$this->dir/style/$sheet.css"; 00579 if (is_readable($sheetfile)) { 00580 $files['theme_'.$sheet] = $sheetfile; 00581 } 00582 } 00583 } 00584 00585 return $files; 00586 } 00587 00593 public function css_urls(moodle_page $page) { 00594 global $CFG; 00595 00596 $rev = theme_get_revision(); 00597 00598 $urls = array(); 00599 00600 if ($rev > -1) { 00601 if (check_browser_version('MSIE', 5)) { 00602 // We need to split the CSS files for IE 00603 $urls[] = new moodle_url($CFG->httpswwwroot.'/theme/styles.php', array('theme'=>$this->name,'rev'=>$rev, 'type'=>'plugins')); 00604 $urls[] = new moodle_url($CFG->httpswwwroot.'/theme/styles.php', array('theme'=>$this->name,'rev'=>$rev, 'type'=>'parents')); 00605 $urls[] = new moodle_url($CFG->httpswwwroot.'/theme/styles.php', array('theme'=>$this->name,'rev'=>$rev, 'type'=>'theme')); 00606 } else { 00607 $urls[] = new moodle_url($CFG->httpswwwroot.'/theme/styles.php', array('theme'=>$this->name,'rev'=>$rev)); 00608 } 00609 } else { 00610 // find out the current CSS and cache it now for 5 seconds 00611 // the point is to construct the CSS only once and pass it through the 00612 // dataroot to the script that actually serves the sheets 00613 if (!defined('THEME_DESIGNER_CACHE_LIFETIME')) { 00614 define('THEME_DESIGNER_CACHE_LIFETIME', 4); // this can be also set in config.php 00615 } 00616 $candidatesheet = "$CFG->cachedir/theme/$this->name/designer.ser"; 00617 if (!file_exists($candidatesheet)) { 00618 $css = $this->css_content(); 00619 check_dir_exists(dirname($candidatesheet)); 00620 file_put_contents($candidatesheet, serialize($css)); 00621 00622 } else if (filemtime($candidatesheet) > time() - THEME_DESIGNER_CACHE_LIFETIME) { 00623 if ($css = file_get_contents($candidatesheet)) { 00624 $css = unserialize($css); 00625 } else { 00626 unlink($candidatesheet); 00627 $css = $this->css_content(); 00628 } 00629 00630 } else { 00631 unlink($candidatesheet); 00632 $css = $this->css_content(); 00633 file_put_contents($candidatesheet, serialize($css)); 00634 } 00635 00636 $baseurl = $CFG->httpswwwroot.'/theme/styles_debug.php'; 00637 00638 if (check_browser_version('MSIE', 5)) { 00639 // lalala, IE does not allow more than 31 linked CSS files from main document 00640 $urls[] = new moodle_url($baseurl, array('theme'=>$this->name, 'type'=>'ie', 'subtype'=>'plugins')); 00641 foreach ($css['parents'] as $parent=>$sheets) { 00642 // We need to serve parents individually otherwise we may easily exceed the style limit IE imposes (4096) 00643 $urls[] = new moodle_url($baseurl, array('theme'=>$this->name,'type'=>'ie', 'subtype'=>'parents', 'sheet'=>$parent)); 00644 } 00645 $urls[] = new moodle_url($baseurl, array('theme'=>$this->name, 'type'=>'ie', 'subtype'=>'theme')); 00646 00647 } else { 00648 foreach ($css['plugins'] as $plugin=>$unused) { 00649 $urls[] = new moodle_url($baseurl, array('theme'=>$this->name,'type'=>'plugin', 'subtype'=>$plugin)); 00650 } 00651 foreach ($css['parents'] as $parent=>$sheets) { 00652 foreach ($sheets as $sheet=>$unused2) { 00653 $urls[] = new moodle_url($baseurl, array('theme'=>$this->name,'type'=>'parent', 'subtype'=>$parent, 'sheet'=>$sheet)); 00654 } 00655 } 00656 foreach ($css['theme'] as $sheet=>$unused) { 00657 $urls[] = new moodle_url($baseurl, array('sheet'=>$sheet, 'theme'=>$this->name, 'type'=>'theme')); // sheet first in order to make long urls easier to read 00658 } 00659 } 00660 } 00661 00662 return $urls; 00663 } 00664 00669 public function css_files() { 00670 $cssfiles = array('plugins'=>array(), 'parents'=>array(), 'theme'=>array()); 00671 00672 // get all plugin sheets 00673 $excludes = $this->resolve_excludes('plugins_exclude_sheets'); 00674 if ($excludes !== true) { 00675 foreach (get_plugin_types() as $type=>$unused) { 00676 if ($type === 'theme' || (!empty($excludes[$type]) and $excludes[$type] === true)) { 00677 continue; 00678 } 00679 $plugins = get_plugin_list($type); 00680 foreach ($plugins as $plugin=>$fulldir) { 00681 if (!empty($excludes[$type]) and is_array($excludes[$type]) 00682 and in_array($plugin, $excludes[$type])) { 00683 continue; 00684 } 00685 00686 $plugincontent = ''; 00687 $sheetfile = "$fulldir/styles.css"; 00688 if (is_readable($sheetfile)) { 00689 $cssfiles['plugins'][$type.'_'.$plugin] = $sheetfile; 00690 } 00691 $sheetthemefile = "$fulldir/styles_{$this->name}.css"; 00692 if (is_readable($sheetthemefile)) { 00693 $cssfiles['plugins'][$type.'_'.$plugin.'_'.$this->name] = $sheetthemefile; 00694 } 00695 } 00696 } 00697 } 00698 00699 // find out wanted parent sheets 00700 $excludes = $this->resolve_excludes('parents_exclude_sheets'); 00701 if ($excludes !== true) { 00702 foreach (array_reverse($this->parent_configs) as $parent_config) { // base first, the immediate parent last 00703 $parent = $parent_config->name; 00704 if (empty($parent_config->sheets) || (!empty($excludes[$parent]) and $excludes[$parent] === true)) { 00705 continue; 00706 } 00707 foreach ($parent_config->sheets as $sheet) { 00708 if (!empty($excludes[$parent]) and is_array($excludes[$parent]) 00709 and in_array($sheet, $excludes[$parent])) { 00710 continue; 00711 } 00712 $sheetfile = "$parent_config->dir/style/$sheet.css"; 00713 if (is_readable($sheetfile)) { 00714 $cssfiles['parents'][$parent][$sheet] = $sheetfile; 00715 } 00716 } 00717 } 00718 } 00719 00720 // current theme sheets 00721 if (is_array($this->sheets)) { 00722 foreach ($this->sheets as $sheet) { 00723 $sheetfile = "$this->dir/style/$sheet.css"; 00724 if (is_readable($sheetfile)) { 00725 $cssfiles['theme'][$sheet] = $sheetfile; 00726 } 00727 } 00728 } 00729 00730 return $cssfiles; 00731 } 00732 00737 public function css_content() { 00738 $files = array_merge($this->css_files(), array('editor'=>$this->editor_css_files())); 00739 $css = $this->css_files_get_contents($files, array()); 00740 return $css; 00741 } 00742 00753 protected function css_files_get_contents($file, array $keys) { 00754 if (is_array($file)) { 00755 foreach ($file as $key=>$f) { 00756 $file[$key] = $this->css_files_get_contents($f, array_merge($keys, array($key))); 00757 } 00758 return $file; 00759 } else { 00760 $comment = '/** Path: '.implode(' ', $keys).' **/'."\n"; 00761 return $comment.$this->post_process(file_get_contents($file)); 00762 } 00763 } 00764 00765 00771 public function javascript_url($inhead) { 00772 global $CFG; 00773 00774 $rev = theme_get_revision(); 00775 $params = array('theme'=>$this->name,'rev'=>$rev); 00776 $params['type'] = $inhead ? 'head' : 'footer'; 00777 00778 return new moodle_url($CFG->httpswwwroot.'/theme/javascript.php', $params); 00779 } 00780 00781 public function javascript_files($type) { 00782 if ($type === 'footer') { 00783 $type = 'javascripts_footer'; 00784 } else { 00785 $type = 'javascripts'; 00786 } 00787 00788 $js = array(); 00789 // find out wanted parent javascripts 00790 $excludes = $this->resolve_excludes('parents_exclude_javascripts'); 00791 if ($excludes !== true) { 00792 foreach (array_reverse($this->parent_configs) as $parent_config) { // base first, the immediate parent last 00793 $parent = $parent_config->name; 00794 if (empty($parent_config->$type)) { 00795 continue; 00796 } 00797 if (!empty($excludes[$parent]) and $excludes[$parent] === true) { 00798 continue; 00799 } 00800 foreach ($parent_config->$type as $javascript) { 00801 if (!empty($excludes[$parent]) and is_array($excludes[$parent]) 00802 and in_array($javascript, $excludes[$parent])) { 00803 continue; 00804 } 00805 $javascriptfile = "$parent_config->dir/javascript/$javascript.js"; 00806 if (is_readable($javascriptfile)) { 00807 $js[] = $javascriptfile; 00808 } 00809 } 00810 } 00811 } 00812 00813 // current theme javascripts 00814 if (is_array($this->$type)) { 00815 foreach ($this->$type as $javascript) { 00816 $javascriptfile = "$this->dir/javascript/$javascript.js"; 00817 if (is_readable($javascriptfile)) { 00818 $js[] = $javascriptfile; 00819 } 00820 } 00821 } 00822 00823 return $js; 00824 } 00825 00833 protected function resolve_excludes($variable, $default=null) { 00834 $setting = $default; 00835 if (is_array($this->{$variable}) or $this->{$variable} === true) { 00836 $setting = $this->{$variable}; 00837 } else { 00838 foreach ($this->parent_configs as $parent_config) { // the immediate parent first, base last 00839 if (!isset($parent_config->{$variable})) { 00840 continue; 00841 } 00842 if (is_array($parent_config->{$variable}) or $parent_config->{$variable} === true) { 00843 $setting = $parent_config->{$variable}; 00844 break; 00845 } 00846 } 00847 } 00848 return $setting; 00849 } 00850 00856 public function javascript_content($type) { 00857 $jsfiles = $this->javascript_files($type); 00858 $js = ''; 00859 foreach ($jsfiles as $jsfile) { 00860 $js .= file_get_contents($jsfile)."\n"; 00861 } 00862 return $js; 00863 } 00864 00865 public function post_process($css) { 00866 global $CFG; 00867 00868 // now resolve all image locations 00869 if (preg_match_all('/\[\[pix:([a-z_]+\|)?([^\]]+)\]\]/', $css, $matches, PREG_SET_ORDER)) { 00870 $replaced = array(); 00871 foreach ($matches as $match) { 00872 if (isset($replaced[$match[0]])) { 00873 continue; 00874 } 00875 $replaced[$match[0]] = true; 00876 $imagename = $match[2]; 00877 $component = rtrim($match[1], '|'); 00878 $imageurl = $this->pix_url($imagename, $component)->out(false); 00879 // we do not need full url because the image.php is always in the same dir 00880 $imageurl = str_replace("$CFG->httpswwwroot/theme/", '', $imageurl); 00881 $css = str_replace($match[0], $imageurl, $css); 00882 } 00883 } 00884 00885 // now resolve all theme settings or do any other postprocessing 00886 $csspostprocess = $this->csspostprocess; 00887 if (function_exists($csspostprocess)) { 00888 $css = $csspostprocess($css, $this); 00889 } 00890 00891 return $css; 00892 } 00893 00901 public function pix_url($imagename, $component) { 00902 global $CFG; 00903 00904 $params = array('theme'=>$this->name, 'image'=>$imagename); 00905 00906 $rev = theme_get_revision(); 00907 if ($rev != -1) { 00908 $params['rev'] = $rev; 00909 } 00910 if (!empty($component) and $component !== 'moodle'and $component !== 'core') { 00911 $params['component'] = $component; 00912 } 00913 00914 return new moodle_url("$CFG->httpswwwroot/theme/image.php", $params); 00915 } 00916 00923 public function resolve_image_location($image, $component) { 00924 global $CFG; 00925 00926 if ($component === 'moodle' or $component === 'core' or empty($component)) { 00927 if ($imagefile = $this->image_exists("$this->dir/pix_core/$image")) { 00928 return $imagefile; 00929 } 00930 foreach (array_reverse($this->parent_configs) as $parent_config) { // base first, the immediate parent last 00931 if ($imagefile = $this->image_exists("$parent_config->dir/pix_core/$image")) { 00932 return $imagefile; 00933 } 00934 } 00935 if ($imagefile = $this->image_exists("$CFG->dirroot/pix/$image")) { 00936 return $imagefile; 00937 } 00938 return null; 00939 00940 } else if ($component === 'theme') { //exception 00941 if ($image === 'favicon') { 00942 return "$this->dir/pix/favicon.ico"; 00943 } 00944 if ($imagefile = $this->image_exists("$this->dir/pix/$image")) { 00945 return $imagefile; 00946 } 00947 foreach (array_reverse($this->parent_configs) as $parent_config) { // base first, the immediate parent last 00948 if ($imagefile = $this->image_exists("$parent_config->dir/pix/$image")) { 00949 return $imagefile; 00950 } 00951 } 00952 return null; 00953 00954 } else { 00955 if (strpos($component, '_') === false) { 00956 $component = 'mod_'.$component; 00957 } 00958 list($type, $plugin) = explode('_', $component, 2); 00959 00960 if ($imagefile = $this->image_exists("$this->dir/pix_plugins/$type/$plugin/$image")) { 00961 return $imagefile; 00962 } 00963 foreach (array_reverse($this->parent_configs) as $parent_config) { // base first, the immediate parent last 00964 if ($imagefile = $this->image_exists("$parent_config->dir/pix_plugins/$type/$plugin/$image")) { 00965 return $imagefile; 00966 } 00967 } 00968 $dir = get_plugin_directory($type, $plugin); 00969 if ($imagefile = $this->image_exists("$dir/pix/$image")) { 00970 return $imagefile; 00971 } 00972 return null; 00973 } 00974 } 00975 00981 private static function image_exists($filepath) { 00982 if (file_exists("$filepath.gif")) { 00983 return "$filepath.gif"; 00984 } else if (file_exists("$filepath.png")) { 00985 return "$filepath.png"; 00986 } else if (file_exists("$filepath.jpg")) { 00987 return "$filepath.jpg"; 00988 } else if (file_exists("$filepath.jpeg")) { 00989 return "$filepath.jpeg"; 00990 } else { 00991 return false; 00992 } 00993 } 00994 01001 private static function find_theme_config($themename, $settings) { 01002 // We have to use the variable name $THEME (upper case) because that 01003 // is what is used in theme config.php files. 01004 01005 if (!$dir = theme_config::find_theme_location($themename)) { 01006 return null; 01007 } 01008 01009 $THEME = new stdClass(); 01010 $THEME->name = $themename; 01011 $THEME->dir = $dir; 01012 $THEME->settings = $settings; 01013 01014 global $CFG; // just in case somebody tries to use $CFG in theme config 01015 include("$THEME->dir/config.php"); 01016 01017 // verify the theme configuration is OK 01018 if (!is_array($THEME->parents)) { 01019 // parents option is mandatory now 01020 return null; 01021 } 01022 01023 return $THEME; 01024 } 01025 01032 private static function find_theme_location($themename) { 01033 global $CFG; 01034 01035 if (file_exists("$CFG->dirroot/theme/$themename/config.php")) { 01036 $dir = "$CFG->dirroot/theme/$themename"; 01037 01038 } else if (!empty($CFG->themedir) and file_exists("$CFG->themedir/$themename/config.php")) { 01039 $dir = "$CFG->themedir/$themename"; 01040 01041 } else { 01042 return null; 01043 } 01044 01045 if (file_exists("$dir/styles.php")) { 01046 //legacy theme - needs to be upgraded - upgrade info is displayed on the admin settings page 01047 return null; 01048 } 01049 01050 return $dir; 01051 } 01052 01061 public function get_renderer(moodle_page $page, $component, $subtype = null, $target = null) { 01062 if (is_null($this->rf)) { 01063 $classname = $this->rendererfactory; 01064 $this->rf = new $classname($this); 01065 } 01066 01067 return $this->rf->get_renderer($page, $component, $subtype, $target); 01068 } 01069 01075 protected function layout_info_for_page($pagelayout) { 01076 if (array_key_exists($pagelayout, $this->layouts)) { 01077 return $this->layouts[$pagelayout]; 01078 } else { 01079 debugging('Invalid page layout specified: ' . $pagelayout); 01080 return $this->layouts['standard']; 01081 } 01082 } 01083 01093 public function layout_file($pagelayout) { 01094 global $CFG; 01095 01096 $layoutinfo = $this->layout_info_for_page($pagelayout); 01097 $layoutfile = $layoutinfo['file']; 01098 01099 if (array_key_exists('theme', $layoutinfo)) { 01100 $themes = array($layoutinfo['theme']); 01101 } else { 01102 $themes = array_merge(array($this->name),$this->parents); 01103 } 01104 01105 foreach ($themes as $theme) { 01106 if ($dir = $this->find_theme_location($theme)) { 01107 $path = "$dir/layout/$layoutfile"; 01108 01109 // Check the template exists, return general base theme template if not. 01110 if (is_readable($path)) { 01111 return $path; 01112 } 01113 } 01114 } 01115 01116 debugging('Can not find layout file for: ' . $pagelayout); 01117 // fallback to standard normal layout 01118 return "$CFG->dirroot/theme/base/layout/general.php"; 01119 } 01120 01126 public function pagelayout_options($pagelayout) { 01127 $info = $this->layout_info_for_page($pagelayout); 01128 if (!empty($info['options'])) { 01129 return $info['options']; 01130 } 01131 return array(); 01132 } 01133 01141 public function setup_blocks($pagelayout, $blockmanager) { 01142 $layoutinfo = $this->layout_info_for_page($pagelayout); 01143 if (!empty($layoutinfo['regions'])) { 01144 $blockmanager->add_regions($layoutinfo['regions']); 01145 $blockmanager->set_default_region($layoutinfo['defaultregion']); 01146 } 01147 } 01148 01149 protected function get_region_name($region, $theme) { 01150 $regionstring = get_string('region-' . $region, 'theme_' . $theme); 01151 // A name exists in this theme, so use it 01152 if (substr($regionstring, 0, 1) != '[') { 01153 return $regionstring; 01154 } 01155 01156 // Otherwise, try to find one elsewhere 01157 // Check parents, if any 01158 foreach ($this->parents as $parentthemename) { 01159 $regionstring = get_string('region-' . $region, 'theme_' . $parentthemename); 01160 if (substr($regionstring, 0, 1) != '[') { 01161 return $regionstring; 01162 } 01163 } 01164 01165 // Last resort, try the base theme for names 01166 return get_string('region-' . $region, 'theme_base'); 01167 } 01168 01173 public function get_all_block_regions() { 01174 $regions = array(); 01175 foreach ($this->layouts as $layoutinfo) { 01176 foreach ($layoutinfo['regions'] as $region) { 01177 $regions[$region] = $this->get_region_name($region, $this->name); 01178 } 01179 } 01180 return $regions; 01181 } 01182 01188 public function get_theme_name() { 01189 return get_string('pluginname', 'theme_'.$this->name); 01190 } 01191 } 01192 01193 01206 class xhtml_container_stack { 01208 protected $opencontainers = array(); 01213 protected $log = array(); 01219 protected $isdebugging; 01220 01221 public function __construct() { 01222 $this->isdebugging = debugging('', DEBUG_DEVELOPER); 01223 } 01224 01232 public function push($type, $closehtml) { 01233 $container = new stdClass; 01234 $container->type = $type; 01235 $container->closehtml = $closehtml; 01236 if ($this->isdebugging) { 01237 $this->log('Open', $type); 01238 } 01239 array_push($this->opencontainers, $container); 01240 } 01241 01249 public function pop($type) { 01250 if (empty($this->opencontainers)) { 01251 debugging('<p>There are no more open containers. This suggests there is a nesting problem.</p>' . 01252 $this->output_log(), DEBUG_DEVELOPER); 01253 return; 01254 } 01255 01256 $container = array_pop($this->opencontainers); 01257 if ($container->type != $type) { 01258 debugging('<p>The type of container to be closed (' . $container->type . 01259 ') does not match the type of the next open container (' . $type . 01260 '). This suggests there is a nesting problem.</p>' . 01261 $this->output_log(), DEBUG_DEVELOPER); 01262 } 01263 if ($this->isdebugging) { 01264 $this->log('Close', $type); 01265 } 01266 return $container->closehtml; 01267 } 01268 01277 public function pop_all_but_last($shouldbenone = false) { 01278 if ($shouldbenone && count($this->opencontainers) != 1) { 01279 debugging('<p>Some HTML tags were opened in the body of the page but not closed.</p>' . 01280 $this->output_log(), DEBUG_DEVELOPER); 01281 } 01282 $output = ''; 01283 while (count($this->opencontainers) > 1) { 01284 $container = array_pop($this->opencontainers); 01285 $output .= $container->closehtml; 01286 } 01287 return $output; 01288 } 01289 01297 public function discard() { 01298 $this->opencontainers = null; 01299 } 01300 01307 protected function log($action, $type) { 01308 $this->log[] = '<li>' . $action . ' ' . $type . ' at:' . 01309 format_backtrace(debug_backtrace()) . '</li>'; 01310 } 01311 01316 protected function output_log() { 01317 return '<ul>' . implode("\n", $this->log) . '</ul>'; 01318 } 01319 }