MediaWiki  1.27.2
LocalisationCache.php
Go to the documentation of this file.
1 <?php
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 
191  function __construct( $conf ) {
193 
194  $this->conf = $conf;
195  $storeConf = [];
196  if ( !empty( $conf['storeClass'] ) ) {
197  $storeClass = $conf['storeClass'];
198  } else {
199  switch ( $conf['store'] ) {
200  case 'files':
201  case 'file':
202  $storeClass = 'LCStoreCDB';
203  break;
204  case 'db':
205  $storeClass = 'LCStoreDB';
206  break;
207  case 'array':
208  $storeClass = 'LCStoreStaticArray';
209  break;
210  case 'detect':
211  if ( !empty( $conf['storeDirectory'] ) ) {
212  $storeClass = 'LCStoreCDB';
213  } elseif ( $wgCacheDirectory ) {
214  $storeConf['directory'] = $wgCacheDirectory;
215  $storeClass = 'LCStoreCDB';
216  } else {
217  $storeClass = 'LCStoreDB';
218  }
219  break;
220  default:
221  throw new MWException(
222  'Please set $wgLocalisationCacheConf[\'store\'] to something sensible.'
223  );
224  }
225  }
226 
227  wfDebugLog( 'caches', get_class( $this ) . ": using store $storeClass" );
228  if ( !empty( $conf['storeDirectory'] ) ) {
229  $storeConf['directory'] = $conf['storeDirectory'];
230  }
231 
232  $this->store = new $storeClass( $storeConf );
233  foreach ( [ 'manualRecache', 'forceRecache' ] as $var ) {
234  if ( isset( $conf[$var] ) ) {
235  $this->$var = $conf[$var];
236  }
237  }
238  }
239 
246  public function isMergeableKey( $key ) {
247  if ( $this->mergeableKeys === null ) {
248  $this->mergeableKeys = array_flip( array_merge(
249  self::$mergeableMapKeys,
250  self::$mergeableListKeys,
251  self::$mergeableAliasListKeys,
252  self::$optionalMergeKeys,
253  self::$magicWordKeys
254  ) );
255  }
256 
257  return isset( $this->mergeableKeys[$key] );
258  }
259 
269  public function getItem( $code, $key ) {
270  if ( !isset( $this->loadedItems[$code][$key] ) ) {
271  $this->loadItem( $code, $key );
272  }
273 
274  if ( $key === 'fallback' && isset( $this->shallowFallbacks[$code] ) ) {
275  return $this->shallowFallbacks[$code];
276  }
277 
278  return $this->data[$code][$key];
279  }
280 
288  public function getSubitem( $code, $key, $subkey ) {
289  if ( !isset( $this->loadedSubitems[$code][$key][$subkey] ) &&
290  !isset( $this->loadedItems[$code][$key] )
291  ) {
292  $this->loadSubitem( $code, $key, $subkey );
293  }
294 
295  if ( isset( $this->data[$code][$key][$subkey] ) ) {
296  return $this->data[$code][$key][$subkey];
297  } else {
298  return null;
299  }
300  }
301 
314  public function getSubitemList( $code, $key ) {
315  if ( in_array( $key, self::$splitKeys ) ) {
316  return $this->getSubitem( $code, 'list', $key );
317  } else {
318  $item = $this->getItem( $code, $key );
319  if ( is_array( $item ) ) {
320  return array_keys( $item );
321  } else {
322  return false;
323  }
324  }
325  }
326 
332  protected function loadItem( $code, $key ) {
333  if ( !isset( $this->initialisedLangs[$code] ) ) {
334  $this->initLanguage( $code );
335  }
336 
337  // Check to see if initLanguage() loaded it for us
338  if ( isset( $this->loadedItems[$code][$key] ) ) {
339  return;
340  }
341 
342  if ( isset( $this->shallowFallbacks[$code] ) ) {
343  $this->loadItem( $this->shallowFallbacks[$code], $key );
344 
345  return;
346  }
347 
348  if ( in_array( $key, self::$splitKeys ) ) {
349  $subkeyList = $this->getSubitem( $code, 'list', $key );
350  foreach ( $subkeyList as $subkey ) {
351  if ( isset( $this->data[$code][$key][$subkey] ) ) {
352  continue;
353  }
354  $this->data[$code][$key][$subkey] = $this->getSubitem( $code, $key, $subkey );
355  }
356  } else {
357  $this->data[$code][$key] = $this->store->get( $code, $key );
358  }
359 
360  $this->loadedItems[$code][$key] = true;
361  }
362 
369  protected function loadSubitem( $code, $key, $subkey ) {
370  if ( !in_array( $key, self::$splitKeys ) ) {
371  $this->loadItem( $code, $key );
372 
373  return;
374  }
375 
376  if ( !isset( $this->initialisedLangs[$code] ) ) {
377  $this->initLanguage( $code );
378  }
379 
380  // Check to see if initLanguage() loaded it for us
381  if ( isset( $this->loadedItems[$code][$key] ) ||
382  isset( $this->loadedSubitems[$code][$key][$subkey] )
383  ) {
384  return;
385  }
386 
387  if ( isset( $this->shallowFallbacks[$code] ) ) {
388  $this->loadSubitem( $this->shallowFallbacks[$code], $key, $subkey );
389 
390  return;
391  }
392 
393  $value = $this->store->get( $code, "$key:$subkey" );
394  $this->data[$code][$key][$subkey] = $value;
395  $this->loadedSubitems[$code][$key][$subkey] = true;
396  }
397 
405  public function isExpired( $code ) {
406  if ( $this->forceRecache && !isset( $this->recachedLangs[$code] ) ) {
407  wfDebug( __METHOD__ . "($code): forced reload\n" );
408 
409  return true;
410  }
411 
412  $deps = $this->store->get( $code, 'deps' );
413  $keys = $this->store->get( $code, 'list' );
414  $preload = $this->store->get( $code, 'preload' );
415  // Different keys may expire separately for some stores
416  if ( $deps === null || $keys === null || $preload === null ) {
417  wfDebug( __METHOD__ . "($code): cache missing, need to make one\n" );
418 
419  return true;
420  }
421 
422  foreach ( $deps as $dep ) {
423  // Because we're unserializing stuff from cache, we
424  // could receive objects of classes that don't exist
425  // anymore (e.g. uninstalled extensions)
426  // When this happens, always expire the cache
427  if ( !$dep instanceof CacheDependency || $dep->isExpired() ) {
428  wfDebug( __METHOD__ . "($code): cache for $code expired due to " .
429  get_class( $dep ) . "\n" );
430 
431  return true;
432  }
433  }
434 
435  return false;
436  }
437 
443  protected function initLanguage( $code ) {
444  if ( isset( $this->initialisedLangs[$code] ) ) {
445  return;
446  }
447 
448  $this->initialisedLangs[$code] = true;
449 
450  # If the code is of the wrong form for a Messages*.php file, do a shallow fallback
451  if ( !Language::isValidBuiltInCode( $code ) ) {
452  $this->initShallowFallback( $code, 'en' );
453 
454  return;
455  }
456 
457  # Recache the data if necessary
458  if ( !$this->manualRecache && $this->isExpired( $code ) ) {
459  if ( Language::isSupportedLanguage( $code ) ) {
460  $this->recache( $code );
461  } elseif ( $code === 'en' ) {
462  throw new MWException( 'MessagesEn.php is missing.' );
463  } else {
464  $this->initShallowFallback( $code, 'en' );
465  }
466 
467  return;
468  }
469 
470  # Preload some stuff
471  $preload = $this->getItem( $code, 'preload' );
472  if ( $preload === null ) {
473  if ( $this->manualRecache ) {
474  // No Messages*.php file. Do shallow fallback to en.
475  if ( $code === 'en' ) {
476  throw new MWException( 'No localisation cache found for English. ' .
477  'Please run maintenance/rebuildLocalisationCache.php.' );
478  }
479  $this->initShallowFallback( $code, 'en' );
480 
481  return;
482  } else {
483  throw new MWException( 'Invalid or missing localisation cache.' );
484  }
485  }
486  $this->data[$code] = $preload;
487  foreach ( $preload as $key => $item ) {
488  if ( in_array( $key, self::$splitKeys ) ) {
489  foreach ( $item as $subkey => $subitem ) {
490  $this->loadedSubitems[$code][$key][$subkey] = true;
491  }
492  } else {
493  $this->loadedItems[$code][$key] = true;
494  }
495  }
496  }
497 
504  public function initShallowFallback( $primaryCode, $fallbackCode ) {
505  $this->data[$primaryCode] =& $this->data[$fallbackCode];
506  $this->loadedItems[$primaryCode] =& $this->loadedItems[$fallbackCode];
507  $this->loadedSubitems[$primaryCode] =& $this->loadedSubitems[$fallbackCode];
508  $this->shallowFallbacks[$primaryCode] = $fallbackCode;
509  }
510 
518  protected function readPHPFile( $_fileName, $_fileType ) {
519  // Disable APC caching
520  MediaWiki\suppressWarnings();
521  $_apcEnabled = ini_set( 'apc.cache_by_default', '0' );
522  MediaWiki\restoreWarnings();
523 
524  include $_fileName;
525 
526  MediaWiki\suppressWarnings();
527  ini_set( 'apc.cache_by_default', $_apcEnabled );
528  MediaWiki\restoreWarnings();
529 
530  if ( $_fileType == 'core' || $_fileType == 'extension' ) {
531  $data = compact( self::$allKeys );
532  } elseif ( $_fileType == 'aliases' ) {
533  $data = compact( 'aliases' );
534  } else {
535  throw new MWException( __METHOD__ . ": Invalid file type: $_fileType" );
536  }
537 
538  return $data;
539  }
540 
547  public function readJSONFile( $fileName ) {
548 
549  if ( !is_readable( $fileName ) ) {
550  return [];
551  }
552 
553  $json = file_get_contents( $fileName );
554  if ( $json === false ) {
555  return [];
556  }
557 
558  $data = FormatJson::decode( $json, true );
559  if ( $data === null ) {
560 
561  throw new MWException( __METHOD__ . ": Invalid JSON file: $fileName" );
562  }
563 
564  // Remove keys starting with '@', they're reserved for metadata and non-message data
565  foreach ( $data as $key => $unused ) {
566  if ( $key === '' || $key[0] === '@' ) {
567  unset( $data[$key] );
568  }
569  }
570 
571  // The JSON format only supports messages, none of the other variables, so wrap the data
572  return [ 'messages' => $data ];
573  }
574 
581  public function getCompiledPluralRules( $code ) {
582  $rules = $this->getPluralRules( $code );
583  if ( $rules === null ) {
584  return null;
585  }
586  try {
587  $compiledRules = Evaluator::compile( $rules );
588  } catch ( CLDRPluralRuleError $e ) {
589  wfDebugLog( 'l10n', $e->getMessage() );
590 
591  return [];
592  }
593 
594  return $compiledRules;
595  }
596 
604  public function getPluralRules( $code ) {
605  if ( $this->pluralRules === null ) {
606  $this->loadPluralFiles();
607  }
608  if ( !isset( $this->pluralRules[$code] ) ) {
609  return null;
610  } else {
611  return $this->pluralRules[$code];
612  }
613  }
614 
622  public function getPluralRuleTypes( $code ) {
623  if ( $this->pluralRuleTypes === null ) {
624  $this->loadPluralFiles();
625  }
626  if ( !isset( $this->pluralRuleTypes[$code] ) ) {
627  return null;
628  } else {
629  return $this->pluralRuleTypes[$code];
630  }
631  }
632 
636  protected function loadPluralFiles() {
637  global $IP;
638  $cldrPlural = "$IP/languages/data/plurals.xml";
639  $mwPlural = "$IP/languages/data/plurals-mediawiki.xml";
640  // Load CLDR plural rules
641  $this->loadPluralFile( $cldrPlural );
642  if ( file_exists( $mwPlural ) ) {
643  // Override or extend
644  $this->loadPluralFile( $mwPlural );
645  }
646  }
647 
655  protected function loadPluralFile( $fileName ) {
656  // Use file_get_contents instead of DOMDocument::load (T58439)
657  $xml = file_get_contents( $fileName );
658  if ( !$xml ) {
659  throw new MWException( "Unable to read plurals file $fileName" );
660  }
661  $doc = new DOMDocument;
662  $doc->loadXML( $xml );
663  $rulesets = $doc->getElementsByTagName( "pluralRules" );
664  foreach ( $rulesets as $ruleset ) {
665  $codes = $ruleset->getAttribute( 'locales' );
666  $rules = [];
667  $ruleTypes = [];
668  $ruleElements = $ruleset->getElementsByTagName( "pluralRule" );
669  foreach ( $ruleElements as $elt ) {
670  $ruleType = $elt->getAttribute( 'count' );
671  if ( $ruleType === 'other' ) {
672  // Don't record "other" rules, which have an empty condition
673  continue;
674  }
675  $rules[] = $elt->nodeValue;
676  $ruleTypes[] = $ruleType;
677  }
678  foreach ( explode( ' ', $codes ) as $code ) {
679  $this->pluralRules[$code] = $rules;
680  $this->pluralRuleTypes[$code] = $ruleTypes;
681  }
682  }
683  }
684 
694  protected function readSourceFilesAndRegisterDeps( $code, &$deps ) {
695  global $IP;
696 
697  // This reads in the PHP i18n file with non-messages l10n data
698  $fileName = Language::getMessagesFileName( $code );
699  if ( !file_exists( $fileName ) ) {
700  $data = [];
701  } else {
702  $deps[] = new FileDependency( $fileName );
703  $data = $this->readPHPFile( $fileName, 'core' );
704  }
705 
706  # Load CLDR plural rules for JavaScript
707  $data['pluralRules'] = $this->getPluralRules( $code );
708  # And for PHP
709  $data['compiledPluralRules'] = $this->getCompiledPluralRules( $code );
710  # Load plural rule types
711  $data['pluralRuleTypes'] = $this->getPluralRuleTypes( $code );
712 
713  $deps['plurals'] = new FileDependency( "$IP/languages/data/plurals.xml" );
714  $deps['plurals-mw'] = new FileDependency( "$IP/languages/data/plurals-mediawiki.xml" );
715 
716  return $data;
717  }
718 
726  protected function mergeItem( $key, &$value, $fallbackValue ) {
727  if ( !is_null( $value ) ) {
728  if ( !is_null( $fallbackValue ) ) {
729  if ( in_array( $key, self::$mergeableMapKeys ) ) {
730  $value = $value + $fallbackValue;
731  } elseif ( in_array( $key, self::$mergeableListKeys ) ) {
732  $value = array_unique( array_merge( $fallbackValue, $value ) );
733  } elseif ( in_array( $key, self::$mergeableAliasListKeys ) ) {
734  $value = array_merge_recursive( $value, $fallbackValue );
735  } elseif ( in_array( $key, self::$optionalMergeKeys ) ) {
736  if ( !empty( $value['inherit'] ) ) {
737  $value = array_merge( $fallbackValue, $value );
738  }
739 
740  if ( isset( $value['inherit'] ) ) {
741  unset( $value['inherit'] );
742  }
743  } elseif ( in_array( $key, self::$magicWordKeys ) ) {
744  $this->mergeMagicWords( $value, $fallbackValue );
745  }
746  }
747  } else {
748  $value = $fallbackValue;
749  }
750  }
751 
756  protected function mergeMagicWords( &$value, $fallbackValue ) {
757  foreach ( $fallbackValue as $magicName => $fallbackInfo ) {
758  if ( !isset( $value[$magicName] ) ) {
759  $value[$magicName] = $fallbackInfo;
760  } else {
761  $oldSynonyms = array_slice( $fallbackInfo, 1 );
762  $newSynonyms = array_slice( $value[$magicName], 1 );
763  $synonyms = array_values( array_unique( array_merge(
764  $newSynonyms, $oldSynonyms ) ) );
765  $value[$magicName] = array_merge( [ $fallbackInfo[0] ], $synonyms );
766  }
767  }
768  }
769 
783  protected function mergeExtensionItem( $codeSequence, $key, &$value, $fallbackValue ) {
784  $used = false;
785  foreach ( $codeSequence as $code ) {
786  if ( isset( $fallbackValue[$code] ) ) {
787  $this->mergeItem( $key, $value, $fallbackValue[$code] );
788  $used = true;
789  }
790  }
791 
792  return $used;
793  }
794 
802  public function getMessagesDirs() {
803  global $wgMessagesDirs, $IP;
804  return [
805  'core' => "$IP/languages/i18n",
806  'api' => "$IP/includes/api/i18n",
807  'oojs-ui' => "$IP/resources/lib/oojs-ui/i18n",
808  ] + $wgMessagesDirs;
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  // $wgMessagesDirs is used in LocalisationCache::getMessagesDirs()
960  $deps['wgMessagesDirs'] = new GlobalDependency( 'wgMessagesDirs' );
961  $deps['version'] = new ConstantDependency( 'LocalisationCache::VERSION' );
962 
963  # Add dependencies to the cache entry
964  $allData['deps'] = $deps;
965 
966  # Replace spaces with underscores in namespace names
967  $allData['namespaceNames'] = str_replace( ' ', '_', $allData['namespaceNames'] );
968 
969  # And do the same for special page aliases. $page is an array.
970  foreach ( $allData['specialPageAliases'] as &$page ) {
971  $page = str_replace( ' ', '_', $page );
972  }
973  # Decouple the reference to prevent accidental damage
974  unset( $page );
975 
976  # If there were no plural rules, return an empty array
977  if ( $allData['pluralRules'] === null ) {
978  $allData['pluralRules'] = [];
979  }
980  if ( $allData['compiledPluralRules'] === null ) {
981  $allData['compiledPluralRules'] = [];
982  }
983  # If there were no plural rule types, return an empty array
984  if ( $allData['pluralRuleTypes'] === null ) {
985  $allData['pluralRuleTypes'] = [];
986  }
987 
988  # Set the list keys
989  $allData['list'] = [];
990  foreach ( self::$splitKeys as $key ) {
991  $allData['list'][$key] = array_keys( $allData[$key] );
992  }
993  # Run hooks
994  $purgeBlobs = true;
995  Hooks::run( 'LocalisationCacheRecache', [ $this, $code, &$allData, &$purgeBlobs ] );
996 
997  if ( is_null( $allData['namespaceNames'] ) ) {
998  throw new MWException( __METHOD__ . ': Localisation data failed sanity check! ' .
999  'Check that your languages/messages/MessagesEn.php file is intact.' );
1000  }
1001 
1002  # Set the preload key
1003  $allData['preload'] = $this->buildPreload( $allData );
1004 
1005  # Save to the process cache and register the items loaded
1006  $this->data[$code] = $allData;
1007  foreach ( $allData as $key => $item ) {
1008  $this->loadedItems[$code][$key] = true;
1009  }
1010 
1011  # Save to the persistent cache
1012  $this->store->startWrite( $code );
1013  foreach ( $allData as $key => $value ) {
1014  if ( in_array( $key, self::$splitKeys ) ) {
1015  foreach ( $value as $subkey => $subvalue ) {
1016  $this->store->set( "$key:$subkey", $subvalue );
1017  }
1018  } else {
1019  $this->store->set( $key, $value );
1020  }
1021  }
1022  $this->store->finishWrite();
1023 
1024  # Clear out the MessageBlobStore
1025  # HACK: If using a null (i.e. disabled) storage backend, we
1026  # can't write to the MessageBlobStore either
1027  if ( $purgeBlobs && !$this->store instanceof LCStoreNull ) {
1028  $blobStore = new MessageBlobStore();
1029  $blobStore->clear();
1030  }
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 }
unloadAll()
Unload all data.
readJSONFile($fileName)
Read a JSON file containing localisation messages.
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.
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
the array() calling protocol came about after MediaWiki 1.4rc1.
readPHPFile($_fileName, $_fileType)
Read a PHP file containing localisation data.
magic word the default is to use $key to get the and $key value or $key value text $key value html to format the value $key
Definition: hooks.txt:2321
if(count($args)==0) $dir
readSourceFilesAndRegisterDeps($code, &$deps)
Read the data from the source files for a given language, and register the relevant dependencies in t...
$IP
Definition: WebStart.php:58
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:1932
unload($code)
Unload the data for a given language from the object cache.
loadItem($code, $key)
Load an item into the cache.
LCStore $store
The persistent store object.
if(!isset($args[0])) $lang
$shallowFallbacks
An array mapping non-existent pseudo-languages to fallback languages.
$initialisedLangs
An array where presence of a key indicates that that language has been initialised.
$value
$conf
Configuration associative array.
recache($code)
Load localisation data for a given language for both core and extensions and save it to the persisten...
static $allKeys
All item keys.
isExpired()
Returns true if the dependency is expired, false otherwise.
getMessagesDirs()
Gets the combined list of messages dirs from core and extensions.
__construct($conf)
Constructor.
when a variable name is used in a it is silently declared as a new local masking the global
Definition: design.txt:93
mergeMagicWords(&$value, $fallbackValue)
$manualRecache
True if recaching should only be done on an explicit call to recache().
loadSubitem($code, $key, $subkey)
Load a subitem into the cache.
getItem($code, $key)
Get a cache item.
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
wfDebug($text, $dest= 'all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
$wgCacheDirectory
Directory for caching data in the local filesystem.
disableBackend()
Disable the storage backend.
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.
wfDebugLog($logGroup, $text, $dest= 'all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not...
isExpired($code)
Returns true if the cache identified by $code is missing or expired.
mergeItem($key, &$value, $fallbackValue)
Merge two localisation values, a primary and a fallback, overwriting the primary value in place...
$pluralRules
Associative array of cached plural rules.
static getMessagesFileName($code)
Definition: Language.php:4279
static $preloadedKeys
Keys which are loaded automatically by initLanguage()
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:358
$forceRecache
True to treat all files as expired until they are regenerated by this object.
static run($event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:131
Class for caching the contents of localisation files, Messages*.php and *.i18n.php.
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.
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:762
static $mergeableListKeys
Keys for items which are a numbered array.
loadPluralFiles()
Load the plural XML files.
getSubitemList($code, $key)
Get the list of subitem keys for a given item.
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 $magicWordKeys
Keys for items that are formatted like $magicWords.
initLanguage($code)
Initialise a language in this object.
$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.
$loadedSubitems
A 3-d associative array, code/key/subkey, where presence indicates that the subitem is loaded...
getPluralRules($code)
Get the plural rules for a given language from the XML files.
Null store backend, used to avoid DB errors during install.
Definition: LCStoreNull.php:24
mergeExtensionItem($codeSequence, $key, &$value, $fallbackValue)
Given an array mapping language code to localisation value, such as is found in extension *...
static $mergeableMapKeys
Keys for items which consist of associative arrays, which may be merged by a fallback sequence...
buildPreload($data)
Build the preload item from the given pre-cache data.
getSubitem($code, $key, $subkey)
Get a subitem, for instance a single message for a given language.
$pluralRuleTypes
Associative array of cached plural rule types.
getCompiledPluralRules($code)
Get the compiled plural rules for a given language from the XML files.
static decode($value, $assoc=false)
Decodes a JSON string.
Definition: FormatJson.php:187
getPluralRuleTypes($code)
Get the plural rule types for a given language from the XML files.
$wgExtensionMessagesFiles['ExtensionNameMagic']
Definition: magicword.txt:43
static isSupportedLanguage($code)
Checks whether any localisation is available for that language tag in MediaWiki (MessagesXx.php exists).
Definition: Language.php:251
initShallowFallback($primaryCode, $fallbackCode)
Create a fallback from one language to another, without creating a complete persistent cache...
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached $page
Definition: hooks.txt:2338
isMergeableKey($key)
Returns true if the given key is mergeable, that is, if it is an associative array which can be merge...
loadPluralFile($fileName)
Load a plural XML file with the given filename, compile the relevant rules, and save the compiled rul...