[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/includes/cache/ -> LocalisationCache.php (source)

   1  <?php
   2  /**
   3   * Cache of the contents of localisation files.
   4   *
   5   * This program is free software; you can redistribute it and/or modify
   6   * it under the terms of the GNU General Public License as published by
   7   * the Free Software Foundation; either version 2 of the License, or
   8   * (at your option) any later version.
   9   *
  10   * This program is distributed in the hope that it will be useful,
  11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13   * GNU General Public License for more details.
  14   *
  15   * You should have received a copy of the GNU General Public License along
  16   * with this program; if not, write to the Free Software Foundation, Inc.,
  17   * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18   * http://www.gnu.org/copyleft/gpl.html
  19   *
  20   * @file
  21   */
  22  
  23  /**
  24   * Class for caching the contents of localisation files, Messages*.php
  25   * and *.i18n.php.
  26   *
  27   * An instance of this class is available using Language::getLocalisationCache().
  28   *
  29   * The values retrieved from here are merged, containing items from extension
  30   * files, core messages files and the language fallback sequence (e.g. zh-cn ->
  31   * zh-hans -> en ). Some common errors are corrected, for example namespace
  32   * names with spaces instead of underscores, but heavyweight processing, such
  33   * as grammatical transformation, is done by the caller.
  34   */
  35  class LocalisationCache {
  36      const VERSION = 2;
  37  
  38      /** Configuration associative array */
  39      private $conf;
  40  
  41      /**
  42       * True if recaching should only be done on an explicit call to recache().
  43       * Setting this reduces the overhead of cache freshness checking, which
  44       * requires doing a stat() for every extension i18n file.
  45       */
  46      private $manualRecache = false;
  47  
  48      /**
  49       * True to treat all files as expired until they are regenerated by this object.
  50       */
  51      private $forceRecache = false;
  52  
  53      /**
  54       * The cache data. 3-d array, where the first key is the language code,
  55       * the second key is the item key e.g. 'messages', and the third key is
  56       * an item specific subkey index. Some items are not arrays and so for those
  57       * items, there are no subkeys.
  58       */
  59      protected $data = array();
  60  
  61      /**
  62       * The persistent store object. An instance of LCStore.
  63       *
  64       * @var LCStore
  65       */
  66      private $store;
  67  
  68      /**
  69       * A 2-d associative array, code/key, where presence indicates that the item
  70       * is loaded. Value arbitrary.
  71       *
  72       * For split items, if set, this indicates that all of the subitems have been
  73       * loaded.
  74       */
  75      private $loadedItems = array();
  76  
  77      /**
  78       * A 3-d associative array, code/key/subkey, where presence indicates that
  79       * the subitem is loaded. Only used for the split items, i.e. messages.
  80       */
  81      private $loadedSubitems = array();
  82  
  83      /**
  84       * An array where presence of a key indicates that that language has been
  85       * initialised. Initialisation includes checking for cache expiry and doing
  86       * any necessary updates.
  87       */
  88      private $initialisedLangs = array();
  89  
  90      /**
  91       * An array mapping non-existent pseudo-languages to fallback languages. This
  92       * is filled by initShallowFallback() when data is requested from a language
  93       * that lacks a Messages*.php file.
  94       */
  95      private $shallowFallbacks = array();
  96  
  97      /**
  98       * An array where the keys are codes that have been recached by this instance.
  99       */
 100      private $recachedLangs = array();
 101  
 102      /**
 103       * All item keys
 104       */
 105      static public $allKeys = array(
 106          'fallback', 'namespaceNames', 'bookstoreList',
 107          'magicWords', 'messages', 'rtl', 'capitalizeAllNouns', 'digitTransformTable',
 108          'separatorTransformTable', 'fallback8bitEncoding', 'linkPrefixExtension',
 109          'linkTrail', 'linkPrefixCharset', 'namespaceAliases',
 110          'dateFormats', 'datePreferences', 'datePreferenceMigrationMap',
 111          'defaultDateFormat', 'extraUserToggles', 'specialPageAliases',
 112          'imageFiles', 'preloadedMessages', 'namespaceGenderAliases',
 113          'digitGroupingPattern', 'pluralRules', 'pluralRuleTypes', 'compiledPluralRules',
 114      );
 115  
 116      /**
 117       * Keys for items which consist of associative arrays, which may be merged
 118       * by a fallback sequence.
 119       */
 120      static public $mergeableMapKeys = array( 'messages', 'namespaceNames',
 121          'dateFormats', 'imageFiles', 'preloadedMessages'
 122      );
 123  
 124      /**
 125       * Keys for items which are a numbered array.
 126       */
 127      static public $mergeableListKeys = array( 'extraUserToggles' );
 128  
 129      /**
 130       * Keys for items which contain an array of arrays of equivalent aliases
 131       * for each subitem. The aliases may be merged by a fallback sequence.
 132       */
 133      static public $mergeableAliasListKeys = array( 'specialPageAliases' );
 134  
 135      /**
 136       * Keys for items which contain an associative array, and may be merged if
 137       * the primary value contains the special array key "inherit". That array
 138       * key is removed after the first merge.
 139       */
 140      static public $optionalMergeKeys = array( 'bookstoreList' );
 141  
 142      /**
 143       * Keys for items that are formatted like $magicWords
 144       */
 145      static public $magicWordKeys = array( 'magicWords' );
 146  
 147      /**
 148       * Keys for items where the subitems are stored in the backend separately.
 149       */
 150      static public $splitKeys = array( 'messages' );
 151  
 152      /**
 153       * Keys which are loaded automatically by initLanguage()
 154       */
 155      static public $preloadedKeys = array( 'dateFormats', 'namespaceNames' );
 156  
 157      /**
 158       * Associative array of cached plural rules. The key is the language code,
 159       * the value is an array of plural rules for that language.
 160       */
 161      private $pluralRules = null;
 162  
 163      /**
 164       * Associative array of cached plural rule types. The key is the language
 165       * code, the value is an array of plural rule types for that language. For
 166       * example, $pluralRuleTypes['ar'] = ['zero', 'one', 'two', 'few', 'many'].
 167       * The index for each rule type matches the index for the rule in
 168       * $pluralRules, thus allowing correlation between the two. The reason we
 169       * don't just use the type names as the keys in $pluralRules is because
 170       * Language::convertPlural applies the rules based on numeric order (or
 171       * explicit numeric parameter), not based on the name of the rule type. For
 172       * example, {{plural:count|wordform1|wordform2|wordform3}}, rather than
 173       * {{plural:count|one=wordform1|two=wordform2|many=wordform3}}.
 174       */
 175      private $pluralRuleTypes = null;
 176  
 177      private $mergeableKeys = null;
 178  
 179      /**
 180       * Constructor.
 181       * For constructor parameters, see the documentation in DefaultSettings.php
 182       * for $wgLocalisationCacheConf.
 183       *
 184       * @param array $conf
 185       * @throws MWException
 186       */
 187  	function __construct( $conf ) {
 188          global $wgCacheDirectory;
 189  
 190          $this->conf = $conf;
 191          $storeConf = array();
 192          if ( !empty( $conf['storeClass'] ) ) {
 193              $storeClass = $conf['storeClass'];
 194          } else {
 195              switch ( $conf['store'] ) {
 196                  case 'files':
 197                  case 'file':
 198                      $storeClass = 'LCStoreCDB';
 199                      break;
 200                  case 'db':
 201                      $storeClass = 'LCStoreDB';
 202                      break;
 203                  case 'detect':
 204                      $storeClass = $wgCacheDirectory ? 'LCStoreCDB' : 'LCStoreDB';
 205                      break;
 206                  default:
 207                      throw new MWException(
 208                          'Please set $wgLocalisationCacheConf[\'store\'] to something sensible.' );
 209              }
 210          }
 211  
 212          wfDebugLog( 'caches', get_class( $this ) . ": using store $storeClass" );
 213          if ( !empty( $conf['storeDirectory'] ) ) {
 214              $storeConf['directory'] = $conf['storeDirectory'];
 215          }
 216  
 217          $this->store = new $storeClass( $storeConf );
 218          foreach ( array( 'manualRecache', 'forceRecache' ) as $var ) {
 219              if ( isset( $conf[$var] ) ) {
 220                  $this->$var = $conf[$var];
 221              }
 222          }
 223      }
 224  
 225      /**
 226       * Returns true if the given key is mergeable, that is, if it is an associative
 227       * array which can be merged through a fallback sequence.
 228       * @param string $key
 229       * @return bool
 230       */
 231  	public function isMergeableKey( $key ) {
 232          if ( $this->mergeableKeys === null ) {
 233              $this->mergeableKeys = array_flip( array_merge(
 234                  self::$mergeableMapKeys,
 235                  self::$mergeableListKeys,
 236                  self::$mergeableAliasListKeys,
 237                  self::$optionalMergeKeys,
 238                  self::$magicWordKeys
 239              ) );
 240          }
 241  
 242          return isset( $this->mergeableKeys[$key] );
 243      }
 244  
 245      /**
 246       * Get a cache item.
 247       *
 248       * Warning: this may be slow for split items (messages), since it will
 249       * need to fetch all of the subitems from the cache individually.
 250       * @param string $code
 251       * @param string $key
 252       * @return mixed
 253       */
 254  	public function getItem( $code, $key ) {
 255          if ( !isset( $this->loadedItems[$code][$key] ) ) {
 256              wfProfileIn( __METHOD__ . '-load' );
 257              $this->loadItem( $code, $key );
 258              wfProfileOut( __METHOD__ . '-load' );
 259          }
 260  
 261          if ( $key === 'fallback' && isset( $this->shallowFallbacks[$code] ) ) {
 262              return $this->shallowFallbacks[$code];
 263          }
 264  
 265          return $this->data[$code][$key];
 266      }
 267  
 268      /**
 269       * Get a subitem, for instance a single message for a given language.
 270       * @param string $code
 271       * @param string $key
 272       * @param string $subkey
 273       * @return mixed|null
 274       */
 275  	public function getSubitem( $code, $key, $subkey ) {
 276          if ( !isset( $this->loadedSubitems[$code][$key][$subkey] ) &&
 277              !isset( $this->loadedItems[$code][$key] )
 278          ) {
 279              wfProfileIn( __METHOD__ . '-load' );
 280              $this->loadSubitem( $code, $key, $subkey );
 281              wfProfileOut( __METHOD__ . '-load' );
 282          }
 283  
 284          if ( isset( $this->data[$code][$key][$subkey] ) ) {
 285              return $this->data[$code][$key][$subkey];
 286          } else {
 287              return null;
 288          }
 289      }
 290  
 291      /**
 292       * Get the list of subitem keys for a given item.
 293       *
 294       * This is faster than array_keys($lc->getItem(...)) for the items listed in
 295       * self::$splitKeys.
 296       *
 297       * Will return null if the item is not found, or false if the item is not an
 298       * array.
 299       * @param string $code
 300       * @param string $key
 301       * @return bool|null|string
 302       */
 303  	public function getSubitemList( $code, $key ) {
 304          if ( in_array( $key, self::$splitKeys ) ) {
 305              return $this->getSubitem( $code, 'list', $key );
 306          } else {
 307              $item = $this->getItem( $code, $key );
 308              if ( is_array( $item ) ) {
 309                  return array_keys( $item );
 310              } else {
 311                  return false;
 312              }
 313          }
 314      }
 315  
 316      /**
 317       * Load an item into the cache.
 318       * @param string $code
 319       * @param string $key
 320       */
 321  	protected function loadItem( $code, $key ) {
 322          if ( !isset( $this->initialisedLangs[$code] ) ) {
 323              $this->initLanguage( $code );
 324          }
 325  
 326          // Check to see if initLanguage() loaded it for us
 327          if ( isset( $this->loadedItems[$code][$key] ) ) {
 328              return;
 329          }
 330  
 331          if ( isset( $this->shallowFallbacks[$code] ) ) {
 332              $this->loadItem( $this->shallowFallbacks[$code], $key );
 333  
 334              return;
 335          }
 336  
 337          if ( in_array( $key, self::$splitKeys ) ) {
 338              $subkeyList = $this->getSubitem( $code, 'list', $key );
 339              foreach ( $subkeyList as $subkey ) {
 340                  if ( isset( $this->data[$code][$key][$subkey] ) ) {
 341                      continue;
 342                  }
 343                  $this->data[$code][$key][$subkey] = $this->getSubitem( $code, $key, $subkey );
 344              }
 345          } else {
 346              $this->data[$code][$key] = $this->store->get( $code, $key );
 347          }
 348  
 349          $this->loadedItems[$code][$key] = true;
 350      }
 351  
 352      /**
 353       * Load a subitem into the cache
 354       * @param string $code
 355       * @param string $key
 356       * @param string $subkey
 357       */
 358  	protected function loadSubitem( $code, $key, $subkey ) {
 359          if ( !in_array( $key, self::$splitKeys ) ) {
 360              $this->loadItem( $code, $key );
 361  
 362              return;
 363          }
 364  
 365          if ( !isset( $this->initialisedLangs[$code] ) ) {
 366              $this->initLanguage( $code );
 367          }
 368  
 369          // Check to see if initLanguage() loaded it for us
 370          if ( isset( $this->loadedItems[$code][$key] ) ||
 371              isset( $this->loadedSubitems[$code][$key][$subkey] )
 372          ) {
 373              return;
 374          }
 375  
 376          if ( isset( $this->shallowFallbacks[$code] ) ) {
 377              $this->loadSubitem( $this->shallowFallbacks[$code], $key, $subkey );
 378  
 379              return;
 380          }
 381  
 382          $value = $this->store->get( $code, "$key:$subkey" );
 383          $this->data[$code][$key][$subkey] = $value;
 384          $this->loadedSubitems[$code][$key][$subkey] = true;
 385      }
 386  
 387      /**
 388       * Returns true if the cache identified by $code is missing or expired.
 389       *
 390       * @param string $code
 391       *
 392       * @return bool
 393       */
 394  	public function isExpired( $code ) {
 395          if ( $this->forceRecache && !isset( $this->recachedLangs[$code] ) ) {
 396              wfDebug( __METHOD__ . "($code): forced reload\n" );
 397  
 398              return true;
 399          }
 400  
 401          $deps = $this->store->get( $code, 'deps' );
 402          $keys = $this->store->get( $code, 'list' );
 403          $preload = $this->store->get( $code, 'preload' );
 404          // Different keys may expire separately for some stores
 405          if ( $deps === null || $keys === null || $preload === null ) {
 406              wfDebug( __METHOD__ . "($code): cache missing, need to make one\n" );
 407  
 408              return true;
 409          }
 410  
 411          foreach ( $deps as $dep ) {
 412              // Because we're unserializing stuff from cache, we
 413              // could receive objects of classes that don't exist
 414              // anymore (e.g. uninstalled extensions)
 415              // When this happens, always expire the cache
 416              if ( !$dep instanceof CacheDependency || $dep->isExpired() ) {
 417                  wfDebug( __METHOD__ . "($code): cache for $code expired due to " .
 418                      get_class( $dep ) . "\n" );
 419  
 420                  return true;
 421              }
 422          }
 423  
 424          return false;
 425      }
 426  
 427      /**
 428       * Initialise a language in this object. Rebuild the cache if necessary.
 429       * @param string $code
 430       * @throws MWException
 431       */
 432  	protected function initLanguage( $code ) {
 433          if ( isset( $this->initialisedLangs[$code] ) ) {
 434              return;
 435          }
 436  
 437          $this->initialisedLangs[$code] = true;
 438  
 439          # If the code is of the wrong form for a Messages*.php file, do a shallow fallback
 440          if ( !Language::isValidBuiltInCode( $code ) ) {
 441              $this->initShallowFallback( $code, 'en' );
 442  
 443              return;
 444          }
 445  
 446          # Recache the data if necessary
 447          if ( !$this->manualRecache && $this->isExpired( $code ) ) {
 448              if ( Language::isSupportedLanguage( $code ) ) {
 449                  $this->recache( $code );
 450              } elseif ( $code === 'en' ) {
 451                  throw new MWException( 'MessagesEn.php is missing.' );
 452              } else {
 453                  $this->initShallowFallback( $code, 'en' );
 454              }
 455  
 456              return;
 457          }
 458  
 459          # Preload some stuff
 460          $preload = $this->getItem( $code, 'preload' );
 461          if ( $preload === null ) {
 462              if ( $this->manualRecache ) {
 463                  // No Messages*.php file. Do shallow fallback to en.
 464                  if ( $code === 'en' ) {
 465                      throw new MWException( 'No localisation cache found for English. ' .
 466                          'Please run maintenance/rebuildLocalisationCache.php.' );
 467                  }
 468                  $this->initShallowFallback( $code, 'en' );
 469  
 470                  return;
 471              } else {
 472                  throw new MWException( 'Invalid or missing localisation cache.' );
 473              }
 474          }
 475          $this->data[$code] = $preload;
 476          foreach ( $preload as $key => $item ) {
 477              if ( in_array( $key, self::$splitKeys ) ) {
 478                  foreach ( $item as $subkey => $subitem ) {
 479                      $this->loadedSubitems[$code][$key][$subkey] = true;
 480                  }
 481              } else {
 482                  $this->loadedItems[$code][$key] = true;
 483              }
 484          }
 485      }
 486  
 487      /**
 488       * Create a fallback from one language to another, without creating a
 489       * complete persistent cache.
 490       * @param string $primaryCode
 491       * @param string $fallbackCode
 492       */
 493  	public function initShallowFallback( $primaryCode, $fallbackCode ) {
 494          $this->data[$primaryCode] =& $this->data[$fallbackCode];
 495          $this->loadedItems[$primaryCode] =& $this->loadedItems[$fallbackCode];
 496          $this->loadedSubitems[$primaryCode] =& $this->loadedSubitems[$fallbackCode];
 497          $this->shallowFallbacks[$primaryCode] = $fallbackCode;
 498      }
 499  
 500      /**
 501       * Read a PHP file containing localisation data.
 502       * @param string $_fileName
 503       * @param string $_fileType
 504       * @throws MWException
 505       * @return array
 506       */
 507  	protected function readPHPFile( $_fileName, $_fileType ) {
 508          wfProfileIn( __METHOD__ );
 509          // Disable APC caching
 510          wfSuppressWarnings();
 511          $_apcEnabled = ini_set( 'apc.cache_by_default', '0' );
 512          wfRestoreWarnings();
 513  
 514          include $_fileName;
 515  
 516          wfSuppressWarnings();
 517          ini_set( 'apc.cache_by_default', $_apcEnabled );
 518          wfRestoreWarnings();
 519  
 520          if ( $_fileType == 'core' || $_fileType == 'extension' ) {
 521              $data = compact( self::$allKeys );
 522          } elseif ( $_fileType == 'aliases' ) {
 523              $data = compact( 'aliases' );
 524          } else {
 525              wfProfileOut( __METHOD__ );
 526              throw new MWException( __METHOD__ . ": Invalid file type: $_fileType" );
 527          }
 528          wfProfileOut( __METHOD__ );
 529  
 530          return $data;
 531      }
 532  
 533      /**
 534       * Read a JSON file containing localisation messages.
 535       * @param string $fileName Name of file to read
 536       * @throws MWException If there is a syntax error in the JSON file
 537       * @return array Array with a 'messages' key, or empty array if the file doesn't exist
 538       */
 539  	public function readJSONFile( $fileName ) {
 540          wfProfileIn( __METHOD__ );
 541  
 542          if ( !is_readable( $fileName ) ) {
 543              wfProfileOut( __METHOD__ );
 544  
 545              return array();
 546          }
 547  
 548          $json = file_get_contents( $fileName );
 549          if ( $json === false ) {
 550              wfProfileOut( __METHOD__ );
 551  
 552              return array();
 553          }
 554  
 555          $data = FormatJson::decode( $json, true );
 556          if ( $data === null ) {
 557              wfProfileOut( __METHOD__ );
 558  
 559              throw new MWException( __METHOD__ . ": Invalid JSON file: $fileName" );
 560          }
 561  
 562          // Remove keys starting with '@', they're reserved for metadata and non-message data
 563          foreach ( $data as $key => $unused ) {
 564              if ( $key === '' || $key[0] === '@' ) {
 565                  unset( $data[$key] );
 566              }
 567          }
 568  
 569          wfProfileOut( __METHOD__ );
 570  
 571          // The JSON format only supports messages, none of the other variables, so wrap the data
 572          return array( 'messages' => $data );
 573      }
 574  
 575      /**
 576       * Get the compiled plural rules for a given language from the XML files.
 577       * @since 1.20
 578       * @param string $code
 579       * @return array|null
 580       */
 581  	public function getCompiledPluralRules( $code ) {
 582          $rules = $this->getPluralRules( $code );
 583          if ( $rules === null ) {
 584              return null;
 585          }
 586          try {
 587              $compiledRules = CLDRPluralRuleEvaluator::compile( $rules );
 588          } catch ( CLDRPluralRuleError $e ) {
 589              wfDebugLog( 'l10n', $e->getMessage() );
 590  
 591              return array();
 592          }
 593  
 594          return $compiledRules;
 595      }
 596  
 597      /**
 598       * Get the plural rules for a given language from the XML files.
 599       * Cached.
 600       * @since 1.20
 601       * @param string $code
 602       * @return array|null
 603       */
 604  	public function getPluralRules( $code ) {
 605          if ( $this->pluralRules === null ) {
 606              $this->loadPluralFiles();
 607          }
 608          if ( !isset( $this->pluralRules[$code] ) ) {
 609              return null;
 610          } else {
 611              return $this->pluralRules[$code];
 612          }
 613      }
 614  
 615      /**
 616       * Get the plural rule types for a given language from the XML files.
 617       * Cached.
 618       * @since 1.22
 619       * @param string $code
 620       * @return array|null
 621       */
 622  	public function getPluralRuleTypes( $code ) {
 623          if ( $this->pluralRuleTypes === null ) {
 624              $this->loadPluralFiles();
 625          }
 626          if ( !isset( $this->pluralRuleTypes[$code] ) ) {
 627              return null;
 628          } else {
 629              return $this->pluralRuleTypes[$code];
 630          }
 631      }
 632  
 633      /**
 634       * Load the plural XML files.
 635       */
 636  	protected function loadPluralFiles() {
 637          global $IP;
 638          $cldrPlural = "$IP/languages/data/plurals.xml";
 639          $mwPlural = "$IP/languages/data/plurals-mediawiki.xml";
 640          // Load CLDR plural rules
 641          $this->loadPluralFile( $cldrPlural );
 642          if ( file_exists( $mwPlural ) ) {
 643              // Override or extend
 644              $this->loadPluralFile( $mwPlural );
 645          }
 646      }
 647  
 648      /**
 649       * Load a plural XML file with the given filename, compile the relevant
 650       * rules, and save the compiled rules in a process-local cache.
 651       *
 652       * @param string $fileName
 653       */
 654  	protected function loadPluralFile( $fileName ) {
 655          $doc = new DOMDocument;
 656          $doc->load( $fileName );
 657          $rulesets = $doc->getElementsByTagName( "pluralRules" );
 658          foreach ( $rulesets as $ruleset ) {
 659              $codes = $ruleset->getAttribute( 'locales' );
 660              $rules = array();
 661              $ruleTypes = array();
 662              $ruleElements = $ruleset->getElementsByTagName( "pluralRule" );
 663              foreach ( $ruleElements as $elt ) {
 664                  $ruleType = $elt->getAttribute( 'count' );
 665                  if ( $ruleType === 'other' ) {
 666                      // Don't record "other" rules, which have an empty condition
 667                      continue;
 668                  }
 669                  $rules[] = $elt->nodeValue;
 670                  $ruleTypes[] = $ruleType;
 671              }
 672              foreach ( explode( ' ', $codes ) as $code ) {
 673                  $this->pluralRules[$code] = $rules;
 674                  $this->pluralRuleTypes[$code] = $ruleTypes;
 675              }
 676          }
 677      }
 678  
 679      /**
 680       * Read the data from the source files for a given language, and register
 681       * the relevant dependencies in the $deps array. If the localisation
 682       * exists, the data array is returned, otherwise false is returned.
 683       *
 684       * @param string $code
 685       * @param array $deps
 686       * @return array
 687       */
 688  	protected function readSourceFilesAndRegisterDeps( $code, &$deps ) {
 689          global $IP;
 690          wfProfileIn( __METHOD__ );
 691  
 692          // This reads in the PHP i18n file with non-messages l10n data
 693          $fileName = Language::getMessagesFileName( $code );
 694          if ( !file_exists( $fileName ) ) {
 695              $data = array();
 696          } else {
 697              $deps[] = new FileDependency( $fileName );
 698              $data = $this->readPHPFile( $fileName, 'core' );
 699          }
 700  
 701          # Load CLDR plural rules for JavaScript
 702          $data['pluralRules'] = $this->getPluralRules( $code );
 703          # And for PHP
 704          $data['compiledPluralRules'] = $this->getCompiledPluralRules( $code );
 705          # Load plural rule types
 706          $data['pluralRuleTypes'] = $this->getPluralRuleTypes( $code );
 707  
 708          $deps['plurals'] = new FileDependency( "$IP/languages/data/plurals.xml" );
 709          $deps['plurals-mw'] = new FileDependency( "$IP/languages/data/plurals-mediawiki.xml" );
 710  
 711          wfProfileOut( __METHOD__ );
 712  
 713          return $data;
 714      }
 715  
 716      /**
 717       * Merge two localisation values, a primary and a fallback, overwriting the
 718       * primary value in place.
 719       * @param string $key
 720       * @param mixed $value
 721       * @param mixed $fallbackValue
 722       */
 723  	protected function mergeItem( $key, &$value, $fallbackValue ) {
 724          if ( !is_null( $value ) ) {
 725              if ( !is_null( $fallbackValue ) ) {
 726                  if ( in_array( $key, self::$mergeableMapKeys ) ) {
 727                      $value = $value + $fallbackValue;
 728                  } elseif ( in_array( $key, self::$mergeableListKeys ) ) {
 729                      $value = array_unique( array_merge( $fallbackValue, $value ) );
 730                  } elseif ( in_array( $key, self::$mergeableAliasListKeys ) ) {
 731                      $value = array_merge_recursive( $value, $fallbackValue );
 732                  } elseif ( in_array( $key, self::$optionalMergeKeys ) ) {
 733                      if ( !empty( $value['inherit'] ) ) {
 734                          $value = array_merge( $fallbackValue, $value );
 735                      }
 736  
 737                      if ( isset( $value['inherit'] ) ) {
 738                          unset( $value['inherit'] );
 739                      }
 740                  } elseif ( in_array( $key, self::$magicWordKeys ) ) {
 741                      $this->mergeMagicWords( $value, $fallbackValue );
 742                  }
 743              }
 744          } else {
 745              $value = $fallbackValue;
 746          }
 747      }
 748  
 749      /**
 750       * @param mixed $value
 751       * @param mixed $fallbackValue
 752       */
 753  	protected function mergeMagicWords( &$value, $fallbackValue ) {
 754          foreach ( $fallbackValue as $magicName => $fallbackInfo ) {
 755              if ( !isset( $value[$magicName] ) ) {
 756                  $value[$magicName] = $fallbackInfo;
 757              } else {
 758                  $oldSynonyms = array_slice( $fallbackInfo, 1 );
 759                  $newSynonyms = array_slice( $value[$magicName], 1 );
 760                  $synonyms = array_values( array_unique( array_merge(
 761                      $newSynonyms, $oldSynonyms ) ) );
 762                  $value[$magicName] = array_merge( array( $fallbackInfo[0] ), $synonyms );
 763              }
 764          }
 765      }
 766  
 767      /**
 768       * Given an array mapping language code to localisation value, such as is
 769       * found in extension *.i18n.php files, iterate through a fallback sequence
 770       * to merge the given data with an existing primary value.
 771       *
 772       * Returns true if any data from the extension array was used, false
 773       * otherwise.
 774       * @param array $codeSequence
 775       * @param string $key
 776       * @param mixed $value
 777       * @param mixed $fallbackValue
 778       * @return bool
 779       */
 780  	protected function mergeExtensionItem( $codeSequence, $key, &$value, $fallbackValue ) {
 781          $used = false;
 782          foreach ( $codeSequence as $code ) {
 783              if ( isset( $fallbackValue[$code] ) ) {
 784                  $this->mergeItem( $key, $value, $fallbackValue[$code] );
 785                  $used = true;
 786              }
 787          }
 788  
 789          return $used;
 790      }
 791  
 792      /**
 793       * Load localisation data for a given language for both core and extensions
 794       * and save it to the persistent cache store and the process cache
 795       * @param string $code
 796       * @throws MWException
 797       */
 798  	public function recache( $code ) {
 799          global $wgExtensionMessagesFiles, $wgMessagesDirs;
 800          wfProfileIn( __METHOD__ );
 801  
 802          if ( !$code ) {
 803              wfProfileOut( __METHOD__ );
 804              throw new MWException( "Invalid language code requested" );
 805          }
 806          $this->recachedLangs[$code] = true;
 807  
 808          # Initial values
 809          $initialData = array_combine(
 810              self::$allKeys,
 811              array_fill( 0, count( self::$allKeys ), null ) );
 812          $coreData = $initialData;
 813          $deps = array();
 814  
 815          # Load the primary localisation from the source file
 816          $data = $this->readSourceFilesAndRegisterDeps( $code, $deps );
 817          if ( $data === false ) {
 818              wfDebug( __METHOD__ . ": no localisation file for $code, using fallback to en\n" );
 819              $coreData['fallback'] = 'en';
 820          } else {
 821              wfDebug( __METHOD__ . ": got localisation for $code from source\n" );
 822  
 823              # Merge primary localisation
 824              foreach ( $data as $key => $value ) {
 825                  $this->mergeItem( $key, $coreData[$key], $value );
 826              }
 827          }
 828  
 829          # Fill in the fallback if it's not there already
 830          if ( is_null( $coreData['fallback'] ) ) {
 831              $coreData['fallback'] = $code === 'en' ? false : 'en';
 832          }
 833          if ( $coreData['fallback'] === false ) {
 834              $coreData['fallbackSequence'] = array();
 835          } else {
 836              $coreData['fallbackSequence'] = array_map( 'trim', explode( ',', $coreData['fallback'] ) );
 837              $len = count( $coreData['fallbackSequence'] );
 838  
 839              # Ensure that the sequence ends at en
 840              if ( $coreData['fallbackSequence'][$len - 1] !== 'en' ) {
 841                  $coreData['fallbackSequence'][] = 'en';
 842              }
 843          }
 844  
 845          $codeSequence = array_merge( array( $code ), $coreData['fallbackSequence'] );
 846  
 847          wfProfileIn( __METHOD__ . '-fallbacks' );
 848  
 849          # Load non-JSON localisation data for extensions
 850          $extensionData = array_combine(
 851              $codeSequence,
 852              array_fill( 0, count( $codeSequence ), $initialData ) );
 853          foreach ( $wgExtensionMessagesFiles as $extension => $fileName ) {
 854              if ( isset( $wgMessagesDirs[$extension] ) ) {
 855                  # This extension has JSON message data; skip the PHP shim
 856                  continue;
 857              }
 858  
 859              $data = $this->readPHPFile( $fileName, 'extension' );
 860              $used = false;
 861  
 862              foreach ( $data as $key => $item ) {
 863                  foreach ( $codeSequence as $csCode ) {
 864                      if ( isset( $item[$csCode] ) ) {
 865                          $this->mergeItem( $key, $extensionData[$csCode][$key], $item[$csCode] );
 866                          $used = true;
 867                      }
 868                  }
 869              }
 870  
 871              if ( $used ) {
 872                  $deps[] = new FileDependency( $fileName );
 873              }
 874          }
 875  
 876          # Load the localisation data for each fallback, then merge it into the full array
 877          $allData = $initialData;
 878          foreach ( $codeSequence as $csCode ) {
 879              $csData = $initialData;
 880  
 881              # Load core messages and the extension localisations.
 882              foreach ( $wgMessagesDirs as $dirs ) {
 883                  foreach ( (array)$dirs as $dir ) {
 884                      $fileName = "$dir/$csCode.json";
 885                      $data = $this->readJSONFile( $fileName );
 886  
 887                      foreach ( $data as $key => $item ) {
 888                          $this->mergeItem( $key, $csData[$key], $item );
 889                      }
 890  
 891                      $deps[] = new FileDependency( $fileName );
 892                  }
 893              }
 894  
 895              # Merge non-JSON extension data
 896              if ( isset( $extensionData[$csCode] ) ) {
 897                  foreach ( $extensionData[$csCode] as $key => $item ) {
 898                      $this->mergeItem( $key, $csData[$key], $item );
 899                  }
 900              }
 901  
 902              if ( $csCode === $code ) {
 903                  # Merge core data into extension data
 904                  foreach ( $coreData as $key => $item ) {
 905                      $this->mergeItem( $key, $csData[$key], $item );
 906                  }
 907              } else {
 908                  # Load the secondary localisation from the source file to
 909                  # avoid infinite cycles on cyclic fallbacks
 910                  $fbData = $this->readSourceFilesAndRegisterDeps( $csCode, $deps );
 911                  if ( $fbData !== false ) {
 912                      # Only merge the keys that make sense to merge
 913                      foreach ( self::$allKeys as $key ) {
 914                          if ( !isset( $fbData[$key] ) ) {
 915                              continue;
 916                          }
 917  
 918                          if ( is_null( $coreData[$key] ) || $this->isMergeableKey( $key ) ) {
 919                              $this->mergeItem( $key, $csData[$key], $fbData[$key] );
 920                          }
 921                      }
 922                  }
 923              }
 924  
 925              # Allow extensions an opportunity to adjust the data for this
 926              # fallback
 927              wfRunHooks( 'LocalisationCacheRecacheFallback', array( $this, $csCode, &$csData ) );
 928  
 929              # Merge the data for this fallback into the final array
 930              if ( $csCode === $code ) {
 931                  $allData = $csData;
 932              } else {
 933                  foreach ( self::$allKeys as $key ) {
 934                      if ( !isset( $csData[$key] ) ) {
 935                          continue;
 936                      }
 937  
 938                      if ( is_null( $allData[$key] ) || $this->isMergeableKey( $key ) ) {
 939                          $this->mergeItem( $key, $allData[$key], $csData[$key] );
 940                      }
 941                  }
 942              }
 943          }
 944  
 945          wfProfileOut( __METHOD__ . '-fallbacks' );
 946  
 947          # Add cache dependencies for any referenced globals
 948          $deps['wgExtensionMessagesFiles'] = new GlobalDependency( 'wgExtensionMessagesFiles' );
 949          $deps['wgMessagesDirs'] = new GlobalDependency( 'wgMessagesDirs' );
 950          $deps['version'] = new ConstantDependency( 'LocalisationCache::VERSION' );
 951  
 952          # Add dependencies to the cache entry
 953          $allData['deps'] = $deps;
 954  
 955          # Replace spaces with underscores in namespace names
 956          $allData['namespaceNames'] = str_replace( ' ', '_', $allData['namespaceNames'] );
 957  
 958          # And do the same for special page aliases. $page is an array.
 959          foreach ( $allData['specialPageAliases'] as &$page ) {
 960              $page = str_replace( ' ', '_', $page );
 961          }
 962          # Decouple the reference to prevent accidental damage
 963          unset( $page );
 964  
 965          # If there were no plural rules, return an empty array
 966          if ( $allData['pluralRules'] === null ) {
 967              $allData['pluralRules'] = array();
 968          }
 969          if ( $allData['compiledPluralRules'] === null ) {
 970              $allData['compiledPluralRules'] = array();
 971          }
 972          # If there were no plural rule types, return an empty array
 973          if ( $allData['pluralRuleTypes'] === null ) {
 974              $allData['pluralRuleTypes'] = array();
 975          }
 976  
 977          # Set the list keys
 978          $allData['list'] = array();
 979          foreach ( self::$splitKeys as $key ) {
 980              $allData['list'][$key] = array_keys( $allData[$key] );
 981          }
 982          # Run hooks
 983          $purgeBlobs = true;
 984          wfRunHooks( 'LocalisationCacheRecache', array( $this, $code, &$allData, &$purgeBlobs ) );
 985  
 986          if ( is_null( $allData['namespaceNames'] ) ) {
 987              wfProfileOut( __METHOD__ );
 988              throw new MWException( __METHOD__ . ': Localisation data failed sanity check! ' .
 989                  'Check that your languages/messages/MessagesEn.php file is intact.' );
 990          }
 991  
 992          # Set the preload key
 993          $allData['preload'] = $this->buildPreload( $allData );
 994  
 995          # Save to the process cache and register the items loaded
 996          $this->data[$code] = $allData;
 997          foreach ( $allData as $key => $item ) {
 998              $this->loadedItems[$code][$key] = true;
 999          }
1000  
1001          # Save to the persistent cache
1002          wfProfileIn( __METHOD__ . '-write' );
1003          $this->store->startWrite( $code );
1004          foreach ( $allData as $key => $value ) {
1005              if ( in_array( $key, self::$splitKeys ) ) {
1006                  foreach ( $value as $subkey => $subvalue ) {
1007                      $this->store->set( "$key:$subkey", $subvalue );
1008                  }
1009              } else {
1010                  $this->store->set( $key, $value );
1011              }
1012          }
1013          $this->store->finishWrite();
1014          wfProfileOut( __METHOD__ . '-write' );
1015  
1016          # Clear out the MessageBlobStore
1017          # HACK: If using a null (i.e. disabled) storage backend, we
1018          # can't write to the MessageBlobStore either
1019          if ( $purgeBlobs && !$this->store instanceof LCStoreNull ) {
1020              MessageBlobStore::getInstance()->clear();
1021          }
1022  
1023          wfProfileOut( __METHOD__ );
1024      }
1025  
1026      /**
1027       * Build the preload item from the given pre-cache data.
1028       *
1029       * The preload item will be loaded automatically, improving performance
1030       * for the commonly-requested items it contains.
1031       * @param array $data
1032       * @return array
1033       */
1034  	protected function buildPreload( $data ) {
1035          $preload = array( 'messages' => array() );
1036          foreach ( self::$preloadedKeys as $key ) {
1037              $preload[$key] = $data[$key];
1038          }
1039  
1040          foreach ( $data['preloadedMessages'] as $subkey ) {
1041              if ( isset( $data['messages'][$subkey] ) ) {
1042                  $subitem = $data['messages'][$subkey];
1043              } else {
1044                  $subitem = null;
1045              }
1046              $preload['messages'][$subkey] = $subitem;
1047          }
1048  
1049          return $preload;
1050      }
1051  
1052      /**
1053       * Unload the data for a given language from the object cache.
1054       * Reduces memory usage.
1055       * @param string $code
1056       */
1057  	public function unload( $code ) {
1058          unset( $this->data[$code] );
1059          unset( $this->loadedItems[$code] );
1060          unset( $this->loadedSubitems[$code] );
1061          unset( $this->initialisedLangs[$code] );
1062          unset( $this->shallowFallbacks[$code] );
1063  
1064          foreach ( $this->shallowFallbacks as $shallowCode => $fbCode ) {
1065              if ( $fbCode === $code ) {
1066                  $this->unload( $shallowCode );
1067              }
1068          }
1069      }
1070  
1071      /**
1072       * Unload all data
1073       */
1074  	public function unloadAll() {
1075          foreach ( $this->initialisedLangs as $lang => $unused ) {
1076              $this->unload( $lang );
1077          }
1078      }
1079  
1080      /**
1081       * Disable the storage backend
1082       */
1083  	public function disableBackend() {
1084          $this->store = new LCStoreNull;
1085          $this->manualRecache = false;
1086      }
1087  }
1088  
1089  /**
1090   * Interface for the persistence layer of LocalisationCache.
1091   *
1092   * The persistence layer is two-level hierarchical cache. The first level
1093   * is the language, the second level is the item or subitem.
1094   *
1095   * Since the data for a whole language is rebuilt in one operation, it needs
1096   * to have a fast and atomic method for deleting or replacing all of the
1097   * current data for a given language. The interface reflects this bulk update
1098   * operation. Callers writing to the cache must first call startWrite(), then
1099   * will call set() a couple of thousand times, then will call finishWrite()
1100   * to commit the operation. When finishWrite() is called, the cache is
1101   * expected to delete all data previously stored for that language.
1102   *
1103   * The values stored are PHP variables suitable for serialize(). Implementations
1104   * of LCStore are responsible for serializing and unserializing.
1105   */
1106  interface LCStore {
1107      /**
1108       * Get a value.
1109       * @param string $code Language code
1110       * @param string $key Cache key
1111       */
1112  	function get( $code, $key );
1113  
1114      /**
1115       * Start a write transaction.
1116       * @param string $code Language code
1117       */
1118  	function startWrite( $code );
1119  
1120      /**
1121       * Finish a write transaction.
1122       */
1123  	function finishWrite();
1124  
1125      /**
1126       * Set a key to a given value. startWrite() must be called before this
1127       * is called, and finishWrite() must be called afterwards.
1128       * @param string $key
1129       * @param mixed $value
1130       */
1131  	function set( $key, $value );
1132  }
1133  
1134  /**
1135   * LCStore implementation which uses the standard DB functions to store data.
1136   * This will work on any MediaWiki installation.
1137   */
1138  class LCStoreDB implements LCStore {
1139      private $currentLang;
1140      private $writesDone = false;
1141  
1142      /** @var DatabaseBase */
1143      private $dbw;
1144      /** @var array */
1145      private $batch = array();
1146  
1147      private $readOnly = false;
1148  
1149  	public function get( $code, $key ) {
1150          if ( $this->writesDone ) {
1151              $db = wfGetDB( DB_MASTER );
1152          } else {
1153              $db = wfGetDB( DB_SLAVE );
1154          }
1155          $row = $db->selectRow( 'l10n_cache', array( 'lc_value' ),
1156              array( 'lc_lang' => $code, 'lc_key' => $key ), __METHOD__ );
1157          if ( $row ) {
1158              return unserialize( $db->decodeBlob( $row->lc_value ) );
1159          } else {
1160              return null;
1161          }
1162      }
1163  
1164  	public function startWrite( $code ) {
1165          if ( $this->readOnly ) {
1166              return;
1167          } elseif ( !$code ) {
1168              throw new MWException( __METHOD__ . ": Invalid language \"$code\"" );
1169          }
1170  
1171          $this->dbw = wfGetDB( DB_MASTER );
1172  
1173          $this->currentLang = $code;
1174          $this->batch = array();
1175      }
1176  
1177  	public function finishWrite() {
1178          if ( $this->readOnly ) {
1179              return;
1180          } elseif ( is_null( $this->currentLang ) ) {
1181              throw new MWException( __CLASS__ . ': must call startWrite() before finishWrite()' );
1182          }
1183  
1184          $this->dbw->begin( __METHOD__ );
1185          try {
1186              $this->dbw->delete( 'l10n_cache',
1187                  array( 'lc_lang' => $this->currentLang ), __METHOD__ );
1188              foreach ( array_chunk( $this->batch, 500 ) as $rows ) {
1189                  $this->dbw->insert( 'l10n_cache', $rows, __METHOD__ );
1190              }
1191              $this->writesDone = true;
1192          } catch ( DBQueryError $e ) {
1193              if ( $this->dbw->wasReadOnlyError() ) {
1194                  $this->readOnly = true; // just avoid site down time
1195              } else {
1196                  throw $e;
1197              }
1198          }
1199          $this->dbw->commit( __METHOD__ );
1200  
1201          $this->currentLang = null;
1202          $this->batch = array();
1203      }
1204  
1205  	public function set( $key, $value ) {
1206          if ( $this->readOnly ) {
1207              return;
1208          } elseif ( is_null( $this->currentLang ) ) {
1209              throw new MWException( __CLASS__ . ': must call startWrite() before set()' );
1210          }
1211  
1212          $this->batch[] = array(
1213              'lc_lang' => $this->currentLang,
1214              'lc_key' => $key,
1215              'lc_value' => $this->dbw->encodeBlob( serialize( $value ) ) );
1216      }
1217  }
1218  
1219  /**
1220   * LCStore implementation which stores data as a collection of CDB files in the
1221   * directory given by $wgCacheDirectory. If $wgCacheDirectory is not set, this
1222   * will throw an exception.
1223   *
1224   * Profiling indicates that on Linux, this implementation outperforms MySQL if
1225   * the directory is on a local filesystem and there is ample kernel cache
1226   * space. The performance advantage is greater when the DBA extension is
1227   * available than it is with the PHP port.
1228   *
1229   * See Cdb.php and http://cr.yp.to/cdb.html
1230   */
1231  class LCStoreCDB implements LCStore {
1232      /** @var CdbReader[] */
1233      private $readers;
1234  
1235      /** @var CdbWriter */
1236      private $writer;
1237  
1238      /** @var string Current language code */
1239      private $currentLang;
1240  
1241      /** @var bool|string Cache directory. False if not set */
1242      private $directory;
1243  
1244  	function __construct( $conf = array() ) {
1245          global $wgCacheDirectory;
1246  
1247          if ( isset( $conf['directory'] ) ) {
1248              $this->directory = $conf['directory'];
1249          } else {
1250              $this->directory = $wgCacheDirectory;
1251          }
1252      }
1253  
1254  	public function get( $code, $key ) {
1255          if ( !isset( $this->readers[$code] ) ) {
1256              $fileName = $this->getFileName( $code );
1257  
1258              $this->readers[$code] = false;
1259              if ( file_exists( $fileName ) ) {
1260                  try {
1261                      $this->readers[$code] = CdbReader::open( $fileName );
1262                  } catch ( CdbException $e ) {
1263                      wfDebug( __METHOD__ . ": unable to open cdb file for reading\n" );
1264                  }
1265              }
1266          }
1267  
1268          if ( !$this->readers[$code] ) {
1269              return null;
1270          } else {
1271              $value = false;
1272              try {
1273                  $value = $this->readers[$code]->get( $key );
1274              } catch ( CdbException $e ) {
1275                  wfDebug( __METHOD__ . ": CdbException caught, error message was "
1276                      . $e->getMessage() . "\n" );
1277              }
1278              if ( $value === false ) {
1279                  return null;
1280              }
1281  
1282              return unserialize( $value );
1283          }
1284      }
1285  
1286  	public function startWrite( $code ) {
1287          if ( !file_exists( $this->directory ) ) {
1288              if ( !wfMkdirParents( $this->directory, null, __METHOD__ ) ) {
1289                  throw new MWException( "Unable to create the localisation store " .
1290                      "directory \"{$this->directory}\"" );
1291              }
1292          }
1293  
1294          // Close reader to stop permission errors on write
1295          if ( !empty( $this->readers[$code] ) ) {
1296              $this->readers[$code]->close();
1297          }
1298  
1299          try {
1300              $this->writer = CdbWriter::open( $this->getFileName( $code ) );
1301          } catch ( CdbException $e ) {
1302              throw new MWException( $e->getMessage() );
1303          }
1304          $this->currentLang = $code;
1305      }
1306  
1307  	public function finishWrite() {
1308          // Close the writer
1309          try {
1310              $this->writer->close();
1311          } catch ( CdbException $e ) {
1312              throw new MWException( $e->getMessage() );
1313          }
1314          $this->writer = null;
1315          unset( $this->readers[$this->currentLang] );
1316          $this->currentLang = null;
1317      }
1318  
1319  	public function set( $key, $value ) {
1320          if ( is_null( $this->writer ) ) {
1321              throw new MWException( __CLASS__ . ': must call startWrite() before calling set()' );
1322          }
1323          try {
1324              $this->writer->set( $key, serialize( $value ) );
1325          } catch ( CdbException $e ) {
1326              throw new MWException( $e->getMessage() );
1327          }
1328      }
1329  
1330  	protected function getFileName( $code ) {
1331          if ( strval( $code ) === '' || strpos( $code, '/' ) !== false ) {
1332              throw new MWException( __METHOD__ . ": Invalid language \"$code\"" );
1333          }
1334  
1335          return "{$this->directory}/l10n_cache-$code.cdb";
1336      }
1337  }
1338  
1339  /**
1340   * Null store backend, used to avoid DB errors during install
1341   */
1342  class LCStoreNull implements LCStore {
1343  	public function get( $code, $key ) {
1344          return null;
1345      }
1346  
1347  	public function startWrite( $code ) {
1348      }
1349  
1350  	public function finishWrite() {
1351      }
1352  
1353  	public function set( $key, $value ) {
1354      }
1355  }
1356  
1357  /**
1358   * A localisation cache optimised for loading large amounts of data for many
1359   * languages. Used by rebuildLocalisationCache.php.
1360   */
1361  class LocalisationCacheBulkLoad extends LocalisationCache {
1362      /**
1363       * A cache of the contents of data files.
1364       * Core files are serialized to avoid using ~1GB of RAM during a recache.
1365       */
1366      private $fileCache = array();
1367  
1368      /**
1369       * Most recently used languages. Uses the linked-list aspect of PHP hashtables
1370       * to keep the most recently used language codes at the end of the array, and
1371       * the language codes that are ready to be deleted at the beginning.
1372       */
1373      private $mruLangs = array();
1374  
1375      /**
1376       * Maximum number of languages that may be loaded into $this->data
1377       */
1378      private $maxLoadedLangs = 10;
1379  
1380      /**
1381       * @param string $fileName
1382       * @param string $fileType
1383       * @return array|mixed
1384       */
1385  	protected function readPHPFile( $fileName, $fileType ) {
1386          $serialize = $fileType === 'core';
1387          if ( !isset( $this->fileCache[$fileName][$fileType] ) ) {
1388              $data = parent::readPHPFile( $fileName, $fileType );
1389  
1390              if ( $serialize ) {
1391                  $encData = serialize( $data );
1392              } else {
1393                  $encData = $data;
1394              }
1395  
1396              $this->fileCache[$fileName][$fileType] = $encData;
1397  
1398              return $data;
1399          } elseif ( $serialize ) {
1400              return unserialize( $this->fileCache[$fileName][$fileType] );
1401          } else {
1402              return $this->fileCache[$fileName][$fileType];
1403          }
1404      }
1405  
1406      /**
1407       * @param string $code
1408       * @param string $key
1409       * @return mixed
1410       */
1411  	public function getItem( $code, $key ) {
1412          unset( $this->mruLangs[$code] );
1413          $this->mruLangs[$code] = true;
1414  
1415          return parent::getItem( $code, $key );
1416      }
1417  
1418      /**
1419       * @param string $code
1420       * @param string $key
1421       * @param string $subkey
1422       * @return mixed
1423       */
1424  	public function getSubitem( $code, $key, $subkey ) {
1425          unset( $this->mruLangs[$code] );
1426          $this->mruLangs[$code] = true;
1427  
1428          return parent::getSubitem( $code, $key, $subkey );
1429      }
1430  
1431      /**
1432       * @param string $code
1433       */
1434  	public function recache( $code ) {
1435          parent::recache( $code );
1436          unset( $this->mruLangs[$code] );
1437          $this->mruLangs[$code] = true;
1438          $this->trimCache();
1439      }
1440  
1441      /**
1442       * @param string $code
1443       */
1444  	public function unload( $code ) {
1445          unset( $this->mruLangs[$code] );
1446          parent::unload( $code );
1447      }
1448  
1449      /**
1450       * Unload cached languages until there are less than $this->maxLoadedLangs
1451       */
1452  	protected function trimCache() {
1453          while ( count( $this->data ) > $this->maxLoadedLangs && count( $this->mruLangs ) ) {
1454              reset( $this->mruLangs );
1455              $code = key( $this->mruLangs );
1456              wfDebug( __METHOD__ . ": unloading $code\n" );
1457              $this->unload( $code );
1458          }
1459      }
1460  }


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1