* (bug 674) Allow users to be blocked from editing a specific article
authorVictor Vasiliev <vasilievvv@users.mediawiki.org>
Sun, 28 Sep 2008 16:08:18 +0000 (16:08 +0000)
committerVictor Vasiliev <vasilievvv@users.mediawiki.org>
Sun, 28 Sep 2008 16:08:18 +0000 (16:08 +0000)
** Also supports blocking user from editing whole namespace
* Replace ugly ipboptions parsing code in Title.php with a simple message

Requires schema change (I showed it to Tim Starling).

16 files changed:
RELEASE-NOTES
includes/AutoLoader.php
includes/DefaultSettings.php
includes/LogPage.php
includes/SpecialPage.php
includes/Title.php
includes/User.php
includes/UserRestriction.php [new file with mode: 0644]
includes/specials/SpecialListUserRestrictions.php [new file with mode: 0644]
includes/specials/SpecialRemoveRestrictions.php [new file with mode: 0644]
includes/specials/SpecialRestrictUser.php [new file with mode: 0644]
languages/Language.php
languages/messages/MessagesEn.php
maintenance/archives/patch-user_restrictions.sql [new file with mode: 0644]
maintenance/tables.sql
maintenance/updaters.inc

index 83d8585..ace79ce 100644 (file)
@@ -144,7 +144,8 @@ The following extensions are migrated into MediaWiki 1.14:
   query string use the article path, rather than the script path
 * Added the ability to set the target attribute on external links with 
   $wgExternalLinkTarget
-
+* (bug 674) Allow admins to block users from editing specific articles and
+  namespaces
 
 === Bug fixes in 1.14 ===
 
