* Allow blocks on anonymous users only.

* Allow or disallow account creation from blocked IP addressess on a per-block basis.
* Prevent duplicate blocks. 
* Fixed the problem of expiry and unblocking erroneously affecting multiple blocks. 
* Fixed confusing lack of error message when a blocked user attempts to create an account. 
* Fixed inefficiency of Special:Ipblocklist in the presence of large numbers of blocks; added indexes and implemented an indexed pager.
This commit is contained in:
Tim Starling 2006-07-10 06:30:03 +00:00
parent e67cbd5a94
commit 1d9922db64
11 changed files with 549 additions and 226 deletions

View file

@ -25,9 +25,7 @@ it from source control: http://www.mediawiki.org/wiki/Download_from_SVN
== Major new features ==
* None!
* (bug 550) Allow blocks on anonymous users only.
== Changes since 1.7 ==
@ -36,6 +34,13 @@ it from source control: http://www.mediawiki.org/wiki/Download_from_SVN
* (bug 6586) Regression in "unblocked" subtitle
* Don't put empty-page message into view-source when page text is blank
* (bug 6587) Remove redundant "allnonarticles" message
* Block improvements: Allow blocks on anonymous users only. Optionally allow
or disallow account creation from blocked IP addresses. Prevent duplicate
blocks. Fixed the problem of expiry and unblocking erroneously affecting
multiple blocks. Fixed confusing lack of error message when a blocked user
attempts to create an account. Fixed inefficiency of Special:Ipblocklist in
the presence of large numbers of blocks; added indexes and implemented an
indexed pager.
== Languages updated ==

View file

