MediaWiki REL1_37
ApiComparePages.php
Go to the documentation of this file.
1<?php
29
33class ApiComparePages extends ApiBase {
34
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 ) {
154 $title = Title::newFromLinkTarget( $title );
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,
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 ],
753 ];
754
755 $ret['slots'] = [
756 ApiBase::PARAM_TYPE => $slotRoles,
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}
const CONTENT_MODEL_WIKITEXT
Definition Defines.php:208
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
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:55
dieWithError( $msg, $code=null, $data=null, $httpCode=0)
Abort execution with an error.
Definition ApiBase.php:1436
const PARAM_DEPRECATED
Definition ApiBase.php:101
getDB()
Gets a default replica DB connection object.
Definition ApiBase.php:651
getMain()
Get the main module.
Definition ApiBase.php:513
const PARAM_TYPE
Definition ApiBase.php:81
const PARAM_DFLT
Definition ApiBase.php:73
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
requireAtLeastOneParameter( $params,... $required)
Die if none of a certain set of parameters is set and not false.
Definition ApiBase.php:961
requireMaxOneParameter( $params,... $required)
Die if more than one of a certain set of parameters is set and not false.
Definition ApiBase.php:936
const PARAM_TEMPLATE_VARS
(array) Indicate that this is a templated parameter, and specify replacements.
Definition ApiBase.php:213
getResult()
Get the result object.
Definition ApiBase.php:628
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:764
addWarning( $msg, $code=null, $data=null)
Add a warning for this module.
Definition ApiBase.php:1354
const PARAM_ALL
Definition ApiBase.php:117
getModuleName()
Get the name of the module being executed by this instance.
Definition ApiBase.php:497
dieWithException(Throwable $exception, array $options=[])
Abort execution with an error derived from a throwable.
Definition ApiBase.php:1448
const PARAM_ISMULTI
Definition ApiBase.php:77
getDiffRevision( $prefix, array $params)
Get the RevisionRecord for one side of the diff.
SlotRoleRegistry $slotRoleRegistry
getExamplesMessages()
Returns usage examples for this module.
IContentHandlerFactory $contentHandlerFactory
setVals(&$vals, $prefix, $rev)
Set value fields from a RevisionRecord object.
getRevisionById( $id)
Load a revision by ID.
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
ContentTransformer $contentTransformer
Title false $guessedTitle
RevisionStore $revisionStore
__construct(ApiMain $mainModule, $moduleName, RevisionStore $revisionStore, SlotRoleRegistry $slotRoleRegistry, IContentHandlerFactory $contentHandlerFactory, ContentTransformer $contentTransformer)
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:49
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:1372
Exception representing a failure to serialize or unserialize a content object.
setContent( $role, Content $content)
Sets the content for the slot with the given role.
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.
A registry service for SlotRoleHandlers, used to define which slot roles are available on which page.
Represents a title within MediaWiki.
Definition Title.php:48
$content
Definition router.php:76