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