LanguageFactory.php 6.5 KB

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