210 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
		
		
			
		
	
	
			210 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
|   | <?php | ||
|  | 
 | ||
|  | /** | ||
|  |  * Class responsible for generating HTMLPurifier_Language objects, managing | ||
|  |  * caching and fallbacks. | ||
|  |  * @note Thanks to MediaWiki for the general logic, although this version | ||
|  |  *       has been entirely rewritten | ||
|  |  * @todo Serialized cache for languages | ||
|  |  */ | ||
|  | class HTMLPurifier_LanguageFactory | ||
|  | { | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Cache of language code information used to load HTMLPurifier_Language objects. | ||
|  |      * Structure is: $factory->cache[$language_code][$key] = $value | ||
|  |      * @type array | ||
|  |      */ | ||
|  |     public $cache; | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Valid keys in the HTMLPurifier_Language object. Designates which | ||
|  |      * variables to slurp out of a message file. | ||
|  |      * @type array | ||
|  |      */ | ||
|  |     public $keys = array('fallback', 'messages', 'errorNames'); | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Instance to validate language codes. | ||
|  |      * @type HTMLPurifier_AttrDef_Lang | ||
|  |      * | ||
|  |      */ | ||
|  |     protected $validator; | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Cached copy of dirname(__FILE__), directory of current file without | ||
|  |      * trailing slash. | ||
|  |      * @type string | ||
|  |      */ | ||
|  |     protected $dir; | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Keys whose contents are a hash map and can be merged. | ||
|  |      * @type array | ||
|  |      */ | ||
|  |     protected $mergeable_keys_map = array('messages' => true, 'errorNames' => true); | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Keys whose contents are a list and can be merged. | ||
|  |      * @value array lookup | ||
|  |      */ | ||
|  |     protected $mergeable_keys_list = array(); | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Retrieve sole instance of the factory. | ||
|  |      * @param HTMLPurifier_LanguageFactory $prototype Optional prototype to overload sole instance with, | ||
|  |      *                   or bool true to reset to default factory. | ||
|  |      * @return HTMLPurifier_LanguageFactory | ||
|  |      */ | ||
|  |     public static function instance($prototype = null) | ||
|  |     { | ||
|  |         static $instance = null; | ||
|  |         if ($prototype !== null) { | ||
|  |             $instance = $prototype; | ||
|  |         } elseif ($instance === null || $prototype == true) { | ||
|  |             $instance = new HTMLPurifier_LanguageFactory(); | ||
|  |             $instance->setup(); | ||
|  |         } | ||
|  |         return $instance; | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Sets up the singleton, much like a constructor | ||
|  |      * @note Prevents people from getting this outside of the singleton | ||
|  |      */ | ||
|  |     public function setup() | ||
|  |     { | ||
|  |         $this->validator = new HTMLPurifier_AttrDef_Lang(); | ||
|  |         $this->dir = HTMLPURIFIER_PREFIX . '/HTMLPurifier'; | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Creates a language object, handles class fallbacks | ||
|  |      * @param HTMLPurifier_Config $config | ||
|  |      * @param HTMLPurifier_Context $context | ||
|  |      * @param bool|string $code Code to override configuration with. Private parameter. | ||
|  |      * @return HTMLPurifier_Language | ||
|  |      */ | ||
|  |     public function create($config, $context, $code = false) | ||
|  |     { | ||
|  |         // validate language code
 | ||
|  |         if ($code === false) { | ||
|  |             $code = $this->validator->validate( | ||
|  |                 $config->get('Core.Language'), | ||
|  |                 $config, | ||
|  |                 $context | ||
|  |             ); | ||
|  |         } else { | ||
|  |             $code = $this->validator->validate($code, $config, $context); | ||
|  |         } | ||
|  |         if ($code === false) { | ||
|  |             $code = 'en'; // malformed code becomes English
 | ||
|  |         } | ||
|  | 
 | ||
|  |         $pcode = str_replace('-', '_', $code); // make valid PHP classname
 | ||
|  |         static $depth = 0; // recursion protection
 | ||
|  | 
 | ||
|  |         if ($code == 'en') { | ||
|  |             $lang = new HTMLPurifier_Language($config, $context); | ||
|  |         } else { | ||
|  |             $class = 'HTMLPurifier_Language_' . $pcode; | ||
|  |             $file  = $this->dir . '/Language/classes/' . $code . '.php'; | ||
|  |             if (file_exists($file) || class_exists($class, false)) { | ||
|  |                 $lang = new $class($config, $context); | ||
|  |             } else { | ||
|  |                 // Go fallback
 | ||
|  |                 $raw_fallback = $this->getFallbackFor($code); | ||
|  |                 $fallback = $raw_fallback ? $raw_fallback : 'en'; | ||
|  |                 $depth++; | ||
|  |                 $lang = $this->create($config, $context, $fallback); | ||
|  |                 if (!$raw_fallback) { | ||
|  |                     $lang->error = true; | ||
|  |                 } | ||
|  |                 $depth--; | ||
|  |             } | ||
|  |         } | ||
|  |         $lang->code = $code; | ||
|  |         return $lang; | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Returns the fallback language for language | ||
|  |      * @note Loads the original language into cache | ||
|  |      * @param string $code language code | ||
|  |      * @return string|bool | ||
|  |      */ | ||
|  |     public function getFallbackFor($code) | ||
|  |     { | ||
|  |         $this->loadLanguage($code); | ||
|  |         return $this->cache[$code]['fallback']; | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Loads language into the cache, handles message file and fallbacks | ||
|  |      * @param string $code language code | ||
|  |      */ | ||
|  |     public function loadLanguage($code) | ||
|  |     { | ||
|  |         static $languages_seen = array(); // recursion guard
 | ||
|  | 
 | ||
|  |         // abort if we've already loaded it
 | ||
|  |         if (isset($this->cache[$code])) { | ||
|  |             return; | ||
|  |         } | ||
|  | 
 | ||
|  |         // generate filename
 | ||
|  |         $filename = $this->dir . '/Language/messages/' . $code . '.php'; | ||
|  | 
 | ||
|  |         // default fallback : may be overwritten by the ensuing include
 | ||
|  |         $fallback = ($code != 'en') ? 'en' : false; | ||
|  | 
 | ||
|  |         // load primary localisation
 | ||
|  |         if (!file_exists($filename)) { | ||
|  |             // skip the include: will rely solely on fallback
 | ||
|  |             $filename = $this->dir . '/Language/messages/en.php'; | ||
|  |             $cache = array(); | ||
|  |         } else { | ||
|  |             include $filename; | ||
|  |             $cache = compact($this->keys); | ||
|  |         } | ||
|  | 
 | ||
|  |         // load fallback localisation
 | ||
|  |         if (!empty($fallback)) { | ||
|  | 
 | ||
|  |             // infinite recursion guard
 | ||
|  |             if (isset($languages_seen[$code])) { | ||
|  |                 trigger_error( | ||
|  |                     'Circular fallback reference in language ' . | ||
|  |                     $code, | ||
|  |                     E_USER_ERROR | ||
|  |                 ); | ||
|  |                 $fallback = 'en'; | ||
|  |             } | ||
|  |             $language_seen[$code] = true; | ||
|  | 
 | ||
|  |             // load the fallback recursively
 | ||
|  |             $this->loadLanguage($fallback); | ||
|  |             $fallback_cache = $this->cache[$fallback]; | ||
|  | 
 | ||
|  |             // merge fallback with current language
 | ||
|  |             foreach ($this->keys as $key) { | ||
|  |                 if (isset($cache[$key]) && isset($fallback_cache[$key])) { | ||
|  |                     if (isset($this->mergeable_keys_map[$key])) { | ||
|  |                         $cache[$key] = $cache[$key] + $fallback_cache[$key]; | ||
|  |                     } elseif (isset($this->mergeable_keys_list[$key])) { | ||
|  |                         $cache[$key] = array_merge($fallback_cache[$key], $cache[$key]); | ||
|  |                     } | ||
|  |                 } else { | ||
|  |                     $cache[$key] = $fallback_cache[$key]; | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         // save to cache for later retrieval
 | ||
|  |         $this->cache[$code] = $cache; | ||
|  |         return; | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | // vim: et sw=4 sts=4
 |