MediaWiki  REL1_31
LocalisationCache.php
Go to the documentation of this file.
1 <?php
23 use CLDRPluralRuleParser\Evaluator;
24 use CLDRPluralRuleParser\Error as CLDRPluralRuleError;
26 
40  const VERSION = 4;
41 
43  private $conf;
44 
50  private $manualRecache = false;
51 
55  private $forceRecache = false;
56 
63  protected $data = [];
64 
70  private $store;
71 
79  private $loadedItems = [];
80 
85  private $loadedSubitems = [];
86 
92  private $initialisedLangs = [];
93 
99  private $shallowFallbacks = [];
100 
104  private $recachedLangs = [];
105 
109  static public $allKeys = [
110  'fallback', 'namespaceNames', 'bookstoreList',
111  'magicWords', 'messages', 'rtl', 'capitalizeAllNouns', 'digitTransformTable',
112  'separatorTransformTable', 'minimumGroupingDigits',
113  'fallback8bitEncoding', 'linkPrefixExtension',
114  'linkTrail', 'linkPrefixCharset', 'namespaceAliases',
115  'dateFormats', 'datePreferences', 'datePreferenceMigrationMap',
116  'defaultDateFormat', 'extraUserToggles', 'specialPageAliases',
117  'imageFiles', 'preloadedMessages', 'namespaceGenderAliases',
118  'digitGroupingPattern', 'pluralRules', 'pluralRuleTypes', 'compiledPluralRules',
119  ];
120 
125  static public $mergeableMapKeys = [ 'messages', 'namespaceNames',
126  'namespaceAliases', 'dateFormats', 'imageFiles', 'preloadedMessages'
127  ];
128 
132  static public $mergeableListKeys = [ 'extraUserToggles' ];
133 
138  static public $mergeableAliasListKeys = [ 'specialPageAliases' ];
139 
145  static public $optionalMergeKeys = [ 'bookstoreList' ];
146 
150  static public $magicWordKeys = [ 'magicWords' ];
151 
155  static public $splitKeys = [ 'messages' ];
156 
160  static public $preloadedKeys = [ 'dateFormats', 'namespaceNames' ];
161 
166  private $pluralRules = null;
167 
180  private $pluralRuleTypes = null;
181 
182  private $mergeableKeys = null;
183 
191  function __construct( $conf ) {
193 
194  $this->conf = $conf;
195  $storeConf = [];
196  if ( !empty( $conf['storeClass'] ) ) {
197  $storeClass = $conf['storeClass'];
198  } else {
199  switch ( $conf['store'] ) {
200  case 'files':
201  case 'file':
202  $storeClass = LCStoreCDB::class;
203  break;
204  case 'db':
205  $storeClass = LCStoreDB::class;
206  break;
207  case 'array':
208  $storeClass = LCStoreStaticArray::class;
209  break;
210  case 'detect':
211  if ( !empty( $conf['storeDirectory'] ) ) {
212  $storeClass = LCStoreCDB::class;
213  } elseif ( $wgCacheDirectory ) {
214  $storeConf['directory'] = $wgCacheDirectory;
215  $storeClass = LCStoreCDB::class;
216  } else {
217  $storeClass = LCStoreDB::class;
218  }
219  break;
220  default:
221  throw new MWException(
222  'Please set $wgLocalisationCacheConf[\'store\'] to something sensible.'
223  );
224  }
225  }
226 
227  wfDebugLog( 'caches', static::class . ": using store $storeClass" );
228  if ( !empty( $conf['storeDirectory'] ) ) {
229  $storeConf['directory'] = $conf['storeDirectory'];
230  }
231 
232  $this->store = new $storeClass( $storeConf );
233  foreach ( [ 'manualRecache', 'forceRecache' ] as $var ) {
234  if ( isset( $conf[$var] ) ) {
235  $this->$var = $conf[$var];
236  }
237  }
238  }
239 
246  public function isMergeableKey( $key ) {
247  if ( $this->mergeableKeys === null ) {
248  $this->mergeableKeys = array_flip( array_merge(
249  self::$mergeableMapKeys,
250  self::$mergeableListKeys,
251  self::$mergeableAliasListKeys,
252  self::$optionalMergeKeys,
253  self::$magicWordKeys
254  ) );
255  }
256 
257  return isset( $this->mergeableKeys[$key] );
258  }
259 
269  public function getItem( $code, $key ) {
270  if ( !isset( $this->loadedItems[$code][$key] ) ) {
271  $this->loadItem( $code, $key );
272  }
273 
274  if ( $key === 'fallback' && isset( $this->shallowFallbacks[$code] ) ) {
275  return $this->shallowFallbacks[$code];
276  }
277 
278  return $this->data[$code][$key];
279  }
280 
288  public function getSubitem( $code, $key, $subkey ) {
289  if ( !isset( $this->loadedSubitems[$code][$key][$subkey] ) &&
290  !isset( $this->loadedItems[$code][$key] )
291  ) {
292  $this->loadSubitem( $code, $key, $subkey );
293  }
294 
295  if ( isset( $this->data[$code][$key][$subkey] ) ) {
296  return $this->data[$code][$key][$subkey];
297  } else {
298  return null;
299  }
300  }
301 
314  public function getSubitemList( $code, $key ) {
315  if ( in_array( $key, self::$splitKeys ) ) {
316  return $this->getSubitem( $code, 'list', $key );
317  } else {
318  $item = $this->getItem( $code, $key );
319  if ( is_array( $item ) ) {
320  return array_keys( $item );
321  } else {
322  return false;
323  }
324  }
325  }
326 
332  protected function loadItem( $code, $key ) {
333  if ( !isset( $this->initialisedLangs[$code] ) ) {
334  $this->initLanguage( $code );
335  }
336 
337  // Check to see if initLanguage() loaded it for us
338  if ( isset( $this->loadedItems[$code][$key] ) ) {
339  return;
340  }
341 
342  if ( isset( $this->shallowFallbacks[$code] ) ) {
343  $this->loadItem( $this->shallowFallbacks[$code], $key );
344 
345  return;
346  }
347 
348  if ( in_array( $key, self::$splitKeys ) ) {
349  $subkeyList = $this->getSubitem( $code, 'list', $key );
350  foreach ( $subkeyList as $subkey ) {
351  if ( isset( $this->data[$code][$key][$subkey] ) ) {
352  continue;
353  }
354  $this->data[$code][$key][$subkey] = $this->getSubitem( $code, $key, $subkey );
355  }
356  } else {
357  $this->data[$code][$key] = $this->store->get( $code, $key );
358  }
359 
360  $this->loadedItems[$code][$key] = true;
361  }
362 
369  protected function loadSubitem( $code, $key, $subkey ) {
370  if ( !in_array( $key, self::$splitKeys ) ) {
371  $this->loadItem( $code, $key );
372 
373  return;
374  }
375 
376  if ( !isset( $this->initialisedLangs[$code] ) ) {
377  $this->initLanguage( $code );
378  }
379 
380  // Check to see if initLanguage() loaded it for us
381  if ( isset( $this->loadedItems[$code][$key] ) ||
382  isset( $this->loadedSubitems[$code][$key][$subkey] )
383  ) {
384  return;
385  }
386 
387  if ( isset( $this->shallowFallbacks[$code] ) ) {
388  $this->loadSubitem( $this->shallowFallbacks[$code], $key, $subkey );
389 
390  return;
391  }
392 
393  $value = $this->store->get( $code, "$key:$subkey" );
394  $this->data[$code][$key][$subkey] = $value;
395  $this->loadedSubitems[$code][$key][$subkey] = true;
396  }
397 
405  public function isExpired( $code ) {
406  if ( $this->forceRecache && !isset( $this->recachedLangs[$code] ) ) {
407  wfDebug( __METHOD__ . "($code): forced reload\n" );
408 
409  return true;
410  }
411 
412  $deps = $this->store->get( $code, 'deps' );
413  $keys = $this->store->get( $code, 'list' );
414  $preload = $this->store->get( $code, 'preload' );
415  // Different keys may expire separately for some stores
416  if ( $deps === null || $keys === null || $preload === null ) {
417  wfDebug( __METHOD__ . "($code): cache missing, need to make one\n" );
418 
419  return true;
420  }
421 
422  foreach ( $deps as $dep ) {
423  // Because we're unserializing stuff from cache, we
424  // could receive objects of classes that don't exist
425  // anymore (e.g. uninstalled extensions)
426  // When this happens, always expire the cache
427  if ( !$dep instanceof CacheDependency || $dep->isExpired() ) {
428  wfDebug( __METHOD__ . "($code): cache for $code expired due to " .
429  get_class( $dep ) . "\n" );
430 
431  return true;
432  }
433  }
434 
435  return false;
436  }
437 
443  protected function initLanguage( $code ) {
444  if ( isset( $this->initialisedLangs[$code] ) ) {
445  return;
446  }
447 
448  $this->initialisedLangs[$code] = true;
449 
450  # If the code is of the wrong form for a Messages*.php file, do a shallow fallback
452  $this->initShallowFallback( $code, 'en' );
453 
454  return;
455  }
456 
457  # Recache the data if necessary
458  if ( !$this->manualRecache && $this->isExpired( $code ) ) {
460  $this->recache( $code );
461  } elseif ( $code === 'en' ) {
462  throw new MWException( 'MessagesEn.php is missing.' );
463  } else {
464  $this->initShallowFallback( $code, 'en' );
465  }
466 
467  return;
468  }
469 
470  # Preload some stuff
471  $preload = $this->getItem( $code, 'preload' );
472  if ( $preload === null ) {
473  if ( $this->manualRecache ) {
474  // No Messages*.php file. Do shallow fallback to en.
475  if ( $code === 'en' ) {
476  throw new MWException( 'No localisation cache found for English. ' .
477  'Please run maintenance/rebuildLocalisationCache.php.' );
478  }
479  $this->initShallowFallback( $code, 'en' );
480 
481  return;
482  } else {
483  throw new MWException( 'Invalid or missing localisation cache.' );
484  }
485  }
486  $this->data[$code] = $preload;
487  foreach ( $preload as $key => $item ) {
488  if ( in_array( $key, self::$splitKeys ) ) {
489  foreach ( $item as $subkey => $subitem ) {
490  $this->loadedSubitems[$code][$key][$subkey] = true;
491  }
492  } else {
493  $this->loadedItems[$code][$key] = true;
494  }
495  }
496  }
497 
504  public function initShallowFallback( $primaryCode, $fallbackCode ) {
505  $this->data[$primaryCode] =& $this->data[$fallbackCode];
506  $this->loadedItems[$primaryCode] =& $this->loadedItems[$fallbackCode];
507  $this->loadedSubitems[$primaryCode] =& $this->loadedSubitems[$fallbackCode];
508  $this->shallowFallbacks[$primaryCode] = $fallbackCode;
509  }
510 
518  protected function readPHPFile( $_fileName, $_fileType ) {
519  // Disable APC caching
520  Wikimedia\suppressWarnings();
521  $_apcEnabled = ini_set( 'apc.cache_by_default', '0' );
522  Wikimedia\restoreWarnings();
523 
524  include $_fileName;
525 
526  Wikimedia\suppressWarnings();
527  ini_set( 'apc.cache_by_default', $_apcEnabled );
528  Wikimedia\restoreWarnings();
529 
530  $data = [];
531  if ( $_fileType == 'core' || $_fileType == 'extension' ) {
532  foreach ( self::$allKeys as $key ) {
533  // Not all keys are set in language files, so
534  // check they exist first
535  if ( isset( $$key ) ) {
536  $data[$key] = $$key;
537  }
538  }
539  } elseif ( $_fileType == 'aliases' ) {
540  if ( isset( $aliases ) ) {
542  $data['aliases'] = $aliases;
543  }
544  } else {
545  throw new MWException( __METHOD__ . ": Invalid file type: $_fileType" );
546  }
547 
548  return $data;
549  }
550 
557  public function readJSONFile( $fileName ) {
558  if ( !is_readable( $fileName ) ) {
559  return [];
560  }
561 
562  $json = file_get_contents( $fileName );
563  if ( $json === false ) {
564  return [];
565  }
566 
567  $data = FormatJson::decode( $json, true );
568  if ( $data === null ) {
569  throw new MWException( __METHOD__ . ": Invalid JSON file: $fileName" );
570  }
571 
572  // Remove keys starting with '@', they're reserved for metadata and non-message data
573  foreach ( $data as $key => $unused ) {
574  if ( $key === '' || $key[0] === '@' ) {
575  unset( $data[$key] );
576  }
577  }
578 
579  // The JSON format only supports messages, none of the other variables, so wrap the data
580  return [ 'messages' => $data ];
581  }
582 
589  public function getCompiledPluralRules( $code ) {
590  $rules = $this->getPluralRules( $code );
591  if ( $rules === null ) {
592  return null;
593  }
594  try {
595  $compiledRules = Evaluator::compile( $rules );
596  } catch ( CLDRPluralRuleError $e ) {
597  wfDebugLog( 'l10n', $e->getMessage() );
598 
599  return [];
600  }
601 
602  return $compiledRules;
603  }
604 
612  public function getPluralRules( $code ) {
613  if ( $this->pluralRules === null ) {
614  $this->loadPluralFiles();
615  }
616  if ( !isset( $this->pluralRules[$code] ) ) {
617  return null;
618  } else {
619  return $this->pluralRules[$code];
620  }
621  }
622 
630  public function getPluralRuleTypes( $code ) {
631  if ( $this->pluralRuleTypes === null ) {
632  $this->loadPluralFiles();
633  }
634  if ( !isset( $this->pluralRuleTypes[$code] ) ) {
635  return null;
636  } else {
637  return $this->pluralRuleTypes[$code];
638  }
639  }
640 
644  protected function loadPluralFiles() {
645  global $IP;
646  $cldrPlural = "$IP/languages/data/plurals.xml";
647  $mwPlural = "$IP/languages/data/plurals-mediawiki.xml";
648  // Load CLDR plural rules
649  $this->loadPluralFile( $cldrPlural );
650  if ( file_exists( $mwPlural ) ) {
651  // Override or extend
652  $this->loadPluralFile( $mwPlural );
653  }
654  }
655 
663  protected function loadPluralFile( $fileName ) {
664  // Use file_get_contents instead of DOMDocument::load (T58439)
665  $xml = file_get_contents( $fileName );
666  if ( !$xml ) {
667  throw new MWException( "Unable to read plurals file $fileName" );
668  }
669  $doc = new DOMDocument;
670  $doc->loadXML( $xml );
671  $rulesets = $doc->getElementsByTagName( "pluralRules" );
672  foreach ( $rulesets as $ruleset ) {
673  $codes = $ruleset->getAttribute( 'locales' );
674  $rules = [];
675  $ruleTypes = [];
676  $ruleElements = $ruleset->getElementsByTagName( "pluralRule" );
677  foreach ( $ruleElements as $elt ) {
678  $ruleType = $elt->getAttribute( 'count' );
679  if ( $ruleType === 'other' ) {
680  // Don't record "other" rules, which have an empty condition
681  continue;
682  }
683  $rules[] = $elt->nodeValue;
684  $ruleTypes[] = $ruleType;
685  }
686  foreach ( explode( ' ', $codes ) as $code ) {
687  $this->pluralRules[$code] = $rules;
688  $this->pluralRuleTypes[$code] = $ruleTypes;
689  }
690  }
691  }
692 
702  protected function readSourceFilesAndRegisterDeps( $code, &$deps ) {
703  global $IP;
704 
705  // This reads in the PHP i18n file with non-messages l10n data
706  $fileName = Language::getMessagesFileName( $code );
707  if ( !file_exists( $fileName ) ) {
708  $data = [];
709  } else {
710  $deps[] = new FileDependency( $fileName );
711  $data = $this->readPHPFile( $fileName, 'core' );
712  }
713 
714  # Load CLDR plural rules for JavaScript
715  $data['pluralRules'] = $this->getPluralRules( $code );
716  # And for PHP
717  $data['compiledPluralRules'] = $this->getCompiledPluralRules( $code );
718  # Load plural rule types
719  $data['pluralRuleTypes'] = $this->getPluralRuleTypes( $code );
720 
721  $deps['plurals'] = new FileDependency( "$IP/languages/data/plurals.xml" );
722  $deps['plurals-mw'] = new FileDependency( "$IP/languages/data/plurals-mediawiki.xml" );
723 
724  return $data;
725  }
726 
734  protected function mergeItem( $key, &$value, $fallbackValue ) {
735  if ( !is_null( $value ) ) {
736  if ( !is_null( $fallbackValue ) ) {
737  if ( in_array( $key, self::$mergeableMapKeys ) ) {
738  $value = $value + $fallbackValue;
739  } elseif ( in_array( $key, self::$mergeableListKeys ) ) {
740  $value = array_unique( array_merge( $fallbackValue, $value ) );
741  } elseif ( in_array( $key, self::$mergeableAliasListKeys ) ) {
742  $value = array_merge_recursive( $value, $fallbackValue );
743  } elseif ( in_array( $key, self::$optionalMergeKeys ) ) {
744  if ( !empty( $value['inherit'] ) ) {
745  $value = array_merge( $fallbackValue, $value );
746  }
747 
748  if ( isset( $value['inherit'] ) ) {
749  unset( $value['inherit'] );
750  }
751  } elseif ( in_array( $key, self::$magicWordKeys ) ) {
752  $this->mergeMagicWords( $value, $fallbackValue );
753  }
754  }
755  } else {
756  $value = $fallbackValue;
757  }
758  }
759 
764  protected function mergeMagicWords( &$value, $fallbackValue ) {
765  foreach ( $fallbackValue as $magicName => $fallbackInfo ) {
766  if ( !isset( $value[$magicName] ) ) {
767  $value[$magicName] = $fallbackInfo;
768  } else {
769  $oldSynonyms = array_slice( $fallbackInfo, 1 );
770  $newSynonyms = array_slice( $value[$magicName], 1 );
771  $synonyms = array_values( array_unique( array_merge(
772  $newSynonyms, $oldSynonyms ) ) );
773  $value[$magicName] = array_merge( [ $fallbackInfo[0] ], $synonyms );
774  }
775  }
776  }
777 
791  protected function mergeExtensionItem( $codeSequence, $key, &$value, $fallbackValue ) {
792  $used = false;
793  foreach ( $codeSequence as $code ) {
794  if ( isset( $fallbackValue[$code] ) ) {
795  $this->mergeItem( $key, $value, $fallbackValue[$code] );
796  $used = true;
797  }
798  }
799 
800  return $used;
801  }
802 
810  public function getMessagesDirs() {
811  global $IP;
812 
813  $config = MediaWikiServices::getInstance()->getMainConfig();
814  $messagesDirs = $config->get( 'MessagesDirs' );
815  return [
816  'core' => "$IP/languages/i18n",
817  'api' => "$IP/includes/api/i18n",
818  'oojs-ui' => "$IP/resources/lib/oojs-ui/i18n",
819  ] + $messagesDirs;
820  }
821 
828  public function recache( $code ) {
830 
831  if ( !$code ) {
832  throw new MWException( "Invalid language code requested" );
833  }
834  $this->recachedLangs[$code] = true;
835 
836  # Initial values
837  $initialData = array_fill_keys( self::$allKeys, null );
838  $coreData = $initialData;
839  $deps = [];
840 
841  # Load the primary localisation from the source file
842  $data = $this->readSourceFilesAndRegisterDeps( $code, $deps );
843  if ( $data === false ) {
844  wfDebug( __METHOD__ . ": no localisation file for $code, using fallback to en\n" );
845  $coreData['fallback'] = 'en';
846  } else {
847  wfDebug( __METHOD__ . ": got localisation for $code from source\n" );
848 
849  # Merge primary localisation
850  foreach ( $data as $key => $value ) {
851  $this->mergeItem( $key, $coreData[$key], $value );
852  }
853  }
854 
855  # Fill in the fallback if it's not there already
856  if ( is_null( $coreData['fallback'] ) ) {
857  $coreData['fallback'] = $code === 'en' ? false : 'en';
858  }
859  if ( $coreData['fallback'] === false ) {
860  $coreData['fallbackSequence'] = [];
861  } else {
862  $coreData['fallbackSequence'] = array_map( 'trim', explode( ',', $coreData['fallback'] ) );
863  $len = count( $coreData['fallbackSequence'] );
864 
865  # Ensure that the sequence ends at en
866  if ( $coreData['fallbackSequence'][$len - 1] !== 'en' ) {
867  $coreData['fallbackSequence'][] = 'en';
868  }
869  }
870 
871  $codeSequence = array_merge( [ $code ], $coreData['fallbackSequence'] );
872  $messageDirs = $this->getMessagesDirs();
873 
874  # Load non-JSON localisation data for extensions
875  $extensionData = array_fill_keys( $codeSequence, $initialData );
876  foreach ( $wgExtensionMessagesFiles as $extension => $fileName ) {
877  if ( isset( $messageDirs[$extension] ) ) {
878  # This extension has JSON message data; skip the PHP shim
879  continue;
880  }
881 
882  $data = $this->readPHPFile( $fileName, 'extension' );
883  $used = false;
884 
885  foreach ( $data as $key => $item ) {
886  foreach ( $codeSequence as $csCode ) {
887  if ( isset( $item[$csCode] ) ) {
888  $this->mergeItem( $key, $extensionData[$csCode][$key], $item[$csCode] );
889  $used = true;
890  }
891  }
892  }
893 
894  if ( $used ) {
895  $deps[] = new FileDependency( $fileName );
896  }
897  }
898 
899  # Load the localisation data for each fallback, then merge it into the full array
900  $allData = $initialData;
901  foreach ( $codeSequence as $csCode ) {
902  $csData = $initialData;
903 
904  # Load core messages and the extension localisations.
905  foreach ( $messageDirs as $dirs ) {
906  foreach ( (array)$dirs as $dir ) {
907  $fileName = "$dir/$csCode.json";
908  $data = $this->readJSONFile( $fileName );
909 
910  foreach ( $data as $key => $item ) {
911  $this->mergeItem( $key, $csData[$key], $item );
912  }
913 
914  $deps[] = new FileDependency( $fileName );
915  }
916  }
917 
918  # Merge non-JSON extension data
919  if ( isset( $extensionData[$csCode] ) ) {
920  foreach ( $extensionData[$csCode] as $key => $item ) {
921  $this->mergeItem( $key, $csData[$key], $item );
922  }
923  }
924 
925  if ( $csCode === $code ) {
926  # Merge core data into extension data
927  foreach ( $coreData as $key => $item ) {
928  $this->mergeItem( $key, $csData[$key], $item );
929  }
930  } else {
931  # Load the secondary localisation from the source file to
932  # avoid infinite cycles on cyclic fallbacks
933  $fbData = $this->readSourceFilesAndRegisterDeps( $csCode, $deps );
934  if ( $fbData !== false ) {
935  # Only merge the keys that make sense to merge
936  foreach ( self::$allKeys as $key ) {
937  if ( !isset( $fbData[$key] ) ) {
938  continue;
939  }
940 
941  if ( is_null( $coreData[$key] ) || $this->isMergeableKey( $key ) ) {
942  $this->mergeItem( $key, $csData[$key], $fbData[$key] );
943  }
944  }
945  }
946  }
947 
948  # Allow extensions an opportunity to adjust the data for this
949  # fallback
950  Hooks::run( 'LocalisationCacheRecacheFallback', [ $this, $csCode, &$csData ] );
951 
952  # Merge the data for this fallback into the final array
953  if ( $csCode === $code ) {
954  $allData = $csData;
955  } else {
956  foreach ( self::$allKeys as $key ) {
957  if ( !isset( $csData[$key] ) ) {
958  continue;
959  }
960 
961  if ( is_null( $allData[$key] ) || $this->isMergeableKey( $key ) ) {
962  $this->mergeItem( $key, $allData[$key], $csData[$key] );
963  }
964  }
965  }
966  }
967 
968  # Add cache dependencies for any referenced globals
969  $deps['wgExtensionMessagesFiles'] = new GlobalDependency( 'wgExtensionMessagesFiles' );
970  // The 'MessagesDirs' config setting is used in LocalisationCache::getMessagesDirs().
971  // We use the key 'wgMessagesDirs' for historical reasons.
972  $deps['wgMessagesDirs'] = new MainConfigDependency( 'MessagesDirs' );
973  $deps['version'] = new ConstantDependency( 'LocalisationCache::VERSION' );
974 
975  # Add dependencies to the cache entry
976  $allData['deps'] = $deps;
977 
978  # Replace spaces with underscores in namespace names
979  $allData['namespaceNames'] = str_replace( ' ', '_', $allData['namespaceNames'] );
980 
981  # And do the same for special page aliases. $page is an array.
982  foreach ( $allData['specialPageAliases'] as &$page ) {
983  $page = str_replace( ' ', '_', $page );
984  }
985  # Decouple the reference to prevent accidental damage
986  unset( $page );
987 
988  # If there were no plural rules, return an empty array
989  if ( $allData['pluralRules'] === null ) {
990  $allData['pluralRules'] = [];
991  }
992  if ( $allData['compiledPluralRules'] === null ) {
993  $allData['compiledPluralRules'] = [];
994  }
995  # If there were no plural rule types, return an empty array
996  if ( $allData['pluralRuleTypes'] === null ) {
997  $allData['pluralRuleTypes'] = [];
998  }
999 
1000  # Set the list keys
1001  $allData['list'] = [];
1002  foreach ( self::$splitKeys as $key ) {
1003  $allData['list'][$key] = array_keys( $allData[$key] );
1004  }
1005  # Run hooks
1006  $purgeBlobs = true;
1007  Hooks::run( 'LocalisationCacheRecache', [ $this, $code, &$allData, &$purgeBlobs ] );
1008 
1009  if ( is_null( $allData['namespaceNames'] ) ) {
1010  throw new MWException( __METHOD__ . ': Localisation data failed sanity check! ' .
1011  'Check that your languages/messages/MessagesEn.php file is intact.' );
1012  }
1013 
1014  # Set the preload key
1015  $allData['preload'] = $this->buildPreload( $allData );
1016 
1017  # Save to the process cache and register the items loaded
1018  $this->data[$code] = $allData;
1019  foreach ( $allData as $key => $item ) {
1020  $this->loadedItems[$code][$key] = true;
1021  }
1022 
1023  # Save to the persistent cache
1024  $this->store->startWrite( $code );
1025  foreach ( $allData as $key => $value ) {
1026  if ( in_array( $key, self::$splitKeys ) ) {
1027  foreach ( $value as $subkey => $subvalue ) {
1028  $this->store->set( "$key:$subkey", $subvalue );
1029  }
1030  } else {
1031  $this->store->set( $key, $value );
1032  }
1033  }
1034  $this->store->finishWrite();
1035 
1036  # Clear out the MessageBlobStore
1037  # HACK: If using a null (i.e. disabled) storage backend, we
1038  # can't write to the MessageBlobStore either
1039  if ( $purgeBlobs && !$this->store instanceof LCStoreNull ) {
1040  $blobStore = new MessageBlobStore();
1041  $blobStore->clear();
1042  }
1043  }
1044 
1053  protected function buildPreload( $data ) {
1054  $preload = [ 'messages' => [] ];
1055  foreach ( self::$preloadedKeys as $key ) {
1056  $preload[$key] = $data[$key];
1057  }
1058 
1059  foreach ( $data['preloadedMessages'] as $subkey ) {
1060  if ( isset( $data['messages'][$subkey] ) ) {
1061  $subitem = $data['messages'][$subkey];
1062  } else {
1063  $subitem = null;
1064  }
1065  $preload['messages'][$subkey] = $subitem;
1066  }
1067 
1068  return $preload;
1069  }
1070 
1076  public function unload( $code ) {
1077  unset( $this->data[$code] );
1078  unset( $this->loadedItems[$code] );
1079  unset( $this->loadedSubitems[$code] );
1080  unset( $this->initialisedLangs[$code] );
1081  unset( $this->shallowFallbacks[$code] );
1082 
1083  foreach ( $this->shallowFallbacks as $shallowCode => $fbCode ) {
1084  if ( $fbCode === $code ) {
1085  $this->unload( $shallowCode );
1086  }
1087  }
1088  }
1089 
1093  public function unloadAll() {
1094  foreach ( $this->initialisedLangs as $lang => $unused ) {
1095  $this->unload( $lang );
1096  }
1097  }
1098 
1102  public function disableBackend() {
1103  $this->store = new LCStoreNull;
1104  $this->manualRecache = false;
1105  }
1106 
1107 }
FileDependency
Definition: CacheDependency.php:152
LocalisationCache\loadSubitem
loadSubitem( $code, $key, $subkey)
Load a subitem into the cache.
Definition: LocalisationCache.php:369
LocalisationCache\$initialisedLangs
$initialisedLangs
An array where presence of a key indicates that that language has been initialised.
Definition: LocalisationCache.php:92
LocalisationCache\$manualRecache
$manualRecache
True if recaching should only be done on an explicit call to recache().
Definition: LocalisationCache.php:50
LocalisationCache\initLanguage
initLanguage( $code)
Initialise a language in this object.
Definition: LocalisationCache.php:443
LocalisationCache\$conf
$conf
Configuration associative array.
Definition: LocalisationCache.php:43
use
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
Definition: APACHE-LICENSE-2.0.txt:10
LocalisationCache\__construct
__construct( $conf)
For constructor parameters, see the documentation in DefaultSettings.php for $wgLocalisationCacheConf...
Definition: LocalisationCache.php:191
array
the array() calling protocol came about after MediaWiki 1.4rc1.
$lang
if(!isset( $args[0])) $lang
Definition: testCompression.php:33
LocalisationCache\isExpired
isExpired( $code)
Returns true if the cache identified by $code is missing or expired.
Definition: LocalisationCache.php:405
LocalisationCache\getSubitemList
getSubitemList( $code, $key)
Get the list of subitem keys for a given item.
Definition: LocalisationCache.php:314
LocalisationCache\$recachedLangs
$recachedLangs
An array where the keys are codes that have been recached by this instance.
Definition: LocalisationCache.php:104
ConstantDependency
Definition: CacheDependency.php:278
MainConfigDependency
Definition: CacheDependency.php:250
$wgCacheDirectory
$wgCacheDirectory
Directory for caching data in the local filesystem.
Definition: DefaultSettings.php:2252
LCStore
Interface for the persistence layer of LocalisationCache.
Definition: LCStore.php:38
Language\getMessagesFileName
static getMessagesFileName( $code)
Definition: Language.php:4502
LocalisationCache\$mergeableAliasListKeys
static $mergeableAliasListKeys
Keys for items which contain an array of arrays of equivalent aliases for each subitem.
Definition: LocalisationCache.php:138
LocalisationCache\readSourceFilesAndRegisterDeps
readSourceFilesAndRegisterDeps( $code, &$deps)
Read the data from the source files for a given language, and register the relevant dependencies in t...
Definition: LocalisationCache.php:702
LocalisationCache\getItem
getItem( $code, $key)
Get a cache item.
Definition: LocalisationCache.php:269
wfDebugLog
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
Definition: GlobalFunctions.php:1087
GlobalDependency
Definition: CacheDependency.php:226
php
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:37
LocalisationCache\recache
recache( $code)
Load localisation data for a given language for both core and extensions and save it to the persisten...
Definition: LocalisationCache.php:828
LocalisationCache\readJSONFile
readJSONFile( $fileName)
Read a JSON file containing localisation messages.
Definition: LocalisationCache.php:557
LocalisationCache\$pluralRules
$pluralRules
Associative array of cached plural rules.
Definition: LocalisationCache.php:166
LocalisationCache\mergeExtensionItem
mergeExtensionItem( $codeSequence, $key, &$value, $fallbackValue)
Given an array mapping language code to localisation value, such as is found in extension *....
Definition: LocalisationCache.php:791
LocalisationCache\$preloadedKeys
static $preloadedKeys
Keys which are loaded automatically by initLanguage()
Definition: LocalisationCache.php:160
LocalisationCache\getPluralRules
getPluralRules( $code)
Get the plural rules for a given language from the XML files.
Definition: LocalisationCache.php:612
FormatJson\decode
static decode( $value, $assoc=false)
Decodes a JSON string.
Definition: FormatJson.php:187
MWException
MediaWiki exception.
Definition: MWException.php:26
LocalisationCache\unload
unload( $code)
Unload the data for a given language from the object cache.
Definition: LocalisationCache.php:1076
LocalisationCache\getMessagesDirs
getMessagesDirs()
Gets the combined list of messages dirs from core and extensions.
Definition: LocalisationCache.php:810
LocalisationCache\mergeMagicWords
mergeMagicWords(&$value, $fallbackValue)
Definition: LocalisationCache.php:764
store
MediaWiki s SiteStore can be cached and stored in a flat in a json format If the SiteStore is frequently the file cache may provide a performance benefit over a database store
Definition: sitescache.txt:4
LocalisationCache\disableBackend
disableBackend()
Disable the storage backend.
Definition: LocalisationCache.php:1102
LocalisationCache\getCompiledPluralRules
getCompiledPluralRules( $code)
Get the compiled plural rules for a given language from the XML files.
Definition: LocalisationCache.php:589
$dirs
$dirs
Definition: mergeMessageFileList.php:194
LocalisationCache\$optionalMergeKeys
static $optionalMergeKeys
Keys for items which contain an associative array, and may be merged if the primary value contains th...
Definition: LocalisationCache.php:145
LocalisationCache\$loadedItems
$loadedItems
A 2-d associative array, code/key, where presence indicates that the item is loaded.
Definition: LocalisationCache.php:79
global
when a variable name is used in a it is silently declared as a new masking the global
Definition: design.txt:95
wfDebug
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:994
LocalisationCache\readPHPFile
readPHPFile( $_fileName, $_fileType)
Read a PHP file containing localisation data.
Definition: LocalisationCache.php:518
Language\isValidBuiltInCode
static isValidBuiltInCode( $code)
Returns true if a language code is of a valid form for the purposes of internal customisation of Medi...
Definition: Language.php:363
LocalisationCache\$mergeableMapKeys
static $mergeableMapKeys
Keys for items which consist of associative arrays, which may be merged by a fallback sequence.
Definition: LocalisationCache.php:125
LocalisationCache\buildPreload
buildPreload( $data)
Build the preload item from the given pre-cache data.
Definition: LocalisationCache.php:1053
LocalisationCache\$mergeableKeys
$mergeableKeys
Definition: LocalisationCache.php:182
$value
$value
Definition: styleTest.css.php:45
LocalisationCache\loadPluralFile
loadPluralFile( $fileName)
Load a plural XML file with the given filename, compile the relevant rules, and save the compiled rul...
Definition: LocalisationCache.php:663
LocalisationCache
Class for caching the contents of localisation files, Messages*.php and *.i18n.php.
Definition: LocalisationCache.php:39
LCStoreNull
Null store backend, used to avoid DB errors during install.
Definition: LCStoreNull.php:24
LocalisationCache\$forceRecache
$forceRecache
True to treat all files as expired until they are regenerated by this object.
Definition: LocalisationCache.php:55
LocalisationCache\loadItem
loadItem( $code, $key)
Load an item into the cache.
Definition: LocalisationCache.php:332
CacheDependency
Definition: CacheDependency.php:136
LocalisationCache\$pluralRuleTypes
$pluralRuleTypes
Associative array of cached plural rule types.
Definition: LocalisationCache.php:180
$code
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable & $code
Definition: hooks.txt:865
LocalisationCache\initShallowFallback
initShallowFallback( $primaryCode, $fallbackCode)
Create a fallback from one language to another, without creating a complete persistent cache.
Definition: LocalisationCache.php:504
$wgExtensionMessagesFiles
$wgExtensionMessagesFiles['ExtensionNameMagic']
Definition: magicword.txt:43
CacheDependency\isExpired
isExpired()
Returns true if the dependency is expired, false otherwise.
as
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:22
LocalisationCache\VERSION
const VERSION
Definition: LocalisationCache.php:40
LocalisationCache\isMergeableKey
isMergeableKey( $key)
Returns true if the given key is mergeable, that is, if it is an associative array which can be merge...
Definition: LocalisationCache.php:246
LocalisationCache\mergeItem
mergeItem( $key, &$value, $fallbackValue)
Merge two localisation values, a primary and a fallback, overwriting the primary value in place.
Definition: LocalisationCache.php:734
MessageBlobStore
This class generates message blobs for use by ResourceLoader modules.
Definition: MessageBlobStore.php:37
$keys
$keys
Definition: testCompression.php:67
LocalisationCache\unloadAll
unloadAll()
Unload all data.
Definition: LocalisationCache.php:1093
LocalisationCache\getPluralRuleTypes
getPluralRuleTypes( $code)
Get the plural rule types for a given language from the XML files.
Definition: LocalisationCache.php:630
LocalisationCache\$data
$data
The cache data.
Definition: LocalisationCache.php:63
class
you have access to all of the normal MediaWiki so you can get a DB use the etc For full docs on the Maintenance class
Definition: maintenance.txt:56
MediaWikiServices
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency MediaWikiServices
Definition: injection.txt:25
Language\isSupportedLanguage
static isSupportedLanguage( $code)
Checks whether any localisation is available for that language tag in MediaWiki (MessagesXx....
Definition: Language.php:256
LocalisationCache\$splitKeys
static $splitKeys
Keys for items where the subitems are stored in the backend separately.
Definition: LocalisationCache.php:155
LocalisationCache\$mergeableListKeys
static $mergeableListKeys
Keys for items which are a numbered array.
Definition: LocalisationCache.php:132
LocalisationCache\getSubitem
getSubitem( $code, $key, $subkey)
Get a subitem, for instance a single message for a given language.
Definition: LocalisationCache.php:288
$IP
$IP
Definition: WebStart.php:52
LocalisationCache\$shallowFallbacks
$shallowFallbacks
An array mapping non-existent pseudo-languages to fallback languages.
Definition: LocalisationCache.php:99
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:203
LocalisationCache\$loadedSubitems
$loadedSubitems
A 3-d associative array, code/key/subkey, where presence indicates that the subitem is loaded.
Definition: LocalisationCache.php:85
LocalisationCache\loadPluralFiles
loadPluralFiles()
Load the plural XML files.
Definition: LocalisationCache.php:644
false
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:187
LocalisationCache\$allKeys
static $allKeys
All item keys.
Definition: LocalisationCache.php:109
LocalisationCache\$magicWordKeys
static $magicWordKeys
Keys for items that are formatted like $magicWords.
Definition: LocalisationCache.php:150
$e
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:2171
data
and how to run hooks for an and one after Each event has a preferably in CamelCase For ArticleDelete hook A clump of code and data that should be run when an event happens This can be either a function and a chunk of data
Definition: hooks.txt:18
LocalisationCache\$store
LCStore $store
The persistent store object.
Definition: LocalisationCache.php:70