Passed
Branch develop (963cc8)
by Schlaefer
09:31
created

UsersController::_edit()   A

Complexity

Conditions 6
Paths 5

Size

Total Lines 30
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 18
nc 5
nop 2
dl 0
loc 30
rs 9.0444
c 1
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 Saito\Exception\Logger\ExceptionLogger;
24
use Saito\Exception\Logger\ForbiddenLogger;
25
use Saito\Exception\SaitoForbiddenException;
26
use Saito\User\Blocker\ManualBlocker;
27
use Saito\User\CurrentUser\CurrentUserInterface;
28
use Siezi\SimpleCaptcha\Model\Validation\SimpleCaptchaValidator;
29
use Stopwatch\Lib\Stopwatch;
30
31
/**
32
 * User controller
33
 */
34
class UsersController extends AppController
35
{
36
    public $helpers = [
37
        'SpectrumColorpicker.SpectrumColorpicker',
38
        'Posting',
39
        'Siezi/SimpleCaptcha.SimpleCaptcha',
40
        'Text'
41
    ];
42
43
    /**
44
     * Are moderators allowed to bloack users
45
     *
46
     * @var bool
47
     */
48
    protected $modLocking = false;
49
50
    /**
51
     * {@inheritDoc}
52
     */
53
    public function initialize()
54
    {
55
        parent::initialize();
56
        $this->loadComponent('Referer');
57
    }
58
59
    /**
60
     * Login user.
61
     *
62
     * @return void|Response
63
     */
64
    public function login()
65
    {
66
        $data = $this->request->getData();
67
        if (empty($data['username'])) {
68
            $logout = $this->_logoutAndComeHereAgain();
69
            if ($logout) {
70
                return $logout;
71
            }
72
73
            /// Show form to user.
74
            if ($this->getRequest()->getQuery('redirect', null)) {
75
                $this->Flash->set(
76
                    __('user.authe.required.exp'),
77
                    ['element' => 'warning', 'params' => ['title' => __('user.authe.required.t')]]
78
                );
79
            };
80
81
            return;
82
        }
83
84
        if ($this->AuthUser->login()) {
85
            // Redirect query-param in URL.
86
            $target = $this->getRequest()->getQuery('redirect');
87
            // Referer from Request
88
            $target = $target ?: $this->referer(null, true);
89
90
            if (!$target || $this->Referer->wasAction('login')) {
91
                $target = '/';
92
            }
93
94
            return $this->redirect($target);
95
        }
96
97
        /// error on login
98
        $username = $this->request->getData('username');
99
        /** @var User */
100
        $readUser = $this->Users->find()
101
            ->where(['username' => $username])
102
            ->first();
103
104
        $message = __('user.authe.e.generic');
105
106
        if (!empty($readUser)) {
107
            $User = $readUser->toSaitoUser();
0 ignored issues
show
Bug introduced by
The method toSaitoUser() does not exist on Cake\Datasource\EntityInterface. It seems like you code against a sub-type of Cake\Datasource\EntityInterface such as App\Model\Entity\User. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

107
            /** @scrutinizer ignore-call */ 
108
            $User = $readUser->toSaitoUser();
Loading history...
108
109
            if (!$User->isActivated()) {
110
                $message = __('user.actv.ny');
111
            } elseif ($User->isLocked()) {
112
                $ends = $this->Users->UserBlocks
113
                    ->getBlockEndsForUser($User->getId());
114
                if ($ends) {
115
                    $time = new Time($ends);
116
                    $data = [
117
                        'name' => $username,
118
                        'end' => $time->timeAgoInWords(['accuracy' => 'hour'])
119
                    ];
120
                    $message = __('user.block.pubExpEnds', $data);
121
                } else {
122
                    $message = __('user.block.pubExp', $username);
123
                }
124
            }
125
        }
126
127
        // don't autofill password
128
        $this->setRequest($this->getRequest()->withData('password', ''));
129
130
        $Logger = new ForbiddenLogger;
131
        $Logger->write(
132
            "Unsuccessful login for user: $username",
133
            ['msgs' => [$message]]
134
        );
135
136
        $this->Flash->set($message, [
137
            'element' => 'error', 'params' => ['title' => __('user.authe.e.t')]
138
        ]);
139
    }
140
141
    /**
142
     * Logout user.
143
     *
144
     * @return void|Response
145
     */
146
    public function logout()
147
    {
148
        $request = $this->getRequest();
149
        $cookies = $request->getCookieCollection();
150
        foreach ($cookies as $cookie) {
151
            $cookie = $cookie->withPath($request->getAttribute('webroot'));
152
            $this->setResponse($this->getResponse()->withExpiredCookie($cookie));
153
        }
154
155
        $this->AuthUser->logout();
156
        $this->redirect('/');
157
    }
158
159
    /**
160
     * Register new user.
161
     *
162
     * @return void|Response
163
     */
164
    public function register()
165
    {
166
        $this->set('status', 'view');
167
168
        $this->AuthUser->logout();
169
170
        $tosRequired = Configure::read('Saito.Settings.tos_enabled');
171
        $this->set(compact('tosRequired'));
172
173
        $user = $this->Users->newEntity();
174
        $this->set('user', $user);
175
176
        if (!$this->request->is('post')) {
177
            $logout = $this->_logoutAndComeHereAgain();
178
            if ($logout) {
179
                return $logout;
180
            }
181
182
            return;
183
        }
184
185
        $data = $this->request->getData();
186
187
        if (!$tosRequired) {
188
            $data['tos_confirm'] = true;
189
        }
190
        $tosConfirmed = $data['tos_confirm'];
191
        if (!$tosConfirmed) {
192
            return;
193
        }
194
195
        $validator = new SimpleCaptchaValidator();
196
        $errors = $validator->errors($this->request->getData());
0 ignored issues
show
Bug introduced by
It seems like $this->request->getData() can also be of type null; however, parameter $data of Cake\Validation\Validator::errors() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

196
        $errors = $validator->errors(/** @scrutinizer ignore-type */ $this->request->getData());
Loading history...
197
198
        $user = $this->Users->register($data);
199
        $user->setErrors($errors);
200
201
        $errors = $user->getErrors();
202
        if (!empty($errors)) {
203
            // registering failed, show form again
204
            if (isset($errors['password'])) {
205
                $user->setErrors($errors);
206
            }
207
            $user->set('tos_confirm', false);
208
            $this->set('user', $user);
209
210
            return;
211
        }
212
213
        // registered successfully
214
        try {
215
            $forumName = Configure::read('Saito.Settings.forum_name');
216
            $subject = __('register_email_subject', $forumName);
217
            $this->SaitoEmail->email(
218
                [
219
                    'recipient' => $user,
220
                    'subject' => $subject,
221
                    'sender' => 'register',
222
                    'template' => 'user_register',
223
                    'viewVars' => ['user' => $user]
224
                ]
225
            );
226
        } catch (\Exception $e) {
227
            $Logger = new ExceptionLogger();
228
            $Logger->write(
229
                'Registering email confirmation failed',
230
                ['e' => $e]
231
            );
232
            $this->set('status', 'fail: email');
233
234
            return;
235
        }
236
237
        $this->set('status', 'success');
238
    }
239
240
    /**
241
     * register success (user clicked link in confirm mail)
242
     *
243
     * @param string $id user-ID
244
     * @return void
245
     * @throws BadRequestException
246
     */
247
    public function rs($id = null)
248
    {
249
        if (!$id) {
250
            throw new BadRequestException();
251
        }
252
        $code = $this->request->getQuery('c');
253
        try {
254
            $activated = $this->Users->activate((int)$id, $code);
0 ignored issues
show
Bug introduced by
It seems like $code can also be of type array and null; however, parameter $code of App\Model\Table\UsersTable::activate() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

254
            $activated = $this->Users->activate((int)$id, /** @scrutinizer ignore-type */ $code);
Loading history...
255
        } catch (\Exception $e) {
256
            $activated = false;
257
        }
258
        if (!$activated) {
259
            $activated = ['status' => 'fail'];
260
        }
261
        $this->set('status', $activated['status']);
262
    }
263
264
    /**
265
     * Show list of all users.
266
     *
267
     * @return void
268
     */
269
    public function index()
270
    {
271
        $menuItems = [
272
            'username' => [__('username_marking'), []],
273
            'user_type' => [__('user_type'), []],
274
            'UserOnline.logged_in' => [
275
                __('userlist_online'),
276
                ['direction' => 'desc']
277
            ],
278
            'registered' => [__('registered'), ['direction' => 'desc']]
279
        ];
280
        $showBlocked = Configure::read('Saito.Settings.block_user_ui');
281
        if ($showBlocked) {
282
            $menuItems['user_lock'] = [
283
                __('user.set.lock.t'),
284
                ['direction' => 'desc']
285
            ];
286
        }
287
288
        $this->paginate = $options = [
0 ignored issues
show
Unused Code introduced by
The assignment to $options is dead and can be removed.
Loading history...
289
            'contain' => ['UserOnline'],
290
            'sortWhitelist' => array_keys($menuItems),
291
            'finder' => 'paginated',
292
            'limit' => 400,
293
            'order' => [
294
                'UserOnline.logged_in' => 'desc',
295
            ]
296
        ];
297
        $users = $this->paginate($this->Users);
298
299
        $showBottomNavigation = true;
300
301
        $this->set(compact('menuItems', 'showBottomNavigation', 'users'));
302
    }
303
304
    /**
305
     * Ignore user.
306
     *
307
     * @return void
308
     */
309
    public function ignore()
310
    {
311
        $this->request->allowMethod('POST');
312
        $blockedId = (int)$this->request->getData('id');
313
        $this->_ignore($blockedId, true);
314
    }
315
316
    /**
317
     * Unignore user.
318
     *
319
     * @return void
320
     */
321
    public function unignore()
322
    {
323
        $this->request->allowMethod('POST');
324
        $blockedId = (int)$this->request->getData('id');
325
        $this->_ignore($blockedId, false);
326
    }
327
328
    /**
329
     * Mark user as un-/ignored
330
     *
331
     * @param int $blockedId user to ignore
332
     * @param bool $set block or unblock
333
     * @return \Cake\Network\Response
334
     */
335
    protected function _ignore($blockedId, $set)
336
    {
337
        $userId = $this->CurrentUser->getId();
338
        if ((int)$userId === (int)$blockedId) {
339
            throw new BadRequestException();
340
        }
341
        if ($set) {
342
            $this->Users->UserIgnores->ignore($userId, $blockedId);
343
        } else {
344
            $this->Users->UserIgnores->unignore($userId, $blockedId);
345
        }
346
347
        return $this->redirect($this->referer());
348
    }
349
350
    /**
351
     * Show user with profile $name
352
     *
353
     * @param string $name username
354
     * @return void
355
     */
356
    public function name($name = null)
357
    {
358
        if (!empty($name)) {
359
            $viewedUser = $this->Users->find()
360
                ->select(['id'])
361
                ->where(['username' => $name])
362
                ->first();
363
            if (!empty($viewedUser)) {
364
                $this->redirect(
365
                    [
366
                        'controller' => 'users',
367
                        'action' => 'view',
368
                        $viewedUser->get('id')
369
                    ]
370
                );
371
372
                return;
373
            }
374
        }
375
        $this->Flash->set(__('Invalid user'), ['element' => 'error']);
376
        $this->redirect('/');
377
    }
378
379
    /**
380
     * View user profile.
381
     *
382
     * @param null $id user-ID
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $id is correct as it would always require null to be passed?
Loading history...
383
     * @return \Cake\Network\Response|void
384
     */
385
    public function view($id = null)
386
    {
387
        // redirect view/<username> to name/<username>
388
        if (!empty($id) && !is_numeric($id)) {
389
            $this->redirect(
390
                ['controller' => 'users', 'action' => 'name', $id]
391
            );
392
393
            return;
394
        }
395
396
        $id = (int)$id;
397
398
        /** @var User */
399
        $user = $this->Users->find()
400
            ->contain(
401
                [
402
                    'UserBlocks' => function ($q) {
403
                        return $q->find('assocUsers');
404
                    },
405
                    'UserOnline'
406
                ]
407
            )
408
            ->where(['Users.id' => (int)$id])
409
            ->first();
410
411
        if (empty($user)) {
412
            $this->Flash->set(__('Invalid user'), ['element' => 'error']);
413
414
            return $this->redirect('/');
415
        }
416
417
        $entriesShownOnPage = 20;
418
        $this->set(
419
            'lastEntries',
420
            $this->Users->Entries->getRecentEntries(
421
                $this->CurrentUser,
422
                ['user_id' => $id, 'limit' => $entriesShownOnPage]
423
            )
424
        );
425
426
        $this->set(
427
            'hasMoreEntriesThanShownOnPage',
428
            ($user->numberOfPostings() - $entriesShownOnPage) > 0
0 ignored issues
show
Bug introduced by
The method numberOfPostings() does not exist on Cake\Datasource\EntityInterface. It seems like you code against a sub-type of Cake\Datasource\EntityInterface such as App\Model\Entity\User. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

428
            ($user->/** @scrutinizer ignore-call */ numberOfPostings() - $entriesShownOnPage) > 0
Loading history...
429
        );
430
431
        if ($this->CurrentUser->getId() === $id) {
432
            $ignores = $this->Users->UserIgnores->getAllIgnoredBy($id);
433
            $user->set('ignores', $ignores);
434
        }
435
436
        $isEditingAllowed = $this->_isEditingAllowed($this->CurrentUser, $id);
437
438
        $blockForm = new BlockForm();
439
        $solved = $this->Users->countSolved($id);
440
        $this->set(compact('blockForm', 'isEditingAllowed', 'solved', 'user'));
441
        $this->set('titleForLayout', $user->get('username'));
442
    }
443
444
    /**
445
     * Set user avatar.
446
     *
447
     * @param string $userId user-ID
448
     * @return void|\Cake\Network\Response
449
     */
450
    public function avatar($userId)
451
    {
452
        $data = [];
453
        if ($this->request->is('post') || $this->request->is('put')) {
454
            $data = [
455
                'avatar' => $this->request->getData('avatar'),
456
                'avatarDelete' => $this->request->getData('avatarDelete')
457
            ];
458
            if (!empty($data['avatarDelete'])) {
459
                $data = [
460
                    'avatar' => null,
461
                    'avatar_dir' => null
462
                ];
463
            }
464
        }
465
        $user = $this->_edit($userId, $data);
466
        if ($user === true) {
0 ignored issues
show
introduced by
The condition $user === true is always false.
Loading history...
467
            return $this->redirect(['action' => 'edit', $userId]);
468
        }
469
470
        $this->set(
471
            'titleForPage',
472
            __('user.avatar.edit.t', [$user->get('username')])
473
        );
474
    }
475
476
    /**
477
     * Edit user.
478
     *
479
     * @param null $id user-ID
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $id is correct as it would always require null to be passed?
Loading history...
480
     *
481
     * @return \Cake\Network\Response|void
482
     */
483
    public function edit($id = null)
484
    {
485
        $data = [];
486
        if ($this->request->is('post') || $this->request->is('put')) {
487
            $data = $this->request->getData();
488
            unset($data['id']);
489
            //= make sure only admin can edit these fields
490
            if (!$this->CurrentUser->permission('saito.core.user.edit')) {
491
                // @td DRY: refactor this admin fields together with view
492
                unset($data['username'], $data['user_email'], $data['user_type']);
493
            }
494
        }
495
        $user = $this->_edit($id, $data);
496
        if ($user === true) {
0 ignored issues
show
introduced by
The condition $user === true is always false.
Loading history...
497
            return $this->redirect(['action' => 'view', $id]);
498
        }
499
500
        $this->set('user', $user);
501
        $this->set(
502
            'titleForPage',
503
            __('user.edit.t', [$user->get('username')])
504
        );
505
506
        $availableThemes = $this->Themes->getAvailable($this->CurrentUser);
507
        $availableThemes = array_combine($availableThemes, $availableThemes);
508
        $currentTheme = $this->Themes->getThemeForUser($this->CurrentUser);
509
        $this->set(compact('availableThemes', 'currentTheme'));
510
    }
511
512
    /**
513
     * Handle user edit core. Retrieve user or patch if data is passed.
514
     *
515
     * @param string $userId user-ID
516
     * @param array|null $data datat to update the user
517
     *
518
     * @return true|User true on successful save, patched user otherwise
519
     */
520
    protected function _edit($userId, array $data = null)
521
    {
522
        if (!$this->_isEditingAllowed($this->CurrentUser, $userId)) {
0 ignored issues
show
Bug introduced by
$userId of type string is incompatible with the type integer expected by parameter $userId of App\Controller\UsersCont...er::_isEditingAllowed(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

522
        if (!$this->_isEditingAllowed($this->CurrentUser, /** @scrutinizer ignore-type */ $userId)) {
Loading history...
523
            throw new \Saito\Exception\SaitoForbiddenException(
524
                "Attempt to edit user $userId.",
525
                ['CurrentUser' => $this->CurrentUser]
526
            );
527
        }
528
        if (!$this->Users->exists($userId)) {
0 ignored issues
show
Bug introduced by
$userId of type string is incompatible with the type ArrayAccess|array|integer expected by parameter $conditions of App\Lib\Model\Table\AppTable::exists(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

528
        if (!$this->Users->exists(/** @scrutinizer ignore-type */ $userId)) {
Loading history...
529
            throw new BadRequestException;
530
        }
531
        /** @var User */
532
        $user = $this->Users->get($userId);
533
534
        if ($data) {
535
            /** @var User */
536
            $user = $this->Users->patchEntity($user, $data);
537
            $errors = $user->getErrors();
538
            if (empty($errors) && $this->Users->save($user)) {
539
                return true;
540
            } else {
541
                $this->Flash->set(
542
                    __('The user could not be saved. Please, try again.'),
543
                    ['element' => 'error']
544
                );
545
            }
546
        }
547
        $this->set('user', $user);
548
549
        return $user;
550
    }
551
552
    /**
553
     * Lock user.
554
     *
555
     * @return \Cake\Network\Response|void
556
     * @throws BadRequestException
557
     */
558
    public function lock()
559
    {
560
        $form = new BlockForm();
561
        if (!$this->modLocking || !$form->validate($this->request->getData())) {
0 ignored issues
show
Bug introduced by
It seems like $this->request->getData() can also be of type null; however, parameter $data of Cake\Form\Form::validate() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

561
        if (!$this->modLocking || !$form->validate(/** @scrutinizer ignore-type */ $this->request->getData())) {
Loading history...
562
            throw new BadRequestException;
563
        }
564
565
        $id = (int)$this->request->getData('lockUserId');
566
        if (!$this->Users->exists($id)) {
567
            throw new NotFoundException('User does not exist.', 1524298280);
568
        }
569
        /** @var User */
570
        $readUser = $this->Users->get($id);
571
572
        if ($id === $this->CurrentUser->getId()) {
573
            $message = __('You can\'t lock yourself.');
574
            $this->Flash->set($message, ['element' => 'error']);
575
        } elseif ($readUser->getRole() === 'admin') {
0 ignored issues
show
Bug introduced by
The method getRole() does not exist on Cake\Datasource\EntityInterface. It seems like you code against a sub-type of Cake\Datasource\EntityInterface such as App\Model\Entity\User. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

575
        } elseif ($readUser->/** @scrutinizer ignore-call */ getRole() === 'admin') {
Loading history...
576
            $message = __('You can\'t lock administrators.');
577
            $this->Flash->set($message, ['element' => 'error']);
578
        } else {
579
            try {
580
                $duration = (int)$this->request->getData('lockPeriod');
581
                $blocker = new ManualBlocker($this->CurrentUser->getId(), $duration);
582
                $status = $this->Users->UserBlocks->block($blocker, $id);
583
                if (!$status) {
584
                    throw new \Exception();
585
                }
586
                $message = __('User {0} is locked.', $readUser->get('username'));
587
                $this->Flash->set($message, ['element' => 'success']);
588
            } catch (\Exception $e) {
589
                $message = __('Error while locking.');
590
                $this->Flash->set($message, ['element' => 'error']);
591
            }
592
        }
593
        $this->redirect($this->referer());
594
    }
595
596
    /**
597
     * Unblock user.
598
     *
599
     * @param string $id user-ID
600
     * @return void
601
     */
602
    public function unlock($id)
603
    {
604
        $user = $this->Users->UserBlocks->findById($id)->contain(['Users'])->first();
0 ignored issues
show
Bug introduced by
The method findById() does not exist on App\Model\Table\UserBlocksTable. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

604
        $user = $this->Users->UserBlocks->/** @scrutinizer ignore-call */ findById($id)->contain(['Users'])->first();
Loading history...
605
606
        if (!$id || !$this->modLocking) {
607
            throw new BadRequestException;
608
        }
609
        if (!$this->Users->UserBlocks->unblock($id)) {
0 ignored issues
show
Bug introduced by
$id of type string is incompatible with the type integer expected by parameter $id of App\Model\Table\UserBlocksTable::unblock(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

609
        if (!$this->Users->UserBlocks->unblock(/** @scrutinizer ignore-type */ $id)) {
Loading history...
610
            $this->Flash->set(
611
                __('Error while unlocking.'),
612
                ['element' => 'error']
613
            );
614
        }
615
616
        $message = __('User {0} is unlocked.', $user->user->get('username'));
0 ignored issues
show
Bug introduced by
Accessing user on the interface Cake\Datasource\EntityInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
617
        $this->Flash->set($message, ['element' => 'success']);
618
        $this->redirect($this->referer());
619
    }
620
621
    /**
622
     * changes user password
623
     *
624
     * @param null $id user-ID
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $id is correct as it would always require null to be passed?
Loading history...
625
     * @return void
626
     * @throws \Saito\Exception\SaitoForbiddenException
627
     * @throws BadRequestException
628
     */
629
    public function changepassword($id = null)
630
    {
631
        if (empty($id)) {
632
            throw new BadRequestException();
633
        }
634
635
        $user = $this->Users->get($id);
636
        $allowed = $this->_isEditingAllowed($this->CurrentUser, $id);
637
        if (empty($user) || !$allowed) {
638
            throw new SaitoForbiddenException(
639
                "Attempt to change password for user $id.",
640
                ['CurrentUser' => $this->CurrentUser]
641
            );
642
        }
643
        $this->set('userId', $id);
644
        $this->set('username', $user->get('username'));
645
646
        //= just show empty form
647
        if (empty($this->request->getData())) {
648
            return;
649
        }
650
651
        $formFields = ['password', 'password_old', 'password_confirm'];
652
653
        //= process submitted form
654
        $data = [];
655
        foreach ($formFields as $field) {
656
            $data[$field] = $this->request->getData($field);
657
        }
658
        $this->Users->patchEntity($user, $data);
659
        $success = $this->Users->save($user);
660
661
        if ($success) {
662
            $this->Flash->set(
663
                __('change_password_success'),
664
                ['element' => 'success']
665
            );
666
            $this->redirect(['controller' => 'users', 'action' => 'edit', $id]);
667
668
            return;
669
        }
670
671
        $errors = $user->getErrors();
672
        if (!empty($errors)) {
673
            $this->Flash->set(
674
                __d('nondynamic', current(array_pop($errors))),
675
                ['element' => 'error']
676
            );
677
        }
678
679
        //= unset all autofill form data
680
        foreach ($formFields as $field) {
681
            $this->request = $this->request->withoutData($field);
682
        }
683
    }
684
685
    /**
686
     * Directly set password for user
687
     *
688
     * @param string $id user-ID
689
     * @return Response|null
690
     */
691
    public function setpassword($id)
692
    {
693
        if (!$this->CurrentUser->permission('saito.core.user.password.set')) {
694
            throw new SaitoForbiddenException(
695
                "Attempt to set password for user $id.",
696
                ['CurrentUser' => $this->CurrentUser]
697
            );
698
        }
699
700
        $user = $this->Users->get($id);
701
702
        if ($this->getRequest()->is('post')) {
703
            $this->Users->patchEntity($user, $this->getRequest()->getData(), ['fields' => 'password']);
0 ignored issues
show
Bug introduced by
It seems like $this->getRequest()->getData() can also be of type null; however, parameter $data of Cake\ORM\Table::patchEntity() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

703
            $this->Users->patchEntity($user, /** @scrutinizer ignore-type */ $this->getRequest()->getData(), ['fields' => 'password']);
Loading history...
704
705
            if ($this->Users->save($user)) {
706
                $this->Flash->set(
707
                    __('user.pw.set.s'),
708
                    ['element' => 'success']
709
                );
710
711
                return $this->redirect(['controller' => 'users', 'action' => 'edit', $id]);
712
            }
713
            $errors = $user->getErrors();
714
            if (!empty($errors)) {
715
                $this->Flash->set(
716
                    __d('nondynamic', current(array_pop($errors))),
717
                    ['element' => 'error']
718
                );
719
            }
720
        }
721
722
        $this->set(compact('user'));
723
    }
724
725
    /**
726
     * Set slidetab-order.
727
     *
728
     * @return \Cake\Network\Response
729
     * @throws BadRequestException
730
     */
731
    public function slidetabOrder()
732
    {
733
        if (!$this->request->is('ajax')) {
734
            throw new BadRequestException;
735
        }
736
737
        $order = $this->request->getData('slidetabOrder');
738
        if (!$order) {
739
            throw new BadRequestException;
740
        }
741
742
        $allowed = $this->Slidetabs->getAvailable();
743
        $order = array_filter(
744
            $order,
0 ignored issues
show
Bug introduced by
It seems like $order can also be of type string; however, parameter $input of array_filter() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

744
            /** @scrutinizer ignore-type */ $order,
Loading history...
745
            function ($item) use ($allowed) {
746
                return in_array($item, $allowed);
747
            }
748
        );
749
        $order = serialize($order);
750
751
        $userId = $this->CurrentUser->getId();
752
        $user = $this->Users->get($userId);
753
        $this->Users->patchEntity($user, ['slidetab_order' => $order]);
754
        $this->Users->save($user);
755
756
        $this->CurrentUser->set('slidetab_order', $order);
757
758
        $this->response = $this->response->withStringBody(true);
0 ignored issues
show
Bug introduced by
true of type true is incompatible with the type string expected by parameter $string of Cake\Http\Response::withStringBody(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

758
        $this->response = $this->response->withStringBody(/** @scrutinizer ignore-type */ true);
Loading history...
759
760
        return $this->response;
761
    }
762
763
    /**
764
     * Shows user's uploads
765
     *
766
     * @return void
767
     */
768
    public function uploads()
769
    {
770
    }
771
772
    /**
773
     * Set category for user.
774
     *
775
     * @param string|null $id category-ID
776
     * @return \Cake\Network\Response
777
     */
778
    public function setcategory(?string $id = null)
779
    {
780
        $userId = $this->CurrentUser->getId();
781
        if ($id === 'all') {
782
            $this->Users->setCategory($userId, 'all');
783
        } elseif (!$id && $this->request->getData()) {
784
            $data = $this->request->getData('CatChooser');
785
            $this->Users->setCategory($userId, $data);
786
        } else {
787
            $this->Users->setCategory($userId, $id);
788
        }
789
790
        return $this->redirect($this->referer());
791
    }
792
793
    /**
794
     * {@inheritdoc}
795
     */
796
    public function beforeFilter(Event $event)
797
    {
798
        parent::beforeFilter($event);
799
        Stopwatch::start('Users->beforeFilter()');
800
801
        $unlocked = ['slidetabToggle', 'slidetabOrder'];
802
        $this->Security->setConfig('unlockedActions', $unlocked);
803
804
        $this->Authentication->allowUnauthenticated(['login', 'logout', 'register', 'rs']);
805
        $this->modLocking = $this->CurrentUser
806
            ->permission('saito.core.user.block');
807
        $this->set('modLocking', $this->modLocking);
808
809
        // Login form times-out and degrades user experience.
810
        // See https://github.com/Schlaefer/Saito/issues/339
811
        if (($this->getRequest()->getParam('action') === 'login')
812
            && $this->components()->has('Security')) {
813
            $this->components()->unload('Security');
814
        }
815
816
        Stopwatch::stop('Users->beforeFilter()');
817
    }
818
819
    /**
820
     * Checks if the current user is allowed to edit user $userId
821
     *
822
     * @param CurrentUserInterface $CurrentUser user
823
     * @param int $userId user-ID
824
     * @return bool
825
     */
826
    protected function _isEditingAllowed(CurrentUserInterface $CurrentUser, $userId)
827
    {
828
        if ($CurrentUser->permission('saito.core.user.edit')) {
829
            return true;
830
        }
831
832
        return $CurrentUser->getId() === (int)$userId;
833
    }
834
835
    /**
836
     * Logout user if logged in and create response to revisit logged out
837
     *
838
     * @return Response|null
839
     */
840
    protected function _logoutAndComeHereAgain(): ?Response
841
    {
842
        if (!$this->CurrentUser->isLoggedIn()) {
843
            return null;
844
        }
845
        $this->AuthUser->logout();
846
847
        return $this->redirect($this->getRequest()->getRequestTarget());
848
    }
849
}
850