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