]> scripts.mit.edu Git - autoinstalls/mediawiki.git/blob - includes/api/ApiParse.php
MediaWiki 1.30.2
[autoinstalls/mediawiki.git] / includes / api / ApiParse.php
1 <?php
2 /**
3  * Created on Dec 01, 2007
4  *
5  * Copyright © 2007 Yuri Astrakhan "<Firstname><Lastname>@gmail.com"
6  *
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.
11  *
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.
16  *
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
21  *
22  * @file
23  */
24
25 use MediaWiki\MediaWikiServices;
26
27 /**
28  * @ingroup API
29  */
30 class ApiParse extends ApiBase {
31
32         /** @var string $section */
33         private $section = null;
34
35         /** @var Content $content */
36         private $content = null;
37
38         /** @var Content $pstContent */
39         private $pstContent = null;
40
41         /** @var bool */
42         private $contentIsDeleted = false, $contentIsSuppressed = false;
43
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' );
47
48                 // Get parameters
49                 $params = $this->extractRequestParams();
50
51                 // No easy way to say that text and title or revid are allowed together
52                 // while the rest aren't, so just do it in three calls.
53                 $this->requireMaxOneParameter( $params, 'page', 'pageid', 'oldid', 'text' );
54                 $this->requireMaxOneParameter( $params, 'page', 'pageid', 'oldid', 'title' );
55                 $this->requireMaxOneParameter( $params, 'page', 'pageid', 'oldid', 'revid' );
56
57                 $text = $params['text'];
58                 $title = $params['title'];
59                 if ( $title === null ) {
60                         $titleProvided = false;
61                         // A title is needed for parsing, so arbitrarily choose one
62                         $title = 'API';
63                 } else {
64                         $titleProvided = true;
65                 }
66
67                 $page = $params['page'];
68                 $pageid = $params['pageid'];
69                 $oldid = $params['oldid'];
70
71                 $model = $params['contentmodel'];
72                 $format = $params['contentformat'];
73
74                 $prop = array_flip( $params['prop'] );
75
76                 if ( isset( $params['section'] ) ) {
77                         $this->section = $params['section'];
78                         if ( !preg_match( '/^((T-)?\d+|new)$/', $this->section ) ) {
79                                 $this->dieWithError( 'apierror-invalidsection' );
80                         }
81                 } else {
82                         $this->section = false;
83                 }
84
85                 // The parser needs $wgTitle to be set, apparently the
86                 // $title parameter in Parser::parse isn't enough *sigh*
87                 // TODO: Does this still need $wgTitle?
88                 global $wgParser, $wgTitle;
89
90                 $redirValues = null;
91
92                 $needContent = isset( $prop['wikitext'] ) ||
93                         isset( $prop['parsetree'] ) || $params['generatexml'];
94
95                 // Return result
96                 $result = $this->getResult();
97
98                 if ( !is_null( $oldid ) || !is_null( $pageid ) || !is_null( $page ) ) {
99                         if ( $this->section === 'new' ) {
100                                 $this->dieWithError( 'apierror-invalidparammix-parse-new-section', 'invalidparammix' );
101                         }
102                         if ( !is_null( $oldid ) ) {
103                                 // Don't use the parser cache
104                                 $rev = Revision::newFromId( $oldid );
105                                 if ( !$rev ) {
106                                         $this->dieWithError( [ 'apierror-nosuchrevid', $oldid ] );
107                                 }
108
109                                 $this->checkTitleUserPermissions( $rev->getTitle(), 'read' );
110                                 if ( !$rev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) {
111                                         $this->dieWithError(
112                                                 [ 'apierror-permissiondenied', $this->msg( 'action-deletedtext' ) ]
113                                         );
114                                 }
115
116                                 $titleObj = $rev->getTitle();
117                                 $wgTitle = $titleObj;
118                                 $pageObj = WikiPage::factory( $titleObj );
119                                 list( $popts, $reset, $suppressCache ) = $this->makeParserOptions( $pageObj, $params );
120                                 $p_result = $this->getParsedContent(
121                                         $pageObj, $popts, $suppressCache, $pageid, $rev, $needContent
122                                 );
123                         } else { // Not $oldid, but $pageid or $page
124                                 if ( $params['redirects'] ) {
125                                         $reqParams = [
126                                                 'redirects' => '',
127                                         ];
128                                         if ( !is_null( $pageid ) ) {
129                                                 $reqParams['pageids'] = $pageid;
130                                         } else { // $page
131                                                 $reqParams['titles'] = $page;
132                                         }
133                                         $req = new FauxRequest( $reqParams );
134                                         $main = new ApiMain( $req );
135                                         $pageSet = new ApiPageSet( $main );
136                                         $pageSet->execute();
137                                         $redirValues = $pageSet->getRedirectTitlesAsResult( $this->getResult() );
138
139                                         $to = $page;
140                                         foreach ( $pageSet->getRedirectTitles() as $title ) {
141                                                 $to = $title->getFullText();
142                                         }
143                                         $pageParams = [ 'title' => $to ];
144                                 } elseif ( !is_null( $pageid ) ) {
145                                         $pageParams = [ 'pageid' => $pageid ];
146                                 } else { // $page
147                                         $pageParams = [ 'title' => $page ];
148                                 }
149
150                                 $pageObj = $this->getTitleOrPageId( $pageParams, 'fromdb' );
151                                 $titleObj = $pageObj->getTitle();
152                                 if ( !$titleObj || !$titleObj->exists() ) {
153                                         $this->dieWithError( 'apierror-missingtitle' );
154                                 }
155
156                                 $this->checkTitleUserPermissions( $titleObj, 'read' );
157                                 $wgTitle = $titleObj;
158
159                                 if ( isset( $prop['revid'] ) ) {
160                                         $oldid = $pageObj->getLatest();
161                                 }
162
163                                 list( $popts, $reset, $suppressCache ) = $this->makeParserOptions( $pageObj, $params );
164                                 $p_result = $this->getParsedContent(
165                                         $pageObj, $popts, $suppressCache, $pageid, null, $needContent
166                                 );
167                         }
168                 } else { // Not $oldid, $pageid, $page. Hence based on $text
169                         $titleObj = Title::newFromText( $title );
170                         if ( !$titleObj || $titleObj->isExternal() ) {
171                                 $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $title ) ] );
172                         }
173                         $revid = $params['revid'];
174                         if ( $revid !== null ) {
175                                 $rev = Revision::newFromId( $revid );
176                                 if ( !$rev ) {
177                                         $this->dieWithError( [ 'apierror-nosuchrevid', $revid ] );
178                                 }
179                                 $pTitleObj = $titleObj;
180                                 $titleObj = $rev->getTitle();
181                                 if ( $titleProvided ) {
182                                         if ( !$titleObj->equals( $pTitleObj ) ) {
183                                                 $this->addWarning( [ 'apierror-revwrongpage', $rev->getId(),
184                                                         wfEscapeWikiText( $pTitleObj->getPrefixedText() ) ] );
185                                         }
186                                 } else {
187                                         // Consider the title derived from the revid as having
188                                         // been provided.
189                                         $titleProvided = true;
190                                 }
191                         }
192                         $wgTitle = $titleObj;
193                         if ( $titleObj->canExist() ) {
194                                 $pageObj = WikiPage::factory( $titleObj );
195                         } else {
196                                 // Do like MediaWiki::initializeArticle()
197                                 $article = Article::newFromTitle( $titleObj, $this->getContext() );
198                                 $pageObj = $article->getPage();
199                         }
200
201                         list( $popts, $reset ) = $this->makeParserOptions( $pageObj, $params );
202                         $textProvided = !is_null( $text );
203
204                         if ( !$textProvided ) {
205                                 if ( $titleProvided && ( $prop || $params['generatexml'] ) ) {
206                                         if ( $revid !== null ) {
207                                                 $this->addWarning( 'apiwarn-parse-revidwithouttext' );
208                                         } else {
209                                                 $this->addWarning( 'apiwarn-parse-titlewithouttext' );
210                                         }
211                                 }
212                                 // Prevent warning from ContentHandler::makeContent()
213                                 $text = '';
214                         }
215
216                         // If we are parsing text, do not use the content model of the default
217                         // API title, but default to wikitext to keep BC.
218                         if ( $textProvided && !$titleProvided && is_null( $model ) ) {
219                                 $model = CONTENT_MODEL_WIKITEXT;
220                                 $this->addWarning( [ 'apiwarn-parse-nocontentmodel', $model ] );
221                         }
222
223                         try {
224                                 $this->content = ContentHandler::makeContent( $text, $titleObj, $model, $format );
225                         } catch ( MWContentSerializationException $ex ) {
226                                 $this->dieWithException( $ex, [
227                                         'wrap' => ApiMessage::create( 'apierror-contentserializationexception', 'parseerror' )
228                                 ] );
229                         }
230
231                         if ( $this->section !== false ) {
232                                 if ( $this->section === 'new' ) {
233                                         // Insert the section title above the content.
234                                         if ( !is_null( $params['sectiontitle'] ) && $params['sectiontitle'] !== '' ) {
235                                                 $this->content = $this->content->addSectionHeader( $params['sectiontitle'] );
236                                         }
237                                 } else {
238                                         $this->content = $this->getSectionContent( $this->content, $titleObj->getPrefixedText() );
239                                 }
240                         }
241
242                         if ( $params['pst'] || $params['onlypst'] ) {
243                                 $this->pstContent = $this->content->preSaveTransform( $titleObj, $this->getUser(), $popts );
244                         }
245                         if ( $params['onlypst'] ) {
246                                 // Build a result and bail out
247                                 $result_array = [];
248                                 if ( $this->contentIsDeleted ) {
249                                         $result_array['textdeleted'] = true;
250                                 }
251                                 if ( $this->contentIsSuppressed ) {
252                                         $result_array['textsuppressed'] = true;
253                                 }
254                                 $result_array['text'] = $this->pstContent->serialize( $format );
255                                 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'text';
256                                 if ( isset( $prop['wikitext'] ) ) {
257                                         $result_array['wikitext'] = $this->content->serialize( $format );
258                                         $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'wikitext';
259                                 }
260                                 if ( !is_null( $params['summary'] ) ||
261                                         ( !is_null( $params['sectiontitle'] ) && $this->section === 'new' )
262                                 ) {
263                                         $result_array['parsedsummary'] = $this->formatSummary( $titleObj, $params );
264                                         $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'parsedsummary';
265                                 }
266
267                                 $result->addValue( null, $this->getModuleName(), $result_array );
268
269                                 return;
270                         }
271
272                         // Not cached (save or load)
273                         if ( $params['pst'] ) {
274                                 $p_result = $this->pstContent->getParserOutput( $titleObj, $revid, $popts );
275                         } else {
276                                 $p_result = $this->content->getParserOutput( $titleObj, $revid, $popts );
277                         }
278                 }
279
280                 $result_array = [];
281
282                 $result_array['title'] = $titleObj->getPrefixedText();
283                 $result_array['pageid'] = $pageid ?: $pageObj->getId();
284                 if ( $this->contentIsDeleted ) {
285                         $result_array['textdeleted'] = true;
286                 }
287                 if ( $this->contentIsSuppressed ) {
288                         $result_array['textsuppressed'] = true;
289                 }
290
291                 if ( $params['disabletoc'] ) {
292                         $p_result->setTOCEnabled( false );
293                 }
294
295                 if ( isset( $params['useskin'] ) ) {
296                         $factory = MediaWikiServices::getInstance()->getSkinFactory();
297                         $skin = $factory->makeSkin( Skin::normalizeKey( $params['useskin'] ) );
298                 } else {
299                         $skin = null;
300                 }
301
302                 $outputPage = null;
303                 if ( $skin || isset( $prop['headhtml'] ) || isset( $prop['categorieshtml'] ) ) {
304                         // Enabling the skin via 'useskin', 'headhtml', or 'categorieshtml'
305                         // gets OutputPage and Skin involved, which (among others) applies
306                         // these hooks:
307                         // - ParserOutputHooks
308                         // - Hook: LanguageLinks
309                         // - Hook: OutputPageParserOutput
310                         // - Hook: OutputPageMakeCategoryLinks
311                         $context = new DerivativeContext( $this->getContext() );
312                         $context->setTitle( $titleObj );
313                         $context->setWikiPage( $pageObj );
314
315                         if ( $skin ) {
316                                 // Use the skin specified by 'useskin'
317                                 $context->setSkin( $skin );
318                                 // Context clones the skin, refetch to stay in sync. (T166022)
319                                 $skin = $context->getSkin();
320                         } else {
321                                 // Make sure the context's skin refers to the context. Without this,
322                                 // $outputPage->getSkin()->getOutput() !== $outputPage which
323                                 // confuses some of the output.
324                                 $context->setSkin( $context->getSkin() );
325                         }
326
327                         $outputPage = new OutputPage( $context );
328                         $outputPage->addParserOutputMetadata( $p_result );
329                         $context->setOutput( $outputPage );
330
331                         if ( $skin ) {
332                                 // Based on OutputPage::output()
333                                 foreach ( $skin->getDefaultModules() as $group ) {
334                                         $outputPage->addModules( $group );
335                                 }
336                         }
337                 }
338
339                 if ( !is_null( $oldid ) ) {
340                         $result_array['revid'] = intval( $oldid );
341                 }
342
343                 if ( $params['redirects'] && !is_null( $redirValues ) ) {
344                         $result_array['redirects'] = $redirValues;
345                 }
346
347                 if ( isset( $prop['text'] ) ) {
348                         $result_array['text'] = $p_result->getText();
349                         $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'text';
350                 }
351
352                 if ( !is_null( $params['summary'] ) ||
353                         ( !is_null( $params['sectiontitle'] ) && $this->section === 'new' )
354                 ) {
355                         $result_array['parsedsummary'] = $this->formatSummary( $titleObj, $params );
356                         $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'parsedsummary';
357                 }
358
359                 if ( isset( $prop['langlinks'] ) ) {
360                         if ( $skin ) {
361                                 $langlinks = $outputPage->getLanguageLinks();
362                         } else {
363                                 $langlinks = $p_result->getLanguageLinks();
364                                 // The deprecated 'effectivelanglinks' option depredates OutputPage
365                                 // support via 'useskin'. If not already applied, then run just this
366                                 // one hook of OutputPage::addParserOutputMetadata here.
367                                 if ( $params['effectivelanglinks'] ) {
368                                         $linkFlags = [];
369                                         Hooks::run( 'LanguageLinks', [ $titleObj, &$langlinks, &$linkFlags ] );
370                                 }
371                         }
372
373                         $result_array['langlinks'] = $this->formatLangLinks( $langlinks );
374                 }
375                 if ( isset( $prop['categories'] ) ) {
376                         $result_array['categories'] = $this->formatCategoryLinks( $p_result->getCategories() );
377                 }
378                 if ( isset( $prop['categorieshtml'] ) ) {
379                         $result_array['categorieshtml'] = $outputPage->getSkin()->getCategories();
380                         $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'categorieshtml';
381                 }
382                 if ( isset( $prop['links'] ) ) {
383                         $result_array['links'] = $this->formatLinks( $p_result->getLinks() );
384                 }
385                 if ( isset( $prop['templates'] ) ) {
386                         $result_array['templates'] = $this->formatLinks( $p_result->getTemplates() );
387                 }
388                 if ( isset( $prop['images'] ) ) {
389                         $result_array['images'] = array_keys( $p_result->getImages() );
390                 }
391                 if ( isset( $prop['externallinks'] ) ) {
392                         $result_array['externallinks'] = array_keys( $p_result->getExternalLinks() );
393                 }
394                 if ( isset( $prop['sections'] ) ) {
395                         $result_array['sections'] = $p_result->getSections();
396                 }
397                 if ( isset( $prop['parsewarnings'] ) ) {
398                         $result_array['parsewarnings'] = $p_result->getWarnings();
399                 }
400
401                 if ( isset( $prop['displaytitle'] ) ) {
402                         $result_array['displaytitle'] = $p_result->getDisplayTitle() ?:
403                                 $titleObj->getPrefixedText();
404                 }
405
406                 if ( isset( $prop['headitems'] ) ) {
407                         if ( $skin ) {
408                                 $result_array['headitems'] = $this->formatHeadItems( $outputPage->getHeadItemsArray() );
409                         } else {
410                                 $result_array['headitems'] = $this->formatHeadItems( $p_result->getHeadItems() );
411                         }
412                 }
413
414                 if ( isset( $prop['headhtml'] ) ) {
415                         $result_array['headhtml'] = $outputPage->headElement( $context->getSkin() );
416                         $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'headhtml';
417                 }
418
419                 if ( isset( $prop['modules'] ) ) {
420                         if ( $skin ) {
421                                 $result_array['modules'] = $outputPage->getModules();
422                                 $result_array['modulescripts'] = $outputPage->getModuleScripts();
423                                 $result_array['modulestyles'] = $outputPage->getModuleStyles();
424                         } else {
425                                 $result_array['modules'] = array_values( array_unique( $p_result->getModules() ) );
426                                 $result_array['modulescripts'] = array_values( array_unique( $p_result->getModuleScripts() ) );
427                                 $result_array['modulestyles'] = array_values( array_unique( $p_result->getModuleStyles() ) );
428                         }
429                 }
430
431                 if ( isset( $prop['jsconfigvars'] ) ) {
432                         $jsconfigvars = $skin ? $outputPage->getJsConfigVars() : $p_result->getJsConfigVars();
433                         $result_array['jsconfigvars'] = ApiResult::addMetadataToResultVars( $jsconfigvars );
434                 }
435
436                 if ( isset( $prop['encodedjsconfigvars'] ) ) {
437                         $jsconfigvars = $skin ? $outputPage->getJsConfigVars() : $p_result->getJsConfigVars();
438                         $result_array['encodedjsconfigvars'] = FormatJson::encode(
439                                 $jsconfigvars,
440                                 false,
441                                 FormatJson::ALL_OK
442                         );
443                         $result_array[ApiResult::META_SUBELEMENTS][] = 'encodedjsconfigvars';
444                 }
445
446                 if ( isset( $prop['modules'] ) &&
447                         !isset( $prop['jsconfigvars'] ) && !isset( $prop['encodedjsconfigvars'] ) ) {
448                         $this->addWarning( 'apiwarn-moduleswithoutvars' );
449                 }
450
451                 if ( isset( $prop['indicators'] ) ) {
452                         if ( $skin ) {
453                                 $result_array['indicators'] = (array)$outputPage->getIndicators();
454                         } else {
455                                 $result_array['indicators'] = (array)$p_result->getIndicators();
456                         }
457                         ApiResult::setArrayType( $result_array['indicators'], 'BCkvp', 'name' );
458                 }
459
460                 if ( isset( $prop['iwlinks'] ) ) {
461                         $result_array['iwlinks'] = $this->formatIWLinks( $p_result->getInterwikiLinks() );
462                 }
463
464                 if ( isset( $prop['wikitext'] ) ) {
465                         $result_array['wikitext'] = $this->content->serialize( $format );
466                         $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'wikitext';
467                         if ( !is_null( $this->pstContent ) ) {
468                                 $result_array['psttext'] = $this->pstContent->serialize( $format );
469                                 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'psttext';
470                         }
471                 }
472                 if ( isset( $prop['properties'] ) ) {
473                         $result_array['properties'] = (array)$p_result->getProperties();
474                         ApiResult::setArrayType( $result_array['properties'], 'BCkvp', 'name' );
475                 }
476
477                 if ( isset( $prop['limitreportdata'] ) ) {
478                         $result_array['limitreportdata'] =
479                                 $this->formatLimitReportData( $p_result->getLimitReportData() );
480                 }
481                 if ( isset( $prop['limitreporthtml'] ) ) {
482                         $result_array['limitreporthtml'] = EditPage::getPreviewLimitReport( $p_result );
483                         $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'limitreporthtml';
484                 }
485
486                 if ( isset( $prop['parsetree'] ) || $params['generatexml'] ) {
487                         if ( $this->content->getModel() != CONTENT_MODEL_WIKITEXT ) {
488                                 $this->dieWithError( 'apierror-parsetree-notwikitext', 'notwikitext' );
489                         }
490
491                         $wgParser->startExternalParse( $titleObj, $popts, Parser::OT_PREPROCESS );
492                         $dom = $wgParser->preprocessToDom( $this->content->getNativeData() );
493                         if ( is_callable( [ $dom, 'saveXML' ] ) ) {
494                                 $xml = $dom->saveXML();
495                         } else {
496                                 $xml = $dom->__toString();
497                         }
498                         $result_array['parsetree'] = $xml;
499                         $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'parsetree';
500                 }
501
502                 $result_mapping = [
503                         'redirects' => 'r',
504                         'langlinks' => 'll',
505                         'categories' => 'cl',
506                         'links' => 'pl',
507                         'templates' => 'tl',
508                         'images' => 'img',
509                         'externallinks' => 'el',
510                         'iwlinks' => 'iw',
511                         'sections' => 's',
512                         'headitems' => 'hi',
513                         'modules' => 'm',
514                         'indicators' => 'ind',
515                         'modulescripts' => 'm',
516                         'modulestyles' => 'm',
517                         'properties' => 'pp',
518                         'limitreportdata' => 'lr',
519                         'parsewarnings' => 'pw'
520                 ];
521                 $this->setIndexedTagNames( $result_array, $result_mapping );
522                 $result->addValue( null, $this->getModuleName(), $result_array );
523         }
524
525         /**
526          * Constructs a ParserOptions object
527          *
528          * @param WikiPage $pageObj
529          * @param array $params
530          *
531          * @return array [ ParserOptions, ScopedCallback, bool $suppressCache ]
532          */
533         protected function makeParserOptions( WikiPage $pageObj, array $params ) {
534                 $popts = $pageObj->makeParserOptions( $this->getContext() );
535                 $popts->enableLimitReport( !$params['disablepp'] && !$params['disablelimitreport'] );
536                 $popts->setIsPreview( $params['preview'] || $params['sectionpreview'] );
537                 $popts->setIsSectionPreview( $params['sectionpreview'] );
538                 $popts->setEditSection( !$params['disableeditsection'] );
539                 if ( $params['disabletidy'] ) {
540                         $popts->setTidy( false );
541                 }
542                 $popts->setWrapOutputClass(
543                         $params['wrapoutputclass'] === '' ? false : $params['wrapoutputclass']
544                 );
545
546                 $reset = null;
547                 $suppressCache = false;
548                 Hooks::run( 'ApiMakeParserOptions',
549                         [ $popts, $pageObj->getTitle(), $params, $this, &$reset, &$suppressCache ] );
550
551                 // Force cache suppression when $popts aren't cacheable.
552                 $suppressCache = $suppressCache || !$popts->isSafeToCache();
553
554                 return [ $popts, $reset, $suppressCache ];
555         }
556
557         /**
558          * @param WikiPage $page
559          * @param ParserOptions $popts
560          * @param bool $suppressCache
561          * @param int $pageId
562          * @param Revision|null $rev
563          * @param bool $getContent
564          * @return ParserOutput
565          */
566         private function getParsedContent(
567                 WikiPage $page, $popts, $suppressCache, $pageId, $rev, $getContent
568         ) {
569                 $revId = $rev ? $rev->getId() : null;
570                 $isDeleted = $rev && $rev->isDeleted( Revision::DELETED_TEXT );
571
572                 if ( $getContent || $this->section !== false || $isDeleted ) {
573                         if ( $rev ) {
574                                 $this->content = $rev->getContent( Revision::FOR_THIS_USER, $this->getUser() );
575                                 if ( !$this->content ) {
576                                         $this->dieWithError( [ 'apierror-missingcontent-revid', $revId ] );
577                                 }
578                         } else {
579                                 $this->content = $page->getContent( Revision::FOR_THIS_USER, $this->getUser() );
580                                 if ( !$this->content ) {
581                                         $this->dieWithError( [ 'apierror-missingcontent-pageid', $pageId ] );
582                                 }
583                         }
584                         $this->contentIsDeleted = $isDeleted;
585                         $this->contentIsSuppressed = $rev &&
586                                 $rev->isDeleted( Revision::DELETED_TEXT | Revision::DELETED_RESTRICTED );
587                 }
588
589                 if ( $this->section !== false ) {
590                         $this->content = $this->getSectionContent(
591                                 $this->content,
592                                 $pageId === null ? $page->getTitle()->getPrefixedText() : $this->msg( 'pageid', $pageId )
593                         );
594                         return $this->content->getParserOutput( $page->getTitle(), $revId, $popts );
595                 }
596
597                 if ( $isDeleted ) {
598                         // getParserOutput can't do revdeled revisions
599                         $pout = $this->content->getParserOutput( $page->getTitle(), $revId, $popts );
600                 } else {
601                         // getParserOutput will save to Parser cache if able
602                         $pout = $page->getParserOutput( $popts, $revId, $suppressCache );
603                 }
604                 if ( !$pout ) {
605                         $this->dieWithError( [ 'apierror-nosuchrevid', $revId ?: $page->getLatest() ] );
606                 }
607
608                 return $pout;
609         }
610
611         /**
612          * Extract the requested section from the given Content
613          *
614          * @param Content $content
615          * @param string|Message $what Identifies the content in error messages, e.g. page title.
616          * @return Content
617          */
618         private function getSectionContent( Content $content, $what ) {
619                 // Not cached (save or load)
620                 $section = $content->getSection( $this->section );
621                 if ( $section === false ) {
622                         $this->dieWithError( [ 'apierror-nosuchsection-what', $this->section, $what ], 'nosuchsection' );
623                 }
624                 if ( $section === null ) {
625                         $this->dieWithError( [ 'apierror-sectionsnotsupported-what', $what ], 'nosuchsection' );
626                         $section = false;
627                 }
628
629                 return $section;
630         }
631
632         /**
633          * This mimicks the behavior of EditPage in formatting a summary
634          *
635          * @param Title $title of the page being parsed
636          * @param Array $params the API parameters of the request
637          * @return Content|bool
638          */
639         private function formatSummary( $title, $params ) {
640                 global $wgParser;
641                 $summary = !is_null( $params['summary'] ) ? $params['summary'] : '';
642                 $sectionTitle = !is_null( $params['sectiontitle'] ) ? $params['sectiontitle'] : '';
643
644                 if ( $this->section === 'new' && ( $sectionTitle === '' || $summary === '' ) ) {
645                         if ( $sectionTitle !== '' ) {
646                                 $summary = $params['sectiontitle'];
647                         }
648                         if ( $summary !== '' ) {
649                                 $summary = wfMessage( 'newsectionsummary' )
650                                         ->rawParams( $wgParser->stripSectionName( $summary ) )
651                                                 ->inContentLanguage()->text();
652                         }
653                 }
654                 return Linker::formatComment( $summary, $title, $this->section === 'new' );
655         }
656
657         private function formatLangLinks( $links ) {
658                 $result = [];
659                 foreach ( $links as $link ) {
660                         $entry = [];
661                         $bits = explode( ':', $link, 2 );
662                         $title = Title::newFromText( $link );
663
664                         $entry['lang'] = $bits[0];
665                         if ( $title ) {
666                                 $entry['url'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
667                                 // localised language name in 'uselang' language
668                                 $entry['langname'] = Language::fetchLanguageName(
669                                         $title->getInterwiki(),
670                                         $this->getLanguage()->getCode()
671                                 );
672
673                                 // native language name
674                                 $entry['autonym'] = Language::fetchLanguageName( $title->getInterwiki() );
675                         }
676                         ApiResult::setContentValue( $entry, 'title', $bits[1] );
677                         $result[] = $entry;
678                 }
679
680                 return $result;
681         }
682
683         private function formatCategoryLinks( $links ) {
684                 $result = [];
685
686                 if ( !$links ) {
687                         return $result;
688                 }
689
690                 // Fetch hiddencat property
691                 $lb = new LinkBatch;
692                 $lb->setArray( [ NS_CATEGORY => $links ] );
693                 $db = $this->getDB();
694                 $res = $db->select( [ 'page', 'page_props' ],
695                         [ 'page_title', 'pp_propname' ],
696                         $lb->constructSet( 'page', $db ),
697                         __METHOD__,
698                         [],
699                         [ 'page_props' => [
700                                 'LEFT JOIN', [ 'pp_propname' => 'hiddencat', 'pp_page = page_id' ]
701                         ] ]
702                 );
703                 $hiddencats = [];
704                 foreach ( $res as $row ) {
705                         $hiddencats[$row->page_title] = isset( $row->pp_propname );
706                 }
707
708                 $linkCache = LinkCache::singleton();
709
710                 foreach ( $links as $link => $sortkey ) {
711                         $entry = [];
712                         $entry['sortkey'] = $sortkey;
713                         // array keys will cast numeric category names to ints, so cast back to string
714                         ApiResult::setContentValue( $entry, 'category', (string)$link );
715                         if ( !isset( $hiddencats[$link] ) ) {
716                                 $entry['missing'] = true;
717
718                                 // We already know the link doesn't exist in the database, so
719                                 // tell LinkCache that before calling $title->isKnown().
720                                 $title = Title::makeTitle( NS_CATEGORY, $link );
721                                 $linkCache->addBadLinkObj( $title );
722                                 if ( $title->isKnown() ) {
723                                         $entry['known'] = true;
724                                 }
725                         } elseif ( $hiddencats[$link] ) {
726                                 $entry['hidden'] = true;
727                         }
728                         $result[] = $entry;
729                 }
730
731                 return $result;
732         }
733
734         private function formatLinks( $links ) {
735                 $result = [];
736                 foreach ( $links as $ns => $nslinks ) {
737                         foreach ( $nslinks as $title => $id ) {
738                                 $entry = [];
739                                 $entry['ns'] = $ns;
740                                 ApiResult::setContentValue( $entry, 'title', Title::makeTitle( $ns, $title )->getFullText() );
741                                 $entry['exists'] = $id != 0;
742                                 $result[] = $entry;
743                         }
744                 }
745
746                 return $result;
747         }
748
749         private function formatIWLinks( $iw ) {
750                 $result = [];
751                 foreach ( $iw as $prefix => $titles ) {
752                         foreach ( array_keys( $titles ) as $title ) {
753                                 $entry = [];
754                                 $entry['prefix'] = $prefix;
755
756                                 $title = Title::newFromText( "{$prefix}:{$title}" );
757                                 if ( $title ) {
758                                         $entry['url'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
759                                 }
760
761                                 ApiResult::setContentValue( $entry, 'title', $title->getFullText() );
762                                 $result[] = $entry;
763                         }
764                 }
765
766                 return $result;
767         }
768
769         private function formatHeadItems( $headItems ) {
770                 $result = [];
771                 foreach ( $headItems as $tag => $content ) {
772                         $entry = [];
773                         $entry['tag'] = $tag;
774                         ApiResult::setContentValue( $entry, 'content', $content );
775                         $result[] = $entry;
776                 }
777
778                 return $result;
779         }
780
781         private function formatLimitReportData( $limitReportData ) {
782                 $result = [];
783
784                 foreach ( $limitReportData as $name => $value ) {
785                         $entry = [];
786                         $entry['name'] = $name;
787                         if ( !is_array( $value ) ) {
788                                 $value = [ $value ];
789                         }
790                         ApiResult::setIndexedTagNameRecursive( $value, 'param' );
791                         $entry = array_merge( $entry, $value );
792                         $result[] = $entry;
793                 }
794
795                 return $result;
796         }
797
798         private function setIndexedTagNames( &$array, $mapping ) {
799                 foreach ( $mapping as $key => $name ) {
800                         if ( isset( $array[$key] ) ) {
801                                 ApiResult::setIndexedTagName( $array[$key], $name );
802                         }
803                 }
804         }
805
806         public function getAllowedParams() {
807                 return [
808                         'title' => null,
809                         'text' => [
810                                 ApiBase::PARAM_TYPE => 'text',
811                         ],
812                         'revid' => [
813                                 ApiBase::PARAM_TYPE => 'integer',
814                         ],
815                         'summary' => null,
816                         'page' => null,
817                         'pageid' => [
818                                 ApiBase::PARAM_TYPE => 'integer',
819                         ],
820                         'redirects' => false,
821                         'oldid' => [
822                                 ApiBase::PARAM_TYPE => 'integer',
823                         ],
824                         'prop' => [
825                                 ApiBase::PARAM_DFLT => 'text|langlinks|categories|links|templates|' .
826                                         'images|externallinks|sections|revid|displaytitle|iwlinks|' .
827                                         'properties|parsewarnings',
828                                 ApiBase::PARAM_ISMULTI => true,
829                                 ApiBase::PARAM_TYPE => [
830                                         'text',
831                                         'langlinks',
832                                         'categories',
833                                         'categorieshtml',
834                                         'links',
835                                         'templates',
836                                         'images',
837                                         'externallinks',
838                                         'sections',
839                                         'revid',
840                                         'displaytitle',
841                                         'headhtml',
842                                         'modules',
843                                         'jsconfigvars',
844                                         'encodedjsconfigvars',
845                                         'indicators',
846                                         'iwlinks',
847                                         'wikitext',
848                                         'properties',
849                                         'limitreportdata',
850                                         'limitreporthtml',
851                                         'parsetree',
852                                         'parsewarnings',
853                                         'headitems',
854                                 ],
855                                 ApiBase::PARAM_HELP_MSG_PER_VALUE => [
856                                         'parsetree' => [ 'apihelp-parse-paramvalue-prop-parsetree', CONTENT_MODEL_WIKITEXT ],
857                                 ],
858                                 ApiBase::PARAM_DEPRECATED_VALUES => [
859                                         'headitems' => 'apiwarn-deprecation-parse-headitems',
860                                 ],
861                         ],
862                         'wrapoutputclass' => 'mw-parser-output',
863                         'pst' => false,
864                         'onlypst' => false,
865                         'effectivelanglinks' => [
866                                 ApiBase::PARAM_DFLT => false,
867                                 ApiBase::PARAM_DEPRECATED => true,
868                         ],
869                         'section' => null,
870                         'sectiontitle' => [
871                                 ApiBase::PARAM_TYPE => 'string',
872                         ],
873                         'disablepp' => [
874                                 ApiBase::PARAM_DFLT => false,
875                                 ApiBase::PARAM_DEPRECATED => true,
876                         ],
877                         'disablelimitreport' => false,
878                         'disableeditsection' => false,
879                         'disabletidy' => false,
880                         'generatexml' => [
881                                 ApiBase::PARAM_DFLT => false,
882                                 ApiBase::PARAM_HELP_MSG => [
883                                         'apihelp-parse-param-generatexml', CONTENT_MODEL_WIKITEXT
884                                 ],
885                                 ApiBase::PARAM_DEPRECATED => true,
886                         ],
887                         'preview' => false,
888                         'sectionpreview' => false,
889                         'disabletoc' => false,
890                         'useskin' => [
891                                 ApiBase::PARAM_TYPE => array_keys( Skin::getAllowedSkins() ),
892                         ],
893                         'contentformat' => [
894                                 ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
895                         ],
896                         'contentmodel' => [
897                                 ApiBase::PARAM_TYPE => ContentHandler::getContentModels(),
898                         ]
899                 ];
900         }
901
902         protected function getExamplesMessages() {
903                 return [
904                         'action=parse&page=Project:Sandbox'
905                                 => 'apihelp-parse-example-page',
906                         'action=parse&text={{Project:Sandbox}}&contentmodel=wikitext'
907                                 => 'apihelp-parse-example-text',
908                         'action=parse&text={{PAGENAME}}&title=Test'
909                                 => 'apihelp-parse-example-texttitle',
910                         'action=parse&summary=Some+[[link]]&prop='
911                                 => 'apihelp-parse-example-summary',
912                 ];
913         }
914
915         public function getHelpUrls() {
916                 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Parsing_wikitext#parse';
917         }
918 }