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