00001 <?php
00013 class UserrightsPage extends SpecialPage {
00014 # The target of the local right-adjuster's interest. Can be gotten from
00015 # either a GET parameter or a subpage-style parameter, so have a member
00016 # variable for it.
00017 protected $mTarget;
00018 protected $isself = false;
00019
00020 public function __construct() {
00021 parent::__construct( 'Userrights' );
00022 }
00023
00024 public function isRestricted() {
00025 return true;
00026 }
00027
00028 public function userCanExecute( $user ) {
00029 return $this->userCanChangeRights( $user, false );
00030 }
00031
00032 public function userCanChangeRights( $user, $checkIfSelf = true ) {
00033 $available = $this->changeableGroups();
00034 return !empty( $available['add'] )
00035 or !empty( $available['remove'] )
00036 or ( ( $this->isself || !$checkIfSelf ) and
00037 (!empty( $available['add-self'] )
00038 or !empty( $available['remove-self'] )));
00039 }
00040
00047 function execute( $par ) {
00048
00049
00050 global $wgUser, $wgRequest;
00051
00052 if( $par ) {
00053 $this->mTarget = $par;
00054 } else {
00055 $this->mTarget = $wgRequest->getVal( 'user' );
00056 }
00057
00058 if (!$this->mTarget) {
00059
00060
00061
00062
00063
00064 $available = $this->changeableGroups();
00065 if (empty($available['add']) && empty($available['remove']))
00066 $this->mTarget = $wgUser->getName();
00067 }
00068
00069 if ($this->mTarget == $wgUser->getName())
00070 $this->isself = true;
00071
00072 if( !$this->userCanChangeRights( $wgUser, true ) ) {
00073
00074 global $wgOut;
00075 $wgOut->showPermissionsErrorPage( array(
00076 $wgUser->isAnon()
00077 ? 'userrights-nologin'
00078 : 'userrights-notallowed' ) );
00079 return;
00080 }
00081
00082 if ( wfReadOnly() ) {
00083 global $wgOut;
00084 $wgOut->readOnlyPage();
00085 return;
00086 }
00087
00088 $this->outputHeader();
00089
00090 $this->setHeaders();
00091
00092
00093 $this->switchForm();
00094
00095 if( $wgRequest->wasPosted() ) {
00096
00097 if( $wgRequest->getCheck( 'saveusergroups' ) ) {
00098 $reason = $wgRequest->getVal( 'user-reason' );
00099 $tok = $wgRequest->getVal( 'wpEditToken' );
00100 if( $wgUser->matchEditToken( $tok, $this->mTarget ) ) {
00101 $this->saveUserGroups(
00102 $this->mTarget,
00103 $reason
00104 );
00105
00106 global $wgOut;
00107
00108 $url = $this->getSuccessURL();
00109 $wgOut->redirect( $url );
00110 return;
00111 }
00112 }
00113 }
00114
00115
00116 if( $this->mTarget ) {
00117 $this->editUserGroupsForm( $this->mTarget );
00118 }
00119 }
00120
00121 function getSuccessURL() {
00122 return $this->getTitle( $this->mTarget )->getFullURL();
00123 }
00124
00133 function saveUserGroups( $username, $reason = '') {
00134 global $wgRequest, $wgUser, $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf;
00135
00136 $user = $this->fetchUser( $username );
00137 if( !$user ) {
00138 return;
00139 }
00140
00141 $allgroups = $this->getAllGroups();
00142 $addgroup = array();
00143 $removegroup = array();
00144
00145
00146
00147 foreach ($allgroups as $group) {
00148
00149
00150 if ($wgRequest->getCheck( "wpGroup-$group" )) {
00151 $addgroup[] = $group;
00152 } else {
00153 $removegroup[] = $group;
00154 }
00155 }
00156
00157
00158 $changeable = $this->changeableGroups();
00159 $addable = array_merge( $changeable['add'], $this->isself ? $changeable['add-self'] : array() );
00160 $removable = array_merge( $changeable['remove'], $this->isself ? $changeable['remove-self'] : array() );
00161
00162 $removegroup = array_unique(
00163 array_intersect( (array)$removegroup, $removable ) );
00164 $addgroup = array_unique(
00165 array_intersect( (array)$addgroup, $addable ) );
00166
00167 $oldGroups = $user->getGroups();
00168 $newGroups = $oldGroups;
00169
00170 if( $removegroup ) {
00171 $newGroups = array_diff($newGroups, $removegroup);
00172 foreach( $removegroup as $group ) {
00173 $user->removeGroup( $group );
00174 }
00175 }
00176 if( $addgroup ) {
00177 $newGroups = array_merge($newGroups, $addgroup);
00178 foreach( $addgroup as $group ) {
00179 $user->addGroup( $group );
00180 }
00181 }
00182 $newGroups = array_unique( $newGroups );
00183
00184
00185 $user->invalidateCache();
00186
00187 wfDebug( 'oldGroups: ' . print_r( $oldGroups, true ) );
00188 wfDebug( 'newGroups: ' . print_r( $newGroups, true ) );
00189 if( $user instanceof User ) {
00190
00191 wfRunHooks( 'UserRights', array( &$user, $addgroup, $removegroup ) );
00192 }
00193
00194 if( $newGroups != $oldGroups ) {
00195 $this->addLogEntry( $user, $oldGroups, $newGroups );
00196 }
00197 }
00198
00202 function addLogEntry( $user, $oldGroups, $newGroups ) {
00203 global $wgRequest;
00204 $log = new LogPage( 'rights' );
00205
00206 $log->addEntry( 'rights',
00207 $user->getUserPage(),
00208 $wgRequest->getText( 'user-reason' ),
00209 array(
00210 $this->makeGroupNameListForLog( $oldGroups ),
00211 $this->makeGroupNameListForLog( $newGroups )
00212 )
00213 );
00214 }
00215
00220 function editUserGroupsForm( $username ) {
00221 global $wgOut;
00222
00223 $user = $this->fetchUser( $username );
00224 if( !$user ) {
00225 return;
00226 }
00227
00228 $groups = $user->getGroups();
00229
00230 $this->showEditUserGroupsForm( $user, $groups );
00231
00232
00233
00234 $this->showLogFragment( $user, $wgOut );
00235 }
00236
00244 function fetchUser( $username ) {
00245 global $wgOut, $wgUser, $wgUserrightsInterwikiDelimiter;
00246
00247 $parts = explode( $wgUserrightsInterwikiDelimiter, $username );
00248 if( count( $parts ) < 2 ) {
00249 $name = trim( $username );
00250 $database = '';
00251 } else {
00252 list( $name, $database ) = array_map( 'trim', $parts );
00253
00254 if( !$wgUser->isAllowed( 'userrights-interwiki' ) ) {
00255 $wgOut->addWikiMsg( 'userrights-no-interwiki' );
00256 return null;
00257 }
00258 if( !UserRightsProxy::validDatabase( $database ) ) {
00259 $wgOut->addWikiMsg( 'userrights-nodatabase', $database );
00260 return null;
00261 }
00262 }
00263
00264 if( $name == '' ) {
00265 $wgOut->addWikiMsg( 'nouserspecified' );
00266 return false;
00267 }
00268
00269 if( $name{0} == '#' ) {
00270
00271
00272 $id = intval( substr( $name, 1 ) );
00273
00274 if( $database == '' ) {
00275 $name = User::whoIs( $id );
00276 } else {
00277 $name = UserRightsProxy::whoIs( $database, $id );
00278 }
00279
00280 if( !$name ) {
00281 $wgOut->addWikiMsg( 'noname' );
00282 return null;
00283 }
00284 }
00285
00286 if( $database == '' ) {
00287 $user = User::newFromName( $name );
00288 } else {
00289 $user = UserRightsProxy::newFromName( $database, $name );
00290 }
00291
00292 if( !$user || $user->isAnon() ) {
00293 $wgOut->addWikiMsg( 'nosuchusershort', $username );
00294 return null;
00295 }
00296
00297 return $user;
00298 }
00299
00300 function makeGroupNameList( $ids ) {
00301 if( empty( $ids ) ) {
00302 return wfMsgForContent( 'rightsnone' );
00303 } else {
00304 return implode( ', ', $ids );
00305 }
00306 }
00307
00308 function makeGroupNameListForLog( $ids ) {
00309 if( empty( $ids ) ) {
00310 return '';
00311 } else {
00312 return $this->makeGroupNameList( $ids );
00313 }
00314 }
00315
00319 function switchForm() {
00320 global $wgOut, $wgScript;
00321 $wgOut->addHTML(
00322 Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'name' => 'uluser', 'id' => 'mw-userrights-form1' ) ) .
00323 Xml::hidden( 'title', $this->getTitle()->getPrefixedText() ) .
00324 Xml::openElement( 'fieldset' ) .
00325 Xml::element( 'legend', array(), wfMsg( 'userrights-lookup-user' ) ) .
00326 Xml::inputLabel( wfMsg( 'userrights-user-editname' ), 'user', 'username', 30, $this->mTarget ) . ' ' .
00327 Xml::submitButton( wfMsg( 'editusergroup' ) ) .
00328 Xml::closeElement( 'fieldset' ) .
00329 Xml::closeElement( 'form' ) . "\n"
00330 );
00331 }
00332
00341 protected function splitGroups( $groups ) {
00342 list($addable, $removable, $addself, $removeself) = array_values( $this->changeableGroups() );
00343
00344 $removable = array_intersect(
00345 array_merge( $this->isself ? $removeself : array(), $removable ),
00346 $groups );
00347 $addable = array_diff(
00348 array_merge( $this->isself ? $addself : array(), $addable ),
00349 $groups );
00350
00351 return array( $addable, $removable );
00352 }
00353
00360 protected function showEditUserGroupsForm( $user, $groups ) {
00361 global $wgOut, $wgUser, $wgLang;
00362
00363 $list = array();
00364 foreach( $groups as $group )
00365 $list[] = self::buildGroupLink( $group );
00366
00367 $grouplist = '';
00368 if( count( $list ) > 0 ) {
00369 $grouplist = wfMsgHtml( 'userrights-groupsmember' );
00370 $grouplist = '<p>' . $grouplist . ' ' . $wgLang->listToText( $list ) . '</p>';
00371 }
00372 $wgOut->addHTML(
00373 Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->getTitle()->getLocalURL(), 'name' => 'editGroup', 'id' => 'mw-userrights-form2' ) ) .
00374 Xml::hidden( 'user', $this->mTarget ) .
00375 Xml::hidden( 'wpEditToken', $wgUser->editToken( $this->mTarget ) ) .
00376 Xml::openElement( 'fieldset' ) .
00377 Xml::element( 'legend', array(), wfMsg( 'userrights-editusergroup' ) ) .
00378 wfMsgExt( 'editinguser', array( 'parse' ), wfEscapeWikiText( $user->getName() ) ) .
00379 wfMsgExt( 'userrights-groups-help', array( 'parse' ) ) .
00380 $grouplist .
00381 Xml::tags( 'p', null, $this->groupCheckboxes( $groups ) ) .
00382 Xml::openElement( 'table', array( 'border' => '0', 'id' => 'mw-userrights-table-outer' ) ) .
00383 "<tr>
00384 <td class='mw-label'>" .
00385 Xml::label( wfMsg( 'userrights-reason' ), 'wpReason' ) .
00386 "</td>
00387 <td class='mw-input'>" .
00388 Xml::input( 'user-reason', 60, false, array( 'id' => 'wpReason', 'maxlength' => 255 ) ) .
00389 "</td>
00390 </tr>
00391 <tr>
00392 <td></td>
00393 <td class='mw-submit'>" .
00394 Xml::submitButton( wfMsg( 'saveusergroups' ), array( 'name' => 'saveusergroups', 'accesskey' => 's' ) ) .
00395 "</td>
00396 </tr>" .
00397 Xml::closeElement( 'table' ) . "\n" .
00398 Xml::closeElement( 'fieldset' ) .
00399 Xml::closeElement( 'form' ) . "\n"
00400 );
00401 }
00402
00409 private static function buildGroupLink( $group ) {
00410 static $cache = array();
00411 if( !isset( $cache[$group] ) )
00412 $cache[$group] = User::makeGroupLinkHtml( $group, User::getGroupName( $group ) );
00413 return $cache[$group];
00414 }
00415
00420 protected static function getAllGroups() {
00421 return User::getAllGroups();
00422 }
00423
00430 private function groupCheckboxes( $usergroups ) {
00431 $allgroups = $this->getAllGroups();
00432 $ret = '';
00433
00434 $column = 1;
00435 $settable_col = '';
00436 $unsettable_col = '';
00437
00438 foreach ($allgroups as $group) {
00439 $set = in_array( $group, $usergroups );
00440 # Should the checkbox be disabled?
00441 $disabled = !(
00442 ( $set && $this->canRemove( $group ) ) ||
00443 ( !$set && $this->canAdd( $group ) ) );
00444 # Do we need to point out that this action is irreversible?
00445 $irreversible = !$disabled && (
00446 ($set && !$this->canAdd( $group )) ||
00447 (!$set && !$this->canRemove( $group ) ) );
00448
00449 $attr = $disabled ? array( 'disabled' => 'disabled' ) : array();
00450 $text = $irreversible
00451 ? wfMsgHtml( 'userrights-irreversible-marker', User::getGroupMember( $group ) )
00452 : User::getGroupMember( $group );
00453 $checkbox = Xml::checkLabel( $text, "wpGroup-$group",
00454 "wpGroup-$group", $set, $attr );
00455 $checkbox = $disabled ? Xml::tags( 'span', array( 'class' => 'mw-userrights-disabled' ), $checkbox ) : $checkbox;
00456
00457 if ($disabled) {
00458 $unsettable_col .= "$checkbox<br />\n";
00459 } else {
00460 $settable_col .= "$checkbox<br />\n";
00461 }
00462 }
00463
00464 if ($column) {
00465 $ret .= Xml::openElement( 'table', array( 'border' => '0', 'class' => 'mw-userrights-groups' ) ) .
00466 "<tr>
00467 ";
00468 if( $settable_col !== '' ) {
00469 $ret .= xml::element( 'th', null, wfMsg( 'userrights-changeable-col' ) );
00470 }
00471 if( $unsettable_col !== '' ) {
00472 $ret .= xml::element( 'th', null, wfMsg( 'userrights-unchangeable-col' ) );
00473 }
00474 $ret.= "</tr>
00475 <tr>
00476 ";
00477 if( $settable_col !== '' ) {
00478 $ret .=
00479 " <td style='vertical-align:top;'>
00480 $settable_col
00481 </td>
00482 ";
00483 }
00484 if( $unsettable_col !== '' ) {
00485 $ret .=
00486 " <td style='vertical-align:top;'>
00487 $unsettable_col
00488 </td>
00489 ";
00490 }
00491 $ret .= Xml::closeElement( 'tr' ) . Xml::closeElement( 'table' );
00492 }
00493
00494 return $ret;
00495 }
00496
00501 private function canRemove( $group ) {
00502
00503
00504 $groups = $this->changeableGroups();
00505 return in_array( $group, $groups['remove'] ) || ($this->isself && in_array( $group, $groups['remove-self'] ));
00506 }
00507
00512 private function canAdd( $group ) {
00513 $groups = $this->changeableGroups();
00514 return in_array( $group, $groups['add'] ) || ($this->isself && in_array( $group, $groups['add-self'] ));
00515 }
00516
00522 function changeableGroups() {
00523 global $wgUser;
00524
00525 if( $wgUser->isAllowed( 'userrights' ) ) {
00526
00527
00528
00529
00530 $all = array_merge( User::getAllGroups() );
00531 return array(
00532 'add' => $all,
00533 'remove' => $all,
00534 'add-self' => array(),
00535 'remove-self' => array()
00536 );
00537 }
00538
00539
00540 $groups = array(
00541 'add' => array(),
00542 'remove' => array(),
00543 'add-self' => array(),
00544 'remove-self' => array() );
00545 $addergroups = $wgUser->getEffectiveGroups();
00546
00547 foreach ($addergroups as $addergroup) {
00548 $groups = array_merge_recursive(
00549 $groups, $this->changeableByGroup($addergroup)
00550 );
00551 $groups['add'] = array_unique( $groups['add'] );
00552 $groups['remove'] = array_unique( $groups['remove'] );
00553 $groups['add-self'] = array_unique( $groups['add-self'] );
00554 $groups['remove-self'] = array_unique( $groups['remove-self'] );
00555 }
00556
00557
00558 wfRunHooks( 'UserrightsChangeableGroups', array( $this, $wgUser, $addergroups, &$groups ) );
00559
00560 return $groups;
00561 }
00562
00569 private function changeableByGroup( $group ) {
00570 global $wgAddGroups, $wgRemoveGroups, $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf;
00571
00572 $groups = array( 'add' => array(), 'remove' => array(), 'add-self' => array(), 'remove-self' => array() );
00573 if( empty($wgAddGroups[$group]) ) {
00574
00575 } elseif( $wgAddGroups[$group] === true ) {
00576
00577 $groups['add'] = User::getAllGroups();
00578 } elseif( is_array($wgAddGroups[$group]) ) {
00579 $groups['add'] = $wgAddGroups[$group];
00580 }
00581
00582
00583 if( empty($wgRemoveGroups[$group]) ) {
00584 } elseif($wgRemoveGroups[$group] === true ) {
00585 $groups['remove'] = User::getAllGroups();
00586 } elseif( is_array($wgRemoveGroups[$group]) ) {
00587 $groups['remove'] = $wgRemoveGroups[$group];
00588 }
00589
00590
00591 if( empty($wgGroupsAddToSelf['user']) || $wgGroupsAddToSelf['user'] !== true ) {
00592 foreach($wgGroupsAddToSelf as $key => $value) {
00593 if( is_int($key) ) {
00594 $wgGroupsAddToSelf['user'][] = $value;
00595 }
00596 }
00597 }
00598
00599 if( empty($wgGroupsRemoveFromSelf['user']) || $wgGroupsRemoveFromSelf['user'] !== true ) {
00600 foreach($wgGroupsRemoveFromSelf as $key => $value) {
00601 if( is_int($key) ) {
00602 $wgGroupsRemoveFromSelf['user'][] = $value;
00603 }
00604 }
00605 }
00606
00607
00608 if( empty($wgGroupsAddToSelf[$group]) ) {
00609 } elseif( $wgGroupsAddToSelf[$group] === true ) {
00610
00611 $groups['add-self'] = User::getAllGroups();
00612 } elseif( is_array($wgGroupsAddToSelf[$group]) ) {
00613 $groups['add-self'] = $wgGroupsAddToSelf[$group];
00614 }
00615
00616 if( empty($wgGroupsRemoveFromSelf[$group]) ) {
00617 } elseif( $wgGroupsRemoveFromSelf[$group] === true ) {
00618 $groups['remove-self'] = User::getAllGroups();
00619 } elseif( is_array($wgGroupsRemoveFromSelf[$group]) ) {
00620 $groups['remove-self'] = $wgGroupsRemoveFromSelf[$group];
00621 }
00622
00623 return $groups;
00624 }
00625
00632 protected function showLogFragment( $user, $output ) {
00633 $output->addHTML( Xml::element( 'h2', null, LogPage::logName( 'rights' ) . "\n" ) );
00634 LogEventsList::showLogExtract( $output, 'rights', $user->getUserPage()->getPrefixedText() );
00635 }
00636 }