|
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()); |
|
|
|
|
|
|
198
|
|
|
|
|
199
|
|
|
$user = $this->Users->register($data); |
|
|
|
|
|
|
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) { |
|
|
|
|
|
|
251
|
|
|
throw new BadRequestException(); |
|
252
|
|
|
} |
|
253
|
|
|
$code = $this->request->getQuery('c'); |
|
254
|
|
|
try { |
|
255
|
|
|
$activated = $this->Users->activate((int)$id, $code); |
|
|
|
|
|
|
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 = [ |
|
|
|
|
|
|
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); |
|
|
|
|
|
|
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())) { |
|
|
|
|
|
|
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(); |
|
|
|
|
|
|
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']); |
|
|
|
|
|
|
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); |
|
|
|
|
|
|
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()) { |
|
|
|
|
|
|
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
|
|
|
|
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.