X-Git-Url: http://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2FRevision.php;h=c1fa4fafe632b4c76a6bb9e35ffc4603af7375d6;hb=107ee818cfd738db3bf1be86a52773e3e7760c0f;hp=8f36e88fbea06079fa13b0d3bbcf482e2b3966f3;hpb=e94861e5dee3105630324558bc0849eee1116d42;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/Revision.php b/includes/Revision.php index 8f36e88fbe..c1fa4fafe6 100644 --- a/includes/Revision.php +++ b/includes/Revision.php @@ -22,12 +22,13 @@ use MediaWiki\Storage\MutableRevisionRecord; use MediaWiki\Storage\RevisionAccessException; +use MediaWiki\Storage\RevisionFactory; +use MediaWiki\Storage\RevisionLookup; use MediaWiki\Storage\RevisionRecord; use MediaWiki\Storage\RevisionStore; use MediaWiki\Storage\RevisionStoreRecord; use MediaWiki\Storage\SlotRecord; use MediaWiki\Storage\SqlBlobStore; -use MediaWiki\User\UserIdentityValue; use Wikimedia\Rdbms\IDatabase; use MediaWiki\Linker\LinkTarget; use MediaWiki\MediaWikiServices; @@ -65,7 +66,21 @@ class Revision implements IDBAccessObject { } /** - * @param bool|string $wikiId The ID of the target wiki database. Use false for the local wiki. + * @return RevisionLookup + */ + protected static function getRevisionLookup() { + return MediaWikiServices::getInstance()->getRevisionLookup(); + } + + /** + * @return RevisionFactory + */ + protected static function getRevisionFactory() { + return MediaWikiServices::getInstance()->getRevisionFactory(); + } + + /** + * @param bool|string $wiki The ID of the target wiki database. Use false for the local wiki. * * @return SqlBlobStore */ @@ -94,54 +109,11 @@ class Revision implements IDBAccessObject { * * @param int $id * @param int $flags (optional) - * @param Title $title (optional) If known you can pass the Title in here. - * Passing no Title may result in another DB query if there are recent writes. * @return Revision|null */ - public static function newFromId( $id, $flags = 0, Title $title = null ) { - /** - * MCR RevisionStore Compat - * - * If the title is not passed in as a param (already known) then select it here. - * - * Do the selection with MASTER if $flags includes READ_LATEST or recent changes - * have happened on our load balancer. - * - * If we select the title here and pass it down it will results in fewer queries - * further down the stack. - */ - if ( !$title ) { - if ( - $flags & self::READ_LATEST || - wfGetLB()->hasOrMadeRecentMasterChanges() - ) { - $dbr = wfGetDB( DB_MASTER ); - } else { - $dbr = wfGetDB( DB_REPLICA ); - } - $row = $dbr->selectRow( - [ 'revision', 'page' ], - [ - 'page_namespace', - 'page_title', - 'page_id', - 'page_latest', - 'page_is_redirect', - 'page_len', - ], - [ 'rev_id' => $id ], - __METHOD__, - [], - [ 'page' => [ 'JOIN', 'page_id=rev_page' ] ] - ); - if ( $row ) { - $title = Title::newFromRow( $row ); - } - wfGetLB()->reuseConnection( $dbr ); - } - - $rec = self::getRevisionStore()->getRevisionById( $id, $flags, $title ); - return $rec === null ? null : new Revision( $rec, $flags, $title ); + public static function newFromId( $id, $flags = 0 ) { + $rec = self::getRevisionLookup()->getRevisionById( $id, $flags ); + return $rec === null ? null : new Revision( $rec, $flags ); } /** @@ -159,7 +131,7 @@ class Revision implements IDBAccessObject { * @return Revision|null */ public static function newFromTitle( LinkTarget $linkTarget, $id = 0, $flags = 0 ) { - $rec = self::getRevisionStore()->getRevisionByTitle( $linkTarget, $id, $flags ); + $rec = self::getRevisionLookup()->getRevisionByTitle( $linkTarget, $id, $flags ); return $rec === null ? null : new Revision( $rec, $flags ); } @@ -178,7 +150,7 @@ class Revision implements IDBAccessObject { * @return Revision|null */ public static function newFromPageId( $pageId, $revId = 0, $flags = 0 ) { - $rec = self::getRevisionStore()->getRevisionByPageId( $pageId, $revId, $flags ); + $rec = self::getRevisionLookup()->getRevisionByPageId( $pageId, $revId, $flags ); return $rec === null ? null : new Revision( $rec, $flags ); } @@ -188,12 +160,11 @@ class Revision implements IDBAccessObject { * * @param object $row * @param array $overrides - * @param Title $title (optional) * * @throws MWException * @return Revision */ - public static function newFromArchiveRow( $row, $overrides = [], Title $title = null ) { + public static function newFromArchiveRow( $row, $overrides = [] ) { /** * MCR Migration: https://phabricator.wikimedia.org/T183564 * This method used to overwrite attributes, then passed to Revision::__construct @@ -205,7 +176,30 @@ class Revision implements IDBAccessObject { unset( $overrides['page'] ); } - $rec = self::getRevisionStore()->newRevisionFromArchiveRow( $row, 0, $title, $overrides ); + /** + * We require a Title for both the Revision object and the RevisionRecord. + * Below is duplicated logic from RevisionStore::newRevisionFromArchiveRow + * to fetch a title in order pass it into the Revision object. + */ + $title = null; + if ( isset( $overrides['title'] ) ) { + if ( !( $overrides['title'] instanceof Title ) ) { + throw new MWException( 'title field override must contain a Title object.' ); + } + + $title = $overrides['title']; + } + if ( $title !== null ) { + if ( isset( $row->ar_namespace ) && isset( $row->ar_title ) ) { + $title = Title::makeTitle( $row->ar_namespace, $row->ar_title ); + } else { + throw new InvalidArgumentException( + 'A Title or ar_namespace and ar_title must be given' + ); + } + } + + $rec = self::getRevisionFactory()->newRevisionFromArchiveRow( $row, 0, $title, $overrides ); return new Revision( $rec, self::READ_NORMAL, $title ); } @@ -214,17 +208,18 @@ class Revision implements IDBAccessObject { * * MCR migration note: replaced by RevisionStore::newRevisionFromRow(). Note that * newFromRow() also accepts arrays, while newRevisionFromRow() does not. Instead, - * a MutableRevisionRecord should be constructed directly. RevisionStore::newRevisionFromArray() - * can be used as a temporary replacement, but should be avoided. + * a MutableRevisionRecord should be constructed directly. + * RevisionStore::newMutableRevisionFromArray() can be used as a temporary replacement, + * but should be avoided. * * @param object|array $row * @return Revision */ public static function newFromRow( $row ) { if ( is_array( $row ) ) { - $rec = self::getRevisionStore()->newMutableRevisionFromArray( $row ); + $rec = self::getRevisionFactory()->newMutableRevisionFromArray( $row ); } else { - $rec = self::getRevisionStore()->newRevisionFromRow( $row ); + $rec = self::getRevisionFactory()->newRevisionFromRow( $row ); } return new Revision( $rec ); @@ -285,7 +280,8 @@ class Revision implements IDBAccessObject { * WARNING: Timestamps may in some circumstances not be unique, * so this isn't the best key to use. * - * @deprecated since 1.31, use RevisionStore::loadRevisionFromTimestamp() instead. + * @deprecated since 1.31, use RevisionStore::getRevisionByTimestamp() + * or RevisionStore::loadRevisionFromTimestamp() instead. * * @param IDatabase $db * @param Title $title @@ -293,7 +289,6 @@ class Revision implements IDBAccessObject { * @return Revision|null */ public static function loadFromTimestamp( $db, $title, $timestamp ) { - // XXX: replace loadRevisionFromTimestamp by getRevisionByTimestamp? $rec = self::getRevisionStore()->loadRevisionFromTimestamp( $db, $title, $timestamp ); return $rec === null ? null : new Revision( $rec ); } @@ -320,7 +315,18 @@ class Revision implements IDBAccessObject { * @return array */ public static function userJoinCond() { + global $wgActorTableSchemaMigrationStage; + wfDeprecated( __METHOD__, '1.31' ); + if ( $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH ) { + // If code is using this instead of self::getQueryInfo(), there's + // no way the join it's trying to do can work once the old fields + // aren't being written anymore. + throw new BadMethodCallException( + 'Cannot use ' . __METHOD__ . ' when $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH' + ); + } + return [ 'LEFT JOIN', [ 'rev_user != 0', 'user_id = rev_user' ] ]; } @@ -343,7 +349,17 @@ class Revision implements IDBAccessObject { * @return array */ public static function selectFields() { - global $wgContentHandlerUseDB; + global $wgContentHandlerUseDB, $wgActorTableSchemaMigrationStage; + + if ( $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH ) { + // If code is using this instead of self::getQueryInfo(), there's a + // decent chance it's going to try to directly access + // $row->rev_user or $row->rev_user_text and we can't give it + // useful values here once those aren't being written anymore. + throw new BadMethodCallException( + 'Cannot use ' . __METHOD__ . ' when $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH' + ); + } wfDeprecated( __METHOD__, '1.31' ); @@ -354,6 +370,7 @@ class Revision implements IDBAccessObject { 'rev_timestamp', 'rev_user_text', 'rev_user', + 'rev_actor' => 'NULL', 'rev_minor_edit', 'rev_deleted', 'rev_len', @@ -361,7 +378,7 @@ class Revision implements IDBAccessObject { 'rev_sha1', ]; - $fields += CommentStore::newKey( 'rev_comment' )->getFields(); + $fields += CommentStore::getStore()->getFields( 'rev_comment' ); if ( $wgContentHandlerUseDB ) { $fields[] = 'rev_content_format'; @@ -378,7 +395,17 @@ class Revision implements IDBAccessObject { * @return array */ public static function selectArchiveFields() { - global $wgContentHandlerUseDB; + global $wgContentHandlerUseDB, $wgActorTableSchemaMigrationStage; + + if ( $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH ) { + // If code is using this instead of self::getQueryInfo(), there's a + // decent chance it's going to try to directly access + // $row->ar_user or $row->ar_user_text and we can't give it + // useful values here once those aren't being written anymore. + throw new BadMethodCallException( + 'Cannot use ' . __METHOD__ . ' when $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH' + ); + } wfDeprecated( __METHOD__, '1.31' ); @@ -386,11 +413,11 @@ class Revision implements IDBAccessObject { 'ar_id', 'ar_page_id', 'ar_rev_id', - 'ar_text', 'ar_text_id', 'ar_timestamp', 'ar_user_text', 'ar_user', + 'ar_actor' => 'NULL', 'ar_minor_edit', 'ar_deleted', 'ar_len', @@ -398,7 +425,7 @@ class Revision implements IDBAccessObject { 'ar_sha1', ]; - $fields += CommentStore::newKey( 'ar_comment' )->getFields(); + $fields += CommentStore::getStore()->getFields( 'ar_comment' ); if ( $wgContentHandlerUseDB ) { $fields[] = 'ar_content_format'; @@ -482,6 +509,9 @@ class Revision implements IDBAccessObject { /** * Do a batched query to get the parent revision lengths + * + * @deprecated in 1.31, use RevisionStore::getRevisionSizes instead. + * * @param IDatabase $db * @param array $revIds * @return array @@ -503,20 +533,22 @@ class Revision implements IDBAccessObject { if ( $row instanceof RevisionRecord ) { $this->mRecord = $row; } elseif ( is_array( $row ) ) { + // If no user is specified, fall back to using the global user object, to stay + // compatible with pre-1.31 behavior. if ( !isset( $row['user'] ) && !isset( $row['user_text'] ) ) { $row['user'] = $wgUser; } - $this->mRecord = self::getRevisionStore()->newMutableRevisionFromArray( + $this->mRecord = self::getRevisionFactory()->newMutableRevisionFromArray( $row, $queryFlags, - $title + $this->ensureTitle( $row, $queryFlags, $title ) ); } elseif ( is_object( $row ) ) { - $this->mRecord = self::getRevisionStore()->newRevisionFromRow( + $this->mRecord = self::getRevisionFactory()->newRevisionFromRow( $row, $queryFlags, - $title + $this->ensureTitle( $row, $queryFlags, $title ) ); } else { throw new InvalidArgumentException( @@ -525,6 +557,51 @@ class Revision implements IDBAccessObject { } } + /** + * Make sure we have *some* Title object for use by the constructor. + * For B/C, the constructor shouldn't fail even for a bad page ID or bad revision ID. + * + * @param array|object $row + * @param int $queryFlags + * @param Title|null $title + * + * @return Title $title if not null, or a Title constructed from information in $row. + */ + private function ensureTitle( $row, $queryFlags, $title = null ) { + if ( $title ) { + return $title; + } + + if ( is_array( $row ) ) { + if ( isset( $row['title'] ) ) { + if ( !( $row['title'] instanceof Title ) ) { + throw new MWException( 'title field must contain a Title object.' ); + } + + return $row['title']; + } + + $pageId = isset( $row['page'] ) ? $row['page'] : 0; + $revId = isset( $row['id'] ) ? $row['id'] : 0; + } else { + $pageId = isset( $row->rev_page ) ? $row->rev_page : 0; + $revId = isset( $row->rev_id ) ? $row->rev_id : 0; + } + + try { + $title = self::getRevisionStore()->getTitle( $pageId, $revId, $queryFlags ); + } catch ( RevisionAccessException $ex ) { + // construct a dummy title! + wfLogWarning( __METHOD__ . ': ' . $ex->getMessage() ); + + // NOTE: this Title will only be used inside RevisionRecord + $title = Title::makeTitleSafe( NS_SPECIAL, "Badtitle/ID=$pageId" ); + $title->resetArticleID( $pageId ); + } + + return $title; + } + /** * @return RevisionRecord */ @@ -577,7 +654,7 @@ class Revision implements IDBAccessObject { */ public function setUserIdAndName( $id, $name ) { if ( $this->mRecord instanceof MutableRevisionRecord ) { - $user = new UserIdentityValue( intval( $id ), $name ); + $user = User::newFromAnyId( intval( $id ), $name, null ); $this->mRecord->setUser( $user ); } else { throw new MWException( __METHOD__ . ' is not supported on this instance' ); @@ -623,20 +700,27 @@ class Revision implements IDBAccessObject { /** * Returns the length of the text in this revision, or null if unknown. * - * @return int + * @return int|null */ public function getSize() { - return $this->mRecord->getSize(); + try { + return $this->mRecord->getSize(); + } catch ( RevisionAccessException $ex ) { + return null; + } } /** * Returns the base36 sha1 of the content in this revision, or null if unknown. * - * @return string + * @return string|null */ public function getSha1() { - // XXX: we may want to drop all the hashing logic, it's not worth the overhead. - return $this->mRecord->getSha1(); + try { + return $this->mRecord->getSha1(); + } catch ( RevisionAccessException $ex ) { + return null; + } } /** @@ -794,7 +878,7 @@ class Revision implements IDBAccessObject { * @return int Rcid of the unpatrolled row, zero if there isn't one */ public function isUnpatrolled() { - return self::getRevisionStore()->isUnpatrolled( $this->mRecord ); + return self::getRevisionStore()->getRcIdIfUnpatrolled( $this->mRecord ); } /** @@ -938,10 +1022,9 @@ class Revision implements IDBAccessObject { * @return Revision|null */ public function getPrevious() { - $rec = self::getRevisionStore()->getPreviousRevision( $this->mRecord, $this->getTitle() ); - return $rec === null - ? null - : new Revision( $rec, self::READ_NORMAL, $this->getTitle() ); + $title = $this->getTitle(); + $rec = self::getRevisionLookup()->getPreviousRevision( $this->mRecord, $title ); + return $rec === null ? null : new Revision( $rec, self::READ_NORMAL, $title ); } /** @@ -950,10 +1033,9 @@ class Revision implements IDBAccessObject { * @return Revision|null */ public function getNext() { - $rec = self::getRevisionStore()->getNextRevision( $this->mRecord, $this->getTitle() ); - return $rec === null - ? null - : new Revision( $rec, self::READ_NORMAL, $this->getTitle() ); + $title = $this->getTitle(); + $rec = self::getRevisionLookup()->getNextRevision( $this->mRecord, $title ); + return $rec === null ? null : new Revision( $rec, self::READ_NORMAL, $title ); } /** @@ -1082,7 +1164,11 @@ class Revision implements IDBAccessObject { $comment = CommentStoreComment::newUnsavedComment( $summary, null ); - $title = Title::newFromID( $pageId ); + $title = Title::newFromID( $pageId, Title::GAID_FOR_UPDATE ); + if ( $title === null ) { + return null; + } + $rec = self::getRevisionStore()->newNullRevision( $dbw, $title, $comment, $minor, $user ); return new Revision( $rec ); @@ -1204,7 +1290,11 @@ class Revision implements IDBAccessObject { ? $pageIdOrTitle : Title::newFromID( $pageIdOrTitle ); - $record = self::getRevisionStore()->getKnownCurrentRevision( $title, $revId ); + if ( !$title ) { + return false; + } + + $record = self::getRevisionLookup()->getKnownCurrentRevision( $title, $revId ); return $record ? new Revision( $record ) : false; } }