* @file
*/
+use MediaWiki\MediaWikiServices;
+
/**
* @ingroup API
*/
/** @var Content $pstContent */
private $pstContent = null;
+ /** @var bool */
+ private $contentIsDeleted = false, $contentIsSuppressed = false;
+
public function execute() {
// The data is hot but user-dependent, like page views, so we set vary cookies
$this->getMain()->setCacheMode( 'anon-public-user-private' );
$redirValues = null;
+ $needContent = isset( $prop['wikitext'] ) ||
+ isset( $prop['parsetree'] ) || $params['generatexml'];
+
// Return result
$result = $this->getResult();
$wgTitle = $titleObj;
$pageObj = WikiPage::factory( $titleObj );
list( $popts, $reset, $suppressCache ) = $this->makeParserOptions( $pageObj, $params );
-
- // If for some reason the "oldid" is actually the current revision, it may be cached
- // Deliberately comparing $pageObj->getLatest() with $rev->getId(), rather than
- // checking $rev->isCurrent(), because $pageObj is what actually ends up being used,
- // and if its ->getLatest() is outdated, $rev->isCurrent() won't tell us that.
- if ( !$suppressCache && $rev->getId() == $pageObj->getLatest() ) {
- // May get from/save to parser cache
- $p_result = $this->getParsedContent( $pageObj, $popts,
- $pageid, isset( $prop['wikitext'] ) );
- } else { // This is an old revision, so get the text differently
- $this->content = $rev->getContent( Revision::FOR_THIS_USER, $this->getUser() );
-
- if ( $this->section !== false ) {
- $this->content = $this->getSectionContent(
- $this->content, $this->msg( 'revid', $rev->getId() )
- );
- }
-
- // Should we save old revision parses to the parser cache?
- $p_result = $this->content->getParserOutput( $titleObj, $rev->getId(), $popts );
- }
+ $p_result = $this->getParsedContent(
+ $pageObj, $popts, $suppressCache, $pageid, $rev, $needContent
+ );
} else { // Not $oldid, but $pageid or $page
if ( $params['redirects'] ) {
$reqParams = [
}
list( $popts, $reset, $suppressCache ) = $this->makeParserOptions( $pageObj, $params );
-
- // Don't pollute the parser cache when setting options that aren't
- // in ParserOptions::optionsHash()
- /// @todo: This should be handled closer to the actual cache instead of here, see T110269
- $suppressCache = $suppressCache ||
- $params['disablepp'] ||
- $params['disablelimitreport'] ||
- $params['preview'] ||
- $params['sectionpreview'] ||
- $params['disabletidy'];
-
- if ( $suppressCache ) {
- $this->content = $this->getContent( $pageObj, $pageid );
- $p_result = $this->content->getParserOutput( $titleObj, null, $popts );
- } else {
- // Potentially cached
- $p_result = $this->getParsedContent( $pageObj, $popts, $pageid,
- isset( $prop['wikitext'] ) );
- }
+ $p_result = $this->getParsedContent(
+ $pageObj, $popts, $suppressCache, $pageid, null, $needContent
+ );
}
} else { // Not $oldid, $pageid, $page. Hence based on $text
$titleObj = Title::newFromText( $title );
if ( $params['onlypst'] ) {
// Build a result and bail out
$result_array = [];
+ if ( $this->contentIsDeleted ) {
+ $result_array['textdeleted'] = true;
+ }
+ if ( $this->contentIsSuppressed ) {
+ $result_array['textsuppressed'] = true;
+ }
$result_array['text'] = $this->pstContent->serialize( $format );
$result_array[ApiResult::META_BC_SUBELEMENTS][] = 'text';
if ( isset( $prop['wikitext'] ) ) {
$result_array['title'] = $titleObj->getPrefixedText();
$result_array['pageid'] = $pageid ?: $pageObj->getId();
+ if ( $this->contentIsDeleted ) {
+ $result_array['textdeleted'] = true;
+ }
+ if ( $this->contentIsSuppressed ) {
+ $result_array['textsuppressed'] = true;
+ }
+
+ if ( $params['disabletoc'] ) {
+ $p_result->setTOCEnabled( false );
+ }
+
+ if ( isset( $params['useskin'] ) ) {
+ $factory = MediaWikiServices::getInstance()->getSkinFactory();
+ $skin = $factory->makeSkin( Skin::normalizeKey( $params['useskin'] ) );
+ } else {
+ $skin = null;
+ }
+
+ $outputPage = null;
+ if ( $skin || isset( $prop['headhtml'] ) || isset( $prop['categorieshtml'] ) ) {
+ // Enabling the skin via 'useskin', 'headhtml', or 'categorieshtml'
+ // gets OutputPage and Skin involved, which (among others) applies
+ // these hooks:
+ // - ParserOutputHooks
+ // - Hook: LanguageLinks
+ // - Hook: OutputPageParserOutput
+ // - Hook: OutputPageMakeCategoryLinks
+ $context = new DerivativeContext( $this->getContext() );
+ $context->setTitle( $titleObj );
+ $context->setWikiPage( $pageObj );
+
+ if ( $skin ) {
+ // Use the skin specified by 'useskin'
+ $context->setSkin( $skin );
+ // Context clones the skin, refetch to stay in sync. (T166022)
+ $skin = $context->getSkin();
+ } else {
+ // Make sure the context's skin refers to the context. Without this,
+ // $outputPage->getSkin()->getOutput() !== $outputPage which
+ // confuses some of the output.
+ $context->setSkin( $context->getSkin() );
+ }
+
+ $outputPage = new OutputPage( $context );
+ $outputPage->addParserOutputMetadata( $p_result );
+ $context->setOutput( $outputPage );
+
+ if ( $skin ) {
+ // Based on OutputPage::output()
+ foreach ( $skin->getDefaultModules() as $group ) {
+ $outputPage->addModules( $group );
+ }
+ }
+ }
if ( !is_null( $oldid ) ) {
$result_array['revid'] = intval( $oldid );
$result_array['redirects'] = $redirValues;
}
- if ( $params['disabletoc'] ) {
- $p_result->setTOCEnabled( false );
- }
-
if ( isset( $prop['text'] ) ) {
$result_array['text'] = $p_result->getText();
$result_array[ApiResult::META_BC_SUBELEMENTS][] = 'text';
}
if ( isset( $prop['langlinks'] ) ) {
- $langlinks = $p_result->getLanguageLinks();
-
- if ( $params['effectivelanglinks'] ) {
- // Link flags are ignored for now, but may in the future be
- // included in the result.
- $linkFlags = [];
- Hooks::run( 'LanguageLinks', [ $titleObj, &$langlinks, &$linkFlags ] );
+ if ( $skin ) {
+ $langlinks = $outputPage->getLanguageLinks();
+ } else {
+ $langlinks = $p_result->getLanguageLinks();
+ // The deprecated 'effectivelanglinks' option depredates OutputPage
+ // support via 'useskin'. If not already applied, then run just this
+ // one hook of OutputPage::addParserOutputMetadata here.
+ if ( $params['effectivelanglinks'] ) {
+ $linkFlags = [];
+ Hooks::run( 'LanguageLinks', [ $titleObj, &$langlinks, &$linkFlags ] );
+ }
}
- } else {
- $langlinks = false;
- }
- if ( isset( $prop['langlinks'] ) ) {
$result_array['langlinks'] = $this->formatLangLinks( $langlinks );
}
if ( isset( $prop['categories'] ) ) {
$result_array['categories'] = $this->formatCategoryLinks( $p_result->getCategories() );
}
if ( isset( $prop['categorieshtml'] ) ) {
- $result_array['categorieshtml'] = $this->categoriesHtml( $p_result->getCategories() );
+ $result_array['categorieshtml'] = $outputPage->getSkin()->getCategories();
$result_array[ApiResult::META_BC_SUBELEMENTS][] = 'categorieshtml';
}
if ( isset( $prop['links'] ) ) {
}
if ( isset( $prop['headitems'] ) ) {
- $result_array['headitems'] = $this->formatHeadItems( $p_result->getHeadItems() );
+ if ( $skin ) {
+ $result_array['headitems'] = $this->formatHeadItems( $outputPage->getHeadItemsArray() );
+ } else {
+ $result_array['headitems'] = $this->formatHeadItems( $p_result->getHeadItems() );
+ }
$this->addDeprecation( 'apiwarn-deprecation-parse-headitems', 'action=parse&prop=headitems' );
}
if ( isset( $prop['headhtml'] ) ) {
- $context = new DerivativeContext( $this->getContext() );
- $context->setTitle( $titleObj );
- $context->setWikiPage( $pageObj );
-
- // We need an OutputPage tied to $context, not to the
- // RequestContext at the root of the stack.
- $output = new OutputPage( $context );
- $output->addParserOutputMetadata( $p_result );
-
- $result_array['headhtml'] = $output->headElement( $context->getSkin() );
+ $result_array['headhtml'] = $outputPage->headElement( $context->getSkin() );
$result_array[ApiResult::META_BC_SUBELEMENTS][] = 'headhtml';
}
if ( isset( $prop['modules'] ) ) {
- $result_array['modules'] = array_values( array_unique( $p_result->getModules() ) );
- $result_array['modulescripts'] = array_values( array_unique( $p_result->getModuleScripts() ) );
- $result_array['modulestyles'] = array_values( array_unique( $p_result->getModuleStyles() ) );
+ if ( $skin ) {
+ $result_array['modules'] = $outputPage->getModules();
+ $result_array['modulescripts'] = $outputPage->getModuleScripts();
+ $result_array['modulestyles'] = $outputPage->getModuleStyles();
+ } else {
+ $result_array['modules'] = array_values( array_unique( $p_result->getModules() ) );
+ $result_array['modulescripts'] = array_values( array_unique( $p_result->getModuleScripts() ) );
+ $result_array['modulestyles'] = array_values( array_unique( $p_result->getModuleStyles() ) );
+ }
}
if ( isset( $prop['jsconfigvars'] ) ) {
- $result_array['jsconfigvars'] =
- ApiResult::addMetadataToResultVars( $p_result->getJsConfigVars() );
+ $jsconfigvars = $skin ? $outputPage->getJsConfigVars() : $p_result->getJsConfigVars();
+ $result_array['jsconfigvars'] = ApiResult::addMetadataToResultVars( $jsconfigvars );
}
if ( isset( $prop['encodedjsconfigvars'] ) ) {
+ $jsconfigvars = $skin ? $outputPage->getJsConfigVars() : $p_result->getJsConfigVars();
$result_array['encodedjsconfigvars'] = FormatJson::encode(
- $p_result->getJsConfigVars(), false, FormatJson::ALL_OK
+ $jsconfigvars,
+ false,
+ FormatJson::ALL_OK
);
$result_array[ApiResult::META_SUBELEMENTS][] = 'encodedjsconfigvars';
}
}
if ( isset( $prop['indicators'] ) ) {
- $result_array['indicators'] = (array)$p_result->getIndicators();
+ if ( $skin ) {
+ $result_array['indicators'] = (array)$outputPage->getIndicators();
+ } else {
+ $result_array['indicators'] = (array)$p_result->getIndicators();
+ }
ApiResult::setArrayType( $result_array['indicators'], 'BCkvp', 'name' );
}
if ( $params['disabletidy'] ) {
$popts->setTidy( false );
}
+ $popts->setWrapOutputClass(
+ $params['wrapoutputclass'] === '' ? false : $params['wrapoutputclass']
+ );
$reset = null;
$suppressCache = false;
Hooks::run( 'ApiMakeParserOptions',
[ $popts, $pageObj->getTitle(), $params, $this, &$reset, &$suppressCache ] );
+ // Force cache suppression when $popts aren't cacheable.
+ $suppressCache = $suppressCache || !$popts->isSafeToCache();
+
return [ $popts, $reset, $suppressCache ];
}
/**
* @param WikiPage $page
* @param ParserOptions $popts
+ * @param bool $suppressCache
* @param int $pageId
- * @param bool $getWikitext
+ * @param Revision|null $rev
+ * @param bool $getContent
* @return ParserOutput
*/
- private function getParsedContent( WikiPage $page, $popts, $pageId = null, $getWikitext = false ) {
- $this->content = $this->getContent( $page, $pageId );
+ private function getParsedContent(
+ WikiPage $page, $popts, $suppressCache, $pageId, $rev, $getContent
+ ) {
+ $revId = $rev ? $rev->getId() : null;
+ $isDeleted = $rev && $rev->isDeleted( Revision::DELETED_TEXT );
+
+ if ( $getContent || $this->section !== false || $isDeleted ) {
+ if ( $rev ) {
+ $this->content = $rev->getContent( Revision::FOR_THIS_USER, $this->getUser() );
+ if ( !$this->content ) {
+ $this->dieWithError( [ 'apierror-missingcontent-revid', $revId ] );
+ }
+ } else {
+ $this->content = $page->getContent( Revision::FOR_THIS_USER, $this->getUser() );
+ if ( !$this->content ) {
+ $this->dieWithError( [ 'apierror-missingcontent-pageid', $pageId ] );
+ }
+ }
+ $this->contentIsDeleted = $isDeleted;
+ $this->contentIsSuppressed = $rev &&
+ $rev->isDeleted( Revision::DELETED_TEXT | Revision::DELETED_RESTRICTED );
+ }
- if ( $this->section !== false && $this->content !== null ) {
- // Not cached (save or load)
- return $this->content->getParserOutput( $page->getTitle(), null, $popts );
+ if ( $this->section !== false ) {
+ $this->content = $this->getSectionContent(
+ $this->content,
+ $pageId === null ? $page->getTitle()->getPrefixedText() : $this->msg( 'pageid', $pageId )
+ );
+ return $this->content->getParserOutput( $page->getTitle(), $revId, $popts );
}
- // Try the parser cache first
- // getParserOutput will save to Parser cache if able
- $pout = $page->getParserOutput( $popts );
- if ( !$pout ) {
- $this->dieWithError( [ 'apierror-nosuchrevid', $page->getLatest() ] );
+ if ( $isDeleted ) {
+ // getParserOutput can't do revdeled revisions
+ $pout = $this->content->getParserOutput( $page->getTitle(), $revId, $popts );
+ } else {
+ // getParserOutput will save to Parser cache if able
+ $pout = $page->getParserOutput( $popts, $revId, $suppressCache );
}
- if ( $getWikitext ) {
- $this->content = $page->getContent( Revision::RAW );
+ if ( !$pout ) {
+ $this->dieWithError( [ 'apierror-nosuchrevid', $revId ?: $page->getLatest() ] );
}
return $pout;
}
- /**
- * Get the content for the given page and the requested section.
- *
- * @param WikiPage $page
- * @param int $pageId
- * @return Content
- */
- private function getContent( WikiPage $page, $pageId = null ) {
- $content = $page->getContent( Revision::RAW ); // XXX: really raw?
-
- if ( $this->section !== false && $content !== null ) {
- $content = $this->getSectionContent(
- $content,
- !is_null( $pageId )
- ? $this->msg( 'pageid', $pageId )
- : $page->getTitle()->getPrefixedText()
- );
- }
- return $content;
- }
-
/**
* Extract the requested section from the given Content
*
* @param Content $content
* @param string|Message $what Identifies the content in error messages, e.g. page title.
- * @return Content|bool
+ * @return Content
*/
private function getSectionContent( Content $content, $what ) {
// Not cached (save or load)
return $result;
}
- private function categoriesHtml( $categories ) {
- $context = $this->getContext();
- $context->getOutput()->addCategoryLinks( $categories );
-
- return $context->getSkin()->getCategories();
- }
-
private function formatLinks( $links ) {
$result = [];
foreach ( $links as $ns => $nslinks ) {
'parsetree' => [ 'apihelp-parse-paramvalue-prop-parsetree', CONTENT_MODEL_WIKITEXT ],
],
],
+ 'wrapoutputclass' => 'mw-parser-output',
'pst' => false,
'onlypst' => false,
- 'effectivelanglinks' => false,
+ 'effectivelanglinks' => [
+ ApiBase::PARAM_DFLT => false,
+ ApiBase::PARAM_DEPRECATED => true,
+ ],
'section' => null,
'sectiontitle' => [
ApiBase::PARAM_TYPE => 'string',
'preview' => false,
'sectionpreview' => false,
'disabletoc' => false,
+ 'useskin' => [
+ ApiBase::PARAM_TYPE => array_keys( Skin::getAllowedSkins() ),
+ ],
'contentformat' => [
ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
],
}
public function getHelpUrls() {
- return 'https://www.mediawiki.org/wiki/API:Parsing_wikitext#parse';
+ return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Parsing_wikitext#parse';
}
}