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