File indexing completed on 2024-12-22 05:36:21
0001 <?php 0002 0003 /** 0004 * Class responsible for generating HTMLPurifier_Language objects, managing 0005 * caching and fallbacks. 0006 * @note Thanks to MediaWiki for the general logic, although this version 0007 * has been entirely rewritten 0008 * @todo Serialized cache for languages 0009 */ 0010 class HTMLPurifier_LanguageFactory 0011 { 0012 0013 /** 0014 * Cache of language code information used to load HTMLPurifier_Language objects. 0015 * Structure is: $factory->cache[$language_code][$key] = $value 0016 * @type array 0017 */ 0018 public $cache; 0019 0020 /** 0021 * Valid keys in the HTMLPurifier_Language object. Designates which 0022 * variables to slurp out of a message file. 0023 * @type array 0024 */ 0025 public $keys = array('fallback', 'messages', 'errorNames'); 0026 0027 /** 0028 * Instance to validate language codes. 0029 * @type HTMLPurifier_AttrDef_Lang 0030 * 0031 */ 0032 protected $validator; 0033 0034 /** 0035 * Cached copy of dirname(__FILE__), directory of current file without 0036 * trailing slash. 0037 * @type string 0038 */ 0039 protected $dir; 0040 0041 /** 0042 * Keys whose contents are a hash map and can be merged. 0043 * @type array 0044 */ 0045 protected $mergeable_keys_map = array('messages' => true, 'errorNames' => true); 0046 0047 /** 0048 * Keys whose contents are a list and can be merged. 0049 * @value array lookup 0050 */ 0051 protected $mergeable_keys_list = array(); 0052 0053 /** 0054 * Retrieve sole instance of the factory. 0055 * @param HTMLPurifier_LanguageFactory $prototype Optional prototype to overload sole instance with, 0056 * or bool true to reset to default factory. 0057 * @return HTMLPurifier_LanguageFactory 0058 */ 0059 public static function instance($prototype = null) 0060 { 0061 static $instance = null; 0062 if ($prototype !== null) { 0063 $instance = $prototype; 0064 } elseif ($instance === null || $prototype == true) { 0065 $instance = new HTMLPurifier_LanguageFactory(); 0066 $instance->setup(); 0067 } 0068 return $instance; 0069 } 0070 0071 /** 0072 * Sets up the singleton, much like a constructor 0073 * @note Prevents people from getting this outside of the singleton 0074 */ 0075 public function setup() 0076 { 0077 $this->validator = new HTMLPurifier_AttrDef_Lang(); 0078 $this->dir = HTMLPURIFIER_PREFIX . '/HTMLPurifier'; 0079 } 0080 0081 /** 0082 * Creates a language object, handles class fallbacks 0083 * @param HTMLPurifier_Config $config 0084 * @param HTMLPurifier_Context $context 0085 * @param bool|string $code Code to override configuration with. Private parameter. 0086 * @return HTMLPurifier_Language 0087 */ 0088 public function create($config, $context, $code = false) 0089 { 0090 // validate language code 0091 if ($code === false) { 0092 $code = $this->validator->validate( 0093 $config->get('Core.Language'), 0094 $config, 0095 $context 0096 ); 0097 } else { 0098 $code = $this->validator->validate($code, $config, $context); 0099 } 0100 if ($code === false) { 0101 $code = 'en'; // malformed code becomes English 0102 } 0103 0104 $pcode = str_replace('-', '_', $code); // make valid PHP classname 0105 static $depth = 0; // recursion protection 0106 0107 if ($code == 'en') { 0108 $lang = new HTMLPurifier_Language($config, $context); 0109 } else { 0110 $class = 'HTMLPurifier_Language_' . $pcode; 0111 $file = $this->dir . '/Language/classes/' . $code . '.php'; 0112 if (file_exists($file) || class_exists($class, false)) { 0113 $lang = new $class($config, $context); 0114 } else { 0115 // Go fallback 0116 $raw_fallback = $this->getFallbackFor($code); 0117 $fallback = $raw_fallback ? $raw_fallback : 'en'; 0118 $depth++; 0119 $lang = $this->create($config, $context, $fallback); 0120 if (!$raw_fallback) { 0121 $lang->error = true; 0122 } 0123 $depth--; 0124 } 0125 } 0126 $lang->code = $code; 0127 return $lang; 0128 } 0129 0130 /** 0131 * Returns the fallback language for language 0132 * @note Loads the original language into cache 0133 * @param string $code language code 0134 * @return string|bool 0135 */ 0136 public function getFallbackFor($code) 0137 { 0138 $this->loadLanguage($code); 0139 return $this->cache[$code]['fallback']; 0140 } 0141 0142 /** 0143 * Loads language into the cache, handles message file and fallbacks 0144 * @param string $code language code 0145 */ 0146 public function loadLanguage($code) 0147 { 0148 static $languages_seen = array(); // recursion guard 0149 0150 // abort if we've already loaded it 0151 if (isset($this->cache[$code])) { 0152 return; 0153 } 0154 0155 // generate filename 0156 $filename = $this->dir . '/Language/messages/' . $code . '.php'; 0157 0158 // default fallback : may be overwritten by the ensuing include 0159 $fallback = ($code != 'en') ? 'en' : false; 0160 0161 // load primary localisation 0162 if (!file_exists($filename)) { 0163 // skip the include: will rely solely on fallback 0164 $filename = $this->dir . '/Language/messages/en.php'; 0165 $cache = array(); 0166 } else { 0167 include $filename; 0168 $cache = compact($this->keys); 0169 } 0170 0171 // load fallback localisation 0172 if (!empty($fallback)) { 0173 0174 // infinite recursion guard 0175 if (isset($languages_seen[$code])) { 0176 trigger_error( 0177 'Circular fallback reference in language ' . 0178 $code, 0179 E_USER_ERROR 0180 ); 0181 $fallback = 'en'; 0182 } 0183 $language_seen[$code] = true; 0184 0185 // load the fallback recursively 0186 $this->loadLanguage($fallback); 0187 $fallback_cache = $this->cache[$fallback]; 0188 0189 // merge fallback with current language 0190 foreach ($this->keys as $key) { 0191 if (isset($cache[$key]) && isset($fallback_cache[$key])) { 0192 if (isset($this->mergeable_keys_map[$key])) { 0193 $cache[$key] = $cache[$key] + $fallback_cache[$key]; 0194 } elseif (isset($this->mergeable_keys_list[$key])) { 0195 $cache[$key] = array_merge($fallback_cache[$key], $cache[$key]); 0196 } 0197 } else { 0198 $cache[$key] = $fallback_cache[$key]; 0199 } 0200 } 0201 } 0202 0203 // save to cache for later retrieval 0204 $this->cache[$code] = $cache; 0205 return; 0206 } 0207 } 0208 0209 // vim: et sw=4 sts=4