MediaWiki REL1_34
ApiComparePages.php
Go to the documentation of this file.
1<?php
28
32class ApiComparePages extends ApiBase {
33
36
39
40 private $guessedTitle = false, $props;
41
42 public function __construct( ApiMain $mainModule, $moduleName, $modulePrefix = '' ) {
43 parent::__construct( $mainModule, $moduleName, $modulePrefix );
44 $this->revisionStore = MediaWikiServices::getInstance()->getRevisionStore();
45 $this->slotRoleRegistry = MediaWikiServices::getInstance()->getSlotRoleRegistry();
46 }
47
48 public function execute() {
49 $params = $this->extractRequestParams();
50
51 // Parameter validation
53 $params, 'fromtitle', 'fromid', 'fromrev', 'fromtext', 'fromslots'
54 );
56 $params, 'totitle', 'toid', 'torev', 'totext', 'torelative', 'toslots'
57 );
58
59 $this->props = array_flip( $params['prop'] );
60
61 // Cache responses publicly by default. This may be overridden later.
62 $this->getMain()->setCacheMode( 'public' );
63
64 // Get the 'from' RevisionRecord
65 list( $fromRev, $fromRelRev, $fromValsRev ) = $this->getDiffRevision( 'from', $params );
66
67 // Get the 'to' RevisionRecord
68 if ( $params['torelative'] !== null ) {
69 if ( !$fromRelRev ) {
70 $this->dieWithError( 'apierror-compare-relative-to-nothing' );
71 }
72 if ( $params['torelative'] !== 'cur' && $fromRelRev instanceof RevisionArchiveRecord ) {
73 // RevisionStore's getPreviousRevision/getNextRevision blow up
74 // when passed an RevisionArchiveRecord for a deleted page
75 $this->dieWithError( [ 'apierror-compare-relative-to-deleted', $params['torelative'] ] );
76 }
77 switch ( $params['torelative'] ) {
78 case 'prev':
79 // Swap 'from' and 'to'
80 list( $toRev, $toRelRev, $toValsRev ) = [ $fromRev, $fromRelRev, $fromValsRev ];
81 $fromRev = $this->revisionStore->getPreviousRevision( $toRelRev );
82 $fromRelRev = $fromRev;
83 $fromValsRev = $fromRev;
84 if ( !$fromRev ) {
85 $title = Title::newFromLinkTarget( $toRelRev->getPageAsLinkTarget() );
86 $this->addWarning( [
87 'apiwarn-compare-no-prev',
88 wfEscapeWikiText( $title->getPrefixedText() ),
89 $toRelRev->getId()
90 ] );
91
92 // (T203433) Create an empty dummy revision as the "previous".
93 // The main slot has to exist, the rest will be handled by DifferenceEngine.
94 $fromRev = $this->revisionStore->newMutableRevisionFromArray( [
95 'title' => $title ?: Title::makeTitle( NS_SPECIAL, 'Badtitle/' . __METHOD__ )
96 ] );
97 $fromRev->setContent(
98 SlotRecord::MAIN,
99 $toRelRev->getContent( SlotRecord::MAIN, RevisionRecord::RAW )
100 ->getContentHandler()
101 ->makeEmptyContent()
102 );
103 }
104 break;
105
106 case 'next':
107 $toRev = $this->revisionStore->getNextRevision( $fromRelRev );
108 $toRelRev = $toRev;
109 $toValsRev = $toRev;
110 if ( !$toRev ) {
111 $title = Title::newFromLinkTarget( $fromRelRev->getPageAsLinkTarget() );
112 $this->addWarning( [
113 'apiwarn-compare-no-next',
114 wfEscapeWikiText( $title->getPrefixedText() ),
115 $fromRelRev->getId()
116 ] );
117
118 // (T203433) The web UI treats "next" as "cur" in this case.
119 // Avoid repeating metadata by making a MutableRevisionRecord with no changes.
120 $toRev = MutableRevisionRecord::newFromParentRevision( $fromRelRev );
121 }
122 break;
123
124 case 'cur':
125 $title = $fromRelRev->getPageAsLinkTarget();
126 $toRev = $this->revisionStore->getRevisionByTitle( $title );
127 if ( !$toRev ) {
128 $title = Title::newFromLinkTarget( $title );
129 $this->dieWithError(
130 [ 'apierror-missingrev-title', wfEscapeWikiText( $title->getPrefixedText() ) ], 'nosuchrevid'
131 );
132 }
133 $toRelRev = $toRev;
134 $toValsRev = $toRev;
135 break;
136 }
137 } else {
138 list( $toRev, $toRelRev, $toValsRev ) = $this->getDiffRevision( 'to', $params );
139 }
140
141 // Handle missing from or to revisions (should never happen)
142 // @codeCoverageIgnoreStart
143 if ( !$fromRev || !$toRev ) {
144 $this->dieWithError( 'apierror-baddiff' );
145 }
146 // @codeCoverageIgnoreEnd
147
148 // Handle revdel
149 if ( !$fromRev->audienceCan(
150 RevisionRecord::DELETED_TEXT, RevisionRecord::FOR_THIS_USER, $this->getUser()
151 ) ) {
152 $this->dieWithError( [ 'apierror-missingcontent-revid', $fromRev->getId() ], 'missingcontent' );
153 }
154 if ( !$toRev->audienceCan(
155 RevisionRecord::DELETED_TEXT, RevisionRecord::FOR_THIS_USER, $this->getUser()
156 ) ) {
157 $this->dieWithError( [ 'apierror-missingcontent-revid', $toRev->getId() ], 'missingcontent' );
158 }
159
160 // Get the diff
161 $context = new DerivativeContext( $this->getContext() );
162 if ( $fromRelRev && $fromRelRev->getPageAsLinkTarget() ) {
163 $context->setTitle( Title::newFromLinkTarget( $fromRelRev->getPageAsLinkTarget() ) );
164 } elseif ( $toRelRev && $toRelRev->getPageAsLinkTarget() ) {
165 $context->setTitle( Title::newFromLinkTarget( $toRelRev->getPageAsLinkTarget() ) );
166 } else {
167 $guessedTitle = $this->guessTitle();
168 if ( $guessedTitle ) {
169 $context->setTitle( $guessedTitle );
170 }
171 }
172 $de = new DifferenceEngine( $context );
173 $de->setRevisions( $fromRev, $toRev );
174 if ( $params['slots'] === null ) {
175 $difftext = $de->getDiffBody();
176 if ( $difftext === false ) {
177 $this->dieWithError( 'apierror-baddiff' );
178 }
179 } else {
180 $difftext = [];
181 foreach ( $params['slots'] as $role ) {
182 $difftext[$role] = $de->getDiffBodyForRole( $role );
183 }
184 }
185
186 // Fill in the response
187 $vals = [];
188 $this->setVals( $vals, 'from', $fromValsRev );
189 $this->setVals( $vals, 'to', $toValsRev );
190
191 if ( isset( $this->props['rel'] ) ) {
192 if ( !$fromRev instanceof MutableRevisionRecord ) {
193 $rev = $this->revisionStore->getPreviousRevision( $fromRev );
194 if ( $rev ) {
195 $vals['prev'] = $rev->getId();
196 }
197 }
198 if ( !$toRev instanceof MutableRevisionRecord ) {
199 $rev = $this->revisionStore->getNextRevision( $toRev );
200 if ( $rev ) {
201 $vals['next'] = $rev->getId();
202 }
203 }
204 }
205
206 if ( isset( $this->props['diffsize'] ) ) {
207 $vals['diffsize'] = 0;
208 foreach ( (array)$difftext as $text ) {
209 $vals['diffsize'] += strlen( $text );
210 }
211 }
212 if ( isset( $this->props['diff'] ) ) {
213 if ( is_array( $difftext ) ) {
214 ApiResult::setArrayType( $difftext, 'kvp', 'diff' );
215 $vals['bodies'] = $difftext;
216 } else {
217 ApiResult::setContentValue( $vals, 'body', $difftext );
218 }
219 }
220
221 // Diffs can be really big and there's little point in having
222 // ApiResult truncate it to an empty response since the diff is the
223 // whole reason this module exists. So pass NO_SIZE_CHECK here.
224 $this->getResult()->addValue( null, $this->getModuleName(), $vals, ApiResult::NO_SIZE_CHECK );
225 }
226
235 private function getRevisionById( $id ) {
236 $rev = $this->revisionStore->getRevisionById( $id );
237 if ( !$rev && $this->getPermissionManager()
238 ->userHasAnyRight( $this->getUser(), 'deletedtext', 'undelete' )
239 ) {
240 // Try the 'archive' table
241 $arQuery = $this->revisionStore->getArchiveQueryInfo();
242 $row = $this->getDB()->selectRow(
243 $arQuery['tables'],
244 array_merge(
245 $arQuery['fields'],
246 [ 'ar_namespace', 'ar_title' ]
247 ),
248 [ 'ar_rev_id' => $id ],
249 __METHOD__,
250 [],
251 $arQuery['joins']
252 );
253 if ( $row ) {
254 $rev = $this->revisionStore->newRevisionFromArchiveRow( $row );
255 // @phan-suppress-next-line PhanUndeclaredProperty
256 $rev->isArchive = true;
257 }
258 }
259 return $rev;
260 }
261
267 private function guessTitle() {
268 if ( $this->guessedTitle !== false ) {
269 return $this->guessedTitle;
270 }
271
272 $this->guessedTitle = null;
273 $params = $this->extractRequestParams();
274
275 foreach ( [ 'from', 'to' ] as $prefix ) {
276 if ( $params["{$prefix}rev"] !== null ) {
277 $rev = $this->getRevisionById( $params["{$prefix}rev"] );
278 if ( $rev ) {
279 $this->guessedTitle = Title::newFromLinkTarget( $rev->getPageAsLinkTarget() );
280 break;
281 }
282 }
283
284 if ( $params["{$prefix}title"] !== null ) {
285 $title = Title::newFromText( $params["{$prefix}title"] );
286 if ( $title && !$title->isExternal() ) {
287 $this->guessedTitle = $title;
288 break;
289 }
290 }
291
292 if ( $params["{$prefix}id"] !== null ) {
293 $title = Title::newFromID( $params["{$prefix}id"] );
294 if ( $title ) {
295 $this->guessedTitle = $title;
296 break;
297 }
298 }
299 }
300
301 return $this->guessedTitle;
302 }
303
309 private function guessModel( $role ) {
310 $params = $this->extractRequestParams();
311
312 $title = null;
313 foreach ( [ 'from', 'to' ] as $prefix ) {
314 if ( $params["{$prefix}rev"] !== null ) {
315 $rev = $this->getRevisionById( $params["{$prefix}rev"] );
316 if ( $rev && $rev->hasSlot( $role ) ) {
317 return $rev->getSlot( $role, RevisionRecord::RAW )->getModel();
318 }
319 }
320 }
321
322 $guessedTitle = $this->guessTitle();
323 if ( $guessedTitle ) {
324 return $this->slotRoleRegistry->getRoleHandler( $role )->getDefaultModel( $guessedTitle );
325 }
326
327 if ( isset( $params["fromcontentmodel-$role"] ) ) {
328 return $params["fromcontentmodel-$role"];
329 }
330 if ( isset( $params["tocontentmodel-$role"] ) ) {
331 return $params["tocontentmodel-$role"];
332 }
333
334 if ( $role === SlotRecord::MAIN ) {
335 if ( isset( $params['fromcontentmodel'] ) ) {
336 return $params['fromcontentmodel'];
337 }
338 if ( isset( $params['tocontentmodel'] ) ) {
339 return $params['tocontentmodel'];
340 }
341 }
342
343 return null;
344 }
345
361 private function getDiffRevision( $prefix, array $params ) {
362 // Back compat params
363 $this->requireMaxOneParameter( $params, "{$prefix}text", "{$prefix}slots" );
364 $this->requireMaxOneParameter( $params, "{$prefix}section", "{$prefix}slots" );
365 if ( $params["{$prefix}text"] !== null ) {
366 $params["{$prefix}slots"] = [ SlotRecord::MAIN ];
367 $params["{$prefix}text-main"] = $params["{$prefix}text"];
368 $params["{$prefix}section-main"] = null;
369 $params["{$prefix}contentmodel-main"] = $params["{$prefix}contentmodel"];
370 $params["{$prefix}contentformat-main"] = $params["{$prefix}contentformat"];
371 }
372
373 $title = null;
374 $rev = null;
375 $suppliedContent = $params["{$prefix}slots"] !== null;
376
377 // Get the revision and title, if applicable
378 $revId = null;
379 if ( $params["{$prefix}rev"] !== null ) {
380 $revId = $params["{$prefix}rev"];
381 } elseif ( $params["{$prefix}title"] !== null || $params["{$prefix}id"] !== null ) {
382 if ( $params["{$prefix}title"] !== null ) {
383 $title = Title::newFromText( $params["{$prefix}title"] );
384 if ( !$title || $title->isExternal() ) {
385 $this->dieWithError(
386 [ 'apierror-invalidtitle', wfEscapeWikiText( $params["{$prefix}title"] ) ]
387 );
388 }
389 } else {
390 $title = Title::newFromID( $params["{$prefix}id"] );
391 if ( !$title ) {
392 $this->dieWithError( [ 'apierror-nosuchpageid', $params["{$prefix}id"] ] );
393 }
394 }
395 $revId = $title->getLatestRevID();
396 if ( !$revId ) {
397 $revId = null;
398 // Only die here if we're not using supplied text
399 if ( !$suppliedContent ) {
400 if ( $title->exists() ) {
401 $this->dieWithError(
402 [ 'apierror-missingrev-title', wfEscapeWikiText( $title->getPrefixedText() ) ], 'nosuchrevid'
403 );
404 } else {
405 $this->dieWithError(
406 [ 'apierror-missingtitle-byname', wfEscapeWikiText( $title->getPrefixedText() ) ],
407 'missingtitle'
408 );
409 }
410 }
411 }
412 }
413 if ( $revId !== null ) {
414 $rev = $this->getRevisionById( $revId );
415 if ( !$rev ) {
416 $this->dieWithError( [ 'apierror-nosuchrevid', $revId ] );
417 }
418 $title = Title::newFromLinkTarget( $rev->getPageAsLinkTarget() );
419
420 // If we don't have supplied content, return here. Otherwise,
421 // continue on below with the supplied content.
422 if ( !$suppliedContent ) {
423 $newRev = $rev;
424
425 // Deprecated 'fromsection'/'tosection'
426 if ( isset( $params["{$prefix}section"] ) ) {
427 $section = $params["{$prefix}section"];
428 $newRev = MutableRevisionRecord::newFromParentRevision( $rev );
429 $content = $rev->getContent( SlotRecord::MAIN, RevisionRecord::FOR_THIS_USER,
430 $this->getUser() );
431 if ( !$content ) {
432 $this->dieWithError(
433 [ 'apierror-missingcontent-revid-role', $rev->getId(), SlotRecord::MAIN ], 'missingcontent'
434 );
435 }
436 $content = $content ? $content->getSection( $section ) : null;
437 if ( !$content ) {
438 $this->dieWithError(
439 [ "apierror-compare-nosuch{$prefix}section", wfEscapeWikiText( $section ) ],
440 "nosuch{$prefix}section"
441 );
442 }
443 $newRev->setContent( SlotRecord::MAIN, $content );
444 }
445
446 return [ $newRev, $rev, $rev ];
447 }
448 }
449
450 // Override $content based on supplied text
451 if ( !$title ) {
452 $title = $this->guessTitle();
453 }
454 if ( $rev ) {
455 $newRev = MutableRevisionRecord::newFromParentRevision( $rev );
456 } else {
457 $newRev = $this->revisionStore->newMutableRevisionFromArray( [
458 'title' => $title ?: Title::makeTitle( NS_SPECIAL, 'Badtitle/' . __METHOD__ )
459 ] );
460 }
461 foreach ( $params["{$prefix}slots"] as $role ) {
462 $text = $params["{$prefix}text-{$role}"];
463 if ( $text === null ) {
464 // The SlotRecord::MAIN role can't be deleted
465 if ( $role === SlotRecord::MAIN ) {
466 $this->dieWithError( [ 'apierror-compare-maintextrequired', $prefix ] );
467 }
468
469 // These parameters make no sense without text. Reject them to avoid
470 // confusion.
471 foreach ( [ 'section', 'contentmodel', 'contentformat' ] as $param ) {
472 if ( isset( $params["{$prefix}{$param}-{$role}"] ) ) {
473 $this->dieWithError( [
474 'apierror-compare-notext',
475 wfEscapeWikiText( "{$prefix}{$param}-{$role}" ),
476 wfEscapeWikiText( "{$prefix}text-{$role}" ),
477 ] );
478 }
479 }
480
481 $newRev->removeSlot( $role );
482 continue;
483 }
484
485 $model = $params["{$prefix}contentmodel-{$role}"];
486 $format = $params["{$prefix}contentformat-{$role}"];
487
488 if ( !$model && $rev && $rev->hasSlot( $role ) ) {
489 $model = $rev->getSlot( $role, RevisionRecord::RAW )->getModel();
490 }
491 if ( !$model && $title && $role === SlotRecord::MAIN ) {
492 // @todo: Use SlotRoleRegistry and do this for all slots
493 $model = $title->getContentModel();
494 }
495 if ( !$model ) {
496 $model = $this->guessModel( $role );
497 }
498 if ( !$model ) {
499 $model = CONTENT_MODEL_WIKITEXT;
500 $this->addWarning( [ 'apiwarn-compare-nocontentmodel', $model ] );
501 }
502
503 try {
504 $content = ContentHandler::makeContent( $text, $title, $model, $format );
505 } catch ( MWContentSerializationException $ex ) {
506 $this->dieWithException( $ex, [
507 'wrap' => ApiMessage::create( 'apierror-contentserializationexception', 'parseerror' )
508 ] );
509 }
510
511 if ( $params["{$prefix}pst"] ) {
512 if ( !$title ) {
513 $this->dieWithError( 'apierror-compare-no-title' );
514 }
515 $popts = ParserOptions::newFromContext( $this->getContext() );
516 $content = $content->preSaveTransform( $title, $this->getUser(), $popts );
517 }
518
519 $section = $params["{$prefix}section-{$role}"];
520 if ( $section !== null && $section !== '' ) {
521 if ( !$rev ) {
522 $this->dieWithError( "apierror-compare-no{$prefix}revision" );
523 }
524 $oldContent = $rev->getContent( $role, RevisionRecord::FOR_THIS_USER, $this->getUser() );
525 if ( !$oldContent ) {
526 $this->dieWithError(
527 [ 'apierror-missingcontent-revid-role', $rev->getId(), wfEscapeWikiText( $role ) ],
528 'missingcontent'
529 );
530 }
531 if ( !$oldContent->getContentHandler()->supportsSections() ) {
532 $this->dieWithError( [ 'apierror-sectionsnotsupported', $content->getModel() ] );
533 }
534 try {
535 $content = $oldContent->replaceSection( $section, $content, '' );
536 } catch ( Exception $ex ) {
537 // Probably a content model mismatch.
538 $content = null;
539 }
540 if ( !$content ) {
541 $this->dieWithError( [ 'apierror-sectionreplacefailed' ] );
542 }
543 }
544
545 // Deprecated 'fromsection'/'tosection'
546 if ( $role === SlotRecord::MAIN && isset( $params["{$prefix}section"] ) ) {
547 $section = $params["{$prefix}section"];
548 $content = $content->getSection( $section );
549 if ( !$content ) {
550 $this->dieWithError(
551 [ "apierror-compare-nosuch{$prefix}section", wfEscapeWikiText( $section ) ],
552 "nosuch{$prefix}section"
553 );
554 }
555 }
556
557 $newRev->setContent( $role, $content );
558 }
559 return [ $newRev, $rev, null ];
560 }
561
569 private function setVals( &$vals, $prefix, $rev ) {
570 if ( $rev ) {
571 $title = Title::newFromLinkTarget( $rev->getPageAsLinkTarget() );
572 if ( isset( $this->props['ids'] ) ) {
573 $vals["{$prefix}id"] = $title->getArticleID();
574 $vals["{$prefix}revid"] = $rev->getId();
575 }
576 if ( isset( $this->props['title'] ) ) {
577 ApiQueryBase::addTitleInfo( $vals, $title, $prefix );
578 }
579 if ( isset( $this->props['size'] ) ) {
580 $vals["{$prefix}size"] = $rev->getSize();
581 }
582
583 $anyHidden = false;
584 if ( $rev->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
585 $vals["{$prefix}texthidden"] = true;
586 $anyHidden = true;
587 }
588
589 if ( $rev->isDeleted( RevisionRecord::DELETED_USER ) ) {
590 $vals["{$prefix}userhidden"] = true;
591 $anyHidden = true;
592 }
593 if ( isset( $this->props['user'] ) ) {
594 $user = $rev->getUser( RevisionRecord::FOR_THIS_USER, $this->getUser() );
595 if ( $user ) {
596 $vals["{$prefix}user"] = $user->getName();
597 $vals["{$prefix}userid"] = $user->getId();
598 }
599 }
600
601 if ( $rev->isDeleted( RevisionRecord::DELETED_COMMENT ) ) {
602 $vals["{$prefix}commenthidden"] = true;
603 $anyHidden = true;
604 }
605 if ( isset( $this->props['comment'] ) || isset( $this->props['parsedcomment'] ) ) {
606 $comment = $rev->getComment( RevisionRecord::FOR_THIS_USER, $this->getUser() );
607 if ( $comment !== null ) {
608 if ( isset( $this->props['comment'] ) ) {
609 $vals["{$prefix}comment"] = $comment->text;
610 }
611 $vals["{$prefix}parsedcomment"] = Linker::formatComment(
612 $comment->text, $title
613 );
614 }
615 }
616
617 if ( $anyHidden ) {
618 $this->getMain()->setCacheMode( 'private' );
619 if ( $rev->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ) {
620 $vals["{$prefix}suppressed"] = true;
621 }
622 }
623
624 // @phan-suppress-next-line PhanUndeclaredProperty
625 if ( !empty( $rev->isArchive ) ) {
626 $this->getMain()->setCacheMode( 'private' );
627 $vals["{$prefix}archive"] = true;
628 }
629 }
630 }
631
632 public function getAllowedParams() {
633 $slotRoles = $this->slotRoleRegistry->getKnownRoles();
634 sort( $slotRoles, SORT_STRING );
635
636 // Parameters for the 'from' and 'to' content
637 $fromToParams = [
638 'title' => null,
639 'id' => [
640 ApiBase::PARAM_TYPE => 'integer'
641 ],
642 'rev' => [
643 ApiBase::PARAM_TYPE => 'integer'
644 ],
645
646 'slots' => [
647 ApiBase::PARAM_TYPE => $slotRoles,
649 ],
650 'text-{slot}' => [
651 ApiBase::PARAM_TEMPLATE_VARS => [ 'slot' => 'slots' ], // fixed below
652 ApiBase::PARAM_TYPE => 'text',
653 ],
654 'section-{slot}' => [
655 ApiBase::PARAM_TEMPLATE_VARS => [ 'slot' => 'slots' ], // fixed below
656 ApiBase::PARAM_TYPE => 'string',
657 ],
658 'contentformat-{slot}' => [
659 ApiBase::PARAM_TEMPLATE_VARS => [ 'slot' => 'slots' ], // fixed below
660 ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
661 ],
662 'contentmodel-{slot}' => [
663 ApiBase::PARAM_TEMPLATE_VARS => [ 'slot' => 'slots' ], // fixed below
664 ApiBase::PARAM_TYPE => ContentHandler::getContentModels(),
665 ],
666 'pst' => false,
667
668 'text' => [
669 ApiBase::PARAM_TYPE => 'text',
671 ],
672 'contentformat' => [
673 ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
675 ],
676 'contentmodel' => [
677 ApiBase::PARAM_TYPE => ContentHandler::getContentModels(),
679 ],
680 'section' => [
681 ApiBase::PARAM_DFLT => null,
683 ],
684 ];
685
686 $ret = [];
687 foreach ( $fromToParams as $k => $v ) {
688 if ( isset( $v[ApiBase::PARAM_TEMPLATE_VARS]['slot'] ) ) {
689 $v[ApiBase::PARAM_TEMPLATE_VARS]['slot'] = 'fromslots';
690 }
691 $ret["from$k"] = $v;
692 }
693 foreach ( $fromToParams as $k => $v ) {
694 if ( isset( $v[ApiBase::PARAM_TEMPLATE_VARS]['slot'] ) ) {
695 $v[ApiBase::PARAM_TEMPLATE_VARS]['slot'] = 'toslots';
696 }
697 $ret["to$k"] = $v;
698 }
699
700 $ret = wfArrayInsertAfter(
701 $ret,
702 [ 'torelative' => [ ApiBase::PARAM_TYPE => [ 'prev', 'next', 'cur' ], ] ],
703 'torev'
704 );
705
706 $ret['prop'] = [
707 ApiBase::PARAM_DFLT => 'diff|ids|title',
709 'diff',
710 'diffsize',
711 'rel',
712 'ids',
713 'title',
714 'user',
715 'comment',
716 'parsedcomment',
717 'size',
718 ],
721 ];
722
723 $ret['slots'] = [
724 ApiBase::PARAM_TYPE => $slotRoles,
726 ApiBase::PARAM_ALL => true,
727 ];
728
729 return $ret;
730 }
731
732 protected function getExamplesMessages() {
733 return [
734 'action=compare&fromrev=1&torev=2'
735 => 'apihelp-compare-example-1',
736 ];
737 }
738}
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
wfArrayInsertAfter(array $array, array $insert, $after)
Insert array into another array after the specified KEY
This abstract class implements many basic API functions, and is the base of all API classes.
Definition ApiBase.php:42
const PARAM_DEPRECATED
(boolean) Is the parameter deprecated (will show a warning)?
Definition ApiBase.php:112
getDB()
Gets a default replica DB connection object.
Definition ApiBase.php:668
dieWithError( $msg, $code=null, $data=null, $httpCode=null)
Abort execution with an error.
Definition ApiBase.php:2014
getMain()
Get the main module.
Definition ApiBase.php:536
const PARAM_TYPE
(string|string[]) Either an array of allowed value strings, or a string type as described below.
Definition ApiBase.php:94
const PARAM_DFLT
(null|boolean|integer|string) Default value of the parameter.
Definition ApiBase.php:55
getPermissionManager()
Obtain a PermissionManager instance that subclasses may use in their authorization checks.
Definition ApiBase.php:710
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:164
const PARAM_TEMPLATE_VARS
(array) Indicate that this is a templated parameter, and specify replacements.
Definition ApiBase.php:252
getResult()
Get the result object.
Definition ApiBase.php:640
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:761
requireMaxOneParameter( $params, $required)
Die if more than one of a certain set of parameters is set and not false.
Definition ApiBase.php:931
dieWithException( $exception, array $options=[])
Abort execution with an error derived from an exception.
Definition ApiBase.php:2026
addWarning( $msg, $code=null, $data=null)
Add a warning for this module.
Definition ApiBase.php:1933
requireAtLeastOneParameter( $params, $required)
Die if none of a certain set of parameters is set and not false.
Definition ApiBase.php:959
const PARAM_ALL
(boolean|string) When PARAM_TYPE has a defined set of values and PARAM_ISMULTI is true,...
Definition ApiBase.php:187
getModuleName()
Get the name of the module being executed by this instance.
Definition ApiBase.php:520
const PARAM_ISMULTI
(boolean) Accept multiple pipe-separated values for this parameter (e.g.
Definition ApiBase.php:58
getDiffRevision( $prefix, array $params)
Get the RevisionRecord for one side of the diff.
getExamplesMessages()
Returns usage examples for this module.
setVals(&$vals, $prefix, $rev)
Set value fields from a RevisionRecord object.
MediaWiki Revision SlotRoleRegistry $slotRoleRegistry
getRevisionById( $id)
Load a revision by ID.
__construct(ApiMain $mainModule, $moduleName, $modulePrefix='')
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
RevisionStore $revisionStore
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
guessTitle()
Guess an appropriate default Title for this request.
guessModel( $role)
Guess an appropriate default content model for this request.
This is the main API class, used for both external and internal processing.
Definition ApiMain.php:41
static create( $msg, $code=null, array $data=null)
Create an IApiMessage for the message.
IContextSource $context
getContext()
Get the base IContextSource object.
An IContextSource implementation which will inherit context from another source but allow individual ...
DifferenceEngine is responsible for rendering the difference between two revisions as HTML.
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:1165
Exception representing a failure to serialize or unserialize a content object.
MediaWikiServices is the service locator for the application scope of MediaWiki.
A RevisionRecord representing a revision of a deleted page persisted in the archive table.
Page revision base class.
Service for looking up page revisions.
Value object representing a content slot associated with a page revision.
const NS_SPECIAL
Definition Defines.php:58
const CONTENT_MODEL_WIKITEXT
Definition Defines.php:224
This class serves as a utility class for this extension.
$content
Definition router.php:78