index 7349bde..daa3cd8 100644 (file)
@@ -197,6 +197,7 @@ $wgAutoloadLocalClasses = array(
        'UserArray' => 'includes/UserArray.php',
        'UserArrayFromResult' => 'includes/UserArray.php',
        'UserMailer' => 'includes/UserMailer.php',
+       'UserRestriction' => 'includes/UserRestriction.php',
        'UserRightsProxy' => 'includes/UserRightsProxy.php',
        'WatchedItem' => 'includes/WatchedItem.php',
        'WatchlistEditor' => 'includes/WatchlistEditor.php',
index 3521bf1..62559d4 100644 (file)
@@ -1176,6 +1176,7 @@ $wgGroupPermissions['sysop']['suppressredirect'] = true;
 $wgGroupPermissions['sysop']['apihighlimits']    = true;
 $wgGroupPermissions['sysop']['browsearchive']    = true;
 $wgGroupPermissions['sysop']['noratelimit']      = true;
+$wgGroupPermissions['sysop']['restrict']         = true;
 #$wgGroupPermissions['sysop']['mergehistory']     = true;
 
 // Permission to change users' group assignments
@@ -2661,6 +2662,7 @@ $wgLogTypes = array( '',
        'patrol',
        'merge',
        'suppress',
+       'restrict',
 );
 
 /**
@@ -2691,6 +2693,7 @@ $wgLogNames = array(
        'patrol'  => 'patrol-log-page',
        'merge'   => 'mergelog',
        'suppress' => 'suppressionlog',
+       'restrict' => 'restrictionlog',
 );
 
 /**
@@ -2711,6 +2714,7 @@ $wgLogHeaders = array(
        'patrol'  => 'patrol-log-header',
        'merge'   => 'mergelogpagetext',
        'suppress' => 'suppressionlogtext',
+       'restrict' => 'restrictionlogtext',
 );
 
 /**
@@ -2743,6 +2747,8 @@ $wgLogActions = array(
        'suppress/event'    => 'logdelete-logentry',
        'suppress/delete'   => 'suppressedarticle',
        'suppress/block'        => 'blocklogentry',
+       'restrict/restrict' => 'restrictentry',
+       'restrict/remove'   => 'restrictremoveentry',
 );
 
 /**
@@ -2813,6 +2819,8 @@ $wgSpecialPageGroups = array(
        'Preferences'               => 'users',
        'Resetpass'                 => 'users',
        'DeletedContributions'      => 'users',
+       'ListUserRestrictions'      => 'users',
+       'RestrictUser'              => 'users',
 
        'Mostlinked'                => 'highuse',
        'Mostlinkedcategories'      => 'highuse',
index 00f2422..523559c 100644 (file)
@@ -193,6 +193,19 @@ class LogPage {
                                        } else {
                                                $rv = wfMsgForContent( $wgLogActions[$key], $titleLink );
                                        }
+                               } elseif( $type == 'restrict' ) {
+                                       if( $params[0] == UserRestriction::PAGE )
+                                               $subj = wfMsgExt( 'restrictlogpage', 'parseinline', $params[1] );
+                                       if( $params[0] == UserRestriction::NAMESPACE )
+                                               $subj = wfMsgExt( 'restrictlognamespace', 'parseinline', $wgLang->getDisplayNsText( $params[1] ) );
+                                       $expiry = '';
+                                       if( $key == 'restrict/restrict' )
+                                               $expiry = $wgLang->translateBlockExpiry( $params[2] );
+                                       if ( $skin ) {
+                                               $rv = wfMsg( $wgLogActions[$key], $titleLink, $subj, $expiry );
+                                       } else {
+                                               $rv = wfMsgForContent( $wgLogActions[$key], $titleLink, $subj, $expiry );
+                                       }
                                } else {
                                        $details = '';
                                        array_unshift( $params, $titleLink );
@@ -260,6 +273,7 @@ class LogPage {
                                }
                                break;
                        case 'rights':
+                       case 'restrict':
                                $text = $wgContLang->ucfirst( $title->getText() );
                                $titleLink = $skin->makeLinkObj( Title::makeTitle( NS_USER, $text ) );
                                break;
index fec6c0a..b382385 100644 (file)
@@ -126,6 +126,9 @@ class SpecialPage
                'Allpages'                  => 'SpecialAllpages',
                'Prefixindex'               => 'SpecialPrefixindex',
                'Ipblocklist'               => array( 'SpecialPage', 'Ipblocklist' ),
+               'ListUserRestrictions'      => array( 'SpecialPage', 'ListUserRestrictions' ),
+               'RemoveRestrictions'        => array( 'UnlistedSpecialPage', 'RemoveRestrictions', 'restrict' ),
+               'RestrictUser'              => array( 'SpecialPage', 'RestrictUser', 'restrict' ),
                'Specialpages'              => array( 'UnlistedSpecialPage', 'Specialpages' ),
                'Contributions'             => array( 'SpecialPage', 'Contributions' ),
                'Emailuser'                 => array( 'UnlistedSpecialPage', 'Emailuser' ),
index ab6a0d0..0bef7bc 100644 (file)
@@ -1107,9 +1107,7 @@ class Title {
                }
                $errors = $this->getUserPermissionsErrorsInternal( $action, $user, $doExpensiveQueries );
 
-               global $wgContLang;
-               global $wgLang;
-               global $wgEmailConfirmToEdit;
+               global $wgContLang, $wgLang, $wgEmailConfirmToEdit;
 
                if ( $wgEmailConfirmToEdit && !$user->isEmailConfirmed() && $action != 'createaccount' ) {
                        $errors[] = array( 'confirmedittext' );
@@ -1141,20 +1139,7 @@ class Title {
                        $blockTimestamp = $wgLang->timeanddate( wfTimestamp( TS_MW, $user->mBlock->mTimestamp ), true );
 
                        if ( $blockExpiry == 'infinity' ) {
-                               // Entry in database (table ipblocks) is 'infinity' but 'ipboptions' uses 'infinite' or 'indefinite'
-                               $scBlockExpiryOptions = wfMsg( 'ipboptions' );
-
-                               foreach ( explode( ',', $scBlockExpiryOptions ) as $option ) {
-                                       if ( strpos( $option, ':' ) == false )
-                                               continue;
-
-                                       list ($show, $value) = explode( ":", $option );
-
-                                       if ( $value == 'infinite' || $value == 'indefinite' ) {
-                                               $blockExpiry = $show;
-                                               break;
-                                       }
-                               }
+                               $blockExpiry = wfMsg( 'ipbinfinite' );
                        } else {
                                $blockExpiry = $wgLang->timeanddate( wfTimestamp( TS_MW, $blockExpiry ), true );
                        }
@@ -1164,9 +1149,9 @@ class Title {
                        $errors[] = array( ($block->mAuto ? 'autoblockedtext' : 'blockedtext'), $link, $reason, $ip, $name, 
                                $blockid, $blockExpiry, $intended, $blockTimestamp );
                }
-               
+
                // Remove the errors being ignored.
-               
+
                foreach( $errors as $index => $error ) {
                        $error_key = is_array($error) ? $error[0] : $error;
                        
@@ -1189,6 +1174,8 @@ class Title {
         * @return \type{\array} Array of arrays of the arguments to wfMsg to explain permissions problems.
         */
        private function getUserPermissionsErrorsInternal( $action, $user, $doExpensiveQueries = true ) {
+               global $wgLang;
+
                wfProfileIn( __METHOD__ );
 
                $errors = array();
@@ -1334,6 +1321,26 @@ class Title {
                        $errors[] = $return;
                }
 
+               // Check per-user restrictions
+               if( $action != 'read' ) {
+                       $r = $user->getRestrictionForPage( $this );
+                       if( !$r )
+                               $r = $user->getRestrictionForNamespace( $this->getNamespace() );
+                       if( $r ) {
+                               $start = $wgLang->timeanddate( $r->getTimestamp() );
+                               $end = $r->getExpiry() == 'infinity' ?
+                                       wfMsg( 'ipbinfinite' ) :
+                                       $wgLang->timeanddate( $r->getExpiry() );
+                               if( $r->isPage() )
+                                       $errors[] = array( 'userrestricted-page', $this->getFullText(),
+                                               $r->getBlockerText(), $r->getReason(), $start, $end );
+                               elseif( $r->isNamespace() ) {
+                                       $errors[] = array( 'userrestricted-namespace', $wgLang->getDisplayNsText( $this->getNamespace() ),
+                                               $r->getBlockerText(), $r->getReason(), $start, $end );
+                               }
+                       }
+               }
+
                wfProfileOut( __METHOD__ );
                return $errors;
        }
index f7d7880..2efe9f1 100644 (file)
@@ -118,6 +118,8 @@ class User {
                'mEditCount',
                // user_group table
                'mGroups',
+               // user_restrictions table
+               'mRestrictions',
        );
 
        /**
@@ -156,6 +158,7 @@ class User {
                'proxyunbannable',
                'purge',
                'read',
+               'restrict',
                'reupload',
                'reupload-shared',
                'rollback',
@@ -177,7 +180,8 @@ class User {
        //@{
        var $mId, $mName, $mRealName, $mPassword, $mNewpassword, $mNewpassTime,
                $mEmail, $mOptions, $mTouched, $mToken, $mEmailAuthenticated,
-               $mEmailToken, $mEmailTokenExpires, $mRegistration, $mGroups;
+               $mEmailToken, $mEmailTokenExpires, $mRegistration, $mGroups,
+               $mRestrictions;
        //@}
 
        /**
@@ -297,6 +301,7 @@ class User {
        function saveToCache() {
                $this->load();
                $this->loadGroups();
+               $this->loadRestrictions();
                if ( $this->isAnon() ) {
                        // Anonymous users are uncached
                        return;
@@ -878,6 +883,7 @@ class User {
                        # Initialise user table data
                        $this->loadFromRow( $s );
                        $this->mGroups = null; // deferred
+                       $this->mRestrictions = null;
                        $this->getEditCount(); // revalidation for nulls
                        return true;
                } else {
@@ -3236,4 +3242,50 @@ class User {
                return true;
        }
 
+       // Restrictions-related block
+
+       public function loadRestrictions() {
+               if( !$this->mRestrictions )
+                       $this->mRestrictions = UserRestriction::fetchForUser( $this->isLoggedIn() ? 
+                               intval( $this->getId() ) : $this->getName()  );
+       }
+
+       public function getRestrictions() {
+               $this->loadRestrictions();
+
+               // Check for expired restrictions. Recache if found expired ones
+               static $checked = false;
+               if( !$checked ) {
+                       $expired = false;
+                       $old = $this->mRestrictions;
+                       $this->mRestrictions = array();
+                       foreach( $old as $restriction ) {
+                               if( $restriction->deleteIfExpired() )
+                                       $expired = true;
+                               else
+                                       $this->mRestrictions[] = $restriction;
+                       }
+                       if( $expired )
+                               $this->saveToCache();
+                       $checked = true;
+               }
+
+               return $this->mRestrictions;
+       }
+
+       public function getRestrictionForPage( Title $page ) {
+               foreach( $this->getRestrictions() as $r ) {
+                       if( $r->isPage() && $page->equals( $r->getPage() ) )
+                               return $r;
+               }
+               return null;
+       }
+
+       public function getRestrictionForNamespace( $nsid ) {
+               foreach( $this->getRestrictions() as $r ) {
+                       if( $r->isNamespace() && $r->getNamespace() == $nsid )
+                               return $r;
+               }
+               return null;
+       }
 }
diff --git a/includes/UserRestriction.php b/includes/UserRestriction.php
new file mode 100644 (file)
index 0000000..e90981f
--- /dev/null
@@ -0,0 +1,158 @@
+<?php
+
+/**
+ * Object that represents a user restriction
+ */
+class UserRestriction {
+       const PAGE = 'page';
+       const NAMESPACE = 'namespace';
+
+       private $mId, $mType, $mNamespace, $mPage, $mSubjectText, $mSubjectId,
+               $mBlockerId, $mBlockerText, $mReason, $mTimestamp, $mExpiry;
+
+       public static function newFromRow( $row ) {
+               if( !$row )
+                       return null;
+
+               $obj = new UserRestriction();
+               $obj->mId = $row->ur_id;
+               $obj->mType = $row->ur_type;
+               if( $obj->mType == self::PAGE ) {
+                       $obj->mPage = Title::makeTitle( $row->ur_page_namespace, $row->ur_page_title );
+               } elseif( $obj->mType == self::NAMESPACE ) {
+                       $obj->mNamespace = $row->ur_namespace;
+               } else {
+                       throw new MWException( "Unknown user restriction type: {$row->ur_type}" );
+               }
+
+               $obj->mSubjectId = $row->ur_user;
+               $obj->mSubjectText = $row->ur_user_text;
+               $obj->mBlockerId = $row->ur_by;
+               $obj->mBlockerText = $row->ur_by_text;
+               $obj->mReason = $row->ur_reason;
+               $obj->mTimestamp = wfTimestamp( TS_MW, $row->ur_timestamp );
+               $obj->mExpiry = $row->ur_expiry;
+               return $obj;
+       }
+
+       public static function fetchForUser( $user, $forWrite = false ) {
+               $dbr = wfGetDB( $forWrite ? DB_MASTER : DB_SLAVE );
+               if( is_int( $user ) )
+                       $query = array( 'ur_user' => $user );
+               else
+                       $query = array( 'ur_user_text' => $user );
+               $res = $dbr->select( 'user_restrictions', '*', $query, __METHOD__ );
+               $result = array();
+               foreach( $res as $row ) {
+                       $result[] = self::newFromRow( $row );
+               }
+               return $result;
+       }
+
+       public static function newFromId( $id, $forWrite = false ) {
+               $dbr = wfGetDB( $forWrite ? DB_MASTER : DB_SLAVE );
+               if( !$id || !is_numeric( $id ) )
+                       return null;
+               $res = $dbr->selectRow( 'user_restrictions', '*', array( 'ur_id' => $id ), __METHOD__ );
+               return self::newFromRow( $res );
+       }
+
+       public function getId() { return $this->mId; }
+       public function setId( $v ) { $this->mId = $v; }
+       public function getType() { return $this->mType; }
+       public function setType( $v ) { $this->mType = $v; }
+       public function getNamespace() { return $this->mNamespace; }
+       public function setNamespace( $v ) { $this->mNamespace = $v; }
+       public function getPage() { return $this->mPage; }
+       public function setPage( $v ) { $this->mPage = $v; }
+       public function getSubjectId() { return $this->mSubjectId; }
+       public function setSubjectId( $v ) { $this->mSubjectId = $v; }
+       public function getSubjectText() { return $this->mSubjectText; }
+       public function setSubjectText( $v ) { $this->mSubjectText = $v; }
+       public function getBlockerId() { return $this->mBlockerId; }
+       public function setBlockerId( $v ) { $this->mBlockerId = $v; }
+       public function getBlockerText() { return $this->mBlockerText; }
+       public function setBlockerText( $v ) { $this->mBlockerText = $v; }
+       public function getReason() { return $this->mReason; }
+       public function setReason( $v ) { $this->mReason = $v; }
+       public function getTimestamp() { return $this->mTimestamp; }
+       public function setTimestamp( $v ) { $this->mTimestamp = $v; }
+       public function getExpiry() { return $this->mExpiry; }
+       public function setExpiry( $v ) { $this->mExpiry = $v; }
+
+       public function isPage() {
+               return $this->mType == self::PAGE;
+       }
+       public function isNamespace() {
+               return $this->mType == self::NAMESPACE;
+       }
+
+       public function isExpired() {
+               return is_numeric( $this->mExpiry ) && $this->mExpiry < wfTimestampNow( TS_MW );
+       }
+
+       public function deleteIfExpired() {
+               if( $this->isExpired() ) {
+                       $this->delete();
+                       return true;
+               } else {
+                       return false;
+               }
+       }
+
+       public function delete() {
+               $dbw = wfGetDB( DB_MASTER );
+               $dbw->delete( 'user_restrictions', array( 'ur_id' => $this->mId ), __METHOD__ );
+               return $dbw->affectedRows();
+       }
+
+       public static function purgeExpired() {
+               $dbw = wfGetDB( DB_MASTER );
+               $dbw->delete( 'user_restrictions', array( 'ur_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ), __METHOD__ );
+       }
+
+       public function commit() {
+               $dbw = wfGetDB( DB_MASTER );
+               $this->setId( $dbw->nextSequenceValue('user_restrictions_ur_id_val') );
+               $row = array(
+                       'ur_id' => $this->mId,
+                       'ur_type' => $this->mType,
+                       'ur_user' => $this->mSubjectId,
+                       'ur_user_text' => $this->mSubjectText,
+                       'ur_by' => $this->mBlockerId,
+                       'ur_by_text' => $this->mBlockerText,
+                       'ur_reason' => $this->mReason,
+                       'ur_timestamp' => $dbw->timestamp( $this->mTimestamp ),
+                       'ur_expiry' => $this->mExpiry,
+               );
+               if( $this->isPage() ) {
+                       $row['ur_page_namespace'] = $this->mPage->getNamespace();
+                       $row['ur_page_title'] = $this->mPage->getDbKey();
+               }
+               if( $this->isNamespace() ) {
+                       $row['ur_namespace'] = $this->mNamespace;
+               }
+               $dbw->insert( 'user_restrictions', $row, __METHOD__ );
+       }
+
+       public static function formatType( $type ) {
+               return wfMsg( 'userrestrictiontype-' . $type );
+       }
+
+       /**
+        * Converts expiry which user input to the internal representation.
+        * Returns false if invalid expiry is set, Block::infinity() on empty value,
+        * Block::infinity() on infinity or 14-symbol timestamp
+        */
+       public static function convertExpiry( $expiry ) {
+               if( !$expiry )
+                       return Block::infinity();
+               if( in_array( $expiry, array( 'infinite', 'infinity', 'indefinite' ) ) )
+                       return Block::infinity();
+               $unix = @strtotime( $expiry );
+               if( !$unix || $unix === -1 )
+                       return false;
+               else
+                       return wfTimestamp( TS_MW, $unix );
+       }
+}
diff --git a/includes/specials/SpecialListUserRestrictions.php b/includes/specials/SpecialListUserRestrictions.php
new file mode 100644 (file)
index 0000000..3c986db
--- /dev/null
@@ -0,0 +1,162 @@
+<?php
+
+function wfSpecialListUserRestrictions() {
+       global $wgOut, $wgRequest;
+       
+       $wgOut->addWikiMsg( 'listuserrestrictions-intro' );
+       $f = new SpecialListUserRestrictionsForm();
+       $wgOut->addHTML( $f->getHTML() );
+
+       if( !mt_rand( 0, 10 ) )
+               UserRestriction::purgeExpired();
+       $pager = new UserRestrictionsPager( $f->getConds() );
+       if( $pager->getNumRows() ) 
+               $wgOut->addHTML( $pager->getNavigationBar() .
+                       Xml::tags( 'ul', null, $pager->getBody() ) .
+                       $pager->getNavigationBar()
+               );
+       elseif( $f->getConds() )
+               $wgOut->addWikiMsg( 'listuserrestrictions-notfound' );
+       else 
+               $wgOut->addWikiMsg( 'listuserrestrictions-empty' );
+}
+
+class SpecialListUserRestrictionsForm {
+       public function getHTML() {
+               global $wgRequest, $wgScript, $wgTitle;
+               $s = '';
+               $legend = wfMsgHtml( 'listuserrestrictions-legend' );
+               $s .= "<fieldset><legend>{$legend}</legend>";
+               $s .= "<form action=\"{$wgScript}\">";
+               $s .= Xml::hidden( 'title', $wgTitle->getPrefixedDbKey() );
+               $s .= Xml::label( wfMsgHtml( 'listuserrestrictions-type' ), 'type' ) . '&nbsp;' .
+                       self::typeSelector( 'type', $wgRequest->getVal( 'type' ), 'type' );
+               $s .= '&nbsp;';
+               $s .= Xml::inputLabel( wfMsgHtml( 'listuserrestrictions-user' ), 'user', 'user',
+                       false, $wgRequest->getVal( 'user' ) );
+               $s .= '<p>';
+               $s .= Xml::label( wfMsgHtml( 'listuserrestrictions-namespace' ), 'namespace' ) . '&nbsp;' .
+                       Xml::namespaceSelector( $wgRequest->getVal( 'namespace' ), '', false, 'namespace' );
+               $s .= '&nbsp;';
+               $s .= Xml::inputLabel( wfMsgHtml( 'listuserrestrictions-page' ), 'page', 'page',
+                       false, $wgRequest->getVal( 'page' ) );
+               $s .= Xml::submitButton( wfMsgHtml( 'listuserrestrictions-submit' ) );
+               $s .= "</p></form></fieldset>";
+               return $s;
+       }
+
+       public static function typeSelector( $name = 'type', $value = '', $id = false ) {
+               $s = new XmlSelect( $name, $id, $value );
+               $s->addOption( wfMsg( 'userrestrictiontype-none' ), '' );
+               $s->addOption( wfMsg( 'userrestrictiontype-page' ), UserRestriction::PAGE );
+               $s->addOption( wfMsg( 'userrestrictiontype-namespace' ), UserRestriction::NAMESPACE );
+               return $s->getHTML();
+       }
+
+       public function getConds() {
+               global $wgRequest;
+               $conds = array();
+
+               $type = $wgRequest->getVal( 'type' );
+               if( in_array( $type, array( UserRestriction::PAGE, UserRestriction::NAMESPACE ) ) )
+                       $conds['ur_type'] = $type;
+
+               $user = $wgRequest->getVal( 'user' );
+               if( $user )
+                       $conds['ur_user_text'] = $user;
+
+               $namespace = $wgRequest->getVal( 'namespace' );
+               if( $namespace || $namespace === '0' )
+                       $conds['ur_namespace'] = $namespace;
+
+               $page = $wgRequest->getVal( 'page' );
+               $title = Title::newFromText( $page );
+               if( $title ) {
+                       $conds['ur_page_namespace'] = $title->getNamespace();
+                       $conds['ur_page_title'] = $title->getDbKey();
+               }
+
+               return $conds;
+       }
+}
+
+class UserRestrictionsPager extends ReverseChronologicalPager {
+       public $mConds;
+
+       public function __construct( $conds = array() ) {
+               $this->mConds = $conds;
+               parent::__construct();
+       }
+
+       public function getStartBody() {
+               # Copied from Special:Ipblocklist
+               wfProfileIn( __METHOD__ );
+               # Do a link batch query
+               $this->mResult->seek( 0 );
+               $lb = new LinkBatch;
+
+               # Faster way
+               # Usernames and titles are in fact related by a simple substitution of space -> underscore
+               # The last few lines of Title::secureAndSplit() tell the story.
+               foreach( $this->mResult as $row ) {
+                       $name = str_replace( ' ', '_', $row->ur_by_text );
+                       $lb->add( NS_USER, $name );
+                       $lb->add( NS_USER_TALK, $name );
+                       $name = str_replace( ' ', '_', $row->ur_user_text );
+                       $lb->add( NS_USER, $name );
+                       $lb->add( NS_USER_TALK, $name );
+                       if( $row->ur_type == UserRestriction::PAGE )
+                               $lb->add( $row->ur_page_namespace, $row->ur_page_title );
+               }
+               $lb->execute();
+               wfProfileOut( __METHOD__ );
+               return '';
+       }
+
+       public function getQueryInfo() {
+               return array(
+                       'tables' => 'user_restrictions',
+                       'fields' => '*',
+                       'conds' => $this->mConds,
+               );
+       }
+
+       public function formatRow( $row ) {
+               return self::formatRestriction( UserRestriction::newFromRow( $row )  );
+       }
+
+       // Split off for use on Special:RestrictUser
+       public static function formatRestriction( $r ) {
+               global $wgUser, $wgLang;
+               $sk = $wgUser->getSkin();
+               $timestamp = $wgLang->timeanddate( $r->getTimestamp(), true );
+               $blockerlink = $sk->userLink( $r->getBlockerId(), $r->getBlockerText() ) .
+                       $sk->userToolLinks( $r->getBlockerId(), $r->getBlockerText() );
+               $subjlink = $sk->userLink( $r->getSubjectId(), $r->getSubjectText() ) .
+                       $sk->userToolLinks( $r->getSubjectId(), $r->getSubjectText() );
+               $expiry = is_numeric( $r->getExpiry() ) ?
+                       wfMsg( 'listuserrestrictions-row-expiry', $wgLang->timeanddate( $r->getExpiry() ) ) :
+                       wfMsg( 'ipbinfinite' );
+               $msg = '';
+               if( $r->isNamespace() ) {
+                       $msg = wfMsgHtml( 'listuserrestrictions-row-ns', $subjlink,
+                               $wgLang->getDisplayNsText( $r->getNamespace() ), $expiry );
+               }
+               if( $r->isPage() ) {
+                       $pagelink = $sk->link( $r->getPage() );
+                       $msg = wfMsgHtml( 'listuserrestrictions-row-page', $subjlink,
+                               $pagelink, $expiry );
+               }
+               $reason = $sk->commentBlock( $r->getReason() );
+               $removelink = '';
+               if( $wgUser->isAllowed( 'restrict' ) ) {
+                       $removelink = '(' . $sk->link( SpecialPage::getTitleFor( 'RemoveRestrictions' ),
+                               wfMsgHtml( 'listuserrestrictions-remove' ), array(), array( 'id' => $r->getId() ) ) . ')';
+               }
+               return "<li>{$timestamp}, {$blockerlink} {$msg} {$reason} {$removelink}</li>\n";
+       }
+
+       public function getIndexField() {
+               return 'ur_timestamp';
+       }
+}
diff --git a/includes/specials/SpecialRemoveRestrictions.php b/includes/specials/SpecialRemoveRestrictions.php
new file mode 100644 (file)
index 0000000..2c157ae
--- /dev/null
@@ -0,0 +1,62 @@
+<?php
+
+function wfSpecialRemoveRestrictions() {
+       global $wgOut, $wgRequest, $wgUser, $wgLang, $wgTitle;
+       $sk = $wgUser->getSkin();
+
+       $id = $wgRequest->getVal( 'id' );
+       if( !is_numeric( $id ) ) {
+               $wgOut->addWikiMsg( 'removerestrictions-noid' );
+               return;
+       }
+
+       UserRestriction::purgeExpired();
+       $r = UserRestriction::newFromId( $id, true );
+       if( !$r ) {
+               $wgOut->addWikiMsg( 'removerestrictions-wrongid' );
+               return;
+       }
+
+       $legend = wfMsgHtml( 'removerestrictions-legend' );
+
+       $form = array();
+       $form['removerestrictions-user'] = $sk->userLink( $r->getSubjectId(), $r->getSubjectText() ) .
+               $sk->userToolLinks( $r->getSubjectId(), $r->getSubjectText() );
+       $form['removerestrictions-type'] = UserRestriction::formatType( $r->getType() );
+       if( $r->isPage() )
+               $form['removerestrictions-page'] = $sk->link( $r->getPage() );
+       if( $r->isNamespace() )
+               $form['removerestrictions-namespace'] = $wgLang->getDisplayNsText( $r->getNamespace() );
+       $form['removerestrictions-reason'] = Xml::input( 'reason' );
+
+       $result = null;
+       if( $wgRequest->wasPosted() && $wgUser->matchEditToken( $wgRequest->getVal( 'edittoken' ) ) )
+               $result = wfSpecialRemoveRestrictionsProcess( $r );
+
+       $wgOut->addWikiMsg( 'removerestrictions-intro' );
+       $wgOut->addHTML( "<fieldset><legend>{$legend}</legend>" );
+       if( $result )
+               $wgOut->addHTML( '<strong class="success">' . wfMsgExt( 'removerestrictions-success',
+                       'parseinline', $r->getSubjectText() ) . '</strong>' );
+       $wgOut->addHTML( Xml::openElement( 'form', array( 'action' => $wgTitle->getLocalUrl( array( 'id' => $id ) ),
+               'method' => 'post' ) ) );
+       $wgOut->addHTML( Xml::buildForm( $form, 'removerestrictions-submit' ) );
+       $wgOut->addHTML( Xml::hidden( 'id', $r->getId() ) );
+       $wgOut->addHTML( Xml::hidden( 'title', $wgTitle->getPrefixedDbKey() ) );
+       $wgOut->addHTML( Xml::hidden( 'edittoken', $wgUser->editToken() ) );
+       $wgOut->addHTML( "</form></fieldset>" );
+}
+
+function wfSpecialRemoveRestrictionsProcess( $r ) {
+       global $wgUser, $wgRequest;
+       $reason = $wgRequest->getVal( 'reason' );
+       $result = $r->delete();
+       $log = new LogPage( 'restrict' );
+       $params = array( $r->getType() );
+       if( $r->isPage() )
+               $params[] = $r->getPage()->getPrefixedDbKey();
+       if( $r->isNamespace() )
+               $params[] = $r->getNamespace();
+       $log->addEntry( 'remove', Title::makeTitle( NS_USER, $r->getSubjectText() ), $reason, $params );
+       return $result;
+}
diff --git a/includes/specials/SpecialRestrictUser.php b/includes/specials/SpecialRestrictUser.php
new file mode 100644 (file)
index 0000000..8ce4b04
--- /dev/null
@@ -0,0 +1,187 @@
+<?php
+
+function wfSpecialRestrictUser( $par = null ) {
+       global $wgOut, $wgRequest;
+       $user = $userOrig = null;
+       if( $par ) {
+               $userOrig = $par;
+       } elseif( $wgRequest->getVal( 'user' ) ) {
+               $userOrig = $wgRequest->getVal( 'user' );
+       } else {
+               $wgOut->addHTML( RestrictUserForm::selectUserForm() );
+               return;
+       }
+       $isIP = User::isIP( $userOrig );
+       if( $isIP )
+               $user = $userOrig;
+       else
+               $user = User::getCanonicalName( $userOrig );
+       $uid = User::idFromName( $user );
+       if( !$uid && !$isIP ) {
+               $err = '<strong class="error">' . wfMsgHtml( 'restrictuser-notfound' ) . '</strong>';
+               $wgOut->addHTML( RestrictUserForm::selectUserForm( $userOrig, $err ) );
+               return;
+       }
+       $wgOut->addHTML( RestrictUserForm::selectUserForm( $user ) );
+
+       UserRestriction::purgeExpired();
+       $old = UserRestriction::fetchForUser( $user, true );
+
+       RestrictUserForm::pageRestrictionForm( $uid, $user, $old );
+       RestrictUserForm::namespaceRestrictionForm( $uid, $user, $old );
+
+       $old = UserRestriction::fetchForUser( $user, true );    // Renew it after possible changes in previous two functions
+       if( $old ) {
+               $wgOut->addHTML( RestrictUserForm::existingRestrictions( $old ) );
+       }
+}
+
+class RestrictUserForm {
+       public static function selectUserForm( $val = null, $error = null ) {
+               global $wgScript, $wgTitle;
+               $legend = wfMsgHtml( 'restrictuser-userselect' );
+               $s  = "<fieldset><legend>{$legend}</legend><form action=\"{$wgScript}\">";
+               if( $error )
+                       $s .= '<p>' . $error . '</p>';
+               $s .= Xml::hidden( 'title', $wgTitle->getPrefixedDbKey() );
+               $form = array( 'restrictuser-user' => Xml::input( 'user', false, $val ) );
+               $s .= Xml::buildForm( $form, 'restrictuser-go' );
+               $s .= "</form></fieldset>";
+               return $s;
+       }
+
+       public static function existingRestrictions( $restrictions ) {
+               require_once( dirname( __FILE__ ) . '/SpecialListUserRestrictions.php' );
+               $legend = wfMsgHtml( 'restrictuser-existing' );
+               $s  = "<fieldset><legend>{$legend}</legend><ul>";
+               foreach( $restrictions as $r )
+                       $s .= UserRestrictionsPager::formatRestriction( $r );
+               $s .= "</ul></fieldset>";
+               return $s;
+       }
+
+       public static function pageRestrictionForm( $uid, $user, $oldRestrictions ) {
+               global $wgOut, $wgTitle, $wgRequest, $wgUser;
+               $error = '';
+               $success = false;
+               if( $wgRequest->wasPosted() && $wgRequest->getVal( 'type' ) == UserRestriction::PAGE &&
+                       $wgUser->matchEditToken( $wgRequest->getVal( 'edittoken' ) ) ) {
+                               $title = Title::newFromText( $wgRequest->getVal( 'page' ) );
+                               if( !$title )
+                                       $error = wfMsgExt( 'restrictuser-badtitle', 'parseinline', $wgRequest->getVal( 'page' ) );
+                               elseif( UserRestriction::convertExpiry( $wgRequest->getVal( 'expiry' ) ) === false )
+                                       $error = wfMsgExt( 'restrictuser-badexpiry', 'parseinline', $wgRequest->getVal( 'expiry' ) );
+                               else 
+                                       foreach( $oldRestrictions as $r )
+                                               if( $r->isPage() && $r->getPage()->equals( $title ) )
+                                                       $error = wfMsgExt( 'restrictuser-duptitle', 'parse' );
+                               if( !$error ) {
+                                       self::doPageRestriction( $uid, $user );
+                                       $success = true;
+                               }
+               }
+               $useRequestValues = $wgRequest->getVal( 'type' ) == UserRestriction::PAGE;
+               $legend = wfMsgHtml( 'restrictuser-legend-page' );
+               $wgOut->addHTML( "<fieldset><legend>{$legend}</legend>" );
+               if( $error )
+                       $wgOut->addHTML( '<strong class="error">' . $error . '</strong>' );
+               if( $success )
+                       $wgOut->addHTML( '<strong class="success">' . wfMsgExt( 'restrictuser-success',
+                               'parseinline', $user ) . '</strong>' );
+               $wgOut->addHTML( Xml::openElement( 'form', array( 'action' => $wgTitle->getLocalUrl(),
+                       'method' => 'post' ) ) );
+               $wgOut->addHTML( Xml::hidden( 'type', UserRestriction::PAGE ) );
+               $wgOut->addHTML( Xml::hidden( 'edittoken', $wgUser->editToken() ) );
+               $wgOut->addHTML( Xml::hidden( 'user', $user ) );
+               $form = array();
+               $form['restrictuser-title'] = Xml::input( 'page', false,
+                       $useRequestValues ? $wgRequest->getVal( 'page' ) : false );
+               $form['restrictuser-expiry'] = Xml::input( 'expiry', false,
+                       $useRequestValues ? $wgRequest->getVal( 'expiry' ) : false );
+               $form['restrictuser-reason'] = Xml::input( 'reason', false,
+                       $useRequestValues ? $wgRequest->getVal( 'reason' ) : false );
+               $wgOut->addHTML( Xml::buildForm( $form, 'restrictuser-sumbit' ) );
+               $wgOut->addHTML( "</form></fieldset>" );
+       }
+
+       public static function doPageRestriction( $uid, $user ) {
+               global $wgUser, $wgRequest;
+               $r = new UserRestriction();
+               $r->setType( UserRestriction::PAGE );
+               $r->setPage( Title::newFromText( $wgRequest->getVal( 'page' ) ) );
+               $r->setSubjectId( $uid );
+               $r->setSubjectText( $user );
+               $r->setBlockerId( $wgUser->getId() );
+               $r->setBlockerText( $wgUser->getName() );
+               $r->setReason( $wgRequest->getVal( 'reason' ) );
+               $r->setExpiry( UserRestriction::convertExpiry( $wgRequest->getVal( 'expiry' ) ) );
+               $r->setTimestamp( wfTimestampNow( TS_MW ) );
+               $r->commit();
+               $logExpiry = $wgRequest->getVal( 'expiry' ) ? $wgRequest->getVal( 'expiry' ) : Block::infinity();
+               $l = new LogPage( 'restrict' );
+               $l->addEntry( 'restrict', Title::makeTitle( NS_USER, $user ), $r->getReason(),
+                       array( $r->getType(), $r->getPage()->getFullText(), $logExpiry) );
+       }
+
+       public static function namespaceRestrictionForm( $uid, $user, $oldRestrictions ) {
+               global $wgOut, $wgTitle, $wgRequest, $wgUser, $wgContLang;
+               $error = '';
+               $success = false;
+               if( $wgRequest->wasPosted() && $wgRequest->getVal( 'type' ) == UserRestriction::NAMESPACE &&
+                       $wgUser->matchEditToken( $wgRequest->getVal( 'edittoken' ) ) ) {
+                               $ns = $wgRequest->getVal( 'namespace' );
+                               if( $wgContLang->getNsText( $ns ) === false )
+                                       $error = wfMsgExt( 'restrictuser-badnamespace', 'parseinline' );
+                               elseif( UserRestriction::convertExpiry( $wgRequest->getVal( 'expiry' ) ) === false )
+                                       $error = wfMsgExt( 'restrictuser-badexpiry', 'parseinline', $wgRequest->getVal( 'expiry' ) );
+                               else 
+                                       foreach( $oldRestrictions as $r )
+                                               if( $r->isNamespace() && $r->getNamespace() == $ns )
+                                                       $error = wfMsgExt( 'restrictuser-dupnamespace', 'parse' );
+                               if( !$error ) {
+                                       self::doNamespaceRestriction( $uid, $user );
+                                       $success = true;
+                               }
+               }
+               $useRequestValues = $wgRequest->getVal( 'type' ) == UserRestriction::NAMESPACE;
+               $legend = wfMsgHtml( 'restrictuser-legend-namespace' );
+               $wgOut->addHTML( "<fieldset><legend>{$legend}</legend>" );
+               if( $error )
+                       $wgOut->addHTML( '<strong class="error">' . $error . '</strong>' );
+               if( $success )
+                       $wgOut->addHTML( '<strong class="success">' . wfMsgExt( 'restrictuser-success',
+                               'parseinline', $user ) . '</strong>' );
+               $wgOut->addHTML( Xml::openElement( 'form', array( 'action' => $wgTitle->getLocalUrl(),
+                       'method' => 'post' ) ) );
+               $wgOut->addHTML( Xml::hidden( 'type', UserRestriction::NAMESPACE ) );
+               $wgOut->addHTML( Xml::hidden( 'edittoken', $wgUser->editToken() ) );
+               $wgOut->addHTML( Xml::hidden( 'user', $user ) );
+               $form = array();
+               $form['restrictuser-namespace'] = Xml::namespaceSelector( $wgRequest->getVal( 'namespace' ) );
+               $form['restrictuser-expiry'] = Xml::input( 'expiry', false,
+                       $useRequestValues ? $wgRequest->getVal( 'expiry' ) : false );
+               $form['restrictuser-reason'] = Xml::input( 'reason', false,
+                       $useRequestValues ? $wgRequest->getVal( 'reason' ) : false );
+               $wgOut->addHTML( Xml::buildForm( $form, 'restrictuser-sumbit' ) );
+               $wgOut->addHTML( "</form></fieldset>" );
+       }
+
+       public static function doNamespaceRestriction( $uid, $user ) {
+               global $wgUser, $wgRequest;
+               $r = new UserRestriction();
+               $r->setType( UserRestriction::NAMESPACE );
+               $r->setNamespace( $wgRequest->getVal( 'namespace' ) );
+               $r->setSubjectId( $uid );
+               $r->setSubjectText( $user );
+               $r->setBlockerId( $wgUser->getId() );
+               $r->setBlockerText( $wgUser->getName() );
+               $r->setReason( $wgRequest->getVal( 'reason' ) );
+               $r->setExpiry( UserRestriction::convertExpiry( $wgRequest->getVal( 'expiry' ) ) );
+               $r->setTimestamp( wfTimestampNow( TS_MW ) );
+               $r->commit();
+               $logExpiry = $wgRequest->getVal( 'expiry' ) ? $wgRequest->getVal( 'expiry' ) : Block::infinity();
+               $l = new LogPage( 'restrict' );
+               $l->addEntry( 'restrict', Title::makeTitle( NS_USER, $user ), $r->getReason(),
+                       array( $r->getType(), $r->getNamespace(), $logExpiry ) );
+       }
+}
index fa485d1..29e8a88 100644 (file)
@@ -264,6 +264,20 @@ class Language {
                return strtr($ns, '_', ' ');
        }
 
+       /**
+        * A convenience function that returns the same thing as
+        * getFormattedNsText() except with '(Main)' for zero namespace.
+        *
+        * @return array
+        */
+       function getDisplayNsText( $index ) {
+               if( $index == 0 ) {
+                       return wfMsg( 'blanknamespace' );
+               } else {
+                       return $this->getFormattedNsText( $index );
+               }
+       }
+
        /**
         * Get a namespace key by value, case insensitive.
         * Only matches namespace names for the current language, not the
index 7588d75..f374e57 100644 (file)
@@ -439,6 +439,9 @@ $specialPageAliases = array(
        'Blankpage'                 => array( 'BlankPage' ),
        'LinkSearch'                => array( 'LinkSearch' ),
        'DeletedContributions'      => array( 'DeletedContributions' ),
+       'ListUserRestrictions'      => array( 'ListUserRestrictions' ),
+       'RemoveRestrictions'        => array( 'RemoveRestrictions' ),
+       'RestrictUser'              => array( 'RestrictUser' ),
 );
 
 /**
@@ -1197,6 +1200,22 @@ It appears to have been deleted.',
 'edit-no-change'                   => 'Your edit was ignored, because no change was made to the text.',
 'edit-already-exists'              => 'Could not create a new page.
 It already exists.',
+'userrestricted-page'              => "<big>'''Your user name or IP address has been restricted from editing page \"$1\".'''</big>
+
+The restriction was put by [[User:$2|$2]].
+The reason given is ''$3''.
+
+Restriction was put at $4 and expires at $5.
+
+You can contact [[User:$2|$2]] or another [[{{MediaWiki:Grouppage-sysop}}|administrator]] to discuss the restriction.",
+'userrestricted-namespace'         => "<big>'''Your user name or IP address has been restricted from editing $1 namespace.'''</big>
+
+The restriction was put by [[User:$2|$2]].
+The reason given is ''$3''.
+
+Restriction was put at $4 and expires at $5.
+
+You can contact [[User:$2|$2]] or another [[{{MediaWiki:Grouppage-sysop}}|administrator]] to discuss the restriction.",
 
 # Parser/template warnings
 'expensive-parserfunction-warning'        => 'Warning: This page contains too many expensive parser function calls.
@@ -1637,6 +1656,7 @@ please see math/README to configure.',
 'right-userrights'           => 'Edit all user rights',
 'right-userrights-interwiki' => 'Edit user rights of users on other wikis',
 'right-siteadmin'            => 'Lock and unlock the database',
+'right-restrict'             => 'Restrict user from editing certain namespaces and pages',
 
 # User rights log
 'rightslog'      => 'User rights log',
@@ -2497,6 +2517,7 @@ Fill in a specific reason below (for example, citing particular pages that were
 'ipbsubmit'                       => 'Block this user',
 'ipbother'                        => 'Other time:',
 'ipboptions'                      => '2 hours:2 hours,1 day:1 day,3 days:3 days,1 week:1 week,2 weeks:2 weeks,1 month:1 month,3 months:3 months,6 months:6 months,1 year:1 year,infinite:infinite', # display1:time1,display2:time2,...
+'ipbinfinite'                     => 'infinite',
 'ipbotheroption'                  => 'other',
 'ipbotherreason'                  => 'Other/additional reason:',
 'ipbhidename'                     => 'Hide username from the block log, active block list and user list',
@@ -2569,6 +2590,69 @@ Please contact your Internet service provider or tech support and inform them of
 You cannot create an account',
 'cant-block-while-blocked'        => 'You cannot block other users while you are blocked.',
 
+# Special:ListUserRestrictions
+'listuserrestrictions'            => 'List of user restrictions',
+'listuserrestrictions-intro'      => 'This list contains all restrictions from editing certain pages and namespaces put on users.
+Note that blocks are listed [[Special:Ipblocklist|there]] and are not listed here.',
+'listuserrestrictions-row-ns'     => 'restricted $1 from editing $2 namespace ($3)',
+'listuserrestrictions-row-page'   => 'restricted $1 from editing $2 ($3)',
+'listuserrestrictions-row-expiry' => 'expires at $1',
+'listuserrestrictions-legend'     => 'Find a restriction',
+'listuserrestrictions-type'       => 'Type:',
+'listuserrestrictions-user'       => 'User:',
+'listuserrestrictions-namespace'  => 'Namespace:',
+'listuserrestrictions-page'       => 'Page:',
+'listuserrestrictions-submit'     => 'Go',
+'listuserrestrictions-notfound'   => 'There is no restriction that matches specified criteria.',
+'listuserrestrictions-empty'      => 'This list is empty.',
+'listuserrestrictions-remove'     => 'remove',
+'userrestrictiontype-none'        => '(none)',
+'userrestrictiontype-namespace'   => 'Namespace',
+'userrestrictiontype-page'        => 'Page',
+
+# Special:RemoveRestrictions
+'removerestrictions'              => 'Remove restriction from a user',
+'removerestrictions-intro'        => 'Use the form below to remove a restriction from a certain user.',
+'removerestrictions-noid'         => 'No restriction ID was specified.',
+'removerestrictions-wrongid'      => 'Restriction with that ID not found. Most probably someone has removed it or it expired.',
+'removerestrictions-legend'       => 'Remove a restriction',
+'removerestrictions-user'         => 'Restricted user:',
+'removerestrictions-type'         => 'Restriction type:',
+'removerestrictions-page'         => 'Page:',
+'removerestrictions-namespace'    => 'Namespace:',
+'removerestrictions-reason'       => 'Reason:',
+'removerestrictions-submit'       => 'Remove the restriction',
+'removerestrictions-success'      => 'Successfully removed the restriction from [[User:$1|$1]].',
+
+# Special:RestrictUser
+'restrictuser'                  => 'Restrict user',
+'restrictuser-userselect'       => 'Select a user',
+'restrictuser-user'             => 'User:',
+'restrictuser-go'               => 'Restrict user',
+'restrictuser-notfound'         => 'User not found',
+'restrictuser-existing'         => 'Existing restrictions',
+'restrictuser-legend-page'      => 'Restrict from editing certain page',
+'restrictuser-legend-namespace' => 'Restrict from editing certain namespace',
+'restrictuser-title'            => 'Page to restrict:',
+'restrictuser-namespace'        => 'Namespace:',
+'restrictuser-expiry'           => 'Expires:',
+'restrictuser-reason'           => 'Reason:',
+'restrictuser-sumbit'           => 'Restrict user',
+'restrictuser-badtitle'         => 'Invalid title specified: $1.',
+'restrictuser-badnamespace'     => 'Invalid namespace specified.',
+'restrictuser-badexpiry'        => 'Invalid expiry specified: $1.',
+'restrictuser-duptitle'         => 'User is already restricted from editing this title.',
+'restrictuser-dupnamespace'     => 'User is already restricted from editing this namespace.',
+'restrictuser-success'          => 'Successfully restricted user $1.',
+
+# Special:Log/restrict
+'restrictionlog'       => 'User restriction log',
+'restrictionlogtext'   => 'This log contains all restrictions put on users by administrators.',
+'restrictentry'        => 'restricted $1 from editing $2 (expiry set to $3)',
+'restrictremoveentry'  => 'removed restriction from $1 for editing $2',
+'restrictlognamespace' => '$1 namespace',
+'restrictlogpage'      => '[[$1]]',
+
 # Developer tools
 'lockdb'              => 'Lock database',
 'unlockdb'            => 'Unlock database',
diff --git a/maintenance/archives/patch-user_restrictions.sql b/maintenance/archives/patch-user_restrictions.sql
new file mode 100644 (file)
index 0000000..b78a108
--- /dev/null
@@ -0,0 +1,40 @@
+-- Allows admins to block user from editing certain namespaces or pages
+
+CREATE TABLE /*$wgDBprefix*/user_restrictions (
+       -- ID of the restriction
+       ur_id int NOT NULL auto_increment,
+
+       -- Restriction type. Block from either editing namespace or page
+       ur_type varbinary(255) NOT NULL,
+       -- Namespace to restrict if ur_type = namespace
+       ur_namespace int default NULL,
+       -- Page to restrict if ur_type = page
+       ur_page_namespace int default NULL,
+       ur_page_title varchar(255) binary default '',
+
+       -- User that is restricted
+       ur_user int unsigned NOT NULL,
+       ur_user_text tinyblob NOT NULL,
+
+       -- User who has done this restriction
+       ur_by int unsigned NOT NULL,
+       ur_by_text varchar(255) binary NOT NULL default '',
+       -- Reason for this restriction
+       ur_reason tinyblob NOT NULL,
+
+       -- Time when this restriction was made
+       ur_timestamp varbinary(14) NOT NULL default '',
+       -- Expiry or "infinity"
+       ur_expiry varbinary(14) NOT NULL default '',
+
+       PRIMARY KEY ur_id (ur_id),
+       -- For looking up restrictions for user
+       INDEX ur_user (ur_user,ur_user_text(255)),
+       -- For Special:ListUserRestrictions
+       INDEX ur_type (ur_type(255),ur_timestamp),
+       INDEX ur_namespace (ur_namespace,ur_timestamp),
+       INDEX ur_page (ur_page_namespace,ur_page_title,ur_timestamp),
+       INDEX ur_timestamp (ur_timestamp),
+       -- For quick removal of expired restrictions
+       INDEX ur_expiry (ur_expiry)
+) /*$wgDBTableOptions*/;
index d4a60af..18b9e34 100644 (file)
@@ -1243,4 +1243,44 @@ CREATE TABLE /*$wgDBprefix*/updatelog (
   PRIMARY KEY (ul_key)
 ) /*$wgDBTableOptions*/;
 
+-- Allows admins to block user from editing certain namespaces or pages
+CREATE TABLE /*$wgDBprefix*/user_restrictions (
+       -- ID of the restriction
+       ur_id int NOT NULL auto_increment,
+
+       -- Restriction type. Block from either editing namespace or page
+       ur_type varbinary(255) NOT NULL,
+       -- Namespace to restrict if ur_type = namespace
+       ur_namespace int default NULL,
+       -- Page to restrict if ur_type = page
+       ur_page_namespace int default NULL,
+       ur_page_title varchar(255) binary default '',
+
+       -- User that is restricted
+       ur_user int unsigned NOT NULL,
+       ur_user_text tinyblob NOT NULL,
+
+       -- User who has done this restriction
+       ur_by int unsigned NOT NULL,
+       ur_by_text varchar(255) binary NOT NULL default '',
+       -- Reason for this restriction
+       ur_reason tinyblob NOT NULL,
+
+       -- Time when this restriction was made
+       ur_timestamp varbinary(14) NOT NULL default '',
+       -- Expiry or "infinity"
+       ur_expiry varbinary(14) NOT NULL default '',
+
+       PRIMARY KEY ur_id (ur_id),
+       -- For looking up restrictions for user
+       INDEX ur_user (ur_user,ur_user_text(255)),
+       -- For Special:ListUserRestrictions
+       INDEX ur_type (ur_type(255),ur_timestamp),
+       INDEX ur_namespace (ur_namespace,ur_timestamp),
+       INDEX ur_page (ur_page_namespace,ur_page_title,ur_timestamp),
+       INDEX ur_timestamp (ur_timestamp),
+       -- For quick removal of expired restrictions
+       INDEX ur_expiry (ur_expiry)
+) /*$wgDBTableOptions*/;
+
 -- vim: sw=2 sts=2 et
index 4980910..659fed5 100644 (file)
@@ -145,9 +145,10 @@ $wgMysqlUpdates = array(
        array( 'update_password_format' ),
        
        // 1.14
-       array( 'add_field', 'site_stats',     'ss_active_users',  'patch-ss_active_users.sql' ),
+       array( 'add_field', 'site_stats',     'ss_active_users',   'patch-ss_active_users.sql' ),
        array( 'do_active_users_init' ),
-       array( 'add_field', 'ipblocks',     'ipb_allow_usertalk',  'patch-ipb_allow_usertalk.sql' )
+       array( 'add_field', 'ipblocks',     'ipb_allow_usertalk',  'patch-ipb_allow_usertalk.sql' ),
+       array( 'add_table', 'user_restrictions',                   'patch-user_restrictions.sql' ),
 );