104 parent::__construct( $options );
111 'createAccount' =>
false,
112 'enableAutoblock' =>
false,
113 'blockEmail' =>
false,
114 'allowUsertalk' =>
false,
118 $options += $defaults;
120 if ( $this->target instanceof
User && $options[
'user'] ) {
121 # Needed for foreign users
122 $this->forcedTargetID = $options[
'user'];
128 $this->mAuto = (bool)$options[
'auto'];
131 $this->
isSitewide( (
bool)$options[
'sitewide'] );
136 $this->mFromMaster =
false;
149 $blockQuery[
'tables'],
150 $blockQuery[
'fields'],
176 'tables' => [
'ipblocks' ] + $commentQuery[
'tables'] + $actorQuery[
'tables'],
183 'ipb_create_account',
184 'ipb_enable_autoblock',
188 'ipb_allow_usertalk',
189 'ipb_parent_block_id',
191 ] + $commentQuery[
'fields'] + $actorQuery[
'fields'],
192 'joins' => $commentQuery[
'joins'] + $actorQuery[
'joins'],
205 (
string)$this->target == (
string)$block->target
206 && $this->type == $block->type
207 && $this->mAuto == $block->mAuto
245 if ( $specificType !==
null ) {
247 'ipb_address' => [ (string)$specificTarget ],
250 $conds = [
'ipb_address' => [] ];
253 # Be aware that the != '' check is explicit, since empty values will be
254 # passed by some callers (T31116)
255 if ( $vagueTarget !=
'' ) {
259 # Slightly weird, but who are we to argue?
260 $conds[
'ipb_address'][] = (string)
$target;
264 $conds[
'ipb_address'][] = (string)
$target;
266 $conds = $db->makeList( $conds,
LIST_OR );
271 $conds[
'ipb_address'][] = (string)
$target;
273 $conds = $db->makeList( $conds,
LIST_OR );
277 throw new MWException(
"Tried to load block with invalid type" );
283 $blockQuery[
'tables'], $blockQuery[
'fields'], $conds, __METHOD__, [], $blockQuery[
'joins']
289 foreach (
$res as $row ) {
292 # Don't use expired blocks
293 if ( $block->isExpired() ) {
297 # Don't use anon only blocks on users
298 if ( $specificType == self::TYPE_USER && !$block->isHardblock() ) {
304 $autoBlocks[] = $block;
307 $blockIds[] = $block->getId();
312 foreach ( $autoBlocks as $block ) {
313 if ( !in_array( $block->mParentBlockId, $blockIds ) ) {
334 if ( count( $blocks ) === 1 ) {
338 # This result could contain a block on the user, a block on the IP, and a russian-doll
339 # set of rangeblocks. We want to choose the most specific one, so keep a leader board.
342 # Lower will be better
343 $bestBlockScore = 100;
344 foreach ( $blocks as $block ) {
346 # This is the number of bits that are allowed to vary in the block, give
347 # or take some floating point errors
351 $size = $max - $bits;
353 # Rank a range block covering a single IP equally with a single-IP block
354 $score = self::TYPE_RANGE - 1 + ( $size / $max );
357 $score = $block->getType();
360 if ( $score < $bestBlockScore ) {
361 $bestBlockScore = $score;
376 if ( $end ===
null ) {
379 # Per T16634, we want to include relevant active rangeblocks; for
380 # rangeblocks, we want to include larger ranges which enclose the given
381 # range. We know that all blocks must be smaller than $wgBlockCIDRLimit,
382 # so we can improve performance by filtering on a LIKE clause
385 $like =
$dbr->buildLike( $chunk,
$dbr->anyString() );
387 # Fairly hard to make a malicious SQL statement out of hex characters,
388 # but stranger things have happened...
389 $safeStart =
$dbr->addQuotes( $start );
390 $safeEnd =
$dbr->addQuotes( $end );
392 return $dbr->makeList(
394 "ipb_range_start $like",
395 "ipb_range_start <= $safeStart",
396 "ipb_range_end >= $safeEnd",
410 if ( substr( $hex, 0, 3 ) ==
'v6-' ) {
411 return 'v6-' . substr( substr( $hex, 3 ), 0, floor(
$wgBlockCIDRLimit[
'IPv6'] / 4 ) );
425 $row->ipb_by, $row->ipb_by_text, $row->ipb_by_actor ??
null
429 $this->mAuto = $row->ipb_auto;
431 $this->mId = (int)$row->ipb_id;
432 $this->mParentBlockId = $row->ipb_parent_block_id;
436 $this->
setExpiry( $db->decodeExpiry( $row->ipb_expiry ) );
440 ->getCommentLegacy( $db,
'ipb_reason', $row )->text
445 $this->
isSitewide( (
bool)$row->ipb_sitewide );
469 public function delete() {
474 if ( !$this->
getId() ) {
476 __METHOD__ .
" requires that the mId member be filled\n"
483 $dbw->delete(
'ipblocks', [
'ipb_parent_block_id' => $this->
getId() ], __METHOD__ );
486 $dbw->delete(
'ipblocks', [
'ipb_id' => $this->
getId() ], __METHOD__ );
488 return $dbw->affectedRows() > 0;
503 throw new MWException(
'Cannot insert a block without a blocker set' );
506 wfDebug( __METHOD__ .
"; timestamp {$this->mTimestamp}\n" );
508 if ( $dbw ===
null ) {
516 $dbw->insert(
'ipblocks', $row, __METHOD__, [
'IGNORE' ] );
517 $affected = $dbw->affectedRows();
519 $this->
setId( $dbw->insertId() );
520 if ( $this->restrictions ) {
525 # Don't collide with expired blocks.
526 # Do this after trying to insert to avoid locking.
528 # T96428: The ipb_address index uses a prefix on a field, so
529 # use a standard SELECT + DELETE to avoid annoying gap locks.
530 $ids = $dbw->selectFieldValues(
'ipblocks',
533 'ipb_address' => $row[
'ipb_address'],
534 'ipb_user' => $row[
'ipb_user'],
535 'ipb_expiry < ' . $dbw->addQuotes( $dbw->timestamp() )
540 $dbw->delete(
'ipblocks', [
'ipb_id' => $ids ], __METHOD__ );
542 $dbw->insert(
'ipblocks', $row, __METHOD__, [
'IGNORE' ] );
543 $affected = $dbw->affectedRows();
544 $this->
setId( $dbw->insertId() );
545 if ( $this->restrictions ) {
554 if ( $wgBlockDisablesLogin && $this->target instanceof
User ) {
556 $this->target->setToken();
557 $this->target->saveSettings();
560 return [
'id' =>
$this->mId,
'autoIds' => $auto_ipd_ids ];
574 wfDebug( __METHOD__ .
"; timestamp {$this->mTimestamp}\n" );
577 $dbw->startAtomic( __METHOD__ );
579 $result = $dbw->update(
582 [
'ipb_id' => $this->
getId() ],
587 if ( $this->restrictions !==
null ) {
589 if ( empty( $this->restrictions ) ) {
603 [
'ipb_parent_block_id' => $this->
getId() ],
608 if ( $this->restrictions !==
null ) {
616 [
'ipb_parent_block_id' => $this->
getId() ],
621 $dbw->endAtomic( __METHOD__ );
625 return [
'id' =>
$this->mId,
'autoIds' => $auto_ipd_ids ];
639 if ( $this->forcedTargetID ) {
642 $uid = $this->target instanceof
User ? $this->target->
getId() : 0;
646 'ipb_address' => (string)$this->target,
648 'ipb_timestamp' => $dbw->
timestamp( $this->getTimestamp() ),
649 'ipb_auto' => $this->mAuto,
653 'ipb_expiry' => $expiry,
689 # If autoblock is enabled, autoblock the LAST IP(s) used
694 'PerformRetroactiveAutoblock', [ $this, &$blockIds ] );
727 $options = [
'ORDER BY' =>
'rc_timestamp DESC' ];
730 $options[
'LIMIT'] = 1;
733 [
'recentchanges' ] + $rcQuery[
'tables'],
741 if ( !
$res->numRows() ) {
742 # No results, don't autoblock anything
743 wfDebug(
"No IP found to retroactively autoblock\n" );
745 foreach (
$res as $row ) {
768 $cache->makeKey(
'ip-autoblock',
'whitelist' ),
770 function ( $curValue, &$ttl, array &$setOpts ) {
773 return explode(
"\n",
774 wfMessage(
'autoblock_whitelist' )->inContentLanguage()->plain() );
778 wfDebug(
"Checking the autoblock whitelist..\n" );
782 if ( substr(
$line, 0, 1 ) !==
'*' ) {
786 $wlEntry = substr(
$line, 1 );
787 $wlEntry = trim( $wlEntry );
789 wfDebug(
"Checking $ip against $wlEntry..." );
791 # Is the IP in this range?
793 wfDebug(
" IP $ip matches $wlEntry, not autoblocking\n" );
810 # If autoblocks are disabled, go away.
815 # Check for presence on the autoblock whitelist.
816 if ( self::isWhitelistedFromAutoblocks( $autoblockIP ) ) {
822 # Allow hooks to cancel the autoblock.
823 if ( !
Hooks::run(
'AbortAutoblock', [ $autoblockIP, &$block ] ) ) {
824 wfDebug(
"Autoblock aborted by hook.\n" );
828 # It's okay to autoblock. Go ahead and insert/update the block...
830 # Do not add a *new* block if the IP is already blocked.
833 # Check if the block is an autoblock and would exceed the user block
834 # if renewed. If so, do nothing, otherwise prolong the block time...
835 if ( $ipblock->mAuto &&
838 # Reset block timestamp to now and its expiry to
839 # $wgAutoblockExpiry in the future
840 $ipblock->updateTimestamp();
845 # Make a new block object with the desired properties.
847 wfDebug(
"Autoblocking {$this->getTarget()}@" . $autoblockIP .
"\n" );
848 $autoblock->setTarget( $autoblockIP );
849 $autoblock->setBlocker( $this->
getBlocker() );
850 $autoblock->setReason(
852 ->inContentLanguage()->plain()
855 $autoblock->setTimestamp( $timestamp );
856 $autoblock->mAuto = 1;
858 # Continue suppressing the name if needed
862 $autoblock->isSitewide( $this->
isSitewide() );
865 if ( $this->
getExpiry() ==
'infinity' ) {
866 # Original block was indefinite, start an autoblock now
867 $autoblock->setExpiry( self::getAutoblockExpiry( $timestamp ) );
869 # If the user is already blocked with an expiry date, we don't
870 # want to pile on top of that.
871 $autoblock->setExpiry( min( $this->
getExpiry(), self::getAutoblockExpiry( $timestamp ) ) );
874 # Insert the block...
875 $status = $autoblock->insert();
887 wfDebug( __METHOD__ .
" -- deleting\n" );
891 wfDebug( __METHOD__ .
" -- not expired\n" );
904 wfDebug( __METHOD__ .
" checking current " . $timestamp .
" vs $this->mExpiry\n" );
928 if ( $this->mAuto ) {
933 $dbw->update(
'ipblocks',
935 'ipb_timestamp' => $dbw->timestamp( $this->getTimestamp() ),
936 'ipb_expiry' => $dbw->timestamp( $this->getExpiry() ),
939 'ipb_id' => $this->
getId(),
952 switch ( $this->type ) {
961 throw new MWException(
"Block with invalid type" );
971 switch ( $this->type ) {
980 throw new MWException(
"Block with invalid type" );
997 private function setId( $blockId ) {
998 $this->mId = (int)$blockId;
1000 if ( is_array( $this->restrictions ) ) {
1002 $blockId, $this->restrictions
1024 return wfSetVar( $this->mFromMaster, $x );
1035 # You can't *not* hardblock a user
1036 return $this->
getType() == self::TYPE_USER
1048 # You can't put an autoblock on an IP or range as we don't have any history to
1049 # look over to get more IPs from
1050 return $this->
getType() == self::TYPE_USER
1060 if ( $this->mAuto ) {
1061 return Html::element(
1063 [
'class' =>
'mw-autoblockid' ],
1064 wfMessage(
'autoblockid', $this->mId )->text()
1067 return htmlspecialchars( $this->
getTarget() );
1102 $blockRestrictionStore->deleteByBlockId( $ids );
1104 $dbw->
delete(
'ipblocks', [
'ipb_id' => $ids ], $fname );
1130 public static function newFromTarget( $specificTarget, $vagueTarget =
null, $fromMaster =
false ) {
1146 $vagueTarget =
null,
1150 if (
$type == self::TYPE_ID ||
$type == self::TYPE_AUTO ) {
1152 return $block ? [ $block ] : [];
1153 } elseif (
$target ===
null && $vagueTarget ==
'' ) {
1154 # We're not going to find anything useful here
1155 # Be aware that the == '' check is explicit, since empty values will be
1156 # passed by some callers (T31116)
1158 } elseif ( in_array(
1160 [ self::TYPE_USER, self::TYPE_IP, self::TYPE_RANGE,
null ] )
1178 if ( $ipChain === [] ) {
1184 foreach ( array_unique( $ipChain ) as $ipaddr ) {
1185 # Discard invalid IP addresses. Since XFF can be spoofed and we do not
1186 # necessarily trust the header given to us, make sure that we are only
1187 # checking for blocks on well-formatted IP addresses (IPv4 and IPv6).
1188 # Do not treat private IP spaces as special as it may be desirable for wikis
1189 # to block those IP ranges in order to stop misbehaving proxies that spoof XFF.
1193 # Don't check trusted IPs (includes local CDNs which will be in every request)
1194 if ( $proxyLookup->isTrustedProxy( $ipaddr ) ) {
1197 # Check both the original IP (to check against single blocks), as well as build
1198 # the clause to check for rangeblocks for the given IP.
1199 $conds[
'ipb_address'][] = $ipaddr;
1203 if ( $conds === [] ) {
1207 if ( $fromMaster ) {
1212 $conds = $db->makeList( $conds,
LIST_OR );
1214 $conds = [ $conds,
'ipb_anon_only' => 0 ];
1217 $rows = $db->select(
1218 $blockQuery[
'tables'],
1219 array_merge( [
'ipb_range_start',
'ipb_range_end' ], $blockQuery[
'fields'] ),
1223 $blockQuery[
'joins']
1227 foreach ( $rows as $row ) {
1229 if ( !$block->isExpired() ) {
1259 if ( $blocks === [] ) {
1261 } elseif ( count( $blocks ) == 1 ) {
1270 return strcmp( $bWeight, $aWeight );
1273 $blocksListExact = [
1275 'disable_create' =>
false,
1279 $blocksListRange = [
1281 'disable_create' =>
false,
1285 $ipChain = array_reverse( $ipChain );
1287 foreach ( $blocks as $block ) {
1290 if ( !$block->isHardblock() && $blocksListExact[
'hard'] ) {
1292 } elseif ( !$block->appliesToRight(
'createaccount' ) && $blocksListExact[
'disable_create'] ) {
1296 foreach ( $ipChain as $checkip ) {
1298 if ( (
string)$block->getTarget() === $checkip ) {
1299 if ( $block->isHardblock() ) {
1300 $blocksListExact[
'hard'] = $blocksListExact[
'hard'] ?: $block;
1301 } elseif ( $block->appliesToRight(
'createaccount' ) ) {
1302 $blocksListExact[
'disable_create'] = $blocksListExact[
'disable_create'] ?: $block;
1303 } elseif ( $block->mAuto ) {
1304 $blocksListExact[
'auto'] = $blocksListExact[
'auto'] ?: $block;
1306 $blocksListExact[
'other'] = $blocksListExact[
'other'] ?: $block;
1310 } elseif ( array_filter( $blocksListExact ) == []
1311 && $block->getRangeStart() <= $checkipHex
1312 && $block->getRangeEnd() >= $checkipHex
1314 if ( $block->isHardblock() ) {
1315 $blocksListRange[
'hard'] = $blocksListRange[
'hard'] ?: $block;
1316 } elseif ( $block->appliesToRight(
'createaccount' ) ) {
1317 $blocksListRange[
'disable_create'] = $blocksListRange[
'disable_create'] ?: $block;
1318 } elseif ( $block->mAuto ) {
1319 $blocksListRange[
'auto'] = $blocksListRange[
'auto'] ?: $block;
1321 $blocksListRange[
'other'] = $blocksListRange[
'other'] ?: $block;
1328 if ( array_filter( $blocksListExact ) == [] ) {
1329 $blocksList = &$blocksListRange;
1331 $blocksList = &$blocksListExact;
1334 $chosenBlock =
null;
1335 if ( $blocksList[
'hard'] ) {
1336 $chosenBlock = $blocksList[
'hard'];
1337 } elseif ( $blocksList[
'disable_create'] ) {
1338 $chosenBlock = $blocksList[
'disable_create'];
1339 } elseif ( $blocksList[
'other'] ) {
1340 $chosenBlock = $blocksList[
'other'];
1341 } elseif ( $blocksList[
'auto'] ) {
1342 $chosenBlock = $blocksList[
'auto'];
1344 throw new MWException(
"Proxy block found, but couldn't be classified." );
1347 return $chosenBlock;
1359 : parent::getType();
1420 $msg =
'blockedtext';
1421 if ( $this->mAuto ) {
1422 $msg =
'autoblockedtext';
1424 $msg =
'blockedtext-partial';
1427 array_unshift( $params, $msg );
1442 if ( $this->restrictions ===
null ) {
1445 if ( !$this->mId ) {
1462 $this->restrictions = array_filter(
$restrictions,
function ( $restriction ) {
1479 if ( $restriction->matches(
$title ) ) {
1502 return (
bool)$restriction;
1514 if ( $pageId <= 0 ) {
1520 return (
bool)$restriction;
1533 if ( $restriction->getType() !==
$type ) {
1537 if ( $restriction->getValue() === $value ) {
1538 return $restriction;
1556 return $isAnon &&
$config->
get(
'CookieSetOnIpBlock' );
1577 class_alias( DatabaseBlock::class,
'Block' );