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 
41  private $guessedTitle = false;
42  private $props;
43 
46 
47  public function __construct( ApiMain $mainModule, $moduleName, $modulePrefix = '' ) {
48  parent::__construct( $mainModule, $moduleName, $modulePrefix );
49  $this->revisionStore = MediaWikiServices::getInstance()->getRevisionStore();
50  $this->slotRoleRegistry = MediaWikiServices::getInstance()->getSlotRoleRegistry();
51  $this->contentHandlerFactory = MediaWikiServices::getInstance()->getContentHandlerFactory();
52  }
53 
54  public function execute() {
55  $params = $this->extractRequestParams();
56 
57  // Parameter validation
59  $params, 'fromtitle', 'fromid', 'fromrev', 'fromtext', 'fromslots'
60  );
62  $params, 'totitle', 'toid', 'torev', 'totext', 'torelative', 'toslots'
63  );
64 
65  $this->props = array_fill_keys( $params['prop'], true );
66 
67  // Cache responses publicly by default. This may be overridden later.
68  $this->getMain()->setCacheMode( 'public' );
69 
70  // Get the 'from' RevisionRecord
71  list( $fromRev, $fromRelRev, $fromValsRev ) = $this->getDiffRevision( 'from', $params );
72 
73  // Get the 'to' RevisionRecord
74  if ( $params['torelative'] !== null ) {
75  if ( !$fromRelRev ) {
76  $this->dieWithError( 'apierror-compare-relative-to-nothing' );
77  }
78  if ( $params['torelative'] !== 'cur' && $fromRelRev instanceof RevisionArchiveRecord ) {
79  // RevisionStore's getPreviousRevision/getNextRevision blow up
80  // when passed an RevisionArchiveRecord for a deleted page
81  $this->dieWithError( [ 'apierror-compare-relative-to-deleted', $params['torelative'] ] );
82  }
83  switch ( $params['torelative'] ) {
84  case 'prev':
85  // Swap 'from' and 'to'
86  list( $toRev, $toRelRev, $toValsRev ) = [ $fromRev, $fromRelRev, $fromValsRev ];
87  $fromRev = $this->revisionStore->getPreviousRevision( $toRelRev );
88  $fromRelRev = $fromRev;
89  $fromValsRev = $fromRev;
90  if ( !$fromRev ) {
91  $title = Title::newFromLinkTarget( $toRelRev->getPageAsLinkTarget() );
92  $this->addWarning( [
93  'apiwarn-compare-no-prev',
94  wfEscapeWikiText( $title->getPrefixedText() ),
95  $toRelRev->getId()
96  ] );
97 
98  // (T203433) Create an empty dummy revision as the "previous".
99  // The main slot has to exist, the rest will be handled by DifferenceEngine.
100  $fromRev = new MutableRevisionRecord(
101  $title ?: $toRev->getPage()
102  );
103  $fromRev->setContent(
104  SlotRecord::MAIN,
105  $toRelRev->getContent( SlotRecord::MAIN, RevisionRecord::RAW )
106  ->getContentHandler()
107  ->makeEmptyContent()
108  );
109  }
110  break;
111 
112  case 'next':
113  $toRev = $this->revisionStore->getNextRevision( $fromRelRev );
114  $toRelRev = $toRev;
115  $toValsRev = $toRev;
116  if ( !$toRev ) {
117  $title = Title::newFromLinkTarget( $fromRelRev->getPageAsLinkTarget() );
118  $this->addWarning( [
119  'apiwarn-compare-no-next',
120  wfEscapeWikiText( $title->getPrefixedText() ),
121  $fromRelRev->getId()
122  ] );
123 
124  // (T203433) The web UI treats "next" as "cur" in this case.
125  // Avoid repeating metadata by making a MutableRevisionRecord with no changes.
126  $toRev = MutableRevisionRecord::newFromParentRevision( $fromRelRev );
127  }
128  break;
129 
130  case 'cur':
131  $title = $fromRelRev->getPageAsLinkTarget();
132  $toRev = $this->revisionStore->getRevisionByTitle( $title );
133  if ( !$toRev ) {
135  $this->dieWithError(
136  [ 'apierror-missingrev-title', wfEscapeWikiText( $title->getPrefixedText() ) ],
137  'nosuchrevid'
138  );
139  }
140  $toRelRev = $toRev;
141  $toValsRev = $toRev;
142  break;
143  }
144  } else {
145  list( $toRev, $toRelRev, $toValsRev ) = $this->getDiffRevision( 'to', $params );
146  }
147 
148  // Handle missing from or to revisions (should never happen)
149  // @codeCoverageIgnoreStart
150  if ( !$fromRev || !$toRev ) {
151  $this->dieWithError( 'apierror-baddiff' );
152  }
153  // @codeCoverageIgnoreEnd
154 
155  // Handle revdel
156  if ( !$fromRev->userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ) {
157  $this->dieWithError( [ 'apierror-missingcontent-revid', $fromRev->getId() ], 'missingcontent' );
158  }
159  if ( !$toRev->userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ) {
160  $this->dieWithError( [ 'apierror-missingcontent-revid', $toRev->getId() ], 'missingcontent' );
161  }
162 
163  // Get the diff
164  $context = new DerivativeContext( $this->getContext() );
165  if ( $fromRelRev && $fromRelRev->getPageAsLinkTarget() ) {
166  $context->setTitle( Title::newFromLinkTarget( $fromRelRev->getPageAsLinkTarget() ) );
167  } elseif ( $toRelRev && $toRelRev->getPageAsLinkTarget() ) {
168  $context->setTitle( Title::newFromLinkTarget( $toRelRev->getPageAsLinkTarget() ) );
169  } else {
170  $guessedTitle = $this->guessTitle();
171  if ( $guessedTitle ) {
172  $context->setTitle( $guessedTitle );
173  }
174  }
175  $de = new DifferenceEngine( $context );
176  $de->setRevisions( $fromRev, $toRev );
177  if ( $params['slots'] === null ) {
178  $difftext = $de->getDiffBody();
179  if ( $difftext === false ) {
180  $this->dieWithError( 'apierror-baddiff' );
181  }
182  } else {
183  $difftext = [];
184  foreach ( $params['slots'] as $role ) {
185  $difftext[$role] = $de->getDiffBodyForRole( $role );
186  }
187  }
188 
189  // Fill in the response
190  $vals = [];
191  $this->setVals( $vals, 'from', $fromValsRev );
192  $this->setVals( $vals, 'to', $toValsRev );
193 
194  if ( isset( $this->props['rel'] ) ) {
195  if ( !$fromRev instanceof MutableRevisionRecord ) {
196  $rev = $this->revisionStore->getPreviousRevision( $fromRev );
197  if ( $rev ) {
198  $vals['prev'] = $rev->getId();
199  }
200  }
201  if ( !$toRev instanceof MutableRevisionRecord ) {
202  $rev = $this->revisionStore->getNextRevision( $toRev );
203  if ( $rev ) {
204  $vals['next'] = $rev->getId();
205  }
206  }
207  }
208 
209  if ( isset( $this->props['diffsize'] ) ) {
210  $vals['diffsize'] = 0;
211  foreach ( (array)$difftext as $text ) {
212  $vals['diffsize'] += strlen( $text );
213  }
214  }
215  if ( isset( $this->props['diff'] ) ) {
216  if ( is_array( $difftext ) ) {
217  ApiResult::setArrayType( $difftext, 'kvp', 'diff' );
218  $vals['bodies'] = $difftext;
219  } else {
220  ApiResult::setContentValue( $vals, 'body', $difftext );
221  }
222  }
223 
224  // Diffs can be really big and there's little point in having
225  // ApiResult truncate it to an empty response since the diff is the
226  // whole reason this module exists. So pass NO_SIZE_CHECK here.
227  $this->getResult()->addValue( null, $this->getModuleName(), $vals, ApiResult::NO_SIZE_CHECK );
228  }
229 
238  private function getRevisionById( $id ) {
239  $rev = $this->revisionStore->getRevisionById( $id );
240  if ( !$rev && $this->getAuthority()->isAllowedAny( 'deletedtext', 'undelete' ) ) {
241  // Try the 'archive' table
242  $arQuery = $this->revisionStore->getArchiveQueryInfo();
243  $row = $this->getDB()->selectRow(
244  $arQuery['tables'],
245  array_merge(
246  $arQuery['fields'],
247  [ 'ar_namespace', 'ar_title' ]
248  ),
249  [ 'ar_rev_id' => $id ],
250  __METHOD__,
251  [],
252  $arQuery['joins']
253  );
254  if ( $row ) {
255  $rev = $this->revisionStore->newRevisionFromArchiveRow( $row );
256  // @phan-suppress-next-line PhanUndeclaredProperty
257  $rev->isArchive = true;
258  }
259  }
260  return $rev;
261  }
262 
268  private function guessTitle() {
269  if ( $this->guessedTitle !== false ) {
270  return $this->guessedTitle;
271  }
272 
273  $this->guessedTitle = null;
274  $params = $this->extractRequestParams();
275 
276  foreach ( [ 'from', 'to' ] as $prefix ) {
277  if ( $params["{$prefix}rev"] !== null ) {
278  $rev = $this->getRevisionById( $params["{$prefix}rev"] );
279  if ( $rev ) {
280  $this->guessedTitle = Title::newFromLinkTarget( $rev->getPageAsLinkTarget() );
281  break;
282  }
283  }
284 
285  if ( $params["{$prefix}title"] !== null ) {
286  $title = Title::newFromText( $params["{$prefix}title"] );
287  if ( $title && !$title->isExternal() ) {
288  $this->guessedTitle = $title;
289  break;
290  }
291  }
292 
293  if ( $params["{$prefix}id"] !== null ) {
294  $title = Title::newFromID( $params["{$prefix}id"] );
295  if ( $title ) {
296  $this->guessedTitle = $title;
297  break;
298  }
299  }
300  }
301 
302  return $this->guessedTitle;
303  }
304 
310  private function guessModel( $role ) {
311  $params = $this->extractRequestParams();
312 
313  $title = null;
314  foreach ( [ 'from', 'to' ] as $prefix ) {
315  if ( $params["{$prefix}rev"] !== null ) {
316  $rev = $this->getRevisionById( $params["{$prefix}rev"] );
317  if ( $rev && $rev->hasSlot( $role ) ) {
318  return $rev->getSlot( $role, RevisionRecord::RAW )->getModel();
319  }
320  }
321  }
322 
323  $guessedTitle = $this->guessTitle();
324  if ( $guessedTitle ) {
325  return $this->slotRoleRegistry->getRoleHandler( $role )->getDefaultModel( $guessedTitle );
326  }
327 
328  if ( isset( $params["fromcontentmodel-$role"] ) ) {
329  return $params["fromcontentmodel-$role"];
330  }
331  if ( isset( $params["tocontentmodel-$role"] ) ) {
332  return $params["tocontentmodel-$role"];
333  }
334 
335  if ( $role === SlotRecord::MAIN ) {
336  if ( isset( $params['fromcontentmodel'] ) ) {
337  return $params['fromcontentmodel'];
338  }
339  if ( isset( $params['tocontentmodel'] ) ) {
340  return $params['tocontentmodel'];
341  }
342  }
343 
344  return null;
345  }
346 
362  private function getDiffRevision( $prefix, array $params ) {
363  // Back compat params
364  $this->requireMaxOneParameter( $params, "{$prefix}text", "{$prefix}slots" );
365  $this->requireMaxOneParameter( $params, "{$prefix}section", "{$prefix}slots" );
366  if ( $params["{$prefix}text"] !== null ) {
367  $params["{$prefix}slots"] = [ SlotRecord::MAIN ];
368  $params["{$prefix}text-main"] = $params["{$prefix}text"];
369  $params["{$prefix}section-main"] = null;
370  $params["{$prefix}contentmodel-main"] = $params["{$prefix}contentmodel"];
371  $params["{$prefix}contentformat-main"] = $params["{$prefix}contentformat"];
372  }
373 
374  $title = null;
375  $rev = null;
376  $suppliedContent = $params["{$prefix}slots"] !== null;
377 
378  // Get the revision and title, if applicable
379  $revId = null;
380  if ( $params["{$prefix}rev"] !== null ) {
381  $revId = $params["{$prefix}rev"];
382  } elseif ( $params["{$prefix}title"] !== null || $params["{$prefix}id"] !== null ) {
383  if ( $params["{$prefix}title"] !== null ) {
384  $title = Title::newFromText( $params["{$prefix}title"] );
385  if ( !$title || $title->isExternal() ) {
386  $this->dieWithError(
387  [ 'apierror-invalidtitle', wfEscapeWikiText( $params["{$prefix}title"] ) ]
388  );
389  }
390  } else {
391  $title = Title::newFromID( $params["{$prefix}id"] );
392  if ( !$title ) {
393  $this->dieWithError( [ 'apierror-nosuchpageid', $params["{$prefix}id"] ] );
394  }
395  }
396  $revId = $title->getLatestRevID();
397  if ( !$revId ) {
398  $revId = null;
399  // Only die here if we're not using supplied text
400  if ( !$suppliedContent ) {
401  if ( $title->exists() ) {
402  $this->dieWithError(
403  [ 'apierror-missingrev-title', wfEscapeWikiText( $title->getPrefixedText() ) ],
404  'nosuchrevid'
405  );
406  } else {
407  $this->dieWithError(
408  [ 'apierror-missingtitle-byname', wfEscapeWikiText( $title->getPrefixedText() ) ],
409  'missingtitle'
410  );
411  }
412  }
413  }
414  }
415  if ( $revId !== null ) {
416  $rev = $this->getRevisionById( $revId );
417  if ( !$rev ) {
418  $this->dieWithError( [ 'apierror-nosuchrevid', $revId ] );
419  }
420  $title = Title::newFromLinkTarget( $rev->getPageAsLinkTarget() );
421 
422  // If we don't have supplied content, return here. Otherwise,
423  // continue on below with the supplied content.
424  if ( !$suppliedContent ) {
425  $newRev = $rev;
426 
427  // Deprecated 'fromsection'/'tosection'
428  if ( isset( $params["{$prefix}section"] ) ) {
429  $section = $params["{$prefix}section"];
430  $newRev = MutableRevisionRecord::newFromParentRevision( $rev );
431  $content = $rev->getContent( SlotRecord::MAIN, RevisionRecord::FOR_THIS_USER,
432  $this->getUser() );
433  if ( !$content ) {
434  $this->dieWithError(
435  [ 'apierror-missingcontent-revid-role', $rev->getId(), SlotRecord::MAIN ], 'missingcontent'
436  );
437  }
438  $content = $content ? $content->getSection( $section ) : null;
439  if ( !$content ) {
440  $this->dieWithError(
441  [ "apierror-compare-nosuch{$prefix}section", wfEscapeWikiText( $section ) ],
442  "nosuch{$prefix}section"
443  );
444  }
445  $newRev->setContent( SlotRecord::MAIN, $content );
446  }
447 
448  return [ $newRev, $rev, $rev ];
449  }
450  }
451 
452  // Override $content based on supplied text
453  if ( !$title ) {
454  $title = $this->guessTitle();
455  }
456  if ( $rev ) {
457  $newRev = MutableRevisionRecord::newFromParentRevision( $rev );
458  } else {
459  $newRev = new MutableRevisionRecord( $title ?: Title::newMainPage() );
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  if ( isset( $this->props['timestamp'] ) ) {
583  $revTimestamp = $rev->getTimestamp();
584  if ( $revTimestamp ) {
585  $vals["{$prefix}timestamp"] = wfTimestamp( TS_ISO_8601, $revTimestamp );
586  }
587  }
588 
589  $anyHidden = false;
590  if ( $rev->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
591  $vals["{$prefix}texthidden"] = true;
592  $anyHidden = true;
593  }
594 
595  if ( $rev->isDeleted( RevisionRecord::DELETED_USER ) ) {
596  $vals["{$prefix}userhidden"] = true;
597  $anyHidden = true;
598  }
599  if ( isset( $this->props['user'] ) ) {
600  $user = $rev->getUser( RevisionRecord::FOR_THIS_USER, $this->getUser() );
601  if ( $user ) {
602  $vals["{$prefix}user"] = $user->getName();
603  $vals["{$prefix}userid"] = $user->getId();
604  }
605  }
606 
607  if ( $rev->isDeleted( RevisionRecord::DELETED_COMMENT ) ) {
608  $vals["{$prefix}commenthidden"] = true;
609  $anyHidden = true;
610  }
611  if ( isset( $this->props['comment'] ) || isset( $this->props['parsedcomment'] ) ) {
612  $comment = $rev->getComment( RevisionRecord::FOR_THIS_USER, $this->getUser() );
613  if ( $comment !== null ) {
614  if ( isset( $this->props['comment'] ) ) {
615  $vals["{$prefix}comment"] = $comment->text;
616  }
617  // @phan-suppress-next-line SecurityCheck-DoubleEscaped false positive
618  $vals["{$prefix}parsedcomment"] = Linker::formatComment(
619  $comment->text, $title
620  );
621  }
622  }
623 
624  if ( $anyHidden ) {
625  $this->getMain()->setCacheMode( 'private' );
626  if ( $rev->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ) {
627  $vals["{$prefix}suppressed"] = true;
628  }
629  }
630 
631  // @phan-suppress-next-line PhanUndeclaredProperty
632  if ( !empty( $rev->isArchive ) ) {
633  $this->getMain()->setCacheMode( 'private' );
634  $vals["{$prefix}archive"] = true;
635  }
636  }
637  }
638 
639  public function getAllowedParams() {
640  $slotRoles = $this->slotRoleRegistry->getKnownRoles();
641  sort( $slotRoles, SORT_STRING );
642 
643  // Parameters for the 'from' and 'to' content
644  $fromToParams = [
645  'title' => null,
646  'id' => [
647  ApiBase::PARAM_TYPE => 'integer'
648  ],
649  'rev' => [
650  ApiBase::PARAM_TYPE => 'integer'
651  ],
652 
653  'slots' => [
654  ApiBase::PARAM_TYPE => $slotRoles,
655  ApiBase::PARAM_ISMULTI => true,
656  ],
657  'text-{slot}' => [
658  ApiBase::PARAM_TEMPLATE_VARS => [ 'slot' => 'slots' ], // fixed below
659  ApiBase::PARAM_TYPE => 'text',
660  ],
661  'section-{slot}' => [
662  ApiBase::PARAM_TEMPLATE_VARS => [ 'slot' => 'slots' ], // fixed below
663  ApiBase::PARAM_TYPE => 'string',
664  ],
665  'contentformat-{slot}' => [
666  ApiBase::PARAM_TEMPLATE_VARS => [ 'slot' => 'slots' ], // fixed below
667  ApiBase::PARAM_TYPE => $this->contentHandlerFactory->getAllContentFormats(),
668  ],
669  'contentmodel-{slot}' => [
670  ApiBase::PARAM_TEMPLATE_VARS => [ 'slot' => 'slots' ], // fixed below
671  ApiBase::PARAM_TYPE => $this->contentHandlerFactory->getContentModels(),
672  ],
673  'pst' => false,
674 
675  'text' => [
676  ApiBase::PARAM_TYPE => 'text',
678  ],
679  'contentformat' => [
680  ApiBase::PARAM_TYPE => $this->contentHandlerFactory->getAllContentFormats(),
682  ],
683  'contentmodel' => [
684  ApiBase::PARAM_TYPE => $this->contentHandlerFactory->getContentModels(),
686  ],
687  'section' => [
688  ApiBase::PARAM_DFLT => null,
690  ],
691  ];
692 
693  $ret = [];
694  foreach ( $fromToParams as $k => $v ) {
695  if ( isset( $v[ApiBase::PARAM_TEMPLATE_VARS]['slot'] ) ) {
696  $v[ApiBase::PARAM_TEMPLATE_VARS]['slot'] = 'fromslots';
697  }
698  $ret["from$k"] = $v;
699  }
700  foreach ( $fromToParams as $k => $v ) {
701  if ( isset( $v[ApiBase::PARAM_TEMPLATE_VARS]['slot'] ) ) {
702  $v[ApiBase::PARAM_TEMPLATE_VARS]['slot'] = 'toslots';
703  }
704  $ret["to$k"] = $v;
705  }
706 
707  $ret = wfArrayInsertAfter(
708  $ret,
709  [ 'torelative' => [ ApiBase::PARAM_TYPE => [ 'prev', 'next', 'cur' ], ] ],
710  'torev'
711  );
712 
713  $ret['prop'] = [
714  ApiBase::PARAM_DFLT => 'diff|ids|title',
716  'diff',
717  'diffsize',
718  'rel',
719  'ids',
720  'title',
721  'user',
722  'comment',
723  'parsedcomment',
724  'size',
725  'timestamp',
726  ],
727  ApiBase::PARAM_ISMULTI => true,
729  ];
730 
731  $ret['slots'] = [
732  ApiBase::PARAM_TYPE => $slotRoles,
733  ApiBase::PARAM_ISMULTI => true,
734  ApiBase::PARAM_ALL => true,
735  ];
736 
737  return $ret;
738  }
739 
740  protected function getExamplesMessages() {
741  return [
742  'action=compare&fromrev=1&torev=2'
743  => 'apihelp-compare-example-1',
744  ];
745  }
746 }
ApiMain
This is the main API class, used for both external and internal processing.
Definition: ApiMain.php:48
wfArrayInsertAfter
wfArrayInsertAfter(array $array, array $insert, $after)
Insert array into another array after the specified KEY
Definition: GlobalFunctions.php:189
ContextSource\$context
IContextSource $context
Definition: ContextSource.php:39
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:385
ContextSource\getContext
getContext()
Get the base IContextSource object.
Definition: ContextSource.php:47
MediaWiki\Revision\RevisionRecord
Page revision base class.
Definition: RevisionRecord.php:47
ApiBase\addWarning
addWarning( $msg, $code=null, $data=null)
Add a warning for this module.
Definition: ApiBase.php:1301
ApiComparePages\setVals
setVals(&$vals, $prefix, $rev)
Set value fields from a RevisionRecord object.
Definition: ApiComparePages.php:569
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:180
MediaWiki\Revision\RevisionStore
Service for looking up page revisions.
Definition: RevisionStore.php:88
ApiBase\dieWithError
dieWithError( $msg, $code=null, $data=null, $httpCode=null)
Abort execution with an error.
Definition: ApiBase.php:1382
ApiBase\PARAM_ALL
const PARAM_ALL
Definition: ApiBase.php:81
ApiComparePages\__construct
__construct(ApiMain $mainModule, $moduleName, $modulePrefix='')
Definition: ApiComparePages.php:47
ApiComparePages\$props
$props
Definition: ApiComparePages.php:42
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:1692
ApiBase\PARAM_TYPE
const PARAM_TYPE
Definition: ApiBase.php:72
ApiBase\getResult
getResult()
Get the result object.
Definition: ApiBase.php:571
ApiComparePages\$slotRoleRegistry
MediaWiki Revision SlotRoleRegistry $slotRoleRegistry
Definition: ApiComparePages.php:38
ApiBase\getDB
getDB()
Gets a default replica DB connection object.
Definition: ApiBase.php:594
ApiResult\NO_SIZE_CHECK
const NO_SIZE_CHECK
For addValue() and similar functions, do not check size while adding a value Don't use this unless yo...
Definition: ApiResult.php:58
Title\newMainPage
static newMainPage(MessageLocalizer $localizer=null)
Create a new Title for the Main Page.
Definition: Title.php:712
ContextSource\getUser
getUser()
Definition: ContextSource.php:136
ApiComparePages\$contentHandlerFactory
IContentHandlerFactory $contentHandlerFactory
Definition: ApiComparePages.php:45
ApiBase
This abstract class implements many basic API functions, and is the base of all API classes.
Definition: ApiBase.php:55
ApiComparePages\guessModel
guessModel( $role)
Guess an appropriate default content model for this request.
Definition: ApiComparePages.php:310
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:470
ApiBase\PARAM_DEPRECATED
const PARAM_DEPRECATED
Definition: ApiBase.php:77
DerivativeContext
An IContextSource implementation which will inherit context from another source but allow individual ...
Definition: DerivativeContext.php:32
ApiResult\setArrayType
static setArrayType(array &$arr, $type, $kvpKeyName=null)
Set the array data type.
Definition: ApiResult.php:719
ApiComparePages
Definition: ApiComparePages.php:32
MWContentSerializationException
Exception representing a failure to serialize or unserialize a content object.
Definition: MWContentSerializationException.php:8
ApiBase\extractRequestParams
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition: ApiBase.php:718
$title
$title
Definition: testCompression.php:38
ApiBase\requireAtLeastOneParameter
requireAtLeastOneParameter( $params,... $required)
Die if none of a certain set of parameters is set and not false.
Definition: ApiBase.php:915
ApiMessage\create
static create( $msg, $code=null, array $data=null)
Create an IApiMessage for the message.
Definition: ApiMessage.php:43
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:144
MediaWiki\Revision\RevisionArchiveRecord
A RevisionRecord representing a revision of a deleted page persisted in the archive table.
Definition: RevisionArchiveRecord.php:41
ApiComparePages\guessTitle
guessTitle()
Guess an appropriate default Title for this request.
Definition: ApiComparePages.php:268
$content
$content
Definition: router.php:76
ApiComparePages\getDiffRevision
getDiffRevision( $prefix, array $params)
Get the RevisionRecord for one side of the diff.
Definition: ApiComparePages.php:362
ApiBase\requireMaxOneParameter
requireMaxOneParameter( $params,... $required)
Die if more than one of a certain set of parameters is set and not false.
Definition: ApiBase.php:890
ParserOptions\newFromContext
static newFromContext(IContextSource $context)
Get a ParserOptions object from a IContextSource object.
Definition: ParserOptions.php:1100
MediaWiki\Content\IContentHandlerFactory
Definition: IContentHandlerFactory.php:10
ContextSource\getAuthority
getAuthority()
Definition: ContextSource.php:144
MediaWiki\Revision\MutableRevisionRecord
Definition: MutableRevisionRecord.php:45
wfEscapeWikiText
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
Definition: GlobalFunctions.php:1456
CONTENT_MODEL_WIKITEXT
const CONTENT_MODEL_WIKITEXT
Definition: Defines.php:208
Title\newFromLinkTarget
static newFromLinkTarget(LinkTarget $linkTarget, $forceClone='')
Returns a Title given a LinkTarget.
Definition: Title.php:297
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:1208
Title
Represents a title within MediaWiki.
Definition: Title.php:49
ApiBase\PARAM_TEMPLATE_VARS
const PARAM_TEMPLATE_VARS
(array) Indicate that this is a templated parameter, and specify replacements.
Definition: ApiBase.php:156
ApiComparePages\$guessedTitle
Title false $guessedTitle
Definition: ApiComparePages.php:41
ApiBase\PARAM_DFLT
const PARAM_DFLT
Definition: ApiBase.php:70
ApiComparePages\getAllowedParams
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
Definition: ApiComparePages.php:639
ApiBase\dieWithException
dieWithException(Throwable $exception, array $options=[])
Abort execution with an error derived from a throwable.
Definition: ApiBase.php:1394
DifferenceEngine
DifferenceEngine is responsible for rendering the difference between two revisions as HTML.
Definition: DifferenceEngine.php:58
ApiComparePages\execute
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
Definition: ApiComparePages.php:54
ApiBase\getModuleName
getModuleName()
Get the name of the module being executed by this instance.
Definition: ApiBase.php:440
ApiBase\PARAM_ISMULTI
const PARAM_ISMULTI
Definition: ApiBase.php:71
ApiComparePages\getExamplesMessages
getExamplesMessages()
Returns usage examples for this module.
Definition: ApiComparePages.php:740
ApiBase\getMain
getMain()
Get the main module.
Definition: ApiBase.php:456
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:138
Title\newFromID
static newFromID( $id, $flags=0)
Create a new Title from an article ID.
Definition: Title.php:531
ApiComparePages\getRevisionById
getRevisionById( $id)
Load a revision by ID.
Definition: ApiComparePages.php:238
ApiQueryBase\addTitleInfo
static addTitleInfo(&$arr, $title, $prefix='')
Add information (title and namespace) about a Title object to a result array.
Definition: ApiQueryBase.php:466
MediaWiki\Revision\SlotRecord
Value object representing a content slot associated with a page revision.
Definition: SlotRecord.php:40
ApiComparePages\$revisionStore
RevisionStore $revisionStore
Definition: ApiComparePages.php:35
MediaWiki
The MediaWiki class is the helper class for the index.php entry point.
Definition: MediaWiki.php:36