Merge "Add support for PHP7 random_bytes in favor of mcrypt_create_iv"
[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
46 // No easy way to say that text & title are allowed together while the
47 // rest aren't, so just do it in two calls.
48 $this->requireMaxOneParameter( $params, 'page', 'pageid', 'oldid', 'text' );
49 $this->requireMaxOneParameter( $params, 'page', 'pageid', 'oldid', 'title' );
50
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 $prop = array_flip( $params['prop'] );
69
70 if ( isset( $params['section'] ) ) {
71 $this->section = $params['section'];
72 if ( !preg_match( '/^((T-)?\d+|new)$/', $this->section ) ) {
73 $this->dieWithError( 'apierror-invalidsection' );
74 }
75 } else {
76 $this->section = false;
77 }
78
79 // The parser needs $wgTitle to be set, apparently the
80 // $title parameter in Parser::parse isn't enough *sigh*
81 // TODO: Does this still need $wgTitle?
82 global $wgParser, $wgTitle;
83
84 $redirValues = null;
85
86 // Return result
87 $result = $this->getResult();
88
89 if ( !is_null( $oldid ) || !is_null( $pageid ) || !is_null( $page ) ) {
90 if ( $this->section === 'new' ) {
91 $this->dieWithError( 'apierror-invalidparammix-parse-new-section', 'invalidparammix' );
92 }
93 if ( !is_null( $oldid ) ) {
94 // Don't use the parser cache
95 $rev = Revision::newFromId( $oldid );
96 if ( !$rev ) {
97 $this->dieWithError( [ 'apierror-nosuchrevid', $oldid ] );
98 }
99
100 $this->checkTitleUserPermissions( $rev->getTitle(), 'read' );
101 if ( !$rev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) {
102 $this->dieWithError(
103 [ 'apierror-permissiondenied', $this->msg( 'action-deletedtext' ) ]
104 );
105 }
106
107 $titleObj = $rev->getTitle();
108 $wgTitle = $titleObj;
109 $pageObj = WikiPage::factory( $titleObj );
110 list( $popts, $reset, $suppressCache ) = $this->makeParserOptions( $pageObj, $params );
111
112 // If for some reason the "oldid" is actually the current revision, it may be cached
113 // Deliberately comparing $pageObj->getLatest() with $rev->getId(), rather than
114 // checking $rev->isCurrent(), because $pageObj is what actually ends up being used,
115 // and if its ->getLatest() is outdated, $rev->isCurrent() won't tell us that.
116 if ( !$suppressCache && $rev->getId() == $pageObj->getLatest() ) {
117 // May get from/save to parser cache
118 $p_result = $this->getParsedContent( $pageObj, $popts,
119 $pageid, isset( $prop['wikitext'] ) );
120 } else { // This is an old revision, so get the text differently
121 $this->content = $rev->getContent( Revision::FOR_THIS_USER, $this->getUser() );
122
123 if ( $this->section !== false ) {
124 $this->content = $this->getSectionContent(
125 $this->content, $this->msg( 'revid', $rev->getId() )
126 );
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->dieWithError( 'apierror-missingtitle' );
163 }
164
165 $this->checkTitleUserPermissions( $titleObj, 'read' );
166 $wgTitle = $titleObj;
167
168 if ( isset( $prop['revid'] ) ) {
169 $oldid = $pageObj->getLatest();
170 }
171
172 list( $popts, $reset, $suppressCache ) = $this->makeParserOptions( $pageObj, $params );
173
174 // Don't pollute the parser cache when setting options that aren't
175 // in ParserOptions::optionsHash()
176 /// @todo: This should be handled closer to the actual cache instead of here, see T110269
177 $suppressCache = $suppressCache ||
178 $params['disablepp'] ||
179 $params['disablelimitreport'] ||
180 $params['preview'] ||
181 $params['sectionpreview'] ||
182 $params['disabletidy'];
183
184 if ( $suppressCache ) {
185 $this->content = $this->getContent( $pageObj, $pageid );
186 $p_result = $this->content->getParserOutput( $titleObj, null, $popts );
187 } else {
188 // Potentially cached
189 $p_result = $this->getParsedContent( $pageObj, $popts, $pageid,
190 isset( $prop['wikitext'] ) );
191 }
192 }
193 } else { // Not $oldid, $pageid, $page. Hence based on $text
194 $titleObj = Title::newFromText( $title );
195 if ( !$titleObj || $titleObj->isExternal() ) {
196 $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $title ) ] );
197 }
198 $wgTitle = $titleObj;
199 if ( $titleObj->canExist() ) {
200 $pageObj = WikiPage::factory( $titleObj );
201 } else {
202 // Do like MediaWiki::initializeArticle()
203 $article = Article::newFromTitle( $titleObj, $this->getContext() );
204 $pageObj = $article->getPage();
205 }
206
207 list( $popts, $reset ) = $this->makeParserOptions( $pageObj, $params );
208 $textProvided = !is_null( $text );
209
210 if ( !$textProvided ) {
211 if ( $titleProvided && ( $prop || $params['generatexml'] ) ) {
212 $this->addWarning( 'apiwarn-parse-titlewithouttext' );
213 }
214 // Prevent warning from ContentHandler::makeContent()
215 $text = '';
216 }
217
218 // If we are parsing text, do not use the content model of the default
219 // API title, but default to wikitext to keep BC.
220 if ( $textProvided && !$titleProvided && is_null( $model ) ) {
221 $model = CONTENT_MODEL_WIKITEXT;
222 $this->addWarning( [ 'apiwarn-parse-nocontentmodel', $model ] );
223 }
224
225 try {
226 $this->content = ContentHandler::makeContent( $text, $titleObj, $model, $format );
227 } catch ( MWContentSerializationException $ex ) {
228 $this->dieWithException( $ex, [
229 'wrap' => ApiMessage::create( 'apierror-contentserializationexception', 'parseerror' )
230 ] );
231 }
232
233 if ( $this->section !== false ) {
234 if ( $this->section === 'new' ) {
235 // Insert the section title above the content.
236 if ( !is_null( $params['sectiontitle'] ) && $params['sectiontitle'] !== '' ) {
237 $this->content = $this->content->addSectionHeader( $params['sectiontitle'] );
238 }
239 } else {
240 $this->content = $this->getSectionContent( $this->content, $titleObj->getPrefixedText() );
241 }
242 }
243
244 if ( $params['pst'] || $params['onlypst'] ) {
245 $this->pstContent = $this->content->preSaveTransform( $titleObj, $this->getUser(), $popts );
246 }
247 if ( $params['onlypst'] ) {
248 // Build a result and bail out
249 $result_array = [];
250 $result_array['text'] = $this->pstContent->serialize( $format );
251 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'text';
252 if ( isset( $prop['wikitext'] ) ) {
253 $result_array['wikitext'] = $this->content->serialize( $format );
254 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'wikitext';
255 }
256 if ( !is_null( $params['summary'] ) ||
257 ( !is_null( $params['sectiontitle'] ) && $this->section === 'new' )
258 ) {
259 $result_array['parsedsummary'] = $this->formatSummary( $titleObj, $params );
260 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'parsedsummary';
261 }
262
263 $result->addValue( null, $this->getModuleName(), $result_array );
264
265 return;
266 }
267
268 // Not cached (save or load)
269 if ( $params['pst'] ) {
270 $p_result = $this->pstContent->getParserOutput( $titleObj, null, $popts );
271 } else {
272 $p_result = $this->content->getParserOutput( $titleObj, null, $popts );
273 }
274 }
275
276 $result_array = [];
277
278 $result_array['title'] = $titleObj->getPrefixedText();
279 $result_array['pageid'] = $pageid ?: $pageObj->getId();
280
281 if ( !is_null( $oldid ) ) {
282 $result_array['revid'] = intval( $oldid );
283 }
284
285 if ( $params['redirects'] && !is_null( $redirValues ) ) {
286 $result_array['redirects'] = $redirValues;
287 }
288
289 if ( $params['disabletoc'] ) {
290 $p_result->setTOCEnabled( false );
291 }
292
293 if ( isset( $prop['text'] ) ) {
294 $result_array['text'] = $p_result->getText();
295 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'text';
296 }
297
298 if ( !is_null( $params['summary'] ) ||
299 ( !is_null( $params['sectiontitle'] ) && $this->section === 'new' )
300 ) {
301 $result_array['parsedsummary'] = $this->formatSummary( $titleObj, $params );
302 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'parsedsummary';
303 }
304
305 if ( isset( $prop['langlinks'] ) ) {
306 $langlinks = $p_result->getLanguageLinks();
307
308 if ( $params['effectivelanglinks'] ) {
309 // Link flags are ignored for now, but may in the future be
310 // included in the result.
311 $linkFlags = [];
312 Hooks::run( 'LanguageLinks', [ $titleObj, &$langlinks, &$linkFlags ] );
313 }
314 } else {
315 $langlinks = false;
316 }
317
318 if ( isset( $prop['langlinks'] ) ) {
319 $result_array['langlinks'] = $this->formatLangLinks( $langlinks );
320 }
321 if ( isset( $prop['categories'] ) ) {
322 $result_array['categories'] = $this->formatCategoryLinks( $p_result->getCategories() );
323 }
324 if ( isset( $prop['categorieshtml'] ) ) {
325 $result_array['categorieshtml'] = $this->categoriesHtml( $p_result->getCategories() );
326 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'categorieshtml';
327 }
328 if ( isset( $prop['links'] ) ) {
329 $result_array['links'] = $this->formatLinks( $p_result->getLinks() );
330 }
331 if ( isset( $prop['templates'] ) ) {
332 $result_array['templates'] = $this->formatLinks( $p_result->getTemplates() );
333 }
334 if ( isset( $prop['images'] ) ) {
335 $result_array['images'] = array_keys( $p_result->getImages() );
336 }
337 if ( isset( $prop['externallinks'] ) ) {
338 $result_array['externallinks'] = array_keys( $p_result->getExternalLinks() );
339 }
340 if ( isset( $prop['sections'] ) ) {
341 $result_array['sections'] = $p_result->getSections();
342 }
343
344 if ( isset( $prop['displaytitle'] ) ) {
345 $result_array['displaytitle'] = $p_result->getDisplayTitle() ?:
346 $titleObj->getPrefixedText();
347 }
348
349 if ( isset( $prop['headitems'] ) ) {
350 $result_array['headitems'] = $this->formatHeadItems( $p_result->getHeadItems() );
351 $this->addDeprecation( 'apiwarn-deprecation-parse-headitems', 'action=parse&prop=headitems' );
352 }
353
354 if ( isset( $prop['headhtml'] ) ) {
355 $context = new DerivativeContext( $this->getContext() );
356 $context->setTitle( $titleObj );
357 $context->setWikiPage( $pageObj );
358
359 // We need an OutputPage tied to $context, not to the
360 // RequestContext at the root of the stack.
361 $output = new OutputPage( $context );
362 $output->addParserOutputMetadata( $p_result );
363
364 $result_array['headhtml'] = $output->headElement( $context->getSkin() );
365 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'headhtml';
366 }
367
368 if ( isset( $prop['modules'] ) ) {
369 $result_array['modules'] = array_values( array_unique( $p_result->getModules() ) );
370 $result_array['modulescripts'] = array_values( array_unique( $p_result->getModuleScripts() ) );
371 $result_array['modulestyles'] = array_values( array_unique( $p_result->getModuleStyles() ) );
372 }
373
374 if ( isset( $prop['jsconfigvars'] ) ) {
375 $result_array['jsconfigvars'] =
376 ApiResult::addMetadataToResultVars( $p_result->getJsConfigVars() );
377 }
378
379 if ( isset( $prop['encodedjsconfigvars'] ) ) {
380 $result_array['encodedjsconfigvars'] = FormatJson::encode(
381 $p_result->getJsConfigVars(), false, FormatJson::ALL_OK
382 );
383 $result_array[ApiResult::META_SUBELEMENTS][] = 'encodedjsconfigvars';
384 }
385
386 if ( isset( $prop['modules'] ) &&
387 !isset( $prop['jsconfigvars'] ) && !isset( $prop['encodedjsconfigvars'] ) ) {
388 $this->addWarning( 'apiwarn-moduleswithoutvars' );
389 }
390
391 if ( isset( $prop['indicators'] ) ) {
392 $result_array['indicators'] = (array)$p_result->getIndicators();
393 ApiResult::setArrayType( $result_array['indicators'], 'BCkvp', 'name' );
394 }
395
396 if ( isset( $prop['iwlinks'] ) ) {
397 $result_array['iwlinks'] = $this->formatIWLinks( $p_result->getInterwikiLinks() );
398 }
399
400 if ( isset( $prop['wikitext'] ) ) {
401 $result_array['wikitext'] = $this->content->serialize( $format );
402 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'wikitext';
403 if ( !is_null( $this->pstContent ) ) {
404 $result_array['psttext'] = $this->pstContent->serialize( $format );
405 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'psttext';
406 }
407 }
408 if ( isset( $prop['properties'] ) ) {
409 $result_array['properties'] = (array)$p_result->getProperties();
410 ApiResult::setArrayType( $result_array['properties'], 'BCkvp', 'name' );
411 }
412
413 if ( isset( $prop['limitreportdata'] ) ) {
414 $result_array['limitreportdata'] =
415 $this->formatLimitReportData( $p_result->getLimitReportData() );
416 }
417 if ( isset( $prop['limitreporthtml'] ) ) {
418 $result_array['limitreporthtml'] = EditPage::getPreviewLimitReport( $p_result );
419 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'limitreporthtml';
420 }
421
422 if ( isset( $prop['parsetree'] ) || $params['generatexml'] ) {
423 if ( $this->content->getModel() != CONTENT_MODEL_WIKITEXT ) {
424 $this->dieWithError( 'apierror-parsetree-notwikitext', 'notwikitext' );
425 }
426
427 $wgParser->startExternalParse( $titleObj, $popts, Parser::OT_PREPROCESS );
428 $dom = $wgParser->preprocessToDom( $this->content->getNativeData() );
429 if ( is_callable( [ $dom, 'saveXML' ] ) ) {
430 $xml = $dom->saveXML();
431 } else {
432 $xml = $dom->__toString();
433 }
434 $result_array['parsetree'] = $xml;
435 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'parsetree';
436 }
437
438 $result_mapping = [
439 'redirects' => 'r',
440 'langlinks' => 'll',
441 'categories' => 'cl',
442 'links' => 'pl',
443 'templates' => 'tl',
444 'images' => 'img',
445 'externallinks' => 'el',
446 'iwlinks' => 'iw',
447 'sections' => 's',
448 'headitems' => 'hi',
449 'modules' => 'm',
450 'indicators' => 'ind',
451 'modulescripts' => 'm',
452 'modulestyles' => 'm',
453 'properties' => 'pp',
454 'limitreportdata' => 'lr',
455 ];
456 $this->setIndexedTagNames( $result_array, $result_mapping );
457 $result->addValue( null, $this->getModuleName(), $result_array );
458 }
459
460 /**
461 * Constructs a ParserOptions object
462 *
463 * @param WikiPage $pageObj
464 * @param array $params
465 *
466 * @return array [ ParserOptions, ScopedCallback, bool $suppressCache ]
467 */
468 protected function makeParserOptions( WikiPage $pageObj, array $params ) {
469 $popts = $pageObj->makeParserOptions( $this->getContext() );
470 $popts->enableLimitReport( !$params['disablepp'] && !$params['disablelimitreport'] );
471 $popts->setIsPreview( $params['preview'] || $params['sectionpreview'] );
472 $popts->setIsSectionPreview( $params['sectionpreview'] );
473 $popts->setEditSection( !$params['disableeditsection'] );
474 if ( $params['disabletidy'] ) {
475 $popts->setTidy( false );
476 }
477
478 $reset = null;
479 $suppressCache = false;
480 Hooks::run( 'ApiMakeParserOptions',
481 [ $popts, $pageObj->getTitle(), $params, $this, &$reset, &$suppressCache ] );
482
483 return [ $popts, $reset, $suppressCache ];
484 }
485
486 /**
487 * @param WikiPage $page
488 * @param ParserOptions $popts
489 * @param int $pageId
490 * @param bool $getWikitext
491 * @return ParserOutput
492 */
493 private function getParsedContent( WikiPage $page, $popts, $pageId = null, $getWikitext = false ) {
494 $this->content = $this->getContent( $page, $pageId );
495
496 if ( $this->section !== false && $this->content !== null ) {
497 // Not cached (save or load)
498 return $this->content->getParserOutput( $page->getTitle(), null, $popts );
499 }
500
501 // Try the parser cache first
502 // getParserOutput will save to Parser cache if able
503 $pout = $page->getParserOutput( $popts );
504 if ( !$pout ) {
505 $this->dieWithError( [ 'apierror-nosuchrevid', $page->getLatest() ] );
506 }
507 if ( $getWikitext ) {
508 $this->content = $page->getContent( Revision::RAW );
509 }
510
511 return $pout;
512 }
513
514 /**
515 * Get the content for the given page and the requested section.
516 *
517 * @param WikiPage $page
518 * @param int $pageId
519 * @return Content
520 */
521 private function getContent( WikiPage $page, $pageId = null ) {
522 $content = $page->getContent( Revision::RAW ); // XXX: really raw?
523
524 if ( $this->section !== false && $content !== null ) {
525 $content = $this->getSectionContent(
526 $content,
527 !is_null( $pageId )
528 ? $this->msg( 'pageid', $pageId )
529 : $page->getTitle()->getPrefixedText()
530 );
531 }
532 return $content;
533 }
534
535 /**
536 * Extract the requested section from the given Content
537 *
538 * @param Content $content
539 * @param string|Message $what Identifies the content in error messages, e.g. page title.
540 * @return Content|bool
541 */
542 private function getSectionContent( Content $content, $what ) {
543 // Not cached (save or load)
544 $section = $content->getSection( $this->section );
545 if ( $section === false ) {
546 $this->dieWithError( [ 'apierror-nosuchsection-what', $this->section, $what ], 'nosuchsection' );
547 }
548 if ( $section === null ) {
549 $this->dieWithError( [ 'apierror-sectionsnotsupported-what', $what ], 'nosuchsection' );
550 $section = false;
551 }
552
553 return $section;
554 }
555
556 /**
557 * This mimicks the behavior of EditPage in formatting a summary
558 *
559 * @param Title $title of the page being parsed
560 * @param Array $params the API parameters of the request
561 * @return Content|bool
562 */
563 private function formatSummary( $title, $params ) {
564 global $wgParser;
565 $summary = !is_null( $params['summary'] ) ? $params['summary'] : '';
566 $sectionTitle = !is_null( $params['sectiontitle'] ) ? $params['sectiontitle'] : '';
567
568 if ( $this->section === 'new' && ( $sectionTitle === '' || $summary === '' ) ) {
569 if ( $sectionTitle !== '' ) {
570 $summary = $params['sectiontitle'];
571 }
572 if ( $summary !== '' ) {
573 $summary = wfMessage( 'newsectionsummary' )
574 ->rawParams( $wgParser->stripSectionName( $summary ) )
575 ->inContentLanguage()->text();
576 }
577 }
578 return Linker::formatComment( $summary, $title, $this->section === 'new' );
579 }
580
581 private function formatLangLinks( $links ) {
582 $result = [];
583 foreach ( $links as $link ) {
584 $entry = [];
585 $bits = explode( ':', $link, 2 );
586 $title = Title::newFromText( $link );
587
588 $entry['lang'] = $bits[0];
589 if ( $title ) {
590 $entry['url'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
591 // localised language name in 'uselang' language
592 $entry['langname'] = Language::fetchLanguageName(
593 $title->getInterwiki(),
594 $this->getLanguage()->getCode()
595 );
596
597 // native language name
598 $entry['autonym'] = Language::fetchLanguageName( $title->getInterwiki() );
599 }
600 ApiResult::setContentValue( $entry, 'title', $bits[1] );
601 $result[] = $entry;
602 }
603
604 return $result;
605 }
606
607 private function formatCategoryLinks( $links ) {
608 $result = [];
609
610 if ( !$links ) {
611 return $result;
612 }
613
614 // Fetch hiddencat property
615 $lb = new LinkBatch;
616 $lb->setArray( [ NS_CATEGORY => $links ] );
617 $db = $this->getDB();
618 $res = $db->select( [ 'page', 'page_props' ],
619 [ 'page_title', 'pp_propname' ],
620 $lb->constructSet( 'page', $db ),
621 __METHOD__,
622 [],
623 [ 'page_props' => [
624 'LEFT JOIN', [ 'pp_propname' => 'hiddencat', 'pp_page = page_id' ]
625 ] ]
626 );
627 $hiddencats = [];
628 foreach ( $res as $row ) {
629 $hiddencats[$row->page_title] = isset( $row->pp_propname );
630 }
631
632 $linkCache = LinkCache::singleton();
633
634 foreach ( $links as $link => $sortkey ) {
635 $entry = [];
636 $entry['sortkey'] = $sortkey;
637 // array keys will cast numeric category names to ints, so cast back to string
638 ApiResult::setContentValue( $entry, 'category', (string)$link );
639 if ( !isset( $hiddencats[$link] ) ) {
640 $entry['missing'] = true;
641
642 // We already know the link doesn't exist in the database, so
643 // tell LinkCache that before calling $title->isKnown().
644 $title = Title::makeTitle( NS_CATEGORY, $link );
645 $linkCache->addBadLinkObj( $title );
646 if ( $title->isKnown() ) {
647 $entry['known'] = true;
648 }
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 formatLimitReportData( $limitReportData ) {
713 $result = [];
714
715 foreach ( $limitReportData as $name => $value ) {
716 $entry = [];
717 $entry['name'] = $name;
718 if ( !is_array( $value ) ) {
719 $value = [ $value ];
720 }
721 ApiResult::setIndexedTagNameRecursive( $value, 'param' );
722 $entry = array_merge( $entry, $value );
723 $result[] = $entry;
724 }
725
726 return $result;
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 [
739 'title' => null,
740 'text' => [
741 ApiBase::PARAM_TYPE => 'text',
742 ],
743 'summary' => null,
744 'page' => null,
745 'pageid' => [
746 ApiBase::PARAM_TYPE => 'integer',
747 ],
748 'redirects' => false,
749 'oldid' => [
750 ApiBase::PARAM_TYPE => 'integer',
751 ],
752 'prop' => [
753 ApiBase::PARAM_DFLT => 'text|langlinks|categories|links|templates|' .
754 'images|externallinks|sections|revid|displaytitle|iwlinks|properties',
755 ApiBase::PARAM_ISMULTI => true,
756 ApiBase::PARAM_TYPE => [
757 'text',
758 'langlinks',
759 'categories',
760 'categorieshtml',
761 'links',
762 'templates',
763 'images',
764 'externallinks',
765 'sections',
766 'revid',
767 'displaytitle',
768 'headitems',
769 'headhtml',
770 'modules',
771 'jsconfigvars',
772 'encodedjsconfigvars',
773 'indicators',
774 'iwlinks',
775 'wikitext',
776 'properties',
777 'limitreportdata',
778 'limitreporthtml',
779 'parsetree',
780 ],
781 ApiBase::PARAM_HELP_MSG_PER_VALUE => [
782 'parsetree' => [ 'apihelp-parse-paramvalue-prop-parsetree', CONTENT_MODEL_WIKITEXT ],
783 ],
784 ],
785 'pst' => false,
786 'onlypst' => false,
787 'effectivelanglinks' => false,
788 'section' => null,
789 'sectiontitle' => [
790 ApiBase::PARAM_TYPE => 'string',
791 ],
792 'disablepp' => [
793 ApiBase::PARAM_DFLT => false,
794 ApiBase::PARAM_DEPRECATED => true,
795 ],
796 'disablelimitreport' => false,
797 'disableeditsection' => false,
798 'disabletidy' => false,
799 'generatexml' => [
800 ApiBase::PARAM_DFLT => false,
801 ApiBase::PARAM_HELP_MSG => [
802 'apihelp-parse-param-generatexml', CONTENT_MODEL_WIKITEXT
803 ],
804 ApiBase::PARAM_DEPRECATED => true,
805 ],
806 'preview' => false,
807 'sectionpreview' => false,
808 'disabletoc' => false,
809 'contentformat' => [
810 ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
811 ],
812 'contentmodel' => [
813 ApiBase::PARAM_TYPE => ContentHandler::getContentModels(),
814 ]
815 ];
816 }
817
818 protected function getExamplesMessages() {
819 return [
820 'action=parse&page=Project:Sandbox'
821 => 'apihelp-parse-example-page',
822 'action=parse&text={{Project:Sandbox}}&contentmodel=wikitext'
823 => 'apihelp-parse-example-text',
824 'action=parse&text={{PAGENAME}}&title=Test'
825 => 'apihelp-parse-example-texttitle',
826 'action=parse&summary=Some+[[link]]&prop='
827 => 'apihelp-parse-example-summary',
828 ];
829 }
830
831 public function getHelpUrls() {
832 return 'https://www.mediawiki.org/wiki/API:Parsing_wikitext#parse';
833 }
834 }