r48746 - Code Review

From MediaWiki.org

Jump to: navigation, search
Repository:MediaWiki
Revision:r48745 | r48746 (on ViewVC) | r48747 >
Date:16:04, 24 March 2009
Author:catrope
Status:resolved (Comments)
Tags:api 
Comment:* API: (bug 15935) Add action=userrights to the API
* 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
Modified paths:

Diff [purge]

Index: trunk/phase3/includes/User.php
===================================================================
--- trunk/phase3/includes/User.php	(revision 48745)
+++ trunk/phase3/includes/User.php	(revision 48746)
@@ -3231,8 +3231,116 @@
 			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.
 	 * Will have no effect for anonymous users.
 	 */
Index: trunk/phase3/includes/api/ApiQueryRecentChanges.php
===================================================================
--- trunk/phase3/includes/api/ApiQueryRecentChanges.php	(revision 48745)
+++ trunk/phase3/includes/api/ApiQueryRecentChanges.php	(revision 48746)
@@ -43,12 +43,13 @@
 	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: trunk/phase3/includes/api/ApiMain.php
===================================================================
--- trunk/phase3/includes/api/ApiMain.php	(revision 48745)
+++ trunk/phase3/includes/api/ApiMain.php	(revision 48746)
@@ -80,6 +80,7 @@
 		'watch' => 'ApiWatch',
 		'patrol' => 'ApiPatrol',
 		'import' => 'ApiImport',
+		'userrights' => 'ApiUserrights',
 	);
 
 	/**
Index: trunk/phase3/includes/api/ApiQueryUsers.php
===================================================================
--- trunk/phase3/includes/api/ApiQueryUsers.php	(revision 48745)
+++ trunk/phase3/includes/api/ApiQueryUsers.php	(revision 48746)
@@ -39,7 +39,37 @@
 	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();
 		$result = $this->getResult();
@@ -115,6 +145,18 @@
 				}
 				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 @@
 			),
 			'users' => array(
 				ApiBase :: PARAM_ISMULTI => true
-			)
+			),
+			'token' => array(
+				ApiBase :: PARAM_TYPE => array_keys($this->getTokenFunctions()),
+				ApiBase :: PARAM_ISMULTI => true
+			),
 		);
 	}
 
@@ -167,7 +213,8 @@
 				'  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: trunk/phase3/includes/AutoLoader.php
===================================================================
--- trunk/phase3/includes/AutoLoader.php	(revision 48745)
+++ trunk/phase3/includes/AutoLoader.php	(revision 48746)
@@ -290,6 +290,7 @@
 	'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: trunk/phase3/includes/specials/SpecialUserrights.php
===================================================================
--- trunk/phase3/includes/specials/SpecialUserrights.php	(revision 48745)
+++ trunk/phase3/includes/specials/SpecialUserrights.php	(revision 48746)
@@ -149,29 +149,46 @@
 				$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 @@
 
 		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 @@
 		return $user;
 	}
 
-	function makeGroupNameList( $ids ) {
+	static function makeGroupNameList( $ids ) {
 		if( empty( $ids ) ) {
 			return wfMsgForContent( 'rightsnone' );
 		} else {
@@ -301,11 +316,11 @@
 		}
 	}
 
-	function makeGroupNameListForLog( $ids ) {
+	static function makeGroupNameListForLog( $ids ) {
 		if( empty( $ids ) ) {
 			return '';
 		} else {
-			return $this->makeGroupNameList( $ids );
+			return self::makeGroupNameList( $ids );
 		}
 	}
 
@@ -511,115 +526,20 @@
 	}
 
 	/**
-	 * 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 ) );
-		
+		wfRunHooks( 'UserrightsChangeableGroups', array( $this,
+			$wgUser, $wgUser->getEffectiveGroups(), &$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];
-		}
-		
-		return $groups;
-	}
-
-	/**
 	 * Show a rights log fragment for the specified user
 	 *
 	 * @param $user User to show log for
Index: trunk/phase3/RELEASE-NOTES
===================================================================
--- trunk/phase3/RELEASE-NOTES	(revision 48745)
+++ trunk/phase3/RELEASE-NOTES	(revision 48746)
@@ -330,6 +330,7 @@
   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 ===
 

Follow-up revisions

RevisionCommit summaryAuthorDate
r48747Followup to r48746: forgot to add new filecatrope16:06, 24 March 2009
r48778Implement changes from r48746 in subclassers of Userrightspage, avoid warningswerdna01:30, 25 March 2009
r48909Revert r48746 (API userrights). Breaks Special:GlobalGroupMembership by changing...werdna05:59, 27 March 2009
r48970Redo r48746 (API userrights, reverted in r48909 and r48910) in a way that doesn'...catrope19:08, 28 March 2009

Comments

#Comment by Catrope (Talk | contribs)   16:07, 24 March 2009

ApiUserrights.php added in r47847

#Comment by Catrope (Talk | contribs)   16:07, 24 March 2009

Oops, r48747

#Comment by Werdna (Talk | contribs)   05:37, 27 March 2009

This breaks global rights management at Special:GlobalGroupMembership.

#Comment by Catrope (Talk | contribs)   19:16, 28 March 2009

Fixed in r48970, r48971

Status & tagging log

  • 14:57, 6 June 2009 Tim Starling (Talk | contribs) changed the status of r48746 [removed: fixme added: resolved]
  • 05:37, 27 March 2009 Werdna (Talk | contribs) changed the status of r48746 [removed: resolved added: fixme]
  • 19:03, 24 March 2009 Brion VIBBER (Talk | contribs) changed the status of r48746 [removed: new added: resolved]
  • 16:05, 24 March 2009 Catrope (Talk | contribs) changed the tags for r48746 [added: api]
Views
Toolbox