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