Completed
Push — develop ( 28dbd2...91eba7 )
by Schlaefer
03:54 queued 01:07
created

UsersController::unlock()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
nc 3
nop 1
dl 0
loc 18
rs 9.6666
c 0
b 0
f 0
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\NotFoundException;
21
use Cake\Http\Response;
22
use Cake\I18n\Time;
23
use Cake\Routing\Router;
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\CurrentUser\CurrentUserInterface;
29
use Siezi\SimpleCaptcha\Model\Validation\SimpleCaptchaValidator;
30
use Stopwatch\Lib\Stopwatch;
31
32
/**
33
 * User controller
34
 */
35
class UsersController extends AppController
36
{
37
    public $helpers = [
38
        'SpectrumColorpicker.SpectrumColorpicker',
39
        'Posting',
40
        'Siezi/SimpleCaptcha.SimpleCaptcha',
41
        'Text'
42
    ];
43
44
    /**
45
     * Are moderators allowed to bloack users
46
     *
47
     * @var bool
48
     */
49
    protected $modLocking = false;
50
51
    /**
52
     * {@inheritDoc}
53
     */
54
    public function initialize()
55
    {
56
        parent::initialize();
57
        $this->loadComponent('Referer');
58
    }
59
60
    /**
61
     * Login user.
62
     *
63
     * @return void|Response
64
     */
65
    public function login()
66
    {
67
        $data = $this->request->getData();
68
        if (empty($data['username'])) {
69
            $logout = $this->_logoutAndComeHereAgain();
70
            if ($logout) {
71
                return $logout;
72
            }
73
74
            /// Show form to user.
75
            if ($this->getRequest()->getQuery('redirect', null)) {
76
                $this->Flash->set(
77
                    __('user.authe.required.exp'),
78
                    ['element' => 'warning', 'params' => ['title' => __('user.authe.required.t')]]
79
                );
80
            };
81
82
            return;
83
        }
84
85
        if ($this->AuthUser->login()) {
86
            // Redirect query-param in URL.
87
            $target = $this->getRequest()->getQuery('redirect');
88
            // Referer from Request
89
            $target = $target ?: $this->referer(null, true);
90
91
            if (!$target || $this->Referer->wasAction('login')) {
92
                $target = '/';
93
            }
94
95
            return $this->redirect($target);
96
        }
97
98
        /// error on login
99
        $username = $this->request->getData('username');
100
        /** @var User */
101
        $readUser = $this->Users->find()
102
            ->where(['username' => $username])
103
            ->first();
104
105
        $message = __('user.authe.e.generic');
106
107
        if (!empty($readUser)) {
108
            $User = $readUser->toSaitoUser();
109
110
            if (!$User->isActivated()) {
111
                $message = __('user.actv.ny');
112
            } elseif ($User->isLocked()) {
113
                $ends = $this->Users->UserBlocks
114
                    ->getBlockEndsForUser($User->getId());
115
                if ($ends) {
116
                    $time = new Time($ends);
117
                    $data = [
118
                        'name' => $username,
119
                        'end' => $time->timeAgoInWords(['accuracy' => 'hour'])
120
                    ];
121
                    $message = __('user.block.pubExpEnds', $data);
122
                } else {
123
                    $message = __('user.block.pubExp', $username);
124
                }
125
            }
126
        }
127
128
        // don't autofill password
129
        $this->setRequest($this->getRequest()->withData('password', ''));
130
131
        $Logger = new ForbiddenLogger;
132
        $Logger->write(
133
            "Unsuccessful login for user: $username",
134
            ['msgs' => [$message]]
135
        );
136
137
        $this->Flash->set($message, [
138
            'element' => 'error', 'params' => ['title' => __('user.authe.e.t')]
139
        ]);
140
    }
141
142
    /**
143
     * Logout user.
144
     *
145
     * @return void|Response
146
     */
147
    public function logout()
148
    {
149
        $request = $this->getRequest();
150
        $cookies = $request->getCookieCollection();
151
        foreach ($cookies as $cookie) {
152
            $cookie = $cookie->withPath($request->getAttribute('webroot'));
153
            $this->setResponse($this->getResponse()->withExpiredCookie($cookie));
154
        }
155
156
        $this->AuthUser->logout();
157
        $this->redirect('/');
158
    }
159
160
    /**
161
     * Register new user.
162
     *
163
     * @return void|Response
164
     */
165
    public function register()
166
    {
167
        $this->set('status', 'view');
168
169
        $this->AuthUser->logout();
170
171
        $tosRequired = Configure::read('Saito.Settings.tos_enabled');
172
        $this->set(compact('tosRequired'));
173
174
        $user = $this->Users->newEntity();
175
        $this->set('user', $user);
176
177
        if (!$this->request->is('post')) {
178
            $logout = $this->_logoutAndComeHereAgain();
179
            if ($logout) {
180
                return $logout;
181
            }
182
183
            return;
184
        }
185
186
        $data = $this->request->getData();
187
188
        if (!$tosRequired) {
189
            $data['tos_confirm'] = true;
190
        }
191
        $tosConfirmed = $data['tos_confirm'];
192
        if (!$tosConfirmed) {
193
            return;
194
        }
195
196
        $validator = new SimpleCaptchaValidator();
197
        $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...
198
199
        $user = $this->Users->register($data);
0 ignored issues
show
Bug introduced by
It seems like $data defined by $this->request->getData() on line 186 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...
200
        $user->setErrors($errors);
201
202
        $errors = $user->getErrors();
203
        if (!empty($errors)) {
204
            // registering failed, show form again
205
            if (isset($errors['password'])) {
206
                $user->setErrors($errors);
207
            }
208
            $user->set('tos_confirm', false);
209
            $this->set('user', $user);
210
211
            return;
212
        }
213
214
        // registered successfully
215
        try {
216
            $forumName = Configure::read('Saito.Settings.forum_name');
217
            $subject = __('register_email_subject', $forumName);
218
            $this->SaitoEmail->email(
219
                [
220
                    'recipient' => $user,
221
                    'subject' => $subject,
222
                    'sender' => 'register',
223
                    'template' => 'user_register',
224
                    'viewVars' => ['user' => $user]
225
                ]
226
            );
227
        } catch (\Exception $e) {
228
            $Logger = new ExceptionLogger();
229
            $Logger->write(
230
                'Registering email confirmation failed',
231
                ['e' => $e]
232
            );
233
            $this->set('status', 'fail: email');
234
235
            return;
236
        }
237
238
        $this->set('status', 'success');
239
    }
240
241
    /**
242
     * register success (user clicked link in confirm mail)
243
     *
244
     * @param string $id user-ID
245
     * @return void
246
     * @throws BadRequestException
247
     */
248
    public function rs($id = null)
249
    {
250
        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...
251
            throw new BadRequestException();
252
        }
253
        $code = $this->request->getQuery('c');
254
        try {
255
            $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 253 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...
256
        } catch (\Exception $e) {
257
            $activated = false;
258
        }
259
        if (!$activated) {
260
            $activated = ['status' => 'fail'];
261
        }
262
        $this->set('status', $activated['status']);
263
    }
