MediaWiki  1.30.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', 'fallback8bitEncoding', 'linkPrefixExtension',
113  'linkTrail', 'linkPrefixCharset', 'namespaceAliases',
114  'dateFormats', 'datePreferences', 'datePreferenceMigrationMap',
115  'defaultDateFormat', 'extraUserToggles', 'specialPageAliases',
116  'imageFiles', 'preloadedMessages', 'namespaceGenderAliases',
117  'digitGroupingPattern', 'pluralRules', 'pluralRuleTypes', 'compiledPluralRules',
118  ];
119 
124  static public $mergeableMapKeys = [ 'messages', 'namespaceNames',
125  'namespaceAliases', 'dateFormats', 'imageFiles', 'preloadedMessages'
126  ];
127 
131  static public $mergeableListKeys = [ 'extraUserToggles' ];
132 
137  static public $mergeableAliasListKeys = [ 'specialPageAliases' ];
138 
144  static public $optionalMergeKeys = [ 'bookstoreList' ];
145 
149  static public $magicWordKeys = [ 'magicWords' ];
150 
154  static public $splitKeys = [ 'messages' ];
155 
159  static public $preloadedKeys = [ 'dateFormats', 'namespaceNames' ];
160 
165  private $pluralRules = null;
166 
179  private $pluralRuleTypes = null;
180 
181  private $mergeableKeys = null;
182 
190  function __construct( $conf ) {
192 
193  $this->conf = $conf;
194  $storeConf = [];
195  if ( !empty( $conf['storeClass'] ) ) {
196  $storeClass = $conf['storeClass'];
197  } else {
198  switch ( $conf['store'] ) {
199  case 'files':
200  case 'file':
201  $storeClass = 'LCStoreCDB';
202  break;
203  case 'db':
204  $storeClass = 'LCStoreDB';
205  break;
206  case 'array':
207  $storeClass = 'LCStoreStaticArray';
208  break;
209  case 'detect':
210  if ( !empty( $conf['storeDirectory'] ) ) {
211  $storeClass = 'LCStoreCDB';
212  } elseif ( $wgCacheDirectory ) {
213  $storeConf['directory'] = $wgCacheDirectory;
214  $storeClass = 'LCStoreCDB';
215  } else {
216  $storeClass = 'LCStoreDB';
217  }
218  break;
219  default:
220  throw new MWException(
221  'Please set $wgLocalisationCacheConf[\'store\'] to something sensible.'
222  );
223  }
224  }
225 
226  wfDebugLog( 'caches', static::class . ": using store $storeClass" );
227  if ( !empty( $conf['storeDirectory'] ) ) {
228  $storeConf['directory'] = $conf['storeDirectory'];
229  }
230 
231  $this->store = new $storeClass( $storeConf );
232  foreach ( [ 'manualRecache', 'forceRecache' ] as $var ) {
233  if ( isset( $conf[$var] ) ) {
234  $this->$var = $conf[$var];
235  }
236  }
237  }
238 
245  public function isMergeableKey( $key ) {
246  if ( $this->mergeableKeys === null ) {
247  $this->mergeableKeys = array_flip( array_merge(
248  self::$mergeableMapKeys,
249  self::$mergeableListKeys,
250  self::$mergeableAliasListKeys,
251  self::$optionalMergeKeys,
252  self::$magicWordKeys
253  ) );
254  }
255 
256  return isset( $this->mergeableKeys[$key] );
257  }
258 
268  public function getItem( $code, $key ) {
269  if ( !isset( $this->loadedItems[$code][$key] ) ) {
270  $this->loadItem( $code, $key );
271  }
272 
273  if ( $key === 'fallback' && isset( $this->shallowFallbacks[$code] ) ) {
274  return $this->shallowFallbacks[$code];
275  }
276 
277  return $this->data[$code][$key];
278  }
279 
287  public function getSubitem( $code, $key, $subkey ) {
288  if ( !isset( $this->loadedSubitems[$code][$key][$subkey] ) &&
289  !isset( $this->loadedItems[$code][$key] )
290  ) {
291  $this->loadSubitem( $code, $key, $subkey );
292  }
293 
294  if ( isset( $this->data[$code][$key][$subkey] ) ) {
295  return $this->data[$code][$key][$subkey];
296  } else {
297  return null;
298  }
299  }
300 
313  public function getSubitemList( $code, $key ) {
314  if ( in_array( $key, self::$splitKeys ) ) {
315  return $this->getSubitem( $code, 'list', $key );
316  } else {
317  $item = $this->getItem( $code, $key );
318  if ( is_array( $item ) ) {
319  return array_keys( $item );
320  } else {
321  return false;
322  }
323  }
324  }
325 
331  protected function loadItem( $code, $key ) {
332  if ( !isset( $this->initialisedLangs[$code] ) ) {
333  $this->initLanguage( $code );
334  }
335 
336  // Check to see if initLanguage() loaded it for us
337  if ( isset( $this->loadedItems[$code][$key] ) ) {
338  return;
339  }
340 
341  if ( isset( $this->shallowFallbacks[$code] ) ) {
342  $this->loadItem( $this->shallowFallbacks[$code], $key );
343 
344  return;
345  }
346 
347  if ( in_array( $key, self::$splitKeys ) ) {
348  $subkeyList = $this->getSubitem( $code, 'list', $key );
349  foreach ( $subkeyList as $subkey ) {
350  if ( isset( $this->data[$code][$key][$subkey] ) ) {
351  continue;
352  }
353  $this->data[$code][$key][$subkey] = $this->getSubitem( $code, $key, $subkey );
354  }
355  } else {
356  $this->data[$code][$key] = $this->store->get( $code, $key );
357  }
358 
359  $this->loadedItems[$code][$key] = true;
360  }
361 
368  protected function loadSubitem( $code, $key, $subkey ) {
369  if ( !in_array( $key, self::$splitKeys ) ) {
370  $this->loadItem( $code, $key );
371 
372  return;
373  }
374 
375  if ( !isset( $this->initialisedLangs[$code] ) ) {
376  $this->initLanguage( $code );
377  }
378 
379  // Check to see if initLanguage() loaded it for us
380  if ( isset( $this->loadedItems[$code][$key] ) ||
381  isset( $this->loadedSubitems[$code][$key][$subkey] )
382  ) {
383  return;
384  }
385 
386  if ( isset( $this->shallowFallbacks[$code] ) ) {
387  $this->loadSubitem( $this->shallowFallbacks[$code], $key, $subkey );
388 
389  return;
390  }
391 
392  $value = $this->store->get( $code, "$key:$subkey" );
393  $this->data[$code][$key][$subkey] = $value;
394  $this->loadedSubitems[$code][$key][$subkey] = true;
395  }
396 
404  public function isExpired( $code ) {
405  if ( $this->forceRecache && !isset( $this->recachedLangs[$code] ) ) {
406  wfDebug( __METHOD__ . "($code): forced reload\n" );
407 
408  return true;
409  }
410 
411  $deps = $this->store->get( $code, 'deps' );
412  $keys = $this->store->get( $code, 'list' );
413  $preload = $this->store->get( $code, 'preload' );
414  // Different keys may expire separately for some stores
415  if ( $deps === null || $keys === null || $preload === null ) {
416  wfDebug( __METHOD__ . "($code): cache missing, need to make one\n" );
417 
418  return true;
419  }
420 
421  foreach ( $deps as $dep ) {
422  // Because we're unserializing stuff from cache, we
423  // could receive objects of classes that don't exist
424  // anymore (e.g. uninstalled extensions)
425  // When this happens, always expire the cache
426  if ( !$dep instanceof CacheDependency || $dep->isExpired() ) {
427  wfDebug( __METHOD__ . "($code): cache for $code expired due to " .
428  get_class( $dep ) . "\n" );
429 
430  return true;
431  }
432  }
433 
434  return false;
435  }
436 
442  protected function initLanguage( $code ) {
443  if ( isset( $this->initialisedLangs[$code] ) ) {
444  return;
445  }
446 
447  $this->initialisedLangs[$code] = true;
448 
449  # If the code is of the wrong form for a Messages*.php file, do a shallow fallback
451  $this->initShallowFallback( $code, 'en' );
452 
453  return;
454  }
455 
456  # Recache the data if necessary
457  if ( !$this->manualRecache && $this->isExpired( $code ) ) {
459  $this->recache( $code );
460  } elseif ( $code === 'en' ) {
461  throw new MWException( 'MessagesEn.php is missing.' );
462  } else {
463  $this->initShallowFallback( $code, 'en' );
464  }
465 
466  return;
467  }
468 
469  # Preload some stuff
470  $preload = $this->getItem( $code, 'preload' );
471  if ( $preload === null ) {
472  if ( $this->manualRecache ) {
473  // No Messages*.php file. Do shallow fallback to en.
474  if ( $code === 'en' ) {
475  throw new MWException( 'No localisation cache found for English. ' .
476  'Please run maintenance/rebuildLocalisationCache.php.' );
477  }
478  $this->initShallowFallback( $code, 'en' );
479 
480  return;
481  } else {
482  throw new MWException( 'Invalid or missing localisation cache.' );
483  }
484  }
485  $this->data[$code] = $preload;
486  foreach ( $preload as $key => $item ) {
487  if ( in_array( $key, self::$splitKeys ) ) {
488  foreach ( $item as $subkey => $subitem ) {
489  $this->loadedSubitems[$code][$key][$subkey] = true;
490  }
491  } else {
492  $this->loadedItems[$code][$key] = true;
493  }
494  }
495  }
496 
503  public function initShallowFallback( $primaryCode, $fallbackCode ) {
504  $this->data[$primaryCode] =& $this->data[$fallbackCode];
505  $this->loadedItems[$primaryCode] =& $this->loadedItems[$fallbackCode];
506  $this->loadedSubitems[$primaryCode] =& $this->loadedSubitems[$fallbackCode];
507  $this->shallowFallbacks[$primaryCode] = $fallbackCode;
508  }
509 
517  protected function readPHPFile( $_fileName, $_fileType ) {
518  // Disable APC caching
519  MediaWiki\suppressWarnings();
520  $_apcEnabled = ini_set( 'apc.cache_by_default', '0' );
521  MediaWiki\restoreWarnings();
522 
523  include $_fileName;
524 
525  MediaWiki\suppressWarnings();
526  ini_set( 'apc.cache_by_default', $_apcEnabled );
527  MediaWiki\restoreWarnings();
528 
529  if ( $_fileType == 'core' || $_fileType == 'extension' ) {
530  $data = compact( self::$allKeys );
531  } elseif ( $_fileType == 'aliases' ) {
532  $data = compact( 'aliases' );
533  } else {
534  throw new MWException( __METHOD__ . ": Invalid file type: $_fileType" );
535  }
536 
537  return $data;
538  }
539 
546  public function readJSONFile( $fileName ) {
547  if ( !is_readable( $fileName ) ) {
548  return [];
549  }
550 
551  $json = file_get_contents( $fileName );
552  if ( $json === false ) {
553  return [];
554  }
555 
556  $data = FormatJson::decode( $json, true );
557  if ( $data === null ) {
558  throw new MWException( __METHOD__ . ": Invalid JSON file: $fileName" );
559  }
560 
561  // Remove keys starting with '@', they're reserved for metadata and non-message data
562  foreach ( $data as $key => $unused ) {
563  if ( $key === '' || $key[0] === '@' ) {
564  unset( $data[$key] );
565  }
566  }
567 
568  // The JSON format only supports messages, none of the other variables, so wrap the data
569  return [ 'messages' => $data ];
570  }
571 
578  public function getCompiledPluralRules( $code ) {
579  $rules = $this->getPluralRules( $code );
580  if ( $rules === null ) {
581  return null;
582  }
583  try {
584  $compiledRules = Evaluator::compile( $rules );
585  } catch ( CLDRPluralRuleError $e ) {
586  wfDebugLog( 'l10n', $e->getMessage() );
587 
588  return [];
589  }
590 
591  return $compiledRules;
592  }
593 
601  public function getPluralRules( $code ) {
602  if ( $this->pluralRules === null ) {
603  $this->loadPluralFiles();
604  }
605  if ( !isset( $this->pluralRules[$code] ) ) {
606  return null;
607  } else {
608  return $this->pluralRules[$code];
609  }
610  }
611 
619  public function getPluralRuleTypes( $code ) {
620  if ( $this->pluralRuleTypes === null ) {
621  $this->loadPluralFiles();
622  }
623  if ( !isset( $this->pluralRuleTypes[$code] ) ) {
624  return null;
625  } else {
626  return $this->pluralRuleTypes[$code];
627  }
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  'api' => "$IP/includes/api/i18n",
807  'oojs-ui' => "$IP/resources/lib/oojs-ui/i18n",
808  ] + $messagesDirs;
809  }
810 
817  public function recache( $code ) {
819 
820  if ( !$code ) {
821  throw new MWException( "Invalid language code requested" );
822  }
823  $this->recachedLangs[$code] = true;
824 
825  # Initial values
826  $initialData = array_fill_keys( self::$allKeys, null );
827  $coreData = $initialData;
828  $deps = [];
829 
830  # Load the primary localisation from the source file
831  $data = $this->readSourceFilesAndRegisterDeps( $code, $deps );
832  if ( $data === false ) {
833  wfDebug( __METHOD__ . ": no localisation file for $code, using fallback to en\n" );
834  $coreData['fallback'] = 'en';
835  } else {
836  wfDebug( __METHOD__ . ": got localisation for $code from source\n" );
837 
838  # Merge primary localisation
839  foreach ( $data as $key => $value ) {
840  $this->mergeItem( $key, $coreData[$key], $value );
841  }
842  }
843 
844  # Fill in the fallback if it's not there already
845  if ( is_null( $coreData['fallback'] ) ) {
846  $coreData['fallback'] = $code === 'en' ? false : 'en';
847  }
848  if ( $coreData['fallback'] === false ) {
849  $coreData['fallbackSequence'] = [];
850  } else {
851  $coreData['fallbackSequence'] = array_map( 'trim', explode( ',', $coreData['fallback'] ) );
852  $len = count( $coreData['fallbackSequence'] );
853 
854  # Ensure that the sequence ends at en
855  if ( $coreData['fallbackSequence'][$len - 1] !== 'en' ) {
856  $coreData['fallbackSequence'][] = 'en';
857  }
858  }
859 
860  $codeSequence = array_merge( [ $code ], $coreData['fallbackSequence'] );
861  $messageDirs = $this->getMessagesDirs();
862 
863  # Load non-JSON localisation data for extensions
864  $extensionData = array_fill_keys( $codeSequence, $initialData );
865  foreach ( $wgExtensionMessagesFiles as $extension => $fileName ) {
866  if ( isset( $messageDirs[$extension] ) ) {
867  # This extension has JSON message data; skip the PHP shim
868  continue;
869  }
870 
871  $data = $this->readPHPFile( $fileName, 'extension' );
872  $used = false;
873 
874  foreach ( $data as $key => $item ) {
875  foreach ( $codeSequence as $csCode ) {
876  if ( isset( $item[$csCode] ) ) {
877  $this->mergeItem( $key, $extensionData[$csCode][$key], $item[$csCode] );
878  $used = true;
879  }
880  }
881  }
882 
883  if ( $used ) {
884  $deps[] = new FileDependency( $fileName );
885  }
886  }
887 
888  # Load the localisation data for each fallback, then merge it into the full array
889  $allData = $initialData;
890  foreach ( $codeSequence as $csCode ) {
891  $csData = $initialData;
892 
893  # Load core messages and the extension localisations.
894  foreach ( $messageDirs as $dirs ) {
895  foreach ( (array)$dirs as $dir ) {
896  $fileName = "$dir/$csCode.json";
897  $data = $this->readJSONFile( $fileName );
898 
899  foreach ( $data as $key => $item ) {
900  $this->mergeItem( $key, $csData[$key], $item );
901  }
902 
903  $deps[] = new FileDependency( $fileName );
904  }
905  }
906 
907  # Merge non-JSON extension data
908  if ( isset( $extensionData[$csCode] ) ) {
909  foreach ( $extensionData[$csCode] as $key => $item ) {
910  $this->mergeItem( $key, $csData[$key], $item );
911  }
912  }
913 
914  if ( $csCode === $code ) {
915  # Merge core data into extension data
916  foreach ( $coreData as $key => $item ) {
917  $this->mergeItem( $key, $csData[$key], $item );
918  }
919  } else {
920  # Load the secondary localisation from the source file to
921  # avoid infinite cycles on cyclic fallbacks
922  $fbData = $this->readSourceFilesAndRegisterDeps( $csCode, $deps );
923  if ( $fbData !== false ) {
924  # Only merge the keys that make sense to merge
925  foreach ( self::$allKeys as $key ) {
926  if ( !isset( $fbData[$key] ) ) {
927  continue;
928  }
929 
930  if ( is_null( $coreData[$key] ) || $this->isMergeableKey( $key ) ) {
931  $this->mergeItem( $key, $csData[$key], $fbData[$key] );
932  }
933  }
934  }
935  }
936 
937  # Allow extensions an opportunity to adjust the data for this
938  # fallback
939  Hooks::run( 'LocalisationCacheRecacheFallback', [ $this, $csCode, &$csData ] );
940 
941  # Merge the data for this fallback into the final array
942  if ( $csCode === $code ) {
943  $allData = $csData;
944  } else {
945  foreach ( self::$allKeys as $key ) {
946  if ( !isset( $csData[$key] ) ) {
947  continue;
948  }
949 
950  if ( is_null( $allData[$key] ) || $this->isMergeableKey( $key ) ) {
951  $this->mergeItem( $key, $allData[$key], $csData[$key] );
952  }
953  }
954  }
955  }
956 
957  # Add cache dependencies for any referenced globals
958  $deps['wgExtensionMessagesFiles'] = new GlobalDependency( 'wgExtensionMessagesFiles' );
959  // The 'MessagesDirs' config setting is used in LocalisationCache::getMessagesDirs().
960  // We use the key 'wgMessagesDirs' for historical reasons.
961  $deps['wgMessagesDirs'] = new MainConfigDependency( 'MessagesDirs' );
962  $deps['version'] = new ConstantDependency( 'LocalisationCache::VERSION' );
963 
964  # Add dependencies to the cache entry
965  $allData['deps'] = $deps;
966 
967  # Replace spaces with underscores in namespace names
968  $allData['namespaceNames'] = str_replace( ' ', '_', $allData['namespaceNames'] );
969 
970  # And do the same for special page aliases. $page is an array.
971  foreach ( $allData['specialPageAliases'] as &$page ) {
972  $page = str_replace( ' ', '_', $page );
973  }
974  # Decouple the reference to prevent accidental damage
975  unset( $page );
976 
977  # If there were no plural rules, return an empty array
978  if ( $allData['pluralRules'] === null ) {
979  $allData['pluralRules'] = [];
980  }
981  if ( $allData['compiledPluralRules'] === null ) {
982  $allData['compiledPluralRules'] = [];
983  }
984  # If there were no plural rule types, return an empty array
985  if ( $allData['pluralRuleTypes'] === null ) {
986  $allData['pluralRuleTypes'] = [];
987  }
988 
989  # Set the list keys
990  $allData['list'] = [];
991  foreach ( self::$splitKeys as $key ) {
992  $allData['list'][$key] = array_keys( $allData[$key] );
993  }
994  # Run hooks
995  $purgeBlobs = true;
996  Hooks::run( 'LocalisationCacheRecache', [ $this, $code, &$allData, &$purgeBlobs ] );
997 
998  if ( is_null( $allData['namespaceNames'] ) ) {
999  throw new MWException( __METHOD__ . ': Localisation data failed sanity check! ' .
1000  'Check that your languages/messages/MessagesEn.php file is intact.' );
1001  }
1002 
1003  # Set the preload key
1004  $allData['preload'] = $this->buildPreload( $allData );
1005 
1006  # Save to the process cache and register the items loaded
1007  $this->data[$code] = $allData;
1008  foreach ( $allData as $key => $item ) {
1009  $this->loadedItems[$code][$key] = true;
1010  }
1011 
1012  # Save to the persistent cache
1013  $this->store->startWrite( $code );
1014  foreach ( $allData as $key => $value ) {
1015  if ( in_array( $key, self::$splitKeys ) ) {
1016  foreach ( $value as $subkey => $subvalue ) {
1017  $this->store->set( "$key:$subkey", $subvalue );
1018  }
1019  } else {
1020  $this->store->set( $key, $value );
1021  }
1022  }
1023  $this->store->finishWrite();
1024 
1025  # Clear out the MessageBlobStore
1026  # HACK: If using a null (i.e. disabled) storage backend, we
1027  # can't write to the MessageBlobStore either
1028  if ( $purgeBlobs && !$this->store instanceof LCStoreNull ) {
1029  $blobStore = new MessageBlobStore();
1030  $blobStore->clear();
1031  }
1032  }
1033 
1042  protected function buildPreload( $data ) {
1043  $preload = [ 'messages' => [] ];
1044  foreach ( self::$preloadedKeys as $key ) {
1045  $preload[$key] = $data[$key];
1046  }
1047 
1048  foreach ( $data['preloadedMessages'] as $subkey ) {
1049  if ( isset( $data['messages'][$subkey] ) ) {
1050  $subitem = $data['messages'][$subkey];
1051  } else {
1052  $subitem = null;
1053  }
1054  $preload['messages'][$subkey] = $subitem;
1055  }
1056 
1057  return $preload;
1058  }
1059 
1065  public function unload( $code ) {
1066  unset( $this->data[$code] );
1067  unset( $this->loadedItems[$code] );
1068  unset( $this->loadedSubitems[$code] );
1069  unset( $this->initialisedLangs[$code] );
1070  unset( $this->shallowFallbacks[$code] );
1071 
1072  foreach ( $this->shallowFallbacks as $shallowCode => $fbCode ) {
1073  if ( $fbCode === $code ) {
1074  $this->unload( $shallowCode );
1075  }
1076  }
1077  }
1078 
1082  public function unloadAll() {
1083  foreach ( $this->initialisedLangs as $lang => $unused ) {
1084  $this->unload( $lang );
1085  }
1086  }
1087 
1091  public function disableBackend() {
1092  $this->store = new LCStoreNull;
1093  $this->manualRecache = false;
1094  }
1095 
1096 }
FileDependency
Definition: CacheDependency.php:153
LocalisationCache\loadSubitem
loadSubitem( $code, $key, $subkey)
Load a subitem into the cache.
Definition: LocalisationCache.php:368
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:442
LocalisationCache\$conf
$conf
Configuration associative array.
Definition: LocalisationCache.php:43
false
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:187
LocalisationCache\__construct
__construct( $conf)
For constructor parameters, see the documentation in DefaultSettings.php for $wgLocalisationCacheConf...
Definition: LocalisationCache.php:190
$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:404
LocalisationCache\getSubitemList
getSubitemList( $code, $key)
Get the list of subitem keys for a given item.
Definition: LocalisationCache.php:313
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
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:279
MainConfigDependency
Definition: CacheDependency.php:251
$wgCacheDirectory
$wgCacheDirectory
Directory for caching data in the local filesystem.
Definition: DefaultSettings.php:2227
LCStore
Interface for the persistence layer of LocalisationCache.
Definition: LCStore.php:38
Language\getMessagesFileName
static getMessagesFileName( $code)
Definition: Language.php:4377
LocalisationCache\$mergeableAliasListKeys
static $mergeableAliasListKeys
Keys for items which contain an array of arrays of equivalent aliases for each subitem.
Definition: LocalisationCache.php:137
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:691
LocalisationCache\getItem
getItem( $code, $key)
Get a cache item.
Definition: LocalisationCache.php:268
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:1140
GlobalDependency
Definition: CacheDependency.php:227
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:817
LocalisationCache\readJSONFile
readJSONFile( $fileName)
Read a JSON file containing localisation messages.
Definition: LocalisationCache.php:546
LocalisationCache\$pluralRules
$pluralRules
Associative array of cached plural rules.
Definition: LocalisationCache.php:165
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:780
LocalisationCache\$preloadedKeys
static $preloadedKeys
Keys which are loaded automatically by initLanguage()
Definition: LocalisationCache.php:159
LocalisationCache\getPluralRules
getPluralRules( $code)
Get the plural rules for a given language from the XML files.
Definition: LocalisationCache.php:601
FormatJson\decode
static decode( $value, $assoc=false)
Decodes a JSON string.
Definition: FormatJson.php:187
MWException
MediaWiki exception.
Definition: MWException.php:26
LocalisationCache\unload
unload( $code)
Unload the data for a given language from the object cache.
Definition: LocalisationCache.php:1065
LocalisationCache\getMessagesDirs
getMessagesDirs()
Gets the combined list of messages dirs from core and extensions.
Definition: LocalisationCache.php:799
LocalisationCache\mergeMagicWords
mergeMagicWords(&$value, $fallbackValue)
Definition: LocalisationCache.php:753
$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:1091
LocalisationCache\getCompiledPluralRules
getCompiledPluralRules( $code)
Get the compiled plural rules for a given language from the XML files.
Definition: LocalisationCache.php:578
$dirs
$dirs
Definition: mergeMessageFileList.php:195
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:144
LocalisationCache\$loadedItems
$loadedItems
A 2-d associative array, code/key, where presence indicates that the item is loaded.
Definition: LocalisationCache.php:79
global
when a variable name is used in a it is silently declared as a new masking the global
Definition: design.txt:93
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:1047
LocalisationCache\readPHPFile
readPHPFile( $_fileName, $_fileType)
Read a PHP file containing localisation data.
Definition: LocalisationCache.php:517
$dir
$dir
Definition: Autoload.php:8
Language\isValidBuiltInCode
static isValidBuiltInCode( $code)
Returns true if a language code is of a valid form for the purposes of internal customisation of Medi...
Definition: Language.php:363
LocalisationCache\$mergeableMapKeys
static $mergeableMapKeys
Keys for items which consist of associative arrays, which may be merged by a fallback sequence.
Definition: LocalisationCache.php:124
$e
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:2141
LocalisationCache\buildPreload
buildPreload( $data)
Build the preload item from the given pre-cache data.
Definition: LocalisationCache.php:1042
LocalisationCache\$mergeableKeys
$mergeableKeys
Definition: LocalisationCache.php:181
$value
$value
Definition: styleTest.css.php:45
LocalisationCache\loadPluralFile
loadPluralFile( $fileName)
Load a plural XML file with the given filename, compile the relevant rules, and save the compiled rul...
Definition: LocalisationCache.php:652
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:331
CacheDependency
Definition: CacheDependency.php:137
LocalisationCache\$pluralRuleTypes
$pluralRuleTypes
Associative array of cached plural rule types.
Definition: LocalisationCache.php:179
$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:781
LocalisationCache\initShallowFallback
initShallowFallback( $primaryCode, $fallbackCode)
Create a fallback from one language to another, without creating a complete persistent cache.
Definition: LocalisationCache.php:503
$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:245
LocalisationCache\mergeItem
mergeItem( $key, &$value, $fallbackValue)
Merge two localisation values, a primary and a fallback, overwriting the primary value in place.
Definition: LocalisationCache.php:723
MessageBlobStore
This class generates message blobs for use by ResourceLoader modules.
Definition: MessageBlobStore.php:37
$keys
$keys
Definition: testCompression.php:65
LocalisationCache\unloadAll
unloadAll()
Unload all data.
Definition: LocalisationCache.php:1082
LocalisationCache\getPluralRuleTypes
getPluralRuleTypes( $code)
Get the plural rule types for a given language from the XML files.
Definition: LocalisationCache.php:619
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:256
LocalisationCache\$splitKeys
static $splitKeys
Keys for items where the subitems are stored in the backend separately.
Definition: LocalisationCache.php:154
LocalisationCache\$mergeableListKeys
static $mergeableListKeys
Keys for items which are a numbered array.
Definition: LocalisationCache.php:131
LocalisationCache\getSubitem
getSubitem( $code, $key, $subkey)
Get a subitem, for instance a single message for a given language.
Definition: LocalisationCache.php:287
LocalisationCache\$shallowFallbacks
$shallowFallbacks
An array mapping non-existent pseudo-languages to fallback languages.
Definition: LocalisationCache.php:99
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:203
LocalisationCache\$loadedSubitems
$loadedSubitems
A 3-d associative array, code/key/subkey, where presence indicates that the subitem is loaded.
Definition: LocalisationCache.php:85
LocalisationCache\loadPluralFiles
loadPluralFiles()
Load the plural XML files.
Definition: LocalisationCache.php:633
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
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:149
array
the array() calling protocol came about after MediaWiki 1.4rc1.
LocalisationCache\$store
LCStore $store
The persistent store object.
Definition: LocalisationCache.php:70