38 use Psr\Log\LoggerAwareInterface;
39 use Psr\Log\LoggerInterface;
43 use Wikimedia\RequestTimeout\TimeoutException;
44 use Wikimedia\ScopedCallback;
50 define(
'MSG_CACHE_VERSION', 2 );
62 MainConfigNames::UseDatabaseMessages,
63 MainConfigNames::MaxMsgCacheEntrySize,
64 MainConfigNames::AdaptiveMessageCache,
65 MainConfigNames::UseXssLanguage,
66 MainConfigNames::RawHtmlMessages,
75 private const FOR_UPDATE = 1;
78 private const WAIT_SEC = 15;
80 private const LOCK_TTL = 30;
86 private const WAN_TTL = ExpirationAwareness::TTL_DAY;
103 private $systemMessageNames;
108 private $cacheVolatile = [];
117 private $maxEntrySize;
123 private $useXssLanguage;
126 private $rawHtmlMessages;
132 private $parserOptions;
134 private $parser =
null;
139 private $inParser =
false;
144 private $clusterCache;
150 private $contLangCode;
152 private $contLangConverter;
154 private $langFactory;
156 private $localisationCache;
158 private $languageNameUtils;
160 private $languageFallback;
164 private $parserFactory;
167 private $messageKeyOverrides;
176 $lckey = strtr( $key,
' ',
'_' );
177 if ( $lckey ===
'' ) {
182 if ( ord( $lckey ) < 128 ) {
183 $lckey[0] = strtolower( $lckey[0] );
185 $lckey = MediaWikiServices::getInstance()->getContentLanguage()->lcfirst( $lckey );
213 LoggerInterface $logger,
222 $this->wanCache = $wanCache;
223 $this->clusterCache = $clusterCache;
224 $this->srvCache = $serverCache;
225 $this->contLang = $contLang;
227 $this->contLangCode = $contLang->
getCode();
228 $this->logger = $logger;
229 $this->langFactory = $langFactory;
230 $this->localisationCache = $localisationCache;
231 $this->languageNameUtils = $languageNameUtils;
232 $this->languageFallback = $languageFallback;
233 $this->hookRunner =
new HookRunner( $hookContainer );
234 $this->parserFactory = $parserFactory;
237 $this->cache =
new MapCacheLRU( self::MAX_REQUEST_LANGUAGES );
240 $this->
disable = !$options->
get( MainConfigNames::UseDatabaseMessages );
241 $this->maxEntrySize = $options->
get( MainConfigNames::MaxMsgCacheEntrySize );
242 $this->adaptive = $options->
get( MainConfigNames::AdaptiveMessageCache );
243 $this->useXssLanguage = $options->
get( MainConfigNames::UseXssLanguage );
244 $this->rawHtmlMessages = $options->
get( MainConfigNames::RawHtmlMessages );
248 $this->logger = $logger;
256 private function getParserOptions() {
257 if ( !$this->parserOptions ) {
259 $user = $context->getUser();
260 if ( !$user->isSafeToLoad() ) {
265 $po->setAllowUnsafeRawHtml(
false );
273 $this->parserOptions->setAllowUnsafeRawHtml(
false );
276 return $this->parserOptions;
285 private function getLocalCache( $code ) {
286 $cacheKey = $this->srvCache->makeKey( __CLASS__, $code );
288 return $this->srvCache->get( $cacheKey );
297 private function saveToLocalCache( $code, $cache ) {
298 $cacheKey = $this->srvCache->makeKey( __CLASS__, $code );
299 $this->srvCache->set( $cacheKey, $cache );
322 private function load(
string $code, $mode =
null ) {
324 if ( $this->isLanguageLoaded( $code ) && $mode !== self::FOR_UPDATE ) {
330 static $shownDisabled =
false;
331 if ( !$shownDisabled ) {
332 $this->logger->debug( __METHOD__ .
': disabled' );
333 $shownDisabled =
true;
340 return $this->loadUnguarded( $code, $mode );
341 }
catch ( Throwable $e ) {
355 private function loadUnguarded( $code, $mode ) {
362 [ $hash, $hashVolatile ] = $this->getValidationHash( $code );
363 $this->cacheVolatile[$code] = $hashVolatile;
364 $volatilityOnlyStaleness =
false;
367 $cache = $this->getLocalCache( $code );
369 $where[] =
'local cache is empty';
370 } elseif ( !isset( $cache[
'HASH'] ) || $cache[
'HASH'] !== $hash ) {
371 $where[] =
'local cache has the wrong hash';
372 $staleCache = $cache;
373 } elseif ( $this->isCacheExpired( $cache ) ) {
374 $where[] =
'local cache is expired';
375 $staleCache = $cache;
376 } elseif ( $hashVolatile ) {
378 $where[] =
'local cache validation key is expired/volatile';
379 $staleCache = $cache;
380 $volatilityOnlyStaleness =
true;
382 $where[] =
'got from local cache';
383 $this->cache->set( $code, $cache );
389 $cacheKey = $this->clusterCache->makeKey(
'messages', $code );
390 for ( $failedAttempts = 0; $failedAttempts <= 1; $failedAttempts++ ) {
391 if ( $volatilityOnlyStaleness && $staleCache ) {
396 $where[] =
'global cache is presumed expired';
398 $cache = $this->clusterCache->get( $cacheKey );
400 $where[] =
'global cache is empty';
401 } elseif ( $this->isCacheExpired( $cache ) ) {
402 $where[] =
'global cache is expired';
403 $staleCache = $cache;
404 } elseif ( $hashVolatile ) {
406 $where[] =
'global cache is expired/volatile';
407 $staleCache = $cache;
409 $where[] =
'got from global cache';
410 $this->cache->set( $code, $cache );
411 $this->saveToCaches( $cache,
'local-only', $code );
420 $loadStatus = $this->loadFromDBWithMainLock( $code, $where, $mode );
421 if ( $loadStatus ===
true ) {
424 } elseif ( $staleCache ) {
426 $where[] =
'using stale cache';
427 $this->cache->set( $code, $staleCache );
430 } elseif ( $failedAttempts > 0 ) {
431 $where[] =
'failed to find cache after waiting';
436 } elseif ( $loadStatus ===
'cantacquire' ) {
439 $where[] =
'waiting for other thread to complete';
440 [ , $ioError ] = $this->getReentrantScopedLock( $code );
442 $where[] =
'failed waiting';
445 $success = $this->loadFromDBWithLocalLock( $code, $where, $mode );
456 $where[] =
'loading FAILED - cache is disabled';
458 $this->cache->set( $code, [] );
459 $this->logger->error( __METHOD__ .
": Failed to load $code" );
464 if ( !$this->isLanguageLoaded( $code ) ) {
465 throw new LogicException(
"Process cache for '$code' should be set by now." );
468 $info = implode(
', ', $where );
469 $this->logger->debug( __METHOD__ .
": Loading $code... $info" );
480 private function loadFromDBWithMainLock( $code, array &$where, $mode =
null ) {
483 $statusKey = $this->clusterCache->makeKey(
'messages', $code,
'status' );
484 $status = $this->clusterCache->get( $statusKey );
485 if ( $status ===
'error' ) {
486 $where[] =
"could not load; method is still globally disabled";
491 $where[] =
'loading from DB';
497 [ $scopedLock ] = $this->getReentrantScopedLock( $code, 0 );
498 if ( !$scopedLock ) {
499 $where[] =
'could not acquire main lock';
500 return 'cantacquire';
503 $cache = $this->loadFromDB( $code, $mode );
504 $this->cache->set( $code, $cache );
505 $saveSuccess = $this->saveToCaches( $cache,
'all', $code );
507 if ( !$saveSuccess ) {
522 $this->clusterCache->set( $statusKey,
'error', 60 * 5 );
523 $where[] =
'could not save cache, disabled globally for 5 minutes';
525 $where[] =
"could not save global cache";
538 private function loadFromDBWithLocalLock( $code, array &$where, $mode =
null ) {
540 $where[] =
'loading from DB using local lock';
542 $scopedLock = $this->srvCache->getScopedLock(
543 $this->srvCache->makeKey(
'messages', $code ),
549 $cache = $this->loadFromDB( $code, $mode );
550 $this->cache->set( $code, $cache );
551 $this->saveToCaches( $cache,
'local-only', $code );
567 private function loadFromDB( $code, $mode =
null ) {
573 if ( $this->adaptive && $code !== $this->contLangCode ) {
574 if ( !$this->cache->has( $this->contLangCode ) ) {
575 $this->load( $this->contLangCode );
577 $mostused = array_keys( $this->cache->get( $this->contLangCode ) );
578 foreach ( $mostused as $key => $value ) {
579 $mostused[$key] =
"$value/$code";
585 'page_is_redirect' => 0,
588 if ( count( $mostused ) ) {
589 $conds[
'page_title'] = $mostused;
590 } elseif ( $code !== $this->contLangCode ) {
591 $conds[] =
'page_title' . $dbr->buildLike( $dbr->anyString(),
'/', $code );
595 $conds[] =
'page_title NOT' .
596 $dbr->buildLike( $dbr->anyString(),
'/', $dbr->anyString() );
600 $res = $dbr->newSelectQueryBuilder()
601 ->select( [
'page_title',
'page_latest' ] )
604 ->andWhere( [
'page_len > ' . intval( $this->maxEntrySize ) ] )
605 ->caller( __METHOD__ .
"($code)-big" )->fetchResultSet();
606 foreach ( $res as $row ) {
608 if ( $this->adaptive || $this->isMainCacheable( $row->page_title ) ) {
609 $cache[$row->page_title] =
'!TOO BIG';
612 $cache[
'EXCESSIVE'][$row->page_title] = $row->page_latest;
617 $revisionStore = MediaWikiServices::getInstance()->getRevisionStore();
619 $revQuery = $revisionStore->getQueryInfo( [
'page' ] );
625 $revQuery[
'joins'][
'revision'] = $revQuery[
'joins'][
'page'];
626 unset( $revQuery[
'joins'][
'page'] );
630 $revQuery[
'tables'] = array_merge(
632 array_diff( $revQuery[
'tables'], [
'page' ] )
638 array_merge( $conds, [
639 'page_len <= ' . intval( $this->maxEntrySize ),
640 'page_latest = rev_id'
642 __METHOD__ .
"($code)-small",
648 [ $cacheableRows, $uncacheableRows ] = $this->separateCacheableRows( $res );
649 $result = $revisionStore->newRevisionsFromBatch( $cacheableRows, [
650 'slots' => [ SlotRecord::MAIN ],
653 $revisions = $result->isOK() ? $result->getValue() : [];
655 foreach ( $cacheableRows as $row ) {
657 $rev = $revisions[$row->rev_id] ??
null;
658 $content = $rev ? $rev->getContent( SlotRecord::MAIN ) :
null;
659 $text = $this->getMessageTextFromContent(
$content );
660 }
catch ( TimeoutException $e ) {
662 }
catch ( Exception $ex ) {
666 if ( !is_string( $text ) ) {
668 $this->logger->error(
670 .
": failed to load message page text for {$row->page_title} ($code)"
673 $entry =
' ' . $text;
675 $cache[$row->page_title] = $entry;
678 foreach ( $uncacheableRows as $row ) {
681 $cache[
'EXCESSIVE'][$row->page_title] = $row->page_latest;
690 $cache[
'HASH'] = md5( serialize( $cache ) );
691 $cache[
'EXPIRY'] =
wfTimestamp( TS_MW, time() + self::WAN_TTL );
692 unset( $cache[
'EXCESSIVE'] );
703 private function isLanguageLoaded( $lang ) {
710 return $this->cache->hasField( $lang,
'VERSION' );
724 private function isMainCacheable( $name, $code =
null ) {
726 $name = $this->contLang->lcfirst( $name );
729 if ( strpos( $name,
'conversiontable/' ) === 0 ) {
732 $msg = preg_replace(
'/\/[a-z0-9-]{2,}$/',
'', $name );
734 if ( $code ===
null ) {
736 if ( $this->systemMessageNames ===
null ) {
737 $this->systemMessageNames = array_fill_keys(
738 $this->localisationCache->getSubitemList( $this->contLangCode,
'messages' ),
741 return isset( $this->systemMessageNames[$msg] );
744 return $this->localisationCache->getSubitem( $code,
'messages', $msg ) !==
null;
755 private function separateCacheableRows( $res ) {
756 if ( $this->adaptive ) {
761 $uncacheableRows = [];
762 foreach ( $res as $row ) {
763 if ( $this->isMainCacheable( $row->page_title ) ) {
764 $cacheableRows[] = $row;
766 $uncacheableRows[] = $row;
769 return [ $cacheableRows, $uncacheableRows ];
784 if ( strpos( $title,
'/' ) !==
false && $code === $this->contLangCode ) {
790 if ( $text ===
false ) {
792 $this->cache->setField( $code, $title,
'!NONEXISTENT' );
795 $this->cache->setField( $code, $title,
' ' . $text );
801 DeferredUpdates::PRESEND
811 [ $scopedLock ] = $this->getReentrantScopedLock( $code );
812 if ( !$scopedLock ) {
813 foreach ( $replacements as [ $title ] ) {
814 $this->logger->error(
815 __METHOD__ .
': could not acquire lock to update {title} ({code})',
816 [
'title' => $title,
'code' => $code ] );
824 if ( $this->load( $code, self::FOR_UPDATE ) ) {
825 $cache = $this->cache->get( $code );
828 $cache = $this->loadFromDB( $code, self::FOR_UPDATE );
831 $newTextByTitle = [];
835 $wikiPageFactory = MediaWikiServices::getInstance()->getWikiPageFactory();
836 foreach ( $replacements as [ $title ] ) {
837 $page = $wikiPageFactory->newFromTitle( Title::makeTitle(
NS_MEDIAWIKI, $title ) );
838 $page->loadPageData( $page::READ_LATEST );
839 $text = $this->getMessageTextFromContent( $page->getContent() );
841 $newTextByTitle[$title] = $text ??
'';
843 if ( !is_string( $text ) ) {
844 $cache[$title] =
'!NONEXISTENT';
845 } elseif ( strlen( $text ) > $this->maxEntrySize ) {
846 $cache[$title] =
'!TOO BIG';
847 $newBigTitles[$title] = $page->getLatest();
849 $cache[$title] =
' ' . $text;
856 $cache[
'HASH'] = md5( serialize( $cache + [
'EXCESSIVE' => $newBigTitles ] ) );
858 foreach ( $newBigTitles as $title => $id ) {
860 $this->wanCache->set(
861 $this->bigMessageCacheKey( $cache[
'HASH'], $title ),
862 ' ' . $newTextByTitle[$title],
868 $cache[
'LATEST'] = time();
870 $this->cache->set( $code, $cache );
875 $this->saveToCaches( $cache,
'all', $code );
877 ScopedCallback::consume( $scopedLock );
881 $this->wanCache->touchCheckKey( $this->
getCheckKey( $code ) );
884 $blobStore = MediaWikiServices::getInstance()->getResourceLoader()->getMessageBlobStore();
885 foreach ( $replacements as [ $title, $msg ] ) {
886 $blobStore->updateMessage( $this->contLang->lcfirst( $msg ) );
887 $this->hookRunner->onMessageCacheReplace( $title, $newTextByTitle[$title] );
897 private function isCacheExpired( $cache ) {
898 return !isset( $cache[
'VERSION'] ) ||
899 !isset( $cache[
'EXPIRY'] ) ||
913 private function saveToCaches( array $cache, $dest, $code =
false ) {
914 if ( $dest ===
'all' ) {
915 $cacheKey = $this->clusterCache->makeKey(
'messages', $code );
916 $success = $this->clusterCache->set( $cacheKey, $cache );
917 $this->setValidationHash( $code, $cache );
922 $this->saveToLocalCache( $code, $cache );
933 private function getValidationHash( $code ) {
935 $value = $this->wanCache->get(
936 $this->wanCache->makeKey(
'messages', $code,
'hash',
'v1' ),
938 [ $this->getCheckKey( $code ) ]
942 $hash = $value[
'hash'];
943 if ( ( time() - $value[
'latest'] ) < WANObjectCache::TTL_MINUTE ) {
949 $expired = ( $curTTL < 0 );
957 return [ $hash, $expired ];
970 private function setValidationHash( $code, array $cache ) {
971 $this->wanCache->set(
972 $this->wanCache->makeKey(
'messages', $code,
'hash',
'v1' ),
974 'hash' => $cache[
'HASH'],
975 'latest' => $cache[
'LATEST'] ?? 0
977 WANObjectCache::TTL_INDEFINITE
987 private function getReentrantScopedLock( $code, $timeout = self::WAIT_SEC ) {
988 $key = $this->clusterCache->makeKey(
'messages', $code );
990 $watchPoint = $this->clusterCache->watchErrors();
991 $scopedLock = $this->clusterCache->getScopedLock(
997 $error = ( !$scopedLock && $this->clusterCache->getLastError( $watchPoint ) );
999 return [ $scopedLock, $error ];
1034 public function get( $key, $useDB =
true, $langcode =
true ) {
1035 if ( is_int( $key ) ) {
1037 $key = (string)$key;
1038 } elseif ( !is_string( $key ) ) {
1039 throw new TypeError(
'Message key must be a string' );
1040 } elseif ( $key ===
'' ) {
1045 $language = $this->getLanguageObject( $langcode );
1051 if ( $this->messageKeyOverrides ===
null ) {
1052 $this->messageKeyOverrides = [];
1053 $this->hookRunner->onMessageCacheFetchOverrides( $this->messageKeyOverrides );
1056 if ( isset( $this->messageKeyOverrides[$lckey] ) ) {
1057 $override = $this->messageKeyOverrides[$lckey];
1061 if ( is_string( $override ) ) {
1064 $lckey = $override( $lckey, $this, $language, $useDB );
1068 $this->hookRunner->onMessageCache__get( $lckey );
1071 $message = $this->getMessageFromFallbackChain(
1078 if ( $message ===
false ) {
1079 $parts = explode(
'/', $lckey );
1083 if ( count( $parts ) === 2 && $parts[1] !==
'' ) {
1084 $message = $this->localisationCache->getSubitem( $parts[1],
'messages', $parts[0] ) ??
false;
1089 if ( $message !==
false ) {
1091 $message = str_replace(
1128 private function getLanguageObject( $langcode ) {
1129 # Identify which language to get or create a language object for.
1130 # Using is_object here due to Stub objects.
1131 if ( is_object( $langcode ) ) {
1132 # Great, we already have the object (hopefully)!
1136 if ( $langcode ===
true || $langcode === $this->contLangCode ) {
1137 # $langcode is the language code of the wikis content language object.
1138 # or it is a boolean and value is true
1139 return $this->contLang;
1143 if ( $langcode ===
false || $langcode ===
$wgLang->getCode() ) {
1144 # $langcode is the language code of user language object.
1145 # or it was a boolean and value is false
1149 $validCodes = array_keys( $this->languageNameUtils->getLanguageNames() );
1150 if ( in_array( $langcode, $validCodes ) ) {
1151 # $langcode corresponds to a valid language.
1152 return $this->langFactory->getLanguage( $langcode );
1155 # $langcode is a string, but not a valid language code; use content language.
1156 $this->logger->debug(
'Invalid language code passed to' . __METHOD__ .
', falling back to content language.' );
1157 return $this->contLang;
1172 private function getMessageFromFallbackChain( $lang, $lckey, $useDB ) {
1176 $message = $this->getMessageForLang( $lang, $lckey, $useDB, $alreadyTried );
1177 if ( $message !==
false ) {
1182 $message = $this->getMessageForLang( $this->contLang, $lckey, $useDB, $alreadyTried );
1196 private function getMessageForLang( $lang, $lckey, $useDB, &$alreadyTried ) {
1197 $langcode = $lang->getCode();
1201 $uckey = $this->contLang->ucfirst( $lckey );
1203 if ( !isset( $alreadyTried[$langcode] ) ) {
1205 $this->getMessagePageName( $langcode, $uckey ),
1208 if ( $message !==
false ) {
1211 $alreadyTried[$langcode] =
true;
1220 if ( $langcode ===
'qqx' ) {
1223 $langcode ===
'x-xss' &&
1224 $this->useXssLanguage &&
1225 !in_array( $lckey, $this->rawHtmlMessages,
true )
1227 $xssViaInnerHtml =
"<script>alert('$lckey')</script>";
1228 $xssViaAttribute =
'">' . $xssViaInnerHtml .
'<x y="';
1229 return $xssViaInnerHtml . $xssViaAttribute .
'($*)';
1233 [ $defaultMessage, $messageSource ] =
1234 $this->localisationCache->getSubitemWithSource( $langcode,
'messages', $lckey );
1235 if ( $messageSource === $langcode ) {
1236 return $defaultMessage;
1241 $fallbackChain = $this->languageFallback->getAll( $langcode );
1243 foreach ( $fallbackChain as $code ) {
1244 if ( isset( $alreadyTried[$code] ) ) {
1250 $this->getMessagePageName( $code, $uckey ), $code );
1252 if ( $message !==
false ) {
1255 $alreadyTried[$code] =
true;
1259 if ( $code === $messageSource ) {
1260 return $defaultMessage;
1265 return $defaultMessage ??
false;
1275 private function getMessagePageName( $langcode, $uckey ) {
1276 if ( $langcode === $this->contLangCode ) {
1280 return "$uckey/$langcode";
1300 $this->load( $code );
1302 $entry = $this->cache->getField( $code, $title );
1304 if ( $entry !==
null ) {
1306 if ( substr( $entry, 0, 1 ) ===
' ' ) {
1308 return (
string)substr( $entry, 1 );
1309 } elseif ( $entry ===
'!NONEXISTENT' ) {
1315 $entry = $this->loadCachedMessagePageEntry(
1318 $this->cache->getField( $code,
'HASH' )
1322 if ( !$this->isMainCacheable( $title, $code ) ) {
1326 $entry = $this->loadCachedMessagePageEntry(
1329 $this->cache->getField( $code,
'HASH' )
1332 if ( $entry ===
null || substr( $entry, 0, 1 ) !==
' ' ) {
1336 $this->hookRunner->onMessagesPreLoad( $title, $message, $code );
1337 if ( $message !==
false ) {
1338 $this->cache->setField( $code, $title,
' ' . $message );
1340 $this->cache->setField( $code, $title,
'!NONEXISTENT' );
1347 if ( $entry !==
false && substr( $entry, 0, 1 ) ===
' ' ) {
1348 if ( $this->cacheVolatile[$code] ) {
1350 $this->logger->debug(
1351 __METHOD__ .
': loading volatile key \'{titleKey}\'',
1352 [
'titleKey' => $title,
'code' => $code ] );
1354 $this->cache->setField( $code, $title, $entry );
1357 return (
string)substr( $entry, 1 );
1360 $this->cache->setField( $code, $title,
'!NONEXISTENT' );
1371 private function loadCachedMessagePageEntry( $dbKey, $code, $hash ) {
1372 $fname = __METHOD__;
1373 return $this->srvCache->getWithSetCallback(
1374 $this->srvCache->makeKey(
'messages-big', $hash, $dbKey ),
1375 BagOStuff::TTL_HOUR,
1376 function () use ( $code, $dbKey, $hash, $fname ) {
1377 return $this->wanCache->getWithSetCallback(
1378 $this->bigMessageCacheKey( $hash, $dbKey ),
1380 function ( $oldValue, &$ttl, &$setOpts ) use ( $dbKey, $code, $fname ) {
1387 $revision = MediaWikiServices::getInstance()
1388 ->getRevisionLookup()
1389 ->getKnownCurrentRevision( $title );
1393 return '!NONEXISTENT';
1395 $content = $revision->getContent( SlotRecord::MAIN );
1397 $message = $this->getMessageTextFromContent(
$content );
1399 $this->logger->warning(
1400 $fname .
': failed to load page text for \'{titleKey}\'',
1401 [
'titleKey' => $dbKey,
'code' => $code ]
1406 if ( !is_string( $message ) ) {
1411 return '!NONEXISTENT';
1414 return ' ' . $message;
1430 if ( $this->inParser || !str_contains( $message,
'{{' ) ) {
1435 $popts = $this->getParserOptions();
1436 $popts->setInterfaceMessage( $interface );
1437 $popts->setTargetLanguage( $language );
1439 $userlang = $popts->setUserLang( $language );
1440 $this->inParser =
true;
1441 $message = $parser->
transformMsg( $message, $popts, $page );
1442 $this->inParser =
false;
1443 $popts->setUserLang( $userlang );
1452 if ( !$this->parser ) {
1453 $this->parser = $this->parserFactory->create();
1456 return $this->parser;
1468 $interface =
false, $language =
null
1472 if ( $this->inParser ) {
1473 return htmlspecialchars( $text );
1477 $popts = $this->getParserOptions();
1478 $popts->setInterfaceMessage( $interface );
1480 if ( is_string( $language ) ) {
1481 $language = $this->langFactory->getLanguage( $language );
1483 $popts->setTargetLanguage( $language );
1486 $logger = LoggerFactory::getInstance(
'GlobalTitleFail' );
1488 __METHOD__ .
' called with no title set.',
1489 [
'exception' =>
new Exception ]
1497 $page = PageReferenceValue::localReference(
1499 'Badtitle/title not set in ' . __METHOD__
1503 $this->inParser =
true;
1504 $res = $parser->
parse( $text, $page, $popts, $linestart );
1505 $this->inParser =
false;
1531 return $this->disable;
1540 $langs = $this->languageNameUtils->getLanguageNames();
1541 foreach ( $langs as $code => $_ ) {
1542 $this->wanCache->touchCheckKey( $this->
getCheckKey( $code ) );
1544 $this->cache->clear();
1552 $pieces = explode(
'/', $key );
1553 if ( count( $pieces ) < 2 ) {
1554 return [ $key, $this->contLangCode ];
1557 $lang = array_pop( $pieces );
1558 if ( !$this->languageNameUtils->getLanguageName(
1560 LanguageNameUtils::AUTONYMS,
1561 LanguageNameUtils::DEFINED
1563 return [ $key, $this->contLangCode ];
1566 $message = implode(
'/', $pieces );
1568 return [ $message, $lang ];
1580 $this->load( $code );
1581 if ( !$this->cache->has( $code ) ) {
1586 $cache = $this->cache->get( $code );
1587 unset( $cache[
'VERSION'] );
1588 unset( $cache[
'EXPIRY'] );
1589 unset( $cache[
'EXCESSIVE'] );
1591 $cache = array_diff( $cache, [
'!NONEXISTENT' ] );
1594 return array_map( [ $this->contLang,
'lcfirst' ], array_keys( $cache ) );
1606 $msgText = $this->getMessageTextFromContent(
$content ) ??
false;
1610 if ( $this->contLangConverter->hasVariants() ) {
1611 $this->contLangConverter->updateConversionTable( $linkTarget );
1620 return $this->wanCache->makeKey(
'messages', $code );
1627 private function getMessageTextFromContent(
Content $content =
null ) {
1635 $msgText =
$content->getWikitextForTransclusion();
1636 if ( $msgText ===
false || $msgText ===
null ) {
1639 $this->logger->warning(
1640 __METHOD__ .
": message content doesn't provide wikitext "
1641 .
"(content model: " .
$content->getModel() .
")" );
1656 private function bigMessageCacheKey( $hash, $title ) {
1657 return $this->wanCache->makeKey(
'messages-big', $hash, $title );
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
const MSG_CACHE_VERSION
MediaWiki message cache structure version.
if(!defined( 'MW_NO_SESSION') &&! $wgCommandLineMode) $wgLang
if(!defined( 'MW_NO_SESSION') &&! $wgCommandLineMode) $wgTitle
Class representing a cache/ephemeral data store.
static addUpdate(DeferrableUpdate $update, $stage=self::POSTSEND)
Add an update to the pending update queue for execution at the appropriate time.
A BagOStuff object with no objects in it.
Base class for language-specific code.
getCode()
Get the internal language code for this language object.
Caching for the contents of localisation files.
Store key-value entries in a size-limited in-memory LRU cache.
A class containing constants representing the names of configuration variables.
Message cache purging and in-place update handler for specific message page changes.
Cache messages that are defined by MediaWiki-namespace pages or by hooks.
refreshAndReplaceInternal(string $code, array $replacements)
const MAX_REQUEST_LANGUAGES
The size of the MapCacheLRU which stores message data.
__construct(WANObjectCache $wanCache, BagOStuff $clusterCache, BagOStuff $serverCache, Language $contLang, LanguageConverterFactory $langConverterFactory, LoggerInterface $logger, ServiceOptions $options, LanguageFactory $langFactory, LocalisationCache $localisationCache, LanguageNameUtils $languageNameUtils, LanguageFallback $languageFallback, HookContainer $hookContainer, ParserFactory $parserFactory)
getMsgFromNamespace( $title, $code)
Get a message from the MediaWiki namespace, with caching.
parse( $text, PageReference $page=null, $linestart=true, $interface=false, $language=null)
transform( $message, $interface=false, $language=null, PageReference $page=null)
updateMessageOverride(LinkTarget $linkTarget, Content $content=null)
Purge message caches when a MediaWiki: page is created, updated, or deleted.
const CONSTRUCTOR_OPTIONS
Options to be included in the ServiceOptions.
isDisabled()
Whether DB/cache usage is disabled for determining messages.
setLogger(LoggerInterface $logger)
clear()
Clear all stored messages in global and local cache.
getAllMessageKeys( $code)
Get all message keys stored in the message cache for a given language.
static normalizeKey( $key)
Normalize message key input.
replace( $title, $text)
Updates cache as necessary when message page is changed.
static newFromContext(IContextSource $context)
Get a ParserOptions object from a IContextSource object.
static newFromAnon()
Get a ParserOptions object for an anonymous user.
parse( $text, PageReference $page, ParserOptions $options, $linestart=true, $clearState=true, $revid=null)
Convert wikitext to HTML Do not call this function recursively.
transformMsg( $text, ParserOptions $options, ?PageReference $page=null)
Wrapper for preprocess()
static getMain()
Get the RequestContext object associated with the main request.
Multi-datacenter aware caching interface.
Base interface for representing page content.