264
265
    /**
266
     * Show list of all users.
267
     *
268
     * @return void
269
     */
270
    public function index()
271
    {
272
        $menuItems = [
273
            'username' => [__('username_marking'), []],
274
            'user_type' => [__('user_type'), []],
275
            'UserOnline.logged_in' => [
276
                __('userlist_online'),
277
                ['direction' => 'desc']
278
            ],
279
            'registered' => [__('registered'), ['direction' => 'desc']]
280
        ];
281
        $showBlocked = Configure::read('Saito.Settings.block_user_ui');
282
        if ($showBlocked) {
283
            $menuItems['user_lock'] = [
284
                __('user.set.lock.t'),
285
                ['direction' => 'desc']
286
            ];
287
        }
288
289
        $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...
290
            'contain' => ['UserOnline'],
291
            'sortWhitelist' => array_keys($menuItems),
292
            'finder' => 'paginated',
293
            'limit' => 400,
294
            'order' => [
295
                'UserOnline.logged_in' => 'desc',
296
            ]
297
        ];
298
        $users = $this->paginate($this->Users);
299
300
        $showBottomNavigation = true;
301
302
        $this->set(compact('menuItems', 'showBottomNavigation', 'users'));
303
    }
304
305
    /**
306
     * Ignore user.
307
     *
308
     * @return void
309
     */
