MediaWiki  master
LocalisationCache.php
Go to the documentation of this file.
1 <?php
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  public static $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  public static $mergeableMapKeys = [ 'messages', 'namespaceNames',
126  'namespaceAliases', 'dateFormats', 'imageFiles', 'preloadedMessages'
127  ];
128 
132  public static $mergeableListKeys = [ 'extraUserToggles' ];
133 
138  public static $mergeableAliasListKeys = [ 'specialPageAliases' ];
139 
145  public static $optionalMergeKeys = [ 'bookstoreList' ];
146 
150  public static $magicWordKeys = [ 'magicWords' ];
151 
155  public static $splitKeys = [ 'messages' ];
156 
160  public static $preloadedKeys = [ 'dateFormats', 'namespaceNames' ];
161 
166  private $pluralRules = null;
167 
181 
182  private $mergeableKeys = null;
183 
191  function __construct( $conf ) {
192  global $wgCacheDirectory;
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  $storeConf['server'] = $conf['storeServer'] ?? [];
207  break;
208  case 'array':
209  $storeClass = LCStoreStaticArray::class;
210  break;
211  case 'detect':
212  if ( !empty( $conf['storeDirectory'] ) ) {
213  $storeClass = LCStoreCDB::class;
214  } elseif ( $wgCacheDirectory ) {
215  $storeConf['directory'] = $wgCacheDirectory;
216  $storeClass = LCStoreCDB::class;
217  } else {
218  $storeClass = LCStoreDB::class;
219  $storeConf['server'] = $conf['storeServer'] ?? [];
220  }
221  break;
222  default:
223  throw new MWException(
224  'Please set $wgLocalisationCacheConf[\'store\'] to something sensible.'
225  );
226  }
227  }
228 
229  wfDebugLog( 'caches', static::class . ": using store $storeClass" );
230  if ( !empty( $conf['storeDirectory'] ) ) {
231  $storeConf['directory'] = $conf['storeDirectory'];
232  }
233 
234  $this->store = new $storeClass( $storeConf );
235  foreach ( [ 'manualRecache', 'forceRecache' ] as $var ) {
236  if ( isset( $conf[$var] ) ) {
237  $this->$var = $conf[$var];
238  }
239  }
240  }
241 
248  public function isMergeableKey( $key ) {
249  if ( $this->mergeableKeys === null ) {
250  $this->mergeableKeys = array_flip( array_merge(
251  self::$mergeableMapKeys,
252  self::$mergeableListKeys,
253  self::$mergeableAliasListKeys,
254  self::$optionalMergeKeys,
255  self::$magicWordKeys
256  ) );
257  }
258 
259  return isset( $this->mergeableKeys[$key] );
260  }
261 
271  public function getItem( $code, $key ) {
272  if ( !isset( $this->loadedItems[$code][$key] ) ) {
273  $this->loadItem( $code, $key );
274  }
275 
276  if ( $key === 'fallback' && isset( $this->shallowFallbacks[$code] ) ) {
277  return $this->shallowFallbacks[$code];
278  }
279 
280  return $this->data[$code][$key];
281  }
282 
290  public function getSubitem( $code, $key, $subkey ) {
291  if ( !isset( $this->loadedSubitems[$code][$key][$subkey] ) &&
292  !isset( $this->loadedItems[$code][$key] )
293  ) {
294  $this->loadSubitem( $code, $key, $subkey );
295  }
296 
297  return $this->data[$code][$key][$subkey] ?? null;
298  }
299 
312  public function getSubitemList( $code, $key ) {
313  if ( in_array( $key, self::$splitKeys ) ) {
314  return $this->getSubitem( $code, 'list', $key );
315  } else {
316  $item = $this->getItem( $code, $key );
317  if ( is_array( $item ) ) {
318  return array_keys( $item );
319  } else {
320  return false;
321  }
322  }
323  }
324 
330  protected function loadItem( $code, $key ) {
331  if ( !isset( $this->initialisedLangs[$code] ) ) {
332  $this->initLanguage( $code );
333  }
334 
335  // Check to see if initLanguage() loaded it for us
336  if ( isset( $this->loadedItems[$code][$key] ) ) {
337  return;
338  }
339 
340  if ( isset( $this->shallowFallbacks[$code] ) ) {
341  $this->loadItem( $this->shallowFallbacks[$code], $key );
342 
343  return;
344  }
345 
346  if ( in_array( $key, self::$splitKeys ) ) {
347  $subkeyList = $this->getSubitem( $code, 'list', $key );
348  foreach ( $subkeyList as $subkey ) {
349  if ( isset( $this->data[$code][$key][$subkey] ) ) {
350  continue;
351  }
352  $this->data[$code][$key][$subkey] = $this->getSubitem( $code, $key, $subkey );
353  }
354  } else {
355  $this->data[$code][$key] = $this->store->get( $code, $key );
356  }
357 
358  $this->loadedItems[$code][$key] = true;
359  }
360 
367  protected function loadSubitem( $code, $key, $subkey ) {
368  if ( !in_array( $key, self::$splitKeys ) ) {
369  $this->loadItem( $code, $key );
370 
371  return;
372  }
373 
374  if ( !isset( $this->initialisedLangs[$code] ) ) {
375  $this->initLanguage( $code );
376  }
377 
378  // Check to see if initLanguage() loaded it for us
379  if ( isset( $this->loadedItems[$code][$key] ) ||
380  isset( $this->loadedSubitems[$code][$key][$subkey] )
381  ) {
382  return;
383  }
384 
385  if ( isset( $this->shallowFallbacks[$code] ) ) {
386  $this->loadSubitem( $this->shallowFallbacks[$code], $key, $subkey );
387 
388  return;
389  }
390 
391  $value = $this->store->get( $code, "$key:$subkey" );
392  $this->data[$code][$key][$subkey] = $value;
393  $this->loadedSubitems[$code][$key][$subkey] = true;
394  }
395 
403  public function isExpired( $code ) {
404  if ( $this->forceRecache && !isset( $this->recachedLangs[$code] ) ) {
405  wfDebug( __METHOD__ . "($code): forced reload\n" );
406 
407  return true;
408  }
409 
410  $deps = $this->store->get( $code, 'deps' );
411  $keys = $this->store->get( $code, 'list' );
412  $preload = $this->store->get( $code, 'preload' );
413  // Different keys may expire separately for some stores
414  if ( $deps === null || $keys === null || $preload === null ) {
415  wfDebug( __METHOD__ . "($code): cache missing, need to make one\n" );
416 
417  return true;
418  }
419 
420  foreach ( $deps as $dep ) {
421  // Because we're unserializing stuff from cache, we
422  // could receive objects of classes that don't exist
423  // anymore (e.g. uninstalled extensions)
424  // When this happens, always expire the cache
425  if ( !$dep instanceof CacheDependency || $dep->isExpired() ) {
426  wfDebug( __METHOD__ . "($code): cache for $code expired due to " .
427  get_class( $dep ) . "\n" );
428 
429  return true;
430  }
431  }
432 
433  return false;
434  }
435 
441  protected function initLanguage( $code ) {
442  if ( isset( $this->initialisedLangs[$code] ) ) {
443  return;
444  }
445 
446  $this->initialisedLangs[$code] = true;
447 
448  # If the code is of the wrong form for a Messages*.php file, do a shallow fallback
449  if ( !Language::isValidBuiltInCode( $code ) ) {
450  $this->initShallowFallback( $code, 'en' );
451 
452  return;
453  }
454 
455  # Recache the data if necessary
456  if ( !$this->manualRecache && $this->isExpired( $code ) ) {
457  if ( Language::isSupportedLanguage( $code ) ) {
458  $this->recache( $code );
459  } elseif ( $code === 'en' ) {
460  throw new MWException( 'MessagesEn.php is missing.' );
461  } else {
462  $this->initShallowFallback( $code, 'en' );
463  }
464 
465  return;
466  }
467 
468  # Preload some stuff
469  $preload = $this->getItem( $code, 'preload' );
470  if ( $preload === null ) {
471  if ( $this->manualRecache ) {
472  // No Messages*.php file. Do shallow fallback to en.
473  if ( $code === 'en' ) {
474  throw new MWException( 'No localisation cache found for English. ' .
475  'Please run maintenance/rebuildLocalisationCache.php.' );
476  }
477  $this->initShallowFallback( $code, 'en' );
478 
479  return;
480  } else {
481  throw new MWException( 'Invalid or missing localisation cache.' );
482  }
483  }
484  $this->data[$code] = $preload;
485  foreach ( $preload as $key => $item ) {
486  if ( in_array( $key, self::$splitKeys ) ) {
487  foreach ( $item as $subkey => $subitem ) {
488  $this->loadedSubitems[$code][$key][$subkey] = true;
489  }
490  } else {
491  $this->loadedItems[$code][$key] = true;
492  }
493  }
494  }
495 
502  public function initShallowFallback( $primaryCode, $fallbackCode ) {
503  $this->data[$primaryCode] =& $this->data[$fallbackCode];
504  $this->loadedItems[$primaryCode] =& $this->loadedItems[$fallbackCode];
505  $this->loadedSubitems[$primaryCode] =& $this->loadedSubitems[$fallbackCode];
506  $this->shallowFallbacks[$primaryCode] = $fallbackCode;
507  }
508 
516  protected function readPHPFile( $_fileName, $_fileType ) {
517  // Disable APC caching
518  Wikimedia\suppressWarnings();
519  $_apcEnabled = ini_set( 'apc.cache_by_default', '0' );
520  Wikimedia\restoreWarnings();
521 
522  include $_fileName;
523 
524  Wikimedia\suppressWarnings();
525  ini_set( 'apc.cache_by_default', $_apcEnabled );
526  Wikimedia\restoreWarnings();
527 
528  $data = [];
529  if ( $_fileType == 'core' || $_fileType == 'extension' ) {
530  foreach ( self::$allKeys as $key ) {
531  // Not all keys are set in language files, so
532  // check they exist first
533  if ( isset( $$key ) ) {
534  $data[$key] = $$key;
535  }
536  }
537  } elseif ( $_fileType == 'aliases' ) {
538  if ( isset( $aliases ) ) {
539  $data['aliases'] = $aliases;
540  }
541  } else {
542  throw new MWException( __METHOD__ . ": Invalid file type: $_fileType" );
543  }
544 
545  return $data;
546  }
547 
554  public function readJSONFile( $fileName ) {
555  if ( !is_readable( $fileName ) ) {
556  return [];
557  }
558 
559  $json = file_get_contents( $fileName );
560  if ( $json === false ) {
561  return [];
562  }
563 
564  $data = FormatJson::decode( $json, true );
565  if ( $data === null ) {
566  throw new MWException( __METHOD__ . ": Invalid JSON file: $fileName" );
567  }
568 
569  // Remove keys starting with '@', they're reserved for metadata and non-message data
570  foreach ( $data as $key => $unused ) {
571  if ( $key === '' || $key[0] === '@' ) {
572  unset( $data[$key] );
573  }
574  }
575 
576  // The JSON format only supports messages, none of the other variables, so wrap the data
577  return [ 'messages' => $data ];
578  }
579 
586  public function getCompiledPluralRules( $code ) {
587  $rules = $this->getPluralRules( $code );
588  if ( $rules === null ) {
589  return null;
590  }
591  try {
592  $compiledRules = Evaluator::compile( $rules );
593  } catch ( CLDRPluralRuleError $e ) {
594  wfDebugLog( 'l10n', $e->getMessage() );
595 
596  return [];
597  }
598 
599  return $compiledRules;
600  }
601 
609  public function getPluralRules( $code ) {
610  if ( $this->pluralRules === null ) {
611  $this->loadPluralFiles();
612  }
613  return $this->pluralRules[$code] ?? null;
614  }
615 
623  public function getPluralRuleTypes( $code ) {
624  if ( $this->pluralRuleTypes === null ) {
625  $this->loadPluralFiles();
626  }
627  return $this->pluralRuleTypes[$code] ?? null;
628  }
629 
633  protected function loadPluralFiles() {
634  global $IP;
635  $cldrPlural = "$IP/languages/data/plurals.xml";
636  $mwPlural = "$IP/languages/data/plurals-mediawiki.xml";
637  // Load CLDR plural rules
638  $this->loadPluralFile( $cldrPlural );
639  if ( file_exists( $mwPlural ) ) {
640  // Override or extend
641  $this->loadPluralFile( $mwPlural );
642  }
643  }
644 
652  protected function loadPluralFile( $fileName ) {
653  // Use file_get_contents instead of DOMDocument::load (T58439)
654  $xml = file_get_contents( $fileName );
655  if ( !$xml ) {
656  throw new MWException( "Unable to read plurals file $fileName" );
657  }
658  $doc = new DOMDocument;
659  $doc->loadXML( $xml );
660  $rulesets = $doc->getElementsByTagName( "pluralRules" );
661  foreach ( $rulesets as $ruleset ) {
662  $codes = $ruleset->getAttribute( 'locales' );
663  $rules = [];
664  $ruleTypes = [];
665  $ruleElements = $ruleset->getElementsByTagName( "pluralRule" );
666  foreach ( $ruleElements as $elt ) {
667  $ruleType = $elt->getAttribute( 'count' );
668  if ( $ruleType === 'other' ) {
669  // Don't record "other" rules, which have an empty condition
670  continue;
671  }
672  $rules[] = $elt->nodeValue;
673  $ruleTypes[] = $ruleType;
674  }
675  foreach ( explode( ' ', $codes ) as $code ) {
676  $this->pluralRules[$code] = $rules;
677  $this->pluralRuleTypes[$code] = $ruleTypes;
678  }
679  }
680  }
681 
691  protected function readSourceFilesAndRegisterDeps( $code, &$deps ) {
692  global $IP;
693 
694  // This reads in the PHP i18n file with non-messages l10n data
695  $fileName = Language::getMessagesFileName( $code );
696  if ( !file_exists( $fileName ) ) {
697  $data = [];
698  } else {
699  $deps[] = new FileDependency( $fileName );
700  $data = $this->readPHPFile( $fileName, 'core' );
701  }
702 
703  # Load CLDR plural rules for JavaScript
704  $data['pluralRules'] = $this->getPluralRules( $code );
705  # And for PHP
706  $data['compiledPluralRules'] = $this->getCompiledPluralRules( $code );
707  # Load plural rule types
708  $data['pluralRuleTypes'] = $this->getPluralRuleTypes( $code );
709 
710  $deps['plurals'] = new FileDependency( "$IP/languages/data/plurals.xml" );
711  $deps['plurals-mw'] = new FileDependency( "$IP/languages/data/plurals-mediawiki.xml" );
712 
713  return $data;
714  }
715 
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 
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( [ $fallbackInfo[0] ], $synonyms );
763  }
764  }
765  }
766 
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 
799  public function getMessagesDirs() {
800  global $IP;
801 
802  $config = MediaWikiServices::getInstance()->getMainConfig();
803  $messagesDirs = $config->get( 'MessagesDirs' );
804  return [
805  'core' => "$IP/languages/i18n",
806  'exif' => "$IP/languages/i18n/exif",
807  'api' => "$IP/includes/api/i18n",
808  'oojs-ui' => "$IP/resources/lib/ooui/i18n",
809  ] + $messagesDirs;
810  }
811 
818  public function recache( $code ) {
820 
821  if ( !$code ) {
822  throw new MWException( "Invalid language code requested" );
823  }
824  $this->recachedLangs[$code] = true;
825 
826  # Initial values
827  $initialData = array_fill_keys( self::$allKeys, null );
828  $coreData = $initialData;
829  $deps = [];
830 
831  # Load the primary localisation from the source file
832  $data = $this->readSourceFilesAndRegisterDeps( $code, $deps );
833  if ( $data === false ) {
834  wfDebug( __METHOD__ . ": no localisation file for $code, using fallback to en\n" );
835  $coreData['fallback'] = 'en';
836  } else {
837  wfDebug( __METHOD__ . ": got localisation for $code from source\n" );
838 
839  # Merge primary localisation
840  foreach ( $data as $key => $value ) {
841  $this->mergeItem( $key, $coreData[$key], $value );
842  }
843  }
844 
845  # Fill in the fallback if it's not there already
846  if ( ( is_null( $coreData['fallback'] ) || $coreData['fallback'] === false ) && $code === 'en' ) {
847  $coreData['fallback'] = false;
848  $coreData['originalFallbackSequence'] = $coreData['fallbackSequence'] = [];
849  } else {
850  if ( !is_null( $coreData['fallback'] ) ) {
851  $coreData['fallbackSequence'] = array_map( 'trim', explode( ',', $coreData['fallback'] ) );
852  } else {
853  $coreData['fallbackSequence'] = [];
854  }
855  $len = count( $coreData['fallbackSequence'] );
856 
857  # Before we add the 'en' fallback for messages, keep a copy of
858  # the original fallback sequence
859  $coreData['originalFallbackSequence'] = $coreData['fallbackSequence'];
860 
861  # Ensure that the sequence ends at 'en' for messages
862  if ( !$len || $coreData['fallbackSequence'][$len - 1] !== 'en' ) {
863  $coreData['fallbackSequence'][] = 'en';
864  }
865  }
866 
867  $codeSequence = array_merge( [ $code ], $coreData['fallbackSequence'] );
868  $messageDirs = $this->getMessagesDirs();
869 
870  # Load non-JSON localisation data for extensions
871  $extensionData = array_fill_keys( $codeSequence, $initialData );
872  foreach ( $wgExtensionMessagesFiles as $extension => $fileName ) {
873  if ( isset( $messageDirs[$extension] ) ) {
874  # This extension has JSON message data; skip the PHP shim
875  continue;
876  }
877 
878  $data = $this->readPHPFile( $fileName, 'extension' );
879  $used = false;
880 
881  foreach ( $data as $key => $item ) {
882  foreach ( $codeSequence as $csCode ) {
883  if ( isset( $item[$csCode] ) ) {
884  $this->mergeItem( $key, $extensionData[$csCode][$key], $item[$csCode] );
885  $used = true;
886  }
887  }
888  }
889 
890  if ( $used ) {
891  $deps[] = new FileDependency( $fileName );
892  }
893  }
894 
895  # Load the localisation data for each fallback, then merge it into the full array
896  $allData = $initialData;
897  foreach ( $codeSequence as $csCode ) {
898  $csData = $initialData;
899 
900  # Load core messages and the extension localisations.
901  foreach ( $messageDirs as $dirs ) {
902  foreach ( (array)$dirs as $dir ) {
903  $fileName = "$dir/$csCode.json";
904  $data = $this->readJSONFile( $fileName );
905 
906  foreach ( $data as $key => $item ) {
907  $this->mergeItem( $key, $csData[$key], $item );
908  }
909 
910  $deps[] = new FileDependency( $fileName );
911  }
912  }
913 
914  # Merge non-JSON extension data
915  if ( isset( $extensionData[$csCode] ) ) {
916  foreach ( $extensionData[$csCode] as $key => $item ) {
917  $this->mergeItem( $key, $csData[$key], $item );
918  }
919  }
920 
921  if ( $csCode === $code ) {
922  # Merge core data into extension data
923  foreach ( $coreData as $key => $item ) {
924  $this->mergeItem( $key, $csData[$key], $item );
925  }
926  } else {
927  # Load the secondary localisation from the source file to
928  # avoid infinite cycles on cyclic fallbacks
929  $fbData = $this->readSourceFilesAndRegisterDeps( $csCode, $deps );
930  if ( $fbData !== false ) {
931  # Only merge the keys that make sense to merge
932  foreach ( self::$allKeys as $key ) {
933  if ( !isset( $fbData[$key] ) ) {
934  continue;
935  }
936 
937  if ( is_null( $coreData[$key] ) || $this->isMergeableKey( $key ) ) {
938  $this->mergeItem( $key, $csData[$key], $fbData[$key] );
939  }
940  }
941  }
942  }
943 
944  # Allow extensions an opportunity to adjust the data for this
945  # fallback
946  Hooks::run( 'LocalisationCacheRecacheFallback', [ $this, $csCode, &$csData ] );
947 
948  # Merge the data for this fallback into the final array
949  if ( $csCode === $code ) {
950  $allData = $csData;
951  } else {
952  foreach ( self::$allKeys as $key ) {
953  if ( !isset( $csData[$key] ) ) {
954  continue;
955  }
956 
957  if ( is_null( $allData[$key] ) || $this->isMergeableKey( $key ) ) {
958  $this->mergeItem( $key, $allData[$key], $csData[$key] );
959  }
960  }
961  }
962  }
963 
964  # Add cache dependencies for any referenced globals
965  $deps['wgExtensionMessagesFiles'] = new GlobalDependency( 'wgExtensionMessagesFiles' );
966  // The 'MessagesDirs' config setting is used in LocalisationCache::getMessagesDirs().
967  // We use the key 'wgMessagesDirs' for historical reasons.
968  $deps['wgMessagesDirs'] = new MainConfigDependency( 'MessagesDirs' );
969  $deps['version'] = new ConstantDependency( 'LocalisationCache::VERSION' );
970 
971  # Add dependencies to the cache entry
972  $allData['deps'] = $deps;
973 
974  # Replace spaces with underscores in namespace names
975  $allData['namespaceNames'] = str_replace( ' ', '_', $allData['namespaceNames'] );
976 
977  # And do the same for special page aliases. $page is an array.
978  foreach ( $allData['specialPageAliases'] as &$page ) {
979  $page = str_replace( ' ', '_', $page );
980  }
981  # Decouple the reference to prevent accidental damage
982  unset( $page );
983 
984  # If there were no plural rules, return an empty array
985  if ( $allData['pluralRules'] === null ) {
986  $allData['pluralRules'] = [];
987  }
988  if ( $allData['compiledPluralRules'] === null ) {
989  $allData['compiledPluralRules'] = [];
990  }
991  # If there were no plural rule types, return an empty array
992  if ( $allData['pluralRuleTypes'] === null ) {
993  $allData['pluralRuleTypes'] = [];
994  }
995 
996  # Set the list keys
997  $allData['list'] = [];
998  foreach ( self::$splitKeys as $key ) {
999  $allData['list'][$key] = array_keys( $allData[$key] );
1000  }
1001  # Run hooks
1002  $purgeBlobs = true;
1003  Hooks::run( 'LocalisationCacheRecache', [ $this, $code, &$allData, &$purgeBlobs ] );
1004 
1005  if ( is_null( $allData['namespaceNames'] ) ) {
1006  throw new MWException( __METHOD__ . ': Localisation data failed sanity check! ' .
1007  'Check that your languages/messages/MessagesEn.php file is intact.' );
1008  }
1009 
1010  # Set the preload key
1011  $allData['preload'] = $this->buildPreload( $allData );
1012 
1013  # Save to the process cache and register the items loaded
1014  $this->data[$code] = $allData;
1015  foreach ( $allData as $key => $item ) {
1016  $this->loadedItems[$code][$key] = true;
1017  }
1018 
1019  # Save to the persistent cache
1020  $this->store->startWrite( $code );
1021  foreach ( $allData as $key => $value ) {
1022  if ( in_array( $key, self::$splitKeys ) ) {
1023  foreach ( $value as $subkey => $subvalue ) {
1024  $this->store->set( "$key:$subkey", $subvalue );
1025  }
1026  } else {
1027  $this->store->set( $key, $value );
1028  }
1029  }
1030  $this->store->finishWrite();
1031 
1032  # Clear out the MessageBlobStore
1033  # HACK: If using a null (i.e. disabled) storage backend, we
1034  # can't write to the MessageBlobStore either
1035  if ( $purgeBlobs && !$this->store instanceof LCStoreNull ) {
1036  $blobStore = new MessageBlobStore(
1037  MediaWikiServices::getInstance()->getResourceLoader()
1038  );
1039  $blobStore->clear();
1040  }
1041  }
1042 
1051  protected function buildPreload( $data ) {
1052  $preload = [ 'messages' => [] ];
1053  foreach ( self::$preloadedKeys as $key ) {
1054  $preload[$key] = $data[$key];
1055  }
1056 
1057  foreach ( $data['preloadedMessages'] as $subkey ) {
1058  $subitem = $data['messages'][$subkey] ?? null;
1059  $preload['messages'][$subkey] = $subitem;
1060  }
1061 
1062  return $preload;
1063  }
1064 
1070  public function unload( $code ) {
1071  unset( $this->data[$code] );
1072  unset( $this->loadedItems[$code] );
1073  unset( $this->loadedSubitems[$code] );
1074  unset( $this->initialisedLangs[$code] );
1075  unset( $this->shallowFallbacks[$code] );
1076 
1077  foreach ( $this->shallowFallbacks as $shallowCode => $fbCode ) {
1078  if ( $fbCode === $code ) {
1079  $this->unload( $shallowCode );
1080  }
1081  }
1082  }
1083 
1087  public function unloadAll() {
1088  foreach ( $this->initialisedLangs as $lang => $unused ) {
1089  $this->unload( $lang );
1090  }
1091  }
1092 
1096  public function disableBackend() {
1097  $this->store = new LCStoreNull;
1098  $this->manualRecache = false;
1099  }
1100 
1101 }
unloadAll()
Unload all data.
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
This class generates message blobs for use by ResourceLoader modules.
static $mergeableAliasListKeys
Keys for items which contain an array of arrays of equivalent aliases for each subitem.
$wgExtensionMessagesFiles['ExtensionNameMagic']
Definition: magicword.txt:43
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:2159
unload( $code)
Unload the data for a given language from the object cache.
LCStore $store
The persistent store object.
if(!isset( $args[0])) $lang
$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.
$value
$conf
Configuration associative array.
static $allKeys
All item keys.
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:23
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().
$wgCacheDirectory
Directory for caching data in the local filesystem.
disableBackend()
Disable the storage backend.
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:412
static $optionalMergeKeys
Keys for items which contain an associative array, and may be merged if the primary value contains th...
$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...
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
static decode( $value, $assoc=false)
Decodes a JSON string.
Definition: FormatJson.php:174
$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...
$forceRecache
True to treat all files as expired until they are regenerated by this object.
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:780
Class for caching the contents of localisation files, Messages*.php and *.i18n.php.
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:780
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.
static getMessagesFileName( $code)
Definition: Language.php:4478
initShallowFallback( $primaryCode, $fallbackCode)
Create a fallback from one language to another, without creating a complete persistent cache...
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
static isSupportedLanguage( $code)
Checks whether any localisation is available for that language tag in MediaWiki (MessagesXx.php exists).
Definition: Language.php:305
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.
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:52
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not...
$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
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.
__construct( $conf)
For constructor parameters, see the documentation in DefaultSettings.php for $wgLocalisationCacheConf...
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:780
isExpired( $code)
Returns true if the cache identified by $code is missing or expired.