namespace MediaWiki\Storage;
+use ActorMigration;
use CommentStore;
use CommentStoreComment;
use Content;
use ContentHandler;
use DBAccessObjectUtils;
use Hooks;
-use \IDBAccessObject;
+use IDBAccessObject;
use InvalidArgumentException;
use IP;
use LogicException;
use Message;
use MWException;
use MWUnknownContentModelException;
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\LoggerInterface;
+use Psr\Log\NullLogger;
use RecentChange;
use stdClass;
use Title;
* @note This was written to act as a drop-in replacement for the corresponding
* static methods in Revision.
*/
-class RevisionStore implements IDBAccessObject, RevisionFactory, RevisionLookup {
+class RevisionStore
+ implements IDBAccessObject, RevisionFactory, RevisionLookup, LoggerAwareInterface {
/**
* @var SqlBlobStore
*/
private $cache;
+ /**
+ * @var CommentStore
+ */
+ private $commentStore;
+
+ /**
+ * @var ActorMigration
+ */
+ private $actorMigration;
+
+ /**
+ * @var LoggerInterface
+ */
+ private $logger;
+
/**
* @todo $blobStore should be allowed to be any BlobStore!
*
* @param LoadBalancer $loadBalancer
* @param SqlBlobStore $blobStore
* @param WANObjectCache $cache
+ * @param CommentStore $commentStore
+ * @param ActorMigration $actorMigration
* @param bool|string $wikiId
*/
public function __construct(
LoadBalancer $loadBalancer,
SqlBlobStore $blobStore,
WANObjectCache $cache,
+ CommentStore $commentStore,
+ ActorMigration $actorMigration,
$wikiId = false
) {
Assert::parameterType( 'string|boolean', $wikiId, '$wikiId' );
$this->loadBalancer = $loadBalancer;
$this->blobStore = $blobStore;
$this->cache = $cache;
+ $this->commentStore = $commentStore;
+ $this->actorMigration = $actorMigration;
$this->wikiId = $wikiId;
+ $this->logger = new NullLogger();
+ }
+
+ public function setLogger( LoggerInterface $logger ) {
+ $this->logger = $logger;
+ }
+
+ /**
+ * @return bool Whether the store is read-only
+ */
+ public function isReadOnly() {
+ return $this->blobStore->isReadOnly();
}
/**
*
* MCR migration note: this corresponds to Revision::getTitle
*
+ * @note this method should be private, external use should be avoided!
+ *
* @param int|null $pageId
* @param int|null $revId
* @param int $queryFlags
* @return Title
* @throws RevisionAccessException
*/
- private function getTitle( $pageId, $revId, $queryFlags = 0 ) {
+ public function getTitle( $pageId, $revId, $queryFlags = self::READ_NORMAL ) {
if ( !$pageId && !$revId ) {
throw new InvalidArgumentException( '$pageId and $revId cannot both be 0 or null' );
}
- $title = null;
+ // This method recalls itself with READ_LATEST if READ_NORMAL doesn't get us a Title
+ // So ignore READ_LATEST_IMMUTABLE flags and handle the fallback logic in this method
+ if ( DBAccessObjectUtils::hasFlags( $queryFlags, self::READ_LATEST_IMMUTABLE ) ) {
+ $queryFlags = self::READ_NORMAL;
+ }
+
+ $canUseTitleNewFromId = ( $pageId !== null && $pageId > 0 && $this->wikiId === false );
+ list( $dbMode, $dbOptions ) = DBAccessObjectUtils::getDBOptions( $queryFlags );
+ $titleFlags = ( $dbMode == DB_MASTER ? Title::GAID_FOR_UPDATE : 0 );
// Loading by ID is best, but Title::newFromID does not support that for foreign IDs.
- if ( $pageId !== null && $pageId > 0 && $this->wikiId === false ) {
+ if ( $canUseTitleNewFromId ) {
// TODO: better foreign title handling (introduce TitleFactory)
- $title = Title::newFromID( $pageId, $queryFlags );
+ $title = Title::newFromID( $pageId, $titleFlags );
+ if ( $title ) {
+ return $title;
+ }
}
// rev_id is defined as NOT NULL, but this revision may not yet have been inserted.
- if ( !$title && $revId !== null && $revId > 0 ) {
- list( $dbMode, $dbOptions, , ) = DBAccessObjectUtils::getDBOptions( $queryFlags );
+ $canUseRevId = ( $revId !== null && $revId > 0 );
- $dbr = $this->getDbConnectionRef( $dbMode );
+ if ( $canUseRevId ) {
+ $dbr = $this->getDBConnectionRef( $dbMode );
// @todo: Title::getSelectFields(), or Title::getQueryInfo(), or something like that
$row = $dbr->selectRow(
[ 'revision', 'page' ],
);
if ( $row ) {
// TODO: better foreign title handling (introduce TitleFactory)
- $title = Title::newFromRow( $row );
+ return Title::newFromRow( $row );
}
}
- if ( !$title ) {
- throw new RevisionAccessException(
- "Could not determine title for page ID $pageId and revision ID $revId"
- );
+ // If we still don't have a title, fallback to master if that wasn't already happening.
+ if ( $dbMode !== DB_MASTER ) {
+ $title = $this->getTitle( $pageId, $revId, self::READ_LATEST );
+ if ( $title ) {
+ $this->logger->info(
+ __METHOD__ . ' fell back to READ_LATEST and got a Title.',
+ [ 'trace' => wfBacktrace() ]
+ );
+ return $title;
+ }
}
- return $title;
+ throw new RevisionAccessException(
+ "Could not determine title for page ID $pageId and revision ID $revId"
+ );
}
/**
}
/**
- * Insert a new revision into the database, returning the new revision ID
- * number on success and dies horribly on failure.
+ * Insert a new revision into the database, returning the new revision record
+ * on success and dies horribly on failure.
*
* MCR migration note: this replaces Revision::insertOn
*
$user = $this->failOnNull( $rev->getUser( RevisionRecord::RAW ), 'user' );
$timestamp = $this->failOnEmpty( $rev->getTimestamp(), 'timestamp field' );
+ // Checks.
+ $this->failOnNull( $user->getId(), 'user field' );
+ $this->failOnEmpty( $user->getName(), 'user_text field' );
+
# Record the edit in revisions
$row = [
'rev_page' => $pageId,
'rev_parent_id' => $parentId,
'rev_text_id' => $textId,
'rev_minor_edit' => $rev->isMinor() ? 1 : 0,
- 'rev_user' => $this->failOnNull( $user->getId(), 'user field' ),
- 'rev_user_text' => $this->failOnEmpty( $user->getName(), 'user_text field' ),
'rev_timestamp' => $dbw->timestamp( $timestamp ),
'rev_deleted' => $rev->getVisibility(),
'rev_len' => $size,
}
list( $commentFields, $commentCallback ) =
- CommentStore::newKey( 'rev_comment' )->insertWithTempTable( $dbw, $comment );
+ $this->commentStore->insertWithTempTable( $dbw, 'rev_comment', $comment );
$row += $commentFields;
+ list( $actorFields, $actorCallback ) =
+ $this->actorMigration->getInsertValuesWithTempTable( $dbw, 'rev_user', $user );
+ $row += $actorFields;
+
if ( $this->contentHandlerUseDB ) {
// MCR migration note: rev_content_model and rev_content_format will go away
$row['rev_id'] = intval( $dbw->insertId() );
}
$commentCallback( $row['rev_id'] );
+ $actorCallback( $row['rev_id'], $row );
// Insert IP revision into ip_changes for use when querying for a range.
- if ( $row['rev_user'] === 0 && IP::isValid( $row['rev_user_text'] ) ) {
+ if ( $user->getId() === 0 && IP::isValid( $user->getName() ) ) {
$ipcRow = [
'ipc_rev_id' => $row['rev_id'],
'ipc_rev_timestamp' => $row['rev_timestamp'],
- 'ipc_hex' => IP::toHex( $row['rev_user_text'] ),
+ 'ipc_hex' => IP::toHex( $user->getName() ),
];
$dbw->insert( 'ip_changes', $ipcRow, __METHOD__ );
}
$newSlot = SlotRecord::newSaved( $row['rev_id'], $blobAddress, $slot );
$slots = new RevisionSlots( [ 'main' => $newSlot ] );
- $user = new UserIdentityValue( intval( $row['rev_user'] ), $row['rev_user_text'] );
-
$rev = new RevisionStoreRecord(
$title,
$user,
'page' => $title->getArticleID(),
'user_text' => $user->getName(),
'user' => $user->getId(),
+ 'actor' => $user->getActorId(),
'comment' => $comment,
'minor_edit' => $minor,
'text_id' => $current->rev_text_id,
/**
* MCR migration note: this replaces Revision::isUnpatrolled
*
+ * @todo This is overly specific, so move or kill this method.
+ *
+ * @param RevisionRecord $rev
+ *
* @return int Rcid of the unpatrolled row, zero if there isn't one
*/
- public function isUnpatrolled( RevisionRecord $rev ) {
+ public function getRcIdIfUnpatrolled( RevisionRecord $rev ) {
$rc = $this->getRecentChange( $rev );
if ( $rc && $rc->getAttribute( 'rc_patrolled' ) == 0 ) {
return $rc->getAttribute( 'rc_id' );
}
// TODO: Select by rc_this_oldid alone - but as of Nov 2017, there is no index on that!
+ $actorWhere = $this->actorMigration->getWhere( $dbr, 'rc_user', $rev->getUser(), false );
$rc = RecentChange::newFromConds(
[
- 'rc_user_text' => $userIdentity->getName(),
+ $actorWhere['conds'],
'rc_timestamp' => $dbr->timestamp( $rev->getTimestamp() ),
'rc_this_oldid' => $rev->getId()
],
'ar_timestamp' => 'rev_timestamp',
'ar_user_text' => 'rev_user_text',
'ar_user' => 'rev_user',
+ 'ar_actor' => 'rev_actor',
'ar_minor_edit' => 'rev_minor_edit',
'ar_deleted' => 'rev_deleted',
'ar_len' => 'rev_len',
$content = null;
$blobData = null;
- $blobFlags = '';
+ $blobFlags = null;
if ( is_object( $row ) ) {
// archive row
- if ( !isset( $row->rev_id ) && isset( $row->ar_user ) ) {
+ if ( !isset( $row->rev_id ) && ( isset( $row->ar_user ) || isset( $row->ar_actor ) ) ) {
$row = $this->mapArchiveFields( $row );
}
if ( isset( $row->rev_text_id ) && $row->rev_text_id > 0 ) {
$mainSlotRow->cont_address = 'tt:' . $row->rev_text_id;
- } elseif ( isset( $row->ar_id ) ) {
- $mainSlotRow->cont_address = 'ar:' . $row->ar_id;
}
if ( isset( $row->old_text ) ) {
// this happens when the text-table gets joined directly, in the pre-1.30 schema
$blobData = isset( $row->old_text ) ? strval( $row->old_text ) : null;
- $blobFlags = isset( $row->old_flags ) ? strval( $row->old_flags ) : '';
+ // Check against selects that might have not included old_flags
+ if ( !property_exists( $row, 'old_flags' ) ) {
+ throw new InvalidArgumentException( 'old_flags was not set in $row' );
+ }
+ $blobFlags = ( $row->old_flags === null ) ? '' : $row->old_flags;
}
$mainSlotRow->slot_revision = intval( $row->rev_id );
$mainSlotRow->format_name = isset( $row['content_format'] )
? strval( $row['content_format'] ) : null;
$blobData = isset( $row['text'] ) ? rtrim( strval( $row['text'] ) ) : null;
- $blobFlags = isset( $row['flags'] ) ? trim( strval( $row['flags'] ) ) : '';
+ // XXX: If the flags field is not set then $blobFlags should be null so that no
+ // decoding will happen. An empty string will result in default decodings.
+ $blobFlags = isset( $row['flags'] ) ? trim( strval( $row['flags'] ) ) : null;
// if we have a Content object, override mText and mContentModel
if ( !empty( $row['content'] ) ) {
*
* @param SlotRecord $slot The SlotRecord to load content for
* @param string|null $blobData The content blob, in the form indicated by $blobFlags
- * @param string $blobFlags Flags indicating how $blobData needs to be processed
+ * @param string|null $blobFlags Flags indicating how $blobData needs to be processed.
+ * Use null if no processing should happen. That is in constrast to the empty string,
+ * which causes the blob to be decoded according to the configured legacy encoding.
* @param string|null $blobFormat MIME type indicating how $dataBlob is encoded
* @param int $queryFlags
*
private function loadSlotContent(
SlotRecord $slot,
$blobData = null,
- $blobFlags = '',
+ $blobFlags = null,
$blobFormat = null,
$queryFlags = 0
) {
if ( $blobData !== null ) {
Assert::parameterType( 'string', $blobData, '$blobData' );
- Assert::parameterType( 'string', $blobFlags, '$blobFlags' );
+ Assert::parameterType( 'string|null', $blobFlags, '$blobFlags' );
$cacheKey = $slot->hasAddress() ? $slot->getAddress() : null;
- $data = $this->blobStore->expandBlob( $blobData, $blobFlags, $cacheKey );
-
- if ( $data === false ) {
- throw new RevisionAccessException(
- "Failed to expand blob data using flags $blobFlags (key: $cacheKey)"
- );
+ if ( $blobFlags === null ) {
+ // No blob flags, so use the blob verbatim.
+ $data = $blobData;
+ } else {
+ $data = $this->blobStore->expandBlob( $blobData, $blobFlags, $cacheKey );
+ if ( $data === false ) {
+ throw new RevisionAccessException(
+ "Failed to expand blob data using flags $blobFlags (key: $cacheKey)"
+ );
+ }
}
+
} else {
$address = $slot->getAddress();
try {
* @param string $timestamp
* @return RevisionRecord|null
*/
- public function getRevisionFromTimestamp( $title, $timestamp ) {
+ public function getRevisionByTimestamp( $title, $timestamp ) {
return $this->newRevisionFromConds(
[
'rev_timestamp' => $timestamp,
$row->$field = $value;
}
- $user = $this->getUserIdentityFromRowObject( $row, 'ar_' );
+ try {
+ $user = User::newFromAnyId(
+ isset( $row->ar_user ) ? $row->ar_user : null,
+ isset( $row->ar_user_text ) ? $row->ar_user_text : null,
+ isset( $row->ar_actor ) ? $row->ar_actor : null
+ );
+ } catch ( InvalidArgumentException $ex ) {
+ wfWarn( __METHOD__ . ': ' . $ex->getMessage() );
+ $user = new UserIdentityValue( 0, '', 0 );
+ }
- $comment = CommentStore::newKey( 'ar_comment' )
+ $comment = $this->commentStore
// Legacy because $row may have come from self::selectFields()
- ->getCommentLegacy( $this->getDBConnection( DB_REPLICA ), $row, true );
+ ->getCommentLegacy( $this->getDBConnection( DB_REPLICA ), 'ar_comment', $row, true );
$mainSlot = $this->emulateMainSlot_1_29( $row, $queryFlags, $title );
$slots = new RevisionSlots( [ 'main' => $mainSlot ] );
return new RevisionArchiveRecord( $title, $user, $comment, $row, $slots, $this->wikiId );
}
- /**
- * @param object $row
- * @param string $prefix Field prefix, such as 'rev_' or 'ar_'.
- *
- * @return UserIdentityValue
- */
- private function getUserIdentityFromRowObject( $row, $prefix = 'rev_' ) {
- $idField = "{$prefix}user";
- $nameField = "{$prefix}user_text";
-
- $userId = intval( $row->$idField );
-
- if ( isset( $row->user_name ) ) {
- $userName = $row->user_name;
- } elseif ( isset( $row->$nameField ) ) {
- $userName = $row->$nameField;
- } else {
- $userName = User::whoIs( $userId );
- }
-
- if ( $userName === false ) {
- wfWarn( __METHOD__ . ': Cannot determine user name for user ID ' . $userId );
- $userName = '';
- }
-
- return new UserIdentityValue( $userId, $userName );
- }
-
/**
* @see RevisionFactory::newRevisionFromRow_1_29
*
$pageId = isset( $row->rev_page ) ? $row->rev_page : 0; // XXX: also check page_id?
$revId = isset( $row->rev_id ) ? $row->rev_id : 0;
- $title = $this->getTitle( $pageId, $revId );
+ $title = $this->getTitle( $pageId, $revId, $queryFlags );
}
if ( !isset( $row->page_latest ) ) {
}
}
- $user = $this->getUserIdentityFromRowObject( $row );
+ try {
+ $user = User::newFromAnyId(
+ isset( $row->rev_user ) ? $row->rev_user : null,
+ isset( $row->rev_user_text ) ? $row->rev_user_text : null,
+ isset( $row->rev_actor ) ? $row->rev_actor : null
+ );
+ } catch ( InvalidArgumentException $ex ) {
+ wfWarn( __METHOD__ . ': ' . $ex->getMessage() );
+ $user = new UserIdentityValue( 0, '', 0 );
+ }
- $comment = CommentStore::newKey( 'rev_comment' )
+ $comment = $this->commentStore
// Legacy because $row may have come from self::selectFields()
- ->getCommentLegacy( $this->getDBConnection( DB_REPLICA ), $row, true );
+ ->getCommentLegacy( $this->getDBConnection( DB_REPLICA ), 'rev_comment', $row, true );
$mainSlot = $this->emulateMainSlot_1_29( $row, $queryFlags, $title );
$slots = new RevisionSlots( [ 'main' => $mainSlot ] );
$pageId = isset( $fields['page'] ) ? $fields['page'] : 0;
$revId = isset( $fields['id'] ) ? $fields['id'] : 0;
- $title = $this->getTitle( $pageId, $revId );
+ $title = $this->getTitle( $pageId, $revId, $queryFlags );
}
if ( !isset( $fields['page'] ) ) {
}
}
- // Replaces old lazy loading logic in Revision::getUserText.
- if ( !isset( $fields['user_text'] ) && isset( $fields['user'] ) ) {
- if ( $fields['user'] instanceof UserIdentity ) {
- /** @var User $user */
- $user = $fields['user'];
- $fields['user_text'] = $user->getName();
- $fields['user'] = $user->getId();
- } else {
- // TODO: wrap this in a callback to make it lazy again.
- $name = $fields['user'] === 0 ? false : User::whoIs( $fields['user'] );
-
- if ( $name === false ) {
- throw new MWException(
- 'user_text not given, and unknown user ID ' . $fields['user']
- );
- }
-
- $fields['user_text'] = $name;
- }
- }
-
if (
isset( $fields['comment'] )
&& !( $fields['comment'] instanceof CommentStoreComment )
if ( isset( $fields['user'] ) && ( $fields['user'] instanceof UserIdentity ) ) {
$user = $fields['user'];
- } elseif ( isset( $fields['user'] ) && isset( $fields['user_text'] ) ) {
- $user = new UserIdentityValue( intval( $fields['user'] ), $fields['user_text'] );
- } elseif ( isset( $fields['user'] ) ) {
- $user = User::newFromId( intval( $fields['user'] ) );
- } elseif ( isset( $fields['user_text'] ) ) {
- $user = User::newFromName( $fields['user_text'] );
-
- // User::newFromName will return false for IP addresses (and invalid names)
- if ( $user == false ) {
- $user = new UserIdentityValue( 0, $fields['user_text'] );
+ } else {
+ try {
+ $user = User::newFromAnyId(
+ isset( $fields['user'] ) ? $fields['user'] : null,
+ isset( $fields['user_text'] ) ? $fields['user_text'] : null,
+ isset( $fields['actor'] ) ? $fields['actor'] : null
+ );
+ } catch ( InvalidArgumentException $ex ) {
+ $user = null;
}
}
$storeWiki = $storeWiki ?: wfWikiID();
$dbWiki = $dbWiki ?: wfWikiID();
- if ( $dbWiki !== $storeWiki ) {
- throw new MWException( "RevisionStore for $storeWiki "
- . "cannot be used with a DB connection for $dbWiki" );
+ if ( $dbWiki === $storeWiki ) {
+ return;
}
+
+ // HACK: counteract encoding imposed by DatabaseDomain
+ $storeWiki = str_replace( '?h', '-', $storeWiki );
+ $dbWiki = str_replace( '?h', '-', $dbWiki );
+
+ if ( $dbWiki === $storeWiki ) {
+ return;
+ }
+
+ throw new MWException( "RevisionStore for $storeWiki "
+ . "cannot be used with a DB connection for $dbWiki" );
}
/**
'rev_page',
'rev_text_id',
'rev_timestamp',
- 'rev_user_text',
- 'rev_user',
'rev_minor_edit',
'rev_deleted',
'rev_len',
'rev_sha1',
] );
- $commentQuery = CommentStore::newKey( 'rev_comment' )->getJoin();
+ $commentQuery = $this->commentStore->getJoin( 'rev_comment' );
$ret['tables'] = array_merge( $ret['tables'], $commentQuery['tables'] );
$ret['fields'] = array_merge( $ret['fields'], $commentQuery['fields'] );
$ret['joins'] = array_merge( $ret['joins'], $commentQuery['joins'] );
+ $actorQuery = $this->actorMigration->getJoin( 'rev_user' );
+ $ret['tables'] = array_merge( $ret['tables'], $actorQuery['tables'] );
+ $ret['fields'] = array_merge( $ret['fields'], $actorQuery['fields'] );
+ $ret['joins'] = array_merge( $ret['joins'], $actorQuery['joins'] );
+
if ( $this->contentHandlerUseDB ) {
$ret['fields'][] = 'rev_content_format';
$ret['fields'][] = 'rev_content_model';
$ret['fields'] = array_merge( $ret['fields'], [
'user_name',
] );
- $ret['joins']['user'] = [ 'LEFT JOIN', [ 'rev_user != 0', 'user_id = rev_user' ] ];
+ $u = $actorQuery['fields']['rev_user'];
+ $ret['joins']['user'] = [ 'LEFT JOIN', [ "$u != 0", "user_id = $u" ] ];
}
if ( in_array( 'text', $options, true ) ) {
* - joins: (array) to include in the `$join_conds` to `IDatabase->select()`
*/
public function getArchiveQueryInfo() {
- $commentQuery = CommentStore::newKey( 'ar_comment' )->getJoin();
+ $commentQuery = $this->commentStore->getJoin( 'ar_comment' );
+ $actorQuery = $this->actorMigration->getJoin( 'ar_user' );
$ret = [
- 'tables' => [ 'archive' ] + $commentQuery['tables'],
+ 'tables' => [ 'archive' ] + $commentQuery['tables'] + $actorQuery['tables'],
'fields' => [
'ar_id',
'ar_page_id',
'ar_text',
'ar_text_id',
'ar_timestamp',
- 'ar_user_text',
- 'ar_user',
'ar_minor_edit',
'ar_deleted',
'ar_len',
'ar_parent_id',
'ar_sha1',
- ] + $commentQuery['fields'],
- 'joins' => $commentQuery['joins'],
+ ] + $commentQuery['fields'] + $actorQuery['fields'],
+ 'joins' => $commentQuery['joins'] + $actorQuery['joins'],
];
if ( $this->contentHandlerUseDB ) {
*
* MCR migration note: this replaces Revision::getParentLengths
*
+ * @param int[] $revIds
+ * @return int[] associative array mapping revision IDs from $revIds to the nominal size
+ * of the corresponding revision.
+ */
+ public function getRevisionSizes( array $revIds ) {
+ return $this->listRevisionSizes( $this->getDBConnection( DB_REPLICA ), $revIds );
+ }
+
+ /**
+ * Do a batched query for the sizes of a set of revisions.
+ *
+ * MCR migration note: this replaces Revision::getParentLengths
+ *
+ * @deprecated use RevisionStore::getRevisionSizes instead.
+ *
* @param IDatabase $db
* @param int[] $revIds
* @return int[] associative array mapping revision IDs from $revIds to the nominal size
* MCR migration note: this replaces Revision::getPrevious
*
* @param RevisionRecord $rev
+ * @param Title $title if known (optional)
*
* @return RevisionRecord|null
*/
- public function getPreviousRevision( RevisionRecord $rev ) {
- $title = $this->getTitle( $rev->getPageId(), $rev->getId() );
+ public function getPreviousRevision( RevisionRecord $rev, Title $title = null ) {
+ if ( $title === null ) {
+ $title = $this->getTitle( $rev->getPageId(), $rev->getId() );
+ }
$prev = $title->getPreviousRevisionID( $rev->getId() );
if ( $prev ) {
return $this->getRevisionByTitle( $title, $prev );
* MCR migration note: this replaces Revision::getNext
*
* @param RevisionRecord $rev
+ * @param Title $title if known (optional)
*
* @return RevisionRecord|null
*/
- public function getNextRevision( RevisionRecord $rev ) {
- $title = $this->getTitle( $rev->getPageId(), $rev->getId() );
+ public function getNextRevision( RevisionRecord $rev, Title $title = null ) {
+ if ( $title === null ) {
+ $title = $this->getTitle( $rev->getPageId(), $rev->getId() );
+ }
$next = $title->getNextRevisionID( $rev->getId() );
if ( $next ) {
return $this->getRevisionByTitle( $title, $next );
return false;
}
+ $revQuery = self::getQueryInfo();
$res = $db->select(
- 'revision',
- 'rev_user',
+ $revQuery['tables'],
+ [
+ 'rev_user' => $revQuery['fields']['rev_user'],
+ ],
[
'rev_page' => $pageId,
'rev_timestamp > ' . $db->addQuotes( $db->timestamp( $since ) )
],
__METHOD__,
- [ 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 50 ]
+ [ 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 50 ],
+ $revQuery['joins']
);
foreach ( $res as $row ) {
if ( $row->rev_user != $userId ) {