310
    public function ignore()
311
    {
312
        $this->request->allowMethod('POST');
313
        $blockedId = (int)$this->request->getData('id');
314
        $this->_ignore($blockedId, true);
315
    }
316
317
    /**
318
     * Unignore user.
319
     *
320
     * @return void
321
     */
322
    public function unignore()
323
    {
324
        $this->request->allowMethod('POST');
325
        $blockedId = (int)$this->request->getData('id');
326
        $this->_ignore($blockedId, false);
327
    }
328
329
    /**
330
     * Mark user as un-/ignored
331
     *
332
     * @param int $blockedId user to ignore
333
     * @param bool $set block or unblock
334
     * @return \Cake\Network\Response
335
     */
336
    protected function _ignore($blockedId, $set)
337
    {
338
        $userId = $this->CurrentUser->getId();
339
        if ((int)$userId === (int)$blockedId) {
340
            throw new BadRequestException();
341
        }
342
        if ($set) {
343
            $this->Users->UserIgnores->ignore($userId, $blockedId);
344
        } else {
345
            $this->Users->UserIgnores->unignore($userId, $blockedId);
346
        }
347
348
        return $this->redirect($this->referer());
349
    }
350
351
    /**
352
     * Show user with profile $name
353
     *
354
     * @param string $name username
355
     * @return void
356
     */
357
    public function name($name = null)
358
    {
359
        if (!empty($name)) {
360
            $viewedUser = $this->Users->find()
361
                ->select(['id'])
362
                ->where(['username' => $name])
363
                ->first();
364
            if (!empty($viewedUser)) {
365
                $this->redirect(
366
                    [
367
                        'controller' => 'users',
368
                        'action' => 'view',
369
                        $viewedUser->get('id')
370
                    ]
371
                );
372
373
                return;
374
            }
375
        }
376
        $this->Flash->set(__('Invalid user'), ['element' => 'error']);
377
        $this->redirect('/');
378
    }
379
380
    /**
381
     * View user profile.
382
     *
383
     * @param null $id user-ID
384
     * @return \Cake\Network\Response|void
385
     */
386
    public function view($id = null)
387
    {
388
        // redirect view/<username> to name/<username>
389
        if (!empty($id) && !is_numeric($id)) {
390
            $this->redirect(
391
                ['controller' => 'users', 'action' => 'name', $id]
392
            );
393
394
            return;
395
        }
396
397
        $id = (int)$id;
398
399
        /** @var User */
400
        $user = $this->Users->find()
401
            ->contain(
402
                [
403
                    'UserBlocks' => function ($q) {
404
                        return $q->find('assocUsers');
405
                    },
406
                    'UserOnline'
407
                ]
408
            )
409
            ->where(['Users.id' => (int)$id])
410
            ->first();
411
412
        if (empty($user)) {
413
            $this->Flash->set(__('Invalid user'), ['element' => 'error']);
414
415
            return $this->redirect('/');
416
        }
417
418
        $entriesShownOnPage = 20;
419
        $this->set(
420
            'lastEntries',
421
            $this->Users->Entries->getRecentEntries(
422
                $this->CurrentUser,
423
                ['user_id' => $id, 'limit' => $entriesShownOnPage]
424
            )
425
        );
426
427
        $this->set(
428
            'hasMoreEntriesThanShownOnPage',
429
            ($user->numberOfPostings() - $entriesShownOnPage) > 0
430
        );
431
432
        if ($this->CurrentUser->getId() === $id) {
433
            $ignores = $this->Users->UserIgnores->getAllIgnoredBy($id);
434
            $user->set('ignores', $ignores);
435
        }
436
437
        $isEditingAllowed = $this->_isEditingAllowed($this->CurrentUser, $id);
438
439
        $blockForm = new BlockForm();
440
        $solved = $this->Users->countSolved($id);
441
        $this->set(compact('blockForm', 'isEditingAllowed', 'solved', 'user'));
442
        $this->set('titleForLayout', $user->get('username'));
443
    }
444
445
    /**
446
     * Set user avatar.
447
     *
448
     * @param string $userId user-ID
449
     * @return void|\Cake\Network\Response
450
     */
451
    public function avatar($userId)
