Jack Phoenix has submitted this change and it was merged.

Change subject: Version 1.4: Special:RegexBlock is now more of a Special:Block 
copy
......................................................................


Version 1.4: Special:RegexBlock is now more of a Special:Block copy

Lots and lots of code was copypasted (and modified) from core
/includes/specials/SpecialBlock.php.

Because core Special:Block supports range blocks, there is theoretical
readiness for implementing range blocks in RegexBlock, but:
* right now at least IPv6 ranges will fail the RegexBlockData::isValidRegex() 
test in RegexBlockForm::validateTargetField()
* even if they wouldn't and ranges would be stored in the blockedby table, 
there's no handling of ranges in RegexBlock::check() yet

Other bugfixes included here:
* Fixed stupid bug with RegexBlockData::isValidRegex() -- unlike what the name 
suggests, the function was checking if the supplied regex is *invalid*
* Renamed RegexBlock::clearExpired() to the more accurate name removeBlock()
* Fixed IPv6 bug in RegexBlock::check() where IPv6 addresses weren't being 
blocked correctly

Bug: T152260
Bug: T152262
Bug: T152166
Bug: T152177
Bug: T152179
Change-Id: Ic76123ffb06a3aceab90a79f4c4ede7f1640bfaa
---
M RegexBlockCore.php
M RegexBlockData.php
D RegexBlockUITemplate.php
M SpecialRegexBlock.php
M extension.json
M i18n/en.json
M regexblock.css
7 files changed, 622 insertions(+), 256 deletions(-)

Approvals:
  Jack Phoenix: Verified; Looks good to me, approved



diff --git a/RegexBlockCore.php b/RegexBlockCore.php
index e9ce7cc..fe8efb3 100644
--- a/RegexBlockCore.php
+++ b/RegexBlockCore.php
@@ -32,7 +32,11 @@
                        return true;
                }
 
-               $ip_to_check = $wgRequest->getIP();
+               // sanitizeIP() check is needed for IPv6 -- upon saving a 
RegexBlock,
+               // IPv6 IPs like ::1 (localhost) are expanded to 
0:0:0:0:0:0:0:1, but
+               // $wgRequest->getIP() contains just "::1" so the checks fail 
and
+               // blocked IPv6 IPs would still be able to edit
+               $ip_to_check = IP::sanitizeIP( $wgRequest->getIP() );
 
                /* First check cache */
                $blocked = self::isBlockedCheck( $current_user, $ip_to_check );
@@ -244,7 +248,7 @@
                                $names = array( 'ips' => '', 'exact' => '', 
'regex' => '' );
                                while ( $row = $res->fetchObject() ) {
                                        $key = 'regex';
-                                       if ( $user->isIP( $row->blckby_name ) 
!= 0 ) {
+                                       if ( User::isIP( $row->blckby_name ) != 
0 ) {
                                                $key = 'ips';
                                        } elseif ( $row->blckby_exact != 0 ) {
                                                $key = 'exact';
@@ -365,7 +369,7 @@
                                        return $ret;
                                } else {
                                        /* clean up an obsolete block */
-                                       self::clearExpired( $single, 
$blocked->blckby_blocker );
+                                       self::removeBlock( $single, 
$blocked->blckby_blocker );
                                }
                        }
                }
@@ -404,25 +408,26 @@
        }
 
        /**
-        * Clean up an existing expired block
+        * Remove a block from the blockedby DB table.
         *
-        * @param string $username Name of the user
-        * @param string $blocker Name of the blocker
+        * @param string $regex Username or regular expression to unblock
+        * @param string $blocker Name of the blocker [unused - remove?]
+        * @return bool True if unblocked succeeded, otherwise false
         */
