use MediaWiki\Linker\LinkTarget;
use MediaWiki\MediaWikiServices;
use MediaWiki\Special\SpecialPageFactory;
+use Psr\Log\NullLogger;
use Wikimedia\ScopedCallback;
+use Psr\Log\LoggerInterface;
/**
* @defgroup Parser Parser
/** @var NamespaceInfo */
private $nsInfo;
+ /** @var LoggerInterface */
+ private $logger;
+
/**
* TODO Make this a const when HHVM support is dropped (T192166)
*
* @param SpecialPageFactory|null $spFactory
* @param LinkRendererFactory|null $linkRendererFactory
* @param NamespaceInfo|null $nsInfo
+ * @param LoggerInterface|null $logger
*/
public function __construct(
- $svcOptions = null, MagicWordFactory $magicWordFactory = null,
- Language $contLang = null, ParserFactory $factory = null, $urlProtocols = null,
- SpecialPageFactory $spFactory = null, $linkRendererFactory = null, $nsInfo = null
+ $svcOptions = null,
+ MagicWordFactory $magicWordFactory = null,
+ Language $contLang = null,
+ ParserFactory $factory = null,
+ $urlProtocols = null,
+ SpecialPageFactory $spFactory = null,
+ $linkRendererFactory = null,
+ $nsInfo = null,
+ $logger = null
) {
- $services = MediaWikiServices::getInstance();
if ( !$svcOptions || is_array( $svcOptions ) ) {
// Pre-1.34 calling convention is the first parameter is just ParserConf, the seventh is
// Config, and the eighth is LinkRendererFactory.
$this->mConf['preprocessorClass'] = self::getDefaultPreprocessorClass();
}
$this->svcOptions = new ServiceOptions( self::$constructorOptions,
- $this->mConf,
- func_num_args() > 6 ? func_get_arg( 6 ) : $services->getMainConfig()
+ $this->mConf, func_num_args() > 6
+ ? func_get_arg( 6 ) : MediaWikiServices::getInstance()->getMainConfig()
);
$linkRendererFactory = func_num_args() > 7 ? func_get_arg( 7 ) : null;
$nsInfo = func_num_args() > 8 ? func_get_arg( 8 ) : null;
self::EXT_LINK_URL_CLASS . '*)\p{Zs}*([^\]\\x00-\\x08\\x0a-\\x1F\\x{FFFD}]*?)\]/Su';
$this->magicWordFactory = $magicWordFactory ??
- $services->getMagicWordFactory();
+ MediaWikiServices::getInstance()->getMagicWordFactory();
- $this->contLang = $contLang ?? $services->getContentLanguage();
+ $this->contLang = $contLang ?? MediaWikiServices::getInstance()->getContentLanguage();
- $this->factory = $factory ?? $services->getParserFactory();
- $this->specialPageFactory = $spFactory ?? $services->getSpecialPageFactory();
- $this->linkRendererFactory = $linkRendererFactory ?? $services->getLinkRendererFactory();
- $this->nsInfo = $nsInfo ?? $services->getNamespaceInfo();
+ $this->factory = $factory ?? MediaWikiServices::getInstance()->getParserFactory();
+ $this->specialPageFactory = $spFactory ??
+ MediaWikiServices::getInstance()->getSpecialPageFactory();
+ $this->linkRendererFactory = $linkRendererFactory ??
+ MediaWikiServices::getInstance()->getLinkRendererFactory();
+ $this->nsInfo = $nsInfo ?? MediaWikiServices::getInstance()->getNamespaceInfo();
+ $this->logger = $logger ?: new NullLogger();
}
/**
*/
public function clearState() {
$this->firstCallInit();
- $this->mOutput = new ParserOutput;
- $this->mOptions->registerWatcher( [ $this->mOutput, 'recordOption' ] );
+ $this->resetOutput();
$this->mAutonumber = 0;
$this->mIncludeCount = [];
$this->mLinkHolders = new LinkHolderArray( $this );
Hooks::run( 'ParserClearState', [ &$parser ] );
}
+ /**
+ * Reset the ParserOutput
+ */
+ public function resetOutput() {
+ $this->mOutput = new ParserOutput;
+ $this->mOptions->registerWatcher( [ $this->mOutput, 'recordOption' ] );
+ }
+
/**
* Convert wikitext to HTML
* Do not call this function recursively.
* @param ParserOptions $options
* @param bool $linestart
* @param bool $clearState
- * @param int|null $revid Number to pass in {{REVISIONID}}
+ * @param int|null $revid ID of the revision being rendered. This is used to render
+ * REVISION* magic words. 0 means that any current revision will be used. Null means
+ * that {{REVISIONID}}/{{REVISIONUSER}} will be empty and {{REVISIONTIMESTAMP}} will
+ * use the current timestamp.
* @return ParserOutput A ParserOutput
* @return-taint escaped
*/
return $this->mStripList;
}
+ /**
+ * Get the StripState
+ *
+ * @return StripState
+ */
+ public function getStripState() {
+ return $this->mStripState;
+ }
+
/**
* Add an item to the strip state
* Returns the unique tag which must be inserted into the stripped text
$value = wfEscapeWikiText( $subjPage->getPrefixedURL() );
break;
case 'pageid': // requested in T25427
- $pageid = $this->getTitle()->getArticleID();
- if ( $pageid == 0 ) {
- # 0 means the page doesn't exist in the database,
- # which means the user is previewing a new page.
- # The vary-revision flag must be set, because the magic word
- # will have a different value once the page is saved.
- $this->mOutput->setFlag( 'vary-revision' );
- wfDebug( __METHOD__ . ": {{PAGEID}} used in a new page, setting vary-revision" );
+ # Inform the edit saving system that getting the canonical output
+ # after page insertion requires a parse that used that exact page ID
+ $this->setOutputFlag( 'vary-page-id', '{{PAGEID}} used' );
+ $value = $this->mTitle->getArticleID();
+ if ( !$value ) {
+ $value = $this->mOptions->getSpeculativePageId();
+ if ( $value ) {
+ $this->mOutput->setSpeculativePageIdUsed( $value );
+ }
}
- $value = $pageid ?: null;
break;
case 'revisionid':
if (
if ( $this->getRevisionId() || $this->mOptions->getSpeculativeRevId() ) {
$value = '-';
} else {
- $this->mOutput->setFlag( 'vary-revision-exists' );
- wfDebug( __METHOD__ . ": {{REVISIONID}} used, setting vary-revision-exists" );
+ $this->setOutputFlag( 'vary-revision-exists', '{{REVISIONID}} used' );
$value = '';
}
} else {
# Inform the edit saving system that getting the canonical output after
- # revision insertion requires another parse using the actual revision ID
- $this->mOutput->setFlag( 'vary-revision-id' );
- wfDebug( __METHOD__ . ": {{REVISIONID}} used, setting vary-revision-id" );
+ # revision insertion requires a parse that used that exact revision ID
+ $this->setOutputFlag( 'vary-revision-id', '{{REVISIONID}} used' );
$value = $this->getRevisionId();
if ( $value === 0 ) {
$rev = $this->getRevisionObject();
case 'revisionuser':
# Inform the edit saving system that getting the canonical output after
# revision insertion requires a parse that used the actual user ID
- $this->mOutput->setFlag( 'vary-user' );
- wfDebug( __METHOD__ . ": {{REVISIONUSER}} used, setting vary-user" );
+ $this->setOutputFlag( 'vary-user', '{{REVISIONUSER}} used' );
$value = $this->getRevisionUser();
break;
case 'revisionsize':
if ( $resNow !== $resThen ) {
# Inform the edit saving system that getting the canonical output after
# revision insertion requires a parse that used an actual revision timestamp
- $this->mOutput->setFlag( 'vary-revision-timestamp' );
- wfDebug( __METHOD__ . ": $variable used, setting vary-revision-timestamp" );
+ $this->setOutputFlag( 'vary-revision-timestamp', "$variable used" );
}
}
if ( $frame === false ) {
$frame = $this->getPreprocessor()->newFrame();
} elseif ( !( $frame instanceof PPFrame ) ) {
- wfDebug( __METHOD__ . " called using plain parameters instead of "
- . "a PPFrame instance. Creating custom frame.\n" );
+ $this->logger->debug(
+ __METHOD__ . " called using plain parameters instead of " .
+ "a PPFrame instance. Creating custom frame."
+ );
$frame = $this->getPreprocessor()->newCustomFrame( $frame );
}
}
} elseif ( $this->nsInfo->isNonincludable( $title->getNamespace() ) ) {
$found = false; # access denied
- wfDebug( __METHOD__ . ": template inclusion denied for " .
- $title->getPrefixedDBkey() . "\n" );
+ $this->logger->debug(
+ __METHOD__ .
+ ": template inclusion denied for " . $title->getPrefixedDBkey()
+ );
} else {
list( $text, $title ) = $this->getTemplateDom( $title );
if ( $text !== false ) {
$this->addTrackingCategory( 'template-loop-category' );
$this->mOutput->addWarning( wfMessage( 'template-loop-warning',
wfEscapeWikiText( $titleText ) )->text() );
- wfDebug( __METHOD__ . ": template loop broken at '$titleText'\n" );
+ $this->logger->debug( __METHOD__ . ": template loop broken at '$titleText'" );
}
}
// Defaults to Parser::statelessFetchTemplate()
$templateCb = $this->mOptions->getTemplateCallback();
$stuff = call_user_func( $templateCb, $title, $this );
- // We use U+007F DELETE to distinguish strip markers from regular text.
+ $rev = $stuff['revision'] ?? null;
$text = $stuff['text'];
if ( is_string( $stuff['text'] ) ) {
+ // We use U+007F DELETE to distinguish strip markers from regular text
$text = strtr( $text, "\x7f", "?" );
}
$finalTitle = $stuff['finalTitle'] ?? $title;
- if ( isset( $stuff['deps'] ) ) {
- foreach ( $stuff['deps'] as $dep ) {
- $this->mOutput->addTemplate( $dep['title'], $dep['page_id'], $dep['rev_id'] );
- if ( $dep['title']->equals( $this->getTitle() ) ) {
- // Self-transclusion; final result may change based on the new page version
- $this->mOutput->setFlag( 'vary-revision' );
- wfDebug( __METHOD__ . ": self transclusion, setting vary-revision" );
- }
+ foreach ( ( $stuff['deps'] ?? [] ) as $dep ) {
+ $this->mOutput->addTemplate( $dep['title'], $dep['page_id'], $dep['rev_id'] );
+ if ( $dep['title']->equals( $this->getTitle() ) && $rev instanceof Revision ) {
+ // Self-transclusion; final result may change based on the new page version
+ $this->setOutputFlag( 'vary-revision-sha1', 'Self transclusion' );
+ $this->getOutput()->setRevisionUsedSha1Base36( $rev->getSha1() );
}
}
+
return [ $text, $finalTitle ];
}
$text = $skip = false;
$finalTitle = $title;
$deps = [];
+ $rev = null;
# Loop to fetch the article, with up to 1 redirect
for ( $i = 0; $i < 2 && is_object( $title ); $i++ ) {
$deps[] = [
'title' => $title,
'page_id' => $title->getArticleID(),
- 'rev_id' => $rev_id ];
+ 'rev_id' => $rev_id
+ ];
if ( $rev && !$title->equals( $rev->getTitle() ) ) {
# We fetched a rev from a different title; register it too...
$deps[] = [
'title' => $rev->getTitle(),
'page_id' => $rev->getPage(),
- 'rev_id' => $rev_id ];
+ 'rev_id' => $rev_id
+ ];
}
if ( $rev ) {
$title = $content->getRedirectTarget();
}
return [
+ 'revision' => $rev,
'text' => $text,
'finalTitle' => $finalTitle,
- 'deps' => $deps ];
+ 'deps' => $deps
+ ];
}
/**
'~~~' => $sigText
] );
# The main two signature forms used above are time-sensitive
- $this->mOutput->setFlag( 'user-signature' );
+ $this->setOutputFlag( 'user-signature', 'User signature detected' );
}
# Context links ("pipe tricks"): [[|name]] and [[name (context)|]]
if ( mb_strlen( $nickname ) > $this->svcOptions->get( 'MaxSigChars' ) ) {
$nickname = $username;
- wfDebug( __METHOD__ . ": $username has overlong signature.\n" );
+ $this->logger->debug( __METHOD__ . ": $username has overlong signature." );
} elseif ( $fancySig !== false ) {
# Sig. might contain markup; validate this
if ( $this->validateSig( $nickname ) !== false ) {
} else {
# Failed to validate; fall back to the default
$nickname = $username;
- wfDebug( __METHOD__ . ": $username has bad XML tags in signature.\n" );
+ $this->logger->debug( __METHOD__ . ": $username has bad XML tags in signature." );
}
}
* @param ParserOptions $options
* @param int $outputType
* @param bool $clearState
+ * @param int|null $revId
*/
public function startExternalParse( Title $title = null, ParserOptions $options,
- $outputType, $clearState = true
+ $outputType, $clearState = true, $revId = null
) {
$this->startParse( $title, $options, $outputType, $clearState );
+ if ( $revId !== null ) {
+ $this->mRevisionId = $revId;
+ }
}
/**
$handlerOptions[$paramName] = $match;
} else {
// Guess not, consider it as caption.
- wfDebug( "$parameterMatch failed parameter validation\n" );
+ $this->logger->debug(
+ "$parameterMatch failed parameter validation" );
$label = $parameterMatch;
}
}
* @deprecated since 1.28; use getOutput()->updateCacheExpiry()
*/
public function disableCache() {
- wfDebug( "Parser output marked as uncacheable.\n" );
+ $this->logger->debug( "Parser output marked as uncacheable." );
if ( !$this->mOutput ) {
throw new MWException( __METHOD__ .
" can only be called when actually parsing something" );
* @since 1.23 (public since 1.23)
*/
public function getRevisionObject() {
- if ( !is_null( $this->mRevisionObject ) ) {
+ if ( $this->mRevisionObject ) {
return $this->mRevisionObject;
}
// NOTE: try to get the RevisionObject even if mRevisionId is null.
- // This is useful when parsing revision that has not yet been saved.
+ // This is useful when parsing a 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
+ $this->mOptions->getCurrentRevisionCallback(),
+ $this->getTitle(),
+ $this
);
if ( $this->mRevisionId === null && $rev && $rev->getId() ) {
OutputPage::setupOOUI();
$this->mOutput->setEnableOOUI( true );
}
+
+ /**
+ * @param string $flag
+ * @param string $reason
+ */
+ protected function setOutputFlag( $flag, $reason ) {
+ $this->mOutput->setFlag( $flag );
+ $name = $this->mTitle->getPrefixedText();
+ $this->logger->debug( __METHOD__ . ": set $flag flag on '$name'; $reason" );
+ }
}