MediaWiki REL1_39
EditResultBuilder.php
Go to the documentation of this file.
1<?php
25namespace MediaWiki\Storage;
26
31use Wikimedia\Assert\Assert;
32
40
41 public const CONSTRUCTOR_OPTIONS = [
43 ];
44
49 private const REVERT_METHOD_TO_CHANGE_TAG = [
50 EditResult::REVERT_UNDO => 'mw-undo',
51 EditResult::REVERT_ROLLBACK => 'mw-rollback',
52 EditResult::REVERT_MANUAL => 'mw-manual-revert'
53 ];
54
56 private $revisionRecord = null;
57
59 private $isNew = false;
60
62 private $originalRevisionId = false;
63
65 private $originalRevision = null;
66
68 private $revertMethod = null;
69
71 private $newestRevertedRevId = null;
72
74 private $oldestRevertedRevId = null;
75
77 private $revertAfterRevId = null;
78
80 private $revisionStore;
81
83 private $softwareTags;
84
86 private $options;
87
94 public function __construct(
95 RevisionStore $revisionStore,
96 array $softwareTags,
97 ServiceOptions $options
98 ) {
99 $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
100
101 $this->revisionStore = $revisionStore;
102 $this->softwareTags = $softwareTags;
103 $this->options = $options;
104 }
105
109 public function buildEditResult(): EditResult {
110 if ( $this->revisionRecord === null ) {
111 throw new PageUpdateException(
112 'Revision was not set prior to building an EditResult'
113 );
114 }
115
116 // If we don't know the original revision ID, but know which one was undone, try to find out
117 $this->guessOriginalRevisionId();
118
119 // do a last-minute check if this was a manual revert
120 $this->detectManualRevert();
121
122 return new EditResult(
123 $this->isNew,
124 $this->originalRevisionId,
125 $this->revertMethod,
126 $this->oldestRevertedRevId,
127 $this->newestRevertedRevId,
128 $this->isExactRevert(),
129 $this->isNullEdit(),
130 $this->getRevertTags()
131 );
132 }
133
140 public function setRevisionRecord( RevisionRecord $revisionRecord ) {
141 $this->revisionRecord = $revisionRecord;
142 }
143
150 public function setIsNew( bool $isNew ) {
151 $this->isNew = $isNew;
152 }
153
163 public function markAsRevert(
164 int $revertMethod,
165 int $newestRevertedRevId,
166 int $revertAfterRevId = null
167 ) {
168 Assert::parameter(
169 in_array(
170 $revertMethod,
172 ),
173 '$revertMethod',
174 'must be one of REVERT_UNDO, REVERT_ROLLBACK, REVERT_MANUAL'
175 );
176 $this->revertAfterRevId = $revertAfterRevId;
177
178 if ( $newestRevertedRevId ) {
179 $this->revertMethod = $revertMethod;
180 $this->newestRevertedRevId = $newestRevertedRevId;
181 $revertAfterRevision = $revertAfterRevId ?
182 $this->revisionStore->getRevisionById( $revertAfterRevId ) :
183 null;
184 $oldestRevertedRev = $revertAfterRevision ?
185 $this->revisionStore->getNextRevision( $revertAfterRevision ) : null;
186 if ( $oldestRevertedRev ) {
187 $this->oldestRevertedRevId = $oldestRevertedRev->getId();
188 } else {
189 // Can't find the oldest reverted revision.
190 // Oh well, just mark the one we know was undone.
191 $this->oldestRevertedRevId = $this->newestRevertedRevId;
192 }
193 }
194 }
195
201 public function setOriginalRevision( $originalRevision ) {
202 if ( $originalRevision instanceof RevisionRecord ) {
203 $this->originalRevision = $originalRevision;
204 $this->originalRevisionId = $originalRevision->getId();
205 } else {
206 $this->originalRevisionId = $originalRevision ?? false;
207 $this->originalRevision = null; // Will be lazy-loaded.
208 }
209 }
210
218 private function detectManualRevert() {
219 $searchRadius = $this->options->get( MainConfigNames::ManualRevertSearchRadius );
220 if ( !$searchRadius ||
221 // we already marked this as a revert
222 $this->revertMethod !== null ||
223 // it's a null edit, nothing was reverted
224 $this->isNullEdit() ||
225 // we wouldn't be able to figure out what was the newest reverted edit
226 // this also discards new pages
227 !$this->revisionRecord->getParentId()
228 ) {
229 return;
230 }
231
232 $revertedToRev = $this->revisionStore->findIdenticalRevision( $this->revisionRecord, $searchRadius );
233 if ( !$revertedToRev ) {
234 return;
235 }
236 $oldestReverted = $this->revisionStore->getNextRevision( $revertedToRev );
237 if ( !$oldestReverted ) {
238 return;
239 }
240
241 $this->setOriginalRevision( $revertedToRev );
242 $this->revertMethod = EditResult::REVERT_MANUAL;
243 $this->oldestRevertedRevId = $oldestReverted->getId();
244 $this->newestRevertedRevId = $this->revisionRecord->getParentId();
245 $this->revertAfterRevId = $revertedToRev->getId();
246 }
247
251 private function guessOriginalRevisionId() {
252 if ( !$this->originalRevisionId ) {
253 if ( $this->revertAfterRevId ) {
254 $this->setOriginalRevision( $this->revertAfterRevId );
255 } elseif ( $this->newestRevertedRevId ) {
256 // Try finding the original revision ID by assuming it's the one before the edit
257 // that is being reverted.
258 $undidRevision = $this->revisionStore->getRevisionById( $this->newestRevertedRevId );
259 if ( $undidRevision ) {
260 $originalRevision = $this->revisionStore->getPreviousRevision( $undidRevision );
261 if ( $originalRevision ) {
262 $this->setOriginalRevision( $originalRevision );
263 }
264 }
265 }
266 }
267
268 // Make sure original revision's content is the same as
269 // the new content and save the original revision ID.
270 if ( $this->getOriginalRevision() &&
271 !$this->getOriginalRevision()->hasSameContent( $this->revisionRecord )
272 ) {
273 $this->setOriginalRevision( false );
274 }
275 }
276
283 private function getOriginalRevision(): ?RevisionRecord {
284 if ( $this->originalRevision ) {
285 return $this->originalRevision;
286 }
287 if ( !$this->originalRevisionId ) {
288 return null;
289 }
290
291 $this->originalRevision = $this->revisionStore->getRevisionById( $this->originalRevisionId );
292 return $this->originalRevision;
293 }
294
301 private function isExactRevert(): bool {
302 if ( $this->isNew || $this->oldestRevertedRevId === null ) {
303 return false;
304 }
305
306 $originalRevision = $this->getOriginalRevision();
307 if ( !$originalRevision ) {
308 // we can't find the original revision for some reason, better return false
309 return false;
310 }
311
312 return $this->revisionRecord->hasSameContent( $originalRevision );
313 }
314
320 private function isNullEdit(): bool {
321 if ( $this->isNew ) {
322 return false;
323 }
324
325 return $this->getOriginalRevision() &&
326 $this->originalRevisionId === $this->revisionRecord->getParentId();
327 }
328
334 private function getRevertTags(): array {
335 if ( isset( self::REVERT_METHOD_TO_CHANGE_TAG[$this->revertMethod] ) ) {
336 $revertTag = self::REVERT_METHOD_TO_CHANGE_TAG[$this->revertMethod];
337 if ( in_array( $revertTag, $this->softwareTags ) ) {
338 return [ $revertTag ];
339 }
340 }
341 return [];
342 }
343}
if(!defined('MW_SETUP_CALLBACK'))
The persistent session ID (if any) loaded at startup.
Definition WebStart.php:82
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.
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.
markAsRevert(int $revertMethod, int $newestRevertedRevId, int $revertAfterRevId=null)
Marks this edit as a revert and applies relevant information.
Object for storing information about the effects of an edit.
Exception representing a failure to update a page entry.