These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /****************************************************************************** |
||
3 | * Wikipedia Account Creation Assistance tool * |
||
4 | * * |
||
5 | * All code in this file is released into the public domain by the ACC * |
||
6 | * Development Team. Please see team.json for a list of contributors. * |
||
7 | ******************************************************************************/ |
||
8 | |||
9 | namespace Waca\Pages; |
||
10 | |||
11 | use Waca\DataObjects\User; |
||
12 | use Waca\DataObjects\UserRole; |
||
13 | use Waca\Exceptions\ApplicationLogicException; |
||
14 | use Waca\Helpers\Logger; |
||
15 | use Waca\Helpers\SearchHelpers\UserSearchHelper; |
||
16 | use Waca\SessionAlert; |
||
17 | use Waca\Tasks\InternalPageBase; |
||
18 | use Waca\WebRequest; |
||
19 | |||
20 | /** |
||
21 | * Class PageUserManagement |
||
22 | * @package Waca\Pages |
||
23 | */ |
||
24 | class PageUserManagement extends InternalPageBase |
||
25 | { |
||
26 | /** @var string */ |
||
27 | private $adminMailingList = '[email protected]'; |
||
28 | |||
29 | /** |
||
30 | * Main function for this page, when no specific actions are called. |
||
31 | */ |
||
32 | protected function main() |
||
33 | { |
||
34 | $this->setHtmlTitle('User Management'); |
||
35 | |||
36 | $database = $this->getDatabase(); |
||
37 | $currentUser = User::getCurrent($database); |
||
38 | |||
39 | if (WebRequest::getBoolean("showAll")) { |
||
40 | $this->assign("showAll", true); |
||
41 | |||
42 | $this->assign("suspendedUsers", |
||
43 | UserSearchHelper::get($database)->byStatus(User::STATUS_SUSPENDED)->fetch()); |
||
44 | $this->assign("declinedUsers", UserSearchHelper::get($database)->byStatus(User::STATUS_DECLINED)->fetch()); |
||
45 | |||
46 | UserSearchHelper::get($database)->getRoleMap($roleMap); |
||
47 | } |
||
48 | else { |
||
49 | $this->assign("showAll", false); |
||
50 | $this->assign("suspendedUsers", array()); |
||
51 | $this->assign("declinedUsers", array()); |
||
52 | |||
53 | UserSearchHelper::get($database)->statusIn(array('New', 'Active'))->getRoleMap($roleMap); |
||
54 | } |
||
55 | |||
56 | $this->assign('newUsers', UserSearchHelper::get($database)->byStatus(User::STATUS_NEW)->fetch()); |
||
57 | $this->assign('normalUsers', |
||
58 | UserSearchHelper::get($database)->byStatus(User::STATUS_ACTIVE)->byRole('user')->fetch()); |
||
59 | $this->assign('adminUsers', |
||
60 | UserSearchHelper::get($database)->byStatus(User::STATUS_ACTIVE)->byRole('admin')->fetch()); |
||
61 | $this->assign('checkUsers', |
||
62 | UserSearchHelper::get($database)->byStatus(User::STATUS_ACTIVE)->byRole('checkuser')->fetch()); |
||
63 | $this->assign('toolRoots', |
||
64 | UserSearchHelper::get($database)->byStatus(User::STATUS_ACTIVE)->byRole('toolRoot')->fetch()); |
||
65 | |||
66 | $this->assign('roles', $roleMap); |
||
67 | |||
68 | $this->getTypeAheadHelper()->defineTypeAheadSource('username-typeahead', function() use ($database) { |
||
69 | return UserSearchHelper::get($database)->fetchColumn('username'); |
||
70 | }); |
||
71 | |||
72 | $this->assign('canApprove', $this->barrierTest('approve', $currentUser)); |
||
73 | $this->assign('canDecline', $this->barrierTest('decline', $currentUser)); |
||
74 | $this->assign('canRename', $this->barrierTest('rename', $currentUser)); |
||
75 | $this->assign('canEditUser', $this->barrierTest('editUser', $currentUser)); |
||
76 | $this->assign('canSuspend', $this->barrierTest('suspend', $currentUser)); |
||
77 | $this->assign('canEditRoles', $this->barrierTest('editRoles', $currentUser)); |
||
78 | |||
79 | $this->setTemplate("usermanagement/main.tpl"); |
||
80 | } |
||
81 | |||
82 | #region Access control |
||
83 | |||
84 | /** |
||
85 | * Action target for editing the roles assigned to a user |
||
86 | */ |
||
87 | protected function editRoles() |
||
88 | { |
||
89 | $this->setHtmlTitle('User Management'); |
||
90 | $database = $this->getDatabase(); |
||
91 | $userId = WebRequest::getInt('user'); |
||
92 | |||
93 | /** @var User $user */ |
||
94 | $user = User::getById($userId, $database); |
||
95 | |||
96 | if ($user === false) { |
||
97 | throw new ApplicationLogicException('Sorry, the user you are trying to edit could not be found.'); |
||
98 | } |
||
99 | |||
100 | $roleData = $this->getRoleData(UserRole::getForUser($user->getId(), $database)); |
||
101 | |||
102 | // Dual-mode action |
||
103 | if (WebRequest::wasPosted()) { |
||
104 | $this->validateCSRFToken(); |
||
105 | |||
106 | $reason = WebRequest::postString('reason'); |
||
107 | if ($reason === false || trim($reason) === '') { |
||
108 | throw new ApplicationLogicException('No reason specified for roles change'); |
||
109 | } |
||
110 | |||
111 | /** @var UserRole[] $delete */ |
||
112 | $delete = array(); |
||
113 | /** @var string[] $delete */ |
||
114 | $add = array(); |
||
115 | |||
116 | foreach ($roleData as $name => $r) { |
||
117 | if ($r['allowEdit'] !== 1) { |
||
118 | // not allowed, to touch this, so ignore it |
||
119 | continue; |
||
120 | } |
||
121 | |||
122 | $newValue = WebRequest::postBoolean('role-' . $name) ? 1 : 0; |
||
123 | if ($newValue !== $r['active']) { |
||
124 | if ($newValue === 0) { |
||
125 | $delete[] = $r['object']; |
||
126 | } |
||
127 | |||
128 | if ($newValue === 1) { |
||
129 | $add[] = $name; |
||
130 | } |
||
131 | } |
||
132 | } |
||
133 | |||
134 | // Check there's something to do |
||
135 | if ((count($add) + count($delete)) === 0) { |
||
136 | $this->redirect('statistics/users', 'detail', array('user' => $user->getId())); |
||
137 | SessionAlert::warning('No changes made to roles.'); |
||
138 | |||
139 | return; |
||
140 | } |
||
141 | |||
142 | $removed = array(); |
||
143 | |||
144 | /** @var UserRole $d */ |
||
145 | foreach ($delete as $d) { |
||
146 | $removed[] = $d->getRole(); |
||
0 ignored issues
–
show
|
|||
147 | $d->delete(); |
||
0 ignored issues
–
show
|
|||
148 | } |
||
149 | |||
150 | View Code Duplication | foreach ($add as $x) { |
|
151 | $a = new UserRole(); |
||
152 | $a->setUser($user->getId()); |
||
153 | $a->setRole($x); |
||
154 | $a->setDatabase($database); |
||
155 | $a->save(); |
||
156 | } |
||
157 | |||
158 | Logger::userRolesEdited($database, $user, $reason, $add, $removed); |
||
159 | |||
160 | // dummy save for optimistic locking. If this fails, the entire txn will roll back. |
||
161 | $user->setUpdateVersion(WebRequest::postInt('updateversion')); |
||
162 | $user->save(); |
||
163 | |||
164 | $this->getNotificationHelper()->userRolesEdited($user, $reason); |
||
165 | SessionAlert::quick('Roles changed for user ' . htmlentities($user->getUsername(), ENT_COMPAT, 'UTF-8')); |
||
166 | |||
167 | $this->redirect('statistics/users', 'detail', array('user' => $user->getId())); |
||
168 | return; |
||
169 | } |
||
170 | else { |
||
171 | $this->assignCSRFToken(); |
||
172 | $this->setTemplate('usermanagement/roleedit.tpl'); |
||
173 | $this->assign('user', $user); |
||
174 | $this->assign('roleData', $roleData); |
||
175 | } |
||
176 | } |
||
177 | |||
178 | /** |
||
179 | * Action target for suspending users |
||
180 | * |
||
181 | * @throws ApplicationLogicException |
||
182 | */ |
||
183 | View Code Duplication | protected function suspend() |
|
184 | { |
||
185 | $this->setHtmlTitle('User Management'); |
||
186 | |||
187 | $database = $this->getDatabase(); |
||
188 | |||
189 | $userId = WebRequest::getInt('user'); |
||
190 | |||
191 | /** @var User $user */ |
||
192 | $user = User::getById($userId, $database); |
||
193 | |||
194 | if ($user === false) { |
||
195 | throw new ApplicationLogicException('Sorry, the user you are trying to suspend could not be found.'); |
||
196 | } |
||
197 | |||
198 | if ($user->isSuspended()) { |
||
199 | throw new ApplicationLogicException('Sorry, the user you are trying to suspend is already suspended.'); |
||
200 | } |
||
201 | |||
202 | // Dual-mode action |
||
203 | if (WebRequest::wasPosted()) { |
||
204 | $this->validateCSRFToken(); |
||
205 | $reason = WebRequest::postString('reason'); |
||
206 | |||
207 | if ($reason === null || trim($reason) === "") { |
||
208 | throw new ApplicationLogicException('No reason provided'); |
||
209 | } |
||
210 | |||
211 | $user->setStatus(User::STATUS_SUSPENDED); |
||
212 | $user->setUpdateVersion(WebRequest::postInt('updateversion')); |
||
213 | $user->save(); |
||
214 | Logger::suspendedUser($database, $user, $reason); |
||
215 | |||
216 | $this->getNotificationHelper()->userSuspended($user, $reason); |
||
217 | SessionAlert::quick('Suspended user ' . htmlentities($user->getUsername(), ENT_COMPAT, 'UTF-8')); |
||
218 | |||
219 | // send email |
||
220 | $this->sendStatusChangeEmail( |
||
221 | 'Your WP:ACC account has been suspended', |
||
222 | 'usermanagement/emails/suspended.tpl', |
||
223 | $reason, |
||
224 | $user, |
||
225 | User::getCurrent($database)->getUsername() |
||
226 | ); |
||
227 | |||
228 | $this->redirect('userManagement'); |
||
229 | |||
230 | return; |
||
231 | } |
||
232 | else { |
||
233 | $this->assignCSRFToken(); |
||
234 | $this->setTemplate('usermanagement/changelevel-reason.tpl'); |
||
235 | $this->assign('user', $user); |
||
236 | $this->assign('status', 'Suspended'); |
||
237 | $this->assign("showReason", true); |
||
238 | } |
||
239 | } |
||
240 | |||
241 | /** |
||
242 | * Entry point for the decline action |
||
243 | * |
||
244 | * @throws ApplicationLogicException |
||
245 | */ |
||
246 | View Code Duplication | protected function decline() |
|
247 | { |
||
248 | $this->setHtmlTitle('User Management'); |
||
249 | |||
250 | $database = $this->getDatabase(); |
||
251 | |||
252 | $userId = WebRequest::getInt('user'); |
||
253 | $user = User::getById($userId, $database); |
||
254 | |||
255 | if ($user === false) { |
||
256 | throw new ApplicationLogicException('Sorry, the user you are trying to decline could not be found.'); |
||
257 | } |
||
258 | |||
259 | if (!$user->isNewUser()) { |
||
260 | throw new ApplicationLogicException('Sorry, the user you are trying to decline is not new.'); |
||
261 | } |
||
262 | |||
263 | // Dual-mode action |
||
264 | if (WebRequest::wasPosted()) { |
||
265 | $this->validateCSRFToken(); |
||
266 | $reason = WebRequest::postString('reason'); |
||
267 | |||
268 | if ($reason === null || trim($reason) === "") { |
||
269 | throw new ApplicationLogicException('No reason provided'); |
||
270 | } |
||
271 | |||
272 | $user->setStatus(User::STATUS_DECLINED); |
||
273 | $user->setUpdateVersion(WebRequest::postInt('updateversion')); |
||
274 | $user->save(); |
||
275 | Logger::declinedUser($database, $user, $reason); |
||
276 | |||
277 | $this->getNotificationHelper()->userDeclined($user, $reason); |
||
278 | SessionAlert::quick('Declined user ' . htmlentities($user->getUsername(), ENT_COMPAT, 'UTF-8')); |
||
279 | |||
280 | // send email |
||
281 | $this->sendStatusChangeEmail( |
||
282 | 'Your WP:ACC account has been declined', |
||
283 | 'usermanagement/emails/declined.tpl', |
||
284 | $reason, |
||
285 | $user, |
||
286 | User::getCurrent($database)->getUsername() |
||
287 | ); |
||
288 | |||
289 | $this->redirect('userManagement'); |
||
290 | |||
291 | return; |
||
292 | } |
||
293 | else { |
||
294 | $this->assignCSRFToken(); |
||
295 | $this->setTemplate('usermanagement/changelevel-reason.tpl'); |
||
296 | $this->assign('user', $user); |
||
297 | $this->assign('status', 'Declined'); |
||
298 | $this->assign("showReason", true); |
||
299 | } |
||
300 | } |
||
301 | |||
302 | /** |
||
303 | * Entry point for the approve action |
||
304 | * |
||
305 | * @throws ApplicationLogicException |
||
306 | */ |
||
307 | protected function approve() |
||
308 | { |
||
309 | $this->setHtmlTitle('User Management'); |
||
310 | |||
311 | $database = $this->getDatabase(); |
||
312 | |||
313 | $userId = WebRequest::getInt('user'); |
||
314 | $user = User::getById($userId, $database); |
||
315 | |||
316 | if ($user === false) { |
||
317 | throw new ApplicationLogicException('Sorry, the user you are trying to approve could not be found.'); |
||
318 | } |
||
319 | |||
320 | if ($user->isActive()) { |
||
321 | throw new ApplicationLogicException('Sorry, the user you are trying to approve is already an active user.'); |
||
322 | } |
||
323 | |||
324 | // Dual-mode action |
||
325 | if (WebRequest::wasPosted()) { |
||
326 | $this->validateCSRFToken(); |
||
327 | $user->setStatus(User::STATUS_ACTIVE); |
||
328 | $user->setUpdateVersion(WebRequest::postInt('updateversion')); |
||
329 | $user->save(); |
||
330 | Logger::approvedUser($database, $user); |
||
331 | |||
332 | $this->getNotificationHelper()->userApproved($user); |
||
333 | SessionAlert::quick('Approved user ' . htmlentities($user->getUsername(), ENT_COMPAT, 'UTF-8')); |
||
334 | |||
335 | // send email |
||
336 | $this->sendStatusChangeEmail( |
||
337 | 'Your WP:ACC account has been approved', |
||
338 | 'usermanagement/emails/approved.tpl', |
||
339 | null, |
||
340 | $user, |
||
341 | User::getCurrent($database)->getUsername() |
||
342 | ); |
||
343 | |||
344 | $this->redirect("userManagement"); |
||
345 | |||
346 | return; |
||
347 | } |
||
348 | else { |
||
349 | $this->assignCSRFToken(); |
||
350 | $this->setTemplate("usermanagement/changelevel-reason.tpl"); |
||
351 | $this->assign("user", $user); |
||
352 | $this->assign("status", "User"); |
||
353 | $this->assign("showReason", false); |
||
354 | } |
||
355 | } |
||
356 | |||
357 | #endregion |
||
358 | |||
359 | #region Renaming / Editing |
||
360 | |||
361 | /** |
||
362 | * Entry point for the rename action |
||
363 | * |
||
364 | * @throws ApplicationLogicException |
||
365 | */ |
||
366 | protected function rename() |
||
367 | { |
||
368 | $this->setHtmlTitle('User Management'); |
||
369 | |||
370 | $database = $this->getDatabase(); |
||
371 | |||
372 | $userId = WebRequest::getInt('user'); |
||
373 | $user = User::getById($userId, $database); |
||
374 | |||
375 | if ($user === false) { |
||
376 | throw new ApplicationLogicException('Sorry, the user you are trying to rename could not be found.'); |
||
377 | } |
||
378 | |||
379 | // Dual-mode action |
||
380 | if (WebRequest::wasPosted()) { |
||
381 | $this->validateCSRFToken(); |
||
382 | $newUsername = WebRequest::postString('newname'); |
||
383 | |||
384 | if ($newUsername === null || trim($newUsername) === "") { |
||
385 | throw new ApplicationLogicException('The new username cannot be empty'); |
||
386 | } |
||
387 | |||
388 | if (User::getByUsername($newUsername, $database) != false) { |
||
389 | throw new ApplicationLogicException('The new username already exists'); |
||
390 | } |
||
391 | |||
392 | $oldUsername = $user->getUsername(); |
||
393 | $user->setUsername($newUsername); |
||
394 | $user->setUpdateVersion(WebRequest::postInt('updateversion')); |
||
395 | |||
396 | $user->save(); |
||
397 | |||
398 | $logEntryData = serialize(array( |
||
399 | 'old' => $oldUsername, |
||
400 | 'new' => $newUsername, |
||
401 | )); |
||
402 | |||
403 | Logger::renamedUser($database, $user, $logEntryData); |
||
404 | |||
405 | SessionAlert::quick("Changed User " |
||
406 | . htmlentities($oldUsername, ENT_COMPAT, 'UTF-8') |
||
407 | . " name to " |
||
408 | . htmlentities($newUsername, ENT_COMPAT, 'UTF-8')); |
||
409 | |||
410 | $this->getNotificationHelper()->userRenamed($user, $oldUsername); |
||
411 | |||
412 | // send an email to the user. |
||
413 | $this->assign('targetUsername', $user->getUsername()); |
||
414 | $this->assign('toolAdmin', User::getCurrent($database)->getUsername()); |
||
415 | $this->assign('oldUsername', $oldUsername); |
||
416 | $this->assign('mailingList', $this->adminMailingList); |
||
417 | |||
418 | $this->getEmailHelper()->sendMail( |
||
419 | $user->getEmail(), |
||
420 | 'Your username on WP:ACC has been changed', |
||
421 | $this->fetchTemplate('usermanagement/emails/renamed.tpl'), |
||
422 | array('Reply-To' => $this->adminMailingList) |
||
423 | ); |
||
424 | |||
425 | $this->redirect("userManagement"); |
||
426 | |||
427 | return; |
||
428 | } |
||
429 | else { |
||
430 | $this->assignCSRFToken(); |
||
431 | $this->setTemplate('usermanagement/renameuser.tpl'); |
||
432 | $this->assign('user', $user); |
||
433 | } |
||
434 | } |
||
435 | |||
436 | /** |
||
437 | * Entry point for the edit action |
||
438 | * |
||
439 | * @throws ApplicationLogicException |
||
440 | */ |
||
441 | protected function editUser() |
||
442 | { |
||
443 | $this->setHtmlTitle('User Management'); |
||
444 | |||
445 | $database = $this->getDatabase(); |
||
446 | |||
447 | $userId = WebRequest::getInt('user'); |
||
448 | $user = User::getById($userId, $database); |
||
449 | |||
450 | if ($user === false) { |
||
451 | throw new ApplicationLogicException('Sorry, the user you are trying to edit could not be found.'); |
||
452 | } |
||
453 | |||
454 | // Dual-mode action |
||
455 | if (WebRequest::wasPosted()) { |
||
456 | $this->validateCSRFToken(); |
||
457 | $newEmail = WebRequest::postEmail('user_email'); |
||
458 | $newOnWikiName = WebRequest::postString('user_onwikiname'); |
||
459 | |||
460 | if ($newEmail === null) { |
||
461 | throw new ApplicationLogicException('Invalid email address'); |
||
462 | } |
||
463 | |||
464 | if (!$user->isOAuthLinked()) { |
||
465 | if (trim($newOnWikiName) == "") { |
||
466 | throw new ApplicationLogicException('New on-wiki username cannot be blank'); |
||
467 | } |
||
468 | |||
469 | $user->setOnWikiName($newOnWikiName); |
||
470 | } |
||
471 | |||
472 | $user->setEmail($newEmail); |
||
473 | |||
474 | $user->setUpdateVersion(WebRequest::postInt('updateversion')); |
||
475 | |||
476 | $user->save(); |
||
477 | |||
478 | Logger::userPreferencesChange($database, $user); |
||
479 | $this->getNotificationHelper()->userPrefChange($user); |
||
480 | SessionAlert::quick('Changes to user\'s preferences have been saved'); |
||
481 | |||
482 | $this->redirect("userManagement"); |
||
483 | |||
484 | return; |
||
485 | } |
||
486 | else { |
||
487 | $this->assignCSRFToken(); |
||
488 | $this->setTemplate('usermanagement/edituser.tpl'); |
||
489 | $this->assign('user', $user); |
||
490 | } |
||
491 | } |
||
492 | |||
493 | #endregion |
||
494 | |||
495 | /** |
||
496 | * Sends a status change email to the user. |
||
497 | * |
||
498 | * @param string $subject The subject of the email |
||
499 | * @param string $template The smarty template to use |
||
500 | * @param string|null $reason The reason for performing the status change |
||
501 | * @param User $user The user affected |
||
502 | * @param string $toolAdminUsername The tool admin's username who is making the edit |
||
503 | */ |
||
504 | private function sendStatusChangeEmail($subject, $template, $reason, $user, $toolAdminUsername) |
||
505 | { |
||
506 | $this->assign('targetUsername', $user->getUsername()); |
||
507 | $this->assign('toolAdmin', $toolAdminUsername); |
||
508 | $this->assign('actionReason', $reason); |
||
509 | $this->assign('mailingList', $this->adminMailingList); |
||
510 | |||
511 | $this->getEmailHelper()->sendMail( |
||
512 | $user->getEmail(), |
||
513 | $subject, |
||
514 | $this->fetchTemplate($template), |
||
515 | array('Reply-To' => $this->adminMailingList) |
||
516 | ); |
||
517 | } |
||
518 | |||
519 | /** |
||
520 | * @param UserRole[] $activeRoles |
||
521 | * |
||
522 | * @return array |
||
523 | */ |
||
524 | private function getRoleData($activeRoles) |
||
525 | { |
||
526 | $availableRoles = $this->getSecurityManager()->getRoleConfiguration()->getAvailableRoles(); |
||
527 | |||
528 | $currentUser = User::getCurrent($this->getDatabase()); |
||
529 | $this->getSecurityManager()->getActiveRoles($currentUser, $userRoles, $inactiveRoles); |
||
530 | |||
531 | $initialValue = array('active' => 0, 'allowEdit' => 0, 'description' => '???', 'object' => null); |
||
532 | |||
533 | $roleData = array(); |
||
534 | foreach ($availableRoles as $role => $data) { |
||
535 | $intersection = array_intersect($data['editableBy'], $userRoles); |
||
536 | |||
537 | $roleData[$role] = $initialValue; |
||
538 | $roleData[$role]['allowEdit'] = count($intersection) > 0 ? 1 : 0; |
||
539 | $roleData[$role]['description'] = $data['description']; |
||
540 | } |
||
541 | |||
542 | foreach ($activeRoles as $role) { |
||
543 | if (!isset($roleData[$role->getRole()])) { |
||
544 | // This value is no longer available in the configuration, allow changing (aka removing) it. |
||
545 | $roleData[$role->getRole()] = $initialValue; |
||
546 | $roleData[$role->getRole()]['allowEdit'] = 1; |
||
547 | } |
||
548 | |||
549 | $roleData[$role->getRole()]['object'] = $role; |
||
550 | $roleData[$role->getRole()]['active'] = 1; |
||
551 | } |
||
552 | |||
553 | return $roleData; |
||
554 | } |
||
555 | } |
||
556 |
Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.