77 MainConfigNames::UseDatabaseMessages,
78 MainConfigNames::MaxMsgCacheEntrySize,
79 MainConfigNames::AdaptiveMessageCache,
80 MainConfigNames::UseXssLanguage,
81 MainConfigNames::RawHtmlMessages,
90 private const FOR_UPDATE = 1;
93 private const WAIT_SEC = 15;
95 private const LOCK_TTL = 30;
100 private const WAN_TTL = BagOStuff::TTL_DAY;
117 private $systemMessageNames;
122 private $cacheVolatile = [];
131 private $maxEntrySize;
137 private $useXssLanguage;
140 private $rawHtmlMessages;
146 private $parserOptions;
149 private array $parsers = [];
150 private int $curParser = -1;
157 private const MAX_PARSER_DEPTH = 5;
162 private $clusterCache;
168 private $contLangCode;
170 private $contLangConverter;
172 private $langFactory;
174 private $localisationCache;
176 private $languageNameUtils;
178 private $languageFallback;
182 private $parserFactory;
185 private $messageKeyOverrides;
194 $lckey = strtr( $key,
' ',
'_' );
195 if ( $lckey ===
'' ) {
200 if ( ord( $lckey ) < 128 ) {
201 $lckey[0] = strtolower( $lckey[0] );
203 $lckey = MediaWikiServices::getInstance()->getContentLanguage()->lcfirst( $lckey );
231 LoggerInterface $logger,
240 $this->wanCache = $wanCache;
241 $this->clusterCache = $clusterCache;
242 $this->srvCache = $serverCache;
243 $this->contLang = $contLang;
245 $this->contLangCode = $contLang->
getCode();
246 $this->logger = $logger;
247 $this->langFactory = $langFactory;
248 $this->localisationCache = $localisationCache;
249 $this->languageNameUtils = $languageNameUtils;
250 $this->languageFallback = $languageFallback;
251 $this->hookRunner =
new HookRunner( $hookContainer );
252 $this->parserFactory = $parserFactory;
255 $this->cache =
new MapCacheLRU( self::MAX_REQUEST_LANGUAGES );
258 $this->
disable = !$options->
get( MainConfigNames::UseDatabaseMessages );
259 $this->maxEntrySize = $options->
get( MainConfigNames::MaxMsgCacheEntrySize );
260 $this->adaptive = $options->
get( MainConfigNames::AdaptiveMessageCache );
261 $this->useXssLanguage = $options->
get( MainConfigNames::UseXssLanguage );
262 $this->rawHtmlMessages = $options->
get( MainConfigNames::RawHtmlMessages );
266 $this->logger = $logger;
274 private function getParserOptions() {
275 if ( !$this->parserOptions ) {
276 $context = RequestContext::getMain();
277 $user = $context->getUser();
278 if ( !$user->isSafeToLoad() ) {
282 $po = ParserOptions::newFromAnon();
283 $po->setAllowUnsafeRawHtml(
false );
287 $this->parserOptions = ParserOptions::newFromContext( $context );
291 $this->parserOptions->setAllowUnsafeRawHtml(
false );
294 return $this->parserOptions;
303 private function getLocalCache( $code ) {
304 $cacheKey = $this->srvCache->makeKey( __CLASS__, $code );
306 return $this->srvCache->get( $cacheKey );
315 private function saveToLocalCache( $code, $cache ) {
316 $cacheKey = $this->srvCache->makeKey( __CLASS__, $code );
317 $this->srvCache->set( $cacheKey, $cache );
340 private function load(
string $code, $mode =
null ) {
342 if ( $this->isLanguageLoaded( $code ) && $mode !== self::FOR_UPDATE ) {
347 if ( $this->disable ) {
348 static $shownDisabled =
false;
349 if ( !$shownDisabled ) {
350 $this->logger->debug( __METHOD__ .
': disabled' );
351 $shownDisabled =
true;
358 return $this->loadUnguarded( $code, $mode );
359 }
catch ( Throwable $e ) {
373 private function loadUnguarded( $code, $mode ) {
380 [ $hash, $hashVolatile ] = $this->getValidationHash( $code );
381 $this->cacheVolatile[$code] = $hashVolatile;
382 $volatilityOnlyStaleness =
false;
385 $cache = $this->getLocalCache( $code );
387 $where[] =
'local cache is empty';
388 } elseif ( !isset( $cache[
'HASH'] ) || $cache[
'HASH'] !== $hash ) {
389 $where[] =
'local cache has the wrong hash';
390 $staleCache = $cache;
391 } elseif ( $this->isCacheExpired( $cache ) ) {
392 $where[] =
'local cache is expired';
393 $staleCache = $cache;
394 } elseif ( $hashVolatile ) {
396 $where[] =
'local cache validation key is expired/volatile';
397 $staleCache = $cache;
398 $volatilityOnlyStaleness =
true;
400 $where[] =
'got from local cache';
401 $this->cache->set( $code, $cache );
407 $cacheKey = $this->clusterCache->makeKey(
'messages', $code );
408 for ( $failedAttempts = 0; $failedAttempts <= 1; $failedAttempts++ ) {
409 if ( $volatilityOnlyStaleness && $staleCache ) {
414 $where[] =
'global cache is presumed expired';
416 $cache = $this->clusterCache->get( $cacheKey );
418 $where[] =
'global cache is empty';
419 } elseif ( $this->isCacheExpired( $cache ) ) {
420 $where[] =
'global cache is expired';
421 $staleCache = $cache;
422 } elseif ( $hashVolatile ) {
424 $where[] =
'global cache is expired/volatile';
425 $staleCache = $cache;
427 $where[] =
'got from global cache';
428 $this->cache->set( $code, $cache );
429 $this->saveToCaches( $cache,
'local-only', $code );
438 $loadStatus = $this->loadFromDBWithMainLock( $code, $where, $mode );
439 if ( $loadStatus ===
true ) {
442 } elseif ( $staleCache ) {
444 $where[] =
'using stale cache';
445 $this->cache->set( $code, $staleCache );
448 } elseif ( $failedAttempts > 0 ) {
449 $where[] =
'failed to find cache after waiting';
454 } elseif ( $loadStatus ===
'cantacquire' ) {
457 $where[] =
'waiting for other thread to complete';
458 [ , $ioError ] = $this->getReentrantScopedLock( $code );
460 $where[] =
'failed waiting';
463 $success = $this->loadFromDBWithLocalLock( $code, $where, $mode );
474 $where[] =
'loading FAILED - cache is disabled';
476 $this->cache->set( $code, [] );
477 $this->logger->error( __METHOD__ .
": Failed to load $code" );
482 if ( !$this->isLanguageLoaded( $code ) ) {
483 throw new LogicException(
"Process cache for '$code' should be set by now." );
486 $info = implode(
', ', $where );
487 $this->logger->debug( __METHOD__ .
": Loading $code... $info" );
498 private function loadFromDBWithMainLock( $code, array &$where, $mode =
null ) {
501 $statusKey = $this->clusterCache->makeKey(
'messages', $code,
'status' );
502 $status = $this->clusterCache->get( $statusKey );
503 if ( $status ===
'error' ) {
504 $where[] =
"could not load; method is still globally disabled";
509 $where[] =
'loading from DB';
515 [ $scopedLock ] = $this->getReentrantScopedLock( $code, 0 );
516 if ( !$scopedLock ) {
517 $where[] =
'could not acquire main lock';
518 return 'cantacquire';
521 $cache = $this->loadFromDB( $code, $mode );
522 $this->cache->set( $code, $cache );
523 $saveSuccess = $this->saveToCaches( $cache,
'all', $code );
525 if ( !$saveSuccess ) {
540 $this->clusterCache->set( $statusKey,
'error', 60 * 5 );
541 $where[] =
'could not save cache, disabled globally for 5 minutes';
543 $where[] =
"could not save global cache";
556 private function loadFromDBWithLocalLock( $code, array &$where, $mode =
null ) {
558 $where[] =
'loading from DB using local lock';
560 $scopedLock = $this->srvCache->getScopedLock(
561 $this->srvCache->makeKey(
'messages', $code ),
567 $cache = $this->loadFromDB( $code, $mode );
568 $this->cache->set( $code, $cache );
569 $this->saveToCaches( $cache,
'local-only', $code );
585 private function loadFromDB( $code, $mode =
null ) {
586 $icp = MediaWikiServices::getInstance()->getConnectionProvider();
588 $dbr = ( $mode === self::FOR_UPDATE ) ? $icp->getPrimaryDatabase() : $icp->getReplicaDatabase();
593 if ( $this->adaptive && $code !== $this->contLangCode ) {
594 if ( !$this->cache->has( $this->contLangCode ) ) {
595 $this->load( $this->contLangCode );
597 $mostused = array_keys( $this->cache->get( $this->contLangCode ) );
598 foreach ( $mostused as $key => $value ) {
599 $mostused[$key] =
"$value/$code";
606 'page_is_redirect' => 0,
609 if ( count( $mostused ) ) {
610 $conds[
'page_title'] = $mostused;
611 } elseif ( $code !== $this->contLangCode ) {
612 $conds[] = $dbr->expr(
615 new LikeValue( $dbr->anyString(),
'/', $code )
620 $conds[] = $dbr->expr(
622 IExpression::NOT_LIKE,
623 new LikeValue( $dbr->anyString(),
'/', $dbr->anyString() )
628 $res = $dbr->newSelectQueryBuilder()
629 ->select( [
'page_title',
'page_latest' ] )
632 ->andWhere( $dbr->expr(
'page_len',
'>', intval( $this->maxEntrySize ) ) )
633 ->caller( __METHOD__ .
"($code)-big" )->fetchResultSet();
634 foreach ( $res as $row ) {
636 if ( $this->adaptive || $this->isMainCacheable( $row->page_title ) ) {
637 $cache[$row->page_title] =
'!TOO BIG';
640 $cache[
'EXCESSIVE'][$row->page_title] = $row->page_latest;
645 $revisionStore = MediaWikiServices::getInstance()->getRevisionStore();
647 $revQuery = $revisionStore->getQueryInfo( [
'page' ] );
653 $revQuery[
'joins'][
'revision'] = $revQuery[
'joins'][
'page'];
654 unset( $revQuery[
'joins'][
'page'] );
658 $revQuery[
'tables'] = array_merge(
660 array_diff( $revQuery[
'tables'], [
'page' ] )
663 $res = $dbr->newSelectQueryBuilder()
664 ->queryInfo( $revQuery )
667 $dbr->expr(
'page_len',
'<=', intval( $this->maxEntrySize ) ),
668 'page_latest = rev_id'
670 ->caller( __METHOD__ .
"($code)-small" )
671 ->straightJoinOption()
675 [ $cacheableRows, $uncacheableRows ] = $this->separateCacheableRows( $res );
676 $result = $revisionStore->newRevisionsFromBatch( $cacheableRows, [
677 'slots' => [ SlotRecord::MAIN ],
680 $revisions = $result->isOK() ? $result->getValue() : [];
682 foreach ( $cacheableRows as $row ) {
684 $rev = $revisions[$row->rev_id] ??
null;
685 $content = $rev ? $rev->getContent( SlotRecord::MAIN ) :
null;
686 $text = $this->getMessageTextFromContent( $content );
687 }
catch ( TimeoutException $e ) {
689 }
catch ( Exception $ex ) {
693 if ( !is_string( $text ) ) {
695 $this->logger->error(
697 .
": failed to load message page text for {$row->page_title} ($code)"
700 $entry =
' ' . $text;
702 $cache[$row->page_title] = $entry;
705 foreach ( $uncacheableRows as $row ) {
708 $cache[
'EXCESSIVE'][$row->page_title] = $row->page_latest;
717 $cache[
'HASH'] = md5( serialize( $cache ) );
718 $cache[
'EXPIRY'] =
wfTimestamp( TS_MW, time() + self::WAN_TTL );
719 unset( $cache[
'EXCESSIVE'] );
730 private function isLanguageLoaded( $lang ) {
737 return $this->cache->hasField( $lang,
'VERSION' );
751 private function isMainCacheable( $name, $code =
null ) {
753 $name = $this->contLang->lcfirst( $name );
756 if ( strpos( $name,
'conversiontable/' ) === 0 ) {
759 $msg = preg_replace(
'/\/[a-z0-9-]{2,}$/',
'', $name );
761 if ( $code ===
null ) {
763 if ( $this->systemMessageNames ===
null ) {
764 $this->systemMessageNames = array_fill_keys(
765 $this->localisationCache->getSubitemList( $this->contLangCode,
'messages' ),
768 return isset( $this->systemMessageNames[$msg] );
771 return $this->localisationCache->getSubitem( $code,
'messages', $msg ) !==
null;
782 private function separateCacheableRows( $res ) {
783 if ( $this->adaptive ) {
788 $uncacheableRows = [];
789 foreach ( $res as $row ) {
790 if ( $this->isMainCacheable( $row->page_title ) ) {
791 $cacheableRows[] = $row;
793 $uncacheableRows[] = $row;
796 return [ $cacheableRows, $uncacheableRows ];
811 if ( strpos( $title,
'/' ) !==
false && $code === $this->contLangCode ) {
817 if ( $text ===
false ) {
819 $this->cache->setField( $code, $title,
'!NONEXISTENT' );
822 $this->cache->setField( $code, $title,
' ' . $text );
826 DeferredUpdates::addUpdate(
828 DeferredUpdates::PRESEND
838 [ $scopedLock ] = $this->getReentrantScopedLock( $code );
839 if ( !$scopedLock ) {
840 foreach ( $replacements as [ $title ] ) {
841 $this->logger->error(
842 __METHOD__ .
': could not acquire lock to update {title} ({code})',
843 [
'title' => $title,
'code' => $code ] );
851 if ( $this->load( $code, self::FOR_UPDATE ) ) {
852 $cache = $this->cache->get( $code );
855 $cache = $this->loadFromDB( $code, self::FOR_UPDATE );
858 $newTextByTitle = [];
862 $wikiPageFactory = MediaWikiServices::getInstance()->getWikiPageFactory();
863 foreach ( $replacements as [ $title ] ) {
864 $page = $wikiPageFactory->newFromTitle( Title::makeTitle(
NS_MEDIAWIKI, $title ) );
865 $page->loadPageData( IDBAccessObject::READ_LATEST );
866 $text = $this->getMessageTextFromContent( $page->getContent() );
868 $newTextByTitle[$title] = $text ??
'';
870 if ( !is_string( $text ) ) {
871 $cache[$title] =
'!NONEXISTENT';
872 } elseif ( strlen( $text ) > $this->maxEntrySize ) {
873 $cache[$title] =
'!TOO BIG';
874 $newBigTitles[$title] = $page->getLatest();
876 $cache[$title] =
' ' . $text;
883 $cache[
'HASH'] = md5( serialize( $cache + [
'EXCESSIVE' => $newBigTitles ] ) );
885 foreach ( $newBigTitles as $title => $id ) {
887 $this->wanCache->set(
888 $this->bigMessageCacheKey( $cache[
'HASH'], $title ),
889 ' ' . $newTextByTitle[$title],
895 $cache[
'LATEST'] = time();
897 $this->cache->set( $code, $cache );
902 $this->saveToCaches( $cache,
'all', $code );
904 ScopedCallback::consume( $scopedLock );
908 $this->wanCache->touchCheckKey( $this->
getCheckKey( $code ) );
911 $blobStore = MediaWikiServices::getInstance()->getResourceLoader()->getMessageBlobStore();
912 foreach ( $replacements as [ $title, $msg ] ) {
913 $blobStore->updateMessage( $this->contLang->lcfirst( $msg ) );
914 $this->hookRunner->onMessageCacheReplace( $title, $newTextByTitle[$title] );
924 private function isCacheExpired( $cache ) {
925 return !isset( $cache[
'VERSION'] ) ||
926 !isset( $cache[
'EXPIRY'] ) ||
940 private function saveToCaches( array $cache, $dest, $code =
false ) {
941 if ( $dest ===
'all' ) {
942 $cacheKey = $this->clusterCache->makeKey(
'messages', $code );
943 $success = $this->clusterCache->set( $cacheKey, $cache );
944 $this->setValidationHash( $code, $cache );
949 $this->saveToLocalCache( $code, $cache );
960 private function getValidationHash( $code ) {
962 $value = $this->wanCache->get(
963 $this->wanCache->makeKey(
'messages', $code,
'hash',
'v1' ),
965 [ $this->getCheckKey( $code ) ]
969 $hash = $value[
'hash'];
970 if ( ( time() - $value[
'latest'] ) < WANObjectCache::TTL_MINUTE ) {
976 $expired = ( $curTTL < 0 );
984 return [ $hash, $expired ];
997 private function setValidationHash( $code, array $cache ) {
998 $this->wanCache->set(
999 $this->wanCache->makeKey(
'messages', $code,
'hash',
'v1' ),
1001 'hash' => $cache[
'HASH'],
1002 'latest' => $cache[
'LATEST'] ?? 0
1004 WANObjectCache::TTL_INDEFINITE
1014 private function getReentrantScopedLock( $code, $timeout = self::WAIT_SEC ) {
1015 $key = $this->clusterCache->makeKey(
'messages', $code );
1017 $watchPoint = $this->clusterCache->watchErrors();
1018 $scopedLock = $this->clusterCache->getScopedLock(
1024 $error = ( !$scopedLock && $this->clusterCache->getLastError( $watchPoint ) );
1026 return [ $scopedLock, $error ];
1065 public function get( $key, $useDB =
true, $language =
null, &$usedKey =
'' ) {
1066 if ( is_int( $key ) ) {
1068 $key = (string)$key;
1069 } elseif ( !is_string( $key ) ) {
1070 throw new TypeError(
'Message key must be a string' );
1071 } elseif ( $key ===
'' ) {
1076 $language ??= $this->contLang;
1077 $language = $this->getLanguageObject( $language );
1080 $lckey = self::normalizeKey( $key );
1083 if ( $this->messageKeyOverrides ===
null ) {
1084 $this->messageKeyOverrides = [];
1085 $this->hookRunner->onMessageCacheFetchOverrides( $this->messageKeyOverrides );
1088 if ( isset( $this->messageKeyOverrides[$lckey] ) ) {
1089 $override = $this->messageKeyOverrides[$lckey];
1093 if ( is_string( $override ) ) {
1096 $lckey = $override( $lckey, $this, $language, $useDB );
1100 $this->hookRunner->onMessageCache__get( $lckey );
1105 $message = $this->getMessageFromFallbackChain(
1112 if ( $message ===
false ) {
1113 $parts = explode(
'/', $lckey );
1117 if ( count( $parts ) === 2 && $parts[1] !==
'' ) {
1118 $message = $this->localisationCache->getSubitem( $parts[1],
'messages', $parts[0] ) ??
false;
1123 if ( $message !==
false ) {
1125 $message = str_replace(
1162 private function getLanguageObject( $langcode ) {
1163 # Identify which language to get or create a language object for.
1164 # Using is_object here due to Stub objects.
1165 if ( is_object( $langcode ) ) {
1166 # Great, we already have the object (hopefully)!
1170 wfDeprecated( __METHOD__ .
' with not a Language object in $langcode',
'1.43' );
1171 if ( $langcode ===
true || $langcode === $this->contLangCode ) {
1172 # $langcode is the language code of the wikis content language object.
1173 # or it is a boolean and value is true
1174 return $this->contLang;
1178 if ( $langcode ===
false || $langcode ===
$wgLang->getCode() ) {
1179 # $langcode is the language code of user language object.
1180 # or it was a boolean and value is false
1184 $validCodes = array_keys( $this->languageNameUtils->getLanguageNames() );
1185 if ( in_array( $langcode, $validCodes ) ) {
1186 # $langcode corresponds to a valid language.
1187 return $this->langFactory->getLanguage( $langcode );
1190 # $langcode is a string, but not a valid language code; use content language.
1191 $this->logger->debug(
'Invalid language code passed to' . __METHOD__ .
', falling back to content language.' );
1192 return $this->contLang;
1207 private function getMessageFromFallbackChain( $lang, $lckey, $useDB ) {
1211 $message = $this->getMessageForLang( $lang, $lckey, $useDB, $alreadyTried );
1212 if ( $message !==
false ) {
1217 $message = $this->getMessageForLang( $this->contLang, $lckey, $useDB, $alreadyTried );
1231 private function getMessageForLang( $lang, $lckey, $useDB, &$alreadyTried ) {
1232 $langcode = $lang->getCode();
1236 $uckey = $this->contLang->ucfirst( $lckey );
1238 if ( !isset( $alreadyTried[$langcode] ) ) {
1240 $this->getMessagePageName( $langcode, $uckey ),
1243 if ( $message !==
false ) {
1246 $alreadyTried[$langcode] =
true;
1255 if ( $langcode ===
'qqx' ) {
1258 $langcode ===
'x-xss' &&
1259 $this->useXssLanguage &&
1260 !in_array( $lckey, $this->rawHtmlMessages,
true )
1262 $xssViaInnerHtml =
"<script>alert('$lckey')</script>";
1263 $xssViaAttribute =
'">' . $xssViaInnerHtml .
'<x y="';
1264 return $xssViaInnerHtml . $xssViaAttribute .
'($*)';
1268 [ $defaultMessage, $messageSource ] =
1269 $this->localisationCache->getSubitemWithSource( $langcode,
'messages', $lckey );
1270 if ( $messageSource === $langcode ) {
1271 return $defaultMessage;
1276 $fallbackChain = $this->languageFallback->getAll( $langcode );
1278 foreach ( $fallbackChain as $code ) {
1279 if ( isset( $alreadyTried[$code] ) ) {
1285 $this->getMessagePageName( $code, $uckey ), $code );
1287 if ( $message !==
false ) {
1290 $alreadyTried[$code] =
true;
1294 if ( $code === $messageSource ) {
1295 return $defaultMessage;
1300 return $defaultMessage ??
false;
1310 private function getMessagePageName( $langcode, $uckey ) {
1311 if ( $langcode === $this->contLangCode ) {
1315 return "$uckey/$langcode";
1335 $this->load( $code );
1337 $entry = $this->cache->getField( $code, $title );
1339 if ( $entry !==
null ) {
1341 if ( substr( $entry, 0, 1 ) ===
' ' ) {
1343 return (
string)substr( $entry, 1 );
1344 } elseif ( $entry ===
'!NONEXISTENT' ) {
1350 $entry = $this->loadCachedMessagePageEntry(
1353 $this->cache->getField( $code,
'HASH' )
1357 if ( !$this->isMainCacheable( $title, $code ) ) {
1361 $entry = $this->loadCachedMessagePageEntry(
1364 $this->cache->getField( $code,
'HASH' )
1367 if ( $entry ===
null || substr( $entry, 0, 1 ) !==
' ' ) {
1371 $this->hookRunner->onMessagesPreLoad( $title, $message, $code );
1372 if ( $message !==
false ) {
1373 $this->cache->setField( $code, $title,
' ' . $message );
1375 $this->cache->setField( $code, $title,
'!NONEXISTENT' );
1382 if ( $entry !==
false && substr( $entry, 0, 1 ) ===
' ' ) {
1383 if ( $this->cacheVolatile[$code] ) {
1385 $this->logger->debug(
1386 __METHOD__ .
': loading volatile key \'{titleKey}\'',
1387 [
'titleKey' => $title,
'code' => $code ] );
1389 $this->cache->setField( $code, $title, $entry );
1392 return (
string)substr( $entry, 1 );
1395 $this->cache->setField( $code, $title,
'!NONEXISTENT' );
1406 private function loadCachedMessagePageEntry( $dbKey, $code, $hash ) {
1407 $fname = __METHOD__;
1408 return $this->srvCache->getWithSetCallback(
1409 $this->srvCache->makeKey(
'messages-big', $hash, $dbKey ),
1410 BagOStuff::TTL_HOUR,
1411 function () use ( $code, $dbKey, $hash, $fname ) {
1412 return $this->wanCache->getWithSetCallback(
1413 $this->bigMessageCacheKey( $hash, $dbKey ),
1415 function ( $oldValue, &$ttl, &$setOpts ) use ( $dbKey, $code, $fname ) {
1417 $setOpts += Database::getCacheSetOptions(
1418 MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase()
1424 $revision = MediaWikiServices::getInstance()
1425 ->getRevisionLookup()
1426 ->getKnownCurrentRevision( $title );
1430 return '!NONEXISTENT';
1432 $content = $revision->getContent( SlotRecord::MAIN );
1434 $message = $this->getMessageTextFromContent( $content );
1436 $this->logger->warning(
1437 $fname .
': failed to load page text for \'{titleKey}\'',
1438 [
'titleKey' => $dbKey,
'code' => $code ]
1443 if ( !is_string( $message ) ) {
1448 return '!NONEXISTENT';
1451 return ' ' . $message;
1467 if ( !str_contains( $message,
'{{' ) ) {
1471 $popts = $this->getParserOptions();
1472 $popts->setInterfaceMessage( $interface );
1473 $popts->setTargetLanguage( $language );
1475 $userlang = $popts->setUserLang( $language );
1478 $parser = $this->getParser();
1480 return '<span class="error">Message transform depth limit exceeded</span>';
1482 $message = $parser->transformMsg( $message, $popts, $page );
1486 $popts->setUserLang( $userlang );
1495 private function getParser(): ?
Parser {
1496 if ( $this->curParser >= self::MAX_PARSER_DEPTH ) {
1497 $this->logger->debug( __METHOD__ .
": Refusing to create a new parser with index {$this->curParser}" );
1500 if ( !isset( $this->parsers[ $this->curParser ] ) ) {
1501 $this->logger->debug( __METHOD__ .
": Creating a new parser with index {$this->curParser}" );
1502 $this->parsers[ $this->curParser ] = $this->parserFactory->create();
1504 return $this->parsers[ $this->curParser ];
1520 bool $linestart =
true,
1521 bool $interface =
false,
1525 'allowTOC' => false,
1526 'enableSectionEditLinks' => false,
1532 'userLang' => $language,
1535 $po = $this->parse( $text, $contextPage, $linestart, $interface, $language );
1536 if ( is_string( $po ) ) {
1540 return MediaWikiServices::getInstance()->getDefaultOutputPipeline()
1541 ->run( $po, $this->getParserOptions(), $options );
1553 $interface =
false, $language =
null
1558 $popts = $this->getParserOptions();
1559 $popts->setInterfaceMessage( $interface );
1561 if ( is_string( $language ) ) {
1562 $language = $this->langFactory->getLanguage( $language );
1564 $popts->setTargetLanguage( $language );
1567 $logger = LoggerFactory::getInstance(
'GlobalTitleFail' );
1569 __METHOD__ .
' called with no title set.',
1570 [
'exception' =>
new RuntimeException ]
1578 $page = PageReferenceValue::localReference(
1580 'Badtitle/title not set in ' . __METHOD__
1586 $parser = $this->getParser();
1588 return '<span class="error">Message parse depth limit exceeded</span>';
1590 return $parser->parse( $text, $page, $popts, $linestart );
1597 $this->disable =
true;
1601 $this->disable =
false;
1617 return $this->disable;
1626 $langs = $this->languageNameUtils->getLanguageNames();
1627 foreach ( $langs as $code => $_ ) {
1628 $this->wanCache->touchCheckKey( $this->getCheckKey( $code ) );
1630 $this->cache->clear();
1638 $pieces = explode(
'/', $key );
1639 if ( count( $pieces ) < 2 ) {
1640 return [ $key, $this->contLangCode ];
1643 $lang = array_pop( $pieces );
1644 if ( !$this->languageNameUtils->getLanguageName(
1646 LanguageNameUtils::AUTONYMS,
1647 LanguageNameUtils::DEFINED
1649 return [ $key, $this->contLangCode ];
1652 $message = implode(
'/', $pieces );
1654 return [ $message, $lang ];
1667 $this->load( $code );
1668 if ( !$this->cache->has( $code ) ) {
1673 $cache = $this->cache->get( $code );
1674 unset( $cache[
'VERSION'] );
1675 unset( $cache[
'EXPIRY'] );
1676 unset( $cache[
'EXCESSIVE'] );
1678 $cache = array_diff( $cache, [
'!NONEXISTENT' ] );
1681 return array_map( [ $this->contLang,
'lcfirst' ], array_keys( $cache ) );
1694 $msgText = $this->getMessageTextFromContent( $content ) ??
false;
1696 $this->replace( $page->getDBkey(), $msgText );
1698 if ( $this->contLangConverter->hasVariants() ) {
1699 $this->contLangConverter->updateConversionTable( $page );
1708 return $this->wanCache->makeKey(
'messages', $code );
1715 private function getMessageTextFromContent( ?
Content $content =
null ) {
1717 if ( $content && $content->isRedirect() ) {
1720 } elseif ( $content ) {
1725 $msgText = $content->getWikitextForTransclusion();
1726 if ( $msgText ===
false || $msgText ===
null ) {
1729 $this->logger->warning(
1730 __METHOD__ .
": message content doesn't provide wikitext "
1731 .
"(content model: " . $content->getModel() .
")" );
1746 private function bigMessageCacheKey( $hash, $title ) {
1747 return $this->wanCache->makeKey(
'messages-big', $hash, $title );