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