35fad4a47496eb84e199cdf2b0f8def1bec9a53c
[lhc/web/wiklou.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 /**
26 * @ingroup API
27 */
28 class ApiParse extends ApiBase {
29
30 /** @var string $section */
31 private $section = null;
32
33 /** @var Content $content */
34 private $content = null;
35
36 /** @var Content $pstContent */
37 private $pstContent = null;
38
39 public function execute() {
40 // The data is hot but user-dependent, like page views, so we set vary cookies
41 $this->getMain()->setCacheMode( 'anon-public-user-private' );
42
43 // Get parameters
44 $params = $this->extractRequestParams();
45 $text = $params['text'];
46 $title = $params['title'];
47 if ( $title === null ) {
48 $titleProvided = false;
49 // A title is needed for parsing, so arbitrarily choose one
50 $title = 'API';
51 } else {
52 $titleProvided = true;
53 }
54
55 $page = $params['page'];
56 $pageid = $params['pageid'];
57 $oldid = $params['oldid'];
58
59 $model = $params['contentmodel'];
60 $format = $params['contentformat'];
61
62 if ( !is_null( $page ) && ( !is_null( $text ) || $titleProvided ) ) {
63 $this->dieUsage(
64 'The page parameter cannot be used together with the text and title parameters',
65 'params'
66 );
67 }
68
69 $prop = array_flip( $params['prop'] );
70
71 if ( isset( $params['section'] ) ) {
72 $this->section = $params['section'];
73 if ( !preg_match( '/^((T-)?\d+|new)$/', $this->section ) ) {
74 $this->dieUsage(
75 'The section parameter must be a valid section id or "new"', 'invalidsection'
76 );
77 }
78 } else {
79 $this->section = false;
80 }
81
82 // The parser needs $wgTitle to be set, apparently the
83 // $title parameter in Parser::parse isn't enough *sigh*
84 // TODO: Does this still need $wgTitle?
85 global $wgParser, $wgTitle;
86
87 $redirValues = null;
88
89 // Return result
90 $result = $this->getResult();
91
92 if ( !is_null( $oldid ) || !is_null( $pageid ) || !is_null( $page ) ) {
93 if ( $this->section === 'new' ) {
94 $this->dieUsage(
95 'section=new cannot be combined with oldid, pageid or page parameters. ' .
96 'Please use text', 'params'
97 );
98 }
99 if ( !is_null( $oldid ) ) {
100 // Don't use the parser cache
101 $rev = Revision::newFromId( $oldid );
102 if ( !$rev ) {
103 $this->dieUsage( "There is no revision ID $oldid", 'missingrev' );
104 }
105 if ( !$rev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) {
106 $this->dieUsage( "You don't have permission to view deleted revisions", 'permissiondenied' );
107 }
108
109 $titleObj = $rev->getTitle();
110 $wgTitle = $titleObj;
111 $pageObj = WikiPage::factory( $titleObj );
112 list( $popts, $reset, $suppressCache ) = $this->makeParserOptions( $pageObj, $params );
113
114 // If for some reason the "oldid" is actually the current revision, it may be cached
115 // Deliberately comparing $pageObj->getLatest() with $rev->getId(), rather than
116 // checking $rev->isCurrent(), because $pageObj is what actually ends up being used,
117 // and if its ->getLatest() is outdated, $rev->isCurrent() won't tell us that.
118 if ( !$suppressCache && $rev->getId() == $pageObj->getLatest() ) {
119 // May get from/save to parser cache
120 $p_result = $this->getParsedContent( $pageObj, $popts,
121 $pageid, isset( $prop['wikitext'] ) );
122 } else { // This is an old revision, so get the text differently
123 $this->content = $rev->getContent( Revision::FOR_THIS_USER, $this->getUser() );
124
125 if ( $this->section !== false ) {
126 $this->content = $this->getSectionContent( $this->content, 'r' . $rev->getId() );
127 }
128
129 // Should we save old revision parses to the parser cache?
130 $p_result = $this->content->getParserOutput( $titleObj, $rev->getId(), $popts );
131 }
132 } else { // Not $oldid, but $pageid or $page
133 if ( $params['redirects'] ) {
134 $reqParams = [
135 'redirects' => '',
136 ];
137 if ( !is_null( $pageid ) ) {
138 $reqParams['pageids'] = $pageid;
139 } else { // $page
140 $reqParams['titles'] = $page;
141 }
142 $req = new FauxRequest( $reqParams );
143 $main = new ApiMain( $req );
144 $pageSet = new ApiPageSet( $main );
145 $pageSet->execute();
146 $redirValues = $pageSet->getRedirectTitlesAsResult( $this->getResult() );
147
148 $to = $page;
149 foreach ( $pageSet->getRedirectTitles() as $title ) {
150 $to = $title->getFullText();
151 }
152 $pageParams = [ 'title' => $to ];
153 } elseif ( !is_null( $pageid ) ) {
154 $pageParams = [ 'pageid' => $pageid ];
155 } else { // $page
156 $pageParams = [ 'title' => $page ];
157 }
158
159 $pageObj = $this->getTitleOrPageId( $pageParams, 'fromdb' );
160 $titleObj = $pageObj->getTitle();
161 if ( !$titleObj || !$titleObj->exists() ) {
162 $this->dieUsage( "The page you specified doesn't exist", 'missingtitle' );
163 }
164 $wgTitle = $titleObj;
165
166 if ( isset( $prop['revid'] ) ) {
167 $oldid = $pageObj->getLatest();
168 }
169
170 list( $popts, $reset, $suppressCache ) = $this->makeParserOptions( $pageObj, $params );
171
172 // Don't pollute the parser cache when setting options that aren't
173 // in ParserOptions::optionsHash()
174 /// @todo: This should be handled closer to the actual cache instead of here, see T110269
175 $suppressCache = $suppressCache ||
176 $params['disablepp'] ||
177 $params['disablelimitreport'] ||
178 $params['preview'] ||
179 $params['sectionpreview'] ||
180 $params['disabletidy'];
181
182 if ( $suppressCache ) {
183 $this->content = $this->getContent( $pageObj, $pageid );
184 $p_result = $this->content->getParserOutput( $titleObj, null, $popts );
185 } else {
186 // Potentially cached
187 $p_result = $this->getParsedContent( $pageObj, $popts, $pageid,
188 isset( $prop['wikitext'] ) );
189 }
190 }
191 } else { // Not $oldid, $pageid, $page. Hence based on $text
192 $titleObj = Title::newFromText( $title );
193 if ( !$titleObj || $titleObj->isExternal() ) {
194 $this->dieUsageMsg( [ 'invalidtitle', $title ] );
195 }
196 $wgTitle = $titleObj;
197 if ( $titleObj->canExist() ) {
198 $pageObj = WikiPage::factory( $titleObj );
199 } else {
200 // Do like MediaWiki::initializeArticle()
201 $article = Article::newFromTitle( $titleObj, $this->getContext() );
202 $pageObj = $article->getPage();
203 }
204
205 list( $popts, $reset ) = $this->makeParserOptions( $pageObj, $params );
206 $textProvided = !is_null( $text );
207
208 if ( !$textProvided ) {
209 if ( $titleProvided && ( $prop || $params['generatexml'] ) ) {
210 $this->setWarning(
211 "'title' used without 'text', and parsed page properties were requested " .
212 "(did you mean to use 'page' instead of 'title'?)"
213 );
214 }
215 // Prevent warning from ContentHandler::makeContent()
216 $text = '';
217 }
218
219 // If we are parsing text, do not use the content model of the default
220 // API title, but default to wikitext to keep BC.
221 if ( $textProvided && !$titleProvided && is_null( $model ) ) {
222 $model = CONTENT_MODEL_WIKITEXT;
223 $this->setWarning( "No 'title' or 'contentmodel' was given, assuming $model." );
224 }
225
226 try {
227 $this->content = ContentHandler::makeContent( $text, $titleObj, $model, $format );
228 } catch ( MWContentSerializationException $ex ) {
229 $this->dieUsage( $ex->getMessage(), 'parseerror' );
230 }
231
232 if ( $this->section !== false ) {
233 if ( $this->section === 'new' ) {
234 // Insert the section title above the content.
235 if ( !is_null( $params['sectiontitle'] ) && $params['sectiontitle'] !== '' ) {
236 $this->content = $this->content->addSectionHeader( $params['sectiontitle'] );
237 }
238 } else {
239 $this->content = $this->getSectionContent( $this->content, $titleObj->getPrefixedText() );
240 }
241 }
242
243 if ( $params['pst'] || $params['onlypst'] ) {
244 $this->pstContent = $this->content->preSaveTransform( $titleObj, $this->getUser(), $popts );
245 }
246 if ( $params['onlypst'] ) {
247 // Build a result and bail out
248 $result_array = [];
249 $result_array['text'] = $this->pstContent->serialize( $format );
250 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'text';
251 if ( isset( $prop['wikitext'] ) ) {
252 $result_array['wikitext'] = $this->content->serialize( $format );
253 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'wikitext';
254 }
255 if ( !is_null( $params['summary'] ) ||
256 ( !is_null( $params['sectiontitle'] ) && $this->section === 'new' )
257 ) {
258 $result_array['parsedsummary'] = $this->formatSummary( $titleObj, $params );
259 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'parsedsummary';
260 }
261
262 $result->addValue( null, $this->getModuleName(), $result_array );
263
264 return;
265 }
266
267 // Not cached (save or load)
268 if ( $params['pst'] ) {
269 $p_result = $this->pstContent->getParserOutput( $titleObj, null, $popts );
270 } else {
271 $p_result = $this->content->getParserOutput( $titleObj, null, $popts );
272 }
273 }
274
275 $result_array = [];
276
277 $result_array['title'] = $titleObj->getPrefixedText();
278 $result_array['pageid'] = $pageid ?: $pageObj->getId();
279
280 if ( !is_null( $oldid ) ) {
281 $result_array['revid'] = intval( $oldid );
282 }
283
284 if ( $params['redirects'] && !is_null( $redirValues ) ) {
285 $result_array['redirects'] = $redirValues;
286 }
287
288 if ( $params['disabletoc'] ) {
289 $p_result->setTOCEnabled( false );
290 }
291
292 if ( isset( $prop['text'] ) ) {
293 $result_array['text'] = $p_result->getText();
294 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'text';
295 }
296
297 if ( !is_null( $params['summary'] ) ||
298 ( !is_null( $params['sectiontitle'] ) && $this->section === 'new' )
299 ) {
300 $result_array['parsedsummary'] = $this->formatSummary( $titleObj, $params );
301 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'parsedsummary';
302 }
303
304 if ( isset( $prop['langlinks'] ) ) {
305 $langlinks = $p_result->getLanguageLinks();
306
307 if ( $params['effectivelanglinks'] ) {
308 // Link flags are ignored for now, but may in the future be
309 // included in the result.
310 $linkFlags = [];
311 Hooks::run( 'LanguageLinks', [ $titleObj, &$langlinks, &$linkFlags ] );
312 }
313 } else {
314 $langlinks = false;
315 }
316
317 if ( isset( $prop['langlinks'] ) ) {
318 $result_array['langlinks'] = $this->formatLangLinks( $langlinks );
319 }
320 if ( isset( $prop['categories'] ) ) {
321 $result_array['categories'] = $this->formatCategoryLinks( $p_result->getCategories() );
322 }
323 if ( isset( $prop['categorieshtml'] ) ) {
324 $result_array['categorieshtml'] = $this->categoriesHtml( $p_result->getCategories() );
325 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'categorieshtml';
326 }
327 if ( isset( $prop['links'] ) ) {
328 $result_array['links'] = $this->formatLinks( $p_result->getLinks() );
329 }
330 if ( isset( $prop['templates'] ) ) {
331 $result_array['templates'] = $this->formatLinks( $p_result->getTemplates() );
332 }
333 if ( isset( $prop['images'] ) ) {
334 $result_array['images'] = array_keys( $p_result->getImages() );
335 }
336 if ( isset( $prop['externallinks'] ) ) {
337 $result_array['externallinks'] = array_keys( $p_result->getExternalLinks() );
338 }
339 if ( isset( $prop['sections'] ) ) {
340 $result_array['sections'] = $p_result->getSections();
341 }
342
343 if ( isset( $prop['displaytitle'] ) ) {
344 $result_array['displaytitle'] = $p_result->getDisplayTitle() ?:
345 $titleObj->getPrefixedText();
346 }
347
348 if ( isset( $prop['headitems'] ) ) {
349 $result_array['headitems'] = $this->formatHeadItems( $p_result->getHeadItems() );
350 $this->logFeatureUsage( 'action=parse&prop=headitems' );
351 $this->setWarning( 'headitems is deprecated since MediaWiki 1.28. '
352 . 'Use prop=headhtml when creating new HTML documents, or '
353 . 'prop=modules|jsconfigvars when updating a document client-side.' );
354 }
355
356 if ( isset( $prop['headhtml'] ) ) {
357 $context = new DerivativeContext( $this->getContext() );
358 $context->setTitle( $titleObj );
359 $context->setWikiPage( $pageObj );
360
361 // We need an OutputPage tied to $context, not to the
362 // RequestContext at the root of the stack.
363 $output = new OutputPage( $context );
364 $output->addParserOutputMetadata( $p_result );
365
366 $result_array['headhtml'] = $output->headElement( $context->getSkin() );
367 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'headhtml';
368 }
369
370 if ( isset( $prop['modules'] ) ) {
371 $result_array['modules'] = array_values( array_unique( $p_result->getModules() ) );
372 $result_array['modulescripts'] = array_values( array_unique( $p_result->getModuleScripts() ) );
373 $result_array['modulestyles'] = array_values( array_unique( $p_result->getModuleStyles() ) );
374 }
375
376 if ( isset( $prop['jsconfigvars'] ) ) {
377 $result_array['jsconfigvars'] =
378 ApiResult::addMetadataToResultVars( $p_result->getJsConfigVars() );
379 }
380
381 if ( isset( $prop['encodedjsconfigvars'] ) ) {
382 $result_array['encodedjsconfigvars'] = FormatJson::encode(
383 $p_result->getJsConfigVars(), false, FormatJson::ALL_OK
384 );
385 $result_array[ApiResult::META_SUBELEMENTS][] = 'encodedjsconfigvars';
386 }
387
388 if ( isset( $prop['modules'] ) &&
389 !isset( $prop['jsconfigvars'] ) && !isset( $prop['encodedjsconfigvars'] ) ) {
390 $this->setWarning( 'Property "modules" was set but not "jsconfigvars" ' .
391 'or "encodedjsconfigvars". Configuration variables are necessary ' .
392 'for proper module usage.' );
393 }
394
395 if ( isset( $prop['indicators'] ) ) {
396 $result_array['indicators'] = (array)$p_result->getIndicators();
397 ApiResult::setArrayType( $result_array['indicators'], 'BCkvp', 'name' );
398 }
399
400 if ( isset( $prop['iwlinks'] ) ) {
401 $result_array['iwlinks'] = $this->formatIWLinks( $p_result->getInterwikiLinks() );
402 }
403
404 if ( isset( $prop['wikitext'] ) ) {
405 $result_array['wikitext'] = $this->content->serialize( $format );
406 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'wikitext';
407 if ( !is_null( $this->pstContent ) ) {
408 $result_array['psttext'] = $this->pstContent->serialize( $format );
409 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'psttext';
410 }
411 }
412 if ( isset( $prop['properties'] ) ) {
413 $result_array['properties'] = (array)$p_result->getProperties();
414 ApiResult::setArrayType( $result_array['properties'], 'BCkvp', 'name' );
415 }
416
417 if ( isset( $prop['limitreportdata'] ) ) {
418 $result_array['limitreportdata'] =
419 $this->formatLimitReportData( $p_result->getLimitReportData() );
420 }
421 if ( isset( $prop['limitreporthtml'] ) ) {
422 $result_array['limitreporthtml'] = EditPage::getPreviewLimitReport( $p_result );
423 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'limitreporthtml';
424 }
425
426 if ( isset( $prop['parsetree'] ) || $params['generatexml'] ) {
427 if ( $this->content->getModel() != CONTENT_MODEL_WIKITEXT ) {
428 $this->dieUsage( 'parsetree is only supported for wikitext content', 'notwikitext' );
429 }
430
431 $wgParser->startExternalParse( $titleObj, $popts, Parser::OT_PREPROCESS );
432 $dom = $wgParser->preprocessToDom( $this->content->getNativeData() );
433 if ( is_callable( [ $dom, 'saveXML' ] ) ) {
434 $xml = $dom->saveXML();
435 } else {
436 $xml = $dom->__toString();
437 }
438 $result_array['parsetree'] = $xml;
439 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'parsetree';
440 }
441
442 $result_mapping = [
443 'redirects' => 'r',
444 'langlinks' => 'll',
445 'categories' => 'cl',
446 'links' => 'pl',
447 'templates' => 'tl',
448 'images' => 'img',
449 'externallinks' => 'el',
450 'iwlinks' => 'iw',
451 'sections' => 's',
452 'headitems' => 'hi',
453 'modules' => 'm',
454 'indicators' => 'ind',
455 'modulescripts' => 'm',
456 'modulestyles' => 'm',
457 'properties' => 'pp',
458 'limitreportdata' => 'lr',
459 ];
460 $this->setIndexedTagNames( $result_array, $result_mapping );
461 $result->addValue( null, $this->getModuleName(), $result_array );
462 }
463
464 /**
465 * Constructs a ParserOptions object
466 *
467 * @param WikiPage $pageObj
468 * @param array $params
469 *
470 * @return array [ ParserOptions, ScopedCallback, bool $suppressCache ]
471 */
472 protected function makeParserOptions( WikiPage $pageObj, array $params ) {
473 $popts = $pageObj->makeParserOptions( $this->getContext() );
474 $popts->enableLimitReport( !$params['disablepp'] && !$params['disablelimitreport'] );
475 $popts->setIsPreview( $params['preview'] || $params['sectionpreview'] );
476 $popts->setIsSectionPreview( $params['sectionpreview'] );
477 $popts->setEditSection( !$params['disableeditsection'] );
478 if ( $params['disabletidy'] ) {
479 $popts->setTidy( false );
480 }
481
482 $reset = null;
483 $suppressCache = false;
484 Hooks::run( 'ApiMakeParserOptions',
485 [ $popts, $pageObj->getTitle(), $params, $this, &$reset, &$suppressCache ] );
486
487 return [ $popts, $reset, $suppressCache ];
488 }
489
490 /**
491 * @param WikiPage $page
492 * @param ParserOptions $popts
493 * @param int $pageId
494 * @param bool $getWikitext
495 * @return ParserOutput
496 */
497 private function getParsedContent( WikiPage $page, $popts, $pageId = null, $getWikitext = false ) {
498 $this->content = $this->getContent( $page, $pageId );
499
500 if ( $this->section !== false && $this->content !== null ) {
501 // Not cached (save or load)
502 return $this->content->getParserOutput( $page->getTitle(), null, $popts );
503 }
504
505 // Try the parser cache first
506 // getParserOutput will save to Parser cache if able
507 $pout = $page->getParserOutput( $popts );
508 if ( !$pout ) {
509 $this->dieUsage( "There is no revision ID {$page->getLatest()}", 'missingrev' );
510 }
511 if ( $getWikitext ) {
512 $this->content = $page->getContent( Revision::RAW );
513 }
514
515 return $pout;
516 }
517
518 /**
519 * Get the content for the given page and the requested section.
520 *
521 * @param WikiPage $page
522 * @param int $pageId
523 * @return Content
524 */
525 private function getContent( WikiPage $page, $pageId = null ) {
526 $content = $page->getContent( Revision::RAW ); // XXX: really raw?
527
528 if ( $this->section !== false && $content !== null ) {
529 $content = $this->getSectionContent(
530 $content,
531 !is_null( $pageId ) ? 'page id ' . $pageId : $page->getTitle()->getPrefixedText()
532 );
533 }
534 return $content;
535 }
536
537 /**
538 * Extract the requested section from the given Content
539 *
540 * @param Content $content
541 * @param string $what Identifies the content in error messages, e.g. page title.
542 * @return Content|bool
543 */
544 private function getSectionContent( Content $content, $what ) {
545 // Not cached (save or load)
546 $section = $content->getSection( $this->section );
547 if ( $section === false ) {
548 $this->dieUsage( "There is no section {$this->section} in $what", 'nosuchsection' );
549 }
550 if ( $section === null ) {
551 $this->dieUsage( "Sections are not supported by $what", 'nosuchsection' );
552 $section = false;
553 }
554
555 return $section;
556 }
557
558 /**
559 * This mimicks the behavior of EditPage in formatting a summary
560 *
561 * @param Title $title of the page being parsed
562 * @param Array $params the API parameters of the request
563 * @return Content|bool
564 */
565 private function formatSummary( $title, $params ) {
566 global $wgParser;
567 $summary = !is_null( $params['summary'] ) ? $params['summary'] : '';
568 $sectionTitle = !is_null( $params['sectiontitle'] ) ? $params['sectiontitle'] : '';
569
570 if ( $this->section === 'new' && ( $sectionTitle === '' || $summary === '' ) ) {
571 if ( $sectionTitle !== '' ) {
572 $summary = $params['sectiontitle'];
573 }
574 if ( $summary !== '' ) {
575 $summary = wfMessage( 'newsectionsummary' )
576 ->rawParams( $wgParser->stripSectionName( $summary ) )
577 ->inContentLanguage()->text();
578 }
579 }
580 return Linker::formatComment( $summary, $title, $this->section === 'new' );
581 }
582
583 private function formatLangLinks( $links ) {
584 $result = [];
585 foreach ( $links as $link ) {
586 $entry = [];
587 $bits = explode( ':', $link, 2 );
588 $title = Title::newFromText( $link );
589
590 $entry['lang'] = $bits[0];
591 if ( $title ) {
592 $entry['url'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
593 // localised language name in 'uselang' language
594 $entry['langname'] = Language::fetchLanguageName(
595 $title->getInterwiki(),
596 $this->getLanguage()->getCode()
597 );
598
599 // native language name
600 $entry['autonym'] = Language::fetchLanguageName( $title->getInterwiki() );
601 }
602 ApiResult::setContentValue( $entry, 'title', $bits[1] );
603 $result[] = $entry;
604 }
605
606 return $result;
607 }
608
609 private function formatCategoryLinks( $links ) {
610 $result = [];
611
612 if ( !$links ) {
613 return $result;
614 }
615
616 // Fetch hiddencat property
617 $lb = new LinkBatch;
618 $lb->setArray( [ NS_CATEGORY => $links ] );
619 $db = $this->getDB();
620 $res = $db->select( [ 'page', 'page_props' ],
621 [ 'page_title', 'pp_propname' ],
622 $lb->constructSet( 'page', $db ),
623 __METHOD__,
624 [],
625 [ 'page_props' => [
626 'LEFT JOIN', [ 'pp_propname' => 'hiddencat', 'pp_page = page_id' ]
627 ] ]
628 );
629 $hiddencats = [];
630 foreach ( $res as $row ) {
631 $hiddencats[$row->page_title] = isset( $row->pp_propname );
632 }
633
634 foreach ( $links as $link => $sortkey ) {
635 $entry = [];
636 $entry['sortkey'] = $sortkey;
637 // array keys will cast numeric category names to ints, so cast back to string
638 ApiResult::setContentValue( $entry, 'category', (string)$link );
639 if ( !isset( $hiddencats[$link] ) ) {
640 $entry['missing'] = true;
641 } elseif ( $hiddencats[$link] ) {
642 $entry['hidden'] = true;
643 }
644 $result[] = $entry;
645 }
646
647 return $result;
648 }
649
650 private function categoriesHtml( $categories ) {
651 $context = $this->getContext();
652 $context->getOutput()->addCategoryLinks( $categories );
653
654 return $context->getSkin()->getCategories();
655 }
656
657 private function formatLinks( $links ) {
658 $result = [];
659 foreach ( $links as $ns => $nslinks ) {
660 foreach ( $nslinks as $title => $id ) {
661 $entry = [];
662 $entry['ns'] = $ns;
663 ApiResult::setContentValue( $entry, 'title', Title::makeTitle( $ns, $title )->getFullText() );
664 $entry['exists'] = $id != 0;
665 $result[] = $entry;
666 }
667 }
668
669 return $result;
670 }
671
672 private function formatIWLinks( $iw ) {
673 $result = [];
674 foreach ( $iw as $prefix => $titles ) {
675 foreach ( array_keys( $titles ) as $title ) {
676 $entry = [];
677 $entry['prefix'] = $prefix;
678
679 $title = Title::newFromText( "{$prefix}:{$title}" );
680 if ( $title ) {
681 $entry['url'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
682 }
683
684 ApiResult::setContentValue( $entry, 'title', $title->getFullText() );
685 $result[] = $entry;
686 }
687 }
688
689 return $result;
690 }
691
692 private function formatHeadItems( $headItems ) {
693 $result = [];
694 foreach ( $headItems as $tag => $content ) {
695 $entry = [];
696 $entry['tag'] = $tag;
697 ApiResult::setContentValue( $entry, 'content', $content );
698 $result[] = $entry;
699 }
700
701 return $result;
702 }
703
704 private function formatLimitReportData( $limitReportData ) {
705 $result = [];
706
707 foreach ( $limitReportData as $name => $value ) {
708 $entry = [];
709 $entry['name'] = $name;
710 if ( !is_array( $value ) ) {
711 $value = [ $value ];
712 }
713 ApiResult::setIndexedTagNameRecursive( $value, 'param' );
714 $entry = array_merge( $entry, $value );
715 $result[] = $entry;
716 }
717
718 return $result;
719 }
720
721 private function setIndexedTagNames( &$array, $mapping ) {
722 foreach ( $mapping as $key => $name ) {
723 if ( isset( $array[$key] ) ) {
724 ApiResult::setIndexedTagName( $array[$key], $name );
725 }
726 }
727 }
728
729 public function getAllowedParams() {
730 return [
731 'title' => null,
732 'text' => [
733 ApiBase::PARAM_TYPE => 'text',
734 ],
735 'summary' => null,
736 'page' => null,
737 'pageid' => [
738 ApiBase::PARAM_TYPE => 'integer',
739 ],
740 'redirects' => false,
741 'oldid' => [
742 ApiBase::PARAM_TYPE => 'integer',
743 ],
744 'prop' => [
745 ApiBase::PARAM_DFLT => 'text|langlinks|categories|links|templates|' .
746 'images|externallinks|sections|revid|displaytitle|iwlinks|properties',
747 ApiBase::PARAM_ISMULTI => true,
748 ApiBase::PARAM_TYPE => [
749 'text',
750 'langlinks',
751 'categories',
752 'categorieshtml',
753 'links',
754 'templates',
755 'images',
756 'externallinks',
757 'sections',
758 'revid',
759 'displaytitle',
760 'headitems',
761 'headhtml',
762 'modules',
763 'jsconfigvars',
764 'encodedjsconfigvars',
765 'indicators',
766 'iwlinks',
767 'wikitext',
768 'properties',
769 'limitreportdata',
770 'limitreporthtml',
771 'parsetree',
772 ],
773 ApiBase::PARAM_HELP_MSG_PER_VALUE => [
774 'parsetree' => [ 'apihelp-parse-paramvalue-prop-parsetree', CONTENT_MODEL_WIKITEXT ],
775 ],
776 ],
777 'pst' => false,
778 'onlypst' => false,
779 'effectivelanglinks' => false,
780 'section' => null,
781 'sectiontitle' => [
782 ApiBase::PARAM_TYPE => 'string',
783 ],
784 'disablepp' => [
785 ApiBase::PARAM_DFLT => false,
786 ApiBase::PARAM_DEPRECATED => true,
787 ],
788 'disablelimitreport' => false,
789 'disableeditsection' => false,
790 'disabletidy' => false,
791 'generatexml' => [
792 ApiBase::PARAM_DFLT => false,
793 ApiBase::PARAM_HELP_MSG => [
794 'apihelp-parse-param-generatexml', CONTENT_MODEL_WIKITEXT
795 ],
796 ApiBase::PARAM_DEPRECATED => true,
797 ],
798 'preview' => false,
799 'sectionpreview' => false,
800 'disabletoc' => false,
801 'contentformat' => [
802 ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
803 ],
804 'contentmodel' => [
805 ApiBase::PARAM_TYPE => ContentHandler::getContentModels(),
806 ]
807 ];
808 }
809
810 protected function getExamplesMessages() {
811 return [
812 'action=parse&page=Project:Sandbox'
813 => 'apihelp-parse-example-page',
814 'action=parse&text={{Project:Sandbox}}&contentmodel=wikitext'
815 => 'apihelp-parse-example-text',
816 'action=parse&text={{PAGENAME}}&title=Test'
817 => 'apihelp-parse-example-texttitle',
818 'action=parse&summary=Some+[[link]]&prop='
819 => 'apihelp-parse-example-summary',
820 ];
821 }
822
823 public function getHelpUrls() {
824 return 'https://www.mediawiki.org/wiki/API:Parsing_wikitext#parse';
825 }
826 }