-       function clearExpired( $username, $blocker ) {
+       public static function removeBlock( $regex, $blocker ) {
                $result = false;
 
                $dbw = self::getDB( DB_MASTER );
 
                $dbw->delete(
                        'blockedby',
-                       array( 'blckby_name' => $username ),
+                       array( 'blckby_name' => $regex ),
                        __METHOD__
                );
 
                if ( $dbw->affectedRows() ) {
                        /* success, remember to delete cache key */
-                       self::unsetKeys( $username );
+                       self::unsetKeys( $regex );
                        $result = true;
                }
 
@@ -469,10 +474,10 @@
        /**
         * The actual blocking goes here, for each blocker
         *
-        * @param string $blocker
+        * @param string $blocker User name of the person who placed the block
         * @param array $blocker_block_data
-        * @param User $user
-        * @param string $user_ip
+        * @param User $user User who is being blocked
+        * @param string $user_ip IP address of the user who is being blocked
         */
        function blocked( $blocker, $blocker_block_data, $user, $user_ip ) {
                if ( $blocker_block_data == null ) {
@@ -684,5 +689,4 @@
                $updater->addExtensionUpdate( array( 'addTable', 
'stats_blockedby', $file, true ) );
                return true;
        }
-
 }
\ No newline at end of file
diff --git a/RegexBlockData.php b/RegexBlockData.php
index 5b28d70..3212d18 100644
--- a/RegexBlockData.php
+++ b/RegexBlockData.php
@@ -280,6 +280,6 @@
         * @return bool
         */
        public static function isValidRegex( $text ) {
-               return ( sprintf( "%s", @preg_match( "/{$text}/", 'regex' ) ) 
=== '' );
+               return ( sprintf( '%s', @preg_match( "/{$text}/", 'regex' ) ) 
!== '' );
        }
 }
\ No newline at end of file
diff --git a/RegexBlockUITemplate.php b/RegexBlockUITemplate.php
deleted file mode 100644
index 92d66db..0000000
--- a/RegexBlockUITemplate.php
+++ /dev/null
@@ -1,86 +0,0 @@
-<?php
-/**
- * @file
- * @ingroup Templates
- */
-if ( !defined( 'MEDIAWIKI' ) ) {
-       die( 1 );
-}
-
-/**
- * HTML template for Special:RegexBlock form
- * @ingroup Templates
- */
-class RegexBlockUITemplate extends QuickTemplate {
-       function execute() {
-               $checkExact = htmlspecialchars( ( 
$this->data['mRegexBlockedExact'] ) ) ? ' checked="checked"' : '';
-               $checkCreation = htmlspecialchars( ( 
$this->data['mRegexBlockedCreation'] ) ) ? ' checked="checked"' : '';
-
-               $msg = '';
-               if ( $this->data['err'] != '' ) {
-                       $this->data['out']->setSubtitle( $this->msg( 
'formerror' ) );
-                       $msg = '<h2 class="errorbox">' . $this->data['err'] . 
'</h2>';
-               } elseif ( $this->data['msg'] != '' ) {
-                       $msg = '<h2 class="successbox">' . $this->data['msg'] . 
'</h2>';
-               }
-?><div class="regexblock-msg"><?php echo $msg ?></div>
-       <div class="regexblock-help"><?php echo wfMessage( 'regexblock-help' 
)->parse() ?></div>
-       <fieldset class="regexblock-fieldset" align="center">
-               <legend><?php echo wfMessage( 'regexblock-form-submit' 
)->escaped() ?></legend>
-               <form name="regexblock" method="post" action="<?php echo 
$this->data['action'] ?>">
-               <table>
-                       <tr>
-                               <td align="right"><?php echo wfMessage( 
'regexblock-form-username' )->escaped() ?></td>
-                               <td align="left">
-                                       <input tabindex="1" 
name="wpRegexBlockedAddress" id="wpRegexBlockedAddress" 
class="mw-autocomplete-user" size="40" value="<?php echo 
$this->data['regexBlockAddress'] ?>" />
-                               </td>
-                       </tr>
-                       <tr>
-                               <td align="right"><?php echo wfMessage( 
'regexblock-form-reason' )->escaped() ?></td>
-                               <td align="left">
-                                       <input tabindex="2" 
name="wpRegexBlockedReason" id="wpRegexBlockedReason" size="40" value="<?php 
echo $this->data['class']->mRegexBlockedReason ?>" />
-                               </td>
-                       </tr>
-                       <tr>
-                               <td align="right"><?php echo wfMessage( 
'regexblock-form-expiry' )->escaped() ?></td>
-                               <td align="left">
-                               <select name="wpRegexBlockedExpire" 
id="wpRegexBlockedExpire" tabindex="3">
-                               <?php
-                               foreach ( $this->data['expiries'] as $k => $v ) 
{
-                                       $selected = htmlspecialchars( ( $k == 
$this->data['class']->mRegexBlockedExpire ) ) ? ' selected="selected"' : '';
-                               ?>
-                                       <option value="<?php echo 
htmlspecialchars( $v ) ?>"<?php echo $selected ?>><?php echo htmlspecialchars( 
$v ) ?></option>
-                               <?php
-                               }
-                               ?>
-                               </select>
-                       </td>
-                       </tr>
-                       <tr>
-                               <td align="right">&#160;</td>
-                               <td align="left">
-                                       <input type="checkbox" tabindex="4" 
name="wpRegexBlockedExact" id="wpRegexBlockedExact" value="1"<?php echo 
$checkExact ?> />
-                                       <label for="wpRegexBlockedExact"><?php 
echo wfMessage( 'regexblock-form-match' )->escaped() ?></label>
-                               </td>
-                       </tr>
-                       <tr>
-                               <td align="right">&#160;</td>
-                               <td align="left">
-                                       <input type="checkbox" tabindex="5" 
name="wpRegexBlockedCreation" id="wpRegexBlockedCreation" value="1"<?php echo 
$checkCreation ?> />
-                                       <label 
for="wpRegexBlockedCreation"><?php echo wfMessage( 
'regexblock-form-account-block' )->escaped() ?></label>
-                               </td>
-                       </tr>
-                       <tr>
-                               <td align="right">&#160;</td>
-                               <td align="left">
-                                       <input tabindex="6" 
name="wpRegexBlockedSubmit" type="submit" value="<?php echo wfMessage( 
'regexblock-form-submit' )->escaped() ?>" />
-                               </td>
-                       </tr>
-               </table>
-               <input type="hidden" name="wpEditToken" value="<?php echo 
$this->data['token'] ?>" />
-       </form>
-       </fieldset>
-       <br />
-<?php
-       }
-}
\ No newline at end of file
diff --git a/SpecialRegexBlock.php b/SpecialRegexBlock.php
index f526cdd..83854e3 100644
--- a/SpecialRegexBlock.php
+++ b/SpecialRegexBlock.php
@@ -14,16 +14,33 @@
  * @author Jack Phoenix <[email protected]>
  * @copyright Copyright © 2007, Wikia Inc.
  * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 
2.0 or later
+ * @note This file heavily reuses GPL-licensed code from MediaWiki core special
+ *       page Special:Block (/includes/specials/SpecialBlock.php).
  */
 
