* @return DerivedPageDataUpdater
*/
private function newDerivedDataUpdater() {
- global $wgContLang, $wgRCWatchCategoryMembership, $wgArticleCountMethod;
+ global $wgRCWatchCategoryMembership, $wgArticleCountMethod;
$derivedDataUpdater = new DerivedPageDataUpdater(
$this, // NOTE: eventually, PageUpdater should not know about WikiPage
$this->getParserCache(),
JobQueueGroup::singleton(),
MessageCache::singleton(),
- $wgContLang,
+ MediaWikiServices::getInstance()->getContentLanguage(),
LoggerFactory::getInstance( 'SaveParse' )
);
* Purges pages that include this page if the text was changed here.
* Every 100th edit, prune the recent changes table.
*
- * @deprecated since 1.32, use PageUpdater::doEditUpdates instead.
+ * @deprecated since 1.32, use PageUpdater::doUpdates instead.
*
* @param Revision $revision
* @param User $user User object that did the revision
* @return string
*/
protected function formatExpiry( $expiry ) {
- global $wgContLang;
-
if ( $expiry != 'infinity' ) {
+ $contLang = MediaWikiServices::getInstance()->getContentLanguage();
return wfMessage(
'protect-expiring',
- $wgContLang->timeanddate( $expiry, false, false ),
- $wgContLang->date( $expiry, false, false ),
- $wgContLang->time( $expiry, false, false )
+ $contLang->timeanddate( $expiry, false, false ),
+ $contLang->date( $expiry, false, false ),
+ $contLang->time( $expiry, false, false )
)->inContentLanguage()->text();
} else {
return wfMessage( 'protect-expiry-indefinite' )
* @return string
*/
public function protectDescriptionLog( array $limit, array $expiry ) {
- global $wgContLang;
-
$protectDescriptionLog = '';
+ $dirMark = MediaWikiServices::getInstance()->getContentLanguage()->getDirMark();
foreach ( array_filter( $limit ) as $action => $restrictions ) {
$expiryText = $this->formatExpiry( $expiry[$action] );
- $protectDescriptionLog .= $wgContLang->getDirMark() .
+ $protectDescriptionLog .=
+ $dirMark .
"[$action=$restrictions] ($expiryText)";
}
public function commitRollback( $fromP, $summary, $bot,
&$resultDetails, User $guser, $tags = null
) {
- global $wgUseRCPatrol, $wgContLang;
+ global $wgUseRCPatrol;
$dbw = wfGetDB( DB_MASTER );
$targetEditorForPublic = $target->getUser( RevisionRecord::FOR_PUBLIC );
// Allow the custom summary to use the same args as the default message
+ $contLang = MediaWikiServices::getInstance()->getContentLanguage();
$args = [
$targetEditorForPublic ? $targetEditorForPublic->getName() : null,
$currentEditorForPublic ? $currentEditorForPublic->getName() : null,
$s->rev_id,
- $wgContLang->timeanddate( wfTimestamp( TS_MW, $s->rev_timestamp ) ),
+ $contLang->timeanddate( wfTimestamp( TS_MW, $s->rev_timestamp ) ),
$current->getId(),
- $wgContLang->timeanddate( $current->getTimestamp() )
+ $contLang->timeanddate( $current->getTimestamp() )
];
if ( $summary instanceof Message ) {
$summary = $summary->params( $args )->inContentLanguage()->text();
// Image redirects
RepoGroup::singleton()->getLocalRepo()->invalidateImageRedirect( $title );
+
+ // Purge cross-wiki cache entities referencing this page
+ self::purgeInterwikiCheckKey( $title );
}
/**
// Clear file cache for this page only
HTMLFileCache::clearFileCache( $title );
+ // Purge ?action=info cache
$revid = $revision ? $revision->getId() : null;
DeferredUpdates::addCallableUpdate( function () use ( $title, $revid ) {
InfoAction::invalidateCache( $title, $revid );
} );
+
+ // Purge cross-wiki cache entities referencing this page
+ self::purgeInterwikiCheckKey( $title );
}
/**#@-*/
+ /**
+ * Purge the check key for cross-wiki cache entries referencing this page
+ *
+ * @param Title $title
+ */
+ private static function purgeInterwikiCheckKey( Title $title ) {
+ global $wgEnableScaryTranscluding;
+
+ if ( !$wgEnableScaryTranscluding ) {
+ return; // @todo: perhaps this wiki is only used as a *source* for content?
+ }
+
+ DeferredUpdates::addCallableUpdate( function () use ( $title ) {
+ $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
+ $cache->resetCheckKey(
+ // Do not include the namespace since there can be multiple aliases to it
+ // due to different namespace text definitions on different wikis. This only
+ // means that some cache invalidations happen that are not strictly needed.
+ $cache->makeGlobalKey( 'interwiki-page', wfWikiID(), $title->getDBkey() )
+ );
+ } );
+ }
+
/**
* Returns a list of categories this page is a member of.
* Results will include hidden categories
public function getMutableCacheKeys( WANObjectCache $cache ) {
$linkCache = MediaWikiServices::getInstance()->getLinkCache();
- return $linkCache->getMutableCacheKeys( $cache, $this->getTitle()->getTitleValue() );
+ return $linkCache->getMutableCacheKeys( $cache, $this->getTitle() );
}
}
*/
use MediaWiki\Linker\LinkRenderer;
use MediaWiki\MediaWikiServices;
+use MediaWiki\Special\SpecialPageFactory;
use Wikimedia\ScopedCallback;
/**
* - Parser::getPreloadText()
* removes <noinclude> sections and <includeonly> tags
*
- * Globals used:
- * object: $wgContLang
- *
* @warning $wgUser or $wgTitle or $wgRequest or $wgLang. Keep them away!
*
* @par Settings:
public $mImageParams = [];
public $mImageParamsMagicArray = [];
public $mMarkerIndex = 0;
+ /**
+ * @var bool Whether firstCallInit still needs to be called
+ */
public $mFirstCall = true;
# Initialised by initialiseVariables()
*/
protected $mLinkRenderer;
+ /** @var MagicWordFactory */
+ private $magicWordFactory;
+
+ /** @var Language */
+ private $contLang;
+
+ /** @var ParserFactory */
+ private $factory;
+
+ /** @var SpecialPageFactory */
+ private $specialPageFactory;
+
/**
- * @param array $conf
+ * @param array $conf See $wgParserConf documentation
+ * @param MagicWordFactory|null $magicWordFactory
+ * @param Language|null $contLang Content language
+ * @param ParserFactory|null $factory
+ * @param string|null $urlProtocols As returned from wfUrlProtocols()
+ * @param SpecialPageFactory|null $spFactory
*/
- public function __construct( $conf = [] ) {
+ public function __construct(
+ array $conf = [], MagicWordFactory $magicWordFactory = null, Language $contLang = null,
+ ParserFactory $factory = null, $urlProtocols = null, SpecialPageFactory $spFactory = null
+ ) {
$this->mConf = $conf;
- $this->mUrlProtocols = wfUrlProtocols();
+ $this->mUrlProtocols = $urlProtocols ?? wfUrlProtocols();
$this->mExtLinkBracketedRegex = '/\[(((?i)' . $this->mUrlProtocols . ')' .
self::EXT_LINK_ADDR .
self::EXT_LINK_URL_CLASS . '*)\p{Zs}*([^\]\\x00-\\x08\\x0a-\\x1F\\x{FFFD}]*?)\]/Su';
$this->mPreprocessorClass = Preprocessor_Hash::class;
}
wfDebug( __CLASS__ . ": using preprocessor: {$this->mPreprocessorClass}\n" );
+
+ $services = MediaWikiServices::getInstance();
+ $this->magicWordFactory = $magicWordFactory ??
+ $services->getMagicWordFactory();
+
+ $this->contLang = $contLang ?? $services->getContentLanguage();
+
+ $this->factory = $factory ?? $services->getParserFactory();
+ $this->specialPageFactory = $spFactory ?? $services->getSpecialPageFactory();
}
/**
* @private
*/
public function clearState() {
- if ( $this->mFirstCall ) {
- $this->firstCallInit();
- }
+ $this->firstCallInit();
$this->mOutput = new ParserOutput;
$this->mOptions->registerWatcher( [ $this->mOutput, 'recordOption' ] );
$this->mAutonumber = 0;
return $this->mLinkRenderer;
}
+ /**
+ * Get the MagicWordFactory that this Parser is using
+ *
+ * @since 1.32
+ * @return MagicWordFactory
+ */
+ public function getMagicWordFactory() {
+ return $this->magicWordFactory;
+ }
+
+ /**
+ * Get the content language that this Parser is using
+ *
+ * @since 1.32
+ * @return Language
+ */
+ public function getContentLanguage() {
+ return $this->contLang;
+ }
+
/**
* Replaces all occurrences of HTML-style comments and the given tags
* in the text with a random marker and returns the next text. The output
if ( $useLinkPrefixExtension ) {
# Match the end of a line for a word that's not followed by whitespace,
# e.g. in the case of 'The Arab al[[Razi]]', 'al' will be matched
- global $wgContLang;
- $charset = $wgContLang->linkPrefixCharset();
+ $charset = $this->contLang->linkPrefixCharset();
$e2 = "/^((?>.*[^$charset]|))(.+)$/sDu";
}
*
* @private
*
- * @param string $index Magic variable identifier as mapped in MagicWord::$mVariableIDs
+ * @param string $index Magic variable identifier as mapped in MagicWordFactory::$mVariableIDs
* @param bool|PPFrame $frame
*
* @throws MWException
* @return string
*/
public function getVariableValue( $index, $frame = false ) {
- global $wgContLang, $wgSitename, $wgServer, $wgServerName;
+ global $wgSitename, $wgServer, $wgServerName;
global $wgArticlePath, $wgScriptPath, $wgStylePath;
if ( is_null( $this->mTitle ) ) {
$value = $this->getRevisionSize();
break;
case 'namespace':
- $value = str_replace( '_', ' ', $wgContLang->getNsText( $this->mTitle->getNamespace() ) );
+ $value = str_replace( '_', ' ',
+ $this->contLang->getNsText( $this->mTitle->getNamespace() ) );
break;
case 'namespacee':
- $value = wfUrlencode( $wgContLang->getNsText( $this->mTitle->getNamespace() ) );
+ $value = wfUrlencode( $this->contLang->getNsText( $this->mTitle->getNamespace() ) );
break;
case 'namespacenumber':
$value = $this->mTitle->getNamespace();
* @return string
*/
private function getRevisionTimestampSubstring( $start, $len, $mtts, $variable ) {
- global $wgContLang;
-
# Get the timezone-adjusted timestamp to be used for this revision
$resNow = substr( $this->getRevisionTimestamp(), $start, $len );
# Possibly set vary-revision if there is not yet an associated revision
if ( !$this->getRevisionObject() ) {
# Get the timezone-adjusted timestamp $mtts seconds in the future
$resThen = substr(
- $wgContLang->userAdjust( wfTimestamp( TS_MW, time() + $mtts ), '' ),
+ $this->contLang->userAdjust( wfTimestamp( TS_MW, time() + $mtts ), '' ),
$start,
$len
);
* @private
*/
public function initialiseVariables() {
- $variableIDs = MagicWord::getVariableIDs();
- $substIDs = MagicWord::getSubstIDs();
+ $variableIDs = $this->magicWordFactory->getVariableIDs();
+ $substIDs = $this->magicWordFactory->getSubstIDs();
- $this->mVariables = new MagicWordArray( $variableIDs );
- $this->mSubstWords = new MagicWordArray( $substIDs );
+ $this->mVariables = $this->magicWordFactory->newArray( $variableIDs );
+ $this->mSubstWords = $this->magicWordFactory->newArray( $substIDs );
}
/**
$id = $this->mVariables->matchStartToEnd( $part1 );
if ( $id !== false ) {
$text = $this->getVariableValue( $id, $frame );
- if ( MagicWord::getCacheTTL( $id ) > -1 ) {
- $this->mOutput->updateCacheExpiry( MagicWord::getCacheTTL( $id ) );
+ if ( $this->magicWordFactory->getCacheTTL( $id ) > -1 ) {
+ $this->mOutput->updateCacheExpiry(
+ $this->magicWordFactory->getCacheTTL( $id ) );
}
$found = true;
}
# MSG, MSGNW and RAW
if ( !$found ) {
# Check for MSGNW:
- $mwMsgnw = MagicWord::get( 'msgnw' );
+ $mwMsgnw = $this->magicWordFactory->get( 'msgnw' );
if ( $mwMsgnw->matchStartAndRemove( $part1 ) ) {
$nowiki = true;
} else {
# Remove obsolete MSG:
- $mwMsg = MagicWord::get( 'msg' );
+ $mwMsg = $this->magicWordFactory->get( 'msg' );
$mwMsg->matchStartAndRemove( $part1 );
}
# Check for RAW:
- $mwRaw = MagicWord::get( 'raw' );
+ $mwRaw = $this->magicWordFactory->get( 'raw' );
if ( $mwRaw->matchStartAndRemove( $part1 ) ) {
$forceRawInterwiki = true;
}
&& $this->mOptions->getAllowSpecialInclusion()
&& $this->ot['html']
) {
- $specialPage = SpecialPageFactory::getPage( $title->getDBkey() );
+ $specialPage = $this->specialPageFactory->getPage( $title->getDBkey() );
// Pass the template arguments as URL parameters.
// "uselang" will have no effect since the Language object
// is forced to the one defined in ParserOptions.
$context->setUser( User::newFromName( '127.0.0.1', false ) );
}
$context->setLanguage( $this->mOptions->getUserLangObj() );
- $ret = SpecialPageFactory::capturePath(
- $title, $context, $this->getLinkRenderer() );
+ $ret = $this->specialPageFactory->capturePath( $title, $context, $this->getLinkRenderer() );
if ( $ret ) {
$text = $context->getOutput()->getHTML();
$this->mOutput->addOutputPageMetadata( $context->getOutput() );
* @return array
*/
public function callParserFunction( $frame, $function, array $args = [] ) {
- global $wgContLang;
-
# Case sensitive functions
if ( isset( $this->mFunctionSynonyms[1][$function] ) ) {
$function = $this->mFunctionSynonyms[1][$function];
} else {
# Case insensitive functions
- $function = $wgContLang->lc( $function );
+ $function = $this->contLang->lc( $function );
if ( isset( $this->mFunctionSynonyms[0][$function] ) ) {
$function = $this->mFunctionSynonyms[0][$function];
} else {
break;
}
} elseif ( $title->getNamespace() == NS_MEDIAWIKI ) {
- global $wgContLang;
- $message = wfMessage( $wgContLang->lcfirst( $title->getText() ) )->inContentLanguage();
+ $message = wfMessage( MediaWikiServices::getInstance()->getContentLanguage()->
+ lcfirst( $title->getText() ) )->inContentLanguage();
if ( !$message->exists() ) {
$text = false;
break;
* Transclude an interwiki link.
*
* @param Title $title
- * @param string $action
+ * @param string $action Usually one of (raw, render)
*
* @return string
*/
public function interwikiTransclude( $title, $action ) {
- global $wgEnableScaryTranscluding;
+ global $wgEnableScaryTranscluding, $wgTranscludeCacheExpiry;
if ( !$wgEnableScaryTranscluding ) {
return wfMessage( 'scarytranscludedisabled' )->inContentLanguage()->text();
}
$url = $title->getFullURL( [ 'action' => $action ] );
-
- if ( strlen( $url ) > 255 ) {
+ if ( strlen( $url ) > 1024 ) {
return wfMessage( 'scarytranscludetoolong' )->inContentLanguage()->text();
}
- return $this->fetchScaryTemplateMaybeFromCache( $url );
- }
- /**
- * @param string $url
- * @return mixed|string
- */
- public function fetchScaryTemplateMaybeFromCache( $url ) {
- global $wgTranscludeCacheExpiry;
- $dbr = wfGetDB( DB_REPLICA );
- $tsCond = $dbr->timestamp( time() - $wgTranscludeCacheExpiry );
- $obj = $dbr->selectRow( 'transcache', [ 'tc_time', 'tc_contents' ],
- [ 'tc_url' => $url, "tc_time >= " . $dbr->addQuotes( $tsCond ) ] );
- if ( $obj ) {
- return $obj->tc_contents;
- }
-
- $req = MWHttpRequest::factory( $url, [], __METHOD__ );
- $status = $req->execute(); // Status object
- if ( $status->isOK() ) {
- $text = $req->getContent();
- } elseif ( $req->getStatus() != 200 ) {
+ $wikiId = $title->getTransWikiID(); // remote wiki ID or false
+
+ $fname = __METHOD__;
+ $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
+
+ $data = $cache->getWithSetCallback(
+ $cache->makeGlobalKey(
+ 'interwiki-transclude',
+ ( $wikiId !== false ) ? $wikiId : 'external',
+ sha1( $url )
+ ),
+ $wgTranscludeCacheExpiry,
+ function ( $oldValue, &$ttl ) use ( $url, $fname, $cache ) {
+ $req = MWHttpRequest::factory( $url, [], $fname );
+
+ $status = $req->execute(); // Status object
+ if ( !$status->isOK() ) {
+ $ttl = $cache::TTL_UNCACHEABLE;
+ } elseif ( $req->getResponseHeader( 'X-Database-Lagged' ) !== null ) {
+ $ttl = min( $cache::TTL_LAGGED, $ttl );
+ }
+
+ return [
+ 'text' => $status->isOK() ? $req->getContent() : null,
+ 'code' => $req->getStatus()
+ ];
+ },
+ [
+ 'checkKeys' => ( $wikiId !== false )
+ ? [ $cache->makeGlobalKey( 'interwiki-page', $wikiId, $title->getDBkey() ) ]
+ : [],
+ 'pcGroup' => 'interwiki-transclude:5',
+ 'pcTTL' => $cache::TTL_PROC_LONG
+ ]
+ );
+
+ if ( is_string( $data['text'] ) ) {
+ $text = $data['text'];
+ } elseif ( $data['code'] != 200 ) {
// Though we failed to fetch the content, this status is useless.
- return wfMessage( 'scarytranscludefailed-httpstatus' )
- ->params( $url, $req->getStatus() /* HTTP status */ )->inContentLanguage()->text();
+ $text = wfMessage( 'scarytranscludefailed-httpstatus' )
+ ->params( $url, $data['code'] )->inContentLanguage()->text();
} else {
- return wfMessage( 'scarytranscludefailed', $url )->inContentLanguage()->text();
+ $text = wfMessage( 'scarytranscludefailed', $url )->inContentLanguage()->text();
}
- $dbw = wfGetDB( DB_MASTER );
- $dbw->replace( 'transcache', [ 'tc_url' ], [
- 'tc_url' => $url,
- 'tc_time' => $dbw->timestamp( time() ),
- 'tc_contents' => $text
- ] );
return $text;
}
*/
public function doDoubleUnderscore( $text ) {
# The position of __TOC__ needs to be recorded
- $mw = MagicWord::get( 'toc' );
+ $mw = $this->magicWordFactory->get( 'toc' );
if ( $mw->match( $text ) ) {
$this->mShowToc = true;
$this->mForceTocPosition = true;
}
# Now match and remove the rest of them
- $mwa = MagicWord::getDoubleUnderscoreArray();
+ $mwa = $this->magicWordFactory->getDoubleUnderscoreArray();
$this->mDoubleUnderscores = $mwa->matchAndRemove( $text );
if ( isset( $this->mDoubleUnderscores['nogallery'] ) ) {
* @return string
*/
private function pstPass2( $text, $user ) {
- global $wgContLang;
-
- # Note: This is the timestamp saved as hardcoded wikitext to
- # the database, we use $wgContLang here in order to give
- # everyone the same signature and use the default one rather
- # than the one selected in each user's preferences.
- # (see also T14815)
+ # Note: This is the timestamp saved as hardcoded wikitext to the database, we use
+ # $this->contLang here in order to give everyone the same signature and use the default one
+ # rather than the one selected in each user's preferences. (see also T14815)
$ts = $this->mOptions->getTimestamp();
$timestamp = MWTimestamp::getLocalInstance( $ts );
$ts = $timestamp->format( 'YmdHis' );
$tzMsg = $timestamp->getTimezoneMessage()->inContentLanguage()->text();
- $d = $wgContLang->timeanddate( $ts, false, false ) . " ($tzMsg)";
+ $d = $this->contLang->timeanddate( $ts, false, false ) . " ($tzMsg)";
# Variable replacement
# Because mOutputType is OT_WIKI, this will only process {{subst:xxx}} type tags
# @todo FIXME: Regex doesn't respect extension tags or nowiki
# => Move this logic to braceSubstitution()
- $substWord = MagicWord::get( 'subst' );
+ $substWord = $this->magicWordFactory->get( 'subst' );
$substRegex = '/\{\{(?!(?:' . $substWord->getBaseRegex() . '))/x' . $substWord->getRegexCase();
$substText = '{{' . $substWord->getSynonym( 0 );
* @return string|callable The old callback function for this name, if any
*/
public function setFunctionHook( $id, callable $callback, $flags = 0 ) {
- global $wgContLang;
-
$oldVal = isset( $this->mFunctionHooks[$id] ) ? $this->mFunctionHooks[$id][0] : null;
$this->mFunctionHooks[$id] = [ $callback, $flags ];
# Add to function cache
- $mw = MagicWord::get( $id );
+ $mw = $this->magicWordFactory->get( $id );
if ( !$mw ) {
throw new MWException( __METHOD__ . '() expecting a magic word identifier.' );
}
foreach ( $synonyms as $syn ) {
# Case
if ( !$sensitive ) {
- $syn = $wgContLang->lc( $syn );
+ $syn = $this->contLang->lc( $syn );
}
# Add leading hash
if ( !( $flags & self::SFH_NO_HASH ) ) {
* @return array
*/
public function getFunctionHooks() {
+ $this->firstCallInit();
return array_keys( $this->mFunctionHooks );
}
unset( $paramMap['img_width'] );
}
- $mwArray = new MagicWordArray( array_keys( $paramMap ) );
+ $mwArray = $this->magicWordFactory->newArray( array_keys( $paramMap ) );
$label = '';
$alt = '';
}
}
$this->mImageParams[$handlerClass] = $paramMap;
- $this->mImageParamsMagicArray[$handlerClass] = new MagicWordArray( array_keys( $paramMap ) );
+ $this->mImageParamsMagicArray[$handlerClass] =
+ $this->magicWordFactory->newArray( array_keys( $paramMap ) );
}
return [ $this->mImageParams[$handlerClass], $this->mImageParamsMagicArray[$handlerClass] ];
}
* @return array
*/
public function getTags() {
+ $this->firstCallInit();
return array_merge(
array_keys( $this->mTransparentTagHooks ),
array_keys( $this->mTagHooks ),
);
}
+ /**
+ * @since 1.32
+ * @return array
+ */
+ public function getFunctionSynonyms() {
+ $this->firstCallInit();
+ return $this->mFunctionSynonyms;
+ }
+
+ /**
+ * @since 1.32
+ * @return string
+ */
+ public function getUrlProtocols() {
+ return $this->mUrlProtocols;
+ }
+
/**
* Replace transparent tags in $text with the values given by the callbacks.
*
*/
public function getRevisionTimestamp() {
if ( is_null( $this->mRevisionTimestamp ) ) {
- global $wgContLang;
-
$revObject = $this->getRevisionObject();
$timestamp = $revObject ? $revObject->getTimestamp() : wfTimestampNow();
# Since this value will be saved into the parser cache, served
# to other users, and potentially even used inside links and such,
# it needs to be consistent for all visitors.
- $this->mRevisionTimestamp = $wgContLang->userAdjust( $timestamp, '' );
-
+ $this->mRevisionTimestamp = $this->contLang->userAdjust( $timestamp, '' );
}
return $this->mRevisionTimestamp;
}
* @return Parser A parser object that is not parsing anything
*/
public function getFreshParser() {
- global $wgParserConf;
if ( $this->mInParse ) {
- return new $wgParserConf['class']( $wgParserConf );
+ return $this->factory->create();
} else {
return $this;
}