MediaWiki  master
ApiComparePages.php
Go to the documentation of this file.
1 <?php
29 
33 class ApiComparePages extends ApiBase {
34 
36  private $revisionStore;
37 
40 
42  private $guessedTitle = false;
43  private $props;
44 
47 
50 
59  public function __construct(
60  ApiMain $mainModule,
61  $moduleName,
66  ) {
67  parent::__construct( $mainModule, $moduleName );
68  $this->revisionStore = $revisionStore;
69  $this->slotRoleRegistry = $slotRoleRegistry;
70  $this->contentHandlerFactory = $contentHandlerFactory;
71  $this->contentTransformer = $contentTransformer;
72  }
73 
74  public function execute() {
75  $params = $this->extractRequestParams();
76 
77  // Parameter validation
79  $params, 'fromtitle', 'fromid', 'fromrev', 'fromtext', 'fromslots'
80  );
82  $params, 'totitle', 'toid', 'torev', 'totext', 'torelative', 'toslots'
83  );
84 
85  $this->props = array_fill_keys( $params['prop'], true );
86 
87  // Cache responses publicly by default. This may be overridden later.
88  $this->getMain()->setCacheMode( 'public' );
89 
90  // Get the 'from' RevisionRecord
91  list( $fromRev, $fromRelRev, $fromValsRev ) = $this->getDiffRevision( 'from', $params );
92 
93  // Get the 'to' RevisionRecord
94  if ( $params['torelative'] !== null ) {
95  if ( !$fromRelRev ) {
96  $this->dieWithError( 'apierror-compare-relative-to-nothing' );
97  }
98  if ( $params['torelative'] !== 'cur' && $fromRelRev instanceof RevisionArchiveRecord ) {
99  // RevisionStore's getPreviousRevision/getNextRevision blow up
100  // when passed an RevisionArchiveRecord for a deleted page
101  $this->dieWithError( [ 'apierror-compare-relative-to-deleted', $params['torelative'] ] );
102  }
103  switch ( $params['torelative'] ) {
104  case 'prev':
105  // Swap 'from' and 'to'
106  list( $toRev, $toRelRev, $toValsRev ) = [ $fromRev, $fromRelRev, $fromValsRev ];
107  $fromRev = $this->revisionStore->getPreviousRevision( $toRelRev );
108  $fromRelRev = $fromRev;
109  $fromValsRev = $fromRev;
110  if ( !$fromRev ) {
111  $title = Title::newFromLinkTarget( $toRelRev->getPageAsLinkTarget() );
112  $this->addWarning( [
113  'apiwarn-compare-no-prev',
114  wfEscapeWikiText( $title->getPrefixedText() ),
115  $toRelRev->getId()
116  ] );
117 
118  // (T203433) Create an empty dummy revision as the "previous".
119  // The main slot has to exist, the rest will be handled by DifferenceEngine.
120  $fromRev = new MutableRevisionRecord(
121  $title ?: $toRev->getPage()
122  );
123  $fromRev->setContent(
124  SlotRecord::MAIN,
125  $toRelRev->getContent( SlotRecord::MAIN, RevisionRecord::RAW )
126  ->getContentHandler()
127  ->makeEmptyContent()
128  );
129  }
130  break;
131 
132  case 'next':
133  $toRev = $this->revisionStore->getNextRevision( $fromRelRev );
134  $toRelRev = $toRev;
135  $toValsRev = $toRev;
136  if ( !$toRev ) {
137  $title = Title::newFromLinkTarget( $fromRelRev->getPageAsLinkTarget() );
138  $this->addWarning( [
139  'apiwarn-compare-no-next',
140  wfEscapeWikiText( $title->getPrefixedText() ),
141  $fromRelRev->getId()
142  ] );
143 
144  // (T203433) The web UI treats "next" as "cur" in this case.
145  // Avoid repeating metadata by making a MutableRevisionRecord with no changes.
146  $toRev = MutableRevisionRecord::newFromParentRevision( $fromRelRev );
147  }
148  break;
149 
150  case 'cur':
151  $title = $fromRelRev->getPageAsLinkTarget();
152  $toRev = $this->revisionStore->getRevisionByTitle( $title );
153  if ( !$toRev ) {
155  $this->dieWithError(
156  [ 'apierror-missingrev-title', wfEscapeWikiText( $title->getPrefixedText() ) ],
157  'nosuchrevid'
158  );
159  }
160  $toRelRev = $toRev;
161  $toValsRev = $toRev;
162  break;
163  }
164  } else {
165  list( $toRev, $toRelRev, $toValsRev ) = $this->getDiffRevision( 'to', $params );
166  }
167 
168  // Handle missing from or to revisions (should never happen)
169  // @codeCoverageIgnoreStart
170  if ( !$fromRev || !$toRev ) {
171  $this->dieWithError( 'apierror-baddiff' );
172  }
173  // @codeCoverageIgnoreEnd
174 
175  // Handle revdel
176  if ( !$fromRev->userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ) {
177  $this->dieWithError( [ 'apierror-missingcontent-revid', $fromRev->getId() ], 'missingcontent' );
178  }
179  if ( !$toRev->userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ) {
180  $this->dieWithError( [ 'apierror-missingcontent-revid', $toRev->getId() ], 'missingcontent' );
181  }
182 
183  // Get the diff
184  $context = new DerivativeContext( $this->getContext() );
185  if ( $fromRelRev && $fromRelRev->getPageAsLinkTarget() ) {
186  $context->setTitle( Title::newFromLinkTarget( $fromRelRev->getPageAsLinkTarget() ) );
187  } elseif ( $toRelRev && $toRelRev->getPageAsLinkTarget() ) {
188  $context->setTitle( Title::newFromLinkTarget( $toRelRev->getPageAsLinkTarget() ) );
189  } else {
190  $guessedTitle = $this->guessTitle();
191  if ( $guessedTitle ) {
192  $context->setTitle( $guessedTitle );
193  }
194  }
195  $de = new DifferenceEngine( $context );
196  $de->setRevisions( $fromRev, $toRev );
197  if ( $params['slots'] === null ) {
198  $difftext = $de->getDiffBody();
199  if ( $difftext === false ) {
200  $this->dieWithError( 'apierror-baddiff' );
201  }
202  } else {
203  $difftext = [];
204  foreach ( $params['slots'] as $role ) {
205  $difftext[$role] = $de->getDiffBodyForRole( $role );
206  }
207  }
208 
209  // Fill in the response
210  $vals = [];
211  $this->setVals( $vals, 'from', $fromValsRev );
212  $this->setVals( $vals, 'to', $toValsRev );
213 
214  if ( isset( $this->props['rel'] ) ) {
215  if ( !$fromRev instanceof MutableRevisionRecord ) {
216  $rev = $this->revisionStore->getPreviousRevision( $fromRev );
217  if ( $rev ) {
218  $vals['prev'] = $rev->getId();
219  }
220  }
221  if ( !$toRev instanceof MutableRevisionRecord ) {
222  $rev = $this->revisionStore->getNextRevision( $toRev );
223  if ( $rev ) {
224  $vals['next'] = $rev->getId();
225  }
226  }
227  }
228 
229  if ( isset( $this->props['diffsize'] ) ) {
230  $vals['diffsize'] = 0;
231  foreach ( (array)$difftext as $text ) {
232  $vals['diffsize'] += strlen( $text );
233  }
234  }
235  if ( isset( $this->props['diff'] ) ) {
236  if ( is_array( $difftext ) ) {
237  ApiResult::setArrayType( $difftext, 'kvp', 'diff' );
238  $vals['bodies'] = $difftext;
239  } else {
240  ApiResult::setContentValue( $vals, 'body', $difftext );
241  }
242  }
243 
244  // Diffs can be really big and there's little point in having
245  // ApiResult truncate it to an empty response since the diff is the
246  // whole reason this module exists. So pass NO_SIZE_CHECK here.
247  $this->getResult()->addValue( null, $this->getModuleName(), $vals, ApiResult::NO_SIZE_CHECK );
248  }
249 
258  private function getRevisionById( $id ) {
259  $rev = $this->revisionStore->getRevisionById( $id );
260  if ( !$rev && $this->getAuthority()->isAllowedAny( 'deletedtext', 'undelete' ) ) {
261  // Try the 'archive' table
262  $arQuery = $this->revisionStore->getArchiveQueryInfo();
263  $row = $this->getDB()->selectRow(
264  $arQuery['tables'],
265  array_merge(
266  $arQuery['fields'],
267  [ 'ar_namespace', 'ar_title' ]
268  ),
269  [ 'ar_rev_id' => $id ],
270  __METHOD__,
271  [],
272  $arQuery['joins']
273  );
274  if ( $row ) {
275  $rev = $this->revisionStore->newRevisionFromArchiveRow( $row );
276  // @phan-suppress-next-line PhanUndeclaredProperty
277  $rev->isArchive = true;
278  }
279  }
280  return $rev;
281  }
282 
288  private function guessTitle() {
289  if ( $this->guessedTitle !== false ) {
290  return $this->guessedTitle;
291  }
292 
293  $this->guessedTitle = null;
294  $params = $this->extractRequestParams();
295 
296  foreach ( [ 'from', 'to' ] as $prefix ) {
297  if ( $params["{$prefix}rev"] !== null ) {
298  $rev = $this->getRevisionById( $params["{$prefix}rev"] );
299  if ( $rev ) {
300  $this->guessedTitle = Title::newFromLinkTarget( $rev->getPageAsLinkTarget() );
301  break;
302  }
303  }
304 
305  if ( $params["{$prefix}title"] !== null ) {
306  $title = Title::newFromText( $params["{$prefix}title"] );
307  if ( $title && !$title->isExternal() ) {
308  $this->guessedTitle = $title;
309  break;
310  }
311  }
312 
313  if ( $params["{$prefix}id"] !== null ) {
314  $title = Title::newFromID( $params["{$prefix}id"] );
315  if ( $title ) {
316  $this->guessedTitle = $title;
317  break;
318  }
319  }
320  }
321 
322  return $this->guessedTitle;
323  }
324 
330  private function guessModel( $role ) {
331  $params = $this->extractRequestParams();
332 
333  $title = null;
334  foreach ( [ 'from', 'to' ] as $prefix ) {
335  if ( $params["{$prefix}rev"] !== null ) {
336  $rev = $this->getRevisionById( $params["{$prefix}rev"] );
337  if ( $rev && $rev->hasSlot( $role ) ) {
338  return $rev->getSlot( $role, RevisionRecord::RAW )->getModel();
339  }
340  }
341  }
342 
343  $guessedTitle = $this->guessTitle();
344  if ( $guessedTitle ) {
345  return $this->slotRoleRegistry->getRoleHandler( $role )->getDefaultModel( $guessedTitle );
346  }
347 
348  if ( isset( $params["fromcontentmodel-$role"] ) ) {
349  return $params["fromcontentmodel-$role"];
350  }
351  if ( isset( $params["tocontentmodel-$role"] ) ) {
352  return $params["tocontentmodel-$role"];
353  }
354 
355  if ( $role === SlotRecord::MAIN ) {
356  if ( isset( $params['fromcontentmodel'] ) ) {
357  return $params['fromcontentmodel'];
358  }
359  if ( isset( $params['tocontentmodel'] ) ) {
360  return $params['tocontentmodel'];
361  }
362  }
363 
364  return null;
365  }
366 
382  private function getDiffRevision( $prefix, array $params ) {
383  // Back compat params
384  $this->requireMaxOneParameter( $params, "{$prefix}text", "{$prefix}slots" );
385  $this->requireMaxOneParameter( $params, "{$prefix}section", "{$prefix}slots" );
386  if ( $params["{$prefix}text"] !== null ) {
387  $params["{$prefix}slots"] = [ SlotRecord::MAIN ];
388  $params["{$prefix}text-main"] = $params["{$prefix}text"];
389  $params["{$prefix}section-main"] = null;
390  $params["{$prefix}contentmodel-main"] = $params["{$prefix}contentmodel"];
391  $params["{$prefix}contentformat-main"] = $params["{$prefix}contentformat"];
392  }
393 
394  $title = null;
395  $rev = null;
396  $suppliedContent = $params["{$prefix}slots"] !== null;
397 
398  // Get the revision and title, if applicable
399  $revId = null;
400  if ( $params["{$prefix}rev"] !== null ) {
401  $revId = $params["{$prefix}rev"];
402  } elseif ( $params["{$prefix}title"] !== null || $params["{$prefix}id"] !== null ) {
403  if ( $params["{$prefix}title"] !== null ) {
404  $title = Title::newFromText( $params["{$prefix}title"] );
405  if ( !$title || $title->isExternal() ) {
406  $this->dieWithError(
407  [ 'apierror-invalidtitle', wfEscapeWikiText( $params["{$prefix}title"] ) ]
408  );
409  }
410  } else {
411  $title = Title::newFromID( $params["{$prefix}id"] );
412  if ( !$title ) {
413  $this->dieWithError( [ 'apierror-nosuchpageid', $params["{$prefix}id"] ] );
414  }
415  }
416  $revId = $title->getLatestRevID();
417  if ( !$revId ) {
418  $revId = null;
419  // Only die here if we're not using supplied text
420  if ( !$suppliedContent ) {
421  if ( $title->exists() ) {
422  $this->dieWithError(
423  [ 'apierror-missingrev-title', wfEscapeWikiText( $title->getPrefixedText() ) ],
424  'nosuchrevid'
425  );
426  } else {
427  $this->dieWithError(
428  [ 'apierror-missingtitle-byname', wfEscapeWikiText( $title->getPrefixedText() ) ],
429  'missingtitle'
430  );
431  }
432  }
433  }
434  }
435  if ( $revId !== null ) {
436  $rev = $this->getRevisionById( $revId );
437  if ( !$rev ) {
438  $this->dieWithError( [ 'apierror-nosuchrevid', $revId ] );
439  }
440  $title = Title::newFromLinkTarget( $rev->getPageAsLinkTarget() );
441 
442  // If we don't have supplied content, return here. Otherwise,
443  // continue on below with the supplied content.
444  if ( !$suppliedContent ) {
445  $newRev = $rev;
446 
447  // Deprecated 'fromsection'/'tosection'
448  if ( isset( $params["{$prefix}section"] ) ) {
449  $section = $params["{$prefix}section"];
450  $newRev = MutableRevisionRecord::newFromParentRevision( $rev );
451  $content = $rev->getContent( SlotRecord::MAIN, RevisionRecord::FOR_THIS_USER,
452  $this->getUser() );
453  if ( !$content ) {
454  $this->dieWithError(
455  [ 'apierror-missingcontent-revid-role', $rev->getId(), SlotRecord::MAIN ], 'missingcontent'
456  );
457  }
458  $content = $content->getSection( $section );
459  if ( !$content ) {
460  $this->dieWithError(
461  [ "apierror-compare-nosuch{$prefix}section", wfEscapeWikiText( $section ) ],
462  "nosuch{$prefix}section"
463  );
464  }
465  $newRev->setContent( SlotRecord::MAIN, $content );
466  }
467 
468  return [ $newRev, $rev, $rev ];
469  }
470  }
471 
472  // Override $content based on supplied text
473  if ( !$title ) {
474  $title = $this->guessTitle();
475  }
476  if ( $rev ) {
477  $newRev = MutableRevisionRecord::newFromParentRevision( $rev );
478  } else {
479  $newRev = new MutableRevisionRecord( $title ?: Title::newMainPage() );
480  }
481  foreach ( $params["{$prefix}slots"] as $role ) {
482  $text = $params["{$prefix}text-{$role}"];
483  if ( $text === null ) {
484  // The SlotRecord::MAIN role can't be deleted
485  if ( $role === SlotRecord::MAIN ) {
486  $this->dieWithError( [ 'apierror-compare-maintextrequired', $prefix ] );
487  }
488 
489  // These parameters make no sense without text. Reject them to avoid
490  // confusion.
491  foreach ( [ 'section', 'contentmodel', 'contentformat' ] as $param ) {
492  if ( isset( $params["{$prefix}{$param}-{$role}"] ) ) {
493  $this->dieWithError( [
494  'apierror-compare-notext',
495  wfEscapeWikiText( "{$prefix}{$param}-{$role}" ),
496  wfEscapeWikiText( "{$prefix}text-{$role}" ),
497  ] );
498  }
499  }
500 
501  $newRev->removeSlot( $role );
502  continue;
503  }
504 
505  $model = $params["{$prefix}contentmodel-{$role}"];
506  $format = $params["{$prefix}contentformat-{$role}"];
507 
508  if ( !$model && $rev && $rev->hasSlot( $role ) ) {
509  $model = $rev->getSlot( $role, RevisionRecord::RAW )->getModel();
510  }
511  if ( !$model && $title && $role === SlotRecord::MAIN ) {
512  // @todo: Use SlotRoleRegistry and do this for all slots
513  $model = $title->getContentModel();
514  }
515  if ( !$model ) {
516  $model = $this->guessModel( $role );
517  }
518  if ( !$model ) {
519  $model = CONTENT_MODEL_WIKITEXT;
520  $this->addWarning( [ 'apiwarn-compare-nocontentmodel', $model ] );
521  }
522 
523  try {
524  $content = ContentHandler::makeContent( $text, $title, $model, $format );
525  } catch ( MWContentSerializationException $ex ) {
526  $this->dieWithException( $ex, [
527  'wrap' => ApiMessage::create( 'apierror-contentserializationexception', 'parseerror' )
528  ] );
529  }
530 
531  if ( $params["{$prefix}pst"] ) {
532  if ( !$title ) {
533  $this->dieWithError( 'apierror-compare-no-title' );
534  }
535  $popts = ParserOptions::newFromContext( $this->getContext() );
536  $content = $this->contentTransformer->preSaveTransform(
537  $content,
538  $title,
539  $this->getUser(),
540  $popts
541  );
542  }
543 
544  $section = $params["{$prefix}section-{$role}"];
545  if ( $section !== null && $section !== '' ) {
546  if ( !$rev ) {
547  $this->dieWithError( "apierror-compare-no{$prefix}revision" );
548  }
549  $oldContent = $rev->getContent( $role, RevisionRecord::FOR_THIS_USER, $this->getUser() );
550  if ( !$oldContent ) {
551  $this->dieWithError(
552  [ 'apierror-missingcontent-revid-role', $rev->getId(), wfEscapeWikiText( $role ) ],
553  'missingcontent'
554  );
555  }
556  if ( !$oldContent->getContentHandler()->supportsSections() ) {
557  $this->dieWithError( [ 'apierror-sectionsnotsupported', $content->getModel() ] );
558  }
559  try {
560  $content = $oldContent->replaceSection( $section, $content, '' );
561  } catch ( Exception $ex ) {
562  // Probably a content model mismatch.
563  $content = null;
564  }
565  if ( !$content ) {
566  $this->dieWithError( [ 'apierror-sectionreplacefailed' ] );
567  }
568  }
569 
570  // Deprecated 'fromsection'/'tosection'
571  if ( $role === SlotRecord::MAIN && isset( $params["{$prefix}section"] ) ) {
572  $section = $params["{$prefix}section"];
573  $content = $content->getSection( $section );
574  if ( !$content ) {
575  $this->dieWithError(
576  [ "apierror-compare-nosuch{$prefix}section", wfEscapeWikiText( $section ) ],
577  "nosuch{$prefix}section"
578  );
579  }
580  }
581 
582  $newRev->setContent( $role, $content );
583  }
584  return [ $newRev, $rev, null ];
585  }
586 
594  private function setVals( &$vals, $prefix, $rev ) {
595  if ( $rev ) {
596  $title = Title::newFromLinkTarget( $rev->getPageAsLinkTarget() );
597  if ( isset( $this->props['ids'] ) ) {
598  $vals["{$prefix}id"] = $title->getArticleID();
599  $vals["{$prefix}revid"] = $rev->getId();
600  }
601  if ( isset( $this->props['title'] ) ) {
602  ApiQueryBase::addTitleInfo( $vals, $title, $prefix );
603  }
604  if ( isset( $this->props['size'] ) ) {
605  $vals["{$prefix}size"] = $rev->getSize();
606  }
607  if ( isset( $this->props['timestamp'] ) ) {
608  $revTimestamp = $rev->getTimestamp();
609  if ( $revTimestamp ) {
610  $vals["{$prefix}timestamp"] = wfTimestamp( TS_ISO_8601, $revTimestamp );
611  }
612  }
613 
614  $anyHidden = false;
615  if ( $rev->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
616  $vals["{$prefix}texthidden"] = true;
617  $anyHidden = true;
618  }
619 
620  if ( $rev->isDeleted( RevisionRecord::DELETED_USER ) ) {
621  $vals["{$prefix}userhidden"] = true;
622  $anyHidden = true;
623  }
624  if ( isset( $this->props['user'] ) ) {
625  $user = $rev->getUser( RevisionRecord::FOR_THIS_USER, $this->getUser() );
626  if ( $user ) {
627  $vals["{$prefix}user"] = $user->getName();
628  $vals["{$prefix}userid"] = $user->getId();
629  }
630  }
631 
632  if ( $rev->isDeleted( RevisionRecord::DELETED_COMMENT ) ) {
633  $vals["{$prefix}commenthidden"] = true;
634  $anyHidden = true;
635  }
636  if ( isset( $this->props['comment'] ) || isset( $this->props['parsedcomment'] ) ) {
637  $comment = $rev->getComment( RevisionRecord::FOR_THIS_USER, $this->getUser() );
638  if ( $comment !== null ) {
639  if ( isset( $this->props['comment'] ) ) {
640  $vals["{$prefix}comment"] = $comment->text;
641  }
642  $vals["{$prefix}parsedcomment"] = Linker::formatComment(
643  $comment->text, $title
644  );
645  }
646  }
647 
648  if ( $anyHidden ) {
649  $this->getMain()->setCacheMode( 'private' );
650  if ( $rev->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ) {
651  $vals["{$prefix}suppressed"] = true;
652  }
653  }
654 
655  // @phan-suppress-next-line PhanUndeclaredProperty
656  if ( !empty( $rev->isArchive ) ) {
657  $this->getMain()->setCacheMode( 'private' );
658  $vals["{$prefix}archive"] = true;
659  }
660  }
661  }
662 
663  public function getAllowedParams() {
664  $slotRoles = $this->slotRoleRegistry->getKnownRoles();
665  sort( $slotRoles, SORT_STRING );
666 
667  // Parameters for the 'from' and 'to' content
668  $fromToParams = [
669  'title' => null,
670  'id' => [
671  ApiBase::PARAM_TYPE => 'integer'
672  ],
673  'rev' => [
674  ApiBase::PARAM_TYPE => 'integer'
675  ],
676 
677  'slots' => [
678  ApiBase::PARAM_TYPE => $slotRoles,
679  ApiBase::PARAM_ISMULTI => true,
680  ],
681  'text-{slot}' => [
682  ApiBase::PARAM_TEMPLATE_VARS => [ 'slot' => 'slots' ], // fixed below
683  ApiBase::PARAM_TYPE => 'text',
684  ],
685  'section-{slot}' => [
686  ApiBase::PARAM_TEMPLATE_VARS => [ 'slot' => 'slots' ], // fixed below
687  ApiBase::PARAM_TYPE => 'string',
688  ],
689  'contentformat-{slot}' => [
690  ApiBase::PARAM_TEMPLATE_VARS => [ 'slot' => 'slots' ], // fixed below
691  ApiBase::PARAM_TYPE => $this->contentHandlerFactory->getAllContentFormats(),
692  ],
693  'contentmodel-{slot}' => [
694  ApiBase::PARAM_TEMPLATE_VARS => [ 'slot' => 'slots' ], // fixed below
695  ApiBase::PARAM_TYPE => $this->contentHandlerFactory->getContentModels(),
696  ],
697  'pst' => false,
698 
699  'text' => [
700  ApiBase::PARAM_TYPE => 'text',
702  ],
703  'contentformat' => [
704  ApiBase::PARAM_TYPE => $this->contentHandlerFactory->getAllContentFormats(),
706  ],
707  'contentmodel' => [
708  ApiBase::PARAM_TYPE => $this->contentHandlerFactory->getContentModels(),
710  ],
711  'section' => [
712  ApiBase::PARAM_DFLT => null,
714  ],
715  ];
716 
717  $ret = [];
718  foreach ( $fromToParams as $k => $v ) {
719  if ( isset( $v[ApiBase::PARAM_TEMPLATE_VARS]['slot'] ) ) {
720  $v[ApiBase::PARAM_TEMPLATE_VARS]['slot'] = 'fromslots';
721  }
722  $ret["from$k"] = $v;
723  }
724  foreach ( $fromToParams as $k => $v ) {
725  if ( isset( $v[ApiBase::PARAM_TEMPLATE_VARS]['slot'] ) ) {
726  $v[ApiBase::PARAM_TEMPLATE_VARS]['slot'] = 'toslots';
727  }
728  $ret["to$k"] = $v;
729  }
730 
731  $ret = wfArrayInsertAfter(
732  $ret,
733  [ 'torelative' => [ ApiBase::PARAM_TYPE => [ 'prev', 'next', 'cur' ], ] ],
734  'torev'
735  );
736 
737  $ret['prop'] = [
738  ApiBase::PARAM_DFLT => 'diff|ids|title',
740  'diff',
741  'diffsize',
742  'rel',
743  'ids',
744  'title',
745  'user',
746  'comment',
747  'parsedcomment',
748  'size',
749  'timestamp',
750  ],
751  ApiBase::PARAM_ISMULTI => true,
753  ];
754 
755  $ret['slots'] = [
756  ApiBase::PARAM_TYPE => $slotRoles,
757  ApiBase::PARAM_ISMULTI => true,
758  ApiBase::PARAM_ALL => true,
759  ];
760 
761  return $ret;
762  }
763 
764  protected function getExamplesMessages() {
765  return [
766  'action=compare&fromrev=1&torev=2'
767  => 'apihelp-compare-example-1',
768  ];
769  }
770 }
ApiMain
This is the main API class, used for both external and internal processing.
Definition: ApiMain.php:49
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:383
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:1297
ApiComparePages\setVals
setVals(&$vals, $prefix, $rev)
Set value fields from a RevisionRecord object.
Definition: ApiComparePages.php:594
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:1379
ApiBase\PARAM_ALL
const PARAM_ALL
Definition: ApiBase.php:81
ApiComparePages\$props
$props
Definition: ApiComparePages.php:43
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:1668
ApiBase\PARAM_TYPE
const PARAM_TYPE
Definition: ApiBase.php:72
ApiBase\getResult
getResult()
Get the result object.
Definition: ApiBase.php:571
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:713
ContextSource\getUser
getUser()
Definition: ContextSource.php:136
ApiComparePages\$contentHandlerFactory
IContentHandlerFactory $contentHandlerFactory
Definition: ApiComparePages.php:46
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:330
ApiComparePages\$contentTransformer
ContentTransformer $contentTransformer
Definition: ApiComparePages.php:49
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
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:715
ApiComparePages
Definition: ApiComparePages.php:33
MWContentSerializationException
Exception representing a failure to serialize or unserialize a content object.
Definition: MWContentSerializationException.php:8
MediaWiki\Content\Transform\ContentTransformer
A service to transform content.
Definition: ContentTransformer.php:15
ApiBase\extractRequestParams
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition: ApiBase.php:707
$title
$title
Definition: testCompression.php:38
ApiComparePages\__construct
__construct(ApiMain $mainModule, $moduleName, RevisionStore $revisionStore, SlotRoleRegistry $slotRoleRegistry, IContentHandlerFactory $contentHandlerFactory, ContentTransformer $contentTransformer)
Definition: ApiComparePages.php:59
ApiBase\requireAtLeastOneParameter
requireAtLeastOneParameter( $params,... $required)
Die if none of a certain set of parameters is set and not false.
Definition: ApiBase.php:904
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:146
MediaWiki\Revision\RevisionArchiveRecord
A RevisionRecord representing a revision of a deleted page persisted in the archive table.
Definition: RevisionArchiveRecord.php:42
ApiComparePages\guessTitle
guessTitle()
Guess an appropriate default Title for this request.
Definition: ApiComparePages.php:288
ApiComparePages\$slotRoleRegistry
SlotRoleRegistry $slotRoleRegistry
Definition: ApiComparePages.php:39
$content
$content
Definition: router.php:76
ApiComparePages\getDiffRevision
getDiffRevision( $prefix, array $params)
Get the RevisionRecord for one side of the diff.
Definition: ApiComparePages.php:382
ApiBase\requireMaxOneParameter
requireMaxOneParameter( $params,... $required)
Die if more than one of a certain set of parameters is set and not false.
Definition: ApiBase.php:879
ParserOptions\newFromContext
static newFromContext(IContextSource $context)
Get a ParserOptions object from a IContextSource object.
Definition: ParserOptions.php:1112
MediaWiki\Content\IContentHandlerFactory
Definition: IContentHandlerFactory.php:10
ContextSource\getAuthority
getAuthority()
Definition: ContextSource.php:144
MediaWiki\Revision\MutableRevisionRecord
Definition: MutableRevisionRecord.php:44
wfEscapeWikiText
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
Definition: GlobalFunctions.php:1459
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:295
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:1372
Title
Represents a title within MediaWiki.
Definition: Title.php:48
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:42
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:663
ApiBase\dieWithException
dieWithException(Throwable $exception, array $options=[])
Abort execution with an error derived from a throwable.
Definition: ApiBase.php:1392
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:74
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:764
MediaWiki\Revision\SlotRoleRegistry
A registry service for SlotRoleHandlers, used to define which slot roles are available on which page.
Definition: SlotRoleRegistry.php:48
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:529
ApiComparePages\getRevisionById
getRevisionById( $id)
Load a revision by ID.
Definition: ApiComparePages.php:258
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:36