67 parent::__construct( $mainModule, $moduleName );
79 $params,
'fromtitle',
'fromid',
'fromrev',
'fromtext',
'fromslots'
82 $params,
'totitle',
'toid',
'torev',
'totext',
'torelative',
'toslots'
85 $this->props = array_fill_keys( $params[
'prop'],
true );
88 $this->
getMain()->setCacheMode(
'public' );
91 list( $fromRev, $fromRelRev, $fromValsRev ) = $this->
getDiffRevision(
'from', $params );
94 if ( $params[
'torelative'] !==
null ) {
96 $this->
dieWithError(
'apierror-compare-relative-to-nothing' );
101 $this->
dieWithError( [
'apierror-compare-relative-to-deleted', $params[
'torelative'] ] );
103 switch ( $params[
'torelative'] ) {
106 list( $toRev, $toRelRev, $toValsRev ) = [ $fromRev, $fromRelRev, $fromValsRev ];
107 $fromRev = $this->revisionStore->getPreviousRevision( $toRelRev );
108 $fromRelRev = $fromRev;
109 $fromValsRev = $fromRev;
111 $title = Title::newFromLinkTarget( $toRelRev->getPageAsLinkTarget() );
113 'apiwarn-compare-no-prev',
121 $title ?: $toRev->getPage()
125 $toRelRev->getContent( SlotRecord::MAIN, RevisionRecord::RAW )
126 ->getContentHandler()
133 $toRev = $this->revisionStore->getNextRevision( $fromRelRev );
137 $title = Title::newFromLinkTarget( $fromRelRev->getPageAsLinkTarget() );
139 'apiwarn-compare-no-next',
146 $toRev = MutableRevisionRecord::newFromParentRevision( $fromRelRev );
151 $title = $fromRelRev->getPageAsLinkTarget();
152 $toRev = $this->revisionStore->getRevisionByTitle(
$title );
165 list( $toRev, $toRelRev, $toValsRev ) = $this->
getDiffRevision(
'to', $params );
170 if ( !$fromRev || !$toRev ) {
176 if ( !$fromRev->userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ) {
177 $this->
dieWithError( [
'apierror-missingcontent-revid', $fromRev->getId() ],
'missingcontent' );
179 if ( !$toRev->userCan( RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ) {
180 $this->
dieWithError( [
'apierror-missingcontent-revid', $toRev->getId() ],
'missingcontent' );
185 if ( $fromRelRev && $fromRelRev->getPageAsLinkTarget() ) {
186 $context->setTitle( Title::newFromLinkTarget( $fromRelRev->getPageAsLinkTarget() ) );
187 } elseif ( $toRelRev && $toRelRev->getPageAsLinkTarget() ) {
188 $context->setTitle( Title::newFromLinkTarget( $toRelRev->getPageAsLinkTarget() ) );
196 $de->setRevisions( $fromRev, $toRev );
197 if ( $params[
'slots'] ===
null ) {
198 $difftext = $de->getDiffBody();
199 if ( $difftext ===
false ) {
204 foreach ( $params[
'slots'] as $role ) {
205 $difftext[$role] = $de->getDiffBodyForRole( $role );
211 $this->
setVals( $vals,
'from', $fromValsRev );
212 $this->
setVals( $vals,
'to', $toValsRev );
214 if ( isset( $this->props[
'rel'] ) ) {
216 $rev = $this->revisionStore->getPreviousRevision( $fromRev );
218 $vals[
'prev'] = $rev->getId();
222 $rev = $this->revisionStore->getNextRevision( $toRev );
224 $vals[
'next'] = $rev->getId();
229 if ( isset( $this->props[
'diffsize'] ) ) {
230 $vals[
'diffsize'] = 0;
231 foreach ( (array)$difftext as $text ) {
232 $vals[
'diffsize'] += strlen( $text );
235 if ( isset( $this->props[
'diff'] ) ) {
236 if ( is_array( $difftext ) ) {
237 ApiResult::setArrayType( $difftext,
'kvp',
'diff' );
238 $vals[
'bodies'] = $difftext;
240 ApiResult::setContentValue( $vals,
'body', $difftext );
259 $rev = $this->revisionStore->getRevisionById( $id );
260 if ( !$rev && $this->
getAuthority()->isAllowedAny(
'deletedtext',
'undelete' ) ) {
262 $arQuery = $this->revisionStore->getArchiveQueryInfo();
263 $row = $this->
getDB()->selectRow(
267 [
'ar_namespace',
'ar_title' ]
269 [
'ar_rev_id' => $id ],
275 $rev = $this->revisionStore->newRevisionFromArchiveRow( $row );
277 $rev->isArchive =
true;
289 if ( $this->guessedTitle !==
false ) {
293 $this->guessedTitle =
null;
296 foreach ( [
'from',
'to' ] as $prefix ) {
297 if ( $params[
"{$prefix}rev"] !==
null ) {
300 $this->guessedTitle = Title::newFromLinkTarget( $rev->getPageAsLinkTarget() );
305 if ( $params[
"{$prefix}title"] !==
null ) {
306 $title = Title::newFromText( $params[
"{$prefix}title"] );
308 $this->guessedTitle =
$title;
313 if ( $params[
"{$prefix}id"] !==
null ) {
314 $title = Title::newFromID( $params[
"{$prefix}id"] );
316 $this->guessedTitle =
$title;
334 foreach ( [
'from',
'to' ] as $prefix ) {
335 if ( $params[
"{$prefix}rev"] !==
null ) {
337 if ( $rev && $rev->hasSlot( $role ) ) {
338 return $rev->getSlot( $role, RevisionRecord::RAW )->getModel();
345 return $this->slotRoleRegistry->getRoleHandler( $role )->getDefaultModel(
$guessedTitle );
348 if ( isset( $params[
"fromcontentmodel-$role"] ) ) {
349 return $params[
"fromcontentmodel-$role"];
351 if ( isset( $params[
"tocontentmodel-$role"] ) ) {
352 return $params[
"tocontentmodel-$role"];
355 if ( $role === SlotRecord::MAIN ) {
356 if ( isset( $params[
'fromcontentmodel'] ) ) {
357 return $params[
'fromcontentmodel'];
359 if ( isset( $params[
'tocontentmodel'] ) ) {
360 return $params[
'tocontentmodel'];
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"];
396 $suppliedContent = $params[
"{$prefix}slots"] !==
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"] );
411 $title = Title::newFromID( $params[
"{$prefix}id"] );
413 $this->
dieWithError( [
'apierror-nosuchpageid', $params[
"{$prefix}id"] ] );
416 $revId =
$title->getLatestRevID();
420 if ( !$suppliedContent ) {
435 if ( $revId !==
null ) {
438 $this->
dieWithError( [
'apierror-nosuchrevid', $revId ] );
440 $title = Title::newFromLinkTarget( $rev->getPageAsLinkTarget() );
444 if ( !$suppliedContent ) {
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,
455 [
'apierror-missingcontent-revid-role', $rev->getId(), SlotRecord::MAIN ],
'missingcontent'
461 [
"apierror-compare-nosuch{$prefix}section",
wfEscapeWikiText( $section ) ],
462 "nosuch{$prefix}section"
465 $newRev->setContent( SlotRecord::MAIN,
$content );
468 return [ $newRev, $rev, $rev ];
477 $newRev = MutableRevisionRecord::newFromParentRevision( $rev );
481 foreach ( $params[
"{$prefix}slots"] as $role ) {
482 $text = $params[
"{$prefix}text-{$role}"];
483 if ( $text ===
null ) {
485 if ( $role === SlotRecord::MAIN ) {
486 $this->
dieWithError( [
'apierror-compare-maintextrequired', $prefix ] );
491 foreach ( [
'section',
'contentmodel',
'contentformat' ] as $param ) {
492 if ( isset( $params[
"{$prefix}{$param}-{$role}"] ) ) {
494 'apierror-compare-notext',
501 $newRev->removeSlot( $role );
505 $model = $params[
"{$prefix}contentmodel-{$role}"];
506 $format = $params[
"{$prefix}contentformat-{$role}"];
508 if ( !$model && $rev && $rev->hasSlot( $role ) ) {
509 $model = $rev->getSlot( $role, RevisionRecord::RAW )->getModel();
511 if ( !$model &&
$title && $role === SlotRecord::MAIN ) {
513 $model =
$title->getContentModel();
520 $this->
addWarning( [
'apiwarn-compare-nocontentmodel', $model ] );
524 $content = ContentHandler::makeContent( $text,
$title, $model, $format );
527 'wrap' => ApiMessage::create(
'apierror-contentserializationexception',
'parseerror' )
531 if ( $params[
"{$prefix}pst"] ) {
535 $popts = ParserOptions::newFromContext( $this->
getContext() );
536 $content = $this->contentTransformer->preSaveTransform(
544 $section = $params[
"{$prefix}section-{$role}"];
545 if ( $section !==
null && $section !==
'' ) {
547 $this->
dieWithError(
"apierror-compare-no{$prefix}revision" );
549 $oldContent = $rev->getContent( $role, RevisionRecord::FOR_THIS_USER, $this->
getUser() );
550 if ( !$oldContent ) {
552 [
'apierror-missingcontent-revid-role', $rev->getId(),
wfEscapeWikiText( $role ) ],
556 if ( !$oldContent->getContentHandler()->supportsSections() ) {
561 }
catch ( Exception $ex ) {
566 $this->
dieWithError( [
'apierror-sectionreplacefailed' ] );
571 if ( $role === SlotRecord::MAIN && isset( $params[
"{$prefix}section"] ) ) {
572 $section = $params[
"{$prefix}section"];
576 [
"apierror-compare-nosuch{$prefix}section",
wfEscapeWikiText( $section ) ],
577 "nosuch{$prefix}section"
582 $newRev->setContent( $role,
$content );
584 return [ $newRev, $rev, null ];
594 private function setVals( &$vals, $prefix, $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();
601 if ( isset( $this->props[
'title'] ) ) {
602 ApiQueryBase::addTitleInfo( $vals,
$title, $prefix );
604 if ( isset( $this->props[
'size'] ) ) {
605 $vals[
"{$prefix}size"] = $rev->getSize();
607 if ( isset( $this->props[
'timestamp'] ) ) {
608 $revTimestamp = $rev->getTimestamp();
609 if ( $revTimestamp ) {
610 $vals[
"{$prefix}timestamp"] =
wfTimestamp( TS_ISO_8601, $revTimestamp );
615 if ( $rev->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
616 $vals[
"{$prefix}texthidden"] =
true;
620 if ( $rev->isDeleted( RevisionRecord::DELETED_USER ) ) {
621 $vals[
"{$prefix}userhidden"] =
true;
624 if ( isset( $this->props[
'user'] ) ) {
625 $user = $rev->getUser( RevisionRecord::FOR_THIS_USER, $this->
getUser() );
627 $vals[
"{$prefix}user"] = $user->getName();
628 $vals[
"{$prefix}userid"] = $user->getId();
632 if ( $rev->isDeleted( RevisionRecord::DELETED_COMMENT ) ) {
633 $vals[
"{$prefix}commenthidden"] =
true;
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;
649 $this->
getMain()->setCacheMode(
'private' );
650 if ( $rev->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ) {
651 $vals[
"{$prefix}suppressed"] =
true;
656 if ( !empty( $rev->isArchive ) ) {
657 $this->
getMain()->setCacheMode(
'private' );
658 $vals[
"{$prefix}archive"] =
true;
664 $slotRoles = $this->slotRoleRegistry->getKnownRoles();
665 sort( $slotRoles, SORT_STRING );
685 'section-{slot}' => [
689 'contentformat-{slot}' => [
693 'contentmodel-{slot}' => [
718 foreach ( $fromToParams as $k => $v ) {
724 foreach ( $fromToParams as $k => $v ) {
766 'action=compare&fromrev=1&torev=2'
767 =>
'apihelp-compare-example-1',
const CONTENT_MODEL_WIKITEXT
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.
dieWithError( $msg, $code=null, $data=null, $httpCode=0)
Abort execution with an error.
getDB()
Gets a default replica DB connection object.
getMain()
Get the main module.
const PARAM_HELP_MSG_PER_VALUE
((string|array|Message)[]) When PARAM_TYPE is an array, this is an array mapping those values to $msg...
requireAtLeastOneParameter( $params,... $required)
Die if none of a certain set of parameters is set and not false.
requireMaxOneParameter( $params,... $required)
Die if more than one of a certain set of parameters is set and not false.
const PARAM_TEMPLATE_VARS
(array) Indicate that this is a templated parameter, and specify replacements.
getResult()
Get the result object.
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
addWarning( $msg, $code=null, $data=null)
Add a warning for this module.
getModuleName()
Get the name of the module being executed by this instance.
dieWithException(Throwable $exception, array $options=[])
Abort execution with an error derived from a throwable.
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.
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...
Exception representing a failure to serialize or unserialize a content object.
A service to transform content.
Represents a title within MediaWiki.