Redo r48746 (API userrights, reverted in r48909 and r48910) in a way that doesn't...
authorRoan Kattouw <catrope@users.mediawiki.org>
Sat, 28 Mar 2009 19:08:47 +0000 (19:08 +0000)
committerRoan Kattouw <catrope@users.mediawiki.org>
Sat, 28 Mar 2009 19:08:47 +0000 (19:08 +0000)
RELEASE-NOTES
docs/hooks.txt
includes/AutoLoader.php
includes/User.php
includes/api/ApiMain.php
includes/api/ApiQueryRecentChanges.php
includes/api/ApiQueryUsers.php
includes/api/ApiUserrights.php [new file with mode: 0644]
includes/specials/SpecialUserrights.php

index 7ddd8f8..9f51e2e 100644 (file)
@@ -345,6 +345,7 @@ it from source control: http://www.mediawiki.org/wiki/Download_from_SVN
   when someone tries to use them
 * BREAKING CHANGE: action=purge requires write rights and, for anonymous users,
   a POST request
+* (bug 15935) Added action=userrights to add/remove users to/from groups
 * (bug 18099) Using appendtext to edit a non-existent page causes an interface
   message to be included in the page text
 * Added uiprop=changeablegroups to meta=userinfo
index f973d6b..286ed90 100644 (file)
@@ -1439,6 +1439,13 @@ $addergroups : Array of groups that the user is in
                                        'add-self'    => array( addablegroups to self ),
                                        'remove-self' => array( removable groups from self )
                                )
+
+'UserRightsLogEntry': before a log entry is added by UserrightsPage::addLogEntry(). If you use this hook to add your own log entries, you can return false to prevent the usual log entry from being added
+$user: User or UserrightsProxy object
+$oldGroups: Array of groups the user was a member of before the change
+$newGroups: Array of groups the user is a member of after the change
+$reason: User-provided summary
+
 'UserRetrieveNewTalks': called when retrieving "You have new messages!" message(s)
 $user: user retrieving new talks messages
 $talks: array of new talks page(s)
index 9404024..8edefe5 100644 (file)
@@ -292,6 +292,7 @@ $wgAutoloadLocalClasses = array(
        'ApiRollback' => 'includes/api/ApiRollback.php',
        'ApiUnblock' => 'includes/api/ApiUnblock.php',
        'ApiUndelete' => 'includes/api/ApiUndelete.php',
+       'ApiUserrights' => 'includes/api/ApiUserrights.php',
        'ApiWatch' => 'includes/api/ApiWatch.php',
        'Services_JSON' => 'includes/api/ApiFormatJson_json.php',
        'Services_JSON_Error' => 'includes/api/ApiFormatJson_json.php',
index cc861ad..4798294 100644 (file)
@@ -3231,6 +3231,114 @@ class User {
                        return $text;
                }
        }
