Merge "Http::getProxy() method to get proxy configuration"
[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 $popts = $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 ( $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 $popts = $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 =
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 $popts = $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'] ) || isset( $prop['headhtml'] ) ) {
349 $context = $this->getContext();
350 $context->setTitle( $titleObj );
351 $context->getOutput()->addParserOutputMetadata( $p_result );
352
353 if ( isset( $prop['headitems'] ) ) {
354 $headItems = $this->formatHeadItems( $p_result->getHeadItems() );
355
356 $css = $this->formatCss( $context->getOutput()->buildCssLinksArray() );
357
358 $scripts = [ $context->getOutput()->getHeadScripts() ];
359
360 $result_array['headitems'] = array_merge( $headItems, $css, $scripts );
361 }
362
363 if ( isset( $prop['headhtml'] ) ) {
364 $result_array['headhtml'] = $context->getOutput()->headElement( $context->getSkin() );
365 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'headhtml';
366 }
367 }
368
369 if ( isset( $prop['modules'] ) ) {
370 $result_array['modules'] = array_values( array_unique( $p_result->getModules() ) );
371 $result_array['modulescripts'] = array_values( array_unique( $p_result->getModuleScripts() ) );
372 $result_array['modulestyles'] = array_values( array_unique( $p_result->getModuleStyles() ) );
373 // To be removed in 1.27
374 $result_array['modulemessages'] = [];
375 $this->setWarning( 'modulemessages is deprecated since MediaWiki 1.26' );
376 }
377
378 if ( isset( $prop['jsconfigvars'] ) ) {
379 $result_array['jsconfigvars'] =
380 ApiResult::addMetadataToResultVars( $p_result->getJsConfigVars() );
381 }
382
383 if ( isset( $prop['encodedjsconfigvars'] ) ) {
384 $result_array['encodedjsconfigvars'] = FormatJson::encode(
385 $p_result->getJsConfigVars(), false, FormatJson::ALL_OK
386 );
387 $result_array[ApiResult::META_SUBELEMENTS][] = 'encodedjsconfigvars';
388 }
389
390 if ( isset( $prop['modules'] ) &&
391 !isset( $prop['jsconfigvars'] ) && !isset( $prop['encodedjsconfigvars'] ) ) {
392 $this->setWarning( 'Property "modules" was set but not "jsconfigvars" ' .
393 'or "encodedjsconfigvars". Configuration variables are necessary ' .
394 'for proper module usage.' );
395 }
396
397 if ( isset( $prop['indicators'] ) ) {
398 $result_array['indicators'] = (array)$p_result->getIndicators();
399 ApiResult::setArrayType( $result_array['indicators'], 'BCkvp', 'name' );
400 }
401
402 if ( isset( $prop['iwlinks'] ) ) {
403 $result_array['iwlinks'] = $this->formatIWLinks( $p_result->getInterwikiLinks() );
404 }
405
406 if ( isset( $prop['wikitext'] ) ) {
407 $result_array['wikitext'] = $this->content->serialize( $format );
408 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'wikitext';
409 if ( !is_null( $this->pstContent ) ) {
410 $result_array['psttext'] = $this->pstContent->serialize( $format );
411 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'psttext';
412 }
413 }
414 if ( isset( $prop['properties'] ) ) {
415 $result_array['properties'] = (array)$p_result->getProperties();
416 ApiResult::setArrayType( $result_array['properties'], 'BCkvp', 'name' );
417 }
418
419 if ( isset( $prop['limitreportdata'] ) ) {
420 $result_array['limitreportdata'] =
421 $this->formatLimitReportData( $p_result->getLimitReportData() );
422 }
423 if ( isset( $prop['limitreporthtml'] ) ) {
424 $result_array['limitreporthtml'] = EditPage::getPreviewLimitReport( $p_result );
425 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'limitreporthtml';
426 }
427
428 if ( isset( $prop['parsetree'] ) || $params['generatexml'] ) {
429 if ( $this->content->getModel() != CONTENT_MODEL_WIKITEXT ) {
430 $this->dieUsage( 'parsetree is only supported for wikitext content', 'notwikitext' );
431 }
432
433 $wgParser->startExternalParse( $titleObj, $popts, Parser::OT_PREPROCESS );
434 $dom = $wgParser->preprocessToDom( $this->content->getNativeData() );
435 if ( is_callable( [ $dom, 'saveXML' ] ) ) {
436 $xml = $dom->saveXML();
437 } else {
438 $xml = $dom->__toString();
439 }
440 $result_array['parsetree'] = $xml;
441 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'parsetree';
442 }
443
444 $result_mapping = [
445 'redirects' => 'r',
446 'langlinks' => 'll',
447 'categories' => 'cl',
448 'links' => 'pl',
449 'templates' => 'tl',
450 'images' => 'img',
451 'externallinks' => 'el',
452 'iwlinks' => 'iw',
453 'sections' => 's',
454 'headitems' => 'hi',
455 'modules' => 'm',
456 'indicators' => 'ind',
457 'modulescripts' => 'm',
458 'modulestyles' => 'm',
459 'modulemessages' => 'm',
460 'properties' => 'pp',
461 'limitreportdata' => 'lr',
462 ];
463 $this->setIndexedTagNames( $result_array, $result_mapping );
464 $result->addValue( null, $this->getModuleName(), $result_array );
465 }
466
467 /**
468 * Constructs a ParserOptions object
469 *
470 * @param WikiPage $pageObj
471 * @param array $params
472 *
473 * @return ParserOptions
474 */
475 protected function makeParserOptions( WikiPage $pageObj, array $params ) {
476
477 $popts = $pageObj->makeParserOptions( $this->getContext() );
478 $popts->enableLimitReport( !$params['disablepp'] && !$params['disablelimitreport'] );
479 $popts->setIsPreview( $params['preview'] || $params['sectionpreview'] );
480 $popts->setIsSectionPreview( $params['sectionpreview'] );
481 $popts->setEditSection( !$params['disableeditsection'] );
482 if ( $params['disabletidy'] ) {
483 $popts->setTidy( false );
484 }
485
486 return $popts;
487 }
488
489 /**
490 * @param WikiPage $page
491 * @param ParserOptions $popts
492 * @param int $pageId
493 * @param bool $getWikitext
494 * @return ParserOutput
495 */
496 private function getParsedContent( WikiPage $page, $popts, $pageId = null, $getWikitext = false ) {
497 $this->content = $this->getContent( $page, $pageId );
498
499 if ( $this->section !== false && $this->content !== null ) {
500 // Not cached (save or load)
501 return $this->content->getParserOutput( $page->getTitle(), null, $popts );
502 }
503
504 // Try the parser cache first
505 // getParserOutput will save to Parser cache if able
506 $pout = $page->getParserOutput( $popts );
507 if ( !$pout ) {
508 $this->dieUsage( "There is no revision ID {$page->getLatest()}", 'missingrev' );
509 }
510 if ( $getWikitext ) {
511 $this->content = $page->getContent( Revision::RAW );
512 }
513
514 return $pout;
515 }
516
517 /**
518 * Get the content for the given page and the requested section.
519 *
520 * @param WikiPage $page
521 * @param int $pageId
522 * @return Content
523 */
524 private function getContent( WikiPage $page, $pageId = null ) {
525 $content = $page->getContent( Revision::RAW ); // XXX: really raw?
526
527 if ( $this->section !== false && $content !== null ) {
528 $content = $this->getSectionContent(
529 $content,
530 !is_null( $pageId ) ? 'page id ' . $pageId : $page->getTitle()->getPrefixedText()
531 );
532 }
533 return $content;
534 }
535
536 /**
537 * Extract the requested section from the given Content
538 *
539 * @param Content $content
540 * @param string $what Identifies the content in error messages, e.g. page title.
541 * @return Content|bool
542 */
543 private function getSectionContent( Content $content, $what ) {
544 // Not cached (save or load)
545 $section = $content->getSection( $this->section );
546 if ( $section === false ) {
547 $this->dieUsage( "There is no section {$this->section} in $what", 'nosuchsection' );
548 }
549 if ( $section === null ) {
550 $this->dieUsage( "Sections are not supported by $what", 'nosuchsection' );
551 $section = false;
552 }
553
554 return $section;
555 }
556
557 /**
558 * This mimicks the behavior of EditPage in formatting a summary
559 *
560 * @param Title $title of the page being parsed
561 * @param Array $params the API parameters of the request
562 * @return Content|bool
563 */
564 private function formatSummary( $title, $params ) {
565 global $wgParser;
566 $summary = !is_null( $params['summary'] ) ? $params['summary'] : '';
567 $sectionTitle = !is_null( $params['sectiontitle'] ) ? $params['sectiontitle'] : '';
568
569 if ( $this->section === 'new' && ( $sectionTitle === '' || $summary === '' ) ) {
570 if ( $sectionTitle !== '' ) {
571 $summary = $params['sectiontitle'];
572 }
573 if ( $summary !== '' ) {
574 $summary = wfMessage( 'newsectionsummary' )
575 ->rawParams( $wgParser->stripSectionName( $summary ) )
576 ->inContentLanguage()->text();
577 }
578 }
579 return Linker::formatComment( $summary, $title, $this->section === 'new' );
580 }
581
582 private function formatLangLinks( $links ) {
583 $result = [];
584 foreach ( $links as $link ) {
585 $entry = [];
586 $bits = explode( ':', $link, 2 );
587 $title = Title::newFromText( $link );
588
589 $entry['lang'] = $bits[0];
590 if ( $title ) {
591 $entry['url'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
592 // localised language name in 'uselang' language
593 $entry['langname'] = Language::fetchLanguageName(
594 $title->getInterwiki(),
595 $this->getLanguage()->getCode()
596 );
597
598 // native language name
599 $entry['autonym'] = Language::fetchLanguageName( $title->getInterwiki() );
600 }
601 ApiResult::setContentValue( $entry, 'title', $bits[1] );
602 $result[] = $entry;
603 }
604
605 return $result;
606 }
607
608 private function formatCategoryLinks( $links ) {
609 $result = [];
610
611 if ( !$links ) {
612 return $result;
613 }
614
615 // Fetch hiddencat property
616 $lb = new LinkBatch;
617 $lb->setArray( [ NS_CATEGORY => $links ] );
618 $db = $this->getDB();
619 $res = $db->select( [ 'page', 'page_props' ],
620 [ 'page_title', 'pp_propname' ],
621 $lb->constructSet( 'page', $db ),
622 __METHOD__,
623 [],
624 [ 'page_props' => [
625 'LEFT JOIN', [ 'pp_propname' => 'hiddencat', 'pp_page = page_id' ]
626 ] ]
627 );
628 $hiddencats = [];
629 foreach ( $res as $row ) {
630 $hiddencats[$row->page_title] = isset( $row->pp_propname );
631 }
632
633 foreach ( $links as $link => $sortkey ) {
634 $entry = [];
635 $entry['sortkey'] = $sortkey;
636 // array keys will cast numeric category names to ints, so cast back to string
637 ApiResult::setContentValue( $entry, 'category', (string)$link );
638 if ( !isset( $hiddencats[$link] ) ) {
639 $entry['missing'] = true;
640 } elseif ( $hiddencats[$link] ) {
641 $entry['hidden'] = true;
642 }
643 $result[] = $entry;
644 }
645
646 return $result;
647 }
648
649 private function categoriesHtml( $categories ) {
650 $context = $this->getContext();
651 $context->getOutput()->addCategoryLinks( $categories );
652
653 return $context->getSkin()->getCategories();
654 }
655
656 private function formatLinks( $links ) {
657 $result = [];
658 foreach ( $links as $ns => $nslinks ) {
659 foreach ( $nslinks as $title => $id ) {
660 $entry = [];
661 $entry['ns'] = $ns;
662 ApiResult::setContentValue( $entry, 'title', Title::makeTitle( $ns, $title )->getFullText() );
663 $entry['exists'] = $id != 0;
664 $result[] = $entry;
665 }
666 }
667
668 return $result;
669 }
670
671 private function formatIWLinks( $iw ) {
672 $result = [];
673 foreach ( $iw as $prefix => $titles ) {
674 foreach ( array_keys( $titles ) as $title ) {
675 $entry = [];
676 $entry['prefix'] = $prefix;
677
678 $title = Title::newFromText( "{$prefix}:{$title}" );
679 if ( $title ) {
680 $entry['url'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
681 }
682
683 ApiResult::setContentValue( $entry, 'title', $title->getFullText() );
684 $result[] = $entry;
685 }
686 }
687
688 return $result;
689 }
690
691 private function formatHeadItems( $headItems ) {
692 $result = [];
693 foreach ( $headItems as $tag => $content ) {
694 $entry = [];
695 $entry['tag'] = $tag;
696 ApiResult::setContentValue( $entry, 'content', $content );
697 $result[] = $entry;
698 }
699
700 return $result;
701 }
702
703 private function formatCss( $css ) {
704 $result = [];
705 foreach ( $css as $file => $link ) {
706 $entry = [];
707 $entry['file'] = $file;
708 ApiResult::setContentValue( $entry, 'link', $link );
709 $result[] = $entry;
710 }
711
712 return $result;
713 }
714
715 private function formatLimitReportData( $limitReportData ) {
716 $result = [];
717
718 foreach ( $limitReportData as $name => $value ) {
719 $entry = [];
720 $entry['name'] = $name;
721 if ( !is_array( $value ) ) {
722 $value = [ $value ];
723 }
724 ApiResult::setIndexedTagNameRecursive( $value, 'param' );
725 $entry = array_merge( $entry, $value );
726 $result[] = $entry;
727 }
728
729 return $result;
730 }
731
732 private function setIndexedTagNames( &$array, $mapping ) {
733 foreach ( $mapping as $key => $name ) {
734 if ( isset( $array[$key] ) ) {
735 ApiResult::setIndexedTagName( $array[$key], $name );
736 }
737 }
738 }
739
740 public function getAllowedParams() {
741 return [
742 'title' => null,
743 'text' => [
744 ApiBase::PARAM_TYPE => 'text',
745 ],
746 'summary' => null,
747 'page' => null,
748 'pageid' => [
749 ApiBase::PARAM_TYPE => 'integer',
750 ],
751 'redirects' => false,
752 'oldid' => [
753 ApiBase::PARAM_TYPE => 'integer',
754 ],
755 'prop' => [
756 ApiBase::PARAM_DFLT => 'text|langlinks|categories|links|templates|' .
757 'images|externallinks|sections|revid|displaytitle|iwlinks|properties',
758 ApiBase::PARAM_ISMULTI => true,
759 ApiBase::PARAM_TYPE => [
760 'text',
761 'langlinks',
762 'categories',
763 'categorieshtml',
764 'links',
765 'templates',
766 'images',
767 'externallinks',
768 'sections',
769 'revid',
770 'displaytitle',
771 'headitems',
772 'headhtml',
773 'modules',
774 'jsconfigvars',
775 'encodedjsconfigvars',
776 'indicators',
777 'iwlinks',
778 'wikitext',
779 'properties',
780 'limitreportdata',
781 'limitreporthtml',
782 'parsetree',
783 ],
784 ApiBase::PARAM_HELP_MSG_PER_VALUE => [
785 'parsetree' => [ 'apihelp-parse-paramvalue-prop-parsetree', CONTENT_MODEL_WIKITEXT ],
786 ],
787 ],
788 'pst' => false,
789 'onlypst' => false,
790 'effectivelanglinks' => false,
791 'section' => null,
792 'sectiontitle' => [
793 ApiBase::PARAM_TYPE => 'string',
794 ],
795 'disablepp' => [
796 ApiBase::PARAM_DFLT => false,
797 ApiBase::PARAM_DEPRECATED => true,
798 ],
799 'disablelimitreport' => false,
800 'disableeditsection' => false,
801 'disabletidy' => false,
802 'generatexml' => [
803 ApiBase::PARAM_DFLT => false,
804 ApiBase::PARAM_HELP_MSG => [
805 'apihelp-parse-param-generatexml', CONTENT_MODEL_WIKITEXT
806 ],
807 ApiBase::PARAM_DEPRECATED => true,
808 ],
809 'preview' => false,
810 'sectionpreview' => false,
811 'disabletoc' => false,
812 'contentformat' => [
813 ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
814 ],
815 'contentmodel' => [
816 ApiBase::PARAM_TYPE => ContentHandler::getContentModels(),
817 ]
818 ];
819 }
820
821 protected function getExamplesMessages() {
822 return [
823 'action=parse&page=Project:Sandbox'
824 => 'apihelp-parse-example-page',
825 'action=parse&text={{Project:Sandbox}}&contentmodel=wikitext'
826 => 'apihelp-parse-example-text',
827 'action=parse&text={{PAGENAME}}&title=Test'
828 => 'apihelp-parse-example-texttitle',
829 'action=parse&summary=Some+[[link]]&prop='
830 => 'apihelp-parse-example-summary',
831 ];
832 }
833
834 public function getHelpUrls() {
835 return 'https://www.mediawiki.org/wiki/API:Parsing_wikitext#parse';
836 }
837 }