Deal with old libxml incompatibilities.
[htmlpurifier.git] / library / HTMLPurifier / LanguageFactory.php
blob4e35272d8732f13a6211bd5c60f5909f7a34399d
1 <?php
3 /**
4 * Class responsible for generating HTMLPurifier_Language objects, managing
5 * caching and fallbacks.
6 * @note Thanks to MediaWiki for the general logic, although this version
7 * has been entirely rewritten
8 * @todo Serialized cache for languages
9 */
10 class HTMLPurifier_LanguageFactory
13 /**
14 * Cache of language code information used to load HTMLPurifier_Language objects.
15 * Structure is: $factory->cache[$language_code][$key] = $value
16 * @type array
18 public $cache;
20 /**
21 * Valid keys in the HTMLPurifier_Language object. Designates which
22 * variables to slurp out of a message file.
23 * @type array
25 public $keys = array('fallback', 'messages', 'errorNames');
27 /**
28 * Instance to validate language codes.
29 * @type HTMLPurifier_AttrDef_Lang
32 protected $validator;
34 /**
35 * Cached copy of dirname(__FILE__), directory of current file without
36 * trailing slash.
37 * @type string
39 protected $dir;
41 /**
42 * Keys whose contents are a hash map and can be merged.
43 * @type array
45 protected $mergeable_keys_map = array('messages' => true, 'errorNames' => true);
47 /**
48 * Keys whose contents are a list and can be merged.
49 * @value array lookup
51 protected $mergeable_keys_list = array();
53 /**
54 * Retrieve sole instance of the factory.
55 * @param HTMLPurifier_LanguageFactory $prototype Optional prototype to overload sole instance with,
56 * or bool true to reset to default factory.
57 * @return HTMLPurifier_LanguageFactory
59 public static function instance($prototype = null)
61 static $instance = null;
62 if ($prototype !== null) {
63 $instance = $prototype;
64 } elseif ($instance === null || $prototype == true) {
65 $instance = new HTMLPurifier_LanguageFactory();
66 $instance->setup();
68 return $instance;
71 /**
72 * Sets up the singleton, much like a constructor
73 * @note Prevents people from getting this outside of the singleton
75 public function setup()
77 $this->validator = new HTMLPurifier_AttrDef_Lang();
78 $this->dir = HTMLPURIFIER_PREFIX . '/HTMLPurifier';
81 /**
82 * Creates a language object, handles class fallbacks
83 * @param HTMLPurifier_Config $config
84 * @param HTMLPurifier_Context $context
85 * @param bool|string $code Code to override configuration with. Private parameter.
86 * @return HTMLPurifier_Language
88 public function create($config, $context, $code = false)
90 // validate language code
91 if ($code === false) {
92 $code = $this->validator->validate(
93 $config->get('Core.Language'),
94 $config,
95 $context
97 } else {
98 $code = $this->validator->validate($code, $config, $context);
100 if ($code === false) {
101 $code = 'en'; // malformed code becomes English
104 $pcode = str_replace('-', '_', $code); // make valid PHP classname
105 static $depth = 0; // recursion protection
107 if ($code == 'en') {
108 $lang = new HTMLPurifier_Language($config, $context);
109 } else {
110 $class = 'HTMLPurifier_Language_' . $pcode;
111 $file = $this->dir . '/Language/classes/' . $code . '.php';
112 if (file_exists($file) || class_exists($class, false)) {
113 $lang = new $class($config, $context);
114 } else {
115 // Go fallback
116 $raw_fallback = $this->getFallbackFor($code);
117 $fallback = $raw_fallback ? $raw_fallback : 'en';
118 $depth++;
119 $lang = $this->create($config, $context, $fallback);
120 if (!$raw_fallback) {
121 $lang->error = true;
123 $depth--;
126 $lang->code = $code;
127 return $lang;
131 * Returns the fallback language for language
132 * @note Loads the original language into cache
133 * @param string $code language code
134 * @return string|bool
136 public function getFallbackFor($code)
138 $this->loadLanguage($code);
139 return $this->cache[$code]['fallback'];
143 * Loads language into the cache, handles message file and fallbacks
144 * @param string $code language code
146 public function loadLanguage($code)
148 static $languages_seen = array(); // recursion guard
150 // abort if we've already loaded it
151 if (isset($this->cache[$code])) {
152 return;
155 // generate filename
156 $filename = $this->dir . '/Language/messages/' . $code . '.php';
158 // default fallback : may be overwritten by the ensuing include
159 $fallback = ($code != 'en') ? 'en' : false;
161 // load primary localisation
162 if (!file_exists($filename)) {
163 // skip the include: will rely solely on fallback
164 $filename = $this->dir . '/Language/messages/en.php';
165 $cache = array();
166 } else {
167 include $filename;
168 $cache = compact($this->keys);
171 // load fallback localisation
172 if (!empty($fallback)) {
174 // infinite recursion guard
175 if (isset($languages_seen[$code])) {
176 trigger_error(
177 'Circular fallback reference in language ' .
178 $code,
179 E_USER_ERROR
181 $fallback = 'en';
183 $language_seen[$code] = true;
185 // load the fallback recursively
186 $this->loadLanguage($fallback);
187 $fallback_cache = $this->cache[$fallback];
189 // merge fallback with current language
190 foreach ($this->keys as $key) {
191 if (isset($cache[$key]) && isset($fallback_cache[$key])) {
192 if (isset($this->mergeable_keys_map[$key])) {
193 $cache[$key] = $cache[$key] + $fallback_cache[$key];
194 } elseif (isset($this->mergeable_keys_list[$key])) {
195 $cache[$key] = array_merge($fallback_cache[$key], $cache[$key]);
197 } else {
198 $cache[$key] = $fallback_cache[$key];
203 // save to cache for later retrieval
204 $this->cache[$code] = $cache;
205 return;
209 // vim: et sw=4 sts=4