Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
34 / 34
100.00% covered (success)
100.00%
13 / 13
CRAP
100.00% covered (success)
100.00%
1 / 1
EditResult
100.00% covered (success)
100.00%
34 / 34
100.00% covered (success)
100.00%
13 / 13
15
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 newFromArray
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
1
 getNewestRevertedRevisionId
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getOldestRevertedRevisionId
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getUndidRevId
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getOriginalRevisionId
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isNew
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isRevert
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 getRevertMethod
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isExactRevert
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isNullEdit
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getRevertTags
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 jsonSerialize
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * @license GPL-2.0-or-later
4 * @file
5 */
6
7namespace MediaWiki\Storage;
8
9use JsonSerializable;
10
11/**
12 * Object for storing information about the effects of an edit.
13 *
14 * This object should be constructed by an EditResultBuilder with relevant information filled in
15 * during the process of saving the revision by the PageUpdater. You can use it to extract
16 * information about whether the edit was a revert and which edits were reverted.
17 *
18 * @since 1.35
19 * @author Ostrzyciel
20 */
21class EditResult implements JsonSerializable {
22
23    // revert methods
24    public const REVERT_UNDO = 1;
25    public const REVERT_ROLLBACK = 2;
26    public const REVERT_MANUAL = 3;
27
28    private const SERIALIZATION_FORMAT_VERSION = '1';
29
30    /** @var bool|int */
31    private $originalRevisionId;
32
33    /**
34     * @param bool $isNew
35     * @param bool|int $originalRevisionId
36     * @param int|null $revertMethod
37     * @param int|null $oldestRevertedRevId
38     * @param int|null $newestRevertedRevId
39     * @param bool $isExactRevert
40     * @param bool $isNullEdit
41     * @param string[] $revertTags
42     *
43     * @internal Use EditResultBuilder for constructing EditResults.
44     */
45    public function __construct(
46        private readonly bool $isNew,
47        $originalRevisionId,
48        private readonly ?int $revertMethod,
49        private readonly ?int $oldestRevertedRevId,
50        private readonly ?int $newestRevertedRevId,
51        private readonly bool $isExactRevert,
52        private readonly bool $isNullEdit,
53        private readonly array $revertTags,
54    ) {
55        $this->originalRevisionId = $originalRevisionId;
56    }
57
58    /**
59     * Recreate the EditResult object from its array representation.
60     *
61     * This must ONLY be used for deserializing EditResult objects serialized using
62     * EditResult::jsonSerialize(). The structure of the array may change without prior
63     * notice.
64     *
65     * Any changes to the format are guaranteed to be backwards-compatible, so this
66     * method will work fine with old serialized EditResults.
67     *
68     * For constructing EditResult objects from scratch use EditResultBuilder.
69     *
70     * @see EditResult::jsonSerialize()
71     *
72     * @param array $a
73     * @phpcs:ignore Generic.Files.LineLength
74     * @phan-param array{isNew:bool,originalRevisionId:bool|int,revertMethod:int|null,newestRevertedRevId:int|null,oldestRevertedRevId:int|null,isExactRevert:bool,isNullEdit:bool,revertTags:string[],version:string} $a
75     *
76     * @return EditResult
77     *
78     * @since 1.36
79     */
80    public static function newFromArray( array $a ) {
81        return new self(
82            $a['isNew'],
83            $a['originalRevisionId'],
84            $a['revertMethod'],
85            $a['oldestRevertedRevId'],
86            $a['newestRevertedRevId'],
87            $a['isExactRevert'],
88            $a['isNullEdit'],
89            $a['revertTags']
90        );
91    }
92
93    /**
94     * Returns the ID of the most recent revision that was reverted by this edit.
95     * The same as getOldestRevertedRevisionId if only a single revision was
96     * reverted. Returns null if the edit was not a revert.
97     *
98     * @see EditResult::isRevert() for information on how a revert is defined
99     *
100     * @return int|null
101     */
102    public function getNewestRevertedRevisionId(): ?int {
103        return $this->newestRevertedRevId;
104    }
105
106    /**
107     * Returns the ID of the oldest revision that was reverted by this edit.
108     * The same as getNewestRevertedRevisionId if only a single revision was
109     * reverted. Returns null if the edit was not a revert.
110     *
111     * @see EditResult::isRevert() for information on how a revert is defined
112     *
113     * @return int|null
114     */
115    public function getOldestRevertedRevisionId(): ?int {
116        return $this->oldestRevertedRevId;
117    }
118
119    /**
120     * If the edit was an undo, returns the oldest revision that was undone.
121     * Method kept for compatibility reasons.
122     */
123    public function getUndidRevId(): int {
124        if ( $this->getRevertMethod() !== self::REVERT_UNDO ) {
125            return 0;
126        }
127        return $this->getOldestRevertedRevisionId() ?? 0;
128    }
129
130    /**
131     * Returns the ID of an earlier revision that is being repeated or restored.
132     *
133     * The original revision's content should match the new revision exactly.
134     *
135     * @return bool|int The original revision id, or false if no earlier revision is known to be
136     * repeated or restored.
137     * The old PageUpdater::getOriginalRevisionId() returned false in such cases. This value would
138     * be then passed on to extensions through hooks, so it may be wise to keep compatibility with
139     * the old behavior.
140     */
141    public function getOriginalRevisionId() {
142        return $this->originalRevisionId;
143    }
144
145    /**
146     * Whether the edit created a new page
147     */
148    public function isNew(): bool {
149        return $this->isNew;
150    }
151
152    /**
153     * Whether the edit was a revert, not necessarily exact.
154     *
155     * An edit is considered a revert if it either:
156     * - Restores the page to an exact previous state (rollbacks, manual reverts and some undos).
157     *   E.g. for edits A B C D, edits C and D are reverted.
158     * - Undoes some edits made previously, not necessarily restoring the page to an exact
159     *   previous state (undo). It is guaranteed that the revert was a "clean" result of a
160     *   three-way merge and no additional changes were made by the reverting user.
161     *   E.g. for edits A B C D, edits B and C are reverted.
162     *
163     * To check whether the edit was an exact revert, please use the isExactRevert() method.
164     * The getRevertMethod() will provide additional information about which kind of revert
165     * was made.
166     */
167    public function isRevert(): bool {
168        return !$this->isNew() && $this->getOldestRevertedRevisionId();
169    }
170
171    /**
172     * Returns the revert method that was used to perform the edit, if any changes were reverted.
173     * Returns null if the edit was not a revert.
174     *
175     * Possible values: REVERT_UNDO, REVERT_ROLLBACK, REVERT_MANUAL
176     *
177     * @see EditResult::isRevert()
178     *
179     * @return int|null
180     */
181    public function getRevertMethod(): ?int {
182        return $this->revertMethod;
183    }
184
185    /**
186     * Whether the edit was an exact revert,
187     * i.e. the contents of the revert revision and restored revision match
188     */
189    public function isExactRevert(): bool {
190        return $this->isExactRevert;
191    }
192
193    /**
194     * An edit is considered null edit if the original revision is equal to the
195     * parent revision, i.e. no changes were made.
196     *
197     * @note The term "null edit" is here understood to include dummy revisions,
198     * which is different from how that term is used elsewhere in the code.
199     */
200    public function isNullEdit(): bool {
201        return $this->isNullEdit;
202    }
203
204    /**
205     * Returns an array of revert-related tags that were applied automatically to this edit.
206     *
207     * @return string[]
208     */
209    public function getRevertTags(): array {
210        return $this->revertTags;
211    }
212
213    /**
214     * Returns an array representing the EditResult object.
215     *
216     * @see EditResult::newFromArray()
217     *
218     * @return array
219     * @phpcs:ignore Generic.Files.LineLength
220     * @phan-return array{isNew:bool,originalRevisionId:bool|int,revertMethod:int|null,newestRevertedRevId:int|null,oldestRevertedRevId:int|null,isExactRevert:bool,isNullEdit:bool,revertTags:string[],version:string}
221     *
222     * @since 1.36
223     */
224    public function jsonSerialize(): array {
225        return [
226            'isNew' => $this->isNew,
227            'originalRevisionId' => $this->originalRevisionId,
228            'revertMethod' => $this->revertMethod,
229            'newestRevertedRevId' => $this->newestRevertedRevId,
230            'oldestRevertedRevId' => $this->oldestRevertedRevId,
231            'isExactRevert' => $this->isExactRevert,
232            'isNullEdit' => $this->isNullEdit,
233            'revertTags' => $this->revertTags,
234            'version' => self::SERIALIZATION_FORMAT_VERSION
235        ];
236    }
237}