From e501e1c51d3f9a759afd010d61b71681bb054901 Mon Sep 17 00:00:00 2001 From: Aryeh Gregor Date: Tue, 26 Jun 2007 04:07:27 +0000 Subject: [PATCH] (bug 6711) Add and to allow finer control over usergroup assignment. Completely reverse-compatible with existing userrights-based setups, but can replace Makesysop and Makebot and open the door to lots of other things as well. Could be moved to extension, I guess, but it just seems a lot simpler to have one interface for all adding/removing of groups. --- RELEASE-NOTES | 6 +- includes/DefaultSettings.php | 14 ++ includes/SpecialUserrights.php | 215 ++++++++++++++++++++++++++++-- languages/messages/MessagesEn.php | 4 + 4 files changed, 227 insertions(+), 12 deletions(-) diff --git a/RELEASE-NOTES b/RELEASE-NOTES index b519f22cbd..f7e0f4ac0d 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -22,12 +22,14 @@ it from source control: http://www.mediawiki.org/wiki/Download_from_SVN * $wgThumbUpright - Adjust width of upright images when parameter 'upright' is used +* $wgAddGroups, $wgRemoveGroups - Finer control over who can assign which + usergroups +* $wgEnotifImpersonal, $wgEnotifUseJobQ - Bulk mail options for large sites == New features since 1.10 == * (bug 8868) Separate "blocked" message for autoblocks * Adding expiry of block to block messages -* Bulk mail options ($wgEnotifImpersonal, $wgEnotifUseJobQ) for large sites * Links to redirect pages in categories are wrapped in * Introduced 'ImageOpenShowImageInlineBefore' hook; see docs/hooks.txt for @@ -105,6 +107,8 @@ it from source control: http://www.mediawiki.org/wiki/Download_from_SVN * Wrap site CSS and JavaScript in a
 tag, like user JS/CSS
 * (bug 10196) Add classes and dir="ltr" to the 
s on CSS and JS pages (new
   classes: mw-user-css, mw-user-js, mw-site-css, mw-site-js)
+* (bug 6711) Add $wgAddGroups and $wgRemoveGroups to allow finer control over
+  usergroup assignment.
 
 == Bugfixes since 1.10 ==
 
diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php
index 67ca01c4be..62f08685cd 100644
--- a/includes/DefaultSettings.php
+++ b/includes/DefaultSettings.php
@@ -1132,6 +1132,20 @@ $wgAutoConfirmAge = 0;
 $wgAutoConfirmCount = 0;
 //$wgAutoConfirmCount = 50;
 
+/**
+ * These settings can be used to give finer control over who can assign which
+ * groups at Special:Userrights.  Example configuration:
+ *
+ * // Bureaucrat can add any group
+ * $wgAddGroups['bureaucrat'] = true; 
+ * // Bureaucrats can only remove bots and sysops
+ * $wgRemoveGroups['bureaucrat'] = array( 'bot', 'sysop' ); 
+ * // Sysops can make bots
+ * $wgAddGroups['sysop'] = array( 'bot' ); 
+ * // Sysops can disable other sysops in an emergency, and disable bots
+ * $wgRemoveGroups['sysop'] = array( 'sysop', 'bot' ); 
+ */
+$wgAddGroups = $wgRemoveGroups = array(); // Add customizations after this line
 
 
 # Proxy scanner settings
