6378d0858c1e3b14305885938ba0556c01947ecc
4 * Special page to allow managing user group membership
6 * @addtogroup SpecialPage
7 * @todo This code is disgusting and needs a total rewrite
11 require_once( dirname(__FILE__
) . '/HTMLForm.php');
14 function wfSpecialUserrights() {
16 $form = new UserrightsForm($wgRequest);
21 * A class to manage user levels rights.
22 * @addtogroup SpecialPage
24 class UserrightsForm
extends HTMLForm
{
25 var $mPosted, $mRequest, $mSaveprefs;
26 /** Escaped local url name*/
30 public function __construct( &$request ) {
31 $this->mPosted
= $request->wasPosted();
32 $this->mRequest
=& $request;
33 $this->mName
= 'userrights';
34 $this->mReason
= $request->getText( 'user-reason' );
36 $titleObj = SpecialPage
::getTitleFor( 'Userrights' );
37 $this->action
= $titleObj->escapeLocalURL();
41 * Manage forms to be shown according to posted data.
42 * Depending on the submit button used, call a form or a save function.
45 // show the general form
47 if( $this->mPosted
) {
48 // show some more forms
49 if( $this->mRequest
->getCheck( 'ssearchuser' ) ) {
50 $this->editUserGroupsForm( $this->mRequest
->getVal( 'user-editname' ) );
54 if( $this->mRequest
->getCheck( 'saveusergroups' ) ) {
56 $username = $this->mRequest
->getVal( 'user-editname' );
57 $reason = $this->mRequest
->getVal( 'user-reason' );
58 if( $wgUser->matchEditToken( $this->mRequest
->getVal( 'wpEditToken' ), $username ) ) {
59 $this->saveUserGroups( $username,
60 $this->mRequest
->getArray( 'member' ),
61 $this->mRequest
->getArray( 'available' ),
70 * Save user groups changes in the database.
71 * Data comes from the editUserGroupsForm() form function
73 * @param string $username Username to apply changes to.
74 * @param array $removegroup id of groups to be removed.
75 * @param array $addgroup id of groups to be added.
76 * @param string $reason Reason for group change
79 function saveUserGroups( $username, $removegroup, $addgroup, $reason = '') {
80 $split = $this->splitUsername( $username );
81 if( WikiError
::isError( $split ) ) {
82 $wgOut->addWikiText( wfMsg( 'userrights-nodatabase', $split->getMessage() ) );
86 list( $database, $name ) = $split;
87 $this->db
=& $this->getDB( $database );
88 $userid = $this->getUserId( $database, $name );
91 $wgOut->addWikiText( wfMsg( 'nosuchusershort', wfEscapeWikiText( $username ) ) );
96 if ($database != '' && !$wgUser->isAllowed('userrights-interwiki')) {
97 $wgOut->addWikiText( wfMsg( 'userrights-no-interwiki' ) );
101 $oldGroups = $this->getUserGroups( $database, $userid );
102 $newGroups = $oldGroups;
103 // remove then add groups
104 if(isset($removegroup)) {
105 $newGroups = array_diff($newGroups, $removegroup);
106 foreach( $removegroup as $group ) {
107 $this->removeUserGroup( $database, $userid, $group );
110 if(isset($addgroup)) {
111 $newGroups = array_merge($newGroups, $addgroup);
112 foreach( $addgroup as $group ) {
113 $this->addUserGroup( $database, $userid, $group );
116 $newGroups = array_unique( $newGroups );
118 // Ensure that caches are cleared
119 $this->touchUser( $database, $userid );
121 wfDebug( 'oldGroups: ' . print_r( $oldGroups, true ) );
122 wfDebug( 'newGroups: ' . print_r( $newGroups, true ) );
123 wfRunHooks( 'UserRights', array( &$u, $addgroup, $removegroup ) );
125 $log = new LogPage( 'rights' );
126 $log->addEntry( 'rights', Title
::makeTitle( NS_USER
, $username ), $this->mReason
, array( $this->makeGroupNameList( $oldGroups ),
127 $this->makeGroupNameList( $newGroups ) ) );
131 * Edit user groups membership
132 * @param string $username Name of the user.
134 function editUserGroupsForm($username) {
135 global $wgOut, $wgUser;
137 $split = $this->splitUsername( $username );
138 if( WikiError
::isError( $split ) ) {
139 $wgOut->addWikiText( wfMsg( 'userrights-nodatabase', $split->getMessage() ) );
143 list( $database, $name ) = $split;
144 $this->db
=& $this->getDB( $database );
145 $userid = $this->getUserId( $database, $name );
148 $wgOut->addWikiText( wfMsg( 'nouserspecified' ) );
150 } elseif( $userid == 0) {
151 $wgOut->addWikiText( wfMsg( 'nosuchusershort', wfEscapeWikiText( $username ) ) );
156 if ($database != '' && !$wgUser->isAllowed('userrights-interwiki')) {
157 $wgOut->addWikiText( wfMsg( 'userrights-no-interwiki' ) );
161 $groups = $this->getUserGroups( $database, $userid );
163 $this->showEditUserGroupsForm( $username, $groups );
165 if ($database == '') {
166 $this->showLogFragment( User
::newFromName($username), $wgOut );
170 function splitUsername( $username ) {
171 $parts = explode( '@', $username );
172 if( count( $parts ) < 2 ) {
173 return array( '', $username );
175 list( $name, $database ) = $parts;
177 global $wgLocalDatabases;
178 return array_search( $database, $wgLocalDatabases ) !== false
179 ?
array( $database, $name )
180 : new WikiError( 'Bogus database suffix "' . wfEscapeWikiText( $database ) . '"' );
184 * Open a database connection to work on for the requested user.
185 * This may be a new connection to another database for remote users.
186 * @param string $database
189 function &getDB( $database ) {
190 if( $database == '' ) {
191 $db =& wfGetDB( DB_MASTER
);
193 global $wgDBuser, $wgDBpassword;
194 $server = $this->getMaster( $database );
195 $db = new Database( $server, $wgDBuser, $wgDBpassword, $database );
201 * Return the master server to connect to for the requested database.
203 function getMaster( $database ) {
204 global $wgDBserver, $wgAlternateMaster;
205 if( isset( $wgAlternateMaster[$database] ) ) {
206 return $wgAlternateMaster[$database];
211 function getUserId( $database, $name ) {
214 return ( $name{0} == "#" )
215 ?
IntVal( substr( $name, 1 ) )
216 : IntVal( $this->db
->selectField( 'user',
218 array( 'user_name' => $name ),
219 'MakesysopStewardForm::getUserId' ) );
222 function getUserGroups( $database, $userid ) {
223 $res = $this->db
->select( 'user_groups',
225 array( 'ug_user' => $userid ),
226 'MakesysopStewardForm::getUserGroups' );
228 while( $row = $this->db
->fetchObject( $res ) ) {
229 $groups[] = $row->ug_group
;
234 function addUserGroup( $database, $userid, $group ) {
235 $this->db
->insert( 'user_groups',
237 'ug_user' => $userid,
238 'ug_group' => $group,
240 'MakesysopStewardForm::addUserGroup',
244 function removeUserGroup( $database, $userid, $group ) {
245 $this->db
->delete( 'user_groups',
247 'ug_user' => $userid,
248 'ug_group' => $group,
250 'MakesysopStewardForm::addUserGroup' );
253 function touchUser( $database, $userid ) {
254 $this->db
->update( 'user',
255 array( 'user_touched' => $this->db
->timestamp() ),
256 array( 'user_id' => $userid ),
257 'MakesysopStewardForm::touchUser' );
260 if ( function_exists( 'wfForeignMemcKey' ) ) {
261 $key = wfForeignMemcKey( $database, false, 'user', 'id', $userid );
263 $key = "$database:user:id:$userid";
265 $wgMemc->delete( $key );
268 function makeGroupNameList( $ids ) {
269 return implode( ', ', $ids );
273 * Output a form to allow searching for a user
275 function switchForm() {
276 global $wgOut, $wgRequest;
277 $username = $wgRequest->getText( 'user-editname' );
278 $form = Xml
::openElement( 'form', array( 'method' => 'post', 'action' => $this->action
, 'name' => 'uluser' ) );
279 $form .= '<fieldset><legend>' . wfMsgHtml( 'userrights-lookup-user' ) . '</legend>';
280 $form .= '<p>' . Xml
::inputLabel( wfMsg( 'userrights-user-editname' ), 'user-editname', 'username', 30, $username ) . '</p>';
281 $form .= '<p>' . Xml
::submitButton( wfMsg( 'editusergroup' ), array( 'name' => 'ssearchuser' ) ) . '</p>';
282 $form .= '</fieldset>';
284 $wgOut->addHTML( $form );
288 * Go through used and available groups and return the ones that this
289 * form will be able to manipulate based on the current user's system
292 * @param $groups Array: list of groups the given user is in
293 * @return Array: Tuple of addable, then removable groups
295 protected function splitGroups( $groups ) {
296 list($addable, $removable) = array_values( $this->changeableGroups() );
297 $removable = array_intersect($removable, $groups ); // Can't remove groups the user doesn't have
298 $addable = array_diff( $addable, $groups ); // Can't add groups the user does have
300 return array( $addable, $removable );
304 * Show the form to edit group memberships.
306 * @todo make all CSS-y and semantic
307 * @param $username String: Name of user you're editing
308 * @param $groups Array: Array of groups the user is in
310 protected function showEditUserGroupsForm( $username, $groups ) {
311 global $wgOut, $wgUser;
313 list( $addable, $removable ) = $this->splitGroups( $groups );
316 Xml
::openElement( 'form', array( 'method' => 'post', 'action' => $this->action
, 'name' => 'editGroup' ) ) .
317 Xml
::hidden( 'user-editname', $username ) .
318 Xml
::hidden( 'wpEditToken', $wgUser->editToken( $username ) ) .
319 Xml
::openElement( 'fieldset' ) .
320 Xml
::element( 'legend', array(), wfMsg( 'userrights-editusergroup' ) ) .
321 $wgOut->parse( wfMsg( 'editinguser', $username ) ) .
322 $this->explainRights() .
329 <td width='50%'>" . $this->removeSelect( $removable ) . "</td>
330 <td width='50%'>" . $this->addSelect( $addable ) . "</td>
336 $wgOut->parse( wfMsg('userrights-groupshelp') ) .
341 Xml
::label( wfMsg( 'userrights-reason' ), 'wpReason' ) .
344 Xml
::input( 'user-reason', 60, false, array( 'id' => 'wpReason', 'maxlength' => 255 ) ) .
350 Xml
::submitButton( wfMsg( 'saveusergroups' ), array( 'name' => 'saveusergroups' ) ) .
354 Xml
::closeElement( 'fieldset' ) .
355 Xml
::closeElement( 'form' ) . "\n"
360 * Prepare a list of groups the user is able to add and remove
364 private function explainRights() {
365 global $wgUser, $wgLang;
368 list( $add, $remove ) = array_values( $this->changeableGroups() );
370 if( count( $add ) > 0 )
371 $out[] = wfMsgExt( 'userrights-available-add', 'parseinline', $wgLang->listToText( $add ) );
372 if( count( $remove ) > 0 )
373 $out[] = wfMsgExt( 'userrights-available-remove', 'parseinline', $wgLang->listToText( $remove ) );
375 return count( $out ) > 0
376 ?
implode( ' ', $out )
377 : wfMsgExt( 'userrights-available-none', 'parseinline' );
381 * Adds the <select> thingie where you can select what groups to remove
383 * @param array $groups The groups that can be removed
384 * @return string XHTML <select> element
386 private function removeSelect( $groups ) {
387 return $this->doSelect( $groups, 'member' );
391 * Adds the <select> thingie where you can select what groups to add
393 * @param array $groups The groups that can be added
394 * @return string XHTML <select> element
396 private function addSelect( $groups ) {
397 return $this->doSelect( $groups, 'available' );
401 * Adds the <select> thingie where you can select what groups to add/remove
403 * @param array $groups The groups that can be added/removed
404 * @param string $name 'member' or 'available'
405 * @return string XHTML <select> element
407 private function doSelect( $groups, $name ) {
408 $ret = wfMsgHtml( "{$this->mName}-groups$name" ) .
409 Xml
::openElement( 'select', array(
410 'name' => "{$name}[]",
411 'multiple' => 'multiple',
413 'style' => 'width: 100%;'
416 foreach ($groups as $group) {
417 $ret .= Xml
::element( 'option', array( 'value' => $group ), User
::getGroupName( $group ) );
419 $ret .= Xml
::closeElement( 'select' );
424 * @param string $group The name of the group to check
425 * @return bool Can we remove the group?
427 private function canRemove( $group ) {
428 // $this->changeableGroups()['remove'] doesn't work, of course. Thanks,
430 $groups = $this->changeableGroups();
431 return in_array( $group, $groups['remove'] );
435 * @param string $group The name of the group to check
436 * @return bool Can we add the group?
438 private function canAdd( $group ) {
439 $groups = $this->changeableGroups();
440 return in_array( $group, $groups['add'] );
444 * Returns an array of the groups that the user can add/remove.
446 * @return Array array( 'add' => array( addablegroups ), 'remove' => array( removablegroups ) )
448 function changeableGroups() {
451 if( $wgUser->isAllowed( 'userrights' ) ) {
452 // This group gives the right to modify everything (reverse-
453 // compatibility with old "userrights lets you change
456 'add' => User
::getAllGroups(),
457 'remove' => User
::getAllGroups()
461 // Okay, it's not so simple, we will have to go through the arrays
462 $groups = array( 'add' => array(), 'remove' => array() );
463 $addergroups = $wgUser->getEffectiveGroups();
465 foreach ($addergroups as $addergroup) {
466 $groups = array_merge_recursive(
467 $groups, $this->changeableByGroup($addergroup)
469 $groups['add'] = array_unique( $groups['add'] );
470 $groups['remove'] = array_unique( $groups['remove'] );
476 * Returns an array of the groups that a particular group can add/remove.
478 * @param String $group The group to check for whether it can add/remove
479 * @return Array array( 'add' => array( addablegroups ), 'remove' => array( removablegroups ) )
481 private function changeableByGroup( $group ) {
482 global $wgAddGroups, $wgRemoveGroups;
484 $groups = array( 'add' => array(), 'remove' => array() );
485 if( empty($wgAddGroups[$group]) ) {
486 // Don't add anything to $groups
487 } elseif( $wgAddGroups[$group] === true ) {
488 // You get everything
489 $groups['add'] = User
::getAllGroups();
490 } elseif( is_array($wgAddGroups[$group]) ) {
491 $groups['add'] = $wgAddGroups[$group];
494 // Same thing for remove
495 if( empty($wgRemoveGroups[$group]) ) {
496 } elseif($wgRemoveGroups[$group] === true ) {
497 $groups['remove'] = User
::getAllGroups();
498 } elseif( is_array($wgRemoveGroups[$group]) ) {
499 $groups['remove'] = $wgRemoveGroups[$group];
505 * Show a rights log fragment for the specified user
507 * @param User $user User to show log for
508 * @param OutputPage $output OutputPage to use
510 protected function showLogFragment( $user, $output ) {
511 $viewer = new LogViewer(
516 'page' => $user->getUserPage()->getPrefixedText(),
521 $output->addHtml( "<h2>" . htmlspecialchars( LogPage
::logName( 'rights' ) ) . "</h2>\n" );
522 $viewer->showList( $output );