160 $this->getMain()->setCacheMode(
'anon-public-user-private' );
163 $params = $this->extractRequestParams();
167 $this->requireMaxOneParameter( $params,
'page',
'pageid',
'oldid',
'text' );
168 $this->requireMaxOneParameter( $params,
'page',
'pageid',
'oldid',
'title' );
169 $this->requireMaxOneParameter( $params,
'page',
'pageid',
'oldid',
'revid' );
171 $text = $params[
'text'];
172 $title = $params[
'title'];
173 if ( $title ===
null ) {
174 $titleProvided =
false;
178 $titleProvided =
true;
181 $page = $params[
'page'];
182 $pageid = $params[
'pageid'];
183 $oldid = $params[
'oldid'];
185 $prop = array_fill_keys( $params[
'prop'],
true );
187 if ( isset( $params[
'section'] ) ) {
188 $this->section = $params[
'section'];
189 if ( !preg_match(
'/^((T-)?\d+|new)$/', $this->section ) ) {
190 $this->dieWithError(
'apierror-invalidsection' );
193 $this->section =
false;
205 $needContent = isset( $prop[
'wikitext'] ) ||
206 isset( $prop[
'parsetree'] ) || $params[
'generatexml'];
209 $result = $this->getResult();
211 if ( $oldid !==
null || $pageid !==
null || $page !==
null ) {
212 if ( $this->section ===
'new' ) {
213 $this->dieWithError(
'apierror-invalidparammix-parse-new-section',
'invalidparammix' );
215 if ( $oldid !==
null ) {
217 $rev = $this->revisionLookup->getRevisionById( $oldid );
219 $this->dieWithError( [
'apierror-nosuchrevid', $oldid ] );
222 $this->checkTitleUserPermissions( $rev->getPage(),
'read' );
224 if ( !$rev->userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ) {
226 [
'apierror-permissiondenied', $this->msg(
'action-deletedtext' ) ]
230 $revLinkTarget = $rev->getPageAsLinkTarget();
231 $titleObj = Title::newFromLinkTarget( $revLinkTarget );
233 $pageObj = $this->wikiPageFactory->newFromTitle( $titleObj );
234 [ $popts, $reset, $suppressCache ] = $this->makeParserOptions( $pageObj, $params );
235 $p_result = $this->getParsedContent(
236 $pageObj, $popts, $suppressCache, $pageid, $rev, $needContent
239 if ( $params[
'redirects'] ) {
244 if ( $pageid !==
null ) {
245 $reqParams[
'pageids'] = $pageid;
246 $pageParams[
'pageid'] = $pageid;
248 $reqParams[
'titles'] = $page;
249 $pageParams[
'title'] = $page;
255 $redirValues = $pageSet->getRedirectTitlesAsResult( $this->getResult() );
257 foreach ( $pageSet->getRedirectTargets() as $redirectTarget ) {
258 $pageParams = [
'title' => $this->titleFormatter->getFullText( $redirectTarget ) ];
260 } elseif ( $pageid !==
null ) {
261 $pageParams = [
'pageid' => $pageid ];
263 $pageParams = [
'title' => $page ];
266 $pageObj = $this->getTitleOrPageId( $pageParams,
'fromdb' );
268 if ( !$titleObj->exists() ) {
269 $this->dieWithError(
'apierror-missingtitle' );
272 $this->checkTitleUserPermissions( $titleObj,
'read' );
275 if ( isset( $prop[
'revid'] ) ) {
279 [ $popts, $reset, $suppressCache ] = $this->makeParserOptions( $pageObj, $params );
280 $p_result = $this->getParsedContent(
281 $pageObj, $popts, $suppressCache, $pageid,
null, $needContent
285 $model = $params[
'contentmodel'];
286 $format = $params[
'contentformat'];
288 $titleObj = Title::newFromText( $title );
289 if ( !$titleObj || $titleObj->isExternal() ) {
290 $this->dieWithError( [
'apierror-invalidtitle',
wfEscapeWikiText( $title ) ] );
292 $revid = $params[
'revid'];
294 if ( $revid !==
null ) {
295 $rev = $this->revisionLookup->getRevisionById( $revid );
297 $this->dieWithError( [
'apierror-nosuchrevid', $revid ] );
299 $pTitleObj = $titleObj;
300 $titleObj = Title::newFromPageIdentity( $rev->getPage() );
301 if ( $titleProvided ) {
302 if ( !$titleObj->equals( $pTitleObj ) ) {
303 $this->addWarning( [
'apierror-revwrongpage', $rev->getId(),
309 $titleProvided =
true;
313 if ( $titleObj->canExist() ) {
314 $pageObj = $this->wikiPageFactory->newFromTitle( $titleObj );
315 [ $popts, $reset ] = $this->makeParserOptions( $pageObj, $params );
319 $popts = ParserOptions::newFromContext( $this->
getContext() );
320 [ $popts, $reset ] = $this->tweakParserOptions( $popts, $titleObj, $params );
323 $textProvided = $text !==
null;
325 if ( !$textProvided ) {
326 if ( $titleProvided && ( $prop || $params[
'generatexml'] ) ) {
327 if ( $revid !==
null ) {
328 $this->addWarning(
'apiwarn-parse-revidwithouttext' );
330 $this->addWarning(
'apiwarn-parse-titlewithouttext' );
339 if ( $textProvided && !$titleProvided && $model ===
null ) {
341 $this->addWarning( [
'apiwarn-parse-nocontentmodel', $model ] );
342 } elseif ( $model ===
null ) {
343 $model = $titleObj->getContentModel();
346 $contentHandler = $this->contentHandlerFactory->getContentHandler( $model );
348 if ( $format && !$contentHandler->isSupportedFormat( $format ) ) {
349 $this->dieWithError( [
'apierror-badformat-generic', $format, $model ] );
353 $this->content = $contentHandler->unserializeContent( $text, $format );
355 $this->dieWithException( $ex, [
356 'wrap' => ApiMessage::create(
'apierror-contentserializationexception',
'parseerror' )
360 if ( $this->section !==
false ) {
361 if ( $this->section ===
'new' ) {
363 if ( $params[
'sectiontitle'] !==
null ) {
364 $this->content = $this->content->addSectionHeader( $params[
'sectiontitle'] );
367 $this->content = $this->getSectionContent( $this->content, $titleObj->getPrefixedText() );
371 if ( $params[
'pst'] || $params[
'onlypst'] ) {
372 $this->pstContent = $this->contentTransformer->preSaveTransform(
375 $this->getUserForPreview(),
379 if ( $params[
'onlypst'] ) {
382 $result_array[
'text'] = $this->pstContent->serialize( $format );
383 $result_array[ApiResult::META_BC_SUBELEMENTS][] =
'text';
384 if ( isset( $prop[
'wikitext'] ) ) {
385 $result_array[
'wikitext'] = $this->content->serialize( $format );
386 $result_array[ApiResult::META_BC_SUBELEMENTS][] =
'wikitext';
388 if ( $params[
'summary'] !==
null ||
389 ( $params[
'sectiontitle'] !==
null && $this->section ===
'new' )
391 $result_array[
'parsedsummary'] = $this->formatSummary( $titleObj, $params );
392 $result_array[ApiResult::META_BC_SUBELEMENTS][] =
'parsedsummary';
395 $result->addValue(
null, $this->getModuleName(), $result_array );
401 if ( $params[
'pst'] ) {
402 $p_result = $this->getContentParserOutput( $this->pstContent, $titleObj, $rev, $popts );
404 $p_result = $this->getContentParserOutput( $this->content, $titleObj, $rev, $popts );
410 $result_array[
'title'] = $titleObj->getPrefixedText();
411 $result_array[
'pageid'] = $pageid ?: $titleObj->getArticleID();
412 if ( $this->contentIsDeleted ) {
413 $result_array[
'textdeleted'] =
true;
415 if ( $this->contentIsSuppressed ) {
416 $result_array[
'textsuppressed'] =
true;
419 if ( isset( $params[
'useskin'] ) ) {
420 $skin = $this->skinFactory->makeSkin( Skin::normalizeKey( $params[
'useskin'] ) );
428 $skin || isset( $prop[
'subtitle'] ) || isset( $prop[
'headhtml'] ) || isset( $prop[
'categorieshtml'] ) ||
429 isset( $params[
'mobileformat'] )
445 $context->setTitle( $titleObj );
448 $context->setWikiPage( $pageObj );
452 $context->setRequest(
new FauxRequest( [
'action' =>
'view' ] ) );
456 $context->setSkin( $skin );
458 $skin = $context->getSkin();
463 $context->setSkin( $context->getSkin() );
468 $outputPage->setArticleFlag(
true );
471 $outputPage->addCategoryLinks( $p_result->getCategoryMap() );
473 if ( $this->content ) {
474 $outputPage->addContentOverride( $titleObj, $this->content );
476 $context->setOutput( $outputPage );
480 $outputPage->loadSkinModules( $skin );
484 if ( $oldid !==
null ) {
485 $result_array[
'revid'] = (int)$oldid;
488 if ( $params[
'redirects'] && $redirValues !==
null ) {
489 $result_array[
'redirects'] = $redirValues;
493 if ( isset( $prop[
'text'] ) || isset( $prop[
'displaytitle'] ) ) {
494 $skin = $context ? $context->getSkin() :
null;
495 $skinOptions = $skin ? $skin->getOptions() : [
502 $oldText = $p_result->getContentHolderText();
503 $newText = $p_result->runOutputPipeline( $popts, [
505 'allowClone' =>
false,
506 'allowTOC' => !$params[
'disabletoc'],
507 'injectTOC' => $skinOptions[
'toc'],
508 'wrapperDivClass' => $params[
'wrapoutputclass'],
509 'deduplicateStyles' => !$params[
'disablestylededuplication'],
510 'userLang' => $context ? $context->getLanguage() :
null,
512 'includeDebugInfo' => !$params[
'disablepp'] && !$params[
'disablelimitreport']
513 ] )->getContentHolderText();
514 $p_result->setContentHolderText( $oldText );
515 if ( isset( $prop[
'text'] ) ) {
516 $result_array[
'text'] = $newText;
517 $result_array[ApiResult::META_BC_SUBELEMENTS][] =
'text';
519 $this->getHookRunner()->onOutputPageBeforeHTML( $context->getOutput(), $result_array[
'text'] );
527 $outputPage->addParserOutputMetadata( $p_result );
529 $this->getHookRunner()->onApiParseMakeOutputPage( $this, $outputPage );
532 if ( $params[
'summary'] !==
null ||
533 ( $params[
'sectiontitle'] !==
null && $this->section ===
'new' )
535 $result_array[
'parsedsummary'] = $this->formatSummary( $titleObj, $params );
536 $result_array[ApiResult::META_BC_SUBELEMENTS][] =
'parsedsummary';
539 if ( isset( $prop[
'langlinks'] ) ) {
541 $langlinks = $outputPage->getLanguageLinks();
543 $langlinks = array_map(
544 static fn ( $item ) => $item[
'link'],
545 $p_result->getLinkList( ParserOutputLinkTypes::LANGUAGE )
550 if ( $params[
'effectivelanglinks'] ) {
551 # for compatibility with old hook, convert to string[]
553 foreach ( $langlinks as $link ) {
554 $s = $link->getInterwiki() .
':' . $link->getText();
555 if ( $link->hasFragment() ) {
556 $s .=
'#' . $link->getFragment();
560 $langlinks = $compat;
563 $this->getHookRunner()->onLanguageLinks( $titleObj, $langlinks, $linkFlags );
567 $result_array[
'langlinks'] = $this->formatLangLinks( $langlinks );
569 if ( isset( $prop[
'categories'] ) ) {
570 $result_array[
'categories'] = $this->formatCategoryLinks( $p_result->getCategoryMap() );
572 if ( isset( $prop[
'categorieshtml'] ) ) {
573 $result_array[
'categorieshtml'] = $outputPage->getSkin()->getCategories();
574 $result_array[ApiResult::META_BC_SUBELEMENTS][] =
'categorieshtml';
576 if ( isset( $prop[
'links'] ) ) {
578 $result_array[
'links'] = $this->formatLinks( $p_result->getLinkList( ParserOutputLinkTypes::LOCAL ) );
580 if ( isset( $prop[
'templates'] ) ) {
581 $result_array[
'templates'] = $this->formatLinks(
582 $p_result->getLinkList( ParserOutputLinkTypes::TEMPLATE )
585 if ( isset( $prop[
'images'] ) ) {
586 $result_array[
'images'] = array_map(
587 static fn ( $item ) => $item[
'link']->getDBkey(),
588 $p_result->getLinkList( ParserOutputLinkTypes::MEDIA )
591 if ( isset( $prop[
'externallinks'] ) ) {
592 $result_array[
'externallinks'] = array_keys( $p_result->getExternalLinks() );
594 if ( isset( $prop[
'sections'] ) ) {
595 $result_array[
'sections'] = $p_result->getSections();
597 if ( isset( $prop[
'tocdata'] ) ) {
598 $result_array[
'tocdata'] = $this->jsonCodec->toJsonArray(
599 $p_result->getTOCData(), TOCData::class
602 if ( isset( $prop[
'sections'] ) || isset( $prop[
'tocdata'] ) ) {
603 $result_array[
'showtoc'] = $p_result->getOutputFlag( ParserOutputFlags::SHOW_TOC );
605 if ( isset( $prop[
'parsewarnings'] ) || isset( $prop[
'parsewarningshtml'] ) ) {
606 $warningMsgs = array_map(
607 static fn ( $mv ) => Message::newFromSpecifier( $mv )
610 ->inContentLanguage(),
611 $p_result->getWarningMsgs()
613 if ( $warningMsgs === [] ) {
616 $warningMsgs = array_map(
617 static fn ( $warning ) =>
new RawMessage(
'$1', [ $warning ] ),
618 $p_result->getWarnings()
621 if ( isset( $prop[
'parsewarnings'] ) ) {
622 $warnings = array_map(
static fn ( $msg ) => $msg->text(), $warningMsgs );
623 $result_array[
'parsewarnings'] = $warnings;
625 if ( isset( $prop[
'parsewarningshtml'] ) ) {
626 $warningsHtml = array_map(
static fn ( $msg ) => $msg->parse(), $warningMsgs );
627 $result_array[
'parsewarningshtml'] = $warningsHtml;
631 if ( isset( $prop[
'displaytitle'] ) ) {
632 $result_array[
'displaytitle'] = $p_result->getDisplayTitle() !==
false
633 ? $p_result->getDisplayTitle()
634 : htmlspecialchars( $titleObj->getPrefixedText(), ENT_NOQUOTES );
637 if ( isset( $prop[
'subtitle'] ) ) {
639 $result_array[
'subtitle'] = $context->getSkin()->prepareSubtitle(
false );
642 if ( isset( $prop[
'headitems'] ) ) {
644 $result_array[
'headitems'] = $this->formatHeadItems( $outputPage->getHeadItemsArray() );
646 $result_array[
'headitems'] = $this->formatHeadItems( $p_result->getHeadItems() );
650 if ( isset( $prop[
'headhtml'] ) ) {
651 $result_array[
'headhtml'] = $outputPage->headElement( $context->getSkin() );
652 $result_array[ApiResult::META_BC_SUBELEMENTS][] =
'headhtml';
655 if ( isset( $prop[
'modules'] ) ) {
657 $result_array[
'modules'] = $outputPage->getModules();
659 $result_array[
'modulescripts'] = [];
660 $result_array[
'modulestyles'] = $outputPage->getModuleStyles();
662 $result_array[
'modules'] = array_values( array_unique( $p_result->getModules() ) );
664 $result_array[
'modulescripts'] = [];
665 $result_array[
'modulestyles'] = array_values( array_unique( $p_result->getModuleStyles() ) );
669 if ( isset( $prop[
'jsconfigvars'] ) ) {
670 $showStrategyKeys = (bool)( $params[
'showstrategykeys'] );
671 $jsconfigvars = $skin ? $outputPage->getJsConfigVars() : $p_result->getJsConfigVars( $showStrategyKeys );
672 $result_array[
'jsconfigvars'] = ApiResult::addMetadataToResultVars( $jsconfigvars );
675 if ( isset( $prop[
'encodedjsconfigvars'] ) ) {
676 $jsconfigvars = $skin ? $outputPage->getJsConfigVars() : $p_result->getJsConfigVars();
677 $result_array[
'encodedjsconfigvars'] = FormatJson::encode(
682 $result_array[ApiResult::META_SUBELEMENTS][] =
'encodedjsconfigvars';
685 if ( isset( $prop[
'modules'] ) &&
686 !isset( $prop[
'jsconfigvars'] ) && !isset( $prop[
'encodedjsconfigvars'] ) ) {
687 $this->addWarning(
'apiwarn-moduleswithoutvars' );
690 if ( isset( $prop[
'indicators'] ) ) {
692 $result_array[
'indicators'] = $outputPage->getIndicators();
694 $result_array[
'indicators'] = $p_result->getIndicators();
696 ApiResult::setArrayType( $result_array[
'indicators'],
'BCkvp',
'name' );
699 if ( isset( $prop[
'iwlinks'] ) ) {
701 static fn ( $item ) => $item[
'link'],
702 $p_result->getLinkList( ParserOutputLinkTypes::INTERWIKI )
704 $result_array[
'iwlinks'] = $this->formatIWLinks( $links );
707 if ( isset( $prop[
'wikitext'] ) ) {
708 $result_array[
'wikitext'] = $this->content->serialize( $format );
709 $result_array[ApiResult::META_BC_SUBELEMENTS][] =
'wikitext';
711 if ( $this->pstContent !==
null ) {
712 $result_array[
'psttext'] = $this->pstContent->serialize( $format );
713 $result_array[ApiResult::META_BC_SUBELEMENTS][] =
'psttext';
716 if ( isset( $prop[
'properties'] ) ) {
717 $result_array[
'properties'] = $p_result->getPageProperties();
718 ApiResult::setArrayType( $result_array[
'properties'],
'BCkvp',
'name' );
721 if ( isset( $prop[
'limitreportdata'] ) ) {
722 $result_array[
'limitreportdata'] =
723 $this->formatLimitReportData( $p_result->getLimitReportData() );
725 if ( isset( $prop[
'limitreporthtml'] ) ) {
726 $result_array[
'limitreporthtml'] = EditPage::getPreviewLimitReport( $p_result );
727 $result_array[ApiResult::META_BC_SUBELEMENTS][] =
'limitreporthtml';
730 if ( isset( $prop[
'parsetree'] ) || $params[
'generatexml'] ) {
732 $this->dieWithError(
'apierror-parsetree-notwikitext',
'notwikitext' );
735 $parser = $this->parserFactory->getInstance();
736 $parser->startExternalParse( $titleObj, $popts, Parser::OT_PREPROCESS );
738 $xml = $parser->preprocessToDom( $this->content->getText() )->__toString();
739 $result_array[
'parsetree'] = $xml;
740 $result_array[ApiResult::META_BC_SUBELEMENTS][] =
'parsetree';
746 'categories' =>
'cl',
750 'externallinks' =>
'el',
756 'indicators' =>
'ind',
757 'modulescripts' =>
'm',
758 'modulestyles' =>
'm',
759 'properties' =>
'pp',
760 'limitreportdata' =>
'lr',
761 'parsewarnings' =>
'pw',
762 'parsewarningshtml' =>
'pw',
764 $this->setIndexedTagNames( $result_array, $result_mapping );
765 $result->addValue(
null, $this->getModuleName(), $result_array );