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