* action=query&prop=deletedrevisions, action=query&list=allrevisions, and
action=query&list=alldeletedrevisions are changed similarly to
&prop=revisions (see the three previous items).
+* (T174032) action=compare now supports multi-content revisions.
+ * It has a 'slots' parameter to select diffing of individual slots. The
+ default behavior is to return one combined diff.
+ * The 'fromtext', 'fromsection', 'fromcontentmodel', 'fromcontentformat',
+ 'totext', 'tosection', 'tocontentmodel', and 'tocontentformat' parameters
+ are deprecated. Specify the new 'fromslots' and 'toslots' to identify which
+ slots have text supplied and the corresponding templated parameters for
+ each slot.
+ * The behavior of 'fromsection' and 'tosection' of extracting one section's
+ content is not being preserved. 'fromsection-{slot}' and 'tosection-{slot}'
+ instead expand the given text as if for a section edit. This effectively
+ declines T183823 in favor of T185723.
=== Action API internal changes in 1.32 ===
* Added 'ApiParseMakeOutputPage' hook.
* @file
*/
+use MediaWiki\MediaWikiServices;
+use MediaWiki\Storage\MutableRevisionRecord;
+use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Storage\RevisionStore;
+
class ApiComparePages extends ApiBase {
- private $guessed = false, $guessedTitle, $guessedModel, $props;
+ /** @var RevisionStore */
+ private $revisionStore;
+
+ private $guessedTitle = false, $props;
+
+ public function __construct( ApiMain $mainModule, $moduleName, $modulePrefix = '' ) {
+ parent::__construct( $mainModule, $moduleName, $modulePrefix );
+ $this->revisionStore = MediaWikiServices::getInstance()->getRevisionStore();
+ }
public function execute() {
$params = $this->extractRequestParams();
// Parameter validation
- $this->requireAtLeastOneParameter( $params, 'fromtitle', 'fromid', 'fromrev', 'fromtext' );
- $this->requireAtLeastOneParameter( $params, 'totitle', 'toid', 'torev', 'totext', 'torelative' );
+ $this->requireAtLeastOneParameter(
+ $params, 'fromtitle', 'fromid', 'fromrev', 'fromtext', 'fromslots'
+ );
+ $this->requireAtLeastOneParameter(
+ $params, 'totitle', 'toid', 'torev', 'totext', 'torelative', 'toslots'
+ );
$this->props = array_flip( $params['prop'] );
// Cache responses publicly by default. This may be overridden later.
$this->getMain()->setCacheMode( 'public' );
- // Get the 'from' Revision and Content
- list( $fromRev, $fromContent, $relRev ) = $this->getDiffContent( 'from', $params );
+ // Get the 'from' RevisionRecord
+ list( $fromRev, $fromRelRev, $fromValsRev ) = $this->getDiffRevision( 'from', $params );
- // Get the 'to' Revision and Content
+ // Get the 'to' RevisionRecord
if ( $params['torelative'] !== null ) {
- if ( !$relRev ) {
+ if ( !$fromRelRev ) {
$this->dieWithError( 'apierror-compare-relative-to-nothing' );
}
switch ( $params['torelative'] ) {
case 'prev':
// Swap 'from' and 'to'
- $toRev = $fromRev;
- $toContent = $fromContent;
- $fromRev = $relRev->getPrevious();
- $fromContent = $fromRev
- ? $fromRev->getContent( Revision::FOR_THIS_USER, $this->getUser() )
- : $toContent->getContentHandler()->makeEmptyContent();
- if ( !$fromContent ) {
- $this->dieWithError(
- [ 'apierror-missingcontent-revid', $fromRev->getId() ], 'missingcontent'
- );
- }
+ list( $toRev, $toRelRev2, $toValsRev ) = [ $fromRev, $fromRelRev, $fromValsRev ];
+ $fromRev = $this->revisionStore->getPreviousRevision( $fromRelRev );
+ $fromRelRev = $fromRev;
+ $fromValsRev = $fromRev;
break;
case 'next':
- $toRev = $relRev->getNext();
- $toContent = $toRev
- ? $toRev->getContent( Revision::FOR_THIS_USER, $this->getUser() )
- : $fromContent;
- if ( !$toContent ) {
- $this->dieWithError( [ 'apierror-missingcontent-revid', $toRev->getId() ], 'missingcontent' );
- }
+ $toRev = $this->revisionStore->getNextRevision( $fromRelRev );
+ $toRelRev = $toRev;
+ $toValsRev = $toRev;
break;
case 'cur':
- $title = $relRev->getTitle();
- $id = $title->getLatestRevID();
- $toRev = $id ? Revision::newFromId( $id ) : null;
+ $title = $fromRelRev->getPageAsLinkTarget();
+ $toRev = $this->revisionStore->getRevisionByTitle( $title );
if ( !$toRev ) {
+ $title = Title::newFromLinkTarget( $title );
$this->dieWithError(
[ 'apierror-missingrev-title', wfEscapeWikiText( $title->getPrefixedText() ) ], 'nosuchrevid'
);
}
- $toContent = $toRev->getContent( Revision::FOR_THIS_USER, $this->getUser() );
- if ( !$toContent ) {
- $this->dieWithError( [ 'apierror-missingcontent-revid', $toRev->getId() ], 'missingcontent' );
- }
+ $toRelRev = $toRev;
+ $toValsRev = $toRev;
break;
}
- $relRev2 = null;
} else {
- list( $toRev, $toContent, $relRev2 ) = $this->getDiffContent( 'to', $params );
+ list( $toRev, $toRelRev, $toValsRev ) = $this->getDiffRevision( 'to', $params );
}
- // Should never happen, but just in case...
- if ( !$fromContent || !$toContent ) {
+ // Handle missing from or to revisions
+ if ( !$fromRev || !$toRev ) {
$this->dieWithError( 'apierror-baddiff' );
}
- // Extract sections, if told to
- if ( isset( $params['fromsection'] ) ) {
- $fromContent = $fromContent->getSection( $params['fromsection'] );
- if ( !$fromContent ) {
- $this->dieWithError(
- [ 'apierror-compare-nosuchfromsection', wfEscapeWikiText( $params['fromsection'] ) ],
- 'nosuchfromsection'
- );
- }
+ // Handle revdel
+ if ( !$fromRev->audienceCan(
+ RevisionRecord::DELETED_TEXT, RevisionRecord::FOR_THIS_USER, $this->getUser()
+ ) ) {
+ $this->dieWithError( [ 'apierror-missingcontent-revid', $fromRev->getId() ], 'missingcontent' );
}
- if ( isset( $params['tosection'] ) ) {
- $toContent = $toContent->getSection( $params['tosection'] );
- if ( !$toContent ) {
- $this->dieWithError(
- [ 'apierror-compare-nosuchtosection', wfEscapeWikiText( $params['tosection'] ) ],
- 'nosuchtosection'
- );
- }
+ if ( !$toRev->audienceCan(
+ RevisionRecord::DELETED_TEXT, RevisionRecord::FOR_THIS_USER, $this->getUser()
+ ) ) {
+ $this->dieWithError( [ 'apierror-missingcontent-revid', $toRev->getId() ], 'missingcontent' );
}
// Get the diff
$context = new DerivativeContext( $this->getContext() );
- if ( $relRev && $relRev->getTitle() ) {
- $context->setTitle( $relRev->getTitle() );
- } elseif ( $relRev2 && $relRev2->getTitle() ) {
- $context->setTitle( $relRev2->getTitle() );
+ if ( $fromRelRev && $fromRelRev->getPageAsLinkTarget() ) {
+ $context->setTitle( Title::newFromLinkTarget( $fromRelRev->getPageAsLinkTarget() ) );
+ } elseif ( $toRelRev && $toRelRev->getPageAsLinkTarget() ) {
+ $context->setTitle( Title::newFromLinkTarget( $toRelRev->getPageAsLinkTarget() ) );
} else {
- $this->guessTitleAndModel();
- if ( $this->guessedTitle ) {
- $context->setTitle( $this->guessedTitle );
+ $guessedTitle = $this->guessTitle();
+ if ( $guessedTitle ) {
+ $context->setTitle( $guessedTitle );
}
}
- $de = $fromContent->getContentHandler()->createDifferenceEngine(
- $context,
- $fromRev ? $fromRev->getId() : 0,
- $toRev ? $toRev->getId() : 0,
- /* $rcid = */ null,
- /* $refreshCache = */ false,
- /* $unhide = */ true
- );
- $de->setContent( $fromContent, $toContent );
- $difftext = $de->getDiffBody();
- if ( $difftext === false ) {
- $this->dieWithError( 'apierror-baddiff' );
+ $de = new DifferenceEngine( $context );
+ $de->setRevisions( $fromRev, $toRev );
+ if ( $params['slots'] === null ) {
+ $difftext = $de->getDiffBody();
+ if ( $difftext === false ) {
+ $this->dieWithError( 'apierror-baddiff' );
+ }
+ } else {
+ $difftext = [];
+ foreach ( $params['slots'] as $role ) {
+ $difftext[$role] = $de->getDiffBodyForRole( $role );
+ }
}
// Fill in the response
$vals = [];
- $this->setVals( $vals, 'from', $fromRev );
- $this->setVals( $vals, 'to', $toRev );
+ $this->setVals( $vals, 'from', $fromValsRev );
+ $this->setVals( $vals, 'to', $toValsRev );
if ( isset( $this->props['rel'] ) ) {
- if ( $fromRev ) {
- $rev = $fromRev->getPrevious();
+ if ( !$fromRev instanceof MutableRevisionRecord ) {
+ $rev = $this->revisionStore->getPreviousRevision( $fromRev );
if ( $rev ) {
$vals['prev'] = $rev->getId();
}
}
- if ( $toRev ) {
- $rev = $toRev->getNext();
+ if ( !$toRev instanceof MutableRevisionRecord ) {
+ $rev = $this->revisionStore->getNextRevision( $toRev );
if ( $rev ) {
$vals['next'] = $rev->getId();
}
}
if ( isset( $this->props['diffsize'] ) ) {
- $vals['diffsize'] = strlen( $difftext );
+ $vals['diffsize'] = 0;
+ foreach ( (array)$difftext as $text ) {
+ $vals['diffsize'] += strlen( $text );
+ }
}
if ( isset( $this->props['diff'] ) ) {
- ApiResult::setContentValue( $vals, 'body', $difftext );
+ if ( is_array( $difftext ) ) {
+ ApiResult::setArrayType( $difftext, 'kvp', 'diff' );
+ $vals['bodies'] = $difftext;
+ } else {
+ ApiResult::setContentValue( $vals, 'body', $difftext );
+ }
}
// Diffs can be really big and there's little point in having
}
/**
- * Guess an appropriate default Title and content model for this request
+ * Load a revision by ID
*
- * Fills in $this->guessedTitle based on the first of 'fromrev',
- * 'fromtitle', 'fromid', 'torev', 'totitle', and 'toid' that's present and
- * valid.
+ * Falls back to checking the archive table if appropriate.
+ *
+ * @param int $id
+ * @return RevisionRecord|null
+ */
+ private function getRevisionById( $id ) {
+ $rev = $this->revisionStore->getRevisionById( $id );
+ if ( !$rev && $this->getUser()->isAllowedAny( 'deletedtext', 'undelete' ) ) {
+ // Try the 'archive' table
+ $arQuery = $this->revisionStore->getArchiveQueryInfo();
+ $row = $this->getDB()->selectRow(
+ $arQuery['tables'],
+ array_merge(
+ $arQuery['fields'],
+ [ 'ar_namespace', 'ar_title' ]
+ ),
+ [ 'ar_rev_id' => $id ],
+ __METHOD__,
+ [],
+ $arQuery['joins']
+ );
+ if ( $row ) {
+ $rev = $this->revisionStore->newRevisionFromArchiveRow( $row );
+ $rev->isArchive = true;
+ }
+ }
+ return $rev;
+ }
+
+ /**
+ * Guess an appropriate default Title for this request
*
- * Fills in $this->guessedModel based on the Revision or Title used to
- * determine $this->guessedTitle, or the 'fromcontentmodel' or
- * 'tocontentmodel' parameters if no title was guessed.
+ * @return Title|null
*/
- private function guessTitleAndModel() {
- if ( $this->guessed ) {
- return;
+ private function guessTitle() {
+ if ( $this->guessedTitle !== false ) {
+ return $this->guessedTitle;
}
- $this->guessed = true;
+ $this->guessedTitle = null;
$params = $this->extractRequestParams();
foreach ( [ 'from', 'to' ] as $prefix ) {
if ( $params["{$prefix}rev"] !== null ) {
- $revId = $params["{$prefix}rev"];
- $rev = Revision::newFromId( $revId );
- if ( !$rev ) {
- // Titles of deleted revisions aren't secret, per T51088
- $arQuery = Revision::getArchiveQueryInfo();
- $row = $this->getDB()->selectRow(
- $arQuery['tables'],
- array_merge(
- $arQuery['fields'],
- [ 'ar_namespace', 'ar_title' ]
- ),
- [ 'ar_rev_id' => $revId ],
- __METHOD__,
- [],
- $arQuery['joins']
- );
- if ( $row ) {
- $rev = Revision::newFromArchiveRow( $row );
- }
- }
+ $rev = $this->getRevisionById( $params["{$prefix}rev"] );
if ( $rev ) {
- $this->guessedTitle = $rev->getTitle();
- $this->guessedModel = $rev->getContentModel();
+ $this->guessedTitle = Title::newFromLinkTarget( $rev->getPageAsLinkTarget() );
break;
}
}
}
}
- if ( !$this->guessedModel ) {
- if ( $this->guessedTitle ) {
- $this->guessedModel = $this->guessedTitle->getContentModel();
- } elseif ( $params['fromcontentmodel'] !== null ) {
- $this->guessedModel = $params['fromcontentmodel'];
- } elseif ( $params['tocontentmodel'] !== null ) {
- $this->guessedModel = $params['tocontentmodel'];
+ return $this->guessedTitle;
+ }
+
+ /**
+ * Guess an appropriate default content model for this request
+ * @param string $role Slot for which to guess the model
+ * @return string|null Guessed content model
+ */
+ private function guessModel( $role ) {
+ $params = $this->extractRequestParams();
+
+ $title = null;
+ foreach ( [ 'from', 'to' ] as $prefix ) {
+ if ( $params["{$prefix}rev"] !== null ) {
+ $rev = $this->getRevisionById( $params["{$prefix}rev"] );
+ if ( $rev ) {
+ if ( $rev->hasSlot( $role ) ) {
+ return $rev->getSlot( $role, RevisionRecord::RAW )->getModel();
+ }
+ }
+ }
+ }
+
+ $guessedTitle = $this->guessTitle();
+ if ( $guessedTitle && $role === 'main' ) {
+ // @todo: Use SlotRoleRegistry and do this for all slots
+ return $guessedTitle->getContentModel();
+ }
+
+ if ( isset( $params["fromcontentmodel-$role"] ) ) {
+ return $params["fromcontentmodel-$role"];
+ }
+ if ( isset( $params["tocontentmodel-$role"] ) ) {
+ return $params["tocontentmodel-$role"];
+ }
+
+ if ( $role === 'main' ) {
+ if ( isset( $params['fromcontentmodel'] ) ) {
+ return $params['fromcontentmodel'];
+ }
+ if ( isset( $params['tocontentmodel'] ) ) {
+ return $params['tocontentmodel'];
}
}
+
+ return null;
}
/**
- * Get the Revision and Content for one side of the diff
+ * Get the RevisionRecord for one side of the diff
*
- * This uses the appropriate set of 'rev', 'id', 'title', 'text', 'pst',
- * 'contentmodel', and 'contentformat' parameters to determine what content
+ * This uses the appropriate set of parameters to determine what content
* should be diffed.
*
* Returns three values:
- * - The revision used to retrieve the content, if any
- * - The content to be diffed
- * - The revision specified, if any, even if not used to retrieve the
- * Content
+ * - A RevisionRecord holding the content
+ * - The revision specified, if any, even if content was supplied
+ * - The revision to pass to setVals(), if any
*
* @param string $prefix 'from' or 'to'
* @param array $params
- * @return array [ Revision|null, Content, Revision|null ]
+ * @return array [ RevisionRecord|null, RevisionRecord|null, RevisionRecord|null ]
*/
- private function getDiffContent( $prefix, array $params ) {
+ private function getDiffRevision( $prefix, array $params ) {
+ // Back compat params
+ $this->requireMaxOneParameter( $params, "{$prefix}text", "{$prefix}slots" );
+ $this->requireMaxOneParameter( $params, "{$prefix}section", "{$prefix}slots" );
+ if ( $params["{$prefix}text"] !== null ) {
+ $params["{$prefix}slots"] = [ 'main' ];
+ $params["{$prefix}text-main"] = $params["{$prefix}text"];
+ $params["{$prefix}section-main"] = null;
+ $params["{$prefix}contentmodel-main"] = $params["{$prefix}contentmodel"];
+ $params["{$prefix}contentformat-main"] = $params["{$prefix}contentformat"];
+ }
+
$title = null;
$rev = null;
- $suppliedContent = $params["{$prefix}text"] !== null;
+ $suppliedContent = $params["{$prefix}slots"] !== null;
// Get the revision and title, if applicable
$revId = null;
}
}
if ( $revId !== null ) {
- $rev = Revision::newFromId( $revId );
- if ( !$rev && $this->getUser()->isAllowedAny( 'deletedtext', 'undelete' ) ) {
- // Try the 'archive' table
- $arQuery = Revision::getArchiveQueryInfo();
- $row = $this->getDB()->selectRow(
- $arQuery['tables'],
- array_merge(
- $arQuery['fields'],
- [ 'ar_namespace', 'ar_title' ]
- ),
- [ 'ar_rev_id' => $revId ],
- __METHOD__,
- [],
- $arQuery['joins']
- );
- if ( $row ) {
- $rev = Revision::newFromArchiveRow( $row );
- $rev->isArchive = true;
- }
- }
+ $rev = $this->getRevisionById( $revId );
if ( !$rev ) {
$this->dieWithError( [ 'apierror-nosuchrevid', $revId ] );
}
- $title = $rev->getTitle();
+ $title = Title::newFromLinkTarget( $rev->getPageAsLinkTarget() );
// If we don't have supplied content, return here. Otherwise,
// continue on below with the supplied content.
if ( !$suppliedContent ) {
- $content = $rev->getContent( Revision::FOR_THIS_USER, $this->getUser() );
- if ( !$content ) {
- $this->dieWithError( [ 'apierror-missingcontent-revid', $revId ], 'missingcontent' );
+ $newRev = $rev;
+
+ // Deprecated 'fromsection'/'tosection'
+ if ( isset( $params["{$prefix}section"] ) ) {
+ $section = $params["{$prefix}section"];
+ $newRev = MutableRevisionRecord::newFromParentRevision( $rev );
+ $content = $rev->getContent( 'main', RevisionRecord::FOR_THIS_USER, $this->getUser() );
+ if ( !$content ) {
+ $this->dieWithError(
+ [ 'apierror-missingcontent-revid-role', $rev->getId(), 'main' ], 'missingcontent'
+ );
+ }
+ $content = $content ? $content->getSection( $section ) : null;
+ if ( !$content ) {
+ $this->dieWithError(
+ [ "apierror-compare-nosuch{$prefix}section", wfEscapeWikiText( $section ) ],
+ "nosuch{$prefix}section"
+ );
+ }
+ $newRev->setContent( 'main', $content );
}
- return [ $rev, $content, $rev ];
+
+ return [ $newRev, $rev, $rev ];
}
}
// Override $content based on supplied text
- $model = $params["{$prefix}contentmodel"];
- $format = $params["{$prefix}contentformat"];
-
- if ( !$model && $rev ) {
- $model = $rev->getContentModel();
- }
- if ( !$model && $title ) {
- $model = $title->getContentModel();
- }
- if ( !$model ) {
- $this->guessTitleAndModel();
- $model = $this->guessedModel;
- }
- if ( !$model ) {
- $model = CONTENT_MODEL_WIKITEXT;
- $this->addWarning( [ 'apiwarn-compare-nocontentmodel', $model ] );
- }
-
if ( !$title ) {
- $this->guessTitleAndModel();
- $title = $this->guessedTitle;
+ $title = $this->guessTitle();
}
-
- try {
- $content = ContentHandler::makeContent( $params["{$prefix}text"], $title, $model, $format );
- } catch ( MWContentSerializationException $ex ) {
- $this->dieWithException( $ex, [
- 'wrap' => ApiMessage::create( 'apierror-contentserializationexception', 'parseerror' )
+ if ( $rev ) {
+ $newRev = MutableRevisionRecord::newFromParentRevision( $rev );
+ } else {
+ $newRev = $this->revisionStore->newMutableRevisionFromArray( [
+ 'title' => $title ?: Title::makeTitle( NS_SPECIAL, 'Badtitle/' . __METHOD__ )
] );
}
+ foreach ( $params["{$prefix}slots"] as $role ) {
+ $text = $params["{$prefix}text-{$role}"];
+ if ( $text === null ) {
+ $newRev->removeSlot( $role );
+ continue;
+ }
+
+ $model = $params["{$prefix}contentmodel-{$role}"];
+ $format = $params["{$prefix}contentformat-{$role}"];
- if ( $params["{$prefix}pst"] ) {
- if ( !$title ) {
- $this->dieWithError( 'apierror-compare-no-title' );
+ if ( !$model && $rev && $rev->hasSlot( $role ) ) {
+ $model = $rev->getSlot( $role, RevisionRecord::RAW )->getModel();
+ }
+ if ( !$model && $title && $role === 'main' ) {
+ // @todo: Use SlotRoleRegistry and do this for all slots
+ $model = $title->getContentModel();
+ }
+ if ( !$model ) {
+ $model = $this->guessModel( $role );
+ }
+ if ( !$model ) {
+ $model = CONTENT_MODEL_WIKITEXT;
+ $this->addWarning( [ 'apiwarn-compare-nocontentmodel', $model ] );
+ }
+
+ try {
+ $content = ContentHandler::makeContent( $text, $title, $model, $format );
+ } catch ( MWContentSerializationException $ex ) {
+ $this->dieWithException( $ex, [
+ 'wrap' => ApiMessage::create( 'apierror-contentserializationexception', 'parseerror' )
+ ] );
+ }
+
+ if ( $params["{$prefix}pst"] ) {
+ if ( !$title ) {
+ $this->dieWithError( 'apierror-compare-no-title' );
+ }
+ $popts = ParserOptions::newFromContext( $this->getContext() );
+ $content = $content->preSaveTransform( $title, $this->getUser(), $popts );
+ }
+
+ $section = $params["{$prefix}section-{$role}"];
+ if ( $section !== null && $section !== '' ) {
+ if ( !$rev ) {
+ $this->dieWithError( "apierror-compare-no{$prefix}revision" );
+ }
+ $oldContent = $rev->getContent( $role, RevisionRecord::FOR_THIS_USER, $this->getUser() );
+ if ( !$oldContent ) {
+ $this->dieWithError(
+ [ 'apierror-missingcontent-revid-role', $rev->getId(), wfEscapeWikiText( $role ) ],
+ 'missingcontent'
+ );
+ }
+ if ( !$oldContent->getContentHandler()->supportsSections() ) {
+ $this->dieWithError( [ 'apierror-sectionsnotsupported', $content->getModel() ] );
+ }
+ try {
+ $content = $oldContent->replaceSection( $section, $content, '' );
+ } catch ( Exception $ex ) {
+ // Probably a content model mismatch.
+ $content = null;
+ }
+ if ( !$content ) {
+ $this->dieWithError( [ 'apierror-sectionreplacefailed' ] );
+ }
+ }
+
+ // Deprecated 'fromsection'/'tosection'
+ if ( $role === 'main' && isset( $params["{$prefix}section"] ) ) {
+ $section = $params["{$prefix}section"];
+ $content = $content->getSection( $section );
+ if ( !$content ) {
+ $this->dieWithError(
+ [ "apierror-compare-nosuch{$prefix}section", wfEscapeWikiText( $section ) ],
+ "nosuch{$prefix}section"
+ );
+ }
}
- $popts = ParserOptions::newFromContext( $this->getContext() );
- $content = $content->preSaveTransform( $title, $this->getUser(), $popts );
- }
- return [ null, $content, $rev ];
+ $newRev->setContent( $role, $content );
+ }
+ return [ $newRev, $rev, null ];
}
/**
- * Set value fields from a Revision object
+ * Set value fields from a RevisionRecord object
+ *
* @param array &$vals Result array to set data into
* @param string $prefix 'from' or 'to'
- * @param Revision|null $rev
+ * @param RevisionRecord|null $rev
*/
private function setVals( &$vals, $prefix, $rev ) {
if ( $rev ) {
- $title = $rev->getTitle();
+ $title = $rev->getPageAsLinkTarget();
if ( isset( $this->props['ids'] ) ) {
$vals["{$prefix}id"] = $title->getArticleID();
$vals["{$prefix}revid"] = $rev->getId();
}
$anyHidden = false;
- if ( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
+ if ( $rev->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
$vals["{$prefix}texthidden"] = true;
$anyHidden = true;
}
- if ( $rev->isDeleted( Revision::DELETED_USER ) ) {
+ if ( $rev->isDeleted( RevisionRecord::DELETED_USER ) ) {
$vals["{$prefix}userhidden"] = true;
$anyHidden = true;
}
- if ( isset( $this->props['user'] ) &&
- $rev->userCan( Revision::DELETED_USER, $this->getUser() )
- ) {
- $vals["{$prefix}user"] = $rev->getUserText( Revision::RAW );
- $vals["{$prefix}userid"] = $rev->getUser( Revision::RAW );
+ if ( isset( $this->props['user'] ) ) {
+ $user = $rev->getUser( RevisionRecord::FOR_THIS_USER, $this->getUser() );
+ if ( $user ) {
+ $vals["{$prefix}user"] = $user->getName();
+ $vals["{$prefix}userid"] = $user->getId();
+ }
}
- if ( $rev->isDeleted( Revision::DELETED_COMMENT ) ) {
+ if ( $rev->isDeleted( RevisionRecord::DELETED_COMMENT ) ) {
$vals["{$prefix}commenthidden"] = true;
$anyHidden = true;
}
- if ( $rev->userCan( Revision::DELETED_COMMENT, $this->getUser() ) ) {
- if ( isset( $this->props['comment'] ) ) {
- $vals["{$prefix}comment"] = $rev->getComment( Revision::RAW );
- }
- if ( isset( $this->props['parsedcomment'] ) ) {
+ if ( isset( $this->props['comment'] ) || isset( $this->props['parsedcomment'] ) ) {
+ $comment = $rev->getComment( RevisionRecord::FOR_THIS_USER, $this->getUser() );
+ if ( $comment !== null ) {
+ if ( isset( $this->props['comment'] ) ) {
+ $vals["{$prefix}comment"] = $comment->text;
+ }
$vals["{$prefix}parsedcomment"] = Linker::formatComment(
- $rev->getComment( Revision::RAW ),
- $rev->getTitle()
+ $comment->text, Title::newFromLinkTarget( $title )
);
}
}
if ( $anyHidden ) {
$this->getMain()->setCacheMode( 'private' );
- if ( $rev->isDeleted( Revision::DELETED_RESTRICTED ) ) {
+ if ( $rev->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ) {
$vals["{$prefix}suppressed"] = true;
}
}
}
public function getAllowedParams() {
+ $slotRoles = MediaWikiServices::getInstance()->getSlotRoleStore()->getMap();
+ if ( !in_array( 'main', $slotRoles, true ) ) {
+ $slotRoles[] = 'main';
+ }
+ sort( $slotRoles, SORT_STRING );
+
// Parameters for the 'from' and 'to' content
$fromToParams = [
'title' => null,
'rev' => [
ApiBase::PARAM_TYPE => 'integer'
],
- 'text' => [
- ApiBase::PARAM_TYPE => 'text'
+
+ 'slots' => [
+ ApiBase::PARAM_TYPE => $slotRoles,
+ ApiBase::PARAM_ISMULTI => true,
+ ],
+ 'text-{slot}' => [
+ ApiBase::PARAM_TEMPLATE_VARS => [ 'slot' => 'slots' ], // fixed below
+ ApiBase::PARAM_TYPE => 'text',
+ ],
+ 'section-{slot}' => [
+ ApiBase::PARAM_TEMPLATE_VARS => [ 'slot' => 'slots' ], // fixed below
+ ApiBase::PARAM_TYPE => 'string',
+ ],
+ 'contentformat-{slot}' => [
+ ApiBase::PARAM_TEMPLATE_VARS => [ 'slot' => 'slots' ], // fixed below
+ ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
+ ],
+ 'contentmodel-{slot}' => [
+ ApiBase::PARAM_TEMPLATE_VARS => [ 'slot' => 'slots' ], // fixed below
+ ApiBase::PARAM_TYPE => ContentHandler::getContentModels(),
],
- 'section' => null,
'pst' => false,
+
+ 'text' => [
+ ApiBase::PARAM_TYPE => 'text',
+ ApiBase::PARAM_DEPRECATED => true,
+ ],
'contentformat' => [
ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
+ ApiBase::PARAM_DEPRECATED => true,
],
'contentmodel' => [
ApiBase::PARAM_TYPE => ContentHandler::getContentModels(),
- ]
+ ApiBase::PARAM_DEPRECATED => true,
+ ],
+ 'section' => [
+ ApiBase::PARAM_DFLT => null,
+ ApiBase::PARAM_DEPRECATED => true,
+ ],
];
$ret = [];
foreach ( $fromToParams as $k => $v ) {
+ if ( isset( $v[ApiBase::PARAM_TEMPLATE_VARS]['slot'] ) ) {
+ $v[ApiBase::PARAM_TEMPLATE_VARS]['slot'] = 'fromslots';
+ }
$ret["from$k"] = $v;
}
foreach ( $fromToParams as $k => $v ) {
+ if ( isset( $v[ApiBase::PARAM_TEMPLATE_VARS]['slot'] ) ) {
+ $v[ApiBase::PARAM_TEMPLATE_VARS]['slot'] = 'toslots';
+ }
$ret["to$k"] = $v;
}
ApiBase::PARAM_HELP_MSG_PER_VALUE => [],
];
+ $ret['slots'] = [
+ ApiBase::PARAM_TYPE => $slotRoles,
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_ALL => true,
+ ];
+
return $ret;
}
"apihelp-compare-param-fromtitle": "First title to compare.",
"apihelp-compare-param-fromid": "First page ID to compare.",
"apihelp-compare-param-fromrev": "First revision to compare.",
- "apihelp-compare-param-fromtext": "Use this text instead of the content of the revision specified by <var>fromtitle</var>, <var>fromid</var> or <var>fromrev</var>.",
+ "apihelp-compare-param-frompst": "Do a pre-save transform on <var>fromtext-{slot}</var>.",
+ "apihelp-compare-param-fromslots": "Override content of the revision specified by <var>fromtitle</var>, <var>fromid</var> or <var>fromrev</var>.\n\nThis parameter specifies the slots that are to be modified. Use <var>fromtext-{slot}</var>, <var>fromcontentmodel-{slot}</var>, and <var>fromcontentformat-{slot}</var> to specify content for each slot.",
+ "apihelp-compare-param-fromtext-{slot}": "Text of the specified slot. If omitted, the slot is removed from the revision.",
+ "apihelp-compare-param-fromsection-{slot}": "When <var>fromtext-{slot}</var> is the content of a single section, this is the section number. It will be merged into the revision specified by <var>fromtitle</var>, <var>fromid</var> or <var>fromrev</var> as if for a section edit.",
+ "apihelp-compare-param-fromcontentmodel-{slot}": "Content model of <var>fromtext-{slot}</var>. If not supplied, it will be guessed based on the other parameters.",
+ "apihelp-compare-param-fromcontentformat-{slot}": "Content serialization format of <var>fromtext-{slot}</var>.",
+ "apihelp-compare-param-fromtext": "Specify <kbd>fromslots=main</kbd> and use <var>fromtext-main</var> instead.",
+ "apihelp-compare-param-fromcontentmodel": "Specify <kbd>fromslots=main</kbd> and use <var>fromcontentmodel-main</var> instead.",
+ "apihelp-compare-param-fromcontentformat": "Specify <kbd>fromslots=main</kbd> and use <var>fromcontentformat-main</var> instead.",
"apihelp-compare-param-fromsection": "Only use the specified section of the specified 'from' content.",
- "apihelp-compare-param-frompst": "Do a pre-save transform on <var>fromtext</var>.",
- "apihelp-compare-param-fromcontentmodel": "Content model of <var>fromtext</var>. If not supplied, it will be guessed based on the other parameters.",
- "apihelp-compare-param-fromcontentformat": "Content serialization format of <var>fromtext</var>.",
"apihelp-compare-param-totitle": "Second title to compare.",
"apihelp-compare-param-toid": "Second page ID to compare.",
"apihelp-compare-param-torev": "Second revision to compare.",
"apihelp-compare-param-torelative": "Use a revision relative to the revision determined from <var>fromtitle</var>, <var>fromid</var> or <var>fromrev</var>. All of the other 'to' options will be ignored.",
- "apihelp-compare-param-totext": "Use this text instead of the content of the revision specified by <var>totitle</var>, <var>toid</var> or <var>torev</var>.",
- "apihelp-compare-param-tosection": "Only use the specified section of the specified 'to' content.",
"apihelp-compare-param-topst": "Do a pre-save transform on <var>totext</var>.",
- "apihelp-compare-param-tocontentmodel": "Content model of <var>totext</var>. If not supplied, it will be guessed based on the other parameters.",
- "apihelp-compare-param-tocontentformat": "Content serialization format of <var>totext</var>.",
+ "apihelp-compare-param-toslots": "Override content of the revision specified by <var>totitle</var>, <var>toid</var> or <var>torev</var>.\n\nThis parameter specifies the slots that are to be modified. Use <var>totext-{slot}</var>, <var>tocontentmodel-{slot}</var>, and <var>tocontentformat-{slot}</var> to specify content for each slot.",
+ "apihelp-compare-param-totext-{slot}": "Text of the specified slot. If omitted, the slot is removed from the revision.",
+ "apihelp-compare-param-tosection-{slot}": "When <var>totext-{slot}</var> is the content of a single section, this is the section number. It will be merged into the revision specified by <var>totitle</var>, <var>toid</var> or <var>torev</var> as if for a section edit.",
+ "apihelp-compare-param-toslots": "Specify content to use instead of the content of the revision specified by <var>totitle</var>, <var>toid</var> or <var>torev</var>.\n\nThis parameter specifies the slots that have content. Use <var>totext-{slot}</var>, <var>tocontentmodel-{slot}</var>, and <var>tocontentformat-{slot}</var> to specify content for each slot.",
+ "apihelp-compare-param-totext-{slot}": "Text of the specified slot.",
+ "apihelp-compare-param-tocontentmodel-{slot}": "Content model of <var>totext-{slot}</var>. If not supplied, it will be guessed based on the other parameters.",
+ "apihelp-compare-param-tocontentformat-{slot}": "Content serialization format of <var>totext-{slot}</var>.",
+ "apihelp-compare-param-totext": "Specify <kbd>toslots=main</kbd> and use <var>totext-main</var> instead.",
+ "apihelp-compare-param-tocontentmodel": "Specify <kbd>toslots=main</kbd> and use <var>tocontentmodel-main</var> instead.",
+ "apihelp-compare-param-tocontentformat": "Specify <kbd>toslots=main</kbd> and use <var>tocontentformat-main</var> instead.",
+ "apihelp-compare-param-tosection": "Only use the specified section of the specified 'to' content.",
"apihelp-compare-param-prop": "Which pieces of information to get.",
"apihelp-compare-paramvalue-prop-diff": "The diff HTML.",
"apihelp-compare-paramvalue-prop-diffsize": "The size of the diff HTML, in bytes.",
"apihelp-compare-paramvalue-prop-comment": "The comment on the 'from' and 'to' revisions.",
"apihelp-compare-paramvalue-prop-parsedcomment": "The parsed comment on the 'from' and 'to' revisions.",
"apihelp-compare-paramvalue-prop-size": "The size of the 'from' and 'to' revisions.",
+ "apihelp-compare-param-slots": "Return individual diffs for these slots, rather than one combined diff for all slots.",
"apihelp-compare-example-1": "Create a diff between revision 1 and 2.",
"apihelp-createaccount-summary": "Create a new user account.",
"apierror-compare-no-title": "Cannot pre-save transform without a title. Try specifying <var>fromtitle</var> or <var>totitle</var>.",
"apierror-compare-nosuchfromsection": "There is no section $1 in the 'from' content.",
"apierror-compare-nosuchtosection": "There is no section $1 in the 'to' content.",
+ "apierror-compare-nofromrevision": "No 'from' revision. Specify <var>fromrev</var>, <var>fromtitle</var>, or <var>fromid</var>.",
+ "apierror-compare-notorevision": "No 'to' revision. Specify <var>torev</var>, <var>totitle</var>, or <var>toid</var>.",
"apierror-compare-relative-to-nothing": "No 'from' revision for <var>torelative</var> to be relative to.",
"apierror-contentserializationexception": "Content serialization failed: $1",
"apierror-contenttoobig": "The content you supplied exceeds the article size limit of $1 {{PLURAL:$1|kilobyte|kilobytes}}.",
"apierror-mimesearchdisabled": "MIME search is disabled in Miser Mode.",
"apierror-missingcontent-pageid": "Missing content for page ID $1.",
"apierror-missingcontent-revid": "Missing content for revision ID $1.",
+ "apierror-missingcontent-revid-role": "Missing content for revision ID $1 for role $2.",
"apierror-missingparam-at-least-one-of": "{{PLURAL:$2|The parameter|At least one of the parameters}} $1 is required.",
"apierror-missingparam-one-of": "{{PLURAL:$2|The parameter|One of the parameters}} $1 is required.",
"apierror-missingparam": "The <var>$1</var> parameter must be set.",
"apihelp-clientlogin-example-login2": "{{doc-apihelp-example|clientlogin}}",
"apihelp-compare-summary": "{{doc-apihelp-summary|compare}}",
"apihelp-compare-extended-description": "{{doc-apihelp-extended-description|compare}}",
- "apihelp-compare-param-fromtitle": "{{doc-apihelp-param|compare|fromtitle}}",
+ "apihelp-compare-param-fromcontentformat": "{{doc-apihelp-param|compare|fromcontentformat}}",
+ "apihelp-compare-param-fromcontentformat-{slot}": "{{doc-apihelp-param|compare|fromcontentformat-{slot} }}",
+ "apihelp-compare-param-fromcontentmodel": "{{doc-apihelp-param|compare|fromcontentmodel}}",
+ "apihelp-compare-param-fromcontentmodel-{slot}": "{{doc-apihelp-param|compare|fromcontentmodel-{slot} }}",
"apihelp-compare-param-fromid": "{{doc-apihelp-param|compare|fromid}}",
+ "apihelp-compare-param-frompst": "{{doc-apihelp-param|compare|frompst}}",
"apihelp-compare-param-fromrev": "{{doc-apihelp-param|compare|fromrev}}",
- "apihelp-compare-param-fromtext": "{{doc-apihelp-param|compare|fromtext}}",
"apihelp-compare-param-fromsection": "{{doc-apihelp-param|compare|fromsection}}",
- "apihelp-compare-param-frompst": "{{doc-apihelp-param|compare|frompst}}",
- "apihelp-compare-param-fromcontentmodel": "{{doc-apihelp-param|compare|fromcontentmodel}}",
- "apihelp-compare-param-fromcontentformat": "{{doc-apihelp-param|compare|fromcontentformat}}",
- "apihelp-compare-param-totitle": "{{doc-apihelp-param|compare|totitle}}",
+ "apihelp-compare-param-fromsection-{slot}": "{{doc-apihelp-param|compare|fromsection-{slot} }}",
+ "apihelp-compare-param-fromslots": "{{doc-apihelp-param|compare|fromslots}}",
+ "apihelp-compare-param-fromtext": "{{doc-apihelp-param|compare|fromtext}}",
+ "apihelp-compare-param-fromtext-{slot}": "{{doc-apihelp-param|compare|fromtext-{slot} }}",
+ "apihelp-compare-param-fromtitle": "{{doc-apihelp-param|compare|fromtitle}}",
+ "apihelp-compare-param-tocontentformat": "{{doc-apihelp-param|compare|tocontentformat}}",
+ "apihelp-compare-param-tocontentformat-{slot}": "{{doc-apihelp-param|compare|tocontentformat-{slot} }}",
+ "apihelp-compare-param-tocontentmodel": "{{doc-apihelp-param|compare|tocontentmodel}}",
+ "apihelp-compare-param-tocontentmodel-{slot}": "{{doc-apihelp-param|compare|tocontentmodel-{slot} }}",
"apihelp-compare-param-toid": "{{doc-apihelp-param|compare|toid}}",
- "apihelp-compare-param-torev": "{{doc-apihelp-param|compare|torev}}",
+ "apihelp-compare-param-topst": "{{doc-apihelp-param|compare|topst}}",
"apihelp-compare-param-torelative": "{{doc-apihelp-param|compare|torelative}}",
- "apihelp-compare-param-totext": "{{doc-apihelp-param|compare|totext}}",
+ "apihelp-compare-param-torev": "{{doc-apihelp-param|compare|torev}}",
"apihelp-compare-param-tosection": "{{doc-apihelp-param|compare|tosection}}",
- "apihelp-compare-param-topst": "{{doc-apihelp-param|compare|topst}}",
- "apihelp-compare-param-tocontentmodel": "{{doc-apihelp-param|compare|tocontentmodel}}",
- "apihelp-compare-param-tocontentformat": "{{doc-apihelp-param|compare|tocontentformat}}",
+ "apihelp-compare-param-tosection-{slot}": "{{doc-apihelp-param|compare|tosection-{slot} }}",
+ "apihelp-compare-param-toslots": "{{doc-apihelp-param|compare|toslots}}",
+ "apihelp-compare-param-totext": "{{doc-apihelp-param|compare|totext}}",
+ "apihelp-compare-param-totext-{slot}": "{{doc-apihelp-param|compare|totext-{slot} }}",
+ "apihelp-compare-param-totitle": "{{doc-apihelp-param|compare|totitle}}",
"apihelp-compare-param-prop": "{{doc-apihelp-param|compare|prop}}",
"apihelp-compare-paramvalue-prop-diff": "{{doc-apihelp-paramvalue|compare|prop|diff}}",
"apihelp-compare-paramvalue-prop-diffsize": "{{doc-apihelp-paramvalue|compare|prop|diffsize}}",
"apihelp-compare-paramvalue-prop-comment": "{{doc-apihelp-paramvalue|compare|prop|comment}}",
"apihelp-compare-paramvalue-prop-parsedcomment": "{{doc-apihelp-paramvalue|compare|prop|parsedcomment}}",
"apihelp-compare-paramvalue-prop-size": "{{doc-apihelp-paramvalue|compare|prop|size}}",
+ "apihelp-compare-param-slots": "{{doc-apihelp-param|compare|slots}}",
"apihelp-compare-example-1": "{{doc-apihelp-example|compare}}",
"apihelp-createaccount-summary": "{{doc-apihelp-summary|createaccount}}",
"apihelp-createaccount-param-preservestate": "{{doc-apihelp-param|createaccount|preservestate|info=This message is displayed in addition to {{msg-mw|api-help-authmanagerhelper-preservestate}}.}}",
"apierror-chunk-too-small": "{{doc-apierror}}\n\nParameters:\n* $1 - Minimum size in bytes.",
"apierror-cidrtoobroad": "{{doc-apierror}}\n\nParameters:\n* $1 - \"IPv4\" or \"IPv6\"\n* $2 - Minimum CIDR mask length.",
"apierror-compare-no-title": "{{doc-apierror}}",
+ "apierror-compare-nofromrevision": "{{doc-apierror}}",
"apierror-compare-nosuchfromsection": "{{doc-apierror}}\n\nParameters:\n* $1 - Section identifier. Probably a number or \"T-\" followed by a number.",
"apierror-compare-nosuchtosection": "{{doc-apierror}}\n\nParameters:\n* $1 - Section identifier. Probably a number or \"T-\" followed by a number.",
+ "apierror-compare-notorevision": "{{doc-apierror}}",
"apierror-compare-relative-to-nothing": "{{doc-apierror}}",
"apierror-contentserializationexception": "{{doc-apierror}}\n\nParameters:\n* $1 - Exception text, may end with punctuation. Currently this is probably English, hopefully we'll fix that in the future.",
"apierror-contenttoobig": "{{doc-apierror}}\n\nParameters:\n* $1 - Maximum article size in kilobytes.",
"apierror-mimesearchdisabled": "{{doc-apierror}}",
"apierror-missingcontent-pageid": "{{doc-apierror}}\n\nParameters:\n* $1 - Page ID number.",
"apierror-missingcontent-revid": "{{doc-apierror}}\n\nParameters:\n* $1 - Revision ID number",
+ "apierror-missingcontent-revid-role": "{{doc-apierror}}\n\nParameters:\n* $1 - Revision ID number\n* $2 - Role name",
"apierror-missingparam-at-least-one-of": "{{doc-apierror}}\n\nParameters:\n* $1 - List of parameter names.\n* $2 - Number of parameters.",
"apierror-missingparam-one-of": "{{doc-apierror}}\n\nParameters:\n* $1 - List of parameter names.\n* $2 - Number of parameters.",
"apierror-missingparam": "{{doc-apierror}}\n\nParameters:\n* $1 - Parameter name.",
return $difftext;
}
+ /**
+ * Get the diff table body for one slot, without header
+ *
+ * @param string $role
+ * @return string|false
+ */
+ public function getDiffBodyForRole( $role ) {
+ $diffRenderers = $this->getSlotDiffRenderers();
+ if ( !isset( $diffRenderers[$role] ) ) {
+ return false;
+ }
+
+ $slotContents = $this->getSlotContents();
+ $slotDiff = $diffRenderers[$role]->getDiff( $slotContents[$role]['old'],
+ $slotContents[$role]['new'] );
+ if ( !$slotDiff ) {
+ return false;
+ }
+
+ if ( $role !== 'main' ) {
+ // TODO use human-readable role name at least
+ $slotTitle = $role;
+ $slotDiff = $this->getSlotHeader( $slotTitle ) . $slotDiff;
+ }
+
+ return $this->localiseDiff( $slotDiff );
+ }
+
/**
* Get a slot header for inclusion in a diff body (as a table row).
*
self::$repl['revF1'] = $this->addPage( 'F', "== Section 1 ==\nF 1.1\n\n== Section 2 ==\nF 1.2" );
self::$repl['pageF'] = Title::newFromText( 'ApiComparePagesTest F' )->getArticleId();
+ self::$repl['revG1'] = $this->addPage( 'G', "== Section 1 ==\nG 1.1", CONTENT_MODEL_TEXT );
+ self::$repl['pageG'] = Title::newFromText( 'ApiComparePagesTest G' )->getArticleId();
+
WikiPage::factory( Title::newFromText( 'ApiComparePagesTest C' ) )
->doDeleteArticleReal( 'Test for ApiComparePagesTest' );
$params += [
'action' => 'compare',
+ 'errorformat' => 'none',
];
$user = $sysop
}
}
+ private static function makeDeprecationWarnings( ...$params ) {
+ $warn = [];
+ foreach ( $params as $p ) {
+ $warn[] = [
+ 'code' => 'deprecation',
+ 'data' => [ 'feature' => "action=compare&{$p}" ],
+ 'module' => 'compare',
+ ];
+ if ( count( $warn ) === 1 ) {
+ $warn[] = [
+ 'code' => 'deprecation-help',
+ 'module' => 'main',
+ ];
+ }
+ }
+
+ return $warn;
+ }
+
public static function provideDiff() {
// phpcs:disable Generic.Files.LineLength.TooLong
return [
],
'Basic diff, text' => [
[
- 'fromtext' => 'From text',
- 'fromcontentmodel' => 'wikitext',
- 'totext' => 'To text {{subst:PAGENAME}}',
- 'tocontentmodel' => 'wikitext',
+ 'fromslots' => 'main',
+ 'fromtext-main' => 'From text',
+ 'fromcontentmodel-main' => 'wikitext',
+ 'toslots' => 'main',
+ 'totext-main' => 'To text {{subst:PAGENAME}}',
+ 'tocontentmodel-main' => 'wikitext',
],
[
'compare' => [
],
'Basic diff, text 2' => [
[
- 'fromtext' => 'From text',
- 'totext' => 'To text {{subst:PAGENAME}}',
- 'tocontentmodel' => 'wikitext',
+ 'fromslots' => 'main',
+ 'fromtext-main' => 'From text',
+ 'toslots' => 'main',
+ 'totext-main' => 'To text {{subst:PAGENAME}}',
+ 'tocontentmodel-main' => 'wikitext',
],
[
'compare' => [
],
'Basic diff, guessed model' => [
[
- 'fromtext' => 'From text',
- 'totext' => 'To text',
+ 'fromslots' => 'main',
+ 'fromtext-main' => 'From text',
+ 'toslots' => 'main',
+ 'totext-main' => 'To text',
],
[
- 'warnings' => [
- 'compare' => [
- 'warnings' => 'No content model could be determined, assuming wikitext.',
- ],
- ],
+ 'warnings' => [ [ 'code' => 'compare-nocontentmodel', 'module' => 'compare' ] ],
'compare' => [
'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1" >Line 1:</td>' . "\n"
. '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
],
'Basic diff, text with title and PST' => [
[
- 'fromtext' => 'From text',
+ 'fromslots' => 'main',
+ 'fromtext-main' => 'From text',
'totitle' => 'Test',
- 'totext' => 'To text {{subst:PAGENAME}}',
+ 'toslots' => 'main',
+ 'totext-main' => 'To text {{subst:PAGENAME}}',
'topst' => true,
],
[
],
'Basic diff, text with page ID and PST' => [
[
- 'fromtext' => 'From text',
+ 'fromslots' => 'main',
+ 'fromtext-main' => 'From text',
'toid' => '{{REPL:pageB}}',
- 'totext' => 'To text {{subst:PAGENAME}}',
+ 'toslots' => 'main',
+ 'totext-main' => 'To text {{subst:PAGENAME}}',
'topst' => true,
],
[
],
'Basic diff, text with revision and PST' => [
[
- 'fromtext' => 'From text',
+ 'fromslots' => 'main',
+ 'fromtext-main' => 'From text',
'torev' => '{{REPL:revB2}}',
- 'totext' => 'To text {{subst:PAGENAME}}',
+ 'toslots' => 'main',
+ 'totext-main' => 'To text {{subst:PAGENAME}}',
'topst' => true,
],
[
],
'Basic diff, text with deleted revision and PST' => [
[
- 'fromtext' => 'From text',
+ 'fromslots' => 'main',
+ 'fromtext-main' => 'From text',
'torev' => '{{REPL:revC2}}',
- 'totext' => 'To text {{subst:PAGENAME}}',
+ 'toslots' => 'main',
+ 'totext-main' => 'To text {{subst:PAGENAME}}',
'topst' => true,
],
[
'Basic diff, test with sections' => [
[
'fromtitle' => 'ApiComparePagesTest F',
- 'fromsection' => 1,
- 'totext' => "== Section 1 ==\nTo text\n\n== Section 2 ==\nTo text?",
- 'tosection' => 2,
+ 'fromslots' => 'main',
+ 'fromtext-main' => "== Section 2 ==\nFrom text?",
+ 'fromsection-main' => 2,
+ 'totitle' => 'ApiComparePagesTest F',
+ 'toslots' => 'main',
+ 'totext-main' => "== Section 1 ==\nTo text?",
+ 'tosection-main' => 1,
],
[
'compare' => [
'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1" >Line 1:</td>' . "\n"
. '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
- . '<tr><td class=\'diff-marker\'>−</td><td class=\'diff-deletedline\'><div>== Section <del class="diffchange diffchange-inline">1 </del>==</div></td><td class=\'diff-marker\'>+</td><td class=\'diff-addedline\'><div>== Section <ins class="diffchange diffchange-inline">2 </ins>==</div></td></tr>' . "\n"
- . '<tr><td class=\'diff-marker\'>−</td><td class=\'diff-deletedline\'><div><del class="diffchange diffchange-inline">F 1.1</del></div></td><td class=\'diff-marker\'>+</td><td class=\'diff-addedline\'><div><ins class="diffchange diffchange-inline">To text?</ins></div></td></tr>' . "\n",
- 'fromid' => '{{REPL:pageF}}',
- 'fromrevid' => '{{REPL:revF1}}',
- 'fromns' => '0',
- 'fromtitle' => 'ApiComparePagesTest F',
+ . '<tr><td class=\'diff-marker\'> </td><td class=\'diff-context\'><div>== Section 1 ==</div></td><td class=\'diff-marker\'> </td><td class=\'diff-context\'><div>== Section 1 ==</div></td></tr>' . "\n"
+ . '<tr><td class=\'diff-marker\'>−</td><td class=\'diff-deletedline\'><div><del class="diffchange diffchange-inline">F 1.1</del></div></td><td class=\'diff-marker\'>+</td><td class=\'diff-addedline\'><div><ins class="diffchange diffchange-inline">To text?</ins></div></td></tr>' . "\n"
+ . '<tr><td class=\'diff-marker\'> </td><td class=\'diff-context\'></td><td class=\'diff-marker\'> </td><td class=\'diff-context\'></td></tr>' . "\n"
+ . '<tr><td class=\'diff-marker\'> </td><td class=\'diff-context\'><div>== Section 2 ==</div></td><td class=\'diff-marker\'> </td><td class=\'diff-context\'><div>== Section 2 ==</div></td></tr>' . "\n"
+ . '<tr><td class=\'diff-marker\'>−</td><td class=\'diff-deletedline\'><div><del class="diffchange diffchange-inline">From text?</del></div></td><td class=\'diff-marker\'>+</td><td class=\'diff-addedline\'><div><ins class="diffchange diffchange-inline">F 1.2</ins></div></td></tr>' . "\n",
]
],
],
]
],
],
+ 'Diff for specific slots' => [
+ // @todo Use a page with multiple slots here
+ [
+ 'fromrev' => '{{REPL:revA1}}',
+ 'torev' => '{{REPL:revA3}}',
+ 'prop' => 'diff',
+ 'slots' => 'main',
+ ],
+ [
+ 'compare' => [
+ 'bodies' => [
+ 'main' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1" >Line 1:</td>' . "\n"
+ . '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
+ . '<tr><td class=\'diff-marker\'>−</td><td class=\'diff-deletedline\'><div>A <del class="diffchange diffchange-inline">1</del></div></td><td class=\'diff-marker\'>+</td><td class=\'diff-addedline\'><div>A <ins class="diffchange diffchange-inline">3</ins></div></td></tr>' . "\n",
+ ],
+ ],
+ ],
+ ],
+ // @todo Add a test for diffing with a deleted slot. Deleting 'main' doesn't work.
+
+ 'Basic diff, deprecated text' => [
+ [
+ 'fromtext' => 'From text',
+ 'fromcontentmodel' => 'wikitext',
+ 'totext' => 'To text {{subst:PAGENAME}}',
+ 'tocontentmodel' => 'wikitext',
+ ],
+ [
+ 'warnings' => self::makeDeprecationWarnings( 'fromtext', 'fromcontentmodel', 'totext', 'tocontentmodel' ),
+ 'compare' => [
+ 'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1" >Line 1:</td>' . "\n"
+ . '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
+ . '<tr><td class=\'diff-marker\'>−</td><td class=\'diff-deletedline\'><div><del class="diffchange diffchange-inline">From </del>text</div></td><td class=\'diff-marker\'>+</td><td class=\'diff-addedline\'><div><ins class="diffchange diffchange-inline">To </ins>text <ins class="diffchange diffchange-inline">{{subst:PAGENAME}}</ins></div></td></tr>' . "\n",
+ ]
+ ],
+ ],
+ 'Basic diff, deprecated text 2' => [
+ [
+ 'fromtext' => 'From text',
+ 'totext' => 'To text {{subst:PAGENAME}}',
+ 'tocontentmodel' => 'wikitext',
+ ],
+ [
+ 'warnings' => self::makeDeprecationWarnings( 'fromtext', 'totext', 'tocontentmodel' ),
+ 'compare' => [
+ 'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1" >Line 1:</td>' . "\n"
+ . '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
+ . '<tr><td class=\'diff-marker\'>−</td><td class=\'diff-deletedline\'><div><del class="diffchange diffchange-inline">From </del>text</div></td><td class=\'diff-marker\'>+</td><td class=\'diff-addedline\'><div><ins class="diffchange diffchange-inline">To </ins>text <ins class="diffchange diffchange-inline">{{subst:PAGENAME}}</ins></div></td></tr>' . "\n",
+ ]
+ ],
+ ],
+ 'Basic diff, deprecated text, guessed model' => [
+ [
+ 'fromtext' => 'From text',
+ 'totext' => 'To text',
+ ],
+ [
+ 'warnings' => array_merge( self::makeDeprecationWarnings( 'fromtext', 'totext' ), [
+ [ 'code' => 'compare-nocontentmodel', 'module' => 'compare' ],
+ ] ),
+ 'compare' => [
+ 'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1" >Line 1:</td>' . "\n"
+ . '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
+ . '<tr><td class=\'diff-marker\'>−</td><td class=\'diff-deletedline\'><div><del class="diffchange diffchange-inline">From </del>text</div></td><td class=\'diff-marker\'>+</td><td class=\'diff-addedline\'><div><ins class="diffchange diffchange-inline">To </ins>text</div></td></tr>' . "\n",
+ ]
+ ],
+ ],
+ 'Basic diff, deprecated text with title and PST' => [
+ [
+ 'fromtext' => 'From text',
+ 'totitle' => 'Test',
+ 'totext' => 'To text {{subst:PAGENAME}}',
+ 'topst' => true,
+ ],
+ [
+ 'warnings' => self::makeDeprecationWarnings( 'fromtext', 'totext' ),
+ 'compare' => [
+ 'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1" >Line 1:</td>' . "\n"
+ . '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
+ . '<tr><td class=\'diff-marker\'>−</td><td class=\'diff-deletedline\'><div><del class="diffchange diffchange-inline">From </del>text</div></td><td class=\'diff-marker\'>+</td><td class=\'diff-addedline\'><div><ins class="diffchange diffchange-inline">To </ins>text <ins class="diffchange diffchange-inline">Test</ins></div></td></tr>' . "\n",
+ ]
+ ],
+ ],
+ 'Basic diff, deprecated text with page ID and PST' => [
+ [
+ 'fromtext' => 'From text',
+ 'toid' => '{{REPL:pageB}}',
+ 'totext' => 'To text {{subst:PAGENAME}}',
+ 'topst' => true,
+ ],
+ [
+ 'warnings' => self::makeDeprecationWarnings( 'fromtext', 'totext' ),
+ 'compare' => [
+ 'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1" >Line 1:</td>' . "\n"
+ . '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
+ . '<tr><td class=\'diff-marker\'>−</td><td class=\'diff-deletedline\'><div><del class="diffchange diffchange-inline">From </del>text</div></td><td class=\'diff-marker\'>+</td><td class=\'diff-addedline\'><div><ins class="diffchange diffchange-inline">To </ins>text <ins class="diffchange diffchange-inline">ApiComparePagesTest B</ins></div></td></tr>' . "\n",
+ ]
+ ],
+ ],
+ 'Basic diff, deprecated text with revision and PST' => [
+ [
+ 'fromtext' => 'From text',
+ 'torev' => '{{REPL:revB2}}',
+ 'totext' => 'To text {{subst:PAGENAME}}',
+ 'topst' => true,
+ ],
+ [
+ 'warnings' => self::makeDeprecationWarnings( 'fromtext', 'totext' ),
+ 'compare' => [
+ 'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1" >Line 1:</td>' . "\n"
+ . '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
+ . '<tr><td class=\'diff-marker\'>−</td><td class=\'diff-deletedline\'><div><del class="diffchange diffchange-inline">From </del>text</div></td><td class=\'diff-marker\'>+</td><td class=\'diff-addedline\'><div><ins class="diffchange diffchange-inline">To </ins>text <ins class="diffchange diffchange-inline">ApiComparePagesTest B</ins></div></td></tr>' . "\n",
+ ]
+ ],
+ ],
+ 'Basic diff, deprecated text with deleted revision and PST' => [
+ [
+ 'fromtext' => 'From text',
+ 'torev' => '{{REPL:revC2}}',
+ 'totext' => 'To text {{subst:PAGENAME}}',
+ 'topst' => true,
+ ],
+ [
+ 'warnings' => self::makeDeprecationWarnings( 'fromtext', 'totext' ),
+ 'compare' => [
+ 'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1" >Line 1:</td>' . "\n"
+ . '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
+ . '<tr><td class=\'diff-marker\'>−</td><td class=\'diff-deletedline\'><div><del class="diffchange diffchange-inline">From </del>text</div></td><td class=\'diff-marker\'>+</td><td class=\'diff-addedline\'><div><ins class="diffchange diffchange-inline">To </ins>text <ins class="diffchange diffchange-inline">ApiComparePagesTest C</ins></div></td></tr>' . "\n",
+ ]
+ ],
+ false, true
+ ],
+ 'Basic diff, test with deprecated sections' => [
+ [
+ 'fromtitle' => 'ApiComparePagesTest F',
+ 'fromsection' => 1,
+ 'totext' => "== Section 1 ==\nTo text\n\n== Section 2 ==\nTo text?",
+ 'tosection' => 2,
+ ],
+ [
+ 'warnings' => self::makeDeprecationWarnings( 'fromsection', 'totext', 'tosection' ),
+ 'compare' => [
+ 'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1" >Line 1:</td>' . "\n"
+ . '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
+ . '<tr><td class=\'diff-marker\'>−</td><td class=\'diff-deletedline\'><div>== Section <del class="diffchange diffchange-inline">1 </del>==</div></td><td class=\'diff-marker\'>+</td><td class=\'diff-addedline\'><div>== Section <ins class="diffchange diffchange-inline">2 </ins>==</div></td></tr>' . "\n"
+ . '<tr><td class=\'diff-marker\'>−</td><td class=\'diff-deletedline\'><div><del class="diffchange diffchange-inline">F 1.1</del></div></td><td class=\'diff-marker\'>+</td><td class=\'diff-addedline\'><div><ins class="diffchange diffchange-inline">To text?</ins></div></td></tr>' . "\n",
+ 'fromid' => '{{REPL:pageF}}',
+ 'fromrevid' => '{{REPL:revF1}}',
+ 'fromns' => '0',
+ 'fromtitle' => 'ApiComparePagesTest F',
+ ]
+ ],
+ ],
+ 'Basic diff, test with deprecated sections and revdel, non-sysop' => [
+ [
+ 'fromrev' => '{{REPL:revB2}}',
+ 'fromsection' => 0,
+ 'torev' => '{{REPL:revB4}}',
+ 'tosection' => 0,
+ ],
+ [],
+ 'missingcontent'
+ ],
+ 'Basic diff, test with deprecated sections and revdel, sysop' => [
+ [
+ 'fromrev' => '{{REPL:revB2}}',
+ 'fromsection' => 0,
+ 'torev' => '{{REPL:revB4}}',
+ 'tosection' => 0,
+ ],
+ [
+ 'warnings' => self::makeDeprecationWarnings( 'fromsection', 'tosection' ),
+ 'compare' => [
+ 'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1" >Line 1:</td>' . "\n"
+ . '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
+ . '<tr><td class=\'diff-marker\'>−</td><td class=\'diff-deletedline\'><div>B <del class="diffchange diffchange-inline">2</del></div></td><td class=\'diff-marker\'>+</td><td class=\'diff-addedline\'><div>B <ins class="diffchange diffchange-inline">4</ins></div></td></tr>' . "\n",
+ 'fromid' => '{{REPL:pageB}}',
+ 'fromrevid' => '{{REPL:revB2}}',
+ 'fromns' => 0,
+ 'fromtitle' => 'ApiComparePagesTest B',
+ 'fromtexthidden' => true,
+ 'fromuserhidden' => true,
+ 'fromcommenthidden' => true,
+ 'toid' => '{{REPL:pageB}}',
+ 'torevid' => '{{REPL:revB4}}',
+ 'tons' => 0,
+ 'totitle' => 'ApiComparePagesTest B',
+ ]
+ ],
+ false, true,
+ ],
'Error, missing title' => [
[
[],
'missingcontent'
],
+ 'Error, Relative diff, no prev' => [
+ [
+ 'fromrev' => '{{REPL:revA1}}',
+ 'torelative' => 'prev',
+ 'prop' => 'ids',
+ ],
+ [],
+ 'baddiff'
+ ],
+ 'Error, Relative diff, no next' => [
+ [
+ 'fromrev' => '{{REPL:revA4}}',
+ 'torelative' => 'next',
+ 'prop' => 'ids',
+ ],
+ [],
+ 'baddiff'
+ ],
+ 'Error, section diff with no revision' => [
+ [
+ 'fromtitle' => 'ApiComparePagesTest F',
+ 'toslots' => 'main',
+ 'totext-main' => "== Section 1 ==\nTo text?",
+ 'tosection-main' => 1,
+ ],
+ [],
+ 'compare-notorevision',
+ ],
+ 'Error, section diff with revdeleted revision' => [
+ [
+ 'fromtitle' => 'ApiComparePagesTest F',
+ 'torev' => '{{REPL:revB2}}',
+ 'toslots' => 'main',
+ 'totext-main' => "== Section 1 ==\nTo text?",
+ 'tosection-main' => 1,
+ ],
+ [],
+ 'missingcontent',
+ ],
+ 'Error, section diff with a content model not supporting sections' => [
+ [
+ 'fromtitle' => 'ApiComparePagesTest G',
+ 'torev' => '{{REPL:revG1}}',
+ 'toslots' => 'main',
+ 'totext-main' => "== Section 1 ==\nTo text?",
+ 'tosection-main' => 1,
+ ],
+ [],
+ 'sectionsnotsupported',
+ ],
+ 'Error, section diff with bad content model' => [
+ [
+ 'fromtitle' => 'ApiComparePagesTest F',
+ 'torev' => '{{REPL:revF1}}',
+ 'toslots' => 'main',
+ 'totext-main' => "== Section 1 ==\nTo text?",
+ 'tosection-main' => 1,
+ 'tocontentmodel-main' => CONTENT_MODEL_TEXT,
+ ],
+ [],
+ 'sectionreplacefailed',
+ ],
];
// phpcs:enable
}