This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * Implements Special:Userrights |
||
4 | * |
||
5 | * This program is free software; you can redistribute it and/or modify |
||
6 | * it under the terms of the GNU General Public License as published by |
||
7 | * the Free Software Foundation; either version 2 of the License, or |
||
8 | * (at your option) any later version. |
||
9 | * |
||
10 | * This program is distributed in the hope that it will be useful, |
||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
13 | * GNU General Public License for more details. |
||
14 | * |
||
15 | * You should have received a copy of the GNU General Public License along |
||
16 | * with this program; if not, write to the Free Software Foundation, Inc., |
||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||
18 | * http://www.gnu.org/copyleft/gpl.html |
||
19 | * |
||
20 | * @file |
||
21 | * @ingroup SpecialPage |
||
22 | */ |
||
23 | |||
24 | /** |
||
25 | * Special page to allow managing user group membership |
||
26 | * |
||
27 | * @ingroup SpecialPage |
||
28 | */ |
||
29 | class UserrightsPage extends SpecialPage { |
||
30 | /** |
||
31 | * The target of the local right-adjuster's interest. Can be gotten from |
||
32 | * either a GET parameter or a subpage-style parameter, so have a member |
||
33 | * variable for it. |
||
34 | * @var null|string $mTarget |
||
35 | */ |
||
36 | protected $mTarget; |
||
37 | /* |
||
38 | * @var null|User $mFetchedUser The user object of the target username or null. |
||
39 | */ |
||
40 | protected $mFetchedUser = null; |
||
41 | protected $isself = false; |
||
42 | |||
43 | public function __construct() { |
||
44 | parent::__construct( 'Userrights' ); |
||
45 | } |
||
46 | |||
47 | public function doesWrites() { |
||
48 | return true; |
||
49 | } |
||
50 | |||
51 | public function isRestricted() { |
||
52 | return true; |
||
53 | } |
||
54 | |||
55 | public function userCanExecute( User $user ) { |
||
56 | return $this->userCanChangeRights( $user, false ); |
||
57 | } |
||
58 | |||
59 | /** |
||
60 | * @param User $user |
||
61 | * @param bool $checkIfSelf |
||
62 | * @return bool |
||
63 | */ |
||
64 | public function userCanChangeRights( $user, $checkIfSelf = true ) { |
||
65 | $available = $this->changeableGroups(); |
||
66 | if ( $user->getId() == 0 ) { |
||
67 | return false; |
||
68 | } |
||
69 | |||
70 | return !empty( $available['add'] ) |
||
71 | || !empty( $available['remove'] ) |
||
72 | || ( ( $this->isself || !$checkIfSelf ) && |
||
73 | ( !empty( $available['add-self'] ) |
||
74 | || !empty( $available['remove-self'] ) ) ); |
||
75 | } |
||
76 | |||
77 | /** |
||
78 | * Manage forms to be shown according to posted data. |
||
79 | * Depending on the submit button used, call a form or a save function. |
||
80 | * |
||
81 | * @param string|null $par String if any subpage provided, else null |
||
82 | * @throws UserBlockedError|PermissionsError |
||
83 | */ |
||
84 | public function execute( $par ) { |
||
85 | // If the visitor doesn't have permissions to assign or remove |
||
86 | // any groups, it's a bit silly to give them the user search prompt. |
||
87 | |||
88 | $user = $this->getUser(); |
||
89 | $request = $this->getRequest(); |
||
90 | $out = $this->getOutput(); |
||
91 | |||
92 | /* |
||
93 | * If the user is blocked and they only have "partial" access |
||
94 | * (e.g. they don't have the userrights permission), then don't |
||
95 | * allow them to use Special:UserRights. |
||
96 | */ |
||
97 | if ( $user->isBlocked() && !$user->isAllowed( 'userrights' ) ) { |
||
98 | throw new UserBlockedError( $user->getBlock() ); |
||
0 ignored issues
–
show
|
|||
99 | } |
||
100 | |||
101 | View Code Duplication | if ( $par !== null ) { |
|
102 | $this->mTarget = $par; |
||
103 | } else { |
||
104 | $this->mTarget = $request->getVal( 'user' ); |
||
105 | } |
||
106 | |||
107 | if ( is_string( $this->mTarget ) ) { |
||
108 | $this->mTarget = trim( $this->mTarget ); |
||
109 | } |
||
110 | |||
111 | $available = $this->changeableGroups(); |
||
112 | |||
113 | if ( $this->mTarget === null ) { |
||
114 | /* |
||
115 | * If the user specified no target, and they can only |
||
116 | * edit their own groups, automatically set them as the |
||
117 | * target. |
||
118 | */ |
||
119 | if ( !count( $available['add'] ) && !count( $available['remove'] ) ) { |
||
120 | $this->mTarget = $user->getName(); |
||
121 | } |
||
122 | } |
||
123 | |||
124 | if ( $this->mTarget !== null && User::getCanonicalName( $this->mTarget ) === $user->getName() ) { |
||
125 | $this->isself = true; |
||
126 | } |
||
127 | |||
128 | $fetchedStatus = $this->fetchUser( $this->mTarget ); |
||
129 | if ( $fetchedStatus->isOK() ) { |
||
130 | $this->mFetchedUser = $fetchedStatus->value; |
||
131 | if ( $this->mFetchedUser instanceof User ) { |
||
132 | // Set the 'relevant user' in the skin, so it displays links like Contributions, |
||
133 | // User logs, UserRights, etc. |
||
134 | $this->getSkin()->setRelevantUser( $this->mFetchedUser ); |
||
135 | } |
||
136 | } |
||
137 | |||
138 | if ( !$this->userCanChangeRights( $user, true ) ) { |
||
139 | if ( $this->isself && $request->getCheck( 'success' ) ) { |
||
140 | // bug 48609: if the user just removed its own rights, this would |
||
141 | // leads it in a "permissions error" page. In that case, show a |
||
142 | // message that it can't anymore use this page instead of an error |
||
143 | $this->setHeaders(); |
||
144 | $out->wrapWikiMsg( "<div class=\"successbox\">\n$1\n</div>", 'userrights-removed-self' ); |
||
145 | $out->returnToMain(); |
||
146 | |||
147 | return; |
||
148 | } |
||
149 | |||
150 | // @todo FIXME: There may be intermediate groups we can mention. |
||
151 | $msg = $user->isAnon() ? 'userrights-nologin' : 'userrights-notallowed'; |
||
152 | throw new PermissionsError( null, [ [ $msg ] ] ); |
||
153 | } |
||
154 | |||
155 | // show a successbox, if the user rights was saved successfully |
||
156 | if ( $request->getCheck( 'success' ) && $this->mFetchedUser !== null ) { |
||
157 | $out->addModules( [ 'mediawiki.special.userrights' ] ); |
||
158 | $out->addModuleStyles( 'mediawiki.notification.convertmessagebox.styles' ); |
||
159 | $out->addHTML( |
||
160 | Html::rawElement( |
||
161 | 'div', |
||
162 | [ |
||
163 | 'class' => 'mw-notify-success successbox', |
||
164 | 'id' => 'mw-preferences-success', |
||
165 | 'data-mw-autohide' => 'false', |
||
166 | ], |
||
167 | Html::element( |
||
168 | 'p', |
||
169 | [], |
||
170 | $this->msg( 'savedrights', $this->mFetchedUser->getName() )->text() |
||
171 | ) |
||
172 | ) |
||
173 | ); |
||
174 | } |
||
175 | |||
176 | $this->checkReadOnly(); |
||
177 | |||
178 | $this->setHeaders(); |
||
179 | $this->outputHeader(); |
||
180 | |||
181 | $out->addModuleStyles( 'mediawiki.special' ); |
||
182 | $this->addHelpLink( 'Help:Assigning permissions' ); |
||
183 | |||
184 | // show the general form |
||
185 | if ( count( $available['add'] ) || count( $available['remove'] ) ) { |
||
186 | $this->switchForm(); |
||
187 | } |
||
188 | |||
189 | if ( |
||
190 | $request->wasPosted() && |
||
191 | $request->getCheck( 'saveusergroups' ) && |
||
192 | $this->mTarget !== null && |
||
193 | $user->matchEditToken( $request->getVal( 'wpEditToken' ), $this->mTarget ) |
||
194 | ) { |
||
195 | // save settings |
||
196 | if ( !$fetchedStatus->isOK() ) { |
||
197 | $this->getOutput()->addWikiText( $fetchedStatus->getWikiText() ); |
||
198 | |||
199 | return; |
||
200 | } |
||
201 | |||
202 | $targetUser = $this->mFetchedUser; |
||
203 | if ( $targetUser instanceof User ) { // UserRightsProxy doesn't have this method (bug 61252) |
||
204 | $targetUser->clearInstanceCache(); // bug 38989 |
||
205 | } |
||
206 | |||
207 | if ( $request->getVal( 'conflictcheck-originalgroups' ) |
||
208 | !== implode( ',', $targetUser->getGroups() ) |
||
209 | ) { |
||
210 | $out->addWikiMsg( 'userrights-conflict' ); |
||
211 | } else { |
||
212 | $this->saveUserGroups( |
||
213 | $this->mTarget, |
||
214 | $request->getVal( 'user-reason' ), |
||
215 | $targetUser |
||
216 | ); |
||
217 | |||
218 | $out->redirect( $this->getSuccessURL() ); |
||
219 | |||
220 | return; |
||
221 | } |
||
222 | } |
||
223 | |||
224 | // show some more forms |
||
225 | if ( $this->mTarget !== null ) { |
||
226 | $this->editUserGroupsForm( $this->mTarget ); |
||
227 | } |
||
228 | } |
||
229 | |||
230 | function getSuccessURL() { |
||
231 | return $this->getPageTitle( $this->mTarget )->getFullURL( [ 'success' => 1 ] ); |
||
232 | } |
||
233 | |||
234 | /** |
||
235 | * Save user groups changes in the database. |
||
236 | * Data comes from the editUserGroupsForm() form function |
||
237 | * |
||
238 | * @param string $username Username to apply changes to. |
||
239 | * @param string $reason Reason for group change |
||
240 | * @param User|UserRightsProxy $user Target user object. |
||
241 | * @return null |
||
242 | */ |
||
243 | function saveUserGroups( $username, $reason, $user ) { |
||
244 | $allgroups = $this->getAllGroups(); |
||
245 | $addgroup = []; |
||
246 | $removegroup = []; |
||
247 | |||
248 | // This could possibly create a highly unlikely race condition if permissions are changed between |
||
249 | // when the form is loaded and when the form is saved. Ignoring it for the moment. |
||
250 | foreach ( $allgroups as $group ) { |
||
251 | // We'll tell it to remove all unchecked groups, and add all checked groups. |
||
252 | // Later on, this gets filtered for what can actually be removed |
||
253 | if ( $this->getRequest()->getCheck( "wpGroup-$group" ) ) { |
||
254 | $addgroup[] = $group; |
||
255 | } else { |
||
256 | $removegroup[] = $group; |
||
257 | } |
||
258 | } |
||
259 | |||
260 | $this->doSaveUserGroups( $user, $addgroup, $removegroup, $reason ); |
||
261 | } |
||
262 | |||
263 | /** |
||
264 | * Save user groups changes in the database. |
||
265 | * |
||
266 | * @param User|UserRightsProxy $user |
||
267 | * @param array $add Array of groups to add |
||
268 | * @param array $remove Array of groups to remove |
||
269 | * @param string $reason Reason for group change |
||
270 | * @return array Tuple of added, then removed groups |
||
271 | */ |
||
272 | function doSaveUserGroups( $user, $add, $remove, $reason = '' ) { |
||
273 | // Validate input set... |
||
274 | $isself = $user->getName() == $this->getUser()->getName(); |
||
275 | $groups = $user->getGroups(); |
||
276 | $changeable = $this->changeableGroups(); |
||
277 | $addable = array_merge( $changeable['add'], $isself ? $changeable['add-self'] : [] ); |
||
278 | $removable = array_merge( $changeable['remove'], $isself ? $changeable['remove-self'] : [] ); |
||
279 | |||
280 | $remove = array_unique( |
||
281 | array_intersect( (array)$remove, $removable, $groups ) ); |
||
282 | $add = array_unique( array_diff( |
||
283 | array_intersect( (array)$add, $addable ), |
||
284 | $groups ) |
||
285 | ); |
||
286 | |||
287 | $oldGroups = $user->getGroups(); |
||
288 | $newGroups = $oldGroups; |
||
289 | |||
290 | // Remove then add groups |
||
291 | View Code Duplication | if ( $remove ) { |
|
292 | foreach ( $remove as $index => $group ) { |
||
293 | if ( !$user->removeGroup( $group ) ) { |
||
294 | unset( $remove[$index] ); |
||
295 | } |
||
296 | } |
||
297 | $newGroups = array_diff( $newGroups, $remove ); |
||
298 | } |
||
299 | View Code Duplication | if ( $add ) { |
|
300 | foreach ( $add as $index => $group ) { |
||
301 | if ( !$user->addGroup( $group ) ) { |
||
302 | unset( $add[$index] ); |
||
303 | } |
||
304 | } |
||
305 | $newGroups = array_merge( $newGroups, $add ); |
||
306 | } |
||
307 | $newGroups = array_unique( $newGroups ); |
||
308 | |||
309 | // Ensure that caches are cleared |
||
310 | $user->invalidateCache(); |
||
311 | |||
312 | // update groups in external authentication database |
||
313 | Hooks::run( 'UserGroupsChanged', [ $user, $add, $remove, $this->getUser(), $reason ] ); |
||
314 | MediaWiki\Auth\AuthManager::callLegacyAuthPlugin( |
||
315 | 'updateExternalDBGroups', [ $user, $add, $remove ] |
||
316 | ); |
||
317 | |||
318 | wfDebug( 'oldGroups: ' . print_r( $oldGroups, true ) . "\n" ); |
||
319 | wfDebug( 'newGroups: ' . print_r( $newGroups, true ) . "\n" ); |
||
320 | // Deprecated in favor of UserGroupsChanged hook |
||
321 | Hooks::run( 'UserRights', [ &$user, $add, $remove ], '1.26' ); |
||
322 | |||
323 | if ( $newGroups != $oldGroups ) { |
||
324 | $this->addLogEntry( $user, $oldGroups, $newGroups, $reason ); |
||
0 ignored issues
–
show
It seems like
$user defined by parameter $user on line 272 can also be of type object<UserRightsProxy> ; however, UserrightsPage::addLogEntry() does only seem to accept object<User> , maybe add an additional type check?
This check looks at variables that have been passed in as parameters and are passed out again to other methods. If the outgoing method call has stricter type requirements than the method itself, an issue is raised. An additional type check may prevent trouble. ![]() |
|||
325 | } |
||
326 | |||
327 | return [ $add, $remove ]; |
||
328 | } |
||
329 | |||
330 | /** |
||
331 | * Add a rights log entry for an action. |
||
332 | * @param User $user |
||
333 | * @param array $oldGroups |
||
334 | * @param array $newGroups |
||
335 | * @param array $reason |
||
336 | */ |
||
337 | function addLogEntry( $user, $oldGroups, $newGroups, $reason ) { |
||
338 | $logEntry = new ManualLogEntry( 'rights', 'rights' ); |
||
339 | $logEntry->setPerformer( $this->getUser() ); |
||
340 | $logEntry->setTarget( $user->getUserPage() ); |
||
341 | $logEntry->setComment( $reason ); |
||
342 | $logEntry->setParameters( [ |
||
343 | '4::oldgroups' => $oldGroups, |
||
344 | '5::newgroups' => $newGroups, |
||
345 | ] ); |
||
346 | $logid = $logEntry->insert(); |
||
347 | $logEntry->publish( $logid ); |
||
348 | } |
||
349 | |||
350 | /** |
||
351 | * Edit user groups membership |
||
352 | * @param string $username Name of the user. |
||
353 | */ |
||
354 | function editUserGroupsForm( $username ) { |
||
355 | $status = $this->fetchUser( $username ); |
||
356 | if ( !$status->isOK() ) { |
||
357 | $this->getOutput()->addWikiText( $status->getWikiText() ); |
||
358 | |||
359 | return; |
||
360 | } else { |
||
361 | $user = $status->value; |
||
362 | } |
||
363 | |||
364 | $groups = $user->getGroups(); |
||
365 | |||
366 | $this->showEditUserGroupsForm( $user, $groups ); |
||
367 | |||
368 | // This isn't really ideal logging behavior, but let's not hide the |
||
369 | // interwiki logs if we're using them as is. |
||
370 | $this->showLogFragment( $user, $this->getOutput() ); |
||
371 | } |
||
372 | |||
373 | /** |
||
374 | * Normalize the input username, which may be local or remote, and |
||
375 | * return a user (or proxy) object for manipulating it. |
||
376 | * |
||
377 | * Side effects: error output for invalid access |
||
378 | * @param string $username |
||
379 | * @return Status |
||
380 | */ |
||
381 | public function fetchUser( $username ) { |
||
382 | $parts = explode( $this->getConfig()->get( 'UserrightsInterwikiDelimiter' ), $username ); |
||
383 | if ( count( $parts ) < 2 ) { |
||
384 | $name = trim( $username ); |
||
385 | $database = ''; |
||
386 | } else { |
||
387 | list( $name, $database ) = array_map( 'trim', $parts ); |
||
388 | |||
389 | if ( $database == wfWikiID() ) { |
||
390 | $database = ''; |
||
391 | } else { |
||
392 | if ( !$this->getUser()->isAllowed( 'userrights-interwiki' ) ) { |
||
393 | return Status::newFatal( 'userrights-no-interwiki' ); |
||
394 | } |
||
395 | if ( !UserRightsProxy::validDatabase( $database ) ) { |
||
396 | return Status::newFatal( 'userrights-nodatabase', $database ); |
||
397 | } |
||
398 | } |
||
399 | } |
||
400 | |||
401 | if ( $name === '' ) { |
||
402 | return Status::newFatal( 'nouserspecified' ); |
||
403 | } |
||
404 | |||
405 | if ( $name[0] == '#' ) { |
||
406 | // Numeric ID can be specified... |
||
407 | // We'll do a lookup for the name internally. |
||
408 | $id = intval( substr( $name, 1 ) ); |
||
409 | |||
410 | if ( $database == '' ) { |
||
411 | $name = User::whoIs( $id ); |
||
412 | } else { |
||
413 | $name = UserRightsProxy::whoIs( $database, $id ); |
||
414 | } |
||
415 | |||
416 | if ( !$name ) { |
||
417 | return Status::newFatal( 'noname' ); |
||
418 | } |
||
419 | } else { |
||
420 | $name = User::getCanonicalName( $name ); |
||
421 | if ( $name === false ) { |
||
422 | // invalid name |
||
423 | return Status::newFatal( 'nosuchusershort', $username ); |
||
424 | } |
||
425 | } |
||
426 | |||
427 | if ( $database == '' ) { |
||
428 | $user = User::newFromName( $name ); |
||
0 ignored issues
–
show
It seems like
$name can also be of type boolean ; however, User::newFromName() does only seem to accept string , maybe add an additional type check?
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check: /**
* @return array|string
*/
function returnsDifferentValues($x) {
if ($x) {
return 'foo';
}
return array();
}
$x = returnsDifferentValues($y);
if (is_array($x)) {
// $x is an array.
}
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue. ![]() |
|||
429 | } else { |
||
430 | $user = UserRightsProxy::newFromName( $database, $name ); |
||
0 ignored issues
–
show
It seems like
$name can also be of type boolean ; however, UserRightsProxy::newFromName() does only seem to accept string , maybe add an additional type check?
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check: /**
* @return array|string
*/
function returnsDifferentValues($x) {
if ($x) {
return 'foo';
}
return array();
}
$x = returnsDifferentValues($y);
if (is_array($x)) {
// $x is an array.
}
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue. ![]() |
|||
431 | } |
||
432 | |||
433 | if ( !$user || $user->isAnon() ) { |
||
434 | return Status::newFatal( 'nosuchusershort', $username ); |
||
435 | } |
||
436 | |||
437 | return Status::newGood( $user ); |
||
438 | } |
||
439 | |||
440 | /** |
||
441 | * @since 1.15 |
||
442 | * |
||
443 | * @param array $ids |
||
444 | * |
||
445 | * @return string |
||
446 | */ |
||
447 | public function makeGroupNameList( $ids ) { |
||
448 | if ( empty( $ids ) ) { |
||
449 | return $this->msg( 'rightsnone' )->inContentLanguage()->text(); |
||
450 | } else { |
||
451 | return implode( ', ', $ids ); |
||
452 | } |
||
453 | } |
||
454 | |||
455 | /** |
||
456 | * Output a form to allow searching for a user |
||
457 | */ |
||
458 | function switchForm() { |
||
459 | $this->getOutput()->addModules( 'mediawiki.userSuggest' ); |
||
460 | |||
461 | $this->getOutput()->addHTML( |
||
462 | Html::openElement( |
||
463 | 'form', |
||
464 | [ |
||
465 | 'method' => 'get', |
||
466 | 'action' => wfScript(), |
||
467 | 'name' => 'uluser', |
||
468 | 'id' => 'mw-userrights-form1' |
||
469 | ] |
||
470 | ) . |
||
471 | Html::hidden( 'title', $this->getPageTitle()->getPrefixedText() ) . |
||
472 | Xml::fieldset( $this->msg( 'userrights-lookup-user' )->text() ) . |
||
473 | Xml::inputLabel( |
||
474 | $this->msg( 'userrights-user-editname' )->text(), |
||
475 | 'user', |
||
476 | 'username', |
||
477 | 30, |
||
478 | str_replace( '_', ' ', $this->mTarget ), |
||
479 | [ |
||
480 | 'class' => 'mw-autocomplete-user', // used by mediawiki.userSuggest |
||
481 | ] + ( |
||
482 | // Set autofocus on blank input and error input |
||
483 | $this->mFetchedUser === null ? [ 'autofocus' => '' ] : [] |
||
484 | ) |
||
485 | ) . ' ' . |
||
486 | Xml::submitButton( |
||
487 | $this->msg( |
||
488 | 'editusergroup', |
||
489 | $this->mFetchedUser === null ? '[]' : $this->mFetchedUser->getName() |
||
490 | )->text() |
||
491 | ) . |
||
492 | Html::closeElement( 'fieldset' ) . |
||
493 | Html::closeElement( 'form' ) . "\n" |
||
494 | ); |
||
495 | } |
||
496 | |||
497 | /** |
||
498 | * Go through used and available groups and return the ones that this |
||
499 | * form will be able to manipulate based on the current user's system |
||
500 | * permissions. |
||
501 | * |
||
502 | * @param array $groups List of groups the given user is in |
||
503 | * @return array Tuple of addable, then removable groups |
||
504 | */ |
||
505 | protected function splitGroups( $groups ) { |
||
506 | list( $addable, $removable, $addself, $removeself ) = array_values( $this->changeableGroups() ); |
||
507 | |||
508 | $removable = array_intersect( |
||
509 | array_merge( $this->isself ? $removeself : [], $removable ), |
||
510 | $groups |
||
511 | ); // Can't remove groups the user doesn't have |
||
512 | $addable = array_diff( |
||
513 | array_merge( $this->isself ? $addself : [], $addable ), |
||
514 | $groups |
||
515 | ); // Can't add groups the user does have |
||
516 | |||
517 | return [ $addable, $removable ]; |
||
518 | } |
||
519 | |||
520 | /** |
||
521 | * Show the form to edit group memberships. |
||
522 | * |
||
523 | * @param User|UserRightsProxy $user User or UserRightsProxy you're editing |
||
524 | * @param array $groups Array of groups the user is in |
||
525 | */ |
||
526 | protected function showEditUserGroupsForm( $user, $groups ) { |
||
527 | $list = []; |
||
528 | $membersList = []; |
||
529 | foreach ( $groups as $group ) { |
||
530 | $list[] = self::buildGroupLink( $group ); |
||
531 | $membersList[] = self::buildGroupMemberLink( $group ); |
||
532 | } |
||
533 | |||
534 | $autoList = []; |
||
535 | $autoMembersList = []; |
||
536 | if ( $user instanceof User ) { |
||
537 | foreach ( Autopromote::getAutopromoteGroups( $user ) as $group ) { |
||
538 | $autoList[] = self::buildGroupLink( $group ); |
||
539 | $autoMembersList[] = self::buildGroupMemberLink( $group ); |
||
540 | } |
||
541 | } |
||
542 | |||
543 | $language = $this->getLanguage(); |
||
544 | $displayedList = $this->msg( 'userrights-groupsmember-type' ) |
||
545 | ->rawParams( |
||
546 | $language->listToText( $list ), |
||
547 | $language->listToText( $membersList ) |
||
548 | )->escaped(); |
||
549 | $displayedAutolist = $this->msg( 'userrights-groupsmember-type' ) |
||
550 | ->rawParams( |
||
551 | $language->listToText( $autoList ), |
||
552 | $language->listToText( $autoMembersList ) |
||
553 | )->escaped(); |
||
554 | |||
555 | $grouplist = ''; |
||
556 | $count = count( $list ); |
||
557 | View Code Duplication | if ( $count > 0 ) { |
|
558 | $grouplist = $this->msg( 'userrights-groupsmember' ) |
||
559 | ->numParams( $count ) |
||
560 | ->params( $user->getName() ) |
||
561 | ->parse(); |
||
562 | $grouplist = '<p>' . $grouplist . ' ' . $displayedList . "</p>\n"; |
||
563 | } |
||
564 | |||
565 | $count = count( $autoList ); |
||
566 | View Code Duplication | if ( $count > 0 ) { |
|
567 | $autogrouplistintro = $this->msg( 'userrights-groupsmember-auto' ) |
||
568 | ->numParams( $count ) |
||
569 | ->params( $user->getName() ) |
||
570 | ->parse(); |
||
571 | $grouplist .= '<p>' . $autogrouplistintro . ' ' . $displayedAutolist . "</p>\n"; |
||
572 | } |
||
573 | |||
574 | $userToolLinks = Linker::userToolLinks( |
||
575 | $user->getId(), |
||
576 | $user->getName(), |
||
577 | false, /* default for redContribsWhenNoEdits */ |
||
578 | Linker::TOOL_LINKS_EMAIL /* Add "send e-mail" link */ |
||
579 | ); |
||
580 | |||
581 | $this->getOutput()->addHTML( |
||
582 | Xml::openElement( |
||
583 | 'form', |
||
584 | [ |
||
585 | 'method' => 'post', |
||
586 | 'action' => $this->getPageTitle()->getLocalURL(), |
||
587 | 'name' => 'editGroup', |
||
588 | 'id' => 'mw-userrights-form2' |
||
589 | ] |
||
590 | ) . |
||
591 | Html::hidden( 'user', $this->mTarget ) . |
||
592 | Html::hidden( 'wpEditToken', $this->getUser()->getEditToken( $this->mTarget ) ) . |
||
593 | Html::hidden( |
||
594 | 'conflictcheck-originalgroups', |
||
595 | implode( ',', $user->getGroups() ) |
||
596 | ) . // Conflict detection |
||
597 | Xml::openElement( 'fieldset' ) . |
||
598 | Xml::element( |
||
599 | 'legend', |
||
600 | [], |
||
601 | $this->msg( 'userrights-editusergroup', $user->getName() )->text() |
||
602 | ) . |
||
603 | $this->msg( 'editinguser' )->params( wfEscapeWikiText( $user->getName() ) ) |
||
604 | ->rawParams( $userToolLinks )->parse() . |
||
605 | $this->msg( 'userrights-groups-help', $user->getName() )->parse() . |
||
606 | $grouplist . |
||
607 | $this->groupCheckboxes( $groups, $user ) . |
||
0 ignored issues
–
show
It seems like
$user defined by parameter $user on line 526 can also be of type object<UserRightsProxy> ; however, UserrightsPage::groupCheckboxes() does only seem to accept object<User> , maybe add an additional type check?
This check looks at variables that have been passed in as parameters and are passed out again to other methods. If the outgoing method call has stricter type requirements than the method itself, an issue is raised. An additional type check may prevent trouble. ![]() |
|||
608 | Xml::openElement( 'table', [ 'id' => 'mw-userrights-table-outer' ] ) . |
||
609 | "<tr> |
||
610 | <td class='mw-label'>" . |
||
611 | Xml::label( $this->msg( 'userrights-reason' )->text(), 'wpReason' ) . |
||
612 | "</td> |
||
613 | <td class='mw-input'>" . |
||
614 | Xml::input( 'user-reason', 60, $this->getRequest()->getVal( 'user-reason', false ), |
||
615 | [ 'id' => 'wpReason', 'maxlength' => 255 ] ) . |
||
616 | "</td> |
||
617 | </tr> |
||
618 | <tr> |
||
619 | <td></td> |
||
620 | <td class='mw-submit'>" . |
||
621 | Xml::submitButton( $this->msg( 'saveusergroups', $user->getName() )->text(), |
||
622 | [ 'name' => 'saveusergroups' ] + |
||
623 | Linker::tooltipAndAccesskeyAttribs( 'userrights-set' ) |
||
624 | ) . |
||
625 | "</td> |
||
626 | </tr>" . |
||
627 | Xml::closeElement( 'table' ) . "\n" . |
||
628 | Xml::closeElement( 'fieldset' ) . |
||
629 | Xml::closeElement( 'form' ) . "\n" |
||
630 | ); |
||
631 | } |
||
632 | |||
633 | /** |
||
634 | * Format a link to a group description page |
||
635 | * |
||
636 | * @param string $group |
||
637 | * @return string |
||
638 | */ |
||
639 | private static function buildGroupLink( $group ) { |
||
640 | return User::makeGroupLinkHTML( $group, User::getGroupName( $group ) ); |
||
641 | } |
||
642 | |||
643 | /** |
||
644 | * Format a link to a group member description page |
||
645 | * |
||
646 | * @param string $group |
||
647 | * @return string |
||
648 | */ |
||
649 | private static function buildGroupMemberLink( $group ) { |
||
650 | return User::makeGroupLinkHTML( $group, User::getGroupMember( $group ) ); |
||
651 | } |
||
652 | |||
653 | /** |
||
654 | * Returns an array of all groups that may be edited |
||
655 | * @return array Array of groups that may be edited. |
||
656 | */ |
||
657 | protected static function getAllGroups() { |
||
658 | return User::getAllGroups(); |
||
659 | } |
||
660 | |||
661 | /** |
||
662 | * Adds a table with checkboxes where you can select what groups to add/remove |
||
663 | * |
||
664 | * @todo Just pass the username string? |
||
665 | * @param array $usergroups Groups the user belongs to |
||
666 | * @param User $user |
||
667 | * @return string XHTML table element with checkboxes |
||
668 | */ |
||
669 | private function groupCheckboxes( $usergroups, $user ) { |
||
670 | $allgroups = $this->getAllGroups(); |
||
671 | $ret = ''; |
||
672 | |||
673 | // Put all column info into an associative array so that extensions can |
||
674 | // more easily manage it. |
||
675 | $columns = [ 'unchangeable' => [], 'changeable' => [] ]; |
||
676 | |||
677 | foreach ( $allgroups as $group ) { |
||
678 | $set = in_array( $group, $usergroups ); |
||
679 | // Should the checkbox be disabled? |
||
680 | $disabled = !( |
||
681 | ( $set && $this->canRemove( $group ) ) || |
||
682 | ( !$set && $this->canAdd( $group ) ) ); |
||
683 | // Do we need to point out that this action is irreversible? |
||
684 | $irreversible = !$disabled && ( |
||
685 | ( $set && !$this->canAdd( $group ) ) || |
||
686 | ( !$set && !$this->canRemove( $group ) ) ); |
||
687 | |||
688 | $checkbox = [ |
||
689 | 'set' => $set, |
||
690 | 'disabled' => $disabled, |
||
691 | 'irreversible' => $irreversible |
||
692 | ]; |
||
693 | |||
694 | if ( $disabled ) { |
||
695 | $columns['unchangeable'][$group] = $checkbox; |
||
696 | } else { |
||
697 | $columns['changeable'][$group] = $checkbox; |
||
698 | } |
||
699 | } |
||
700 | |||
701 | // Build the HTML table |
||
702 | $ret .= Xml::openElement( 'table', [ 'class' => 'mw-userrights-groups' ] ) . |
||
703 | "<tr>\n"; |
||
704 | foreach ( $columns as $name => $column ) { |
||
705 | if ( $column === [] ) { |
||
706 | continue; |
||
707 | } |
||
708 | // Messages: userrights-changeable-col, userrights-unchangeable-col |
||
709 | $ret .= Xml::element( |
||
710 | 'th', |
||
711 | null, |
||
712 | $this->msg( 'userrights-' . $name . '-col', count( $column ) )->text() |
||
713 | ); |
||
714 | } |
||
715 | |||
716 | $ret .= "</tr>\n<tr>\n"; |
||
717 | foreach ( $columns as $column ) { |
||
718 | if ( $column === [] ) { |
||
719 | continue; |
||
720 | } |
||
721 | $ret .= "\t<td style='vertical-align:top;'>\n"; |
||
722 | foreach ( $column as $group => $checkbox ) { |
||
723 | $attr = $checkbox['disabled'] ? [ 'disabled' => 'disabled' ] : []; |
||
724 | |||
725 | $member = User::getGroupMember( $group, $user->getName() ); |
||
726 | if ( $checkbox['irreversible'] ) { |
||
727 | $text = $this->msg( 'userrights-irreversible-marker', $member )->text(); |
||
728 | } else { |
||
729 | $text = $member; |
||
730 | } |
||
731 | $checkboxHtml = Xml::checkLabel( $text, "wpGroup-" . $group, |
||
732 | "wpGroup-" . $group, $checkbox['set'], $attr ); |
||
733 | $ret .= "\t\t" . ( $checkbox['disabled'] |
||
734 | ? Xml::tags( 'span', [ 'class' => 'mw-userrights-disabled' ], $checkboxHtml ) |
||
735 | : $checkboxHtml |
||
736 | ) . "<br />\n"; |
||
737 | } |
||
738 | $ret .= "\t</td>\n"; |
||
739 | } |
||
740 | $ret .= Xml::closeElement( 'tr' ) . Xml::closeElement( 'table' ); |
||
741 | |||
742 | return $ret; |
||
743 | } |
||
744 | |||
745 | /** |
||
746 | * @param string $group The name of the group to check |
||
747 | * @return bool Can we remove the group? |
||
748 | */ |
||
749 | View Code Duplication | private function canRemove( $group ) { |
|
750 | // $this->changeableGroups()['remove'] doesn't work, of course. Thanks, PHP. |
||
751 | $groups = $this->changeableGroups(); |
||
752 | |||
753 | return in_array( |
||
754 | $group, |
||
755 | $groups['remove'] ) || ( $this->isself && in_array( $group, $groups['remove-self'] ) |
||
756 | ); |
||
757 | } |
||
758 | |||
759 | /** |
||
760 | * @param string $group The name of the group to check |
||
761 | * @return bool Can we add the group? |
||
762 | */ |
||
763 | View Code Duplication | private function canAdd( $group ) { |
|
764 | $groups = $this->changeableGroups(); |
||
765 | |||
766 | return in_array( |
||
767 | $group, |
||
768 | $groups['add'] ) || ( $this->isself && in_array( $group, $groups['add-self'] ) |
||
769 | ); |
||
770 | } |
||
771 | |||
772 | /** |
||
773 | * Returns $this->getUser()->changeableGroups() |
||
774 | * |
||
775 | * @return array Array( |
||
776 | * 'add' => array( addablegroups ), |
||
777 | * 'remove' => array( removablegroups ), |
||
778 | * 'add-self' => array( addablegroups to self ), |
||
779 | * 'remove-self' => array( removable groups from self ) |
||
780 | * ) |
||
781 | */ |
||
782 | function changeableGroups() { |
||
783 | return $this->getUser()->changeableGroups(); |
||
784 | } |
||
785 | |||
786 | /** |
||
787 | * Show a rights log fragment for the specified user |
||
788 | * |
||
789 | * @param User $user User to show log for |
||
790 | * @param OutputPage $output OutputPage to use |
||
791 | */ |
||
792 | protected function showLogFragment( $user, $output ) { |
||
793 | $rightsLogPage = new LogPage( 'rights' ); |
||
794 | $output->addHTML( Xml::element( 'h2', null, $rightsLogPage->getName()->text() ) ); |
||
795 | LogEventsList::showLogExtract( $output, 'rights', $user->getUserPage() ); |
||
796 | } |
||
797 | |||
798 | /** |
||
799 | * Return an array of subpages beginning with $search that this special page will accept. |
||
800 | * |
||
801 | * @param string $search Prefix to search for |
||
802 | * @param int $limit Maximum number of results to return (usually 10) |
||
803 | * @param int $offset Number of results to skip (usually 0) |
||
804 | * @return string[] Matching subpages |
||
805 | */ |
||
806 | View Code Duplication | public function prefixSearchSubpages( $search, $limit, $offset ) { |
|
807 | $user = User::newFromName( $search ); |
||
808 | if ( !$user ) { |
||
809 | // No prefix suggestion for invalid user |
||
810 | return []; |
||
811 | } |
||
812 | // Autocomplete subpage as user list - public to allow caching |
||
813 | return UserNamePrefixSearch::search( 'public', $search, $limit, $offset ); |
||
814 | } |
||
815 | |||
816 | protected function getGroupName() { |
||
817 | return 'users'; |
||
818 | } |
||
819 | } |
||
820 |
Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code: