r15482 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r15481‎ | r15482 | r15483 >
Date:06:30, 10 July 2006
Author:tstarling
Status:old
Tags:
Comment:
* 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.
Modified paths:
  • /trunk/phase3/RELEASE-NOTES (modified) (history)
  • /trunk/phase3/includes/Block.php (modified) (history)
  • /trunk/phase3/includes/SpecialBlockip.php (modified) (history)
  • /trunk/phase3/includes/SpecialIpblocklist.php (modified) (history)
  • /trunk/phase3/includes/SpecialUserlogin.php (modified) (history)
  • /trunk/phase3/includes/User.php (modified) (history)
  • /trunk/phase3/languages/Messages.php (modified) (history)
  • /trunk/phase3/maintenance/archives/patch-ipb_anon_only.sql (added) (history)
  • /trunk/phase3/maintenance/mysql5/tables.sql (modified) (history)
  • /trunk/phase3/maintenance/tables.sql (modified) (history)
  • /trunk/phase3/maintenance/updaters.inc (modified) (history)

Diff [purge]

Index: trunk/phase3/maintenance/archives/patch-ipb_anon_only.sql
@@ -0,0 +1,43 @@
 2+-- Add extra option fields to the ipblocks table, add some extra indexes,
 3+-- convert infinity values in ipb_expiry to something that sorts better,
 4+-- extend ipb_address and range fields, add a unique index for block conflict
 5+-- detection.
 6+
 7+-- Conflicts in the new unique index can be handled by creating a new
 8+-- table and inserting into it instead of doing an ALTER TABLE.
 9+
 10+
 11+DROP TABLE IF EXISTS /*$wgDBprefix*/ipblocks_newunique;
 12+
 13+CREATE TABLE /*$wgDBprefix*/ipblocks_newunique (
 14+ ipb_id int(8) NOT NULL auto_increment,
 15+ ipb_address tinyblob NOT NULL default '',
 16+ ipb_user int(8) unsigned NOT NULL default '0',
 17+ ipb_by int(8) unsigned NOT NULL default '0',
 18+ ipb_reason tinyblob NOT NULL default '',
 19+ ipb_timestamp char(14) binary NOT NULL default '',
 20+ ipb_auto boolean NOT NULL default 0,
 21+ ipb_anon_only boolean NOT NULL default 0,
 22+ ipb_create_account boolean NOT NULL default 1,
 23+ ipb_expiry char(14) binary NOT NULL default '',
 24+ ipb_range_start tinyblob NOT NULL default '',
 25+ ipb_range_end tinyblob NOT NULL default '',
 26+
 27+ PRIMARY KEY ipb_id (ipb_id),
 28+ UNIQUE INDEX ipb_address_unique (ipb_address(255), ipb_user, ipb_auto),
 29+ INDEX ipb_user (ipb_user),
 30+ INDEX ipb_range (ipb_range_start(8), ipb_range_end(8)),
 31+ INDEX ipb_timestamp (ipb_timestamp),
 32+ INDEX ipb_expiry (ipb_expiry)
 33+
 34+) TYPE=InnoDB;
 35+
 36+INSERT IGNORE INTO /*$wgDBprefix*/ipblocks_newunique
 37+ (ipb_id, ipb_address, ipb_user, ipb_by, ipb_reason, ipb_timestamp, ipb_auto, ipb_expiry, ipb_range_start, ipb_range_end)
 38+ SELECT ipb_id, ipb_address, ipb_user, ipb_by, ipb_reason, ipb_timestamp, ipb_auto, ipb_expiry, ipb_range_start, ipb_range_end
 39+ FROM /*$wgDBprefix*/ipblocks;
 40+
 41+DROP TABLE IF EXISTS /*$wgDBprefix*/ipblocks_old;
 42+RENAME TABLE /*$wgDBprefix*/ipblocks TO /*$wgDBprefix*/ipblocks_old;
 43+RENAME TABLE /*$wgDBprefix*/ipblocks_newunique TO /*$wgDBprefix*/ipblocks;
 44+
Property changes on: trunk/phase3/maintenance/archives/patch-ipb_anon_only.sql
___________________________________________________________________
Name: svn:eol-style
145 + native
Index: trunk/phase3/maintenance/updaters.inc
@@ -56,6 +56,7 @@
5757 array( 'interwiki', 'iw_trans', 'patch-interwiki-trans.sql' ),
5858 array( 'ipblocks', 'ipb_range_start', 'patch-ipb_range_start.sql' ),
5959 array( 'site_stats', 'ss_images', 'patch-ss_images.sql' ),
 60+ array( 'ipblocks', 'ipb_anon_only', 'patch-ipb_anon_only.sql' ),
6061 );
6162
6263 function rename_table( $from, $to, $patch ) {
Index: trunk/phase3/maintenance/mysql5/tables.sql
@@ -583,8 +583,14 @@
584584 -- Indicates that the IP address was banned because a banned
585585 -- user accessed a page through it. If this is 1, ipb_address
586586 -- will be hidden, and the block identified by block ID number.
587 - ipb_auto tinyint(1) NOT NULL default '0',
 587+ ipb_auto boolean NOT NULL default '0',
588588
 589+ -- If set to 1, block applies only to logged-out users
 590+ ipb_anon_only boolean NOT NULL default 0,
 591+
 592+ -- Block prevents account creation from matching IP addresses
 593+ ipb_create_account boolean NOT NULL default 1,
 594+
589595 -- Time at which the block will expire.
590596 ipb_expiry char(14) binary NOT NULL default '',
591597
@@ -594,9 +600,15 @@
595601 ipb_range_end varchar(32) NOT NULL default '',
596602
597603 PRIMARY KEY ipb_id (ipb_id),
598 - INDEX ipb_address (ipb_address),
 604+
 605+ -- Unique index to support "user already blocked" messages
 606+ -- Any new options which prevent collisions should be included
 607+ UNIQUE INDEX ipb_address (ipb_address(255), ipb_user, ipb_auto, ipb_anon_only),
 608+
599609 INDEX ipb_user (ipb_user),
600 - INDEX ipb_range (ipb_range_start(8), ipb_range_end(8))
 610+ INDEX ipb_range (ipb_range_start(8), ipb_range_end(8)),
 611+ INDEX ipb_timestamp (ipb_timestamp),
 612+ INDEX ipb_expiry (ipb_expiry)
601613
602614 ) TYPE=InnoDB, DEFAULT CHARSET=utf8;
603615
@@ -1006,4 +1018,4 @@
10071019
10081020 UNIQUE KEY ( qci_type )
10091021
1010 -) TYPE=InnoDB;
\ No newline at end of file
 1022+) TYPE=InnoDB;
Index: trunk/phase3/maintenance/tables.sql
@@ -552,7 +552,7 @@
553553 ipb_id int(8) NOT NULL auto_increment,
554554
555555 -- Blocked IP address in dotted-quad form or user name.
556 - ipb_address varchar(40) binary NOT NULL default '',
 556+ ipb_address tinyblob NOT NULL default '',
