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 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
17 *
18 * @file
19 */
20
21namespace MediaWiki\Storage;
22
23use JsonSerializable;
24
25/**
26 * Object for storing information about the effects of an edit.
27 *
28 * This object should be constructed by an EditResultBuilder with relevant information filled in
29 * during the process of saving the revision by the PageUpdater. You can use it to extract
30 * information about whether the edit was a revert and which edits were reverted.
31 *
32 * @since 1.35
33 * @author Ostrzyciel
34 */
35class EditResult implements JsonSerializable {
36
37    // revert methods
38    public const REVERT_UNDO = 1;
39    public const REVERT_ROLLBACK = 2;
40    public const REVERT_MANUAL = 3;
41
42    private const SERIALIZATION_FORMAT_VERSION = '1';
43
44    /** @var bool */
45    private $isNew;
46
47    /** @var bool|int */
48    private $originalRevisionId;
49
50    /** @var int|null */
51    private $revertMethod;
52
53    /** @var int|null */
54    private $newestRevertedRevId;
55
56    /** @var int|null */
57    private $oldestRevertedRevId;
58
59    /** @var bool */
60    private $isExactRevert;
61
62    /** @var bool */
63    private $isNullEdit;
64
65    /** @var string[] */
66    private $revertTags;
67
68    /**
69     * @param bool $isNew
70     * @param bool|int $originalRevisionId
71     * @param int|null $revertMethod
72     * @param int|null $oldestReverted
73     * @param int|null $newestReverted
74     * @param bool $isExactRevert
75     * @param bool $isNullEdit
76     * @param string[] $revertTags
77     *
78     * @internal Use EditResultBuilder for constructing EditResults.
79     */
80    public function __construct(
81        bool $isNew,
82        $originalRevisionId,
83        ?int $revertMethod,
84        ?int $oldestReverted,
85        ?int $newestReverted,
86        bool $isExactRevert,
87        bool $isNullEdit,
88        array $revertTags
89    ) {
90        $this->isNew = $isNew;
91        $this->originalRevisionId = $originalRevisionId;
92        $this->revertMethod = $revertMethod;
93        $this->oldestRevertedRevId = $oldestReverted;
94        $this->newestRevertedRevId = $newestReverted;
95        $this->isExactRevert = $isExactRevert;
96        $this->isNullEdit = $isNullEdit;
97        $this->revertTags = $revertTags;
98    }
99
100    /**
101     * Recreate the EditResult object from its array representation.
102     *
103     * This must ONLY be used for deserializing EditResult objects serialized using
104     * EditResult::jsonSerialize(). The structure of the array may change without prior
105     * notice.
106     *
107     * Any changes to the format are guaranteed to be backwards-compatible, so this
108     * method will work fine with old serialized EditResults.
109     *
110     * For constructing EditResult objects from scratch use EditResultBuilder.
111     *
112     * @see EditResult::jsonSerialize()
113     *
114     * @param array $a
115     * @phpcs:ignore Generic.Files.LineLength
116     * @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
117     *
118     * @return EditResult
119     *
120     * @since 1.36
121     */
122    public static function newFromArray( array $a ) {
123        return new self(
124            $a['isNew'],
125            $a['originalRevisionId'],
126            $a['revertMethod'],
127            $a['oldestRevertedRevId'],
128            $a['newestRevertedRevId'],
129            $a['isExactRevert'],
130            $a['isNullEdit'],
131            $a['revertTags']
132        );
133    }
134
135    /**
136     * Returns the ID of the most recent revision that was reverted by this edit.
137     * The same as getOldestRevertedRevisionId if only a single revision was
138     * reverted. Returns null if the edit was not a revert.
139     *
140     * @see EditResult::isRevert() for information on how a revert is defined
141     *
142     * @return int|null
143     */
144    public function getNewestRevertedRevisionId(): ?int {
145        return $this->newestRevertedRevId;
146    }
147
148    /**
149     * Returns the ID of the oldest revision that was reverted by this edit.
150     * The same as getOldestRevertedRevisionId if only a single revision was
151     * reverted. Returns null if the edit was not a revert.
152     *
153     * @see EditResult::isRevert() for information on how a revert is defined
154     *
155     * @return int|null
156     */
157    public function getOldestRevertedRevisionId(): ?int {
158        return $this->oldestRevertedRevId;
159    }
160
161    /**
162     * If the edit was an undo, returns the oldest revision that was undone.
163     * Method kept for compatibility reasons.
164     */
165    public function getUndidRevId(): int {
166        if ( $this->getRevertMethod() !== self::REVERT_UNDO ) {
167            return 0;
168        }
169        return $this->getOldestRevertedRevisionId() ?? 0;
170    }
171
172    /**
173     * Returns the ID of an earlier revision that is being repeated or restored.
174     *
175     * The original revision's content should match the new revision exactly.
176     *
177     * @return bool|int The original revision id, or false if no earlier revision is known to be
178     * repeated or restored.
179     * The old PageUpdater::getOriginalRevisionId() returned false in such cases. This value would
180     * be then passed on to extensions through hooks, so it may be wise to keep compatibility with
181     * the old behavior.
182     */
183    public function getOriginalRevisionId() {
184        return $this->originalRevisionId;
185    }
186
187    /**
188     * Whether the edit created a new page
189     */
190    public function isNew(): bool {
191        return $this->isNew;
192    }
193
194    /**
195     * Whether the edit was a revert, not necessarily exact.
196     *
197     * An edit is considered a revert if it either:
198     * - Restores the page to an exact previous state (rollbacks, manual reverts and some undos).
199     *   E.g. for edits A B C D, edits C and D are reverted.
200     * - Undoes some edits made previously, not necessarily restoring the page to an exact
201     *   previous state (undo). It is guaranteed that the revert was a "clean" result of a
202     *   three-way merge and no additional changes were made by the reverting user.
203     *   E.g. for edits A B C D, edits B and C are reverted.
204     *
205     * To check whether the edit was an exact revert, please use the isExactRevert() method.
206     * The getRevertMethod() will provide additional information about which kind of revert
207     * was made.
208     */
209    public function isRevert(): bool {
210        return !$this->isNew() && $this->getOldestRevertedRevisionId();
211    }
212
213    /**
214     * Returns the revert method that was used to perform the edit, if any changes were reverted.
215     * Returns null if the edit was not a revert.
216     *
217     * Possible values: REVERT_UNDO, REVERT_ROLLBACK, REVERT_MANUAL
218     *
219     * @see EditResult::isRevert()
220     *
221     * @return int|null
222     */
223    public function getRevertMethod(): ?int {
224        return $this->revertMethod;
225    }
226
227    /**
228     * Whether the edit was an exact revert,
229     * i.e. the contents of the revert revision and restored revision match
230     */
231    public function isExactRevert(): bool {
232        return $this->isExactRevert;
233    }
234
235    /**
236     * An edit is a null edit if the original revision is equal to the parent revision,
237     * i.e. no changes were made.
238     */
239    public function isNullEdit(): bool {
240        return $this->isNullEdit;
241    }
242
243    /**
244     * Returns an array of revert-related tags that were applied automatically to this edit.
245     *
246     * @return string[]
247     */
248    public function getRevertTags(): array {
249        return $this->revertTags;
250    }
251
252    /**
253     * Returns an array representing the EditResult object.
254     *
255     * @see EditResult::newFromArray()
256     *
257     * @return array
258     * @phpcs:ignore Generic.Files.LineLength
259     * @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}
260     *
261     * @since 1.36
262     */
263    public function jsonSerialize(): array {
264        return [
265            'isNew' => $this->isNew,
266            'originalRevisionId' => $this->originalRevisionId,
267            'revertMethod' => $this->revertMethod,
268            'newestRevertedRevId' => $this->newestRevertedRevId,
269            'oldestRevertedRevId' => $this->oldestRevertedRevId,
270            'isExactRevert' => $this->isExactRevert,
271            'isNullEdit' => $this->isNullEdit,
272            'revertTags' => $this->revertTags,
273            'version' => self::SERIALIZATION_FORMAT_VERSION
274        ];
275    }
276}