43 parent::__construct( $mainModule, $moduleName, $modulePrefix );
44 $this->revisionStore = MediaWikiServices::getInstance()->getRevisionStore();
45 $this->slotRoleRegistry = MediaWikiServices::getInstance()->getSlotRoleRegistry();
53 $params,
'fromtitle',
'fromid',
'fromrev',
'fromtext',
'fromslots'
56 $params,
'totitle',
'toid',
'torev',
'totext',
'torelative',
'toslots'
59 $this->props = array_flip( $params[
'prop'] );
62 $this->
getMain()->setCacheMode(
'public' );
65 list( $fromRev, $fromRelRev, $fromValsRev ) = $this->
getDiffRevision(
'from', $params );
68 if ( $params[
'torelative'] !==
null ) {
70 $this->
dieWithError(
'apierror-compare-relative-to-nothing' );
75 $this->
dieWithError( [
'apierror-compare-relative-to-deleted', $params[
'torelative'] ] );
77 switch ( $params[
'torelative'] ) {
80 list( $toRev, $toRelRev, $toValsRev ) = [ $fromRev, $fromRelRev, $fromValsRev ];
81 $fromRev = $this->revisionStore->getPreviousRevision( $toRelRev );
82 $fromRelRev = $fromRev;
83 $fromValsRev = $fromRev;
85 $title = Title::newFromLinkTarget( $toRelRev->getPageAsLinkTarget() );
87 'apiwarn-compare-no-prev',
94 $fromRev = $this->revisionStore->newMutableRevisionFromArray( [
95 'title' =>
$title ?: Title::makeTitle(
NS_SPECIAL,
'Badtitle/' . __METHOD__ )
99 $toRelRev->getContent( SlotRecord::MAIN, RevisionRecord::RAW )
100 ->getContentHandler()
107 $toRev = $this->revisionStore->getNextRevision( $fromRelRev );
111 $title = Title::newFromLinkTarget( $fromRelRev->getPageAsLinkTarget() );
113 'apiwarn-compare-no-next',
120 $toRev = MutableRevisionRecord::newFromParentRevision( $fromRelRev );
125 $title = $fromRelRev->getPageAsLinkTarget();
126 $toRev = $this->revisionStore->getRevisionByTitle(
$title );
138 list( $toRev, $toRelRev, $toValsRev ) = $this->
getDiffRevision(
'to', $params );
143 if ( !$fromRev || !$toRev ) {
149 if ( !$fromRev->audienceCan(
150 RevisionRecord::DELETED_TEXT, RevisionRecord::FOR_THIS_USER, $this->getUser()
152 $this->
dieWithError( [
'apierror-missingcontent-revid', $fromRev->getId() ],
'missingcontent' );
154 if ( !$toRev->audienceCan(
155 RevisionRecord::DELETED_TEXT, RevisionRecord::FOR_THIS_USER, $this->getUser()
157 $this->
dieWithError( [
'apierror-missingcontent-revid', $toRev->getId() ],
'missingcontent' );
162 if ( $fromRelRev && $fromRelRev->getPageAsLinkTarget() ) {
163 $context->setTitle( Title::newFromLinkTarget( $fromRelRev->getPageAsLinkTarget() ) );
164 } elseif ( $toRelRev && $toRelRev->getPageAsLinkTarget() ) {
165 $context->setTitle( Title::newFromLinkTarget( $toRelRev->getPageAsLinkTarget() ) );
173 $de->setRevisions( $fromRev, $toRev );
174 if ( $params[
'slots'] ===
null ) {
175 $difftext = $de->getDiffBody();
176 if ( $difftext ===
false ) {
181 foreach ( $params[
'slots'] as $role ) {
182 $difftext[$role] = $de->getDiffBodyForRole( $role );
188 $this->
setVals( $vals,
'from', $fromValsRev );
189 $this->
setVals( $vals,
'to', $toValsRev );
191 if ( isset( $this->props[
'rel'] ) ) {
193 $rev = $this->revisionStore->getPreviousRevision( $fromRev );
195 $vals[
'prev'] = $rev->getId();
199 $rev = $this->revisionStore->getNextRevision( $toRev );
201 $vals[
'next'] = $rev->getId();
206 if ( isset( $this->props[
'diffsize'] ) ) {
207 $vals[
'diffsize'] = 0;
208 foreach ( (array)$difftext as $text ) {
209 $vals[
'diffsize'] += strlen( $text );
212 if ( isset( $this->props[
'diff'] ) ) {
213 if ( is_array( $difftext ) ) {
215 $vals[
'bodies'] = $difftext;
236 $rev = $this->revisionStore->getRevisionById( $id );
238 ->userHasAnyRight( $this->
getUser(),
'deletedtext',
'undelete' )
241 $arQuery = $this->revisionStore->getArchiveQueryInfo();
242 $row = $this->
getDB()->selectRow(
246 [
'ar_namespace',
'ar_title' ]
248 [
'ar_rev_id' => $id ],
254 $rev = $this->revisionStore->newRevisionFromArchiveRow( $row );
256 $rev->isArchive =
true;
268 if ( $this->guessedTitle !==
false ) {
272 $this->guessedTitle =
null;
275 foreach ( [
'from',
'to' ] as $prefix ) {
276 if ( $params[
"{$prefix}rev"] !==
null ) {
279 $this->guessedTitle = Title::newFromLinkTarget( $rev->getPageAsLinkTarget() );
284 if ( $params[
"{$prefix}title"] !==
null ) {
285 $title = Title::newFromText( $params[
"{$prefix}title"] );
287 $this->guessedTitle =
$title;
292 if ( $params[
"{$prefix}id"] !==
null ) {
293 $title = Title::newFromID( $params[
"{$prefix}id"] );
295 $this->guessedTitle =
$title;
313 foreach ( [
'from',
'to' ] as $prefix ) {
314 if ( $params[
"{$prefix}rev"] !==
null ) {
316 if ( $rev && $rev->hasSlot( $role ) ) {
317 return $rev->getSlot( $role, RevisionRecord::RAW )->getModel();
324 return $this->slotRoleRegistry->getRoleHandler( $role )->getDefaultModel(
$guessedTitle );
327 if ( isset( $params[
"fromcontentmodel-$role"] ) ) {
328 return $params[
"fromcontentmodel-$role"];
330 if ( isset( $params[
"tocontentmodel-$role"] ) ) {
331 return $params[
"tocontentmodel-$role"];
334 if ( $role === SlotRecord::MAIN ) {
335 if ( isset( $params[
'fromcontentmodel'] ) ) {
336 return $params[
'fromcontentmodel'];
338 if ( isset( $params[
'tocontentmodel'] ) ) {
339 return $params[
'tocontentmodel'];
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"];
375 $suppliedContent = $params[
"{$prefix}slots"] !==
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"] );
390 $title = Title::newFromID( $params[
"{$prefix}id"] );
392 $this->
dieWithError( [
'apierror-nosuchpageid', $params[
"{$prefix}id"] ] );
395 $revId =
$title->getLatestRevID();
399 if ( !$suppliedContent ) {
413 if ( $revId !==
null ) {
416 $this->
dieWithError( [
'apierror-nosuchrevid', $revId ] );
418 $title = Title::newFromLinkTarget( $rev->getPageAsLinkTarget() );
422 if ( !$suppliedContent ) {
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,
433 [
'apierror-missingcontent-revid-role', $rev->getId(), SlotRecord::MAIN ],
'missingcontent'
439 [
"apierror-compare-nosuch{$prefix}section",
wfEscapeWikiText( $section ) ],
440 "nosuch{$prefix}section"
443 $newRev->setContent( SlotRecord::MAIN,
$content );
446 return [ $newRev, $rev, $rev ];
455 $newRev = MutableRevisionRecord::newFromParentRevision( $rev );
457 $newRev = $this->revisionStore->newMutableRevisionFromArray( [
458 'title' =>
$title ?: Title::makeTitle(
NS_SPECIAL,
'Badtitle/' . __METHOD__ )
461 foreach ( $params[
"{$prefix}slots"] as $role ) {
462 $text = $params[
"{$prefix}text-{$role}"];
463 if ( $text ===
null ) {
465 if ( $role === SlotRecord::MAIN ) {
466 $this->
dieWithError( [
'apierror-compare-maintextrequired', $prefix ] );
471 foreach ( [
'section',
'contentmodel',
'contentformat' ] as $param ) {
472 if ( isset( $params[
"{$prefix}{$param}-{$role}"] ) ) {
474 'apierror-compare-notext',
481 $newRev->removeSlot( $role );
485 $model = $params[
"{$prefix}contentmodel-{$role}"];
486 $format = $params[
"{$prefix}contentformat-{$role}"];
488 if ( !$model && $rev && $rev->hasSlot( $role ) ) {
489 $model = $rev->getSlot( $role, RevisionRecord::RAW )->getModel();
491 if ( !$model &&
$title && $role === SlotRecord::MAIN ) {
493 $model =
$title->getContentModel();
500 $this->
addWarning( [
'apiwarn-compare-nocontentmodel', $model ] );
504 $content = ContentHandler::makeContent( $text,
$title, $model, $format );
507 'wrap' =>
ApiMessage::create(
'apierror-contentserializationexception',
'parseerror' )
511 if ( $params[
"{$prefix}pst"] ) {
515 $popts = ParserOptions::newFromContext( $this->
getContext() );
519 $section = $params[
"{$prefix}section-{$role}"];
520 if ( $section !==
null && $section !==
'' ) {
522 $this->
dieWithError(
"apierror-compare-no{$prefix}revision" );
524 $oldContent = $rev->getContent( $role, RevisionRecord::FOR_THIS_USER, $this->
getUser() );
525 if ( !$oldContent ) {
527 [
'apierror-missingcontent-revid-role', $rev->getId(),
wfEscapeWikiText( $role ) ],
531 if ( !$oldContent->getContentHandler()->supportsSections() ) {
536 }
catch ( Exception $ex ) {
541 $this->
dieWithError( [
'apierror-sectionreplacefailed' ] );
546 if ( $role === SlotRecord::MAIN && isset( $params[
"{$prefix}section"] ) ) {
547 $section = $params[
"{$prefix}section"];
551 [
"apierror-compare-nosuch{$prefix}section",
wfEscapeWikiText( $section ) ],
552 "nosuch{$prefix}section"
557 $newRev->setContent( $role,
$content );
559 return [ $newRev, $rev, null ];
569 private function setVals( &$vals, $prefix, $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();
576 if ( isset( $this->props[
'title'] ) ) {
579 if ( isset( $this->props[
'size'] ) ) {
580 $vals[
"{$prefix}size"] = $rev->getSize();
584 if ( $rev->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
585 $vals[
"{$prefix}texthidden"] =
true;
589 if ( $rev->isDeleted( RevisionRecord::DELETED_USER ) ) {
590 $vals[
"{$prefix}userhidden"] =
true;
593 if ( isset( $this->props[
'user'] ) ) {
594 $user = $rev->getUser( RevisionRecord::FOR_THIS_USER, $this->
getUser() );
596 $vals[
"{$prefix}user"] = $user->getName();
597 $vals[
"{$prefix}userid"] = $user->getId();
601 if ( $rev->isDeleted( RevisionRecord::DELETED_COMMENT ) ) {
602 $vals[
"{$prefix}commenthidden"] =
true;
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;
618 $this->
getMain()->setCacheMode(
'private' );
619 if ( $rev->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ) {
620 $vals[
"{$prefix}suppressed"] =
true;
625 if ( !empty( $rev->isArchive ) ) {
626 $this->
getMain()->setCacheMode(
'private' );
627 $vals[
"{$prefix}archive"] =
true;
633 $slotRoles = $this->slotRoleRegistry->getKnownRoles();
634 sort( $slotRoles, SORT_STRING );
654 'section-{slot}' => [
658 'contentformat-{slot}' => [
662 'contentmodel-{slot}' => [
687 foreach ( $fromToParams as $k => $v ) {
693 foreach ( $fromToParams as $k => $v ) {
734 'action=compare&fromrev=1&torev=2'
735 =>
'apihelp-compare-example-1',
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.
const PARAM_DEPRECATED
(boolean) Is the parameter deprecated (will show a warning)?
getDB()
Gets a default replica DB connection object.
dieWithError( $msg, $code=null, $data=null, $httpCode=null)
Abort execution with an error.
getMain()
Get the main module.
const PARAM_TYPE
(string|string[]) Either an array of allowed value strings, or a string type as described below.
const PARAM_DFLT
(null|boolean|integer|string) Default value of the parameter.
getPermissionManager()
Obtain a PermissionManager instance that subclasses may use in their authorization checks.
const PARAM_HELP_MSG_PER_VALUE
((string|array|Message)[]) When PARAM_TYPE is an array, this is an array mapping those values to $msg...
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,...
requireMaxOneParameter( $params, $required)
Die if more than one of a certain set of parameters is set and not false.
dieWithException( $exception, array $options=[])
Abort execution with an error derived from an exception.
addWarning( $msg, $code=null, $data=null)
Add a warning for this module.
requireAtLeastOneParameter( $params, $required)
Die if none of a certain set of parameters is set and not false.
const PARAM_ALL
(boolean|string) When PARAM_TYPE has a defined set of values and PARAM_ISMULTI is true,...
getModuleName()
Get the name of the module being executed by this instance.
const PARAM_ISMULTI
(boolean) Accept multiple pipe-separated values for this parameter (e.g.
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.
static create( $msg, $code=null, array $data=null)
Create an IApiMessage for the message.
static addTitleInfo(&$arr, $title, $prefix='')
Add information (title and namespace) about a Title object to a result array.
static setArrayType(array &$arr, $type, $kvpKeyName=null)
Set the array data type.
const NO_SIZE_CHECK
For addValue() and similar functions, do not check size while adding a value Don't use this unless yo...
static setContentValue(array &$arr, $name, $value, $flags=0)
Add an output value to the array by name and mark as META_CONTENT.
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.
const CONTENT_MODEL_WIKITEXT