MediaWiki  master
LocalisationCache.php
Go to the documentation of this file.
1 <?php
24 use CLDRPluralRuleParser\Error as CLDRPluralRuleError;
28 
42  const VERSION = 4;
43 
45  private $options;
46 
52  private $manualRecache = false;
53 
60  protected $data = [];
61 
67  private $store;
68 
72  private $logger;
73 
76 
78  private $langNameUtils;
79 
87  private $loadedItems = [];
88 
93  private $loadedSubitems = [];
94 
100  private $initialisedLangs = [];
101 
107  private $shallowFallbacks = [];
108 
112  private $recachedLangs = [];
113 
117  public static $allKeys = [
118  'fallback', 'namespaceNames', 'bookstoreList',
119  'magicWords', 'messages', 'rtl', 'capitalizeAllNouns',
120  'digitTransformTable', 'separatorTransformTable',
121  'minimumGroupingDigits', 'fallback8bitEncoding',
122  'linkPrefixExtension', 'linkTrail', 'linkPrefixCharset',
123  'namespaceAliases', 'dateFormats', 'datePreferences',
124  'datePreferenceMigrationMap', 'defaultDateFormat',
125  'specialPageAliases', 'imageFiles', 'preloadedMessages',
126  'namespaceGenderAliases', 'digitGroupingPattern', 'pluralRules',
127  'pluralRuleTypes', 'compiledPluralRules',
128  ];
129 
134  public static $mergeableMapKeys = [ 'messages', 'namespaceNames',
135  'namespaceAliases', 'dateFormats', 'imageFiles', 'preloadedMessages'
136  ];
137 
141  public static $mergeableListKeys = [];
142 
147  public static $mergeableAliasListKeys = [ 'specialPageAliases' ];
148 
154  public static $optionalMergeKeys = [ 'bookstoreList' ];
155 
159  public static $magicWordKeys = [ 'magicWords' ];
160 
164  public static $splitKeys = [ 'messages' ];
165 
169  public static $preloadedKeys = [ 'dateFormats', 'namespaceNames' ];
170 
175  private $pluralRules = null;
176 
190 
191  private $mergeableKeys = null;
192 
199  public static $constructorOptions = [
200  // True to treat all files as expired until they are regenerated by this object.
201  'forceRecache',
202  'manualRecache',
203  'ExtensionMessagesFiles',
204  'MessagesDirs',
205  ];
206 
222  function __construct(
224  LCStore $store,
225  LoggerInterface $logger,
226  array $clearStoreCallbacks,
228  ) {
229  $options->assertRequiredOptions( self::$constructorOptions );
230 
231  $this->options = $options;
232  $this->store = $store;
233  $this->logger = $logger;
234  $this->clearStoreCallbacks = $clearStoreCallbacks;
235  $this->langNameUtils = $langNameUtils;
236 
237  // Keep this separate from $this->options so it can be mutable
238  $this->manualRecache = $options->get( 'manualRecache' );
239  }
240 
247  public function isMergeableKey( $key ) {
248  if ( $this->mergeableKeys === null ) {
249  $this->mergeableKeys = array_flip( array_merge(
250  self::$mergeableMapKeys,
251  self::$mergeableListKeys,
252  self::$mergeableAliasListKeys,
253  self::$optionalMergeKeys,
254  self::$magicWordKeys
255  ) );
256  }
257 
258  return isset( $this->mergeableKeys[$key] );
259  }
260 
270  public function getItem( $code, $key ) {
271  if ( !isset( $this->loadedItems[$code][$key] ) ) {
272  $this->loadItem( $code, $key );
273  }
274 
275  if ( $key === 'fallback' && isset( $this->shallowFallbacks[$code] ) ) {
276  return $this->shallowFallbacks[$code];
277  }
278 
279  return $this->data[$code][$key];
280  }
281 
289  public function getSubitem( $code, $key, $subkey ) {
290  if ( !isset( $this->loadedSubitems[$code][$key][$subkey] ) &&
291  !isset( $this->loadedItems[$code][$key] )
292  ) {
293  $this->loadSubitem( $code, $key, $subkey );
294  }
295 
296  return $this->data[$code][$key][$subkey] ?? null;
297  }
298 
311  public function getSubitemList( $code, $key ) {
312  if ( in_array( $key, self::$splitKeys ) ) {
313  return $this->getSubitem( $code, 'list', $key );
314  } else {
315  $item = $this->getItem( $code, $key );
316  if ( is_array( $item ) ) {
317  return array_keys( $item );
318  } else {
319  return false;
320  }
321  }
322  }
323 
329  protected function loadItem( $code, $key ) {
330  if ( !isset( $this->initialisedLangs[$code] ) ) {
331  $this->initLanguage( $code );
332  }
333 
334  // Check to see if initLanguage() loaded it for us
335  if ( isset( $this->loadedItems[$code][$key] ) ) {
336  return;
337  }
338 
339  if ( isset( $this->shallowFallbacks[$code] ) ) {
340  $this->loadItem( $this->shallowFallbacks[$code], $key );
341 
342  return;
343  }
344 
345  if ( in_array( $key, self::$splitKeys ) ) {
346  $subkeyList = $this->getSubitem( $code, 'list', $key );
347  foreach ( $subkeyList as $subkey ) {
348  if ( isset( $this->data[$code][$key][$subkey] ) ) {
349  continue;
350  }
351  $this->data[$code][$key][$subkey] = $this->getSubitem( $code, $key, $subkey );
352  }
353  } else {
354  $this->data[$code][$key] = $this->store->get( $code, $key );
355  }
356 
357  $this->loadedItems[$code][$key] = true;
358  }
359 
366  protected function loadSubitem( $code, $key, $subkey ) {
367  if ( !in_array( $key, self::$splitKeys ) ) {
368  $this->loadItem( $code, $key );
369 
370  return;
371  }
372 
373  if ( !isset( $this->initialisedLangs[$code] ) ) {
374  $this->initLanguage( $code );
375  }
376 
377  // Check to see if initLanguage() loaded it for us
378  if ( isset( $this->loadedItems[$code][$key] ) ||
379  isset( $this->loadedSubitems[$code][$key][$subkey] )
380  ) {
381  return;
382  }
383 
384  if ( isset( $this->shallowFallbacks[$code] ) ) {
385  $this->loadSubitem( $this->shallowFallbacks[$code], $key, $subkey );
386 
387  return;
388  }
389 
390  $value = $this->store->get( $code, "$key:$subkey" );
391  $this->data[$code][$key][$subkey] = $value;
392  $this->loadedSubitems[$code][$key][$subkey] = true;
393  }
394 
402  public function isExpired( $code ) {
403  if ( $this->options->get( 'forceRecache' ) && !isset( $this->recachedLangs[$code] ) ) {
404  $this->logger->debug( __METHOD__ . "($code): forced reload" );
405 
406  return true;
407  }
408 
409  $deps = $this->store->get( $code, 'deps' );
410  $keys = $this->store->get( $code, 'list' );
411  $preload = $this->store->get( $code, 'preload' );
412  // Different keys may expire separately for some stores
413  if ( $deps === null || $keys === null || $preload === null ) {
414  $this->logger->debug( __METHOD__ . "($code): cache missing, need to make one" );
415 
416  return true;
417  }
418 
419  foreach ( $deps as $dep ) {
420  // Because we're unserializing stuff from cache, we
421  // could receive objects of classes that don't exist
422  // anymore (e.g. uninstalled extensions)
423  // When this happens, always expire the cache
424  if ( !$dep instanceof CacheDependency || $dep->isExpired() ) {
425  $this->logger->debug( __METHOD__ . "($code): cache for $code expired due to " .
426  get_class( $dep ) );
427 
428  return true;
429  }
430  }
431 
432  return false;
433  }
434 
440  protected function initLanguage( $code ) {
441  if ( isset( $this->initialisedLangs[$code] ) ) {
442  return;
443  }
444 
445  $this->initialisedLangs[$code] = true;
446 
447  # If the code is of the wrong form for a Messages*.php file, do a shallow fallback
448  if ( !$this->langNameUtils->isValidBuiltInCode( $code ) ) {
449  $this->initShallowFallback( $code, 'en' );
450 
451  return;
452  }
453 
454  # Recache the data if necessary
455  if ( !$this->manualRecache && $this->isExpired( $code ) ) {
456  if ( $this->langNameUtils->isSupportedLanguage( $code ) ) {
457  $this->recache( $code );
458  } elseif ( $code === 'en' ) {
459  throw new MWException( 'MessagesEn.php is missing.' );
460  } else {
461  $this->initShallowFallback( $code, 'en' );
462  }
463 
464  return;
465  }
466 
467  # Preload some stuff
468  $preload = $this->getItem( $code, 'preload' );
469  if ( $preload === null ) {
470  if ( $this->manualRecache ) {
471  // No Messages*.php file. Do shallow fallback to en.
472  if ( $code === 'en' ) {
473  throw new MWException( 'No localisation cache found for English. ' .
474  'Please run maintenance/rebuildLocalisationCache.php.' );
475  }
476  $this->initShallowFallback( $code, 'en' );
477 
478  return;
479  } else {
480  throw new MWException( 'Invalid or missing localisation cache.' );
481  }
482  }
483  $this->data[$code] = $preload;
484  foreach ( $preload as $key => $item ) {
485  if ( in_array( $key, self::$splitKeys ) ) {
486  foreach ( $item as $subkey => $subitem ) {
487  $this->loadedSubitems[$code][$key][$subkey] = true;
488  }
489  } else {
490  $this->loadedItems[$code][$key] = true;
491  }
492  }
493  }
494 
501  public function initShallowFallback( $primaryCode, $fallbackCode ) {
502  $this->data[$primaryCode] =& $this->data[$fallbackCode];
503  $this->loadedItems[$primaryCode] =& $this->loadedItems[$fallbackCode];
504  $this->loadedSubitems[$primaryCode] =& $this->loadedSubitems[$fallbackCode];
505  $this->shallowFallbacks[$primaryCode] = $fallbackCode;
506  }
507 
515  protected function readPHPFile( $_fileName, $_fileType ) {
516  // Disable APC caching
517  Wikimedia\suppressWarnings();
518  $_apcEnabled = ini_set( 'apc.cache_by_default', '0' );
519  Wikimedia\restoreWarnings();
520 
521  include $_fileName;
522 
523  Wikimedia\suppressWarnings();
524  ini_set( 'apc.cache_by_default', $_apcEnabled );
525  Wikimedia\restoreWarnings();
526 
527  $data = [];
528  if ( $_fileType == 'core' || $_fileType == 'extension' ) {
529  foreach ( self::$allKeys as $key ) {
530  // Not all keys are set in language files, so
531  // check they exist first
532  if ( isset( $$key ) ) {
533  $data[$key] = $$key;
534  }
535  }
536  } elseif ( $_fileType == 'aliases' ) {
537  if ( isset( $aliases ) ) {
538  $data['aliases'] = $aliases;
539  }
540  } else {
541  throw new MWException( __METHOD__ . ": Invalid file type: $_fileType" );
542  }
543 
544  return $data;
545  }
546 
553  public function readJSONFile( $fileName ) {
554  if ( !is_readable( $fileName ) ) {
555  return [];
556  }
557 
558  $json = file_get_contents( $fileName );
559  if ( $json === false ) {
560  return [];
561  }
562 
563  $data = FormatJson::decode( $json, true );
564  if ( $data === null ) {
565  throw new MWException( __METHOD__ . ": Invalid JSON file: $fileName" );
566  }
567 
568  // Remove keys starting with '@', they're reserved for metadata and non-message data
569  foreach ( $data as $key => $unused ) {
570  if ( $key === '' || $key[0] === '@' ) {
571  unset( $data[$key] );
572  }
573  }
574 
575  // The JSON format only supports messages, none of the other variables, so wrap the data
576  return [ 'messages' => $data ];
577  }
578 
585  public function getCompiledPluralRules( $code ) {
586  $rules = $this->getPluralRules( $code );
587  if ( $rules === null ) {
588  return null;
589  }
590  try {
591  $compiledRules = Evaluator::compile( $rules );
592  } catch ( CLDRPluralRuleError $e ) {
593  $this->logger->debug( $e->getMessage() );
594 
595  return [];
596  }
597 
598  return $compiledRules;
599  }
600 
608  public function getPluralRules( $code ) {
609  if ( $this->pluralRules === null ) {
610  $this->loadPluralFiles();
611  }
612  return $this->pluralRules[$code] ?? null;
613  }
614 
622  public function getPluralRuleTypes( $code ) {
623  if ( $this->pluralRuleTypes === null ) {
624  $this->loadPluralFiles();
625  }
626  return $this->pluralRuleTypes[$code] ?? null;
627  }
628 
632  protected function loadPluralFiles() {
633  global $IP;
634  $cldrPlural = "$IP/languages/data/plurals.xml";
635  $mwPlural = "$IP/languages/data/plurals-mediawiki.xml";
636  // Load CLDR plural rules
637  $this->loadPluralFile( $cldrPlural );
638  if ( file_exists( $mwPlural ) ) {
639  // Override or extend
640  $this->loadPluralFile( $mwPlural );
641  }
642  }
643 
651  protected function loadPluralFile( $fileName ) {
652  // Use file_get_contents instead of DOMDocument::load (T58439)
653  $xml = file_get_contents( $fileName );
654  if ( !$xml ) {
655  throw new MWException( "Unable to read plurals file $fileName" );
656  }
657  $doc = new DOMDocument;
658  $doc->loadXML( $xml );
659  $rulesets = $doc->getElementsByTagName( "pluralRules" );
660  foreach ( $rulesets as $ruleset ) {
661  $codes = $ruleset->getAttribute( 'locales' );
662  $rules = [];
663  $ruleTypes = [];
664  $ruleElements = $ruleset->getElementsByTagName( "pluralRule" );
665  foreach ( $ruleElements as $elt ) {
666  $ruleType = $elt->getAttribute( 'count' );
667  if ( $ruleType === 'other' ) {
668  // Don't record "other" rules, which have an empty condition
669  continue;
670  }
671  $rules[] = $elt->nodeValue;
672  $ruleTypes[] = $ruleType;
673  }
674  foreach ( explode( ' ', $codes ) as $code ) {
675  $this->pluralRules[$code] = $rules;
676  $this->pluralRuleTypes[$code] = $ruleTypes;
677  }
678  }
679  }
680 
690  protected function readSourceFilesAndRegisterDeps( $code, &$deps ) {
691  global $IP;
692 
693  // This reads in the PHP i18n file with non-messages l10n data
694  $fileName = $this->langNameUtils->getMessagesFileName( $code );
695  if ( !file_exists( $fileName ) ) {
696  $data = [];
697  } else {
698  $deps[] = new FileDependency( $fileName );
699  $data = $this->readPHPFile( $fileName, 'core' );
700  }
701 
702  # Load CLDR plural rules for JavaScript
703  $data['pluralRules'] = $this->getPluralRules( $code );
704  # And for PHP
705  $data['compiledPluralRules'] = $this->getCompiledPluralRules( $code );
706  # Load plural rule types
707  $data['pluralRuleTypes'] = $this->getPluralRuleTypes( $code );
708 
709  $deps['plurals'] = new FileDependency( "$IP/languages/data/plurals.xml" );
710  $deps['plurals-mw'] = new FileDependency( "$IP/languages/data/plurals-mediawiki.xml" );
711 
712  return $data;
713  }
714 
722  protected function mergeItem( $key, &$value, $fallbackValue ) {
723  if ( !is_null( $value ) ) {
724  if ( !is_null( $fallbackValue ) ) {
725  if ( in_array( $key, self::$mergeableMapKeys ) ) {
726  $value = $value + $fallbackValue;
727  } elseif ( in_array( $key, self::$mergeableListKeys ) ) {
728  $value = array_unique( array_merge( $fallbackValue, $value ) );
729  } elseif ( in_array( $key, self::$mergeableAliasListKeys ) ) {
730  $value = array_merge_recursive( $value, $fallbackValue );
731  } elseif ( in_array( $key, self::$optionalMergeKeys ) ) {
732  if ( !empty( $value['inherit'] ) ) {
733  $value = array_merge( $fallbackValue, $value );
734  }
735 
736  if ( isset( $value['inherit'] ) ) {
737  unset( $value['inherit'] );
738  }
739  } elseif ( in_array( $key, self::$magicWordKeys ) ) {
740  $this->mergeMagicWords( $value, $fallbackValue );
741  }
742  }
743  } else {
744  $value = $fallbackValue;
745  }
746  }
747 
752  protected function mergeMagicWords( &$value, $fallbackValue ) {
753  foreach ( $fallbackValue as $magicName => $fallbackInfo ) {
754  if ( !isset( $value[$magicName] ) ) {
755  $value[$magicName] = $fallbackInfo;
756  } else {
757  $oldSynonyms = array_slice( $fallbackInfo, 1 );
758  $newSynonyms = array_slice( $value[$magicName], 1 );
759  $synonyms = array_values( array_unique( array_merge(
760  $newSynonyms, $oldSynonyms ) ) );
761  $value[$magicName] = array_merge( [ $fallbackInfo[0] ], $synonyms );
762  }
763  }
764  }
765 
779  protected function mergeExtensionItem( $codeSequence, $key, &$value, $fallbackValue ) {
780  $used = false;
781  foreach ( $codeSequence as $code ) {
782  if ( isset( $fallbackValue[$code] ) ) {
783  $this->mergeItem( $key, $value, $fallbackValue[$code] );
784  $used = true;
785  }
786  }
787 
788  return $used;
789  }
790 
798  public function getMessagesDirs() {
799  global $IP;
800 
801  return [
802  'core' => "$IP/languages/i18n",
803  'exif' => "$IP/languages/i18n/exif",
804  'api' => "$IP/includes/api/i18n",
805  'oojs-ui' => "$IP/resources/lib/ooui/i18n",
806  ] + $this->options->get( 'MessagesDirs' );
807  }
808 
815  public function recache( $code ) {
816  if ( !$code ) {
817  throw new MWException( "Invalid language code requested" );
818  }
819  $this->recachedLangs[$code] = true;
820 
821  # Initial values
822  $initialData = array_fill_keys( self::$allKeys, null );
823  $coreData = $initialData;
824  $deps = [];
825 
826  # Load the primary localisation from the source file
827  $data = $this->readSourceFilesAndRegisterDeps( $code, $deps );
828  if ( $data === false ) {
829  $this->logger->debug( __METHOD__ . ": no localisation file for $code, using fallback to en" );
830  $coreData['fallback'] = 'en';
831  } else {
832  $this->logger->debug( __METHOD__ . ": got localisation for $code from source" );
833 
834  # Merge primary localisation
835  foreach ( $data as $key => $value ) {
836  $this->mergeItem( $key, $coreData[$key], $value );
837  }
838  }
839 
840  # Fill in the fallback if it's not there already
841  if ( ( is_null( $coreData['fallback'] ) || $coreData['fallback'] === false ) && $code === 'en' ) {
842  $coreData['fallback'] = false;
843  $coreData['originalFallbackSequence'] = $coreData['fallbackSequence'] = [];
844  } else {
845  if ( !is_null( $coreData['fallback'] ) ) {
846  $coreData['fallbackSequence'] = array_map( 'trim', explode( ',', $coreData['fallback'] ) );
847  } else {
848  $coreData['fallbackSequence'] = [];
849  }
850  $len = count( $coreData['fallbackSequence'] );
851 
852  # Before we add the 'en' fallback for messages, keep a copy of
853  # the original fallback sequence
854  $coreData['originalFallbackSequence'] = $coreData['fallbackSequence'];
855 
856  # Ensure that the sequence ends at 'en' for messages
857  if ( !$len || $coreData['fallbackSequence'][$len - 1] !== 'en' ) {
858  $coreData['fallbackSequence'][] = 'en';
859  }
860  }
861 
862  $codeSequence = array_merge( [ $code ], $coreData['fallbackSequence'] );
863  $messageDirs = $this->getMessagesDirs();
864 
865  # Load non-JSON localisation data for extensions
866  $extensionData = array_fill_keys( $codeSequence, $initialData );
867  foreach ( $this->options->get( 'ExtensionMessagesFiles' ) as $extension => $fileName ) {
868  if ( isset( $messageDirs[$extension] ) ) {
869  # This extension has JSON message data; skip the PHP shim
870  continue;
871  }
872 
873  $data = $this->readPHPFile( $fileName, 'extension' );
874  $used = false;
875 
876  foreach ( $data as $key => $item ) {
877  foreach ( $codeSequence as $csCode ) {
878  if ( isset( $item[$csCode] ) ) {
879  $this->mergeItem( $key, $extensionData[$csCode][$key], $item[$csCode] );
880  $used = true;
881  }
882  }
883  }
884 
885  if ( $used ) {
886  $deps[] = new FileDependency( $fileName );
887  }
888  }
889 
890  # Load the localisation data for each fallback, then merge it into the full array
891  $allData = $initialData;
892  foreach ( $codeSequence as $csCode ) {
893  $csData = $initialData;
894 
895  # Load core messages and the extension localisations.
896  foreach ( $messageDirs as $dirs ) {
897  foreach ( (array)$dirs as $dir ) {
898  $fileName = "$dir/$csCode.json";
899  $data = $this->readJSONFile( $fileName );
900 
901  foreach ( $data as $key => $item ) {
902  $this->mergeItem( $key, $csData[$key], $item );
903  }
904 
905  $deps[] = new FileDependency( $fileName );
906  }
907  }
908 
909  # Merge non-JSON extension data
910  if ( isset( $extensionData[$csCode] ) ) {
911  foreach ( $extensionData[$csCode] as $key => $item ) {
912  $this->mergeItem( $key, $csData[$key], $item );
913  }
914  }
915 
916  if ( $csCode === $code ) {
917  # Merge core data into extension data
918  foreach ( $coreData as $key => $item ) {
919  $this->mergeItem( $key, $csData[$key], $item );
920  }
921  } else {
922  # Load the secondary localisation from the source file to
923  # avoid infinite cycles on cyclic fallbacks
924  $fbData = $this->readSourceFilesAndRegisterDeps( $csCode, $deps );
925  if ( $fbData !== false ) {
926  # Only merge the keys that make sense to merge
927  foreach ( self::$allKeys as $key ) {
928  if ( !isset( $fbData[$key] ) ) {
929  continue;
930  }
931 
932  if ( is_null( $coreData[$key] ) || $this->isMergeableKey( $key ) ) {
933  $this->mergeItem( $key, $csData[$key], $fbData[$key] );
934  }
935  }
936  }
937  }
938 
939  # Allow extensions an opportunity to adjust the data for this
940  # fallback
941  Hooks::run( 'LocalisationCacheRecacheFallback', [ $this, $csCode, &$csData ] );
942 
943  # Merge the data for this fallback into the final array
944  if ( $csCode === $code ) {
945  $allData = $csData;
946  } else {
947  foreach ( self::$allKeys as $key ) {
948  if ( !isset( $csData[$key] ) ) {
949  continue;
950  }
951 
952  if ( is_null( $allData[$key] ) || $this->isMergeableKey( $key ) ) {
953  $this->mergeItem( $key, $allData[$key], $csData[$key] );
954  }
955  }
956  }
957  }
958 
959  # Add cache dependencies for any referenced globals
960  $deps['wgExtensionMessagesFiles'] = new GlobalDependency( 'wgExtensionMessagesFiles' );
961  // The 'MessagesDirs' config setting is used in LocalisationCache::getMessagesDirs().
962  // We use the key 'wgMessagesDirs' for historical reasons.
963  $deps['wgMessagesDirs'] = new MainConfigDependency( 'MessagesDirs' );
964  $deps['version'] = new ConstantDependency( 'LocalisationCache::VERSION' );
965 
966  # Add dependencies to the cache entry
967  $allData['deps'] = $deps;
968 
969  # Replace spaces with underscores in namespace names
970  $allData['namespaceNames'] = str_replace( ' ', '_', $allData['namespaceNames'] );
971 
972  # And do the same for special page aliases. $page is an array.
973  foreach ( $allData['specialPageAliases'] as &$page ) {
974  $page = str_replace( ' ', '_', $page );
975  }
976  # Decouple the reference to prevent accidental damage
977  unset( $page );
978 
979  # If there were no plural rules, return an empty array
980  if ( $allData['pluralRules'] === null ) {
981  $allData['pluralRules'] = [];
982  }
983  if ( $allData['compiledPluralRules'] === null ) {
984  $allData['compiledPluralRules'] = [];
985  }
986  # If there were no plural rule types, return an empty array
987  if ( $allData['pluralRuleTypes'] === null ) {
988  $allData['pluralRuleTypes'] = [];
989  }
990 
991  # Set the list keys
992  $allData['list'] = [];
993  foreach ( self::$splitKeys as $key ) {
994  $allData['list'][$key] = array_keys( $allData[$key] );
995  }
996  # Run hooks
997  $unused = true; // Used to be $purgeBlobs, removed in 1.34
998  Hooks::run( 'LocalisationCacheRecache', [ $this, $code, &$allData, &$unused ] );
999 
1000  if ( is_null( $allData['namespaceNames'] ) ) {
1001  throw new MWException( __METHOD__ . ': Localisation data failed sanity check! ' .
1002  'Check that your languages/messages/MessagesEn.php file is intact.' );
1003  }
1004 
1005  # Set the preload key
1006  $allData['preload'] = $this->buildPreload( $allData );
1007 
1008  # Save to the process cache and register the items loaded
1009  $this->data[$code] = $allData;
1010  foreach ( $allData as $key => $item ) {
1011  $this->loadedItems[$code][$key] = true;
1012  }
1013 
1014  # Save to the persistent cache
1015  $this->store->startWrite( $code );
1016  foreach ( $allData as $key => $value ) {
1017  if ( in_array( $key, self::$splitKeys ) ) {
1018  foreach ( $value as $subkey => $subvalue ) {
1019  $this->store->set( "$key:$subkey", $subvalue );
1020  }
1021  } else {
1022  $this->store->set( $key, $value );
1023  }
1024  }
1025  $this->store->finishWrite();
1026 
1027  # Clear out the MessageBlobStore
1028  # HACK: If using a null (i.e. disabled) storage backend, we
1029  # can't write to the MessageBlobStore either
1030  if ( !$this->store instanceof LCStoreNull ) {
1031  foreach ( $this->clearStoreCallbacks as $callback ) {
1032  $callback();
1033  }
1034  }
1035  }
1036 
1045  protected function buildPreload( $data ) {
1046  $preload = [ 'messages' => [] ];
1047  foreach ( self::$preloadedKeys as $key ) {
1048  $preload[$key] = $data[$key];
1049  }
1050 
1051  foreach ( $data['preloadedMessages'] as $subkey ) {
1052  $subitem = $data['messages'][$subkey] ?? null;
1053  $preload['messages'][$subkey] = $subitem;
1054  }
1055 
1056  return $preload;
1057  }
1058 
1064  public function unload( $code ) {
1065  unset( $this->data[$code] );
1066  unset( $this->loadedItems[$code] );
1067  unset( $this->loadedSubitems[$code] );
1068  unset( $this->initialisedLangs[$code] );
1069  unset( $this->shallowFallbacks[$code] );
1070 
1071  foreach ( $this->shallowFallbacks as $shallowCode => $fbCode ) {
1072  if ( $fbCode === $code ) {
1073  $this->unload( $shallowCode );
1074  }
1075  }
1076  }
1077 
1081  public function unloadAll() {
1082  foreach ( $this->initialisedLangs as $lang => $unused ) {
1083  $this->unload( $lang );
1084  }
1085  }
1086 
1090  public function disableBackend() {
1091  $this->store = new LCStoreNull;
1092  $this->manualRecache = false;
1093  }
1094 }
unloadAll()
Unload all data.
static $mergeableAliasListKeys
Keys for items which contain an array of arrays of equivalent aliases for each subitem.
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:6
readSourceFilesAndRegisterDeps( $code, &$deps)
Read the data from the source files for a given language, and register the relevant dependencies in t...
getItem( $code, $key)
Get a cache item.
getSubitem( $code, $key, $subkey)
Get a subitem, for instance a single message for a given language.
mergeExtensionItem( $codeSequence, $key, &$value, $fallbackValue)
Given an array mapping language code to localisation value, such as is found in extension *...
mergeItem( $key, &$value, $fallbackValue)
Merge two localisation values, a primary and a fallback, overwriting the primary value in place...
$IP
Definition: WebStart.php:41
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:2158
unload( $code)
Unload the data for a given language from the object cache.
LCStore $store
The persistent store object.
if(!isset( $args[0])) $lang
callable [] $clearStoreCallbacks
See comment for parameter in constructor.
$shallowFallbacks
An array mapping non-existent pseudo-languages to fallback languages.
readJSONFile( $fileName)
Read a JSON file containing localisation messages.
initLanguage( $code)
Initialise a language in this object.
$initialisedLangs
An array where presence of a key indicates that that language has been initialised.
LanguageNameUtils $langNameUtils
$value
static $allKeys
All item keys.
isExpired()
Returns true if the dependency is expired, false otherwise.
getMessagesDirs()
Gets the combined list of messages dirs from core and extensions.
mergeMagicWords(&$value, $fallbackValue)
$manualRecache
True if recaching should only be done on an explicit call to recache().
disableBackend()
Disable the storage backend.
A class for passing options to services.
static $optionalMergeKeys
Keys for items which contain an associative array, and may be merged if the primary value contains th...
LoggerInterface $logger
$recachedLangs
An array where the keys are codes that have been recached by this instance.
getSubitemList( $code, $key)
Get the list of subitem keys for a given item.
buildPreload( $data)
Build the preload item from the given pre-cache data.
recache( $code)
Load localisation data for a given language for both core and extensions and save it to the persisten...
static decode( $value, $assoc=false)
Decodes a JSON string.
Definition: FormatJson.php:174
ServiceOptions $options
$pluralRules
Associative array of cached plural rules.
static $preloadedKeys
Keys which are loaded automatically by initLanguage()
loadPluralFile( $fileName)
Load a plural XML file with the given filename, compile the relevant rules, and save the compiled rul...
__construct(ServiceOptions $options, LCStore $store, LoggerInterface $logger, array $clearStoreCallbacks, LanguageNameUtils $langNameUtils)
For constructor parameters, see the documentation in DefaultSettings.php for $wgLocalisationCacheConf...
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not null
Definition: hooks.txt:773
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not it can be in the form of< username >< more info > e g for bot passwords intended to be added to log contexts Fields it might only if the login was with a bot password 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:773
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:9
$data
The cache data.
getPluralRules( $code)
Get the plural rules for a given language from the XML files.
initShallowFallback( $primaryCode, $fallbackCode)
Create a fallback from one language to another, without creating a complete persistent cache...
assertRequiredOptions(array $expectedKeys)
Assert that the list of options provided in this instance exactly match $expectedKeys, without regard for order.
static $mergeableListKeys
Keys for items which are a numbered array.
loadPluralFiles()
Load the plural XML files.
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:35
Interface for the persistence layer of LocalisationCache.
Definition: LCStore.php:38
Using a hook running we can avoid having all this option specific stuff in our mainline code Using the function We ve cleaned up the code here by removing clumps of infrequently used code and moving them off somewhere else It s much easier for someone working with this code to see what s _really_ going and make changes or fix bugs In we can take all the code that deals with the little used title reversing options(say) and put it in one place. Instead of having little title-reversing if-blocks spread all over the codebase in showAnArticle
getPluralRuleTypes( $code)
Get the plural rule types for a given language from the XML files.
static $magicWordKeys
Keys for items that are formatted like $magicWords.
$loadedItems
A 2-d associative array, code/key, where presence indicates that the item is loaded.
static $splitKeys
Keys for items where the subitems are stored in the backend separately.
getCompiledPluralRules( $code)
Get the compiled plural rules for a given language from the XML files.
$loadedSubitems
A 3-d associative array, code/key/subkey, where presence indicates that the subitem is loaded...
Null store backend, used to avoid DB errors during install.
Definition: LCStoreNull.php:24
static array $constructorOptions
readPHPFile( $_fileName, $_fileType)
Read a PHP file containing localisation data.
static $mergeableMapKeys
Keys for items which consist of associative arrays, which may be merged by a fallback sequence...
$pluralRuleTypes
Associative array of cached plural rule types.
loadSubitem( $code, $key, $subkey)
Load a subitem into the cache.
A service that provides utilities to do with language names and codes.
loadItem( $code, $key)
Load an item into the cache.
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
isMergeableKey( $key)
Returns true if the given key is mergeable, that is, if it is an associative array which can be merge...
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not it can be in the form of< username >< more info > e g for bot passwords intended to be added to log contexts Fields it might include
Definition: hooks.txt:773
isExpired( $code)
Returns true if the cache identified by $code is missing or expired.