ApiParse: Add Skin::getDefaultModules() in useskin mode
[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 use MediaWiki\MediaWikiServices;
26
27 /**
28 * @ingroup API
29 */
30 class ApiParse extends ApiBase {
31
32 /** @var string $section */
33 private $section = null;
34
35 /** @var Content $content */
36 private $content = null;
37
38 /** @var Content $pstContent */
39 private $pstContent = null;
40
41 public function execute() {
42 // The data is hot but user-dependent, like page views, so we set vary cookies
43 $this->getMain()->setCacheMode( 'anon-public-user-private' );
44
45 // Get parameters
46 $params = $this->extractRequestParams();
47
48 // No easy way to say that text & title are allowed together while the
49 // rest aren't, so just do it in two calls.
50 $this->requireMaxOneParameter( $params, 'page', 'pageid', 'oldid', 'text' );
51 $this->requireMaxOneParameter( $params, 'page', 'pageid', 'oldid', 'title' );
52
53 $text = $params['text'];
54 $title = $params['title'];
55 if ( $title === null ) {
56 $titleProvided = false;
57 // A title is needed for parsing, so arbitrarily choose one
58 $title = 'API';
59 } else {
60 $titleProvided = true;
61 }
62
63 $page = $params['page'];
64 $pageid = $params['pageid'];
65 $oldid = $params['oldid'];
66
67 $model = $params['contentmodel'];
68 $format = $params['contentformat'];
69
70 $prop = array_flip( $params['prop'] );
71
72 if ( isset( $params['section'] ) ) {
73 $this->section = $params['section'];
74 if ( !preg_match( '/^((T-)?\d+|new)$/', $this->section ) ) {
75 $this->dieWithError( 'apierror-invalidsection' );
76 }
77 } else {
78 $this->section = false;
79 }
80
81 // The parser needs $wgTitle to be set, apparently the
82 // $title parameter in Parser::parse isn't enough *sigh*
83 // TODO: Does this still need $wgTitle?
84 global $wgParser, $wgTitle;
85
86 $redirValues = null;
87
88 // Return result
89 $result = $this->getResult();
90
91 if ( !is_null( $oldid ) || !is_null( $pageid ) || !is_null( $page ) ) {
92 if ( $this->section === 'new' ) {
93 $this->dieWithError( 'apierror-invalidparammix-parse-new-section', 'invalidparammix' );
94 }
95 if ( !is_null( $oldid ) ) {
96 // Don't use the parser cache
97 $rev = Revision::newFromId( $oldid );
98 if ( !$rev ) {
99 $this->dieWithError( [ 'apierror-nosuchrevid', $oldid ] );
100 }
101
102 $this->checkTitleUserPermissions( $rev->getTitle(), 'read' );
103 if ( !$rev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) {
104 $this->dieWithError(
105 [ 'apierror-permissiondenied', $this->msg( 'action-deletedtext' ) ]
106 );
107 }
108
109 $titleObj = $rev->getTitle();
110 $wgTitle = $titleObj;
111 $pageObj = WikiPage::factory( $titleObj );
112 list( $popts, $reset, $suppressCache ) = $this->makeParserOptions( $pageObj, $params );
113
114 // If for some reason the "oldid" is actually the current revision, it may be cached
115 // Deliberately comparing $pageObj->getLatest() with $rev->getId(), rather than
116 // checking $rev->isCurrent(), because $pageObj is what actually ends up being used,
117 // and if its ->getLatest() is outdated, $rev->isCurrent() won't tell us that.
118 if ( !$suppressCache && $rev->getId() == $pageObj->getLatest() ) {
119 // May get from/save to parser cache
120 $p_result = $this->getParsedContent( $pageObj, $popts,
121 $pageid, isset( $prop['wikitext'] ) );
122 } else { // This is an old revision, so get the text differently
123 $this->content = $rev->getContent( Revision::FOR_THIS_USER, $this->getUser() );
124
125 if ( $this->section !== false ) {
126 $this->content = $this->getSectionContent(
127 $this->content, $this->msg( 'revid', $rev->getId() )
128 );
129 }
130
131 // Should we save old revision parses to the parser cache?
132 $p_result = $this->content->getParserOutput( $titleObj, $rev->getId(), $popts );
133 }
134 } else { // Not $oldid, but $pageid or $page
135 if ( $params['redirects'] ) {
136 $reqParams = [
137 'redirects' => '',
138 ];
139 if ( !is_null( $pageid ) ) {
140 $reqParams['pageids'] = $pageid;
141 } else { // $page
142 $reqParams['titles'] = $page;
143 }
144 $req = new FauxRequest( $reqParams );
145 $main = new ApiMain( $req );
146 $pageSet = new ApiPageSet( $main );
147 $pageSet->execute();
148 $redirValues = $pageSet->getRedirectTitlesAsResult( $this->getResult() );
149
150 $to = $page;
151 foreach ( $pageSet->getRedirectTitles() as $title ) {
152 $to = $title->getFullText();
153 }
154 $pageParams = [ 'title' => $to ];
155 } elseif ( !is_null( $pageid ) ) {
156 $pageParams = [ 'pageid' => $pageid ];
157 } else { // $page
158 $pageParams = [ 'title' => $page ];
159 }
160
161 $pageObj = $this->getTitleOrPageId( $pageParams, 'fromdb' );
162 $titleObj = $pageObj->getTitle();
163 if ( !$titleObj || !$titleObj->exists() ) {
164 $this->dieWithError( 'apierror-missingtitle' );
165 }
166
167 $this->checkTitleUserPermissions( $titleObj, 'read' );
168 $wgTitle = $titleObj;
169
170 if ( isset( $prop['revid'] ) ) {
171 $oldid = $pageObj->getLatest();
172 }
173
174 list( $popts, $reset, $suppressCache ) = $this->makeParserOptions( $pageObj, $params );
175
176 // Don't pollute the parser cache when setting options that aren't
177 // in ParserOptions::optionsHash()
178 /// @todo: This should be handled closer to the actual cache instead of here, see T110269
179 $suppressCache = $suppressCache ||
180 $params['disablepp'] ||
181 $params['disablelimitreport'] ||
182 $params['preview'] ||
183 $params['sectionpreview'] ||
184 $params['disabletidy'];
185
186 if ( $suppressCache ) {
187 $this->content = $this->getContent( $pageObj, $pageid );
188 $p_result = $this->content->getParserOutput( $titleObj, null, $popts );
189 } else {
190 // Potentially cached
191 $p_result = $this->getParsedContent( $pageObj, $popts, $pageid,
192 isset( $prop['wikitext'] ) );
193 }
194 }
195 } else { // Not $oldid, $pageid, $page. Hence based on $text
196 $titleObj = Title::newFromText( $title );
197 if ( !$titleObj || $titleObj->isExternal() ) {
198 $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $title ) ] );
199 }
200 $wgTitle = $titleObj;
201 if ( $titleObj->canExist() ) {
202 $pageObj = WikiPage::factory( $titleObj );
203 } else {
204 // Do like MediaWiki::initializeArticle()
205 $article = Article::newFromTitle( $titleObj, $this->getContext() );
206 $pageObj = $article->getPage();
207 }
208
209 list( $popts, $reset ) = $this->makeParserOptions( $pageObj, $params );
210 $textProvided = !is_null( $text );
211
212 if ( !$textProvided ) {
213 if ( $titleProvided && ( $prop || $params['generatexml'] ) ) {
214 $this->addWarning( 'apiwarn-parse-titlewithouttext' );
215 }
216 // Prevent warning from ContentHandler::makeContent()
217 $text = '';
218 }
219
220 // If we are parsing text, do not use the content model of the default
221 // API title, but default to wikitext to keep BC.
222 if ( $textProvided && !$titleProvided && is_null( $model ) ) {
223 $model = CONTENT_MODEL_WIKITEXT;
224 $this->addWarning( [ 'apiwarn-parse-nocontentmodel', $model ] );
225 }
226
227 try {
228 $this->content = ContentHandler::makeContent( $text, $titleObj, $model, $format );
229 } catch ( MWContentSerializationException $ex ) {
230 $this->dieWithException( $ex, [
231 'wrap' => ApiMessage::create( 'apierror-contentserializationexception', '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 ( $params['disabletoc'] ) {
284 $p_result->setTOCEnabled( false );
285 }
286
287 if ( isset( $params['useskin'] ) ) {
288 $factory = MediaWikiServices::getInstance()->getSkinFactory();
289 $skin = $factory->makeSkin( Skin::normalizeKey( $params['useskin'] ) );
290 } else {
291 $skin = null;
292 }
293
294 $outputPage = null;
295 if ( $skin || isset( $prop['headhtml'] ) ) {
296 // Enabling the skin via 'useskin' or 'headhtml' gets OutputPage and
297 // Skin involved, which (among others) applies these hooks:
298 // - ParserOutputHooks
299 // - Hook: LanguageLinks
300 // - Hook: OutputPageParserOutput
301 $context = new DerivativeContext( $this->getContext() );
302 $context->setTitle( $titleObj );
303 $context->setWikiPage( $pageObj );
304
305 if ( $skin ) {
306 // Use the skin specified by 'useskin'
307 $context->setSkin( $skin );
308 // Context clones the skin, refetch to stay in sync. (T166022)
309 $skin = $context->getSkin();
310 }
311
312 $outputPage = new OutputPage( $context );
313 $outputPage->addParserOutputMetadata( $p_result );
314 $context->setOutput( $outputPage );
315
316 if ( $skin ) {
317 // Based on OutputPage::output()
318 foreach ( $skin->getDefaultModules() as $group ) {
319 $outputPage->addModules( $group );
320 }
321 }
322 }
323
324 if ( !is_null( $oldid ) ) {
325 $result_array['revid'] = intval( $oldid );
326 }
327
328 if ( $params['redirects'] && !is_null( $redirValues ) ) {
329 $result_array['redirects'] = $redirValues;
330 }
331
332 if ( isset( $prop['text'] ) ) {
333 $result_array['text'] = $p_result->getText();
334 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'text';
335 }
336
337 if ( !is_null( $params['summary'] ) ||
338 ( !is_null( $params['sectiontitle'] ) && $this->section === 'new' )
339 ) {
340 $result_array['parsedsummary'] = $this->formatSummary( $titleObj, $params );
341 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'parsedsummary';
342 }
343
344 if ( isset( $prop['langlinks'] ) ) {
345 if ( $skin ) {
346 $langlinks = $outputPage->getLanguageLinks();
347 } else {
348 $langlinks = $p_result->getLanguageLinks();
349 // The deprecated 'effectivelanglinks' option depredates OutputPage
350 // support via 'useskin'. If not already applied, then run just this
351 // one hook of OutputPage::addParserOutputMetadata here.
352 if ( $params['effectivelanglinks'] ) {
353 $linkFlags = [];
354 Hooks::run( 'LanguageLinks', [ $titleObj, &$langlinks, &$linkFlags ] );
355 }
356 }
357
358 $result_array['langlinks'] = $this->formatLangLinks( $langlinks );
359 }
360 if ( isset( $prop['categories'] ) ) {
361 $result_array['categories'] = $this->formatCategoryLinks( $p_result->getCategories() );
362 }
363 if ( isset( $prop['categorieshtml'] ) ) {
364 $result_array['categorieshtml'] = $this->categoriesHtml( $p_result->getCategories() );
365 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'categorieshtml';
366 }
367 if ( isset( $prop['links'] ) ) {
368 $result_array['links'] = $this->formatLinks( $p_result->getLinks() );
369 }
370 if ( isset( $prop['templates'] ) ) {
371 $result_array['templates'] = $this->formatLinks( $p_result->getTemplates() );
372 }
373 if ( isset( $prop['images'] ) ) {
374 $result_array['images'] = array_keys( $p_result->getImages() );
375 }
376 if ( isset( $prop['externallinks'] ) ) {
377 $result_array['externallinks'] = array_keys( $p_result->getExternalLinks() );
378 }
379 if ( isset( $prop['sections'] ) ) {
380 $result_array['sections'] = $p_result->getSections();
381 }
382 if ( isset( $prop['parsewarnings'] ) ) {
383 $result_array['parsewarnings'] = $p_result->getWarnings();
384 }
385
386 if ( isset( $prop['displaytitle'] ) ) {
387 $result_array['displaytitle'] = $p_result->getDisplayTitle() ?:
388 $titleObj->getPrefixedText();
389 }
390
391 if ( isset( $prop['headitems'] ) ) {
392 if ( $skin ) {
393 $result_array['headitems'] = $this->formatHeadItems( $outputPage->getHeadItemsArray() );
394 } else {
395 $result_array['headitems'] = $this->formatHeadItems( $p_result->getHeadItems() );
396 }
397 $this->addDeprecation( 'apiwarn-deprecation-parse-headitems', 'action=parse&prop=headitems' );
398 }
399
400 if ( isset( $prop['headhtml'] ) ) {
401 $result_array['headhtml'] = $outputPage->headElement( $context->getSkin() );
402 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'headhtml';
403 }
404
405 if ( isset( $prop['modules'] ) ) {
406 if ( $skin ) {
407 $result_array['modules'] = $outputPage->getModules();
408 $result_array['modulescripts'] = $outputPage->getModuleScripts();
409 $result_array['modulestyles'] = $outputPage->getModuleStyles();
410 } else {
411 $result_array['modules'] = array_values( array_unique( $p_result->getModules() ) );
412 $result_array['modulescripts'] = array_values( array_unique( $p_result->getModuleScripts() ) );
413 $result_array['modulestyles'] = array_values( array_unique( $p_result->getModuleStyles() ) );
414 }
415 }
416
417 if ( isset( $prop['jsconfigvars'] ) ) {
418 $jsconfigvars = $skin ? $outputPage->getJsConfigVars() : $p_result->getJsConfigVars();
419 $result_array['jsconfigvars'] = ApiResult::addMetadataToResultVars( $jsconfigvars );
420 }
421
422 if ( isset( $prop['encodedjsconfigvars'] ) ) {
423 $jsconfigvars = $skin ? $outputPage->getJsConfigVars() : $p_result->getJsConfigVars();
424 $result_array['encodedjsconfigvars'] = FormatJson::encode(
425 $jsconfigvars,
426 false,
427 FormatJson::ALL_OK
428 );
429 $result_array[ApiResult::META_SUBELEMENTS][] = 'encodedjsconfigvars';
430 }
431
432 if ( isset( $prop['modules'] ) &&
433 !isset( $prop['jsconfigvars'] ) && !isset( $prop['encodedjsconfigvars'] ) ) {
434 $this->addWarning( 'apiwarn-moduleswithoutvars' );
435 }
436
437 if ( isset( $prop['indicators'] ) ) {
438 if ( $skin ) {
439 $result_array['indicators'] = (array)$outputPage->getIndicators();
440 } else {
441 $result_array['indicators'] = (array)$p_result->getIndicators();
442 }
443 ApiResult::setArrayType( $result_array['indicators'], 'BCkvp', 'name' );
444 }
445
446 if ( isset( $prop['iwlinks'] ) ) {
447 $result_array['iwlinks'] = $this->formatIWLinks( $p_result->getInterwikiLinks() );
448 }
449
450 if ( isset( $prop['wikitext'] ) ) {
451 $result_array['wikitext'] = $this->content->serialize( $format );
452 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'wikitext';
453 if ( !is_null( $this->pstContent ) ) {
454 $result_array['psttext'] = $this->pstContent->serialize( $format );
455 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'psttext';
456 }
457 }
458 if ( isset( $prop['properties'] ) ) {
459 $result_array['properties'] = (array)$p_result->getProperties();
460 ApiResult::setArrayType( $result_array['properties'], 'BCkvp', 'name' );
461 }
462
463 if ( isset( $prop['limitreportdata'] ) ) {
464 $result_array['limitreportdata'] =
465 $this->formatLimitReportData( $p_result->getLimitReportData() );
466 }
467 if ( isset( $prop['limitreporthtml'] ) ) {
468 $result_array['limitreporthtml'] = EditPage::getPreviewLimitReport( $p_result );
469 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'limitreporthtml';
470 }
471
472 if ( isset( $prop['parsetree'] ) || $params['generatexml'] ) {
473 if ( $this->content->getModel() != CONTENT_MODEL_WIKITEXT ) {
474 $this->dieWithError( 'apierror-parsetree-notwikitext', 'notwikitext' );
475 }
476
477 $wgParser->startExternalParse( $titleObj, $popts, Parser::OT_PREPROCESS );
478 $dom = $wgParser->preprocessToDom( $this->content->getNativeData() );
479 if ( is_callable( [ $dom, 'saveXML' ] ) ) {
480 $xml = $dom->saveXML();
481 } else {
482 $xml = $dom->__toString();
483 }
484 $result_array['parsetree'] = $xml;
485 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'parsetree';
486 }
487
488 $result_mapping = [
489 'redirects' => 'r',
490 'langlinks' => 'll',
491 'categories' => 'cl',
492 'links' => 'pl',
493 'templates' => 'tl',
494 'images' => 'img',
495 'externallinks' => 'el',
496 'iwlinks' => 'iw',
497 'sections' => 's',
498 'headitems' => 'hi',
499 'modules' => 'm',
500 'indicators' => 'ind',
501 'modulescripts' => 'm',
502 'modulestyles' => 'm',
503 'properties' => 'pp',
504 'limitreportdata' => 'lr',
505 'parsewarnings' => 'pw'
506 ];
507 $this->setIndexedTagNames( $result_array, $result_mapping );
508 $result->addValue( null, $this->getModuleName(), $result_array );
509 }
510
511 /**
512 * Constructs a ParserOptions object
513 *
514 * @param WikiPage $pageObj
515 * @param array $params
516 *
517 * @return array [ ParserOptions, ScopedCallback, bool $suppressCache ]
518 */
519 protected function makeParserOptions( WikiPage $pageObj, array $params ) {
520 $popts = $pageObj->makeParserOptions( $this->getContext() );
521 $popts->enableLimitReport( !$params['disablepp'] && !$params['disablelimitreport'] );
522 $popts->setIsPreview( $params['preview'] || $params['sectionpreview'] );
523 $popts->setIsSectionPreview( $params['sectionpreview'] );
524 $popts->setEditSection( !$params['disableeditsection'] );
525 if ( $params['disabletidy'] ) {
526 $popts->setTidy( false );
527 }
528 $popts->setWrapOutputClass(
529 $params['wrapoutputclass'] === '' ? false : $params['wrapoutputclass']
530 );
531
532 $reset = null;
533 $suppressCache = false;
534 Hooks::run( 'ApiMakeParserOptions',
535 [ $popts, $pageObj->getTitle(), $params, $this, &$reset, &$suppressCache ] );
536
537 return [ $popts, $reset, $suppressCache ];
538 }
539
540 /**
541 * @param WikiPage $page
542 * @param ParserOptions $popts
543 * @param int $pageId
544 * @param bool $getWikitext
545 * @return ParserOutput
546 */
547 private function getParsedContent( WikiPage $page, $popts, $pageId = null, $getWikitext = false ) {
548 $this->content = $this->getContent( $page, $pageId );
549
550 if ( $this->section !== false && $this->content !== null ) {
551 // Not cached (save or load)
552 return $this->content->getParserOutput( $page->getTitle(), null, $popts );
553 }
554
555 // Try the parser cache first
556 // getParserOutput will save to Parser cache if able
557 $pout = $page->getParserOutput( $popts );
558 if ( !$pout ) {
559 $this->dieWithError( [ 'apierror-nosuchrevid', $page->getLatest() ] );
560 }
561 if ( $getWikitext ) {
562 $this->content = $page->getContent( Revision::RAW );
563 }
564
565 return $pout;
566 }
567
568 /**
569 * Get the content for the given page and the requested section.
570 *
571 * @param WikiPage $page
572 * @param int $pageId
573 * @return Content
574 */
575 private function getContent( WikiPage $page, $pageId = null ) {
576 $content = $page->getContent( Revision::RAW ); // XXX: really raw?
577
578 if ( $this->section !== false && $content !== null ) {
579 $content = $this->getSectionContent(
580 $content,
581 !is_null( $pageId )
582 ? $this->msg( 'pageid', $pageId )
583 : $page->getTitle()->getPrefixedText()
584 );
585 }
586 return $content;
587 }
588
589 /**
590 * Extract the requested section from the given Content
591 *
592 * @param Content $content
593 * @param string|Message $what Identifies the content in error messages, e.g. page title.
594 * @return Content|bool
595 */
596 private function getSectionContent( Content $content, $what ) {
597 // Not cached (save or load)
598 $section = $content->getSection( $this->section );
599 if ( $section === false ) {
600 $this->dieWithError( [ 'apierror-nosuchsection-what', $this->section, $what ], 'nosuchsection' );
601 }
602 if ( $section === null ) {
603 $this->dieWithError( [ 'apierror-sectionsnotsupported-what', $what ], 'nosuchsection' );
604 $section = false;
605 }
606
607 return $section;
608 }
609
610 /**
611 * This mimicks the behavior of EditPage in formatting a summary
612 *
613 * @param Title $title of the page being parsed
614 * @param Array $params the API parameters of the request
615 * @return Content|bool
616 */
617 private function formatSummary( $title, $params ) {
618 global $wgParser;
619 $summary = !is_null( $params['summary'] ) ? $params['summary'] : '';
620 $sectionTitle = !is_null( $params['sectiontitle'] ) ? $params['sectiontitle'] : '';
621
622 if ( $this->section === 'new' && ( $sectionTitle === '' || $summary === '' ) ) {
623 if ( $sectionTitle !== '' ) {
624 $summary = $params['sectiontitle'];
625 }
626 if ( $summary !== '' ) {
627 $summary = wfMessage( 'newsectionsummary' )
628 ->rawParams( $wgParser->stripSectionName( $summary ) )
629 ->inContentLanguage()->text();
630 }
631 }
632 return Linker::formatComment( $summary, $title, $this->section === 'new' );
633 }
634
635 private function formatLangLinks( $links ) {
636 $result = [];
637 foreach ( $links as $link ) {
638 $entry = [];
639 $bits = explode( ':', $link, 2 );
640 $title = Title::newFromText( $link );
641
642 $entry['lang'] = $bits[0];
643 if ( $title ) {
644 $entry['url'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
645 // localised language name in 'uselang' language
646 $entry['langname'] = Language::fetchLanguageName(
647 $title->getInterwiki(),
648 $this->getLanguage()->getCode()
649 );
650
651 // native language name
652 $entry['autonym'] = Language::fetchLanguageName( $title->getInterwiki() );
653 }
654 ApiResult::setContentValue( $entry, 'title', $bits[1] );
655 $result[] = $entry;
656 }
657
658 return $result;
659 }
660
661 private function formatCategoryLinks( $links ) {
662 $result = [];
663
664 if ( !$links ) {
665 return $result;
666 }
667
668 // Fetch hiddencat property
669 $lb = new LinkBatch;
670 $lb->setArray( [ NS_CATEGORY => $links ] );
671 $db = $this->getDB();
672 $res = $db->select( [ 'page', 'page_props' ],
673 [ 'page_title', 'pp_propname' ],
674 $lb->constructSet( 'page', $db ),
675 __METHOD__,
676 [],
677 [ 'page_props' => [
678 'LEFT JOIN', [ 'pp_propname' => 'hiddencat', 'pp_page = page_id' ]
679 ] ]
680 );
681 $hiddencats = [];
682 foreach ( $res as $row ) {
683 $hiddencats[$row->page_title] = isset( $row->pp_propname );
684 }
685
686 $linkCache = LinkCache::singleton();
687
688 foreach ( $links as $link => $sortkey ) {
689 $entry = [];
690 $entry['sortkey'] = $sortkey;
691 // array keys will cast numeric category names to ints, so cast back to string
692 ApiResult::setContentValue( $entry, 'category', (string)$link );
693 if ( !isset( $hiddencats[$link] ) ) {
694 $entry['missing'] = true;
695
696 // We already know the link doesn't exist in the database, so
697 // tell LinkCache that before calling $title->isKnown().
698 $title = Title::makeTitle( NS_CATEGORY, $link );
699 $linkCache->addBadLinkObj( $title );
700 if ( $title->isKnown() ) {
701 $entry['known'] = true;
702 }
703 } elseif ( $hiddencats[$link] ) {
704 $entry['hidden'] = true;
705 }
706 $result[] = $entry;
707 }
708
709 return $result;
710 }
711
712 private function categoriesHtml( $categories ) {
713 $context = $this->getContext();
714 $context->getOutput()->addCategoryLinks( $categories );
715
716 return $context->getSkin()->getCategories();
717 }
718
719 private function formatLinks( $links ) {
720 $result = [];
721 foreach ( $links as $ns => $nslinks ) {
722 foreach ( $nslinks as $title => $id ) {
723 $entry = [];
724 $entry['ns'] = $ns;
725 ApiResult::setContentValue( $entry, 'title', Title::makeTitle( $ns, $title )->getFullText() );
726 $entry['exists'] = $id != 0;
727 $result[] = $entry;
728 }
729 }
730
731 return $result;
732 }
733
734 private function formatIWLinks( $iw ) {
735 $result = [];
736 foreach ( $iw as $prefix => $titles ) {
737 foreach ( array_keys( $titles ) as $title ) {
738 $entry = [];
739 $entry['prefix'] = $prefix;
740
741 $title = Title::newFromText( "{$prefix}:{$title}" );
742 if ( $title ) {
743 $entry['url'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
744 }
745
746 ApiResult::setContentValue( $entry, 'title', $title->getFullText() );
747 $result[] = $entry;
748 }
749 }
750
751 return $result;
752 }
753
754 private function formatHeadItems( $headItems ) {
755 $result = [];
756 foreach ( $headItems as $tag => $content ) {
757 $entry = [];
758 $entry['tag'] = $tag;
759 ApiResult::setContentValue( $entry, 'content', $content );
760 $result[] = $entry;
761 }
762
763 return $result;
764 }
765
766 private function formatLimitReportData( $limitReportData ) {
767 $result = [];
768
769 foreach ( $limitReportData as $name => $value ) {
770 $entry = [];
771 $entry['name'] = $name;
772 if ( !is_array( $value ) ) {
773 $value = [ $value ];
774 }
775 ApiResult::setIndexedTagNameRecursive( $value, 'param' );
776 $entry = array_merge( $entry, $value );
777 $result[] = $entry;
778 }
779
780 return $result;
781 }
782
783 private function setIndexedTagNames( &$array, $mapping ) {
784 foreach ( $mapping as $key => $name ) {
785 if ( isset( $array[$key] ) ) {
786 ApiResult::setIndexedTagName( $array[$key], $name );
787 }
788 }
789 }
790
791 public function getAllowedParams() {
792 return [
793 'title' => null,
794 'text' => [
795 ApiBase::PARAM_TYPE => 'text',
796 ],
797 'summary' => null,
798 'page' => null,
799 'pageid' => [
800 ApiBase::PARAM_TYPE => 'integer',
801 ],
802 'redirects' => false,
803 'oldid' => [
804 ApiBase::PARAM_TYPE => 'integer',
805 ],
806 'prop' => [
807 ApiBase::PARAM_DFLT => 'text|langlinks|categories|links|templates|' .
808 'images|externallinks|sections|revid|displaytitle|iwlinks|' .
809 'properties|parsewarnings',
810 ApiBase::PARAM_ISMULTI => true,
811 ApiBase::PARAM_TYPE => [
812 'text',
813 'langlinks',
814 'categories',
815 'categorieshtml',
816 'links',
817 'templates',
818 'images',
819 'externallinks',
820 'sections',
821 'revid',
822 'displaytitle',
823 'headitems',
824 'headhtml',
825 'modules',
826 'jsconfigvars',
827 'encodedjsconfigvars',
828 'indicators',
829 'iwlinks',
830 'wikitext',
831 'properties',
832 'limitreportdata',
833 'limitreporthtml',
834 'parsetree',
835 'parsewarnings'
836 ],
837 ApiBase::PARAM_HELP_MSG_PER_VALUE => [
838 'parsetree' => [ 'apihelp-parse-paramvalue-prop-parsetree', CONTENT_MODEL_WIKITEXT ],
839 ],
840 ],
841 'wrapoutputclass' => 'mw-parser-output',
842 'pst' => false,
843 'onlypst' => false,
844 'effectivelanglinks' => [
845 ApiBase::PARAM_DFLT => false,
846 ApiBase::PARAM_DEPRECATED => true,
847 ],
848 'section' => null,
849 'sectiontitle' => [
850 ApiBase::PARAM_TYPE => 'string',
851 ],
852 'disablepp' => [
853 ApiBase::PARAM_DFLT => false,
854 ApiBase::PARAM_DEPRECATED => true,
855 ],
856 'disablelimitreport' => false,
857 'disableeditsection' => false,
858 'disabletidy' => false,
859 'generatexml' => [
860 ApiBase::PARAM_DFLT => false,
861 ApiBase::PARAM_HELP_MSG => [
862 'apihelp-parse-param-generatexml', CONTENT_MODEL_WIKITEXT
863 ],
864 ApiBase::PARAM_DEPRECATED => true,
865 ],
866 'preview' => false,
867 'sectionpreview' => false,
868 'disabletoc' => false,
869 'useskin' => [
870 ApiBase::PARAM_TYPE => array_keys( Skin::getAllowedSkins() ),
871 ],
872 'contentformat' => [
873 ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
874 ],
875 'contentmodel' => [
876 ApiBase::PARAM_TYPE => ContentHandler::getContentModels(),
877 ]
878 ];
879 }
880
881 protected function getExamplesMessages() {
882 return [
883 'action=parse&page=Project:Sandbox'
884 => 'apihelp-parse-example-page',
885 'action=parse&text={{Project:Sandbox}}&contentmodel=wikitext'
886 => 'apihelp-parse-example-text',
887 'action=parse&text={{PAGENAME}}&title=Test'
888 => 'apihelp-parse-example-texttitle',
889 'action=parse&summary=Some+[[link]]&prop='
890 => 'apihelp-parse-example-summary',
891 ];
892 }
893
894 public function getHelpUrls() {
895 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Parsing_wikitext#parse';
896 }
897 }