Completed
Branch feature/currentUserRefactoring (c13c1d)
by Schlaefer
04:13
created

UsersController::avatar()   B

Complexity

Conditions 8
Paths 7

Size

Total Lines 51

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
nc 7
nop 1
dl 0
loc 51
rs 7.8246
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
declare(strict_types=1);
4
5
/**
6
 * Saito - The Threaded Web Forum
7
 *
8
 * @copyright Copyright (c) the Saito Project Developers
9
 * @link https://github.com/Schlaefer/Saito
10
 * @license http://opensource.org/licenses/MIT
11
 */
12
13
namespace App\Controller;
14
15
use App\Form\BlockForm;
16
use App\Model\Entity\User;
17
use Cake\Core\Configure;
18
use Cake\Event\Event;
19
use Cake\Http\Exception\BadRequestException;
20
use Cake\Http\Exception\ForbiddenException;
21
use Cake\Http\Response;
22
use Cake\I18n\Time;
23
use Saito\App\Registry;
24
use Saito\Exception\Logger\ExceptionLogger;
25
use Saito\Exception\Logger\ForbiddenLogger;
26
use Saito\Exception\SaitoForbiddenException;
27
use Saito\User\Blocker\ManualBlocker;
28
use Saito\User\Permission\Identifier\Owner;
29
use Saito\User\Permission\Identifier\Role;
30
use Saito\User\Permission\Permissions;
31
use Siezi\SimpleCaptcha\Model\Validation\SimpleCaptchaValidator;
32
use Stopwatch\Lib\Stopwatch;
33
34
/**
35
 * User controller
36
 */
