MediaWiki
REL1_23
|
00001 <?php 00022 class Block { 00023 /* public*/ var $mReason, $mTimestamp, $mAuto, $mExpiry, $mHideName; 00024 00025 protected 00026 $mId, 00027 $mFromMaster, 00028 00029 $mBlockEmail, 00030 $mDisableUsertalk, 00031 $mCreateAccount, 00032 $mParentBlockId; 00033 00035 protected $target; 00036 00038 protected $forcedTargetID; 00039 00041 protected $type; 00042 00044 protected $blocker; 00045 00047 protected $isHardblock = true; 00048 00050 protected $isAutoblocking = true; 00051 00052 # TYPE constants 00053 const TYPE_USER = 1; 00054 const TYPE_IP = 2; 00055 const TYPE_RANGE = 3; 00056 const TYPE_AUTO = 4; 00057 const TYPE_ID = 5; 00058 00064 function __construct( $address = '', $user = 0, $by = 0, $reason = '', 00065 $timestamp = 0, $auto = 0, $expiry = '', $anonOnly = 0, $createAccount = 0, $enableAutoblock = 0, 00066 $hideName = 0, $blockEmail = 0, $allowUsertalk = 0, $byText = '' 00067 ) { 00068 if ( $timestamp === 0 ) { 00069 $timestamp = wfTimestampNow(); 00070 } 00071 00072 if ( count( func_get_args() ) > 0 ) { 00073 # Soon... :D 00074 # wfDeprecated( __METHOD__ . " with arguments" ); 00075 } 00076 00077 $this->setTarget( $address ); 00078 if ( $this->target instanceof User && $user ) { 00079 $this->forcedTargetID = $user; // needed for foreign users 00080 } 00081 if ( $by ) { // local user 00082 $this->setBlocker( User::newFromID( $by ) ); 00083 } else { // foreign user 00084 $this->setBlocker( $byText ); 00085 } 00086 $this->mReason = $reason; 00087 $this->mTimestamp = wfTimestamp( TS_MW, $timestamp ); 00088 $this->mAuto = $auto; 00089 $this->isHardblock( !$anonOnly ); 00090 $this->prevents( 'createaccount', $createAccount ); 00091 if ( $expiry == 'infinity' || $expiry == wfGetDB( DB_SLAVE )->getInfinity() ) { 00092 $this->mExpiry = 'infinity'; 00093 } else { 00094 $this->mExpiry = wfTimestamp( TS_MW, $expiry ); 00095 } 00096 $this->isAutoblocking( $enableAutoblock ); 00097 $this->mHideName = $hideName; 00098 $this->prevents( 'sendemail', $blockEmail ); 00099 $this->prevents( 'editownusertalk', !$allowUsertalk ); 00100 00101 $this->mFromMaster = false; 00102 } 00103 00110 public static function newFromID( $id ) { 00111 $dbr = wfGetDB( DB_SLAVE ); 00112 $res = $dbr->selectRow( 00113 'ipblocks', 00114 self::selectFields(), 00115 array( 'ipb_id' => $id ), 00116 __METHOD__ 00117 ); 00118 if ( $res ) { 00119 return self::newFromRow( $res ); 00120 } else { 00121 return null; 00122 } 00123 } 00124 00130 public static function selectFields() { 00131 return array( 00132 'ipb_id', 00133 'ipb_address', 00134 'ipb_by', 00135 'ipb_by_text', 00136 'ipb_reason', 00137 'ipb_timestamp', 00138 'ipb_auto', 00139 'ipb_anon_only', 00140 'ipb_create_account', 00141 'ipb_enable_autoblock', 00142 'ipb_expiry', 00143 'ipb_deleted', 00144 'ipb_block_email', 00145 'ipb_allow_usertalk', 00146 'ipb_parent_block_id', 00147 ); 00148 } 00149 00158 public function equals( Block $block ) { 00159 return ( 00160 (string)$this->target == (string)$block->target 00161 && $this->type == $block->type 00162 && $this->mAuto == $block->mAuto 00163 && $this->isHardblock() == $block->isHardblock() 00164 && $this->prevents( 'createaccount' ) == $block->prevents( 'createaccount' ) 00165 && $this->mExpiry == $block->mExpiry 00166 && $this->isAutoblocking() == $block->isAutoblocking() 00167 && $this->mHideName == $block->mHideName 00168 && $this->prevents( 'sendemail' ) == $block->prevents( 'sendemail' ) 00169 && $this->prevents( 'editownusertalk' ) == $block->prevents( 'editownusertalk' ) 00170 && $this->mReason == $block->mReason 00171 ); 00172 } 00173 00184 protected function newLoad( $vagueTarget = null ) { 00185 $db = wfGetDB( $this->mFromMaster ? DB_MASTER : DB_SLAVE ); 00186 00187 if ( $this->type !== null ) { 00188 $conds = array( 00189 'ipb_address' => array( (string)$this->target ), 00190 ); 00191 } else { 00192 $conds = array( 'ipb_address' => array() ); 00193 } 00194 00195 # Be aware that the != '' check is explicit, since empty values will be 00196 # passed by some callers (bug 29116) 00197 if ( $vagueTarget != '' ) { 00198 list( $target, $type ) = self::parseTarget( $vagueTarget ); 00199 switch ( $type ) { 00200 case self::TYPE_USER: 00201 # Slightly weird, but who are we to argue? 00202 $conds['ipb_address'][] = (string)$target; 00203 break; 00204 00205 case self::TYPE_IP: 00206 $conds['ipb_address'][] = (string)$target; 00207 $conds[] = self::getRangeCond( IP::toHex( $target ) ); 00208 $conds = $db->makeList( $conds, LIST_OR ); 00209 break; 00210 00211 case self::TYPE_RANGE: 00212 list( $start, $end ) = IP::parseRange( $target ); 00213 $conds['ipb_address'][] = (string)$target; 00214 $conds[] = self::getRangeCond( $start, $end ); 00215 $conds = $db->makeList( $conds, LIST_OR ); 00216 break; 00217 00218 default: 00219 throw new MWException( "Tried to load block with invalid type" ); 00220 } 00221 } 00222 00223 $res = $db->select( 'ipblocks', self::selectFields(), $conds, __METHOD__ ); 00224 00225 # This result could contain a block on the user, a block on the IP, and a russian-doll 00226 # set of rangeblocks. We want to choose the most specific one, so keep a leader board. 00227 $bestRow = null; 00228 00229 # Lower will be better 00230 $bestBlockScore = 100; 00231 00232 # This is begging for $this = $bestBlock, but that's not allowed in PHP :( 00233 $bestBlockPreventsEdit = null; 00234 00235 foreach ( $res as $row ) { 00236 $block = self::newFromRow( $row ); 00237 00238 # Don't use expired blocks 00239 if ( $block->deleteIfExpired() ) { 00240 continue; 00241 } 00242 00243 # Don't use anon only blocks on users 00244 if ( $this->type == self::TYPE_USER && !$block->isHardblock() ) { 00245 continue; 00246 } 00247 00248 if ( $block->getType() == self::TYPE_RANGE ) { 00249 # This is the number of bits that are allowed to vary in the block, give 00250 # or take some floating point errors 00251 $end = wfBaseconvert( $block->getRangeEnd(), 16, 10 ); 00252 $start = wfBaseconvert( $block->getRangeStart(), 16, 10 ); 00253 $size = log( $end - $start + 1, 2 ); 00254 00255 # This has the nice property that a /32 block is ranked equally with a 00256 # single-IP block, which is exactly what it is... 00257 $score = self::TYPE_RANGE - 1 + ( $size / 128 ); 00258 00259 } else { 00260 $score = $block->getType(); 00261 } 00262 00263 if ( $score < $bestBlockScore ) { 00264 $bestBlockScore = $score; 00265 $bestRow = $row; 00266 $bestBlockPreventsEdit = $block->prevents( 'edit' ); 00267 } 00268 } 00269 00270 if ( $bestRow !== null ) { 00271 $this->initFromRow( $bestRow ); 00272 $this->prevents( 'edit', $bestBlockPreventsEdit ); 00273 return true; 00274 } else { 00275 return false; 00276 } 00277 } 00278 00285 public static function getRangeCond( $start, $end = null ) { 00286 if ( $end === null ) { 00287 $end = $start; 00288 } 00289 # Per bug 14634, we want to include relevant active rangeblocks; for 00290 # rangeblocks, we want to include larger ranges which enclose the given 00291 # range. We know that all blocks must be smaller than $wgBlockCIDRLimit, 00292 # so we can improve performance by filtering on a LIKE clause 00293 $chunk = self::getIpFragment( $start ); 00294 $dbr = wfGetDB( DB_SLAVE ); 00295 $like = $dbr->buildLike( $chunk, $dbr->anyString() ); 00296 00297 # Fairly hard to make a malicious SQL statement out of hex characters, 00298 # but stranger things have happened... 00299 $safeStart = $dbr->addQuotes( $start ); 00300 $safeEnd = $dbr->addQuotes( $end ); 00301 00302 return $dbr->makeList( 00303 array( 00304 "ipb_range_start $like", 00305 "ipb_range_start <= $safeStart", 00306 "ipb_range_end >= $safeEnd", 00307 ), 00308 LIST_AND 00309 ); 00310 } 00311 00318 protected static function getIpFragment( $hex ) { 00319 global $wgBlockCIDRLimit; 00320 if ( substr( $hex, 0, 3 ) == 'v6-' ) { 00321 return 'v6-' . substr( substr( $hex, 3 ), 0, floor( $wgBlockCIDRLimit['IPv6'] / 4 ) ); 00322 } else { 00323 return substr( $hex, 0, floor( $wgBlockCIDRLimit['IPv4'] / 4 ) ); 00324 } 00325 } 00326 00332 protected function initFromRow( $row ) { 00333 $this->setTarget( $row->ipb_address ); 00334 if ( $row->ipb_by ) { // local user 00335 $this->setBlocker( User::newFromID( $row->ipb_by ) ); 00336 } else { // foreign user 00337 $this->setBlocker( $row->ipb_by_text ); 00338 } 00339 00340 $this->mReason = $row->ipb_reason; 00341 $this->mTimestamp = wfTimestamp( TS_MW, $row->ipb_timestamp ); 00342 $this->mAuto = $row->ipb_auto; 00343 $this->mHideName = $row->ipb_deleted; 00344 $this->mId = $row->ipb_id; 00345 $this->mParentBlockId = $row->ipb_parent_block_id; 00346 00347 // I wish I didn't have to do this 00348 $db = wfGetDB( DB_SLAVE ); 00349 if ( $row->ipb_expiry == $db->getInfinity() ) { 00350 $this->mExpiry = 'infinity'; 00351 } else { 00352 $this->mExpiry = wfTimestamp( TS_MW, $row->ipb_expiry ); 00353 } 00354 00355 $this->isHardblock( !$row->ipb_anon_only ); 00356 $this->isAutoblocking( $row->ipb_enable_autoblock ); 00357 00358 $this->prevents( 'createaccount', $row->ipb_create_account ); 00359 $this->prevents( 'sendemail', $row->ipb_block_email ); 00360 $this->prevents( 'editownusertalk', !$row->ipb_allow_usertalk ); 00361 } 00362 00368 public static function newFromRow( $row ) { 00369 $block = new Block; 00370 $block->initFromRow( $row ); 00371 return $block; 00372 } 00373 00380 public function delete() { 00381 if ( wfReadOnly() ) { 00382 return false; 00383 } 00384 00385 if ( !$this->getId() ) { 00386 throw new MWException( "Block::delete() requires that the mId member be filled\n" ); 00387 } 00388 00389 $dbw = wfGetDB( DB_MASTER ); 00390 $dbw->delete( 'ipblocks', array( 'ipb_parent_block_id' => $this->getId() ), __METHOD__ ); 00391 $dbw->delete( 'ipblocks', array( 'ipb_id' => $this->getId() ), __METHOD__ ); 00392 00393 return $dbw->affectedRows() > 0; 00394 } 00395 00404 public function insert( $dbw = null ) { 00405 wfDebug( "Block::insert; timestamp {$this->mTimestamp}\n" ); 00406 00407 if ( $dbw === null ) { 00408 $dbw = wfGetDB( DB_MASTER ); 00409 } 00410 00411 # Don't collide with expired blocks 00412 Block::purgeExpired(); 00413 00414 $row = $this->getDatabaseArray(); 00415 $row['ipb_id'] = $dbw->nextSequenceValue( "ipblocks_ipb_id_seq" ); 00416 00417 $dbw->insert( 00418 'ipblocks', 00419 $row, 00420 __METHOD__, 00421 array( 'IGNORE' ) 00422 ); 00423 $affected = $dbw->affectedRows(); 00424 $this->mId = $dbw->insertId(); 00425 00426 if ( $affected ) { 00427 $auto_ipd_ids = $this->doRetroactiveAutoblock(); 00428 return array( 'id' => $this->mId, 'autoIds' => $auto_ipd_ids ); 00429 } 00430 00431 return false; 00432 } 00433 00441 public function update() { 00442 wfDebug( "Block::update; timestamp {$this->mTimestamp}\n" ); 00443 $dbw = wfGetDB( DB_MASTER ); 00444 00445 $dbw->startAtomic( __METHOD__ ); 00446 00447 $dbw->update( 00448 'ipblocks', 00449 $this->getDatabaseArray( $dbw ), 00450 array( 'ipb_id' => $this->getId() ), 00451 __METHOD__ 00452 ); 00453 00454 $affected = $dbw->affectedRows(); 00455 00456 if ( $this->isAutoblocking() ) { 00457 // update corresponding autoblock(s) (bug 48813) 00458 $dbw->update( 00459 'ipblocks', 00460 $this->getAutoblockUpdateArray(), 00461 array( 'ipb_parent_block_id' => $this->getId() ), 00462 __METHOD__ 00463 ); 00464 } else { 00465 // autoblock no longer required, delete corresponding autoblock(s) 00466 $dbw->delete( 00467 'ipblocks', 00468 array( 'ipb_parent_block_id' => $this->getId() ), 00469 __METHOD__ 00470 ); 00471 } 00472 00473 $dbw->endAtomic( __METHOD__ ); 00474 00475 if ( $affected ) { 00476 $auto_ipd_ids = $this->doRetroactiveAutoblock(); 00477 return array( 'id' => $this->mId, 'autoIds' => $auto_ipd_ids ); 00478 } 00479 00480 return false; 00481 } 00482 00488 protected function getDatabaseArray( $db = null ) { 00489 if ( !$db ) { 00490 $db = wfGetDB( DB_SLAVE ); 00491 } 00492 $expiry = $db->encodeExpiry( $this->mExpiry ); 00493 00494 if ( $this->forcedTargetID ) { 00495 $uid = $this->forcedTargetID; 00496 } else { 00497 $uid = $this->target instanceof User ? $this->target->getID() : 0; 00498 } 00499 00500 $a = array( 00501 'ipb_address' => (string)$this->target, 00502 'ipb_user' => $uid, 00503 'ipb_by' => $this->getBy(), 00504 'ipb_by_text' => $this->getByName(), 00505 'ipb_reason' => $this->mReason, 00506 'ipb_timestamp' => $db->timestamp( $this->mTimestamp ), 00507 'ipb_auto' => $this->mAuto, 00508 'ipb_anon_only' => !$this->isHardblock(), 00509 'ipb_create_account' => $this->prevents( 'createaccount' ), 00510 'ipb_enable_autoblock' => $this->isAutoblocking(), 00511 'ipb_expiry' => $expiry, 00512 'ipb_range_start' => $this->getRangeStart(), 00513 'ipb_range_end' => $this->getRangeEnd(), 00514 'ipb_deleted' => intval( $this->mHideName ), // typecast required for SQLite 00515 'ipb_block_email' => $this->prevents( 'sendemail' ), 00516 'ipb_allow_usertalk' => !$this->prevents( 'editownusertalk' ), 00517 'ipb_parent_block_id' => $this->mParentBlockId 00518 ); 00519 00520 return $a; 00521 } 00522 00526 protected function getAutoblockUpdateArray() { 00527 return array( 00528 'ipb_by' => $this->getBy(), 00529 'ipb_by_text' => $this->getByName(), 00530 'ipb_reason' => $this->mReason, 00531 'ipb_create_account' => $this->prevents( 'createaccount' ), 00532 'ipb_deleted' => (int)$this->mHideName, // typecast required for SQLite 00533 'ipb_allow_usertalk' => !$this->prevents( 'editownusertalk' ), 00534 ); 00535 } 00536 00543 protected function doRetroactiveAutoblock() { 00544 $blockIds = array(); 00545 # If autoblock is enabled, autoblock the LAST IP(s) used 00546 if ( $this->isAutoblocking() && $this->getType() == self::TYPE_USER ) { 00547 wfDebug( "Doing retroactive autoblocks for " . $this->getTarget() . "\n" ); 00548 00549 $continue = wfRunHooks( 00550 'PerformRetroactiveAutoblock', array( $this, &$blockIds ) ); 00551 00552 if ( $continue ) { 00553 self::defaultRetroactiveAutoblock( $this, $blockIds ); 00554 } 00555 } 00556 return $blockIds; 00557 } 00558 00567 protected static function defaultRetroactiveAutoblock( Block $block, array &$blockIds ) { 00568 global $wgPutIPinRC; 00569 00570 // No IPs are in recentchanges table, so nothing to select 00571 if ( !$wgPutIPinRC ) { 00572 return; 00573 } 00574 00575 $dbr = wfGetDB( DB_SLAVE ); 00576 00577 $options = array( 'ORDER BY' => 'rc_timestamp DESC' ); 00578 $conds = array( 'rc_user_text' => (string)$block->getTarget() ); 00579 00580 // Just the last IP used. 00581 $options['LIMIT'] = 1; 00582 00583 $res = $dbr->select( 'recentchanges', array( 'rc_ip' ), $conds, 00584 __METHOD__, $options ); 00585 00586 if ( !$res->numRows() ) { 00587 # No results, don't autoblock anything 00588 wfDebug( "No IP found to retroactively autoblock\n" ); 00589 } else { 00590 foreach ( $res as $row ) { 00591 if ( $row->rc_ip ) { 00592 $id = $block->doAutoblock( $row->rc_ip ); 00593 if ( $id ) { 00594 $blockIds[] = $id; 00595 } 00596 } 00597 } 00598 } 00599 } 00600 00608 public static function isWhitelistedFromAutoblocks( $ip ) { 00609 global $wgMemc; 00610 00611 // Try to get the autoblock_whitelist from the cache, as it's faster 00612 // than getting the msg raw and explode()'ing it. 00613 $key = wfMemcKey( 'ipb', 'autoblock', 'whitelist' ); 00614 $lines = $wgMemc->get( $key ); 00615 if ( !$lines ) { 00616 $lines = explode( "\n", wfMessage( 'autoblock_whitelist' )->inContentLanguage()->plain() ); 00617 $wgMemc->set( $key, $lines, 3600 * 24 ); 00618 } 00619 00620 wfDebug( "Checking the autoblock whitelist..\n" ); 00621 00622 foreach ( $lines as $line ) { 00623 # List items only 00624 if ( substr( $line, 0, 1 ) !== '*' ) { 00625 continue; 00626 } 00627 00628 $wlEntry = substr( $line, 1 ); 00629 $wlEntry = trim( $wlEntry ); 00630 00631 wfDebug( "Checking $ip against $wlEntry..." ); 00632 00633 # Is the IP in this range? 00634 if ( IP::isInRange( $ip, $wlEntry ) ) { 00635 wfDebug( " IP $ip matches $wlEntry, not autoblocking\n" ); 00636 return true; 00637 } else { 00638 wfDebug( " No match\n" ); 00639 } 00640 } 00641 00642 return false; 00643 } 00644 00651 public function doAutoblock( $autoblockIP ) { 00652 # If autoblocks are disabled, go away. 00653 if ( !$this->isAutoblocking() ) { 00654 return false; 00655 } 00656 00657 # Check for presence on the autoblock whitelist. 00658 if ( self::isWhitelistedFromAutoblocks( $autoblockIP ) ) { 00659 return false; 00660 } 00661 00662 # Allow hooks to cancel the autoblock. 00663 if ( !wfRunHooks( 'AbortAutoblock', array( $autoblockIP, &$this ) ) ) { 00664 wfDebug( "Autoblock aborted by hook.\n" ); 00665 return false; 00666 } 00667 00668 # It's okay to autoblock. Go ahead and insert/update the block... 00669 00670 # Do not add a *new* block if the IP is already blocked. 00671 $ipblock = Block::newFromTarget( $autoblockIP ); 00672 if ( $ipblock ) { 00673 # Check if the block is an autoblock and would exceed the user block 00674 # if renewed. If so, do nothing, otherwise prolong the block time... 00675 if ( $ipblock->mAuto && // @todo Why not compare $ipblock->mExpiry? 00676 $this->mExpiry > Block::getAutoblockExpiry( $ipblock->mTimestamp ) 00677 ) { 00678 # Reset block timestamp to now and its expiry to 00679 # $wgAutoblockExpiry in the future 00680 $ipblock->updateTimestamp(); 00681 } 00682 return false; 00683 } 00684 00685 # Make a new block object with the desired properties. 00686 $autoblock = new Block; 00687 wfDebug( "Autoblocking {$this->getTarget()}@" . $autoblockIP . "\n" ); 00688 $autoblock->setTarget( $autoblockIP ); 00689 $autoblock->setBlocker( $this->getBlocker() ); 00690 $autoblock->mReason = wfMessage( 'autoblocker', $this->getTarget(), $this->mReason ) 00691 ->inContentLanguage()->plain(); 00692 $timestamp = wfTimestampNow(); 00693 $autoblock->mTimestamp = $timestamp; 00694 $autoblock->mAuto = 1; 00695 $autoblock->prevents( 'createaccount', $this->prevents( 'createaccount' ) ); 00696 # Continue suppressing the name if needed 00697 $autoblock->mHideName = $this->mHideName; 00698 $autoblock->prevents( 'editownusertalk', $this->prevents( 'editownusertalk' ) ); 00699 $autoblock->mParentBlockId = $this->mId; 00700 00701 if ( $this->mExpiry == 'infinity' ) { 00702 # Original block was indefinite, start an autoblock now 00703 $autoblock->mExpiry = Block::getAutoblockExpiry( $timestamp ); 00704 } else { 00705 # If the user is already blocked with an expiry date, we don't 00706 # want to pile on top of that. 00707 $autoblock->mExpiry = min( $this->mExpiry, Block::getAutoblockExpiry( $timestamp ) ); 00708 } 00709 00710 # Insert the block... 00711 $status = $autoblock->insert(); 00712 return $status 00713 ? $status['id'] 00714 : false; 00715 } 00716 00721 public function deleteIfExpired() { 00722 wfProfileIn( __METHOD__ ); 00723 00724 if ( $this->isExpired() ) { 00725 wfDebug( "Block::deleteIfExpired() -- deleting\n" ); 00726 $this->delete(); 00727 $retVal = true; 00728 } else { 00729 wfDebug( "Block::deleteIfExpired() -- not expired\n" ); 00730 $retVal = false; 00731 } 00732 00733 wfProfileOut( __METHOD__ ); 00734 return $retVal; 00735 } 00736 00741 public function isExpired() { 00742 $timestamp = wfTimestampNow(); 00743 wfDebug( "Block::isExpired() checking current " . $timestamp . " vs $this->mExpiry\n" ); 00744 00745 if ( !$this->mExpiry ) { 00746 return false; 00747 } else { 00748 return $timestamp > $this->mExpiry; 00749 } 00750 } 00751 00756 public function isValid() { 00757 return $this->getTarget() != null; 00758 } 00759 00763 public function updateTimestamp() { 00764 if ( $this->mAuto ) { 00765 $this->mTimestamp = wfTimestamp(); 00766 $this->mExpiry = Block::getAutoblockExpiry( $this->mTimestamp ); 00767 00768 $dbw = wfGetDB( DB_MASTER ); 00769 $dbw->update( 'ipblocks', 00770 array( /* SET */ 00771 'ipb_timestamp' => $dbw->timestamp( $this->mTimestamp ), 00772 'ipb_expiry' => $dbw->timestamp( $this->mExpiry ), 00773 ), 00774 array( /* WHERE */ 00775 'ipb_address' => (string)$this->getTarget() 00776 ), 00777 __METHOD__ 00778 ); 00779 } 00780 } 00781 00787 public function getRangeStart() { 00788 switch ( $this->type ) { 00789 case self::TYPE_USER: 00790 return ''; 00791 case self::TYPE_IP: 00792 return IP::toHex( $this->target ); 00793 case self::TYPE_RANGE: 00794 list( $start, /*...*/ ) = IP::parseRange( $this->target ); 00795 return $start; 00796 default: 00797 throw new MWException( "Block with invalid type" ); 00798 } 00799 } 00800 00806 public function getRangeEnd() { 00807 switch ( $this->type ) { 00808 case self::TYPE_USER: 00809 return ''; 00810 case self::TYPE_IP: 00811 return IP::toHex( $this->target ); 00812 case self::TYPE_RANGE: 00813 list( /*...*/, $end ) = IP::parseRange( $this->target ); 00814 return $end; 00815 default: 00816 throw new MWException( "Block with invalid type" ); 00817 } 00818 } 00819 00825 public function getBy() { 00826 $blocker = $this->getBlocker(); 00827 return ( $blocker instanceof User ) 00828 ? $blocker->getId() 00829 : 0; 00830 } 00831 00837 public function getByName() { 00838 $blocker = $this->getBlocker(); 00839 return ( $blocker instanceof User ) 00840 ? $blocker->getName() 00841 : (string)$blocker; // username 00842 } 00843 00848 public function getId() { 00849 return $this->mId; 00850 } 00851 00858 public function fromMaster( $x = null ) { 00859 return wfSetVar( $this->mFromMaster, $x ); 00860 } 00861 00867 public function isHardblock( $x = null ) { 00868 wfSetVar( $this->isHardblock, $x ); 00869 00870 # You can't *not* hardblock a user 00871 return $this->getType() == self::TYPE_USER 00872 ? true 00873 : $this->isHardblock; 00874 } 00875 00876 public function isAutoblocking( $x = null ) { 00877 wfSetVar( $this->isAutoblocking, $x ); 00878 00879 # You can't put an autoblock on an IP or range as we don't have any history to 00880 # look over to get more IPs from 00881 return $this->getType() == self::TYPE_USER 00882 ? $this->isAutoblocking 00883 : false; 00884 } 00885 00892 public function prevents( $action, $x = null ) { 00893 switch ( $action ) { 00894 case 'edit': 00895 # For now... <evil laugh> 00896 return true; 00897 00898 case 'createaccount': 00899 return wfSetVar( $this->mCreateAccount, $x ); 00900 00901 case 'sendemail': 00902 return wfSetVar( $this->mBlockEmail, $x ); 00903 00904 case 'editownusertalk': 00905 return wfSetVar( $this->mDisableUsertalk, $x ); 00906 00907 default: 00908 return null; 00909 } 00910 } 00911 00916 public function getRedactedName() { 00917 if ( $this->mAuto ) { 00918 return Html::rawElement( 00919 'span', 00920 array( 'class' => 'mw-autoblockid' ), 00921 wfMessage( 'autoblockid', $this->mId ) 00922 ); 00923 } else { 00924 return htmlspecialchars( $this->getTarget() ); 00925 } 00926 } 00927 00934 public static function getAutoblockExpiry( $timestamp ) { 00935 global $wgAutoblockExpiry; 00936 00937 return wfTimestamp( TS_MW, wfTimestamp( TS_UNIX, $timestamp ) + $wgAutoblockExpiry ); 00938 } 00939 00943 public static function purgeExpired() { 00944 if ( wfReadOnly() ) { 00945 return; 00946 } 00947 00948 $method = __METHOD__; 00949 $dbw = wfGetDB( DB_MASTER ); 00950 $dbw->onTransactionIdle( function() use ( $dbw, $method ) { 00951 $dbw->delete( 'ipblocks', 00952 array( 'ipb_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ), $method ); 00953 } ); 00954 } 00955 00976 public static function newFromTarget( $specificTarget, $vagueTarget = null, $fromMaster = false ) { 00977 00978 list( $target, $type ) = self::parseTarget( $specificTarget ); 00979 if ( $type == Block::TYPE_ID || $type == Block::TYPE_AUTO ) { 00980 return Block::newFromID( $target ); 00981 00982 } elseif ( $target === null && $vagueTarget == '' ) { 00983 # We're not going to find anything useful here 00984 # Be aware that the == '' check is explicit, since empty values will be 00985 # passed by some callers (bug 29116) 00986 return null; 00987 00988 } elseif ( in_array( 00989 $type, 00990 array( Block::TYPE_USER, Block::TYPE_IP, Block::TYPE_RANGE, null ) ) 00991 ) { 00992 $block = new Block(); 00993 $block->fromMaster( $fromMaster ); 00994 00995 if ( $type !== null ) { 00996 $block->setTarget( $target ); 00997 } 00998 00999 if ( $block->newLoad( $vagueTarget ) ) { 01000 return $block; 01001 } 01002 } 01003 return null; 01004 } 01005 01016 public static function getBlocksForIPList( array $ipChain, $isAnon, $fromMaster = false ) { 01017 if ( !count( $ipChain ) ) { 01018 return array(); 01019 } 01020 01021 wfProfileIn( __METHOD__ ); 01022 $conds = array(); 01023 foreach ( array_unique( $ipChain ) as $ipaddr ) { 01024 # Discard invalid IP addresses. Since XFF can be spoofed and we do not 01025 # necessarily trust the header given to us, make sure that we are only 01026 # checking for blocks on well-formatted IP addresses (IPv4 and IPv6). 01027 # Do not treat private IP spaces as special as it may be desirable for wikis 01028 # to block those IP ranges in order to stop misbehaving proxies that spoof XFF. 01029 if ( !IP::isValid( $ipaddr ) ) { 01030 continue; 01031 } 01032 # Don't check trusted IPs (includes local squids which will be in every request) 01033 if ( wfIsTrustedProxy( $ipaddr ) ) { 01034 continue; 01035 } 01036 # Check both the original IP (to check against single blocks), as well as build 01037 # the clause to check for rangeblocks for the given IP. 01038 $conds['ipb_address'][] = $ipaddr; 01039 $conds[] = self::getRangeCond( IP::toHex( $ipaddr ) ); 01040 } 01041 01042 if ( !count( $conds ) ) { 01043 wfProfileOut( __METHOD__ ); 01044 return array(); 01045 } 01046 01047 if ( $fromMaster ) { 01048 $db = wfGetDB( DB_MASTER ); 01049 } else { 01050 $db = wfGetDB( DB_SLAVE ); 01051 } 01052 $conds = $db->makeList( $conds, LIST_OR ); 01053 if ( !$isAnon ) { 01054 $conds = array( $conds, 'ipb_anon_only' => 0 ); 01055 } 01056 $selectFields = array_merge( 01057 array( 'ipb_range_start', 'ipb_range_end' ), 01058 Block::selectFields() 01059 ); 01060 $rows = $db->select( 'ipblocks', 01061 $selectFields, 01062 $conds, 01063 __METHOD__ 01064 ); 01065 01066 $blocks = array(); 01067 foreach ( $rows as $row ) { 01068 $block = self::newFromRow( $row ); 01069 if ( !$block->deleteIfExpired() ) { 01070 $blocks[] = $block; 01071 } 01072 } 01073 01074 wfProfileOut( __METHOD__ ); 01075 return $blocks; 01076 } 01077 01095 public static function chooseBlock( array $blocks, array $ipChain ) { 01096 if ( !count( $blocks ) ) { 01097 return null; 01098 } elseif ( count( $blocks ) == 1 ) { 01099 return $blocks[0]; 01100 } 01101 01102 wfProfileIn( __METHOD__ ); 01103 01104 // Sort hard blocks before soft ones and secondarily sort blocks 01105 // that disable account creation before those that don't. 01106 usort( $blocks, function( Block $a, Block $b ) { 01107 $aWeight = (int)$a->isHardblock() . (int)$a->prevents( 'createaccount' ); 01108 $bWeight = (int)$b->isHardblock() . (int)$b->prevents( 'createaccount' ); 01109 return strcmp( $bWeight, $aWeight ); // highest weight first 01110 } ); 01111 01112 $blocksListExact = array( 01113 'hard' => false, 01114 'disable_create' => false, 01115 'other' => false, 01116 'auto' => false 01117 ); 01118 $blocksListRange = array( 01119 'hard' => false, 01120 'disable_create' => false, 01121 'other' => false, 01122 'auto' => false 01123 ); 01124 $ipChain = array_reverse( $ipChain ); 01125 01126 foreach ( $blocks as $block ) { 01127 // Stop searching if we have already have a "better" block. This 01128 // is why the order of the blocks matters 01129 if ( !$block->isHardblock() && $blocksListExact['hard'] ) { 01130 break; 01131 } elseif ( !$block->prevents( 'createaccount' ) && $blocksListExact['disable_create'] ) { 01132 break; 01133 } 01134 01135 foreach ( $ipChain as $checkip ) { 01136 $checkipHex = IP::toHex( $checkip ); 01137 if ( (string)$block->getTarget() === $checkip ) { 01138 if ( $block->isHardblock() ) { 01139 $blocksListExact['hard'] = $blocksListExact['hard'] ?: $block; 01140 } elseif ( $block->prevents( 'createaccount' ) ) { 01141 $blocksListExact['disable_create'] = $blocksListExact['disable_create'] ?: $block; 01142 } elseif ( $block->mAuto ) { 01143 $blocksListExact['auto'] = $blocksListExact['auto'] ?: $block; 01144 } else { 01145 $blocksListExact['other'] = $blocksListExact['other'] ?: $block; 01146 } 01147 // We found closest exact match in the ip list, so go to the next Block 01148 break; 01149 } elseif ( array_filter( $blocksListExact ) == array() 01150 && $block->getRangeStart() <= $checkipHex 01151 && $block->getRangeEnd() >= $checkipHex 01152 ) { 01153 if ( $block->isHardblock() ) { 01154 $blocksListRange['hard'] = $blocksListRange['hard'] ?: $block; 01155 } elseif ( $block->prevents( 'createaccount' ) ) { 01156 $blocksListRange['disable_create'] = $blocksListRange['disable_create'] ?: $block; 01157 } elseif ( $block->mAuto ) { 01158 $blocksListRange['auto'] = $blocksListRange['auto'] ?: $block; 01159 } else { 01160 $blocksListRange['other'] = $blocksListRange['other'] ?: $block; 01161 } 01162 break; 01163 } 01164 } 01165 } 01166 01167 if ( array_filter( $blocksListExact ) == array() ) { 01168 $blocksList = &$blocksListRange; 01169 } else { 01170 $blocksList = &$blocksListExact; 01171 } 01172 01173 $chosenBlock = null; 01174 if ( $blocksList['hard'] ) { 01175 $chosenBlock = $blocksList['hard']; 01176 } elseif ( $blocksList['disable_create'] ) { 01177 $chosenBlock = $blocksList['disable_create']; 01178 } elseif ( $blocksList['other'] ) { 01179 $chosenBlock = $blocksList['other']; 01180 } elseif ( $blocksList['auto'] ) { 01181 $chosenBlock = $blocksList['auto']; 01182 } else { 01183 wfProfileOut( __METHOD__ ); 01184 throw new MWException( "Proxy block found, but couldn't be classified." ); 01185 } 01186 01187 wfProfileOut( __METHOD__ ); 01188 return $chosenBlock; 01189 } 01190 01200 public static function parseTarget( $target ) { 01201 # We may have been through this before 01202 if ( $target instanceof User ) { 01203 if ( IP::isValid( $target->getName() ) ) { 01204 return array( $target, self::TYPE_IP ); 01205 } else { 01206 return array( $target, self::TYPE_USER ); 01207 } 01208 } elseif ( $target === null ) { 01209 return array( null, null ); 01210 } 01211 01212 $target = trim( $target ); 01213 01214 if ( IP::isValid( $target ) ) { 01215 # We can still create a User if it's an IP address, but we need to turn 01216 # off validation checking (which would exclude IP addresses) 01217 return array( 01218 User::newFromName( IP::sanitizeIP( $target ), false ), 01219 Block::TYPE_IP 01220 ); 01221 01222 } elseif ( IP::isValidBlock( $target ) ) { 01223 # Can't create a User from an IP range 01224 return array( IP::sanitizeRange( $target ), Block::TYPE_RANGE ); 01225 } 01226 01227 # Consider the possibility that this is not a username at all 01228 # but actually an old subpage (bug #29797) 01229 if ( strpos( $target, '/' ) !== false ) { 01230 # An old subpage, drill down to the user behind it 01231 $parts = explode( '/', $target ); 01232 $target = $parts[0]; 01233 } 01234 01235 $userObj = User::newFromName( $target ); 01236 if ( $userObj instanceof User ) { 01237 # Note that since numbers are valid usernames, a $target of "12345" will be 01238 # considered a User. If you want to pass a block ID, prepend a hash "#12345", 01239 # since hash characters are not valid in usernames or titles generally. 01240 return array( $userObj, Block::TYPE_USER ); 01241 01242 } elseif ( preg_match( '/^#\d+$/', $target ) ) { 01243 # Autoblock reference in the form "#12345" 01244 return array( substr( $target, 1 ), Block::TYPE_AUTO ); 01245 01246 } else { 01247 # WTF? 01248 return array( null, null ); 01249 } 01250 } 01251 01256 public function getType() { 01257 return $this->mAuto 01258 ? self::TYPE_AUTO 01259 : $this->type; 01260 } 01261 01269 public function getTargetAndType() { 01270 return array( $this->getTarget(), $this->getType() ); 01271 } 01272 01279 public function getTarget() { 01280 return $this->target; 01281 } 01282 01288 public function getExpiry() { 01289 return $this->mExpiry; 01290 } 01291 01296 public function setTarget( $target ) { 01297 list( $this->target, $this->type ) = self::parseTarget( $target ); 01298 } 01299 01304 public function getBlocker() { 01305 return $this->blocker; 01306 } 01307 01312 public function setBlocker( $user ) { 01313 $this->blocker = $user; 01314 } 01315 01323 public function getPermissionsError( IContextSource $context ) { 01324 $blocker = $this->getBlocker(); 01325 if ( $blocker instanceof User ) { // local user 01326 $blockerUserpage = $blocker->getUserPage(); 01327 $link = "[[{$blockerUserpage->getPrefixedText()}|{$blockerUserpage->getText()}]]"; 01328 } else { // foreign user 01329 $link = $blocker; 01330 } 01331 01332 $reason = $this->mReason; 01333 if ( $reason == '' ) { 01334 $reason = $context->msg( 'blockednoreason' )->text(); 01335 } 01336 01337 /* $ip returns who *is* being blocked, $intended contains who was meant to be blocked. 01338 * This could be a username, an IP range, or a single IP. */ 01339 $intended = $this->getTarget(); 01340 01341 $lang = $context->getLanguage(); 01342 return array( 01343 $this->mAuto ? 'autoblockedtext' : 'blockedtext', 01344 $link, 01345 $reason, 01346 $context->getRequest()->getIP(), 01347 $this->getByName(), 01348 $this->getId(), 01349 $lang->formatExpiry( $this->mExpiry ), 01350 (string)$intended, 01351 $lang->timeanddate( wfTimestamp( TS_MW, $this->mTimestamp ), true ), 01352 ); 01353 } 01354 }