X-Git-Url: https://git.heureux-cyclage.org/?p=lhc%2Fweb%2Fwiklou.git;a=blobdiff_plain;f=includes%2FRevision.php;h=518360c07e5d4f1148b807dae60eccb4f738fafb;hp=006e70064537d4803a7f9cac1b2a4377ea09465a;hb=5120937028f768749d058aa91dde82a96de0af1c;hpb=7e105610397b55d67f42f31496e92496477500e3 diff --git a/includes/Revision.php b/includes/Revision.php index 006e700645..518360c07e 100644 --- a/includes/Revision.php +++ b/includes/Revision.php @@ -193,7 +193,7 @@ class Revision implements IDBAccessObject { 'page' => isset( $row->ar_page_id ) ? $row->ar_page_id : null, 'id' => isset( $row->ar_rev_id ) ? $row->ar_rev_id : null, 'comment' => CommentStore::newKey( 'ar_comment' ) - // Legacy because $row probably came from self::selectArchiveFields() + // Legacy because $row may have come from self::selectArchiveFields() ->getCommentLegacy( wfGetDB( DB_REPLICA ), $row, true )->text, 'user' => $row->ar_user, 'user_text' => $row->ar_user_text, @@ -362,7 +362,7 @@ class Revision implements IDBAccessObject { $row = self::fetchFromConds( $db, $conditions, $flags ); if ( $row ) { $rev = new Revision( $row ); - $rev->mWiki = $db->getWikiID(); + $rev->mWiki = $db->getDomainID(); return $rev; } @@ -403,22 +403,18 @@ class Revision implements IDBAccessObject { * @return stdClass */ private static function fetchFromConds( $db, $conditions, $flags = 0 ) { - $fields = array_merge( - self::selectFields(), - self::selectPageFields(), - self::selectUserFields() - ); + $revQuery = self::getQueryInfo( [ 'page', 'user' ] ); $options = []; if ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING ) { $options[] = 'FOR UPDATE'; } return $db->selectRow( - [ 'revision', 'page', 'user' ], - $fields, + $revQuery['tables'], + $revQuery['fields'], $conditions, __METHOD__, $options, - [ 'page' => self::pageJoinCond(), 'user' => self::userJoinCond() ] + $revQuery['joins'] ); } @@ -426,9 +422,11 @@ class Revision implements IDBAccessObject { * Return the value of a select() JOIN conds array for the user table. * This will get user table rows for logged-in users. * @since 1.19 + * @deprecated since 1.31, use self::getQueryInfo( [ 'user' ] ) instead. * @return array */ public static function userJoinCond() { + wfDeprecated( __METHOD__, '1.31' ); return [ 'LEFT JOIN', [ 'rev_user != 0', 'user_id = rev_user' ] ]; } @@ -436,22 +434,25 @@ class Revision implements IDBAccessObject { * Return the value of a select() page conds array for the page table. * This will assure that the revision(s) are not orphaned from live pages. * @since 1.19 + * @deprecated since 1.31, use self::getQueryInfo( [ 'page' ] ) instead. * @return array */ public static function pageJoinCond() { + wfDeprecated( __METHOD__, '1.31' ); return [ 'INNER JOIN', [ 'page_id = rev_page' ] ]; } /** * 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(). + * @deprecated since 1.31, use self::getQueryInfo() instead. * @return array */ public static function selectFields() { global $wgContentHandlerUseDB; + wfDeprecated( __METHOD__, '1.31' ); + $fields = [ 'rev_id', 'rev_page', @@ -479,12 +480,14 @@ class Revision implements IDBAccessObject { /** * 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(). + * @deprecated since 1.31, use self::getArchiveQueryInfo() instead. * @return array */ public static function selectArchiveFields() { global $wgContentHandlerUseDB; + + wfDeprecated( __METHOD__, '1.31' ); + $fields = [ 'ar_id', 'ar_page_id', @@ -513,9 +516,11 @@ class Revision implements IDBAccessObject { /** * Return the list of text fields that should be selected to read the * revision text + * @deprecated since 1.31, use self::getQueryInfo( [ 'text' ] ) instead. * @return array */ public static function selectTextFields() { + wfDeprecated( __METHOD__, '1.31' ); return [ 'old_text', 'old_flags' @@ -524,9 +529,11 @@ class Revision implements IDBAccessObject { /** * Return the list of page fields that should be selected from page table + * @deprecated since 1.31, use self::getQueryInfo( [ 'page' ] ) instead. * @return array */ public static function selectPageFields() { + wfDeprecated( __METHOD__, '1.31' ); return [ 'page_namespace', 'page_title', @@ -539,12 +546,128 @@ class Revision implements IDBAccessObject { /** * Return the list of user fields that should be selected from user table + * @deprecated since 1.31, use self::getQueryInfo( [ 'user' ] ) instead. * @return array */ public static function selectUserFields() { + wfDeprecated( __METHOD__, '1.31' ); return [ 'user_name' ]; } + /** + * Return the tables, fields, and join conditions to be selected to create + * a new revision object. + * @since 1.31 + * @param array $options Any combination of the following strings + * - 'page': Join with the page table, and select fields to identify the page + * - 'user': Join with the user table, and select the user name + * - 'text': Join with the text table, and select fields to load page text + * @return array With three keys: + * - tables: (string[]) to include in the `$table` to `IDatabase->select()` + * - fields: (string[]) to include in the `$vars` to `IDatabase->select()` + * - joins: (array) to include in the `$join_conds` to `IDatabase->select()` + */ + public static function getQueryInfo( $options = [] ) { + global $wgContentHandlerUseDB; + + $commentQuery = CommentStore::newKey( 'rev_comment' )->getJoin(); + $ret = [ + 'tables' => [ 'revision' ] + $commentQuery['tables'], + 'fields' => [ + 'rev_id', + 'rev_page', + 'rev_text_id', + 'rev_timestamp', + 'rev_user_text', + 'rev_user', + 'rev_minor_edit', + 'rev_deleted', + 'rev_len', + 'rev_parent_id', + 'rev_sha1', + ] + $commentQuery['fields'], + 'joins' => $commentQuery['joins'], + ]; + + if ( $wgContentHandlerUseDB ) { + $ret['fields'][] = 'rev_content_format'; + $ret['fields'][] = 'rev_content_model'; + } + + if ( in_array( 'page', $options, true ) ) { + $ret['tables'][] = 'page'; + $ret['fields'] = array_merge( $ret['fields'], [ + 'page_namespace', + 'page_title', + 'page_id', + 'page_latest', + 'page_is_redirect', + 'page_len', + ] ); + $ret['joins']['page'] = [ 'INNER JOIN', [ 'page_id = rev_page' ] ]; + } + + if ( in_array( 'user', $options, true ) ) { + $ret['tables'][] = 'user'; + $ret['fields'] = array_merge( $ret['fields'], [ + 'user_name', + ] ); + $ret['joins']['user'] = [ 'LEFT JOIN', [ 'rev_user != 0', 'user_id = rev_user' ] ]; + } + + if ( in_array( 'text', $options, true ) ) { + $ret['tables'][] = 'text'; + $ret['fields'] = array_merge( $ret['fields'], [ + 'old_text', + 'old_flags' + ] ); + $ret['joins']['text'] = [ 'INNER JOIN', [ 'rev_text_id=old_id' ] ]; + } + + return $ret; + } + + /** + * Return the tables, fields, and join conditions to be selected to create + * a new archived revision object. + * @since 1.31 + * @return array With three keys: + * - tables: (string[]) to include in the `$table` to `IDatabase->select()` + * - fields: (string[]) to include in the `$vars` to `IDatabase->select()` + * - joins: (array) to include in the `$join_conds` to `IDatabase->select()` + */ + public static function getArchiveQueryInfo() { + global $wgContentHandlerUseDB; + + $commentQuery = CommentStore::newKey( 'ar_comment' )->getJoin(); + $ret = [ + 'tables' => [ 'archive' ] + $commentQuery['tables'], + 'fields' => [ + 'ar_id', + 'ar_page_id', + 'ar_rev_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'], + ]; + + if ( $wgContentHandlerUseDB ) { + $ret['fields'][] = 'ar_content_format'; + $ret['fields'][] = 'ar_content_model'; + } + + return $ret; + } + /** * Do a batched query to get the parent revision lengths * @param IDatabase $db @@ -571,168 +694,184 @@ class Revision implements IDBAccessObject { * @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 = 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_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 may have come 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(); } /** @@ -821,11 +960,21 @@ class Revision implements IDBAccessObject { // rev_id is defined as NOT NULL, but this revision may not yet have been inserted. if ( $this->mId !== null ) { $dbr = wfGetLB( $this->mWiki )->getConnectionRef( DB_REPLICA, [], $this->mWiki ); + // @todo: Title::getSelectFields(), or Title::getQueryInfo(), or something like that $row = $dbr->selectRow( - [ 'page', 'revision' ], - self::selectPageFields(), - [ 'page_id=rev_page', 'rev_id' => $this->mId ], - __METHOD__ + [ 'revision', 'page' ], + [ + 'page_namespace', + 'page_title', + 'page_id', + 'page_latest', + 'page_is_redirect', + 'page_len', + ], + [ 'rev_id' => $this->mId ], + __METHOD__, + [], + [ 'page' => [ 'JOIN', 'page_id=rev_page' ] ] ); if ( $row ) { // @TODO: better foreign title handling @@ -1941,7 +2090,7 @@ class Revision implements IDBAccessObject { $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 );