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
68 public function __construct(
69 private readonly RevisionStore $revisionStore,
70 private readonly array $softwareTags,
71 private readonly ServiceOptions $options,
72 ) {
73 $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
74 }
75
76 public function buildEditResult(): EditResult {
77 if ( $this->revisionRecord === null ) {
78 throw new PageUpdateException(
79 'Revision was not set prior to building an EditResult'
80 );
81 }
82
83 // If we don't know the original revision ID, but know which one was undone, try to find out
84 $this->guessOriginalRevisionId();
85
86 // do a last-minute check if this was a manual revert
87 $this->detectManualRevert();
88
89 return new EditResult(
90 $this->isNew,
91 $this->originalRevisionId,
92 $this->revertMethod,
93 $this->oldestRevertedRevId,
94 $this->newestRevertedRevId,
95 $this->isExactRevert(),
96 $this->isNullEdit(),
97 $this->getRevertTags()
98 );
99 }
100
105 public function setRevisionRecord( RevisionRecord $revisionRecord ) {
106 $this->revisionRecord = $revisionRecord;
107 }
108
113 public function setIsNew( bool $isNew ) {
114 $this->isNew = $isNew;
115 }
116
126 public function markAsRevert(
127 int $revertMethod,
128 int $newestRevertedRevId,
129 ?int $revertAfterRevId = null
130 ) {
131 Assert::parameter(
132 in_array(
133 $revertMethod,
135 ),
136 '$revertMethod',
137 'must be one of REVERT_UNDO, REVERT_ROLLBACK, REVERT_MANUAL'
138 );
139 $this->revertAfterRevId = $revertAfterRevId;
140
141 if ( $newestRevertedRevId ) {
142 $this->revertMethod = $revertMethod;
143 $this->newestRevertedRevId = $newestRevertedRevId;
144 $revertAfterRevision = $revertAfterRevId ?
145 $this->revisionStore->getRevisionById( $revertAfterRevId ) :
146 null;
147 $oldestRevertedRev = $revertAfterRevision ?
148 $this->revisionStore->getNextRevision( $revertAfterRevision ) : null;
149 if ( $oldestRevertedRev ) {
150 $this->oldestRevertedRevId = $oldestRevertedRev->getId();
151 } else {
152 // Can't find the oldest reverted revision.
153 // Oh well, just mark the one we know was undone.
154 $this->oldestRevertedRevId = $this->newestRevertedRevId;
155 }
156 }
157 }
158
164 public function setOriginalRevision( $originalRevision ) {
165 if ( $originalRevision instanceof RevisionRecord ) {
166 $this->originalRevision = $originalRevision;
167 $this->originalRevisionId = $originalRevision->getId();
168 } else {
169 $this->originalRevisionId = $originalRevision ?? false;
170 $this->originalRevision = null; // Will be lazy-loaded.
171 }
172 }
173
181 private function detectManualRevert() {
182 $searchRadius = $this->options->get( MainConfigNames::ManualRevertSearchRadius );
183 if ( !$searchRadius ||
184 // we already marked this as a revert
185 $this->revertMethod !== null ||
186 // it's a null edit, nothing was reverted
187 $this->isNullEdit() ||
188 // we wouldn't be able to figure out what was the newest reverted edit
189 // this also discards new pages
190 !$this->revisionRecord->getParentId()
191 ) {
192 return;
193 }
194
195 $revertedToRev = $this->revisionStore->findIdenticalRevision( $this->revisionRecord, $searchRadius );
196 if ( !$revertedToRev ) {
197 return;
198 }
199 $oldestReverted = $this->revisionStore->getNextRevision( $revertedToRev );
200 if ( !$oldestReverted ) {
201 return;
202 }
203
204 $this->setOriginalRevision( $revertedToRev );
205 $this->revertMethod = EditResult::REVERT_MANUAL;
206 $this->oldestRevertedRevId = $oldestReverted->getId();
207 $this->newestRevertedRevId = $this->revisionRecord->getParentId();
208 $this->revertAfterRevId = $revertedToRev->getId();
209 }
210
214 private function guessOriginalRevisionId() {
215 if ( !$this->originalRevisionId ) {
216 if ( $this->revertAfterRevId ) {
217 $this->setOriginalRevision( $this->revertAfterRevId );
218 } elseif ( $this->newestRevertedRevId ) {
219 // Try finding the original revision ID by assuming it's the one before the edit
220 // that is being reverted.
221 $undidRevision = $this->revisionStore->getRevisionById( $this->newestRevertedRevId );
222 if ( $undidRevision ) {
223 $originalRevision = $this->revisionStore->getPreviousRevision( $undidRevision );
224 if ( $originalRevision ) {
225 $this->setOriginalRevision( $originalRevision );
226 }
227 }
228 }
229 }
230
231 // Make sure original revision's content is the same as
232 // the new content and save the original revision ID.
233 if ( $this->getOriginalRevision() &&
234 !$this->getOriginalRevision()->hasSameContent( $this->revisionRecord )
235 ) {
236 $this->setOriginalRevision( false );
237 }
238 }
239
249 private function getOriginalRevision(): ?RevisionRecord {
250 if ( $this->originalRevision ) {
251 return $this->originalRevision;
252 }
253 if ( !$this->originalRevisionId ) {
254 return null;
255 }
256
257 $this->originalRevision = $this->revisionStore->getRevisionById( $this->originalRevisionId );
258 return $this->originalRevision;
259 }
260
265 private function isExactRevert(): bool {
266 if ( $this->isNew || $this->oldestRevertedRevId === null ) {
267 return false;
268 }
269
270 $originalRevision = $this->getOriginalRevision();
271 if ( !$originalRevision ) {
272 // we can't find the original revision for some reason, better return false
273 return false;
274 }
275
276 return $this->revisionRecord->hasSameContent( $originalRevision );
277 }
278
285 private function isNullEdit(): bool {
286 if ( $this->isNew ) {
287 return false;
288 }
289
290 return $this->getOriginalRevision() &&
291 $this->originalRevisionId === $this->revisionRecord->getParentId();
292 }
293
299 private function getRevertTags(): array {
300 if ( $this->revertMethod !== null ) {
301 $revertTag = self::REVERT_METHOD_TO_CHANGE_TAG[$this->revertMethod];
302 if ( in_array( $revertTag, $this->softwareTags ) ) {
303 return [ $revertTag ];
304 }
305 }
306 return [];
307 }
308}
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:71
A class for passing options to services.
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.
__construct(private readonly RevisionStore $revisionStore, private readonly array $softwareTags, private readonly ServiceOptions $options,)
setIsNew(bool $isNew)
Set whether the edit created a new page.
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.