+       
+       /**
+        * Returns an array of the groups that a particular group can add/remove.
+        *
+        * @param $group String: the group to check for whether it can add/remove
+        * @return Array array( 'add' => array( addablegroups ),
+        *  'remove' => array( removablegroups ),
+        *  'add-self' => array( addablegroups to self),
+        *  'remove-self' => array( removable groups from self) )
+        */
+       static function changeableByGroup( $group ) {
+               global $wgAddGroups, $wgRemoveGroups, $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf;
+
+               $groups = array( 'add' => array(), 'remove' => array(), 'add-self' => array(), 'remove-self' => array() );
+               if( empty($wgAddGroups[$group]) ) {
+                       // Don't add anything to $groups
+               } elseif( $wgAddGroups[$group] === true ) {
+                       // You get everything
+                       $groups['add'] = self::getAllGroups();
+               } elseif( is_array($wgAddGroups[$group]) ) {
+                       $groups['add'] = $wgAddGroups[$group];
+               }
+
+               // Same thing for remove
+               if( empty($wgRemoveGroups[$group]) ) {
+               } elseif($wgRemoveGroups[$group] === true ) {
+                       $groups['remove'] = self::getAllGroups();
+               } 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) {
+                               if( is_int($key) ) {
+                                       $wgGroupsAddToSelf['user'][] = $value;
+                               }
+                       }
+               }
+               
+               if( empty($wgGroupsRemoveFromSelf['user']) || $wgGroupsRemoveFromSelf['user'] !== true ) {
+                       foreach($wgGroupsRemoveFromSelf as $key => $value) {
+                               if( is_int($key) ) {
+                                       $wgGroupsRemoveFromSelf['user'][] = $value;
+                               }
+                       }
+               }
+               
+               // Now figure out what groups the user can add to him/herself
+               if( empty($wgGroupsAddToSelf[$group]) ) {
+               } elseif( $wgGroupsAddToSelf[$group] === true ) {
+                       // No idea WHY this would be used, but it's there
+                       $groups['add-self'] = User::getAllGroups();
+               } 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 ),
+        *  'remove' => array( removablegroups ),
+        *  'add-self' => array( addablegroups to self),
+        *  'remove-self' => array( removable groups from self) )
+        */
+       function changeableGroups() {
+               if( $this->isAllowed( 'userrights' ) ) {
+                       // This group gives the right to modify everything (reverse-
+                       // compatibility with old "userrights lets you change
+                       // everything")
+                       // Using array_merge to make the groups reindexed
+                       $all = array_merge( User::getAllGroups() );
+                       return array(
+                               'add' => $all,
+                               'remove' => $all,
+                               'add-self' => array(),
+                               'remove-self' => array()
+                       );
+               }
+
+               // Okay, it's not so simple, we will have to go through the arrays
+               $groups = array(
+                               'add' => array(),
+                               'remove' => array(),
+                               'add-self' => array(),
+                               'remove-self' => array() );
+               $addergroups = $this->getEffectiveGroups();
+
+               foreach ($addergroups as $addergroup) {
+                       $groups = array_merge_recursive(
+                               $groups, $this->changeableByGroup($addergroup)
+                       );
+                       $groups['add']    = array_unique( $groups['add'] );
+                       $groups['remove'] = array_unique( $groups['remove'] );
+                       $groups['add-self'] = array_unique( $groups['add-self'] );
+                       $groups['remove-self'] = array_unique( $groups['remove-self'] );
+               }
+               return $groups;
+       }
 
        /**
         * Increment the user's edit-count field.
index 3fa0d72..42aaacc 100644 (file)
@@ -80,6 +80,7 @@ class ApiMain extends ApiBase {
                'watch' => 'ApiWatch',
                'patrol' => 'ApiPatrol',
                'import' => 'ApiImport',
+               'userrights' => 'ApiUserrights',
        );
 
        /**
index 9dacb86..94b1fee 100644 (file)
@@ -43,12 +43,13 @@ class ApiQueryRecentChanges extends ApiQueryBase {
        private $fld_comment = false, $fld_user = false, $fld_flags = false,
                        $fld_timestamp = false, $fld_title = false, $fld_ids = false,
                        $fld_sizes = false;
-       
+       /**
+        * Get an array mapping token names to their handler functions.
+        * The prototype for a token function is func($pageid, $title, $rc)
+        * it should return a token or false (permission denied)
+        * @return array(tokenname => function)
+        */
        protected function getTokenFunctions() {
-               // tokenname => function
-               // function prototype is func($pageid, $title, $rev)
-               // should return token or false
-
                // Don't call the hooks twice
                if(isset($this->tokenFunctions))
                        return $this->tokenFunctions;
index 51fa44e..9f51c75 100644 (file)
@@ -39,6 +39,36 @@ if (!defined('MEDIAWIKI')) {
        public function __construct($query, $moduleName) {
                parent :: __construct($query, $moduleName, 'us');
        }
+       
+       /**
+        * Get an array mapping token names to their handler functions.
+        * The prototype for a token function is func($user)
+        * it should return a token or false (permission denied)
+        * @return array(tokenname => function)
+        */
+       protected function getTokenFunctions() {
+               // Don't call the hooks twice
+               if(isset($this->tokenFunctions))
+                       return $this->tokenFunctions;
+
+               // If we're in JSON callback mode, no tokens can be obtained
+               if(!is_null($this->getMain()->getRequest()->getVal('callback')))
+                       return array();
+
+               $this->tokenFunctions = array(
+                       'userrights' => array( 'ApiQueryUsers', 'getUserrightsToken' ),
+               );
+               wfRunHooks('APIQueryUsersTokens', array(&$this->tokenFunctions));
+               return $this->tokenFunctions;
+       }
+       
+       public static function getUserrightsToken($user)
+       {
+               global $wgUser;
+               // Since the permissions check for userrights is non-trivial,
+               // don't bother with it here
+               return $wgUser->editToken($user->getName());
+       }
 
        public function execute() {
                $params = $this->extractRequestParams();
@@ -115,6 +145,18 @@ if (!defined('MEDIAWIKI')) {
                                }
                                if(isset($this->prop['emailable']) && $user->canReceiveEmail())
                                        $data[$name]['emailable'] = '';
+                               if(!is_null($params['token']))
+                               {
+                                       $tokenFunctions = $this->getTokenFunctions();
+                                       foreach($params['token'] as $t)
+                                       {
+                                               $val = call_user_func($tokenFunctions[$t], $user);
+                                               if($val === false)
+                                                       $this->setWarning("Action '$t' is not allowed for the current user");
+                                               else
+                                                       $data[$name][$t . 'token'] = $val;
+                                       }
+                               }
                        }
                }
                // Second pass: add result data to $retval
@@ -153,7 +195,11 @@ if (!defined('MEDIAWIKI')) {
                        ),
                        'users' => array(
                                ApiBase :: PARAM_ISMULTI => true
-                       )
+                       ),
+                       'token' => array(
+                               ApiBase :: PARAM_TYPE => array_keys($this->getTokenFunctions()),
+                               ApiBase :: PARAM_ISMULTI => true
+                       ),
                );
        }
 
