MediaWiki  master
EditResultBuilder.php
Go to the documentation of this file.
1 <?php
25 namespace MediaWiki\Storage;
26 
32 
40 
41  public const CONSTRUCTOR_OPTIONS = [
42  'ManualRevertSearchRadius',
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 $revisionStore;
78 
80  private $softwareTags;
81 
83  private $loadBalancer;
84 
86  private $options;
87 
97  public function __construct(
99  array $softwareTags,
102  ) {
103  $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
104 
105  $this->revisionStore = $revisionStore;
106  $this->softwareTags = $softwareTags;
107  $this->loadBalancer = $loadBalancer;
108  $this->options = $options;
109  }
110 
116  public function buildEditResult() : EditResult {
117  if ( $this->revisionRecord === null ) {
118  throw new PageUpdateException(
119  'Revision was not set prior to building an EditResult'
120  );
121  }
122 
123  // do a last-minute check if this was a manual revert
124  $this->detectManualRevert();
125 
126  return new EditResult(
127  $this->isNew,
128  $this->originalRevisionId,
129  $this->revertMethod,
130  $this->oldestRevertedRevId,
131  $this->newestRevertedRevId,
132  $this->isExactRevert(),
133  $this->isNullEdit(),
134  $this->getRevertTags()
135  );
136  }
137 
145  $this->revisionRecord = $revisionRecord;
146  }
147 
154  public function setIsNew( bool $isNew ) {
155  $this->isNew = $isNew;
156  }
157 
168  public function markAsRevert(
169  int $revertMethod,
171  int $newestRevertedRevId = 0
172  ) {
173  if ( $oldestRevertedRevId === 0 ) {
174  return;
175  }
176  if ( $newestRevertedRevId === 0 ) {
178  }
179 
180  $this->revertMethod = $revertMethod;
181  $this->oldestRevertedRevId = $oldestRevertedRevId;
182  $this->newestRevertedRevId = $newestRevertedRevId;
183  }
184 
190  public function setOriginalRevisionId( $originalRevId ) {
191  $this->originalRevisionId = $originalRevId;
192  }
193 
201  private function detectManualRevert() {
202  $searchRadius = $this->options->get( 'ManualRevertSearchRadius' );
203  if ( !$searchRadius ||
204  // we already marked this as a revert
205  $this->revertMethod !== null ||
206  // it's a null edit, nothing was reverted
207  $this->isNullEdit() ||
208  // we wouldn't be able to figure out what was the newest reverted edit
209  // this also discards new pages
210  !$this->revisionRecord->getParentId()
211  ) {
212  return;
213  }
214 
215  $revertedToRev = $this->findIdenticalRevision( $searchRadius );
216  if ( !$revertedToRev ) {
217  return;
218  }
219  $oldestReverted = $this->revisionStore->getNextRevision(
220  $revertedToRev,
221  RevisionStore::READ_LATEST
222  );
223  if ( !$oldestReverted ) {
224  return;
225  }
226 
227  $this->setOriginalRevisionId( $revertedToRev->getId() );
228  $this->markAsRevert(
230  $oldestReverted->getId(),
231  $this->revisionRecord->getParentId()
232  );
233  }
234 
243  private function findIdenticalRevision( int $searchRadius ) : ?RevisionStoreRecord {
244  // We use master just in case we encounter replication lag.
245  // This is mostly for cases where a revert is applied rapidly after someone saves
246  // the previous edit.
247  $db = $this->loadBalancer->getConnection( DB_MASTER );
248  $revQuery = $this->revisionStore->getQueryInfo();
249  $subquery = $db->buildSelectSubquery(
250  $revQuery['tables'],
251  $revQuery['fields'],
252  [ 'rev_page' => $this->revisionRecord->getPageId() ],
253  __METHOD__,
254  [
255  'ORDER BY' => [
256  'rev_timestamp DESC',
257  // for cases where there are multiple revs with same timestamp
258  'rev_id DESC'
259  ],
260  'LIMIT' => $searchRadius,
261  // skip the most recent edit, we can't revert to it anyway
262  'OFFSET' => 1
263  ],
264  $revQuery['joins']
265  );
266 
267  // selectRow effectively uses LIMIT 1 clause, returning only the first result
268  $revisionRow = $db->selectRow(
269  [ 'recent_revs' => $subquery ],
270  '*',
271  [ 'rev_sha1' => $this->revisionRecord->getSha1() ],
272  __METHOD__
273  );
274 
275  return $revisionRow ?
276  $this->revisionStore->newRevisionFromRow( $revisionRow )
277  : null;
278  }
279 
288  private function getOriginalRevision(
289  int $flags = RevisionStore::READ_NORMAL
290  ) : ?RevisionRecord {
291  if ( $this->originalRevision ) {
293  }
294  if ( $this->originalRevisionId === false ) {
295  return null;
296  }
297 
298  $this->originalRevision = $this->revisionStore->getRevisionById(
299  $this->originalRevisionId,
300  $flags
301  );
303  }
304 
311  private function isExactRevert() : bool {
312  if ( $this->isNew || $this->oldestRevertedRevId === null ) {
313  return false;
314  }
315 
316  if ( $this->getOriginalRevision() === null ) {
317  // we can't find the original revision for some reason, better return false
318  return false;
319  }
320 
321  return $this->revisionRecord->hasSameContent( $this->getOriginalRevision() );
322  }
323 
329  private function isNullEdit() : bool {
330  if ( $this->isNew ) {
331  return false;
332  }
333 
334  return $this->getOriginalRevision() &&
335  $this->originalRevisionId === $this->revisionRecord->getParentId();
336  }
337 
343  private function getRevertTags() : array {
344  if ( isset( self::REVERT_METHOD_TO_CHANGE_TAG[$this->revertMethod] ) ) {
345  $revertTag = self::REVERT_METHOD_TO_CHANGE_TAG[$this->revertMethod];
346  if ( in_array( $revertTag, $this->softwareTags ) ) {
347  return [ $revertTag ];
348  }
349  }
350  return [];
351  }
352 }
MediaWiki\Storage\EditResultBuilder\$originalRevision
RevisionRecord null $originalRevision
Definition: EditResultBuilder.php:65
Revision\RevisionRecord
Page revision base class.
Definition: RevisionRecord.php:47
MediaWiki\Storage\EditResultBuilder\getOriginalRevision
getOriginalRevision(int $flags=RevisionStore::READ_NORMAL)
Returns the revision that is being repeated or restored.
Definition: EditResultBuilder.php:288
MediaWiki\Storage\EditResultBuilder\$revisionStore
RevisionStore $revisionStore
Definition: EditResultBuilder.php:77
MediaWiki\Storage\EditResult\REVERT_ROLLBACK
const REVERT_ROLLBACK
Definition: EditResult.php:42
Revision\RevisionStore
Service for looking up page revisions.
Definition: RevisionStore.php:89
MediaWiki\Storage\EditResultBuilder\setIsNew
setIsNew(bool $isNew)
Set whether the edit created a new page.
Definition: EditResultBuilder.php:154
MediaWiki\Storage\EditResultBuilder\$revertMethod
int null $revertMethod
Definition: EditResultBuilder.php:68
$revQuery
$revQuery
Definition: testCompression.php:56
MediaWiki\Storage\EditResultBuilder\CONSTRUCTOR_OPTIONS
const CONSTRUCTOR_OPTIONS
Definition: EditResultBuilder.php:41
MediaWiki\Storage\EditResultBuilder\$options
ServiceOptions $options
Definition: EditResultBuilder.php:86
MediaWiki\Storage\EditResultBuilder\getRevertTags
getRevertTags()
Returns an array of revert-related tags that will be applied automatically to this edit.
Definition: EditResultBuilder.php:343
MediaWiki\Storage\EditResultBuilder\setOriginalRevisionId
setOriginalRevisionId( $originalRevId)
Sets the ID of an earlier revision that is being repeated or restored.
Definition: EditResultBuilder.php:190
MediaWiki\Storage\EditResultBuilder\$revisionRecord
RevisionRecord null $revisionRecord
Definition: EditResultBuilder.php:56
MediaWiki\Config\ServiceOptions
A class for passing options to services.
Definition: ServiceOptions.php:27
MediaWiki\Storage\EditResultBuilder\$newestRevertedRevId
int null $newestRevertedRevId
Definition: EditResultBuilder.php:71
MediaWiki\Storage\EditResultBuilder\setRevisionRecord
setRevisionRecord(RevisionRecord $revisionRecord)
Set the revision associated with this edit.
Definition: EditResultBuilder.php:144
MediaWiki\Storage\EditResultBuilder
Builder class for the EditResult object.
Definition: EditResultBuilder.php:39
DB_MASTER
const DB_MASTER
Definition: defines.php:26
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:49
MediaWiki\Storage\EditResultBuilder\$softwareTags
string[] $softwareTags
Definition: EditResultBuilder.php:80
MediaWiki\Storage\EditResultBuilder\$loadBalancer
ILoadBalancer $loadBalancer
Definition: EditResultBuilder.php:83
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:329
MediaWiki\Storage\EditResultBuilder\isExactRevert
isExactRevert()
Whether the edit was an exact revert, i.e.
Definition: EditResultBuilder.php:311
MediaWiki\Storage\EditResultBuilder\markAsRevert
markAsRevert(int $revertMethod, int $oldestRevertedRevId, int $newestRevertedRevId=0)
Marks this edit as a revert and applies relevant information.
Definition: EditResultBuilder.php:168
Revision\RevisionStoreRecord
A RevisionRecord representing an existing revision persisted in the revision table.
Definition: RevisionStoreRecord.php:40
MediaWiki\Storage
Definition: BlobAccessException.php:23
MediaWiki\Storage\EditResultBuilder\$oldestRevertedRevId
int null $oldestRevertedRevId
Definition: EditResultBuilder.php:74
MediaWiki\Storage\EditResultBuilder\$originalRevisionId
bool int $originalRevisionId
Definition: EditResultBuilder.php:62
MediaWiki\Storage\EditResultBuilder\__construct
__construct(RevisionStore $revisionStore, array $softwareTags, ILoadBalancer $loadBalancer, ServiceOptions $options)
EditResultBuilder constructor.
Definition: EditResultBuilder.php:97
MediaWiki\Storage\EditResultBuilder\$isNew
bool $isNew
Definition: EditResultBuilder.php:59
MediaWiki\Storage\EditResultBuilder\findIdenticalRevision
findIdenticalRevision(int $searchRadius)
Tries to find an identical revision to $this->revisionRecord in $searchRadius most recent revisions o...
Definition: EditResultBuilder.php:243
MediaWiki\Storage\EditResult\REVERT_UNDO
const REVERT_UNDO
Definition: EditResult.php:41
MediaWiki\Storage\EditResultBuilder\detectManualRevert
detectManualRevert()
If this edit was not already marked as a revert using EditResultBuilder::markAsRevert(),...
Definition: EditResultBuilder.php:201
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()
Builds the EditResult object.
Definition: EditResultBuilder.php:116
Wikimedia\Rdbms\ILoadBalancer
Database cluster connection, tracking, load balancing, and transaction manager interface.
Definition: ILoadBalancer.php:81
MediaWiki\Config\ServiceOptions\assertRequiredOptions
assertRequiredOptions(array $expectedKeys)
Assert that the list of options provided in this instance exactly match $expectedKeys,...
Definition: ServiceOptions.php:66