Merge "ApiParse: Use existing OutputPage for prop=categorieshtml"
[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'] ) || isset( $prop['categorieshtml'] ) ) {
296 // Enabling the skin via 'useskin', 'headhtml', or 'categorieshtml'
297 // gets OutputPage and Skin involved, which (among others) applies
298 // these hooks:
299 // - ParserOutputHooks
300 // - Hook: LanguageLinks
301 // - Hook: OutputPageParserOutput
302 // - Hook: OutputPageMakeCategoryLinks
303 $context = new DerivativeContext( $this->getContext() );
304 $context->setTitle( $titleObj );
305 $context->setWikiPage( $pageObj );
306
307 if ( $skin ) {
308 // Use the skin specified by 'useskin'
309 $context->setSkin( $skin );
310 // Context clones the skin, refetch to stay in sync. (T166022)
311 $skin = $context->getSkin();
312 } else {
313 // Make sure the context's skin refers to the context. Without this,
314 // $outputPage->getSkin()->getOutput() !== $outputPage which
315 // confuses some of the output.
316 $context->setSkin( $context->getSkin() );
317 }
318
319 $outputPage = new OutputPage( $context );
320 $outputPage->addParserOutputMetadata( $p_result );
321 $context->setOutput( $outputPage );
322
323 if ( $skin ) {
324 // Based on OutputPage::output()
325 foreach ( $skin->getDefaultModules() as $group ) {
326 $outputPage->addModules( $group );
327 }
328 }
329 }
330
331 if ( !is_null( $oldid ) ) {
332 $result_array['revid'] = intval( $oldid );
333 }
334
335 if ( $params['redirects'] && !is_null( $redirValues ) ) {
336 $result_array['redirects'] = $redirValues;
337 }
338
339 if ( isset( $prop['text'] ) ) {
340 $result_array['text'] = $p_result->getText();
341 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'text';
342 }
343
344 if ( !is_null( $params['summary'] ) ||
345 ( !is_null( $params['sectiontitle'] ) && $this->section === 'new' )
346 ) {
347 $result_array['parsedsummary'] = $this->formatSummary( $titleObj, $params );
348 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'parsedsummary';
349 }
350
351 if ( isset( $prop['langlinks'] ) ) {
352 if ( $skin ) {
353 $langlinks = $outputPage->getLanguageLinks();
354 } else {
355 $langlinks = $p_result->getLanguageLinks();
356 // The deprecated 'effectivelanglinks' option depredates OutputPage
357 // support via 'useskin'. If not already applied, then run just this
358 // one hook of OutputPage::addParserOutputMetadata here.
359 if ( $params['effectivelanglinks'] ) {
360 $linkFlags = [];
361 Hooks::run( 'LanguageLinks', [ $titleObj, &$langlinks, &$linkFlags ] );
362 }
363 }
364
365 $result_array['langlinks'] = $this->formatLangLinks( $langlinks );
366 }
367 if ( isset( $prop['categories'] ) ) {
368 $result_array['categories'] = $this->formatCategoryLinks( $p_result->getCategories() );
369 }
370 if ( isset( $prop['categorieshtml'] ) ) {
371 $result_array['categorieshtml'] = $outputPage->getSkin()->getCategories();
372 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'categorieshtml';
373 }
374 if ( isset( $prop['links'] ) ) {
375 $result_array['links'] = $this->formatLinks( $p_result->getLinks() );
376 }
377 if ( isset( $prop['templates'] ) ) {
378 $result_array['templates'] = $this->formatLinks( $p_result->getTemplates() );
379 }
380 if ( isset( $prop['images'] ) ) {
381 $result_array['images'] = array_keys( $p_result->getImages() );
382 }
383 if ( isset( $prop['externallinks'] ) ) {
384 $result_array['externallinks'] = array_keys( $p_result->getExternalLinks() );
385 }
386 if ( isset( $prop['sections'] ) ) {
387 $result_array['sections'] = $p_result->getSections();
388 }
389 if ( isset( $prop['parsewarnings'] ) ) {
390 $result_array['parsewarnings'] = $p_result->getWarnings();
391 }
392
393 if ( isset( $prop['displaytitle'] ) ) {
394 $result_array['displaytitle'] = $p_result->getDisplayTitle() ?:
395 $titleObj->getPrefixedText();
396 }
397
398 if ( isset( $prop['headitems'] ) ) {
399 if ( $skin ) {
400 $result_array['headitems'] = $this->formatHeadItems( $outputPage->getHeadItemsArray() );
401 } else {
402 $result_array['headitems'] = $this->formatHeadItems( $p_result->getHeadItems() );
403 }
404 $this->addDeprecation( 'apiwarn-deprecation-parse-headitems', 'action=parse&prop=headitems' );
405 }
406
407 if ( isset( $prop['headhtml'] ) ) {
408 $result_array['headhtml'] = $outputPage->headElement( $context->getSkin() );
409 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'headhtml';
410 }
411
412 if ( isset( $prop['modules'] ) ) {
413 if ( $skin ) {
414 $result_array['modules'] = $outputPage->getModules();
415 $result_array['modulescripts'] = $outputPage->getModuleScripts();
416 $result_array['modulestyles'] = $outputPage->getModuleStyles();
417 } else {
418 $result_array['modules'] = array_values( array_unique( $p_result->getModules() ) );
419 $result_array['modulescripts'] = array_values( array_unique( $p_result->getModuleScripts() ) );
420 $result_array['modulestyles'] = array_values( array_unique( $p_result->getModuleStyles() ) );
421 }
422 }
423
424 if ( isset( $prop['jsconfigvars'] ) ) {
425 $jsconfigvars = $skin ? $outputPage->getJsConfigVars() : $p_result->getJsConfigVars();
426 $result_array['jsconfigvars'] = ApiResult::addMetadataToResultVars( $jsconfigvars );
427 }
428
429 if ( isset( $prop['encodedjsconfigvars'] ) ) {
430 $jsconfigvars = $skin ? $outputPage->getJsConfigVars() : $p_result->getJsConfigVars();
431 $result_array['encodedjsconfigvars'] = FormatJson::encode(
432 $jsconfigvars,
433 false,
434 FormatJson::ALL_OK
435 );
436 $result_array[ApiResult::META_SUBELEMENTS][] = 'encodedjsconfigvars';
437 }
438
439 if ( isset( $prop['modules'] ) &&
440 !isset( $prop['jsconfigvars'] ) && !isset( $prop['encodedjsconfigvars'] ) ) {
441 $this->addWarning( 'apiwarn-moduleswithoutvars' );
442 }
443
444 if ( isset( $prop['indicators'] ) ) {
445 if ( $skin ) {
446 $result_array['indicators'] = (array)$outputPage->getIndicators();
447 } else {
448 $result_array['indicators'] = (array)$p_result->getIndicators();
449 }
450 ApiResult::setArrayType( $result_array['indicators'], 'BCkvp', 'name' );
451 }
452
453 if ( isset( $prop['iwlinks'] ) ) {
454 $result_array['iwlinks'] = $this->formatIWLinks( $p_result->getInterwikiLinks() );
455 }
456
457 if ( isset( $prop['wikitext'] ) ) {
458 $result_array['wikitext'] = $this->content->serialize( $format );
459 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'wikitext';
460 if ( !is_null( $this->pstContent ) ) {
461 $result_array['psttext'] = $this->pstContent->serialize( $format );
462 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'psttext';
463 }
464 }
465 if ( isset( $prop['properties'] ) ) {
466 $result_array['properties'] = (array)$p_result->getProperties();
467 ApiResult::setArrayType( $result_array['properties'], 'BCkvp', 'name' );
468 }
469
470 if ( isset( $prop['limitreportdata'] ) ) {
471 $result_array['limitreportdata'] =
472 $this->formatLimitReportData( $p_result->getLimitReportData() );
473 }
474 if ( isset( $prop['limitreporthtml'] ) ) {
475 $result_array['limitreporthtml'] = EditPage::getPreviewLimitReport( $p_result );
476 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'limitreporthtml';
477 }
478
479 if ( isset( $prop['parsetree'] ) || $params['generatexml'] ) {
480 if ( $this->content->getModel() != CONTENT_MODEL_WIKITEXT ) {
481 $this->dieWithError( 'apierror-parsetree-notwikitext', 'notwikitext' );
482 }
483
484 $wgParser->startExternalParse( $titleObj, $popts, Parser::OT_PREPROCESS );
485 $dom = $wgParser->preprocessToDom( $this->content->getNativeData() );
486 if ( is_callable( [ $dom, 'saveXML' ] ) ) {
487 $xml = $dom->saveXML();
488 } else {
489 $xml = $dom->__toString();
490 }
491 $result_array['parsetree'] = $xml;
492 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'parsetree';
493 }
494
495 $result_mapping = [
496 'redirects' => 'r',
497 'langlinks' => 'll',
498 'categories' => 'cl',
499 'links' => 'pl',
500 'templates' => 'tl',
501 'images' => 'img',
502 'externallinks' => 'el',
503 'iwlinks' => 'iw',
504 'sections' => 's',
505 'headitems' => 'hi',
506 'modules' => 'm',
507 'indicators' => 'ind',
508 'modulescripts' => 'm',
509 'modulestyles' => 'm',
510 'properties' => 'pp',
511 'limitreportdata' => 'lr',
512 'parsewarnings' => 'pw'
513 ];
514 $this->setIndexedTagNames( $result_array, $result_mapping );
515 $result->addValue( null, $this->getModuleName(), $result_array );
516 }
517
518 /**
519 * Constructs a ParserOptions object
520 *
521 * @param WikiPage $pageObj
522 * @param array $params
523 *
524 * @return array [ ParserOptions, ScopedCallback, bool $suppressCache ]
525 */
526 protected function makeParserOptions( WikiPage $pageObj, array $params ) {
527 $popts = $pageObj->makeParserOptions( $this->getContext() );
528 $popts->enableLimitReport( !$params['disablepp'] && !$params['disablelimitreport'] );
529 $popts->setIsPreview( $params['preview'] || $params['sectionpreview'] );
530 $popts->setIsSectionPreview( $params['sectionpreview'] );
531 $popts->setEditSection( !$params['disableeditsection'] );
532 if ( $params['disabletidy'] ) {
533 $popts->setTidy( false );
534 }
535 $popts->setWrapOutputClass(
536 $params['wrapoutputclass'] === '' ? false : $params['wrapoutputclass']
537 );
538
539 $reset = null;
540 $suppressCache = false;
541 Hooks::run( 'ApiMakeParserOptions',
542 [ $popts, $pageObj->getTitle(), $params, $this, &$reset, &$suppressCache ] );
543
544 return [ $popts, $reset, $suppressCache ];
545 }
546
547 /**
548 * @param WikiPage $page
549 * @param ParserOptions $popts
550 * @param int $pageId
551 * @param bool $getWikitext
552 * @return ParserOutput
553 */
554 private function getParsedContent( WikiPage $page, $popts, $pageId = null, $getWikitext = false ) {
555 $this->content = $this->getContent( $page, $pageId );
556
557 if ( $this->section !== false && $this->content !== null ) {
558 // Not cached (save or load)
559 return $this->content->getParserOutput( $page->getTitle(), null, $popts );
560 }
561
562 // Try the parser cache first
563 // getParserOutput will save to Parser cache if able
564 $pout = $page->getParserOutput( $popts );
565 if ( !$pout ) {
566 $this->dieWithError( [ 'apierror-nosuchrevid', $page->getLatest() ] );
567 }
568 if ( $getWikitext ) {
569 $this->content = $page->getContent( Revision::RAW );
570 }
571
572 return $pout;
573 }
574
575 /**
576 * Get the content for the given page and the requested section.
577 *
578 * @param WikiPage $page
579 * @param int $pageId
580 * @return Content
581 */
582 private function getContent( WikiPage $page, $pageId = null ) {
583 $content = $page->getContent( Revision::RAW ); // XXX: really raw?
584
585 if ( $this->section !== false && $content !== null ) {
586 $content = $this->getSectionContent(
587 $content,
588 !is_null( $pageId )
589 ? $this->msg( 'pageid', $pageId )
590 : $page->getTitle()->getPrefixedText()
591 );
592 }
593 return $content;
594 }
595
596 /**
597 * Extract the requested section from the given Content
598 *
599 * @param Content $content
600 * @param string|Message $what Identifies the content in error messages, e.g. page title.
601 * @return Content|bool
602 */
603 private function getSectionContent( Content $content, $what ) {
604 // Not cached (save or load)
605 $section = $content->getSection( $this->section );
606 if ( $section === false ) {
607 $this->dieWithError( [ 'apierror-nosuchsection-what', $this->section, $what ], 'nosuchsection' );
608 }
609 if ( $section === null ) {
610 $this->dieWithError( [ 'apierror-sectionsnotsupported-what', $what ], 'nosuchsection' );
611 $section = false;
612 }
613
614 return $section;
615 }
616
617 /**
618 * This mimicks the behavior of EditPage in formatting a summary
619 *
620 * @param Title $title of the page being parsed
621 * @param Array $params the API parameters of the request
622 * @return Content|bool
623 */
624 private function formatSummary( $title, $params ) {
625 global $wgParser;
626 $summary = !is_null( $params['summary'] ) ? $params['summary'] : '';
627 $sectionTitle = !is_null( $params['sectiontitle'] ) ? $params['sectiontitle'] : '';
628
629 if ( $this->section === 'new' && ( $sectionTitle === '' || $summary === '' ) ) {
630 if ( $sectionTitle !== '' ) {
631 $summary = $params['sectiontitle'];
632 }
633 if ( $summary !== '' ) {
634 $summary = wfMessage( 'newsectionsummary' )
635 ->rawParams( $wgParser->stripSectionName( $summary ) )
636 ->inContentLanguage()->text();
637 }
638 }
639 return Linker::formatComment( $summary, $title, $this->section === 'new' );
640 }
641
642 private function formatLangLinks( $links ) {
643 $result = [];
644 foreach ( $links as $link ) {
645 $entry = [];
646 $bits = explode( ':', $link, 2 );
647 $title = Title::newFromText( $link );
648
649 $entry['lang'] = $bits[0];
650 if ( $title ) {
651 $entry['url'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
652 // localised language name in 'uselang' language
653 $entry['langname'] = Language::fetchLanguageName(
654 $title->getInterwiki(),
655 $this->getLanguage()->getCode()
656 );
657
658 // native language name
659 $entry['autonym'] = Language::fetchLanguageName( $title->getInterwiki() );
660 }
661 ApiResult::setContentValue( $entry, 'title', $bits[1] );
662 $result[] = $entry;
663 }
664
665 return $result;
666 }
667
668 private function formatCategoryLinks( $links ) {
669 $result = [];
670
671 if ( !$links ) {
672 return $result;
673 }
674
675 // Fetch hiddencat property
676 $lb = new LinkBatch;
677 $lb->setArray( [ NS_CATEGORY => $links ] );
678 $db = $this->getDB();
679 $res = $db->select( [ 'page', 'page_props' ],
680 [ 'page_title', 'pp_propname' ],
681 $lb->constructSet( 'page', $db ),
682 __METHOD__,
683 [],
684 [ 'page_props' => [
685 'LEFT JOIN', [ 'pp_propname' => 'hiddencat', 'pp_page = page_id' ]
686 ] ]
687 );
688 $hiddencats = [];
689 foreach ( $res as $row ) {
690 $hiddencats[$row->page_title] = isset( $row->pp_propname );
691 }
692
693 $linkCache = LinkCache::singleton();
694
695 foreach ( $links as $link => $sortkey ) {
696 $entry = [];
697 $entry['sortkey'] = $sortkey;
698 // array keys will cast numeric category names to ints, so cast back to string
699 ApiResult::setContentValue( $entry, 'category', (string)$link );
700 if ( !isset( $hiddencats[$link] ) ) {
701 $entry['missing'] = true;
702
703 // We already know the link doesn't exist in the database, so
704 // tell LinkCache that before calling $title->isKnown().
705 $title = Title::makeTitle( NS_CATEGORY, $link );
706 $linkCache->addBadLinkObj( $title );
707 if ( $title->isKnown() ) {
708 $entry['known'] = true;
709 }
710 } elseif ( $hiddencats[$link] ) {
711 $entry['hidden'] = true;
712 }
713 $result[] = $entry;
714 }
715
716 return $result;
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 }