-class RegexBlockForm extends SpecialPage {
+class RegexBlockForm extends FormSpecialPage {
        public $numResults = 0;
        public $numStatResults = 0;
        public $mAction;
        public $mFilter, $mRegexFilter;
        public $mLimit;
        public $mOffset;
-       public $mError, $mMsg;
+
+       /** @var User|string|null User to be blocked, as passed either by 
parameter (url?wpTarget=Foo)
+        * or as subpage (Special:Block/Foo) */
+       protected $target;
+
+       /** @var int Block::TYPE_ constant */
+       protected $type;
+
+       /** @var User|string The previous block target */
+       protected $previousTarget;
+
+       /** @var bool */
+       protected $alreadyBlocked;
+
+       /** @var array */
+       protected $preErrors = [];
 
        /**
         * Constructor -- set up the new, restricted special page
@@ -31,7 +48,6 @@
        public function __construct() {
                $this->mAction = '';
                $this->mFilter = $this->mRegexFilter = '';
-               $this->mError = $this->mMsg = '';
                parent::__construct( 'RegexBlock', 'regexblock' );
        }
 
@@ -52,30 +68,25 @@
        /**
         * Show the special page
         *
-        * @param string|null $subpage Parameter passed to the page, if any
+        * @param string|null $par Parameter passed to the page, if any
         */
-       public function execute( $subpage ) {
+       public function execute( $par ) {
                $out = $this->getOutput();
                $request = $this->getRequest();
                $user = $this->getUser();
 
-               # If the user doesn't have the required 'regexblock' 
permission, display an error
-               if ( !$user->isAllowed( 'regexblock' ) ) {
-                       throw new PermissionsError( 'regexblock' );
-               }
+               $this->setParameter( $par );
+               $this->setHeaders();
 
-               # Show a message if the database is in read-only mode
-               $this->checkReadOnly();
-
-               # If user is blocked, s/he doesn't need to access this page
-               if ( $user->isBlocked() ) {
-                       throw new UserBlockedError( $user->getBlock() );
-               }
+               // This will throw exceptions if there's a problem
+               $this->checkExecutePermissions( $user );
 
                // Initial output
                $this->mTitle = $this->getPageTitle();
 
-               $this->setHeaders();
+               // Our custom CSS
+               $out->addModuleStyles( 'ext.regexBlock.styles' );
+
                $out->setPageTitle( $this->msg( 'regexblock-page-title' ) );
 
                $this->mAction = $request->getVal( 'action' );
@@ -84,36 +95,22 @@
 
                list( $this->mLimit, $this->mOffset ) = 
$request->getLimitOffset();
 
-               $this->mRegexBlockedAddress = $this->mRegexBlockedExact = 
$this->mRegexBlockedCreation = $this->mRegexBlockedExpire = 
$this->mRegexBlockedReason = '';
-               if ( $this->mAction == 'submit' ) {
-                       $this->mRegexBlockedAddress = htmlspecialchars( 
$request->getVal( 'wpRegexBlockedAddress', $request->getVal( 'ip' ) ) );
-                       $this->mRegexBlockedExact = $request->getInt( 
'wpRegexBlockedExact' );
-                       $this->mRegexBlockedCreation = $request->getInt( 
'wpRegexBlockedCreation' );
-                       $this->mRegexBlockedExpire = htmlspecialchars( 
$request->getVal( 'wpRegexBlockedExpire' ) );
-                       $this->mRegexBlockedReason = htmlspecialchars( 
$request->getVal( 'wpRegexBlockedReason' ) );
-               }
-
                /* Actions */
                switch ( $this->mAction ) {
                        case 'success_block':
-                               $out->setSubTitle( $this->msg( 
'regexblock-block-success' ) );
-                               $this->mMsg = $this->msg( 
'regexblock-block-log', $request->getVal( 'ip' ) )->parse();
+                               $out->setSubtitle( $this->msg( 
'regexblock-block-success' ) );
+                               $out->wrapWikiMsg( '<div 
class="successbox">$1</div><br />' . "\n", [ 'regexblock-block-log', urldecode( 
$request->getVal( 'ip' ) ) ] );
                                break;
                        case 'success_unblock':
-                               $out->setSubTitle( $this->msg( 
'regexblock-unblock-success' ) );
-                               $this->mMsg = $this->msg( 
'regexblock-unblock-log', $request->getVal( 'ip' ) )->parse();
+                               $out->setSubtitle( $this->msg( 
'regexblock-unblock-success' ) );
+                               $out->wrapWikiMsg( '<div 
class="successbox">$1</div><br />' . "\n", [ 'regexblock-unblock-log', 
urldecode( $request->getVal( 'ip' ) ) ] );
                                break;
                        case 'failure_unblock':
-                               $this->mError = $this->msg( 
'regexblock-unblock-error', $request->getVal( 'ip' ) )->text();
+                               $out->wrapWikiMsg( '<div 
class="errorbox">$1</div><br />' . "\n", [ 'regexblock-unblock-error', 
urldecode( $request->getVal( 'ip' ) ) ] );
                                break;
                        case 'stats':
                                $blckid = $request->getVal( 'blckid' );
                                $this->showStatsList( $blckid );
-                               break;
-                       case 'submit':
-                               if ( $request->wasPosted() && 
$user->matchEditToken( $request->getVal( 'wpEditToken' ) ) ) {
-                                       $this->mAction = $this->doSubmit();
-                               }
                                break;
                        case 'delete':
                                $this->deleteFromRegexBlockList();
@@ -121,44 +118,12 @@
                }
 
                if ( !in_array( $this->mAction, array( 'submit', 'stats' ) ) ) {
-                       $this->showForm();
-                       unset( $this->mError );
-                       unset( $this->mMsg );
+                       $form = $this->getForm();
+                       if ( $form->show() ) {
+                               $this->onSuccess();
+                       }
                        $this->showRegexList();
                }