452
    {
453
        $data = [];
454
        if ($this->request->is('post') || $this->request->is('put')) {
455
            $data = [
456
                'avatar' => $this->request->getData('avatar'),
457
                'avatarDelete' => $this->request->getData('avatarDelete')
458
            ];
459
            if (!empty($data['avatarDelete'])) {
460
                $data = [
461
                    'avatar' => null,
462
                    'avatar_dir' => null
463
                ];
464
            }
465
        }
466
        $user = $this->_edit($userId, $data);
467
        if ($user === true) {
468
            return $this->redirect(['action' => 'edit', $userId]);
469
        }
470
471
        $this->set(
472
            'titleForPage',
473
            __('user.avatar.edit.t', [$user->get('username')])
474
        );
475
    }
476
477
    /**
478
     * Edit user.
479
     *
480
     * @param null $id user-ID
481
     *
482
     * @return \Cake\Network\Response|void
483
     */
484
    public function edit($id = null)
485
    {
486
        $data = [];
487
        if ($this->request->is('post') || $this->request->is('put')) {
488
            $data = $this->request->getData();
489
            unset($data['id']);
490
            //= make sure only admin can edit these fields
491
            if (!$this->CurrentUser->permission('saito.core.user.edit')) {
492
                // @td DRY: refactor this admin fields together with view
493
                unset($data['username'], $data['user_email'], $data['user_type']);
494
            }
495
        }
496
        $user = $this->_edit($id, $data);
0 ignored issues
show
Bug introduced by
It seems like $data defined by $this->request->getData() on line 488 can also be of type string; however, App\Controller\UsersController::_edit() does only seem to accept null|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...
497
        if ($user === true) {
498
            return $this->redirect(['action' => 'view', $id]);
499
        }
500
501
        $this->set('user', $user);
502
        $this->set(
503
            'titleForPage',
504
            __('user.edit.t', [$user->get('username')])
505
        );
506
507
        $availableThemes = $this->Themes->getAvailable($this->CurrentUser);
508
        $availableThemes = array_combine($availableThemes, $availableThemes);
509
        $currentTheme = $this->Themes->getThemeForUser($this->CurrentUser);
510
        $this->set(compact('availableThemes', 'currentTheme'));
511
    }
512
513
    /**
514
     * Handle user edit core. Retrieve user or patch if data is passed.
515
     *
516
     * @param string $userId user-ID
517
     * @param array|null $data datat to update the user
518
     *
519
     * @return true|User true on successful save, patched user otherwise
520
     */
521
    protected function _edit($userId, array $data = null)
522
    {
523
        if (!$this->_isEditingAllowed($this->CurrentUser, $userId)) {
524
            throw new \Saito\Exception\SaitoForbiddenException(
525
                "Attempt to edit user $userId.",
526
                ['CurrentUser' => $this->CurrentUser]
527
            );
528
        }
529
        if (!$this->Users->exists($userId)) {
530
            throw new BadRequestException;
531
        }
532
        /** @var User */
533
        $user = $this->Users->get($userId);
534
535
        if ($data) {
536
            /** @var User */
537
            $user = $this->Users->patchEntity($user, $data);
538
            $errors = $user->getErrors();
539
            if (empty($errors) && $this->Users->save($user)) {
540
                return true;
541
            } else {
542
                $this->Flash->set(
543
                    __('The user could not be saved. Please, try again.'),
544
                    ['element' => 'error']
545
                );
546
            }
547
        }
548
        $this->set('user', $user);
549
550
        return $user;
551
    }
552
553
    /**
554
     * Lock user.
555
     *
556
     * @return \Cake\Network\Response|void
557
     * @throws BadRequestException
558
     */
559
    public function lock()
