MediaWiki
REL1_24
|
00001 <?php 00022 class Block { 00024 public $mReason; 00025 00027 public $mTimestamp; 00028 00030 public $mAuto; 00031 00033 public $mExpiry; 00034 00035 public $mHideName; 00036 00038 public $mParentBlockId; 00039 00041 protected $mId; 00042 00044 protected $mFromMaster; 00045 00047 protected $mBlockEmail; 00048 00050 protected $mDisableUsertalk; 00051 00053 protected $mCreateAccount; 00054 00056 protected $target; 00057 00059 protected $forcedTargetID; 00060 00062 protected $type; 00063 00065 protected $blocker; 00066 00068 protected $isHardblock = true; 00069 00071 protected $isAutoblocking = true; 00072 00073 # TYPE constants 00074 const TYPE_USER = 1; 00075 const TYPE_IP = 2; 00076 const TYPE_RANGE = 3; 00077 const TYPE_AUTO = 4; 00078 const TYPE_ID = 5; 00079 00098 function __construct( $address = '', $user = 0, $by = 0, $reason = '', 00099 $timestamp = 0, $auto = 0, $expiry = '', $anonOnly = 0, $createAccount = 0, $enableAutoblock = 0, 00100 $hideName = 0, $blockEmail = 0, $allowUsertalk = 0, $byText = '' 00101 ) { 00102 if ( $timestamp === 0 ) { 00103 $timestamp = wfTimestampNow(); 00104 } 00105 00106 if ( count( func_get_args() ) > 0 ) { 00107 # Soon... :D 00108 # wfDeprecated( __METHOD__ . " with arguments" ); 00109 } 00110 00111 $this->setTarget( $address ); 00112 if ( $this->target instanceof User && $user ) { 00113 $this->forcedTargetID = $user; // needed for foreign users 00114 } 00115 if ( $by ) { // local user 00116 $this->setBlocker( User::newFromID( $by ) ); 00117 } else { // foreign user 00118 $this->setBlocker( $byText ); 00119 } 00120 $this->mReason = $reason; 00121 $this->mTimestamp = wfTimestamp( TS_MW, $timestamp ); 00122 $this->mAuto = $auto; 00123 $this->isHardblock( !$anonOnly ); 00124 $this->prevents( 'createaccount', $createAccount ); 00125 if ( $expiry == 'infinity' || $expiry == wfGetDB( DB_SLAVE )->getInfinity() ) { 00126 $this->mExpiry = 'infinity'; 00127 } else { 00128 $this->mExpiry = wfTimestamp( TS_MW, $expiry ); 00129 } 00130 $this->isAutoblocking( $enableAutoblock ); 00131 $this->mHideName = $hideName; 00132 $this->prevents( 'sendemail', $blockEmail ); 00133 $this->prevents( 'editownusertalk', !$allowUsertalk ); 00134 00135 $this->mFromMaster = false; 00136 } 00137 00144 public static function newFromID( $id ) { 00145 $dbr = wfGetDB( DB_SLAVE ); 00146 $res = $dbr->selectRow( 00147 'ipblocks', 00148 self::selectFields(), 00149 array( 'ipb_id' => $id ), 00150 __METHOD__ 00151 ); 00152 if ( $res ) { 00153 return self::newFromRow( $res ); 00154 } else { 00155 return null; 00156 } 00157 } 00158 00164 public static function selectFields() { 00165 return array( 00166 'ipb_id', 00167 'ipb_address', 00168 'ipb_by', 00169 'ipb_by_text', 00170 'ipb_reason', 00171 'ipb_timestamp', 00172 'ipb_auto', 00173 'ipb_anon_only', 00174 'ipb_create_account', 00175 'ipb_enable_autoblock', 00176 'ipb_expiry', 00177 'ipb_deleted', 00178 'ipb_block_email', 00179 'ipb_allow_usertalk', 00180 'ipb_parent_block_id', 00181 ); 00182 } 00183 00192 public function equals( Block $block ) { 00193 return ( 00194 (string)$this->target == (string)$block->target 00195 && $this->type == $block->type 00196 && $this->mAuto == $block->mAuto 00197 && $this->isHardblock() == $block->isHardblock() 00198 && $this->prevents( 'createaccount' ) == $block->prevents( 'createaccount' ) 00199 && $this->mExpiry == $block->mExpiry 00200 && $this->isAutoblocking() == $block->isAutoblocking() 00201 && $this->mHideName == $block->mHideName 00202 && $this->prevents( 'sendemail' ) == $block->prevents( 'sendemail' ) 00203 && $this->prevents( 'editownusertalk' ) == $block->prevents( 'editownusertalk' ) 00204 && $this->mReason == $block->mReason 00205 ); 00206 } 00207 00218 protected function newLoad( $vagueTarget = null ) { 00219 $db = wfGetDB( $this->mFromMaster ? DB_MASTER : DB_SLAVE ); 00220 00221 if ( $this->type !== null ) { 00222 $conds = array( 00223 'ipb_address' => array( (string)$this->target ), 00224 ); 00225 } else { 00226 $conds = array( 'ipb_address' => array() ); 00227 } 00228 00229 # Be aware that the != '' check is explicit, since empty values will be 00230 # passed by some callers (bug 29116) 00231 if ( $vagueTarget != '' ) { 00232 list( $target, $type ) = self::parseTarget( $vagueTarget ); 00233 switch ( $type ) { 00234 case self::TYPE_USER: 00235 # Slightly weird, but who are we to argue? 00236 $conds['ipb_address'][] = (string)$target; 00237 break; 00238 00239 case self::TYPE_IP: 00240 $conds['ipb_address'][] = (string)$target; 00241 $conds[] = self::getRangeCond( IP::toHex( $target ) ); 00242 $conds = $db->makeList( $conds, LIST_OR ); 00243 break; 00244 00245 case self::TYPE_RANGE: 00246 list( $start, $end ) = IP::parseRange( $target ); 00247 $conds['ipb_address'][] = (string)$target; 00248 $conds[] = self::getRangeCond( $start, $end ); 00249 $conds = $db->makeList( $conds, LIST_OR ); 00250 break; 00251 00252 default: 00253 throw new MWException( "Tried to load block with invalid type" ); 00254 } 00255 } 00256 00257 $res = $db->select( 'ipblocks', self::selectFields(), $conds, __METHOD__ ); 00258 00259 # This result could contain a block on the user, a block on the IP, and a russian-doll 00260 # set of rangeblocks. We want to choose the most specific one, so keep a leader board. 00261 $bestRow = null; 00262 00263 # Lower will be better 00264 $bestBlockScore = 100; 00265 00266 # This is begging for $this = $bestBlock, but that's not allowed in PHP :( 00267 $bestBlockPreventsEdit = null; 00268 00269 foreach ( $res as $row ) { 00270 $block = self::newFromRow( $row ); 00271 00272 # Don't use expired blocks 00273 if ( $block->deleteIfExpired() ) { 00274 continue; 00275 } 00276 00277 # Don't use anon only blocks on users 00278 if ( $this->type == self::TYPE_USER && !$block->isHardblock() ) { 00279 continue; 00280 } 00281 00282 if ( $block->getType() == self::TYPE_RANGE ) { 00283 # This is the number of bits that are allowed to vary in the block, give 00284 # or take some floating point errors 00285 $end = wfBaseconvert( $block->getRangeEnd(), 16, 10 ); 00286 $start = wfBaseconvert( $block->getRangeStart(), 16, 10 ); 00287 $size = log( $end - $start + 1, 2 ); 00288 00289 # This has the nice property that a /32 block is ranked equally with a 00290 # single-IP block, which is exactly what it is... 00291 $score = self::TYPE_RANGE - 1 + ( $size / 128 ); 00292 00293 } else { 00294 $score = $block->getType(); 00295 } 00296 00297 if ( $score < $bestBlockScore ) { 00298 $bestBlockScore = $score; 00299 $bestRow = $row; 00300 $bestBlockPreventsEdit = $block->prevents( 'edit' ); 00301 } 00302 } 00303 00304 if ( $bestRow !== null ) { 00305 $this->initFromRow( $bestRow ); 00306 $this->prevents( 'edit', $bestBlockPreventsEdit ); 00307 return true; 00308 } else { 00309 return false; 00310 } 00311 } 00312 00319 public static function getRangeCond( $start, $end = null ) { 00320 if ( $end === null ) { 00321 $end = $start; 00322 } 00323 # Per bug 14634, we want to include relevant active rangeblocks; for 00324 # rangeblocks, we want to include larger ranges which enclose the given 00325 # range. We know that all blocks must be smaller than $wgBlockCIDRLimit, 00326 # so we can improve performance by filtering on a LIKE clause 00327 $chunk = self::getIpFragment( $start ); 00328 $dbr = wfGetDB( DB_SLAVE ); 00329 $like = $dbr->buildLike( $chunk, $dbr->anyString() ); 00330 00331 # Fairly hard to make a malicious SQL statement out of hex characters, 00332 # but stranger things have happened... 00333 $safeStart = $dbr->addQuotes( $start ); 00334 $safeEnd = $dbr->addQuotes( $end ); 00335 00336 return $dbr->makeList( 00337 array( 00338 "ipb_range_start $like", 00339 "ipb_range_start <= $safeStart", 00340 "ipb_range_end >= $safeEnd", 00341 ), 00342 LIST_AND 00343 ); 00344 } 00345 00352 protected static function getIpFragment( $hex ) { 00353 global $wgBlockCIDRLimit; 00354 if ( substr( $hex, 0, 3 ) == 'v6-' ) { 00355 return 'v6-' . substr( substr( $hex, 3 ), 0, floor( $wgBlockCIDRLimit['IPv6'] / 4 ) ); 00356 } else { 00357 return substr( $hex, 0, floor( $wgBlockCIDRLimit['IPv4'] / 4 ) ); 00358 } 00359 } 00360 00366 protected function initFromRow( $row ) { 00367 $this->setTarget( $row->ipb_address ); 00368 if ( $row->ipb_by ) { // local user 00369 $this->setBlocker( User::newFromID( $row->ipb_by ) ); 00370 } else { // foreign user 00371 $this->setBlocker( $row->ipb_by_text ); 00372 } 00373 00374 $this->mReason = $row->ipb_reason; 00375 $this->mTimestamp = wfTimestamp( TS_MW, $row->ipb_timestamp ); 00376 $this->mAuto = $row->ipb_auto; 00377 $this->mHideName = $row->ipb_deleted; 00378 $this->mId = $row->ipb_id; 00379 $this->mParentBlockId = $row->ipb_parent_block_id; 00380 00381 // I wish I didn't have to do this 00382 $db = wfGetDB( DB_SLAVE ); 00383 if ( $row->ipb_expiry == $db->getInfinity() ) { 00384 $this->mExpiry = 'infinity'; 00385 } else { 00386 $this->mExpiry = wfTimestamp( TS_MW, $row->ipb_expiry ); 00387 } 00388 00389 $this->isHardblock( !$row->ipb_anon_only ); 00390 $this->isAutoblocking( $row->ipb_enable_autoblock ); 00391 00392 $this->prevents( 'createaccount', $row->ipb_create_account ); 00393 $this->prevents( 'sendemail', $row->ipb_block_email ); 00394 $this->prevents( 'editownusertalk', !$row->ipb_allow_usertalk ); 00395 } 00396 00402 public static function newFromRow( $row ) { 00403 $block = new Block; 00404 $block->initFromRow( $row ); 00405 return $block; 00406 } 00407 00414 public function delete() { 00415 if ( wfReadOnly() ) { 00416 return false; 00417 } 00418 00419 if ( !$this->getId() ) { 00420 throw new MWException( "Block::delete() requires that the mId member be filled\n" ); 00421 } 00422 00423 $dbw = wfGetDB( DB_MASTER ); 00424 $dbw->delete( 'ipblocks', array( 'ipb_parent_block_id' => $this->getId() ), __METHOD__ ); 00425 $dbw->delete( 'ipblocks', array( 'ipb_id' => $this->getId() ), __METHOD__ ); 00426 00427 return $dbw->affectedRows() > 0; 00428 } 00429 00438 public function insert( $dbw = null ) { 00439 wfDebug( "Block::insert; timestamp {$this->mTimestamp}\n" ); 00440 00441 if ( $dbw === null ) { 00442 $dbw = wfGetDB( DB_MASTER ); 00443 } 00444 00445 # Don't collide with expired blocks 00446 Block::purgeExpired(); 00447 00448 $row = $this->getDatabaseArray(); 00449 $row['ipb_id'] = $dbw->nextSequenceValue( "ipblocks_ipb_id_seq" ); 00450 00451 $dbw->insert( 00452 'ipblocks', 00453 $row, 00454 __METHOD__, 00455 array( 'IGNORE' ) 00456 ); 00457 $affected = $dbw->affectedRows(); 00458 $this->mId = $dbw->insertId(); 00459 00460 if ( $affected ) { 00461 $auto_ipd_ids = $this->doRetroactiveAutoblock(); 00462 return array( 'id' => $this->mId, 'autoIds' => $auto_ipd_ids ); 00463 } 00464 00465 return false; 00466 } 00467 00475 public function update() { 00476 wfDebug( "Block::update; timestamp {$this->mTimestamp}\n" ); 00477 $dbw = wfGetDB( DB_MASTER ); 00478 00479 $dbw->startAtomic( __METHOD__ ); 00480 00481 $dbw->update( 00482 'ipblocks', 00483 $this->getDatabaseArray( $dbw ), 00484 array( 'ipb_id' => $this->getId() ), 00485 __METHOD__ 00486 ); 00487 00488 $affected = $dbw->affectedRows(); 00489 00490 if ( $this->isAutoblocking() ) { 00491 // update corresponding autoblock(s) (bug 48813) 00492 $dbw->update( 00493 'ipblocks', 00494 $this->getAutoblockUpdateArray(), 00495 array( 'ipb_parent_block_id' => $this->getId() ), 00496 __METHOD__ 00497 ); 00498 } else { 00499 // autoblock no longer required, delete corresponding autoblock(s) 00500 $dbw->delete( 00501 'ipblocks', 00502 array( 'ipb_parent_block_id' => $this->getId() ), 00503 __METHOD__ 00504 ); 00505 } 00506 00507 $dbw->endAtomic( __METHOD__ ); 00508 00509 if ( $affected ) { 00510 $auto_ipd_ids = $this->doRetroactiveAutoblock(); 00511 return array( 'id' => $this->mId, 'autoIds' => $auto_ipd_ids ); 00512 } 00513 00514 return false; 00515 } 00516 00522 protected function getDatabaseArray( $db = null ) { 00523 if ( !$db ) { 00524 $db = wfGetDB( DB_SLAVE ); 00525 } 00526 $expiry = $db->encodeExpiry( $this->mExpiry ); 00527 00528 if ( $this->forcedTargetID ) { 00529 $uid = $this->forcedTargetID; 00530 } else { 00531 $uid = $this->target instanceof User ? $this->target->getID() : 0; 00532 } 00533 00534 $a = array( 00535 'ipb_address' => (string)$this->target, 00536 'ipb_user' => $uid, 00537 'ipb_by' => $this->getBy(), 00538 'ipb_by_text' => $this->getByName(), 00539 'ipb_reason' => $this->mReason, 00540 'ipb_timestamp' => $db->timestamp( $this->mTimestamp ), 00541 'ipb_auto' => $this->mAuto, 00542 'ipb_anon_only' => !$this->isHardblock(), 00543 'ipb_create_account' => $this->prevents( 'createaccount' ), 00544 'ipb_enable_autoblock' => $this->isAutoblocking(), 00545 'ipb_expiry' => $expiry, 00546 'ipb_range_start' => $this->getRangeStart(), 00547 'ipb_range_end' => $this->getRangeEnd(), 00548 'ipb_deleted' => intval( $this->mHideName ), // typecast required for SQLite 00549 'ipb_block_email' => $this->prevents( 'sendemail' ), 00550 'ipb_allow_usertalk' => !$this->prevents( 'editownusertalk' ), 00551 'ipb_parent_block_id' => $this->mParentBlockId 00552 ); 00553 00554 return $a; 00555 } 00556 00560 protected function getAutoblockUpdateArray() { 00561 return array( 00562 'ipb_by' => $this->getBy(), 00563 'ipb_by_text' => $this->getByName(), 00564 'ipb_reason' => $this->mReason, 00565 'ipb_create_account' => $this->prevents( 'createaccount' ), 00566 'ipb_deleted' => (int)$this->mHideName, // typecast required for SQLite 00567 'ipb_allow_usertalk' => !$this->prevents( 'editownusertalk' ), 00568 ); 00569 } 00570 00577 protected function doRetroactiveAutoblock() { 00578 $blockIds = array(); 00579 # If autoblock is enabled, autoblock the LAST IP(s) used 00580 if ( $this->isAutoblocking() && $this->getType() == self::TYPE_USER ) { 00581 wfDebug( "Doing retroactive autoblocks for " . $this->getTarget() . "\n" ); 00582 00583 $continue = wfRunHooks( 00584 'PerformRetroactiveAutoblock', array( $this, &$blockIds ) ); 00585 00586 if ( $continue ) { 00587 self::defaultRetroactiveAutoblock( $this, $blockIds ); 00588 } 00589 } 00590 return $blockIds; 00591 } 00592 00600 protected static function defaultRetroactiveAutoblock( Block $block, array &$blockIds ) { 00601 global $wgPutIPinRC; 00602 00603 // No IPs are in recentchanges table, so nothing to select 00604 if ( !$wgPutIPinRC ) { 00605 return; 00606 } 00607 00608 $dbr = wfGetDB( DB_SLAVE ); 00609 00610 $options = array( 'ORDER BY' => 'rc_timestamp DESC' ); 00611 $conds = array( 'rc_user_text' => (string)$block->getTarget() ); 00612 00613 // Just the last IP used. 00614 $options['LIMIT'] = 1; 00615 00616 $res = $dbr->select( 'recentchanges', array( 'rc_ip' ), $conds, 00617 __METHOD__, $options ); 00618 00619 if ( !$res->numRows() ) { 00620 # No results, don't autoblock anything 00621 wfDebug( "No IP found to retroactively autoblock\n" ); 00622 } else { 00623 foreach ( $res as $row ) { 00624 if ( $row->rc_ip ) { 00625 $id = $block->doAutoblock( $row->rc_ip ); 00626 if ( $id ) { 00627 $blockIds[] = $id; 00628 } 00629 } 00630 } 00631 } 00632 } 00633 00641 public static function isWhitelistedFromAutoblocks( $ip ) { 00642 global $wgMemc; 00643 00644 // Try to get the autoblock_whitelist from the cache, as it's faster 00645 // than getting the msg raw and explode()'ing it. 00646 $key = wfMemcKey( 'ipb', 'autoblock', 'whitelist' ); 00647 $lines = $wgMemc->get( $key ); 00648 if ( !$lines ) { 00649 $lines = explode( "\n", wfMessage( 'autoblock_whitelist' )->inContentLanguage()->plain() ); 00650 $wgMemc->set( $key, $lines, 3600 * 24 ); 00651 } 00652 00653 wfDebug( "Checking the autoblock whitelist..\n" ); 00654 00655 foreach ( $lines as $line ) { 00656 # List items only 00657 if ( substr( $line, 0, 1 ) !== '*' ) { 00658 continue; 00659 } 00660 00661 $wlEntry = substr( $line, 1 ); 00662 $wlEntry = trim( $wlEntry ); 00663 00664 wfDebug( "Checking $ip against $wlEntry..." ); 00665 00666 # Is the IP in this range? 00667 if ( IP::isInRange( $ip, $wlEntry ) ) { 00668 wfDebug( " IP $ip matches $wlEntry, not autoblocking\n" ); 00669 return true; 00670 } else { 00671 wfDebug( " No match\n" ); 00672 } 00673 } 00674 00675 return false; 00676 } 00677 00684 public function doAutoblock( $autoblockIP ) { 00685 # If autoblocks are disabled, go away. 00686 if ( !$this->isAutoblocking() ) { 00687 return false; 00688 } 00689 00690 # Check for presence on the autoblock whitelist. 00691 if ( self::isWhitelistedFromAutoblocks( $autoblockIP ) ) { 00692 return false; 00693 } 00694 00695 # Allow hooks to cancel the autoblock. 00696 if ( !wfRunHooks( 'AbortAutoblock', array( $autoblockIP, &$this ) ) ) { 00697 wfDebug( "Autoblock aborted by hook.\n" ); 00698 return false; 00699 } 00700 00701 # It's okay to autoblock. Go ahead and insert/update the block... 00702 00703 # Do not add a *new* block if the IP is already blocked. 00704 $ipblock = Block::newFromTarget( $autoblockIP ); 00705 if ( $ipblock ) { 00706 # Check if the block is an autoblock and would exceed the user block 00707 # if renewed. If so, do nothing, otherwise prolong the block time... 00708 if ( $ipblock->mAuto && // @todo Why not compare $ipblock->mExpiry? 00709 $this->mExpiry > Block::getAutoblockExpiry( $ipblock->mTimestamp ) 00710 ) { 00711 # Reset block timestamp to now and its expiry to 00712 # $wgAutoblockExpiry in the future 00713 $ipblock->updateTimestamp(); 00714 } 00715 return false; 00716 } 00717 00718 # Make a new block object with the desired properties. 00719 $autoblock = new Block; 00720 wfDebug( "Autoblocking {$this->getTarget()}@" . $autoblockIP . "\n" ); 00721 $autoblock->setTarget( $autoblockIP ); 00722 $autoblock->setBlocker( $this->getBlocker() ); 00723 $autoblock->mReason = wfMessage( 'autoblocker', $this->getTarget(), $this->mReason ) 00724 ->inContentLanguage()->plain(); 00725 $timestamp = wfTimestampNow(); 00726 $autoblock->mTimestamp = $timestamp; 00727 $autoblock->mAuto = 1; 00728 $autoblock->prevents( 'createaccount', $this->prevents( 'createaccount' ) ); 00729 # Continue suppressing the name if needed 00730 $autoblock->mHideName = $this->mHideName; 00731 $autoblock->prevents( 'editownusertalk', $this->prevents( 'editownusertalk' ) ); 00732 $autoblock->mParentBlockId = $this->mId; 00733 00734 if ( $this->mExpiry == 'infinity' ) { 00735 # Original block was indefinite, start an autoblock now 00736 $autoblock->mExpiry = Block::getAutoblockExpiry( $timestamp ); 00737 } else { 00738 # If the user is already blocked with an expiry date, we don't 00739 # want to pile on top of that. 00740 $autoblock->mExpiry = min( $this->mExpiry, Block::getAutoblockExpiry( $timestamp ) ); 00741 } 00742 00743 # Insert the block... 00744 $status = $autoblock->insert(); 00745 return $status 00746 ? $status['id'] 00747 : false; 00748 } 00749 00754 public function deleteIfExpired() { 00755 wfProfileIn( __METHOD__ ); 00756 00757 if ( $this->isExpired() ) { 00758 wfDebug( "Block::deleteIfExpired() -- deleting\n" ); 00759 $this->delete(); 00760 $retVal = true; 00761 } else { 00762 wfDebug( "Block::deleteIfExpired() -- not expired\n" ); 00763 $retVal = false; 00764 } 00765 00766 wfProfileOut( __METHOD__ ); 00767 return $retVal; 00768 } 00769 00774 public function isExpired() { 00775 $timestamp = wfTimestampNow(); 00776 wfDebug( "Block::isExpired() checking current " . $timestamp . " vs $this->mExpiry\n" ); 00777 00778 if ( !$this->mExpiry ) { 00779 return false; 00780 } else { 00781 return $timestamp > $this->mExpiry; 00782 } 00783 } 00784 00789 public function isValid() { 00790 return $this->getTarget() != null; 00791 } 00792 00796 public function updateTimestamp() { 00797 if ( $this->mAuto ) { 00798 $this->mTimestamp = wfTimestamp(); 00799 $this->mExpiry = Block::getAutoblockExpiry( $this->mTimestamp ); 00800 00801 $dbw = wfGetDB( DB_MASTER ); 00802 $dbw->update( 'ipblocks', 00803 array( /* SET */ 00804 'ipb_timestamp' => $dbw->timestamp( $this->mTimestamp ), 00805 'ipb_expiry' => $dbw->timestamp( $this->mExpiry ), 00806 ), 00807 array( /* WHERE */ 00808 'ipb_address' => (string)$this->getTarget() 00809 ), 00810 __METHOD__ 00811 ); 00812 } 00813 } 00814 00820 public function getRangeStart() { 00821 switch ( $this->type ) { 00822 case self::TYPE_USER: 00823 return ''; 00824 case self::TYPE_IP: 00825 return IP::toHex( $this->target ); 00826 case self::TYPE_RANGE: 00827 list( $start, /*...*/ ) = IP::parseRange( $this->target ); 00828 return $start; 00829 default: 00830 throw new MWException( "Block with invalid type" ); 00831 } 00832 } 00833 00839 public function getRangeEnd() { 00840 switch ( $this->type ) { 00841 case self::TYPE_USER: 00842 return ''; 00843 case self::TYPE_IP: 00844 return IP::toHex( $this->target ); 00845 case self::TYPE_RANGE: 00846 list( /*...*/, $end ) = IP::parseRange( $this->target ); 00847 return $end; 00848 default: 00849 throw new MWException( "Block with invalid type" ); 00850 } 00851 } 00852 00858 public function getBy() { 00859 $blocker = $this->getBlocker(); 00860 return ( $blocker instanceof User ) 00861 ? $blocker->getId() 00862 : 0; 00863 } 00864 00870 public function getByName() { 00871 $blocker = $this->getBlocker(); 00872 return ( $blocker instanceof User ) 00873 ? $blocker->getName() 00874 : (string)$blocker; // username 00875 } 00876 00881 public function getId() { 00882 return $this->mId; 00883 } 00884 00891 public function fromMaster( $x = null ) { 00892 return wfSetVar( $this->mFromMaster, $x ); 00893 } 00894 00900 public function isHardblock( $x = null ) { 00901 wfSetVar( $this->isHardblock, $x ); 00902 00903 # You can't *not* hardblock a user 00904 return $this->getType() == self::TYPE_USER 00905 ? true 00906 : $this->isHardblock; 00907 } 00908 00909 public function isAutoblocking( $x = null ) { 00910 wfSetVar( $this->isAutoblocking, $x ); 00911 00912 # You can't put an autoblock on an IP or range as we don't have any history to 00913 # look over to get more IPs from 00914 return $this->getType() == self::TYPE_USER 00915 ? $this->isAutoblocking 00916 : false; 00917 } 00918 00925 public function prevents( $action, $x = null ) { 00926 switch ( $action ) { 00927 case 'edit': 00928 # For now... <evil laugh> 00929 return true; 00930 00931 case 'createaccount': 00932 return wfSetVar( $this->mCreateAccount, $x ); 00933 00934 case 'sendemail': 00935 return wfSetVar( $this->mBlockEmail, $x ); 00936 00937 case 'editownusertalk': 00938 return wfSetVar( $this->mDisableUsertalk, $x ); 00939 00940 default: 00941 return null; 00942 } 00943 } 00944 00949 public function getRedactedName() { 00950 if ( $this->mAuto ) { 00951 return Html::rawElement( 00952 'span', 00953 array( 'class' => 'mw-autoblockid' ), 00954 wfMessage( 'autoblockid', $this->mId ) 00955 ); 00956 } else { 00957 return htmlspecialchars( $this->getTarget() ); 00958 } 00959 } 00960 00967 public static function getAutoblockExpiry( $timestamp ) { 00968 global $wgAutoblockExpiry; 00969 00970 return wfTimestamp( TS_MW, wfTimestamp( TS_UNIX, $timestamp ) + $wgAutoblockExpiry ); 00971 } 00972 00976 public static function purgeExpired() { 00977 if ( wfReadOnly() ) { 00978 return; 00979 } 00980 00981 $method = __METHOD__; 00982 $dbw = wfGetDB( DB_MASTER ); 00983 $dbw->onTransactionIdle( function () use ( $dbw, $method ) { 00984 $dbw->delete( 'ipblocks', 00985 array( 'ipb_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ), $method ); 00986 } ); 00987 } 00988 01009 public static function newFromTarget( $specificTarget, $vagueTarget = null, $fromMaster = false ) { 01010 01011 list( $target, $type ) = self::parseTarget( $specificTarget ); 01012 if ( $type == Block::TYPE_ID || $type == Block::TYPE_AUTO ) { 01013 return Block::newFromID( $target ); 01014 01015 } elseif ( $target === null && $vagueTarget == '' ) { 01016 # We're not going to find anything useful here 01017 # Be aware that the == '' check is explicit, since empty values will be 01018 # passed by some callers (bug 29116) 01019 return null; 01020 01021 } elseif ( in_array( 01022 $type, 01023 array( Block::TYPE_USER, Block::TYPE_IP, Block::TYPE_RANGE, null ) ) 01024 ) { 01025 $block = new Block(); 01026 $block->fromMaster( $fromMaster ); 01027 01028 if ( $type !== null ) { 01029 $block->setTarget( $target ); 01030 } 01031 01032 if ( $block->newLoad( $vagueTarget ) ) { 01033 return $block; 01034 } 01035 } 01036 return null; 01037 } 01038 01049 public static function getBlocksForIPList( array $ipChain, $isAnon, $fromMaster = false ) { 01050 if ( !count( $ipChain ) ) { 01051 return array(); 01052 } 01053 01054 wfProfileIn( __METHOD__ ); 01055 $conds = array(); 01056 foreach ( array_unique( $ipChain ) as $ipaddr ) { 01057 # Discard invalid IP addresses. Since XFF can be spoofed and we do not 01058 # necessarily trust the header given to us, make sure that we are only 01059 # checking for blocks on well-formatted IP addresses (IPv4 and IPv6). 01060 # Do not treat private IP spaces as special as it may be desirable for wikis 01061 # to block those IP ranges in order to stop misbehaving proxies that spoof XFF. 01062 if ( !IP::isValid( $ipaddr ) ) { 01063 continue; 01064 } 01065 # Don't check trusted IPs (includes local squids which will be in every request) 01066 if ( IP::isTrustedProxy( $ipaddr ) ) { 01067 continue; 01068 } 01069 # Check both the original IP (to check against single blocks), as well as build 01070 # the clause to check for rangeblocks for the given IP. 01071 $conds['ipb_address'][] = $ipaddr; 01072 $conds[] = self::getRangeCond( IP::toHex( $ipaddr ) ); 01073 } 01074 01075 if ( !count( $conds ) ) { 01076 wfProfileOut( __METHOD__ ); 01077 return array(); 01078 } 01079 01080 if ( $fromMaster ) { 01081 $db = wfGetDB( DB_MASTER ); 01082 } else { 01083 $db = wfGetDB( DB_SLAVE ); 01084 } 01085 $conds = $db->makeList( $conds, LIST_OR ); 01086 if ( !$isAnon ) { 01087 $conds = array( $conds, 'ipb_anon_only' => 0 ); 01088 } 01089 $selectFields = array_merge( 01090 array( 'ipb_range_start', 'ipb_range_end' ), 01091 Block::selectFields() 01092 ); 01093 $rows = $db->select( 'ipblocks', 01094 $selectFields, 01095 $conds, 01096 __METHOD__ 01097 ); 01098 01099 $blocks = array(); 01100 foreach ( $rows as $row ) { 01101 $block = self::newFromRow( $row ); 01102 if ( !$block->deleteIfExpired() ) { 01103 $blocks[] = $block; 01104 } 01105 } 01106 01107 wfProfileOut( __METHOD__ ); 01108 return $blocks; 01109 } 01110 01128 public static function chooseBlock( array $blocks, array $ipChain ) { 01129 if ( !count( $blocks ) ) { 01130 return null; 01131 } elseif ( count( $blocks ) == 1 ) { 01132 return $blocks[0]; 01133 } 01134 01135 wfProfileIn( __METHOD__ ); 01136 01137 // Sort hard blocks before soft ones and secondarily sort blocks 01138 // that disable account creation before those that don't. 01139 usort( $blocks, function ( Block $a, Block $b ) { 01140 $aWeight = (int)$a->isHardblock() . (int)$a->prevents( 'createaccount' ); 01141 $bWeight = (int)$b->isHardblock() . (int)$b->prevents( 'createaccount' ); 01142 return strcmp( $bWeight, $aWeight ); // highest weight first 01143 } ); 01144 01145 $blocksListExact = array( 01146 'hard' => false, 01147 'disable_create' => false, 01148 'other' => false, 01149 'auto' => false 01150 ); 01151 $blocksListRange = array( 01152 'hard' => false, 01153 'disable_create' => false, 01154 'other' => false, 01155 'auto' => false 01156 ); 01157 $ipChain = array_reverse( $ipChain ); 01158 01159 foreach ( $blocks as $block ) { 01160 // Stop searching if we have already have a "better" block. This 01161 // is why the order of the blocks matters 01162 if ( !$block->isHardblock() && $blocksListExact['hard'] ) { 01163 break; 01164 } elseif ( !$block->prevents( 'createaccount' ) && $blocksListExact['disable_create'] ) { 01165 break; 01166 } 01167 01168 foreach ( $ipChain as $checkip ) { 01169 $checkipHex = IP::toHex( $checkip ); 01170 if ( (string)$block->getTarget() === $checkip ) { 01171 if ( $block->isHardblock() ) { 01172 $blocksListExact['hard'] = $blocksListExact['hard'] ?: $block; 01173 } elseif ( $block->prevents( 'createaccount' ) ) { 01174 $blocksListExact['disable_create'] = $blocksListExact['disable_create'] ?: $block; 01175 } elseif ( $block->mAuto ) { 01176 $blocksListExact['auto'] = $blocksListExact['auto'] ?: $block; 01177 } else { 01178 $blocksListExact['other'] = $blocksListExact['other'] ?: $block; 01179 } 01180 // We found closest exact match in the ip list, so go to the next Block 01181 break; 01182 } elseif ( array_filter( $blocksListExact ) == array() 01183 && $block->getRangeStart() <= $checkipHex 01184 && $block->getRangeEnd() >= $checkipHex 01185 ) { 01186 if ( $block->isHardblock() ) { 01187 $blocksListRange['hard'] = $blocksListRange['hard'] ?: $block; 01188 } elseif ( $block->prevents( 'createaccount' ) ) { 01189 $blocksListRange['disable_create'] = $blocksListRange['disable_create'] ?: $block; 01190 } elseif ( $block->mAuto ) { 01191 $blocksListRange['auto'] = $blocksListRange['auto'] ?: $block; 01192 } else { 01193 $blocksListRange['other'] = $blocksListRange['other'] ?: $block; 01194 } 01195 break; 01196 } 01197 } 01198 } 01199 01200 if ( array_filter( $blocksListExact ) == array() ) { 01201 $blocksList = &$blocksListRange; 01202 } else { 01203 $blocksList = &$blocksListExact; 01204 } 01205 01206 $chosenBlock = null; 01207 if ( $blocksList['hard'] ) { 01208 $chosenBlock = $blocksList['hard']; 01209 } elseif ( $blocksList['disable_create'] ) { 01210 $chosenBlock = $blocksList['disable_create']; 01211 } elseif ( $blocksList['other'] ) { 01212 $chosenBlock = $blocksList['other']; 01213 } elseif ( $blocksList['auto'] ) { 01214 $chosenBlock = $blocksList['auto']; 01215 } else { 01216 wfProfileOut( __METHOD__ ); 01217 throw new MWException( "Proxy block found, but couldn't be classified." ); 01218 } 01219 01220 wfProfileOut( __METHOD__ ); 01221 return $chosenBlock; 01222 } 01223 01233 public static function parseTarget( $target ) { 01234 # We may have been through this before 01235 if ( $target instanceof User ) { 01236 if ( IP::isValid( $target->getName() ) ) { 01237 return array( $target, self::TYPE_IP ); 01238 } else { 01239 return array( $target, self::TYPE_USER ); 01240 } 01241 } elseif ( $target === null ) { 01242 return array( null, null ); 01243 } 01244 01245 $target = trim( $target ); 01246 01247 if ( IP::isValid( $target ) ) { 01248 # We can still create a User if it's an IP address, but we need to turn 01249 # off validation checking (which would exclude IP addresses) 01250 return array( 01251 User::newFromName( IP::sanitizeIP( $target ), false ), 01252 Block::TYPE_IP 01253 ); 01254 01255 } elseif ( IP::isValidBlock( $target ) ) { 01256 # Can't create a User from an IP range 01257 return array( IP::sanitizeRange( $target ), Block::TYPE_RANGE ); 01258 } 01259 01260 # Consider the possibility that this is not a username at all 01261 # but actually an old subpage (bug #29797) 01262 if ( strpos( $target, '/' ) !== false ) { 01263 # An old subpage, drill down to the user behind it 01264 $parts = explode( '/', $target ); 01265 $target = $parts[0]; 01266 } 01267 01268 $userObj = User::newFromName( $target ); 01269 if ( $userObj instanceof User ) { 01270 # Note that since numbers are valid usernames, a $target of "12345" will be 01271 # considered a User. If you want to pass a block ID, prepend a hash "#12345", 01272 # since hash characters are not valid in usernames or titles generally. 01273 return array( $userObj, Block::TYPE_USER ); 01274 01275 } elseif ( preg_match( '/^#\d+$/', $target ) ) { 01276 # Autoblock reference in the form "#12345" 01277 return array( substr( $target, 1 ), Block::TYPE_AUTO ); 01278 01279 } else { 01280 # WTF? 01281 return array( null, null ); 01282 } 01283 } 01284 01289 public function getType() { 01290 return $this->mAuto 01291 ? self::TYPE_AUTO 01292 : $this->type; 01293 } 01294 01302 public function getTargetAndType() { 01303 return array( $this->getTarget(), $this->getType() ); 01304 } 01305 01312 public function getTarget() { 01313 return $this->target; 01314 } 01315 01321 public function getExpiry() { 01322 return $this->mExpiry; 01323 } 01324 01329 public function setTarget( $target ) { 01330 list( $this->target, $this->type ) = self::parseTarget( $target ); 01331 } 01332 01337 public function getBlocker() { 01338 return $this->blocker; 01339 } 01340 01345 public function setBlocker( $user ) { 01346 $this->blocker = $user; 01347 } 01348 01356 public function getPermissionsError( IContextSource $context ) { 01357 $blocker = $this->getBlocker(); 01358 if ( $blocker instanceof User ) { // local user 01359 $blockerUserpage = $blocker->getUserPage(); 01360 $link = "[[{$blockerUserpage->getPrefixedText()}|{$blockerUserpage->getText()}]]"; 01361 } else { // foreign user 01362 $link = $blocker; 01363 } 01364 01365 $reason = $this->mReason; 01366 if ( $reason == '' ) { 01367 $reason = $context->msg( 'blockednoreason' )->text(); 01368 } 01369 01370 /* $ip returns who *is* being blocked, $intended contains who was meant to be blocked. 01371 * This could be a username, an IP range, or a single IP. */ 01372 $intended = $this->getTarget(); 01373 01374 $lang = $context->getLanguage(); 01375 return array( 01376 $this->mAuto ? 'autoblockedtext' : 'blockedtext', 01377 $link, 01378 $reason, 01379 $context->getRequest()->getIP(), 01380 $this->getByName(), 01381 $this->getId(), 01382 $lang->formatExpiry( $this->mExpiry ), 01383 (string)$intended, 01384 $lang->userTimeAndDate( $this->mTimestamp, $context->getUser() ), 01385 ); 01386 } 01387 }