-       }
-
-       /**
-        * Show the form for blocking IPs / users
-        */
-       private function showForm() {
-               $out = $this->getOutput();
-               $request = $this->getRequest();
-
-               $token = htmlspecialchars( $this->getUser()->getEditToken() );
-               $action = htmlspecialchars( $this->mTitle->getLocalURL( array( 
'action' => 'submit' ) + $this->makeListUrlParams() ), ENT_QUOTES );
-
-               $expiries = SpecialBlock::getSuggestedDurations(); // 
RegexBlockData::getExpireValues();
-               $regexBlockAddress = ( empty( $this->mRegexBlockedAddress ) && 
( $request->getVal( 'ip' ) != null ) &&
-                       ( $request->getVal( 'action' ) == null ) ) ? 
$request->getVal( 'ip' ) : $this->mRegexBlockedAddress;
-
-               $tpl = new RegexBlockUITemplate;
-               $tpl->setRef( 'class', $this );
-               $tpl->setRef( 'out', $out );
-               $tpl->set( 'msg', $this->mMsg );
-               $tpl->set( 'action', $action );
-               $tpl->set( 'regexBlockAddress', $regexBlockAddress );
-               $tpl->set( 'mRegexBlockedExact', $this->mRegexBlockedExact );
-               $tpl->set( 'mRegexBlockedCreation', 
$this->mRegexBlockedCreation );
-               $tpl->set( 'err', $this->mError );
-               $tpl->set( 'expiries', $expiries );
-               $tpl->set( 'token', $token );
-
-               // CSS & JS
-               $out->addModuleStyles( 'ext.regexBlock.styles' );
-               $out->addModules( 'mediawiki.userSuggest' );
-
-               $out->addTemplate( $tpl );
        }
 
        /**
@@ -167,7 +132,7 @@
        private function showRegexList() {
                $out = $this->getOutput();
 
-               $action = htmlspecialchars( $this->mTitle->getLocalURL( 
$this->makeListUrlParams() ), ENT_QUOTES );
+               $action = htmlspecialchars( $this->getPageTitle()->getFullURL( 
$this->makeListUrlParams() ), ENT_QUOTES );
 
                $regexData = new RegexBlockData();
                $lang = $this->getLanguage();
@@ -197,7 +162,8 @@
                        <form name="regexlist" method="get" action="' . $action 
. '">
                                ' . $this->msg( 'regexblock-view-blocked' 
)->text() . '
                                <select name="filter">
-                                       <option value="">' . $this->msg( 
'regexblock-view-all' )->text() . '</option>' );
+                                       <option value="">' . $this->msg( 
'regexblock-view-all' )->text() . '</option>'
+               );
 
                if ( is_array( $blockers ) ) {
                        foreach ( $blockers as $id => $blocker ) {
@@ -207,19 +173,22 @@
                }
 
                $out->addHTML(
-                       '</select>&#160;' . $this->msg( 
'regexblock-regex-filter' )->text() . $this->msg( 'word-separator' )->text() . '
-                               <input type="text" name="rfilter" 
id="regex_filter" value="' . $this->mRegexFilter . '" />
-                               <input type="submit" value="' . $this->msg( 
'regexblock-view-go' )->text() . '" />
+                       '</select>&#160;' . $this->msg( 
'regexblock-regex-filter' )->text() . $this->msg( 'word-separator' )->text() .
+                               Html::hidden( 'title', $this->getPageTitle() ) .
+                               Html::input( 'rfilter', $this->mRegexFilter, 
'text', [ 'id' => 'regex_filter' ] ) .
+                               '<input type="submit" value="' . $this->msg( 
'regexblock-view-go' )->text() . '" />
                        </form>
                        <br />
-                       <form name="regexbyid" method="get" action="' . $action 
. '">
-                               <input type="hidden" name="action" 
value="stats" />' .
+                       <form name="regexbyid" method="get" action="' . $action 
. '">' .
+                               Html::hidden( 'title', $this->getPageTitle() ) .
+                               Html::hidden( 'action', 'stats' ) .
                                $this->msg( 'regexblock-view-block-id' 
)->text() .
                                $this->msg( 'word-separator' )->text() .
                                '<input type="text" name="blckid" id="blckid" 
value="" />
                                <input type="submit" value="' . $this->msg( 
'regexblock-view-go' )->text() . '" />
                        </form>'
                );
+
                if ( !empty( $blockers ) ) {
                        $out->addHTML( '<ul id="regexblock_blocks">' );
                        $loop = 0;
@@ -241,7 +210,7 @@
                                $reason = '<i>' . $row['reason'] . '</i>';
                                $stats_link = Linker::linkKnown(
                                        $this->mTitle,
-                                       $this->msg( 'regexblock-view-stats' ),
+                                       $this->msg( 'regexblock-view-stats' 
)->text(),
                                        array(),
                                        array( 'action' => 'stats', 'blckid' => 
$row['blckid'] )
                                );
@@ -259,8 +228,8 @@
 
                                $out->addHTML(
                                        '<li>
-                                       <b><span class="regexblock-target">' . 
$row['blckby_name'] . '</span>' . $comma . $exact_match . $space . 
$create_block . '</b>' . $comma . '
-                                       (' . $this->msg( 
'regexblock-view-block-by' ) . ' <b>' . $row['blocker'] . '</b>, ' . $reason . 
') ' .
+                                       <code class="regexblock-target">' . 
$row['blckby_name'] . '</code><b>' . $comma . $exact_match . $space . 
$create_block . '</b>' . $comma . '
+                                       (' . $this->msg( 
'regexblock-view-block-by' )->text() . ' <b>' . $row['blocker'] . '</b>, ' . 
$reason . ') ' .
                                         $this->msg( 'regexblock-view-time', 
$row['datim'], $row['date'], $row['time'] )->text() . $comma .
                                        '(' . $unblock_link . ') ' . $comma . 
$row['expiry'] . $comma . ' (' . $stats_link . ')
                                        </li>'
@@ -289,66 +258,16 @@
                return $pieces;
        }
 
-       /* On submit */
-       private function doSubmit() {
-               /* empty name */
-               if ( strlen( $this->mRegexBlockedAddress ) == 0 ) {
-                       $this->mError = $this->msg( 
'regexblock-form-submit-empty' )->text();
-                       return false;
-               }
-
-               /* castrate regexes */
-               if ( RegexBlockData::isValidRegex( $this->mRegexBlockedAddress 
) ) {
-                       $this->mError = $this->msg( 
'regexblock-form-submit-regex' )->text();
-                       return false;
-               }
-
-               /* check expiry */
-               if ( strlen( $this->mRegexBlockedExpire ) == 0 ) {
-                       $this->mError = $this->msg( 
'regexblock-form-submit-expiry' )->text();
-                       return false;
-               }
-
-               if ( $this->mRegexBlockedExpire != 'infinite' ) {
-                       $expiry = strtotime( $this->mRegexBlockedExpire );
-                       if ( $expiry < 0 || $expiry === false ) {
-                               $this->mError = $this->msg( 
'ipb_expiry_invalid' )->text();
-                               return false;
-                       }
-                       $expiry = wfTimestamp( TS_MW, $expiry );
-               } else {
-                       $expiry = $this->mRegexBlockedExpire;
-               }
-
-               $result = RegexBlockData::blockUser(
-                       $this->mRegexBlockedAddress,
-                       $expiry,
-                       $this->mRegexBlockedExact,
-                       $this->mRegexBlockedCreation,
-                       $this->mRegexBlockedReason
-               );
-               /* clear memcached */
-               RegexBlock::unsetKeys( $this->mRegexBlockedAddress );
-
-               /* redirect */
-               $this->getOutput()->redirect( $this->mTitle->getFullURL( array(
-                       'action' => 'success_block',
-                       'ip' => $this->mRegexBlockedAddress
-               ) + $this->makeListUrlParams() ) );
-
-               return;
-       }
-
        /**
         * Remove name or address from list - without confirmation
         */
        private function deleteFromRegexBlockList() {
                $request = $this->getRequest();
 
-               $ip = $request->getVal( 'ip' );
+               $ip = urldecode( $request->getVal( 'ip' ) );
                $blocker = $request->getVal( 'blocker' );
 
-               $result = RegexBlock::clearExpired( $ip, $blocker );
+               $result = RegexBlock::removeBlock( $ip, $blocker );
 
                if ( $result === true ) {
                        $this->getOutput()->redirect( 
$this->mTitle->getFullURL( array(
@@ -442,4 +361,553 @@
                        $out->addWikiMsg( 'regexblock-nodata-found' );
                }
        }
+
+       /**
+        * Handle some magic here
+        *
+        * @param string $par
+        */
+       protected function setParameter( $par ) {
+               # Extract variables from the request. Try not to get into a 
situation where we
+               # need to extract *every* variable from the form just for 
processing here, but
+               # there are legitimate uses for some variables
+               $request = $this->getRequest();
+               list( $this->target, $this->type ) = self::getTargetAndType( 
$par, $request );
+               if ( $this->target instanceof User ) {
+                       # Set the 'relevant user' in the skin, so it displays 
links like Contributions,
+                       # User logs, UserRights, etc.
+                       $this->getSkin()->setRelevantUser( $this->target );
+               }
+
+               list( $this->previousTarget, /*...*/ ) =
+                       self::parseTarget( $request->getVal( 'wpPreviousTarget' 
) );
+       }
+
+       /**
+        * Customizes the HTMLForm a bit
+        *
+        * @param HTMLForm $form
+        */
+       protected function alterForm( HTMLForm $form ) {
+               $form->setWrapperLegendMsg( 'blockip-legend' );
+               $form->setHeaderText( '' );
+               $form->setSubmitDestructive();
+
+               $form->setSubmitTextMsg( 'regexblock-form-submit' );
+
+               $this->addHelpLink( 'Help:Blocking users' );
+
+               # Don't need to do anything if the form has been posted
+               if ( !$this->getRequest()->wasPosted() && $this->preErrors ) {
+                       $s = $form->formatErrors( $this->preErrors );
+                       if ( $s ) {
+                               $form->addHeaderText( Html::rawElement(
+                                       'div',
+                                       [ 'class' => 'error' ],
+                                       $s
+                               ) );
+                       }
+               }
+       }
+
+       /**
+        * Get the HTMLForm descriptor array for the block form
+        * @return array
+        */
+       protected function getFormFields() {
+               $suggestedDurations = self::getSuggestedDurations();
+
+               $a = [
+                       'Target' => [ // @note Formerly called 
wpRegexBlockedAddress
+                               'type' => 'text',
+                               'label-message' => 'regexblock-form-username', 
// 'ipaddressorusername',
+                               'id' => 'mw-bi-target',
+                               'size' => '45',
+                               'autofocus' => true,
+                               'required' => true,
+                               'validation-callback' => [ __CLASS__, 
'validateTargetField' ],
+                               'cssclass' => 'mw-autocomplete-user', // used 
by mediawiki.userSuggest
+                       ],
+                       'Expiry' => [
+                               'type' => !count( $suggestedDurations ) ? 
'text' : 'selectorother',
+                               'label-message' => 'ipbexpiry',
+                               'required' => true,
+                               'options' => $suggestedDurations,
+                               'other' => $this->msg( 'ipbother' )->text(),
+                               'default' => $this->msg( 'ipb-default-expiry' 
)->inContentLanguage()->text(),
+                       ],
+                       'Reason' => [
+                               'type' => 'selectandother',
+                               'maxlength' => 255,
+                               'label-message' => 'ipbreason',
+                               'options-message' => 'ipbreason-dropdown',
+                       ],
+                       /*'CreateAccount'*/'RegexBlockedCreation' => [
+                               'type' => 'check',
+                               'label-message' => 
'regexblock-form-account-block', //'ipbcreateaccount',
+                               'default' => true,
+                       ],
+                       'RegexBlockedExact' => [
+                               'type' => 'check',
+                               'label-message' => 'regexblock-form-match',
+                               'default' => false
+                       ]
+               ];
+
+               # This is basically a copy of the Target field, but the user 
can't change it, so we
+               # can see if the warnings we maybe showed to the user before 
still apply
+               $a['PreviousTarget'] = [
+                       'type' => 'hidden',
+                       'default' => false,
+               ];
+
+               $this->maybeAlterFormDefaults( $a );
+
+               return $a;
+       }
+
+       /**
+        * If the user has already been blocked with similar settings, load 
that block
+        * and change the defaults for the form fields to match the existing 
settings.
+        * @param array $fields HTMLForm descriptor array
+        * @return bool Whether fields were altered (that is, whether the 
target is
+        *     already blocked)
+        */
+       protected function maybeAlterFormDefaults( &$fields ) {
+               # This will be overwritten by request data
+               $fields['Target']['default'] = (string)$this->target;
+
+               if ( $this->target ) {
+                       if ( !RegexBlockData::isValidRegex( 
(string)$this->target ) ) {
+                               $this->preErrors = array_merge( 
$this->preErrors, [ 'regexblock-form-submit-regex' ] );
+                       }
+                       /*
+                       $status = SpecialBlock::validateTarget( $this->target, 
$this->getUser() );
+                       if ( !$status->isOK() ) {
+                               $errors = $status->getErrorsArray();
+                               $this->preErrors = array_merge( 
$this->preErrors, $errors );
+                       }
+                       */
+               }
+
+               # This won't be
+               $fields['PreviousTarget']['default'] = (string)$this->target;
+       }
+
+       /**
+        * Add header elements like help text, etc.
+        * @return string
+        */
+       protected function preText() {
+               $this->getOutput()->addModules( [ 'mediawiki.special.block', 
'mediawiki.userSuggest' ] );
+
+               $blockCIDRLimit = $this->getConfig()->get( 'BlockCIDRLimit' );
+               // @note Originally used 'blockiptext'
+               // @todo FIXME: (eventually) use the passed params in the i18n 
msg for realz
+               $text = $this->msg( 'regexblock-help', $blockCIDRLimit['IPv4'], 
$blockCIDRLimit['IPv6'] )->parse();
+
+               return $text;
+       }
+
+       /**
+        * Add footer elements to the form
+        * @return string
+        */
+       protected function postText() {
+               $links = [];
+
+               $this->getOutput()->addModuleStyles( 'mediawiki.special' );
+
+               # Link to the user's contributions, if applicable
+               if ( $this->target instanceof User ) {
+                       $contribsPage = SpecialPage::getTitleFor( 
'Contributions', $this->target->getName() );
+                       $links[] = Linker::link(
+                               $contribsPage,
+                               $this->msg( 'ipb-blocklist-contribs', 
$this->target->getName() )->escaped()
+                       );
+               }
+
+               $user = $this->getUser();
+
+               # Link to edit the block dropdown reasons, if applicable
+               if ( $user->isAllowed( 'editinterface' ) ) {
+                       $links[] = Linker::linkKnown(
+                               $this->msg( 'ipbreason-dropdown' 
)->inContentLanguage()->getTitle(),
+                               $this->msg( 'ipb-edit-dropdown' )->escaped(),
+                               [],
+                               [ 'action' => 'edit' ]
+                       );
+               }
+
+               $text = Html::rawElement(
+                       'p',
+                       [ 'class' => 'mw-ipb-conveniencelinks' ],
+                       $this->getLanguage()->pipeList( $links )
+               );
+
+               $userTitle = self::getTargetUserTitle( $this->target );
+               if ( $userTitle ) {
+                       # Get relevant extracts from the block and suppression 
logs, if possible
+                       $out = '';
+
+                       LogEventsList::showLogExtract(
+                               $out,
+                               'block',
+                               $userTitle,
+                               '',
+                               [
+                                       'lim' => 10,
+                                       'msgKey' => [ 'blocklog-showlog', 
$userTitle->getText() ],
+                                       'showIfEmpty' => false
+                               ]
+                       );
+                       $text .= $out;
+               }
+
+               return $text;
+       }
+
+       /**
+        * Get a user page target for things like logs.
+        * This handles account and IP range targets.
+        * @param User|string $target
+        * @return Title|null
+        */
+       protected static function getTargetUserTitle( $target ) {
+               if ( $target instanceof User ) {
+                       return $target->getUserPage();
+               } elseif ( IP::isIPAddress( $target ) ) {
+                       return Title::makeTitleSafe( NS_USER, $target );
+               }
+
+               return null;
+       }
+
+       /**
+        * Determine the target of the block, and the type of target
+        *
+        * @param string $par Subpage parameter passed to setup, or data value 
from
+        *     the HTMLForm
+        * @param WebRequest $request Optionally try and get data from a 
request too
+        * @return array( User|string|null, Block::TYPE_ constant|null )
+        */
+       public static function getTargetAndType( $par, WebRequest $request = 
null ) {
+               $i = 0;
+               $target = null;
+
+               while ( true ) {
+                       switch ( $i++ ) {
+                               case 0:
+                                       # The HTMLForm will check wpTarget 
first and only if it doesn't get
+                                       # a value use the default, which will 
be generated from the options
+                                       # below; so this has to have a higher 
precedence here than $par, or
+                                       # we could end up with different values 
in $this->target and the HTMLForm!
+                                       if ( $request instanceof WebRequest ) {
+                                               $target = $request->getText( 
'wpTarget', null );
+                                       }
+                                       break;
+                               case 1:
+                                       $target = $par;
+                                       break;
+                               case 2:
+                                       if ( $request instanceof WebRequest ) {
+                                               $target = $request->getText( 
'ip', null );
+                                       }
+                                       break;
+                               case 3:
+                                       # B/C @since 1.18
+                                       if ( $request instanceof WebRequest ) {
+                                               $target = $request->getText( 
'wpBlockAddress', null );
+                                       }
+                                       break;
+                               case 4:
+                                       break 2;
+                       }
+
+                       list( $target, $type ) = self::parseTarget( $target );
+
+                       if ( $type !== null ) {
+                               return [ $target, $type ];
+                       }
+               }
+
+               return [ null, null ];
+       }
+
+       /**
+        * <s>From an existing Block,</s> get the target and the type of target.
+        * Note that, except for null, it is always safe to treat the target
+        * as a string; for User objects this will return User::__toString()
+        * which in turn gives User::getName().
+        *
+        * Had to override this to take regexes into account, which 
SpecialBlock's
+        * method obviously doesn't, because as of MW 1.28 core doesn't have 
native
+        * support for blocking via regexes. One day...
+        *
+        * @param string|int|User|null $target
+        * @return array( User|String|null, Block::TYPE_ constant|null )
+        */
+       public static function parseTarget( $target ) {
+               # We may have been through this before
+               if ( $target instanceof User ) {
+                       if ( IP::isValid( $target->getName() ) ) {
+                               return [ $target, self::TYPE_IP ];
+                       } else {
+                               return [ $target, self::TYPE_USER ];
+                       }
+               } elseif ( $target === null ) {
+                       return [ null, null ];
+               }
+
+               $target = trim( $target );
+
+               if ( IP::isValid( $target ) ) {
+                       # We can still create a User if it's an IP address, but 
we need to turn
+                       # off validation checking (which would exclude IP 
addresses)
+                       return [
+                               User::newFromName( IP::sanitizeIP( $target ), 
false ),
+                               Block::TYPE_IP
+                       ];
+
+               } elseif ( IP::isValidBlock( $target ) ) {
+                       # Can't create a User from an IP range
+                       return [ IP::sanitizeRange( $target ), 
Block::TYPE_RANGE ];
+               }
+
+               # Consider the possibility that this is not a username at all
+               # but actually an old subpage (bug #29797)
+               if ( strpos( $target, '/' ) !== false ) {
+                       # An old subpage, drill down to the user behind it
+                       $target = explode( '/', $target )[0];
+               }
+
+               $userObj = User::newFromName( $target );
+               if ( $userObj instanceof User ) {
+                       # Note that since numbers are valid usernames, a 
$target of "12345" will be
+                       # considered a User.  If you want to pass a block ID, 
prepend a hash "#12345",
+                       # since hash characters are not valid in usernames or 
titles generally.
+                       return [ $userObj, Block::TYPE_USER ];
+               } elseif ( RegexBlockData::isValidRegex( $target ) ) {
+                       return [ $target, 6 /* Block::TYPE_ constants are 
numbered 1-5, so using 6 here is safe for now */ ];
+               } else {
+                       # WTF?
+                       return [ null, null ];
+               }
+       }
+
+       /**
+        * HTMLForm field validation callback for Target field.
+        *
+        * @param string $value User-supplied value to check for validity
+        * @param array $alldata
+        * @param HTMLForm $form
+        * @return Message
+        */
+       public static function validateTargetField( $value, $alldata, $form ) {
+               if ( RegexBlockData::isValidRegex( $value ) ) {
+                       // valid regex is all it takes [[for now]]...
+                       return true;
+               } else {
+                       $errors = [];
+                       $errors[0] = [ 'regexblock-form-submit-regex' ];
+                       return call_user_func_array( [ $form, 'msg' ], 
$errors[0] );
+               }
+       }
+
+       /**
+        * Given the form data, actually implement a block.<s>This is also 
called from ApiBlock.</s>
+        *
+        * @param array $data
+        * @param IContextSource $context
+        * @return bool|string
+        */
+       public static function processForm( array $data, IContextSource 
$context ) {
+               global $wgContLang;
+
+               $performer = $context->getUser();
+
+               // Handled by field validator callback
+               // self::validateTargetField( $data['Target'] );
+
+               /** @var User $target */
+               list( $target, $type ) = self::getTargetAndType( 
$data['Target'] );
+               if ( $type == Block::TYPE_USER ) {
+                       $user = $target;
+                       $target = $user->getName();
+                       $userId = $user->getId();
+
+                       # Give admins a heads-up before they go and block 
themselves. Much messier
+                       # to do this for IPs, but it's pretty unlikely they'd 
ever get the 'block'
+                       # permission anyway, although the code does allow for 
it.
+                       # Note: Important to use $target instead of 
$data['Target']
+                       # since both $data['PreviousTarget'] and $target are 
normalized
+                       # but $data['target'] gets overridden by 
(non-normalized) request variable
+                       # from previous request.
+                       if ( $target === $performer->getName() &&
+                               ( $data['PreviousTarget'] !== $target )
+                       ) {
+                               return [ 'ipb-blockingself', 
'ipb-confirmaction' ];
+                       }
+               } elseif ( $type == Block::TYPE_RANGE ) {
+                       $user = null;
+                       $userId = 0;
+               } elseif ( $type == Block::TYPE_IP ) {
+                       $user = null;
+                       $target = $target->getName();
+                       $userId = 0;
+               } elseif ( $type == 6 /* = our own identifier for regex-based 
blocks */ ) {
+                       // for RegexBlock assume that this case means that the 
target is
+                       // a regular expression, for which there is no 
Block::TYPE_*
+                       // constant in MW core, obviously...
+                       $user = null;
+                       $userId = 0;
+               } else {
+                       # This should have been caught in the form field 
validation
+                       return [ 'badipaddress' ];
+               }
+
+               $expiryTime = SpecialBlock::parseExpiryInput( $data['Expiry'] );
+
+               if (
+                       // an expiry time is needed
+                       ( strlen( $data['Expiry'] ) == 0 ) ||
+                       // can't be a larger string as 50 (it should be a time 
format in any way)
+                       ( strlen( $data['Expiry'] ) > 50 ) ||
+                       // check, if the time could be parsed
+                       !$expiryTime
+               ) {
+                       return [ 'ipb_expiry_invalid' ];
+               }
+
+               // an expiry time should be in the future, not in the
+               // past (wouldn't make any sense) - bug T123069
+               if ( $expiryTime < wfTimestampNow() ) {
+                       return [ 'ipb_expiry_old' ];
+               }
+
+               $result = RegexBlockData::blockUser(
+                       $target,
+                       $expiryTime,
+                       $data['RegexBlockedExact'],
+                       $data['RegexBlockedCreation'],
+                       # Truncate reason for whole multibyte characters
+                       $wgContLang->truncate( $data['Reason'][0], 255 )
+               );
+
+               // clear memcached
+               RegexBlock::unsetKeys( $target );
+
+/*
+               $logAction = 'block';
+
+               # Prepare log parameters
+               $logParams = [];
+               $logParams['5::duration'] = $data['Expiry'];
+               $logParams['6::flags'] = self::blockLogFlags( $data, $type );
+
+               # Make log entry
+               $logEntry = new ManualLogEntry( 'regexblock', $logAction );
+               $logEntry->setTarget( Title::makeTitle( NS_USER, $target ) );
+               $logEntry->setComment( $data['Reason'][0] );
+               $logEntry->setPerformer( $performer );
+               $logEntry->setParameters( $logParams );
+               # Relate log ID to block IDs (bug 25763)
+               $blockIds = array_merge( [ $status['id'] ], $status['autoIds'] 
);
+               $logEntry->setRelations( [ 'ipb_id' => $blockIds ] );
+               $logId = $logEntry->insert();
+               $logEntry->publish( $logId );
+*/
+               # Report to the user
+               return true;
+       }
+
+       /**
+        * Get an array of suggested block durations from MediaWiki:Ipboptions
+        *
+        * @param Language|null $lang The language to get the durations in, or 
null to use
+        *     the wiki's content language
+        * @return array
+        */
+       public static function getSuggestedDurations( $lang = null ) {
+               $a = [];
+               $msg = $lang === null
+                       ? wfMessage( 'ipboptions' )->inContentLanguage()->text()
+                       : wfMessage( 'ipboptions' )->inLanguage( $lang 
)->text();
+
+               if ( $msg == '-' ) {
+                       return [];
+               }
+
+               foreach ( explode( ',', $msg ) as $option ) {
+                       if ( strpos( $option, ':' ) === false ) {
+                               $option = "$option:$option";
+                       }
+
+                       list( $show, $value ) = explode( ':', $option );
+                       $a[$show] = $value;
+               }
+
+               return $a;
+       }
+
+       /**
+        * Return a comma-delimited list of "flags" to be passed to the log
+        * reader for this block, to provide more information in the logs
+        * @param array $data From HTMLForm data
+        * @param int $type Block::TYPE_ constant (USER, RANGE, or IP)
+        * @return string
+        */
+       protected static function blockLogFlags( array $data, $type ) {
+               $flags = [];
+
+               if ( $data['RegexBlockedCreation'] ) {
+                       // For grepping: message block-log-flags-nocreate
+                       $flags[] = 'nocreate';
+               }
+
+               // For grepping: message block-log-flags-nousertalk
+               $flags[] = 'nousertalk';
+
+               return implode( ',', $flags );
+       }
+
+       /**
+        * Process the form on POST submission.
+        * @param array $data
+        * @param HTMLForm $form
+        * @return bool|array True for success, false for didn't-try, array of 
errors on failure
+        */
+       public function onSubmit( array $data, HTMLForm $form = null ) {
+               return self::processForm( $data, $form->getContext() );
+       }
+
+       /**
+        * Do something exciting on successful processing of the form, most 
likely to show a
+        * confirmation message
+        */
+       public function onSuccess() {
+               $out = $this->getOutput();
+               $this->getOutput()->redirect( 
$this->getPageTitle()->getFullURL( array(
+                       'action' => 'success_block',
+                       'ip' => $this->target
+               ) + $this->makeListUrlParams() ) );
+       }
+
+       /**
+        * Return an array of subpages beginning with $search that this special 
page will accept.
+        *
+        * @param string $search Prefix to search for
+        * @param int $limit Maximum number of results to return (usually 10)
+        * @param int $offset Number of results to skip (usually 0)
+        * @return string[] Matching subpages
+        */
+       public function prefixSearchSubpages( $search, $limit, $offset ) {
+               $user = User::newFromName( $search );
+               if ( !$user ) {
+                       // No prefix suggestion for invalid user
+                       return [];
+               }
+               // Autocomplete subpage as user list - public to allow caching
+               return UserNamePrefixSearch::search( 'public', $search, $limit, 
$offset );
+       }
 }
