Passed
Push — rbac ( 82b65f...a43d05 )
by Michael
02:23
created

PageUserManagement::editRoles()   C

Complexity

Conditions 14
Paths 63

Size

Total Lines 88
Code Lines 49

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 49
c 0
b 0
f 0
dl 0
loc 88
rs 6.2666
cc 14
nc 63
nop 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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();
147
                $d->delete();
148
            }
149
150
            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
    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
    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