MediaWiki  REL1_22
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 
00037     // @var Integer Hack for foreign blocking (CentralAuth)
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 
00114     public static function newFromDB( $address, $user = 0 ) {
00115         wfDeprecated( __METHOD__, '1.18' );
00116         return self::newFromTarget( User::whoIs( $user ), $address );
00117     }
00118 
00125     public static function newFromID( $id ) {
00126         $dbr = wfGetDB( DB_SLAVE );
00127         $res = $dbr->selectRow(
00128             'ipblocks',
00129             self::selectFields(),
00130             array( 'ipb_id' => $id ),
00131             __METHOD__
00132         );
00133         if ( $res ) {
00134             return self::newFromRow( $res );
00135         } else {
00136             return null;
00137         }
00138     }
00139 
00145     public static function selectFields() {
00146         return array(
00147             'ipb_id',
00148             'ipb_address',
00149             'ipb_by',
00150             'ipb_by_text',
00151             'ipb_reason',
00152             'ipb_timestamp',
00153             'ipb_auto',
00154             'ipb_anon_only',
00155             'ipb_create_account',
00156             'ipb_enable_autoblock',
00157             'ipb_expiry',
00158             'ipb_deleted',
00159             'ipb_block_email',
00160             'ipb_allow_usertalk',
00161             'ipb_parent_block_id',
00162         );
00163     }
00164 
00173     public function equals( Block $block ) {
00174         return (
00175             (string)$this->target == (string)$block->target
00176             && $this->type == $block->type
00177             && $this->mAuto == $block->mAuto
00178             && $this->isHardblock() == $block->isHardblock()
00179             && $this->prevents( 'createaccount' ) == $block->prevents( 'createaccount' )
00180             && $this->mExpiry == $block->mExpiry
00181             && $this->isAutoblocking() == $block->isAutoblocking()
00182             && $this->mHideName == $block->mHideName
00183             && $this->prevents( 'sendemail' ) == $block->prevents( 'sendemail' )
00184             && $this->prevents( 'editownusertalk' ) == $block->prevents( 'editownusertalk' )
00185             && $this->mReason == $block->mReason
00186         );
00187     }
00188 
00194     public function clear() {
00195         wfDeprecated( __METHOD__, '1.18' );
00196         # Noop
00197     }
00198 
00207     public function load( $address = '', $user = 0 ) {
00208         wfDeprecated( __METHOD__, '1.18' );
00209         if ( $user ) {
00210             $username = User::whoIs( $user );
00211             $block = self::newFromTarget( $username, $address );
00212         } else {
00213             $block = self::newFromTarget( null, $address );
00214         }
00215 
00216         if ( $block instanceof Block ) {
00217             # This is mildly evil, but hey, it's B/C :D
00218             foreach ( $block as $variable => $value ) {
00219                 $this->$variable = $value;
00220             }
00221             return true;
00222         } else {
00223             return false;
00224         }
00225     }
00226 
00237     protected function newLoad( $vagueTarget = null ) {
00238         $db = wfGetDB( $this->mFromMaster ? DB_MASTER : DB_SLAVE );
00239 
00240         if ( $this->type !== null ) {
00241             $conds = array(
00242                 'ipb_address' => array( (string)$this->target ),
00243             );
00244         } else {
00245             $conds = array( 'ipb_address' => array() );
00246         }
00247 
00248         # Be aware that the != '' check is explicit, since empty values will be
00249         # passed by some callers (bug 29116)
00250         if ( $vagueTarget != '' ) {
00251             list( $target, $type ) = self::parseTarget( $vagueTarget );
00252             switch ( $type ) {
00253                 case self::TYPE_USER:
00254                     # Slightly weird, but who are we to argue?
00255                     $conds['ipb_address'][] = (string)$target;
00256                     break;
00257 
00258                 case self::TYPE_IP:
00259                     $conds['ipb_address'][] = (string)$target;
00260                     $conds[] = self::getRangeCond( IP::toHex( $target ) );
00261                     $conds = $db->makeList( $conds, LIST_OR );
00262                     break;
00263 
00264                 case self::TYPE_RANGE:
00265                     list( $start, $end ) = IP::parseRange( $target );
00266                     $conds['ipb_address'][] = (string)$target;
00267                     $conds[] = self::getRangeCond( $start, $end );
00268                     $conds = $db->makeList( $conds, LIST_OR );
00269                     break;
00270 
00271                 default:
00272                     throw new MWException( "Tried to load block with invalid type" );
00273             }
00274         }
00275 
00276         $res = $db->select( 'ipblocks', self::selectFields(), $conds, __METHOD__ );
00277 
00278         # This result could contain a block on the user, a block on the IP, and a russian-doll
00279         # set of rangeblocks.  We want to choose the most specific one, so keep a leader board.
00280         $bestRow = null;
00281 
00282         # Lower will be better
00283         $bestBlockScore = 100;
00284 
00285         # This is begging for $this = $bestBlock, but that's not allowed in PHP :(
00286         $bestBlockPreventsEdit = null;
00287 
00288         foreach ( $res as $row ) {
00289             $block = self::newFromRow( $row );
00290 
00291             # Don't use expired blocks
00292             if ( $block->deleteIfExpired() ) {
00293                 continue;
00294             }
00295 
00296             # Don't use anon only blocks on users
00297             if ( $this->type == self::TYPE_USER && !$block->isHardblock() ) {
00298                 continue;
00299             }
00300 
00301             if ( $block->getType() == self::TYPE_RANGE ) {
00302                 # This is the number of bits that are allowed to vary in the block, give
00303                 # or take some floating point errors
00304                 $end = wfBaseconvert( $block->getRangeEnd(), 16, 10 );
00305                 $start = wfBaseconvert( $block->getRangeStart(), 16, 10 );
00306                 $size = log( $end - $start + 1, 2 );
00307 
00308                 # This has the nice property that a /32 block is ranked equally with a
00309                 # single-IP block, which is exactly what it is...
00310                 $score = self::TYPE_RANGE - 1 + ( $size / 128 );
00311 
00312             } else {
00313                 $score = $block->getType();
00314             }
00315 
00316             if ( $score < $bestBlockScore ) {
00317                 $bestBlockScore = $score;
00318                 $bestRow = $row;
00319                 $bestBlockPreventsEdit = $block->prevents( 'edit' );
00320             }
00321         }
00322 
00323         if ( $bestRow !== null ) {
00324             $this->initFromRow( $bestRow );
00325             $this->prevents( 'edit', $bestBlockPreventsEdit );
00326             return true;
00327         } else {
00328             return false;
00329         }
00330     }
00331 
00338     public static function getRangeCond( $start, $end = null ) {
00339         if ( $end === null ) {
00340             $end = $start;
00341         }
00342         # Per bug 14634, we want to include relevant active rangeblocks; for
00343         # rangeblocks, we want to include larger ranges which enclose the given
00344         # range. We know that all blocks must be smaller than $wgBlockCIDRLimit,
00345         # so we can improve performance by filtering on a LIKE clause
00346         $chunk = self::getIpFragment( $start );
00347         $dbr = wfGetDB( DB_SLAVE );
00348         $like = $dbr->buildLike( $chunk, $dbr->anyString() );
00349 
00350         # Fairly hard to make a malicious SQL statement out of hex characters,
00351         # but stranger things have happened...
00352         $safeStart = $dbr->addQuotes( $start );
00353         $safeEnd = $dbr->addQuotes( $end );
00354 
00355         return $dbr->makeList(
00356             array(
00357                 "ipb_range_start $like",
00358                 "ipb_range_start <= $safeStart",
00359                 "ipb_range_end >= $safeEnd",
00360             ),
00361             LIST_AND
00362         );
00363     }
00364 
00371     protected static function getIpFragment( $hex ) {
00372         global $wgBlockCIDRLimit;
00373         if ( substr( $hex, 0, 3 ) == 'v6-' ) {
00374             return 'v6-' . substr( substr( $hex, 3 ), 0, floor( $wgBlockCIDRLimit['IPv6'] / 4 ) );
00375         } else {
00376             return substr( $hex, 0, floor( $wgBlockCIDRLimit['IPv4'] / 4 ) );
00377         }
00378     }
00379 
00385     protected function initFromRow( $row ) {
00386         $this->setTarget( $row->ipb_address );
00387         if ( $row->ipb_by ) { // local user
00388             $this->setBlocker( User::newFromID( $row->ipb_by ) );
00389         } else { // foreign user
00390             $this->setBlocker( $row->ipb_by_text );
00391         }
00392 
00393         $this->mReason = $row->ipb_reason;
00394         $this->mTimestamp = wfTimestamp( TS_MW, $row->ipb_timestamp );
00395         $this->mAuto = $row->ipb_auto;
00396         $this->mHideName = $row->ipb_deleted;
00397         $this->mId = $row->ipb_id;
00398         $this->mParentBlockId = $row->ipb_parent_block_id;
00399 
00400         // I wish I didn't have to do this
00401         $db = wfGetDB( DB_SLAVE );
00402         if ( $row->ipb_expiry == $db->getInfinity() ) {
00403             $this->mExpiry = 'infinity';
00404         } else {
00405             $this->mExpiry = wfTimestamp( TS_MW, $row->ipb_expiry );
00406         }
00407 
00408         $this->isHardblock( !$row->ipb_anon_only );
00409         $this->isAutoblocking( $row->ipb_enable_autoblock );
00410 
00411         $this->prevents( 'createaccount', $row->ipb_create_account );
00412         $this->prevents( 'sendemail', $row->ipb_block_email );
00413         $this->prevents( 'editownusertalk', !$row->ipb_allow_usertalk );
00414     }
00415 
00421     public static function newFromRow( $row ) {
00422         $block = new Block;
00423         $block->initFromRow( $row );
00424         return $block;
00425     }
00426 
00433     public function delete() {
00434         if ( wfReadOnly() ) {
00435             return false;
00436         }
00437 
00438         if ( !$this->getId() ) {
00439             throw new MWException( "Block::delete() requires that the mId member be filled\n" );
00440         }
00441 
00442         $dbw = wfGetDB( DB_MASTER );
00443         $dbw->delete( 'ipblocks', array( 'ipb_parent_block_id' => $this->getId() ), __METHOD__ );
00444         $dbw->delete( 'ipblocks', array( 'ipb_id' => $this->getId() ), __METHOD__ );
00445 
00446         return $dbw->affectedRows() > 0;
00447     }
00448 
00457     public function insert( $dbw = null ) {
00458         wfDebug( "Block::insert; timestamp {$this->mTimestamp}\n" );
00459 
00460         if ( $dbw === null ) {
00461             $dbw = wfGetDB( DB_MASTER );
00462         }
00463 
00464         # Don't collide with expired blocks
00465         Block::purgeExpired();
00466 
00467         $row = $this->getDatabaseArray();
00468         $row['ipb_id'] = $dbw->nextSequenceValue( "ipblocks_ipb_id_seq" );
00469 
00470         $dbw->insert(
00471             'ipblocks',
00472             $row,
00473             __METHOD__,
00474             array( 'IGNORE' )
00475         );
00476         $affected = $dbw->affectedRows();
00477         $this->mId = $dbw->insertId();
00478 
00479         if ( $affected ) {
00480             $auto_ipd_ids = $this->doRetroactiveAutoblock();
00481             return array( 'id' => $this->mId, 'autoIds' => $auto_ipd_ids );
00482         }
00483 
00484         return false;
00485     }
00486 
00494     public function update() {
00495         wfDebug( "Block::update; timestamp {$this->mTimestamp}\n" );
00496         $dbw = wfGetDB( DB_MASTER );
00497 
00498         $dbw->update(
00499             'ipblocks',
00500             $this->getDatabaseArray( $dbw ),
00501             array( 'ipb_id' => $this->getId() ),
00502             __METHOD__
00503         );
00504 
00505         return $dbw->affectedRows();
00506     }
00507 
00513     protected function getDatabaseArray( $db = null ) {
00514         if ( !$db ) {
00515             $db = wfGetDB( DB_SLAVE );
00516         }
00517         $expiry = $db->encodeExpiry( $this->mExpiry );
00518 
00519         if ( $this->forcedTargetID ) {
00520             $uid = $this->forcedTargetID;
00521         } else {
00522             $uid = $this->target instanceof User ? $this->target->getID() : 0;
00523         }
00524 
00525         $a = array(
00526             'ipb_address'          => (string)$this->target,
00527             'ipb_user'             => $uid,
00528             'ipb_by'               => $this->getBy(),
00529             'ipb_by_text'          => $this->getByName(),
00530             'ipb_reason'           => $this->mReason,
00531             'ipb_timestamp'        => $db->timestamp( $this->mTimestamp ),
00532             'ipb_auto'             => $this->mAuto,
00533             'ipb_anon_only'        => !$this->isHardblock(),
00534             'ipb_create_account'   => $this->prevents( 'createaccount' ),
00535             'ipb_enable_autoblock' => $this->isAutoblocking(),
00536             'ipb_expiry'           => $expiry,
00537             'ipb_range_start'      => $this->getRangeStart(),
00538             'ipb_range_end'        => $this->getRangeEnd(),
00539             'ipb_deleted'          => intval( $this->mHideName ), // typecast required for SQLite
00540             'ipb_block_email'      => $this->prevents( 'sendemail' ),
00541             'ipb_allow_usertalk'   => !$this->prevents( 'editownusertalk' ),
00542             'ipb_parent_block_id'  => $this->mParentBlockId
00543         );
00544 
00545         return $a;
00546     }
00547 
00554     protected function doRetroactiveAutoblock() {
00555         $blockIds = array();
00556         # If autoblock is enabled, autoblock the LAST IP(s) used
00557         if ( $this->isAutoblocking() && $this->getType() == self::TYPE_USER ) {
00558             wfDebug( "Doing retroactive autoblocks for " . $this->getTarget() . "\n" );
00559 
00560             $continue = wfRunHooks(
00561                 'PerformRetroactiveAutoblock', array( $this, &$blockIds ) );
00562 
00563             if ( $continue ) {
00564                 self::defaultRetroactiveAutoblock( $this, $blockIds );
00565             }
00566         }
00567         return $blockIds;
00568     }
00569 
00578     protected static function defaultRetroactiveAutoblock( Block $block, array &$blockIds ) {
00579         global $wgPutIPinRC;
00580 
00581         // No IPs are in recentchanges table, so nothing to select
00582         if ( !$wgPutIPinRC ) {
00583             return;
00584         }
00585 
00586         $dbr = wfGetDB( DB_SLAVE );
00587 
00588         $options = array( 'ORDER BY' => 'rc_timestamp DESC' );
00589         $conds = array( 'rc_user_text' => (string)$block->getTarget() );
00590 
00591         // Just the last IP used.
00592         $options['LIMIT'] = 1;
00593 
00594         $res = $dbr->select( 'recentchanges', array( 'rc_ip' ), $conds,
00595             __METHOD__, $options );
00596 
00597         if ( !$res->numRows() ) {
00598             # No results, don't autoblock anything
00599             wfDebug( "No IP found to retroactively autoblock\n" );
00600         } else {
00601             foreach ( $res as $row ) {
00602                 if ( $row->rc_ip ) {
00603                     $id = $block->doAutoblock( $row->rc_ip );
00604                     if ( $id ) {
00605                         $blockIds[] = $id;
00606                     }
00607                 }
00608             }
00609         }
00610     }
00611 
00619     public static function isWhitelistedFromAutoblocks( $ip ) {
00620         global $wgMemc;
00621 
00622         // Try to get the autoblock_whitelist from the cache, as it's faster
00623         // than getting the msg raw and explode()'ing it.
00624         $key = wfMemcKey( 'ipb', 'autoblock', 'whitelist' );
00625         $lines = $wgMemc->get( $key );
00626         if ( !$lines ) {
00627             $lines = explode( "\n", wfMessage( 'autoblock_whitelist' )->inContentLanguage()->plain() );
00628             $wgMemc->set( $key, $lines, 3600 * 24 );
00629         }
00630 
00631         wfDebug( "Checking the autoblock whitelist..\n" );
00632 
00633         foreach ( $lines as $line ) {
00634             # List items only
00635             if ( substr( $line, 0, 1 ) !== '*' ) {
00636                 continue;
00637             }
00638 
00639             $wlEntry = substr( $line, 1 );
00640             $wlEntry = trim( $wlEntry );
00641 
00642             wfDebug( "Checking $ip against $wlEntry..." );
00643 
00644             # Is the IP in this range?
00645             if ( IP::isInRange( $ip, $wlEntry ) ) {
00646                 wfDebug( " IP $ip matches $wlEntry, not autoblocking\n" );
00647                 return true;
00648             } else {
00649                 wfDebug( " No match\n" );
00650             }
00651         }
00652 
00653         return false;
00654     }
00655 
00662     public function doAutoblock( $autoblockIP ) {
00663         # If autoblocks are disabled, go away.
00664         if ( !$this->isAutoblocking() ) {
00665             return false;
00666         }
00667 
00668         # Check for presence on the autoblock whitelist.
00669         if ( self::isWhitelistedFromAutoblocks( $autoblockIP ) ) {
00670             return false;
00671         }
00672 
00673         # Allow hooks to cancel the autoblock.
00674         if ( !wfRunHooks( 'AbortAutoblock', array( $autoblockIP, &$this ) ) ) {
00675             wfDebug( "Autoblock aborted by hook.\n" );
00676             return false;
00677         }
00678 
00679         # It's okay to autoblock. Go ahead and insert/update the block...
00680 
00681         # Do not add a *new* block if the IP is already blocked.
00682         $ipblock = Block::newFromTarget( $autoblockIP );
00683         if ( $ipblock ) {
00684             # Check if the block is an autoblock and would exceed the user block
00685             # if renewed. If so, do nothing, otherwise prolong the block time...
00686             if ( $ipblock->mAuto && // @todo Why not compare $ipblock->mExpiry?
00687                 $this->mExpiry > Block::getAutoblockExpiry( $ipblock->mTimestamp )
00688             ) {
00689                 # Reset block timestamp to now and its expiry to
00690                 # $wgAutoblockExpiry in the future
00691                 $ipblock->updateTimestamp();
00692             }
00693             return false;
00694         }
00695 
00696         # Make a new block object with the desired properties.
00697         $autoblock = new Block;
00698         wfDebug( "Autoblocking {$this->getTarget()}@" . $autoblockIP . "\n" );
00699         $autoblock->setTarget( $autoblockIP );
00700         $autoblock->setBlocker( $this->getBlocker() );
00701         $autoblock->mReason = wfMessage( 'autoblocker', $this->getTarget(), $this->mReason )->inContentLanguage()->plain();
00702         $timestamp = wfTimestampNow();
00703         $autoblock->mTimestamp = $timestamp;
00704         $autoblock->mAuto = 1;
00705         $autoblock->prevents( 'createaccount', $this->prevents( 'createaccount' ) );
00706         # Continue suppressing the name if needed
00707         $autoblock->mHideName = $this->mHideName;
00708         $autoblock->prevents( 'editownusertalk', $this->prevents( 'editownusertalk' ) );
00709         $autoblock->mParentBlockId = $this->mId;
00710 
00711         if ( $this->mExpiry == 'infinity' ) {
00712             # Original block was indefinite, start an autoblock now
00713             $autoblock->mExpiry = Block::getAutoblockExpiry( $timestamp );
00714         } else {
00715             # If the user is already blocked with an expiry date, we don't
00716             # want to pile on top of that.
00717             $autoblock->mExpiry = min( $this->mExpiry, Block::getAutoblockExpiry( $timestamp ) );
00718         }
00719 
00720         # Insert the block...
00721         $status = $autoblock->insert();
00722         return $status
00723             ? $status['id']
00724             : false;
00725     }
00726 
00731     public function deleteIfExpired() {
00732         wfProfileIn( __METHOD__ );
00733 
00734         if ( $this->isExpired() ) {
00735             wfDebug( "Block::deleteIfExpired() -- deleting\n" );
00736             $this->delete();
00737             $retVal = true;
00738         } else {
00739             wfDebug( "Block::deleteIfExpired() -- not expired\n" );
00740             $retVal = false;
00741         }
00742 
00743         wfProfileOut( __METHOD__ );
00744         return $retVal;
00745     }
00746 
00751     public function isExpired() {
00752         $timestamp = wfTimestampNow();
00753         wfDebug( "Block::isExpired() checking current " . $timestamp . " vs $this->mExpiry\n" );
00754 
00755         if ( !$this->mExpiry ) {
00756             return false;
00757         } else {
00758             return $timestamp > $this->mExpiry;
00759         }
00760     }
00761 
00766     public function isValid() {
00767         return $this->getTarget() != null;
00768     }
00769 
00773     public function updateTimestamp() {
00774         if ( $this->mAuto ) {
00775             $this->mTimestamp = wfTimestamp();
00776             $this->mExpiry = Block::getAutoblockExpiry( $this->mTimestamp );
00777 
00778             $dbw = wfGetDB( DB_MASTER );
00779             $dbw->update( 'ipblocks',
00780                 array( /* SET */
00781                     'ipb_timestamp' => $dbw->timestamp( $this->mTimestamp ),
00782                     'ipb_expiry' => $dbw->timestamp( $this->mExpiry ),
00783                 ),
00784                 array( /* WHERE */
00785                     'ipb_address' => (string)$this->getTarget()
00786                 ),
00787                 __METHOD__
00788             );
00789         }
00790     }
00791 
00797     public function getRangeStart() {
00798         switch ( $this->type ) {
00799             case self::TYPE_USER:
00800                 return '';
00801             case self::TYPE_IP:
00802                 return IP::toHex( $this->target );
00803             case self::TYPE_RANGE:
00804                 list( $start, /*...*/ ) = IP::parseRange( $this->target );
00805                 return $start;
00806             default:
00807                 throw new MWException( "Block with invalid type" );
00808         }
00809     }
00810 
00816     public function getRangeEnd() {
00817         switch ( $this->type ) {
00818             case self::TYPE_USER:
00819                 return '';
00820             case self::TYPE_IP:
00821                 return IP::toHex( $this->target );
00822             case self::TYPE_RANGE:
00823                 list( /*...*/, $end ) = IP::parseRange( $this->target );
00824                 return $end;
00825             default:
00826                 throw new MWException( "Block with invalid type" );
00827         }
00828     }
00829 
00835     public function getBy() {
00836         $blocker = $this->getBlocker();
00837         return ( $blocker instanceof User )
00838             ? $blocker->getId()
00839             : 0;
00840     }
00841 
00847     public function getByName() {
00848         $blocker = $this->getBlocker();
00849         return ( $blocker instanceof User )
00850             ? $blocker->getName()
00851             : (string)$blocker; // username
00852     }
00853 
00858     public function getId() {
00859         return $this->mId;
00860     }
00861 
00868     public function forUpdate( $x = null ) {
00869         wfDeprecated( __METHOD__, '1.18' );
00870         # noop
00871     }
00872 
00879     public function fromMaster( $x = null ) {
00880         return wfSetVar( $this->mFromMaster, $x );
00881     }
00882 
00888     public function isHardblock( $x = null ) {
00889         wfSetVar( $this->isHardblock, $x );
00890 
00891         # You can't *not* hardblock a user
00892         return $this->getType() == self::TYPE_USER
00893             ? true
00894             : $this->isHardblock;
00895     }
00896 
00897     public function isAutoblocking( $x = null ) {
00898         wfSetVar( $this->isAutoblocking, $x );
00899 
00900         # You can't put an autoblock on an IP or range as we don't have any history to
00901         # look over to get more IPs from
00902         return $this->getType() == self::TYPE_USER
00903             ? $this->isAutoblocking
00904             : false;
00905     }
00906 
00913     public function prevents( $action, $x = null ) {
00914         switch ( $action ) {
00915             case 'edit':
00916                 # For now... <evil laugh>
00917                 return true;
00918 
00919             case 'createaccount':
00920                 return wfSetVar( $this->mCreateAccount, $x );
00921 
00922             case 'sendemail':
00923                 return wfSetVar( $this->mBlockEmail, $x );
00924 
00925             case 'editownusertalk':
00926                 return wfSetVar( $this->mDisableUsertalk, $x );
00927 
00928             default:
00929                 return null;
00930         }
00931     }
00932 
00937     public function getRedactedName() {
00938         if ( $this->mAuto ) {
00939             return Html::rawElement(
00940                 'span',
00941                 array( 'class' => 'mw-autoblockid' ),
00942                 wfMessage( 'autoblockid', $this->mId )
00943             );
00944         } else {
00945             return htmlspecialchars( $this->getTarget() );
00946         }
00947     }
00948 
00957     public static function encodeExpiry( $expiry, $db ) {
00958         wfDeprecated( __METHOD__, '1.18' );
00959         return $db->encodeExpiry( $expiry );
00960     }
00961 
00970     public static function decodeExpiry( $expiry, $timestampType = TS_MW ) {
00971         wfDeprecated( __METHOD__, '1.18' );
00972         global $wgContLang;
00973         return $wgContLang->formatExpiry( $expiry, $timestampType );
00974     }
00975 
00982     public static function getAutoblockExpiry( $timestamp ) {
00983         global $wgAutoblockExpiry;
00984 
00985         return wfTimestamp( TS_MW, wfTimestamp( TS_UNIX, $timestamp ) + $wgAutoblockExpiry );
00986     }
00987 
00995     public static function normaliseRange( $range ) {
00996         wfDeprecated( __METHOD__, '1.18' );
00997         return IP::sanitizeRange( $range );
00998     }
00999 
01003     public static function purgeExpired() {
01004         if ( wfReadOnly() ) {
01005             return;
01006         }
01007 
01008         $method = __METHOD__;
01009         $dbw = wfGetDB( DB_MASTER );
01010         $dbw->onTransactionIdle( function() use ( $dbw, $method ) {
01011             $dbw->delete( 'ipblocks',
01012                 array( 'ipb_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ), $method );
01013         } );
01014     }
01015 
01022     public static function infinity() {
01023         wfDeprecated( __METHOD__, '1.18' );
01024         return wfGetDB( DB_SLAVE )->getInfinity();
01025     }
01026 
01034     public static function parseExpiryInput( $expiry ) {
01035         wfDeprecated( __METHOD__, '1.18' );
01036         return SpecialBlock::parseExpiryInput( $expiry );
01037     }
01038 
01059     public static function newFromTarget( $specificTarget, $vagueTarget = null, $fromMaster = false ) {
01060 
01061         list( $target, $type ) = self::parseTarget( $specificTarget );
01062         if ( $type == Block::TYPE_ID || $type == Block::TYPE_AUTO ) {
01063             return Block::newFromID( $target );
01064 
01065         } elseif ( $target === null && $vagueTarget == '' ) {
01066             # We're not going to find anything useful here
01067             # Be aware that the == '' check is explicit, since empty values will be
01068             # passed by some callers (bug 29116)
01069             return null;
01070 
01071         } elseif ( in_array( $type, array( Block::TYPE_USER, Block::TYPE_IP, Block::TYPE_RANGE, null ) ) ) {
01072             $block = new Block();
01073             $block->fromMaster( $fromMaster );
01074 
01075             if ( $type !== null ) {
01076                 $block->setTarget( $target );
01077             }
01078 
01079             if ( $block->newLoad( $vagueTarget ) ) {
01080                 return $block;
01081             }
01082         }
01083         return null;
01084     }
01085 
01086 
01097     public static function getBlocksForIPList( array $ipChain, $isAnon, $fromMaster = false ) {
01098         if ( !count( $ipChain ) ) {
01099             return array();
01100         }
01101 
01102         wfProfileIn( __METHOD__ );
01103         $conds = array();
01104         foreach ( array_unique( $ipChain ) as $ipaddr ) {
01105             # Discard invalid IP addresses. Since XFF can be spoofed and we do not
01106             # necessarily trust the header given to us, make sure that we are only
01107             # checking for blocks on well-formatted IP addresses (IPv4 and IPv6).
01108             # Do not treat private IP spaces as special as it may be desirable for wikis
01109             # to block those IP ranges in order to stop misbehaving proxies that spoof XFF.
01110             if ( !IP::isValid( $ipaddr ) ) {
01111                 continue;
01112             }
01113             # Don't check trusted IPs (includes local squids which will be in every request)
01114             if ( wfIsTrustedProxy( $ipaddr ) ) {
01115                 continue;
01116             }
01117             # Check both the original IP (to check against single blocks), as well as build
01118             # the clause to check for rangeblocks for the given IP.
01119             $conds['ipb_address'][] = $ipaddr;
01120             $conds[] = self::getRangeCond( IP::toHex( $ipaddr ) );
01121         }
01122 
01123         if ( !count( $conds ) ) {
01124             wfProfileOut( __METHOD__ );
01125             return array();
01126         }
01127 
01128         if ( $fromMaster ) {
01129             $db = wfGetDB( DB_MASTER );
01130         } else {
01131             $db = wfGetDB( DB_SLAVE );
01132         }
01133         $conds = $db->makeList( $conds, LIST_OR );
01134         if ( !$isAnon ) {
01135             $conds = array( $conds, 'ipb_anon_only' => 0 );
01136         }
01137         $selectFields = array_merge(
01138             array( 'ipb_range_start', 'ipb_range_end' ),
01139             Block::selectFields()
01140         );
01141         $rows = $db->select( 'ipblocks',
01142             $selectFields,
01143             $conds,
01144             __METHOD__
01145         );
01146 
01147         $blocks = array();
01148         foreach ( $rows as $row ) {
01149             $block = self::newFromRow( $row );
01150             if ( !$block->deleteIfExpired()  ) {
01151                 $blocks[] = $block;
01152             }
01153         }
01154 
01155         wfProfileOut( __METHOD__ );
01156         return $blocks;
01157     }
01158 
01176     public static function chooseBlock( array $blocks, array $ipChain ) {
01177         if ( !count( $blocks ) ) {
01178             return null;
01179         } elseif ( count( $blocks ) == 1 ) {
01180             return $blocks[0];
01181         }
01182 
01183         wfProfileIn( __METHOD__ );
01184 
01185         // Sort hard blocks before soft ones and secondarily sort blocks
01186         // that disable account creation before those that don't.
01187         usort( $blocks, function( Block $a, Block $b ) {
01188             $aWeight = (int)$a->isHardblock() . (int)$a->prevents( 'createaccount' );
01189             $bWeight = (int)$b->isHardblock() . (int)$b->prevents( 'createaccount' );
01190             return strcmp( $bWeight, $aWeight ); // highest weight first
01191         } );
01192 
01193         $blocksListExact = array(
01194             'hard' => false,
01195             'disable_create' => false,
01196             'other' => false,
01197             'auto' => false
01198         );
01199         $blocksListRange = array(
01200             'hard' => false,
01201             'disable_create' => false,
01202             'other' => false,
01203             'auto' => false
01204         );
01205         $ipChain = array_reverse( $ipChain );
01206 
01207         foreach ( $blocks as $block ) {
01208             // Stop searching if we have already have a "better" block. This
01209             // is why the order of the blocks matters
01210             if ( !$block->isHardblock() && $blocksListExact['hard'] ) {
01211                 break;
01212             } elseif ( !$block->prevents( 'createaccount' ) && $blocksListExact['disable_create'] ) {
01213                 break;
01214             }
01215 
01216             foreach ( $ipChain as $checkip ) {
01217                 $checkipHex = IP::toHex( $checkip );
01218                 if ( (string)$block->getTarget() === $checkip ) {
01219                     if ( $block->isHardblock() ) {
01220                         $blocksListExact['hard'] = $blocksListExact['hard'] ?: $block;
01221                     } elseif ( $block->prevents( 'createaccount' ) ) {
01222                         $blocksListExact['disable_create'] = $blocksListExact['disable_create'] ?: $block;
01223                     } elseif ( $block->mAuto ) {
01224                         $blocksListExact['auto'] = $blocksListExact['auto'] ?: $block;
01225                     } else {
01226                         $blocksListExact['other'] = $blocksListExact['other'] ?: $block;
01227                     }
01228                     // We found closest exact match in the ip list, so go to the next Block
01229                     break;
01230                 } elseif ( array_filter( $blocksListExact ) == array()
01231                     && $block->getRangeStart() <= $checkipHex
01232                     && $block->getRangeEnd() >= $checkipHex
01233                 ) {
01234                     if ( $block->isHardblock() ) {
01235                         $blocksListRange['hard'] = $blocksListRange['hard'] ?: $block;
01236                     } elseif ( $block->prevents( 'createaccount' ) ) {
01237                         $blocksListRange['disable_create'] = $blocksListRange['disable_create'] ?: $block;
01238                     } elseif ( $block->mAuto ) {
01239                         $blocksListRange['auto'] = $blocksListRange['auto'] ?: $block;
01240                     } else {
01241                         $blocksListRange['other'] = $blocksListRange['other'] ?: $block;
01242                     }
01243                     break;
01244                 }
01245             }
01246         }
01247 
01248         if ( array_filter( $blocksListExact ) == array() ) {
01249             $blocksList = &$blocksListRange;
01250         } else {
01251             $blocksList = &$blocksListExact;
01252         }
01253 
01254         $chosenBlock = null;
01255         if ( $blocksList['hard'] ) {
01256             $chosenBlock = $blocksList['hard'];
01257         } elseif ( $blocksList['disable_create'] ) {
01258             $chosenBlock = $blocksList['disable_create'];
01259         } elseif ( $blocksList['other'] ) {
01260             $chosenBlock = $blocksList['other'];
01261         } elseif ( $blocksList['auto'] ) {
01262             $chosenBlock = $blocksList['auto'];
01263         } else {
01264             wfProfileOut( __METHOD__ );
01265             throw new MWException( "Proxy block found, but couldn't be classified." );
01266         }
01267 
01268         wfProfileOut( __METHOD__ );
01269         return $chosenBlock;
01270     }
01271 
01281     public static function parseTarget( $target ) {
01282         # We may have been through this before
01283         if ( $target instanceof User ) {
01284             if ( IP::isValid( $target->getName() ) ) {
01285                 return array( $target, self::TYPE_IP );
01286             } else {
01287                 return array( $target, self::TYPE_USER );
01288             }
01289         } elseif ( $target === null ) {
01290             return array( null, null );
01291         }
01292 
01293         $target = trim( $target );
01294 
01295         if ( IP::isValid( $target ) ) {
01296             # We can still create a User if it's an IP address, but we need to turn
01297             # off validation checking (which would exclude IP addresses)
01298             return array(
01299                 User::newFromName( IP::sanitizeIP( $target ), false ),
01300                 Block::TYPE_IP
01301             );
01302 
01303         } elseif ( IP::isValidBlock( $target ) ) {
01304             # Can't create a User from an IP range
01305             return array( IP::sanitizeRange( $target ), Block::TYPE_RANGE );
01306         }
01307 
01308         # Consider the possibility that this is not a username at all
01309         # but actually an old subpage (bug #29797)
01310         if ( strpos( $target, '/' ) !== false ) {
01311             # An old subpage, drill down to the user behind it
01312             $parts = explode( '/', $target );
01313             $target = $parts[0];
01314         }
01315 
01316         $userObj = User::newFromName( $target );
01317         if ( $userObj instanceof User ) {
01318             # Note that since numbers are valid usernames, a $target of "12345" will be
01319             # considered a User.  If you want to pass a block ID, prepend a hash "#12345",
01320             # since hash characters are not valid in usernames or titles generally.
01321             return array( $userObj, Block::TYPE_USER );
01322 
01323         } elseif ( preg_match( '/^#\d+$/', $target ) ) {
01324             # Autoblock reference in the form "#12345"
01325             return array( substr( $target, 1 ), Block::TYPE_AUTO );
01326 
01327         } else {
01328             # WTF?
01329             return array( null, null );
01330         }
01331     }
01332 
01337     public function getType() {
01338         return $this->mAuto
01339             ? self::TYPE_AUTO
01340             : $this->type;
01341     }
01342 
01350     public function getTargetAndType() {
01351         return array( $this->getTarget(), $this->getType() );
01352     }
01353 
01360     public function getTarget() {
01361         return $this->target;
01362     }
01363 
01369     public function getExpiry() {
01370         return $this->mExpiry;
01371     }
01372 
01377     public function setTarget( $target ) {
01378         list( $this->target, $this->type ) = self::parseTarget( $target );
01379     }
01380 
01385     public function getBlocker() {
01386         return $this->blocker;
01387     }
01388 
01393     public function setBlocker( $user ) {
01394         $this->blocker = $user;
01395     }
01396 
01404     public function getPermissionsError( IContextSource $context ) {
01405         $blocker = $this->getBlocker();
01406         if ( $blocker instanceof User ) { // local user
01407             $blockerUserpage = $blocker->getUserPage();
01408             $link = "[[{$blockerUserpage->getPrefixedText()}|{$blockerUserpage->getText()}]]";
01409         } else { // foreign user
01410             $link = $blocker;
01411         }
01412 
01413         $reason = $this->mReason;
01414         if ( $reason == '' ) {
01415             $reason = $context->msg( 'blockednoreason' )->text();
01416         }
01417 
01418         /* $ip returns who *is* being blocked, $intended contains who was meant to be blocked.
01419          * This could be a username, an IP range, or a single IP. */
01420         $intended = $this->getTarget();
01421 
01422         $lang = $context->getLanguage();
01423         return array(
01424             $this->mAuto ? 'autoblockedtext' : 'blockedtext',
01425             $link,
01426             $reason,
01427             $context->getRequest()->getIP(),
01428             $this->getByName(),
01429             $this->getId(),
01430             $lang->formatExpiry( $this->mExpiry ),
01431             (string)$intended,
01432             $lang->timeanddate( wfTimestamp( TS_MW, $this->mTimestamp ), true ),
01433         );
01434     }
01435 }