MediaWiki  master
ApiParse.php
Go to the documentation of this file.
1 <?php
27 
31 class ApiParse extends ApiBase {
32 
34  private $section = null;
35 
37  private $content = null;
38 
40  private $pstContent = null;
41 
43  private $contentIsDeleted = false, $contentIsSuppressed = false;
44 
45  public function execute() {
46  // The data is hot but user-dependent, like page views, so we set vary cookies
47  $this->getMain()->setCacheMode( 'anon-public-user-private' );
48 
49  // Get parameters
50  $params = $this->extractRequestParams();
51 
52  // No easy way to say that text and title or revid are allowed together
53  // while the rest aren't, so just do it in three calls.
54  $this->requireMaxOneParameter( $params, 'page', 'pageid', 'oldid', 'text' );
55  $this->requireMaxOneParameter( $params, 'page', 'pageid', 'oldid', 'title' );
56  $this->requireMaxOneParameter( $params, 'page', 'pageid', 'oldid', 'revid' );
57 
58  $text = $params['text'];
59  $title = $params['title'];
60  if ( $title === null ) {
61  $titleProvided = false;
62  // A title is needed for parsing, so arbitrarily choose one
63  $title = 'API';
64  } else {
65  $titleProvided = true;
66  }
67 
68  $page = $params['page'];
69  $pageid = $params['pageid'];
70  $oldid = $params['oldid'];
71 
72  $model = $params['contentmodel'];
73  $format = $params['contentformat'];
74 
75  $prop = array_flip( $params['prop'] );
76 
77  if ( isset( $params['section'] ) ) {
78  $this->section = $params['section'];
79  if ( !preg_match( '/^((T-)?\d+|new)$/', $this->section ) ) {
80  $this->dieWithError( 'apierror-invalidsection' );
81  }
82  } else {
83  $this->section = false;
84  }
85 
86  // The parser needs $wgTitle to be set, apparently the
87  // $title parameter in Parser::parse isn't enough *sigh*
88  // TODO: Does this still need $wgTitle?
89  global $wgTitle;
90 
91  $redirValues = null;
92 
93  $needContent = isset( $prop['wikitext'] ) ||
94  isset( $prop['parsetree'] ) || $params['generatexml'];
95 
96  // Return result
97  $result = $this->getResult();
98 
99  $revisionLookup = MediaWikiServices::getInstance()->getRevisionLookup();
100  if ( $oldid !== null || $pageid !== null || $page !== null ) {
101  if ( $this->section === 'new' ) {
102  $this->dieWithError( 'apierror-invalidparammix-parse-new-section', 'invalidparammix' );
103  }
104  if ( $oldid !== null ) {
105  // Don't use the parser cache
106  $rev = $revisionLookup->getRevisionById( $oldid );
107  if ( !$rev ) {
108  $this->dieWithError( [ 'apierror-nosuchrevid', $oldid ] );
109  }
110 
111  $revLinkTarget = $rev->getPageAsLinkTarget();
112  $this->checkTitleUserPermissions( $revLinkTarget, 'read' );
113 
114  if ( !$rev->audienceCan(
115  RevisionRecord::DELETED_TEXT,
116  RevisionRecord::FOR_THIS_USER,
117  $this->getUser()
118  ) ) {
119  $this->dieWithError(
120  [ 'apierror-permissiondenied', $this->msg( 'action-deletedtext' ) ]
121  );
122  }
123 
124  $titleObj = Title::newFromLinkTarget( $revLinkTarget );
125  $wgTitle = $titleObj;
126  $pageObj = WikiPage::factory( $titleObj );
127  list( $popts, $reset, $suppressCache ) = $this->makeParserOptions( $pageObj, $params );
128  $p_result = $this->getParsedContent(
129  $pageObj, $popts, $suppressCache, $pageid, $rev, $needContent
130  );
131  } else { // Not $oldid, but $pageid or $page
132  if ( $params['redirects'] ) {
133  $reqParams = [
134  'redirects' => '',
135  ];
136  $pageParams = [];
137  if ( $pageid !== null ) {
138  $reqParams['pageids'] = $pageid;
139  $pageParams['pageid'] = $pageid;
140  } else { // $page
141  $reqParams['titles'] = $page;
142  $pageParams['title'] = $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  foreach ( $pageSet->getRedirectTitles() as $title ) {
151  $pageParams = [ 'title' => $title->getFullText() ];
152  }
153  } elseif ( $pageid !== null ) {
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  $p_result = $this->getParsedContent(
174  $pageObj, $popts, $suppressCache, $pageid, null, $needContent
175  );
176  }
177  } else { // Not $oldid, $pageid, $page. Hence based on $text
178  $titleObj = Title::newFromText( $title );
179  if ( !$titleObj || $titleObj->isExternal() ) {
180  $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $title ) ] );
181  }
182  $revid = $params['revid'];
183  if ( $revid !== null ) {
184  $rev = $revisionLookup->getRevisionById( $revid );
185  if ( !$rev ) {
186  $this->dieWithError( [ 'apierror-nosuchrevid', $revid ] );
187  }
188  $pTitleObj = $titleObj;
189  $titleObj = Title::newFromLinkTarget( $rev->getPageAsLinkTarget() );
190  if ( $titleProvided ) {
191  if ( !$titleObj->equals( $pTitleObj ) ) {
192  $this->addWarning( [ 'apierror-revwrongpage', $rev->getId(),
193  wfEscapeWikiText( $pTitleObj->getPrefixedText() ) ] );
194  }
195  } else {
196  // Consider the title derived from the revid as having
197  // been provided.
198  $titleProvided = true;
199  }
200  }
201  $wgTitle = $titleObj;
202  if ( $titleObj->canExist() ) {
203  $pageObj = WikiPage::factory( $titleObj );
204  } else {
205  // Do like MediaWiki::initializeArticle()
206  $article = Article::newFromTitle( $titleObj, $this->getContext() );
207  $pageObj = $article->getPage();
208  }
209 
210  list( $popts, $reset ) = $this->makeParserOptions( $pageObj, $params );
211  $textProvided = $text !== null;
212 
213  if ( !$textProvided ) {
214  if ( $titleProvided && ( $prop || $params['generatexml'] ) ) {
215  if ( $revid !== null ) {
216  $this->addWarning( 'apiwarn-parse-revidwithouttext' );
217  } else {
218  $this->addWarning( 'apiwarn-parse-titlewithouttext' );
219  }
220  }
221  // Prevent warning from ContentHandler::makeContent()
222  $text = '';
223  }
224 
225  // If we are parsing text, do not use the content model of the default
226  // API title, but default to wikitext to keep BC.
227  if ( $textProvided && !$titleProvided && $model === null ) {
228  $model = CONTENT_MODEL_WIKITEXT;
229  $this->addWarning( [ 'apiwarn-parse-nocontentmodel', $model ] );
230  }
231 
232  try {
233  $this->content = ContentHandler::makeContent( $text, $titleObj, $model, $format );
234  } catch ( MWContentSerializationException $ex ) {
235  $this->dieWithException( $ex, [
236  'wrap' => ApiMessage::create( 'apierror-contentserializationexception', 'parseerror' )
237  ] );
238  }
239 
240  if ( $this->section !== false ) {
241  if ( $this->section === 'new' ) {
242  // Insert the section title above the content.
243  if ( $params['sectiontitle'] !== null && $params['sectiontitle'] !== '' ) {
244  $this->content = $this->content->addSectionHeader( $params['sectiontitle'] );
245  }
246  } else {
247  $this->content = $this->getSectionContent( $this->content, $titleObj->getPrefixedText() );
248  }
249  }
250 
251  if ( $params['pst'] || $params['onlypst'] ) {
252  $this->pstContent = $this->content->preSaveTransform( $titleObj, $this->getUser(), $popts );
253  }
254  if ( $params['onlypst'] ) {
255  // Build a result and bail out
256  $result_array = [];
257  $result_array['text'] = $this->pstContent->serialize( $format );
258  $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'text';
259  if ( isset( $prop['wikitext'] ) ) {
260  $result_array['wikitext'] = $this->content->serialize( $format );
261  $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'wikitext';
262  }
263  if ( $params['summary'] !== null ||
264  ( $params['sectiontitle'] !== null && $this->section === 'new' )
265  ) {
266  $result_array['parsedsummary'] = $this->formatSummary( $titleObj, $params );
267  $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'parsedsummary';
268  }
269 
270  $result->addValue( null, $this->getModuleName(), $result_array );
271 
272  return;
273  }
274 
275  // Not cached (save or load)
276  if ( $params['pst'] ) {
277  $p_result = $this->pstContent->getParserOutput( $titleObj, $revid, $popts );
278  } else {
279  $p_result = $this->content->getParserOutput( $titleObj, $revid, $popts );
280  }
281  }
282 
283  $result_array = [];
284 
285  $result_array['title'] = $titleObj->getPrefixedText();
286  $result_array['pageid'] = $pageid ?: $pageObj->getId();
287  if ( $this->contentIsDeleted ) {
288  $result_array['textdeleted'] = true;
289  }
290  if ( $this->contentIsSuppressed ) {
291  $result_array['textsuppressed'] = true;
292  }
293 
294  if ( isset( $params['useskin'] ) ) {
295  $factory = MediaWikiServices::getInstance()->getSkinFactory();
296  $skin = $factory->makeSkin( Skin::normalizeKey( $params['useskin'] ) );
297  } else {
298  $skin = null;
299  }
300 
301  $outputPage = null;
302  $context = null;
303  if ( $skin || isset( $prop['headhtml'] ) || isset( $prop['categorieshtml'] ) ) {
304  // Enabling the skin via 'useskin', 'headhtml', or 'categorieshtml'
305  // gets OutputPage and Skin involved, which (among others) applies
306  // these hooks:
307  // - ParserOutputHooks
308  // - Hook: LanguageLinks
309  // - Hook: OutputPageParserOutput
310  // - Hook: OutputPageMakeCategoryLinks
311  $context = new DerivativeContext( $this->getContext() );
312  $context->setTitle( $titleObj );
313  $context->setWikiPage( $pageObj );
314 
315  if ( $skin ) {
316  // Use the skin specified by 'useskin'
317  $context->setSkin( $skin );
318  // Context clones the skin, refetch to stay in sync. (T166022)
319  $skin = $context->getSkin();
320  } else {
321  // Make sure the context's skin refers to the context. Without this,
322  // $outputPage->getSkin()->getOutput() !== $outputPage which
323  // confuses some of the output.
324  $context->setSkin( $context->getSkin() );
325  }
326 
327  $outputPage = new OutputPage( $context );
328  $outputPage->addParserOutputMetadata( $p_result );
329  if ( $this->content ) {
330  $outputPage->addContentOverride( $titleObj, $this->content );
331  }
332  $context->setOutput( $outputPage );
333 
334  if ( $skin ) {
335  // Based on OutputPage::headElement()
336  $skin->setupSkinUserCss( $outputPage );
337  // Based on OutputPage::output()
338  $outputPage->loadSkinModules( $skin );
339  }
340 
341  Hooks::run( 'ApiParseMakeOutputPage', [ $this, $outputPage ] );
342  }
343 
344  if ( $oldid !== null ) {
345  $result_array['revid'] = (int)$oldid;
346  }
347 
348  if ( $params['redirects'] && $redirValues !== null ) {
349  $result_array['redirects'] = $redirValues;
350  }
351 
352  if ( isset( $prop['text'] ) ) {
353  $result_array['text'] = $p_result->getText( [
354  'allowTOC' => !$params['disabletoc'],
355  'enableSectionEditLinks' => !$params['disableeditsection'],
356  'wrapperDivClass' => $params['wrapoutputclass'],
357  'deduplicateStyles' => !$params['disablestylededuplication'],
358  'skin' => $context ? $context->getSkin() : null,
359  ] );
360  $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'text';
361  }
362 
363  if ( $params['summary'] !== null ||
364  ( $params['sectiontitle'] !== null && $this->section === 'new' )
365  ) {
366  $result_array['parsedsummary'] = $this->formatSummary( $titleObj, $params );
367  $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'parsedsummary';
368  }
369 
370  if ( isset( $prop['langlinks'] ) ) {
371  if ( $skin ) {
372  $langlinks = $outputPage->getLanguageLinks();
373  } else {
374  $langlinks = $p_result->getLanguageLinks();
375  // The deprecated 'effectivelanglinks' option depredates OutputPage
376  // support via 'useskin'. If not already applied, then run just this
377  // one hook of OutputPage::addParserOutputMetadata here.
378  if ( $params['effectivelanglinks'] ) {
379  $linkFlags = [];
380  Hooks::run( 'LanguageLinks', [ $titleObj, &$langlinks, &$linkFlags ] );
381  }
382  }
383 
384  $result_array['langlinks'] = $this->formatLangLinks( $langlinks );
385  }
386  if ( isset( $prop['categories'] ) ) {
387  $result_array['categories'] = $this->formatCategoryLinks( $p_result->getCategories() );
388  }
389  if ( isset( $prop['categorieshtml'] ) ) {
390  $result_array['categorieshtml'] = $outputPage->getSkin()->getCategories();
391  $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'categorieshtml';
392  }
393  if ( isset( $prop['links'] ) ) {
394  $result_array['links'] = $this->formatLinks( $p_result->getLinks() );
395  }
396  if ( isset( $prop['templates'] ) ) {
397  $result_array['templates'] = $this->formatLinks( $p_result->getTemplates() );
398  }
399  if ( isset( $prop['images'] ) ) {
400  $result_array['images'] = array_keys( $p_result->getImages() );
401  }
402  if ( isset( $prop['externallinks'] ) ) {
403  $result_array['externallinks'] = array_keys( $p_result->getExternalLinks() );
404  }
405  if ( isset( $prop['sections'] ) ) {
406  $result_array['sections'] = $p_result->getSections();
407  }
408  if ( isset( $prop['parsewarnings'] ) ) {
409  $result_array['parsewarnings'] = $p_result->getWarnings();
410  }
411 
412  if ( isset( $prop['displaytitle'] ) ) {
413  $result_array['displaytitle'] = $p_result->getDisplayTitle() !== false
414  ? $p_result->getDisplayTitle() : $titleObj->getPrefixedText();
415  }
416 
417  if ( isset( $prop['headitems'] ) ) {
418  if ( $skin ) {
419  $result_array['headitems'] = $this->formatHeadItems( $outputPage->getHeadItemsArray() );
420  } else {
421  $result_array['headitems'] = $this->formatHeadItems( $p_result->getHeadItems() );
422  }
423  }
424 
425  if ( isset( $prop['headhtml'] ) ) {
426  $result_array['headhtml'] = $outputPage->headElement( $context->getSkin() );
427  $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'headhtml';
428  }
429 
430  if ( isset( $prop['modules'] ) ) {
431  if ( $skin ) {
432  $result_array['modules'] = $outputPage->getModules();
433  // Deprecated since 1.32 (T188689)
434  $result_array['modulescripts'] = [];
435  $result_array['modulestyles'] = $outputPage->getModuleStyles();
436  } else {
437  $result_array['modules'] = array_values( array_unique( $p_result->getModules() ) );
438  // Deprecated since 1.32 (T188689)
439  $result_array['modulescripts'] = [];
440  $result_array['modulestyles'] = array_values( array_unique( $p_result->getModuleStyles() ) );
441  }
442  }
443 
444  if ( isset( $prop['jsconfigvars'] ) ) {
445  $jsconfigvars = $skin ? $outputPage->getJsConfigVars() : $p_result->getJsConfigVars();
446  $result_array['jsconfigvars'] = ApiResult::addMetadataToResultVars( $jsconfigvars );
447  }
448 
449  if ( isset( $prop['encodedjsconfigvars'] ) ) {
450  $jsconfigvars = $skin ? $outputPage->getJsConfigVars() : $p_result->getJsConfigVars();
451  $result_array['encodedjsconfigvars'] = FormatJson::encode(
452  $jsconfigvars,
453  false,
455  );
456  $result_array[ApiResult::META_SUBELEMENTS][] = 'encodedjsconfigvars';
457  }
458 
459  if ( isset( $prop['modules'] ) &&
460  !isset( $prop['jsconfigvars'] ) && !isset( $prop['encodedjsconfigvars'] ) ) {
461  $this->addWarning( 'apiwarn-moduleswithoutvars' );
462  }
463 
464  if ( isset( $prop['indicators'] ) ) {
465  if ( $skin ) {
466  $result_array['indicators'] = (array)$outputPage->getIndicators();
467  } else {
468  $result_array['indicators'] = (array)$p_result->getIndicators();
469  }
470  ApiResult::setArrayType( $result_array['indicators'], 'BCkvp', 'name' );
471  }
472 
473  if ( isset( $prop['iwlinks'] ) ) {
474  $result_array['iwlinks'] = $this->formatIWLinks( $p_result->getInterwikiLinks() );
475  }
476 
477  if ( isset( $prop['wikitext'] ) ) {
478  $result_array['wikitext'] = $this->content->serialize( $format );
479  $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'wikitext';
480  if ( $this->pstContent !== null ) {
481  $result_array['psttext'] = $this->pstContent->serialize( $format );
482  $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'psttext';
483  }
484  }
485  if ( isset( $prop['properties'] ) ) {
486  $result_array['properties'] = (array)$p_result->getProperties();
487  ApiResult::setArrayType( $result_array['properties'], 'BCkvp', 'name' );
488  }
489 
490  if ( isset( $prop['limitreportdata'] ) ) {
491  $result_array['limitreportdata'] =
492  $this->formatLimitReportData( $p_result->getLimitReportData() );
493  }
494  if ( isset( $prop['limitreporthtml'] ) ) {
495  $result_array['limitreporthtml'] = EditPage::getPreviewLimitReport( $p_result );
496  $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'limitreporthtml';
497  }
498 
499  if ( isset( $prop['parsetree'] ) || $params['generatexml'] ) {
500  if ( $this->content->getModel() != CONTENT_MODEL_WIKITEXT ) {
501  $this->dieWithError( 'apierror-parsetree-notwikitext', 'notwikitext' );
502  }
503 
504  $parser = MediaWikiServices::getInstance()->getParser();
505  $parser->startExternalParse( $titleObj, $popts, Parser::OT_PREPROCESS );
506  // @phan-suppress-next-line PhanUndeclaredMethod
507  $xml = $parser->preprocessToDom( $this->content->getText() )->__toString();
508  $result_array['parsetree'] = $xml;
509  $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'parsetree';
510  }
511 
512  $result_mapping = [
513  'redirects' => 'r',
514  'langlinks' => 'll',
515  'categories' => 'cl',
516  'links' => 'pl',
517  'templates' => 'tl',
518  'images' => 'img',
519  'externallinks' => 'el',
520  'iwlinks' => 'iw',
521  'sections' => 's',
522  'headitems' => 'hi',
523  'modules' => 'm',
524  'indicators' => 'ind',
525  'modulescripts' => 'm',
526  'modulestyles' => 'm',
527  'properties' => 'pp',
528  'limitreportdata' => 'lr',
529  'parsewarnings' => 'pw'
530  ];
531  $this->setIndexedTagNames( $result_array, $result_mapping );
532  $result->addValue( null, $this->getModuleName(), $result_array );
533  }
534 
543  protected function makeParserOptions( WikiPage $pageObj, array $params ) {
544  $popts = $pageObj->makeParserOptions( $this->getContext() );
545  $popts->enableLimitReport( !$params['disablepp'] && !$params['disablelimitreport'] );
546  $popts->setIsPreview( $params['preview'] || $params['sectionpreview'] );
547  $popts->setIsSectionPreview( $params['sectionpreview'] );
548  if ( $params['disabletidy'] ) {
549  $popts->setTidy( false );
550  }
551  if ( $params['wrapoutputclass'] !== '' ) {
552  $popts->setWrapOutputClass( $params['wrapoutputclass'] );
553  }
554 
555  $reset = null;
556  $suppressCache = false;
557  Hooks::run( 'ApiMakeParserOptions',
558  [ $popts, $pageObj->getTitle(), $params, $this, &$reset, &$suppressCache ] );
559 
560  // Force cache suppression when $popts aren't cacheable.
561  $suppressCache = $suppressCache || !$popts->isSafeToCache();
562 
563  return [ $popts, $reset, $suppressCache ];
564  }
565 
575  private function getParsedContent(
576  WikiPage $page, $popts, $suppressCache, $pageId, $rev, $getContent
577  ) {
578  $revId = $rev ? $rev->getId() : null;
579  $isDeleted = $rev && $rev->isDeleted( RevisionRecord::DELETED_TEXT );
580 
581  if ( $getContent || $this->section !== false || $isDeleted ) {
582  if ( $rev ) {
583  $this->content = $rev->getContent(
584  SlotRecord::MAIN, RevisionRecord::FOR_THIS_USER, $this->getUser()
585  );
586  if ( !$this->content ) {
587  $this->dieWithError( [ 'apierror-missingcontent-revid', $revId ] );
588  }
589  } else {
590  $this->content = $page->getContent( RevisionRecord::FOR_THIS_USER, $this->getUser() );
591  if ( !$this->content ) {
592  $this->dieWithError( [ 'apierror-missingcontent-pageid', $page->getId() ] );
593  }
594  }
595  $this->contentIsDeleted = $isDeleted;
596  $this->contentIsSuppressed = $rev &&
597  $rev->isDeleted( RevisionRecord::DELETED_TEXT | RevisionRecord::DELETED_RESTRICTED );
598  }
599 
600  if ( $this->section !== false ) {
601  $this->content = $this->getSectionContent(
602  $this->content,
603  $pageId === null ? $page->getTitle()->getPrefixedText() : $this->msg( 'pageid', $pageId )
604  );
605  return $this->content->getParserOutput( $page->getTitle(), $revId, $popts );
606  }
607 
608  if ( $isDeleted ) {
609  // getParserOutput can't do revdeled revisions
610  $pout = $this->content->getParserOutput( $page->getTitle(), $revId, $popts );
611  } else {
612  // getParserOutput will save to Parser cache if able
613  $pout = $page->getParserOutput( $popts, $revId, $suppressCache );
614  }
615  if ( !$pout ) {
616  // @codeCoverageIgnoreStart
617  $this->dieWithError( [ 'apierror-nosuchrevid', $revId ?: $page->getLatest() ] );
618  // @codeCoverageIgnoreEnd
619  }
620 
621  return $pout;
622  }
623 
631  private function getSectionContent( Content $content, $what ) {
632  // Not cached (save or load)
633  $section = $content->getSection( $this->section );
634  if ( $section === false ) {
635  $this->dieWithError( [ 'apierror-nosuchsection-what', $this->section, $what ], 'nosuchsection' );
636  }
637  if ( $section === null ) {
638  $this->dieWithError( [ 'apierror-sectionsnotsupported-what', $what ], 'nosuchsection' );
639  $section = false;
640  }
641 
642  return $section;
643  }
644 
652  private function formatSummary( $title, $params ) {
653  $summary = $params['summary'] ?? '';
654  $sectionTitle = $params['sectiontitle'] ?? '';
655 
656  if ( $this->section === 'new' && ( $sectionTitle === '' || $summary === '' ) ) {
657  if ( $sectionTitle !== '' ) {
658  $summary = $params['sectiontitle'];
659  }
660  if ( $summary !== '' ) {
661  $summary = wfMessage( 'newsectionsummary' )
662  ->rawParams( MediaWikiServices::getInstance()->getParser()
663  ->stripSectionName( $summary ) )
664  ->inContentLanguage()->text();
665  }
666  }
667  return Linker::formatComment( $summary, $title, $this->section === 'new' );
668  }
669 
670  private function formatLangLinks( $links ) {
671  $languageNameUtils = MediaWikiServices::getInstance()->getLanguageNameUtils();
672  $result = [];
673  foreach ( $links as $link ) {
674  $entry = [];
675  $bits = explode( ':', $link, 2 );
676  $title = Title::newFromText( $link );
677 
678  $entry['lang'] = $bits[0];
679  if ( $title ) {
680  $entry['url'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
681  // localised language name in 'uselang' language
682  $entry['langname'] = $languageNameUtils->getLanguageName(
683  $title->getInterwiki(),
684  $this->getLanguage()->getCode()
685  );
686 
687  // native language name
688  $entry['autonym'] = $languageNameUtils->getLanguageName( $title->getInterwiki() );
689  }
690  ApiResult::setContentValue( $entry, 'title', $bits[1] );
691  $result[] = $entry;
692  }
693 
694  return $result;
695  }
696 
697  private function formatCategoryLinks( $links ) {
698  $result = [];
699 
700  if ( !$links ) {
701  return $result;
702  }
703 
704  // Fetch hiddencat property
705  $lb = new LinkBatch;
706  $lb->setArray( [ NS_CATEGORY => $links ] );
707  $db = $this->getDB();
708  $res = $db->select( [ 'page', 'page_props' ],
709  [ 'page_title', 'pp_propname' ],
710  $lb->constructSet( 'page', $db ),
711  __METHOD__,
712  [],
713  [ 'page_props' => [
714  'LEFT JOIN', [ 'pp_propname' => 'hiddencat', 'pp_page = page_id' ]
715  ] ]
716  );
717  $hiddencats = [];
718  foreach ( $res as $row ) {
719  $hiddencats[$row->page_title] = isset( $row->pp_propname );
720  }
721 
722  $linkCache = MediaWikiServices::getInstance()->getLinkCache();
723 
724  foreach ( $links as $link => $sortkey ) {
725  $entry = [];
726  $entry['sortkey'] = $sortkey;
727  // array keys will cast numeric category names to ints, so cast back to string
728  ApiResult::setContentValue( $entry, 'category', (string)$link );
729  if ( !isset( $hiddencats[$link] ) ) {
730  $entry['missing'] = true;
731 
732  // We already know the link doesn't exist in the database, so
733  // tell LinkCache that before calling $title->isKnown().
734  $title = Title::makeTitle( NS_CATEGORY, $link );
735  $linkCache->addBadLinkObj( $title );
736  if ( $title->isKnown() ) {
737  $entry['known'] = true;
738  }
739  } elseif ( $hiddencats[$link] ) {
740  $entry['hidden'] = true;
741  }
742  $result[] = $entry;
743  }
744 
745  return $result;
746  }
747 
748  private function formatLinks( $links ) {
749  $result = [];
750  foreach ( $links as $ns => $nslinks ) {
751  foreach ( $nslinks as $title => $id ) {
752  $entry = [];
753  $entry['ns'] = $ns;
754  ApiResult::setContentValue( $entry, 'title', Title::makeTitle( $ns, $title )->getFullText() );
755  $entry['exists'] = $id != 0;
756  $result[] = $entry;
757  }
758  }
759 
760  return $result;
761  }
762 
763  private function formatIWLinks( $iw ) {
764  $result = [];
765  foreach ( $iw as $prefix => $titles ) {
766  foreach ( array_keys( $titles ) as $title ) {
767  $entry = [];
768  $entry['prefix'] = $prefix;
769 
770  $title = Title::newFromText( "{$prefix}:{$title}" );
771  if ( $title ) {
772  $entry['url'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
773  }
774 
775  ApiResult::setContentValue( $entry, 'title', $title->getFullText() );
776  $result[] = $entry;
777  }
778  }
779 
780  return $result;
781  }
782 
783  private function formatHeadItems( $headItems ) {
784  $result = [];
785  foreach ( $headItems as $tag => $content ) {
786  $entry = [];
787  $entry['tag'] = $tag;
788  ApiResult::setContentValue( $entry, 'content', $content );
789  $result[] = $entry;
790  }
791 
792  return $result;
793  }
794 
795  private function formatLimitReportData( $limitReportData ) {
796  $result = [];
797 
798  foreach ( $limitReportData as $name => $value ) {
799  $entry = [];
800  $entry['name'] = $name;
801  if ( !is_array( $value ) ) {
802  $value = [ $value ];
803  }
804  ApiResult::setIndexedTagNameRecursive( $value, 'param' );
805  $entry = array_merge( $entry, $value );
806  $result[] = $entry;
807  }
808 
809  return $result;
810  }
811 
812  private function setIndexedTagNames( &$array, $mapping ) {
813  foreach ( $mapping as $key => $name ) {
814  if ( isset( $array[$key] ) ) {
815  ApiResult::setIndexedTagName( $array[$key], $name );
816  }
817  }
818  }
819 
820  public function getAllowedParams() {
821  return [
822  'title' => null,
823  'text' => [
824  ApiBase::PARAM_TYPE => 'text',
825  ],
826  'revid' => [
827  ApiBase::PARAM_TYPE => 'integer',
828  ],
829  'summary' => null,
830  'page' => null,
831  'pageid' => [
832  ApiBase::PARAM_TYPE => 'integer',
833  ],
834  'redirects' => false,
835  'oldid' => [
836  ApiBase::PARAM_TYPE => 'integer',
837  ],
838  'prop' => [
839  ApiBase::PARAM_DFLT => 'text|langlinks|categories|links|templates|' .
840  'images|externallinks|sections|revid|displaytitle|iwlinks|' .
841  'properties|parsewarnings',
842  ApiBase::PARAM_ISMULTI => true,
844  'text',
845  'langlinks',
846  'categories',
847  'categorieshtml',
848  'links',
849  'templates',
850  'images',
851  'externallinks',
852  'sections',
853  'revid',
854  'displaytitle',
855  'headhtml',
856  'modules',
857  'jsconfigvars',
858  'encodedjsconfigvars',
859  'indicators',
860  'iwlinks',
861  'wikitext',
862  'properties',
863  'limitreportdata',
864  'limitreporthtml',
865  'parsetree',
866  'parsewarnings',
867  'headitems',
868  ],
870  'parsetree' => [ 'apihelp-parse-paramvalue-prop-parsetree', CONTENT_MODEL_WIKITEXT ],
871  ],
873  'headitems' => 'apiwarn-deprecation-parse-headitems',
874  ],
875  ],
876  'wrapoutputclass' => 'mw-parser-output',
877  'pst' => false,
878  'onlypst' => false,
879  'effectivelanglinks' => [
880  ApiBase::PARAM_DFLT => false,
882  ],
883  'section' => null,
884  'sectiontitle' => [
885  ApiBase::PARAM_TYPE => 'string',
886  ],
887  'disablepp' => [
888  ApiBase::PARAM_DFLT => false,
890  ],
891  'disablelimitreport' => false,
892  'disableeditsection' => false,
893  'disabletidy' => [
894  ApiBase::PARAM_DFLT => false,
895  ApiBase::PARAM_DEPRECATED => true, // Since 1.32
896  ],
897  'disablestylededuplication' => false,
898  'generatexml' => [
899  ApiBase::PARAM_DFLT => false,
901  'apihelp-parse-param-generatexml', CONTENT_MODEL_WIKITEXT
902  ],
904  ],
905  'preview' => false,
906  'sectionpreview' => false,
907  'disabletoc' => false,
908  'useskin' => [
909  ApiBase::PARAM_TYPE => array_keys( Skin::getAllowedSkins() ),
910  ],
911  'contentformat' => [
912  ApiBase::PARAM_TYPE => $this->getContentHandlerFactory()->getAllContentFormats(),
913  ],
914  'contentmodel' => [
915  ApiBase::PARAM_TYPE => $this->getContentHandlerFactory()->getContentModels(),
916  ],
917  ];
918  }
919 
920  protected function getExamplesMessages() {
921  return [
922  'action=parse&page=Project:Sandbox'
923  => 'apihelp-parse-example-page',
924  'action=parse&text={{Project:Sandbox}}&contentmodel=wikitext'
925  => 'apihelp-parse-example-text',
926  'action=parse&text={{PAGENAME}}&title=Test'
927  => 'apihelp-parse-example-texttitle',
928  'action=parse&summary=Some+[[link]]&prop='
929  => 'apihelp-parse-example-summary',
930  ];
931  }
932 
933  public function getHelpUrls() {
934  return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Parsing_wikitext#parse';
935  }
936 
938  return MediaWikiServices::getInstance()->getContentHandlerFactory();
939  }
940 }
ApiMain
This is the main API class, used for both external and internal processing.
Definition: ApiMain.php:44
ContextSource\$context
IContextSource $context
Definition: ContextSource.php:33
ApiParse\formatIWLinks
formatIWLinks( $iw)
Definition: ApiParse.php:763
FauxRequest
WebRequest clone which takes values from a provided array.
Definition: FauxRequest.php:33
Title\newFromText
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:332
ContextSource\getContext
getContext()
Get the base IContextSource object.
Definition: ContextSource.php:40
ApiParse\getParsedContent
getParsedContent(WikiPage $page, $popts, $suppressCache, $pageId, $rev, $getContent)
Definition: ApiParse.php:575
Revision\RevisionRecord
Page revision base class.
Definition: RevisionRecord.php:46
ApiBase\addWarning
addWarning( $msg, $code=null, $data=null)
Add a warning for this module.
Definition: ApiBase.php:1298
IContextSource\getSkin
getSkin()
LinkBatch
Class representing a list of titles The execute() method checks them all for existence and adds them ...
Definition: LinkBatch.php:35
ApiParse\formatLangLinks
formatLangLinks( $links)
Definition: ApiParse.php:670
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:137
ApiBase\dieWithError
dieWithError( $msg, $code=null, $data=null, $httpCode=null)
Abort execution with an error.
Definition: ApiBase.php:1379
ApiBase\PARAM_HELP_MSG
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition: ApiBase.php:96
ApiResult\META_BC_SUBELEMENTS
const META_BC_SUBELEMENTS
Key for the 'BC subelements' metadata item.
Definition: ApiResult.php:143
ApiBase\getTitleOrPageId
getTitleOrPageId( $params, $load=false)
Get a WikiPage object from a title or pageid param, if possible.
Definition: ApiBase.php:928
ApiBase\PARAM_TYPE
const PARAM_TYPE
(boolean) Inverse of IntegerDef::PARAM_IGNORE_RANGE
Definition: ApiBase.php:60
ApiParse\formatLinks
formatLinks( $links)
Definition: ApiParse.php:748
ApiBase\getResult
getResult()
Get the result object.
Definition: ApiBase.php:538
WikiPage
Class representing a MediaWiki article and history.
Definition: WikiPage.php:48
OT_PREPROCESS
const OT_PREPROCESS
Definition: Defines.php:175
WikiPage\makeParserOptions
makeParserOptions( $context)
Get parser options suitable for rendering the primary article wikitext.
Definition: WikiPage.php:1957
wfMessage
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Definition: GlobalFunctions.php:1201
ApiBase\getDB
getDB()
Gets a default replica DB connection object.
Definition: ApiBase.php:566
$wgTitle
if(isset( $_SERVER['PATH_INFO']) && $_SERVER['PATH_INFO'] !='') $wgTitle
Definition: api.php:59
CONTENT_MODEL_WIKITEXT
const CONTENT_MODEL_WIKITEXT
Definition: Defines.php:224
ApiParse\$contentIsDeleted
bool $contentIsDeleted
Definition: ApiParse.php:43
$res
$res
Definition: testCompression.php:57
ContextSource\getUser
getUser()
Definition: ContextSource.php:120
ApiBase\PARAM_DEPRECATED_VALUES
const PARAM_DEPRECATED_VALUES
(boolean) Inverse of IntegerDef::PARAM_IGNORE_RANGE
Definition: ApiBase.php:72
LinkBatch\setArray
setArray( $array)
Set the link list to a given 2-d array First key is the namespace, second is the DB key,...
Definition: LinkBatch.php:147
FormatJson\ALL_OK
const ALL_OK
Skip escaping as many characters as reasonably possible.
Definition: FormatJson.php:55
ApiPageSet
This class contains a list of pages that the client has requested.
Definition: ApiPageSet.php:42
ApiBase
This abstract class implements many basic API functions, and is the base of all API classes.
Definition: ApiBase.php:48
ContextSource\getLanguage
getLanguage()
Definition: ContextSource.php:128
ApiParse\getContentHandlerFactory
getContentHandlerFactory()
Definition: ApiParse.php:937
ApiResult\setContentValue
static setContentValue(array &$arr, $name, $value, $flags=0)
Add an output value to the array by name and mark as META_CONTENT.
Definition: ApiResult.php:466
ApiBase\PARAM_DEPRECATED
const PARAM_DEPRECATED
(boolean) Inverse of IntegerDef::PARAM_IGNORE_RANGE
Definition: ApiBase.php:65
DerivativeContext
An IContextSource implementation which will inherit context from another source but allow individual ...
Definition: DerivativeContext.php:30
ApiParse
Definition: ApiParse.php:31
FormatJson\encode
static encode( $value, $pretty=false, $escaping=0)
Returns the JSON representation of a value.
Definition: FormatJson.php:115
ApiParse\$pstContent
Content $pstContent
Definition: ApiParse.php:40
ApiResult\setArrayType
static setArrayType(array &$arr, $type, $kvpKeyName=null)
Set the array data type.
Definition: ApiResult.php:716
ApiParse\getHelpUrls
getHelpUrls()
Return links to more detailed help pages about the module.
Definition: ApiParse.php:933
WikiPage\factory
static factory(Title $title)
Create a WikiPage object of the appropriate class for the given title.
Definition: WikiPage.php:143
Content\getSection
getSection( $sectionId)
Returns the section with the given ID.
ApiResult\addMetadataToResultVars
static addMetadataToResultVars( $vars, $forceHash=true)
Add the correct metadata to an array of vars we want to export through the API.
Definition: ApiResult.php:1138
Skin\normalizeKey
static normalizeKey( $key)
Normalize a skin preference value to a form that can be loaded.
Definition: Skin.php:94
ApiParse\getAllowedParams
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
Definition: ApiParse.php:820
WikiPage\getParserOutput
getParserOutput(ParserOptions $parserOptions, $oldid=null, $forceParse=false)
Get a ParserOutput for the given ParserOptions and revision ID.
Definition: WikiPage.php:1231
ApiResult\META_SUBELEMENTS
const META_SUBELEMENTS
Key for the 'subelements' metadata item.
Definition: ApiResult.php:78
WikiPage\getId
getId()
Definition: WikiPage.php:569
ApiParse\formatLimitReportData
formatLimitReportData( $limitReportData)
Definition: ApiParse.php:795
EditPage\getPreviewLimitReport
static getPreviewLimitReport(ParserOutput $output=null)
Get the Limit report for page previews.
Definition: EditPage.php:3798
WikiPage\getTitle
getTitle()
Get the title object of the article.
Definition: WikiPage.php:307
MWContentSerializationException
Exception representing a failure to serialize or unserialize a content object.
Definition: MWContentSerializationException.php:7
PROTO_CURRENT
const PROTO_CURRENT
Definition: Defines.php:211
ApiBase\extractRequestParams
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition: ApiBase.php:659
$title
$title
Definition: testCompression.php:38
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:595
NS_CATEGORY
const NS_CATEGORY
Definition: Defines.php:83
WikiPage\getLatest
getLatest()
Get the page_latest field.
Definition: WikiPage.php:683
ApiMessage\create
static create( $msg, $code=null, array $data=null)
Create an IApiMessage for the message.
Definition: ApiMessage.php:40
ApiParse\formatSummary
formatSummary( $title, $params)
This mimicks the behavior of EditPage in formatting a summary.
Definition: ApiParse.php:652
ContentHandler\makeContent
static makeContent( $text, Title $title=null, $modelId=null, $format=null)
Convenience function for creating a Content object from a given textual representation.
Definition: ContentHandler.php:136
ApiParse\formatCategoryLinks
formatCategoryLinks( $links)
Definition: ApiParse.php:697
ContextSource\msg
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
Definition: ContextSource.php:168
ApiResult\setIndexedTagName
static setIndexedTagName(array &$arr, $tag)
Set the tag name for numeric-keyed values in XML format.
Definition: ApiResult.php:604
ApiBase\requireMaxOneParameter
requireMaxOneParameter( $params,... $required)
Die if more than one of a certain set of parameters is set and not false.
Definition: ApiBase.php:831
MediaWiki\Content\IContentHandlerFactory
Definition: IContentHandlerFactory.php:10
ApiParse\setIndexedTagNames
setIndexedTagNames(&$array, $mapping)
Definition: ApiParse.php:812
wfEscapeWikiText
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
Definition: GlobalFunctions.php:1488
Title\newFromLinkTarget
static newFromLinkTarget(LinkTarget $linkTarget, $forceClone='')
Returns a Title given a LinkTarget.
Definition: Title.php:284
Linker\formatComment
static formatComment( $comment, $title=null, $local=false, $wikiId=null)
This function is called by all recent changes variants, by the page history, and by the user contribu...
Definition: Linker.php:1183
Content
Base interface for content objects.
Definition: Content.php:34
ApiParse\$section
string false null $section
Definition: ApiParse.php:34
ApiBase\PARAM_DFLT
const PARAM_DFLT
(boolean) Inverse of IntegerDef::PARAM_IGNORE_RANGE
Definition: ApiBase.php:58
ApiParse\getExamplesMessages
getExamplesMessages()
Returns usage examples for this module.
Definition: ApiParse.php:920
ApiBase\dieWithException
dieWithException(Throwable $exception, array $options=[])
Abort execution with an error derived from a throwable.
Definition: ApiBase.php:1391
ApiBase\getModuleName
getModuleName()
Get the name of the module being executed by this instance.
Definition: ApiBase.php:418
ApiBase\PARAM_ISMULTI
const PARAM_ISMULTI
(boolean) Inverse of IntegerDef::PARAM_IGNORE_RANGE
Definition: ApiBase.php:59
ApiParse\getSectionContent
getSectionContent(Content $content, $what)
Extract the requested section from the given Content.
Definition: ApiParse.php:631
ApiBase\getMain
getMain()
Get the main module.
Definition: ApiBase.php:434
ApiBase\checkTitleUserPermissions
checkTitleUserPermissions(LinkTarget $linkTarget, $actions, array $options=[])
Helper function for permission-denied errors.
Definition: ApiBase.php:1506
Skin\getAllowedSkins
static getAllowedSkins()
Fetch the list of user-selectable skins in regards to $wgSkipSkins.
Definition: Skin.php:69
ApiParse\formatHeadItems
formatHeadItems( $headItems)
Definition: ApiParse.php:783
ApiResult\setIndexedTagNameRecursive
static setIndexedTagNameRecursive(array &$arr, $tag)
Set indexed tag name on $arr and all subarrays.
Definition: ApiResult.php:629
ApiBase\PARAM_HELP_MSG_PER_VALUE
const PARAM_HELP_MSG_PER_VALUE
((string|array|Message)[]) When PARAM_TYPE is an array, this is an array mapping those values to $msg...
Definition: ApiBase.php:129
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
ApiParse\makeParserOptions
makeParserOptions(WikiPage $pageObj, array $params)
Constructs a ParserOptions object.
Definition: ApiParse.php:543
WikiPage\getContent
getContent( $audience=RevisionRecord::FOR_PUBLIC, User $user=null)
Get the content of the current revision.
Definition: WikiPage.php:786
ApiParse\$content
Content $content
Definition: ApiParse.php:37
Revision\SlotRecord
Value object representing a content slot associated with a page revision.
Definition: SlotRecord.php:39
wfExpandUrl
wfExpandUrl( $url, $defaultProto=PROTO_CURRENT)
Expand a potentially local URL to a fully-qualified URL.
Definition: GlobalFunctions.php:491
ApiParse\$contentIsSuppressed
bool $contentIsSuppressed
Definition: ApiParse.php:43
ApiParse\execute
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
Definition: ApiParse.php:45
Article\newFromTitle
static newFromTitle( $title, IContextSource $context)
Create an Article object of the appropriate class for the given page.
Definition: Article.php:181