Failed Conditions
Push — newinternal ( b66232...216d62 )
by Simon
16:33 queued 06:35
created

PageUserManagement::suspend()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 57

Duplication

Lines 57
Ratio 100 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 0
Metric Value
cc 6
nc 5
nop 0
dl 57
loc 57
ccs 0
cts 42
cp 0
crap 42
rs 8.3158
c 0
b 0
f 0

How to fix   Long Method   

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\OAuthUserHelper;
16
use Waca\Helpers\SearchHelpers\UserSearchHelper;
17
use Waca\SessionAlert;
18
use Waca\Tasks\InternalPageBase;
19
use Waca\WebRequest;
20
21
/**
22
 * Class PageUserManagement
23
 * @package Waca\Pages
24
 */
25
class PageUserManagement extends InternalPageBase
26
{
27
    /** @var string */
28
    private $adminMailingList = '[email protected]';
29
30
    /**
31
     * Main function for this page, when no specific actions are called.
32
     */
33
    protected function main()
34
    {
35
        $this->setHtmlTitle('User Management');
36
37
        $database = $this->getDatabase();
38
        $currentUser = User::getCurrent($database);
39
40
        // A bit hacky, but it's better than my last solution of creating an object for each user and passing that to
41
        // the template. I still don't have a particularly good way of handling this.
42
        OAuthUserHelper::prepareTokenCountStatement($database);
43
44
        if (WebRequest::getBoolean("showAll")) {
45
            $this->assign("showAll", true);
46
47
            $suspendedUsers = UserSearchHelper::get($database)->byStatus(User::STATUS_SUSPENDED)->fetch();
48
            $this->assign("suspendedUsers", $suspendedUsers);
49
50
            $declinedUsers = UserSearchHelper::get($database)->byStatus(User::STATUS_DECLINED)->fetch();
51
            $this->assign("declinedUsers", $declinedUsers);
52
53
            UserSearchHelper::get($database)->getRoleMap($roleMap);
54
        }
55
        else {
56
            $this->assign("showAll", false);
57
            $this->assign("suspendedUsers", array());
58
            $this->assign("declinedUsers", array());
59
60
            UserSearchHelper::get($database)->statusIn(array('New', 'Active'))->getRoleMap($roleMap);
61
        }
62
63
        $newUsers = UserSearchHelper::get($database)->byStatus(User::STATUS_NEW)->fetch();
64
        $normalUsers = UserSearchHelper::get($database)->byStatus(User::STATUS_ACTIVE)->byRole('user')->fetch();
65
        $adminUsers = UserSearchHelper::get($database)->byStatus(User::STATUS_ACTIVE)->byRole('admin')->fetch();
66
        $checkUsers = UserSearchHelper::get($database)->byStatus(User::STATUS_ACTIVE)->byRole('checkuser')->fetch();
67
        $toolRoots = UserSearchHelper::get($database)->byStatus(User::STATUS_ACTIVE)->byRole('toolRoot')->fetch();
68
        $this->assign('newUsers', $newUsers);
69
        $this->assign('normalUsers', $normalUsers);
70
        $this->assign('adminUsers', $adminUsers);
71
        $this->assign('checkUsers', $checkUsers);
72
        $this->assign('toolRoots', $toolRoots);
73
74
        $this->assign('roles', $roleMap);
75
76
        $this->getTypeAheadHelper()->defineTypeAheadSource('username-typeahead', function() use ($database) {
77
            return UserSearchHelper::get($database)->fetchColumn('username');
78
        });
79
80
        $this->assign('canApprove', $this->barrierTest('approve', $currentUser));
81
        $this->assign('canDecline', $this->barrierTest('decline', $currentUser));
82
        $this->assign('canRename', $this->barrierTest('rename', $currentUser));
83
        $this->assign('canEditUser', $this->barrierTest('editUser', $currentUser));
84
        $this->assign('canSuspend', $this->barrierTest('suspend', $currentUser));
85
        $this->assign('canEditRoles', $this->barrierTest('editRoles', $currentUser));
86
87
        $this->setTemplate("usermanagement/main.tpl");
88
    }
89
90
    #region Access control
91
92
    /**
93
     * Action target for editing the roles assigned to a user
94
     */
95
    protected function editRoles()
96
    {
97
        $this->setHtmlTitle('User Management');
98
        $database = $this->getDatabase();
99
        $userId = WebRequest::getInt('user');
100
101
        /** @var User $user */
102
        $user = User::getById($userId, $database);
103
104
        if ($user === false) {
105
            throw new ApplicationLogicException('Sorry, the user you are trying to edit could not be found.');
106
        }
107
108
        $roleData = $this->getRoleData(UserRole::getForUser($user->getId(), $database));
109
110
        // Dual-mode action
111
        if (WebRequest::wasPosted()) {
112
            $this->validateCSRFToken();
113
114
            $reason = WebRequest::postString('reason');
115
            if ($reason === false || trim($reason) === '') {
116
                throw new ApplicationLogicException('No reason specified for roles change');
117
            }
118
119
            /** @var UserRole[] $delete */
120
            $delete = array();
121
            /** @var string[] $delete */
122
            $add = array();
123
124
            foreach ($roleData as $name => $r) {
125
                if ($r['allowEdit'] !== 1) {
126
                    // not allowed, to touch this, so ignore it
127
                    continue;
128
                }
129
130
                $newValue = WebRequest::postBoolean('role-' . $name) ? 1 : 0;
131
                if ($newValue !== $r['active']) {
132
                    if ($newValue === 0) {
133
                        $delete[] = $r['object'];
134
                    }
135
136
                    if ($newValue === 1) {
137
                        $add[] = $name;
138
                    }
139
                }
140
            }
141
142
            // Check there's something to do
143
            if ((count($add) + count($delete)) === 0) {
144
                $this->redirect('statistics/users', 'detail', array('user' => $user->getId()));
145
                SessionAlert::warning('No changes made to roles.');
146
147
                return;
148
            }
149
150
            $removed = array();
151
152
            /** @var UserRole $d */
153
            foreach ($delete as $d) {
154
                $removed[] = $d->getRole();
0 ignored issues
show
Bug introduced by
The method getRole cannot be called on $d (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
155
                $d->delete();
0 ignored issues
show
Bug introduced by
The method delete cannot be called on $d (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
156
            }
157
158 View Code Duplication
            foreach ($add as $x) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
159
                $a = new UserRole();
160
                $a->setUser($user->getId());
161
                $a->setRole($x);
162
                $a->setDatabase($database);
163
                $a->save();
164
            }
165
166
            Logger::userRolesEdited($database, $user, $reason, $add, $removed);
167
168
            // dummy save for optimistic locking. If this fails, the entire txn will roll back.
169
            $user->setUpdateVersion(WebRequest::postInt('updateversion'));
170
            $user->save();
171
172
            $this->getNotificationHelper()->userRolesEdited($user, $reason);
173
            SessionAlert::quick('Roles changed for user ' . htmlentities($user->getUsername(), ENT_COMPAT, 'UTF-8'));
174
175
            $this->redirect('statistics/users', 'detail', array('user' => $user->getId()));
176
177
            return;
178
        }
179
        else {
180
            $this->assignCSRFToken();
181
            $this->setTemplate('usermanagement/roleedit.tpl');
182
            $this->assign('user', $user);
183
            $this->assign('roleData', $roleData);
184
        }
185
    }
186
187
    /**
188
     * Action target for suspending users
189
     *
190
     * @throws ApplicationLogicException
191
     */
192 View Code Duplication
    protected function suspend()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
193
    {
194
        $this->setHtmlTitle('User Management');
195
196
        $database = $this->getDatabase();
197
198
        $userId = WebRequest::getInt('user');
199
200
        /** @var User $user */
201
        $user = User::getById($userId, $database);
202
203
        if ($user === false) {
204
            throw new ApplicationLogicException('Sorry, the user you are trying to suspend could not be found.');
205
        }
206
207
        if ($user->isSuspended()) {
208
            throw new ApplicationLogicException('Sorry, the user you are trying to suspend is already suspended.');
209
        }
210
211
        // Dual-mode action
212
        if (WebRequest::wasPosted()) {
213
            $this->validateCSRFToken();
214
            $reason = WebRequest::postString('reason');
215
216
            if ($reason === null || trim($reason) === "") {
217
                throw new ApplicationLogicException('No reason provided');
218
            }
219
220
            $user->setStatus(User::STATUS_SUSPENDED);
221
            $user->setUpdateVersion(WebRequest::postInt('updateversion'));
222
            $user->save();
223
            Logger::suspendedUser($database, $user, $reason);
224
225
            $this->getNotificationHelper()->userSuspended($user, $reason);
226
            SessionAlert::quick('Suspended user ' . htmlentities($user->getUsername(), ENT_COMPAT, 'UTF-8'));
227
228
            // send email
229
            $this->sendStatusChangeEmail(
230
                'Your WP:ACC account has been suspended',
231
                'usermanagement/emails/suspended.tpl',
232
                $reason,
233
                $user,
234
                User::getCurrent($database)->getUsername()
235
            );
236
237
            $this->redirect('userManagement');
238
239
            return;
240
        }
241
        else {
242
            $this->assignCSRFToken();
243
            $this->setTemplate('usermanagement/changelevel-reason.tpl');
244
            $this->assign('user', $user);
245
            $this->assign('status', 'Suspended');
246
            $this->assign("showReason", true);
247
        }
248
    }
249
250
    /**
251
     * Entry point for the decline action
252
     *
253
     * @throws ApplicationLogicException
254
     */
255 View Code Duplication
    protected function decline()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
256
    {
257
        $this->setHtmlTitle('User Management');
258
259
        $database = $this->getDatabase();
260
261
        $userId = WebRequest::getInt('user');
262
        $user = User::getById($userId, $database);
263
264
        if ($user === false) {
265
            throw new ApplicationLogicException('Sorry, the user you are trying to decline could not be found.');
266
        }
267
268
        if (!$user->isNewUser()) {
269
            throw new ApplicationLogicException('Sorry, the user you are trying to decline is not new.');
270
        }
271
272
        // Dual-mode action
273
        if (WebRequest::wasPosted()) {
274
            $this->validateCSRFToken();
275
            $reason = WebRequest::postString('reason');
276
277
            if ($reason === null || trim($reason) === "") {
278
                throw new ApplicationLogicException('No reason provided');
279
            }
280
281
            $user->setStatus(User::STATUS_DECLINED);
282
            $user->setUpdateVersion(WebRequest::postInt('updateversion'));
283
            $user->save();
284
            Logger::declinedUser($database, $user, $reason);
285
286
            $this->getNotificationHelper()->userDeclined($user, $reason);
287
            SessionAlert::quick('Declined user ' . htmlentities($user->getUsername(), ENT_COMPAT, 'UTF-8'));
288
289
            // send email
290
            $this->sendStatusChangeEmail(
291
                'Your WP:ACC account has been declined',
292
                'usermanagement/emails/declined.tpl',
293
                $reason,
294
                $user,
295
                User::getCurrent($database)->getUsername()
296
            );
297
298
            $this->redirect('userManagement');
299
300
            return;
301
        }
302
        else {
303
            $this->assignCSRFToken();
304
            $this->setTemplate('usermanagement/changelevel-reason.tpl');
305
            $this->assign('user', $user);
306
            $this->assign('status', 'Declined');
307
            $this->assign("showReason", true);
308
        }
309
    }
310
311
    /**
312
     * Entry point for the approve action
313
     *
314
     * @throws ApplicationLogicException
315
     */
316
    protected function approve()
317
    {
318
        $this->setHtmlTitle('User Management');
319
320
        $database = $this->getDatabase();
321
322
        $userId = WebRequest::getInt('user');
323
        $user = User::getById($userId, $database);
324
325
        if ($user === false) {
326
            throw new ApplicationLogicException('Sorry, the user you are trying to approve could not be found.');
327
        }
328
329
        if ($user->isActive()) {
330
            throw new ApplicationLogicException('Sorry, the user you are trying to approve is already an active user.');
331
        }
332
333
        // Dual-mode action
334
        if (WebRequest::wasPosted()) {
335
            $this->validateCSRFToken();
336
            $user->setStatus(User::STATUS_ACTIVE);
337
            $user->setUpdateVersion(WebRequest::postInt('updateversion'));
338
            $user->save();
339
            Logger::approvedUser($database, $user);
340
341
            $this->getNotificationHelper()->userApproved($user);
342
            SessionAlert::quick('Approved user ' . htmlentities($user->getUsername(), ENT_COMPAT, 'UTF-8'));
343
344
            // send email
345
            $this->sendStatusChangeEmail(
346
                'Your WP:ACC account has been approved',
347
                'usermanagement/emails/approved.tpl',
348
                null,
349
                $user,
350
                User::getCurrent($database)->getUsername()
351
            );
352
353
            $this->redirect("userManagement");
354
355
            return;
356
        }
357
        else {
358
            $this->assignCSRFToken();
359
            $this->setTemplate("usermanagement/changelevel-reason.tpl");
360
            $this->assign("user", $user);
361
            $this->assign("status", "User");
362
            $this->assign("showReason", false);
363
        }
364
    }
365
366
    #endregion
367
368
    #region Renaming / Editing
369
370
    /**
371
     * Entry point for the rename action
372
     *
373
     * @throws ApplicationLogicException
374
     */
375
    protected function rename()
376
    {
377
        $this->setHtmlTitle('User Management');
378
379
        $database = $this->getDatabase();
380
381
        $userId = WebRequest::getInt('user');
382
        $user = User::getById($userId, $database);
383
384
        if ($user === false) {
385
            throw new ApplicationLogicException('Sorry, the user you are trying to rename could not be found.');
386
        }
387
388
        // Dual-mode action
389
        if (WebRequest::wasPosted()) {
390
            $this->validateCSRFToken();
391
            $newUsername = WebRequest::postString('newname');
392
393
            if ($newUsername === null || trim($newUsername) === "") {
394
                throw new ApplicationLogicException('The new username cannot be empty');
395
            }
396
397
            if (User::getByUsername($newUsername, $database) != false) {
398
                throw new ApplicationLogicException('The new username already exists');
399
            }
400
401
            $oldUsername = $user->getUsername();
402
            $user->setUsername($newUsername);
403
            $user->setUpdateVersion(WebRequest::postInt('updateversion'));
404
405
            $user->save();
406
407
            $logEntryData = serialize(array(
408
                'old' => $oldUsername,
409
                'new' => $newUsername,
410
            ));
411
412
            Logger::renamedUser($database, $user, $logEntryData);
413
414
            SessionAlert::quick("Changed User "
415
                . htmlentities($oldUsername, ENT_COMPAT, 'UTF-8')
416
                . " name to "
417
                . htmlentities($newUsername, ENT_COMPAT, 'UTF-8'));
418
419
            $this->getNotificationHelper()->userRenamed($user, $oldUsername);
420
421
            // send an email to the user.
422
            $this->assign('targetUsername', $user->getUsername());
423
            $this->assign('toolAdmin', User::getCurrent($database)->getUsername());
424
            $this->assign('oldUsername', $oldUsername);
425
            $this->assign('mailingList', $this->adminMailingList);
426
427
            $this->getEmailHelper()->sendMail(
428
                $user->getEmail(),
429
                'Your username on WP:ACC has been changed',
430
                $this->fetchTemplate('usermanagement/emails/renamed.tpl'),
431
                array('Reply-To' => $this->adminMailingList)
432
            );
433
434
            $this->redirect("userManagement");
435
436
            return;
437
        }
438
        else {
439
            $this->assignCSRFToken();
440
            $this->setTemplate('usermanagement/renameuser.tpl');
441
            $this->assign('user', $user);
442
        }
443
    }
444
445
    /**
446
     * Entry point for the edit action
447
     *
448
     * @throws ApplicationLogicException
449
     */
450
    protected function editUser()
451
    {
452
        $this->setHtmlTitle('User Management');
453
454
        $database = $this->getDatabase();
455
456
        $userId = WebRequest::getInt('user');
457
        $user = User::getById($userId, $database);
458
        $oauth = new OAuthUserHelper($user, $database, $this->getOAuthProtocolHelper(), $this->getSiteConfiguration());
0 ignored issues
show
Security Bug introduced by
It seems like $user defined by \Waca\DataObjects\User::...yId($userId, $database) on line 457 can also be of type false; however, Waca\Helpers\OAuthUserHelper::__construct() does only seem to accept object<Waca\DataObjects\User>, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
459
460
        if ($user === false) {
461
            throw new ApplicationLogicException('Sorry, the user you are trying to edit could not be found.');
462
        }
463
464
        // Dual-mode action
465
        if (WebRequest::wasPosted()) {
466
            $this->validateCSRFToken();
467
            $newEmail = WebRequest::postEmail('user_email');
468
            $newOnWikiName = WebRequest::postString('user_onwikiname');
469
470
            if ($newEmail === null) {
471
                throw new ApplicationLogicException('Invalid email address');
472
            }
473
474
            if (!$oauth->isFullyLinked()) {
475
                if (trim($newOnWikiName) == "") {
476
                    throw new ApplicationLogicException('New on-wiki username cannot be blank');
477
                }
478
479
                $user->setOnWikiName($newOnWikiName);
480
            }
481
482
            $user->setEmail($newEmail);
483
484
            $user->setUpdateVersion(WebRequest::postInt('updateversion'));
485
486
            $user->save();
487
488
            Logger::userPreferencesChange($database, $user);
489
            $this->getNotificationHelper()->userPrefChange($user);
490
            SessionAlert::quick('Changes to user\'s preferences have been saved');
491
492
            $this->redirect("userManagement");
493
494
            return;
495
        }
496
        else {
497
            $this->assignCSRFToken();
498
            $oauth = new OAuthUserHelper($user, $database, $this->getOAuthProtocolHelper(),
499
                $this->getSiteConfiguration());
500
            $this->setTemplate('usermanagement/edituser.tpl');
501
            $this->assign('user', $user);
502
            $this->assign('oauth', $oauth);
503
        }
504
    }
505
506
    #endregion
507
508
    /**
509
     * Sends a status change email to the user.
510
     *
511
     * @param string      $subject           The subject of the email
512
     * @param string      $template          The smarty template to use
513
     * @param string|null $reason            The reason for performing the status change
514
     * @param User        $user              The user affected
515
     * @param string      $toolAdminUsername The tool admin's username who is making the edit
516
     */
517
    private function sendStatusChangeEmail($subject, $template, $reason, $user, $toolAdminUsername)
518
    {
519
        $this->assign('targetUsername', $user->getUsername());
520
        $this->assign('toolAdmin', $toolAdminUsername);
521
        $this->assign('actionReason', $reason);
522
        $this->assign('mailingList', $this->adminMailingList);
523
524
        $this->getEmailHelper()->sendMail(
525
            $user->getEmail(),
526
            $subject,
527
            $this->fetchTemplate($template),
528
            array('Reply-To' => $this->adminMailingList)
529
        );
530
    }
531
532
    /**
533
     * @param UserRole[] $activeRoles
534
     *
535
     * @return array
536
     */
537
    private function getRoleData($activeRoles)
538
    {
539
        $availableRoles = $this->getSecurityManager()->getRoleConfiguration()->getAvailableRoles();
540
541
        $currentUser = User::getCurrent($this->getDatabase());
542
        $this->getSecurityManager()->getActiveRoles($currentUser, $userRoles, $inactiveRoles);
543
544
        $initialValue = array('active' => 0, 'allowEdit' => 0, 'description' => '???', 'object' => null);
545
546
        $roleData = array();
547
        foreach ($availableRoles as $role => $data) {
548
            $intersection = array_intersect($data['editableBy'], $userRoles);
549
550
            $roleData[$role] = $initialValue;
551
            $roleData[$role]['allowEdit'] = count($intersection) > 0 ? 1 : 0;
552
            $roleData[$role]['description'] = $data['description'];
553
        }
554
555
        foreach ($activeRoles as $role) {
556
            if (!isset($roleData[$role->getRole()])) {
557
                // This value is no longer available in the configuration, allow changing (aka removing) it.
558
                $roleData[$role->getRole()] = $initialValue;
559
                $roleData[$role->getRole()]['allowEdit'] = 1;
560
            }
561
562
            $roleData[$role->getRole()]['object'] = $role;
563
            $roleData[$role->getRole()]['active'] = 1;
564
        }
565
566
        return $roleData;
567
    }
568
}
569