MediaWiki  master
EditResultBuilder.php
Go to the documentation of this file.
1 <?php
25 namespace MediaWiki\Storage;
26 
31 use 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.
Definition: EditResult.php:38
Exception representing a failure to update a page entry.