@@ -167,7 +213,8 @@ if (!defined('MEDIAWIKI')) {
                                '  registration - adds the user\'s registration timestamp',
                                '  emailable    - tags if the user can and wants to receive e-mail through [[Special:Emailuser]]',
                        ),
-                       'users' => 'A list of users to obtain the same information for'
+                       'users' => 'A list of users to obtain the same information for',
+                       'token' => 'Which tokens to obtain for each user',
                );
        }
 
diff --git a/includes/api/ApiUserrights.php b/includes/api/ApiUserrights.php
new file mode 100644 (file)
index 0000000..9e6c9f1
--- /dev/null
@@ -0,0 +1,117 @@
+<?php
+
+/*
+ * Created on Mar 24, 2009
+ * API for MediaWiki 1.8+
+ *
+ * Copyright (C) 2009 Roan Kattouw <Firstname>.<Lastname>@home.nl
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+if (!defined('MEDIAWIKI')) {
+       // Eclipse helper - will be ignored in production
+       require_once ("ApiBase.php");
+}
+
+
+/**
+ * @ingroup API
+ */
+class ApiUserrights extends ApiBase {
+
+       public function __construct($main, $action) {
+               parent :: __construct($main, $action);
+       }
+
+       public function execute() {
+               global $wgUser;
+               $params = $this->extractRequestParams();
+               if(is_null($params['user']))
+                       $this->dieUsageMsg(array('missingparam', 'user'));
+               $user = User::newFromName($params['user']);
+               if($user->isAnon())
+                       $this->dieUsageMsg(array('nosuchuser', $params['user']));
+               if(is_null($params['token']))
+                       $this->dieUsageMsg(array('missingparam', 'token'));
+               if(!$wgUser->matchEditToken($params['token'], $user->getName()))
+                       $this->dieUsageMsg(array('sessionfailure'));
+               
+               $r['user'] = $user->getName();
+               list($r['added'], $r['removed']) =
+                       UserrightsPage::doSaveUserGroups(
+                               $user, (array)$params['add'],
+                               (array)$params['remove'], $params['reason']);
+
+               $this->getResult()->setIndexedTagName($r['added'], 'group');
+               $this->getResult()->setIndexedTagName($r['removed'], 'group');
+               $this->getResult()->addValue(null, $this->getModuleName(), $r);
+       }
+
+       public function mustBePosted() {
+               return true;
+       }
+
+       public function isWriteMode() {
+               return true;
+       }
+
+       public function getAllowedParams() {
+               return array (
+                       'user' => array(
+                               ApiBase :: PARAM_TYPE => 'user'
+                       ),
+                       'add' => array(
+                               ApiBase :: PARAM_TYPE => User::getAllGroups(),
+                               ApiBase :: PARAM_ISMULTI => true
+                       ),
+                       'remove' => array(
+                               ApiBase :: PARAM_TYPE => User::getAllGroups(),
+                               ApiBase :: PARAM_ISMULTI => true
+                       ),
+                       'token' => null,
+                       'reason' => array(
+                               ApiBase :: PARAM_DFLT => ''
+                       )
+               );
+       }
+
+       public function getParamDescription() {
+               return array (
+                       'user' => 'User name',
+                       'add' => 'Add the user to these groups',
+                       'remove' => 'Remove the user from these groups',
+                       'token' => 'A userrights token previously retrieved through list=users',
+                       'reason' => 'Reason for the change',
+               );
+       }
+
+       public function getDescription() {
+               return array(
+                       'Add/remove a user to/from groups',
+               );
+       }
+
+       protected function getExamples() {
+               return array (
+                       'api.php?action=userrights&user=FooBot&add=bot&remove=sysop|bureaucrat&token=123ABC'
+               );
+       }
+
+       public function getVersion() {
+               return __CLASS__ . ': $Id$';
+       }
+}
index 5e8d42a..bee4320 100644 (file)
@@ -149,29 +149,46 @@ class UserrightsPage extends SpecialPage {
                                $removegroup[] = $group;
                        }
                }