560
    {
561
        $form = new BlockForm();
562
        if (!$this->modLocking || !$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...
563
            throw new BadRequestException;
564
        }
565
566
        $id = (int)$this->request->getData('lockUserId');
567
        if (!$this->Users->exists($id)) {
568
            throw new NotFoundException('User does not exist.', 1524298280);
569
        }
570
        /** @var User */
571
        $readUser = $this->Users->get($id);
572
573
        if ($id === $this->CurrentUser->getId()) {
574
            $message = __('You can\'t lock yourself.');
575
            $this->Flash->set($message, ['element' => 'error']);
576
        } elseif ($readUser->getRole() === 'admin') {
577
            $message = __('You can\'t lock administrators.');
578
            $this->Flash->set($message, ['element' => 'error']);
579
        } else {
580
            try {
581
                $duration = (int)$this->request->getData('lockPeriod');
582
                $blocker = new ManualBlocker($this->CurrentUser->getId(), $duration);
583
                $status = $this->Users->UserBlocks->block($blocker, $id);
584
                if (!$status) {
585
                    throw new \Exception();
586
                }
587
                $message = __('User {0} is locked.', $readUser->get('username'));
588
                $this->Flash->set($message, ['element' => 'success']);
589
            } catch (\Exception $e) {
590
                $message = __('Error while locking.');
591
                $this->Flash->set($message, ['element' => 'error']);
592
            }
593
        }
594
        $this->redirect($this->referer());
595
    }
596
597
    /**
598
     * Unblock user.
599
     *
600
     * @param string $id user-ID
601
     * @return void
602
     */
603
    public function unlock($id)
604
    {
605
        $user = $this->Users->UserBlocks->findById($id)->contain(['Users'])->first();
0 ignored issues
show
Documentation Bug introduced by
The method findById does not exist on object<App\Model\Table\UserBlocksTable>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
606
607
        if (!$id || !$this->modLocking) {
608
            throw new BadRequestException;
609
        }
610
        if (!$this->Users->UserBlocks->unblock($id)) {
611
            $this->Flash->set(
612
                __('Error while unlocking.'),
613
                ['element' => 'error']
614
            );
615
        }
616
617
        $message = __('User {0} is unlocked.', $user->user->get('username'));
618
        $this->Flash->set($message, ['element' => 'success']);
619
        $this->redirect($this->referer());
620
    }
621
622
    /**
623
     * changes user password
624
     *
625
     * @param null $id user-ID
626
     * @return void
627
     * @throws \Saito\Exception\SaitoForbiddenException
628
     * @throws BadRequestException
629
     */
630
    public function changepassword($id = null)
631
    {
632
        if (!$id) {
633
            throw new BadRequestException();
634
        }
635
636
        $user = $this->Users->get($id);
637
        $allowed = $this->_isEditingAllowed($this->CurrentUser, $id);
638
        if (empty($user) || !$allowed) {
639
            throw new SaitoForbiddenException(
640
                "Attempt to change password for user $id.",
641
                ['CurrentUser' => $this->CurrentUser]
642
            );
643
        }
644
        $this->set('userId', $id);
645
        $this->set('username', $user->get('username'));
646
647
        //= just show empty form
648
        if (empty($this->request->getData())) {
649
            return;
650
        }
651
652
        $formFields = ['password', 'password_old', 'password_confirm'];
653
654
        //= process submitted form
655
        $data = [];
656
        foreach ($formFields as $field) {
657
            $data[$field] = $this->request->getData($field);
658
        }
659
        $this->Users->patchEntity($user, $data);
660
        $success = $this->Users->save($user);
661
662
        if ($success) {
663
            $this->Flash->set(
664
                __('change_password_success'),
665
                ['element' => 'success']
666
            );
667
            $this->redirect(['controller' => 'users', 'action' => 'edit', $id]);
668
669
            return;
670
        }
671
672
        $errors = $user->getErrors();
673
        if (!empty($errors)) {
674
            $this->Flash->set(
675
                __d('nondynamic', current(array_pop($errors))),
676
                ['element' => 'error']
677
            );
678
        }
679
680
        //= unset all autofill form data
681
        foreach ($formFields as $field) {
682
            $this->request = $this->request->withoutData($field);
683
        }
684
    }
685
686
    /**
687
     * Directly set password for user
688
     *
689
     * @param string $id user-ID
690
     * @return Response|null
691
     */
692
    public function setpassword($id)
693
    {
694
        if (!$this->CurrentUser->permission('saito.core.user.password.set')) {
695
            throw new SaitoForbiddenException(
696
                "Attempt to set password for user $id.",
697
                ['CurrentUser' => $this->CurrentUser]
698
            );
699
        }
700
701
        $user = $this->Users->get($id);
702
703
        if ($this->getRequest()->is('post')) {
704
            $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...
705
706
            if ($this->Users->save($user)) {
707
                $this->Flash->set(
708
                    __('user.pw.set.s'),
709
                    ['element' => 'success']
710
                );
711
712
                return $this->redirect(['controller' => 'users', 'action' => 'edit', $id]);
713
            }
714
            $errors = $user->getErrors();
715
            if (!empty($errors)) {
716
                $this->Flash->set(
717
                    __d('nondynamic', current(array_pop($errors))),
718
                    ['element' => 'error']
719
                );
720
            }
721
        }
722
723
        $this->set(compact('user'));
724
    }
