MediaWiki  1.32.0
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 ) {
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
450  $this->initShallowFallback( $code, 'en' );
451 
452  return;
453  }
454 
455  # Recache the data if necessary
456  if ( !$this->manualRecache && $this->isExpired( $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 ) ) {
540  $data['aliases'] = $aliases;
541  }
542  } else {
543  throw new MWException( __METHOD__ . ": Invalid file type: $_fileType" );
544  }
545 
546  return $data;
547  }
548 
555  public function readJSONFile( $fileName ) {
556  if ( !is_readable( $fileName ) ) {
557  return [];
558  }
559 
560  $json = file_get_contents( $fileName );
561  if ( $json === false ) {
562  return [];
563  }
564 
565  $data = FormatJson::decode( $json, true );
566  if ( $data === null ) {
567  throw new MWException( __METHOD__ . ": Invalid JSON file: $fileName" );
568  }
569 
570  // Remove keys starting with '@', they're reserved for metadata and non-message data
571  foreach ( $data as $key => $unused ) {
572  if ( $key === '' || $key[0] === '@' ) {
573  unset( $data[$key] );
574  }
575  }
576 
577  // The JSON format only supports messages, none of the other variables, so wrap the data
578  return [ 'messages' => $data ];
579  }
580 
587  public function getCompiledPluralRules( $code ) {
588  $rules = $this->getPluralRules( $code );
589  if ( $rules === null ) {
590  return null;
591  }
592  try {
593  $compiledRules = Evaluator::compile( $rules );
594  } catch ( CLDRPluralRuleError $e ) {
595  wfDebugLog( 'l10n', $e->getMessage() );
596 
597  return [];
598  }
599 
600  return $compiledRules;
601  }
602 
610  public function getPluralRules( $code ) {
611  if ( $this->pluralRules === null ) {
612  $this->loadPluralFiles();
613  }
614  return $this->pluralRules[$code] ?? null;
615  }
616 
624  public function getPluralRuleTypes( $code ) {
625  if ( $this->pluralRuleTypes === null ) {
626  $this->loadPluralFiles();
627  }
628  return $this->pluralRuleTypes[$code] ?? null;
629  }
630 
634  protected function loadPluralFiles() {
635  global $IP;
636  $cldrPlural = "$IP/languages/data/plurals.xml";
637  $mwPlural = "$IP/languages/data/plurals-mediawiki.xml";
638  // Load CLDR plural rules
639  $this->loadPluralFile( $cldrPlural );
640  if ( file_exists( $mwPlural ) ) {
641  // Override or extend
642  $this->loadPluralFile( $mwPlural );
643  }
644  }
645 
653  protected function loadPluralFile( $fileName ) {
654  // Use file_get_contents instead of DOMDocument::load (T58439)
655  $xml = file_get_contents( $fileName );
656  if ( !$xml ) {
657  throw new MWException( "Unable to read plurals file $fileName" );
658  }
659  $doc = new DOMDocument;
660  $doc->loadXML( $xml );
661  $rulesets = $doc->getElementsByTagName( "pluralRules" );
662  foreach ( $rulesets as $ruleset ) {
663  $codes = $ruleset->getAttribute( 'locales' );
664  $rules = [];
665  $ruleTypes = [];
666  $ruleElements = $ruleset->getElementsByTagName( "pluralRule" );
667  foreach ( $ruleElements as $elt ) {
668  $ruleType = $elt->getAttribute( 'count' );
669  if ( $ruleType === 'other' ) {
670  // Don't record "other" rules, which have an empty condition
671  continue;
672  }
673  $rules[] = $elt->nodeValue;
674  $ruleTypes[] = $ruleType;
675  }
676  foreach ( explode( ' ', $codes ) as $code ) {
677  $this->pluralRules[$code] = $rules;
678  $this->pluralRuleTypes[$code] = $ruleTypes;
679  }
680  }
681  }
682 
692  protected function readSourceFilesAndRegisterDeps( $code, &$deps ) {
693  global $IP;
694 
695  // This reads in the PHP i18n file with non-messages l10n data
696  $fileName = Language::getMessagesFileName( $code );
697  if ( !file_exists( $fileName ) ) {
698  $data = [];
699  } else {
700  $deps[] = new FileDependency( $fileName );
701  $data = $this->readPHPFile( $fileName, 'core' );
702  }
703 
704  # Load CLDR plural rules for JavaScript
705  $data['pluralRules'] = $this->getPluralRules( $code );
706  # And for PHP
707  $data['compiledPluralRules'] = $this->getCompiledPluralRules( $code );
708  # Load plural rule types
709  $data['pluralRuleTypes'] = $this->getPluralRuleTypes( $code );
710 
711  $deps['plurals'] = new FileDependency( "$IP/languages/data/plurals.xml" );
712  $deps['plurals-mw'] = new FileDependency( "$IP/languages/data/plurals-mediawiki.xml" );
713 
714  return $data;
715  }
716 
724  protected function mergeItem( $key, &$value, $fallbackValue ) {
725  if ( !is_null( $value ) ) {
726  if ( !is_null( $fallbackValue ) ) {
727  if ( in_array( $key, self::$mergeableMapKeys ) ) {
728  $value = $value + $fallbackValue;
729  } elseif ( in_array( $key, self::$mergeableListKeys ) ) {
730  $value = array_unique( array_merge( $fallbackValue, $value ) );
731  } elseif ( in_array( $key, self::$mergeableAliasListKeys ) ) {
732  $value = array_merge_recursive( $value, $fallbackValue );
733  } elseif ( in_array( $key, self::$optionalMergeKeys ) ) {
734  if ( !empty( $value['inherit'] ) ) {
735  $value = array_merge( $fallbackValue, $value );
736  }
737 
738  if ( isset( $value['inherit'] ) ) {
739  unset( $value['inherit'] );
740  }
741  } elseif ( in_array( $key, self::$magicWordKeys ) ) {
742  $this->mergeMagicWords( $value, $fallbackValue );
743  }
744  }
745  } else {
746  $value = $fallbackValue;
747  }
748  }
749 
754  protected function mergeMagicWords( &$value, $fallbackValue ) {
755  foreach ( $fallbackValue as $magicName => $fallbackInfo ) {
756  if ( !isset( $value[$magicName] ) ) {
757  $value[$magicName] = $fallbackInfo;
758  } else {
759  $oldSynonyms = array_slice( $fallbackInfo, 1 );
760  $newSynonyms = array_slice( $value[$magicName], 1 );
761  $synonyms = array_values( array_unique( array_merge(
762  $newSynonyms, $oldSynonyms ) ) );
763  $value[$magicName] = array_merge( [ $fallbackInfo[0] ], $synonyms );
764  }
765  }
766  }
767 
781  protected function mergeExtensionItem( $codeSequence, $key, &$value, $fallbackValue ) {
782  $used = false;
783  foreach ( $codeSequence as $code ) {
784  if ( isset( $fallbackValue[$code] ) ) {
785  $this->mergeItem( $key, $value, $fallbackValue[$code] );
786  $used = true;
787  }
788  }
789 
790  return $used;
791  }
792 
800  public function getMessagesDirs() {
801  global $IP;
802 
803  $config = MediaWikiServices::getInstance()->getMainConfig();
804  $messagesDirs = $config->get( 'MessagesDirs' );
805  return [
806  'core' => "$IP/languages/i18n",
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  $blobStore->clear();
1038  }
1039  }
1040 
1049  protected function buildPreload( $data ) {
1050  $preload = [ 'messages' => [] ];
1051  foreach ( self::$preloadedKeys as $key ) {
1052  $preload[$key] = $data[$key];
1053  }
1054 
1055  foreach ( $data['preloadedMessages'] as $subkey ) {
1056  $subitem = $data['messages'][$subkey] ?? null;
1057  $preload['messages'][$subkey] = $subitem;
1058  }
1059 
1060  return $preload;
1061  }
1062 
1068  public function unload( $code ) {
1069  unset( $this->data[$code] );
1070  unset( $this->loadedItems[$code] );
1071  unset( $this->loadedSubitems[$code] );
1072  unset( $this->initialisedLangs[$code] );
1073  unset( $this->shallowFallbacks[$code] );
1074 
1075  foreach ( $this->shallowFallbacks as $shallowCode => $fbCode ) {
1076  if ( $fbCode === $code ) {
1077  $this->unload( $shallowCode );
1078  }
1079  }
1080  }
1081 
1085  public function unloadAll() {
1086  foreach ( $this->initialisedLangs as $lang => $unused ) {
1087  $this->unload( $lang );
1088  }
1089  }
1090 
1094  public function disableBackend() {
1095  $this->store = new LCStoreNull;
1096  $this->manualRecache = false;
1097  }
1098 
1099 }
FileDependency
Definition: CacheDependency.php:152
LocalisationCache\loadSubitem
loadSubitem( $code, $key, $subkey)
Load a subitem into the cache.
Definition: LocalisationCache.php:367
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:441
LocalisationCache\$conf
$conf
Configuration associative array.
Definition: LocalisationCache.php:43
LocalisationCache\__construct
__construct( $conf)
For constructor parameters, see the documentation in DefaultSettings.php for $wgLocalisationCacheConf...
Definition: LocalisationCache.php:191
$lang
if(!isset( $args[0])) $lang
Definition: testCompression.php:33
captcha-old.count
count
Definition: captcha-old.py:249
LocalisationCache\isExpired
isExpired( $code)
Returns true if the cache identified by $code is missing or expired.
Definition: LocalisationCache.php:403
LocalisationCache\getSubitemList
getSubitemList( $code, $key)
Get the list of subitem keys for a given item.
Definition: LocalisationCache.php:312
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:2317
LCStore
Interface for the persistence layer of LocalisationCache.
Definition: LCStore.php:38
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:6
Language\getMessagesFileName
static getMessagesFileName( $code)
Definition: Language.php:4556
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:692
LocalisationCache\getItem
getItem( $code, $key)
Get a cache item.
Definition: LocalisationCache.php:271
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:1082
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:35
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:818
LocalisationCache\readJSONFile
readJSONFile( $fileName)
Read a JSON file containing localisation messages.
Definition: LocalisationCache.php:555
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:781
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:610
FormatJson\decode
static decode( $value, $assoc=false)
Decodes a JSON string.
Definition: FormatJson.php:164
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:1068
LocalisationCache\getMessagesDirs
getMessagesDirs()
Gets the combined list of messages dirs from core and extensions.
Definition: LocalisationCache.php:800
LocalisationCache\mergeMagicWords
mergeMagicWords(&$value, $fallbackValue)
Definition: LocalisationCache.php:754
$IP
$IP
Definition: update.php:3
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:1
LocalisationCache\disableBackend
disableBackend()
Disable the storage backend.
Definition: LocalisationCache.php:1094
use
as see the revision history and available at free of to any person obtaining a copy of this software and associated documentation to deal in the Software without including without limitation the rights to use
Definition: MIT-LICENSE.txt:10
$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:813
LocalisationCache\getCompiledPluralRules
getCompiledPluralRules( $code)
Get the compiled plural rules for a given language from the XML files.
Definition: LocalisationCache.php:587
$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
array
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))
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:988
LocalisationCache\readPHPFile
readPHPFile( $_fileName, $_fileType)
Read a PHP file containing localisation data.
Definition: LocalisationCache.php:516
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:411
LocalisationCache\$mergeableMapKeys
static $mergeableMapKeys
Keys for items which consist of associative arrays, which may be merged by a fallback sequence.
Definition: LocalisationCache.php:125
$e
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:2213
LocalisationCache\buildPreload
buildPreload( $data)
Build the preload item from the given pre-cache data.
Definition: LocalisationCache.php:1049
LocalisationCache\$mergeableKeys
$mergeableKeys
Definition: LocalisationCache.php:182
$value
$value
Definition: styleTest.css.php:49
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:653
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:330
CacheDependency
Definition: CacheDependency.php:136
LocalisationCache\$pluralRuleTypes
$pluralRuleTypes
Associative array of cached plural rule types.
Definition: LocalisationCache.php:180
LocalisationCache\initShallowFallback
initShallowFallback( $primaryCode, $fallbackCode)
Create a fallback from one language to another, without creating a complete persistent cache.
Definition: LocalisationCache.php:502
$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:9
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:248
LocalisationCache\mergeItem
mergeItem( $key, &$value, $fallbackValue)
Merge two localisation values, a primary and a fallback, overwriting the primary value in place.
Definition: LocalisationCache.php:724
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:1085
LocalisationCache\getPluralRuleTypes
getPluralRuleTypes( $code)
Get the plural rule types for a given language from the XML files.
Definition: LocalisationCache.php:624
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:52
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:23
Language\isSupportedLanguage
static isSupportedLanguage( $code)
Checks whether any localisation is available for that language tag in MediaWiki (MessagesXx....
Definition: Language.php:304
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:290
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:200
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:634
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
LocalisationCache\$store
LCStore $store
The persistent store object.
Definition: LocalisationCache.php:70