+               self::doSaveUserGroups( $user, $addgroup, $removegroup, $reason );
+       }
+       
+       /**
+        * Save user groups changes in the database.
+        *
+        * @param $user User object
+        * @param $add Array of groups to add
+        * @param $remove Array of groups to remove
+        * @param $reason String: reason for group change
+        * @return Array: Tuple of added, then removed groups
+        */
+       static function doSaveUserGroups( $user, $add, $remove, $reason = '' ) {
+               global $wgUser;
 
                // Validate input set...
-               $changeable = $this->changeableGroups();
-               $addable = array_merge( $changeable['add'], $this->isself ? $changeable['add-self'] : array() );
-               $removable = array_merge( $changeable['remove'], $this->isself ? $changeable['remove-self'] : array() );
+               $isself = ($user->getName() == $wgUser->getName());
+               $groups = $user->getGroups();
+               $changeable = $wgUser->changeableGroups();
+               $addable = array_merge( $changeable['add'], $isself ? $changeable['add-self'] : array() );
+               $removable = array_merge( $changeable['remove'], $isself ? $changeable['remove-self'] : array() );
 
-               $removegroup = array_unique(
-                       array_intersect( (array)$removegroup, $removable ) );
-               $addgroup = array_unique(
-                       array_intersect( (array)$addgroup, $addable ) );
+               $remove = array_unique(
+                       array_intersect( (array)$remove, $removable, $groups ) );
+               $add = array_unique( array_diff(
+                       array_intersect( (array)$add, $addable ),
+                       $groups ) );
 
                $oldGroups = $user->getGroups();
                $newGroups = $oldGroups;
                // remove then add groups
-               if( $removegroup ) {
-                       $newGroups = array_diff($newGroups, $removegroup);
-                       foreach( $removegroup as $group ) {
+               if( $remove ) {
+                       $newGroups = array_diff($newGroups, $remove);
+                       foreach( $remove as $group ) {
                                $user->removeGroup( $group );
                        }
                }
-               if( $addgroup ) {
-                       $newGroups = array_merge($newGroups, $addgroup);
-                       foreach( $addgroup as $group ) {
+               if( $add ) {
+                       $newGroups = array_merge($newGroups, $add);
+                       foreach( $add as $group ) {
                                $user->addGroup( $group );
                        }
                }
@@ -182,29 +199,34 @@ class UserrightsPage extends SpecialPage {
 
                wfDebug( 'oldGroups: ' . print_r( $oldGroups, true ) );
                wfDebug( 'newGroups: ' . print_r( $newGroups, true ) );
-               if( $user instanceof User ) {
-                       // hmmm
-                       wfRunHooks( 'UserRights', array( &$user, $addgroup, $removegroup ) );
-               }
+               wfRunHooks( 'UserRights', array( &$user, $add, $remove ) );
 
                if( $newGroups != $oldGroups ) {
-                       $this->addLogEntry( $user, $oldGroups, $newGroups );
+                       self::addLogEntry( $user, $oldGroups, $newGroups, $reason );
                }
+               return array( $add, $remove );
        }
+
        
        /**
         * Add a rights log entry for an action.
         */
-       function addLogEntry( $user, $oldGroups, $newGroups ) {
-               global $wgRequest;
+       static function addLogEntry( $user, $oldGroups, $newGroups, $reason ) {
+               // Just overriding addLogEntry in a subclass would be cleaner,
+               // but that requires PHP 5.3 (late static bindings)
+               if( !wfRunHooks( 'UserRightsLogEntry', array( $user, $oldGroups,
+                               $newGroups, $reason ) ) ) {
+                       return;
+               }
+               
                $log = new LogPage( 'rights' );
 
                $log->addEntry( 'rights',
                        $user->getUserPage(),
-                       $wgRequest->getText( 'user-reason' ),
+                       $reason,
                        array(
-                               $this->makeGroupNameListForLog( $oldGroups ),
-                               $this->makeGroupNameListForLog( $newGroups )
+                               self::makeGroupNameListForLog( $oldGroups ),
+                               self::makeGroupNameListForLog( $newGroups )
                        )
                );
        }
@@ -293,7 +315,7 @@ class UserrightsPage extends SpecialPage {
                return $user;
        }
 
-       function makeGroupNameList( $ids ) {
+       static function makeGroupNameList( $ids ) {
                if( empty( $ids ) ) {
                        return wfMsgForContent( 'rightsnone' );
                } else {
@@ -301,11 +323,11 @@ class UserrightsPage extends SpecialPage {
                }
        }
 
-       function makeGroupNameListForLog( $ids ) {
+       static function makeGroupNameListForLog( $ids ) {
                if( empty( $ids ) ) {
                        return '';
                } else {
-                       return $this->makeGroupNameList( $ids );
+                       return self::makeGroupNameList( $ids );
                }
        }
 
@@ -511,111 +533,16 @@ class UserrightsPage extends SpecialPage {
        }
 
        /**
-        * Returns an array of the groups that the user can add/remove.
+        * Returns $wgUser->changeableGroups()
         *
         * @return Array array( 'add' => array( addablegroups ), 'remove' => array( removablegroups ) , 'add-self' => array( addablegroups to self), 'remove-self' => array( removable groups from self) )
         */
        function changeableGroups() {
                global $wgUser;
-
-               if( $wgUser->isAllowed( 'userrights' ) ) {
-                       // This group gives the right to modify everything (reverse-
-                       // compatibility with old "userrights lets you change
-                       // everything")
-                       // Using array_merge to make the groups reindexed
-                       $all = array_merge( User::getAllGroups() );
-                       return array(
-                               'add' => $all,
-                               'remove' => $all,
-                               'add-self' => array(),
-                               'remove-self' => array()
-                       );
-               }
-
-               // Okay, it's not so simple, we will have to go through the arrays
-               $groups = array(
-                               'add' => array(),
-                               'remove' => array(),
-                               'add-self' => array(),
-                               'remove-self' => array() );
-               $addergroups = $wgUser->getEffectiveGroups();
-
-               foreach ($addergroups as $addergroup) {
-                       $groups = array_merge_recursive(
-                               $groups, $this->changeableByGroup($addergroup)
-                       );
-                       $groups['add']    = array_unique( $groups['add'] );
-                       $groups['remove'] = array_unique( $groups['remove'] );
-                       $groups['add-self'] = array_unique( $groups['add-self'] );
-                       $groups['remove-self'] = array_unique( $groups['remove-self'] );
-               }
-               
+               $groups = $wgUser->changeableGroups();
                // Run a hook because we can
-               wfRunHooks( 'UserrightsChangeableGroups', array( $this, $wgUser, $addergroups, &$groups ) );
-               
-               return $groups;
-       }
-
-       /**
-        * Returns an array of the groups that a particular group can add/remove.
-        *
-        * @param $group String: the group to check for whether it can add/remove
-        * @return Array array( 'add' => array( addablegroups ), 'remove' => array( removablegroups ) , 'add-self' => array( addablegroups to self), 'remove-self' => array( removable groups from self) )
-        */
-       private function changeableByGroup( $group ) {
-               global $wgAddGroups, $wgRemoveGroups, $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf;
-
-               $groups = array( 'add' => array(), 'remove' => array(), 'add-self' => array(), 'remove-self' => array() );
-               if( empty($wgAddGroups[$group]) ) {
-                       // Don't add anything to $groups
-               } elseif( $wgAddGroups[$group] === true ) {
-                       // You get everything
-                       $groups['add'] = User::getAllGroups();
-               } elseif( is_array($wgAddGroups[$group]) ) {
-                       $groups['add'] = $wgAddGroups[$group];
-               }
-
-               // Same thing for remove
-               if( empty($wgRemoveGroups[$group]) ) {
-               } elseif($wgRemoveGroups[$group] === true ) {
-                       $groups['remove'] = User::getAllGroups();
-               } 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) {
-                               if( is_int($key) ) {
-                                       $wgGroupsAddToSelf['user'][] = $value;
-                               }
-                       }
-               }
-               
-               if( empty($wgGroupsRemoveFromSelf['user']) || $wgGroupsRemoveFromSelf['user'] !== true ) {
-                       foreach($wgGroupsRemoveFromSelf as $key => $value) {
-                               if( is_int($key) ) {
-                                       $wgGroupsRemoveFromSelf['user'][] = $value;
-                               }
-                       }
-               }
-               
-               // Now figure out what groups the user can add to him/herself
-               if( empty($wgGroupsAddToSelf[$group]) ) {
-               } elseif( $wgGroupsAddToSelf[$group] === true ) {
-                       // No idea WHY this would be used, but it's there
-                       $groups['add-self'] = User::getAllGroups();
-               } 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];
-               }
-               
+               wfRunHooks( 'UserrightsChangeableGroups', array( $this,
+                       $wgUser, $wgUser->getEffectiveGroups(), &$groups ) );
                return $groups;
        }