$attribs = $overrides + [
'page' => isset( $row->ar_page_id ) ? $row->ar_page_id : null,
'id' => isset( $row->ar_rev_id ) ? $row->ar_rev_id : null,
- 'comment' => $row->ar_comment,
+ 'comment' => CommentStore::newKey( 'ar_comment' )
+ // Legacy because $row probably came from self::selectArchiveFields()
+ ->getCommentLegacy( wfGetDB( DB_REPLICA ), $row, true )->text,
'user' => $row->ar_user,
'user_text' => $row->ar_user_text,
'timestamp' => $row->ar_timestamp,
$row = self::fetchFromConds( $db, $conditions, $flags );
if ( $row ) {
$rev = new Revision( $row );
- $rev->mWiki = $db->getWikiID();
+ $rev->mWiki = $db->getDomainID();
return $rev;
}
/**
* Return the list of revision fields that should be selected to create
* a new revision.
+ * @todo Deprecate this in favor of a method that returns tables and joins
+ * as well, and use CommentStore::getJoin().
* @return array
*/
public static function selectFields() {
'rev_page',
'rev_text_id',
'rev_timestamp',
- 'rev_comment',
'rev_user_text',
'rev_user',
'rev_minor_edit',
'rev_sha1',
];
+ $fields += CommentStore::newKey( 'rev_comment' )->getFields();
+
if ( $wgContentHandlerUseDB ) {
$fields[] = 'rev_content_format';
$fields[] = 'rev_content_model';
/**
* Return the list of revision fields that should be selected to create
* a new revision from an archive row.
+ * @todo Deprecate this in favor of a method that returns tables and joins
+ * as well, and use CommentStore::getJoin().
* @return array
*/
public static function selectArchiveFields() {
'ar_text',
'ar_text_id',
'ar_timestamp',
- 'ar_comment',
'ar_user_text',
'ar_user',
'ar_minor_edit',
'ar_sha1',
];
+ $fields += CommentStore::newKey( 'ar_comment' )->getFields();
+
if ( $wgContentHandlerUseDB ) {
$fields[] = 'ar_content_format';
$fields[] = 'ar_content_model';
* @throws MWException
* @access private
*/
- function __construct( $row ) {
+ public function __construct( $row ) {
if ( is_object( $row ) ) {
- $this->mId = intval( $row->rev_id );
- $this->mPage = intval( $row->rev_page );
- $this->mTextId = intval( $row->rev_text_id );
- $this->mComment = $row->rev_comment;
- $this->mUser = intval( $row->rev_user );
- $this->mMinorEdit = intval( $row->rev_minor_edit );
- $this->mTimestamp = $row->rev_timestamp;
- $this->mDeleted = intval( $row->rev_deleted );
-
- if ( !isset( $row->rev_parent_id ) ) {
- $this->mParentId = null;
- } else {
- $this->mParentId = intval( $row->rev_parent_id );
- }
-
- if ( !isset( $row->rev_len ) ) {
- $this->mSize = null;
- } else {
- $this->mSize = intval( $row->rev_len );
- }
-
- if ( !isset( $row->rev_sha1 ) ) {
- $this->mSha1 = null;
- } else {
- $this->mSha1 = $row->rev_sha1;
- }
+ $this->constructFromDbRowObject( $row );
+ } elseif ( is_array( $row ) ) {
+ $this->constructFromRowArray( $row );
+ } else {
+ throw new MWException( 'Revision constructor passed invalid row format.' );
+ }
+ $this->mUnpatrolled = null;
+ }
- if ( isset( $row->page_latest ) ) {
- $this->mCurrent = ( $row->rev_id == $row->page_latest );
- $this->mTitle = Title::newFromRow( $row );
- } else {
- $this->mCurrent = false;
- $this->mTitle = null;
- }
+ /**
+ * @param object $row
+ */
+ private function constructFromDbRowObject( $row ) {
+ $this->mId = intval( $row->rev_id );
+ $this->mPage = intval( $row->rev_page );
+ $this->mTextId = intval( $row->rev_text_id );
+ $this->mComment = CommentStore::newKey( 'rev_comment' )
+ // Legacy because $row probably came from self::selectFields()
+ ->getCommentLegacy( wfGetDB( DB_REPLICA ), $row, true )->text;
+ $this->mUser = intval( $row->rev_user );
+ $this->mMinorEdit = intval( $row->rev_minor_edit );
+ $this->mTimestamp = $row->rev_timestamp;
+ $this->mDeleted = intval( $row->rev_deleted );
+
+ if ( !isset( $row->rev_parent_id ) ) {
+ $this->mParentId = null;
+ } else {
+ $this->mParentId = intval( $row->rev_parent_id );
+ }
- if ( !isset( $row->rev_content_model ) ) {
- $this->mContentModel = null; # determine on demand if needed
- } else {
- $this->mContentModel = strval( $row->rev_content_model );
- }
+ if ( !isset( $row->rev_len ) ) {
+ $this->mSize = null;
+ } else {
+ $this->mSize = intval( $row->rev_len );
+ }
- if ( !isset( $row->rev_content_format ) ) {
- $this->mContentFormat = null; # determine on demand if needed
- } else {
- $this->mContentFormat = strval( $row->rev_content_format );
- }
+ if ( !isset( $row->rev_sha1 ) ) {
+ $this->mSha1 = null;
+ } else {
+ $this->mSha1 = $row->rev_sha1;
+ }
- // Lazy extraction...
- $this->mText = null;
- if ( isset( $row->old_text ) ) {
- $this->mTextRow = $row;
- } else {
- // 'text' table row entry will be lazy-loaded
- $this->mTextRow = null;
- }
+ if ( isset( $row->page_latest ) ) {
+ $this->mCurrent = ( $row->rev_id == $row->page_latest );
+ $this->mTitle = Title::newFromRow( $row );
+ } else {
+ $this->mCurrent = false;
+ $this->mTitle = null;
+ }
- // Use user_name for users and rev_user_text for IPs...
- $this->mUserText = null; // lazy load if left null
- if ( $this->mUser == 0 ) {
- $this->mUserText = $row->rev_user_text; // IP user
- } elseif ( isset( $row->user_name ) ) {
- $this->mUserText = $row->user_name; // logged-in user
- }
- $this->mOrigUserText = $row->rev_user_text;
- } elseif ( is_array( $row ) ) {
- // Build a new revision to be saved...
- global $wgUser; // ugh
-
- # if we have a content object, use it to set the model and type
- if ( !empty( $row['content'] ) ) {
- // @todo when is that set? test with external store setup! check out insertOn() [dk]
- if ( !empty( $row['text_id'] ) ) {
- throw new MWException( "Text already stored in external store (id {$row['text_id']}), " .
- "can't serialize content object" );
- }
+ if ( !isset( $row->rev_content_model ) ) {
+ $this->mContentModel = null; # determine on demand if needed
+ } else {
+ $this->mContentModel = strval( $row->rev_content_model );
+ }
- $row['content_model'] = $row['content']->getModel();
- # note: mContentFormat is initializes later accordingly
- # note: content is serialized later in this method!
- # also set text to null?
- }
+ if ( !isset( $row->rev_content_format ) ) {
+ $this->mContentFormat = null; # determine on demand if needed
+ } else {
+ $this->mContentFormat = strval( $row->rev_content_format );
+ }
- $this->mId = isset( $row['id'] ) ? intval( $row['id'] ) : null;
- $this->mPage = isset( $row['page'] ) ? intval( $row['page'] ) : null;
- $this->mTextId = isset( $row['text_id'] ) ? intval( $row['text_id'] ) : null;
- $this->mUserText = isset( $row['user_text'] )
- ? strval( $row['user_text'] ) : $wgUser->getName();
- $this->mUser = isset( $row['user'] ) ? intval( $row['user'] ) : $wgUser->getId();
- $this->mMinorEdit = isset( $row['minor_edit'] ) ? intval( $row['minor_edit'] ) : 0;
- $this->mTimestamp = isset( $row['timestamp'] )
- ? strval( $row['timestamp'] ) : wfTimestampNow();
- $this->mDeleted = isset( $row['deleted'] ) ? intval( $row['deleted'] ) : 0;
- $this->mSize = isset( $row['len'] ) ? intval( $row['len'] ) : null;
- $this->mParentId = isset( $row['parent_id'] ) ? intval( $row['parent_id'] ) : null;
- $this->mSha1 = isset( $row['sha1'] ) ? strval( $row['sha1'] ) : null;
-
- $this->mContentModel = isset( $row['content_model'] )
- ? strval( $row['content_model'] ) : null;
- $this->mContentFormat = isset( $row['content_format'] )
- ? strval( $row['content_format'] ) : null;
-
- // Enforce spacing trimming on supplied text
- $this->mComment = isset( $row['comment'] ) ? trim( strval( $row['comment'] ) ) : null;
- $this->mText = isset( $row['text'] ) ? rtrim( strval( $row['text'] ) ) : null;
+ // Lazy extraction...
+ $this->mText = null;
+ if ( isset( $row->old_text ) ) {
+ $this->mTextRow = $row;
+ } else {
+ // 'text' table row entry will be lazy-loaded
$this->mTextRow = null;
+ }
- $this->mTitle = isset( $row['title'] ) ? $row['title'] : null;
-
- // if we have a Content object, override mText and mContentModel
- if ( !empty( $row['content'] ) ) {
- if ( !( $row['content'] instanceof Content ) ) {
- throw new MWException( '`content` field must contain a Content object.' );
- }
-
- $handler = $this->getContentHandler();
- $this->mContent = $row['content'];
+ // Use user_name for users and rev_user_text for IPs...
+ $this->mUserText = null; // lazy load if left null
+ if ( $this->mUser == 0 ) {
+ $this->mUserText = $row->rev_user_text; // IP user
+ } elseif ( isset( $row->user_name ) ) {
+ $this->mUserText = $row->user_name; // logged-in user
+ }
+ $this->mOrigUserText = $row->rev_user_text;
+ }
- $this->mContentModel = $this->mContent->getModel();
- $this->mContentHandler = null;
+ /**
+ * @param array $row
+ *
+ * @throws MWException
+ */
+ private function constructFromRowArray( array $row ) {
+ // Build a new revision to be saved...
+ global $wgUser; // ugh
- $this->mText = $handler->serializeContent( $row['content'], $this->getContentFormat() );
- } elseif ( $this->mText !== null ) {
- $handler = $this->getContentHandler();
- $this->mContent = $handler->unserializeContent( $this->mText );
+ # if we have a content object, use it to set the model and type
+ if ( !empty( $row['content'] ) ) {
+ if ( !( $row['content'] instanceof Content ) ) {
+ throw new MWException( '`content` field must contain a Content object.' );
}
- // If we have a Title object, make sure it is consistent with mPage.
- if ( $this->mTitle && $this->mTitle->exists() ) {
- if ( $this->mPage === null ) {
- // if the page ID wasn't known, set it now
- $this->mPage = $this->mTitle->getArticleID();
- } elseif ( $this->mTitle->getArticleID() !== $this->mPage ) {
- // Got different page IDs. This may be legit (e.g. during undeletion),
- // but it seems worth mentioning it in the log.
- wfDebug( "Page ID " . $this->mPage . " mismatches the ID " .
- $this->mTitle->getArticleID() . " provided by the Title object." );
- }
+ // @todo when is that set? test with external store setup! check out insertOn() [dk]
+ if ( !empty( $row['text_id'] ) ) {
+ throw new MWException( "Text already stored in external store (id {$row['text_id']}), " .
+ "can't serialize content object" );
}
- $this->mCurrent = false;
+ $row['content_model'] = $row['content']->getModel();
+ # note: mContentFormat is initializes later accordingly
+ # note: content is serialized later in this method!
+ # also set text to null?
+ }
+
+ $this->mId = isset( $row['id'] ) ? intval( $row['id'] ) : null;
+ $this->mPage = isset( $row['page'] ) ? intval( $row['page'] ) : null;
+ $this->mTextId = isset( $row['text_id'] ) ? intval( $row['text_id'] ) : null;
+ $this->mUserText = isset( $row['user_text'] )
+ ? strval( $row['user_text'] ) : $wgUser->getName();
+ $this->mUser = isset( $row['user'] ) ? intval( $row['user'] ) : $wgUser->getId();
+ $this->mMinorEdit = isset( $row['minor_edit'] ) ? intval( $row['minor_edit'] ) : 0;
+ $this->mTimestamp = isset( $row['timestamp'] )
+ ? strval( $row['timestamp'] ) : wfTimestampNow();
+ $this->mDeleted = isset( $row['deleted'] ) ? intval( $row['deleted'] ) : 0;
+ $this->mSize = isset( $row['len'] ) ? intval( $row['len'] ) : null;
+ $this->mParentId = isset( $row['parent_id'] ) ? intval( $row['parent_id'] ) : null;
+ $this->mSha1 = isset( $row['sha1'] ) ? strval( $row['sha1'] ) : null;
+
+ $this->mContentModel = isset( $row['content_model'] )
+ ? strval( $row['content_model'] ) : null;
+ $this->mContentFormat = isset( $row['content_format'] )
+ ? strval( $row['content_format'] ) : null;
+
+ // Enforce spacing trimming on supplied text
+ $this->mComment = isset( $row['comment'] ) ? trim( strval( $row['comment'] ) ) : null;
+ $this->mText = isset( $row['text'] ) ? rtrim( strval( $row['text'] ) ) : null;
+ $this->mTextRow = null;
+
+ $this->mTitle = isset( $row['title'] ) ? $row['title'] : null;
+
+ // if we have a Content object, override mText and mContentModel
+ if ( !empty( $row['content'] ) ) {
+ $handler = $this->getContentHandler();
+ $this->mContent = $row['content'];
- // If we still have no length, see it we have the text to figure it out
- if ( !$this->mSize && $this->mContent !== null ) {
- $this->mSize = $this->mContent->getSize();
- }
+ $this->mContentModel = $this->mContent->getModel();
+ $this->mContentHandler = null;
- // Same for sha1
- if ( $this->mSha1 === null ) {
- $this->mSha1 = $this->mText === null ? null : self::base36Sha1( $this->mText );
+ $this->mText = $handler->serializeContent( $row['content'], $this->getContentFormat() );
+ } elseif ( $this->mText !== null ) {
+ $handler = $this->getContentHandler();
+ $this->mContent = $handler->unserializeContent( $this->mText );
+ }
+
+ // If we have a Title object, make sure it is consistent with mPage.
+ if ( $this->mTitle && $this->mTitle->exists() ) {
+ if ( $this->mPage === null ) {
+ // if the page ID wasn't known, set it now
+ $this->mPage = $this->mTitle->getArticleID();
+ } elseif ( $this->mTitle->getArticleID() !== $this->mPage ) {
+ // Got different page IDs. This may be legit (e.g. during undeletion),
+ // but it seems worth mentioning it in the log.
+ wfDebug( "Page ID " . $this->mPage . " mismatches the ID " .
+ $this->mTitle->getArticleID() . " provided by the Title object." );
}
+ }
- // force lazy init
- $this->getContentModel();
- $this->getContentFormat();
- } else {
- throw new MWException( 'Revision constructor passed invalid row format.' );
+ $this->mCurrent = false;
+
+ // If we still have no length, see it we have the text to figure it out
+ if ( !$this->mSize && $this->mContent !== null ) {
+ $this->mSize = $this->mContent->getSize();
}
- $this->mUnpatrolled = null;
+
+ // Same for sha1
+ if ( $this->mSha1 === null ) {
+ $this->mSha1 = $this->mText === null ? null : self::base36Sha1( $this->mText );
+ }
+
+ // force lazy init
+ $this->getContentModel();
+ $this->getContentFormat();
}
/**
*
* @param IDatabase $dbw (master connection)
* @throws MWException
- * @return int
+ * @return int The revision ID
*/
public function insertOn( $dbw ) {
global $wgDefaultExternalStore, $wgContentHandlerUseDB;
# Record the text (or external storage URL) to the text table
if ( $this->mTextId === null ) {
- $old_id = $dbw->nextSequenceValue( 'text_old_id_seq' );
$dbw->insert( 'text',
[
- 'old_id' => $old_id,
'old_text' => $data,
'old_flags' => $flags,
], __METHOD__
}
# Record the edit in revisions
- $rev_id = $this->mId !== null
- ? $this->mId
- : $dbw->nextSequenceValue( 'revision_rev_id_seq' );
$row = [
- 'rev_id' => $rev_id,
'rev_page' => $this->mPage,
'rev_text_id' => $this->mTextId,
- 'rev_comment' => $this->mComment,
'rev_minor_edit' => $this->mMinorEdit ? 1 : 0,
'rev_user' => $this->mUser,
'rev_user_text' => $this->mUserText,
? self::base36Sha1( $this->mText )
: $this->mSha1,
];
+ if ( $this->mId !== null ) {
+ $row['rev_id'] = $this->mId;
+ }
+
+ list( $commentFields, $commentCallback ) =
+ CommentStore::newKey( 'rev_comment' )->insertWithTempTable( $dbw, $this->mComment );
+ $row += $commentFields;
if ( $wgContentHandlerUseDB ) {
// NOTE: Store null for the default model and format, to save space.
$dbw->insert( 'revision', $row, __METHOD__ );
if ( $this->mId === null ) {
- // Only if nextSequenceValue() was called
+ // Only if auto-increment was used
$this->mId = $dbw->insertId();
}
+ $commentCallback( $this->mId );
// Assertion to try to catch T92046
if ( (int)$this->mId === 0 ) {
);
}
+ // Insert IP revision into ip_changes for use when querying for a range.
+ if ( $this->mUser === 0 && IP::isValid( $this->mUserText ) ) {
+ $ipcRow = [
+ 'ipc_rev_id' => $this->mId,
+ 'ipc_rev_timestamp' => $row['rev_timestamp'],
+ 'ipc_hex' => IP::toHex( $row['rev_user_text'] ),
+ ];
+ $dbw->insert( 'ip_changes', $ipcRow, __METHOD__ );
+ }
+
// Avoid PHP 7.1 warning of passing $this by reference
$revision = $this;
Hooks::run( 'RevisionInsertComplete', [ &$revision, $data, $flags ] );
* @return Revision|null Revision or null on error
*/
public static function newNullRevision( $dbw, $pageId, $summary, $minor, $user = null ) {
- global $wgContentHandlerUseDB, $wgContLang;
+ global $wgContentHandlerUseDB;
$fields = [ 'page_latest', 'page_namespace', 'page_title',
'rev_text_id', 'rev_len', 'rev_sha1' ];
$user = $wgUser;
}
- // Truncate for whole multibyte characters
- $summary = $wgContLang->truncate( $summary, 255 );
-
$row = [
'page' => $pageId,
'user_text' => $user->getName(),
$cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
return $cache->getWithSetCallback(
// Page/rev IDs passed in from DB to reflect history merges
- $cache->makeGlobalKey( 'revision', $db->getWikiID(), $pageId, $revId ),
+ $cache->makeGlobalKey( 'revision', $db->getDomainID(), $pageId, $revId ),
$cache::TTL_WEEK,
function ( $curValue, &$ttl, array &$setOpts ) use ( $db, $pageId, $revId ) {
$setOpts += Database::getCacheSetOptions( $db );