Merge "Add CollationFa"
[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 // @todo: Internationalize MWContentSerializationException
229 $this->dieWithError(
230 [ 'apierror-contentserializationexception', wfEscapeWikiText( $ex->getMessage() ) ],
231 'parseerror'
232 );
233 }
234
235 if ( $this->section !== false ) {
236 if ( $this->section === 'new' ) {
237 // Insert the section title above the content.
238 if ( !is_null( $params['sectiontitle'] ) && $params['sectiontitle'] !== '' ) {
239 $this->content = $this->content->addSectionHeader( $params['sectiontitle'] );
240 }
241 } else {
242 $this->content = $this->getSectionContent( $this->content, $titleObj->getPrefixedText() );
243 }
244 }
245
246 if ( $params['pst'] || $params['onlypst'] ) {
247 $this->pstContent = $this->content->preSaveTransform( $titleObj, $this->getUser(), $popts );
248 }
249 if ( $params['onlypst'] ) {
250 // Build a result and bail out
251 $result_array = [];
252 $result_array['text'] = $this->pstContent->serialize( $format );
253 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'text';
254 if ( isset( $prop['wikitext'] ) ) {
255 $result_array['wikitext'] = $this->content->serialize( $format );
256 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'wikitext';
257 }
258 if ( !is_null( $params['summary'] ) ||
259 ( !is_null( $params['sectiontitle'] ) && $this->section === 'new' )
260 ) {
261 $result_array['parsedsummary'] = $this->formatSummary( $titleObj, $params );
262 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'parsedsummary';
263 }
264
265 $result->addValue( null, $this->getModuleName(), $result_array );
266
267 return;
268 }
269
270 // Not cached (save or load)
271 if ( $params['pst'] ) {
272 $p_result = $this->pstContent->getParserOutput( $titleObj, null, $popts );
273 } else {
274 $p_result = $this->content->getParserOutput( $titleObj, null, $popts );
275 }
276 }
277
278 $result_array = [];
279
280 $result_array['title'] = $titleObj->getPrefixedText();
281 $result_array['pageid'] = $pageid ?: $pageObj->getId();
282
283 if ( !is_null( $oldid ) ) {
284 $result_array['revid'] = intval( $oldid );
285 }
286
287 if ( $params['redirects'] && !is_null( $redirValues ) ) {
288 $result_array['redirects'] = $redirValues;
289 }
290
291 if ( $params['disabletoc'] ) {
292 $p_result->setTOCEnabled( false );
293 }
294
295 if ( isset( $prop['text'] ) ) {
296 $result_array['text'] = $p_result->getText();
297 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'text';
298 }
299
300 if ( !is_null( $params['summary'] ) ||
301 ( !is_null( $params['sectiontitle'] ) && $this->section === 'new' )
302 ) {
303 $result_array['parsedsummary'] = $this->formatSummary( $titleObj, $params );
304 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'parsedsummary';
305 }
306
307 if ( isset( $prop['langlinks'] ) ) {
308 $langlinks = $p_result->getLanguageLinks();
309
310 if ( $params['effectivelanglinks'] ) {
311 // Link flags are ignored for now, but may in the future be
312 // included in the result.
313 $linkFlags = [];
314 Hooks::run( 'LanguageLinks', [ $titleObj, &$langlinks, &$linkFlags ] );
315 }
316 } else {
317 $langlinks = false;
318 }
319
320 if ( isset( $prop['langlinks'] ) ) {
321 $result_array['langlinks'] = $this->formatLangLinks( $langlinks );
322 }
323 if ( isset( $prop['categories'] ) ) {
324 $result_array['categories'] = $this->formatCategoryLinks( $p_result->getCategories() );
325 }
326 if ( isset( $prop['categorieshtml'] ) ) {
327 $result_array['categorieshtml'] = $this->categoriesHtml( $p_result->getCategories() );
328 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'categorieshtml';
329 }
330 if ( isset( $prop['links'] ) ) {
331 $result_array['links'] = $this->formatLinks( $p_result->getLinks() );
332 }
333 if ( isset( $prop['templates'] ) ) {
334 $result_array['templates'] = $this->formatLinks( $p_result->getTemplates() );
335 }
336 if ( isset( $prop['images'] ) ) {
337 $result_array['images'] = array_keys( $p_result->getImages() );
338 }
339 if ( isset( $prop['externallinks'] ) ) {
340 $result_array['externallinks'] = array_keys( $p_result->getExternalLinks() );
341 }
342 if ( isset( $prop['sections'] ) ) {
343 $result_array['sections'] = $p_result->getSections();
344 }
345
346 if ( isset( $prop['displaytitle'] ) ) {
347 $result_array['displaytitle'] = $p_result->getDisplayTitle() ?:
348 $titleObj->getPrefixedText();
349 }
350
351 if ( isset( $prop['headitems'] ) ) {
352 $result_array['headitems'] = $this->formatHeadItems( $p_result->getHeadItems() );
353 $this->addDeprecation( 'apiwarn-deprecation-parse-headitems', 'action=parse&prop=headitems' );
354 }
355
356 if ( isset( $prop['headhtml'] ) ) {
357 $context = new DerivativeContext( $this->getContext() );
358 $context->setTitle( $titleObj );
359 $context->setWikiPage( $pageObj );
360
361 // We need an OutputPage tied to $context, not to the
362 // RequestContext at the root of the stack.
363 $output = new OutputPage( $context );
364 $output->addParserOutputMetadata( $p_result );
365
366 $result_array['headhtml'] = $output->headElement( $context->getSkin() );
367 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'headhtml';
368 }
369
370 if ( isset( $prop['modules'] ) ) {
371 $result_array['modules'] = array_values( array_unique( $p_result->getModules() ) );
372 $result_array['modulescripts'] = array_values( array_unique( $p_result->getModuleScripts() ) );
373 $result_array['modulestyles'] = array_values( array_unique( $p_result->getModuleStyles() ) );
374 }
375
376 if ( isset( $prop['jsconfigvars'] ) ) {
377 $result_array['jsconfigvars'] =
378 ApiResult::addMetadataToResultVars( $p_result->getJsConfigVars() );
379 }
380
381 if ( isset( $prop['encodedjsconfigvars'] ) ) {
382 $result_array['encodedjsconfigvars'] = FormatJson::encode(
383 $p_result->getJsConfigVars(), false, FormatJson::ALL_OK
384 );
385 $result_array[ApiResult::META_SUBELEMENTS][] = 'encodedjsconfigvars';
386 }
387
388 if ( isset( $prop['modules'] ) &&
389 !isset( $prop['jsconfigvars'] ) && !isset( $prop['encodedjsconfigvars'] ) ) {
390 $this->addWarning( 'apiwarn-moduleswithoutvars' );
391 }
392
393 if ( isset( $prop['indicators'] ) ) {
394 $result_array['indicators'] = (array)$p_result->getIndicators();
395 ApiResult::setArrayType( $result_array['indicators'], 'BCkvp', 'name' );
396 }
397
398 if ( isset( $prop['iwlinks'] ) ) {
399 $result_array['iwlinks'] = $this->formatIWLinks( $p_result->getInterwikiLinks() );
400 }
401
402 if ( isset( $prop['wikitext'] ) ) {
403 $result_array['wikitext'] = $this->content->serialize( $format );
404 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'wikitext';
405 if ( !is_null( $this->pstContent ) ) {
406 $result_array['psttext'] = $this->pstContent->serialize( $format );
407 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'psttext';
408 }
409 }
410 if ( isset( $prop['properties'] ) ) {
411 $result_array['properties'] = (array)$p_result->getProperties();
412 ApiResult::setArrayType( $result_array['properties'], 'BCkvp', 'name' );
413 }
414
415 if ( isset( $prop['limitreportdata'] ) ) {
416 $result_array['limitreportdata'] =
417 $this->formatLimitReportData( $p_result->getLimitReportData() );
418 }
419 if ( isset( $prop['limitreporthtml'] ) ) {
420 $result_array['limitreporthtml'] = EditPage::getPreviewLimitReport( $p_result );
421 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'limitreporthtml';
422 }
423
424 if ( isset( $prop['parsetree'] ) || $params['generatexml'] ) {
425 if ( $this->content->getModel() != CONTENT_MODEL_WIKITEXT ) {
426 $this->dieWithError( 'apierror-parsetree-notwikitext', 'notwikitext' );
427 }
428
429 $wgParser->startExternalParse( $titleObj, $popts, Parser::OT_PREPROCESS );
430 $dom = $wgParser->preprocessToDom( $this->content->getNativeData() );
431 if ( is_callable( [ $dom, 'saveXML' ] ) ) {
432 $xml = $dom->saveXML();
433 } else {
434 $xml = $dom->__toString();
435 }
436 $result_array['parsetree'] = $xml;
437 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'parsetree';
438 }
439
440 $result_mapping = [
441 'redirects' => 'r',
442 'langlinks' => 'll',
443 'categories' => 'cl',
444 'links' => 'pl',
445 'templates' => 'tl',
446 'images' => 'img',
447 'externallinks' => 'el',
448 'iwlinks' => 'iw',
449 'sections' => 's',
450 'headitems' => 'hi',
451 'modules' => 'm',
452 'indicators' => 'ind',
453 'modulescripts' => 'm',
454 'modulestyles' => 'm',
455 'properties' => 'pp',
456 'limitreportdata' => 'lr',
457 ];
458 $this->setIndexedTagNames( $result_array, $result_mapping );
459 $result->addValue( null, $this->getModuleName(), $result_array );
460 }
461
462 /**
463 * Constructs a ParserOptions object
464 *
465 * @param WikiPage $pageObj
466 * @param array $params
467 *
468 * @return array [ ParserOptions, ScopedCallback, bool $suppressCache ]
469 */
470 protected function makeParserOptions( WikiPage $pageObj, array $params ) {
471 $popts = $pageObj->makeParserOptions( $this->getContext() );
472 $popts->enableLimitReport( !$params['disablepp'] && !$params['disablelimitreport'] );
473 $popts->setIsPreview( $params['preview'] || $params['sectionpreview'] );
474 $popts->setIsSectionPreview( $params['sectionpreview'] );
475 $popts->setEditSection( !$params['disableeditsection'] );
476 if ( $params['disabletidy'] ) {
477 $popts->setTidy( false );
478 }
479
480 $reset = null;
481 $suppressCache = false;
482 Hooks::run( 'ApiMakeParserOptions',
483 [ $popts, $pageObj->getTitle(), $params, $this, &$reset, &$suppressCache ] );
484
485 return [ $popts, $reset, $suppressCache ];
486 }
487
488 /**
489 * @param WikiPage $page
490 * @param ParserOptions $popts
491 * @param int $pageId
492 * @param bool $getWikitext
493 * @return ParserOutput
494 */
495 private function getParsedContent( WikiPage $page, $popts, $pageId = null, $getWikitext = false ) {
496 $this->content = $this->getContent( $page, $pageId );
497
498 if ( $this->section !== false && $this->content !== null ) {
499 // Not cached (save or load)
500 return $this->content->getParserOutput( $page->getTitle(), null, $popts );
501 }
502
503 // Try the parser cache first
504 // getParserOutput will save to Parser cache if able
505 $pout = $page->getParserOutput( $popts );
506 if ( !$pout ) {
507 $this->dieWithError( [ 'apierror-nosuchrevid', $page->getLatest() ] );
508 }
509 if ( $getWikitext ) {
510 $this->content = $page->getContent( Revision::RAW );
511 }
512
513 return $pout;
514 }
515
516 /**
517 * Get the content for the given page and the requested section.
518 *
519 * @param WikiPage $page
520 * @param int $pageId
521 * @return Content
522 */
523 private function getContent( WikiPage $page, $pageId = null ) {
524 $content = $page->getContent( Revision::RAW ); // XXX: really raw?
525
526 if ( $this->section !== false && $content !== null ) {
527 $content = $this->getSectionContent(
528 $content,
529 !is_null( $pageId )
530 ? $this->msg( 'pageid', $pageId )
531 : $page->getTitle()->getPrefixedText()
532 );
533 }
534 return $content;
535 }
536
537 /**
538 * Extract the requested section from the given Content
539 *
540 * @param Content $content
541 * @param string|Message $what Identifies the content in error messages, e.g. page title.
542 * @return Content|bool
543 */
544 private function getSectionContent( Content $content, $what ) {
545 // Not cached (save or load)
546 $section = $content->getSection( $this->section );
547 if ( $section === false ) {
548 $this->dieWithError( [ 'apierror-nosuchsection-what', $this->section, $what ], 'nosuchsection' );
549 }
550 if ( $section === null ) {
551 $this->dieWithError( [ 'apierror-sectionsnotsupported-what', $what ], 'nosuchsection' );
552 $section = false;
553 }
554
555 return $section;
556 }
557
558 /**
559 * This mimicks the behavior of EditPage in formatting a summary
560 *
561 * @param Title $title of the page being parsed
562 * @param Array $params the API parameters of the request
563 * @return Content|bool
564 */
565 private function formatSummary( $title, $params ) {
566 global $wgParser;
567 $summary = !is_null( $params['summary'] ) ? $params['summary'] : '';
568 $sectionTitle = !is_null( $params['sectiontitle'] ) ? $params['sectiontitle'] : '';
569
570 if ( $this->section === 'new' && ( $sectionTitle === '' || $summary === '' ) ) {
571 if ( $sectionTitle !== '' ) {
572 $summary = $params['sectiontitle'];
573 }
574 if ( $summary !== '' ) {
575 $summary = wfMessage( 'newsectionsummary' )
576 ->rawParams( $wgParser->stripSectionName( $summary ) )
577 ->inContentLanguage()->text();
578 }
579 }
580 return Linker::formatComment( $summary, $title, $this->section === 'new' );
581 }
582
583 private function formatLangLinks( $links ) {
584 $result = [];
585 foreach ( $links as $link ) {
586 $entry = [];
587 $bits = explode( ':', $link, 2 );
588 $title = Title::newFromText( $link );
589
590 $entry['lang'] = $bits[0];
591 if ( $title ) {
592 $entry['url'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
593 // localised language name in 'uselang' language
594 $entry['langname'] = Language::fetchLanguageName(
595 $title->getInterwiki(),
596 $this->getLanguage()->getCode()
597 );
598
599 // native language name
600 $entry['autonym'] = Language::fetchLanguageName( $title->getInterwiki() );
601 }
602 ApiResult::setContentValue( $entry, 'title', $bits[1] );
603 $result[] = $entry;
604 }
605
606 return $result;
607 }
608
609 private function formatCategoryLinks( $links ) {
610 $result = [];
611
612 if ( !$links ) {
613 return $result;
614 }
615
616 // Fetch hiddencat property
617 $lb = new LinkBatch;
618 $lb->setArray( [ NS_CATEGORY => $links ] );
619 $db = $this->getDB();
620 $res = $db->select( [ 'page', 'page_props' ],
621 [ 'page_title', 'pp_propname' ],
622 $lb->constructSet( 'page', $db ),
623 __METHOD__,
624 [],
625 [ 'page_props' => [
626 'LEFT JOIN', [ 'pp_propname' => 'hiddencat', 'pp_page = page_id' ]
627 ] ]
628 );
629 $hiddencats = [];
630 foreach ( $res as $row ) {
631 $hiddencats[$row->page_title] = isset( $row->pp_propname );
632 }
633
634 $linkCache = LinkCache::singleton();
635
636 foreach ( $links as $link => $sortkey ) {
637 $entry = [];
638 $entry['sortkey'] = $sortkey;
639 // array keys will cast numeric category names to ints, so cast back to string
640 ApiResult::setContentValue( $entry, 'category', (string)$link );
641 if ( !isset( $hiddencats[$link] ) ) {
642 $entry['missing'] = true;
643
644 // We already know the link doesn't exist in the database, so
645 // tell LinkCache that before calling $title->isKnown().
646 $title = Title::makeTitle( NS_CATEGORY, $link );
647 $linkCache->addBadLinkObj( $title );
648 if ( $title->isKnown() ) {
649 $entry['known'] = true;
650 }
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 }