\ No newline at end of file
diff --git a/extension.json b/extension.json
index f6feba2..4b2b43f 100644
--- a/extension.json
+++ b/extension.json
@@ -1,6 +1,6 @@
 {
        "name": "RegexBlock",
-       "version": "1.3",
+       "version": "1.4",
        "author": [
                "Bartek Łapiński",
                "Tomasz Klim",
@@ -31,8 +31,7 @@
        "AutoloadClasses": {
                "RegexBlock": "RegexBlockCore.php",
                "RegexBlockData": "RegexBlockData.php",
-               "RegexBlockForm": "SpecialRegexBlock.php",
-               "RegexBlockUITemplate": "RegexBlockUITemplate.php"
+               "RegexBlockForm": "SpecialRegexBlock.php"
        },
        "Hooks": {
                "ContributionsToolLinks": [
diff --git a/i18n/en.json b/i18n/en.json
index f4b5fba..b354cd7 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -22,7 +22,6 @@
        "regexblock-reason-regex": "This username is prevented from editing due 
to vandalism or other disruption by a user with a similar name.\nPlease create 
an alternate user name or [[$1|contact us]] about the problem",
        "regexblock-form-username": "IP address or username:",
        "regexblock-form-reason": "Reason:",
-       "regexblock-form-expiry": "Expiry:",
        "regexblock-form-match": "Exact match",
        "regexblock-form-account-block": "Block creation of new accounts",
        "regexblock-form-submit": "Block this user",
diff --git a/regexblock.css b/regexblock.css
index 5477038..cea8ded 100644
--- a/regexblock.css
+++ b/regexblock.css
@@ -21,29 +21,11 @@
        border: 1px solid #2F6FAB;
 }
 
-.regexblock-fieldset input[name="wpRegexBlockedSubmit"] {
-       background-color: #900;
-       background-image: -moz-linear-gradient(top, #c00 20%, #670000 70%);
-       background-image: -webkit-gradient(linear, 0% 20%, 0% 70%, from(#c00), 
to(#670000));
-       border-color: #ff0000;
-       color: #FFF;
-       box-shadow: 0 0 0 1px #800000;
-       -moz-box-shadow: 0 0 0 1px #800000;
-       -webkit-box-shadow: 0 1px 0 #800000, 0 -1px 0 #800000, 1px 0 0 #800000, 
-1px 0 0 #800000;
-}
-
 /* dashed blue line between rows */
 #regexblock_blocks li,
 #regexblock_triggers li {
        border-bottom: 1px dashed #778899;
-       font-size: 11px;
        padding-bottom: 2px;
-}
-
-/* target name in green */
-#regexblock_blocks .regexblock-target {
-       color: #3B7F07;
-       font-size: 12px;
 }
 
 /* expired block time in red */

-- 
To view, visit https://gerrit.wikimedia.org/r/325151
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: Ic76123ffb06a3aceab90a79f4c4ede7f1640bfaa
Gerrit-PatchSet: 2
Gerrit-Project: mediawiki/extensions/RegexBlock
Gerrit-Branch: master
Gerrit-Owner: Jack Phoenix <[email protected]>
Gerrit-Reviewer: Jack Phoenix <[email protected]>
Gerrit-Reviewer: Siebrand <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to