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