Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
90.00% covered (success)
90.00%
45 / 50
91.67% covered (success)
91.67%
11 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
RevisionStoreRecord
90.00% covered (success)
90.00%
45 / 50
91.67% covered (success)
91.67%
11 / 12
22.48
0.00% covered (danger)
0.00%
0 / 1
 __construct
85.29% covered (warning)
85.29%
29 / 34
0.00% covered (danger)
0.00%
0 / 1
7.16
 getPage
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isCurrent
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isDeleted
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 userCan
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 getId
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getSize
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getSha1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getUser
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getComment
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getTimestamp
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isReadyForInsertion
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * A RevisionRecord representing an existing revision persisted in the revision table.
4 *
5 * @license GPL-2.0-or-later
6 * @file
7 */
8
9namespace MediaWiki\Revision;
10
11use InvalidArgumentException;
12use MediaWiki\CommentStore\CommentStoreComment;
13use MediaWiki\Page\PageIdentity;
14use MediaWiki\Page\ProperPageIdentity;
15use MediaWiki\Permissions\Authority;
16use MediaWiki\User\UserIdentity;
17use MediaWiki\Utils\MWTimestamp;
18use Wikimedia\Assert\Assert;
19use Wikimedia\Timestamp\TimestampFormat as TS;
20
21/**
22 * A RevisionRecord representing an existing revision persisted in the revision table.
23 * RevisionStoreRecord has no optional fields, getters will never return null.
24 *
25 * @since 1.31
26 * @since 1.32 Renamed from MediaWiki\Storage\RevisionStoreRecord
27 */
28class RevisionStoreRecord extends RevisionRecord {
29
30    /** @var bool */
31    protected $mCurrent = false;
32
33    /**
34     * @note Avoid calling this constructor directly. Use the appropriate methods
35     * in RevisionStore instead.
36     *
37     * @param PageIdentity $page The page this RevisionRecord is associated with.
38     * @param UserIdentity $user
39     * @param CommentStoreComment $comment
40     * @param \stdClass $row A row from the revision table. Use RevisionStore::getQueryInfo() to build
41     *        a query that yields the required fields.
42     * @param RevisionSlots $slots The slots of this revision.
43     * @param false|string $wikiId Relevant wiki id or self::LOCAL for the current one.
44     */
45    public function __construct(
46        PageIdentity $page,
47        UserIdentity $user,
48        CommentStoreComment $comment,
49        \stdClass $row,
50        RevisionSlots $slots,
51        $wikiId = self::LOCAL
52    ) {
53        parent::__construct( $page, $slots, $wikiId );
54        $this->mId = intval( $row->rev_id );
55        $this->mPageId = intval( $row->rev_page );
56        $this->mComment = $comment;
57
58        Assert::parameter(
59            $page->exists(),
60            '$page',
61            'must represent an existing page'
62        );
63        Assert::parameter(
64            $page->getId( $wikiId ) === $this->mPageId,
65            '$page',
66            'must match the rev_page field in $row'
67        );
68        Assert::postcondition(
69            parent::getPage() instanceof ProperPageIdentity,
70            'The parent constructor should have ensured that we have a ProperPageIdentity now.'
71        );
72
73        // Don't use MWTimestamp::convert, instead let any detailed exception from MWTimestamp
74        // bubble up (T254210)
75        $timestamp = ( new MWTimestamp( $row->rev_timestamp ) )->getTimestamp( TS::MW );
76
77        $this->mUser = $user;
78        $this->mMinorEdit = (bool)$row->rev_minor_edit;
79        $this->mTimestamp = $timestamp;
80        $this->mDeleted = intval( $row->rev_deleted );
81
82        // NOTE: rev_parent_id = 0 indicates that there is no parent revision, while null
83        // indicates that the parent revision is unknown. As per MW 1.31, the database schema
84        // allows rev_parent_id to be NULL.
85        $this->mParentId = isset( $row->rev_parent_id ) ? intval( $row->rev_parent_id ) : null;
86        $this->mSize = isset( $row->rev_len ) ? intval( $row->rev_len ) : null;
87
88        // NOTE: we must not call $this->mTitle->getLatestRevID() here, since the state of
89        // page_latest may be in limbo during revision creation. In that case, calling
90        // $this->mTitle->getLatestRevID() would cause a bad value to be cached in the Title
91        // object. During page creation, that bad value would be 0.
92        if ( isset( $row->page_latest ) ) {
93            $this->mCurrent = ( $row->rev_id == $row->page_latest );
94        }
95
96        $pageIdBasedOnPage = $this->getArticleId( $this->mPage );
97        if ( $this->mPageId && $pageIdBasedOnPage && $this->mPageId !== $pageIdBasedOnPage ) {
98            throw new InvalidArgumentException(
99                'The given page (' . $this->mPage . ')' .
100                ' does not belong to page ID ' . $this->mPageId .
101                ' but actually belongs to ' . $this->getArticleId( $this->mPage )
102            );
103        }
104    }
105
106    /**
107     * Returns the page this revision belongs to.
108     *
109     * @return ProperPageIdentity (before 1.44, this was returning a PageIdentity)
110     */
111    public function getPage(): ProperPageIdentity {
112        // Override to narrow the return type.
113        // We checked in the constructor that the page is a proper page.
114        // @phan-suppress-next-line PhanTypeMismatchReturnSuperType
115        return parent::getPage();
116    }
117
118    /**
119     * @inheritDoc
120     */
121    public function isCurrent() {
122        return $this->mCurrent;
123    }
124
125    /** @inheritDoc */
126    public function isDeleted( int $field ) {
127        if ( $this->isCurrent() && $field === self::DELETED_TEXT ) {
128            // Latest revisions of pages cannot have the content hidden. Skipping this
129            // check is very useful for Parser as it fetches templates using newKnownCurrent().
130            // Calling getVisibility() in that case triggers a verification database query.
131            return false; // no need to check
132        }
133
134        return parent::isDeleted( $field );
135    }
136
137    /** @inheritDoc */
138    public function userCan( int $field, Authority $performer ) {
139        if ( $this->isCurrent() && $field === self::DELETED_TEXT ) {
140            // Latest revisions of pages cannot have the content hidden. Skipping this
141            // check is very useful for Parser as it fetches templates using newKnownCurrent().
142            // Calling getVisibility() in that case triggers a verification database query.
143            return true; // no need to check
144        }
145
146        return parent::userCan( $field, $performer );
147    }
148
149    /**
150     * @param string|false $wikiId The wiki ID expected by the caller.
151     * @return int|null The revision id, never null.
152     */
153    public function getId( $wikiId = self::LOCAL ) {
154        // overwritten just to add a guarantee to the contract
155        return parent::getId( $wikiId );
156    }
157
158    /**
159     * @throws RevisionAccessException if the size was unknown and could not be calculated.
160     * @return int The nominal revision size, never null. May be computed on the fly.
161     */
162    public function getSize() {
163        // If length is null, calculate and remember it (potentially SLOW!).
164        // This is for compatibility with old database rows that don't have the field set.
165        $this->mSize ??= $this->mSlots->computeSize();
166
167        return $this->mSize;
168    }
169
170    /**
171     * @throws RevisionAccessException if the hash was unknown and could not be calculated.
172     * @return string The revision hash, never null. May be computed on the fly.
173     */
174    public function getSha1() {
175        return $this->mSlots->computeSha1();
176    }
177
178    /** @inheritDoc */
179    public function getUser( int $audience = self::FOR_PUBLIC, ?Authority $performer = null ) {
180        // overwritten just to add a guarantee to the contract
181        return parent::getUser( $audience, $performer );
182    }
183
184    /** @inheritDoc */
185    public function getComment( int $audience = self::FOR_PUBLIC, ?Authority $performer = null ) {
186        // overwritten just to add a guarantee to the contract
187        return parent::getComment( $audience, $performer );
188    }
189
190    /**
191     * @return string timestamp, never null
192     */
193    public function getTimestamp() {
194        // overwritten just to add a guarantee to the contract
195        return parent::getTimestamp();
196    }
197
198    /**
199     * @see RevisionStore::isComplete
200     *
201     * @return bool always true.
202     */
203    public function isReadyForInsertion() {
204        return true;
205    }
206
207}