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  if ( isset( $this->props['timestamp'] ) ) {
591  $revTimestamp = $rev->getTimestamp();
592  if ( $revTimestamp ) {
593  $vals["{$prefix}timestamp"] = wfTimestamp( TS_ISO_8601, $revTimestamp );
594  }
595  }
596 
597  $anyHidden = false;
598  if ( $rev->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
599  $vals["{$prefix}texthidden"] = true;
600  $anyHidden = true;
601  }
602 
603  if ( $rev->isDeleted( RevisionRecord::DELETED_USER ) ) {
604  $vals["{$prefix}userhidden"] = true;
605  $anyHidden = true;
606  }
607  if ( isset( $this->props['user'] ) ) {
608  $user = $rev->getUser( RevisionRecord::FOR_THIS_USER, $this->getUser() );
609  if ( $user ) {
610  $vals["{$prefix}user"] = $user->getName();
611  $vals["{$prefix}userid"] = $user->getId();
612  }
613  }
614 
615  if ( $rev->isDeleted( RevisionRecord::DELETED_COMMENT ) ) {
616  $vals["{$prefix}commenthidden"] = true;
617  $anyHidden = true;
618  }
619  if ( isset( $this->props['comment'] ) || isset( $this->props['parsedcomment'] ) ) {
620  $comment = $rev->getComment( RevisionRecord::FOR_THIS_USER, $this->getUser() );
621  if ( $comment !== null ) {
622  if ( isset( $this->props['comment'] ) ) {
623  $vals["{$prefix}comment"] = $comment->text;
624  }
625  $vals["{$prefix}parsedcomment"] = Linker::formatComment(
626  $comment->text, $title
627  );
628  }
629  }
630 
631  if ( $anyHidden ) {
632  $this->getMain()->setCacheMode( 'private' );
633  if ( $rev->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ) {
634  $vals["{$prefix}suppressed"] = true;
635  }
636  }
637 
638  // @phan-suppress-next-line PhanUndeclaredProperty
639  if ( !empty( $rev->isArchive ) ) {
640  $this->getMain()->setCacheMode( 'private' );
641  $vals["{$prefix}archive"] = true;
642  }
643  }
644  }
645 
646  public function getAllowedParams() {
647  $slotRoles = $this->slotRoleRegistry->getKnownRoles();
648  sort( $slotRoles, SORT_STRING );
649 
650  // Parameters for the 'from' and 'to' content
651  $fromToParams = [
652  'title' => null,
653  'id' => [
654  ApiBase::PARAM_TYPE => 'integer'
655  ],
656  'rev' => [
657  ApiBase::PARAM_TYPE => 'integer'
658  ],
659 
660  'slots' => [
661  ApiBase::PARAM_TYPE => $slotRoles,
662  ApiBase::PARAM_ISMULTI => true,
663  ],
664  'text-{slot}' => [
665  ApiBase::PARAM_TEMPLATE_VARS => [ 'slot' => 'slots' ], // fixed below
666  ApiBase::PARAM_TYPE => 'text',
667  ],
668  'section-{slot}' => [
669  ApiBase::PARAM_TEMPLATE_VARS => [ 'slot' => 'slots' ], // fixed below
670  ApiBase::PARAM_TYPE => 'string',
671  ],
672  'contentformat-{slot}' => [
673  ApiBase::PARAM_TEMPLATE_VARS => [ 'slot' => 'slots' ], // fixed below
674  ApiBase::PARAM_TYPE => $this->getContentHandlerFactory()->getAllContentFormats(),
675  ],
676  'contentmodel-{slot}' => [
677  ApiBase::PARAM_TEMPLATE_VARS => [ 'slot' => 'slots' ], // fixed below
678  ApiBase::PARAM_TYPE => $this->getContentHandlerFactory()->getContentModels(),
679  ],
680  'pst' => false,
681 
682  'text' => [
683  ApiBase::PARAM_TYPE => 'text',
685  ],
686  'contentformat' => [
687  ApiBase::PARAM_TYPE => $this->getContentHandlerFactory()->getAllContentFormats(),
689  ],
690  'contentmodel' => [
691  ApiBase::PARAM_TYPE => $this->getContentHandlerFactory()->getContentModels(),
693  ],
694  'section' => [
695  ApiBase::PARAM_DFLT => null,
697  ],
698  ];
699 
700  $ret = [];
701  foreach ( $fromToParams as $k => $v ) {
702  if ( isset( $v[ApiBase::PARAM_TEMPLATE_VARS]['slot'] ) ) {
703  $v[ApiBase::PARAM_TEMPLATE_VARS]['slot'] = 'fromslots';
704  }
705  $ret["from$k"] = $v;
706  }
707  foreach ( $fromToParams as $k => $v ) {
708  if ( isset( $v[ApiBase::PARAM_TEMPLATE_VARS]['slot'] ) ) {
709  $v[ApiBase::PARAM_TEMPLATE_VARS]['slot'] = 'toslots';
710  }
711  $ret["to$k"] = $v;
712  }
713 
714  $ret = wfArrayInsertAfter(
715  $ret,
716  [ 'torelative' => [ ApiBase::PARAM_TYPE => [ 'prev', 'next', 'cur' ], ] ],
717  'torev'
718  );
719 
720  $ret['prop'] = [
721  ApiBase::PARAM_DFLT => 'diff|ids|title',
723  'diff',
724  'diffsize',
725  'rel',
726  'ids',
727  'title',
728  'user',
729  'comment',
730  'parsedcomment',
731  'size',
732  'timestamp',
733  ],
734  ApiBase::PARAM_ISMULTI => true,
736  ];
737 
738  $ret['slots'] = [
739  ApiBase::PARAM_TYPE => $slotRoles,
740  ApiBase::PARAM_ISMULTI => true,
741  ApiBase::PARAM_ALL => true,
742  ];
743 
744  return $ret;
745  }
746 
747  protected function getExamplesMessages() {
748  return [
749  'action=compare&fromrev=1&torev=2'
750  => 'apihelp-compare-example-1',
751  ];
752  }
753 
756  }
757 }
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:206
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:329
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:1334
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:152
Revision\RevisionStore
Service for looking up page revisions.
Definition: RevisionStore.php:80
ApiBase\dieWithError
dieWithError( $msg, $code=null, $data=null, $httpCode=null)
Abort execution with an error.
Definition: ApiBase.php:1415
ApiBase\PARAM_ALL
const PARAM_ALL
(boolean) Inverse of IntegerDef::PARAM_IGNORE_RANGE
Definition: ApiBase.php:77
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:1811
ApiBase\PARAM_TYPE
const PARAM_TYPE
(boolean) Inverse of IntegerDef::PARAM_IGNORE_RANGE
Definition: ApiBase.php:68
ApiBase\getResult
getResult()
Get the result object.
Definition: ApiBase.php:546
ApiComparePages\$slotRoleRegistry
MediaWiki Revision SlotRoleRegistry $slotRoleRegistry
Definition: ApiComparePages.php:38
ApiBase\getDB
getDB()
Gets a default replica DB connection object.
Definition: ApiBase.php:574
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:50
ApiComparePages\guessModel
guessModel( $role)
Guess an appropriate default content model for this request.
Definition: ApiComparePages.php:316
Revision
Definition: Revision.php:39
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:73
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:695
$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:592
ApiBase\requireAtLeastOneParameter
requireAtLeastOneParameter( $params,... $required)
Die if none of a certain set of parameters is set and not false.
Definition: ApiBase.php:892
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:140
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:274
$content
$content
Definition: router.php:76
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:867
ParserOptions\newFromContext
static newFromContext(IContextSource $context)
Get a ParserOptions object from a IContextSource object.
Definition: ParserOptions.php:1113
MediaWiki\Content\IContentHandlerFactory
Definition: IContentHandlerFactory.php:10
Revision\MutableRevisionRecord
Definition: MutableRevisionRecord.php:43
ApiBase\getPermissionManager
getPermissionManager()
Obtain a PermissionManager instance that subclasses may use in their authorization checks.
Definition: ApiBase.php:616
wfEscapeWikiText
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
Definition: GlobalFunctions.php:1490
Title\newFromLinkTarget
static newFromLinkTarget(LinkTarget $linkTarget, $forceClone='')
Returns a Title given a LinkTarget.
Definition: Title.php:281
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:1199
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:155
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:66
ApiComparePages\getAllowedParams
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
Definition: ApiComparePages.php:646
ApiBase\dieWithException
dieWithException(Throwable $exception, array $options=[])
Abort execution with an error derived from a throwable.
Definition: ApiBase.php:1427
DifferenceEngine
DifferenceEngine is responsible for rendering the difference between two revisions as HTML.
Definition: DifferenceEngine.php:56
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:426
ApiBase\PARAM_ISMULTI
const PARAM_ISMULTI
(boolean) Inverse of IntegerDef::PARAM_IGNORE_RANGE
Definition: ApiBase.php:67
ApiComparePages\getExamplesMessages
getExamplesMessages()
Returns usage examples for this module.
Definition: ApiComparePages.php:747
ApiBase\getMain
getMain()
Get the main module.
Definition: ApiBase.php:442
ApiComparePages\getContentHandlerFactory
getContentHandlerFactory()
Definition: ApiComparePages.php:754
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:137
Title\newFromID
static newFromID( $id, $flags=0)
Create a new Title from an article ID.
Definition: Title.php:473
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:463
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:36