X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2Fapi%2FApiParse.php;h=b2e03c80ee6bd932b8c64cb6ed3172ca4ff8d479;hb=178bada116bc5aa336191f4867fec5cae2258cd6;hp=d6489688e6ab2889f35147130b0157868d4a59e3;hpb=dc1632d58ee3b016697667bfc003338141bc3ce7;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/api/ApiParse.php b/includes/api/ApiParse.php index d6489688e6..b2e03c80ee 100644 --- a/includes/api/ApiParse.php +++ b/includes/api/ApiParse.php @@ -22,6 +22,8 @@ * @file */ +use MediaWiki\MediaWikiServices; + /** * @ingroup API */ @@ -36,6 +38,9 @@ class ApiParse extends ApiBase { /** @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' ); @@ -83,6 +88,9 @@ class ApiParse extends ApiBase { $redirValues = null; + $needContent = isset( $prop['wikitext'] ) || + isset( $prop['parsetree'] ) || $params['generatexml']; + // Return result $result = $this->getResult(); @@ -108,27 +116,9 @@ class ApiParse extends ApiBase { $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 = [ @@ -170,25 +160,9 @@ class ApiParse extends ApiBase { } 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 ); @@ -247,6 +221,12 @@ class ApiParse extends ApiBase { 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'] ) ) { @@ -277,6 +257,60 @@ class ApiParse extends ApiBase { $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 ); @@ -286,10 +320,6 @@ class ApiParse extends ApiBase { $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'; @@ -303,26 +333,26 @@ class ApiParse extends ApiBase { } 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'] ) ) { @@ -350,38 +380,42 @@ class ApiParse extends ApiBase { } 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'; } @@ -392,7 +426,11 @@ class ApiParse extends ApiBase { } 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' ); } @@ -478,70 +516,81 @@ class ApiParse extends ApiBase { 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) @@ -659,13 +708,6 @@ class ApiParse extends ApiBase { 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 ) { @@ -788,9 +830,13 @@ class ApiParse extends ApiBase { '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', @@ -812,6 +858,9 @@ class ApiParse extends ApiBase { 'preview' => false, 'sectionpreview' => false, 'disabletoc' => false, + 'useskin' => [ + ApiBase::PARAM_TYPE => array_keys( Skin::getAllowedSkins() ), + ], 'contentformat' => [ ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(), ],