557557
558558 -- Blocked user ID or 0 for IP blocks.
559559 ipb_user int(8) unsigned NOT NULL default '0',
@@ -570,20 +570,32 @@
571571 -- Indicates that the IP address was banned because a banned
572572 -- user accessed a page through it. If this is 1, ipb_address
573573 -- will be hidden, and the block identified by block ID number.
574 - ipb_auto tinyint(1) NOT NULL default '0',
 574+ ipb_auto boolean NOT NULL default 0,
 575+
 576+ -- If set to 1, block applies only to logged-out users
 577+ ipb_anon_only boolean NOT NULL default 0,
 578+
 579+ -- Block prevents account creation from matching IP addresses
 580+ ipb_create_account boolean NOT NULL default 1,
575581
576582 -- Time at which the block will expire.
577583 ipb_expiry char(14) binary NOT NULL default '',
578584
579585 -- Start and end of an address range, in hexadecimal
580586 -- Size chosen to allow IPv6
581 - ipb_range_start varchar(32) NOT NULL default '',
582 - ipb_range_end varchar(32) NOT NULL default '',
 587+ ipb_range_start tinyblob NOT NULL default '',
 588+ ipb_range_end tinyblob NOT NULL default '',
583589
584590 PRIMARY KEY ipb_id (ipb_id),
585 - INDEX ipb_address (ipb_address),
 591+
 592+ -- Unique index to support "user already blocked" messages
 593+ -- Any new options which prevent collisions should be included
 594+ UNIQUE INDEX ipb_address (ipb_address(255), ipb_user, ipb_auto, ipb_anon_only),
 595+
586596 INDEX ipb_user (ipb_user),
587 - INDEX ipb_range (ipb_range_start(8), ipb_range_end(8))
 597+ INDEX ipb_range (ipb_range_start(8), ipb_range_end(8)),
 598+ INDEX ipb_timestamp (ipb_timestamp),
 599+ INDEX ipb_expiry (ipb_expiry)
588600
589601 ) TYPE=InnoDB;
590602
@@ -913,10 +925,10 @@
914926 -- Cache of interwiki transclusion
915927 --
916928 CREATE TABLE /*$wgDBprefix*/transcache (
917 - tc_url VARCHAR(255) NOT NULL,
918 - tc_contents TEXT,
919 - tc_time INT NOT NULL,
920 - UNIQUE INDEX tc_url_idx(tc_url)
 929+ tc_url VARCHAR(255) NOT NULL,
 930+ tc_contents TEXT,
 931+ tc_time INT NOT NULL,
 932+ UNIQUE INDEX tc_url_idx(tc_url)
921933 ) TYPE=InnoDB;
922934
923935 CREATE TABLE /*$wgDBprefix*/logging (
@@ -951,14 +963,14 @@
952964 ) TYPE=InnoDB;
953965
954966 CREATE TABLE /*$wgDBprefix*/trackbacks (
955 - tb_id integer AUTO_INCREMENT PRIMARY KEY,
956 - tb_page integer REFERENCES page(page_id) ON DELETE CASCADE,
957 - tb_title varchar(255) NOT NULL,
958 - tb_url varchar(255) NOT NULL,
959 - tb_ex text,
960 - tb_name varchar(255),
 967+ tb_id integer AUTO_INCREMENT PRIMARY KEY,
 968+ tb_page integer REFERENCES page(page_id) ON DELETE CASCADE,
 969+ tb_title varchar(255) NOT NULL,
 970+ tb_url varchar(255) NOT NULL,
 971+ tb_ex text,
 972+ tb_name varchar(255),
961973
962 - INDEX (tb_page)
 974+ INDEX (tb_page)
963975 ) TYPE=InnoDB;
964976
965977
@@ -986,13 +998,15 @@
987999 -- Details of updates to cached special pages
9881000 CREATE TABLE /*$wgDBprefix*/querycache_info (
9891001
990 - -- Special page name
991 - -- Corresponds to a qc_type value
992 - qci_type varchar(32) NOT NULL default '',
 1002+ -- Special page name
 1003+ -- Corresponds to a qc_type value
 1004+ qci_type varchar(32) NOT NULL default '',
9931005
994 - -- Timestamp of last update
995 - qci_timestamp char(14) NOT NULL default '19700101000000',
 1006+ -- Timestamp of last update
 1007+ qci_timestamp char(14) NOT NULL default '19700101000000',
9961008
997 - UNIQUE KEY ( qci_type )
 1009+UNIQUE KEY ( qci_type )
9981010
9991011 ) TYPE=InnoDB;
 1012+
 1013+-- vim: sw=2 sts=2 et
Index: trunk/phase3/includes/User.php
@@ -24,6 +24,7 @@
2525 */
2626 var $mBlockedby; //!<
2727 var $mBlockreason; //!<
 28+ var $mBlock; //!<
