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
103 public function buildEditResult(): EditResult {
104 if ( $this->revisionRecord === null ) {
105 throw new PageUpdateException(
106 'Revision was not set prior to building an EditResult'
107 );
108 }
109
110 // If we don't know the original revision ID, but know which one was undone, try to find out
111 $this->guessOriginalRevisionId();
112
113 // do a last-minute check if this was a manual revert
114 $this->detectManualRevert();
115
116 return new EditResult(
117 $this->isNew,
118 $this->originalRevisionId,
119 $this->revertMethod,
120 $this->oldestRevertedRevId,
121 $this->newestRevertedRevId,
122 $this->isExactRevert(),
123 $this->isNullEdit(),
124 $this->getRevertTags()
125 );
126 }
127
132 public function setRevisionRecord( RevisionRecord $revisionRecord ) {
133 $this->revisionRecord = $revisionRecord;
134 }
135
140 public function setIsNew( bool $isNew ) {
141 $this->isNew = $isNew;
142 }
143
153 public function markAsRevert(
154 int $revertMethod,
155 int $newestRevertedRevId,
156 ?int $revertAfterRevId = null
157 ) {
158 Assert::parameter(
159 in_array(
160 $revertMethod,
162 ),
163 '$revertMethod',
164 'must be one of REVERT_UNDO, REVERT_ROLLBACK, REVERT_MANUAL'
165 );
166 $this->revertAfterRevId = $revertAfterRevId;
167
168 if ( $newestRevertedRevId ) {
169 $this->revertMethod = $revertMethod;
170 $this->newestRevertedRevId = $newestRevertedRevId;
171 $revertAfterRevision = $revertAfterRevId ?
172 $this->revisionStore->getRevisionById( $revertAfterRevId ) :
173 null;
174 $oldestRevertedRev = $revertAfterRevision ?
175 $this->revisionStore->getNextRevision( $revertAfterRevision ) : null;
176 if ( $oldestRevertedRev ) {
177 $this->oldestRevertedRevId = $oldestRevertedRev->getId();
178 } else {
179 // Can't find the oldest reverted revision.
180 // Oh well, just mark the one we know was undone.
181 $this->oldestRevertedRevId = $this->newestRevertedRevId;
182 }
183 }
184 }
185
191 public function setOriginalRevision( $originalRevision ) {
192 if ( $originalRevision instanceof RevisionRecord ) {
193 $this->originalRevision = $originalRevision;
194 $this->originalRevisionId = $originalRevision->getId();
195 } else {
196 $this->originalRevisionId = $originalRevision ?? false;
197 $this->originalRevision = null; // Will be lazy-loaded.
198 }
199 }
200
208 private function detectManualRevert() {
209 $searchRadius = $this->options->get( MainConfigNames::ManualRevertSearchRadius );
210 if ( !$searchRadius ||
211 // we already marked this as a revert
212 $this->revertMethod !== null ||
213 // it's a null edit, nothing was reverted
214 $this->isNullEdit() ||
215 // we wouldn't be able to figure out what was the newest reverted edit
216 // this also discards new pages
217 !$this->revisionRecord->getParentId()
218 ) {
219 return;
220 }
221
222 $revertedToRev = $this->revisionStore->findIdenticalRevision( $this->revisionRecord, $searchRadius );
223 if ( !$revertedToRev ) {
224 return;
225 }
226 $oldestReverted = $this->revisionStore->getNextRevision( $revertedToRev );
227 if ( !$oldestReverted ) {
228 return;
229 }
230
231 $this->setOriginalRevision( $revertedToRev );
232 $this->revertMethod = EditResult::REVERT_MANUAL;
233 $this->oldestRevertedRevId = $oldestReverted->getId();
234 $this->newestRevertedRevId = $this->revisionRecord->getParentId();
235 $this->revertAfterRevId = $revertedToRev->getId();
236 }
237
241 private function guessOriginalRevisionId() {
242 if ( !$this->originalRevisionId ) {
243 if ( $this->revertAfterRevId ) {
244 $this->setOriginalRevision( $this->revertAfterRevId );
245 } elseif ( $this->newestRevertedRevId ) {
246 // Try finding the original revision ID by assuming it's the one before the edit
247 // that is being reverted.
248 $undidRevision = $this->revisionStore->getRevisionById( $this->newestRevertedRevId );
249 if ( $undidRevision ) {
250 $originalRevision = $this->revisionStore->getPreviousRevision( $undidRevision );
251 if ( $originalRevision ) {
252 $this->setOriginalRevision( $originalRevision );
253 }
254 }
255 }
256 }
257
258 // Make sure original revision's content is the same as
259 // the new content and save the original revision ID.
260 if ( $this->getOriginalRevision() &&
261 !$this->getOriginalRevision()->hasSameContent( $this->revisionRecord )
262 ) {
263 $this->setOriginalRevision( false );
264 }
265 }
266
273 private function getOriginalRevision(): ?RevisionRecord {
274 if ( $this->originalRevision ) {
275 return $this->originalRevision;
276 }
277 if ( !$this->originalRevisionId ) {
278 return null;
279 }
280
281 $this->originalRevision = $this->revisionStore->getRevisionById( $this->originalRevisionId );
282 return $this->originalRevision;
283 }
284
289 private function isExactRevert(): bool {
290 if ( $this->isNew || $this->oldestRevertedRevId === null ) {
291 return false;
292 }
293
294 $originalRevision = $this->getOriginalRevision();
295 if ( !$originalRevision ) {
296 // we can't find the original revision for some reason, better return false
297 return false;
298 }
299
300 return $this->revisionRecord->hasSameContent( $originalRevision );
301 }
302
306 private function isNullEdit(): bool {
307 if ( $this->isNew ) {
308 return false;
309 }
310
311 return $this->getOriginalRevision() &&
312 $this->originalRevisionId === $this->revisionRecord->getParentId();
313 }
314
320 private function getRevertTags(): array {
321 if ( isset( self::REVERT_METHOD_TO_CHANGE_TAG[$this->revertMethod] ) ) {
322 $revertTag = self::REVERT_METHOD_TO_CHANGE_TAG[$this->revertMethod];
323 if ( in_array( $revertTag, $this->softwareTags ) ) {
324 return [ $revertTag ];
325 }
326 }
327 return [];
328 }
329}
if(!defined('MW_SETUP_CALLBACK'))
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.
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.