@ -9,7 +9,6 @@
* All the functions in this class assume the object is either explicitly
* loaded or filled. It is not load-on-demand. There are no accessors.
*
* To use delete(), you only need to fill $mAddress
* Globals used: $wgAutoblockExpiry, $wgAntiLockFlags
*
* @todo This could be used everywhere, but it isn't.
@ -18,7 +17,7 @@
class Block
{
/* public*/ var $mAddress, $mUser, $mBy, $mReason, $mTimestamp, $mAuto, $mId, $mExpiry,
$mRangeStart, $mRangeEnd;
$mRangeStart, $mRangeEnd, $mAnonOnly;
/* private */ var $mNetworkBits, $mIntegerAddr, $mForUpdate, $mFromMaster, $mByName;
const EB_KEEP_EXPIRED = 1;
@ -26,19 +25,18 @@ class Block
const EB_RANGE_ONLY = 4;
function Block( $address = '', $user = '', $by = 0, $reason = '',
$timestamp = '' , $auto = 0, $expiry = '' )
$timestamp = '' , $auto = 0, $expiry = '', $anonOnly = 0, $createAccount = 0 )
{
$this->mId = 0;
$this->mAddress = $address;
$this->mUser = $user;
$this->mBy = $by;
$this->mReason = $reason;
$this->mTimestamp = wfTimestamp(TS_MW,$timestamp);
$this->mAuto = $auto;
if( empty( $expiry ) ) {
$this->mExpiry = $expiry;
} else {
$this->mExpiry = wfTimestamp( TS_MW, $expiry );
}
$this->mAnonOnly = $anonOnly;
$this->mCreateAccount = $createAccount;
$this->mExpiry = self::decodeExpiry( $expiry );
$this->mForUpdate = false;
$this->mFromMaster = false;
@ -46,19 +44,36 @@ class Block
$this->initialiseRange();
}
/*static*/ function newFromDB( $address, $user = 0, $killExpired = true )
static function newFromDB( $address, $user = 0, $killExpired = true )
{
$ban = new Block();
$ban->load( $address, $user, $killExpired );
return $ban;
$block = new Block();
$block->load( $address, $user, $killExpired );
if ( $block->isValid() ) {
return $block;
} else {
return null;
}
}
static function newFromID( $id )
{
$dbr =& wfGetDB( DB_SLAVE );
$res = $dbr->resultObject( $dbr->select( 'ipblocks', '*',
array( 'ipb_id' => $id ), __METHOD__ ) );
$block = new Block;
if ( $block->loadFromResult( $res ) ) {
return $block;
} else {
return null;
}
}
function clear()
{
$this->mAddress = $this->mReason = $this->mTimestamp = '';
$this->mUser = $this->mBy = 0;
$this->mId = $this->mAnonOnly = $this->mCreateAccount =
$this->mAuto = $this->mUser = $this->mBy = 0;
$this->mByName = false;
}
/**
@ -70,56 +85,80 @@ class Block
if ( $this->mForUpdate || $this->mFromMaster ) {
$db =& wfGetDB( DB_MASTER );
if ( !$this->mForUpdate || ($wgAntiLockFlags & ALF_NO_BLOCK_LOCK) ) {
$options = '';
$options = array();
} else {
$options = 'FOR UPDATE';
$options = array( 'FOR UPDATE' );
}
} else {
$db =& wfGetDB( DB_SLAVE );
$options = '';
$options = array();
}
return $db;
}
/**
* Get a ban from the DB, with either the given address or the given username
*
* @param string $address The IP address of the user, or blank to skip IP blocks
* @param integer $user The user ID, or zero for anonymous users
* @param bool $killExpired Whether to delete expired rows while loading
*
*/
function load( $address = '', $user = 0, $killExpired = true )
{
$fname = 'Block::load';
wfDebug( "Block::load: '$address', '$user', $killExpired\n" );
$options = '';
$options = array();
$db =& $this->getDBOptions( $options );
$ret = false;
$killed = false;
$ipblocks = $db->tableName( 'ipblocks' );
if ( 0 == $user && $address == '' ) {
# Invalid user specification, not blocked
$this->clear();
return false;
} elseif ( $address == '' ) {
$sql = "SELECT * FROM $ipblocks WHERE ipb_user={$user} $options";
} elseif ( $user == '' ) {
$sql = "SELECT * FROM $ipblocks WHERE ipb_address=" . $db->addQuotes( $address ) . " $options";
} elseif ( $options == '' ) {
# If there are no options (e.g. FOR UPDATE), use a UNION
# so that the query can make efficient use of indices
$sql = "SELECT * FROM $ipblocks WHERE ipb_address='" . $db->strencode( $address ) .
"' UNION SELECT * FROM $ipblocks WHERE ipb_user={$user}";
} else {
# If there are options, a UNION can not be used, use one
# SELECT instead. Will do a full table scan.
$sql = "SELECT * FROM $ipblocks WHERE (ipb_address='" . $db->strencode( $address ) .
"' OR ipb_user={$user}) $options";
}
$res = $db->query( $sql, $fname );
if ( 0 != $db->numRows( $res ) ) {
# Try user block
if ( $user ) {
$res = $db->resultObject( $db->select( 'ipblocks', '*', array( 'ipb_user' => $user ),
__METHOD__, $options ) );
if ( $this->loadFromResult( $res, $killExpired ) ) {
return true;
}
}
# Try IP block
if ( $address ) {
$conds = array( 'ipb_address' => $address );
if ( $user ) {
$conds['ipb_anon_only'] = 0;
}
$res = $db->resultObject( $db->select( 'ipblocks', '*', $conds, __METHOD__, $options ) );
if ( $this->loadFromResult( $res, $killExpired ) ) {
return true;
}
}
# Try range block
if ( $this->loadRange( $address, $killExpired, $user == 0 ) ) {
return true;
}
# Give up
$this->clear();
return false;
}
/**
* Fill in member variables from a result wrapper
*/
function loadFromResult( ResultWrapper $res, $killExpired = true ) {
$ret = false;
if ( 0 != $res->numRows() ) {
# Get first block
$row = $db->fetchObject( $res );
$row = $res->fetchObject();
$this->initFromRow( $row );
if ( $killExpired ) {
@ -127,7 +166,7 @@ class Block
do {
$killed = $this->deleteIfExpired();
if ( $killed ) {
$row = $db->fetchObject( $res );
$row = $res->fetchObject();
if ( $row ) {
$this->initFromRow( $row );
}
@ -135,26 +174,14 @@ class Block
} while ( $killed && $row );
# If there were any left after the killing finished, return true
if ( !$row ) {
$ret = false;
$this->clear();
} else {
if ( $row ) {
$ret = true;
}
} else {
$ret = true;
}
}
$db->freeResult( $res );
# No blocks found yet? Try looking for range blocks
if ( !$ret && $address != '' ) {
$ret = $this->loadRange( $address, $killExpired );
}
if ( !$ret ) {
$this->clear();
}
$res->free();
return $ret;
}
@ -162,10 +189,8 @@ class Block
* Search the database for any range blocks matching the given address, and
* load the row if one is found.
*/
function loadRange( $address, $killExpired = true )
function loadRange( $address, $killExpired = true, $isAnon = true )
{
$fname = 'Block::loadRange';
$iaddr = wfIP2Hex( $address );
if ( $iaddr === false ) {
# Invalid address
@ -176,27 +201,19 @@ class Block
# Blocks should not cross a /16 boundary.
$range = substr( $iaddr, 0, 4 );
$options = '';
$options = array();
$db =& $this->getDBOptions( $options );
$ipblocks = $db->tableName( 'ipblocks' );
$sql = "SELECT * FROM $ipblocks WHERE ipb_range_start LIKE '$range%' ".
"AND ipb_range_start <= '$iaddr' AND ipb_range_end >= '$iaddr' $options";
$res = $db->query( $sql, $fname );
$row = $db->fetchObject( $res );
$success = false;
if ( $row ) {
# Found a row, initialise this object
$this->initFromRow( $row );
# Is it expired?
if ( !$killExpired || !$this->deleteIfExpired() ) {
# No, return true
$success = true;
}
$conds = array(
"ipb_range_start LIKE '$range%'",
"ipb_range_start <= '$iaddr'",
"ipb_range_end >= '$iaddr'"
);
if ( !$isAnon ) {
$conds['ipb_anon_only'] = 0;
}
$db->freeResult( $res );
$res = $db->resultObject( $db->select( 'ipblocks', '*', $conds, __METHOD__, $options ) );
$success = $this->loadFromResult( $res, $killExpired );
return $success;
}
@ -220,10 +237,10 @@ class Block
$this->mUser = $row->ipb_user;
$this->mBy = $row->ipb_by;
$this->mAuto = $row->ipb_auto;
$this->mAnonOnly = $row->ipb_anon_only;
$this->mCreateAccount = $row->ipb_create_account;
$this->mId = $row->ipb_id;
$this->mExpiry = $row->ipb_expiry ?
wfTimestamp(TS_MW,$row->ipb_expiry) :
$row->ipb_expiry;
$this->mExpiry = self::decodeExpiry( $row->ipb_expiry );
if ( isset( $row->user_name ) ) {
$this->mByName = $row->user_name;
} else {
@ -304,24 +321,27 @@ class Block
function delete()
{
$fname = 'Block::delete';
if (wfReadOnly()) {
return;
return false;
}
if ( !$this->mId ) {
throw new MWException( "Block::delete() now requires that the mId member be filled\n" );
}
$dbw =& wfGetDB( DB_MASTER );
if ( $this->mAddress == '' ) {
$condition = array( 'ipb_id' => $this->mId );
} else {
$condition = array( 'ipb_address' => $this->mAddress );
}
return( $dbw->delete( 'ipblocks', $condition, $fname ) > 0 ? true : false );
$dbw =& wfGetDB( DB_MASTER );
$dbw->delete( 'ipblocks', array( 'ipb_id' => $this->mId ), __METHOD__ );
return $dbw->affectedRows() > 0;
}
function insert()
{
wfDebug( "Block::insert; timestamp {$this->mTimestamp}\n" );
$dbw =& wfGetDB( DB_MASTER );
$dbw->begin();
# Don't collide with expired blocks
Block::purgeExpired();
$ipb_id = $dbw->nextSequenceValue('ipblocks_ipb_id_val');
$dbw->insert( 'ipblocks',
array(
@ -332,13 +352,16 @@ class Block
'ipb_reason' => $this->mReason,
'ipb_timestamp' => $dbw->timestamp($this->mTimestamp),
'ipb_auto' => $this->mAuto,
'ipb_expiry' => $this->mExpiry ?
$dbw->timestamp($this->mExpiry) :
$this->mExpiry,
'ipb_anon_only' => $this->mAnonOnly,
'ipb_create_account' => $this->mCreateAccount,
'ipb_expiry' => self::encodeExpiry( $this->mExpiry, $dbw ),
'ipb_range_start' => $this->mRangeStart,
'ipb_range_end' => $this->mRangeEnd,
), 'Block::insert'
), 'Block::insert', array( 'IGNORE' )
);
$affected = $dbw->affectedRows();
$dbw->commit();
return $affected;
}
function deleteIfExpired()
@ -417,13 +440,43 @@ class Block
return wfSetVar( $this->mFromMaster, $x );
}
/* static */ function getAutoblockExpiry( $timestamp )
function getRedactedName() {
if ( $this->mAuto ) {
return '#' . $this->mId;
} else {
return $this->mAddress;
}
}
/**
* Encode expiry for DB
*/
static function encodeExpiry( $expiry, $db ) {
if ( $expiry == '' || $expiry == Block::infinity() ) {
return Block::infinity();
} else {
return $db->timestamp( $expiry );
}
}
/**
* Decode expiry which has come from the DB
*/
static function decodeExpiry( $expiry ) {
if ( $expiry == '' || $expiry == Block::infinity() ) {
return Block::infinity();
} else {
return wfTimestamp( TS_MW, $expiry );
}
}
static function getAutoblockExpiry( $timestamp )
{
global $wgAutoblockExpiry;
return wfTimestamp( TS_MW, wfTimestamp( TS_UNIX, $timestamp ) + $wgAutoblockExpiry );
}
/* static */ function normaliseRange( $range )
static function normaliseRange( $range )
{
$parts = explode( '/', $range );
if ( count( $parts ) == 2 ) {
@ -436,5 +489,28 @@ class Block
return $range;
}
/**
* Purge expired blocks from the ipblocks table
*/
static function purgeExpired() {
$dbw =& wfGetDB( DB_MASTER );
$dbw->delete( 'ipblocks', array( 'ipb_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ), __METHOD__ );
}
static function infinity() {
# This is a special keyword for timestamps in PostgreSQL, and
# works with CHAR(14) as well because "i" sorts after all numbers.
return 'infinity';
/*
static $infinity;
if ( !isset( $infinity ) ) {
$dbr =& wfGetDB( DB_SLAVE );
$infinity = $dbr->bigTimestamp();
}
return $infinity;
*/
}
}
?>

View file

@ -46,6 +46,15 @@ class IPBlockForm {
$this->BlockReason = $wgRequest->getText( 'wpBlockReason' );
$this->BlockExpiry = $wgRequest->getVal( 'wpBlockExpiry', wfMsg('ipbotheroption') );
$this->BlockOther = $wgRequest->getVal( 'wpBlockOther', '' );
$this->BlockAnonOnly = $wgRequest->getBool( 'wpAnonOnly' );
# Unchecked checkboxes are not included in the form data at all, so having one
# that is true by default is a bit tricky
if ( $wgRequest->wasPosted() ) {
$this->BlockCreateAccount = $wgRequest->getBool( 'wpCreateAccount', false );
} else {
$this->BlockCreateAccount = $wgRequest->getBool( 'wpCreateAccount', true );
}
}
function showForm( $err ) {
@ -64,6 +73,8 @@ class IPBlockForm {
$mIpbothertime = wfMsgHtml( 'ipbotheroption' );
$mIpbreason = wfMsgHtml( 'ipbreason' );
$mIpbsubmit = wfMsgHtml( 'ipbsubmit' );
$mIpbanononly = wfMsgHtml( 'ipbanononly' );
$mIpbcreateaccount = wfMsgHtml( 'ipbcreateaccount' );
$titleObj = Title::makeTitle( NS_SPECIAL, 'Blockip' );
$action = $titleObj->escapeLocalURL( "action=submit" );
@ -77,6 +88,8 @@ class IPBlockForm {
$scBlockReason = htmlspecialchars( $this->BlockReason );
$scBlockOtherTime = htmlspecialchars( $this->BlockOther );
$scBlockExpiryOptions = htmlspecialchars( wfMsgForContent( 'ipboptions' ) );
$anonOnlyChecked = $this->BlockAnonOnly ? 'checked' : '';
$createAccountChecked = $this->BlockCreateAccount ? 'checked' : '';
$showblockoptions = $scBlockExpiryOptions != '-';
if (!$showblockoptions)
@ -102,7 +115,7 @@ class IPBlockForm {
<tr>
<td align=\"right\">{$mIpaddress}:</td>
<td align=\"left\">
<input tabindex='1' type='text' size='20' name=\"wpBlockAddress\" value=\"{$scBlockAddress}\" />
<input tabindex='1' type='text' size='40' name=\"wpBlockAddress\" value=\"{$scBlockAddress}\" />
</td>
</tr>
<tr>");
@ -133,6 +146,24 @@ class IPBlockForm {
<tr>
<td>&nbsp;</td>
<td align=\"left\">
<label>
<input type='checkbox' name='wpAnonOnly' value='1' $anonOnlyChecked />
{$mIpbanononly}
</label>
</td>
</tr>
<tr>
<td>&nbsp;</td>
<td align=\"left\">
<label>
<input type='checkbox' name='wpCreateAccount' value='1' $createAccountChecked />
{$mIpbcreateaccount}
</label>
</td>
</tr>
<tr>
<td style='padding-top: 1em'>&nbsp;</td>
<td style='padding-top: 1em' align=\"left\">
<input tabindex='4' type='submit' name=\"wpBlock\" value=\"{$mIpbsubmit}\" />
</td>
</tr>
@ -188,7 +219,7 @@ class IPBlockForm {
}
if ( $expirestr == 'infinite' || $expirestr == 'indefinite' ) {
$expiry = '';
$expiry = Block::infinity();
} else {
# Convert GNU-style date, on error returns -1 for PHP <5.1 and false for PHP >=5.1
$expiry = strtotime( $expirestr );
@ -199,20 +230,24 @@ class IPBlockForm {
}
$expiry = wfTimestamp( TS_MW, $expiry );
}
# Create block
# Note: for a user block, ipb_address is only for display purposes
$ban = new Block( $this->BlockAddress, $userId, $wgUser->getID(),
$this->BlockReason, wfTimestampNow(), 0, $expiry );
$block = new Block( $this->BlockAddress, $userId, $wgUser->getID(),
$this->BlockReason, wfTimestampNow(), 0, $expiry, $this->BlockAnonOnly,
$this->BlockCreateAccount );
if (wfRunHooks('BlockIp', array(&$ban, &$wgUser))) {
if (wfRunHooks('BlockIp', array(&$block, &$wgUser))) {
$ban->insert();
if ( !$block->insert() ) {
$this->showForm( wfMsg( 'ipb_already_blocked',
htmlspecialchars( $this->BlockAddress ) ) );
return;
}
wfRunHooks('BlockIpComplete', array($ban, $wgUser));
wfRunHooks('BlockIpComplete', array($block, $wgUser));
# Make log entry
$log = new LogPage( 'block' );

View file

@ -12,13 +12,15 @@ function wfSpecialIpblocklist() {
global $wgUser, $wgOut, $wgRequest;
$ip = $wgRequest->getVal( 'wpUnblockAddress', $wgRequest->getVal( 'ip' ) );
$id = $wgRequest->getVal( 'id' );
$reason = $wgRequest->getText( 'wpUnblockReason' );
$action = $wgRequest->getText( 'action' );
$successip = $wgRequest->getVal( 'successip' );
$ipu = new IPUnblockForm( $ip, $reason );
$ipu = new IPUnblockForm( $ip, $id, $reason );
if ( "success" == $action ) {
$ipu->showList( $wgOut->parse( wfMsg( 'unblocked', $ip ) ) );
$ipu->showList( $wgOut->parse( wfMsg( 'unblocked', $successip ) ) );
} else if ( "submit" == $action && $wgRequest->wasPosted() &&
$wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) {
if ( ! $wgUser->isAllowed('block') ) {
@ -39,10 +41,11 @@ function wfSpecialIpblocklist() {
* @subpackage SpecialPage
*/
class IPUnblockForm {
var $ip, $reason;
var $ip, $reason, $id;
function IPUnblockForm( $ip, $reason ) {
function IPUnblockForm( $ip, $id, $reason ) {
$this->ip = $ip;
$this->id = $id;
$this->reason = $reason;
}
@ -64,13 +67,27 @@ class IPUnblockForm {
}
$token = htmlspecialchars( $wgUser->editToken() );
$addressPart = false;
if ( $this->id ) {
$block = Block::newFromID( $this->id );
if ( $block ) {
$encName = htmlspecialchars( $block->getRedactedName() );
$encId = htmlspecialchars( $this->id );
$addressPart = $encName . "<input type='hidden' name=\"id\" value=\"$encId\" />";
}
}
if ( !$addressPart ) {
$addressPart = "<input tabindex='1' type='text' size='20' " .
"name=\"wpUnblockAddress\" value=\"" . htmlspecialchars( $this->ip ) . "\" />";
}
$wgOut->addHTML( "
<form id=\"unblockip\" method=\"post\" action=\"{$action}\">
<table border='0'>
<tr>
<td align='right'>{$ipa}:</td>
<td align='left'>
<input tabindex='1' type='text' size='20' name=\"wpUnblockAddress\" value=\"" . htmlspecialchars( $this->ip ) . "\" />
{$addressPart}
</td>
</tr>
<tr>
@ -94,27 +111,46 @@ class IPUnblockForm {
function doSubmit() {
global $wgOut;
$block = new Block();
$this->ip = trim( $this->ip );
if ( $this->ip{0} == "#" ) {
$block->mId = substr( $this->ip, 1 );
if ( $this->id ) {
$block = Block::newFromID( $this->id );
if ( $block ) {
$this->ip = $block->getRedactedName();
}
} else {
$block->mAddress = $this->ip;
$block = new Block();
$this->ip = trim( $this->ip );
if ( substr( $this->ip, 0, 1 ) == "#" ) {
$id = substr( $this->ip, 1 );
$block = Block::newFromID( $id );
} else {
$block = Block::newFromDB( $this->ip );
if ( !$block ) {
$block = null;
}
}
}
$success = false;
if ( $block ) {
# Delete block
if ( $block->delete() ) {
# Make log entry
$log = new LogPage( 'block' );
$log->addEntry( 'unblock', Title::makeTitle( NS_USER, $this->ip ), $this->reason );
$success = true;
}
}
# Delete block (if it exists)
# We should probably check for errors rather than just declaring success
$block->delete();
# Make log entry
$log = new LogPage( 'block' );
$log->addEntry( 'unblock', Title::makeTitle( NS_USER, $this->ip ), $this->reason );
# Report to the user
$titleObj = Title::makeTitle( NS_SPECIAL, "Ipblocklist" );
$success = $titleObj->getFullURL( "action=success&ip=" . urlencode( $this->ip ) );
$wgOut->redirect( $success );
if ( $success ) {
# Report to the user
$titleObj = Title::makeTitle( NS_SPECIAL, "Ipblocklist" );
$success = $titleObj->getFullURL( "action=success&successip=" . urlencode( $this->ip ) );
$wgOut->redirect( $success );
} else {
if ( !$this->ip && $this->id ) {
$this->ip = '#' . $this->id;
}
$this->showForm( wfMsg( 'ipb_cant_unblock', htmlspecialchars( $this->id ) ) );
}
}
function showList( $msg ) {
@ -124,29 +160,43 @@ class IPUnblockForm {
if ( "" != $msg ) {
$wgOut->setSubtitle( $msg );
}
global $wgRequest;
list( $this->limit, $this->offset ) = $wgRequest->getLimitOffset();
$this->counter = 0;
$paging = '<p>' . wfViewPrevNext( $this->offset, $this->limit,
Title::makeTitle( NS_SPECIAL, 'Ipblocklist' ),
'ip=' . urlencode( $this->ip ) ) . "</p>\n";
$wgOut->addHTML( $paging );
$search = $this->searchForm();
$wgOut->addHTML( $search );
$wgOut->addHTML( "<ul>" );
if( !Block::enumBlocks( array( &$this, "addRow" ), 0 ) ) {
// FIXME hack to solve #bug 1487
$wgOut->addHTML( '<li>'.wfMsgHtml( 'ipblocklistempty' ).'</li>' );
// Purge expired entries on one in every 10 queries
if ( !mt_rand( 0, 10 ) ) {
Block::purgeExpired();
}
$wgOut->addHTML( "</ul>\n" );
$wgOut->addHTML( $paging );
$conds = array();
if ( $this->ip == '' ) {
// No extra conditions
} elseif ( substr( $this->ip, 0, 1 ) == '#' ) {
$conds['ipb_id'] = substr( $this->ip, 1 );
} elseif ( wfIP2Unsigned( $this->ip ) !== false ) {
$conds['ipb_address'] = $this->ip;
$conds['ipb_auto'] = 0;
} else {
$user = User::newFromName( $this->ip );
if ( ( $id = $user->getID() ) != 0 ) {
$conds['ipb_user'] = $id;
}
}
$pager = new IPBlocklistPager( $this, $conds );
$s = $pager->getNavigationBar() .
$this->searchForm();
if ( $pager->getNumRows() ) {
$s .= "<ul>" .
$pager->getBody() .
"</ul>";
} else {
$s .= '<p>' . wfMsgHTML( 'ipblocklistempty' ) . '</p>';
}
$s .= $pager->getNavigationBar();
$wgOut->addHTML( $s );
}
function searchForm() {
global $wgTitle;
global $wgTitle, $wgRequest;
return
wfElement( 'form', array(
'action' => $wgTitle->getLocalUrl() ),
@ -158,7 +208,7 @@ class IPUnblockForm {
wfElement( 'input', array(
'type' => 'hidden',
'name' => 'limit',
'value' => $this->limit ) ).
'value' => $wgRequest->getText( 'limit' ) ) ) .
wfElement( 'input', array(
'name' => 'ip',
'value' => $this->ip ) ) .
@ -171,33 +221,10 @@ class IPUnblockForm {
/**
* Callback function to output a block
*/
function addRow( $block, $tag ) {
global $wgOut, $wgUser, $wgLang;
function formatRow( $block ) {
global $wgUser, $wgLang;
if( $this->ip != '' ) {
if( $block->mAuto ) {
if( stristr( $block->mId, $this->ip ) == false ) {
return;
}
} else {
if( stristr( $block->mAddress, $this->ip ) == false ) {
return;
}
}
}
// Loading blocks is fast; displaying them is slow.
// Quick hack for paging.
$this->counter++;
if( $this->counter <= $this->offset ) {
return;
}
if( $this->counter - $this->offset > $this->limit ) {
return;
}
$fname = 'IPUnblockForm-addRow';
wfProfileIn( $fname );
wfProfileIn( __METHOD__ );
static $sk=null, $msg=null;
@ -205,14 +232,15 @@ class IPUnblockForm {
$sk = $wgUser->getSkin();
if( is_null( $msg ) ) {
$msg = array();
foreach( array( 'infiniteblock', 'expiringblock', 'contribslink', 'unblocklink' ) as $key ) {
$keys = array( 'infiniteblock', 'expiringblock', 'contribslink', 'unblocklink',
'anononlyblock', 'createaccountblock' );
foreach( $keys as $key ) {
$msg[$key] = wfMsgHtml( $key );
}
$msg['blocklistline'] = wfMsg( 'blocklistline' );
$msg['contribslink'] = wfMsg( 'contribslink' );
}
# Prepare links to the blocker's user and talk pages
$blocker_name = $block->getByName();
$blocker = $sk->MakeLinkObj( Title::makeTitle( NS_USER, $blocker_name ), $blocker_name );
@ -220,35 +248,101 @@ class IPUnblockForm {
# Prepare links to the block target's user and contribs. pages (as applicable, don't do it for autoblocks)
if( $block->mAuto ) {
$target = '#' . $block->mId; # Hide the IP addresses of auto-blocks; privacy
$target = $block->getRedactedName(); # Hide the IP addresses of auto-blocks; privacy
} else {
$target = $sk->makeLinkObj( Title::makeTitle( NS_USER, $block->mAddress ), $block->mAddress );
$target .= ' (' . $sk->makeKnownLinkObj( Title::makeTitle( NS_SPECIAL, 'Contributions' ), $msg['contribslink'], 'target=' . urlencode( $block->mAddress ) ) . ')';
}
# Prep the address for the unblock link, masking autoblocks as before
$addr = $block->mAuto ? '#' . $block->mId : $block->mAddress;
$formattedTime = $wgLang->timeanddate( $block->mTimestamp, true );
if ( $block->mExpiry === "" ) {
$formattedExpiry = $msg['infiniteblock'];
$properties = array();
if ( $block->mExpiry === "" || $block->mExpiry === Block::infinity() ) {
$properties[] = $msg['infiniteblock'];
} else {
$formattedExpiry = wfMsgReplaceArgs( $msg['expiringblock'],
$properties[] = wfMsgReplaceArgs( $msg['expiringblock'],
array( $wgLang->timeanddate( $block->mExpiry, true ) ) );
}
if ( $block->mAnonOnly ) {
$properties[] = $msg['anononlyblock'];
}
if ( $block->mCreateAccount ) {
$properties[] = $msg['createaccountblock'];
}
$properties = implode( ', ', $properties );
$line = wfMsgReplaceArgs( $msg['blocklistline'], array( $formattedTime, $blocker, $target, $formattedExpiry ) );
$line = wfMsgReplaceArgs( $msg['blocklistline'], array( $formattedTime, $blocker, $target, $properties ) );
$wgOut->addHTML( "<li>{$line}" );
$s = "<li>{$line}";
if ( $wgUser->isAllowed('block') ) {
$titleObj = Title::makeTitle( NS_SPECIAL, "Ipblocklist" );
$wgOut->addHTML( ' (' . $sk->makeKnownLinkObj($titleObj, $msg['unblocklink'], 'action=unblock&ip=' . urlencode( $addr ) ) . ')' );
$s .= ' (' . $sk->makeKnownLinkObj($titleObj, $msg['unblocklink'], 'action=unblock&id=' . urlencode( $block->mId ) ) . ')';
}
$wgOut->addHTML( $sk->commentBlock( $block->mReason ) );
$wgOut->addHTML( "</li>\n" );
wfProfileOut( $fname );
$s .= $sk->commentBlock( $block->mReason );
$s .= "</li>\n";
wfProfileOut( __METHOD__ );
return $s;
}
}
class IPBlocklistPager extends ReverseChronologicalPager {
public $mForm, $mConds;
function __construct( $form, $conds = array() ) {
$this->mForm = $form;
$this->mConds = $conds;
parent::__construct();
}
function getStartBody() {
wfProfileIn( __METHOD__ );
# Do a link batch query
$this->mResult->seek( 0 );
$lb = new LinkBatch;
/*
while ( $row = $this->mResult->fetchObject() ) {
$lb->addObj( Title::makeTitleSafe( NS_USER, $row->user_name ) );
$lb->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->user_name ) );
$lb->addObj( Title::makeTitleSafe( NS_USER, $row->ipb_address ) );
$lb->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->ipb_address ) );
}*/
# Faster way
# Usernames and titles are in fact related by a simple substitution of space -> underscore
# The last few lines of Title::secureAndSplit() tell the story.
while ( $row = $this->mResult->fetchObject() ) {
$name = str_replace( ' ', '_', $row->user_name );
$lb->add( NS_USER, $name );
$lb->add( NS_USER_TALK, $name );
$name = str_replace( ' ', '_', $row->ipb_address );
$lb->add( NS_USER, $name );
$lb->add( NS_USER_TALK, $name );
}
$lb->execute();
wfProfileOut( __METHOD__ );
return '';
}
function formatRow( $row ) {
$block = new Block;
$block->initFromRow( $row );
return $this->mForm->formatRow( $block );
}
function getQueryInfo() {
$conds = $this->mConds;
$conds[] = 'ipb_expiry>' . $this->mDb->addQuotes( $this->mDb->timestamp() );
$conds[] = 'ipb_by=user_id';
return array(
'tables' => array( 'ipblocks', 'user' ),
'fields' => 'ipblocks.*,user_name',
'conds' => $conds,
);
}
function getIndexField() {
return 'ipb_timestamp';
}
}

View file

@ -470,6 +470,27 @@ class LoginForm {
$wgOut->returnToMain( false );
}
/** */
function userBlockedMessage() {
global $wgOut;
# Let's be nice about this, it's likely that this feature will be used
# for blocking large numbers of innocent people, e.g. range blocks on
# schools. Don't blame it on the user. There's a small chance that it
# really is the user's fault, i.e. the username is blocked and they
# haven't bothered to log out before trying to create an account to
# evade it, but we'll leave that to their guilty conscience to figure
# out.
$wgOut->setPageTitle( wfMsg( 'cantcreateaccounttitle' ) );
$wgOut->setRobotpolicy( 'noindex,nofollow' );
$wgOut->setArticleRelated( false );
$ip = wfGetIP();
$wgOut->addWikiText( wfMsg( 'cantcreateaccounttext', $ip ) );
$wgOut->returnToMain( false );
}
/**
* @private
*/
@ -477,9 +498,14 @@ class LoginForm {
global $wgUser, $wgOut, $wgAllowRealName, $wgEnableEmail;
global $wgCookiePrefix, $wgAuth, $wgLoginLanguageSelector;
if ( $this->mType == 'signup' && !$wgUser->isAllowedToCreateAccount() ) {
$this->userNotPrivilegedMessage();
return;
if ( $this->mType == 'signup' ) {
if ( !$wgUser->isAllowed( 'createaccount' ) ) {
$this->userNotPrivilegedMessage();
return;
} elseif ( $wgUser->isBlockedFromCreateAccount() ) {
$this->userBlockedMessage();
return;
}
}
if ( '' == $this->mName ) {
@ -570,7 +596,7 @@ class LoginForm {
function showCreateOrLoginLink( &$user ) {
if( $this->mType == 'signup' ) {
return( true );
} elseif( $user->isAllowedToCreateAccount() ) {
} elseif( $user->isAllowed( 'createaccount' ) ) {
return( true );
} else {
return( false );

View file

@ -24,6 +24,7 @@ class User {
*/
var $mBlockedby; //!<
var $mBlockreason; //!<
var $mBlock; //!<
var $mDataLoaded; //!<
var $mEmail; //!<
var $mEmailAuthenticated; //!<
@ -114,8 +115,6 @@ class User {
*/
function __sleep() {
return array(
'mBlockedby',
'mBlockreason',
'mDataLoaded',
'mEmail',
'mEmailAuthenticated',
@ -436,16 +435,17 @@ class User {
$ip = wfGetIP();
# User/IP blocking
$block = new Block();
$block->fromMaster( !$bFromSlave );
if ( $block->load( $ip , $this->mId ) ) {
$this->mBlock = new Block();
$this->mBlock->fromMaster( !$bFromSlave );
if ( $this->mBlock->load( $ip , $this->mId ) ) {
wfDebug( "$fname: Found block.\n" );
$this->mBlockedby = $block->mBy;
$this->mBlockreason = $block->mReason;
$this->mBlockedby = $this->mBlock->mBy;
$this->mBlockreason = $this->mBlock->mReason;
if ( $this->isLoggedIn() ) {
$this->spreadBlock();
}
} else {
$this->mBlock = null;
wfDebug( "$fname: No block.\n" );
}
@ -694,6 +694,8 @@ class User {
$user->loadFromDatabase();
} else {
wfDebug( "User::loadFromSession() got from cache!\n" );
# Set block status to unloaded, that should be loaded every time
$user->mBlockedby = -1;
}
if ( isset( $_SESSION['wsToken'] ) ) {
@ -1532,13 +1534,13 @@ class User {
}
$userblock = Block::newFromDB( '', $this->mId );
if ( !$userblock->isValid() ) {
if ( !$userblock ) {
return;
}
# Check if this IP address is already blocked
$ipblock = Block::newFromDB( wfGetIP() );
if ( $ipblock->isValid() ) {
if ( $ipblock ) {
# If the user is already blocked. Then check if the autoblock would
# excede the user block. If it would excede, then do nothing, else
# prolong block time
@ -1612,8 +1614,13 @@ class User {
return $confstr;
}
function isBlockedFromCreateAccount() {
$this->getBlockedStatus();
return $this->mBlock && $this->mBlock->mCreateAccount;
}
function isAllowedToCreateAccount() {
return $this->isAllowed( 'createaccount' ) && !$this->isBlocked();
return $this->isAllowed( 'createaccount' ) && !$this->isBlockedFromCreateAccount();
}
/**

View file

@ -555,6 +555,10 @@ the text into a text file and save it for later.</strong>',
'nocreatetitle' => 'Page creation limited',
'nocreatetext' => 'This site has restricted the ability to create new pages.
You can go back and edit an existing page, or [[Special:Userlogin|log in or create an account]].',
'cantcreateaccounttitle' => 'Can\'t create account',
'cantcreateaccounttext' => 'Account creation from this IP address (<b>$1</b>) has been blocked.
This is probably due to persistent vandalism from your school or Internet service
provider. ',
# History pages
#
@ -1271,6 +1275,8 @@ pages that were vandalized).",
'ipadressorusername' => 'IP Address or username',
'ipbexpiry' => 'Expiry',
'ipbreason' => 'Reason',
'ipbanononly' => 'Block anonymous users only',
'ipbcreateaccount' => 'Prevent account creation',
'ipbsubmit' => 'Block this user',
'ipbother' => 'Other time',
'ipboptions' => '2 hours:2 hours,1 day:1 day,3 days:3 days,1 week:1 week,2 weeks:2 weeks,1 month:1 month,3 months:3 months,6 months:6 months,1 year:1 year,infinite:infinite',
@ -1288,6 +1294,8 @@ to a previously blocked IP address or username.',
'blocklistline' => "$1, $2 blocked $3 ($4)",
'infiniteblock' => 'infinite',
'expiringblock' => 'expires $1',
'anononlyblock' => 'anon. only',
'createaccountblock' => 'account creation blocked',
'ipblocklistempty' => 'The blocklist is empty.',
'blocklink' => 'block',
'unblocklink' => 'unblock',
@ -1301,8 +1309,10 @@ the list of currently operational bans and blocks.',
'unblocklogentry' => 'unblocked $1',
'range_block_disabled' => 'The sysop ability to create range blocks is disabled.',
'ipb_expiry_invalid' => 'Expiry time invalid.',
'ipb_already_blocked' => '"$1" is already blocked',
'ip_range_invalid' => 'Invalid IP range.',
'proxyblocker' => 'Proxy blocker',
'ipb_cant_unblock' => 'Error: Block ID $1 not found. It may have been unblocked already.',
'proxyblockreason' => 'Your IP address has been blocked because it is an open proxy. Please contact your Internet service provider or tech support and inform them of this serious security problem.',
'proxyblocksuccess' => 'Done.',
'sorbs' => 'SORBS DNSBL',

View file

@ -0,0 +1,43 @@
-- Add extra option fields to the ipblocks table, add some extra indexes,
-- convert infinity values in ipb_expiry to something that sorts better,
-- extend ipb_address and range fields, add a unique index for block conflict
-- detection.
-- Conflicts in the new unique index can be handled by creating a new
-- table and inserting into it instead of doing an ALTER TABLE.
DROP TABLE IF EXISTS /*$wgDBprefix*/ipblocks_newunique;
CREATE TABLE /*$wgDBprefix*/ipblocks_newunique (
ipb_id int(8) NOT NULL auto_increment,
ipb_address tinyblob NOT NULL default '',
ipb_user int(8) unsigned NOT NULL default '0',
ipb_by int(8) unsigned NOT NULL default '0',
ipb_reason tinyblob NOT NULL default '',
ipb_timestamp char(14) binary NOT NULL default '',
ipb_auto boolean NOT NULL default 0,
ipb_anon_only boolean NOT NULL default 0,
ipb_create_account boolean NOT NULL default 1,
ipb_expiry char(14) binary NOT NULL default '',
ipb_range_start tinyblob NOT NULL default '',
ipb_range_end tinyblob NOT NULL default '',
PRIMARY KEY ipb_id (ipb_id),
UNIQUE INDEX ipb_address_unique (ipb_address(255), ipb_user, ipb_auto),
INDEX ipb_user (ipb_user),
INDEX ipb_range (ipb_range_start(8), ipb_range_end(8)),
INDEX ipb_timestamp (ipb_timestamp),
INDEX ipb_expiry (ipb_expiry)
) TYPE=InnoDB;
INSERT IGNORE INTO /*$wgDBprefix*/ipblocks_newunique
(ipb_id, ipb_address, ipb_user, ipb_by, ipb_reason, ipb_timestamp, ipb_auto, ipb_expiry, ipb_range_start, ipb_range_end)
SELECT ipb_id, ipb_address, ipb_user, ipb_by, ipb_reason, ipb_timestamp, ipb_auto, ipb_expiry, ipb_range_start, ipb_range_end
FROM /*$wgDBprefix*/ipblocks;
DROP TABLE IF EXISTS /*$wgDBprefix*/ipblocks_old;
RENAME TABLE /*$wgDBprefix*/ipblocks TO /*$wgDBprefix*/ipblocks_old;
RENAME TABLE /*$wgDBprefix*/ipblocks_newunique TO /*$wgDBprefix*/ipblocks;

View file

@ -583,8 +583,14 @@ CREATE TABLE /*$wgDBprefix*/ipblocks (
-- Indicates that the IP address was banned because a banned
-- user accessed a page through it. If this is 1, ipb_address
-- will be hidden, and the block identified by block ID number.
ipb_auto tinyint(1) NOT NULL default '0',
ipb_auto boolean NOT NULL default '0',
-- If set to 1, block applies only to logged-out users
ipb_anon_only boolean NOT NULL default 0,
-- Block prevents account creation from matching IP addresses
ipb_create_account boolean NOT NULL default 1,
-- Time at which the block will expire.
ipb_expiry char(14) binary NOT NULL default '',
@ -594,9 +600,15 @@ CREATE TABLE /*$wgDBprefix*/ipblocks (
ipb_range_end varchar(32) NOT NULL default '',
PRIMARY KEY ipb_id (ipb_id),
INDEX ipb_address (ipb_address),
-- Unique index to support "user already blocked" messages
-- Any new options which prevent collisions should be included
UNIQUE INDEX ipb_address (ipb_address(255), ipb_user, ipb_auto, ipb_anon_only),
INDEX ipb_user (ipb_user),
INDEX ipb_range (ipb_range_start(8), ipb_range_end(8))
INDEX ipb_range (ipb_range_start(8), ipb_range_end(8)),
INDEX ipb_timestamp (ipb_timestamp),
INDEX ipb_expiry (ipb_expiry)
) TYPE=InnoDB, DEFAULT CHARSET=utf8;
@ -1006,4 +1018,4 @@ CREATE TABLE /*$wgDBprefix*/querycache_info (
UNIQUE KEY ( qci_type )
) TYPE=InnoDB;
) TYPE=InnoDB;

View file

@ -552,7 +552,7 @@ CREATE TABLE /*$wgDBprefix*/ipblocks (
ipb_id int(8) NOT NULL auto_increment,
-- Blocked IP address in dotted-quad form or user name.
ipb_address varchar(40) binary NOT NULL default '',
ipb_address tinyblob NOT NULL default '',
-- Blocked user ID or 0 for IP blocks.
ipb_user int(8) unsigned NOT NULL default '0',
@ -570,20 +570,32 @@ CREATE TABLE /*$wgDBprefix*/ipblocks (
-- Indicates that the IP address was banned because a banned
-- user accessed a page through it. If this is 1, ipb_address
-- will be hidden, and the block identified by block ID number.
ipb_auto tinyint(1) NOT NULL default '0',
ipb_auto boolean NOT NULL default 0,
-- If set to 1, block applies only to logged-out users
ipb_anon_only boolean NOT NULL default 0,
-- Block prevents account creation from matching IP addresses
ipb_create_account boolean NOT NULL default 1,
-- Time at which the block will expire.
ipb_expiry char(14) binary NOT NULL default '',
-- Start and end of an address range, in hexadecimal
-- Size chosen to allow IPv6
ipb_range_start varchar(32) NOT NULL default '',
ipb_range_end varchar(32) NOT NULL default '',
ipb_range_start tinyblob NOT NULL default '',
ipb_range_end tinyblob NOT NULL default '',
PRIMARY KEY ipb_id (ipb_id),
INDEX ipb_address (ipb_address),
-- Unique index to support "user already blocked" messages
-- Any new options which prevent collisions should be included
UNIQUE INDEX ipb_address (ipb_address(255), ipb_user, ipb_auto, ipb_anon_only),
INDEX ipb_user (ipb_user),
INDEX ipb_range (ipb_range_start(8), ipb_range_end(8))
INDEX ipb_range (ipb_range_start(8), ipb_range_end(8)),
INDEX ipb_timestamp (ipb_timestamp),
INDEX ipb_expiry (ipb_expiry)
) TYPE=InnoDB;
@ -913,10 +925,10 @@ CREATE TABLE /*$wgDBprefix*/objectcache (
-- Cache of interwiki transclusion
--
CREATE TABLE /*$wgDBprefix*/transcache (
tc_url VARCHAR(255) NOT NULL,
tc_contents TEXT,
tc_time INT NOT NULL,
UNIQUE INDEX tc_url_idx(tc_url)
tc_url VARCHAR(255) NOT NULL,
tc_contents TEXT,
tc_time INT NOT NULL,
UNIQUE INDEX tc_url_idx(tc_url)
) TYPE=InnoDB;
CREATE TABLE /*$wgDBprefix*/logging (
@ -951,14 +963,14 @@ CREATE TABLE /*$wgDBprefix*/logging (
) TYPE=InnoDB;
CREATE TABLE /*$wgDBprefix*/trackbacks (
tb_id integer AUTO_INCREMENT PRIMARY KEY,
tb_page integer REFERENCES page(page_id) ON DELETE CASCADE,
tb_title varchar(255) NOT NULL,
tb_url varchar(255) NOT NULL,
tb_ex text,
tb_name varchar(255),
tb_id integer AUTO_INCREMENT PRIMARY KEY,
tb_page integer REFERENCES page(page_id) ON DELETE CASCADE,
tb_title varchar(255) NOT NULL,
tb_url varchar(255) NOT NULL,
tb_ex text,
tb_name varchar(255),
INDEX (tb_page)
INDEX (tb_page)
) TYPE=InnoDB;
@ -986,13 +998,15 @@ CREATE TABLE /*$wgDBprefix*/job (
-- Details of updates to cached special pages
CREATE TABLE /*$wgDBprefix*/querycache_info (
-- Special page name
-- Corresponds to a qc_type value
qci_type varchar(32) NOT NULL default '',
-- Special page name
-- Corresponds to a qc_type value
qci_type varchar(32) NOT NULL default '',
-- Timestamp of last update
qci_timestamp char(14) NOT NULL default '19700101000000',
-- Timestamp of last update
qci_timestamp char(14) NOT NULL default '19700101000000',
UNIQUE KEY ( qci_type )
UNIQUE KEY ( qci_type )
) TYPE=InnoDB;
-- vim: sw=2 sts=2 et

View file

@ -56,6 +56,7 @@ $wgNewFields = array(
array( 'interwiki', 'iw_trans', 'patch-interwiki-trans.sql' ),
array( 'ipblocks', 'ipb_range_start', 'patch-ipb_range_start.sql' ),
array( 'site_stats', 'ss_images', 'patch-ss_images.sql' ),
array( 'ipblocks', 'ipb_anon_only', 'patch-ipb_anon_only.sql' ),
);
function rename_table( $from, $to, $patch ) {