*/
use MediaWiki\Linker\LinkRenderer;
use MediaWiki\MediaWikiServices;
+use MediaWiki\Special\SpecialPageFactory;
use Wikimedia\ScopedCallback;
/**
/** @var ParserFactory */
private $factory;
+ /** @var SpecialPageFactory */
+ private $specialPageFactory;
+
/**
* @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(
array $conf = [], MagicWordFactory $magicWordFactory = null, Language $contLang = null,
- ParserFactory $factory = null, $urlProtocols = null
+ ParserFactory $factory = null, $urlProtocols = null, SpecialPageFactory $spFactory = null
) {
$this->mConf = $conf;
$this->mUrlProtocols = $urlProtocols ?? wfUrlProtocols();
}
wfDebug( __CLASS__ . ": using preprocessor: {$this->mPreprocessorClass}\n" );
+ $services = MediaWikiServices::getInstance();
$this->magicWordFactory = $magicWordFactory ??
- MediaWikiServices::getInstance()->getMagicWordFactory();
+ $services->getMagicWordFactory();
- $this->contLang = $contLang ?? MediaWikiServices::getInstance()->getContentLanguage();
+ $this->contLang = $contLang ?? $services->getContentLanguage();
- $this->factory = $factory ?? MediaWikiServices::getInstance()->getParserFactory();
+ $this->factory = $factory ?? $services->getParserFactory();
+ $this->specialPageFactory = $spFactory ?? $services->getSpecialPageFactory();
}
/**
* Do not call this function recursively.
*
* @param string $text Text we want to parse
+ * @param-taint $text escapes_htmlnoent
* @param Title $title
* @param ParserOptions $options
* @param bool $linestart
* @param bool $clearState
* @param int|null $revid Number to pass in {{REVISIONID}}
* @return ParserOutput A ParserOutput
+ * @return-taint escaped
*/
public function parse(
$text, Title $title, ParserOptions $options,
# with CSS (T37247)
$class = $this->mOptions->getWrapOutputClass();
if ( $class !== false && !$this->mOptions->getInterfaceMessage() ) {
- $text = Html::rawElement( 'div', [ 'class' => $class ], $text );
+ $this->mOutput->addWrapperDivClass( $class );
}
$this->mOutput->setText( $text );
* $text are not expanded
*
* @param string $text Text extension wants to have parsed
+ * @param-taint $text escapes_htmlnoent
* @param bool|PPFrame $frame The frame to use for expanding any template variables
* @return string UNSAFE half-parsed HTML
+ * @return-taint escaped
*/
public function recursiveTagParse( $text, $frame = false ) {
// Avoid PHP 7.1 warning from passing $this by reference
* @since 1.25
*
* @param string $text Text extension wants to have parsed
+ * @param-taint $text escapes_htmlnoent
* @param bool|PPFrame $frame The frame to use for expanding any template variables
* @return string Fully parsed HTML
+ * @return-taint escaped
*/
public function recursiveTagParseFully( $text, $frame = false ) {
$text = $this->recursiveTagParse( $text, $frame );
* @private
*
* @param string $text The text to parse
+ * @param-taint $text escapes_html
* @param bool $isMain Whether this is being called from the main parse() function
* @param PPFrame|bool $frame A pre-processor frame
*
$this->mOutput->setFlag( 'vary-revision-id' );
wfDebug( __METHOD__ . ": {{REVISIONID}} used, setting vary-revision-id...\n" );
$value = $this->mRevisionId;
- if ( !$value && $this->mOptions->getSpeculativeRevIdCallback() ) {
- $value = call_user_func( $this->mOptions->getSpeculativeRevIdCallback() );
- $this->mOutput->setSpeculativeRevIdUsed( $value );
+
+ if ( !$value ) {
+ $rev = $this->getRevisionObject();
+ if ( $rev ) {
+ $value = $rev->getId();
+ }
+ }
+
+ if ( !$value ) {
+ $value = $this->mOptions->getSpeculativeRevId();
+ if ( $value ) {
+ $this->mOutput->setSpeculativeRevIdUsed( $value );
+ }
}
break;
case 'revisionday':
&& $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() );
* 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;
}
if ( !is_null( $this->mRevisionObject ) ) {
return $this->mRevisionObject;
}
- if ( is_null( $this->mRevisionId ) ) {
- return null;
- }
+ // NOTE: try to get the RevisionObject even if mRevisionId is null.
+ // This is useful when parsing revision that has not yet been saved.
+ // However, if we get back a saved revision even though we are in
+ // preview mode, we'll have to ignore it, see below.
+ // NOTE: This callback may be used to inject an OLD revision that was
+ // already loaded, so "current" is a bit of a misnomer. We can't just
+ // skip it if mRevisionId is set.
$rev = call_user_func(
$this->mOptions->getCurrentRevisionCallback(), $this->getTitle(), $this
);
- # If the parse is for a new revision, then the callback should have
- # already been set to force the object and should match mRevisionId.
- # If not, try to fetch by mRevisionId for sanity.
- if ( $rev && $rev->getId() != $this->mRevisionId ) {
+ if ( $this->mRevisionId === null && $rev && $rev->getId() ) {
+ // We are in preview mode (mRevisionId is null), and the current revision callback
+ // returned an existing revision. Ignore it and return null, it's probably the page's
+ // current revision, which is not what we want here. Note that we do want to call the
+ // callback to allow the unsaved revision to be injected here, e.g. for
+ // self-transclusion previews.
+ return null;
+ }
+
+ // If the parse is for a new revision, then the callback should have
+ // already been set to force the object and should match mRevisionId.
+ // If not, try to fetch by mRevisionId for sanity.
+ if ( $this->mRevisionId && $rev && $rev->getId() != $this->mRevisionId ) {
$rev = Revision::newFromId( $this->mRevisionId );
}