MediaWiki master
EditResultBuilder.php
Go to the documentation of this file.
1<?php
21namespace MediaWiki\Storage;
22
27use Wikimedia\Assert\Assert;
28
37
38 public const CONSTRUCTOR_OPTIONS = [
40 ];
41
46 private const REVERT_METHOD_TO_CHANGE_TAG = [
47 EditResult::REVERT_UNDO => 'mw-undo',
48 EditResult::REVERT_ROLLBACK => 'mw-rollback',
49 EditResult::REVERT_MANUAL => 'mw-manual-revert'
50 ];
51
53 private $revisionRecord = null;
54
56 private $isNew = false;
57
59 private $originalRevisionId = false;
60
62 private $originalRevision = null;
63
65 private $revertMethod = null;
66
68 private $newestRevertedRevId = null;
69
71 private $oldestRevertedRevId = null;
72
74 private $revertAfterRevId = null;
75
77 private $revisionStore;
78
80 private $softwareTags;
81
83 private $options;
84
91 public function __construct(
92 RevisionStore $revisionStore,
93 array $softwareTags,
94 ServiceOptions $options
95 ) {
96 $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
97
98 $this->revisionStore = $revisionStore;
99 $this->softwareTags = $softwareTags;
100 $this->options = $options;
101 }
102
106 public function buildEditResult(): EditResult {
107 if ( $this->revisionRecord === null ) {
108 throw new PageUpdateException(
109 'Revision was not set prior to building an EditResult'
110 );
111 }
112
113 // If we don't know the original revision ID, but know which one was undone, try to find out
114 $this->guessOriginalRevisionId();
115
116 // do a last-minute check if this was a manual revert
117 $this->detectManualRevert();
118
119 return new EditResult(
120 $this->isNew,
121 $this->originalRevisionId,
122 $this->revertMethod,
123 $this->oldestRevertedRevId,
124 $this->newestRevertedRevId,
125 $this->isExactRevert(),
126 $this->isNullEdit(),
127 $this->getRevertTags()
128 );
129 }
130
137 public function setRevisionRecord( RevisionRecord $revisionRecord ) {
138 $this->revisionRecord = $revisionRecord;
139 }
140
147 public function setIsNew( bool $isNew ) {
148 $this->isNew = $isNew;
149 }
150
160 public function markAsRevert(
161 int $revertMethod,
162 int $newestRevertedRevId,
163 ?int $revertAfterRevId = null
164 ) {
165 Assert::parameter(
166 in_array(
167 $revertMethod,
169 ),
170 '$revertMethod',
171 'must be one of REVERT_UNDO, REVERT_ROLLBACK, REVERT_MANUAL'
172 );
173 $this->revertAfterRevId = $revertAfterRevId;
174
175 if ( $newestRevertedRevId ) {
176 $this->revertMethod = $revertMethod;
177 $this->newestRevertedRevId = $newestRevertedRevId;
178 $revertAfterRevision = $revertAfterRevId ?
179 $this->revisionStore->getRevisionById( $revertAfterRevId ) :
180 null;
181 $oldestRevertedRev = $revertAfterRevision ?
182 $this->revisionStore->getNextRevision( $revertAfterRevision ) : null;
183 if ( $oldestRevertedRev ) {
184 $this->oldestRevertedRevId = $oldestRevertedRev->getId();
185 } else {
186 // Can't find the oldest reverted revision.
187 // Oh well, just mark the one we know was undone.
188 $this->oldestRevertedRevId = $this->newestRevertedRevId;
189 }
190 }
191 }
192
198 public function setOriginalRevision( $originalRevision ) {
199 if ( $originalRevision instanceof RevisionRecord ) {
200 $this->originalRevision = $originalRevision;
201 $this->originalRevisionId = $originalRevision->getId();
202 } else {
203 $this->originalRevisionId = $originalRevision ?? false;
204 $this->originalRevision = null; // Will be lazy-loaded.
205 }
206 }
207
215 private function detectManualRevert() {
216 $searchRadius = $this->options->get( MainConfigNames::ManualRevertSearchRadius );
217 if ( !$searchRadius ||
218 // we already marked this as a revert
219 $this->revertMethod !== null ||
220 // it's a null edit, nothing was reverted
221 $this->isNullEdit() ||
222 // we wouldn't be able to figure out what was the newest reverted edit
223 // this also discards new pages
224 !$this->revisionRecord->getParentId()
225 ) {
226 return;
227 }
228
229 $revertedToRev = $this->revisionStore->findIdenticalRevision( $this->revisionRecord, $searchRadius );
230 if ( !$revertedToRev ) {
231 return;
232 }
233 $oldestReverted = $this->revisionStore->getNextRevision( $revertedToRev );
234 if ( !$oldestReverted ) {
235 return;
236 }
237
238 $this->setOriginalRevision( $revertedToRev );
239 $this->revertMethod = EditResult::REVERT_MANUAL;
240 $this->oldestRevertedRevId = $oldestReverted->getId();
241 $this->newestRevertedRevId = $this->revisionRecord->getParentId();
242 $this->revertAfterRevId = $revertedToRev->getId();
243 }
244
248 private function guessOriginalRevisionId() {
249 if ( !$this->originalRevisionId ) {
250 if ( $this->revertAfterRevId ) {
251 $this->setOriginalRevision( $this->revertAfterRevId );
252 } elseif ( $this->newestRevertedRevId ) {
253 // Try finding the original revision ID by assuming it's the one before the edit
254 // that is being reverted.
255 $undidRevision = $this->revisionStore->getRevisionById( $this->newestRevertedRevId );
256 if ( $undidRevision ) {
257 $originalRevision = $this->revisionStore->getPreviousRevision( $undidRevision );
258 if ( $originalRevision ) {
259 $this->setOriginalRevision( $originalRevision );
260 }
261 }
262 }
263 }
264
265 // Make sure original revision's content is the same as
266 // the new content and save the original revision ID.
267 if ( $this->getOriginalRevision() &&
268 !$this->getOriginalRevision()->hasSameContent( $this->revisionRecord )
269 ) {
270 $this->setOriginalRevision( false );
271 }
272 }
273
280 private function getOriginalRevision(): ?RevisionRecord {
281 if ( $this->originalRevision ) {
282 return $this->originalRevision;
283 }
284 if ( !$this->originalRevisionId ) {
285 return null;
286 }
287
288 $this->originalRevision = $this->revisionStore->getRevisionById( $this->originalRevisionId );
289 return $this->originalRevision;
290 }
291
298 private function isExactRevert(): bool {
299 if ( $this->isNew || $this->oldestRevertedRevId === null ) {
300 return false;
301 }
302
303 $originalRevision = $this->getOriginalRevision();
304 if ( !$originalRevision ) {
305 // we can't find the original revision for some reason, better return false
306 return false;
307 }
308
309 return $this->revisionRecord->hasSameContent( $originalRevision );
310 }
311
317 private function isNullEdit(): bool {
318 if ( $this->isNew ) {
319 return false;
320 }
321
322 return $this->getOriginalRevision() &&
323 $this->originalRevisionId === $this->revisionRecord->getParentId();
324 }
325
331 private function getRevertTags(): array {
332 if ( isset( self::REVERT_METHOD_TO_CHANGE_TAG[$this->revertMethod] ) ) {
333 $revertTag = self::REVERT_METHOD_TO_CHANGE_TAG[$this->revertMethod];
334 if ( in_array( $revertTag, $this->softwareTags ) ) {
335 return [ $revertTag ];
336 }
337 }
338 return [];
339 }
340}
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:81
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.