37
class UsersController extends AppController
38
{
39
    public $helpers = [
40
        'SpectrumColorpicker.SpectrumColorpicker',
41
        'Posting',
42
        'Siezi/SimpleCaptcha.SimpleCaptcha',
43
        'Text'
44
    ];
45
46
    /**
47
     * {@inheritDoc}
48
     */
49
    public function initialize()
50
    {
51
        parent::initialize();
52
        $this->loadComponent('Referer');
53
    }
54
55
    /**
56
     * Login user.
57
     *
58
     * @return void|Response
59
     */
60
    public function login()
61
    {
62
        $data = $this->request->getData();
63
        if (empty($data['username'])) {
64
            $logout = $this->_logoutAndComeHereAgain();
65
            if ($logout) {
66
                return $logout;
67
            }
68
69
            /// Show form to user.
70
            if ($this->getRequest()->getQuery('redirect', null)) {
71
                $this->Flash->set(
72
                    __('user.authe.required.exp'),
73
                    ['element' => 'warning', 'params' => ['title' => __('user.authe.required.t')]]
74
                );
75
            };
76
77
            return;
78
        }
79
80
        if ($this->AuthUser->login()) {
81
            // Redirect query-param in URL.
82
            $target = $this->getRequest()->getQuery('redirect');
83
            // Referer from Request
84
            $target = $target ?: $this->referer(null, true);
85
86
            if (!$target || $this->Referer->wasAction('login')) {
87
                $target = '/';
88
            }
89
90
            return $this->redirect($target);
91
        }
92
93
        /// error on login
94
        $username = $this->request->getData('username');
95
        /** @var User */
96
        $User = $this->Users->find()
97
            ->where(['username' => $username])
98
            ->first();
99
100
        $message = __('user.authe.e.generic');
101
102
        if (!empty($User)) {
103
            if (!$User->isActivated()) {
104
                $message = __('user.actv.ny');
105
            } elseif ($User->isLocked()) {
106
                $ends = $this->Users->UserBlocks
107
                    ->getBlockEndsForUser($User->getId());
108
                if ($ends) {
109
                    $time = new Time($ends);
110
                    $data = [
111
                        'name' => $username,
112
                        'end' => $time->timeAgoInWords(['accuracy' => 'hour'])
113
                    ];
114
                    $message = __('user.block.pubExpEnds', $data);
115
                } else {
116
                    $message = __('user.block.pubExp', $username);
117
                }
118
            }
119
        }
120
121
        // don't autofill password
122
        $this->setRequest($this->getRequest()->withData('password', ''));
123
124
        $Logger = new ForbiddenLogger;
125
        $Logger->write(
126
            "Unsuccessful login for user: $username",
127
            ['msgs' => [$message]]
128
        );
129
130
        $this->Flash->set($message, [
131
            'element' => 'error', 'params' => ['title' => __('user.authe.e.t')]
132
        ]);
133
    }
134
135
    /**
136
     * Logout user.
137
     *
138
     * @return void|Response
139
     */
140
    public function logout()
141
    {
142
        $request = $this->getRequest();
143
        $cookies = $request->getCookieCollection();
144
        foreach ($cookies as $cookie) {
145
            $cookie = $cookie->withPath($request->getAttribute('webroot'));
146
            $this->setResponse($this->getResponse()->withExpiredCookie($cookie));
147
        }
148
149
        $this->AuthUser->logout();
150
        $this->redirect('/');
151
    }
152
153
    /**
154
     * Register new user.
155
     *
156
     * @return void|Response
157
     */
158
    public function register()
159
    {
160
        $this->set('status', 'view');
161
162
        $this->AuthUser->logout();
163
164
        $tosRequired = Configure::read('Saito.Settings.tos_enabled');
165
        $this->set(compact('tosRequired'));
166
167
        $user = $this->Users->newEntity();
168
        $this->set('user', $user);
169
170
        if (!$this->request->is('post')) {
171
            $logout = $this->_logoutAndComeHereAgain();
172
            if ($logout) {
173
                return $logout;
174
            }
175
176
            return;
177
        }
178
179
        $data = $this->request->getData();
180
181
        if (!$tosRequired) {
182
            $data['tos_confirm'] = true;
183
        }
184
        $tosConfirmed = $data['tos_confirm'];
185
        if (!$tosConfirmed) {
186
            return;
187
        }
188
189
        $validator = new SimpleCaptchaValidator();
190
        $errors = $validator->errors($this->request->getData());
0 ignored issues
show
Bug introduced by
It seems like $this->request->getData() targeting Cake\Http\ServerRequest::getData() can also be of type null or string; however, Cake\Validation\Validator::errors() does only seem to accept array, maybe add an additional type check?

This check looks at variables that 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.

Loading history...
191
192
        $user = $this->Users->register($data);
0 ignored issues
show
Bug introduced by
It seems like $data defined by $this->request->getData() on line 179 can also be of type string; however, App\Model\Table\UsersTable::register() does only seem to accept array, 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.

Loading history...
193
        $user->setErrors($errors);
194
195
        $errors = $user->getErrors();
196
        if (!empty($errors)) {
197
            // registering failed, show form again
198
            if (isset($errors['password'])) {
199
                $user->setErrors($errors);
200
            }
201
            $user->set('tos_confirm', false);
202
            $this->set('user', $user);
203
204
            return;
205
        }
206
207
        // registered successfully
208
        try {
209
            $forumName = Configure::read('Saito.Settings.forum_name');
210
            $subject = __('register_email_subject', $forumName);
211
            $this->SaitoEmail->email(
212
                [
213
                    'recipient' => $user,
214
                    'subject' => $subject,
215
                    'sender' => 'register',
216
                    'template' => 'user_register',
217
                    'viewVars' => ['user' => $user]
218
                ]
219
            );
220
        } catch (\Exception $e) {
221
            $Logger = new ExceptionLogger();
222
            $Logger->write(
223
                'Registering email confirmation failed',
224
                ['e' => $e]
225
            );
226
            $this->set('status', 'fail: email');
227
228
            return;
229
        }
230
231
        $this->set('status', 'success');
232
    }
233
234
    /**
235
     * register success (user clicked link in confirm mail)
236
     *
237
     * @param string $id user-ID
238
     * @return void
239
     * @throws BadRequestException
240
     */
241
    public function rs($id = null)
242
    {
243
        if (!$id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $id of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
244
            throw new BadRequestException();
245
        }
246
        $code = $this->request->getQuery('c');
247
        try {
248
            $activated = $this->Users->activate((int)$id, $code);
0 ignored issues
show
Bug introduced by
It seems like $code defined by $this->request->getQuery('c') on line 246 can also be of type array or null; however, App\Model\Table\UsersTable::activate() 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.

Loading history...
249
        } catch (\Exception $e) {
250
            $activated = false;
251
        }
252
        if (!$activated) {
253
            $activated = ['status' => 'fail'];
254
        }
255
        $this->set('status', $activated['status']);
256
    }
257
258
    /**
259
     * Show list of all users.
260
     *
261
     * @return void
262
     */
263
    public function index()
264
    {
265
        $menuItems = [
266
            'username' => [__('username_marking'), []],
267
            'user_type' => [__('user_type'), []],
268
            'UserOnline.logged_in' => [
269
                __('userlist_online'),
270
                ['direction' => 'desc']
271
            ],
272
            'registered' => [__('registered'), ['direction' => 'desc']]
273
        ];
274
        $showBlocked = $this->CurrentUser->permission('saito.core.user.lock.view');
275
        if ($showBlocked) {
276
            $menuItems['user_lock'] = [
277
                __('user.set.lock.t'),
278
                ['direction' => 'desc']
279
            ];
280
        }
281
282
        $this->paginate = $options = [
0 ignored issues
show
Unused Code introduced by
$options is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
283
            'contain' => ['UserOnline'],
284
            'sortWhitelist' => array_keys($menuItems),
285
            'finder' => 'paginated',
286
            'limit' => 400,
287
            'order' => [
288
                'UserOnline.logged_in' => 'desc',
289
            ]
290
        ];
291
        $users = $this->paginate($this->Users);
292
293
        $showBottomNavigation = true;
294
295
        $this->set(compact('menuItems', 'showBottomNavigation', 'users'));
296
    }
297
298
    /**
299
     * Ignore user.
300
     *
301
     * @return void
302
     */
303
    public function ignore()
304
    {
305
        $this->request->allowMethod('POST');
306
        $blockedId = (int)$this->request->getData('id');
307
        $this->_ignore($blockedId, true);
308
    }
309
310
    /**
311
     * Unignore user.
312
     *
313
     * @return void
314
     */
315
    public function unignore()
316
    {
317
        $this->request->allowMethod('POST');
318
        $blockedId = (int)$this->request->getData('id');
319
        $this->_ignore($blockedId, false);
320
    }
321
322
    /**
323
     * Mark user as un-/ignored
324
     *
325
     * @param int $blockedId user to ignore
326
     * @param bool $set block or unblock
327
     * @return \Cake\Network\Response
328
     */
329
    protected function _ignore($blockedId, $set)
330
    {
331
        $userId = $this->CurrentUser->getId();
332
        if ((int)$userId === (int)$blockedId) {
333
            throw new BadRequestException();
334
        }
335
        if ($set) {
336
            $this->Users->UserIgnores->ignore($userId, $blockedId);
337
        } else {
338
            $this->Users->UserIgnores->unignore($userId, $blockedId);
339
        }
340
341
        return $this->redirect($this->referer());
342
    }
343
344
    /**
345
     * Show user with profile $name
346
     *
347
     * @param string $name username
348
     * @return void
349
     */
350
    public function name($name = null)
351
    {
352
        if (!empty($name)) {
353
            $viewedUser = $this->Users->find()
354
                ->select(['id'])
355
                ->where(['username' => $name])
356
                ->first();
357
            if (!empty($viewedUser)) {
358
                $this->redirect(
359
                    [
360
                        'controller' => 'users',
361
                        'action' => 'view',
362
                        $viewedUser->get('id')
363
                    ]
364
                );
365
366
                return;
367
            }
368
        }
369
        $this->Flash->set(__('Invalid user'), ['element' => 'error']);
370
        $this->redirect('/');
371
    }
372
373
    /**
374
     * View user profile.
375
     *
376
     * @param null $id user-ID
377
     * @return \Cake\Network\Response|void
378
     */
379
    public function view($id = null)
380
    {
381
        // redirect view/<username> to name/<username>
382
        if (!empty($id) && !is_numeric($id)) {
383
            $this->redirect(
384
                ['controller' => 'users', 'action' => 'name', $id]
385
            );
386
387
            return;
388
        }
389
390
        $id = (int)$id;
391
392
        /** @var User */
393
        $user = $this->Users->find()
394
            ->contain(
395
                [
396
                    'UserBlocks' => function ($q) {
397
                        return $q->find('assocUsers');
398
                    },
399
                    'UserOnline'
400
                ]
401
            )
402
            ->where(['Users.id' => (int)$id])
403
            ->first();
404
405
        if (empty($user)) {
406
            $this->Flash->set(__('Invalid user'), ['element' => 'error']);
407
408
            return $this->redirect('/');
409
        }
410
411
        $entriesShownOnPage = 20;
412
        $this->set(
413
            'lastEntries',
414
            $this->Users->Entries->getRecentPostings(
415
                $this->CurrentUser,
416
                ['user_id' => $id, 'limit' => $entriesShownOnPage]
417
            )
418
        );
419
420
        $this->set(
421
            'hasMoreEntriesThanShownOnPage',
422
            ($user->numberOfPostings() - $entriesShownOnPage) > 0
423
        );
424
425
        if ($this->CurrentUser->getId() === $id) {
426
            $ignores = $this->Users->UserIgnores->getAllIgnoredBy($id);
427
            $user->set('ignores', $ignores);
428
        }
429
430
        $blockForm = new BlockForm();
431
        $solved = $this->Users->countSolved($id);
432
        $this->set(compact('blockForm', 'isEditingAllowed', 'solved', 'user'));
433
        $this->set('titleForLayout', $user->get('username'));
434
    }
435
436
    /**
437
     * Set user avatar.
438
     *
439
     * @param string $userId user-ID
440
     * @return void|\Cake\Network\Response
441
     */
442
    public function avatar($userId)
443
    {
444
        if (!$this->Users->exists($userId)) {
445
            throw new BadRequestException;
446
        }
447
448
        /** @var User */
449
        $user = $this->Users->get($userId);
450
451
        $permissionEditing = $this->CurrentUser->permission(
452
            'saito.core.user.edit',
453
            new Role($user->getRole()),
454
            new Owner($user)
455
        );
456
        if (!$permissionEditing) {
457
            throw new \Saito\Exception\SaitoForbiddenException(
458
                "Attempt to edit user $userId.",
459
                ['CurrentUser' => $this->CurrentUser]
460
            );
461
        }
462
463
        if ($this->request->is('post') || $this->request->is('put')) {
464
            $data = [
465
                'avatar' => $this->request->getData('avatar'),
466
                'avatarDelete' => $this->request->getData('avatarDelete')
467
            ];
468
            if (!empty($data['avatarDelete'])) {
469
                $data = [
470
                    'avatar' => null,
471
                    'avatar_dir' => null
472
                ];
473
            }
474
            $patched = $this->Users->patchEntity($user, $data);
475
            $errors = $patched->getErrors();
476
            if (empty($errors) && $this->Users->save($patched)) {
477
                return $this->redirect(['action' => 'edit', $userId]);
478
            } else {
479
                $this->Flash->set(
480
                    __('The user could not be saved. Please, try again.'),
481
                    ['element' => 'error']
482
                );
483
            }
484
        }
485
486
        $this->set('user', $user);
487
488
        $this->set(
489
            'titleForPage',
490
            __('user.avatar.edit.t', [$user->get('username')])
491
        );
492
    }
493
494
    /**
495
     * Edit user.
496
     *
497
     * @param null $id user-ID
498
     *
499
     * @return \Cake\Network\Response|void
500
     */
501
    public function edit($id = null)
502
    {
503
        /** @var User */
504
        $user = $this->Users->get($id);
505
506
        $permissionEditing = $this->CurrentUser->permission(
507
            'saito.core.user.edit',
508
            new Role($user->getRole()),
509
            new Owner($user)
510
        );
511
        if (!$permissionEditing) {
512
            throw new \Saito\Exception\SaitoForbiddenException(
513
                sprintf('Attempt to edit user "%s".', $user->get('id')),
514
                ['CurrentUser' => $this->CurrentUser]
515
            );
516
        }
517
518
        if ($this->request->is('post') || $this->request->is('put')) {
519
            $data = $this->request->getData();
520
            $patched = $this->Users->patchEntity($user, $data);
0 ignored issues
show
Bug introduced by
It seems like $data defined by $this->request->getData() on line 519 can also be of type null or string; however, Cake\ORM\Table::patchEntity() does only seem to accept array, 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.

Loading history...
521
            $errors = $patched->getErrors();
522
            if (empty($errors) && $this->Users->save($patched)) {
523
                return $this->redirect(['action' => 'view', $id]);
524
            }
525
526
            $this->Flash->set(
527
                __('The user could not be saved. Please, try again.'),
528
                ['element' => 'error']
529
            );
530
        }
531
        $this->set('user', $user);
532
533
        $this->set(
534
            'titleForPage',
535
            __('user.edit.t', [$user->get('username')])
536
        );
537
538
        $availableThemes = $this->Themes->getAvailable($this->CurrentUser);
539
        $availableThemes = array_combine($availableThemes, $availableThemes);
540
        $currentTheme = $this->Themes->getThemeForUser($this->CurrentUser);
541
        $this->set(compact('availableThemes', 'currentTheme'));
542
    }
543
544
    /**
545
     * delete user
546
     *
547
     * @param string $id user-ID
548
     * @return \Cake\Network\Response|void
549
     */
550
    public function delete($id)
551
    {
552
        $id = (int)$id;
553
        /** @var User */
554
        $readUser = $this->Users->get($id);
555
556
        /// Check permission
557
        $permission = $this->CurrentUser->permission(
558
            'saito.core.user.delete',
559
            new Role($readUser->getRole())
560
        );
561
        if (!$permission) {
562
            throw new ForbiddenException(
563
                sprintf(
564
                    'User "%s" is not allowed to delete user "%s".',
565
                    $this->CurrentUser->get('username'),
566
                    $readUser->get('username')
567
                ),
568
                1571811593
569
            );
570
        }
571
572
        $this->set('user', $readUser);
573
574
        $failure = false;
575
        if (!$this->request->getData('userdeleteconfirm')) {
576
            $failure = true;
577
            $this->Flash->set(__('user.del.fail.3'), ['element' => 'error']);
578
        } elseif ($this->CurrentUser->isUser($readUser)) {
579
            $failure = true;
580
            $this->Flash->set(__('user.del.fail.1'), ['element' => 'error']);
581
        }
582
583
        if (!$failure) {
584
            $result = $this->Users->deleteAllExceptEntries($id);
585
            if (empty($result)) {
586
                $failure = true;
587
                $this->Flash->set(__('user.del.fail.2'), ['element' => 'error']);
588
            }
589
        }
590
591
        if ($failure) {
592
            return $this->redirect(
593
                [
594
                    'prefix' => false,
595
                    'controller' => 'users',
596
                    'action' => 'view',
597
                    $id
598
                ]
599
            );
600
        }
601
602
        $this->Flash->set(
603
            __('user.del.ok.m', $readUser->get('username')),
604
            ['element' => 'success']
605
        );
606
607
        return $this->redirect('/');
608
    }
609
610
    /**
611
     * Lock user.
612
     *
613
     * @return \Cake\Network\Response|void
614
     * @throws BadRequestException
615
     */
616
    public function lock()
617
    {
618
        $form = new BlockForm();
619
        if (!$form->validate($this->request->getData())) {
0 ignored issues
show
Bug introduced by
It seems like $this->request->getData() targeting Cake\Http\ServerRequest::getData() can also be of type null or string; however, Cake\Form\Form::validate() does only seem to accept array, maybe add an additional type check?

This check looks at variables that 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.

Loading history...
620
            throw new BadRequestException;
621
        }
622
623
        $id = (int)$this->request->getData('lockUserId');
624
625
        /** @var User */
626
        $readUser = $this->Users->get($id);
627
628
        $permission = $this->CurrentUser->permission(
629
            'saito.core.user.lock.set',
630
            new Role($readUser->getRole())
631
        );
632
        if (!$permission) {
633
            throw new ForbiddenException(null, 1571316877);
634
        }
635
636
        if ($this->CurrentUser->isUser($readUser)) {
637
            $message = __('You can\'t lock yourself.');
638
            $this->Flash->set($message, ['element' => 'error']);
639
        } else {
640
            try {
641
                $duration = (int)$this->request->getData('lockPeriod');
642
                $blocker = new ManualBlocker($this->CurrentUser->getId(), $duration);
643
                $status = $this->Users->UserBlocks->block($blocker, $id);
644
                if (!$status) {
645
                    throw new \Exception();
646
                }
647
                $message = __('User {0} is locked.', $readUser->get('username'));
648
                $this->Flash->set($message, ['element' => 'success']);
649
            } catch (\Exception $e) {
650
                $message = __('Error while locking.');
651
                $this->Flash->set($message, ['element' => 'error']);
652
            }
653
        }
654
655
        return $this->redirect($this->referer());
656
    }
657
658
    /**
659
     * Unblock user.
660
     *
661
     * @param string $id user-ID
662
     * @return void
663
     */
664
    public function unlock(string $id)
665
    {
666
        $id = (int)$id;
667
668
        /** @var User */
669
        $user = $this->Users
670
            ->find()
671
            ->matching('UserBlocks', function ($q) use ($id) {
672
                return $q->where(['UserBlocks.id' => $id]);
673
            })
674
            ->first();
675
676
        $permission = $this->CurrentUser->permission(
677
            'saito.core.user.lock.set',
678
            new Role($user->getRole())
679
        );
680
        if (!$permission) {
681
            throw new ForbiddenException(null, 1571316877);
682
        }
683
684
        if (!$this->Users->UserBlocks->unblock($id)) {
685
            $this->Flash->set(
686
                __('Error while unlocking.'),
687
                ['element' => 'error']
688
            );
689
        }
690
691
        $message = __('User {0} is unlocked.', $user->get('username'));
692
        $this->Flash->set($message, ['element' => 'success']);
693
        $this->redirect($this->referer());
694
    }
695
696
    /**
697
     * changes user password
698
     *
699
     * @param null $id user-ID
700
     * @return void
701
     * @throws \Saito\Exception\SaitoForbiddenException
702
     * @throws BadRequestException
703
     */
704
    public function changepassword($id = null)
705
    {
706
        if (empty($id)) {
707
            throw new BadRequestException();
708
        }
709
710
        /** @var User */
711
        $user = $this->Users->get($id);
712
        $allowed = $this->CurrentUser->isUser($user);
713
        if (empty($user) || !$allowed) {
714
            throw new SaitoForbiddenException(
715
                "Attempt to change password for user $id.",
716
                ['CurrentUser' => $this->CurrentUser]
717
            );
718
        }
719
        $this->set('userId', $id);
720
        $this->set('username', $user->get('username'));
721
722
        //= just show empty form
723
        if (empty($this->request->getData())) {
724
            return;
725
        }
726
727
        $formFields = ['password', 'password_old', 'password_confirm'];
728
729
        //= process submitted form
730
        $data = [];
731
        foreach ($formFields as $field) {
732
            $data[$field] = $this->request->getData($field);
733
        }
734
        $this->Users->patchEntity($user, $data);
735
        $success = $this->Users->save($user);
736
737
        if ($success) {
738
            $this->Flash->set(
739
                __('change_password_success'),
740
                ['element' => 'success']
741
            );
742
            $this->redirect(['controller' => 'users', 'action' => 'edit', $id]);
743
744
            return;
745
        }
746
747
        $errors = $user->getErrors();
748
        if (!empty($errors)) {
749
            $this->Flash->set(
750
                __d('nondynamic', current(array_pop($errors))),
751
                ['element' => 'error']
752
            );
753
        }
754
755
        //= unset all autofill form data
756
        foreach ($formFields as $field) {
757
            $this->request = $this->request->withoutData($field);
758
        }
759
    }
760
761
    /**
762
     * Directly set password for user
763
     *
764
     * @param string $id user-ID
765
     * @return Response|null
766
     */
767
    public function setpassword($id)
768
    {
769
        /** @var User */
770
        $user = $this->Users->get($id);
771
772
        if (!$this->CurrentUser->permission('saito.core.user.password.set', new Role($user->getRole()))) {
773
            throw new SaitoForbiddenException(
774
                "Attempt to set password for user $id.",
775
                ['CurrentUser' => $this->CurrentUser]
776
            );
777
        }
778
779
        if ($this->getRequest()->is('post')) {
780
            $this->Users->patchEntity($user, $this->getRequest()->getData(), ['fields' => 'password']);
0 ignored issues
show
Bug introduced by
It seems like $this->getRequest()->getData() targeting Cake\Http\ServerRequest::getData() can also be of type null or string; however, Cake\ORM\Table::patchEntity() does only seem to accept array, maybe add an additional type check?

This check looks at variables that 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.

Loading history...
781
782
            if ($this->Users->save($user)) {
783
                $this->Flash->set(
784
                    __('user.pw.set.s'),
785
                    ['element' => 'success']
786
                );
787
788
                return $this->redirect(['controller' => 'users', 'action' => 'edit', $id]);
789
            }
790
            $errors = $user->getErrors();
791
            if (!empty($errors)) {
792
                $this->Flash->set(
793
                    __d('nondynamic', current(array_pop($errors))),
794
                    ['element' => 'error']
795
                );
796
            }
797
        }
798
799
        $this->set(compact('user'));
800
    }
801
802
    /**
803
     * View and set user role
804
     *
805
     * @param string $id User-ID
806
     * @return void|Response
807
     */
808
    public function role($id)
809
    {
810
        /** @var User */
811
        $user = $this->Users->get($id);
812
        if (!$this->CurrentUser->permission('saito.core.user.role.set', new Role($user->getRole()))) {
813
            throw new ForbiddenException();
814
        }
815
816
        /** @var Permissions */
817
        $Permissions = Registry::get('Permissions');
818
        $roles = $Permissions->getRoles()->get($this->CurrentUser->getRole(), false);
819
820
        if ($this->getRequest()->is('post') || $this->getRequest()->is('put')) {
821
            $type = $this->getRequest()->getData('user_type');
822
            $patched = $this->Users->patchEntity($user, ['user_type' => $type]);
823
824
            $errors = $patched->getErrors();
825
            if (empty($errors)) {
826
                $this->Users->save($patched);
827
828
                return $this->redirect(['action' => 'edit', $user->get('id')]);
829
            }
830
831
            $msg = current(current($errors));
832
            $this->Flash->set($msg, ['element' => 'error']);
833
        }
834
835
        $this->set(compact('roles', 'user'));
836
    }
837
838
    /**
839
     * Set slidetab-order.
840
     *
841
     * @return \Cake\Network\Response
842
     * @throws BadRequestException
843
     */
844
    public function slidetabOrder()
845
    {
846
        if (!$this->request->is('ajax')) {
847
            throw new BadRequestException;
848
        }
849
850
        $order = $this->request->getData('slidetabOrder');
851
        if (!$order) {
852
            throw new BadRequestException;
853
        }
854
855
        $allowed = $this->Slidetabs->getAvailable();
856
        $order = array_filter(
857
            $order,
858
            function ($item) use ($allowed) {
859
                return in_array($item, $allowed);
860
            }
861
        );
862
        $order = serialize($order);
863
864
        $userId = $this->CurrentUser->getId();
865
        $user = $this->Users->get($userId);
866
        $this->Users->patchEntity($user, ['slidetab_order' => $order]);
867
        $this->Users->save($user);
868
869
        $this->CurrentUser->set('slidetab_order', $order);
870
871
        $this->response = $this->response->withStringBody(true);
0 ignored issues
show
Documentation introduced by
true is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
872
873
        return $this->response;
874
    }
875
876
    /**
877
     * Shows user's uploads
878
     *
879
     * @return void
880
     */
881
    public function uploads()
882
    {
883
    }
884
885
    /**
886
     * Set category for user.
887
     *
888
     * @param string|null $id category-ID
889
     * @return \Cake\Network\Response
890
     */
891
    public function setcategory(?string $id = null)
892
    {
893
        $userId = $this->CurrentUser->getId();
894
        if ($id === 'all') {
895
            $this->Users->setCategory($userId, 'all');
896
        } elseif (!$id && $this->request->getData()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $id of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
897
            $data = $this->request->getData('CatChooser');
898
            $this->Users->setCategory($userId, $data);
899
        } else {
900
            $this->Users->setCategory($userId, $id);
901
        }
902
903
        return $this->redirect($this->referer());
904
    }
905
906
    /**
907
     * {@inheritdoc}
908
     */
909
    public function beforeFilter(Event $event)
910
    {
911
        parent::beforeFilter($event);
912
        Stopwatch::start('Users->beforeFilter()');
913
914
        $unlocked = ['slidetabToggle', 'slidetabOrder'];
915
        $this->Security->setConfig('unlockedActions', $unlocked);
916
917
        $this->Authentication->allowUnauthenticated(['login', 'logout', 'register', 'rs']);
918
        $this->AuthUser->authorizeAction('register', 'saito.core.user.register');
919
        $this->AuthUser->authorizeAction('rs', 'saito.core.user.register');
920
921
        // Login form times-out and degrades user experience.
922
        // See https://github.com/Schlaefer/Saito/issues/339
923
        if (($this->getRequest()->getParam('action') === 'login')
924
            && $this->components()->has('Security')) {
925
            $this->components()->unload('Security');
926
        }
927
928
        Stopwatch::stop('Users->beforeFilter()');
929
    }
930
931
    /**
932
     * Logout user if logged in and create response to revisit logged out
933
     *
934
     * @return Response|null
935
     */
936
    protected function _logoutAndComeHereAgain(): ?Response
937
    {
938
        if (!$this->CurrentUser->isLoggedIn()) {
939
            return null;
940
        }
941
        $this->AuthUser->logout();
942
943
        return $this->redirect($this->getRequest()->getRequestTarget());
944
    }
945
}
946