LanguageFactory.php 6.2 KB

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