Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
61.11% covered (warning)
61.11%
44 / 72
81.82% covered (warning)
81.82%
18 / 22
CRAP
0.00% covered (danger)
0.00%
0 / 1
MutableRevisionRecord
61.11% covered (warning)
61.11%
44 / 72
81.82% covered (warning)
81.82%
18 / 22
78.46
0.00% covered (danger)
0.00%
0 / 1
 newFromParentRevision
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 newUpdatedRevisionRecord
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
12
 __construct
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 setParentId
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setSlot
37.50% covered (danger)
37.50%
3 / 8
0.00% covered (danger)
0.00%
0 / 1
5.20
 inheritSlot
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setContent
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 removeSlot
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 applyUpdate
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setComment
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setSha1
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setSize
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setVisibility
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setTimestamp
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setMinorEdit
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setId
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setUser
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setPageId
57.14% covered (warning)
57.14%
4 / 7
0.00% covered (danger)
0.00%
0 / 1
3.71
 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%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getSlots
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 resetAggregateValues
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * Mutable RevisionRecord implementation, for building new revision entries programmatically.
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @file
21 */
22
23namespace MediaWiki\Revision;
24
25use Content;
26use InvalidArgumentException;
27use MediaWiki\CommentStore\CommentStoreComment;
28use MediaWiki\Page\PageIdentity;
29use MediaWiki\Storage\RevisionSlotsUpdate;
30use MediaWiki\User\UserIdentity;
31use MediaWiki\Utils\MWTimestamp;
32
33/**
34 * Mutable RevisionRecord implementation, for building new revision entries programmatically.
35 * Provides setters for all fields.
36 *
37 * @newable
38 *
39 * @since 1.31
40 * @since 1.32 Renamed from MediaWiki\Storage\MutableRevisionRecord
41 * @property MutableRevisionSlots $mSlots
42 */
43class MutableRevisionRecord extends RevisionRecord {
44
45    /**
46     * Returns an incomplete MutableRevisionRecord which uses $parent as its
47     * parent revision, and inherits all slots form it. If saved unchanged,
48     * the new revision will act as a null-revision.
49     *
50     * @param RevisionRecord $parent
51     *
52     * @return MutableRevisionRecord
53     */
54    public static function newFromParentRevision( RevisionRecord $parent ) {
55        $rev = new MutableRevisionRecord( $parent->getPage(), $parent->getWikiId() );
56
57        foreach ( $parent->getSlotRoles() as $role ) {
58            $slot = $parent->getSlot( $role, self::RAW );
59            $rev->inheritSlot( $slot );
60        }
61
62        $rev->setPageId( $parent->getPageId() );
63        $rev->setParentId( $parent->getId() );
64
65        return $rev;
66    }
67
68    /**
69     * Returns a MutableRevisionRecord which is an updated version of $revision with $slots
70     * added.
71     * @param RevisionRecord $revision
72     * @param SlotRecord[] $slots
73     * @return MutableRevisionRecord
74     * @since 1.36
75     */
76    public static function newUpdatedRevisionRecord(
77        RevisionRecord $revision,
78        array $slots
79    ): MutableRevisionRecord {
80        $newRevisionRecord = new MutableRevisionRecord(
81            $revision->getPage(),
82            $revision->getWikiId()
83        );
84
85        $newRevisionRecord->setId( $revision->getId( $revision->getWikiId() ) );
86        $newRevisionRecord->setPageId( $revision->getPageId( $revision->getWikiId() ) );
87        $newRevisionRecord->setParentId( $revision->getParentId( $revision->getWikiId() ) );
88        $newRevisionRecord->setUser( $revision->getUser() );
89
90        foreach ( $revision->getSlots()->getSlots() as $slot ) {
91            $newRevisionRecord->setSlot( $slot );
92        }
93
94        foreach ( $slots as $slot ) {
95            $newRevisionRecord->setSlot( $slot );
96        }
97
98        return $newRevisionRecord;
99    }
100
101    /**
102     * @stable to call.
103     *
104     * @param PageIdentity $page The page this RevisionRecord is associated with.
105     * @param false|string $wikiId Relevant wiki id or self::LOCAL for the current one.
106     */
107    public function __construct( PageIdentity $page, $wikiId = self::LOCAL ) {
108        $slots = new MutableRevisionSlots( [], function () {
109            $this->resetAggregateValues();
110        } );
111
112        parent::__construct( $page, $slots, $wikiId );
113    }
114
115    /**
116     * @param int $parentId
117     * @return self
118     */
119    public function setParentId( int $parentId ) {
120        $this->mParentId = $parentId;
121
122        return $this;
123    }
124
125    /**
126     * Sets the given slot. If a slot with the same role is already present in the revision,
127     * it is replaced.
128     *
129     * @note This can only be used with a fresh "unattached" SlotRecord. Calling code that has a
130     * SlotRecord from another revision should use inheritSlot(). Calling code that has access to
131     * a Content object can use setContent().
132     *
133     * @note This may cause the slot meta-data for the revision to be lazy-loaded.
134     *
135     * @note Calling this method will cause the revision size and hash to be re-calculated upon
136     *       the next call to getSize() and getSha1(), respectively.
137     *
138     * @param SlotRecord $slot
139     * @return self
140     */
141    public function setSlot( SlotRecord $slot ) {
142        if ( $slot->hasRevision() && $slot->getRevision() !== $this->getId() ) {
143            throw new InvalidArgumentException(
144                'The given slot must be an unsaved, unattached one. '
145                . 'This slot is already attached to revision ' . $slot->getRevision() . '. '
146                . 'Use inheritSlot() instead to preserve a slot from a previous revision.'
147            );
148        }
149
150        $this->mSlots->setSlot( $slot );
151
152        return $this;
153    }
154
155    /**
156     * "Inherits" the given slot's content.
157     *
158     * If a slot with the same role is already present in the revision, it is replaced.
159     *
160     * @note This may cause the slot meta-data for the revision to be lazy-loaded.
161     *
162     * @param SlotRecord $parentSlot
163     * @return self
164     */
165    public function inheritSlot( SlotRecord $parentSlot ) {
166        $this->mSlots->inheritSlot( $parentSlot );
167
168        return $this;
169    }
170
171    /**
172     * Sets the content for the slot with the given role.
173     *
174     * If a slot with the same role is already present in the revision, it is replaced.
175     * Calling code that has access to a SlotRecord can use inheritSlot() instead.
176     *
177     * @note This may cause the slot meta-data for the revision to be lazy-loaded.
178     *
179     * @note Calling this method will cause the revision size and hash to be re-calculated upon
180     *       the next call to getSize() and getSha1(), respectively.
181     *
182     * @param string $role
183     * @param Content $content
184     * @return self
185     */
186    public function setContent( $role, Content $content ) {
187        $this->mSlots->setContent( $role, $content );
188
189        return $this;
190    }
191
192    /**
193     * Removes the slot with the given role from this revision.
194     * This effectively ends the "stream" with that role on the revision's page.
195     * Future revisions will no longer inherit this slot, unless it is added back explicitly.
196     *
197     * @note This may cause the slot meta-data for the revision to be lazy-loaded.
198     *
199     * @note Calling this method will cause the revision size and hash to be re-calculated upon
200     *       the next call to getSize() and getSha1(), respectively.
201     *
202     * @param string $role
203     * @return self
204     */
205    public function removeSlot( $role ) {
206        $this->mSlots->removeSlot( $role );
207
208        return $this;
209    }
210
211    /**
212     * Applies the given update to the slots of this revision.
213     *
214     * @param RevisionSlotsUpdate $update
215     * @return self
216     */
217    public function applyUpdate( RevisionSlotsUpdate $update ) {
218        $update->apply( $this->mSlots );
219
220        return $this;
221    }
222
223    /**
224     * @param CommentStoreComment $comment
225     * @return self
226     */
227    public function setComment( CommentStoreComment $comment ) {
228        $this->mComment = $comment;
229
230        return $this;
231    }
232
233    /**
234     * Set revision hash, for optimization. Prevents getSha1() from re-calculating the hash.
235     *
236     * @note This should only be used if the calling code is sure that the given hash is correct
237     * for the revision's content, and there is no chance of the content being manipulated
238     * later. When in doubt, this method should not be called.
239     *
240     * @param string $sha1 SHA1 hash as a base36 string.
241     * @return self
242     */
243    public function setSha1( string $sha1 ) {
244        $this->mSha1 = $sha1;
245
246        return $this;
247    }
248
249    /**
250     * Set nominal revision size, for optimization. Prevents getSize() from re-calculating the size.
251     *
252     * @note This should only be used if the calling code is sure that the given size is correct
253     * for the revision's content, and there is no chance of the content being manipulated
254     * later. When in doubt, this method should not be called.
255     *
256     * @param int $size nominal size in bogo-bytes
257     * @return self
258     */
259    public function setSize( int $size ) {
260        $this->mSize = $size;
261
262        return $this;
263    }
264
265    /**
266     * @param int $visibility
267     * @return self
268     */
269    public function setVisibility( int $visibility ) {
270        $this->mDeleted = $visibility;
271
272        return $this;
273    }
274
275    /**
276     * @param string $timestamp A timestamp understood by MWTimestamp
277     * @return self
278     */
279    public function setTimestamp( string $timestamp ) {
280        $this->mTimestamp = MWTimestamp::convert( TS_MW, $timestamp );
281
282        return $this;
283    }
284
285    /**
286     * @param bool $minorEdit
287     * @return self
288     */
289    public function setMinorEdit( bool $minorEdit ) {
290        $this->mMinorEdit = $minorEdit;
291
292        return $this;
293    }
294
295    /**
296     * Set the revision ID.
297     *
298     * MCR migration note: this replaced Revision::setId
299     *
300     * @warning Use this with care, especially when preparing a revision for insertion
301     *          into the database! The revision ID should only be fixed in special cases
302     *          like preserving the original ID when restoring a revision.
303     *
304     * @param int $id
305     * @return self
306     */
307    public function setId( int $id ) {
308        $this->mId = $id;
309
310        return $this;
311    }
312
313    /**
314     * Sets the user identity associated with the revision
315     *
316     * @param UserIdentity $user
317     * @return self
318     */
319    public function setUser( UserIdentity $user ) {
320        $this->mUser = $user;
321
322        return $this;
323    }
324
325    /**
326     * @param int $pageId
327     * @return self
328     */
329    public function setPageId( int $pageId ) {
330        $pageIdBasedOnPage = $this->getArticleId( $this->mPage );
331        if ( $pageIdBasedOnPage && $pageIdBasedOnPage !== $this->getArticleId( $this->mPage ) ) {
332            throw new InvalidArgumentException(
333                'The given page does not belong to page ID ' . $this->mPageId
334            );
335        }
336
337        $this->mPageId = $pageId;
338
339        return $this;
340    }
341
342    /**
343     * Returns the nominal size of this revision.
344     *
345     * MCR migration note: this replaced Revision::getSize
346     *
347     * @return int The nominal size, may be computed on the fly if not yet known.
348     */
349    public function getSize() {
350        // If not known, re-calculate and remember. Will be reset when slots change.
351        $this->mSize ??= $this->mSlots->computeSize();
352
353        return $this->mSize;
354    }
355
356    /**
357     * Returns the base36 sha1 of this revision.
358     *
359     * MCR migration note: this replaced Revision::getSha1
360     *
361     * @return string The revision hash, may be computed on the fly if not yet known.
362     */
363    public function getSha1() {
364        // If not known, re-calculate and remember. Will be reset when slots change.
365        $this->mSha1 ??= $this->mSlots->computeSha1();
366
367        return $this->mSha1;
368    }
369
370    /**
371     * Returns the slots defined for this revision as a MutableRevisionSlots instance,
372     * which can be modified to defined the slots for this revision.
373     *
374     * @return MutableRevisionSlots
375     */
376    public function getSlots(): MutableRevisionSlots {
377        // Overwritten just to guarantee the more narrow return type.
378        // @phan-suppress-next-line PhanTypeMismatchReturnSuperType
379        return parent::getSlots();
380    }
381
382    /**
383     * Invalidate cached aggregate values such as hash and size.
384     * Used as a callback by MutableRevisionSlots.
385     */
386    private function resetAggregateValues() {
387        $this->mSize = null;
388        $this->mSha1 = null;
389    }
390
391}