Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.
   1  <?php
   2  
   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
  11  {
  12  
  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

  17       */
  18      public $cache;
  19  
  20      /**

  21       * Valid keys in the HTMLPurifier_Language object. Designates which

  22       * variables to slurp out of a message file.

  23       * @type array

  24       */
  25      public $keys = array('fallback', 'messages', 'errorNames');
  26  
  27      /**

  28       * Instance to validate language codes.

  29       * @type HTMLPurifier_AttrDef_Lang

  30       *

  31       */
  32      protected $validator;
  33  
  34      /**

  35       * Cached copy of dirname(__FILE__), directory of current file without

  36       * trailing slash.

  37       * @type string

  38       */
  39      protected $dir;
  40  
  41      /**

  42       * Keys whose contents are a hash map and can be merged.

  43       * @type array

  44       */
  45      protected $mergeable_keys_map = array('messages' => true, 'errorNames' => true);
  46  
  47      /**

  48       * Keys whose contents are a list and can be merged.

  49       * @value array lookup

  50       */
  51      protected $mergeable_keys_list = array();
  52  
  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

  58       */
  59      public static function instance($prototype = null)
  60      {
  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();
  67          }
  68          return $instance;
  69      }
  70  
  71      /**

  72       * Sets up the singleton, much like a constructor

  73       * @note Prevents people from getting this outside of the singleton

  74       */
  75      public function setup()
  76      {
  77          $this->validator = new HTMLPurifier_AttrDef_Lang();
  78          $this->dir = HTMLPURIFIER_PREFIX . '/HTMLPurifier';
  79      }
  80  
  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

  87       */
  88      public function create($config, $context, $code = false)
  89      {
  90          // validate language code

  91          if ($code === false) {
  92              $code = $this->validator->validate(
  93                  $config->get('Core.Language'),
  94                  $config,
  95                  $context
  96              );
  97          } else {
  98              $code = $this->validator->validate($code, $config, $context);
  99          }
 100          if ($code === false) {
 101              $code = 'en'; // malformed code becomes English

 102          }
 103  
 104          $pcode = str_replace('-', '_', $code); // make valid PHP classname

 105          static $depth = 0; // recursion protection

 106  
 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;
 122                  }
 123                  $depth--;
 124              }
 125          }
 126          $lang->code = $code;
 127          return $lang;
 128      }
 129  
 130      /**

 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

 135       */
 136      public function getFallbackFor($code)
 137      {
 138          $this->loadLanguage($code);
 139          return $this->cache[$code]['fallback'];
 140      }
 141  
 142      /**

 143       * Loads language into the cache, handles message file and fallbacks

 144       * @param string $code language code

 145       */
 146      public function loadLanguage($code)
 147      {
 148          static $languages_seen = array(); // recursion guard

 149  
 150          // abort if we've already loaded it

 151          if (isset($this->cache[$code])) {
 152              return;
 153          }
 154  
 155          // generate filename

 156          $filename = $this->dir . '/Language/messages/' . $code . '.php';
 157  
 158          // default fallback : may be overwritten by the ensuing include

 159          $fallback = ($code != 'en') ? 'en' : false;
 160  
 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);
 169          }
 170  
 171          // load fallback localisation

 172          if (!empty($fallback)) {
 173  
 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
 180                  );
 181                  $fallback = 'en';
 182              }
 183              $language_seen[$code] = true;
 184  
 185              // load the fallback recursively

 186              $this->loadLanguage($fallback);
 187              $fallback_cache = $this->cache[$fallback];
 188  
 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]);
 196                      }
 197                  } else {
 198                      $cache[$key] = $fallback_cache[$key];
 199                  }
 200              }
 201          }
 202  
 203          // save to cache for later retrieval

 204          $this->cache[$code] = $cache;
 205          return;
 206      }
 207  }
 208  
 209  // vim: et sw=4 sts=4