* Add autopromote condition APCOND_BLOCKED to autopromote blocked users to various...
authorRyan Schmidt <skizzerz@users.mediawiki.org>
Thu, 18 Jun 2009 02:50:16 +0000 (02:50 +0000)
committerRyan Schmidt <skizzerz@users.mediawiki.org>
Thu, 18 Jun 2009 02:50:16 +0000 (02:50 +0000)
* Add $wgRemoveGroups as a means of restricting a group's rights. The syntax is identical to $wgGroupPermissions, but users in these groups will have these rights stripped from them.
* Modify Special:ListGroupRights so that it displays revoked permissions as well (the display of assigned vs. revoked is changeable via css).
* Bump $wgStyleVersion

RELEASE-NOTES
includes/Article.php
includes/Autopromote.php
includes/DefaultSettings.php
includes/Defines.php
includes/ProtectionForm.php
includes/User.php
includes/specials/SpecialListgrouprights.php
languages/messages/MessagesEn.php
languages/messages/MessagesQqq.php
skins/common/shared.css

index f58cabf..63d6fac 100644 (file)
@@ -87,6 +87,11 @@ it from source control: http://www.mediawiki.org/wiki/Download_from_SVN
   the arrangement of checkboxes on the Special:UserRights form
 * Add hook 'UserrightsSaveUserGroups' to give extensions the ability to modify
   the groups being added and removed last-minute.
+* Add autopromote condition APCOND_BLOCKED to autopromote blocked users to various
+  user groups.
+* Add $wgRemoveGroups as a means of restricting a group's rights. The syntax is
+  identical to $wgGroupPermissions, but users in these groups will have these rights
+  stripped from them.
 
 === Bug fixes in 1.16 ===
 
index ef8ae82..d0b8786 100644 (file)
@@ -108,7 +108,7 @@ class Article {
                        return null;
                }
                $dbw = wfGetDB( DB_MASTER );
