27 use InvalidArgumentException;
36 use Psr\Log\LoggerInterface;
66 private $actorStoreFactory;
69 private $blockRestrictionStore;
72 private $commentStore;
78 private $loadBalancer;
81 private $readOnlyMode;
100 LoggerInterface $logger,
108 $wikiId = DatabaseBlock::LOCAL
112 $this->wikiId = $wikiId;
114 $this->options = $options;
115 $this->logger = $logger;
116 $this->actorStoreFactory = $actorStoreFactory;
117 $this->blockRestrictionStore = $blockRestrictionStore;
118 $this->commentStore = $commentStore;
119 $this->hookRunner =
new HookRunner( $hookContainer );
120 $this->loadBalancer = $loadBalancer;
121 $this->readOnlyMode = $readOnlyMode;
122 $this->userFactory = $userFactory;
131 if ( $this->readOnlyMode->isReadOnly() ) {
135 $dbw = $this->loadBalancer->getConnectionRef(
DB_PRIMARY, [], $this->wikiId );
136 $store = $this->blockRestrictionStore;
142 static function (
IDatabase $dbw, $fname ) use ( $store, $limit ) {
149 [
'LIMIT' => $limit ]
152 $store->deleteByBlockId( $ids );
153 $dbw->
delete(
'ipblocks', [
'ipb_id' => $ids ], $fname );
166 private function checkDatabaseDomain( $expectedWiki, ?
IDatabase $db =
null ) {
168 $dbDomain = $db->getDomainID();
169 $storeDomain = $this->loadBalancer->resolveDomainID( $expectedWiki );
170 if ( $dbDomain !== $storeDomain ) {
171 throw new InvalidArgumentException(
172 "DB connection domain '$dbDomain' does not match '$storeDomain'"
176 if ( $expectedWiki !== $this->wikiId ) {
177 throw new InvalidArgumentException(
178 "Must provide a database connection for wiki '$expectedWiki'."
200 if ( !$blocker || $blocker->getName() ===
'' ) {
201 throw new MWException(
'Cannot insert a block without a blocker set' );
204 $this->checkDatabaseDomain( $block->
getWikiId(), $database );
206 $this->logger->debug(
'Inserting block; timestamp ' . $block->
getTimestamp() );
210 $dbw = $database ?: $this->loadBalancer->getConnectionRef(
DB_PRIMARY, [], $this->wikiId );
211 $row = $this->getArrayForDatabaseBlock( $block, $dbw );
213 $dbw->insert(
'ipblocks', $row, __METHOD__, [
'IGNORE' ] );
214 $affected = $dbw->affectedRows();
217 $block->
setId( $dbw->insertId() );
219 if ( $restrictions ) {
220 $this->blockRestrictionStore->insert( $restrictions );
229 $ids = $dbw->selectFieldValues(
233 'ipb_address' => $row[
'ipb_address'],
234 'ipb_user' => $row[
'ipb_user'],
235 'ipb_expiry < ' . $dbw->addQuotes( $dbw->timestamp() )
240 $dbw->delete(
'ipblocks', [
'ipb_id' => $ids ], __METHOD__ );
241 $this->blockRestrictionStore->deleteByBlockId( $ids );
242 $dbw->insert(
'ipblocks', $row, __METHOD__, [
'IGNORE' ] );
243 $affected = $dbw->affectedRows();
244 $block->
setId( $dbw->insertId() );
246 if ( $restrictions ) {
247 $this->blockRestrictionStore->insert( $restrictions );
253 $autoBlockIds = $this->doRetroactiveAutoblock( $block );
257 if ( $targetUserIdentity ) {
258 $targetUser = $this->userFactory->newFromUserIdentity( $targetUserIdentity );
261 $targetUser->setToken();
262 $targetUser->saveSettings();
266 return [
'id' => $block->
getId( $this->wikiId ),
'autoIds' => $autoBlockIds ];
281 $this->logger->debug(
'Updating block; timestamp ' . $block->
getTimestamp() );
283 $this->checkDatabaseDomain( $block->
getWikiId() );
285 $blockId = $block->
getId( $this->wikiId );
288 __METHOD__ .
" requires that a block id be set\n"
292 $dbw = $this->loadBalancer->getConnectionRef(
DB_PRIMARY, [], $this->wikiId );
294 $row = $this->getArrayForDatabaseBlock( $block, $dbw );
295 $dbw->startAtomic( __METHOD__ );
297 $result = $dbw->update(
300 [
'ipb_id' => $blockId ],
306 if ( $restrictions !==
null ) {
308 if ( empty( $restrictions ) ) {
309 $success = $this->blockRestrictionStore->deleteByBlockId( $blockId );
311 $success = $this->blockRestrictionStore->update( $restrictions );
321 $this->getArrayForAutoblockUpdate( $block ),
322 [
'ipb_parent_block_id' => $blockId ],
327 if ( $restrictions !==
null ) {
328 $this->blockRestrictionStore->updateByParentBlockId(
335 $this->blockRestrictionStore->deleteByParentBlockId( $blockId );
338 [
'ipb_parent_block_id' => $blockId ],
343 $dbw->endAtomic( __METHOD__ );
346 $autoBlockIds = $this->doRetroactiveAutoblock( $block );
347 return [
'id' => $blockId,
'autoIds' => $autoBlockIds ];
361 if ( $this->readOnlyMode->isReadOnly() ) {
365 $this->checkDatabaseDomain( $block->getWikiId() );
367 $blockId = $block->getId( $this->wikiId );
371 __METHOD__ .
" requires that a block id be set\n"
374 $dbw = $this->loadBalancer->getConnectionRef(
DB_PRIMARY, [], $this->wikiId );
376 $this->blockRestrictionStore->deleteByParentBlockId( $blockId );
379 [
'ipb_parent_block_id' => $blockId ],
383 $this->blockRestrictionStore->deleteByBlockId( $blockId );
386 [
'ipb_id' => $blockId ],
390 return $dbw->affectedRows() > 0;
401 private function getArrayForDatabaseBlock(
402 DatabaseBlock $block,
405 $expiry = $dbw->encodeExpiry( $block->getExpiry() );
407 if ( $block->getTargetUserIdentity() ) {
408 $userId = $block->getTargetUserIdentity()->getId( $this->wikiId );
412 $blocker = $block->getBlocker();
414 throw new \RuntimeException( __METHOD__ .
': this block does not have a blocker' );
417 $blockerActor = $this->actorStoreFactory
418 ->getActorStore( $dbw->getDomainID() )
419 ->acquireActorId( $blocker, $dbw );
422 'ipb_address' => $block->getTargetName(),
423 'ipb_user' => $userId,
424 'ipb_by_actor' => $blockerActor,
425 'ipb_timestamp' => $dbw->timestamp( $block->getTimestamp() ),
427 'ipb_anon_only' => !$block->isHardblock(),
428 'ipb_create_account' => $block->isCreateAccountBlocked(),
429 'ipb_enable_autoblock' => $block->isAutoblocking(),
430 'ipb_expiry' => $expiry,
431 'ipb_range_start' => $block->getRangeStart(),
432 'ipb_range_end' => $block->getRangeEnd(),
433 'ipb_deleted' => intval( $block->getHideName() ),
434 'ipb_block_email' => $block->isEmailBlocked(),
435 'ipb_allow_usertalk' => $block->isUsertalkEditAllowed(),
436 'ipb_parent_block_id' => $block->getParentBlockId(),
437 'ipb_sitewide' => $block->isSitewide(),
439 $commentArray = $this->commentStore->insert(
442 $block->getReasonComment()
445 $combinedArray = $blockArray + $commentArray;
446 return $combinedArray;
455 private function getArrayForAutoblockUpdate( DatabaseBlock $block ): array {
456 $blocker = $block->getBlocker();
458 throw new \RuntimeException( __METHOD__ .
': this block does not have a blocker' );
460 $dbw = $this->loadBalancer->getConnectionRef(
DB_PRIMARY, [], $this->wikiId );
461 $blockerActor = $this->actorStoreFactory
462 ->getActorNormalization( $this->wikiId )
463 ->acquireActorId( $blocker, $dbw );
465 'ipb_by_actor' => $blockerActor,
466 'ipb_create_account' => $block->isCreateAccountBlocked(),
467 'ipb_deleted' => (int)$block->getHideName(),
468 'ipb_allow_usertalk' => $block->isUsertalkEditAllowed(),
469 'ipb_sitewide' => $block->isSitewide(),
472 $commentArray = $this->commentStore->insert(
475 $block->getReasonComment()
478 $combinedArray = $blockArray + $commentArray;
479 return $combinedArray;
489 private function doRetroactiveAutoblock( DatabaseBlock $block ): array {
492 if ( $block->isAutoblocking() && $block->getType() == AbstractBlock::TYPE_USER ) {
493 $this->logger->debug(
494 'Doing retroactive autoblocks for ' . $block->getTargetName()
497 $hookAutoBlocked = [];
498 $continue = $this->hookRunner->onPerformRetroactiveAutoblock(
504 $coreAutoBlocked = $this->performRetroactiveAutoblock( $block );
505 $autoBlockIds = array_merge( $hookAutoBlocked, $coreAutoBlocked );
507 $autoBlockIds = $hookAutoBlocked;
510 return $autoBlockIds;
520 private function performRetroactiveAutoblock( DatabaseBlock $block ): array {
521 if ( !$this->options->get( MainConfigNames::PutIPinRC ) ) {
526 $type = $block->getType();
527 if (
$type !== AbstractBlock::TYPE_USER ) {
532 $dbr = $this->loadBalancer->getConnectionRef(
DB_REPLICA, [], $this->wikiId );
534 $targetUser = $block->getTargetUserIdentity();
535 $actor = $targetUser ? $this->actorStoreFactory
536 ->getActorNormalization( $this->wikiId )
537 ->findActorId( $targetUser,
$dbr ) :
null;
540 $this->logger->debug(
'No actor found to retroactively autoblock' );
544 $rcIp =
$dbr->selectField(
547 [
'rc_actor' => $actor ],
549 [
'ORDER BY' =>
'rc_timestamp DESC' ]
553 $this->logger->debug(
'No IP found to retroactively autoblock' );
557 $id = $block->doAutoblock( $rcIp );
if(!defined('MW_SETUP_CALLBACK'))
Deferrable Update for closure/callback updates that should use auto-commit mode.
Class for managing the deferral of updates within the scope of a PHP script invocation.
static addUpdate(DeferrableUpdate $update, $stage=self::POSTSEND)
Add an update to the pending update queue for execution at the appropriate time.
A class containing constants representing the names of configuration variables.
const UpdateRowsPerQuery
Name constant for the UpdateRowsPerQuery setting, for use with Config::get()
const BlockDisablesLogin
Name constant for the BlockDisablesLogin setting, for use with Config::get()
const PutIPinRC
Name constant for the PutIPinRC setting, for use with Config::get()
A service class for fetching the wiki's current read-only mode.