MediaWiki  1.31.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 ) {
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  break;
207  case 'array':
208  $storeClass = LCStoreStaticArray::class;
209  break;
210  case 'detect':
211  if ( !empty( $conf['storeDirectory'] ) ) {
212  $storeClass = LCStoreCDB::class;
213  } elseif ( $wgCacheDirectory ) {
214  $storeConf['directory'] = $wgCacheDirectory;
215  $storeClass = LCStoreCDB::class;
216  } else {
217  $storeClass = LCStoreDB::class;
218  }
219  break;
220  default:
221  throw new MWException(
222  'Please set $wgLocalisationCacheConf[\'store\'] to something sensible.'
223  );
224  }
225  }
226 
227  wfDebugLog( 'caches', static::class . ": 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
452  $this->initShallowFallback( $code, 'en' );
453 
454  return;
455  }
456 
457  # Recache the data if necessary
458  if ( !$this->manualRecache && $this->isExpired( $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  Wikimedia\suppressWarnings();
521  $_apcEnabled = ini_set( 'apc.cache_by_default', '0' );
522  Wikimedia\restoreWarnings();
523 
524  include $_fileName;
525 
526  Wikimedia\suppressWarnings();
527  ini_set( 'apc.cache_by_default', $_apcEnabled );
528  Wikimedia\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  if ( !is_readable( $fileName ) ) {
549  return [];
550  }
551 
552  $json = file_get_contents( $fileName );
553  if ( $json === false ) {
554  return [];
555  }
556 
557  $data = FormatJson::decode( $json, true );
558  if ( $data === null ) {
559  throw new MWException( __METHOD__ . ": Invalid JSON file: $fileName" );
560  }
561 
562  // Remove keys starting with '@', they're reserved for metadata and non-message data
563  foreach ( $data as $key => $unused ) {
564  if ( $key === '' || $key[0] === '@' ) {
565  unset( $data[$key] );
566  }
567  }
568 
569  // The JSON format only supports messages, none of the other variables, so wrap the data
570  return [ 'messages' => $data ];
571  }
572 
579  public function getCompiledPluralRules( $code ) {
580  $rules = $this->getPluralRules( $code );
581  if ( $rules === null ) {
582  return null;
583  }
584  try {
585  $compiledRules = Evaluator::compile( $rules );
586  } catch ( CLDRPluralRuleError $e ) {
587  wfDebugLog( 'l10n', $e->getMessage() );
588 
589  return [];
590  }
591 
592  return $compiledRules;
593  }
594 
602  public function getPluralRules( $code ) {
603  if ( $this->pluralRules === null ) {
604  $this->loadPluralFiles();
605  }
606  if ( !isset( $this->pluralRules[$code] ) ) {
607  return null;
608  } else {
609  return $this->pluralRules[$code];
610  }
611  }
612 
620  public function getPluralRuleTypes( $code ) {
621  if ( $this->pluralRuleTypes === null ) {
622  $this->loadPluralFiles();
623  }
624  if ( !isset( $this->pluralRuleTypes[$code] ) ) {
625  return null;
626  } else {
627  return $this->pluralRuleTypes[$code];
628  }
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/oojs-ui/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'] ) ) {
847  $coreData['fallback'] = $code === 'en' ? false : 'en';
848  }
849  if ( $coreData['fallback'] === false ) {
850  $coreData['fallbackSequence'] = [];
851  } else {
852  $coreData['fallbackSequence'] = array_map( 'trim', explode( ',', $coreData['fallback'] ) );
853  $len = count( $coreData['fallbackSequence'] );
854 
855  # Ensure that the sequence ends at en
856  if ( $coreData['fallbackSequence'][$len - 1] !== 'en' ) {
857  $coreData['fallbackSequence'][] = 'en';
858  }
859  }
860 
861  $codeSequence = array_merge( [ $code ], $coreData['fallbackSequence'] );
862  $messageDirs = $this->getMessagesDirs();
863 
864  # Load non-JSON localisation data for extensions
865  $extensionData = array_fill_keys( $codeSequence, $initialData );
866  foreach ( $wgExtensionMessagesFiles as $extension => $fileName ) {
867  if ( isset( $messageDirs[$extension] ) ) {
868  # This extension has JSON message data; skip the PHP shim
869  continue;
870  }
871 
872  $data = $this->readPHPFile( $fileName, 'extension' );
873  $used = false;
874 
875  foreach ( $data as $key => $item ) {
876  foreach ( $codeSequence as $csCode ) {
877  if ( isset( $item[$csCode] ) ) {
878  $this->mergeItem( $key, $extensionData[$csCode][$key], $item[$csCode] );
879  $used = true;
880  }
881  }
882  }
883 
884  if ( $used ) {
885  $deps[] = new FileDependency( $fileName );
886  }
887  }
888 
889  # Load the localisation data for each fallback, then merge it into the full array
890  $allData = $initialData;
891  foreach ( $codeSequence as $csCode ) {
892  $csData = $initialData;
893 
894  # Load core messages and the extension localisations.
895  foreach ( $messageDirs as $dirs ) {
896  foreach ( (array)$dirs as $dir ) {
897  $fileName = "$dir/$csCode.json";
898  $data = $this->readJSONFile( $fileName );
899 
900  foreach ( $data as $key => $item ) {
901  $this->mergeItem( $key, $csData[$key], $item );
902  }
903 
904  $deps[] = new FileDependency( $fileName );
905  }
906  }
907 
908  # Merge non-JSON extension data
909  if ( isset( $extensionData[$csCode] ) ) {
910  foreach ( $extensionData[$csCode] as $key => $item ) {
911  $this->mergeItem( $key, $csData[$key], $item );
912  }
913  }
914 
915  if ( $csCode === $code ) {
916  # Merge core data into extension data
917  foreach ( $coreData as $key => $item ) {
918  $this->mergeItem( $key, $csData[$key], $item );
919  }
920  } else {
921  # Load the secondary localisation from the source file to
922  # avoid infinite cycles on cyclic fallbacks
923  $fbData = $this->readSourceFilesAndRegisterDeps( $csCode, $deps );
924  if ( $fbData !== false ) {
925  # Only merge the keys that make sense to merge
926  foreach ( self::$allKeys as $key ) {
927  if ( !isset( $fbData[$key] ) ) {
928  continue;
929  }
930 
931  if ( is_null( $coreData[$key] ) || $this->isMergeableKey( $key ) ) {
932  $this->mergeItem( $key, $csData[$key], $fbData[$key] );
933  }
934  }
935  }
936  }
937 
938  # Allow extensions an opportunity to adjust the data for this
939  # fallback
940  Hooks::run( 'LocalisationCacheRecacheFallback', [ $this, $csCode, &$csData ] );
941 
942  # Merge the data for this fallback into the final array
943  if ( $csCode === $code ) {
944  $allData = $csData;
945  } else {
946  foreach ( self::$allKeys as $key ) {
947  if ( !isset( $csData[$key] ) ) {
948  continue;
949  }
950 
951  if ( is_null( $allData[$key] ) || $this->isMergeableKey( $key ) ) {
952  $this->mergeItem( $key, $allData[$key], $csData[$key] );
953  }
954  }
955  }
956  }
957 
958  # Add cache dependencies for any referenced globals
959  $deps['wgExtensionMessagesFiles'] = new GlobalDependency( 'wgExtensionMessagesFiles' );
960  // The 'MessagesDirs' config setting is used in LocalisationCache::getMessagesDirs().
961  // We use the key 'wgMessagesDirs' for historical reasons.
962  $deps['wgMessagesDirs'] = new MainConfigDependency( 'MessagesDirs' );
963  $deps['version'] = new ConstantDependency( 'LocalisationCache::VERSION' );
964 
965  # Add dependencies to the cache entry
966  $allData['deps'] = $deps;
967 
968  # Replace spaces with underscores in namespace names
969  $allData['namespaceNames'] = str_replace( ' ', '_', $allData['namespaceNames'] );
970 
971  # And do the same for special page aliases. $page is an array.
972  foreach ( $allData['specialPageAliases'] as &$page ) {
973  $page = str_replace( ' ', '_', $page );
974  }
975  # Decouple the reference to prevent accidental damage
976  unset( $page );
977 
978  # If there were no plural rules, return an empty array
979  if ( $allData['pluralRules'] === null ) {
980  $allData['pluralRules'] = [];
981  }
982  if ( $allData['compiledPluralRules'] === null ) {
983  $allData['compiledPluralRules'] = [];
984  }
985  # If there were no plural rule types, return an empty array
986  if ( $allData['pluralRuleTypes'] === null ) {
987  $allData['pluralRuleTypes'] = [];
988  }
989 
990  # Set the list keys
991  $allData['list'] = [];
992  foreach ( self::$splitKeys as $key ) {
993  $allData['list'][$key] = array_keys( $allData[$key] );
994  }
995  # Run hooks
996  $purgeBlobs = true;
997  Hooks::run( 'LocalisationCacheRecache', [ $this, $code, &$allData, &$purgeBlobs ] );
998 
999  if ( is_null( $allData['namespaceNames'] ) ) {
1000  throw new MWException( __METHOD__ . ': Localisation data failed sanity check! ' .
1001  'Check that your languages/messages/MessagesEn.php file is intact.' );
1002  }
1003 
1004  # Set the preload key
1005  $allData['preload'] = $this->buildPreload( $allData );
1006 
1007  # Save to the process cache and register the items loaded
1008  $this->data[$code] = $allData;
1009  foreach ( $allData as $key => $item ) {
1010  $this->loadedItems[$code][$key] = true;
1011  }
1012 
1013  # Save to the persistent cache
1014  $this->store->startWrite( $code );
1015  foreach ( $allData as $key => $value ) {
1016  if ( in_array( $key, self::$splitKeys ) ) {
1017  foreach ( $value as $subkey => $subvalue ) {
1018  $this->store->set( "$key:$subkey", $subvalue );
1019  }
1020  } else {
1021  $this->store->set( $key, $value );
1022  }
1023  }
1024  $this->store->finishWrite();
1025 
1026  # Clear out the MessageBlobStore
1027  # HACK: If using a null (i.e. disabled) storage backend, we
1028  # can't write to the MessageBlobStore either
1029  if ( $purgeBlobs && !$this->store instanceof LCStoreNull ) {
1030  $blobStore = new MessageBlobStore();
1031  $blobStore->clear();
1032  }
1033  }
1034 
1043  protected function buildPreload( $data ) {
1044  $preload = [ 'messages' => [] ];
1045  foreach ( self::$preloadedKeys as $key ) {
1046  $preload[$key] = $data[$key];
1047  }
1048 
1049  foreach ( $data['preloadedMessages'] as $subkey ) {
1050  if ( isset( $data['messages'][$subkey] ) ) {
1051  $subitem = $data['messages'][$subkey];
1052  } else {
1053  $subitem = null;
1054  }
1055  $preload['messages'][$subkey] = $subitem;
1056  }
1057 
1058  return $preload;
1059  }
1060 
1066  public function unload( $code ) {
1067  unset( $this->data[$code] );
1068  unset( $this->loadedItems[$code] );
1069  unset( $this->loadedSubitems[$code] );
1070  unset( $this->initialisedLangs[$code] );
1071  unset( $this->shallowFallbacks[$code] );
1072 
1073  foreach ( $this->shallowFallbacks as $shallowCode => $fbCode ) {
1074  if ( $fbCode === $code ) {
1075  $this->unload( $shallowCode );
1076  }
1077  }
1078  }
1079 
1083  public function unloadAll() {
1084  foreach ( $this->initialisedLangs as $lang => $unused ) {
1085  $this->unload( $lang );
1086  }
1087  }
1088 
1092  public function disableBackend() {
1093  $this->store = new LCStoreNull;
1094  $this->manualRecache = false;
1095  }
1096 
1097 }
FileDependency
Definition: CacheDependency.php:152
LocalisationCache\loadSubitem
loadSubitem( $code, $key, $subkey)
Load a subitem into the cache.
Definition: LocalisationCache.php:369
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:443
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: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:405
LocalisationCache\getSubitemList
getSubitemList( $code, $key)
Get the list of subitem keys for a given item.
Definition: LocalisationCache.php:314
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:278
MainConfigDependency
Definition: CacheDependency.php:250
$wgCacheDirectory
$wgCacheDirectory
Directory for caching data in the local filesystem.
Definition: DefaultSettings.php:2231
LCStore
Interface for the persistence layer of LocalisationCache.
Definition: LCStore.php:38
Language\getMessagesFileName
static getMessagesFileName( $code)
Definition: Language.php:4490
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:269
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:1075
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:547
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:602
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:1066
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:1092
LocalisationCache\getCompiledPluralRules
getCompiledPluralRules( $code)
Get the compiled plural rules for a given language from the XML files.
Definition: LocalisationCache.php:579
$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
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:982
LocalisationCache\readPHPFile
readPHPFile( $_fileName, $_fileType)
Read a PHP file containing localisation data.
Definition: LocalisationCache.php:518
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: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:2163
LocalisationCache\buildPreload
buildPreload( $data)
Build the preload item from the given pre-cache data.
Definition: LocalisationCache.php:1043
LocalisationCache\$mergeableKeys
$mergeableKeys
Definition: LocalisationCache.php:182
$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: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:332
CacheDependency
Definition: CacheDependency.php:136
LocalisationCache\$pluralRuleTypes
$pluralRuleTypes
Associative array of cached plural rule types.
Definition: LocalisationCache.php:180
$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:783
LocalisationCache\initShallowFallback
initShallowFallback( $primaryCode, $fallbackCode)
Create a fallback from one language to another, without creating a complete persistent cache.
Definition: LocalisationCache.php:504
$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:246
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:1083
LocalisationCache\getPluralRuleTypes
getPluralRuleTypes( $code)
Get the plural rule types for a given language from the XML files.
Definition: LocalisationCache.php:620
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: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:288
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:634
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:150
array
the array() calling protocol came about after MediaWiki 1.4rc1.
LocalisationCache\$store
LCStore $store
The persistent store object.
Definition: LocalisationCache.php:70