MediaWiki  master
DatabaseBlockStore.php
Go to the documentation of this file.
1 <?php
23 namespace MediaWiki\Block;
24 
25 use ActorMigration;
27 use CommentStore;
28 use DeferredUpdates;
32 use MWException;
33 use Psr\Log\LoggerInterface;
34 use ReadOnlyMode;
35 use User;
38 
45 
47  private $options;
48 
52  public const CONSTRUCTOR_OPTIONS = [
53  'PutIPinRC',
54  'BlockDisablesLogin',
55  ];
56 
58  private $logger;
59 
61  private $actorMigration;
62 
65 
67  private $commentStore;
68 
70  private $hookRunner;
71 
73  private $loadBalancer;
74 
76  private $readOnlyMode;
77 
88  public function __construct(
90  LoggerInterface $logger,
94  HookContainer $hookContainer,
97  ) {
98  $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
99 
100  $this->options = $options;
101  $this->logger = $logger;
102  $this->actorMigration = $actorMigration;
103  $this->blockRestrictionStore = $blockRestrictionStore;
104  $this->commentStore = $commentStore;
105  $this->hookRunner = new HookRunner( $hookContainer );
106  $this->loadBalancer = $loadBalancer;
107  $this->readOnlyMode = $readOnlyMode;
108  }
109 
115  public function purgeExpiredBlocks() {
116  if ( $this->readOnlyMode->isReadOnly() ) {
117  return;
118  }
119 
120  $dbw = $this->loadBalancer->getConnectionRef( DB_MASTER );
122 
124  $dbw,
125  __METHOD__,
126  function ( IDatabase $dbw, $fname ) use ( $blockRestrictionStore ) {
127  $ids = $dbw->selectFieldValues(
128  'ipblocks',
129  'ipb_id',
130  [ 'ipb_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ],
131  $fname
132  );
133  if ( $ids ) {
135  $dbw->delete( 'ipblocks', [ 'ipb_id' => $ids ], $fname );
136  }
137  }
138  ) );
139  }
140 
151  public function insertBlock(
152  DatabaseBlock $block,
153  IDatabase $database = null
154  ) {
155  if ( !$block->getBlocker() || $block->getBlocker()->getName() === '' ) {
156  throw new MWException( 'Cannot insert a block without a blocker set' );
157  }
158 
159  $this->logger->debug( 'Inserting block; timestamp ' . $block->getTimestamp() );
160 
161  // TODO T258866 - consider passing the database
162  $this->purgeExpiredBlocks();
163 
164  $dbw = $database ?: $this->loadBalancer->getConnectionRef( DB_MASTER );
165  $row = $this->getArrayForDatabaseBlock( $block, $dbw );
166 
167  $dbw->insert( 'ipblocks', $row, __METHOD__, [ 'IGNORE' ] );
168  $affected = $dbw->affectedRows();
169 
170  if ( $affected ) {
171  $block->setId( $dbw->insertId() );
172  if ( $block->getRawRestrictions() ) {
173  $this->blockRestrictionStore->insert( $block->getRawRestrictions() );
174  }
175  }
176 
177  // Don't collide with expired blocks.
178  // Do this after trying to insert to avoid locking.
179  if ( !$affected ) {
180  // T96428: The ipb_address index uses a prefix on a field, so
181  // use a standard SELECT + DELETE to avoid annoying gap locks.
182  $ids = $dbw->selectFieldValues(
183  'ipblocks',
184  'ipb_id',
185  [
186  'ipb_address' => $row['ipb_address'],
187  'ipb_user' => $row['ipb_user'],
188  'ipb_expiry < ' . $dbw->addQuotes( $dbw->timestamp() )
189  ],
190  __METHOD__
191  );
192  if ( $ids ) {
193  $dbw->delete( 'ipblocks', [ 'ipb_id' => $ids ], __METHOD__ );
194  $this->blockRestrictionStore->deleteByBlockId( $ids );
195  $dbw->insert( 'ipblocks', $row, __METHOD__, [ 'IGNORE' ] );
196  $affected = $dbw->affectedRows();
197  $block->setId( $dbw->insertId() );
198  if ( $block->getRawRestrictions() ) {
199  $this->blockRestrictionStore->insert( $block->getRawRestrictions() );
200  }
201  }
202  }
203 
204  if ( $affected ) {
205  $autoBlockIds = $this->doRetroactiveAutoblock( $block );
206 
207  if ( $this->options->get( 'BlockDisablesLogin' ) ) {
208  $target = $block->getTarget();
209  if ( $target instanceof User ) {
210  // Change user login token to force them to be logged out.
211  $target->setToken();
212  $target->saveSettings();
213  }
214  }
215 
216  return [ 'id' => $block->getId(), 'autoIds' => $autoBlockIds ];
217  }
218 
219  return false;
220  }
221 
230  public function updateBlock( DatabaseBlock $block ) {
231  $this->logger->debug( 'Updating block; timestamp ' . $block->getTimestamp() );
232 
233  $dbw = $this->loadBalancer->getConnectionRef( DB_MASTER );
234  $row = $this->getArrayForDatabaseBlock( $block, $dbw );
235  $dbw->startAtomic( __METHOD__ );
236 
237  $result = $dbw->update(
238  'ipblocks',
239  $row,
240  [ 'ipb_id' => $block->getId() ],
241  __METHOD__
242  );
243 
244  // Only update the restrictions if they have been modified.
245  $restrictions = $block->getRawRestrictions();
246  if ( $restrictions !== null ) {
247  // An empty array should remove all of the restrictions.
248  if ( empty( $restrictions ) ) {
249  $success = $this->blockRestrictionStore->deleteByBlockId( $block->getId() );
250  } else {
251  $success = $this->blockRestrictionStore->update( $restrictions );
252  }
253  // Update the result. The first false is the result, otherwise, true.
254  $result = $result && $success;
255  }
256 
257  if ( $block->isAutoblocking() ) {
258  // update corresponding autoblock(s) (T50813)
259  $dbw->update(
260  'ipblocks',
261  $this->getArrayForAutoblockUpdate( $block ),
262  [ 'ipb_parent_block_id' => $block->getId() ],
263  __METHOD__
264  );
265 
266  // Only update the restrictions if they have been modified.
267  if ( $restrictions !== null ) {
268  $this->blockRestrictionStore->updateByParentBlockId(
269  $block->getId(),
270  $restrictions
271  );
272  }
273  } else {
274  // autoblock no longer required, delete corresponding autoblock(s)
275  $this->blockRestrictionStore->deleteByParentBlockId( $block->getId() );
276  $dbw->delete(
277  'ipblocks',
278  [ 'ipb_parent_block_id' => $block->getId() ],
279  __METHOD__
280  );
281  }
282 
283  $dbw->endAtomic( __METHOD__ );
284 
285  if ( $result ) {
286  $autoBlockIds = $this->doRetroactiveAutoblock( $block );
287  return [ 'id' => $block->getId(), 'autoIds' => $autoBlockIds ];
288  }
289 
290  return false;
291  }
292 
300  public function deleteBlock( DatabaseBlock $block ) : bool {
301  if ( $this->readOnlyMode->isReadOnly() ) {
302  return false;
303  }
304 
305  $blockId = $block->getId();
306 
307  if ( !$blockId ) {
308  throw new MWException(
309  __METHOD__ . " requires that a block id be set\n"
310  );
311  }
312  $dbw = $this->loadBalancer->getConnectionRef( DB_MASTER );
313 
314  $this->blockRestrictionStore->deleteByParentBlockId( $blockId );
315  $dbw->delete(
316  'ipblocks',
317  [ 'ipb_parent_block_id' => $blockId ],
318  __METHOD__
319  );
320 
321  $this->blockRestrictionStore->deleteByBlockId( $blockId );
322  $dbw->delete(
323  'ipblocks',
324  [ 'ipb_id' => $blockId ],
325  __METHOD__
326  );
327 
328  return $dbw->affectedRows() > 0;
329  }
330 
338  private function getArrayForDatabaseBlock(
339  DatabaseBlock $block,
340  IDatabase $dbw
341  ) : array {
342  $expiry = $dbw->encodeExpiry( $block->getExpiry() );
343 
344  $target = $block->getTarget();
345  $forcedTargetId = $block->getForcedTargetId();
346  if ( $forcedTargetId ) {
347  $userId = $forcedTargetId;
348  } elseif ( $target instanceof User ) {
349  $userId = $target->getId();
350  } else {
351  $userId = 0;
352  }
353 
354  $blockArray = [
355  'ipb_address' => (string)$target,
356  'ipb_user' => $userId,
357  'ipb_timestamp' => $dbw->timestamp( $block->getTimestamp() ),
358  'ipb_auto' => $block->getType() === AbstractBlock::TYPE_AUTO,
359  'ipb_anon_only' => !$block->isHardblock(),
360  'ipb_create_account' => $block->isCreateAccountBlocked(),
361  'ipb_enable_autoblock' => $block->isAutoblocking(),
362  'ipb_expiry' => $expiry,
363  'ipb_range_start' => $block->getRangeStart(),
364  'ipb_range_end' => $block->getRangeEnd(),
365  'ipb_deleted' => intval( $block->getHideName() ), // typecast required for SQLite
366  'ipb_block_email' => $block->isEmailBlocked(),
367  'ipb_allow_usertalk' => $block->isUsertalkEditAllowed(),
368  'ipb_parent_block_id' => $block->getParentBlockId(),
369  'ipb_sitewide' => $block->isSitewide(),
370  ];
371  $commentArray = $this->commentStore->insert(
372  $dbw,
373  'ipb_reason',
374  $block->getReasonComment()
375  );
376  $actorArray = $this->actorMigration->getInsertValues(
377  $dbw,
378  'ipb_by',
379  $block->getBlocker()
380  );
381 
382  $combinedArray = $blockArray + $commentArray + $actorArray;
383  return $combinedArray;
384  }
385 
392  private function getArrayForAutoblockUpdate( DatabaseBlock $block ) : array {
393  $blockArray = [
394  'ipb_create_account' => $block->isCreateAccountBlocked(),
395  'ipb_deleted' => (int)$block->getHideName(), // typecast required for SQLite
396  'ipb_allow_usertalk' => $block->isUsertalkEditAllowed(),
397  'ipb_sitewide' => $block->isSitewide(),
398  ];
399 
400  $dbw = $this->loadBalancer->getConnectionRef( DB_MASTER );
401  $commentArray = $this->commentStore->insert(
402  $dbw,
403  'ipb_reason',
404  $block->getReasonComment()
405  );
406  $actorArray = $this->actorMigration->getInsertValues(
407  $dbw,
408  'ipb_by',
409  $block->getBlocker()
410  );
411 
412  $combinedArray = $blockArray + $commentArray + $actorArray;
413  return $combinedArray;
414  }
415 
423  private function doRetroactiveAutoblock( DatabaseBlock $block ) : array {
424  $autoBlockIds = [];
425  // If autoblock is enabled, autoblock the LAST IP(s) used
426  if ( $block->isAutoblocking() && $block->getType() == AbstractBlock::TYPE_USER ) {
427  $this->logger->debug(
428  'Doing retroactive autoblocks for ' . $block->getTarget()
429  );
430 
431  $hookAutoBlocked = [];
432  $continue = $this->hookRunner->onPerformRetroactiveAutoblock(
433  $block,
434  $hookAutoBlocked
435  );
436 
437  if ( $continue ) {
438  $coreAutoBlocked = $this->performRetroactiveAutoblock( $block );
439  $autoBlockIds = array_merge( $hookAutoBlocked, $coreAutoBlocked );
440  } else {
441  $autoBlockIds = $hookAutoBlocked;
442  }
443  }
444  return $autoBlockIds;
445  }
446 
454  private function performRetroactiveAutoblock( DatabaseBlock $block ) : array {
455  if ( !$this->options->get( 'PutIPinRC' ) ) {
456  // No IPs in the recent changes table to autoblock
457  return [];
458  }
459 
460  list( $target, $type ) = $block->getTargetAndType();
461  if ( $type !== AbstractBlock::TYPE_USER ) {
462  // Autoblocks only apply to users
463  return [];
464  }
465 
466  $dbr = $this->loadBalancer->getConnectionRef( DB_REPLICA );
467  $rcQuery = $this->actorMigration->getWhere( $dbr, 'rc_user', $target, false );
468 
469  $options = [
470  'ORDER BY' => 'rc_timestamp DESC',
471  'LIMIT' => 1,
472  ];
473 
474  $res = $dbr->select(
475  [ 'recentchanges' ] + $rcQuery['tables'],
476  [ 'rc_ip' ],
477  $rcQuery['conds'],
478  __METHOD__,
479  $options,
480  $rcQuery['joins']
481  );
482 
483  if ( !$res->numRows() ) {
484  $this->logger->debug( 'No IP found to retroactively autoblock' );
485  return [];
486  }
487 
488  $blockIds = [];
489  foreach ( $res as $row ) {
490  if ( $row->rc_ip ) {
491  $id = $block->doAutoblock( $row->rc_ip );
492  if ( $id ) {
493  $blockIds[] = $id;
494  }
495  }
496  }
497  return $blockIds;
498  }
499 
500 }
MediaWiki\Block\DatabaseBlockStore\$hookRunner
HookRunner $hookRunner
Definition: DatabaseBlockStore.php:70
MediaWiki\Block\DatabaseBlockStore\$blockRestrictionStore
BlockRestrictionStore $blockRestrictionStore
Definition: DatabaseBlockStore.php:64
MediaWiki\Block
Definition: AbstractBlock.php:21
MediaWiki\Block\DatabaseBlockStore\$logger
LoggerInterface $logger
Definition: DatabaseBlockStore.php:58
if
if(ini_get( 'mbstring.func_overload')) if(!defined('MW_ENTRY_POINT'))
Pre-config setup: Before loading LocalSettings.php.
Definition: Setup.php:85
AutoCommitUpdate
Deferrable Update for closure/callback updates that should use auto-commit mode.
Definition: AutoCommitUpdate.php:9
MediaWiki\Block\DatabaseBlockStore\performRetroactiveAutoblock
performRetroactiveAutoblock(DatabaseBlock $block)
Actually retroactively autoblocks the last IP used by the user (if it is a user) blocked by this bloc...
Definition: DatabaseBlockStore.php:454
MediaWiki\Block\DatabaseBlock\getRangeStart
getRangeStart()
Get the IP address at the start of the range in Hex form.
Definition: DatabaseBlock.php:713
ReadOnlyMode
A service class for fetching the wiki's current read-only mode.
Definition: ReadOnlyMode.php:11
Wikimedia\Rdbms\IDatabase\encodeExpiry
encodeExpiry( $expiry)
Encode an expiry time into the DBMS dependent format.
MediaWiki\Block\DatabaseBlock\getType
getType()
Get the type of target for this particular block.int|null AbstractBlock::TYPE_ constant,...
Definition: DatabaseBlock.php:1117
DeferredUpdates\addUpdate
static addUpdate(DeferrableUpdate $update, $stage=self::POSTSEND)
Add an update to the deferred update queue for execution at the appropriate time.
Definition: DeferredUpdates.php:106
MediaWiki\Block\AbstractBlock\isCreateAccountBlocked
isCreateAccountBlocked( $x=null)
Get or set the flag indicating whether this block blocks the target from creating an account.
Definition: AbstractBlock.php:229
MediaWiki\Block\DatabaseBlock\getBlocker
getBlocker()
Get the user who implemented this block.
Definition: DatabaseBlock.php:1354
MediaWiki\Block\AbstractBlock\TYPE_AUTO
const TYPE_AUTO
Definition: AbstractBlock.php:92
MediaWiki\Block\DatabaseBlockStore\$loadBalancer
ILoadBalancer $loadBalancer
Definition: DatabaseBlockStore.php:73
MediaWiki\Block\DatabaseBlockStore\getArrayForDatabaseBlock
getArrayForDatabaseBlock(DatabaseBlock $block, IDatabase $dbw)
Get an array suitable for passing to $dbw->insert() or $dbw->update()
Definition: DatabaseBlockStore.php:338
CommentStore
Handle database storage of comments such as edit summaries and log reasons.
Definition: CommentStore.php:42
MediaWiki\Block\DatabaseBlockStore\purgeExpiredBlocks
purgeExpiredBlocks()
Delete expired blocks from the ipblocks table.
Definition: DatabaseBlockStore.php:115
MediaWiki\Block\DatabaseBlockStore\$commentStore
CommentStore $commentStore
Definition: DatabaseBlockStore.php:67
$success
$success
Definition: NoLocalSettings.php:42
ActorMigration
This class handles the logic for the actor table migration and should always be used in lieu of direc...
Definition: ActorMigration.php:39
MediaWiki\Block\DatabaseBlockStore
Definition: DatabaseBlockStore.php:44
$res
$res
Definition: testCompression.php:57
MediaWiki\Block\DatabaseBlock\getRawRestrictions
getRawRestrictions()
Get restrictions without loading from database if not yet loaded.
Definition: DatabaseBlock.php:1212
MediaWiki\Block\AbstractBlock\getTargetAndType
getTargetAndType()
Get the target and target type for this particular block.
Definition: AbstractBlock.php:450
MediaWiki\Block\DatabaseBlockStore\$options
ServiceOptions $options
Definition: DatabaseBlockStore.php:47
MediaWiki\Block\DatabaseBlockStore\updateBlock
updateBlock(DatabaseBlock $block)
Update a block in the DB with new parameters.
Definition: DatabaseBlockStore.php:230
Wikimedia\Rdbms\IDatabase
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:38
$dbr
$dbr
Definition: testCompression.php:54
MediaWiki\Block\DatabaseBlock
A DatabaseBlock (unlike a SystemBlock) is stored in the database, may give rise to autoblocks and may...
Definition: DatabaseBlock.php:52
Wikimedia\Rdbms\IDatabase\timestamp
timestamp( $ts=0)
Convert a timestamp in one of the formats accepted by ConvertibleTimestamp to the format used for ins...
MWException
MediaWiki exception.
Definition: MWException.php:29
MediaWiki\Config\ServiceOptions
A class for passing options to services.
Definition: ServiceOptions.php:25
MediaWiki\Block\DatabaseBlockStore\CONSTRUCTOR_OPTIONS
const CONSTRUCTOR_OPTIONS
Definition: DatabaseBlockStore.php:52
DeferredUpdates
Class for managing the deferred updates.
Definition: DeferredUpdates.php:62
MediaWiki\Block\DatabaseBlockStore\doRetroactiveAutoblock
doRetroactiveAutoblock(DatabaseBlock $block)
Handles retroactively autoblocking the last IP used by the user (if it is a user) blocked by an auto ...
Definition: DatabaseBlockStore.php:423
MediaWiki\Block\AbstractBlock\isEmailBlocked
isEmailBlocked( $x=null)
Get or set the flag indicating whether this block blocks the target from sending emails.
Definition: AbstractBlock.php:242
MediaWiki\Block\AbstractBlock\getReasonComment
getReasonComment()
Get the reason for creating the block.
Definition: AbstractBlock.php:173
MediaWiki\Block\DatabaseBlock\getRangeEnd
getRangeEnd()
Get the IP address at the end of the range in Hex form.
Definition: DatabaseBlock.php:732
MediaWiki\Block\DatabaseBlockStore\getArrayForAutoblockUpdate
getArrayForAutoblockUpdate(DatabaseBlock $block)
Get an array suitable for autoblock updates.
Definition: DatabaseBlockStore.php:392
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
MediaWiki\Block\AbstractBlock\isSitewide
isSitewide( $x=null)
Indicates that the block is a sitewide block.
Definition: AbstractBlock.php:216
DB_MASTER
const DB_MASTER
Definition: defines.php:26
MediaWiki\Block\DatabaseBlock\isAutoblocking
isAutoblocking( $x=null)
Definition: DatabaseBlock.php:821
MediaWiki\Block\AbstractBlock\TYPE_USER
const TYPE_USER
Definition: AbstractBlock.php:89
MediaWiki\Block\DatabaseBlock\getParentBlockId
getParentBlockId()
Definition: DatabaseBlock.php:787
MediaWiki\Block\DatabaseBlockStore\insertBlock
insertBlock(DatabaseBlock $block, IDatabase $database=null)
Insert a block into the block table.
Definition: DatabaseBlockStore.php:151
MediaWiki\Block\AbstractBlock\getExpiry
getExpiry()
Get the block expiry time.
Definition: AbstractBlock.php:470
MediaWiki\Block\DatabaseBlockStore\$actorMigration
ActorMigration $actorMigration
Definition: DatabaseBlockStore.php:61
MediaWiki\Block\AbstractBlock\getTimestamp
getTimestamp()
Get the timestamp indicating when the block was created.
Definition: AbstractBlock.php:490
MediaWiki\Block\DatabaseBlock\getId
getId()
Get the block ID.int|null
Definition: DatabaseBlock.php:760
MediaWiki\Block\DatabaseBlock\setId
setId( $blockId)
Set the block ID.
Definition: DatabaseBlock.php:771
Wikimedia\Rdbms\IDatabase\addQuotes
addQuotes( $s)
Escape and quote a raw value string for use in a SQL query.
Wikimedia\Rdbms\IDatabase\selectFieldValues
selectFieldValues( $table, $var, $cond='', $fname=__METHOD__, $options=[], $join_conds=[])
A SELECT wrapper which returns a list of single field values from result rows.
MediaWiki\Block\BlockRestrictionStore\deleteByBlockId
deleteByBlockId( $blockId)
Delete the restrictions by block ID.
Definition: BlockRestrictionStore.php:262
MediaWiki\Block\DatabaseBlock\doAutoblock
doAutoblock( $autoblockIP)
Autoblocks the given IP, referring to this block.
Definition: DatabaseBlock.php:576
MediaWiki\HookContainer\HookContainer
HookContainer class.
Definition: HookContainer.php:44
MediaWiki\Block\AbstractBlock\getHideName
getHideName()
Get whether the block hides the target's username.
Definition: AbstractBlock.php:193
MediaWiki\HookContainer\HookRunner
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
Definition: HookRunner.php:562
MediaWiki\Block\DatabaseBlockStore\deleteBlock
deleteBlock(DatabaseBlock $block)
Delete a DatabaseBlock from the database.
Definition: DatabaseBlockStore.php:300
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:55
MediaWiki\Block\DatabaseBlockStore\$readOnlyMode
ReadOnlyMode $readOnlyMode
Definition: DatabaseBlockStore.php:76
MediaWiki\Block\AbstractBlock\getTarget
getTarget()
Get the target for this particular block.
Definition: AbstractBlock.php:460
MediaWiki\Block\DatabaseBlock\isHardblock
isHardblock( $x=null)
Get/set whether the block is a hardblock (affects logged-in users on a given IP/range)
Definition: DatabaseBlock.php:808
MediaWiki\Block\DatabaseBlockStore\__construct
__construct(ServiceOptions $options, LoggerInterface $logger, ActorMigration $actorMigration, BlockRestrictionStore $blockRestrictionStore, CommentStore $commentStore, HookContainer $hookContainer, ILoadBalancer $loadBalancer, ReadOnlyMode $readOnlyMode)
Definition: DatabaseBlockStore.php:88
MediaWiki\Block\AbstractBlock\isUsertalkEditAllowed
isUsertalkEditAllowed( $x=null)
Get or set the flag indicating whether this block blocks the target from editing their own user talk ...
Definition: AbstractBlock.php:255
Wikimedia\Rdbms\IDatabase\delete
delete( $table, $conds, $fname=__METHOD__)
Delete all rows in a table that match a condition.
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:62
MediaWiki\Block\BlockRestrictionStore
Definition: BlockRestrictionStore.php:34
$type
$type
Definition: testCompression.php:52