* API: (bug 15935) Add action=userrights to the API
authorRoan Kattouw <catrope@users.mediawiki.org>
Tue, 24 Mar 2009 16:04:50 +0000 (16:04 +0000)
committerRoan Kattouw <catrope@users.mediawiki.org>
Tue, 24 Mar 2009 16:04:50 +0000 (16:04 +0000)
* Add ustoken=userrights to list=users
* Move the non-UI part of UserrightsPage::saveUserGroups() to the static and more generic doSaveUserGroups()
* Add a $reason parameter to UserrightsPage::addLogEntry() and make it and its helpers static
* Move UserrightsPage::changeableGroups() and changeableByGroup() to the User class and make the latter static
* In doSaveUserGroups(), drop groups that the user doesn't have from $remove (and those that they do have from $add), and return array($add, $remove)
* Fix up a comment in ApiQueryRecentChanges

RELEASE-NOTES
includes/AutoLoader.php
includes/User.php
includes/api/ApiMain.php
includes/api/ApiQueryRecentChanges.php
includes/api/ApiQueryUsers.php
includes/specials/SpecialUserrights.php

index ab11533..af7abc3 100644 (file)
@@ -330,6 +330,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
 
 === Languages updated in 1.15 ===
 
index 85e7e66..d830c03 100644 (file)
@@ -290,6 +290,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 191de40..fad1fb8 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',
                );
        }
 
index 5e824d0..11fc9bd 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 $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,27 @@ 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 ) {
                $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 +308,7 @@ class UserrightsPage extends SpecialPage {
                return $user;
        }
 
-       function makeGroupNameList( $ids ) {
+       static function makeGroupNameList( $ids ) {
                if( empty( $ids ) ) {
                        return wfMsgForContent( 'rightsnone' );
                } else {
@@ -301,11 +316,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 +526,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;
        }