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