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()); |
|
|
|
|
191
|
|
|
|
192
|
|
|
$user = $this->Users->register($data); |
|
|
|
|
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) { |
|
|
|
|
244
|
|
|
throw new BadRequestException(); |
245
|
|
|
} |
246
|
|
|
$code = $this->request->getQuery('c'); |
247
|
|
|
try { |
248
|
|
|
$activated = $this->Users->activate((int)$id, $code); |
|
|
|
|
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 = [ |
|
|
|
|
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); |
|
|
|
|
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())) { |
|
|
|
|
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']); |
|
|
|
|
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); |
|
|
|
|
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()) { |
|
|
|
|
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
|
|
|
|
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.