X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2FStorage%2FRevisionStore.php;h=d832104ca185c07f4fbce16a7b9b86ae148dba49;hb=8fd5a99f4e261d37cd7237a1992b1bcf03a764d3;hp=3101aea754c28c36fd56410797b66d6d241ba14b;hpb=0caca7aefdf9ebb31c90adb25f1bc6758704c42c;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/Storage/RevisionStore.php b/includes/Storage/RevisionStore.php index 3101aea754..d832104ca1 100644 --- a/includes/Storage/RevisionStore.php +++ b/includes/Storage/RevisionStore.php @@ -42,6 +42,9 @@ use MediaWiki\User\UserIdentityValue; use Message; use MWException; use MWUnknownContentModelException; +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerInterface; +use Psr\Log\NullLogger; use RecentChange; use stdClass; use Title; @@ -61,7 +64,8 @@ use Wikimedia\Rdbms\LoadBalancer; * @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 @@ -88,18 +92,30 @@ class RevisionStore implements IDBAccessObject, RevisionFactory, RevisionLookup */ private $cache; + /** + * @var CommentStore + */ + private $commentStore; + + /** + * @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 bool|string $wikiId */ public function __construct( LoadBalancer $loadBalancer, SqlBlobStore $blobStore, WANObjectCache $cache, + CommentStore $commentStore, $wikiId = false ) { Assert::parameterType( 'string|boolean', $wikiId, '$wikiId' ); @@ -107,7 +123,13 @@ class RevisionStore implements IDBAccessObject, RevisionFactory, RevisionLookup $this->loadBalancer = $loadBalancer; $this->blobStore = $blobStore; $this->cache = $cache; + $this->commentStore = $commentStore; $this->wikiId = $wikiId; + $this->logger = new NullLogger(); + } + + public function setLogger( LoggerInterface $logger ) { + $this->logger = $logger; } /** @@ -164,6 +186,8 @@ class RevisionStore implements IDBAccessObject, RevisionFactory, RevisionLookup * * 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 @@ -171,24 +195,35 @@ class RevisionStore implements IDBAccessObject, RevisionFactory, RevisionLookup * @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' ], @@ -207,17 +242,25 @@ class RevisionStore implements IDBAccessObject, RevisionFactory, RevisionLookup ); 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' => wfDebugBacktrace() ] + ); + return $title; + } } - return $title; + throw new RevisionAccessException( + "Could not determine title for page ID $pageId and revision ID $revId" + ); } /** @@ -255,8 +298,8 @@ class RevisionStore implements IDBAccessObject, RevisionFactory, RevisionLookup } /** - * 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 * @@ -358,7 +401,7 @@ class RevisionStore implements IDBAccessObject, RevisionFactory, RevisionLookup } list( $commentFields, $commentCallback ) = - CommentStore::newKey( 'rev_comment' )->insertWithTempTable( $dbw, $comment ); + $this->commentStore->insertWithTempTable( $dbw, 'rev_comment', $comment ); $row += $commentFields; if ( $this->contentHandlerUseDB ) { @@ -687,7 +730,7 @@ class RevisionStore implements IDBAccessObject, RevisionFactory, RevisionLookup $content = null; $blobData = null; - $blobFlags = ''; + $blobFlags = null; if ( is_object( $row ) ) { // archive row @@ -704,7 +747,11 @@ class RevisionStore implements IDBAccessObject, RevisionFactory, RevisionLookup 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 ); @@ -733,7 +780,9 @@ class RevisionStore implements IDBAccessObject, RevisionFactory, RevisionLookup $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'] ) ) { @@ -795,7 +844,9 @@ class RevisionStore implements IDBAccessObject, RevisionFactory, RevisionLookup * * @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 * @@ -805,23 +856,28 @@ class RevisionStore implements IDBAccessObject, RevisionFactory, RevisionLookup 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 { @@ -1021,9 +1077,9 @@ class RevisionStore implements IDBAccessObject, RevisionFactory, RevisionLookup $user = $this->getUserIdentityFromRowObject( $row, 'ar_' ); - $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 ] ); @@ -1091,9 +1147,9 @@ class RevisionStore implements IDBAccessObject, RevisionFactory, RevisionLookup $user = $this->getUserIdentityFromRowObject( $row ); - $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 ] ); @@ -1566,7 +1622,7 @@ class RevisionStore implements IDBAccessObject, RevisionFactory, RevisionLookup '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'] ); @@ -1623,7 +1679,7 @@ class RevisionStore implements IDBAccessObject, RevisionFactory, RevisionLookup * - 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' ); $ret = [ 'tables' => [ 'archive' ] + $commentQuery['tables'], 'fields' => [ @@ -1707,11 +1763,14 @@ class RevisionStore implements IDBAccessObject, RevisionFactory, RevisionLookup * 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 ); @@ -1725,11 +1784,14 @@ class RevisionStore implements IDBAccessObject, RevisionFactory, RevisionLookup * 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 );