Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.
   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