MediaWiki master
EditResultBuilder.php
Go to the documentation of this file.
1<?php
7namespace MediaWiki\Storage;
8
13use Wikimedia\Assert\Assert;
14
23
24 public const CONSTRUCTOR_OPTIONS = [
26 ];
27
32 private const REVERT_METHOD_TO_CHANGE_TAG = [
33 EditResult::REVERT_UNDO => 'mw-undo',
34 EditResult::REVERT_ROLLBACK => 'mw-rollback',
35 EditResult::REVERT_MANUAL => 'mw-manual-revert'
36 ];
37
39 private $revisionRecord = null;
40
42 private $isNew = false;
43
45 private $originalRevisionId = false;
46
48 private $originalRevision = null;
49
51 private $revertMethod = null;
52
54 private $newestRevertedRevId = null;
55
57 private $oldestRevertedRevId = null;
58
60 private $revertAfterRevId = null;
61
63 private $revisionStore;
64
66 private $softwareTags;
67
69 private $options;
70
77 public function __construct(
78 RevisionStore $revisionStore,
79 array $softwareTags,
80 ServiceOptions $options
81 ) {
82 $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
83
84 $this->revisionStore = $revisionStore;
85 $this->softwareTags = $softwareTags;
86 $this->options = $options;
87 }
88
89 public function buildEditResult(): EditResult {
90 if ( $this->revisionRecord === null ) {
91 throw new PageUpdateException(
92 'Revision was not set prior to building an EditResult'
93 );
94 }
95
96 // If we don't know the original revision ID, but know which one was undone, try to find out
97 $this->guessOriginalRevisionId();
98
99 // do a last-minute check if this was a manual revert
100 $this->detectManualRevert();
101
102 return new EditResult(
103 $this->isNew,
104 $this->originalRevisionId,
105 $this->revertMethod,
106 $this->oldestRevertedRevId,
107 $this->newestRevertedRevId,
108 $this->isExactRevert(),
109 $this->isNullEdit(),
110 $this->getRevertTags()
111 );
112 }
113
118 public function setRevisionRecord( RevisionRecord $revisionRecord ) {
119 $this->revisionRecord = $revisionRecord;
120 }
121
126 public function setIsNew( bool $isNew ) {
127 $this->isNew = $isNew;
128 }
129
139 public function markAsRevert(
140 int $revertMethod,
141 int $newestRevertedRevId,
142 ?int $revertAfterRevId = null
143 ) {
144 Assert::parameter(
145 in_array(
146 $revertMethod,
148 ),
149 '$revertMethod',
150 'must be one of REVERT_UNDO, REVERT_ROLLBACK, REVERT_MANUAL'
151 );
152 $this->revertAfterRevId = $revertAfterRevId;
153
154 if ( $newestRevertedRevId ) {
155 $this->revertMethod = $revertMethod;
156 $this->newestRevertedRevId = $newestRevertedRevId;
157 $revertAfterRevision = $revertAfterRevId ?
158 $this->revisionStore->getRevisionById( $revertAfterRevId ) :
159 null;
160 $oldestRevertedRev = $revertAfterRevision ?
161 $this->revisionStore->getNextRevision( $revertAfterRevision ) : null;
162 if ( $oldestRevertedRev ) {
163 $this->oldestRevertedRevId = $oldestRevertedRev->getId();
164 } else {
165 // Can't find the oldest reverted revision.
166 // Oh well, just mark the one we know was undone.
167 $this->oldestRevertedRevId = $this->newestRevertedRevId;
168 }
169 }
170 }
171
177 public function setOriginalRevision( $originalRevision ) {
178 if ( $originalRevision instanceof RevisionRecord ) {
179 $this->originalRevision = $originalRevision;
180 $this->originalRevisionId = $originalRevision->getId();
181 } else {
182 $this->originalRevisionId = $originalRevision ?? false;
183 $this->originalRevision = null; // Will be lazy-loaded.
184 }
185 }
186
194 private function detectManualRevert() {
195 $searchRadius = $this->options->get( MainConfigNames::ManualRevertSearchRadius );
196 if ( !$searchRadius ||
197 // we already marked this as a revert
198 $this->revertMethod !== null ||
199 // it's a null edit, nothing was reverted
200 $this->isNullEdit() ||
201 // we wouldn't be able to figure out what was the newest reverted edit
202 // this also discards new pages
203 !$this->revisionRecord->getParentId()
204 ) {
205 return;
206 }
207
208 $revertedToRev = $this->revisionStore->findIdenticalRevision( $this->revisionRecord, $searchRadius );
209 if ( !$revertedToRev ) {
210 return;
211 }
212 $oldestReverted = $this->revisionStore->getNextRevision( $revertedToRev );
213 if ( !$oldestReverted ) {
214 return;
215 }
216
217 $this->setOriginalRevision( $revertedToRev );
218 $this->revertMethod = EditResult::REVERT_MANUAL;
219 $this->oldestRevertedRevId = $oldestReverted->getId();
220 $this->newestRevertedRevId = $this->revisionRecord->getParentId();
221 $this->revertAfterRevId = $revertedToRev->getId();
222 }
223
227 private function guessOriginalRevisionId() {
228 if ( !$this->originalRevisionId ) {
229 if ( $this->revertAfterRevId ) {
230 $this->setOriginalRevision( $this->revertAfterRevId );
231 } elseif ( $this->newestRevertedRevId ) {
232 // Try finding the original revision ID by assuming it's the one before the edit
233 // that is being reverted.
234 $undidRevision = $this->revisionStore->getRevisionById( $this->newestRevertedRevId );
235 if ( $undidRevision ) {
236 $originalRevision = $this->revisionStore->getPreviousRevision( $undidRevision );
237 if ( $originalRevision ) {
238 $this->setOriginalRevision( $originalRevision );
239 }
240 }
241 }
242 }
243
244 // Make sure original revision's content is the same as
245 // the new content and save the original revision ID.
246 if ( $this->getOriginalRevision() &&
247 !$this->getOriginalRevision()->hasSameContent( $this->revisionRecord )
248 ) {
249 $this->setOriginalRevision( false );
250 }
251 }
252
259 private function getOriginalRevision(): ?RevisionRecord {
260 if ( $this->originalRevision ) {
261 return $this->originalRevision;
262 }
263 if ( !$this->originalRevisionId ) {
264 return null;
265 }
266
267 $this->originalRevision = $this->revisionStore->getRevisionById( $this->originalRevisionId );
268 return $this->originalRevision;
269 }
270
275 private function isExactRevert(): bool {
276 if ( $this->isNew || $this->oldestRevertedRevId === null ) {
277 return false;
278 }
279
280 $originalRevision = $this->getOriginalRevision();
281 if ( !$originalRevision ) {
282 // we can't find the original revision for some reason, better return false
283 return false;
284 }
285
286 return $this->revisionRecord->hasSameContent( $originalRevision );
287 }
288
292 private function isNullEdit(): bool {
293 if ( $this->isNew ) {
294 return false;
295 }
296
297 return $this->getOriginalRevision() &&
298 $this->originalRevisionId === $this->revisionRecord->getParentId();
299 }
300
306 private function getRevertTags(): array {
307 if ( $this->revertMethod !== null ) {
308 $revertTag = self::REVERT_METHOD_TO_CHANGE_TAG[$this->revertMethod];
309 if ( in_array( $revertTag, $this->softwareTags ) ) {
310 return [ $revertTag ];
311 }
312 }
313 return [];
314 }
315}
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:69
A class for passing options to services.
assertRequiredOptions(array $expectedKeys)
Assert that the list of options provided in this instance exactly match $expectedKeys,...
A class containing constants representing the names of configuration variables.
const ManualRevertSearchRadius
Name constant for the ManualRevertSearchRadius setting, for use with Config::get()
Page revision base class.
Service for looking up page revisions.
Builder class for the EditResult object.
markAsRevert(int $revertMethod, int $newestRevertedRevId, ?int $revertAfterRevId=null)
Marks this edit as a revert and applies relevant information.
setIsNew(bool $isNew)
Set whether the edit created a new page.
__construct(RevisionStore $revisionStore, array $softwareTags, ServiceOptions $options)
setRevisionRecord(RevisionRecord $revisionRecord)
Set the revision associated with this edit.
Object for storing information about the effects of an edit.
Exception representing a failure to update a page entry.