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