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