MediaWiki  master
ApiComparePages.php
Go to the documentation of this file.
1 <?php
28 
32 class ApiComparePages extends ApiBase {
33 
35  private $revisionStore;
36 
39 
40  private $guessedTitle = false, $props;
41 
42  public function __construct( ApiMain $mainModule, $moduleName, $modulePrefix = '' ) {
43  parent::__construct( $mainModule, $moduleName, $modulePrefix );
44  $this->revisionStore = MediaWikiServices::getInstance()->getRevisionStore();
45  $this->slotRoleRegistry = MediaWikiServices::getInstance()->getSlotRoleRegistry();
46  }
47 
48  public function execute() {
49  $params = $this->extractRequestParams();
50 
51  // Parameter validation
53  $params, 'fromtitle', 'fromid', 'fromrev', 'fromtext', 'fromslots'
54  );
56  $params, 'totitle', 'toid', 'torev', 'totext', 'torelative', 'toslots'
57  );
58 
59  $this->props = array_flip( $params['prop'] );
60 
61  // Cache responses publicly by default. This may be overridden later.
62  $this->getMain()->setCacheMode( 'public' );
63 
64  // Get the 'from' RevisionRecord
65  list( $fromRev, $fromRelRev, $fromValsRev ) = $this->getDiffRevision( 'from', $params );
66 
67  // Get the 'to' RevisionRecord
68  if ( $params['torelative'] !== null ) {
69  if ( !$fromRelRev ) {
70  $this->dieWithError( 'apierror-compare-relative-to-nothing' );
71  }
72  if ( $params['torelative'] !== 'cur' && $fromRelRev instanceof RevisionArchiveRecord ) {
73  // RevisionStore's getPreviousRevision/getNextRevision blow up
74  // when passed an RevisionArchiveRecord for a deleted page
75  $this->dieWithError( [ 'apierror-compare-relative-to-deleted', $params['torelative'] ] );
76  }
77  switch ( $params['torelative'] ) {
78  case 'prev':
79  // Swap 'from' and 'to'
80  list( $toRev, $toRelRev, $toValsRev ) = [ $fromRev, $fromRelRev, $fromValsRev ];
81  $fromRev = $this->revisionStore->getPreviousRevision( $toRelRev );
82  $fromRelRev = $fromRev;
83  $fromValsRev = $fromRev;
84  if ( !$fromRev ) {
85  $title = Title::newFromLinkTarget( $toRelRev->getPageAsLinkTarget() );
86  $this->addWarning( [
87  'apiwarn-compare-no-prev',
88  wfEscapeWikiText( $title->getPrefixedText() ),
89  $toRelRev->getId()
90  ] );
91 
92  // (T203433) Create an empty dummy revision as the "previous".
93  // The main slot has to exist, the rest will be handled by DifferenceEngine.
94  $fromRev = $this->revisionStore->newMutableRevisionFromArray( [
95  'title' => $title ?: Title::makeTitle( NS_SPECIAL, 'Badtitle/' . __METHOD__ )
96  ] );
97  $fromRev->setContent(
98  SlotRecord::MAIN,
99  $toRelRev->getContent( SlotRecord::MAIN, RevisionRecord::RAW )
100  ->getContentHandler()
101  ->makeEmptyContent()
102  );
103  }
104  break;
105 
106  case 'next':
107  $toRev = $this->revisionStore->getNextRevision( $fromRelRev );
108  $toRelRev = $toRev;
109  $toValsRev = $toRev;
110  if ( !$toRev ) {
111  $title = Title::newFromLinkTarget( $fromRelRev->getPageAsLinkTarget() );
112  $this->addWarning( [
113  'apiwarn-compare-no-next',
114  wfEscapeWikiText( $title->getPrefixedText() ),
115  $fromRelRev->getId()
116  ] );
117 
118  // (T203433) The web UI treats "next" as "cur" in this case.
119  // Avoid repeating metadata by making a MutableRevisionRecord with no changes.
120  $toRev = MutableRevisionRecord::newFromParentRevision( $fromRelRev );
121  }
122  break;
123 
124  case 'cur':
125  $title = $fromRelRev->getPageAsLinkTarget();
126  $toRev = $this->revisionStore->getRevisionByTitle( $title );
127  if ( !$toRev ) {
129  $this->dieWithError(
130  [ 'apierror-missingrev-title', wfEscapeWikiText( $title->getPrefixedText() ) ], 'nosuchrevid'
131  );
132  }
133  $toRelRev = $toRev;
134  $toValsRev = $toRev;
135  break;
136  }
137  } else {
138  list( $toRev, $toRelRev, $toValsRev ) = $this->getDiffRevision( 'to', $params );
139  }
140 
141  // Handle missing from or to revisions (should never happen)
142  // @codeCoverageIgnoreStart
143  if ( !$fromRev || !$toRev ) {
144  $this->dieWithError( 'apierror-baddiff' );
145  }
146  // @codeCoverageIgnoreEnd
147 
148  // Handle revdel
149  if ( !$fromRev->audienceCan(
150  RevisionRecord::DELETED_TEXT, RevisionRecord::FOR_THIS_USER, $this->getUser()
151  ) ) {
152  $this->dieWithError( [ 'apierror-missingcontent-revid', $fromRev->getId() ], 'missingcontent' );
153  }
154  if ( !$toRev->audienceCan(
155  RevisionRecord::DELETED_TEXT, RevisionRecord::FOR_THIS_USER, $this->getUser()
156  ) ) {
157  $this->dieWithError( [ 'apierror-missingcontent-revid', $toRev->getId() ], 'missingcontent' );
158  }
159 
160  // Get the diff
161  $context = new DerivativeContext( $this->getContext() );
162  if ( $fromRelRev && $fromRelRev->getPageAsLinkTarget() ) {
163  $context->setTitle( Title::newFromLinkTarget( $fromRelRev->getPageAsLinkTarget() ) );
164  } elseif ( $toRelRev && $toRelRev->getPageAsLinkTarget() ) {
165  $context->setTitle( Title::newFromLinkTarget( $toRelRev->getPageAsLinkTarget() ) );
166  } else {
167  $guessedTitle = $this->guessTitle();
168  if ( $guessedTitle ) {
169  $context->setTitle( $guessedTitle );
170  }
171  }
172  $de = new DifferenceEngine( $context );
173  $de->setRevisions( $fromRev, $toRev );
174  if ( $params['slots'] === null ) {
175  $difftext = $de->getDiffBody();
176  if ( $difftext === false ) {
177  $this->dieWithError( 'apierror-baddiff' );
178  }
179  } else {
180  $difftext = [];
181  foreach ( $params['slots'] as $role ) {
182  $difftext[$role] = $de->getDiffBodyForRole( $role );
183  }
184  }
185 
186  // Fill in the response
187  $vals = [];
188  $this->setVals( $vals, 'from', $fromValsRev );
189  $this->setVals( $vals, 'to', $toValsRev );
190 
191  if ( isset( $this->props['rel'] ) ) {
192  if ( !$fromRev instanceof MutableRevisionRecord ) {
193  $rev = $this->revisionStore->getPreviousRevision( $fromRev );
194  if ( $rev ) {
195  $vals['prev'] = $rev->getId();
196  }
197  }
198  if ( !$toRev instanceof MutableRevisionRecord ) {
199  $rev = $this->revisionStore->getNextRevision( $toRev );
200  if ( $rev ) {
201  $vals['next'] = $rev->getId();
202  }
203  }
204  }
205 
206  if ( isset( $this->props['diffsize'] ) ) {
207  $vals['diffsize'] = 0;
208  foreach ( (array)$difftext as $text ) {
209  $vals['diffsize'] += strlen( $text );
210  }
211  }
212  if ( isset( $this->props['diff'] ) ) {
213  if ( is_array( $difftext ) ) {
214  ApiResult::setArrayType( $difftext, 'kvp', 'diff' );
215  $vals['bodies'] = $difftext;
216  } else {
217  ApiResult::setContentValue( $vals, 'body', $difftext );
218  }
219  }
220 
221  // Diffs can be really big and there's little point in having
222  // ApiResult truncate it to an empty response since the diff is the
223  // whole reason this module exists. So pass NO_SIZE_CHECK here.
224  $this->getResult()->addValue( null, $this->getModuleName(), $vals, ApiResult::NO_SIZE_CHECK );
225  }
226 
235  private function getRevisionById( $id ) {
236  $rev = $this->revisionStore->getRevisionById( $id );
237  if ( !$rev && $this->getPermissionManager()
238  ->userHasAnyRight( $this->getUser(), 'deletedtext', 'undelete' )
239  ) {
240  // Try the 'archive' table
241  $arQuery = $this->revisionStore->getArchiveQueryInfo();
242  $row = $this->getDB()->selectRow(
243  $arQuery['tables'],
244  array_merge(
245  $arQuery['fields'],
246  [ 'ar_namespace', 'ar_title' ]
247  ),
248  [ 'ar_rev_id' => $id ],
249  __METHOD__,
250  [],
251  $arQuery['joins']
252  );
253  if ( $row ) {
254  $rev = $this->revisionStore->newRevisionFromArchiveRow( $row );
255  // @phan-suppress-next-line PhanUndeclaredProperty
256  $rev->isArchive = true;
257  }
258  }
259  return $rev;
260  }
261 
267  private function guessTitle() {
268  if ( $this->guessedTitle !== false ) {
269  return $this->guessedTitle;
270  }
271 
272  $this->guessedTitle = null;
273  $params = $this->extractRequestParams();
274 
275  foreach ( [ 'from', 'to' ] as $prefix ) {
276  if ( $params["{$prefix}rev"] !== null ) {
277  $rev = $this->getRevisionById( $params["{$prefix}rev"] );
278  if ( $rev ) {
279  $this->guessedTitle = Title::newFromLinkTarget( $rev->getPageAsLinkTarget() );
280  break;
281  }
282  }
283 
284  if ( $params["{$prefix}title"] !== null ) {
285  $title = Title::newFromText( $params["{$prefix}title"] );
286  if ( $title && !$title->isExternal() ) {
287  $this->guessedTitle = $title;
288  break;
289  }
290  }
291 
292  if ( $params["{$prefix}id"] !== null ) {
293  $title = Title::newFromID( $params["{$prefix}id"] );
294  if ( $title ) {
295  $this->guessedTitle = $title;
296  break;
297  }
298  }
299  }
300 
301  return $this->guessedTitle;
302  }
303 
309  private function guessModel( $role ) {
310  $params = $this->extractRequestParams();
311 
312  $title = null;
313  foreach ( [ 'from', 'to' ] as $prefix ) {
314  if ( $params["{$prefix}rev"] !== null ) {
315  $rev = $this->getRevisionById( $params["{$prefix}rev"] );
316  if ( $rev && $rev->hasSlot( $role ) ) {
317  return $rev->getSlot( $role, RevisionRecord::RAW )->getModel();
318  }
319  }
320  }
321 
322  $guessedTitle = $this->guessTitle();
323  if ( $guessedTitle ) {
324  return $this->slotRoleRegistry->getRoleHandler( $role )->getDefaultModel( $guessedTitle );
325  }
326 
327  if ( isset( $params["fromcontentmodel-$role"] ) ) {
328  return $params["fromcontentmodel-$role"];
329  }
330  if ( isset( $params["tocontentmodel-$role"] ) ) {
331  return $params["tocontentmodel-$role"];
332  }
333 
334  if ( $role === SlotRecord::MAIN ) {
335  if ( isset( $params['fromcontentmodel'] ) ) {
336  return $params['fromcontentmodel'];
337  }
338  if ( isset( $params['tocontentmodel'] ) ) {
339  return $params['tocontentmodel'];
340  }
341  }
342 
343  return null;
344  }
345 
361  private function getDiffRevision( $prefix, array $params ) {
362  // Back compat params
363  $this->requireMaxOneParameter( $params, "{$prefix}text", "{$prefix}slots" );
364  $this->requireMaxOneParameter( $params, "{$prefix}section", "{$prefix}slots" );
365  if ( $params["{$prefix}text"] !== null ) {
366  $params["{$prefix}slots"] = [ SlotRecord::MAIN ];
367  $params["{$prefix}text-main"] = $params["{$prefix}text"];
368  $params["{$prefix}section-main"] = null;
369  $params["{$prefix}contentmodel-main"] = $params["{$prefix}contentmodel"];
370  $params["{$prefix}contentformat-main"] = $params["{$prefix}contentformat"];
371  }
372 
373  $title = null;
374  $rev = null;
375  $suppliedContent = $params["{$prefix}slots"] !== null;
376 
377  // Get the revision and title, if applicable
378  $revId = null;
379  if ( $params["{$prefix}rev"] !== null ) {
380  $revId = $params["{$prefix}rev"];
381  } elseif ( $params["{$prefix}title"] !== null || $params["{$prefix}id"] !== null ) {
382  if ( $params["{$prefix}title"] !== null ) {
383  $title = Title::newFromText( $params["{$prefix}title"] );
384  if ( !$title || $title->isExternal() ) {
385  $this->dieWithError(
386  [ 'apierror-invalidtitle', wfEscapeWikiText( $params["{$prefix}title"] ) ]
387  );
388  }
389  } else {
390  $title = Title::newFromID( $params["{$prefix}id"] );
391  if ( !$title ) {
392  $this->dieWithError( [ 'apierror-nosuchpageid', $params["{$prefix}id"] ] );
393  }
394  }
395  $revId = $title->getLatestRevID();
396  if ( !$revId ) {
397  $revId = null;
398  // Only die here if we're not using supplied text
399  if ( !$suppliedContent ) {
400  if ( $title->exists() ) {
401  $this->dieWithError(
402  [ 'apierror-missingrev-title', wfEscapeWikiText( $title->getPrefixedText() ) ], 'nosuchrevid'
403  );
404  } else {
405  $this->dieWithError(
406  [ 'apierror-missingtitle-byname', wfEscapeWikiText( $title->getPrefixedText() ) ],
407  'missingtitle'
408  );
409  }
410  }
411  }
412  }
413  if ( $revId !== null ) {
414  $rev = $this->getRevisionById( $revId );
415  if ( !$rev ) {
416  $this->dieWithError( [ 'apierror-nosuchrevid', $revId ] );
417  }
418  $title = Title::newFromLinkTarget( $rev->getPageAsLinkTarget() );
419 
420  // If we don't have supplied content, return here. Otherwise,
421  // continue on below with the supplied content.
422  if ( !$suppliedContent ) {
423  $newRev = $rev;
424 
425  // Deprecated 'fromsection'/'tosection'
426  if ( isset( $params["{$prefix}section"] ) ) {
427  $section = $params["{$prefix}section"];
428  $newRev = MutableRevisionRecord::newFromParentRevision( $rev );
429  $content = $rev->getContent( SlotRecord::MAIN, RevisionRecord::FOR_THIS_USER,
430  $this->getUser() );
431  if ( !$content ) {
432  $this->dieWithError(
433  [ 'apierror-missingcontent-revid-role', $rev->getId(), SlotRecord::MAIN ], 'missingcontent'
434  );
435  }
436  $content = $content ? $content->getSection( $section ) : null;
437  if ( !$content ) {
438  $this->dieWithError(
439  [ "apierror-compare-nosuch{$prefix}section", wfEscapeWikiText( $section ) ],
440  "nosuch{$prefix}section"
441  );
442  }
443  $newRev->setContent( SlotRecord::MAIN, $content );
444  }
445 
446  return [ $newRev, $rev, $rev ];
447  }
448  }
449 
450  // Override $content based on supplied text
451  if ( !$title ) {
452  $title = $this->guessTitle();
453  }
454  if ( $rev ) {
455  $newRev = MutableRevisionRecord::newFromParentRevision( $rev );
456  } else {
457  $newRev = $this->revisionStore->newMutableRevisionFromArray( [
458  'title' => $title ?: Title::makeTitle( NS_SPECIAL, 'Badtitle/' . __METHOD__ )
459  ] );
460  }
461  foreach ( $params["{$prefix}slots"] as $role ) {
462  $text = $params["{$prefix}text-{$role}"];
463  if ( $text === null ) {
464  // The SlotRecord::MAIN role can't be deleted
465  if ( $role === SlotRecord::MAIN ) {
466  $this->dieWithError( [ 'apierror-compare-maintextrequired', $prefix ] );
467  }
468 
469  // These parameters make no sense without text. Reject them to avoid
470  // confusion.
471  foreach ( [ 'section', 'contentmodel', 'contentformat' ] as $param ) {
472  if ( isset( $params["{$prefix}{$param}-{$role}"] ) ) {
473  $this->dieWithError( [
474  'apierror-compare-notext',
475  wfEscapeWikiText( "{$prefix}{$param}-{$role}" ),
476  wfEscapeWikiText( "{$prefix}text-{$role}" ),
477  ] );
478  }
479  }
480 
481  $newRev->removeSlot( $role );
482  continue;
483  }
484 
485  $model = $params["{$prefix}contentmodel-{$role}"];
486  $format = $params["{$prefix}contentformat-{$role}"];
487 
488  if ( !$model && $rev && $rev->hasSlot( $role ) ) {
489  $model = $rev->getSlot( $role, RevisionRecord::RAW )->getModel();
490  }
491  if ( !$model && $title && $role === SlotRecord::MAIN ) {
492  // @todo: Use SlotRoleRegistry and do this for all slots
493  $model = $title->getContentModel();
494  }
495  if ( !$model ) {
496  $model = $this->guessModel( $role );
497  }
498  if ( !$model ) {
499  $model = CONTENT_MODEL_WIKITEXT;
500  $this->addWarning( [ 'apiwarn-compare-nocontentmodel', $model ] );
501  }
502 
503  try {
504  $content = ContentHandler::makeContent( $text, $title, $model, $format );
505  } catch ( MWContentSerializationException $ex ) {
506  $this->dieWithException( $ex, [
507  'wrap' => ApiMessage::create( 'apierror-contentserializationexception', 'parseerror' )
508  ] );
509  }
510 
511  if ( $params["{$prefix}pst"] ) {
512  if ( !$title ) {
513  $this->dieWithError( 'apierror-compare-no-title' );
514  }
515  $popts = ParserOptions::newFromContext( $this->getContext() );
516  $content = $content->preSaveTransform( $title, $this->getUser(), $popts );
517  }
518 
519  $section = $params["{$prefix}section-{$role}"];
520  if ( $section !== null && $section !== '' ) {
521  if ( !$rev ) {
522  $this->dieWithError( "apierror-compare-no{$prefix}revision" );
523  }
524  $oldContent = $rev->getContent( $role, RevisionRecord::FOR_THIS_USER, $this->getUser() );
525  if ( !$oldContent ) {
526  $this->dieWithError(
527  [ 'apierror-missingcontent-revid-role', $rev->getId(), wfEscapeWikiText( $role ) ],
528  'missingcontent'
529  );
530  }
531  if ( !$oldContent->getContentHandler()->supportsSections() ) {
532  $this->dieWithError( [ 'apierror-sectionsnotsupported', $content->getModel() ] );
533  }
534  try {
535  $content = $oldContent->replaceSection( $section, $content, '' );
536  } catch ( Exception $ex ) {
537  // Probably a content model mismatch.
538  $content = null;
539  }
540  if ( !$content ) {
541  $this->dieWithError( [ 'apierror-sectionreplacefailed' ] );
542  }
543  }
544 
545  // Deprecated 'fromsection'/'tosection'
546  if ( $role === SlotRecord::MAIN && isset( $params["{$prefix}section"] ) ) {
547  $section = $params["{$prefix}section"];
548  $content = $content->getSection( $section );
549  if ( !$content ) {
550  $this->dieWithError(
551  [ "apierror-compare-nosuch{$prefix}section", wfEscapeWikiText( $section ) ],
552  "nosuch{$prefix}section"
553  );
554  }
555  }
556 
557  $newRev->setContent( $role, $content );
558  }
559  return [ $newRev, $rev, null ];
560  }
561 
569  private function setVals( &$vals, $prefix, $rev ) {
570  if ( $rev ) {
571  $title = Title::newFromLinkTarget( $rev->getPageAsLinkTarget() );
572  if ( isset( $this->props['ids'] ) ) {
573  $vals["{$prefix}id"] = $title->getArticleID();
574  $vals["{$prefix}revid"] = $rev->getId();
575  }
576  if ( isset( $this->props['title'] ) ) {
577  ApiQueryBase::addTitleInfo( $vals, $title, $prefix );
578  }
579  if ( isset( $this->props['size'] ) ) {
580  $vals["{$prefix}size"] = $rev->getSize();
581  }
582 
583  $anyHidden = false;
584  if ( $rev->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
585  $vals["{$prefix}texthidden"] = true;
586  $anyHidden = true;
587  }
588 
589  if ( $rev->isDeleted( RevisionRecord::DELETED_USER ) ) {
590  $vals["{$prefix}userhidden"] = true;
591  $anyHidden = true;
592  }
593  if ( isset( $this->props['user'] ) ) {
594  $user = $rev->getUser( RevisionRecord::FOR_THIS_USER, $this->getUser() );
595  if ( $user ) {
596  $vals["{$prefix}user"] = $user->getName();
597  $vals["{$prefix}userid"] = $user->getId();
598  }
599  }
600 
601  if ( $rev->isDeleted( RevisionRecord::DELETED_COMMENT ) ) {
602  $vals["{$prefix}commenthidden"] = true;
603  $anyHidden = true;
604  }
605  if ( isset( $this->props['comment'] ) || isset( $this->props['parsedcomment'] ) ) {
606  $comment = $rev->getComment( RevisionRecord::FOR_THIS_USER, $this->getUser() );
607  if ( $comment !== null ) {
608  if ( isset( $this->props['comment'] ) ) {
609  $vals["{$prefix}comment"] = $comment->text;
610  }
611  $vals["{$prefix}parsedcomment"] = Linker::formatComment(
612  $comment->text, $title
613  );
614  }
615  }
616 
617  if ( $anyHidden ) {
618  $this->getMain()->setCacheMode( 'private' );
619  if ( $rev->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ) {
620  $vals["{$prefix}suppressed"] = true;
621  }
622  }
623 
624  // @phan-suppress-next-line PhanUndeclaredProperty
625  if ( !empty( $rev->isArchive ) ) {
626  $this->getMain()->setCacheMode( 'private' );
627  $vals["{$prefix}archive"] = true;
628  }
629  }
630  }
631 
632  public function getAllowedParams() {
633  $slotRoles = $this->slotRoleRegistry->getKnownRoles();
634  sort( $slotRoles, SORT_STRING );
635 
636  // Parameters for the 'from' and 'to' content
637  $fromToParams = [
638  'title' => null,
639  'id' => [
640  ApiBase::PARAM_TYPE => 'integer'
641  ],
642  'rev' => [
643  ApiBase::PARAM_TYPE => 'integer'
644  ],
645 
646  'slots' => [
647  ApiBase::PARAM_TYPE => $slotRoles,
648  ApiBase::PARAM_ISMULTI => true,
649  ],
650  'text-{slot}' => [
651  ApiBase::PARAM_TEMPLATE_VARS => [ 'slot' => 'slots' ], // fixed below
652  ApiBase::PARAM_TYPE => 'text',
653  ],
654  'section-{slot}' => [
655  ApiBase::PARAM_TEMPLATE_VARS => [ 'slot' => 'slots' ], // fixed below
656  ApiBase::PARAM_TYPE => 'string',
657  ],
658  'contentformat-{slot}' => [
659  ApiBase::PARAM_TEMPLATE_VARS => [ 'slot' => 'slots' ], // fixed below
661  ],
662  'contentmodel-{slot}' => [
663  ApiBase::PARAM_TEMPLATE_VARS => [ 'slot' => 'slots' ], // fixed below
665  ],
666  'pst' => false,
667 
668  'text' => [
669  ApiBase::PARAM_TYPE => 'text',
671  ],
672  'contentformat' => [
675  ],
676  'contentmodel' => [
679  ],
680  'section' => [
681  ApiBase::PARAM_DFLT => null,
683  ],
684  ];
685 
686  $ret = [];
687  foreach ( $fromToParams as $k => $v ) {
688  if ( isset( $v[ApiBase::PARAM_TEMPLATE_VARS]['slot'] ) ) {
689  $v[ApiBase::PARAM_TEMPLATE_VARS]['slot'] = 'fromslots';
690  }
691  $ret["from$k"] = $v;
692  }
693  foreach ( $fromToParams as $k => $v ) {
694  if ( isset( $v[ApiBase::PARAM_TEMPLATE_VARS]['slot'] ) ) {
695  $v[ApiBase::PARAM_TEMPLATE_VARS]['slot'] = 'toslots';
696  }
697  $ret["to$k"] = $v;
698  }
699 
700  $ret = wfArrayInsertAfter(
701  $ret,
702  [ 'torelative' => [ ApiBase::PARAM_TYPE => [ 'prev', 'next', 'cur' ], ] ],
703  'torev'
704  );
705 
706  $ret['prop'] = [
707  ApiBase::PARAM_DFLT => 'diff|ids|title',
709  'diff',
710  'diffsize',
711  'rel',
712  'ids',
713  'title',
714  'user',
715  'comment',
716  'parsedcomment',
717  'size',
718  ],
719  ApiBase::PARAM_ISMULTI => true,
721  ];
722 
723  $ret['slots'] = [
724  ApiBase::PARAM_TYPE => $slotRoles,
725  ApiBase::PARAM_ISMULTI => true,
726  ApiBase::PARAM_ALL => true,
727  ];
728 
729  return $ret;
730  }
731 
732  protected function getExamplesMessages() {
733  return [
734  'action=compare&fromrev=1&torev=2'
735  => 'apihelp-compare-example-1',
736  ];
737  }
738 }
requireAtLeastOneParameter( $params,... $required)
Die if none of a certain set of parameters is set and not false.
Definition: ApiBase.php:953
const PARAM_TYPE
(string|string[]) Either an array of allowed value strings, or a string type as described below...
Definition: ApiBase.php:94
DifferenceEngine is responsible for rendering the difference between two revisions as HTML...
static newFromContext(IContextSource $context)
Get a ParserOptions object from a IContextSource object.
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking, formatting, etc.
static addTitleInfo(&$arr, $title, $prefix='')
Add information (title and namespace) about a Title object to a result array.
getResult()
Get the result object.
Definition: ApiBase.php:640
const CONTENT_MODEL_WIKITEXT
Definition: Defines.php:215
static newFromID( $id, $flags=0)
Create a new Title from an article ID.
Definition: Title.php:467
static getAllContentFormats()
const PARAM_DFLT
(null|boolean|integer|string) Default value of the parameter.
Definition: ApiBase.php:55
getMain()
Get the main module.
Definition: ApiBase.php:536
static getContentModels()
getDB()
Gets a default replica DB connection object.
Definition: ApiBase.php:668
An IContextSource implementation which will inherit context from another source but allow individual ...
const NS_SPECIAL
Definition: Defines.php:49
dieWithError( $msg, $code=null, $data=null, $httpCode=null)
Abort execution with an error.
Definition: ApiBase.php:2005
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user...
Definition: ApiBase.php:761
dieWithException( $exception, array $options=[])
Abort execution with an error derived from an exception.
Definition: ApiBase.php:2017
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:164
const PARAM_ALL
(boolean|string) When PARAM_TYPE has a defined set of values and PARAM_ISMULTI is true...
Definition: ApiBase.php:187
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:478
IContextSource $context
setVals(&$vals, $prefix, $rev)
Set value fields from a RevisionRecord object.
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:1165
guessModel( $role)
Guess an appropriate default content model for this request.
MediaWiki Revision SlotRoleRegistry $slotRoleRegistry
guessTitle()
Guess an appropriate default Title for this request.
getRevisionById( $id)
Load a revision by ID.
getContext()
Get the base IContextSource object.
This is the main API class, used for both external and internal processing.
Definition: ApiMain.php:42
const NO_SIZE_CHECK
For addValue() and similar functions, do not check size while adding a value Don&#39;t use this unless yo...
Definition: ApiResult.php:58
getModuleName()
Get the name of the module being executed by this instance.
Definition: ApiBase.php:520
getDiffRevision( $prefix, array $params)
Get the RevisionRecord for one side of the diff.
A RevisionRecord representing a revision of a deleted page persisted in the archive table...
static newFromLinkTarget(LinkTarget $linkTarget, $forceClone='')
Returns a Title given a LinkTarget.
Definition: Title.php:271
const PARAM_TEMPLATE_VARS
(array) Indicate that this is a templated parameter, and specify replacements.
Definition: ApiBase.php:252
wfArrayInsertAfter(array $array, array $insert, $after)
Insert array into another array after the specified KEY
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:586
addWarning( $msg, $code=null, $data=null)
Add a warning for this module.
Definition: ApiBase.php:1924
const PARAM_ISMULTI
(boolean) Accept multiple pipe-separated values for this parameter (e.g.
Definition: ApiBase.php:58
getPermissionManager()
Obtain a PermissionManager instance that subclasses may use in their authorization checks...
Definition: ApiBase.php:710
This abstract class implements many basic API functions, and is the base of all API classes...
Definition: ApiBase.php:42
RevisionStore $revisionStore
const PARAM_DEPRECATED
(boolean) Is the parameter deprecated (will show a warning)?
Definition: ApiBase.php:112
$content
Definition: router.php:78
static setArrayType(array &$arr, $type, $kvpKeyName=null)
Set the array data type.
Definition: ApiResult.php:728
requireMaxOneParameter( $params,... $required)
Die if more than one of a certain set of parameters is set and not false.
Definition: ApiBase.php:928
static makeContent( $text, Title $title=null, $modelId=null, $format=null)
Convenience function for creating a Content object from a given textual representation.
__construct(ApiMain $mainModule, $moduleName, $modulePrefix='')
Exception representing a failure to serialize or unserialize a content object.
static create( $msg, $code=null, array $data=null)
Create an IApiMessage for the message.
Definition: ApiMessage.php:40
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:319