69 MainConfigNames::UseDatabaseMessages,
70 MainConfigNames::MaxMsgCacheEntrySize,
71 MainConfigNames::AdaptiveMessageCache,
72 MainConfigNames::UseXssLanguage,
73 MainConfigNames::RawHtmlMessages,
82 private const FOR_UPDATE = 1;
85 private const WAIT_SEC = 15;
87 private const LOCK_TTL = 30;
93 private const WAN_TTL = ExpirationAwareness::TTL_DAY;
110 private $systemMessageNames;
115 private $cacheVolatile = [];
124 private $maxEntrySize;
130 private $useXssLanguage;
133 private $rawHtmlMessages;
139 private $parserOptions;
142 private $parser =
null;
147 private $inParser =
false;
152 private $clusterCache;
158 private $contLangCode;
160 private $contLangConverter;
162 private $langFactory;
164 private $localisationCache;
166 private $languageNameUtils;
168 private $languageFallback;
172 private $parserFactory;
175 private $messageKeyOverrides;
184 $lckey = strtr( $key,
' ',
'_' );
185 if ( $lckey ===
'' ) {
190 if ( ord( $lckey ) < 128 ) {
191 $lckey[0] = strtolower( $lckey[0] );
193 $lckey = MediaWikiServices::getInstance()->getContentLanguage()->lcfirst( $lckey );
221 LoggerInterface $logger,
230 $this->wanCache = $wanCache;
231 $this->clusterCache = $clusterCache;
232 $this->srvCache = $serverCache;
233 $this->contLang = $contLang;
235 $this->contLangCode = $contLang->
getCode();
236 $this->logger = $logger;
237 $this->langFactory = $langFactory;
238 $this->localisationCache = $localisationCache;
239 $this->languageNameUtils = $languageNameUtils;
240 $this->languageFallback = $languageFallback;
241 $this->hookRunner =
new HookRunner( $hookContainer );
242 $this->parserFactory = $parserFactory;
245 $this->cache =
new MapCacheLRU( self::MAX_REQUEST_LANGUAGES );
248 $this->
disable = !$options->
get( MainConfigNames::UseDatabaseMessages );
249 $this->maxEntrySize = $options->
get( MainConfigNames::MaxMsgCacheEntrySize );
250 $this->adaptive = $options->
get( MainConfigNames::AdaptiveMessageCache );
251 $this->useXssLanguage = $options->
get( MainConfigNames::UseXssLanguage );
252 $this->rawHtmlMessages = $options->
get( MainConfigNames::RawHtmlMessages );
256 $this->logger = $logger;
264 private function getParserOptions() {
265 if ( !$this->parserOptions ) {
266 $context = RequestContext::getMain();
267 $user = $context->getUser();
268 if ( !$user->isSafeToLoad() ) {
272 $po = ParserOptions::newFromAnon();
273 $po->setAllowUnsafeRawHtml(
false );
281 $this->parserOptions->setAllowUnsafeRawHtml(
false );
284 return $this->parserOptions;
293 private function getLocalCache( $code ) {
294 $cacheKey = $this->srvCache->makeKey( __CLASS__, $code );
296 return $this->srvCache->get( $cacheKey );
305 private function saveToLocalCache( $code, $cache ) {
306 $cacheKey = $this->srvCache->makeKey( __CLASS__, $code );
307 $this->srvCache->set( $cacheKey, $cache );
330 private function load(
string $code, $mode =
null ) {
332 if ( $this->isLanguageLoaded( $code ) && $mode !== self::FOR_UPDATE ) {
337 if ( $this->disable ) {
338 static $shownDisabled =
false;
339 if ( !$shownDisabled ) {
340 $this->logger->debug( __METHOD__ .
': disabled' );
341 $shownDisabled =
true;
348 return $this->loadUnguarded( $code, $mode );
349 }
catch ( Throwable $e ) {
363 private function loadUnguarded( $code, $mode ) {
370 [ $hash, $hashVolatile ] = $this->getValidationHash( $code );
371 $this->cacheVolatile[$code] = $hashVolatile;
372 $volatilityOnlyStaleness =
false;
375 $cache = $this->getLocalCache( $code );
377 $where[] =
'local cache is empty';
378 } elseif ( !isset( $cache[
'HASH'] ) || $cache[
'HASH'] !== $hash ) {
379 $where[] =
'local cache has the wrong hash';
380 $staleCache = $cache;
381 } elseif ( $this->isCacheExpired( $cache ) ) {
382 $where[] =
'local cache is expired';
383 $staleCache = $cache;
384 } elseif ( $hashVolatile ) {
386 $where[] =
'local cache validation key is expired/volatile';
387 $staleCache = $cache;
388 $volatilityOnlyStaleness =
true;
390 $where[] =
'got from local cache';
391 $this->cache->set( $code, $cache );
397 $cacheKey = $this->clusterCache->makeKey(
'messages', $code );
398 for ( $failedAttempts = 0; $failedAttempts <= 1; $failedAttempts++ ) {
399 if ( $volatilityOnlyStaleness && $staleCache ) {
404 $where[] =
'global cache is presumed expired';
406 $cache = $this->clusterCache->get( $cacheKey );
408 $where[] =
'global cache is empty';
409 } elseif ( $this->isCacheExpired( $cache ) ) {
410 $where[] =
'global cache is expired';
411 $staleCache = $cache;
412 } elseif ( $hashVolatile ) {
414 $where[] =
'global cache is expired/volatile';
415 $staleCache = $cache;
417 $where[] =
'got from global cache';
418 $this->cache->set( $code, $cache );
419 $this->saveToCaches( $cache,
'local-only', $code );
428 $loadStatus = $this->loadFromDBWithMainLock( $code, $where, $mode );
429 if ( $loadStatus ===
true ) {
432 } elseif ( $staleCache ) {
434 $where[] =
'using stale cache';
435 $this->cache->set( $code, $staleCache );
438 } elseif ( $failedAttempts > 0 ) {
439 $where[] =
'failed to find cache after waiting';
444 } elseif ( $loadStatus ===
'cantacquire' ) {
447 $where[] =
'waiting for other thread to complete';
448 [ , $ioError ] = $this->getReentrantScopedLock( $code );
450 $where[] =
'failed waiting';
453 $success = $this->loadFromDBWithLocalLock( $code, $where, $mode );
464 $where[] =
'loading FAILED - cache is disabled';
466 $this->cache->set( $code, [] );
467 $this->logger->error( __METHOD__ .
": Failed to load $code" );
472 if ( !$this->isLanguageLoaded( $code ) ) {
473 throw new LogicException(
"Process cache for '$code' should be set by now." );
476 $info = implode(
', ', $where );
477 $this->logger->debug( __METHOD__ .
": Loading $code... $info" );
488 private function loadFromDBWithMainLock( $code, array &$where, $mode =
null ) {
491 $statusKey = $this->clusterCache->makeKey(
'messages', $code,
'status' );
492 $status = $this->clusterCache->get( $statusKey );
493 if ( $status ===
'error' ) {
494 $where[] =
"could not load; method is still globally disabled";
499 $where[] =
'loading from DB';
505 [ $scopedLock ] = $this->getReentrantScopedLock( $code, 0 );
506 if ( !$scopedLock ) {
507 $where[] =
'could not acquire main lock';
508 return 'cantacquire';
511 $cache = $this->loadFromDB( $code, $mode );
512 $this->cache->set( $code, $cache );
513 $saveSuccess = $this->saveToCaches( $cache,
'all', $code );
515 if ( !$saveSuccess ) {
530 $this->clusterCache->set( $statusKey,
'error', 60 * 5 );
531 $where[] =
'could not save cache, disabled globally for 5 minutes';
533 $where[] =
"could not save global cache";
546 private function loadFromDBWithLocalLock( $code, array &$where, $mode =
null ) {
548 $where[] =
'loading from DB using local lock';
550 $scopedLock = $this->srvCache->getScopedLock(
551 $this->srvCache->makeKey(
'messages', $code ),
557 $cache = $this->loadFromDB( $code, $mode );
558 $this->cache->set( $code, $cache );
559 $this->saveToCaches( $cache,
'local-only', $code );
575 private function loadFromDB( $code, $mode =
null ) {
576 $icp = MediaWikiServices::getInstance()->getConnectionProvider();
578 $dbr = ( $mode === self::FOR_UPDATE ) ? $icp->getPrimaryDatabase() : $icp->getReplicaDatabase();
583 if ( $this->adaptive && $code !== $this->contLangCode ) {
584 if ( !$this->cache->has( $this->contLangCode ) ) {
585 $this->load( $this->contLangCode );
587 $mostused = array_keys( $this->cache->get( $this->contLangCode ) );
588 foreach ( $mostused as $key => $value ) {
589 $mostused[$key] =
"$value/$code";
595 'page_is_redirect' => 0,
598 if ( count( $mostused ) ) {
599 $conds[
'page_title'] = $mostused;
600 } elseif ( $code !== $this->contLangCode ) {
601 $conds[] = $dbr->expr(
604 new LikeValue( $dbr->anyString(),
'/', $code )
609 $conds[] = $dbr->expr(
611 IExpression::NOT_LIKE,
612 new LikeValue( $dbr->anyString(),
'/', $dbr->anyString() )
617 $res = $dbr->newSelectQueryBuilder()
618 ->select( [
'page_title',
'page_latest' ] )
621 ->andWhere( [
'page_len > ' . intval( $this->maxEntrySize ) ] )
622 ->caller( __METHOD__ .
"($code)-big" )->fetchResultSet();
623 foreach ( $res as $row ) {
625 if ( $this->adaptive || $this->isMainCacheable( $row->page_title ) ) {
626 $cache[$row->page_title] =
'!TOO BIG';
629 $cache[
'EXCESSIVE'][$row->page_title] = $row->page_latest;
634 $revisionStore = MediaWikiServices::getInstance()->getRevisionStore();
636 $revQuery = $revisionStore->getQueryInfo( [
'page' ] );
642 $revQuery[
'joins'][
'revision'] = $revQuery[
'joins'][
'page'];
643 unset( $revQuery[
'joins'][
'page'] );
647 $revQuery[
'tables'] = array_merge(
649 array_diff( $revQuery[
'tables'], [
'page' ] )
652 $res = $dbr->newSelectQueryBuilder()
653 ->queryInfo( $revQuery )
656 $dbr->expr(
'page_len',
'<=', intval( $this->maxEntrySize ) ),
657 'page_latest = rev_id'
659 ->caller( __METHOD__ .
"($code)-small" )
660 ->straightJoinOption()
664 [ $cacheableRows, $uncacheableRows ] = $this->separateCacheableRows( $res );
665 $result = $revisionStore->newRevisionsFromBatch( $cacheableRows, [
666 'slots' => [ SlotRecord::MAIN ],
669 $revisions = $result->isOK() ? $result->getValue() : [];
671 foreach ( $cacheableRows as $row ) {
673 $rev = $revisions[$row->rev_id] ??
null;
674 $content = $rev ? $rev->getContent( SlotRecord::MAIN ) :
null;
675 $text = $this->getMessageTextFromContent( $content );
676 }
catch ( TimeoutException $e ) {
678 }
catch ( Exception $ex ) {
682 if ( !is_string( $text ) ) {
684 $this->logger->error(
686 .
": failed to load message page text for {$row->page_title} ($code)"
689 $entry =
' ' . $text;
691 $cache[$row->page_title] = $entry;
694 foreach ( $uncacheableRows as $row ) {
697 $cache[
'EXCESSIVE'][$row->page_title] = $row->page_latest;
706 $cache[
'HASH'] = md5( serialize( $cache ) );
707 $cache[
'EXPIRY'] =
wfTimestamp( TS_MW, time() + self::WAN_TTL );
708 unset( $cache[
'EXCESSIVE'] );
719 private function isLanguageLoaded( $lang ) {
726 return $this->cache->hasField( $lang,
'VERSION' );
740 private function isMainCacheable( $name, $code =
null ) {
742 $name = $this->contLang->lcfirst( $name );
745 if ( strpos( $name,
'conversiontable/' ) === 0 ) {
748 $msg = preg_replace(
'/\/[a-z0-9-]{2,}$/',
'', $name );
750 if ( $code ===
null ) {
752 if ( $this->systemMessageNames ===
null ) {
753 $this->systemMessageNames = array_fill_keys(
754 $this->localisationCache->getSubitemList( $this->contLangCode,
'messages' ),
757 return isset( $this->systemMessageNames[$msg] );
760 return $this->localisationCache->getSubitem( $code,
'messages', $msg ) !==
null;
771 private function separateCacheableRows( $res ) {
772 if ( $this->adaptive ) {
777 $uncacheableRows = [];
778 foreach ( $res as $row ) {
779 if ( $this->isMainCacheable( $row->page_title ) ) {
780 $cacheableRows[] = $row;
782 $uncacheableRows[] = $row;
785 return [ $cacheableRows, $uncacheableRows ];
800 if ( strpos( $title,
'/' ) !==
false && $code === $this->contLangCode ) {
806 if ( $text ===
false ) {
808 $this->cache->setField( $code, $title,
'!NONEXISTENT' );
811 $this->cache->setField( $code, $title,
' ' . $text );
815 DeferredUpdates::addUpdate(
817 DeferredUpdates::PRESEND
827 [ $scopedLock ] = $this->getReentrantScopedLock( $code );
828 if ( !$scopedLock ) {
829 foreach ( $replacements as [ $title ] ) {
830 $this->logger->error(
831 __METHOD__ .
': could not acquire lock to update {title} ({code})',
832 [
'title' => $title,
'code' => $code ] );
840 if ( $this->load( $code, self::FOR_UPDATE ) ) {
841 $cache = $this->cache->get( $code );
844 $cache = $this->loadFromDB( $code, self::FOR_UPDATE );
847 $newTextByTitle = [];
851 $wikiPageFactory = MediaWikiServices::getInstance()->getWikiPageFactory();
852 foreach ( $replacements as [ $title ] ) {
853 $page = $wikiPageFactory->newFromTitle( Title::makeTitle(
NS_MEDIAWIKI, $title ) );
854 $page->loadPageData( IDBAccessObject::READ_LATEST );
855 $text = $this->getMessageTextFromContent( $page->getContent() );
857 $newTextByTitle[$title] = $text ??
'';
859 if ( !is_string( $text ) ) {
860 $cache[$title] =
'!NONEXISTENT';
861 } elseif ( strlen( $text ) > $this->maxEntrySize ) {
862 $cache[$title] =
'!TOO BIG';
863 $newBigTitles[$title] = $page->getLatest();
865 $cache[$title] =
' ' . $text;
872 $cache[
'HASH'] = md5( serialize( $cache + [
'EXCESSIVE' => $newBigTitles ] ) );
874 foreach ( $newBigTitles as $title => $id ) {
876 $this->wanCache->set(
877 $this->bigMessageCacheKey( $cache[
'HASH'], $title ),
878 ' ' . $newTextByTitle[$title],
884 $cache[
'LATEST'] = time();
886 $this->cache->set( $code, $cache );
891 $this->saveToCaches( $cache,
'all', $code );
893 ScopedCallback::consume( $scopedLock );
897 $this->wanCache->touchCheckKey( $this->
getCheckKey( $code ) );
900 $blobStore = MediaWikiServices::getInstance()->getResourceLoader()->getMessageBlobStore();
901 foreach ( $replacements as [ $title, $msg ] ) {
902 $blobStore->updateMessage( $this->contLang->lcfirst( $msg ) );
903 $this->hookRunner->onMessageCacheReplace( $title, $newTextByTitle[$title] );
913 private function isCacheExpired( $cache ) {
914 return !isset( $cache[
'VERSION'] ) ||
915 !isset( $cache[
'EXPIRY'] ) ||
929 private function saveToCaches( array $cache, $dest, $code =
false ) {
930 if ( $dest ===
'all' ) {
931 $cacheKey = $this->clusterCache->makeKey(
'messages', $code );
932 $success = $this->clusterCache->set( $cacheKey, $cache );
933 $this->setValidationHash( $code, $cache );
938 $this->saveToLocalCache( $code, $cache );
949 private function getValidationHash( $code ) {
951 $value = $this->wanCache->get(
952 $this->wanCache->makeKey(
'messages', $code,
'hash',
'v1' ),
954 [ $this->getCheckKey( $code ) ]
958 $hash = $value[
'hash'];
959 if ( ( time() - $value[
'latest'] ) < WANObjectCache::TTL_MINUTE ) {
965 $expired = ( $curTTL < 0 );
973 return [ $hash, $expired ];
986 private function setValidationHash( $code, array $cache ) {
987 $this->wanCache->set(
988 $this->wanCache->makeKey(
'messages', $code,
'hash',
'v1' ),
990 'hash' => $cache[
'HASH'],
991 'latest' => $cache[
'LATEST'] ?? 0
993 WANObjectCache::TTL_INDEFINITE
1003 private function getReentrantScopedLock( $code, $timeout = self::WAIT_SEC ) {
1004 $key = $this->clusterCache->makeKey(
'messages', $code );
1006 $watchPoint = $this->clusterCache->watchErrors();
1007 $scopedLock = $this->clusterCache->getScopedLock(
1013 $error = ( !$scopedLock && $this->clusterCache->getLastError( $watchPoint ) );
1015 return [ $scopedLock, $error ];
1052 public function get( $key, $useDB =
true, $langcode =
true, &$usedKey =
'' ) {
1053 if ( is_int( $key ) ) {
1055 $key = (string)$key;
1056 } elseif ( !is_string( $key ) ) {
1057 throw new TypeError(
'Message key must be a string' );
1058 } elseif ( $key ===
'' ) {
1063 $language = $this->getLanguageObject( $langcode );
1066 $lckey = self::normalizeKey( $key );
1069 if ( $this->messageKeyOverrides ===
null ) {
1070 $this->messageKeyOverrides = [];
1071 $this->hookRunner->onMessageCacheFetchOverrides( $this->messageKeyOverrides );
1074 if ( isset( $this->messageKeyOverrides[$lckey] ) ) {
1075 $override = $this->messageKeyOverrides[$lckey];
1079 if ( is_string( $override ) ) {
1082 $lckey = $override( $lckey, $this, $language, $useDB );
1086 $this->hookRunner->onMessageCache__get( $lckey );
1091 $message = $this->getMessageFromFallbackChain(
1098 if ( $message ===
false ) {
1099 $parts = explode(
'/', $lckey );
1103 if ( count( $parts ) === 2 && $parts[1] !==
'' ) {
1104 $message = $this->localisationCache->getSubitem( $parts[1],
'messages', $parts[0] ) ??
false;
1109 if ( $message !==
false ) {
1111 $message = str_replace(
1148 private function getLanguageObject( $langcode ) {
1149 # Identify which language to get or create a language object for.
1150 # Using is_object here due to Stub objects.
1151 if ( is_object( $langcode ) ) {
1152 # Great, we already have the object (hopefully)!
1156 if ( $langcode ===
true || $langcode === $this->contLangCode ) {
1157 # $langcode is the language code of the wikis content language object.
1158 # or it is a boolean and value is true
1159 return $this->contLang;
1163 if ( $langcode ===
false || $langcode ===
$wgLang->getCode() ) {
1164 # $langcode is the language code of user language object.
1165 # or it was a boolean and value is false
1169 $validCodes = array_keys( $this->languageNameUtils->getLanguageNames() );
1170 if ( in_array( $langcode, $validCodes ) ) {
1171 # $langcode corresponds to a valid language.
1172 return $this->langFactory->getLanguage( $langcode );
1175 # $langcode is a string, but not a valid language code; use content language.
1176 $this->logger->debug(
'Invalid language code passed to' . __METHOD__ .
', falling back to content language.' );
1177 return $this->contLang;
1192 private function getMessageFromFallbackChain( $lang, $lckey, $useDB ) {
1196 $message = $this->getMessageForLang( $lang, $lckey, $useDB, $alreadyTried );
1197 if ( $message !==
false ) {
1202 $message = $this->getMessageForLang( $this->contLang, $lckey, $useDB, $alreadyTried );
1216 private function getMessageForLang( $lang, $lckey, $useDB, &$alreadyTried ) {
1217 $langcode = $lang->getCode();
1221 $uckey = $this->contLang->ucfirst( $lckey );
1223 if ( !isset( $alreadyTried[$langcode] ) ) {
1225 $this->getMessagePageName( $langcode, $uckey ),
1228 if ( $message !==
false ) {
1231 $alreadyTried[$langcode] =
true;
1240 if ( $langcode ===
'qqx' ) {
1243 $langcode ===
'x-xss' &&
1244 $this->useXssLanguage &&
1245 !in_array( $lckey, $this->rawHtmlMessages,
true )
1247 $xssViaInnerHtml =
"<script>alert('$lckey')</script>";
1248 $xssViaAttribute =
'">' . $xssViaInnerHtml .
'<x y="';
1249 return $xssViaInnerHtml . $xssViaAttribute .
'($*)';
1253 [ $defaultMessage, $messageSource ] =
1254 $this->localisationCache->getSubitemWithSource( $langcode,
'messages', $lckey );
1255 if ( $messageSource === $langcode ) {
1256 return $defaultMessage;
1261 $fallbackChain = $this->languageFallback->getAll( $langcode );
1263 foreach ( $fallbackChain as $code ) {
1264 if ( isset( $alreadyTried[$code] ) ) {
1270 $this->getMessagePageName( $code, $uckey ), $code );
1272 if ( $message !==
false ) {
1275 $alreadyTried[$code] =
true;
1279 if ( $code === $messageSource ) {
1280 return $defaultMessage;
1285 return $defaultMessage ??
false;
1295 private function getMessagePageName( $langcode, $uckey ) {
1296 if ( $langcode === $this->contLangCode ) {
1300 return "$uckey/$langcode";
1320 $this->load( $code );
1322 $entry = $this->cache->getField( $code, $title );
1324 if ( $entry !==
null ) {
1326 if ( substr( $entry, 0, 1 ) ===
' ' ) {
1328 return (
string)substr( $entry, 1 );
1329 } elseif ( $entry ===
'!NONEXISTENT' ) {
1335 $entry = $this->loadCachedMessagePageEntry(
1338 $this->cache->getField( $code,
'HASH' )
1342 if ( !$this->isMainCacheable( $title, $code ) ) {
1346 $entry = $this->loadCachedMessagePageEntry(
1349 $this->cache->getField( $code,
'HASH' )
1352 if ( $entry ===
null || substr( $entry, 0, 1 ) !==
' ' ) {
1356 $this->hookRunner->onMessagesPreLoad( $title, $message, $code );
1357 if ( $message !==
false ) {
1358 $this->cache->setField( $code, $title,
' ' . $message );
1360 $this->cache->setField( $code, $title,
'!NONEXISTENT' );
1367 if ( $entry !==
false && substr( $entry, 0, 1 ) ===
' ' ) {
1368 if ( $this->cacheVolatile[$code] ) {
1370 $this->logger->debug(
1371 __METHOD__ .
': loading volatile key \'{titleKey}\'',
1372 [
'titleKey' => $title,
'code' => $code ] );
1374 $this->cache->setField( $code, $title, $entry );
1377 return (
string)substr( $entry, 1 );
1380 $this->cache->setField( $code, $title,
'!NONEXISTENT' );
1391 private function loadCachedMessagePageEntry( $dbKey, $code, $hash ) {
1392 $fname = __METHOD__;
1393 return $this->srvCache->getWithSetCallback(
1394 $this->srvCache->makeKey(
'messages-big', $hash, $dbKey ),
1395 BagOStuff::TTL_HOUR,
1396 function () use ( $code, $dbKey, $hash, $fname ) {
1397 return $this->wanCache->getWithSetCallback(
1398 $this->bigMessageCacheKey( $hash, $dbKey ),
1400 function ( $oldValue, &$ttl, &$setOpts ) use ( $dbKey, $code, $fname ) {
1402 $setOpts += Database::getCacheSetOptions(
1403 MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase()
1409 $revision = MediaWikiServices::getInstance()
1410 ->getRevisionLookup()
1411 ->getKnownCurrentRevision( $title );
1415 return '!NONEXISTENT';
1417 $content = $revision->getContent( SlotRecord::MAIN );
1419 $message = $this->getMessageTextFromContent( $content );
1421 $this->logger->warning(
1422 $fname .
': failed to load page text for \'{titleKey}\'',
1423 [
'titleKey' => $dbKey,
'code' => $code ]
1428 if ( !is_string( $message ) ) {
1433 return '!NONEXISTENT';
1436 return ' ' . $message;
1452 if ( $this->inParser || !str_contains( $message,
'{{' ) ) {
1457 $popts = $this->getParserOptions();
1458 $popts->setInterfaceMessage( $interface );
1459 $popts->setTargetLanguage( $language );
1461 $userlang = $popts->setUserLang( $language );
1462 $this->inParser =
true;
1463 $message = $parser->
transformMsg( $message, $popts, $page );
1464 $this->inParser =
false;
1465 $popts->setUserLang( $userlang );
1474 if ( !$this->parser ) {
1475 $this->parser = $this->parserFactory->create();
1478 return $this->parser;
1490 $interface =
false, $language =
null
1494 if ( $this->inParser ) {
1495 return htmlspecialchars( $text );
1499 $popts = $this->getParserOptions();
1500 $popts->setInterfaceMessage( $interface );
1502 if ( is_string( $language ) ) {
1503 $language = $this->langFactory->getLanguage( $language );
1505 $popts->setTargetLanguage( $language );
1508 $logger = LoggerFactory::getInstance(
'GlobalTitleFail' );
1510 __METHOD__ .
' called with no title set.',
1511 [
'exception' =>
new RuntimeException ]
1519 $page = PageReferenceValue::localReference(
1521 'Badtitle/title not set in ' . __METHOD__
1525 $this->inParser =
true;
1526 $res = $parser->
parse( $text, $page, $popts, $linestart );
1527 $this->inParser =
false;
1553 return $this->disable;
1562 $langs = $this->languageNameUtils->getLanguageNames();
1563 foreach ( $langs as $code => $_ ) {
1564 $this->wanCache->touchCheckKey( $this->
getCheckKey( $code ) );
1566 $this->cache->clear();
1574 $pieces = explode(
'/', $key );
1575 if ( count( $pieces ) < 2 ) {
1576 return [ $key, $this->contLangCode ];
1579 $lang = array_pop( $pieces );
1580 if ( !$this->languageNameUtils->getLanguageName(
1582 LanguageNameUtils::AUTONYMS,
1583 LanguageNameUtils::DEFINED
1585 return [ $key, $this->contLangCode ];
1588 $message = implode(
'/', $pieces );
1590 return [ $message, $lang ];
1603 $this->load( $code );
1604 if ( !$this->cache->has( $code ) ) {
1609 $cache = $this->cache->get( $code );
1610 unset( $cache[
'VERSION'] );
1611 unset( $cache[
'EXPIRY'] );
1612 unset( $cache[
'EXCESSIVE'] );
1614 $cache = array_diff( $cache, [
'!NONEXISTENT' ] );
1617 return array_map( [ $this->contLang,
'lcfirst' ], array_keys( $cache ) );
1629 $msgText = $this->getMessageTextFromContent( $content ) ??
false;
1633 if ( $this->contLangConverter->hasVariants() ) {
1634 $this->contLangConverter->updateConversionTable( $linkTarget );
1643 return $this->wanCache->makeKey(
'messages', $code );
1650 private function getMessageTextFromContent(
Content $content =
null ) {
1658 $msgText = $content->getWikitextForTransclusion();
1659 if ( $msgText ===
false || $msgText ===
null ) {
1662 $this->logger->warning(
1663 __METHOD__ .
": message content doesn't provide wikitext "
1664 .
"(content model: " . $content->getModel() .
")" );
1679 private function bigMessageCacheKey( $hash, $title ) {
1680 return $this->wanCache->makeKey(
'messages-big', $hash, $title );