725
726
    /**
727
     * Set slidetab-order.
728
     *
729
     * @return \Cake\Network\Response
730
     * @throws BadRequestException
731
     */
732
    public function slidetabOrder()
733
    {
734
        if (!$this->request->is('ajax')) {
735
            throw new BadRequestException;
736
        }
737
738
        $order = $this->request->getData('slidetabOrder');
739
        if (!$order) {
740
            throw new BadRequestException;
741
        }
742
743
        $allowed = $this->Slidetabs->getAvailable();
744
        $order = array_filter(
745
            $order,
746
            function ($item) use ($allowed) {
747
                return in_array($item, $allowed);
748
            }
749
        );
750
        $order = serialize($order);
751
752
        $userId = $this->CurrentUser->getId();
753
        $user = $this->Users->get($userId);
754
        $this->Users->patchEntity($user, ['slidetab_order' => $order]);
755
        $this->Users->save($user);
756
757
        $this->CurrentUser->set('slidetab_order', $order);
758
759
        $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...
760
761
        return $this->response;
762
    }
763
764
    /**
765
     * Shows user's uploads
766
     *
767
     * @return void
768
     */
769
    public function uploads()
770
    {
771
    }
772
773
    /**
774
     * Set category for user.
775
     *
776
     * @param string|null $id category-ID
777
     * @return \Cake\Network\Response
778
     */
779
    public function setcategory(?string $id = null)
780
    {
781
        $userId = $this->CurrentUser->getId();
782
        if ($id === 'all') {
783
            $this->Users->setCategory($userId, 'all');
784
        } 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...
785
            $data = $this->request->getData('CatChooser');
786
            $this->Users->setCategory($userId, $data);
787
        } else {
788
            $this->Users->setCategory($userId, $id);
789
        }
790
791
        return $this->redirect($this->referer());
792
    }
793
794
    /**
795
     * {@inheritdoc}
796
     */
797
    public function beforeFilter(Event $event)
798
    {
799
        parent::beforeFilter($event);
800
        Stopwatch::start('Users->beforeFilter()');
801
802
        $unlocked = ['slidetabToggle', 'slidetabOrder'];
803
        $this->Security->setConfig('unlockedActions', $unlocked);
804
805
        $this->Authentication->allowUnauthenticated(['login', 'logout', 'register', 'rs']);
806
        $this->modLocking = $this->CurrentUser
807
            ->permission('saito.core.user.block');
808
        $this->set('modLocking', $this->modLocking);
809
810
        // Login form times-out and degrades user experience.
811
        // See https://github.com/Schlaefer/Saito/issues/339
812
        if (($this->getRequest()->getParam('action') === 'login')
813
            && $this->components()->has('Security')) {
814
            $this->components()->unload('Security');
815
        }
816
817
        Stopwatch::stop('Users->beforeFilter()');
818
    }
819
820
    /**
821
     * Checks if the current user is allowed to edit user $userId
822
     *
823
     * @param CurrentUserInterface $CurrentUser user
824
     * @param int $userId user-ID
825
     * @return bool
826
     */
827
    protected function _isEditingAllowed(CurrentUserInterface $CurrentUser, $userId)
828
    {
829
        if ($CurrentUser->permission('saito.core.user.edit')) {
830
            return true;
831
        }
832
833
        return $CurrentUser->getId() === (int)$userId;
834
    }
835
836
    /**
837
     * Logout user if logged in and create response to revisit logged out
838
     *
839
     * @return Response|null
840
     */
841
    protected function _logoutAndComeHereAgain(): ?Response
842
    {
843
        if (!$this->CurrentUser->isLoggedIn()) {
844
            return null;
845
        }
846
        $this->AuthUser->logout();
847
848
        return $this->redirect($this->getRequest()->getRequestTarget());
849
    }
850
}
851