2829 var $mDataLoaded; //!<
2930 var $mEmail; //!<
3031 var $mEmailAuthenticated; //!<
@@ -114,8 +115,6 @@
115116 */
116117 function __sleep() {
117118 return array(
118 -'mBlockedby',
119 -'mBlockreason',
120119 'mDataLoaded',
121120 'mEmail',
122121 'mEmailAuthenticated',
@@ -436,16 +435,17 @@
437436 $ip = wfGetIP();
438437
439438 # User/IP blocking
440 - $block = new Block();
441 - $block->fromMaster( !$bFromSlave );
442 - if ( $block->load( $ip , $this->mId ) ) {
 439+ $this->mBlock = new Block();
 440+ $this->mBlock->fromMaster( !$bFromSlave );
 441+ if ( $this->mBlock->load( $ip , $this->mId ) ) {
443442 wfDebug( "$fname: Found block.\n" );
444 - $this->mBlockedby = $block->mBy;
445 - $this->mBlockreason = $block->mReason;
 443+ $this->mBlockedby = $this->mBlock->mBy;
 444+ $this->mBlockreason = $this->mBlock->mReason;
446445 if ( $this->isLoggedIn() ) {
447446 $this->spreadBlock();
448447 }
449448 } else {
 449+ $this->mBlock = null;
450450 wfDebug( "$fname: No block.\n" );
451451 }
452452
@@ -694,6 +694,8 @@
695695 $user->loadFromDatabase();
696696 } else {
697697 wfDebug( "User::loadFromSession() got from cache!\n" );
 698+ # Set block status to unloaded, that should be loaded every time
 699+ $user->mBlockedby = -1;
698700 }
699701
700702 if ( isset( $_SESSION['wsToken'] ) ) {
@@ -1532,13 +1534,13 @@
15331535 }
15341536
15351537 $userblock = Block::newFromDB( '', $this->mId );
1536 - if ( !$userblock->isValid() ) {
 1538+ if ( !$userblock ) {
15371539 return;
15381540 }
15391541
15401542 # Check if this IP address is already blocked
15411543 $ipblock = Block::newFromDB( wfGetIP() );
1542 - if ( $ipblock->isValid() ) {
 1544+ if ( $ipblock ) {
15431545 # If the user is already blocked. Then check if the autoblock would
15441546 # excede the user block. If it would excede, then do nothing, else
15451547 # prolong block time
@@ -1612,8 +1614,13 @@
16131615 return $confstr;
16141616 }
16151617
 1618+ function isBlockedFromCreateAccount() {
 1619+ $this->getBlockedStatus();
 1620+ return $this->mBlock && $this->mBlock->mCreateAccount;
 1621+ }
 1622+
16161623 function isAllowedToCreateAccount() {
1617 - return $this->isAllowed( 'createaccount' ) && !$this->isBlocked();
 1624+ return $this->isAllowed( 'createaccount' ) && !$this->isBlockedFromCreateAccount();
16181625 }
16191626
16201627 /**
Index: trunk/phase3/includes/SpecialUserlogin.php
@@ -470,6 +470,27 @@
471471 $wgOut->returnToMain( false );
472472 }
473473
 474+ /** */
 475+ function userBlockedMessage() {
 476+ global $wgOut;
 477+
 478+ # Let's be nice about this, it's likely that this feature will be used
 479+ # for blocking large numbers of innocent people, e.g. range blocks on
 480+ # schools. Don't blame it on the user. There's a small chance that it
 481+ # really is the user's fault, i.e. the username is blocked and they
 482+ # haven't bothered to log out before trying to create an account to
 483+ # evade it, but we'll leave that to their guilty conscience to figure
 484+ # out.
 485+
 486+ $wgOut->setPageTitle( wfMsg( 'cantcreateaccounttitle' ) );
 487+ $wgOut->setRobotpolicy( 'noindex,nofollow' );
 488+ $wgOut->setArticleRelated( false );
 489+
 490+ $ip = wfGetIP();
 491+ $wgOut->addWikiText( wfMsg( 'cantcreateaccounttext', $ip ) );
 492+ $wgOut->returnToMain( false );
 493+ }
 494+
474495 /**
475496 * @private
476497 */
@@ -477,9 +498,14 @@
478499 global $wgUser, $wgOut, $wgAllowRealName, $wgEnableEmail;
479500 global $wgCookiePrefix, $wgAuth, $wgLoginLanguageSelector;
480501
481 - if ( $this->mType == 'signup' && !$wgUser->isAllowedToCreateAccount() ) {
482 - $this->userNotPrivilegedMessage();
483 - return;
 502+ if ( $this->mType == 'signup' ) {
 503+ if ( !$wgUser->isAllowed( 'createaccount' ) ) {
 504+ $this->userNotPrivilegedMessage();
 505+ return;
 506+ } elseif ( $wgUser->isBlockedFromCreateAccount() ) {
 507+ $this->userBlockedMessage();
 508+ return;
 509+ }
484510 }
485511
486512 if ( '' == $this->mName ) {
@@ -570,7 +596,7 @@
571597 function showCreateOrLoginLink( &$user ) {
572598 if( $this->mType == 'signup' ) {
573599 return( true );
574 - } elseif( $user->isAllowedToCreateAccount() ) {
 600+ } elseif( $user->isAllowed( 'createaccount' ) ) {
575601 return( true );
576602 } else {
577603 return( false );
Index: trunk/phase3/includes/SpecialBlockip.php
@@ -46,6 +46,15 @@
4747 $this->BlockReason = $wgRequest->getText( 'wpBlockReason' );
4848 $this->BlockExpiry = $wgRequest->getVal( 'wpBlockExpiry', wfMsg('ipbotheroption') );
4949 $this->BlockOther = $wgRequest->getVal( 'wpBlockOther', '' );
 50+ $this->BlockAnonOnly = $wgRequest->getBool( 'wpAnonOnly' );
 51+
 52+ # Unchecked checkboxes are not included in the form data at all, so having one
 53+ # that is true by default is a bit tricky
 54+ if ( $wgRequest->wasPosted() ) {
 55+ $this->BlockCreateAccount = $wgRequest->getBool( 'wpCreateAccount', false );
 56+ } else {
 57+ $this->BlockCreateAccount = $wgRequest->getBool( 'wpCreateAccount', true );
 58+ }
5059 }
5160
5261 function showForm( $err ) {
@@ -64,6 +73,8 @@
6574 $mIpbothertime = wfMsgHtml( 'ipbotheroption' );
6675 $mIpbreason = wfMsgHtml( 'ipbreason' );
6776 $mIpbsubmit = wfMsgHtml( 'ipbsubmit' );
 77+ $mIpbanononly = wfMsgHtml( 'ipbanononly' );
 78+ $mIpbcreateaccount = wfMsgHtml( 'ipbcreateaccount' );
6879 $titleObj = Title::makeTitle( NS_SPECIAL, 'Blockip' );
6980 $action = $titleObj->escapeLocalURL( "action=submit" );
7081
@@ -77,6 +88,8 @@
7889 $scBlockReason = htmlspecialchars( $this->BlockReason );
7990 $scBlockOtherTime = htmlspecialchars( $this->BlockOther );
8091 $scBlockExpiryOptions = htmlspecialchars( wfMsgForContent( 'ipboptions' ) );
 92+ $anonOnlyChecked = $this->BlockAnonOnly ? 'checked' : '';
 93+ $createAccountChecked = $this->BlockCreateAccount ? 'checked' : '';
8194
8295 $showblockoptions = $scBlockExpiryOptions != '-';
8396 if (!$showblockoptions)
@@ -102,7 +115,7 @@
103116 <tr>
104117 <td align=\"right\">{$mIpaddress}:</td>
105118 <td align=\"left\">
106 - <input tabindex='1' type='text' size='20' name=\"wpBlockAddress\" value=\"{$scBlockAddress}\" />
 119+ <input tabindex='1' type='text' size='40' name=\"wpBlockAddress\" value=\"{$scBlockAddress}\" />
107120 </td>
108121 </tr>
109122 <tr>");
@@ -133,6 +146,24 @@
134147 <tr>
135148 <td>&nbsp;</td>
136149 <td align=\"left\">
 150+ <label>
 151+ <input type='checkbox' name='wpAnonOnly' value='1' $anonOnlyChecked />
 152+ {$mIpbanononly}
 153+ </label>
 154+ </td>
 155+ </tr>
 156+ <tr>
 157+ <td>&nbsp;</td>
 158+ <td align=\"left\">
 159+ <label>
 160+ <input type='checkbox' name='wpCreateAccount' value='1' $createAccountChecked />
 161+ {$mIpbcreateaccount}
 162+ </label>
 163+ </td>
 164+ </tr>
 165+ <tr>
 166+ <td style='padding-top: 1em'>&nbsp;</td>
 167+ <td style='padding-top: 1em' align=\"left\">
137168 <input tabindex='4' type='submit' name=\"wpBlock\" value=\"{$mIpbsubmit}\" />
138169 </td>
139170 </tr>
@@ -188,7 +219,7 @@
189220 }
190221
191222 if ( $expirestr == 'infinite' || $expirestr == 'indefinite' ) {
192 - $expiry = '';
 223+ $expiry = Block::infinity();
193224 } else {
194225 # Convert GNU-style date, on error returns -1 for PHP <5.1 and false for PHP >=5.1
195226 $expiry = strtotime( $expirestr );
@@ -199,20 +230,24 @@
200231 }
201232
202233 $expiry = wfTimestamp( TS_MW, $expiry );
203 -
204234 }
205235
206236 # Create block
207237 # Note: for a user block, ipb_address is only for display purposes
208238
209 - $ban = new Block( $this->BlockAddress, $userId, $wgUser->getID(),
210 - $this->BlockReason, wfTimestampNow(), 0, $expiry );
 239+ $block = new Block( $this->BlockAddress, $userId, $wgUser->getID(),
 240+ $this->BlockReason, wfTimestampNow(), 0, $expiry, $this->BlockAnonOnly,
 241+ $this->BlockCreateAccount );
211242
212 - if (wfRunHooks('BlockIp', array(&$ban, &$wgUser))) {
 243+ if (wfRunHooks('BlockIp', array(&$block, &$wgUser))) {
213244
214 - $ban->insert();
 245+ if ( !$block->insert() ) {
 246+ $this->showForm( wfMsg( 'ipb_already_blocked',
 247+ htmlspecialchars( $this->BlockAddress ) ) );
 248+ return;
 249+ }
215250
216 - wfRunHooks('BlockIpComplete', array($ban, $wgUser));
 251+ wfRunHooks('BlockIpComplete', array($block, $wgUser));
217252
218253 # Make log entry
219254 $log = new LogPage( 'block' );
Index: trunk/phase3/includes/SpecialIpblocklist.php
@@ -12,13 +12,15 @@
1313 global $wgUser, $wgOut, $wgRequest;
1414
1515 $ip = $wgRequest->getVal( 'wpUnblockAddress', $wgRequest->getVal( 'ip' ) );
 16+ $id = $wgRequest->getVal( 'id' );
1617 $reason = $wgRequest->getText( 'wpUnblockReason' );
1718 $action = $wgRequest->getText( 'action' );
 19+ $successip = $wgRequest->getVal( 'successip' );
1820
19 - $ipu = new IPUnblockForm( $ip, $reason );
 21+ $ipu = new IPUnblockForm( $ip, $id, $reason );
2022
2123 if ( "success" == $action ) {
22 - $ipu->showList( $wgOut->parse( wfMsg( 'unblocked', $ip ) ) );
 24+ $ipu->showList( $wgOut->parse( wfMsg( 'unblocked', $successip ) ) );
2325 } else if ( "submit" == $action && $wgRequest->wasPosted() &&
2426 $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) {
2527 if ( ! $wgUser->isAllowed('block') ) {
@@ -39,10 +41,11 @@
4042 * @subpackage SpecialPage
4143 */
4244 class IPUnblockForm {
43 - var $ip, $reason;
 45+ var $ip, $reason, $id;
4446
45 - function IPUnblockForm( $ip, $reason ) {
 47+ function IPUnblockForm( $ip, $id, $reason ) {
4648 $this->ip = $ip;
 49+ $this->id = $id;
4750 $this->reason = $reason;
4851 }
4952
@@ -64,13 +67,27 @@
6568 }
6669 $token = htmlspecialchars( $wgUser->editToken() );
6770
 71+ $addressPart = false;
 72+ if ( $this->id ) {
 73+ $block = Block::newFromID( $this->id );
 74+ if ( $block ) {
 75+ $encName = htmlspecialchars( $block->getRedactedName() );
 76+ $encId = htmlspecialchars( $this->id );
 77+ $addressPart = $encName . "<input type='hidden' name=\"id\" value=\"$encId\" />";
 78+ }
 79+ }
 80+ if ( !$addressPart ) {
 81+ $addressPart = "<input tabindex='1' type='text' size='20' " .
 82+ "name=\"wpUnblockAddress\" value=\"" . htmlspecialchars( $this->ip ) . "\" />";
 83+ }
 84+
6885 $wgOut->addHTML( "
6986 <form id=\"unblockip\" method=\"post\" action=\"{$action}\">
7087 <table border='0'>
7188 <tr>
7289 <td align='right'>{$ipa}:</td>
7390 <td align='left'>
74 - <input tabindex='1' type='text' size='20' name=\"wpUnblockAddress\" value=\"" . htmlspecialchars( $this->ip ) . "\" />
 91+ {$addressPart}
7592 </td>
7693 </tr>
7794 <tr>
@@ -94,27 +111,46 @@
95112 function doSubmit() {
96113 global $wgOut;
97114
98 - $block = new Block();
99 - $this->ip = trim( $this->ip );
 115+ if ( $this->id ) {
 116+ $block = Block::newFromID( $this->id );
 117+ if ( $block ) {
 118+ $this->ip = $block->getRedactedName();
 119+ }
 120+ } else {
 121+ $block = new Block();
 122+ $this->ip = trim( $this->ip );
 123+ if ( substr( $this->ip, 0, 1 ) == "#" ) {
 124+ $id = substr( $this->ip, 1 );
 125+ $block = Block::newFromID( $id );
 126+ } else {
 127+ $block = Block::newFromDB( $this->ip );
 128+ if ( !$block ) {
 129+ $block = null;
 130+ }
 131+ }
 132+ }
 133+ $success = false;
 134+ if ( $block ) {
 135+ # Delete block
 136+ if ( $block->delete() ) {
 137+ # Make log entry
 138+ $log = new LogPage( 'block' );
 139+ $log->addEntry( 'unblock', Title::makeTitle( NS_USER, $this->ip ), $this->reason );
 140+ $success = true;
 141+ }
 142+ }
100143
101 - if ( $this->ip{0} == "#" ) {
102 - $block->mId = substr( $this->ip, 1 );
 144+ if ( $success ) {
 145+ # Report to the user
 146+ $titleObj = Title::makeTitle( NS_SPECIAL, "Ipblocklist" );
 147+ $success = $titleObj->getFullURL( "action=success&successip=" . urlencode( $this->ip ) );
 148+ $wgOut->redirect( $success );
103149 } else {
104 - $block->mAddress = $this->ip;
 150+ if ( !$this->ip && $this->id ) {
 151+ $this->ip = '#' . $this->id;
 152+ }
 153+ $this->showForm( wfMsg( 'ipb_cant_unblock', htmlspecialchars( $this->id ) ) );
105154 }
106 -
107 - # Delete block (if it exists)
108 - # We should probably check for errors rather than just declaring success
109 - $block->delete();
110 -
111 - # Make log entry
112 - $log = new LogPage( 'block' );
113 - $log->addEntry( 'unblock', Title::makeTitle( NS_USER, $this->ip ), $this->reason );
114 -
115 - # Report to the user
116 - $titleObj = Title::makeTitle( NS_SPECIAL, "Ipblocklist" );
117 - $success = $titleObj->getFullURL( "action=success&ip=" . urlencode( $this->ip ) );
118 - $wgOut->redirect( $success );
119155 }
120156
121157 function showList( $msg ) {
@@ -124,29 +160,43 @@
125161 if ( "" != $msg ) {
126162 $wgOut->setSubtitle( $msg );
127163 }
128 - global $wgRequest;
129 - list( $this->limit, $this->offset ) = $wgRequest->getLimitOffset();
130 - $this->counter = 0;
131164
132 - $paging = '<p>' . wfViewPrevNext( $this->offset, $this->limit,
133 - Title::makeTitle( NS_SPECIAL, 'Ipblocklist' ),
134 - 'ip=' . urlencode( $this->ip ) ) . "</p>\n";
135 - $wgOut->addHTML( $paging );
 165+ // Purge expired entries on one in every 10 queries
 166+ if ( !mt_rand( 0, 10 ) ) {
 167+ Block::purgeExpired();
 168+ }
136169
137 - $search = $this->searchForm();
138 - $wgOut->addHTML( $search );
139 -
140 - $wgOut->addHTML( "<ul>" );
141 - if( !Block::enumBlocks( array( &$this, "addRow" ), 0 ) ) {
142 - // FIXME hack to solve #bug 1487
143 - $wgOut->addHTML( '<li>'.wfMsgHtml( 'ipblocklistempty' ).'</li>' );
 170+ $conds = array();
 171+ if ( $this->ip == '' ) {
 172+ // No extra conditions
 173+ } elseif ( substr( $this->ip, 0, 1 ) == '#' ) {
 174+ $conds['ipb_id'] = substr( $this->ip, 1 );
 175+ } elseif ( wfIP2Unsigned( $this->ip ) !== false ) {
 176+ $conds['ipb_address'] = $this->ip;
 177+ $conds['ipb_auto'] = 0;
 178+ } else {
 179+ $user = User::newFromName( $this->ip );
 180+ if ( ( $id = $user->getID() ) != 0 ) {
 181+ $conds['ipb_user'] = $id;
 182+ }
144183 }
145 - $wgOut->addHTML( "</ul>\n" );
146 - $wgOut->addHTML( $paging );
 184+
 185+ $pager = new IPBlocklistPager( $this, $conds );
 186+ $s = $pager->getNavigationBar() .
 187+ $this->searchForm();
 188+ if ( $pager->getNumRows() ) {
 189+ $s .= "<ul>" .
 190+ $pager->getBody() .
 191+ "</ul>";
 192+ } else {
 193+ $s .= '<p>' . wfMsgHTML( 'ipblocklistempty' ) . '</p>';
 194+ }
 195+ $s .= $pager->getNavigationBar();
 196+ $wgOut->addHTML( $s );
147197 }
148198
149199 function searchForm() {
150 - global $wgTitle;
 200+ global $wgTitle, $wgRequest;
151201 return
152202 wfElement( 'form', array(
153203 'action' => $wgTitle->getLocalUrl() ),
@@ -158,7 +208,7 @@
159209 wfElement( 'input', array(
160210 'type' => 'hidden',
161211 'name' => 'limit',
162 - 'value' => $this->limit ) ).
 212+ 'value' => $wgRequest->getText( 'limit' ) ) ) .
163213 wfElement( 'input', array(
164214 'name' => 'ip',
165215 'value' => $this->ip ) ) .
@@ -171,48 +221,26 @@
172222 /**
173223 * Callback function to output a block
174224 */
175 - function addRow( $block, $tag ) {
176 - global $wgOut, $wgUser, $wgLang;
 225+ function formatRow( $block ) {
 226+ global $wgUser, $wgLang;
177227
178 - if( $this->ip != '' ) {
179 - if( $block->mAuto ) {
180 - if( stristr( $block->mId, $this->ip ) == false ) {
181 - return;
182 - }
183 - } else {
184 - if( stristr( $block->mAddress, $this->ip ) == false ) {
185 - return;
186 - }
187 - }
188 - }
 228+ wfProfileIn( __METHOD__ );
189229
190 - // Loading blocks is fast; displaying them is slow.
191 - // Quick hack for paging.
192 - $this->counter++;
193 - if( $this->counter <= $this->offset ) {
194 - return;
195 - }
196 - if( $this->counter - $this->offset > $this->limit ) {
197 - return;
198 - }
199 -
200 - $fname = 'IPUnblockForm-addRow';
201 - wfProfileIn( $fname );
202 -
203230 static $sk=null, $msg=null;
204231
205232 if( is_null( $sk ) )
206233 $sk = $wgUser->getSkin();
207234 if( is_null( $msg ) ) {
208235 $msg = array();
209 - foreach( array( 'infiniteblock', 'expiringblock', 'contribslink', 'unblocklink' ) as $key ) {
 236+ $keys = array( 'infiniteblock', 'expiringblock', 'contribslink', 'unblocklink',
 237+ 'anononlyblock', 'createaccountblock' );
 238+ foreach( $keys as $key ) {
210239 $msg[$key] = wfMsgHtml( $key );
211240 }
212241 $msg['blocklistline'] = wfMsg( 'blocklistline' );
213242 $msg['contribslink'] = wfMsg( 'contribslink' );
214243 }
215244
216 -
217245 # Prepare links to the blocker's user and talk pages
218246 $blocker_name = $block->getByName();
219247 $blocker = $sk->MakeLinkObj( Title::makeTitle( NS_USER, $blocker_name ), $blocker_name );
@@ -220,36 +248,102 @@
221249
222250 # Prepare links to the block target's user and contribs. pages (as applicable, don't do it for autoblocks)
223251 if( $block->mAuto ) {
224 - $target = '#' . $block->mId; # Hide the IP addresses of auto-blocks; privacy
 252+ $target = $block->getRedactedName(); # Hide the IP addresses of auto-blocks; privacy
225253 } else {
226254 $target = $sk->makeLinkObj( Title::makeTitle( NS_USER, $block->mAddress ), $block->mAddress );
227255 $target .= ' (' . $sk->makeKnownLinkObj( Title::makeTitle( NS_SPECIAL, 'Contributions' ), $msg['contribslink'], 'target=' . urlencode( $block->mAddress ) ) . ')';
228256 }
229257
230 - # Prep the address for the unblock link, masking autoblocks as before
231 - $addr = $block->mAuto ? '#' . $block->mId : $block->mAddress;
232 -
233258 $formattedTime = $wgLang->timeanddate( $block->mTimestamp, true );
234259
235 - if ( $block->mExpiry === "" ) {
236 - $formattedExpiry = $msg['infiniteblock'];
 260+ $properties = array();
 261+ if ( $block->mExpiry === "" || $block->mExpiry === Block::infinity() ) {
 262+ $properties[] = $msg['infiniteblock'];
237263 } else {
238 - $formattedExpiry = wfMsgReplaceArgs( $msg['expiringblock'],
 264+ $properties[] = wfMsgReplaceArgs( $msg['expiringblock'],
239265 array( $wgLang->timeanddate( $block->mExpiry, true ) ) );
240266 }
 267+ if ( $block->mAnonOnly ) {
 268+ $properties[] = $msg['anononlyblock'];
 269+ }
 270+ if ( $block->mCreateAccount ) {
 271+ $properties[] = $msg['createaccountblock'];
 272+ }
 273+ $properties = implode( ', ', $properties );
241274
242 - $line = wfMsgReplaceArgs( $msg['blocklistline'], array( $formattedTime, $blocker, $target, $formattedExpiry ) );
 275+ $line = wfMsgReplaceArgs( $msg['blocklistline'], array( $formattedTime, $blocker, $target, $properties ) );
243276
244 - $wgOut->addHTML( "<li>{$line}" );
 277+ $s = "<li>{$line}";
245278
246279 if ( $wgUser->isAllowed('block') ) {
247280 $titleObj = Title::makeTitle( NS_SPECIAL, "Ipblocklist" );
248 - $wgOut->addHTML( ' (' . $sk->makeKnownLinkObj($titleObj, $msg['unblocklink'], 'action=unblock&ip=' . urlencode( $addr ) ) . ')' );
 281+ $s .= ' (' . $sk->makeKnownLinkObj($titleObj, $msg['unblocklink'], 'action=unblock&id=' . urlencode( $block->mId ) ) . ')';
249282 }
250 - $wgOut->addHTML( $sk->commentBlock( $block->mReason ) );
251 - $wgOut->addHTML( "</li>\n" );
252 - wfProfileOut( $fname );
 283+ $s .= $sk->commentBlock( $block->mReason );
 284+ $s .= "</li>\n";
 285+ wfProfileOut( __METHOD__ );
 286+ return $s;
253287 }
254288 }
255289
 290+class IPBlocklistPager extends ReverseChronologicalPager {
 291+ public $mForm, $mConds;
 292+
 293+ function __construct( $form, $conds = array() ) {
 294+ $this->mForm = $form;
 295+ $this->mConds = $conds;
 296+ parent::__construct();
 297+ }
 298+
 299+ function getStartBody() {
 300+ wfProfileIn( __METHOD__ );
 301+ # Do a link batch query
 302+ $this->mResult->seek( 0 );
 303+ $lb = new LinkBatch;
 304+
 305+ /*
 306+ while ( $row = $this->mResult->fetchObject() ) {
 307+ $lb->addObj( Title::makeTitleSafe( NS_USER, $row->user_name ) );
 308+ $lb->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->user_name ) );
 309+ $lb->addObj( Title::makeTitleSafe( NS_USER, $row->ipb_address ) );
 310+ $lb->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->ipb_address ) );
 311+ }*/
 312+ # Faster way
 313+ # Usernames and titles are in fact related by a simple substitution of space -> underscore
 314+ # The last few lines of Title::secureAndSplit() tell the story.
 315+ while ( $row = $this->mResult->fetchObject() ) {
 316+ $name = str_replace( ' ', '_', $row->user_name );
 317+ $lb->add( NS_USER, $name );
 318+ $lb->add( NS_USER_TALK, $name );
 319+ $name = str_replace( ' ', '_', $row->ipb_address );
 320+ $lb->add( NS_USER, $name );
 321+ $lb->add( NS_USER_TALK, $name );
 322+ }
 323+ $lb->execute();
 324+ wfProfileOut( __METHOD__ );
 325+ return '';
 326+ }
 327+
 328+ function formatRow( $row ) {
 329+ $block = new Block;
 330+ $block->initFromRow( $row );
 331+ return $this->mForm->formatRow( $block );
 332+ }
 333+
 334+ function getQueryInfo() {
 335+ $conds = $this->mConds;
 336+ $conds[] = 'ipb_expiry>' . $this->mDb->addQuotes( $this->mDb->timestamp() );
 337+ $conds[] = 'ipb_by=user_id';
 338+ return array(
 339+ 'tables' => array( 'ipblocks', 'user' ),
 340+ 'fields' => 'ipblocks.*,user_name',
 341+ 'conds' => $conds,
 342+ );
 343+ }
 344+
 345+ function getIndexField() {
 346+ return 'ipb_timestamp';
 347+ }
 348+}
 349+
256350 ?>
Index: trunk/phase3/includes/Block.php
@@ -9,7 +9,6 @@
1010 * All the functions in this class assume the object is either explicitly
1111 * loaded or filled. It is not load-on-demand. There are no accessors.
1212 *
13 - * To use delete(), you only need to fill $mAddress
1413 * Globals used: $wgAutoblockExpiry, $wgAntiLockFlags
1514 *
1615 * @todo This could be used everywhere, but it isn't.
@@ -18,7 +17,7 @@
1918 class Block
2019 {
2120 /* public*/ var $mAddress, $mUser, $mBy, $mReason, $mTimestamp, $mAuto, $mId, $mExpiry,
22 - $mRangeStart, $mRangeEnd;
 21+ $mRangeStart, $mRangeEnd, $mAnonOnly;
2322 /* private */ var $mNetworkBits, $mIntegerAddr, $mForUpdate, $mFromMaster, $mByName;
2423
2524 const EB_KEEP_EXPIRED = 1;
@@ -26,19 +25,18 @@
2726 const EB_RANGE_ONLY = 4;
2827
2928 function Block( $address = '', $user = '', $by = 0, $reason = '',
30 - $timestamp = '' , $auto = 0, $expiry = '' )
 29+ $timestamp = '' , $auto = 0, $expiry = '', $anonOnly = 0, $createAccount = 0 )
3130 {
 31+ $this->mId = 0;
3232 $this->mAddress = $address;
3333 $this->mUser = $user;
3434 $this->mBy = $by;
3535 $this->mReason = $reason;
3636 $this->mTimestamp = wfTimestamp(TS_MW,$timestamp);
3737 $this->mAuto = $auto;
38 - if( empty( $expiry ) ) {
39 - $this->mExpiry = $expiry;
40 - } else {
41 - $this->mExpiry = wfTimestamp( TS_MW, $expiry );
42 - }
 38+ $this->mAnonOnly = $anonOnly;
 39+ $this->mCreateAccount = $createAccount;
 40+ $this->mExpiry = self::decodeExpiry( $expiry );
4341
4442 $this->mForUpdate = false;
4543 $this->mFromMaster = false;
@@ -46,19 +44,36 @@
4745 $this->initialiseRange();
4846 }
4947
50 - /*static*/ function newFromDB( $address, $user = 0, $killExpired = true )
 48+ static function newFromDB( $address, $user = 0, $killExpired = true )
5149 {
52 - $ban = new Block();
53 - $ban->load( $address, $user, $killExpired );
54 - return $ban;
 50+ $block = new Block();
 51+ $block->load( $address, $user, $killExpired );
 52+ if ( $block->isValid() ) {
 53+ return $block;
 54+ } else {
 55+ return null;
 56+ }
5557 }
5658
 59+ static function newFromID( $id )
 60+ {
 61+ $dbr =& wfGetDB( DB_SLAVE );
 62+ $res = $dbr->resultObject( $dbr->select( 'ipblocks', '*',
 63+ array( 'ipb_id' => $id ), __METHOD__ ) );
 64+ $block = new Block;
 65+ if ( $block->loadFromResult( $res ) ) {
 66+ return $block;
 67+ } else {
 68+ return null;
 69+ }
 70+ }
 71+
5772 function clear()
5873 {
5974 $this->mAddress = $this->mReason = $this->mTimestamp = '';
60 - $this->mUser = $this->mBy = 0;
 75+ $this->mId = $this->mAnonOnly = $this->mCreateAccount =
 76+ $this->mAuto = $this->mUser = $this->mBy = 0;
6177 $this->mByName = false;
62 -
6378 }
6479
6580 /**
@@ -70,56 +85,80 @@
7186 if ( $this->mForUpdate || $this->mFromMaster ) {
7287 $db =& wfGetDB( DB_MASTER );
7388 if ( !$this->mForUpdate || ($wgAntiLockFlags & ALF_NO_BLOCK_LOCK) ) {
74 - $options = '';
 89+ $options = array();
7590 } else {
76 - $options = 'FOR UPDATE';
 91+ $options = array( 'FOR UPDATE' );
7792 }
7893 } else {
7994 $db =& wfGetDB( DB_SLAVE );
80 - $options = '';
 95+ $options = array();
8196 }
8297 return $db;
8398 }
8499
85100 /**
86101 * Get a ban from the DB, with either the given address or the given username
 102+ *
 103+ * @param string $address The IP address of the user, or blank to skip IP blocks
 104+ * @param integer $user The user ID, or zero for anonymous users
 105+ * @param bool $killExpired Whether to delete expired rows while loading
 106+ *
87107 */
88108 function load( $address = '', $user = 0, $killExpired = true )
89109 {
90 - $fname = 'Block::load';
91110 wfDebug( "Block::load: '$address', '$user', $killExpired\n" );
92111
93 - $options = '';
 112+ $options = array();
94113 $db =& $this->getDBOptions( $options );
95114
96115 $ret = false;
97116 $killed = false;
98 - $ipblocks = $db->tableName( 'ipblocks' );
99117
100118 if ( 0 == $user && $address == '' ) {
101119 # Invalid user specification, not blocked
102120 $this->clear();
103121 return false;
104 - } elseif ( $address == '' ) {
105 - $sql = "SELECT * FROM $ipblocks WHERE ipb_user={$user} $options";
106 - } elseif ( $user == '' ) {
107 - $sql = "SELECT * FROM $ipblocks WHERE ipb_address=" . $db->addQuotes( $address ) . " $options";
108 - } elseif ( $options == '' ) {
109 - # If there are no options (e.g. FOR UPDATE), use a UNION
110 - # so that the query can make efficient use of indices
111 - $sql = "SELECT * FROM $ipblocks WHERE ipb_address='" . $db->strencode( $address ) .
112 - "' UNION SELECT * FROM $ipblocks WHERE ipb_user={$user}";
113 - } else {
114 - # If there are options, a UNION can not be used, use one
115 - # SELECT instead. Will do a full table scan.
116 - $sql = "SELECT * FROM $ipblocks WHERE (ipb_address='" . $db->strencode( $address ) .
117 - "' OR ipb_user={$user}) $options";
118122 }
119123
120 - $res = $db->query( $sql, $fname );
121 - if ( 0 != $db->numRows( $res ) ) {
 124+ # Try user block
 125+ if ( $user ) {
 126+ $res = $db->resultObject( $db->select( 'ipblocks', '*', array( 'ipb_user' => $user ),
 127+ __METHOD__, $options ) );
 128+ if ( $this->loadFromResult( $res, $killExpired ) ) {
 129+ return true;
 130+ }
 131+ }
 132+
 133+ # Try IP block
 134+ if ( $address ) {
 135+ $conds = array( 'ipb_address' => $address );
 136+ if ( $user ) {
 137+ $conds['ipb_anon_only'] = 0;
 138+ }
 139+ $res = $db->resultObject( $db->select( 'ipblocks', '*', $conds, __METHOD__, $options ) );
 140+ if ( $this->loadFromResult( $res, $killExpired ) ) {
 141+ return true;
 142+ }
 143+ }
 144+
 145+ # Try range block
 146+ if ( $this->loadRange( $address, $killExpired, $user == 0 ) ) {
 147+ return true;
 148+ }
 149+
 150+ # Give up
 151+ $this->clear();
 152+ return false;
 153+ }
 154+
 155+ /**
 156+ * Fill in member variables from a result wrapper
 157+ */
 158+ function loadFromResult( ResultWrapper $res, $killExpired = true ) {
 159+ $ret = false;
 160+ if ( 0 != $res->numRows() ) {
122161 # Get first block
123 - $row = $db->fetchObject( $res );
 162+ $row = $res->fetchObject();
124163 $this->initFromRow( $row );
125164
126165 if ( $killExpired ) {
@@ -127,7 +166,7 @@
128167 do {
129168 $killed = $this->deleteIfExpired();
130169 if ( $killed ) {
131 - $row = $db->fetchObject( $res );
 170+ $row = $res->fetchObject();
132171 if ( $row ) {
133172 $this->initFromRow( $row );
134173 }
@@ -135,26 +174,14 @@
136175 } while ( $killed && $row );
137176
138177 # If there were any left after the killing finished, return true
139 - if ( !$row ) {
140 - $ret = false;
141 - $this->clear();
142 - } else {
 178+ if ( $row ) {
143179 $ret = true;
144180 }
145181 } else {
146182 $ret = true;
147183 }
148184 }
149 - $db->freeResult( $res );
150 -
151 - # No blocks found yet? Try looking for range blocks
152 - if ( !$ret && $address != '' ) {
153 - $ret = $this->loadRange( $address, $killExpired );
154 - }
155 - if ( !$ret ) {
156 - $this->clear();
157 - }
158 -
 185+ $res->free();
159186 return $ret;
160187 }
161188
@@ -162,10 +189,8 @@
163190 * Search the database for any range blocks matching the given address, and
164191 * load the row if one is found.
165192 */
166 - function loadRange( $address, $killExpired = true )
 193+ function loadRange( $address, $killExpired = true, $isAnon = true )
167194 {
168 - $fname = 'Block::loadRange';
169 -
170195 $iaddr = wfIP2Hex( $address );
171196 if ( $iaddr === false ) {
172197 # Invalid address
@@ -176,27 +201,19 @@
177202 # Blocks should not cross a /16 boundary.
178203 $range = substr( $iaddr, 0, 4 );
179204
180 - $options = '';
 205+ $options = array();
181206 $db =& $this->getDBOptions( $options );
182 - $ipblocks = $db->tableName( 'ipblocks' );
183 - $sql = "SELECT * FROM $ipblocks WHERE ipb_range_start LIKE '$range%' ".
184 - "AND ipb_range_start <= '$iaddr' AND ipb_range_end >= '$iaddr' $options";
185 - $res = $db->query( $sql, $fname );
186 - $row = $db->fetchObject( $res );
187 -
188 - $success = false;
189 - if ( $row ) {
190 - # Found a row, initialise this object
191 - $this->initFromRow( $row );
192 -
193 - # Is it expired?
194 - if ( !$killExpired || !$this->deleteIfExpired() ) {
195 - # No, return true
196 - $success = true;
197 - }
 207+ $conds = array(
 208+ "ipb_range_start LIKE '$range%'",
 209+ "ipb_range_start <= '$iaddr'",
 210+ "ipb_range_end >= '$iaddr'"
 211+ );
 212+ if ( !$isAnon ) {
 213+ $conds['ipb_anon_only'] = 0;
198214 }
199215
200 - $db->freeResult( $res );
 216+ $res = $db->resultObject( $db->select( 'ipblocks', '*', $conds, __METHOD__, $options ) );
 217+ $success = $this->loadFromResult( $res, $killExpired );
201218 return $success;
202219 }
203220
@@ -220,10 +237,10 @@
221238 $this->mUser = $row->ipb_user;
222239 $this->mBy = $row->ipb_by;
223240 $this->mAuto = $row->ipb_auto;
 241+ $this->mAnonOnly = $row->ipb_anon_only;
 242+ $this->mCreateAccount = $row->ipb_create_account;
224243 $this->mId = $row->ipb_id;
225 - $this->mExpiry = $row->ipb_expiry ?
226 - wfTimestamp(TS_MW,$row->ipb_expiry) :
227 - $row->ipb_expiry;
 244+ $this->mExpiry = self::decodeExpiry( $row->ipb_expiry );
228245 if ( isset( $row->user_name ) ) {
229246 $this->mByName = $row->user_name;
230247 } else {
@@ -304,24 +321,27 @@
305322
306323 function delete()
307324 {
308 - $fname = 'Block::delete';
309325 if (wfReadOnly()) {
310 - return;
 326+ return false;
311327 }
 328+ if ( !$this->mId ) {
 329+ throw new MWException( "Block::delete() now requires that the mId member be filled\n" );
 330+ }
 331+
312332 $dbw =& wfGetDB( DB_MASTER );
313 -
314 - if ( $this->mAddress == '' ) {
315 - $condition = array( 'ipb_id' => $this->mId );
316 - } else {
317 - $condition = array( 'ipb_address' => $this->mAddress );
318 - }
319 - return( $dbw->delete( 'ipblocks', $condition, $fname ) > 0 ? true : false );
 333+ $dbw->delete( 'ipblocks', array( 'ipb_id' => $this->mId ), __METHOD__ );
 334+ return $dbw->affectedRows() > 0;
320335 }
321336
322337 function insert()
323338 {
324339 wfDebug( "Block::insert; timestamp {$this->mTimestamp}\n" );
325340 $dbw =& wfGetDB( DB_MASTER );
 341+ $dbw->begin();
 342+
 343+ # Don't collide with expired blocks
 344+ Block::purgeExpired();
 345+
326346 $ipb_id = $dbw->nextSequenceValue('ipblocks_ipb_id_val');
327347 $dbw->insert( 'ipblocks',
328348 array(
@@ -332,13 +352,16 @@
333353 'ipb_reason' => $this->mReason,
334354 'ipb_timestamp' => $dbw->timestamp($this->mTimestamp),
335355 'ipb_auto' => $this->mAuto,
336 - 'ipb_expiry' => $this->mExpiry ?
337 - $dbw->timestamp($this->mExpiry) :
338 - $this->mExpiry,
 356+ 'ipb_anon_only' => $this->mAnonOnly,
 357+ 'ipb_create_account' => $this->mCreateAccount,
 358+ 'ipb_expiry' => self::encodeExpiry( $this->mExpiry, $dbw ),
339359 'ipb_range_start' => $this->mRangeStart,
340360 'ipb_range_end' => $this->mRangeEnd,
341 - ), 'Block::insert'
 361+ ), 'Block::insert', array( 'IGNORE' )
342362 );
 363+ $affected = $dbw->affectedRows();
 364+ $dbw->commit();
 365+ return $affected;
343366 }
344367
345368 function deleteIfExpired()
@@ -417,13 +440,43 @@
418441 return wfSetVar( $this->mFromMaster, $x );
419442 }
420443
421 - /* static */ function getAutoblockExpiry( $timestamp )
 444+ function getRedactedName() {
 445+ if ( $this->mAuto ) {
 446+ return '#' . $this->mId;
 447+ } else {
 448+ return $this->mAddress;
 449+ }
 450+ }
 451+
 452+ /**
 453+ * Encode expiry for DB
 454+ */
 455+ static function encodeExpiry( $expiry, $db ) {
 456+ if ( $expiry == '' || $expiry == Block::infinity() ) {
 457+ return Block::infinity();
 458+ } else {
 459+ return $db->timestamp( $expiry );
 460+ }
 461+ }
 462+
 463+ /**
 464+ * Decode expiry which has come from the DB
 465+ */
 466+ static function decodeExpiry( $expiry ) {
 467+ if ( $expiry == '' || $expiry == Block::infinity() ) {
 468+ return Block::infinity();
 469+ } else {
 470+ return wfTimestamp( TS_MW, $expiry );
 471+ }
 472+ }
 473+
 474+ static function getAutoblockExpiry( $timestamp )
422475 {
423476 global $wgAutoblockExpiry;
424477 return wfTimestamp( TS_MW, wfTimestamp( TS_UNIX, $timestamp ) + $wgAutoblockExpiry );
425478 }
426479
427 - /* static */ function normaliseRange( $range )
 480+ static function normaliseRange( $range )
428481 {
429482 $parts = explode( '/', $range );
430483 if ( count( $parts ) == 2 ) {
@@ -436,5 +489,28 @@
437490 return $range;
438491 }
439492
 493+ /**
 494+ * Purge expired blocks from the ipblocks table
 495+ */
 496+ static function purgeExpired() {
 497+ $dbw =& wfGetDB( DB_MASTER );
 498+ $dbw->delete( 'ipblocks', array( 'ipb_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ), __METHOD__ );
 499+ }
 500+
 501+ static function infinity() {
 502+ # This is a special keyword for timestamps in PostgreSQL, and
 503+ # works with CHAR(14) as well because "i" sorts after all numbers.
 504+ return 'infinity';
 505+
 506+ /*
 507+ static $infinity;
 508+ if ( !isset( $infinity ) ) {
 509+ $dbr =& wfGetDB( DB_SLAVE );
 510+ $infinity = $dbr->bigTimestamp();
 511+ }
 512+ return $infinity;
 513+ */
 514+ }
 515+
440516 }
441517 ?>
Index: trunk/phase3/RELEASE-NOTES
@@ -25,10 +25,8 @@
2626
2727 == Major new features ==
2828
29 -* None!
 29+* (bug 550) Allow blocks on anonymous users only.
3030
31 -
32 -
3331 == Changes since 1.7 ==
3432
3533 * (bug 6562) Removed unmaintained ParserXml.php for now
@@ -36,6 +34,13 @@
3735 * (bug 6586) Regression in "unblocked" subtitle
3836 * Don't put empty-page message into view-source when page text is blank
3937 * (bug 6587) Remove redundant "allnonarticles" message
 38+* Block improvements: Allow blocks on anonymous users only. Optionally allow
 39+ or disallow account creation from blocked IP addresses. Prevent duplicate
 40+ blocks. Fixed the problem of expiry and unblocking erroneously affecting
 41+ multiple blocks. Fixed confusing lack of error message when a blocked user
 42+ attempts to create an account. Fixed inefficiency of Special:Ipblocklist in
 43+ the presence of large numbers of blocks; added indexes and implemented an
 44+ indexed pager.
4045
4146 == Languages updated ==
4247
Index: trunk/phase3/languages/Messages.php
@@ -555,6 +555,10 @@
556556 'nocreatetitle' => 'Page creation limited',
557557 'nocreatetext' => 'This site has restricted the ability to create new pages.
558558 You can go back and edit an existing page, or [[Special:Userlogin|log in or create an account]].',
 559+'cantcreateaccounttitle' => 'Can\'t create account',
 560+'cantcreateaccounttext' => 'Account creation from this IP address (<b>$1</b>) has been blocked.
 561+This is probably due to persistent vandalism from your school or Internet service
 562+provider. ',
559563
560564 # History pages
561565 #
@@ -1271,6 +1275,8 @@
12721276 'ipadressorusername' => 'IP Address or username',
12731277 'ipbexpiry' => 'Expiry',
12741278 'ipbreason' => 'Reason',
 1279+'ipbanononly' => 'Block anonymous users only',
 1280+'ipbcreateaccount' => 'Prevent account creation',
12751281 'ipbsubmit' => 'Block this user',
12761282 'ipbother' => 'Other time',
12771283 '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 @@
12891295 'blocklistline' => "$1, $2 blocked $3 ($4)",
12901296 'infiniteblock' => 'infinite',
12911297 'expiringblock' => 'expires $1',
 1298+'anononlyblock' => 'anon. only',
 1299+'createaccountblock' => 'account creation blocked',
12921300 'ipblocklistempty' => 'The blocklist is empty.',
12931301 'blocklink' => 'block',
12941302 'unblocklink' => 'unblock',
@@ -1301,8 +1309,10 @@
13021310 'unblocklogentry' => 'unblocked $1',
13031311 'range_block_disabled' => 'The sysop ability to create range blocks is disabled.',
13041312 'ipb_expiry_invalid' => 'Expiry time invalid.',
 1313+'ipb_already_blocked' => '"$1" is already blocked',
13051314 'ip_range_invalid' => 'Invalid IP range.',
13061315 'proxyblocker' => 'Proxy blocker',
 1316+'ipb_cant_unblock' => 'Error: Block ID $1 not found. It may have been unblocked already.',
13071317 '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.',
13081318 'proxyblocksuccess' => 'Done.',
13091319 'sorbs' => 'SORBS DNSBL',