MediaWiki  master
EditResultBuilder.php
Go to the documentation of this file.
1 <?php
25 namespace MediaWiki\Storage;
26 
32 
40 
45  private const REVERT_METHOD_TO_CHANGE_TAG = [
46  EditResult::REVERT_UNDO => 'mw-undo',
47  EditResult::REVERT_ROLLBACK => 'mw-rollback',
48  EditResult::REVERT_MANUAL => 'mw-manual-revert'
49  ];
50 
52  private $revisionRecord = null;
53 
55  private $isNew = false;
56 
58  private $originalRevisionId = false;
59 
61  private $originalRevision = null;
62 
64  private $revertMethod = null;
65 
67  private $newestRevertedRevId = null;
68 
70  private $oldestRevertedRevId = null;
71 
73  private $revisionStore;
74 
76  private $softwareTags;
77 
79  private $loadBalancer;
80 
82  private $options;
83 
93  public function __construct(
95  array $softwareTags,
98  ) {
99  $this->revisionStore = $revisionStore;
100  $this->softwareTags = $softwareTags;
101  $this->loadBalancer = $loadBalancer;
102  $this->options = $options;
103  }
104 
110  public function buildEditResult() : EditResult {
111  if ( $this->revisionRecord === null ) {
112  throw new PageUpdateException(
113  'Revision was not set prior to building an EditResult'
114  );
115  }
116 
117  // do a last-minute check if this was a manual revert
118  $this->detectManualRevert();
119 
120  return new EditResult(
121  $this->isNew,
122  $this->originalRevisionId,
123  $this->revertMethod,
124  $this->oldestRevertedRevId,
125  $this->newestRevertedRevId,
126  $this->isExactRevert(),
127  $this->isNullEdit(),
128  $this->getRevertTags()
129  );
130  }
131 
139  $this->revisionRecord = $revisionRecord;
140  }
141 
148  public function setIsNew( bool $isNew ) {
149  $this->isNew = $isNew;
150  }
151 
162  public function markAsRevert(
163  int $revertMethod,
165  int $newestRevertedRevId = 0
166  ) {
167  if ( $oldestRevertedRevId === 0 ) {
168  return;
169  }
170  if ( $newestRevertedRevId === 0 ) {
172  }
173 
174  $this->revertMethod = $revertMethod;
175  $this->oldestRevertedRevId = $oldestRevertedRevId;
176  $this->newestRevertedRevId = $newestRevertedRevId;
177  }
178 
184  public function setOriginalRevisionId( $originalRevId ) {
185  $this->originalRevisionId = $originalRevId;
186  }
187 
195  private function detectManualRevert() {
196  $searchRadius = $this->options->get( 'ManualRevertSearchRadius' );
197  if ( !$searchRadius ||
198  // we already marked this as a revert
199  $this->revertMethod !== null ||
200  // it's a null edit, nothing was reverted
201  $this->isNullEdit() ||
202  // we wouldn't be able to figure out what was the newest reverted edit
203  // this also discards new pages
204  !$this->revisionRecord->getParentId()
205  ) {
206  return;
207  }
208 
209  $revertedToRev = $this->findIdenticalRevision( $searchRadius );
210  if ( !$revertedToRev ) {
211  return;
212  }
213  $oldestReverted = $this->revisionStore->getNextRevision(
214  $revertedToRev,
215  RevisionStore::READ_LATEST
216  );
217  if ( !$oldestReverted ) {
218  return;
219  }
220 
221  $this->setOriginalRevisionId( $revertedToRev->getId() );
222  $this->markAsRevert(
224  $oldestReverted->getId(),
225  $this->revisionRecord->getParentId()
226  );
227  }
228 
237  private function findIdenticalRevision( int $searchRadius ) : ?RevisionStoreRecord {
238  // We use master just in case we encounter replication lag.
239  // This is mostly for cases where a revert is applied rapidly after someone saves
240  // the previous edit.
241  $db = $this->loadBalancer->getConnection( DB_MASTER );
242  $revQuery = $this->revisionStore->getQueryInfo();
243  $subquery = $db->buildSelectSubquery(
244  $revQuery['tables'],
245  $revQuery['fields'],
246  [ 'rev_page' => $this->revisionRecord->getPageId() ],
247  __METHOD__,
248  [
249  'ORDER BY' => [
250  'rev_timestamp DESC',
251  // for cases where there are multiple revs with same timestamp
252  'rev_id DESC'
253  ],
254  'LIMIT' => $searchRadius,
255  // skip the most recent edit, we can't revert to it anyway
256  'OFFSET' => 1
257  ],
258  $revQuery['joins']
259  );
260 
261  // selectRow effectively uses LIMIT 1 clause, returning only the first result
262  $revisionRow = $db->selectRow(
263  [ 'recent_revs' => $subquery ],
264  '*',
265  [ 'rev_sha1' => $this->revisionRecord->getSha1() ],
266  __METHOD__
267  );
268 
269  return $revisionRow ?
270  $this->revisionStore->newRevisionFromRow( $revisionRow )
271  : null;
272  }
273 
282  private function getOriginalRevision(
283  int $flags = RevisionStore::READ_NORMAL
284  ) : ?RevisionRecord {
285  if ( $this->originalRevision ) {
287  }
288  if ( $this->originalRevisionId === false ) {
289  return null;
290  }
291 
292  $this->originalRevision = $this->revisionStore->getRevisionById(
293  $this->originalRevisionId,
294  $flags
295  );
297  }
298 
305  private function isExactRevert() : bool {
306  if ( $this->isNew || $this->oldestRevertedRevId === null ) {
307  return false;
308  }
309 
310  if ( $this->getOriginalRevision() === null ) {
311  // we can't find the original revision for some reason, better return false
312  return false;
313  }
314 
315  return $this->revisionRecord->hasSameContent( $this->getOriginalRevision() );
316  }
317 
323  private function isNullEdit() : bool {
324  if ( $this->isNew ) {
325  return false;
326  }
327 
328  return $this->getOriginalRevision() &&
329  $this->originalRevisionId === $this->revisionRecord->getParentId();
330  }
331 
337  private function getRevertTags() : array {
338  if ( isset( self::REVERT_METHOD_TO_CHANGE_TAG[$this->revertMethod] ) ) {
339  $revertTag = self::REVERT_METHOD_TO_CHANGE_TAG[$this->revertMethod];
340  if ( in_array( $revertTag, $this->softwareTags ) ) {
341  return [ $revertTag ];
342  }
343  }
344  return [];
345  }
346 }
MediaWiki\Storage\EditResultBuilder\$originalRevision
RevisionRecord null $originalRevision
Definition: EditResultBuilder.php:61
Revision\RevisionRecord
Page revision base class.
Definition: RevisionRecord.php:46
MediaWiki\Storage\EditResultBuilder\getOriginalRevision
getOriginalRevision(int $flags=RevisionStore::READ_NORMAL)
Returns the revision that is being repeated or restored.
Definition: EditResultBuilder.php:282
MediaWiki\Storage\EditResultBuilder\$revisionStore
RevisionStore $revisionStore
Definition: EditResultBuilder.php:73
MediaWiki\Storage\EditResult\REVERT_ROLLBACK
const REVERT_ROLLBACK
Definition: EditResult.php:42
Revision\RevisionStore
Service for looking up page revisions.
Definition: RevisionStore.php:81
MediaWiki\Storage\EditResultBuilder\setIsNew
setIsNew(bool $isNew)
Set whether the edit created a new page.
Definition: EditResultBuilder.php:148
MediaWiki\Storage\EditResultBuilder\$revertMethod
int null $revertMethod
Definition: EditResultBuilder.php:64
$revQuery
$revQuery
Definition: testCompression.php:56
MediaWiki\Storage\EditResultBuilder\$options
ServiceOptions $options
Definition: EditResultBuilder.php:82
MediaWiki\Storage\EditResultBuilder\getRevertTags
getRevertTags()
Returns an array of revert-related tags that will be applied automatically to this edit.
Definition: EditResultBuilder.php:337
MediaWiki\Storage\EditResultBuilder\setOriginalRevisionId
setOriginalRevisionId( $originalRevId)
Sets the ID of an earlier revision that is being repeated or restored.
Definition: EditResultBuilder.php:184
MediaWiki\Storage\EditResultBuilder\$revisionRecord
RevisionRecord null $revisionRecord
Definition: EditResultBuilder.php:52
MediaWiki\Config\ServiceOptions
A class for passing options to services.
Definition: ServiceOptions.php:25
MediaWiki\Storage\EditResultBuilder\$newestRevertedRevId
int null $newestRevertedRevId
Definition: EditResultBuilder.php:67
MediaWiki\Storage\EditResultBuilder\setRevisionRecord
setRevisionRecord(RevisionRecord $revisionRecord)
Set the revision associated with this edit.
Definition: EditResultBuilder.php:138
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:45
MediaWiki\Storage\EditResultBuilder\$softwareTags
string[] $softwareTags
Definition: EditResultBuilder.php:76
MediaWiki\Storage\EditResultBuilder\$loadBalancer
ILoadBalancer $loadBalancer
Definition: EditResultBuilder.php:79
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:323
MediaWiki\Storage\EditResultBuilder\isExactRevert
isExactRevert()
Whether the edit was an exact revert, i.e.
Definition: EditResultBuilder.php:305
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:162
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:70
MediaWiki\Storage\EditResultBuilder\$originalRevisionId
bool int $originalRevisionId
Definition: EditResultBuilder.php:58
MediaWiki\Storage\EditResultBuilder\__construct
__construct(RevisionStore $revisionStore, array $softwareTags, ILoadBalancer $loadBalancer, ServiceOptions $options)
EditResultBuilder constructor.
Definition: EditResultBuilder.php:93
MediaWiki\Storage\EditResultBuilder\$isNew
bool $isNew
Definition: EditResultBuilder.php:55
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:237
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:195
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:110
Wikimedia\Rdbms\ILoadBalancer
Database cluster connection, tracking, load balancing, and transaction manager interface.
Definition: ILoadBalancer.php:81