-               $dbw->replace( 'redirect', array('rd_from'), 
+               $dbw->replace( 'redirect', array('rd_from'),
                        array(
                                'rd_from' => $this->getID(),
                                'rd_namespace' => $retval->getNamespace(),
@@ -228,7 +228,7 @@ class Article {
                        return $this->mContent;
                }
        }
-       
+
        /**
         * Get the text of the current revision. No side-effects...
         *
@@ -260,12 +260,12 @@ class Article {
                global $wgParser;
                return $wgParser->getSection( $text, $section );
        }
-       
+
        /**
         * Get the text that needs to be saved in order to undo all revisions
         * between $undo and $undoafter. Revisions must belong to the same page,
         * must exist and must not be deleted
-        * @param $undo Revision 
+        * @param $undo Revision
         * @param $undoafter Revision Must be an earlier revision than $undo
         * @return mixed string on success, false on failure
         */
@@ -545,7 +545,7 @@ class Article {
        public function exists() {
                return $this->getId() > 0;
        }
-       
+
        /**
         * Check if this page is something we're going to be showing
         * some sort of sensible content for. If we return false, page
@@ -568,10 +568,10 @@ class Article {
                                $this->mCounter = 0;
                        } else {
                                $dbr = wfGetDB( DB_SLAVE );
-                               $this->mCounter = $dbr->selectField( 'page', 
-                                       'page_counter', 
-                                       array( 'page_id' => $id ), 
-                                       __METHOD__, 
+                               $this->mCounter = $dbr->selectField( 'page',
+                                       'page_counter',
+                                       array( 'page_id' => $id ),
+                                       __METHOD__,
                                        $this->getSelectOptions()
                                );
                        }
@@ -731,7 +731,7 @@ class Article {
                if( $wgOut->isPrintable() ) {
                        $wgOut->parserOptions()->setIsPrintable( true );
                }
-               
+
                wfProfileIn( __METHOD__ );
 
                # Get variables from query string
@@ -813,7 +813,7 @@ class Article {
                        wfProfileOut( __METHOD__ );
                        return;
                }
-               
+
                if( $ns == NS_USER || $ns == NS_USER_TALK ) {
                        # User/User_talk subpages are not modified. (bug 11443)
                        if( !$this->mTitle->isSubpage() ) {
@@ -907,7 +907,7 @@ class Article {
                                        $text = wfMsgExt( 'noarticletext', 'parsemag' );
                                }
                        }
-                       
+
                        # Non-existent pages
                        if( $this->getID() === 0 ) {
                                $wgOut->setRobotPolicy( 'noindex,nofollow' );
@@ -931,7 +931,7 @@ class Article {
                                wfProfileOut( __METHOD__ );
                                return;
                        }
-                       
+
                        # For ?curid=x urls, disallow indexing
                        if( $wgRequest->getInt('curid') )
                                $wgOut->setRobotPolicy( 'noindex,follow' );
@@ -1074,7 +1074,7 @@ class Article {
                $this->viewUpdates();
                wfProfileOut( __METHOD__ );
        }
-       
+
        protected function showLogs() {
                global $wgUser, $wgOut;
                $loglist = new LogEventsList( $wgUser->getSkin(), $wgOut );
@@ -1094,7 +1094,7 @@ class Article {
                                        SpecialPage::getTitleFor( 'Log' ),
                                        wfMsgHtml( 'log-fulllog' ),
                                        array(),
-                                       array( 'page' => $this->mTitle->getPrefixedText() ) 
+                                       array( 'page' => $this->mTitle->getPrefixedText() )
                                ) );
                        }
                        $wgOut->addHTML( '</div>' );
@@ -1131,7 +1131,7 @@ class Article {
                $imageUrl = $wgStylePath . '/common/images/redirect' . $imageDir . '.png';
                $imageUrl2 = $wgStylePath . '/common/images/nextredirect' . $imageDir . '.png';
                $alt2 = $wgContLang->isRTL() ? '&larr;' : '&rarr;'; // should -> and <- be used instead of entities?
-               
+
                if( $appendSubtitle ) {
                        $wgOut->appendSubtitle( wfMsgHtml( 'redirectpagesub' ) );
                }
@@ -1570,9 +1570,9 @@ class Article {
         *          Fill in blank summaries with generated text where possible
         *
         * If neither EDIT_NEW nor EDIT_UPDATE is specified, the status of the article will be detected.
-        * If EDIT_UPDATE is specified and the article doesn't exist, the function will an 
-        * edit-gone-missing error. If EDIT_NEW is specified and the article does exist, an 
-        * edit-already-exists error will be returned. These two conditions are also possible with 
+        * If EDIT_UPDATE is specified and the article doesn't exist, the function will an
+        * edit-gone-missing error. If EDIT_NEW is specified and the article does exist, an
+        * edit-already-exists error will be returned. These two conditions are also possible with
         * auto-detection due to MediaWiki's performance-optimised locking strategy.
         *
         * @param $baseRevId the revision ID this edit was based off, if any
@@ -1607,7 +1607,7 @@ class Article {
                $status = Status::newGood( array() );
 
                # Load $this->mTitle->getArticleID() and $this->mLatest if it's not already
-               $this->loadPageData(); 
+               $this->loadPageData();
 
                if( !($flags & EDIT_NEW) && !($flags & EDIT_UPDATE) ) {
                        $aid = $this->mTitle->getArticleID();
@@ -1688,9 +1688,9 @@ class Article {
 
                                # Update page
                                #
-                               # Note that we use $this->mLatest instead of fetching a value from the master DB 
-                               # during the course of this function. This makes sure that EditPage can detect 
-                               # edit conflicts reliably, either by $ok here, or by $article->getTimestamp() 
+                               # Note that we use $this->mLatest instead of fetching a value from the master DB
+                               # during the course of this function. This makes sure that EditPage can detect
+                               # edit conflicts reliably, either by $ok here, or by $article->getTimestamp()
                                # before this function is called. A previous function used a separate query, this
                                # creates a window where concurrent edits can cause an ignored edit conflict.
                                $ok = $this->updateRevisionOn( $dbw, $revision, $this->mLatest );
@@ -1883,12 +1883,12 @@ class Article {
                        $wgOut->showErrorPage( 'rcpatroldisabled', 'rcpatroldisabledtext' );
                        return;
                }
-               
+
                if( in_array(array('hookaborted'), $errors) ) {
                        // The hook itself has handled any output
                        return;
                }
-               
+
                if( in_array(array('markedaspatrollederror-noautopatrol'), $errors) ) {
                        $wgOut->setPageTitle( wfMsg( 'markedaspatrollederror' ) );
                        $wgOut->addWikiMsg( 'markedaspatrollederror-noautopatrol' );
@@ -2014,12 +2014,12 @@ class Article {
                        wfDebug( "updateRestrictions failed: $id <= 0\n" );
                        return false;
                }
-               
+
                if ( wfReadOnly() ) {
                        wfDebug( "updateRestrictions failed: read-only\n" );
                        return false;
                }
-               
+
                if ( !$this->mTitle->userCan( 'protect' ) ) {
                        wfDebug( "updateRestrictions failed: insufficient permissions\n" );
                        return false;
@@ -2064,7 +2064,7 @@ class Article {
                        if( wfRunHooks( 'ArticleProtect', array( &$this, &$wgUser, $limit, $reason ) ) ) {
 
                                $dbw = wfGetDB( DB_MASTER );
-                               
+
                                # Prepare a null revision to be added to the history
                                $modified = $current != '' && $protect;
                                if( $protect ) {
@@ -2080,9 +2080,9 @@ class Article {
                                # The schema allows multiple restrictions
                                if(!in_array('protect', $editrestriction) && !in_array('sysop', $editrestriction))
                                        $cascade = false;
-                               $cascade_description = '';       
+                               $cascade_description = '';
                                if( $cascade ) {
-                                       $cascade_description = ' ['.wfMsgForContent('protect-summary-cascade').']';      
+                                       $cascade_description = ' ['.wfMsgForContent('protect-summary-cascade').']';
                                }
 
                                if( $reason )
@@ -2094,15 +2094,15 @@ class Article {
                                foreach( $limit as $action => $restrictions  ) {
                                        if ( !isset($expiry[$action]) )
                                                $expiry[$action] = 'infinite';
-                                       
+
                                        $encodedExpiry[$action] = Block::encodeExpiry($expiry[$action], $dbw );
                                        if( $restrictions != '' ) {
                                                $protect_description .= "[$action=$restrictions] (";
                                                if( $encodedExpiry[$action] != 'infinity' ) {
-                                                       $protect_description .= wfMsgForContent( 'protect-expiring', 
+                                                       $protect_description .= wfMsgForContent( 'protect-expiring',
                                                                $wgContLang->timeanddate( $expiry[$action], false, false ) ,
                                                                $wgContLang->date( $expiry[$action], false, false ) ,
-                                                               $wgContLang->time( $expiry[$action], false, false ) );   
+                                                               $wgContLang->time( $expiry[$action], false, false ) );
                                                } else {
                                                        $protect_description .= wfMsgForContent( 'protect-expiry-indefinite' );
                                                }
@@ -2110,7 +2110,7 @@ class Article {
                                        }
                                }
                                $protect_description = trim($protect_description);
-                                       
+
                                if( $protect_description && $protect )
                                        $editComment .= " ($protect_description)";
                                if( $cascade )
@@ -2119,9 +2119,9 @@ class Article {
                                foreach( $limit as $action => $restrictions ) {
                                        if($restrictions != '' ) {
                                                $dbw->replace( 'page_restrictions', array(array('pr_page', 'pr_type')),
-                                                       array( 'pr_page' => $id, 
-                                                               'pr_type' => $action, 
-                                                               'pr_level' => $restrictions, 
+                                                       array( 'pr_page' => $id,
+                                                               'pr_type' => $action,
+                                                               'pr_level' => $restrictions,
                                                                'pr_cascade' => ($cascade && $action == 'edit') ? 1 : 0,
                                                                'pr_expiry' => $encodedExpiry[$action] ), __METHOD__  );
                                        } else {
@@ -2219,7 +2219,7 @@ class Article {
                if( $res === false )
                        // This page has no revisions, which is very weird
                        return false;
-                       
+
                $hasHistory = ( $res->numRows() > 1 );
                $row = $dbw->fetchObject( $res );
                $onlyAuthor = $row->rev_user_text;
@@ -2243,7 +2243,7 @@ class Article {
                        else
                                $reason = wfMsgForContent( 'excontent', '$1' );
                }
-               
+
                if( $reason == '-' ) {
                        // Allow these UI messages to be blanked out cleanly
                        return '';
@@ -2454,7 +2454,7 @@ class Article {
                }
                $checkWatch = $wgUser->getBoolOption( 'watchdeletion' ) || $this->mTitle->userIsWatching();
 
-               $form = Xml::openElement( 'form', array( 'method' => 'post', 
+               $form = Xml::openElement( 'form', array( 'method' => 'post',
                        'action' => $this->mTitle->getLocalURL( 'action=delete' ), 'id' => 'deleteconfirm' ) ) .
                        Xml::openElement( 'fieldset', array( 'id' => 'mw-delete-table' ) ) .
                        Xml::tags( 'legend', null, wfMsgExt( 'delete-legend', array( 'parsemag', 'escapenoentities' ) ) ) .
@@ -2474,7 +2474,7 @@ class Article {
                                        Xml::label( wfMsg( 'deleteotherreason' ), 'wpReason' ) .
                                "</td>
                                <td class='mw-input'>" .
-                                       Xml::input( 'wpReason', 60, $reason, array( 'type' => 'text', 'maxlength' => '255', 
+                                       Xml::input( 'wpReason', 60, $reason, array( 'type' => 'text', 'maxlength' => '255',
                                                'tabindex' => '2', 'id' => 'wpReason' ) ) .
                                "</td>
                        </tr>
@@ -2624,7 +2624,7 @@ class Article {
                        $dbw->rollback();
                        return false;
                }
-               
+
                # Fix category table counts
                $cats = array();
                $res = $dbw->select( 'categorylinks', 'cl_to', array( 'cl_from' => $id ), __METHOD__ );
@@ -2654,7 +2654,7 @@ class Article {
                if( !$dbw->cleanupTriggers() ) {
                        # Clean up recentchanges entries...
                        $dbw->delete( 'recentchanges',
-                               array( 'rc_type != '.RC_LOG, 
+                               array( 'rc_type != '.RC_LOG,
                                        'rc_namespace' => $this->mTitle->getNamespace(),
                                        'rc_title' => $this->mTitle->getDBkey() ),
                                __METHOD__ );
@@ -2865,7 +2865,7 @@ class Article {
                        if( isset( $details['current'] ) ){
                                $current = $details['current'];
                                if( $current->getComment() != '' ) {
-                                       $wgOut->addWikiMsgArray( 'editcomment', array( 
+                                       $wgOut->addWikiMsgArray( 'editcomment', array(
                                                $wgUser->getSkin()->formatComment( $current->getComment() ) ), array( 'replaceafter' ) );
                                }
                        }
@@ -2989,7 +2989,7 @@ class Article {
                # Update the links tables
                $u = new LinksUpdate( $this->mTitle, $editInfo->output );
                $u->doUpdate();
-               
+
                wfRunHooks( 'ArticleEditUpdates', array( &$this, &$editInfo, $changed ) );
 
                if( wfRunHooks( 'ArticleEditUpdatesDeleteFromRecentchanges', array( &$this ) ) ) {
index c8a4c03..9b22a7b 100644 (file)
@@ -18,9 +18,9 @@ class Autopromote {
                        if( self::recCheckCondition( $cond, $user ) )
                                $promote[] = $group;
                }
-               
+
                wfRunHooks( 'GetAutoPromoteGroups', array( $user, &$promote ) );
-               
+
                return $promote;
        }
 
@@ -116,6 +116,8 @@ class Autopromote {
                                return $cond[1] == wfGetIP();
                        case APCOND_IPINRANGE:
                                return IP::isInRange( wfGetIP(), $cond[1] );
+                       case APCOND_BLOCKED:
+                               return $user->isBlocked();
                        default:
                                $result = null;
                                wfRunHooks( 'AutopromoteCondition', array( $cond[0], array_slice( $cond, 1 ), $user, &$result ) );
index e9ff710..c5233e3 100644 (file)
@@ -226,7 +226,7 @@ $wgFileStore['deleted']['hash'] = 3;         ///< 3-level subdirectory split
  *                      equivalent to the corresponding member of $wgDBservers
  *    tablePrefix       Table prefix, the foreign wiki's $wgDBprefix
  *    hasSharedCache    True if the wiki's shared cache is accessible via the local $wgMemc
- * 
+ *
  * ForeignAPIRepo:
  *    apibase              Use for the foreign API's URL
  *    apiThumbCacheExpiry  How long to locally cache thumbs for
@@ -739,7 +739,7 @@ $wgParserCacheExpireTime = 86400;
 $wgSessionsInMemcached = false;
 
 /** This is used for setting php's session.save_handler. In practice, you will
- * almost never need to change this ever. Other options might be 'user' or 
+ * almost never need to change this ever. Other options might be 'user' or
  * 'session_mysql.' Setting to null skips setting this entirely (which might be
  * useful if you're doing cross-application sessions, see bug 11381) */
 $wgSessionHandler = 'files';
@@ -793,7 +793,7 @@ $wgEditEncoding   = '';
 
 /**
  * Locale for LC_CTYPE, to work around http://bugs.php.net/bug.php?id=45132
- * For Unix-like operating systems, set this to to a locale that has a UTF-8 
+ * For Unix-like operating systems, set this to to a locale that has a UTF-8
  * character set. Only the character set is relevant.
  */
 $wgShellLocale = 'en_US.utf8';
@@ -980,11 +980,11 @@ $wgExtraSubtitle  = '';
 $wgSiteSupportPage     = ''; # A page where you users can receive donations
 
 /**
- * Set this to a string to put the wiki into read-only mode. The text will be 
- * used as an explanation to users. 
+ * Set this to a string to put the wiki into read-only mode. The text will be
+ * used as an explanation to users.
  *
- * This prevents most write operations via the web interface. Cache updates may 
- * still be possible. To prevent database writes completely, use the read_only 
+ * This prevents most write operations via the web interface. Cache updates may
+ * still be possible. To prevent database writes completely, use the read_only
  * option in MySQL.
  */
 $wgReadOnly             = null;
@@ -999,7 +999,7 @@ $wgReadOnlyFile         = false; ///< defaults to "{$wgUploadDirectory}/lock_yBg
 /**
  * Filename for debug logging. See http://www.mediawiki.org/wiki/How_to_debug
  * The debug log file should be not be publicly accessible if it is used, as it
- * may contain private data. 
+ * may contain private data.
  */
 $wgDebugLogFile         = '';
 
@@ -1009,14 +1009,14 @@ $wgDebugLogFile         = '';
 $wgDebugLogPrefix       = '';
 
 /**
- * If true, instead of redirecting, show a page with a link to the redirect 
+ * If true, instead of redirecting, show a page with a link to the redirect
  * destination. This allows for the inspection of PHP error messages, and easy
  * resubmission of form data. For developer use only.
  */
 $wgDebugRedirects              = false;
 
 /**
- * If true, log debugging data from action=raw. 
+ * If true, log debugging data from action=raw.
  * This is normally false to avoid overlapping debug entries due to gen=css and
  * gen=js requests.
  */
@@ -1027,7 +1027,7 @@ $wgDebugRawPage         = false;
  *
  * This may occasionally be useful when supporting a non-technical end-user. It's
  * more secure than exposing the debug log file to the web, since the output only
- * contains private data for the current user. But it's not ideal for development 
+ * contains private data for the current user. But it's not ideal for development
  * use since data is lost on fatal errors and redirects.
  */
 $wgDebugComments        = false;
@@ -1104,7 +1104,7 @@ $wgUseCategoryBrowser   = false;
  * same options.
  *
  * This can provide a significant speedup for medium to large pages,
- * so you probably want to keep it on. Extensions that conflict with the 
+ * so you probably want to keep it on. Extensions that conflict with the
  * parser cache should disable the cache on a per-page basis instead.
  */
 $wgEnableParserCache = true;
@@ -1293,6 +1293,15 @@ $wgGroupPermissions['bureaucrat']['noratelimit'] = true;
  */
 # $wgGroupPermissions['developer']['siteadmin'] = true;
 
+/**
+ * Permission keys revoked from users in each group.
+ * This acts the same way as wgGroupPermissions above, except that
+ * if the user is in a group here, the permission will be removed from them.
+ *
+ * Improperly setting this could mean that your users will be unable to perform
+ * certain essential tasks, so use at your own risk!
+ */
+$wgRevokePermissions = array();
 
 /**
  * Implicit groups, aren't shown on Special:Listusers or somewhere else
@@ -1304,7 +1313,7 @@ $wgImplicitGroups = array( '*', 'user', 'autoconfirmed' );
  * are allowed to add or revoke.
  *
  * Setting the list of groups to add or revoke to true is equivalent to "any group".
- * 
+ *
  * For example, to allow sysops to add themselves to the "bot" group:
  *
  *    $wgGroupsAddToSelf = array( 'sysop' => array( 'bot' ) );
@@ -1315,7 +1324,7 @@ $wgImplicitGroups = array( '*', 'user', 'autoconfirmed' );
  *
  * This allows users in the '*' group (i.e. any user) to remove themselves from
  * any group that they happen to be in.
- * 
+ *
  */
 $wgGroupsAddToSelf = array();
 $wgGroupsRemoveFromSelf = array();
@@ -1389,6 +1398,7 @@ $wgAutoConfirmCount = 0;
  *   array( APCOND_ISIP, ip ), *OR*
  *   array( APCOND_IPINRANGE, range ), *OR*
  *   array( APCOND_AGE_FROM_EDIT, seconds since first edit ), *OR*
+ *   array( APCOND_BLOCKED ), *OR*
  *   similar constructs defined by extensions.
  *
  * If $wgEmailAuthentication is off, APCOND_EMAILCONFIRMED will be true for any
@@ -1486,7 +1496,7 @@ $wgCacheEpoch = '20030516000000';
  * to ensure that client-side caches do not keep obsolete copies of global
  * styles.
  */
-$wgStyleVersion = '226';
+$wgStyleVersion = '227';
 
 
 # Server-side caching:
@@ -1709,7 +1719,7 @@ $wgAllowExternalImagesFrom = '';
  * Or false to disable it
  */
 $wgEnableImageWhitelist = true;
+
 /** Allows to move images and other media files */
 $wgAllowImageMoving = true;
 
@@ -1986,13 +1996,13 @@ $wgNamespacesToBeSearchedDefault = array(
 );
 
 /**
- * Namespaces to be searched when user clicks the "Help" tab 
+ * Namespaces to be searched when user clicks the "Help" tab
  * on Special:Search
- * 
+ *
  * Same format as $wgNamespacesToBeSearchedDefault
- */  
+ */
 $wgNamespacesToBeSearchedHelp = array(
-       NS_PROJECT        => true,      
+       NS_PROJECT        => true,
        NS_HELP           => true,
 );
 
@@ -2055,8 +2065,8 @@ $wgSharpenParameter = '0x0.4';
 /** Reduction in linear dimensions below which sharpening will be enabled */
 $wgSharpenReductionThreshold = 0.85;
 
-/** 
- * Temporary directory used for ImageMagick. The directory must exist. Leave 
+/**
+ * Temporary directory used for ImageMagick. The directory must exist. Leave
  * this set to false to let ImageMagick decide for itself.
  */
 $wgImageMagickTempDir = false;
@@ -2196,19 +2206,19 @@ $wgRC2UDPPort = false;
 /**
  * Prefix to prepend to each UDP packet.
  * This can be used to identify the wiki. A script is available called
- * mxircecho.py which listens on a UDP port, and uses a prefix ending in a 
+ * mxircecho.py which listens on a UDP port, and uses a prefix ending in a
  * tab to identify the IRC channel to send the log line to.
  */
 $wgRC2UDPPrefix = '';
 
 /**
- * If this is set to true, $wgLocalInterwiki will be prepended to links in the 
+ * If this is set to true, $wgLocalInterwiki will be prepended to links in the
  * IRC feed. If this is set to a string, that string will be used as the prefix.
  */
 $wgRC2UDPInterwikiPrefix = false;
 
 /**
- * Set to true to omit "bot" edits (by users with the bot permission) from the 
+ * Set to true to omit "bot" edits (by users with the bot permission) from the
  * UDP feed.
  */
 $wgRC2UDPOmitBots = false;
@@ -2514,8 +2524,8 @@ $wgDefaultUserOptions = array(
        'disablemail'                     => 0,
 );
 
-/** 
- * Whether or not to allow and use real name fields. 
+/**
+ * Whether or not to allow and use real name fields.
  * @deprecated in 1.16, use $wgHiddenPrefs[] = 'realname' below to disable real
  * names
  */
@@ -2926,7 +2936,7 @@ $wgLogRestrictions = array(
  *
  * This is associative array of log type => boolean "hide by default"
  *
- * See $wgLogTypes for a list of available log types. 
+ * See $wgLogTypes for a list of available log types.
  *
  * For example:
  *   $wgFilterLogTypes => array(
@@ -2935,7 +2945,7 @@ $wgLogRestrictions = array(
  *   );
  *
  * Will display show/hide links for the move and import logs. Move logs will be
- * hidden by default unless the link is clicked. Import logs will be shown by 
+ * hidden by default unless the link is clicked. Import logs will be shown by
  * default, and hidden when the link is clicked.
  *
  * A message of the form log-show-hide-<type> should be added, and will be used
@@ -3733,7 +3743,7 @@ $wgMaximumMovedPages = 100;
 
 /**
  * Fix double redirects after a page move.
- * Tends to conflict with page move vandalism, use only on a private wiki. 
+ * Tends to conflict with page move vandalism, use only on a private wiki.
  */
 $wgFixDoubleRedirects = false;
 
@@ -3755,7 +3765,7 @@ $wgMaxRedirects = 1;
  * other namespaces cannot be invalidated by this variable.
  */
 $wgInvalidRedirectTargets = array( 'Filepath', 'Mypage', 'Mytalk' );
+
 /**
  * Array of namespaces to generate a sitemap for when the
  * maintenance/generateSitemap.php script is run, or false if one is to be ge-
index 67d9ad5..046161e 100644 (file)
@@ -228,3 +228,4 @@ define( 'APCOND_INGROUPS', 4 );
 define( 'APCOND_ISIP', 5 );
 define( 'APCOND_IPINRANGE', 6 );
 define( 'APCOND_AGE_FROM_EDIT', 7 );
+define( 'APCOND_BLOCKED', 8 );
index 4c68d38..8c91544 100644 (file)
@@ -38,9 +38,9 @@ class ProtectionForm {
        /** Map of action to "other" expiry time. Used in preference to mExpirySelection. */
        var $mExpiry = array();
 
-       /** 
-        * Map of action to value selected in expiry drop-down list. 
-        * Will be set to 'othertime' whenever mExpiry is set. 
+       /**
+        * Map of action to value selected in expiry drop-down list.
+        * Will be set to 'othertime' whenever mExpiry is set.
         */
        var $mExpirySelection = array();
 
@@ -127,7 +127,7 @@ class ProtectionForm {
                }
        }
 
-       /** 
+       /**
         * Get the expiry time for a given action, by combining the relevant inputs.
         * Returns a 14-char timestamp or "infinity", or false if the input was invalid
         */
@@ -230,7 +230,7 @@ class ProtectionForm {
                        $this->show( wfMsg( 'sessionfailure' ) );
                        return false;
                }
-               
+
                # Create reason string. Use list and/or custom string.
                $reasonstr = $this->mReasonSelection;
                if ( $reasonstr != 'other' && $this->mReason != '' ) {
@@ -296,8 +296,8 @@ class ProtectionForm {
                $out = '';
                if( !$this->disabled ) {
                        $out .= $this->buildScript();
-                       $out .= Xml::openElement( 'form', array( 'method' => 'post', 
-                               'action' => $this->mTitle->getLocalUrl( 'action=protect' ), 
+                       $out .= Xml::openElement( 'form', array( 'method' => 'post',
+                               'action' => $this->mTitle->getLocalUrl( 'action=protect' ),
                                'id' => 'mw-Protect-Form', 'onsubmit' => 'ProtectionForm.enableUnchainedInputs(true)' ) );
                        $out .= Xml::hidden( 'wpEditToken',$wgUser->editToken() );
                }
@@ -321,7 +321,7 @@ class ProtectionForm {
 
                        $reasonDropDown = Xml::listDropDown( 'wpProtectReasonSelection',
                                wfMsgForContent( 'protect-dropdown' ),
-                               wfMsgForContent( 'protect-otherreason-op' ), 
+                               wfMsgForContent( 'protect-otherreason-op' ),
                                $this->mReasonSelection,
                                'mwProtect-reason', 4 );
                        $scExpiryOptions = wfMsgForContent( 'protect-expiry-options' );
@@ -336,14 +336,14 @@ class ProtectionForm {
                                $timestamp = $wgLang->timeanddate( $this->mExistingExpiry[$action] );
                                $d = $wgLang->date( $this->mExistingExpiry[$action] );
                                $t = $wgLang->time( $this->mExistingExpiry[$action] );
-                               $expiryFormOptions .= 
-                                       Xml::option( 
+                               $expiryFormOptions .=
+                                       Xml::option(
                                                wfMsg( 'protect-existing-expiry', $timestamp, $d, $t ),
                                                'existing',
                                                $this->mExpirySelection[$action] == 'existing'
                                        ) . "\n";
                        }
-                       
+
                        $expiryFormOptions .= Xml::option( wfMsg( 'protect-othertime-op' ), "othertime" ) . "\n";
                        foreach( explode(',', $scExpiryOptions) as $option ) {
                                if ( strpos($option, ":") === false ) {
@@ -399,13 +399,13 @@ class ProtectionForm {
                        $out .= '<tr>
                                        <td></td>
                                        <td class="mw-input">' .
-                                               Xml::checkLabel( wfMsg( 'protect-cascade' ), 'mwProtect-cascade', 'mwProtect-cascade', 
+                                               Xml::checkLabel( wfMsg( 'protect-cascade' ), 'mwProtect-cascade', 'mwProtect-cascade',
                                                        $this->mCascade, $this->disabledAttrib ) .
                                        "</td>
                                </tr>\n";
                        $out .= Xml::closeElement( 'tbody' ) . Xml::closeElement( 'table' );
                }
-               
+
                # Add manual and custom reason field/selects as well as submit
                if( !$this->disabled ) {
                        $out .=  Xml::openElement( 'table', array( 'id' => 'mw-protect-table3' ) ) .
@@ -424,7 +424,7 @@ class ProtectionForm {
                                                {$mProtectreason}
                                        </td>
                                        <td class='mw-input'>" .
-                                               Xml::input( 'mwProtect-reason', 60, $this->mReason, array( 'type' => 'text', 
+                                               Xml::input( 'mwProtect-reason', 60, $this->mReason, array( 'type' => 'text',
                                                        'id' => 'mwProtect-reason', 'maxlength' => 255 ) ) .
                                        "</td>
                                </tr>
index c227d29..d57932e 100644 (file)
@@ -43,7 +43,7 @@ class PasswordError extends MWException {
 class User {
 
        /**
-        * \type{\arrayof{\string}} A list of default user toggles, i.e., boolean user 
+        * \type{\arrayof{\string}} A list of default user toggles, i.e., boolean user
          * preferences that are displayed by Special:Preferences as checkboxes.
         * This list can be extended via the UserToggles hook or by
         * $wgContLang::getExtraUserToggles().
@@ -95,8 +95,8 @@ class User {
        );
 
        /**
-        * \type{\arrayof{\string}} List of member variables which are saved to the 
-        * shared cache (memcached). Any operation which changes the 
+        * \type{\arrayof{\string}} List of member variables which are saved to the
+        * shared cache (memcached). Any operation which changes the
         * corresponding database fields must call a cache-clearing function.
         * @showinitializer
         */
@@ -124,7 +124,7 @@ class User {
 
        /**
         * \type{\arrayof{\string}} Core rights.
-        * Each of these should have a corresponding message of the form 
+        * Each of these should have a corresponding message of the form
         * "right-$right".
         * @showinitializer
         */
@@ -211,14 +211,14 @@ class User {
        /** @name Lazy-initialized variables, invalidated with clearInstanceCache */
        //@{
        var $mNewtalk, $mDatePreference, $mBlockedby, $mHash, $mSkin, $mRights,
-               $mBlockreason, $mBlock, $mEffectiveGroups, $mBlockedGlobally, 
+               $mBlockreason, $mBlock, $mEffectiveGroups, $mBlockedGlobally,
                $mLocked, $mHideName, $mOptions;
        //@}
 
        /**
         * Lightweight constructor for an anonymous user.
         * Use the User::newFrom* factory functions for other kinds of users.
-        * 
+        *
         * @see newFromName()
         * @see newFromId()
         * @see newFromConfirmationCode()
@@ -325,8 +325,8 @@ class User {
                global $wgMemc;
                $wgMemc->set( $key, $data );
        }
-       
-       
+
+
        /** @name newFrom*() static factory methods */
        //@{
 
@@ -341,8 +341,8 @@ class User {
         *    User::getCanonicalName(), except that true is accepted as an alias
         *    for 'valid', for BC.
         *
-        * @return \type{User} The User object, or null if the username is invalid. If the 
-        *    username is not present in the database, the result will be a user object 
+        * @return \type{User} The User object, or null if the username is invalid. If the
+        *    username is not present in the database, the result will be a user object
         *    with a name, zero user ID and default settings.
         */
        static function newFromName( $name, $validate = 'valid' ) {
@@ -420,9 +420,9 @@ class User {
                $user->loadFromRow( $row );
                return $user;
        }
-       
+
        //@}
-       
+
 
        /**
         * Get the username corresponding to a given user ID
@@ -661,7 +661,7 @@ class User {
                        return false;
 
                # Clean up name according to title rules
-               $t = ($validate === 'valid') ? 
+               $t = ($validate === 'valid') ?
                        Title::newFromText( $name ) : Title::makeTitle( NS_USER, $name );
                # Check for invalid titles
                if( is_null( $t ) ) {
@@ -755,9 +755,9 @@ class User {
        }
 
        /**
-        * Set cached properties to default. 
+        * Set cached properties to default.
         *
-        * @note This no longer clears uncached lazy-initialised properties; 
+        * @note This no longer clears uncached lazy-initialised properties;
         *       the constructor does that instead.
         * @private
         */
@@ -819,7 +819,7 @@ class User {
                        $sId = intval( $_COOKIE["{$wgCookiePrefix}UserID"] );
                        if( isset( $_SESSION['wsUserID'] ) && $sId != $_SESSION['wsUserID'] ) {
                                $this->loadDefaults(); // Possible collision!
-                               wfDebugLog( 'loginSessions', "Session user ID ({$_SESSION['wsUserID']}) and 
+                               wfDebugLog( 'loginSessions', "Session user ID ({$_SESSION['wsUserID']}) and
                                        cookie user ID ($sId) don't match!" );
                                return false;
                        }
@@ -937,7 +937,7 @@ class User {
                $this->mEmailToken = $row->user_email_token;
                $this->mEmailTokenExpires = wfTimestampOrNull( TS_MW, $row->user_email_token_expires );
                $this->mRegistration = wfTimestampOrNull( TS_MW, $row->user_registration );
-               $this->mEditCount = $row->user_editcount; 
+               $this->mEditCount = $row->user_editcount;
        }
 
        /**
@@ -1065,7 +1065,7 @@ class User {
                // due to -1 !== 0. Probably session-related... Nothing should be
                // overwriting mBlockedby, surely?
                $this->load();
-               
+
                $this->mBlockedby = 0;
                $this->mHideName = 0;
                $this->mAllowUsertalk = 0;
@@ -1089,8 +1089,8 @@ class User {
                                $this->spreadBlock();
                        }
                } else {
-                       // Bug 13611: don't remove mBlock here, to allow account creation blocks to 
-                       // apply to users. Note that the existence of $this->mBlock is not used to 
+                       // Bug 13611: don't remove mBlock here, to allow account creation blocks to
+                       // apply to users. Note that the existence of $this->mBlock is not used to
                        // check for edit blocks, $this->mBlockedby is instead.
                }
 
@@ -1275,7 +1275,7 @@ class User {
 
        /**
         * Check if user is blocked
-        * 
+        *
         * @param $bFromSlave \bool Whether to check the slave database instead of the master
         * @return \bool True if blocked, false otherwise
         */
@@ -1287,7 +1287,7 @@ class User {
 
        /**
         * Check if user is blocked from editing a particular article
-        * 
+        *
         * @param $title      \string Title to check
         * @param $bFromSlave \bool   Whether to check the slave database instead of the master
         * @return \bool True if blocked, false otherwise
@@ -1327,7 +1327,7 @@ class User {
                $this->getBlockedStatus();
                return $this->mBlockreason;
        }
-       
+
        /**
         * If user is blocked, return the ID for the block
         * @return \int Block ID
@@ -1336,12 +1336,12 @@ class User {
                $this->getBlockedStatus();
                return ($this->mBlock ? $this->mBlock->mId : false);
        }
-       
+
        /**
         * Check if user is blocked on all wikis.
         * Do not use for actual edit permission checks!
         * This is intented for quick UI checks.
-        * 
+        *
         * @param $ip \type{\string} IP address, uses current client if none given
         * @return \type{\bool} True if blocked, false otherwise
         */
@@ -1360,10 +1360,10 @@ class User {
                $this->mBlockedGlobally = (bool)$blocked;
                return $this->mBlockedGlobally;
        }
-       
+
        /**
         * Check if user account is locked
-        * 
+        *
         * @return \type{\bool} True if locked, false otherwise
         */
        function isLocked() {
@@ -1375,10 +1375,10 @@ class User {
                $this->mLocked = (bool)$authUser->isLocked();
                return $this->mLocked;
        }
-       
+
        /**
         * Check if user account is hidden
-        * 
+        *
         * @return \type{\bool} True if hidden, false otherwise
         */
        function isHidden() {
@@ -1733,7 +1733,7 @@ class User {
                $this->mNewpassword = '';
                $this->mNewpassTime = null;
        }
-       
+
        /**
         * Get the user's current token.
         * @return \string Token
@@ -1742,7 +1742,7 @@ class User {
                $this->load();
                return $this->mToken;
        }
-       
+
        /**
         * Set the random token (used for persistent authentication)
         * Called from loadDefaults() among other places.
@@ -1766,7 +1766,7 @@ class User {
                        $this->mToken = $token;
                }
        }
-       
+
        /**
         * Set the cookie password
         *
@@ -1793,7 +1793,7 @@ class User {
        }
 
        /**
-        * Has password reminder email been sent within the last 
+        * Has password reminder email been sent within the last
         * $wgPasswordReminderResendTime hours?
         * @return \bool True or false
         */
@@ -1880,7 +1880,7 @@ class User {
                        return $defaultOverride;
                }
        }
-       
+
        /**
         * Get the user's current setting for a given option, as a boolean value.
         *
@@ -1892,7 +1892,7 @@ class User {
                return (bool)$this->getOption( $oname );
        }
 
-       
+
        /**
         * Get the user's current setting for a given option, as a boolean value.
         *
@@ -1932,10 +1932,10 @@ class User {
                
                $this->mOptions[$oname] = $val;
        }
-       
+
        /**
         * Reset all options to the site defaults
-        */     
+        */
        function resetOptions() {
                $this->mOptions = User::getDefaultOptions();
        }
@@ -2112,7 +2112,7 @@ class User {
                        if( !$wgUseRCPatrol && !$wgUseNPPatrol )
                                return false;
                }
-               # Use strict parameter to avoid matching numeric 0 accidentally inserted 
+               # Use strict parameter to avoid matching numeric 0 accidentally inserted
                # by misconfiguration: 0 == 'foo'
                return in_array( $action, $this->getRights(), true );
        }
@@ -2156,7 +2156,7 @@ class User {
                                global $wgDefaultSkin;
                                $userSkin = $wgDefaultSkin;
                        }
-                       
+
                        $this->mSkin =& Skin::newFromKey( $userSkin );
                        wfProfileOut( __METHOD__ );
                }
@@ -2309,20 +2309,20 @@ class User {
                        }
                }
        }
-       
+
        /**
-        * Set a cookie on the user's client. Wrapper for 
+        * Set a cookie on the user's client. Wrapper for
         * WebResponse::setCookie
         * @param $name \string Name of the cookie to set
         * @param $value \string Value to set
-        * @param $exp \int Expiration time, as a UNIX time value; 
+        * @param $exp \int Expiration time, as a UNIX time value;
         *                   if 0 or not specified, use the default $wgCookieExpiration
         */
        protected function setCookie( $name, $value, $exp=0 ) {
                global $wgRequest;
                $wgRequest->response()->setcookie( $name, $value, $exp );
        }
-       
+
        /**
         * Clear a cookie on the user's client
         * @param $name \string Name of the cookie to clear
@@ -2337,7 +2337,7 @@ class User {
        function setCookies() {
                $this->load();
                if ( 0 == $this->mId ) return;
-               $session = array( 
+               $session = array(
                        'wsUserID' => $this->mId,
                        'wsToken' => $this->mToken,
                        'wsUserName' => $this->getName()
@@ -2351,9 +2351,9 @@ class User {
                } else {
                        $cookies['Token'] = false;
                }
-               
+
                wfRunHooks( 'UserSetCookies', array( $this, &$session, &$cookies ) );
-               #check for null, since the hook could cause a null value 
+               #check for null, since the hook could cause a null value
                if ( !is_null( $session ) && isset( $_SESSION ) ){
                        $_SESSION = $session + $_SESSION;
                }
@@ -2669,27 +2669,27 @@ class User {
        function isNewbie() {
                return !$this->isAllowed( 'autoconfirmed' );
        }
-       
+
        /**
         * Is the user active? We check to see if they've made at least
         * X number of edits in the last Y days.
-        * 
+        *
         * @return \bool True if the user is active, false if not.
         */
        public function isActiveEditor() {
                global $wgActiveUserEditCount, $wgActiveUserDays;
                $dbr = wfGetDB( DB_SLAVE );
-               
+
                // Stolen without shame from RC
                $cutoff_unixtime = time() - ( $wgActiveUserDays * 86400 );
                $cutoff_unixtime = $cutoff_unixtime - ( $cutoff_unixtime % 86400 );
                $oldTime = $dbr->addQuotes( $dbr->timestamp( $cutoff_unixtime ) );
-               
+
                $res = $dbr->select( 'revision', '1',
                                array( 'rev_user_text' => $this->getName(), "rev_timestamp > $oldTime"),
                                __METHOD__,
                                array('LIMIT' => $wgActiveUserEditCount ) );
-               
+
                $count = $dbr->numRows($res);
                $dbr->freeResult($res);
 
@@ -2833,7 +2833,7 @@ class User {
                $url = $this->confirmationTokenUrl( $token );
                $invalidateURL = $this->invalidationTokenUrl( $token );
                $this->saveSettings();
-               
+
                return $this->sendMail( wfMsg( 'confirmemail_subject' ),
                        wfMsg( 'confirmemail_body',
                                wfGetIP(),
@@ -2907,7 +2907,7 @@ class User {
        function invalidationTokenUrl( $token ) {
                return $this->getTokenUrl( 'Invalidateemail', $token );
        }
-       
+
        /**
         * Internal function to format the e-mail validation/invalidation URLs.
         * This uses $wgArticlePath directly as a quickie hack to use the
@@ -3039,7 +3039,7 @@ class User {
                        ? $this->mRegistration
                        : false;
        }
-       
+
        /**
         * Get the timestamp of the first edit
         *
@@ -3056,7 +3056,7 @@ class User {
                );
                if( !$time ) return false; // no edits
                return wfTimestamp( TS_MW, $time );
-       }       
+       }
 
        /**
         * Get the permissions associated with a given list of groups
@@ -3065,7 +3065,7 @@ class User {
         * @return \type{\arrayof{\string}} List of permission key names for given groups combined
         */
        static function getGroupPermissions( $groups ) {
-               global $wgGroupPermissions;
+               global $wgGroupPermissions, $wgRevokePermissions;
                $rights = array();
                foreach( $groups as $group ) {
                        if( isset( $wgGroupPermissions[$group] ) ) {
@@ -3073,13 +3073,17 @@ class User {
                                        // array_filter removes empty items
                                        array_keys( array_filter( $wgGroupPermissions[$group] ) ) );
                        }
+                       if( isset( $wgRevokePermissions[$group] ) ) {
+                               $rights = array_diff( $rights,
+                                       array_keys( array_filter( $wgRevokePermissions[$group] ) ) );
+                       }
                }
                return array_unique($rights);
        }
-       
+
        /**
         * Get all the groups who have a given permission
-        * 
+        *
         * @param $role \string Role to check
         * @return \type{\arrayof{\string}} List of internal group names with the given permission
         */
@@ -3133,9 +3137,9 @@ class User {
         * @return \type{\arrayof{\string}} Array of internal group names
         */
        static function getAllGroups() {
-               global $wgGroupPermissions;
+               global $wgGroupPermissions, $wgRevokePermissions;
                return array_diff(
-                       array_keys( $wgGroupPermissions ),
+                       array_merge( array_keys( $wgGroupPermissions ), array_keys( $wgRevokePermissions ) ),
                        self::getImplicitGroups()
                );
        }
@@ -3187,7 +3191,7 @@ class User {
        }
 
        /**
-        * Create a link to the group in HTML, if available; 
+        * Create a link to the group in HTML, if available;
         * else return the group name.
         *
         * @param $group \string Internal name of the group
@@ -3209,7 +3213,7 @@ class User {
        }
 
        /**
-        * Create a link to the group in Wikitext, if available; 
+        * Create a link to the group in Wikitext, if available;
         * else return the group name.
         *
         * @param $group \string Internal name of the group
@@ -3228,7 +3232,7 @@ class User {
                        return $text;
                }
        }
-       
+
        /**
         * Returns an array of the groups that a particular group can add/remove.
         *
@@ -3258,7 +3262,7 @@ class User {
                } elseif( is_array($wgRemoveGroups[$group]) ) {
                        $groups['remove'] = $wgRemoveGroups[$group];
                }
-               
+
                // Re-map numeric keys of AddToSelf/RemoveFromSelf to the 'user' key for backwards compatibility
                if( empty($wgGroupsAddToSelf['user']) || $wgGroupsAddToSelf['user'] !== true ) {
                        foreach($wgGroupsAddToSelf as $key => $value) {
@@ -3267,7 +3271,7 @@ class User {
                                }
                        }
                }
-               
+
                if( empty($wgGroupsRemoveFromSelf['user']) || $wgGroupsRemoveFromSelf['user'] !== true ) {
                        foreach($wgGroupsRemoveFromSelf as $key => $value) {
                                if( is_int($key) ) {
@@ -3275,7 +3279,7 @@ class User {
                                }
                        }
                }
-               
+
                // Now figure out what groups the user can add to him/herself
                if( empty($wgGroupsAddToSelf[$group]) ) {
                } elseif( $wgGroupsAddToSelf[$group] === true ) {
@@ -3284,17 +3288,17 @@ class User {
                } elseif( is_array($wgGroupsAddToSelf[$group]) ) {
                        $groups['add-self'] = $wgGroupsAddToSelf[$group];
                }
-               
+
                if( empty($wgGroupsRemoveFromSelf[$group]) ) {
                } elseif( $wgGroupsRemoveFromSelf[$group] === true ) {
                        $groups['remove-self'] = User::getAllGroups();
                } elseif( is_array($wgGroupsRemoveFromSelf[$group]) ) {
                        $groups['remove-self'] = $wgGroupsRemoveFromSelf[$group];
                }
-               
+
                return $groups;
        }
-       
+
        /**
         * Returns an array of groups that this user can add and remove
         * @return Array array( 'add' => array( addablegroups ),
@@ -3380,7 +3384,7 @@ class User {
                // edit count in user cache too
                $this->invalidateCache();
        }
-       
+
        /**
         * Get the description of a given right
         *
@@ -3417,7 +3421,7 @@ class User {
         * Make a new-style password hash
         *
         * @param $password \string Plain-text password
-        * @param $salt \string Optional salt, may be random or the user ID. 
+        * @param $salt \string Optional salt, may be random or the user ID.
         *                     If unspecified or false, will generate one automatically
         * @return \string Password hash
         */
@@ -3428,7 +3432,7 @@ class User {
                if( !wfRunHooks( 'UserCryptPassword', array( &$password, &$salt, &$wgPasswordSalt, &$hash ) ) ) {
                        return $hash;
                }
-               
+
                if( $wgPasswordSalt ) {
                        if ( $salt === false ) {
                                $salt = substr( wfGenerateToken(), 0, 8 );
@@ -3451,12 +3455,12 @@ class User {
        static function comparePasswords( $hash, $password, $userId = false ) {
                $m = false;
                $type = substr( $hash, 0, 3 );
-               
+
                $result = false;
                if( !wfRunHooks( 'UserComparePasswords', array( &$hash, &$password, &$userId, &$result ) ) ) {
                        return $result;
                }
-               
+
                if ( $type == ':A:' ) {
                        # Unsalted
                        return md5( $password ) === substr( $hash, 3 );
@@ -3469,7 +3473,7 @@ class User {
                        return self::oldCrypt( $password, $userId ) === $hash;
                }
        }
-       
+
        /**
         * Add a newuser log entry for this user
         * @param $byEmail Boolean: account made by email?
index 8a44efc..3147627 100644 (file)
@@ -25,7 +25,7 @@ class SpecialListGroupRights extends SpecialPage {
         */
        public function execute( $par ) {
                global $wgOut, $wgImplicitGroups, $wgMessageCache;
-               global $wgGroupPermissions, $wgAddGroups, $wgRemoveGroups;
+               global $wgGroupPermissions, $wgRevokePermissions, $wgAddGroups, $wgRemoveGroups;
                global $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf;
                $wgMessageCache->loadAllMessages();
 
@@ -89,9 +89,9 @@ class SpecialListGroupRights extends SpecialPage {
                                $grouplink = '';
                        }
 
+                       $revoke = isset( $wgRevokePermissions[$group] ) ? $wgRevokePermissions[$group] : array();
                        $addgroups = isset( $wgAddGroups[$group] ) ? $wgAddGroups[$group] : array();
                        $removegroups = isset( $wgRemoveGroups[$group] ) ? $wgRemoveGroups[$group] : array();
-
                        $addgroupsSelf = isset( $wgGroupsAddToSelf[$group] ) ? $wgGroupsAddToSelf[$group] : array();
                        $removegroupsSelf = isset( $wgGroupsRemoveFromSelf[$group] ) ? $wgGroupsRemoveFromSelf[$group] : array();
 
@@ -101,27 +101,33 @@ class SpecialListGroupRights extends SpecialPage {
                                                $grouppage . $grouplink .
                                        '</td>
                                        <td>' .
-                                               self::formatPermissions( $permissions, $addgroups, $removegroups, $addgroupsSelf, $removegroupsSelf ) .
+                                               self::formatPermissions( $permissions, $revoke, $addgroups, $removegroups, $addgroupsSelf, $removegroupsSelf ) .
                                        '</td>
                                </tr>'
                        );
                }
                $wgOut->addHTML(
-                       Xml::closeElement( 'table' ) . "\n"
+                       Xml::closeElement( 'table' ) . "\n<br /><hr />\n"
                );
+               $wgOut->addWikiMsg( 'listgrouprights-key' );
        }
 
        /**
         * Create a user-readable list of permissions from the given array.
         *
         * @param $permissions Array of permission => bool (from $wgGroupPermissions items)
+        * @param $revoke Array of permission => bool (from $wgRevokePermissions items)
+        * @param $add Array of groups this group is allowed to add or true
+        * @param $remove Array of groups this group is allowed to remove or true
+        * @param $addSelf Array of groups this group is allowed to add to self or true
+        * @param $removeSelf Array of group this group is allowed to remove from self or true
         * @return string List of all granted permissions, separated by comma separator
         */
-        private static function formatPermissions( $permissions, $add, $remove, $addSelf, $removeSelf ) {
+        private static function formatPermissions( $permissions, $revoke, $add, $remove, $addSelf, $removeSelf ) {
                global $wgLang;
                $r = array();
                foreach( $permissions as $permission => $granted ) {
-                       if ( $granted ) {
+                       if( $granted ) {
                                $description = wfMsgExt( 'listgrouprights-right-display', array( 'parseinline' ),
                                        User::getRightDescription( $permission ),
                                        $permission
@@ -129,6 +135,15 @@ class SpecialListGroupRights extends SpecialPage {
                                $r[] = $description;
                        }
                }
+               foreach( $revoke as $permission => $revoked ) {
+                       if( $revoked ) {
+                               $description = wfMsgExt( 'lisgrouprights-right-revoked', array( 'parseinline' ),
+                                       User::getRightDescription( $permission ),
+                                       $permission
+                               );
+                               $r[] = $description;
+                       }
+               }
                sort( $r );
                if( $add === true ){
                        $r[] = wfMsgExt( 'listgrouprights-addgroup-all', array( 'escape' ) );
index 27f1f39..da35943 100644 (file)
@@ -2340,11 +2340,14 @@ Supported protocols: <tt>$1</tt>',
 'listgrouprights'                      => 'User group rights',
 'listgrouprights-summary'              => 'The following is a list of user groups defined on this wiki, with their associated access rights.
 There may be [[{{MediaWiki:Listgrouprights-helppage}}|additional information]] about individual rights.',
+'listgrouprights-key'                  => '* <span class="listgrouprights-granted">Granted right</span>
+* <span class="listgrouprights-revoked">Revoked right</span>',
 'listgrouprights-group'                => 'Group',
 'listgrouprights-rights'               => 'Rights',
 'listgrouprights-helppage'             => 'Help:Group rights',
 'listgrouprights-members'              => '(list of members)',
-'listgrouprights-right-display'        => '$1 ($2)', # only translate this message to other languages if you have to change it
+'listgrouprights-right-display'        => '<span class="listgrouprights-granted">$1 ($2)</span>', # only translate this message to other languages if you have to change it
+'listgrouprights-right-revoked'        => '<span class="listgrouprights-revoked">$1 ($2)</span>', # only translate this message to other languages if you have to change it
 'listgrouprights-addgroup'             => 'Add {{PLURAL:$2|group|groups}}: $1',
 'listgrouprights-removegroup'          => 'Remove {{PLURAL:$2|group|groups}}: $1',
 'listgrouprights-addgroup-all'         => 'Add all groups',
@@ -3103,7 +3106,7 @@ You can view its source',
 'tooltip-recreate'                => 'Recreate the page even though it has been deleted',
 'tooltip-upload'                  => 'Start upload',
 'tooltip-rollback'                => '"Rollback" reverts edit(s) to this page of the last contributor in one click',
-'tooltip-undo'                    => '"Undo" reverts this edit and opens the edit form in preview mode. 
+'tooltip-undo'                    => '"Undo" reverts this edit and opens the edit form in preview mode.
 It allows adding a reason in the summary.',
 
 # Stylesheets
index a860881..3d11f12 100644 (file)
@@ -57,6 +57,7 @@
  * @author Platonides
  * @author Purodha
  * @author Raymond
+ * @author Ryan Schmidt
  * @author SPQRobin
  * @author Sanbec
  * @author Sborsody
@@ -298,7 +299,7 @@ This can also appear in the credits page if the credits feature is enabled,for e
 {{Identical|Error}}',
 'returnto'         => '{{Identical|Return to $1}}',
 'tagline'          => 'Used to idenify the source of copied information. Do not change <nowiki>{{SITENAME}}</nowiki>.',
-'help'             => 'General text (noun) used in the sidebar (by default). 
+'help'             => 'General text (noun) used in the sidebar (by default).
 
 See also [[MediaWiki:Helppage/{{SUBPAGENAME}}|{{int:helppage}}]] and [[MediaWiki:Edithelp/{{SUBPAGENAME}}|{{int:edithelp}}]].
 
@@ -387,12 +388,12 @@ See also [[MediaWiki:Lastmodifiedatby/{{SUBPAGENAME}}]].',
 'currentevents-url'    => "Target page of ''{{Mediawiki:currentevents}}'' in the sidebar. See also {{msg|currentevents}}.
 {{doc-important|Do not translate <tt>Project:</tt> part.}}",
 'disclaimers'          => 'Used as display name for the link to [[{{MediaWiki:Disclaimerpage}}]] shown at the bottom of every page on the wiki. Example [[{{MediaWiki:Disclaimerpage}}|{{MediaWiki:Disclaimers}}]].',
-'disclaimerpage'       => 'Used as page for that contains the site disclaimer. Used at the bottom of every page on the wiki. Example: [[{{MediaWiki:Disclaimerpage}}|{{MediaWiki:Disclaimers}}]]. 
+'disclaimerpage'       => 'Used as page for that contains the site disclaimer. Used at the bottom of every page on the wiki. Example: [[{{MediaWiki:Disclaimerpage}}|{{MediaWiki:Disclaimers}}]].
 {{doc-important|Do not change <tt>Project:</tt> part.}}',
 'edithelp'             => 'This is the text that appears on the editing help link that is near the bottom of the editing page',
-'edithelppage'         => 'The help page displayed when a user clicks on editing help link which is present on the right of Show changes button. 
+'edithelppage'         => 'The help page displayed when a user clicks on editing help link which is present on the right of Show changes button.
 {{doc-important|Do not change <tt>Help:</tt> part.}}',
-'helppage'             => 'The link destination used by default in the sidebar, and in {{msg|noarticletext}}. 
+'helppage'             => 'The link destination used by default in the sidebar, and in {{msg|noarticletext}}.
 {{doc-important|Do not change <tt>Help:</tt> part.}}
 {{Identical|HelpContent}}',
 'mainpage'             => 'Defines the link and display name of the main page of the wiki. Shown as the top link in the navigation part of the interface. Please do not change it too often, that could break things!
@@ -454,7 +455,7 @@ The format is: "{{int:youhavenewmessages| [[MediaWiki:Newmessageslink/{{SUBPAGEN
 
 {{Identical|Hide}}',
 'restorelink'             => "This text is always displayed in conjunction with the {{msg-mw|thisisdeleted}} message (View or restore $1?). The user will see
-View or restore <nowiki>{{PLURAL:$1|one deleted edit|$1 deleted edits}}</nowiki>?    i.e ''View or restore one deleted edit?''     or 
+View or restore <nowiki>{{PLURAL:$1|one deleted edit|$1 deleted edits}}</nowiki>?    i.e ''View or restore one deleted edit?''     or
 ''View or restore n deleted edits?''",
 'feed-unavailable'        => 'This message is displayed when a user tries to use an RSS or Atom feed on a wiki where such feeds have been disabled.',
 'site-rss-feed'           => "Used in the HTML header of a wiki's RSS feed.
@@ -493,7 +494,7 @@ HTML markup cannot be used.",
 # Main script and global functions
 'nosuchspecialpage' => 'The title of the error you get when trying to open a special page which does not exist. The text of the warning is the message {{msg-mw|nospecialpagetext}}. Example: [[Special:Nosuchpage]]',
 'nospecialpagetext' => 'This error is shown when trying to open a special page which does not exist, e.g. [[Special:Nosuchpage]].
-* The title of this error is the message {{msg-mw|nosuchspecialpage}}. 
+* The title of this error is the message {{msg-mw|nosuchspecialpage}}.
 * Link <code><nowiki>[[Special:SpecialPages|{{int:specialpages}}]]</nowiki></code> should remain untranslated.',
 
 # General errors
@@ -506,7 +507,7 @@ HTML markup cannot be used.",
 
 '''Parameters'''
 * $1: Pagename
-* $2: Content of 
+* $2: Content of
 *# {{msg|Missingarticle-rev}} - Permalink with invalid revision#
 *# {{msg|Missingarticle-diff}} - Diff with invalid revision#",
 'missingarticle-rev'   => 'Parameter $2 of {{msg|Missing-article}}: It is shown after the articlename.
@@ -692,7 +693,7 @@ Parameters:
 * <tt>$8</tt> is the timestamp when the block started',
 'blockednoreason'                  => '{{Identical|No reason given}}',
 'whitelistedittext'                => '* $1 is a link to [[Special:UserLogin]] with {{msg-mw|loginreqlink}} as link description',
-'nosuchsectiontext'                => 'This message is displayed when a user tries to edit a section that does not exist. 
+'nosuchsectiontext'                => 'This message is displayed when a user tries to edit a section that does not exist.
 
 Parameter $1 is the content of section parameter in the URL (for example 1234 in the URL http://translatewiki.net/w/i.php?title=Sandbox&action=edit&section=1234)',
 'loginreqlink'                     => 'Take a look on inflection. Used as parameter in {{msg-mw|loginreqpagetext}}, {{msg-mw|whitelistedittext}}, {{msg-mw|watchlistanontext‎}} and {{msg-mw|Confirmemail needlogin}}.
@@ -860,7 +861,7 @@ Parameter $1 is either {{msg|revdelete-content}} (when unhiding the page content
 *Parameter $2 is the number of revisions for which the restrictions are changed.
 
 Please note that the parameters in a log entry will appear in the log only in the default language of the wiki. View [[Special:Log]] for examples on translatewiki.net with English default language.',
-'logdelete-log-message'       => 'This log message appears in brackets after the message {{msg|logdelete-logentry}} in the deletion or suppression logs when changing the visibility of a log entry for events. For a brief description of the process of changing the visibility of events and their log entries see this [http://www.mediawiki.org/wiki/RevisionDelete mediawiki explanation]. 
+'logdelete-log-message'       => 'This log message appears in brackets after the message {{msg|logdelete-logentry}} in the deletion or suppression logs when changing the visibility of a log entry for events. For a brief description of the process of changing the visibility of events and their log entries see this [http://www.mediawiki.org/wiki/RevisionDelete mediawiki explanation].
 
 *Parameter $1 is either {{msg|revdelete-hid}} (when hiding data), {{msg|revdelete-unhid}} (when unhiding data), {{msg|revdelete-restricted}} (when applying restrictions for sysops),  {{msg|revdelete-unrestricted}} (when removing restrictions for sysops), or a combination of those messages.
 *Parameter $2 is the number of events for which the restrictions are changed.
@@ -985,7 +986,7 @@ $1 is the relevance of this result in per cent.
 'searchall'                      => '{{Identical|All}}',
 'showingresults'                 => "This message is used on some special pages such as 'Wanted categories'. $1 is the total number of results in the batch shown and $2 is the number of the first item listed.",
 'showingresultsnum'              => '$3 is the number of results on the page and $2 is the first number in the batch of results.',
-'showingresultstotal'            => 'Text above list of search results on special page of search results. 
+'showingresultstotal'            => 'Text above list of search results on special page of search results.
 * $1–$2 is the range of results shown on the page
 * $3 is the total number of results from the search
 * $4 is the number of results shown on the page (equal to the size of the $1–$2 interval)',
@@ -1222,7 +1223,7 @@ See also
 * {{msg|right-suppressionlog|pl=yes}}
 * {{msg|right-suppressrevision|pl=yes}}
 * {{msg|right-deleterevision|pl=yes}}',
-'right-ipblock-exempt'        => 'This user automatically 
+'right-ipblock-exempt'        => 'This user automatically
 bypasses IP blocks, auto-blocks and range blocks - so I presume - but I am uncertain',
 'right-rollback'              => '{{Identical|Rollback}}',
 'right-markbotedits'          => '{{doc-right}}
@@ -1943,8 +1944,8 @@ Shown as subtitle of the protection form. $1 is the title of the page to be (un)
 <tt><nowiki>* Groupname</nowiki></tt> - defines a new group<br />
 <tt><nowiki>** Reason</nowiki></tt> - defines a reason in this group',
 'protect-edit-reasonlist'   => 'Shown beneath the page protection form on the right side. It is a link to [[MediaWiki:Protect-dropdown]]. See also {{msg|Delete-edit-reasonlist}} and {{msg|Ipb-edit-dropdown}}.',
-'protect-expiry-options'    => "* Description: Options for the duration of the block. 
-* <font color=\"red\">Be careful:</font> '''1 translation:1 english''', so the first part is the translation and the second part should stay in English. 
+'protect-expiry-options'    => "* Description: Options for the duration of the block.
+* <font color=\"red\">Be careful:</font> '''1 translation:1 english''', so the first part is the translation and the second part should stay in English.
 * Example: See e.g. [[MediaWiki:Protect-expiry-options/nl]] if you still don't know how to do it.
 
 {{Identical|Infinite}}",
@@ -2087,8 +2088,8 @@ Parameter $1 is the message "[[MediaWiki:Hide/{{SUBPAGENAME}}|hide]]" or "[[Medi
 'ipbenableautoblock'           => '{{Identical|Automatically block ...}}',
 'ipbsubmit'                    => '{{Identical|Block this user}}',
 'ipbother'                     => '{{Identical|Other time}}',
-'ipboptions'                   => "* Description: Options for the duration of the block. 
-* <font color=\"red\">Be careful:</font> '''1 translation:1 english''', so the first part is the translation and the second part should stay in English. 
+'ipboptions'                   => "* Description: Options for the duration of the block.
+* <font color=\"red\">Be careful:</font> '''1 translation:1 english''', so the first part is the translation and the second part should stay in English.
 * Example: See e.g. [[MediaWiki:Ipboptions/nl]] if you still don't know how to do it.
 
 {{Identical|Infinite}}",
@@ -2117,8 +2118,8 @@ Parameter $1 is the message "[[MediaWiki:Hide/{{SUBPAGENAME}}|hide]]" or "[[Medi
 * $1 - word "{{msg|Hide}}" or "{{msg|Show}}"',
 'ipblocklist-submit'           => '{{Identical|Search}}',
 'blocklistline'                => 'This is the text of an entry in the Special:IPBlockList.
-* $1 is the hour and date of the block. 
-* $2 is the sysop. 
+* $1 is the hour and date of the block.
+* $2 is the sysop.
 * $3 is the blocked user or IP (with link to contributions and talk)
 * $4 contains "hour and date of expiry, details (\'\'reason\'\')"
 
@@ -2137,7 +2138,7 @@ See also {{msg-mw|Blocklogentry}}.',
 
 {{Identical|Block log}}',
 'blocklog-fulllog'             => 'Shown at Special:BlockIP at the end of the block log if there are more than 10 entries for this user, see [[Special:BlockIP/Raymond]] as example (visible for sysops only).',
-'blocklogentry'                => 'This is the text of an entry in the Block log (and RC), after hour (and date, only in the Block log) and sysop name: 
+'blocklogentry'                => 'This is the text of an entry in the Block log (and RC), after hour (and date, only in the Block log) and sysop name:
 * $1 is the blocked user or IP (with link to contributions and talk)
 * $2 is the duration of the block (hours, days etc.) or the specified expiry date
 * $3 contains "(details) (\'\'reason\'\')"
@@ -2355,7 +2356,7 @@ If the length of the translated message is over 60 characters (including spaces)
 This message appears at the very end of the list of names in the message [[MediaWiki:Othercontribs/{{SUBPAGENAME}}|othercontribs]]. If there are no anonymous users in the credits list then this message does not appear at all.
 
 * $1 is the number of anonymous users in the message',
-'siteuser'         => "This message is shown when viewing the credits of a page (example: {{fullurl:Main Page|action=credits}}). Note that this action is disabled by default (currently enabled on translatewiki.net). 
+'siteuser'         => "This message is shown when viewing the credits of a page (example: {{fullurl:Main Page|action=credits}}). Note that this action is disabled by default (currently enabled on translatewiki.net).
 
 This message is the variable $3 in the message {{msg-mw|lastmodifiedatby}}. This message only appears if the user has not entered his 'real name' in his preferences. The variable $1 in this message is a user name.
 
@@ -2369,7 +2370,7 @@ See also {{msg-mw|Siteusers}}.",
 See also [[MediaWiki:Lastmodifiedat/{{SUBPAGENAME}}]].",
 'othercontribs'    => 'This message is shown when viewing the credits of a page (example: {{fullurl:Main Page|action=credits}}). Note that this action is disabled by default (currently enabled on translatewiki.net - to use type <nowiki>&action=credits</nowiki> at the end of any URL in the address bar).
 * $1: the list of author(s) of the revisions preceding the current revision. It appears after the message [[Mediawiki:lastmodifiedatby/{{SUBPAGENAME}}]]. If there are no previous authors this message does not appear at all. If needed the messages [[Mediawiki:siteusers/{{SUBPAGENAME}}]], [[Mediawiki:anonymous/{{SUBPAGENAME}}]] and [[Mediawiki:and/{{SUBPAGENAME}}]] are part of the list of names.',
-'others'           => 'The following explanation is guesswork. This message is shown when viewing the credits of a page (example: {{fullurl:Main Page|action=credits}}). Note that this action is disabled by default (currently enabled on translatewiki.net - to use type <nowiki>&action=credits</nowiki> at the end of any URL in the address bar). 
+'others'           => 'The following explanation is guesswork. This message is shown when viewing the credits of a page (example: {{fullurl:Main Page|action=credits}}). Note that this action is disabled by default (currently enabled on translatewiki.net - to use type <nowiki>&action=credits</nowiki> at the end of any URL in the address bar).
 
 The message appears at the end of the list of credits given in the message [[Mediawiki:Othercontribs/{{SUBPAGENAME}}]] if the number of contributors is above a certain level.',
 'siteusers'        => 'This message is shown when viewing the credits of a page (example: {{fullurl:Main Page|action=credits}}). Note that this action is disabled by default (currently enabled on translatewiki.net).
@@ -2813,7 +2814,7 @@ Please leave the link http://www.mediawiki.org/wiki/Manual:External_editors exac
 *$3 is a URL to [[Special:ConfirmEmail]]
 *$4 is a time and date (duplicated by $6 and $7)
 *$5 is a URL to [[Special:InvalidateEmail]]
-*$6 is a date 
+*$6 is a date
 *$7 is a time',
 'confirmemail_invalidated' => 'This is the text of the special page [[Special:InvalidateEmail|InvalidateEmail]] (with the title in [[Mediawiki:Invalidateemail]]) where user goes if he chooses the cancel e-mail confirmation link from the confirmation e-mail.',
 'invalidateemail'          => "This is the '''name of the special page''' where user goes if he chooses the cancel e-mail confirmation link from the confirmation e-mail.",
@@ -3009,14 +3010,14 @@ There are no such extensions here, so look at [[wikipedia:Special:Version]] for
 
 * $1: width of the file
 * $2: height of the file
-* $3: File size 
+* $3: File size
 * $4: MIME type',
 'fileduplicatesearch-result-1' => 'Result line after the list of files of [[Special:FileDuplicateSearch]]
 
 $1 is the name of the requested file.',
 'fileduplicatesearch-result-n' => 'Result line after the list of files of [[Special:FileDuplicateSearch]]
 
-* $1 is the name of the requested file. 
+* $1 is the name of the requested file.
 * $2 is the number of identical duplicates of the requested file',
 
 # Special:SpecialPages
@@ -3077,4 +3078,7 @@ Used on [[Special:Tags]]. Verb. Used as display text on a link to create/edit a
 
 {{Identical|Other}}',
 
+# Special:ListGroupRights
+'listgrouprights-key' => 'Footer note for the [[Special:ListGroupRights]] page'
+
 );
index 9c20de4..cbdf488 100644 (file)
@@ -106,7 +106,7 @@ p.mw-filedelete-editreasons, p.mw-delete-editreasons {
 
 div.searchresult {
        font-size: 95%;
-       width:38em;     
+       width:38em;
 }
 
 .mw-search-results {
@@ -133,7 +133,7 @@ div.searchresult {
        border: 1px solid silver;
 }
 .mw-search-formheader div.search-types {
-       float:left; 
+       float:left;
        padding-left: 0.25em;
 }
 .mw-search-formheader div.search-types > ul {
@@ -261,7 +261,7 @@ div#mw-search-interwiki-caption {
        padding-right: 0.15em;
        padding-bottom: 0.2em;
        padding-top: 0.15em;
-       background-color:#ececec; 
+       background-color:#ececec;
        border-top:1px solid #BBBBBB;
 }
 
@@ -318,29 +318,29 @@ table.mw-userrights-groups * td,table.mw-userrights-groups * th {
        padding-right: 1.5em;
 }
 
-/* 
+/*
  * OpenSearch ajax suggestions
  */
 .os-suggest {
-       overflow: auto; 
-       overflow-x: hidden; 
+       overflow: auto;
+       overflow-x: hidden;
        position: absolute;
        top: 0px;
        left: 0px;
        width: 0px;
-       background-color: white; 
+       background-color: white;
        background-color: Window;
        border-style: solid;
        border-color: #AAAAAA;
        border-width: 1px;
-       z-index:99; 
-       visibility:hidden; 
-       font-size:95%;  
+       z-index:99;
+       visibility:hidden;
+       font-size:95%;
 }
 
 table.os-suggest-results {
        font-size: 95%;
-       cursor: pointer; 
+       cursor: pointer;
        border: 0;
        border-collapse: collapse;
        width: 100%;
@@ -348,7 +348,7 @@ table.os-suggest-results {
 
 td.os-suggest-result, td.os-suggest-result-hl {
        white-space: nowrap;
-       background-color: white; 
+       background-color: white;
        background-color: Window;
        color: black;
        color: WindowText;
@@ -356,7 +356,7 @@ td.os-suggest-result, td.os-suggest-result-hl {
 }
 td.os-suggest-result-hl,
 td.os-suggest-result-hl-webkit {
-       background-color: #4C59A6; 
+       background-color: #4C59A6;
        color: white;
 }
 td.os-suggest-result-hl {
@@ -367,7 +367,7 @@ td.os-suggest-result-hl {
 }
 
 .os-suggest-toggle {
-       position: relative; 
+       position: relative;
        left: 1ex;
        font-size: 65%;
 }
@@ -402,6 +402,7 @@ td.os-suggest-result-hl {
 table.mw-listgrouprights-table tr  {
        vertical-align: top;
 }
+.listgrouprights-revoked { text-decoration: line-through; }
 
 /* Special:Statistics styling */
 td.mw-statistics-numbers {