MediaWiki  REL1_23
Block.php
Go to the documentation of this file.
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 }