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