MediaWiki  master
EditResultBuilder.php
Go to the documentation of this file.
1 <?php
25 namespace MediaWiki\Storage;
26 
30 use Wikimedia\Assert\Assert;
31 
39 
40  public const CONSTRUCTOR_OPTIONS = [
41  'ManualRevertSearchRadius',
42  ];
43 
48  private const REVERT_METHOD_TO_CHANGE_TAG = [
49  EditResult::REVERT_UNDO => 'mw-undo',
50  EditResult::REVERT_ROLLBACK => 'mw-rollback',
51  EditResult::REVERT_MANUAL => 'mw-manual-revert'
52  ];
53 
55  private $revisionRecord = null;
56 
58  private $isNew = false;
59 
61  private $originalRevisionId = false;
62 
64  private $originalRevision = null;
65 
67  private $revertMethod = null;
68 
70  private $newestRevertedRevId = null;
71 
73  private $oldestRevertedRevId = null;
74 
76  private $revertAfterRevId = null;
77 
79  private $revisionStore;
80 
82  private $softwareTags;
83 
85  private $options;
86 
93  public function __construct(
95  array $softwareTags,
97  ) {
98  $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
99 
100  $this->revisionStore = $revisionStore;
101  $this->softwareTags = $softwareTags;
102  $this->options = $options;
103  }
104 
108  public function buildEditResult(): EditResult {
109  if ( $this->revisionRecord === null ) {
110  throw new PageUpdateException(
111  'Revision was not set prior to building an EditResult'
112  );
113  }
114 
115  // If we don't know the original revision ID, but know which one was undone, try to find out
116  $this->guessOriginalRevisionId();
117 
118  // do a last-minute check if this was a manual revert
119  $this->detectManualRevert();
120 
121  return new EditResult(
122  $this->isNew,
123  $this->originalRevisionId,
124  $this->revertMethod,
125  $this->oldestRevertedRevId,
126  $this->newestRevertedRevId,
127  $this->isExactRevert(),
128  $this->isNullEdit(),
129  $this->getRevertTags()
130  );
131  }
132 
140  $this->revisionRecord = $revisionRecord;
141  }
142 
149  public function setIsNew( bool $isNew ) {
150  $this->isNew = $isNew;
151  }
152 
162  public function markAsRevert(
163  int $revertMethod,
165  int $revertAfterRevId = null
166  ) {
167  Assert::parameter(
168  in_array(
171  ),
172  '$revertMethod',
173  'must be one of REVERT_UNDO, REVERT_ROLLBACK, REVERT_MANUAL'
174  );
175  $this->revertAfterRevId = $revertAfterRevId;
176 
177  if ( $newestRevertedRevId ) {
178  $this->revertMethod = $revertMethod;
179  $this->newestRevertedRevId = $newestRevertedRevId;
180  $revertAfterRevision = $revertAfterRevId ?
181  $this->revisionStore->getRevisionById( $revertAfterRevId ) :
182  null;
183  $oldestRevertedRev = $revertAfterRevision ?
184  $this->revisionStore->getNextRevision( $revertAfterRevision ) : null;
185  if ( $oldestRevertedRev ) {
186  $this->oldestRevertedRevId = $oldestRevertedRev->getId();
187  } else {
188  // Can't find the oldest reverted revision.
189  // Oh well, just mark the one we know was undone.
190  $this->oldestRevertedRevId = $this->newestRevertedRevId;
191  }
192  }
193  }
194 
201  if ( $originalRevision instanceof RevisionRecord ) {
202  $this->originalRevision = $originalRevision;
203  $this->originalRevisionId = $originalRevision->getId();
204  } else {
205  $this->originalRevisionId = $originalRevision ?? false;
206  $this->originalRevision = null; // Will be lazy-loaded.
207  }
208  }
209 
217  private function detectManualRevert() {
218  $searchRadius = $this->options->get( 'ManualRevertSearchRadius' );
219  if ( !$searchRadius ||
220  // we already marked this as a revert
221  $this->revertMethod !== null ||
222  // it's a null edit, nothing was reverted
223  $this->isNullEdit() ||
224  // we wouldn't be able to figure out what was the newest reverted edit
225  // this also discards new pages
226  !$this->revisionRecord->getParentId()
227  ) {
228  return;
229  }
230 
231  $revertedToRev = $this->revisionStore->findIdenticalRevision( $this->revisionRecord, $searchRadius );
232  if ( !$revertedToRev ) {
233  return;
234  }
235  $oldestReverted = $this->revisionStore->getNextRevision( $revertedToRev );
236  if ( !$oldestReverted ) {
237  return;
238  }
239 
240  $this->setOriginalRevision( $revertedToRev );
241  $this->revertMethod = EditResult::REVERT_MANUAL;
242  $this->oldestRevertedRevId = $oldestReverted->getId();
243  $this->newestRevertedRevId = $this->revisionRecord->getParentId();
244  $this->revertAfterRevId = $revertedToRev->getId();
245  }
246 
250  private function guessOriginalRevisionId() {
251  if ( !$this->originalRevisionId ) {
252  if ( $this->revertAfterRevId ) {
253  $this->setOriginalRevision( $this->revertAfterRevId );
254  } elseif ( $this->newestRevertedRevId ) {
255  // Try finding the original revision ID by assuming it's the one before the edit
256  // that is being reverted.
257  $undidRevision = $this->revisionStore->getRevisionById( $this->newestRevertedRevId );
258  if ( $undidRevision ) {
259  $originalRevision = $this->revisionStore->getPreviousRevision( $undidRevision );
260  if ( $originalRevision ) {
262  }
263  }
264  }
265  }
266 
267  // Make sure original revision's content is the same as
268  // the new content and save the original revision ID.
269  if ( $this->getOriginalRevision() &&
270  !$this->getOriginalRevision()->hasSameContent( $this->revisionRecord )
271  ) {
272  $this->setOriginalRevision( false );
273  }
274  }
275 
282  private function getOriginalRevision(): ?RevisionRecord {
283  if ( $this->originalRevision ) {
285  }
286  if ( !$this->originalRevisionId ) {
287  return null;
288  }
289 
290  $this->originalRevision = $this->revisionStore->getRevisionById( $this->originalRevisionId );
292  }
293 
300  private function isExactRevert(): bool {
301  if ( $this->isNew || $this->oldestRevertedRevId === null ) {
302  return false;
303  }
304 
305  if ( !$this->getOriginalRevision() ) {
306  // we can't find the original revision for some reason, better return false
307  return false;
308  }
309 
310  return $this->revisionRecord->hasSameContent( $this->getOriginalRevision() );
311  }
312 
318  private function isNullEdit(): bool {
319  if ( $this->isNew ) {
320  return false;
321  }
322 
323  return $this->getOriginalRevision() &&
324  $this->originalRevisionId === $this->revisionRecord->getParentId();
325  }
326 
332  private function getRevertTags(): array {
333  if ( isset( self::REVERT_METHOD_TO_CHANGE_TAG[$this->revertMethod] ) ) {
334  $revertTag = self::REVERT_METHOD_TO_CHANGE_TAG[$this->revertMethod];
335  if ( in_array( $revertTag, $this->softwareTags ) ) {
336  return [ $revertTag ];
337  }
338  }
339  return [];
340  }
341 }
MediaWiki\Storage\EditResultBuilder\$originalRevision
RevisionRecord null $originalRevision
Definition: EditResultBuilder.php:64
MediaWiki\Storage\EditResultBuilder\markAsRevert
markAsRevert(int $revertMethod, int $newestRevertedRevId, int $revertAfterRevId=null)
Marks this edit as a revert and applies relevant information.
Definition: EditResultBuilder.php:162
MediaWiki\Revision\RevisionRecord
Page revision base class.
Definition: RevisionRecord.php:47
MediaWiki\Storage\EditResultBuilder\$revisionStore
RevisionStore $revisionStore
Definition: EditResultBuilder.php:79
MediaWiki\Storage\EditResult\REVERT_ROLLBACK
const REVERT_ROLLBACK
Definition: EditResult.php:42
MediaWiki\Revision\RevisionStore
Service for looking up page revisions.
Definition: RevisionStore.php:88
MediaWiki\Storage\EditResultBuilder\setIsNew
setIsNew(bool $isNew)
Set whether the edit created a new page.
Definition: EditResultBuilder.php:149
MediaWiki\Storage\EditResultBuilder\guessOriginalRevisionId
guessOriginalRevisionId()
In case we have not got the original revision ID, try to guess.
Definition: EditResultBuilder.php:250
MediaWiki\Storage\EditResultBuilder\$revertMethod
int null $revertMethod
Definition: EditResultBuilder.php:67
MediaWiki\Storage\EditResultBuilder\$originalRevisionId
int bool $originalRevisionId
Definition: EditResultBuilder.php:61
MediaWiki\Storage\EditResultBuilder\CONSTRUCTOR_OPTIONS
const CONSTRUCTOR_OPTIONS
Definition: EditResultBuilder.php:40
MediaWiki\Storage\EditResultBuilder\$options
ServiceOptions $options
Definition: EditResultBuilder.php:85
MediaWiki\Storage\EditResultBuilder\getRevertTags
getRevertTags()
Returns an array of revert-related tags that will be applied automatically to this edit.
Definition: EditResultBuilder.php:332
MediaWiki\Storage\EditResultBuilder\$revisionRecord
RevisionRecord null $revisionRecord
Definition: EditResultBuilder.php:55
MediaWiki\Storage\EditResultBuilder\__construct
__construct(RevisionStore $revisionStore, array $softwareTags, ServiceOptions $options)
Definition: EditResultBuilder.php:93
MediaWiki\Config\ServiceOptions
A class for passing options to services.
Definition: ServiceOptions.php:27
MediaWiki\Storage\EditResultBuilder\$newestRevertedRevId
int null $newestRevertedRevId
Definition: EditResultBuilder.php:70
MediaWiki\Storage\EditResultBuilder\setRevisionRecord
setRevisionRecord(RevisionRecord $revisionRecord)
Set the revision associated with this edit.
Definition: EditResultBuilder.php:139
MediaWiki\Storage\EditResultBuilder
Builder class for the EditResult object.
Definition: EditResultBuilder.php:38
MediaWiki\Storage\EditResultBuilder\REVERT_METHOD_TO_CHANGE_TAG
const REVERT_METHOD_TO_CHANGE_TAG
A mapping from EditResult's revert methods to relevant change tags.
Definition: EditResultBuilder.php:48
MediaWiki\Storage\EditResultBuilder\$softwareTags
string[] $softwareTags
Definition: EditResultBuilder.php:82
MediaWiki\Storage\EditResult
Object for storing information about the effects of an edit.
Definition: EditResult.php:38
MediaWiki\Storage\EditResultBuilder\isNullEdit
isNullEdit()
An edit is a null edit if the original revision is equal to the parent revision.
Definition: EditResultBuilder.php:318
MediaWiki\Storage\EditResultBuilder\isExactRevert
isExactRevert()
Whether the edit was an exact revert, i.e.
Definition: EditResultBuilder.php:300
MediaWiki\Storage\EditResultBuilder\$revertAfterRevId
int null $revertAfterRevId
Definition: EditResultBuilder.php:76
MediaWiki\Storage
Definition: BlobAccessException.php:23
MediaWiki\Storage\EditResultBuilder\$oldestRevertedRevId
int null $oldestRevertedRevId
Definition: EditResultBuilder.php:73
MediaWiki\Storage\EditResultBuilder\$isNew
bool $isNew
Definition: EditResultBuilder.php:58
MediaWiki\Revision\RevisionRecord\getId
getId( $wikiId=self::LOCAL)
Get revision ID.
Definition: RevisionRecord.php:279
MediaWiki\Storage\EditResultBuilder\setOriginalRevision
setOriginalRevision( $originalRevision)
Definition: EditResultBuilder.php:200
MediaWiki\Storage\EditResult\REVERT_UNDO
const REVERT_UNDO
Definition: EditResult.php:41
MediaWiki\Storage\EditResultBuilder\getOriginalRevision
getOriginalRevision()
Returns the revision that is being repeated or restored.
Definition: EditResultBuilder.php:282
MediaWiki\Storage\EditResultBuilder\detectManualRevert
detectManualRevert()
If this edit was not already marked as a revert using EditResultBuilder::markAsRevert(),...
Definition: EditResultBuilder.php:217
MediaWiki\Storage\PageUpdateException
Exception representing a failure to update a page entry.
Definition: PageUpdateException.php:33
MediaWiki\Storage\EditResult\REVERT_MANUAL
const REVERT_MANUAL
Definition: EditResult.php:43
MediaWiki\Storage\EditResultBuilder\buildEditResult
buildEditResult()
Definition: EditResultBuilder.php:108
MediaWiki\Config\ServiceOptions\assertRequiredOptions
assertRequiredOptions(array $expectedKeys)
Assert that the list of options provided in this instance exactly match $expectedKeys,...
Definition: ServiceOptions.php:71