diff --git a/includes/SpecialUserrights.php b/includes/SpecialUserrights.php
index 0e61622b85..201ee4074a 100644
--- a/includes/SpecialUserrights.php
+++ b/includes/SpecialUserrights.php
@@ -27,7 +27,7 @@ class UserrightsForm extends HTMLForm {
 	var $action;
 
 	/** Constructor*/
-	function UserrightsForm ( &$request ) {
+	public function __construct( &$request ) {
 		$this->mPosted = $request->wasPosted();
 		$this->mRequest =& $request;
 		$this->mName = 'userrights';
@@ -94,13 +94,17 @@ class UserrightsForm extends HTMLForm {
 		if(isset($removegroup)) {
 			$newGroups = array_diff($newGroups, $removegroup);
 			foreach( $removegroup as $group ) {
-				$u->removeGroup( $group );
+				if ( $this->canRemove( $group ) ) {
+					$u->removeGroup( $group );
+				}
 			}
 		}
 		if(isset($addgroup)) {
 			$newGroups = array_merge($newGroups, $addgroup);
 			foreach( $addgroup as $group ) {
-				$u->addGroup( $group );
+				if ( $this->canAdd( $group ) ) {
+					$u->addGroup( $group );
+				}
 			}
 		}
 		$newGroups = array_unique( $newGroups );
@@ -108,7 +112,7 @@ class UserrightsForm extends HTMLForm {
 		wfDebug( 'oldGroups: ' . print_r( $oldGroups, true ) );
 		wfDebug( 'newGroups: ' . print_r( $newGroups, true ) );
 
-		wfRunHooks( 'UserRights', array( &$u, $addgroup, $removegroup ) );
+		wfRunHooks( 'UserRights', array( &$u, $addgroup, $removegroup ) );	
 		$log = new LogPage( 'rights' );
 		$log->addEntry( 'rights', Title::makeTitle( NS_USER, $u->getName() ), $reason, array( $this->makeGroupNameList( $oldGroups ),
 			$this->makeGroupNameList( $newGroups ) ) );
@@ -139,7 +143,7 @@ class UserrightsForm extends HTMLForm {
 	 */
 	function editUserGroupsForm($username) {
 		global $wgOut;
-
+	
 		$user = User::newFromName($username);
 		if( is_null( $user ) ) {
 			$wgOut->addWikiText( wfMsg( 'nouserspecified' ) );
@@ -149,12 +153,24 @@ class UserrightsForm extends HTMLForm {
 			return;
 		}
 
-		$groups = $user->getGroups();
-		$this->showEditUserGroupsForm( $username, $groups );
+		list($addable, $removable) = array_values( $this->changeableGroups() );
+		$removable = array_intersect($removable, $user->getGroups()); // Can't remove groups the user doesn't have
+		$addable   = array_diff(     $addable,   $user->getGroups()); // Can't add groups the user does have
+
+		$this->showEditUserGroupsForm( $username, $addable, $removable );
 	}
-	
-	function showEditUserGroupsForm( $username, $groups ) {
+
+	/**
+	 * Show the form to edit group memberships.
+	 *
+	 * @todo make all CSS-y and semantic
+	 * @param $username  String: Name of user you're editing
+	 * @param $addable   Array:  Array of groups that can be added
+	 * @param $removable Array:  Array of groups that can be removed
+	 */
+	private function showEditUserGroupsForm( $username, $addable, $removable ) {
 		global $wgOut, $wgUser;
+
 		$wgOut->addHTML(
 			Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->action, 'name' => 'editGroup' ) ) .
 			Xml::hidden( 'user-editname', $username ) .
@@ -162,14 +178,15 @@ class UserrightsForm extends HTMLForm {
 			Xml::openElement( 'fieldset' ) .
 			Xml::element( 'legend', array(), wfMsg( 'userrights-editusergroup' ) ) .
 			$wgOut->parse( wfMsg( 'editinguser', $username ) ) .
+			$this->explainRights() .
 			"
@@ -197,5 +214,181 @@ class UserrightsForm extends HTMLForm {
 			Xml::closeElement( 'form' ) . "\n"
 		);
 	}
+
+	/**
+	 * Explains what groups the user can add and remove, and why.
+	 *
+	 * @return string Explanatory sanitized HTML message
+	 */
+	private function explainRights() {
+		global $wgUser;
+		$groups = $wgUser->getEffectiveGroups();
+		foreach( $groups as $group ) {
+			if( $this->changeableByGroup( $group ) == array(
+				'add' => array(),
+				'remove' => array()
+			) ) {
+				// Can't add or remove anything, ignore this group
+				$groups = array_diff( $groups, array( $group ) );
+			}
+		}
+		$grouplists = array( $groups );
+		list( $grouplists[1], $grouplists[2] ) = array_values( $this->changeableGroups() );
+		
+		// Now format them nicely for display (yay mutable variables? I'm sick
+		// of thinking up new names)
+		foreach( $grouplists as &$list ) {
+			if( $list == array() ) {
+				$list = wfMsgExt( 'userrights-list-nogroups', 'parseinline' );
+			} else {
+				$list = wfMsgExt(
+					'userrights-list-groups',
+					'parseinline',
+					count( $list ),
+					implode(
+						$list,
+						wfMsgHtml( 'userrights-list-separator' )
+					)
+				);
+			}
+		}
+		
+		return wfMsgExt(
+			'userrights-list',
+			'parse',
+			$grouplists[0],
+			$grouplists[1],
+			$grouplists[2]
+		);
+
+	}
+
+	/**
+	 * Adds the  element
+	 */
+	private function removeSelect( $groups ) {
+		return $this->doSelect( $groups, 'member' );
+	}
+
+	/**
+	 * Adds the  element
+	 */
+	private function addSelect( $groups ) {
+		return $this->doSelect( $groups, 'available' );
+	}
+
+	/**
+	 * Adds the  element
+	 */
+	private function doSelect( $groups, $name ) {
+		$ret = wfMsgHtml( "{$this->mName}-groups$name" ) .
+		Xml::openElement( 'select', array(
+				'name' => "{$name}[]",
+				'multiple' => 'multiple',
+				'size' => '6',
+				'style' => 'width: 100%;'
+			)
+		);
+		foreach ($groups as $group) {
+			$ret .= Xml::element( 'option', array( 'value' => $group ), User::getGroupName( $group ) );
+		}
+		$ret .= Xml::closeElement( 'select' );
+		return $ret;
+	}
+
+	/**
+	 * @param  string $group The name of the group to check
+	 * @return bool Can we remove the group?
+	 */
+	private function canRemove( $group ) {
+		// $this->changeableGroups()['remove'] doesn't work, of course. Thanks,
+		// PHP.
+		$groups = $this->changeableGroups();
+		return in_array( $group, $groups['remove'] );
+	}
+
+	/**
+	 * @param  string $group The name of the group to check
+	 * @return bool Can we add the group?
+	 */
+	private function canAdd( $group ) {
+		$groups = $this->changeableGroups();
+		return in_array( $group, $groups['add'] );
+	}
+
+	/**
+	 * Returns an array of the groups that the user can add/remove.
+	 *
+	 * @return Array array( 'add' => array( addablegroups ), 'remove' => array( removablegroups ) )
+	 */
+	private function changeableGroups() {
+		global $wgUser, $wgGroupPermissions;
+
+		$groups = array( 'add' => array(), 'remove' => 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'] );
+		}
+		return $groups;
+	}
+
+	/**
+	 * Returns an array of the groups that a particular group can add/remove.
+	 *
+	 * @param String $group The group to check for whether it can add/remove
+	 * @return Array array( 'add' => array( addablegroups ), 'remove' => array( removablegroups ) )
+	 */	
+	private function changeableByGroup( $group ) {
+		global $wgGroupPermissions, $wgAddGroups, $wgRemoveGroups;
+	
+		if( empty($wgGroupPermissions[$group]['userrights']) ) {
+			// This group doesn't give the right to modify anything
+			return array( 'add' => array(), 'remove' => array() );
+		}
+		if( empty($wgAddGroups[$group]) and empty($wgRemoveGroups[$group]) ) {
+			// This group gives the right to modify everything (reverse-
+			// compatibility with old "userrights lets you change
+			// everything")
+			return array(
+				'add' => User::getAllGroups(),
+				'remove' => User::getAllGroups()
+			);
+		}
+		
+		// Okay, it's not so simple, we have to go through the arrays
+		$groups = array( 'add' => array(), 'remove' => 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];
+		}
+		return $groups;
+	}
 } // end class UserrightsForm
 ?>
diff --git a/languages/messages/MessagesEn.php b/languages/messages/MessagesEn.php
index 0f697c88a1..8a560a0a83 100644
--- a/languages/messages/MessagesEn.php
+++ b/languages/messages/MessagesEn.php
@@ -1277,6 +1277,10 @@ containing all of the search terms will appear in the result).",
 'userrights-groupshelp'      => 'Select groups you want the user to be removed from or added to.
 Unselected groups will not be changed. You can deselect a group with CTRL + Left Click',
 'userrights-reason'          => 'Reason for change:',
+'userrights-list'            => 'Because you are a member of $1, you can add $2 and remove $3.',
+'userrights-list-nogroups'   => 'no groups',
+'userrights-list-groups'     => 'the {{PLURAL:$1|group|groups}} $2',
+'userrights-list-separator'  => ', ',
 
 # Groups
 'group'            => 'Group:',
-- 
2.20.1

- - + +
" . HTMLSelectGroups( 'member', $this->mName.'-groupsmember', $groups, true, 6 ) . "" . HTMLSelectGroups( 'available', $this->mName.'-groupsavailable', $groups, true, 6, true) . "" . $this->removeSelect( $removable ) . "" . $this->addSelect( $addable ) . "