* Allow blocks on anonymous users only.
authorTim Starling <tstarling@users.mediawiki.org>
Mon, 10 Jul 2006 06:30:03 +0000 (06:30 +0000)
committerTim Starling <tstarling@users.mediawiki.org>
Mon, 10 Jul 2006 06:30:03 +0000 (06:30 +0000)
* 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.

RELEASE-NOTES
includes/Block.php
includes/SpecialBlockip.php
includes/SpecialIpblocklist.php
includes/SpecialUserlogin.php
includes/User.php
languages/Messages.php
maintenance/archives/patch-ipb_anon_only.sql [new file with mode: 0644]
maintenance/mysql5/tables.sql
maintenance/tables.sql
maintenance/updaters.inc

index fbd4018..c4959bf 100644 (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 ==
 
index 26fa444..7bee08f 100644 (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;
                }
-               $dbw =& wfGetDB( DB_MASTER );
-
-               if ( $this->mAddress == '' ) {
-                       $condition = array( 'ipb_id' => $this->mId );
-               } else {
-                       $condition = array( 'ipb_address' => $this->mAddress );
+               if ( !$this->mId ) {
+                       throw new MWException( "Block::delete() now requires that the mId member be filled\n" );
                }
-               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;
+                */
+       }
+
 }
 ?>
index b3f67ab..4ca376b 100644 (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' );
index a4f960a..024385b 100644 (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 );
+               // Purge expired entries on one in every 10 queries
+               if ( !mt_rand( 0, 10 ) ) {
+                       Block::purgeExpired();
+               }
 
-               $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>' );
+               $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>';
                }
-               $wgOut->addHTML( "</ul>\n" );
-               $wgOut->addHTML( $paging );
+               $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;
-
-               if( $this->ip != '' ) {
-                       if( $block->mAuto ) {
-                               if( stristr( $block->mId, $this->ip ) == false ) {
-                                       return;
-                               }
-                       } else {
-                               if( stristr( $block->mAddress, $this->ip ) == false ) {
-                                       return;
-                               }
-                       }
-               }
+       function formatRow( $block ) {
+               global $wgUser, $wgLang;
 
-               // 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';
        }
 }
 
index 4ee35b1..5f72ee9 100644 (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 );
index f242628..3d4d6fc 100644 (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();
        }
 
        /**
index d7b2e8a..1f02b2b 100644 (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',
diff --git a/maintenance/archives/patch-ipb_anon_only.sql b/maintenance/archives/patch-ipb_anon_only.sql
new file mode 100644 (file)
index 0000000..ea7d889
--- /dev/null
@@ -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;
+
index cc6818d..0ee6e2a 100644 (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;
\ No newline at end of file
+) TYPE=InnoDB;
index 288d4a0..b00115e 100644 (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),
-
-       INDEX (tb_page)
+  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)
 ) 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
index 164a00c..0174168 100644 (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 ) {