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_flip( $params['prop'] );
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 = $this->revisionStore->newMutableRevisionFromArray( [
101  'title' => $title ?: Title::makeTitle( NS_SPECIAL, 'Badtitle/' . __METHOD__ )
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->audienceCan(
157  RevisionRecord::DELETED_TEXT, RevisionRecord::FOR_THIS_USER, $this->getUser()
158  ) ) {
159  $this->dieWithError( [ 'apierror-missingcontent-revid', $fromRev->getId() ], 'missingcontent' );
160  }
161  if ( !$toRev->audienceCan(
162  RevisionRecord::DELETED_TEXT, RevisionRecord::FOR_THIS_USER, $this->getUser()
163  ) ) {
164  $this->dieWithError( [ 'apierror-missingcontent-revid', $toRev->getId() ], 'missingcontent' );
165  }
166 
167  // Get the diff
168  $context = new DerivativeContext( $this->getContext() );
169  if ( $fromRelRev && $fromRelRev->getPageAsLinkTarget() ) {
170  $context->setTitle( Title::newFromLinkTarget( $fromRelRev->getPageAsLinkTarget() ) );
171  } elseif ( $toRelRev && $toRelRev->getPageAsLinkTarget() ) {
172  $context->setTitle( Title::newFromLinkTarget( $toRelRev->getPageAsLinkTarget() ) );
173  } else {
174  $guessedTitle = $this->guessTitle();
175  if ( $guessedTitle ) {
176  $context->setTitle( $guessedTitle );
177  }
178  }
179  $de = new DifferenceEngine( $context );
180  $de->setRevisions( $fromRev, $toRev );
181  if ( $params['slots'] === null ) {
182  $difftext = $de->getDiffBody();
183  if ( $difftext === false ) {
184  $this->dieWithError( 'apierror-baddiff' );
185  }
186  } else {
187  $difftext = [];
188  foreach ( $params['slots'] as $role ) {
189  $difftext[$role] = $de->getDiffBodyForRole( $role );
190  }
191  }
192 
193  // Fill in the response
194  $vals = [];
195  $this->setVals( $vals, 'from', $fromValsRev );
196  $this->setVals( $vals, 'to', $toValsRev );
197 
198  if ( isset( $this->props['rel'] ) ) {
199  if ( !$fromRev instanceof MutableRevisionRecord ) {
200  $rev = $this->revisionStore->getPreviousRevision( $fromRev );
201  if ( $rev ) {
202  $vals['prev'] = $rev->getId();
203  }
204  }
205  if ( !$toRev instanceof MutableRevisionRecord ) {
206  $rev = $this->revisionStore->getNextRevision( $toRev );
207  if ( $rev ) {
208  $vals['next'] = $rev->getId();
209  }
210  }
211  }
212 
213  if ( isset( $this->props['diffsize'] ) ) {
214  $vals['diffsize'] = 0;
215  foreach ( (array)$difftext as $text ) {
216  $vals['diffsize'] += strlen( $text );
217  }
218  }
219  if ( isset( $this->props['diff'] ) ) {
220  if ( is_array( $difftext ) ) {
221  ApiResult::setArrayType( $difftext, 'kvp', 'diff' );
222  $vals['bodies'] = $difftext;
223  } else {
224  ApiResult::setContentValue( $vals, 'body', $difftext );
225  }
226  }
227 
228  // Diffs can be really big and there's little point in having
229  // ApiResult truncate it to an empty response since the diff is the
230  // whole reason this module exists. So pass NO_SIZE_CHECK here.
231  $this->getResult()->addValue( null, $this->getModuleName(), $vals, ApiResult::NO_SIZE_CHECK );
232  }
233 
242  private function getRevisionById( $id ) {
243  $rev = $this->revisionStore->getRevisionById( $id );
244  if ( !$rev && $this->getPermissionManager()
245  ->userHasAnyRight( $this->getUser(), 'deletedtext', 'undelete' )
246  ) {
247  // Try the 'archive' table
248  $arQuery = $this->revisionStore->getArchiveQueryInfo();
249  $row = $this->getDB()->selectRow(
250  $arQuery['tables'],
251  array_merge(
252  $arQuery['fields'],
253  [ 'ar_namespace', 'ar_title' ]
254  ),
255  [ 'ar_rev_id' => $id ],
256  __METHOD__,
257  [],
258  $arQuery['joins']
259  );
260  if ( $row ) {
261  $rev = $this->revisionStore->newRevisionFromArchiveRow( $row );
262  // @phan-suppress-next-line PhanUndeclaredProperty
263  $rev->isArchive = true;
264  }
265  }
266  return $rev;
267  }
268 
274  private function guessTitle() {
275  if ( $this->guessedTitle !== false ) {
276  return $this->guessedTitle;
277  }
278 
279  $this->guessedTitle = null;
280  $params = $this->extractRequestParams();
281 
282  foreach ( [ 'from', 'to' ] as $prefix ) {
283  if ( $params["{$prefix}rev"] !== null ) {
284  $rev = $this->getRevisionById( $params["{$prefix}rev"] );
285  if ( $rev ) {
286  $this->guessedTitle = Title::newFromLinkTarget( $rev->getPageAsLinkTarget() );
287  break;
288  }
289  }
290 
291  if ( $params["{$prefix}title"] !== null ) {
292  $title = Title::newFromText( $params["{$prefix}title"] );
293  if ( $title && !$title->isExternal() ) {
294  $this->guessedTitle = $title;
295  break;
296  }
297  }
298 
299  if ( $params["{$prefix}id"] !== null ) {
300  $title = Title::newFromID( $params["{$prefix}id"] );
301  if ( $title ) {
302  $this->guessedTitle = $title;
303  break;
304  }
305  }
306  }
307 
308  return $this->guessedTitle;
309  }
310 
316  private function guessModel( $role ) {
317  $params = $this->extractRequestParams();
318 
319  $title = null;
320  foreach ( [ 'from', 'to' ] as $prefix ) {
321  if ( $params["{$prefix}rev"] !== null ) {
322  $rev = $this->getRevisionById( $params["{$prefix}rev"] );
323  if ( $rev && $rev->hasSlot( $role ) ) {
324  return $rev->getSlot( $role, RevisionRecord::RAW )->getModel();
325  }
326  }
327  }
328 
329  $guessedTitle = $this->guessTitle();
330  if ( $guessedTitle ) {
331  return $this->slotRoleRegistry->getRoleHandler( $role )->getDefaultModel( $guessedTitle );
332  }
333 
334  if ( isset( $params["fromcontentmodel-$role"] ) ) {
335  return $params["fromcontentmodel-$role"];
336  }
337  if ( isset( $params["tocontentmodel-$role"] ) ) {
338  return $params["tocontentmodel-$role"];
339  }
340 
341  if ( $role === SlotRecord::MAIN ) {
342  if ( isset( $params['fromcontentmodel'] ) ) {
343  return $params['fromcontentmodel'];
344  }
345  if ( isset( $params['tocontentmodel'] ) ) {
346  return $params['tocontentmodel'];
347  }
348  }
349 
350  return null;
351  }
352 
368  private function getDiffRevision( $prefix, array $params ) {
369  // Back compat params
370  $this->requireMaxOneParameter( $params, "{$prefix}text", "{$prefix}slots" );
371  $this->requireMaxOneParameter( $params, "{$prefix}section", "{$prefix}slots" );
372  if ( $params["{$prefix}text"] !== null ) {
373  $params["{$prefix}slots"] = [ SlotRecord::MAIN ];
374  $params["{$prefix}text-main"] = $params["{$prefix}text"];
375  $params["{$prefix}section-main"] = null;
376  $params["{$prefix}contentmodel-main"] = $params["{$prefix}contentmodel"];
377  $params["{$prefix}contentformat-main"] = $params["{$prefix}contentformat"];
378  }
379 
380  $title = null;
381  $rev = null;
382  $suppliedContent = $params["{$prefix}slots"] !== null;
383 
384  // Get the revision and title, if applicable
385  $revId = null;
386  if ( $params["{$prefix}rev"] !== null ) {
387  $revId = $params["{$prefix}rev"];
388  } elseif ( $params["{$prefix}title"] !== null || $params["{$prefix}id"] !== null ) {
389  if ( $params["{$prefix}title"] !== null ) {
390  $title = Title::newFromText( $params["{$prefix}title"] );
391  if ( !$title || $title->isExternal() ) {
392  $this->dieWithError(
393  [ 'apierror-invalidtitle', wfEscapeWikiText( $params["{$prefix}title"] ) ]
394  );
395  }
396  } else {
397  $title = Title::newFromID( $params["{$prefix}id"] );
398  if ( !$title ) {
399  $this->dieWithError( [ 'apierror-nosuchpageid', $params["{$prefix}id"] ] );
400  }
401  }
402  $revId = $title->getLatestRevID();
403  if ( !$revId ) {
404  $revId = null;
405  // Only die here if we're not using supplied text
406  if ( !$suppliedContent ) {
407  if ( $title->exists() ) {
408  $this->dieWithError(
409  [ 'apierror-missingrev-title', wfEscapeWikiText( $title->getPrefixedText() ) ],
410  'nosuchrevid'
411  );
412  } else {
413  $this->dieWithError(
414  [ 'apierror-missingtitle-byname', wfEscapeWikiText( $title->getPrefixedText() ) ],
415  'missingtitle'
416  );
417  }
418  }
419  }
420  }
421  if ( $revId !== null ) {
422  $rev = $this->getRevisionById( $revId );
423  if ( !$rev ) {
424  $this->dieWithError( [ 'apierror-nosuchrevid', $revId ] );
425  }
426  $title = Title::newFromLinkTarget( $rev->getPageAsLinkTarget() );
427 
428  // If we don't have supplied content, return here. Otherwise,
429  // continue on below with the supplied content.
430  if ( !$suppliedContent ) {
431  $newRev = $rev;
432 
433  // Deprecated 'fromsection'/'tosection'
434  if ( isset( $params["{$prefix}section"] ) ) {
435  $section = $params["{$prefix}section"];
436  $newRev = MutableRevisionRecord::newFromParentRevision( $rev );
437  $content = $rev->getContent( SlotRecord::MAIN, RevisionRecord::FOR_THIS_USER,
438  $this->getUser() );
439  if ( !$content ) {
440  $this->dieWithError(
441  [ 'apierror-missingcontent-revid-role', $rev->getId(), SlotRecord::MAIN ], 'missingcontent'
442  );
443  }
444  $content = $content ? $content->getSection( $section ) : null;
445  if ( !$content ) {
446  $this->dieWithError(
447  [ "apierror-compare-nosuch{$prefix}section", wfEscapeWikiText( $section ) ],
448  "nosuch{$prefix}section"
449  );
450  }
451  $newRev->setContent( SlotRecord::MAIN, $content );
452  }
453 
454  return [ $newRev, $rev, $rev ];
455  }
456  }
457 
458  // Override $content based on supplied text
459  if ( !$title ) {
460  $title = $this->guessTitle();
461  }
462  if ( $rev ) {
463  $newRev = MutableRevisionRecord::newFromParentRevision( $rev );
464  } else {
465  $newRev = $this->revisionStore->newMutableRevisionFromArray( [
466  'title' => $title ?: Title::makeTitle( NS_SPECIAL, 'Badtitle/' . __METHOD__ )
467  ] );
468  }
469  foreach ( $params["{$prefix}slots"] as $role ) {
470  $text = $params["{$prefix}text-{$role}"];
471  if ( $text === null ) {
472  // The SlotRecord::MAIN role can't be deleted
473  if ( $role === SlotRecord::MAIN ) {
474  $this->dieWithError( [ 'apierror-compare-maintextrequired', $prefix ] );
475  }
476 
477  // These parameters make no sense without text. Reject them to avoid
478  // confusion.
479  foreach ( [ 'section', 'contentmodel', 'contentformat' ] as $param ) {
480  if ( isset( $params["{$prefix}{$param}-{$role}"] ) ) {
481  $this->dieWithError( [
482  'apierror-compare-notext',
483  wfEscapeWikiText( "{$prefix}{$param}-{$role}" ),
484  wfEscapeWikiText( "{$prefix}text-{$role}" ),
485  ] );
486  }
487  }
488 
489  $newRev->removeSlot( $role );
490  continue;
491  }
492 
493  $model = $params["{$prefix}contentmodel-{$role}"];
494  $format = $params["{$prefix}contentformat-{$role}"];
495 
496  if ( !$model && $rev && $rev->hasSlot( $role ) ) {
497  $model = $rev->getSlot( $role, RevisionRecord::RAW )->getModel();
498  }
499  if ( !$model && $title && $role === SlotRecord::MAIN ) {
500  // @todo: Use SlotRoleRegistry and do this for all slots
501  $model = $title->getContentModel();
502  }
503  if ( !$model ) {
504  $model = $this->guessModel( $role );
505  }
506  if ( !$model ) {
507  $model = CONTENT_MODEL_WIKITEXT;
508  $this->addWarning( [ 'apiwarn-compare-nocontentmodel', $model ] );
509  }
510 
511  try {
512  $content = ContentHandler::makeContent( $text, $title, $model, $format );
513  } catch ( MWContentSerializationException $ex ) {
514  $this->dieWithException( $ex, [
515  'wrap' => ApiMessage::create( 'apierror-contentserializationexception', 'parseerror' )
516  ] );
517  }
518 
519  if ( $params["{$prefix}pst"] ) {
520  if ( !$title ) {
521  $this->dieWithError( 'apierror-compare-no-title' );
522  }
523  $popts = ParserOptions::newFromContext( $this->getContext() );
524  $content = $content->preSaveTransform( $title, $this->getUser(), $popts );
525  }
526 
527  $section = $params["{$prefix}section-{$role}"];
528  if ( $section !== null && $section !== '' ) {
529  if ( !$rev ) {
530  $this->dieWithError( "apierror-compare-no{$prefix}revision" );
531  }
532  $oldContent = $rev->getContent( $role, RevisionRecord::FOR_THIS_USER, $this->getUser() );
533  if ( !$oldContent ) {
534  $this->dieWithError(
535  [ 'apierror-missingcontent-revid-role', $rev->getId(), wfEscapeWikiText( $role ) ],
536  'missingcontent'
537  );
538  }
539  if ( !$oldContent->getContentHandler()->supportsSections() ) {
540  $this->dieWithError( [ 'apierror-sectionsnotsupported', $content->getModel() ] );
541  }
542  try {
543  $content = $oldContent->replaceSection( $section, $content, '' );
544  } catch ( Exception $ex ) {
545  // Probably a content model mismatch.
546  $content = null;
547  }
548  if ( !$content ) {
549  $this->dieWithError( [ 'apierror-sectionreplacefailed' ] );
550  }
551  }
552 
553  // Deprecated 'fromsection'/'tosection'
554  if ( $role === SlotRecord::MAIN && isset( $params["{$prefix}section"] ) ) {
555  $section = $params["{$prefix}section"];
556  $content = $content->getSection( $section );
557  if ( !$content ) {
558  $this->dieWithError(
559  [ "apierror-compare-nosuch{$prefix}section", wfEscapeWikiText( $section ) ],
560  "nosuch{$prefix}section"
561  );
562  }
563  }
564 
565  $newRev->setContent( $role, $content );
566  }
567  return [ $newRev, $rev, null ];
568  }
569 
577  private function setVals( &$vals, $prefix, $rev ) {
578  if ( $rev ) {
579  $title = Title::newFromLinkTarget( $rev->getPageAsLinkTarget() );
580  if ( isset( $this->props['ids'] ) ) {
581  $vals["{$prefix}id"] = $title->getArticleID();
582  $vals["{$prefix}revid"] = $rev->getId();
583  }
584  if ( isset( $this->props['title'] ) ) {
585  ApiQueryBase::addTitleInfo( $vals, $title, $prefix );
586  }
587  if ( isset( $this->props['size'] ) ) {
588  $vals["{$prefix}size"] = $rev->getSize();
589  }
590 
591  $anyHidden = false;
592  if ( $rev->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
593  $vals["{$prefix}texthidden"] = true;
594  $anyHidden = true;
595  }
596 
597  if ( $rev->isDeleted( RevisionRecord::DELETED_USER ) ) {
598  $vals["{$prefix}userhidden"] = true;
599  $anyHidden = true;
600  }
601  if ( isset( $this->props['user'] ) ) {
602  $user = $rev->getUser( RevisionRecord::FOR_THIS_USER, $this->getUser() );
603  if ( $user ) {
604  $vals["{$prefix}user"] = $user->getName();
605  $vals["{$prefix}userid"] = $user->getId();
606  }
607  }
608 
609  if ( $rev->isDeleted( RevisionRecord::DELETED_COMMENT ) ) {
610  $vals["{$prefix}commenthidden"] = true;
611  $anyHidden = true;
612  }
613  if ( isset( $this->props['comment'] ) || isset( $this->props['parsedcomment'] ) ) {
614  $comment = $rev->getComment( RevisionRecord::FOR_THIS_USER, $this->getUser() );
615  if ( $comment !== null ) {
616  if ( isset( $this->props['comment'] ) ) {
617  $vals["{$prefix}comment"] = $comment->text;
618  }
619  $vals["{$prefix}parsedcomment"] = Linker::formatComment(
620  $comment->text, $title
621  );
622  }
623  }
624 
625  if ( $anyHidden ) {
626  $this->getMain()->setCacheMode( 'private' );
627  if ( $rev->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ) {
628  $vals["{$prefix}suppressed"] = true;
629  }
630  }
631 
632  // @phan-suppress-next-line PhanUndeclaredProperty
633  if ( !empty( $rev->isArchive ) ) {
634  $this->getMain()->setCacheMode( 'private' );
635  $vals["{$prefix}archive"] = true;
636  }
637  }
638  }
639 
640  public function getAllowedParams() {
641  $slotRoles = $this->slotRoleRegistry->getKnownRoles();
642  sort( $slotRoles, SORT_STRING );
643 
644  // Parameters for the 'from' and 'to' content
645  $fromToParams = [
646  'title' => null,
647  'id' => [
648  ApiBase::PARAM_TYPE => 'integer'
649  ],
650  'rev' => [
651  ApiBase::PARAM_TYPE => 'integer'
652  ],
653 
654  'slots' => [
655  ApiBase::PARAM_TYPE => $slotRoles,
656  ApiBase::PARAM_ISMULTI => true,
657  ],
658  'text-{slot}' => [
659  ApiBase::PARAM_TEMPLATE_VARS => [ 'slot' => 'slots' ], // fixed below
660  ApiBase::PARAM_TYPE => 'text',
661  ],
662  'section-{slot}' => [
663  ApiBase::PARAM_TEMPLATE_VARS => [ 'slot' => 'slots' ], // fixed below
664  ApiBase::PARAM_TYPE => 'string',
665  ],
666  'contentformat-{slot}' => [
667  ApiBase::PARAM_TEMPLATE_VARS => [ 'slot' => 'slots' ], // fixed below
668  ApiBase::PARAM_TYPE => $this->getContentHandlerFactory()->getAllContentFormats(),
669  ],
670  'contentmodel-{slot}' => [
671  ApiBase::PARAM_TEMPLATE_VARS => [ 'slot' => 'slots' ], // fixed below
672  ApiBase::PARAM_TYPE => $this->getContentHandlerFactory()->getContentModels(),
673  ],
674  'pst' => false,
675 
676  'text' => [
677  ApiBase::PARAM_TYPE => 'text',
679  ],
680  'contentformat' => [
681  ApiBase::PARAM_TYPE => $this->getContentHandlerFactory()->getAllContentFormats(),
683  ],
684  'contentmodel' => [
685  ApiBase::PARAM_TYPE => $this->getContentHandlerFactory()->getContentModels(),
687  ],
688  'section' => [
689  ApiBase::PARAM_DFLT => null,
691  ],
692  ];
693 
694  $ret = [];
695  foreach ( $fromToParams as $k => $v ) {
696  if ( isset( $v[ApiBase::PARAM_TEMPLATE_VARS]['slot'] ) ) {
697  $v[ApiBase::PARAM_TEMPLATE_VARS]['slot'] = 'fromslots';
698  }
699  $ret["from$k"] = $v;
700  }
701  foreach ( $fromToParams as $k => $v ) {
702  if ( isset( $v[ApiBase::PARAM_TEMPLATE_VARS]['slot'] ) ) {
703  $v[ApiBase::PARAM_TEMPLATE_VARS]['slot'] = 'toslots';
704  }
705  $ret["to$k"] = $v;
706  }
707 
708  $ret = wfArrayInsertAfter(
709  $ret,
710  [ 'torelative' => [ ApiBase::PARAM_TYPE => [ 'prev', 'next', 'cur' ], ] ],
711  'torev'
712  );
713 
714  $ret['prop'] = [
715  ApiBase::PARAM_DFLT => 'diff|ids|title',
717  'diff',
718  'diffsize',
719  'rel',
720  'ids',
721  'title',
722  'user',
723  'comment',
724  'parsedcomment',
725  'size',
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 
749  }
750 }
ApiMain
This is the main API class, used for both external and internal processing.
Definition: ApiMain.php:44
wfArrayInsertAfter
wfArrayInsertAfter(array $array, array $insert, $after)
Insert array into another array after the specified KEY
Definition: GlobalFunctions.php:207
ContextSource\$context
IContextSource $context
Definition: ContextSource.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
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
ApiComparePages\setVals
setVals(&$vals, $prefix, $rev)
Set value fields from a RevisionRecord object.
Definition: ApiComparePages.php:577
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:137
Revision\RevisionStore
Service for looking up page revisions.
Definition: RevisionStore.php:77
ApiBase\dieWithError
dieWithError( $msg, $code=null, $data=null, $httpCode=null)
Abort execution with an error.
Definition: ApiBase.php:1379
ApiBase\PARAM_ALL
const PARAM_ALL
(boolean) Inverse of IntegerDef::PARAM_IGNORE_RANGE
Definition: ApiBase.php:69
ApiComparePages\__construct
__construct(ApiMain $mainModule, $moduleName, $modulePrefix='')
Definition: ApiComparePages.php:47
ApiComparePages\$props
$props
Definition: ApiComparePages.php:42
ApiBase\PARAM_TYPE
const PARAM_TYPE
(boolean) Inverse of IntegerDef::PARAM_IGNORE_RANGE
Definition: ApiBase.php:60
ApiBase\getResult
getResult()
Get the result object.
Definition: ApiBase.php:538
ApiComparePages\$slotRoleRegistry
MediaWiki Revision SlotRoleRegistry $slotRoleRegistry
Definition: ApiComparePages.php:38
ApiBase\getDB
getDB()
Gets a default replica DB connection object.
Definition: ApiBase.php:566
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
CONTENT_MODEL_WIKITEXT
const CONTENT_MODEL_WIKITEXT
Definition: Defines.php:224
ContextSource\getUser
getUser()
Definition: ContextSource.php:120
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:48
ApiComparePages\guessModel
guessModel( $role)
Guess an appropriate default content model for this request.
Definition: ApiComparePages.php:316
Revision
Definition: Revision.php:40
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
NS_SPECIAL
const NS_SPECIAL
Definition: Defines.php:58
DerivativeContext
An IContextSource implementation which will inherit context from another source but allow individual ...
Definition: DerivativeContext.php:30
ApiResult\setArrayType
static setArrayType(array &$arr, $type, $kvpKeyName=null)
Set the array data type.
Definition: ApiResult.php:716
ApiComparePages
Definition: ApiComparePages.php:32
MWContentSerializationException
Exception representing a failure to serialize or unserialize a content object.
Definition: MWContentSerializationException.php:7
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
ApiBase\requireAtLeastOneParameter
requireAtLeastOneParameter( $params,... $required)
Die if none of a certain set of parameters is set and not false.
Definition: ApiBase.php:856
ApiMessage\create
static create( $msg, $code=null, array $data=null)
Create an IApiMessage for the message.
Definition: ApiMessage.php:40
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
Revision\RevisionArchiveRecord
A RevisionRecord representing a revision of a deleted page persisted in the archive table.
Definition: RevisionArchiveRecord.php:40
ApiComparePages\guessTitle
guessTitle()
Guess an appropriate default Title for this request.
Definition: ApiComparePages.php:274
$content
$content
Definition: router.php:78
ApiComparePages\getDiffRevision
getDiffRevision( $prefix, array $params)
Get the RevisionRecord for one side of the diff.
Definition: ApiComparePages.php:368
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
ParserOptions\newFromContext
static newFromContext(IContextSource $context)
Get a ParserOptions object from a IContextSource object.
Definition: ParserOptions.php:1035
MediaWiki\Content\IContentHandlerFactory
Definition: IContentHandlerFactory.php:10
Revision\MutableRevisionRecord
Definition: MutableRevisionRecord.php:42
ApiBase\getPermissionManager
getPermissionManager()
Obtain a PermissionManager instance that subclasses may use in their authorization checks.
Definition: ApiBase.php:608
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
Title
Represents a title within MediaWiki.
Definition: Title.php:42
ApiBase\PARAM_TEMPLATE_VARS
const PARAM_TEMPLATE_VARS
(array) Indicate that this is a templated parameter, and specify replacements.
Definition: ApiBase.php:147
ApiComparePages\$guessedTitle
Title false $guessedTitle
Definition: ApiComparePages.php:41
ApiBase\PARAM_DFLT
const PARAM_DFLT
(boolean) Inverse of IntegerDef::PARAM_IGNORE_RANGE
Definition: ApiBase.php:58
ApiComparePages\getAllowedParams
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
Definition: ApiComparePages.php:640
ApiBase\dieWithException
dieWithException(Throwable $exception, array $options=[])
Abort execution with an error derived from a throwable.
Definition: ApiBase.php:1391
DifferenceEngine
DifferenceEngine is responsible for rendering the difference between two revisions as HTML.
Definition: DifferenceEngine.php:54
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:418
ApiBase\PARAM_ISMULTI
const PARAM_ISMULTI
(boolean) Inverse of IntegerDef::PARAM_IGNORE_RANGE
Definition: ApiBase.php:59
ApiComparePages\getExamplesMessages
getExamplesMessages()
Returns usage examples for this module.
Definition: ApiComparePages.php:740
ApiBase\getMain
getMain()
Get the main module.
Definition: ApiBase.php:434
ApiComparePages\getContentHandlerFactory
getContentHandlerFactory()
Definition: ApiComparePages.php:747
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
Title\newFromID
static newFromID( $id, $flags=0)
Create a new Title from an article ID.
Definition: Title.php:476
ApiComparePages\getRevisionById
getRevisionById( $id)
Load a revision by ID.
Definition: ApiComparePages.php:242
ApiQueryBase\addTitleInfo
static addTitleInfo(&$arr, $title, $prefix='')
Add information (title and namespace) about a Title object to a result array.
Definition: ApiQueryBase.php:462
Revision\SlotRecord
Value object representing a content slot associated with a page revision.
Definition: SlotRecord.php:39
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:35