Clean up after r14751:
[lhc/web/wiklou.git] / includes / Block.php
1 <?php
2 /**
3 * Blocks and bans object
4 * @package MediaWiki
5 */
6
7 /**
8 * The block class
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.
11 *
12 * To use delete(), you only need to fill $mAddress
13 * Globals used: $wgAutoblockExpiry, $wgAntiLockFlags
14 *
15 * @todo This could be used everywhere, but it isn't.
16 * @package MediaWiki
17 */
18 class Block
19 {
20 /* public*/ var $mAddress, $mUser, $mBy, $mReason, $mTimestamp, $mAuto, $mId, $mExpiry,
21 $mRangeStart, $mRangeEnd;
22 /* private */ var $mNetworkBits, $mIntegerAddr, $mForUpdate, $mFromMaster, $mByName;
23
24 const EB_KEEP_EXPIRED = 1;
25 const EB_FOR_UPDATE = 2;
26 const EB_RANGE_ONLY = 4;
27
28 function Block( $address = '', $user = '', $by = 0, $reason = '',
29 $timestamp = '' , $auto = 0, $expiry = '' )
30 {
31 $this->mAddress = $address;
32 $this->mUser = $user;
33 $this->mBy = $by;
34 $this->mReason = $reason;
35 $this->mTimestamp = wfTimestamp(TS_MW,$timestamp);
36 $this->mAuto = $auto;
37 if( empty( $expiry ) ) {
38 $this->mExpiry = $expiry;
39 } else {
40 $this->mExpiry = wfTimestamp( TS_MW, $expiry );
41 }
42
43 $this->mForUpdate = false;
44 $this->mFromMaster = false;
45 $this->mByName = false;
46 $this->initialiseRange();
47 }
48
49 /*static*/ function newFromDB( $address, $user = 0, $killExpired = true )
50 {
51 $ban = new Block();
52 $ban->load( $address, $user, $killExpired );
53 return $ban;
54 }
55
56 function clear()
57 {
58 $this->mAddress = $this->mReason = $this->mTimestamp = '';
59 $this->mUser = $this->mBy = 0;
60 $this->mByName = false;
61
62 }
63
64 /**
65 * Get the DB object and set the reference parameter to the query options
66 */
67 function &getDBOptions( &$options )
68 {
69 global $wgAntiLockFlags;
70 if ( $this->mForUpdate || $this->mFromMaster ) {
71 $db =& wfGetDB( DB_MASTER );
72 if ( !$this->mForUpdate || ($wgAntiLockFlags & ALF_NO_BLOCK_LOCK) ) {
73 $options = '';
74 } else {
75 $options = 'FOR UPDATE';
76 }
77 } else {
78 $db =& wfGetDB( DB_SLAVE );
79 $options = '';
80 }
81 return $db;
82 }
83
84 /**
85 * Get a ban from the DB, with either the given address or the given username
86 */
87 function load( $address = '', $user = 0, $killExpired = true )
88 {
89 $fname = 'Block::load';
90 wfDebug( "Block::load: '$address', '$user', $killExpired\n" );
91
92 $options = '';
93 $db =& $this->getDBOptions( $options );
94
95 $ret = false;
96 $killed = false;
97 $ipblocks = $db->tableName( 'ipblocks' );
98
99 if ( 0 == $user && $address == '' ) {
100 # Invalid user specification, not blocked
101 $this->clear();
102 return false;
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}";
112 } else {
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";
117 }
118
119 $res = $db->query( $sql, $fname );
120 if ( 0 != $db->numRows( $res ) ) {
121 # Get first block
122 $row = $db->fetchObject( $res );
123 $this->initFromRow( $row );
124
125 if ( $killExpired ) {
126 # If requested, delete expired rows
127 do {
128 $killed = $this->deleteIfExpired();
129 if ( $killed ) {
130 $row = $db->fetchObject( $res );
131 if ( $row ) {
132 $this->initFromRow( $row );
133 }
134 }
135 } while ( $killed && $row );
136
137 # If there were any left after the killing finished, return true
138 if ( !$row ) {
139 $ret = false;
140 $this->clear();
141 } else {
142 $ret = true;
143 }
144 } else {
145 $ret = true;
146 }
147 }
148 $db->freeResult( $res );
149
150 # No blocks found yet? Try looking for range blocks
151 if ( !$ret && $address != '' ) {
152 $ret = $this->loadRange( $address, $killExpired );
153 }
154 if ( !$ret ) {
155 $this->clear();
156 }
157
158 return $ret;
159 }
160
161 /**
162 * Search the database for any range blocks matching the given address, and
163 * load the row if one is found.
164 */
165 function loadRange( $address, $killExpired = true )
166 {
167 $fname = 'Block::loadRange';
168
169 $iaddr = wfIP2Hex( $address );
170 if ( $iaddr === false ) {
171 # Invalid address
172 return false;
173 }
174
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 );
178
179 $options = '';
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 );
186
187 $success = false;
188 if ( $row ) {
189 # Found a row, initialise this object
190 $this->initFromRow( $row );
191
192 # Is it expired?
193 if ( !$killExpired || !$this->deleteIfExpired() ) {
194 # No, return true
195 $success = true;
196 }
197 }
198
199 $db->freeResult( $res );
200 return $success;
201 }
202
203 /**
204 * Determine if a given integer IPv4 address is in a given CIDR network
205 */
206 function isAddressInRange( $addr, $range ) {
207 list( $network, $bits ) = wfParseCIDR( $range );
208 if ( $network !== false && $addr >> ( 32 - $bits ) == $network >> ( 32 - $bits ) ) {
209 return true;
210 } else {
211 return false;
212 }
213 }
214
215 function initFromRow( $row )
216 {
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) :
226 $row->ipb_expiry;
227 if ( isset( $row->user_name ) ) {
228 $this->mByName = $row->user_name;
229 } else {
230 $this->mByName = false;
231 }
232 $this->mRangeStart = $row->ipb_range_start;
233 $this->mRangeEnd = $row->ipb_range_end;
234 }
235
236 function initialiseRange()
237 {
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 );
245 }
246 }
247 }
248
249 /**
250 * Callback with a Block object for every block
251 * @return integer number of blocks;
252 */
253 /*static*/ function enumBlocks( $callback, $tag, $flags = 0 )
254 {
255 global $wgAntiLockFlags;
256
257 $block = new Block();
258 if ( $flags & Block::EB_FOR_UPDATE ) {
259 $db =& wfGetDB( DB_MASTER );
260 if ( $wgAntiLockFlags & ALF_NO_BLOCK_LOCK ) {
261 $options = '';
262 } else {
263 $options = 'FOR UPDATE';
264 }
265 $block->forUpdate( true );
266 } else {
267 $db =& wfGetDB( DB_SLAVE );
268 $options = '';
269 }
270 if ( $flags & Block::EB_RANGE_ONLY ) {
271 $cond = " AND ipb_range_start <> ''";
272 } else {
273 $cond = '';
274 }
275
276 $now = wfTimestampNow();
277
278 extract( $db->tableNames( 'ipblocks', 'user' ) );
279
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 );
284
285 while ( $row = $db->fetchObject( $res ) ) {
286 $block->initFromRow( $row );
287 if ( ( $flags & Block::EB_RANGE_ONLY ) && $block->mRangeStart == '' ) {
288 continue;
289 }
290
291 if ( !( $flags & Block::EB_KEEP_EXPIRED ) ) {
292 if ( $block->mExpiry && $now > $block->mExpiry ) {
293 $block->delete();
294 } else {
295 call_user_func( $callback, $block, $tag );
296 }
297 } else {
298 call_user_func( $callback, $block, $tag );
299 }
300 }
301 wfFreeResult( $res );
302 return $num_rows;
303 }
304
305 function delete()
306 {
307 $fname = 'Block::delete';
308 if (wfReadOnly()) {
309 return;
310 }
311 $dbw =& wfGetDB( DB_MASTER );
312
313 if ( $this->mAddress == '' ) {
314 $condition = array( 'ipb_id' => $this->mId );
315 } else {
316 $condition = array( 'ipb_address' => $this->mAddress );
317 }
318 return( $dbw->delete( 'ipblocks', $condition, $fname ) > 0 ? true : false );
319 }
320
321 function insert()
322 {
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',
327 array(
328 'ipb_id' => $ipb_id,
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) :
337 $this->mExpiry,
338 'ipb_range_start' => $this->mRangeStart,
339 'ipb_range_end' => $this->mRangeEnd,
340 ), 'Block::insert'
341 );
342 }
343
344 function deleteIfExpired()
345 {
346 $fname = 'Block::deleteIfExpired';
347 wfProfileIn( $fname );
348 if ( $this->isExpired() ) {
349 wfDebug( "Block::deleteIfExpired() -- deleting\n" );
350 $this->delete();
351 $retVal = true;
352 } else {
353 wfDebug( "Block::deleteIfExpired() -- not expired\n" );
354 $retVal = false;
355 }
356 wfProfileOut( $fname );
357 return $retVal;
358 }
359
360 function isExpired()
361 {
362 wfDebug( "Block::isExpired() checking current " . wfTimestampNow() . " vs $this->mExpiry\n" );
363 if ( !$this->mExpiry ) {
364 return false;
365 } else {
366 return wfTimestampNow() > $this->mExpiry;
367 }
368 }
369
370 function isValid()
371 {
372 return $this->mAddress != '';
373 }
374
375 function updateTimestamp()
376 {
377 if ( $this->mAuto ) {
378 $this->mTimestamp = wfTimestamp();
379 $this->mExpiry = Block::getAutoblockExpiry( $this->mTimestamp );
380
381 $dbw =& wfGetDB( DB_MASTER );
382 $dbw->update( 'ipblocks',
383 array( /* SET */
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'
389 );
390 }
391 }
392
393 /*
394 function getIntegerAddr()
395 {
396 return $this->mIntegerAddr;
397 }
398
399 function getNetworkBits()
400 {
401 return $this->mNetworkBits;
402 }*/
403
404 function getByName()
405 {
406 if ( $this->mByName === false ) {
407 $this->mByName = User::whoIs( $this->mBy );
408 }
409 return $this->mByName;
410 }
411
412 function forUpdate( $x = NULL ) {
413 return wfSetVar( $this->mForUpdate, $x );
414 }
415
416 function fromMaster( $x = NULL ) {
417 return wfSetVar( $this->mFromMaster, $x );
418 }
419
420 /* static */ function getAutoblockExpiry( $timestamp )
421 {
422 global $wgAutoblockExpiry;
423 return wfTimestamp( TS_MW, wfTimestamp( TS_UNIX, $timestamp ) + $wgAutoblockExpiry );
424 }
425
426 /* static */ function normaliseRange( $range )
427 {
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]}";
435 }
436 return $range;
437 }
438
439 }
440 ?>