3 * Blocks and bans object
9 * All the functions in this class assume the object is either explicitly
10 * loaded or filled. It is not load-on-demand. There are no accessors.
12 * To use delete(), you only need to fill $mAddress
13 * Globals used: $wgAutoblockExpiry, $wgAntiLockFlags
15 * @todo This could be used everywhere, but it isn't.
20 /* public*/ var $mAddress, $mUser, $mBy, $mReason, $mTimestamp, $mAuto, $mId, $mExpiry,
21 $mRangeStart, $mRangeEnd;
22 /* private */ var $mNetworkBits, $mIntegerAddr, $mForUpdate, $mFromMaster, $mByName;
24 const EB_KEEP_EXPIRED
= 1;
25 const EB_FOR_UPDATE
= 2;
26 const EB_RANGE_ONLY
= 4;
28 function Block( $address = '', $user = '', $by = 0, $reason = '',
29 $timestamp = '' , $auto = 0, $expiry = '' )
31 $this->mAddress
= $address;
34 $this->mReason
= $reason;
35 $this->mTimestamp
= wfTimestamp(TS_MW
,$timestamp);
37 if( empty( $expiry ) ) {
38 $this->mExpiry
= $expiry;
40 $this->mExpiry
= wfTimestamp( TS_MW
, $expiry );
43 $this->mForUpdate
= false;
44 $this->mFromMaster
= false;
45 $this->mByName
= false;
46 $this->initialiseRange();
49 /*static*/ function newFromDB( $address, $user = 0, $killExpired = true )
52 $ban->load( $address, $user, $killExpired );
58 $this->mAddress
= $this->mReason
= $this->mTimestamp
= '';
59 $this->mUser
= $this->mBy
= 0;
60 $this->mByName
= false;
65 * Get the DB object and set the reference parameter to the query options
67 function &getDBOptions( &$options )
69 global $wgAntiLockFlags;
70 if ( $this->mForUpdate ||
$this->mFromMaster
) {
71 $db =& wfGetDB( DB_MASTER
);
72 if ( !$this->mForUpdate ||
($wgAntiLockFlags & ALF_NO_BLOCK_LOCK
) ) {
75 $options = 'FOR UPDATE';
78 $db =& wfGetDB( DB_SLAVE
);
85 * Get a ban from the DB, with either the given address or the given username
87 function load( $address = '', $user = 0, $killExpired = true )
89 $fname = 'Block::load';
90 wfDebug( "Block::load: '$address', '$user', $killExpired\n" );
93 $db =& $this->getDBOptions( $options );
97 $ipblocks = $db->tableName( 'ipblocks' );
99 if ( 0 == $user && $address == '' ) {
100 # Invalid user specification, not blocked
103 } elseif ( $address == '' ) {
104 $sql = "SELECT * FROM $ipblocks WHERE ipb_user={$user} $options";
105 } elseif ( $user == '' ) {
106 $sql = "SELECT * FROM $ipblocks WHERE ipb_address=" . $db->addQuotes( $address ) . " $options";
107 } elseif ( $options == '' ) {
108 # If there are no options (e.g. FOR UPDATE), use a UNION
109 # so that the query can make efficient use of indices
110 $sql = "SELECT * FROM $ipblocks WHERE ipb_address='" . $db->strencode( $address ) .
111 "' UNION SELECT * FROM $ipblocks WHERE ipb_user={$user}";
113 # If there are options, a UNION can not be used, use one
114 # SELECT instead. Will do a full table scan.
115 $sql = "SELECT * FROM $ipblocks WHERE (ipb_address='" . $db->strencode( $address ) .
116 "' OR ipb_user={$user}) $options";
119 $res = $db->query( $sql, $fname );
120 if ( 0 != $db->numRows( $res ) ) {
122 $row = $db->fetchObject( $res );
123 $this->initFromRow( $row );
125 if ( $killExpired ) {
126 # If requested, delete expired rows
128 $killed = $this->deleteIfExpired();
130 $row = $db->fetchObject( $res );
132 $this->initFromRow( $row );
135 } while ( $killed && $row );
137 # If there were any left after the killing finished, return true
148 $db->freeResult( $res );
150 # No blocks found yet? Try looking for range blocks
151 if ( !$ret && $address != '' ) {
152 $ret = $this->loadRange( $address, $killExpired );
162 * Search the database for any range blocks matching the given address, and
163 * load the row if one is found.
165 function loadRange( $address, $killExpired = true )
167 $fname = 'Block::loadRange';
169 $iaddr = wfIP2Hex( $address );
170 if ( $iaddr === false ) {
175 # Only scan ranges which start in this /16, this improves search speed
176 # Blocks should not cross a /16 boundary.
177 $range = substr( $iaddr, 0, 4 );
180 $db =& $this->getDBOptions( $options );
181 $ipblocks = $db->tableName( 'ipblocks' );
182 $sql = "SELECT * FROM $ipblocks WHERE ipb_range_start LIKE '$range%' ".
183 "AND ipb_range_start <= '$iaddr' AND ipb_range_end >= '$iaddr' $options";
184 $res = $db->query( $sql, $fname );
185 $row = $db->fetchObject( $res );
189 # Found a row, initialise this object
190 $this->initFromRow( $row );
193 if ( !$killExpired ||
!$this->deleteIfExpired() ) {
199 $db->freeResult( $res );
204 * Determine if a given integer IPv4 address is in a given CIDR network
206 function isAddressInRange( $addr, $range ) {
207 list( $network, $bits ) = wfParseCIDR( $range );
208 if ( $network !== false && $addr >> ( 32 - $bits ) == $network >> ( 32 - $bits ) ) {
215 function initFromRow( $row )
217 $this->mAddress
= $row->ipb_address
;
218 $this->mReason
= $row->ipb_reason
;
219 $this->mTimestamp
= wfTimestamp(TS_MW
,$row->ipb_timestamp
);
220 $this->mUser
= $row->ipb_user
;
221 $this->mBy
= $row->ipb_by
;
222 $this->mAuto
= $row->ipb_auto
;
223 $this->mId
= $row->ipb_id
;
224 $this->mExpiry
= $row->ipb_expiry ?
225 wfTimestamp(TS_MW
,$row->ipb_expiry
) :
227 if ( isset( $row->user_name
) ) {
228 $this->mByName
= $row->user_name
;
230 $this->mByName
= false;
232 $this->mRangeStart
= $row->ipb_range_start
;
233 $this->mRangeEnd
= $row->ipb_range_end
;
236 function initialiseRange()
238 $this->mRangeStart
= '';
239 $this->mRangeEnd
= '';
240 if ( $this->mUser
== 0 ) {
241 list( $network, $bits ) = wfParseCIDR( $this->mAddress
);
242 if ( $network !== false ) {
243 $this->mRangeStart
= sprintf( '%08X', $network );
244 $this->mRangeEnd
= sprintf( '%08X', $network +
(1 << (32 - $bits)) - 1 );
250 * Callback with a Block object for every block
251 * @return integer number of blocks;
253 /*static*/ function enumBlocks( $callback, $tag, $flags = 0 )
255 global $wgAntiLockFlags;
257 $block = new Block();
258 if ( $flags & Block
::EB_FOR_UPDATE
) {
259 $db =& wfGetDB( DB_MASTER
);
260 if ( $wgAntiLockFlags & ALF_NO_BLOCK_LOCK
) {
263 $options = 'FOR UPDATE';
265 $block->forUpdate( true );
267 $db =& wfGetDB( DB_SLAVE
);
270 if ( $flags & Block
::EB_RANGE_ONLY
) {
271 $cond = " AND ipb_range_start <> ''";
276 $now = wfTimestampNow();
278 extract( $db->tableNames( 'ipblocks', 'user' ) );
280 $sql = "SELECT $ipblocks.*,user_name FROM $ipblocks,$user " .
281 "WHERE user_id=ipb_by $cond ORDER BY ipb_timestamp DESC $options";
282 $res = $db->query( $sql, 'Block::enumBlocks' );
283 $num_rows = $db->numRows( $res );
285 while ( $row = $db->fetchObject( $res ) ) {
286 $block->initFromRow( $row );
287 if ( ( $flags & Block
::EB_RANGE_ONLY
) && $block->mRangeStart
== '' ) {
291 if ( !( $flags & Block
::EB_KEEP_EXPIRED
) ) {
292 if ( $block->mExpiry
&& $now > $block->mExpiry
) {
295 call_user_func( $callback, $block, $tag );
298 call_user_func( $callback, $block, $tag );
301 wfFreeResult( $res );
307 $fname = 'Block::delete';
311 $dbw =& wfGetDB( DB_MASTER
);
313 if ( $this->mAddress
== '' ) {
314 $condition = array( 'ipb_id' => $this->mId
);
316 $condition = array( 'ipb_address' => $this->mAddress
);
318 return( $dbw->delete( 'ipblocks', $condition, $fname ) > 0 ?
true : false );
323 wfDebug( "Block::insert; timestamp {$this->mTimestamp}\n" );
324 $dbw =& wfGetDB( DB_MASTER
);
325 $ipb_id = $dbw->nextSequenceValue('ipblocks_ipb_id_val');
326 $dbw->insert( 'ipblocks',
329 'ipb_address' => $this->mAddress
,
330 'ipb_user' => $this->mUser
,
331 'ipb_by' => $this->mBy
,
332 'ipb_reason' => $this->mReason
,
333 'ipb_timestamp' => $dbw->timestamp($this->mTimestamp
),
334 'ipb_auto' => $this->mAuto
,
335 'ipb_expiry' => $this->mExpiry ?
336 $dbw->timestamp($this->mExpiry
) :
338 'ipb_range_start' => $this->mRangeStart
,
339 'ipb_range_end' => $this->mRangeEnd
,
344 function deleteIfExpired()
346 $fname = 'Block::deleteIfExpired';
347 wfProfileIn( $fname );
348 if ( $this->isExpired() ) {
349 wfDebug( "Block::deleteIfExpired() -- deleting\n" );
353 wfDebug( "Block::deleteIfExpired() -- not expired\n" );
356 wfProfileOut( $fname );
362 wfDebug( "Block::isExpired() checking current " . wfTimestampNow() . " vs $this->mExpiry\n" );
363 if ( !$this->mExpiry
) {
366 return wfTimestampNow() > $this->mExpiry
;
372 return $this->mAddress
!= '';
375 function updateTimestamp()
377 if ( $this->mAuto
) {
378 $this->mTimestamp
= wfTimestamp();
379 $this->mExpiry
= Block
::getAutoblockExpiry( $this->mTimestamp
);
381 $dbw =& wfGetDB( DB_MASTER
);
382 $dbw->update( 'ipblocks',
384 'ipb_timestamp' => $dbw->timestamp($this->mTimestamp
),
385 'ipb_expiry' => $dbw->timestamp($this->mExpiry
),
386 ), array( /* WHERE */
387 'ipb_address' => $this->mAddress
388 ), 'Block::updateTimestamp'
394 function getIntegerAddr()
396 return $this->mIntegerAddr;
399 function getNetworkBits()
401 return $this->mNetworkBits;
406 if ( $this->mByName
=== false ) {
407 $this->mByName
= User
::whoIs( $this->mBy
);
409 return $this->mByName
;
412 function forUpdate( $x = NULL ) {
413 return wfSetVar( $this->mForUpdate
, $x );
416 function fromMaster( $x = NULL ) {
417 return wfSetVar( $this->mFromMaster
, $x );
420 /* static */ function getAutoblockExpiry( $timestamp )
422 global $wgAutoblockExpiry;
423 return wfTimestamp( TS_MW
, wfTimestamp( TS_UNIX
, $timestamp ) +
$wgAutoblockExpiry );
426 /* static */ function normaliseRange( $range )
428 $parts = explode( '/', $range );
429 if ( count( $parts ) == 2 ) {
430 $shift = 32 - $parts[1];
431 $ipint = wfIP2Unsigned( $parts[0] );
432 $ipint = $ipint >> $shift << $shift;
433 $newip = long2ip( $ipint );
434 $range = "$newip/{$parts[1]}";