API: Add the ability to flag parameter values as deprecated
[mediawiki.git] / includes / api / ApiParse.php
blob031fbf76a7aee7dc271e66c02fca8249a01abff0
1 <?php
2 /**
3 * Created on Dec 01, 2007
5 * Copyright © 2007 Yuri Astrakhan "<Firstname><Lastname>@gmail.com"
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 * http://www.gnu.org/copyleft/gpl.html
22 * @file
25 use MediaWiki\MediaWikiServices;
27 /**
28 * @ingroup API
30 class ApiParse extends ApiBase {
32 /** @var string $section */
33 private $section = null;
35 /** @var Content $content */
36 private $content = null;
38 /** @var Content $pstContent */
39 private $pstContent = null;
41 /** @var bool */
42 private $contentIsDeleted = false, $contentIsSuppressed = false;
44 public function execute() {
45 // The data is hot but user-dependent, like page views, so we set vary cookies
46 $this->getMain()->setCacheMode( 'anon-public-user-private' );
48 // Get parameters
49 $params = $this->extractRequestParams();
51 // No easy way to say that text & title are allowed together while the
52 // rest aren't, so just do it in two calls.
53 $this->requireMaxOneParameter( $params, 'page', 'pageid', 'oldid', 'text' );
54 $this->requireMaxOneParameter( $params, 'page', 'pageid', 'oldid', 'title' );
56 $text = $params['text'];
57 $title = $params['title'];
58 if ( $title === null ) {
59 $titleProvided = false;
60 // A title is needed for parsing, so arbitrarily choose one
61 $title = 'API';
62 } else {
63 $titleProvided = true;
66 $page = $params['page'];
67 $pageid = $params['pageid'];
68 $oldid = $params['oldid'];
70 $model = $params['contentmodel'];
71 $format = $params['contentformat'];
73 $prop = array_flip( $params['prop'] );
75 if ( isset( $params['section'] ) ) {
76 $this->section = $params['section'];
77 if ( !preg_match( '/^((T-)?\d+|new)$/', $this->section ) ) {
78 $this->dieWithError( 'apierror-invalidsection' );
80 } else {
81 $this->section = false;
84 // The parser needs $wgTitle to be set, apparently the
85 // $title parameter in Parser::parse isn't enough *sigh*
86 // TODO: Does this still need $wgTitle?
87 global $wgParser, $wgTitle;
89 $redirValues = null;
91 $needContent = isset( $prop['wikitext'] ) ||
92 isset( $prop['parsetree'] ) || $params['generatexml'];
94 // Return result
95 $result = $this->getResult();
97 if ( !is_null( $oldid ) || !is_null( $pageid ) || !is_null( $page ) ) {
98 if ( $this->section === 'new' ) {
99 $this->dieWithError( 'apierror-invalidparammix-parse-new-section', 'invalidparammix' );
101 if ( !is_null( $oldid ) ) {
102 // Don't use the parser cache
103 $rev = Revision::newFromId( $oldid );
104 if ( !$rev ) {
105 $this->dieWithError( [ 'apierror-nosuchrevid', $oldid ] );
108 $this->checkTitleUserPermissions( $rev->getTitle(), 'read' );
109 if ( !$rev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) {
110 $this->dieWithError(
111 [ 'apierror-permissiondenied', $this->msg( 'action-deletedtext' ) ]
115 $titleObj = $rev->getTitle();
116 $wgTitle = $titleObj;
117 $pageObj = WikiPage::factory( $titleObj );
118 list( $popts, $reset, $suppressCache ) = $this->makeParserOptions( $pageObj, $params );
119 $p_result = $this->getParsedContent(
120 $pageObj, $popts, $suppressCache, $pageid, $rev, $needContent
122 } else { // Not $oldid, but $pageid or $page
123 if ( $params['redirects'] ) {
124 $reqParams = [
125 'redirects' => '',
127 if ( !is_null( $pageid ) ) {
128 $reqParams['pageids'] = $pageid;
129 } else { // $page
130 $reqParams['titles'] = $page;
132 $req = new FauxRequest( $reqParams );
133 $main = new ApiMain( $req );
134 $pageSet = new ApiPageSet( $main );
135 $pageSet->execute();
136 $redirValues = $pageSet->getRedirectTitlesAsResult( $this->getResult() );
138 $to = $page;
139 foreach ( $pageSet->getRedirectTitles() as $title ) {
140 $to = $title->getFullText();
142 $pageParams = [ 'title' => $to ];
143 } elseif ( !is_null( $pageid ) ) {
144 $pageParams = [ 'pageid' => $pageid ];
145 } else { // $page
146 $pageParams = [ 'title' => $page ];
149 $pageObj = $this->getTitleOrPageId( $pageParams, 'fromdb' );
150 $titleObj = $pageObj->getTitle();
151 if ( !$titleObj || !$titleObj->exists() ) {
152 $this->dieWithError( 'apierror-missingtitle' );
155 $this->checkTitleUserPermissions( $titleObj, 'read' );
156 $wgTitle = $titleObj;
158 if ( isset( $prop['revid'] ) ) {
159 $oldid = $pageObj->getLatest();
162 list( $popts, $reset, $suppressCache ) = $this->makeParserOptions( $pageObj, $params );
163 $p_result = $this->getParsedContent(
164 $pageObj, $popts, $suppressCache, $pageid, null, $needContent
167 } else { // Not $oldid, $pageid, $page. Hence based on $text
168 $titleObj = Title::newFromText( $title );
169 if ( !$titleObj || $titleObj->isExternal() ) {
170 $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $title ) ] );
172 $wgTitle = $titleObj;
173 if ( $titleObj->canExist() ) {
174 $pageObj = WikiPage::factory( $titleObj );
175 } else {
176 // Do like MediaWiki::initializeArticle()
177 $article = Article::newFromTitle( $titleObj, $this->getContext() );
178 $pageObj = $article->getPage();
181 list( $popts, $reset ) = $this->makeParserOptions( $pageObj, $params );
182 $textProvided = !is_null( $text );
184 if ( !$textProvided ) {
185 if ( $titleProvided && ( $prop || $params['generatexml'] ) ) {
186 $this->addWarning( 'apiwarn-parse-titlewithouttext' );
188 // Prevent warning from ContentHandler::makeContent()
189 $text = '';
192 // If we are parsing text, do not use the content model of the default
193 // API title, but default to wikitext to keep BC.
194 if ( $textProvided && !$titleProvided && is_null( $model ) ) {
195 $model = CONTENT_MODEL_WIKITEXT;
196 $this->addWarning( [ 'apiwarn-parse-nocontentmodel', $model ] );
199 try {
200 $this->content = ContentHandler::makeContent( $text, $titleObj, $model, $format );
201 } catch ( MWContentSerializationException $ex ) {
202 $this->dieWithException( $ex, [
203 'wrap' => ApiMessage::create( 'apierror-contentserializationexception', 'parseerror' )
204 ] );
207 if ( $this->section !== false ) {
208 if ( $this->section === 'new' ) {
209 // Insert the section title above the content.
210 if ( !is_null( $params['sectiontitle'] ) && $params['sectiontitle'] !== '' ) {
211 $this->content = $this->content->addSectionHeader( $params['sectiontitle'] );
213 } else {
214 $this->content = $this->getSectionContent( $this->content, $titleObj->getPrefixedText() );
218 if ( $params['pst'] || $params['onlypst'] ) {
219 $this->pstContent = $this->content->preSaveTransform( $titleObj, $this->getUser(), $popts );
221 if ( $params['onlypst'] ) {
222 // Build a result and bail out
223 $result_array = [];
224 if ( $this->contentIsDeleted ) {
225 $result_array['textdeleted'] = true;
227 if ( $this->contentIsSuppressed ) {
228 $result_array['textsuppressed'] = true;
230 $result_array['text'] = $this->pstContent->serialize( $format );
231 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'text';
232 if ( isset( $prop['wikitext'] ) ) {
233 $result_array['wikitext'] = $this->content->serialize( $format );
234 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'wikitext';
236 if ( !is_null( $params['summary'] ) ||
237 ( !is_null( $params['sectiontitle'] ) && $this->section === 'new' )
239 $result_array['parsedsummary'] = $this->formatSummary( $titleObj, $params );
240 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'parsedsummary';
243 $result->addValue( null, $this->getModuleName(), $result_array );
245 return;
248 // Not cached (save or load)
249 if ( $params['pst'] ) {
250 $p_result = $this->pstContent->getParserOutput( $titleObj, null, $popts );
251 } else {
252 $p_result = $this->content->getParserOutput( $titleObj, null, $popts );
256 $result_array = [];
258 $result_array['title'] = $titleObj->getPrefixedText();
259 $result_array['pageid'] = $pageid ?: $pageObj->getId();
260 if ( $this->contentIsDeleted ) {
261 $result_array['textdeleted'] = true;
263 if ( $this->contentIsSuppressed ) {
264 $result_array['textsuppressed'] = true;
267 if ( $params['disabletoc'] ) {
268 $p_result->setTOCEnabled( false );
271 if ( isset( $params['useskin'] ) ) {
272 $factory = MediaWikiServices::getInstance()->getSkinFactory();
273 $skin = $factory->makeSkin( Skin::normalizeKey( $params['useskin'] ) );
274 } else {
275 $skin = null;
278 $outputPage = null;
279 if ( $skin || isset( $prop['headhtml'] ) || isset( $prop['categorieshtml'] ) ) {
280 // Enabling the skin via 'useskin', 'headhtml', or 'categorieshtml'
281 // gets OutputPage and Skin involved, which (among others) applies
282 // these hooks:
283 // - ParserOutputHooks
284 // - Hook: LanguageLinks
285 // - Hook: OutputPageParserOutput
286 // - Hook: OutputPageMakeCategoryLinks
287 $context = new DerivativeContext( $this->getContext() );
288 $context->setTitle( $titleObj );
289 $context->setWikiPage( $pageObj );
291 if ( $skin ) {
292 // Use the skin specified by 'useskin'
293 $context->setSkin( $skin );
294 // Context clones the skin, refetch to stay in sync. (T166022)
295 $skin = $context->getSkin();
296 } else {
297 // Make sure the context's skin refers to the context. Without this,
298 // $outputPage->getSkin()->getOutput() !== $outputPage which
299 // confuses some of the output.
300 $context->setSkin( $context->getSkin() );
303 $outputPage = new OutputPage( $context );
304 $outputPage->addParserOutputMetadata( $p_result );
305 $context->setOutput( $outputPage );
307 if ( $skin ) {
308 // Based on OutputPage::output()
309 foreach ( $skin->getDefaultModules() as $group ) {
310 $outputPage->addModules( $group );
315 if ( !is_null( $oldid ) ) {
316 $result_array['revid'] = intval( $oldid );
319 if ( $params['redirects'] && !is_null( $redirValues ) ) {
320 $result_array['redirects'] = $redirValues;
323 if ( isset( $prop['text'] ) ) {
324 $result_array['text'] = $p_result->getText();
325 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'text';
328 if ( !is_null( $params['summary'] ) ||
329 ( !is_null( $params['sectiontitle'] ) && $this->section === 'new' )
331 $result_array['parsedsummary'] = $this->formatSummary( $titleObj, $params );
332 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'parsedsummary';
335 if ( isset( $prop['langlinks'] ) ) {
336 if ( $skin ) {
337 $langlinks = $outputPage->getLanguageLinks();
338 } else {
339 $langlinks = $p_result->getLanguageLinks();
340 // The deprecated 'effectivelanglinks' option depredates OutputPage
341 // support via 'useskin'. If not already applied, then run just this
342 // one hook of OutputPage::addParserOutputMetadata here.
343 if ( $params['effectivelanglinks'] ) {
344 $linkFlags = [];
345 Hooks::run( 'LanguageLinks', [ $titleObj, &$langlinks, &$linkFlags ] );
349 $result_array['langlinks'] = $this->formatLangLinks( $langlinks );
351 if ( isset( $prop['categories'] ) ) {
352 $result_array['categories'] = $this->formatCategoryLinks( $p_result->getCategories() );
354 if ( isset( $prop['categorieshtml'] ) ) {
355 $result_array['categorieshtml'] = $outputPage->getSkin()->getCategories();
356 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'categorieshtml';
358 if ( isset( $prop['links'] ) ) {
359 $result_array['links'] = $this->formatLinks( $p_result->getLinks() );
361 if ( isset( $prop['templates'] ) ) {
362 $result_array['templates'] = $this->formatLinks( $p_result->getTemplates() );
364 if ( isset( $prop['images'] ) ) {
365 $result_array['images'] = array_keys( $p_result->getImages() );
367 if ( isset( $prop['externallinks'] ) ) {
368 $result_array['externallinks'] = array_keys( $p_result->getExternalLinks() );
370 if ( isset( $prop['sections'] ) ) {
371 $result_array['sections'] = $p_result->getSections();
373 if ( isset( $prop['parsewarnings'] ) ) {
374 $result_array['parsewarnings'] = $p_result->getWarnings();
377 if ( isset( $prop['displaytitle'] ) ) {
378 $result_array['displaytitle'] = $p_result->getDisplayTitle() ?:
379 $titleObj->getPrefixedText();
382 if ( isset( $prop['headitems'] ) ) {
383 if ( $skin ) {
384 $result_array['headitems'] = $this->formatHeadItems( $outputPage->getHeadItemsArray() );
385 } else {
386 $result_array['headitems'] = $this->formatHeadItems( $p_result->getHeadItems() );
390 if ( isset( $prop['headhtml'] ) ) {
391 $result_array['headhtml'] = $outputPage->headElement( $context->getSkin() );
392 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'headhtml';
395 if ( isset( $prop['modules'] ) ) {
396 if ( $skin ) {
397 $result_array['modules'] = $outputPage->getModules();
398 $result_array['modulescripts'] = $outputPage->getModuleScripts();
399 $result_array['modulestyles'] = $outputPage->getModuleStyles();
400 } else {
401 $result_array['modules'] = array_values( array_unique( $p_result->getModules() ) );
402 $result_array['modulescripts'] = array_values( array_unique( $p_result->getModuleScripts() ) );
403 $result_array['modulestyles'] = array_values( array_unique( $p_result->getModuleStyles() ) );
407 if ( isset( $prop['jsconfigvars'] ) ) {
408 $jsconfigvars = $skin ? $outputPage->getJsConfigVars() : $p_result->getJsConfigVars();
409 $result_array['jsconfigvars'] = ApiResult::addMetadataToResultVars( $jsconfigvars );
412 if ( isset( $prop['encodedjsconfigvars'] ) ) {
413 $jsconfigvars = $skin ? $outputPage->getJsConfigVars() : $p_result->getJsConfigVars();
414 $result_array['encodedjsconfigvars'] = FormatJson::encode(
415 $jsconfigvars,
416 false,
417 FormatJson::ALL_OK
419 $result_array[ApiResult::META_SUBELEMENTS][] = 'encodedjsconfigvars';
422 if ( isset( $prop['modules'] ) &&
423 !isset( $prop['jsconfigvars'] ) && !isset( $prop['encodedjsconfigvars'] ) ) {
424 $this->addWarning( 'apiwarn-moduleswithoutvars' );
427 if ( isset( $prop['indicators'] ) ) {
428 if ( $skin ) {
429 $result_array['indicators'] = (array)$outputPage->getIndicators();
430 } else {
431 $result_array['indicators'] = (array)$p_result->getIndicators();
433 ApiResult::setArrayType( $result_array['indicators'], 'BCkvp', 'name' );
436 if ( isset( $prop['iwlinks'] ) ) {
437 $result_array['iwlinks'] = $this->formatIWLinks( $p_result->getInterwikiLinks() );
440 if ( isset( $prop['wikitext'] ) ) {
441 $result_array['wikitext'] = $this->content->serialize( $format );
442 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'wikitext';
443 if ( !is_null( $this->pstContent ) ) {
444 $result_array['psttext'] = $this->pstContent->serialize( $format );
445 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'psttext';
448 if ( isset( $prop['properties'] ) ) {
449 $result_array['properties'] = (array)$p_result->getProperties();
450 ApiResult::setArrayType( $result_array['properties'], 'BCkvp', 'name' );
453 if ( isset( $prop['limitreportdata'] ) ) {
454 $result_array['limitreportdata'] =
455 $this->formatLimitReportData( $p_result->getLimitReportData() );
457 if ( isset( $prop['limitreporthtml'] ) ) {
458 $result_array['limitreporthtml'] = EditPage::getPreviewLimitReport( $p_result );
459 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'limitreporthtml';
462 if ( isset( $prop['parsetree'] ) || $params['generatexml'] ) {
463 if ( $this->content->getModel() != CONTENT_MODEL_WIKITEXT ) {
464 $this->dieWithError( 'apierror-parsetree-notwikitext', 'notwikitext' );
467 $wgParser->startExternalParse( $titleObj, $popts, Parser::OT_PREPROCESS );
468 $dom = $wgParser->preprocessToDom( $this->content->getNativeData() );
469 if ( is_callable( [ $dom, 'saveXML' ] ) ) {
470 $xml = $dom->saveXML();
471 } else {
472 $xml = $dom->__toString();
474 $result_array['parsetree'] = $xml;
475 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'parsetree';
478 $result_mapping = [
479 'redirects' => 'r',
480 'langlinks' => 'll',
481 'categories' => 'cl',
482 'links' => 'pl',
483 'templates' => 'tl',
484 'images' => 'img',
485 'externallinks' => 'el',
486 'iwlinks' => 'iw',
487 'sections' => 's',
488 'headitems' => 'hi',
489 'modules' => 'm',
490 'indicators' => 'ind',
491 'modulescripts' => 'm',
492 'modulestyles' => 'm',
493 'properties' => 'pp',
494 'limitreportdata' => 'lr',
495 'parsewarnings' => 'pw'
497 $this->setIndexedTagNames( $result_array, $result_mapping );
498 $result->addValue( null, $this->getModuleName(), $result_array );
502 * Constructs a ParserOptions object
504 * @param WikiPage $pageObj
505 * @param array $params
507 * @return array [ ParserOptions, ScopedCallback, bool $suppressCache ]
509 protected function makeParserOptions( WikiPage $pageObj, array $params ) {
510 $popts = $pageObj->makeParserOptions( $this->getContext() );
511 $popts->enableLimitReport( !$params['disablepp'] && !$params['disablelimitreport'] );
512 $popts->setIsPreview( $params['preview'] || $params['sectionpreview'] );
513 $popts->setIsSectionPreview( $params['sectionpreview'] );
514 $popts->setEditSection( !$params['disableeditsection'] );
515 if ( $params['disabletidy'] ) {
516 $popts->setTidy( false );
518 $popts->setWrapOutputClass(
519 $params['wrapoutputclass'] === '' ? false : $params['wrapoutputclass']
522 $reset = null;
523 $suppressCache = false;
524 Hooks::run( 'ApiMakeParserOptions',
525 [ $popts, $pageObj->getTitle(), $params, $this, &$reset, &$suppressCache ] );
527 // Force cache suppression when $popts aren't cacheable.
528 $suppressCache = $suppressCache || !$popts->isSafeToCache();
530 return [ $popts, $reset, $suppressCache ];
534 * @param WikiPage $page
535 * @param ParserOptions $popts
536 * @param bool $suppressCache
537 * @param int $pageId
538 * @param Revision|null $rev
539 * @param bool $getContent
540 * @return ParserOutput
542 private function getParsedContent(
543 WikiPage $page, $popts, $suppressCache, $pageId, $rev, $getContent
545 $revId = $rev ? $rev->getId() : null;
546 $isDeleted = $rev && $rev->isDeleted( Revision::DELETED_TEXT );
548 if ( $getContent || $this->section !== false || $isDeleted ) {
549 if ( $rev ) {
550 $this->content = $rev->getContent( Revision::FOR_THIS_USER, $this->getUser() );
551 if ( !$this->content ) {
552 $this->dieWithError( [ 'apierror-missingcontent-revid', $revId ] );
554 } else {
555 $this->content = $page->getContent( Revision::FOR_THIS_USER, $this->getUser() );
556 if ( !$this->content ) {
557 $this->dieWithError( [ 'apierror-missingcontent-pageid', $pageId ] );
560 $this->contentIsDeleted = $isDeleted;
561 $this->contentIsSuppressed = $rev &&
562 $rev->isDeleted( Revision::DELETED_TEXT | Revision::DELETED_RESTRICTED );
565 if ( $this->section !== false ) {
566 $this->content = $this->getSectionContent(
567 $this->content,
568 $pageId === null ? $page->getTitle()->getPrefixedText() : $this->msg( 'pageid', $pageId )
570 return $this->content->getParserOutput( $page->getTitle(), $revId, $popts );
573 if ( $isDeleted ) {
574 // getParserOutput can't do revdeled revisions
575 $pout = $this->content->getParserOutput( $page->getTitle(), $revId, $popts );
576 } else {
577 // getParserOutput will save to Parser cache if able
578 $pout = $page->getParserOutput( $popts, $revId, $suppressCache );
580 if ( !$pout ) {
581 $this->dieWithError( [ 'apierror-nosuchrevid', $revId ?: $page->getLatest() ] );
584 return $pout;
588 * Extract the requested section from the given Content
590 * @param Content $content
591 * @param string|Message $what Identifies the content in error messages, e.g. page title.
592 * @return Content
594 private function getSectionContent( Content $content, $what ) {
595 // Not cached (save or load)
596 $section = $content->getSection( $this->section );
597 if ( $section === false ) {
598 $this->dieWithError( [ 'apierror-nosuchsection-what', $this->section, $what ], 'nosuchsection' );
600 if ( $section === null ) {
601 $this->dieWithError( [ 'apierror-sectionsnotsupported-what', $what ], 'nosuchsection' );
602 $section = false;
605 return $section;
609 * This mimicks the behavior of EditPage in formatting a summary
611 * @param Title $title of the page being parsed
612 * @param Array $params the API parameters of the request
613 * @return Content|bool
615 private function formatSummary( $title, $params ) {
616 global $wgParser;
617 $summary = !is_null( $params['summary'] ) ? $params['summary'] : '';
618 $sectionTitle = !is_null( $params['sectiontitle'] ) ? $params['sectiontitle'] : '';
620 if ( $this->section === 'new' && ( $sectionTitle === '' || $summary === '' ) ) {
621 if ( $sectionTitle !== '' ) {
622 $summary = $params['sectiontitle'];
624 if ( $summary !== '' ) {
625 $summary = wfMessage( 'newsectionsummary' )
626 ->rawParams( $wgParser->stripSectionName( $summary ) )
627 ->inContentLanguage()->text();
630 return Linker::formatComment( $summary, $title, $this->section === 'new' );
633 private function formatLangLinks( $links ) {
634 $result = [];
635 foreach ( $links as $link ) {
636 $entry = [];
637 $bits = explode( ':', $link, 2 );
638 $title = Title::newFromText( $link );
640 $entry['lang'] = $bits[0];
641 if ( $title ) {
642 $entry['url'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
643 // localised language name in 'uselang' language
644 $entry['langname'] = Language::fetchLanguageName(
645 $title->getInterwiki(),
646 $this->getLanguage()->getCode()
649 // native language name
650 $entry['autonym'] = Language::fetchLanguageName( $title->getInterwiki() );
652 ApiResult::setContentValue( $entry, 'title', $bits[1] );
653 $result[] = $entry;
656 return $result;
659 private function formatCategoryLinks( $links ) {
660 $result = [];
662 if ( !$links ) {
663 return $result;
666 // Fetch hiddencat property
667 $lb = new LinkBatch;
668 $lb->setArray( [ NS_CATEGORY => $links ] );
669 $db = $this->getDB();
670 $res = $db->select( [ 'page', 'page_props' ],
671 [ 'page_title', 'pp_propname' ],
672 $lb->constructSet( 'page', $db ),
673 __METHOD__,
675 [ 'page_props' => [
676 'LEFT JOIN', [ 'pp_propname' => 'hiddencat', 'pp_page = page_id' ]
679 $hiddencats = [];
680 foreach ( $res as $row ) {
681 $hiddencats[$row->page_title] = isset( $row->pp_propname );
684 $linkCache = LinkCache::singleton();
686 foreach ( $links as $link => $sortkey ) {
687 $entry = [];
688 $entry['sortkey'] = $sortkey;
689 // array keys will cast numeric category names to ints, so cast back to string
690 ApiResult::setContentValue( $entry, 'category', (string)$link );
691 if ( !isset( $hiddencats[$link] ) ) {
692 $entry['missing'] = true;
694 // We already know the link doesn't exist in the database, so
695 // tell LinkCache that before calling $title->isKnown().
696 $title = Title::makeTitle( NS_CATEGORY, $link );
697 $linkCache->addBadLinkObj( $title );
698 if ( $title->isKnown() ) {
699 $entry['known'] = true;
701 } elseif ( $hiddencats[$link] ) {
702 $entry['hidden'] = true;
704 $result[] = $entry;
707 return $result;
710 private function formatLinks( $links ) {
711 $result = [];
712 foreach ( $links as $ns => $nslinks ) {
713 foreach ( $nslinks as $title => $id ) {
714 $entry = [];
715 $entry['ns'] = $ns;
716 ApiResult::setContentValue( $entry, 'title', Title::makeTitle( $ns, $title )->getFullText() );
717 $entry['exists'] = $id != 0;
718 $result[] = $entry;
722 return $result;
725 private function formatIWLinks( $iw ) {
726 $result = [];
727 foreach ( $iw as $prefix => $titles ) {
728 foreach ( array_keys( $titles ) as $title ) {
729 $entry = [];
730 $entry['prefix'] = $prefix;
732 $title = Title::newFromText( "{$prefix}:{$title}" );
733 if ( $title ) {
734 $entry['url'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
737 ApiResult::setContentValue( $entry, 'title', $title->getFullText() );
738 $result[] = $entry;
742 return $result;
745 private function formatHeadItems( $headItems ) {
746 $result = [];
747 foreach ( $headItems as $tag => $content ) {
748 $entry = [];
749 $entry['tag'] = $tag;
750 ApiResult::setContentValue( $entry, 'content', $content );
751 $result[] = $entry;
754 return $result;
757 private function formatLimitReportData( $limitReportData ) {
758 $result = [];
760 foreach ( $limitReportData as $name => $value ) {
761 $entry = [];
762 $entry['name'] = $name;
763 if ( !is_array( $value ) ) {
764 $value = [ $value ];
766 ApiResult::setIndexedTagNameRecursive( $value, 'param' );
767 $entry = array_merge( $entry, $value );
768 $result[] = $entry;
771 return $result;
774 private function setIndexedTagNames( &$array, $mapping ) {
775 foreach ( $mapping as $key => $name ) {
776 if ( isset( $array[$key] ) ) {
777 ApiResult::setIndexedTagName( $array[$key], $name );
782 public function getAllowedParams() {
783 return [
784 'title' => null,
785 'text' => [
786 ApiBase::PARAM_TYPE => 'text',
788 'summary' => null,
789 'page' => null,
790 'pageid' => [
791 ApiBase::PARAM_TYPE => 'integer',
793 'redirects' => false,
794 'oldid' => [
795 ApiBase::PARAM_TYPE => 'integer',
797 'prop' => [
798 ApiBase::PARAM_DFLT => 'text|langlinks|categories|links|templates|' .
799 'images|externallinks|sections|revid|displaytitle|iwlinks|' .
800 'properties|parsewarnings',
801 ApiBase::PARAM_ISMULTI => true,
802 ApiBase::PARAM_TYPE => [
803 'text',
804 'langlinks',
805 'categories',
806 'categorieshtml',
807 'links',
808 'templates',
809 'images',
810 'externallinks',
811 'sections',
812 'revid',
813 'displaytitle',
814 'headhtml',
815 'modules',
816 'jsconfigvars',
817 'encodedjsconfigvars',
818 'indicators',
819 'iwlinks',
820 'wikitext',
821 'properties',
822 'limitreportdata',
823 'limitreporthtml',
824 'parsetree',
825 'parsewarnings',
826 'headitems',
828 ApiBase::PARAM_HELP_MSG_PER_VALUE => [
829 'parsetree' => [ 'apihelp-parse-paramvalue-prop-parsetree', CONTENT_MODEL_WIKITEXT ],
831 ApiBase::PARAM_DEPRECATED_VALUES => [
832 'headitems' => 'apiwarn-deprecation-parse-headitems',
835 'wrapoutputclass' => 'mw-parser-output',
836 'pst' => false,
837 'onlypst' => false,
838 'effectivelanglinks' => [
839 ApiBase::PARAM_DFLT => false,
840 ApiBase::PARAM_DEPRECATED => true,
842 'section' => null,
843 'sectiontitle' => [
844 ApiBase::PARAM_TYPE => 'string',
846 'disablepp' => [
847 ApiBase::PARAM_DFLT => false,
848 ApiBase::PARAM_DEPRECATED => true,
850 'disablelimitreport' => false,
851 'disableeditsection' => false,
852 'disabletidy' => false,
853 'generatexml' => [
854 ApiBase::PARAM_DFLT => false,
855 ApiBase::PARAM_HELP_MSG => [
856 'apihelp-parse-param-generatexml', CONTENT_MODEL_WIKITEXT
858 ApiBase::PARAM_DEPRECATED => true,
860 'preview' => false,
861 'sectionpreview' => false,
862 'disabletoc' => false,
863 'useskin' => [
864 ApiBase::PARAM_TYPE => array_keys( Skin::getAllowedSkins() ),
866 'contentformat' => [
867 ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
869 'contentmodel' => [
870 ApiBase::PARAM_TYPE => ContentHandler::getContentModels(),
875 protected function getExamplesMessages() {
876 return [
877 'action=parse&page=Project:Sandbox'
878 => 'apihelp-parse-example-page',
879 'action=parse&text={{Project:Sandbox}}&contentmodel=wikitext'
880 => 'apihelp-parse-example-text',
881 'action=parse&text={{PAGENAME}}&title=Test'
882 => 'apihelp-parse-example-texttitle',
883 'action=parse&summary=Some+[[link]]&prop='
884 => 'apihelp-parse-example-summary',
888 public function getHelpUrls() {
889 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Parsing_wikitext#parse';