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\Model\Table; |
||||||
14 | |||||||
15 | use App\Lib\Model\Table\AppTable; |
||||||
16 | use App\Lib\Model\Table\FieldFilter; |
||||||
17 | use App\Model\Table\EntriesTable; |
||||||
18 | use App\Model\Table\UserBlocksTable; |
||||||
19 | use App\Model\Table\UserIgnoresTable; |
||||||
20 | use Authentication\PasswordHasher\DefaultPasswordHasher; |
||||||
21 | use Authentication\PasswordHasher\PasswordHasherFactory; |
||||||
22 | use Authentication\PasswordHasher\PasswordHasherInterface; |
||||||
23 | use Cake\Core\Configure; |
||||||
24 | use Cake\Database\Schema\TableSchema; |
||||||
25 | use Cake\Datasource\EntityInterface; |
||||||
26 | use Cake\Datasource\Exception\RecordNotFoundException; |
||||||
27 | use Cake\Event\Event; |
||||||
28 | use Cake\ORM\Entity; |
||||||
29 | use Cake\ORM\Query; |
||||||
30 | use Cake\ORM\TableRegistry; |
||||||
31 | use Cake\Validation\Validation; |
||||||
32 | use Cake\Validation\Validator; |
||||||
33 | use DateTimeInterface; |
||||||
34 | use Saito\App\Registry; |
||||||
35 | use Saito\User\Permission\Permissions; |
||||||
36 | use Saito\User\Upload\AvatarFilenameListener; |
||||||
37 | use Stopwatch\Lib\Stopwatch; |
||||||
38 | |||||||
39 | /** |
||||||
40 | * Users table |
||||||
41 | * |
||||||
42 | * @property EntriesTable $Entries |
||||||
43 | * @property UserBlocksTable $UserBlocks |
||||||
44 | * @property UserIgnoresTable $UserIgnores |
||||||
45 | * @property UserOnlineTable $UserOnline |
||||||
46 | */ |
||||||
47 | class UsersTable extends AppTable |
||||||
48 | { |
||||||
49 | /** |
||||||
50 | * Max lenght for username. |
||||||
51 | * |
||||||
52 | * Constrained to 191 due to InnoDB index max-length on MySQL 5.6. |
||||||
53 | */ |
||||||
54 | public const USERNAME_MAXLENGTH = 191; |
||||||
55 | |||||||
56 | /** |
||||||
57 | * {@inheritDoc} |
||||||
58 | */ |
||||||
59 | protected $_defaultConfig = [ |
||||||
60 | 'user_name_disallowed_chars' => ['\'', ';', '&', '<', '>'], |
||||||
61 | ]; |
||||||
62 | |||||||
63 | /** |
||||||
64 | * {@inheritDoc} |
||||||
65 | */ |
||||||
66 | public function initialize(array $config) |
||||||
67 | { |
||||||
68 | $this->addBehavior( |
||||||
69 | 'Cron.Cron', |
||||||
70 | [ |
||||||
71 | 'registerGc' => [ |
||||||
72 | 'id' => 'User.registerGc', |
||||||
73 | 'due' => '+1 day', |
||||||
74 | ], |
||||||
75 | 'userBlockGc' => [ |
||||||
76 | 'id' => 'User.userBlockGc', |
||||||
77 | 'due' => '+15 minutes', |
||||||
78 | ], |
||||||
79 | ] |
||||||
80 | ); |
||||||
81 | |||||||
82 | $avatarRootDir = Configure::read('Saito.Settings.uploadDirectory'); |
||||||
83 | $this->addBehavior( |
||||||
84 | 'Proffer.Proffer', |
||||||
85 | [ |
||||||
86 | 'avatar' => [ // The name of your upload field (filename) |
||||||
87 | 'root' => $avatarRootDir, |
||||||
88 | 'dir' => 'avatar_dir', // field for upload directory |
||||||
89 | 'thumbnailSizes' => [ |
||||||
90 | 'square' => ['w' => 100, 'h' => 100], |
||||||
91 | ], |
||||||
92 | // Options are Imagick, Gd or Gmagick |
||||||
93 | 'thumbnailMethod' => 'Gd', |
||||||
94 | ], |
||||||
95 | ] |
||||||
96 | ); |
||||||
97 | $this->getEventManager()->on(new AvatarFilenameListener($avatarRootDir)); |
||||||
98 | |||||||
99 | $this->hasOne( |
||||||
100 | 'UserOnline', |
||||||
101 | ['dependent' => true, 'foreignKey' => 'user_id'] |
||||||
102 | ); |
||||||
103 | |||||||
104 | $this->hasMany( |
||||||
105 | 'Bookmarks', |
||||||
106 | ['foreignKey' => 'user_id', 'dependent' => true] |
||||||
107 | ); |
||||||
108 | $this->hasMany('Drafts', ['dependent' => true]); |
||||||
109 | $this->hasMany('UserIgnores', ['foreignKey' => 'user_id']); |
||||||
110 | $this->hasMany( |
||||||
111 | 'Entries', |
||||||
112 | [ |
||||||
113 | 'foreignKey' => 'user_id', |
||||||
114 | 'conditions' => ['Entries.user_id' => 'Users.id'], |
||||||
115 | ] |
||||||
116 | ); |
||||||
117 | $this->hasMany( |
||||||
118 | 'ImageUploader.Uploads', |
||||||
119 | ['dependent' => true, 'foreignKey' => 'user_id'] |
||||||
120 | ); |
||||||
121 | $this->hasMany( |
||||||
122 | 'UserReads', |
||||||
123 | ['foreignKey' => 'user_id', 'dependent' => true] |
||||||
124 | ); |
||||||
125 | $this->hasMany( |
||||||
126 | 'UserBlocks', |
||||||
127 | [ |
||||||
128 | 'foreignKey' => 'user_id', |
||||||
129 | 'dependent' => true, |
||||||
130 | 'sort' => [ |
||||||
131 | 'UserBlocks.ended IS NULL DESC', |
||||||
132 | 'UserBlocks.ended' => 'DESC', |
||||||
133 | 'UserBlocks.id' => 'DESC', |
||||||
134 | ], |
||||||
135 | ] |
||||||
136 | ); |
||||||
137 | } |
||||||
138 | |||||||
139 | /** |
||||||
140 | * {@inheritDoc} |
||||||
141 | */ |
||||||
142 | public function validationDefault(Validator $validator) |
||||||
143 | { |
||||||
144 | $validator->setProvider( |
||||||
145 | 'saito', |
||||||
146 | 'Saito\Validation\SaitoValidationProvider' |
||||||
147 | ); |
||||||
148 | |||||||
149 | $validator |
||||||
0 ignored issues
–
show
|
|||||||
150 | ->setProvider( |
||||||
151 | 'proffer', |
||||||
152 | 'Proffer\Model\Validation\ProfferRules' |
||||||
153 | ) |
||||||
154 | ->allowEmpty('avatar_dir') |
||||||
155 | ->allowEmpty('avatar') |
||||||
156 | ->add( |
||||||
157 | 'avatar', |
||||||
158 | 'avatar-extension', |
||||||
159 | [ |
||||||
160 | 'rule' => ['extension', ['jpg', 'jpeg', 'png']], |
||||||
161 | 'message' => __('user.avatar.error.extension', ['jpg, jpeg, png']), |
||||||
162 | ] |
||||||
163 | ) |
||||||
164 | ->add( |
||||||
165 | 'avatar', |
||||||
166 | 'avatar-size', |
||||||
167 | [ |
||||||
168 | 'rule' => ['fileSize', Validation::COMPARE_LESS, '3MB'], |
||||||
169 | 'message' => __('user.avatar.error.size', ['3']), |
||||||
170 | ] |
||||||
171 | ) |
||||||
172 | ->add( |
||||||
173 | 'avatar', |
||||||
174 | 'avatar-mime', |
||||||
175 | [ |
||||||
176 | 'rule' => ['mimetype', ['image/jpeg', 'image/png']], |
||||||
177 | 'message' => __('user.avatar.error.mime'), |
||||||
178 | ] |
||||||
179 | ) |
||||||
180 | ->add( |
||||||
181 | 'avatar', |
||||||
182 | 'avatar-dimension', |
||||||
183 | [ |
||||||
184 | 'rule' => [ |
||||||
185 | 'dimensions', |
||||||
186 | [ |
||||||
187 | 'min' => ['w' => 100, 'h' => 100], |
||||||
188 | 'max' => ['w' => 1500, 'h' => 1500], |
||||||
189 | ], |
||||||
190 | ], |
||||||
191 | 'message' => __( |
||||||
192 | 'user.avatar.error.dimension', |
||||||
193 | ['100x100', '1500x1500'] |
||||||
194 | ), |
||||||
195 | 'provider' => 'proffer', |
||||||
196 | ] |
||||||
197 | ); |
||||||
198 | |||||||
199 | $validator |
||||||
0 ignored issues
–
show
The function
Cake\Validation\Validator::notEmpty() has been deprecated: 3.7.0 Use notEmptyString(), notEmptyArray(), notEmptyFile(), notEmptyDate(), notEmptyTime() or notEmptyDateTime() instead.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead. ![]() |
|||||||
200 | ->notEmpty('password') |
||||||
201 | ->add( |
||||||
202 | 'password', |
||||||
203 | [ |
||||||
204 | 'pwConfirm' => [ |
||||||
205 | 'rule' => [$this, 'validateConfirmPassword'], |
||||||
206 | 'last' => true, |
||||||
207 | 'message' => __('error_password_confirm'), |
||||||
208 | ], |
||||||
209 | ] |
||||||
210 | ); |
||||||
211 | |||||||
212 | $validator |
||||||
0 ignored issues
–
show
The function
Cake\Validation\Validator::notEmpty() has been deprecated: 3.7.0 Use notEmptyString(), notEmptyArray(), notEmptyFile(), notEmptyDate(), notEmptyTime() or notEmptyDateTime() instead.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead. ![]() |
|||||||
213 | ->notEmpty('password_old') |
||||||
214 | ->add( |
||||||
215 | 'password_old', |
||||||
216 | [ |
||||||
217 | 'pwCheckOld' => [ |
||||||
218 | 'rule' => [$this, 'validateCheckOldPassword'], |
||||||
219 | 'last' => true, |
||||||
220 | 'message' => 'validation_error_pwCheckOld', |
||||||
221 | ], |
||||||
222 | ] |
||||||
223 | ); |
||||||
224 | |||||||
225 | $validator |
||||||
0 ignored issues
–
show
The function
Cake\Validation\Validator::notEmpty() has been deprecated: 3.7.0 Use notEmptyString(), notEmptyArray(), notEmptyFile(), notEmptyDate(), notEmptyTime() or notEmptyDateTime() instead.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead. ![]() |
|||||||
226 | ->notEmpty('username', __('error_no_name')) |
||||||
227 | ->add( |
||||||
228 | 'username', |
||||||
229 | [ |
||||||
230 | 'isUnique' => [ |
||||||
231 | 'rule' => 'validateIsUniqueCiString', |
||||||
232 | 'provider' => 'saito', |
||||||
233 | 'last' => true, |
||||||
234 | 'message' => __('error_name_reserved'), |
||||||
235 | ], |
||||||
236 | 'isUsernameEqual' => [ |
||||||
237 | 'on' => 'create', |
||||||
238 | 'last' => true, |
||||||
239 | 'rule' => [$this, 'validateUsernameEqual'], |
||||||
240 | ], |
||||||
241 | 'hasAllowedChars' => [ |
||||||
242 | 'rule' => [$this, 'validateHasAllowedChars'], |
||||||
243 | 'message' => __( |
||||||
244 | 'model.user.validate.username.hasAllowedChars' |
||||||
245 | ), |
||||||
246 | ], |
||||||
247 | 'isNotEmoji' => [ |
||||||
248 | 'rule' => 'utf8', |
||||||
249 | 'message' => __( |
||||||
250 | 'model.user.validate.username.hasAllowedChars' |
||||||
251 | ), |
||||||
252 | ], |
||||||
253 | 'maxLength' => [ |
||||||
254 | 'last' => true, |
||||||
255 | 'message' => __('vld.users.username.maxlength', self::USERNAME_MAXLENGTH), |
||||||
256 | 'rule' => ['maxLength', self::USERNAME_MAXLENGTH], |
||||||
257 | ], |
||||||
258 | ] |
||||||
259 | ); |
||||||
260 | |||||||
261 | $validator |
||||||
0 ignored issues
–
show
The function
Cake\Validation\Validator::notEmpty() has been deprecated: 3.7.0 Use notEmptyString(), notEmptyArray(), notEmptyFile(), notEmptyDate(), notEmptyTime() or notEmptyDateTime() instead.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead. ![]() |
|||||||
262 | ->notEmpty('user_email') |
||||||
263 | ->add( |
||||||
264 | 'user_email', |
||||||
265 | [ |
||||||
266 | 'isUnique' => [ |
||||||
267 | 'rule' => 'validateUnique', |
||||||
268 | 'provider' => 'table', |
||||||
269 | 'last' => true, |
||||||
270 | 'message' => __('error_email_reserved'), |
||||||
271 | ], |
||||||
272 | 'isEmail' => [ |
||||||
273 | 'rule' => ['email', true], |
||||||
274 | 'last' => true, |
||||||
275 | 'message' => __('error_email_wrong'), |
||||||
276 | ], |
||||||
277 | ] |
||||||
278 | ); |
||||||
279 | |||||||
280 | $validator->add( |
||||||
281 | 'user_forum_refresh_time', |
||||||
282 | [ |
||||||
283 | 'numeric' => ['rule' => 'numeric'], |
||||||
284 | 'greaterNull' => ['rule' => ['comparison', '>=', 0]], |
||||||
285 | 'maxLength' => ['rule' => ['maxLength', 3]], |
||||||
286 | ] |
||||||
287 | ); |
||||||
288 | |||||||
289 | $validator->add( |
||||||
290 | 'user_type', |
||||||
291 | [ |
||||||
292 | 'allowedType' => [ |
||||||
293 | 'rule' => [$this, 'validateUserRoleExists'], |
||||||
294 | ], |
||||||
295 | ] |
||||||
296 | ); |
||||||
297 | |||||||
298 | $validator->notEmpty('registered'); |
||||||
0 ignored issues
–
show
The function
Cake\Validation\Validator::notEmpty() has been deprecated: 3.7.0 Use notEmptyString(), notEmptyArray(), notEmptyFile(), notEmptyDate(), notEmptyTime() or notEmptyDateTime() instead.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead. ![]() |
|||||||
299 | |||||||
300 | $validator->add( |
||||||
301 | 'logins', |
||||||
302 | ['numeric' => ['rule' => ['numeric']]] |
||||||
303 | ); |
||||||
304 | |||||||
305 | $validator->add( |
||||||
306 | 'personal_messages', |
||||||
307 | ['bool' => ['rule' => ['boolean']]] |
||||||
308 | ); |
||||||
309 | |||||||
310 | $validator->add( |
||||||
311 | 'user_lock', |
||||||
312 | ['bool' => ['rule' => ['boolean']]] |
||||||
313 | ); |
||||||
314 | |||||||
315 | $validator |
||||||
0 ignored issues
–
show
The function
Cake\Validation\Validator::notEmpty() has been deprecated: 3.7.0 Use notEmptyString(), notEmptyArray(), notEmptyFile(), notEmptyDate(), notEmptyTime() or notEmptyDateTime() instead.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead. ![]() |
|||||||
316 | ->notEmpty('activate_code') |
||||||
317 | ->add( |
||||||
318 | 'activate_code', |
||||||
319 | [ |
||||||
320 | 'numeric' => ['rule' => ['numeric']], |
||||||
321 | 'between' => ['rule' => ['range', 0, 9999999]], |
||||||
322 | ] |
||||||
323 | ); |
||||||
324 | |||||||
325 | $validator->add( |
||||||
326 | 'user_signatures_hide', |
||||||
327 | ['bool' => ['rule' => ['boolean']]] |
||||||
328 | ); |
||||||
329 | |||||||
330 | $validator->add( |
||||||
331 | 'user_signature_images_hide', |
||||||
332 | ['bool' => ['rule' => ['boolean']]] |
||||||
333 | ); |
||||||
334 | |||||||
335 | $validator->add( |
||||||
336 | 'user_automaticaly_mark_as_read', |
||||||
337 | ['bool' => ['rule' => ['boolean']]] |
||||||
338 | ); |
||||||
339 | |||||||
340 | $validator->add( |
||||||
341 | 'user_sort_last_answer', |
||||||
342 | ['bool' => ['rule' => ['boolean']]] |
||||||
343 | ); |
||||||
344 | |||||||
345 | $validator |
||||||
0 ignored issues
–
show
The function
Cake\Validation\Validator::allowEmpty() has been deprecated: 3.7.0 Use allowEmptyString(), allowEmptyArray(), allowEmptyFile(), allowEmptyDate(), allowEmptyTime() or allowEmptyDateTime() instead.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead. ![]() |
|||||||
346 | ->allowEmpty('user_color_new_postings') |
||||||
347 | ->add( |
||||||
348 | 'user_color_new_postings', |
||||||
349 | [ |
||||||
350 | 'hexformat' => [ |
||||||
351 | 'rule' => ['custom', '/^#?[a-f0-9]{0,6}$/i'], |
||||||
352 | ], |
||||||
353 | ] |
||||||
354 | ); |
||||||
355 | $validator |
||||||
0 ignored issues
–
show
The function
Cake\Validation\Validator::allowEmpty() has been deprecated: 3.7.0 Use allowEmptyString(), allowEmptyArray(), allowEmptyFile(), allowEmptyDate(), allowEmptyTime() or allowEmptyDateTime() instead.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead. ![]() |
|||||||
356 | ->allowEmpty('user_color_old_postings') |
||||||
357 | ->add( |
||||||
358 | 'user_color_old_postings', |
||||||
359 | [ |
||||||
360 | 'hexformat' => [ |
||||||
361 | 'rule' => ['custom', '/^#?[a-f0-9]{0,6}$/i'], |
||||||
362 | ], |
||||||
363 | ] |
||||||
364 | ); |
||||||
365 | $validator |
||||||
0 ignored issues
–
show
The function
Cake\Validation\Validator::allowEmpty() has been deprecated: 3.7.0 Use allowEmptyString(), allowEmptyArray(), allowEmptyFile(), allowEmptyDate(), allowEmptyTime() or allowEmptyDateTime() instead.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead. ![]() |
|||||||
366 | ->allowEmpty('user_color_actual_posting') |
||||||
367 | ->add( |
||||||
368 | 'user_color_actual_posting', |
||||||
369 | [ |
||||||
370 | 'hexformat' => [ |
||||||
371 | 'rule' => ['custom', '/^#?[a-f0-9]{0,6}$/i'], |
||||||
372 | ], |
||||||
373 | ] |
||||||
374 | ); |
||||||
375 | |||||||
376 | return $validator; |
||||||
377 | } |
||||||
378 | |||||||
379 | /** |
||||||
380 | * {@inheritDoc} |
||||||
381 | */ |
||||||
382 | protected function _initializeSchema(TableSchema $table) |
||||||
383 | { |
||||||
384 | $table->setColumnType('avatar', 'proffer.file'); |
||||||
385 | $table->setColumnType('user_category_custom', 'serialize'); |
||||||
386 | |||||||
387 | return $table; |
||||||
388 | } |
||||||
389 | |||||||
390 | /** |
||||||
391 | * set last refresh |
||||||
392 | * |
||||||
393 | * @param int $userId user-ID |
||||||
394 | * @param DateTimeInterface|null $lastRefresh last refresh |
||||||
395 | * @return void |
||||||
396 | */ |
||||||
397 | public function setLastRefresh(int $userId, DateTimeInterface $lastRefresh = null) |
||||||
398 | { |
||||||
399 | Stopwatch::start('Users->setLastRefresh()'); |
||||||
400 | $data['last_refresh_tmp'] = bDate(); |
||||||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||||||
401 | |||||||
402 | if ($lastRefresh) { |
||||||
403 | $data['last_refresh'] = $lastRefresh; |
||||||
404 | } |
||||||
405 | |||||||
406 | $this->query() |
||||||
407 | ->update() |
||||||
408 | ->set($data) |
||||||
409 | ->where(['id' => $userId]) |
||||||
410 | ->execute(); |
||||||
411 | |||||||
412 | Stopwatch::end('Users->setLastRefresh()'); |
||||||
413 | } |
||||||
414 | |||||||
415 | /** |
||||||
416 | * Increment logins |
||||||
417 | * |
||||||
418 | * @param Entity $user user |
||||||
419 | * @param int $amount amount |
||||||
420 | * @return void |
||||||
421 | * @throws \Exception |
||||||
422 | */ |
||||||
423 | public function incrementLogins(Entity $user, $amount = 1) |
||||||
424 | { |
||||||
425 | $data = [ |
||||||
426 | 'logins' => $user->get('logins') + $amount, |
||||||
427 | 'last_login' => bDate(), |
||||||
428 | ]; |
||||||
429 | $this->patchEntity($user, $data); |
||||||
430 | if (!$this->save($user)) { |
||||||
431 | throw new \Exception('Increment logins failed.'); |
||||||
432 | } |
||||||
433 | } |
||||||
434 | |||||||
435 | /** |
||||||
436 | * get userlist |
||||||
437 | * |
||||||
438 | * @return array |
||||||
439 | */ |
||||||
440 | public function userlist() |
||||||
441 | { |
||||||
442 | return $this->find( |
||||||
443 | 'list', |
||||||
444 | ['keyField' => 'id', 'valueField' => 'username'] |
||||||
445 | )->toArray(); |
||||||
446 | } |
||||||
447 | |||||||
448 | /** |
||||||
449 | * Removes a user and all his data execpt for his entries |
||||||
450 | * |
||||||
451 | * @param int $userId user-ID |
||||||
452 | * @return bool |
||||||
453 | */ |
||||||
454 | public function deleteAllExceptEntries(int $userId) |
||||||
455 | { |
||||||
456 | $user = $this->get($userId); |
||||||
457 | if (empty($user)) { |
||||||
458 | return false; |
||||||
459 | } |
||||||
460 | |||||||
461 | try { |
||||||
462 | $this->Entries->anonymizeEntriesFromUser($userId); |
||||||
463 | $this->UserIgnores->deleteUser($userId); |
||||||
464 | $this->delete($user); |
||||||
465 | } catch (\Exception $e) { |
||||||
466 | return false; |
||||||
467 | } |
||||||
468 | $this->dispatchDbEvent('Cmd.Cache.clear', ['cache' => 'Thread']); |
||||||
469 | |||||||
470 | return true; |
||||||
471 | } |
||||||
472 | |||||||
473 | /** |
||||||
474 | * Updates the hashed password if hash-algo is out-of-date |
||||||
475 | * |
||||||
476 | * @param int $userId user-ID |
||||||
477 | * @param string $password password |
||||||
478 | * @return void |
||||||
479 | */ |
||||||
480 | public function autoUpdatePassword(int $userId, string $password): void |
||||||
481 | { |
||||||
482 | $user = $this->get($userId, ['fields' => ['id', 'password']]); |
||||||
483 | $oldPassword = $user->get('password'); |
||||||
484 | $needsRehash = $this->getPasswordHasher()->needsRehash($oldPassword); |
||||||
485 | if ($needsRehash) { |
||||||
486 | $user->set('password', $password); |
||||||
487 | $this->save($user); |
||||||
488 | } |
||||||
489 | } |
||||||
490 | |||||||
491 | /** |
||||||
492 | * Post processing when updating a username. |
||||||
493 | * |
||||||
494 | * @param Entity $entity The updated entity. |
||||||
495 | * @return void |
||||||
496 | */ |
||||||
497 | protected function updateUsername(Entity $entity) |
||||||
498 | { |
||||||
499 | // Using associating with $this->Entries->updateAll() not working in |
||||||
500 | // Cake 3.8. |
||||||
501 | $Entries = TableRegistry::getTableLocator()->get('Entries'); |
||||||
502 | $Entries->updateAll( |
||||||
503 | ['name' => $entity->get('username')], |
||||||
504 | ['user_id' => $entity->get('id')] |
||||||
505 | ); |
||||||
506 | |||||||
507 | $Entries->updateAll( |
||||||
508 | ['edited_by' => $entity->get('username')], |
||||||
509 | ['edited_by' => $entity->getOriginal('username')] |
||||||
510 | ); |
||||||
511 | |||||||
512 | $this->dispatchDbEvent('Cmd.Cache.clear', ['cache' => 'Thread']); |
||||||
513 | } |
||||||
514 | |||||||
515 | /** |
||||||
516 | * {@inheritDoc} |
||||||
517 | */ |
||||||
518 | public function afterSave(Event $event, Entity $entity, \ArrayObject $options) |
||||||
0 ignored issues
–
show
The parameter
$options is not used and could be removed.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for parameters that have been defined for a function or method, but which are not used in the method body. ![]() The parameter
$event is not used and could be removed.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for parameters that have been defined for a function or method, but which are not used in the method body. ![]() |
|||||||
519 | { |
||||||
520 | if ($entity->isDirty('username')) { |
||||||
521 | $this->updateUsername($entity); |
||||||
522 | } |
||||||
523 | } |
||||||
524 | |||||||
525 | /** |
||||||
526 | * {@inheritDoc} |
||||||
527 | */ |
||||||
528 | public function beforeSave( |
||||||
529 | Event $event, |
||||||
0 ignored issues
–
show
The parameter
$event is not used and could be removed.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for parameters that have been defined for a function or method, but which are not used in the method body. ![]() |
|||||||
530 | Entity $entity, |
||||||
531 | \ArrayObject $options |
||||||
0 ignored issues
–
show
The parameter
$options is not used and could be removed.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for parameters that have been defined for a function or method, but which are not used in the method body. ![]() |
|||||||
532 | ) { |
||||||
533 | if ($entity->isDirty('password')) { |
||||||
534 | $hashedPassword = $this->getPasswordHasher()->hash($entity->get('password')); |
||||||
535 | $entity->set('password', $hashedPassword); |
||||||
536 | } |
||||||
537 | } |
||||||
538 | |||||||
539 | /** |
||||||
540 | * {@inheritDoc} |
||||||
541 | */ |
||||||
542 | public function beforeValidate( |
||||||
543 | Event $event, |
||||||
0 ignored issues
–
show
The parameter
$event is not used and could be removed.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for parameters that have been defined for a function or method, but which are not used in the method body. ![]() |
|||||||
544 | Entity $entity, |
||||||
545 | \ArrayObject $options, |
||||||
0 ignored issues
–
show
The parameter
$options is not used and could be removed.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for parameters that have been defined for a function or method, but which are not used in the method body. ![]() |
|||||||
546 | Validator $validator |
||||||
0 ignored issues
–
show
The parameter
$validator is not used and could be removed.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for parameters that have been defined for a function or method, but which are not used in the method body. ![]() |
|||||||
547 | ) { |
||||||
548 | if ($entity->isDirty('user_forum_refresh_time')) { |
||||||
549 | $time = $entity->get('user_forum_refresh_time'); |
||||||
550 | if (empty($time)) { |
||||||
551 | $entity->set('user_forum_refresh_time', 0); |
||||||
552 | } |
||||||
553 | } |
||||||
554 | } |
||||||
555 | |||||||
556 | /** |
||||||
557 | * validate old password |
||||||
558 | * |
||||||
559 | * @param string $value value |
||||||
560 | * @param array $context context |
||||||
561 | * @return bool |
||||||
562 | */ |
||||||
563 | public function validateCheckOldPassword($value, array $context) |
||||||
564 | { |
||||||
565 | $userId = $context['data']['id']; |
||||||
566 | $oldPasswordHash = $this->get($userId, ['fields' => ['password']]) |
||||||
567 | ->get('password'); |
||||||
568 | |||||||
569 | return $this->getPasswordHasher()->check($value, $oldPasswordHash); |
||||||
570 | } |
||||||
571 | |||||||
572 | /** |
||||||
573 | * validate confirm password |
||||||
574 | * |
||||||
575 | * @param string $value value |
||||||
576 | * @param array $context context |
||||||
577 | * @return bool |
||||||
578 | */ |
||||||
579 | public function validateConfirmPassword($value, array $context) |
||||||
580 | { |
||||||
581 | if ($value === $context['data']['password_confirm']) { |
||||||
582 | return true; |
||||||
583 | } |
||||||
584 | |||||||
585 | return false; |
||||||
586 | } |
||||||
587 | |||||||
588 | /** |
||||||
589 | * Validate allowed chars |
||||||
590 | * |
||||||
591 | * @param string $value value |
||||||
592 | * @param array $context context |
||||||
593 | * @return bool |
||||||
594 | */ |
||||||
595 | public function validateHasAllowedChars($value, array $context) |
||||||
0 ignored issues
–
show
The parameter
$context is not used and could be removed.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for parameters that have been defined for a function or method, but which are not used in the method body. ![]() |
|||||||
596 | { |
||||||
597 | foreach ($this->getConfig('user_name_disallowed_chars') as $char) { |
||||||
598 | if (mb_strpos($value, $char) !== false) { |
||||||
599 | return false; |
||||||
600 | } |
||||||
601 | } |
||||||
602 | |||||||
603 | return true; |
||||||
604 | } |
||||||
605 | |||||||
606 | /** |
||||||
607 | * Check if the role exists |
||||||
608 | * |
||||||
609 | * @param string $value value |
||||||
610 | * @param array $context context |
||||||
611 | * @return bool|string |
||||||
612 | */ |
||||||
613 | public function validateUserRoleExists($value, array $context) |
||||||
0 ignored issues
–
show
The parameter
$context is not used and could be removed.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for parameters that have been defined for a function or method, but which are not used in the method body. ![]() |
|||||||
614 | { |
||||||
615 | /** @var Permissions */ |
||||||
616 | $Permissions = Registry::get('Permissions'); |
||||||
617 | $roles = array_column($Permissions->getRoles()->getAvailable(), 'type'); |
||||||
618 | if (in_array($value, $roles)) { |
||||||
619 | return true; |
||||||
620 | } |
||||||
621 | |||||||
622 | return __('vld.user.user_type.allowedType', h($value)); |
||||||
623 | } |
||||||
624 | |||||||
625 | /** |
||||||
626 | * checks if equal username exists |
||||||
627 | * |
||||||
628 | * @param string $value value |
||||||
629 | * @param array $context context |
||||||
630 | * @return bool|string |
||||||
631 | */ |
||||||
632 | public function validateUsernameEqual($value, array $context) |
||||||
0 ignored issues
–
show
The parameter
$context is not used and could be removed.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for parameters that have been defined for a function or method, but which are not used in the method body. ![]() |
|||||||
633 | { |
||||||
634 | Stopwatch::start('validateUsernameEqual'); |
||||||
635 | $users = $this->userlist(); |
||||||
636 | $lc = mb_strtolower($value); |
||||||
637 | foreach ($users as $name) { |
||||||
638 | if ($name === $value) { |
||||||
639 | continue; |
||||||
640 | } |
||||||
641 | $name = mb_strtolower($name); |
||||||
642 | $distance = levenshtein($lc, $name); |
||||||
643 | if ($distance < 2) { |
||||||
644 | return __('error.name.equalExists', $name); |
||||||
645 | } |
||||||
646 | } |
||||||
647 | Stopwatch::stop('validateUsernameEqual'); |
||||||
648 | |||||||
649 | return true; |
||||||
650 | } |
||||||
651 | |||||||
652 | /** |
||||||
653 | * Registers new user |
||||||
654 | * |
||||||
655 | * @param array $data data |
||||||
656 | * @param bool $activate activate |
||||||
657 | * @return EntityInterface |
||||||
658 | */ |
||||||
659 | public function register($data, $activate = false): EntityInterface |
||||||
660 | { |
||||||
661 | $defaults = [ |
||||||
662 | 'registered' => bDate(), |
||||||
663 | 'user_type' => 'user', |
||||||
664 | ]; |
||||||
665 | $fields = [ |
||||||
666 | 'password', |
||||||
667 | 'registered', |
||||||
668 | 'user_email', |
||||||
669 | 'user_type', |
||||||
670 | 'username', |
||||||
671 | ]; |
||||||
672 | |||||||
673 | if ($activate !== true) { |
||||||
674 | $defaults['activate_code'] = mt_rand(1000000, 9999999); |
||||||
675 | $fields[] = 'activate_code'; |
||||||
676 | } |
||||||
677 | |||||||
678 | $data = array_merge($data, $defaults); |
||||||
679 | |||||||
680 | $fieldFilter = (new FieldFilter())->setConfig('register', $fields); |
||||||
681 | if (!$fieldFilter->requireFields($data, 'register')) { |
||||||
682 | throw new \RuntimeException( |
||||||
683 | 'Required fields for registration were not provided.', |
||||||
684 | 1563789683 |
||||||
685 | ); |
||||||
686 | } |
||||||
687 | |||||||
688 | $user = $this->newEntity($data, ['fields' => $fields]); |
||||||
689 | $errors = $user->getErrors(); |
||||||
690 | if (!empty($errors)) { |
||||||
691 | return $user; |
||||||
692 | } |
||||||
693 | $user = $this->save($user); |
||||||
694 | if ($user !== false) { |
||||||
695 | $this->dispatchDbEvent('saito.core.user.register.after', [ |
||||||
696 | 'subject' => $user, |
||||||
697 | 'table' => $this, |
||||||
698 | ]); |
||||||
699 | } |
||||||
700 | |||||||
701 | return $user; |
||||||
0 ignored issues
–
show
|
|||||||
702 | } |
||||||
703 | |||||||
704 | /** |
||||||
705 | * Garbage collection for registration |
||||||
706 | * |
||||||
707 | * Deletes all timed out and unactivated registrations |
||||||
708 | * |
||||||
709 | * @return void |
||||||
710 | */ |
||||||
711 | public function registerGc() |
||||||
712 | { |
||||||
713 | $this->deleteAll( |
||||||
714 | [ |
||||||
715 | 'activate_code >' => 0, |
||||||
716 | 'registered <' => bDate(time() - 86400), |
||||||
717 | ] |
||||||
718 | ); |
||||||
719 | } |
||||||
720 | |||||||
721 | /** |
||||||
722 | * calls garbage collection for UserBlock |
||||||
723 | * |
||||||
724 | * UserBlock is lazy-loaded rarely and gc may not trigger often enough (at |
||||||
725 | * least with manual blocking and ignore blocking only) |
||||||
726 | * |
||||||
727 | * @return void |
||||||
728 | */ |
||||||
729 | public function userBlockGc() |
||||||
730 | { |
||||||
731 | $this->UserBlocks->gc(); |
||||||
732 | } |
||||||
733 | |||||||
734 | /** |
||||||
735 | * activates user |
||||||
736 | * |
||||||
737 | * @param int $userId user-ID |
||||||
738 | * @param string $code activation code |
||||||
739 | * @return array|bool false if activation failed; array with status and |
||||||
740 | * user data on success |
||||||
741 | * @throws \InvalidArgumentException |
||||||
742 | */ |
||||||
743 | public function activate(int $userId, string $code) |
||||||
744 | { |
||||||
745 | try { |
||||||
746 | $user = $this->get($userId); |
||||||
747 | } catch (RecordNotFoundException $e) { |
||||||
748 | throw new \InvalidArgumentException(); |
||||||
749 | } |
||||||
750 | |||||||
751 | $activateCode = strval($user->get('activate_code')); |
||||||
752 | |||||||
753 | if (empty($activateCode)) { |
||||||
754 | return ['status' => 'already', 'User' => $user]; |
||||||
755 | } elseif ($activateCode !== $code) { |
||||||
756 | return false; |
||||||
757 | } |
||||||
758 | |||||||
759 | $user->set('activate_code', 0); |
||||||
760 | $user = $this->save($user); |
||||||
761 | if ($user === false) { |
||||||
762 | return false; |
||||||
763 | } |
||||||
764 | |||||||
765 | $this->dispatchDbEvent('saito.core.user.activate.after', [ |
||||||
766 | 'subject' => $user, |
||||||
767 | 'table' => $this, |
||||||
768 | ]); |
||||||
769 | |||||||
770 | return ['status' => 'activated', 'User' => $user]; |
||||||
771 | } |
||||||
772 | |||||||
773 | /** |
||||||
774 | * Count solved posting for a user. |
||||||
775 | * |
||||||
776 | * |
||||||
777 | * @param int $userId user-ID |
||||||
778 | * @return int count |
||||||
779 | */ |
||||||
780 | public function countSolved($userId) |
||||||
781 | { |
||||||
782 | $count = $this->find() |
||||||
783 | ->select(['Users.id']) |
||||||
784 | ->where(['Users.id' => $userId]) |
||||||
785 | ->join( |
||||||
786 | [ |
||||||
787 | 'Entries' => [ |
||||||
788 | 'table' => $this->Entries->getTable(), |
||||||
789 | 'type' => 'INNER', |
||||||
790 | 'conditions' => [ |
||||||
791 | [ |
||||||
792 | 'Entries.solves >' => '0', |
||||||
793 | 'Entries.user_id' => $userId, |
||||||
794 | ], |
||||||
795 | ], |
||||||
796 | ], |
||||||
797 | 'Root' => [ |
||||||
798 | 'table' => $this->Entries->getTable(), |
||||||
799 | 'type' => 'INNER', |
||||||
800 | // Don't answers to own question. |
||||||
801 | 'conditions' => [ |
||||||
802 | 'Root.id = Entries.solves', |
||||||
803 | 'Root.user_id != Users.id', |
||||||
804 | ], |
||||||
805 | ], |
||||||
806 | ] |
||||||
807 | ); |
||||||
808 | |||||||
809 | return $count->count(); |
||||||
810 | } |
||||||
811 | |||||||
812 | /** |
||||||
813 | * Set view categories preferences |
||||||
814 | * |
||||||
815 | * ## $category |
||||||
816 | * |
||||||
817 | * - 'all': set to all categories |
||||||
818 | * - array: (cat_id1 => true|1|'1', cat_id2 => true|1|'1') |
||||||
819 | * - int: set to single category_id |
||||||
820 | * |
||||||
821 | * @param int $userId user-ID |
||||||
822 | * @param string|int|array $category category |
||||||
823 | * @return void |
||||||
824 | * @throws \InvalidArgumentException |
||||||
825 | */ |
||||||
826 | public function setCategory($userId, $category) |
||||||
827 | { |
||||||
828 | $User = $this->find()->select(['id' => $userId])->first(); |
||||||
829 | if (!$User) { |
||||||
830 | throw new \InvalidArgumentException( |
||||||
831 | "Can't find user with id $userId.", |
||||||
832 | 1420807691 |
||||||
833 | ); |
||||||
834 | } |
||||||
835 | |||||||
836 | if ($category === 'all') { |
||||||
837 | //=if show all cateogries |
||||||
838 | $active = -1; |
||||||
839 | } elseif (is_array($category)) { |
||||||
840 | //=if set a custom set of categories |
||||||
841 | $active = 0; |
||||||
842 | |||||||
843 | $availableCats = $this->Entries->Categories->find('list')->toArray( |
||||||
844 | ); |
||||||
845 | $categories = array_intersect_key($category, $availableCats); |
||||||
846 | if (count($categories) === 0) { |
||||||
847 | throw new \InvalidArgumentException(); |
||||||
848 | } |
||||||
849 | $newCats = []; |
||||||
850 | foreach ($categories as $cat => $v) { |
||||||
851 | $newCats[$cat] = ($v === true || $v === 1 || $v === '1'); |
||||||
852 | } |
||||||
853 | $User->set('user_category_custom', $newCats); |
||||||
854 | } else { |
||||||
855 | //=if set a single category |
||||||
856 | $category = (int)$category; |
||||||
857 | if ( |
||||||
858 | $category > 0 && $this->Entries->Categories->exists((int)$category) |
||||||
859 | ) { |
||||||
860 | $active = $category; |
||||||
861 | } else { |
||||||
862 | throw new \InvalidArgumentException(); |
||||||
863 | } |
||||||
864 | } |
||||||
865 | |||||||
866 | $User->set('user_category_active', $active); |
||||||
867 | $this->save($User); |
||||||
0 ignored issues
–
show
It seems like
$User can also be of type array ; however, parameter $entity of Cake\ORM\Table::save() does only seem to accept Cake\Datasource\EntityInterface , 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
![]() |
|||||||
868 | } |
||||||
869 | |||||||
870 | /** |
||||||
871 | * Get default password hasher for hashing user passwords. |
||||||
872 | * |
||||||
873 | * @return PasswordHasherInterface |
||||||
874 | */ |
||||||
875 | public function getPasswordHasher(): PasswordHasherInterface |
||||||
876 | { |
||||||
877 | return PasswordHasherFactory::build(DefaultPasswordHasher::class); |
||||||
878 | } |
||||||
879 | |||||||
880 | /** |
||||||
881 | * Finds a user with additional profil informations from associated tables |
||||||
882 | * |
||||||
883 | * @param Query $query query |
||||||
884 | * @param array $options options |
||||||
885 | * @return Query |
||||||
886 | */ |
||||||
887 | public function findProfile(Query $query, array $options): Query |
||||||
0 ignored issues
–
show
The parameter
$options is not used and could be removed.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for parameters that have been defined for a function or method, but which are not used in the method body. ![]() |
|||||||
888 | { |
||||||
889 | $query |
||||||
890 | // ->enableHydration(false) |
||||||
891 | ->contain( |
||||||
892 | [ |
||||||
893 | 'UserIgnores' => function ($query) { |
||||||
894 | return $query->enableHydration(false)->select( |
||||||
895 | ['blocked_user_id', 'user_id'] |
||||||
896 | ); |
||||||
897 | }, |
||||||
898 | ] |
||||||
899 | ); |
||||||
900 | |||||||
901 | return $query; |
||||||
902 | } |
||||||
903 | |||||||
904 | /** |
||||||
905 | * Find all sorted by username |
||||||
906 | * |
||||||
907 | * @param Query $query query |
||||||
908 | * @param array $options options |
||||||
909 | * @return Query |
||||||
910 | */ |
||||||
911 | public function findPaginated(Query $query, array $options) |
||||||
0 ignored issues
–
show
The parameter
$options is not used and could be removed.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for parameters that have been defined for a function or method, but which are not used in the method body. ![]() |
|||||||
912 | { |
||||||
913 | $query |
||||||
914 | ->contain(['UserOnline']) |
||||||
915 | ->order(['Users.username' => 'ASC']); |
||||||
916 | |||||||
917 | return $query; |
||||||
918 | } |
||||||
919 | |||||||
920 | /** |
||||||
921 | * Find the latest, successfully registered user |
||||||
922 | * |
||||||
923 | * @param Query $query query |
||||||
924 | * @param array $options options |
||||||
925 | * @return Query |
||||||
926 | */ |
||||||
927 | public function findLatest(Query $query, array $options) |
||||||
0 ignored issues
–
show
The parameter
$options is not used and could be removed.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for parameters that have been defined for a function or method, but which are not used in the method body. ![]() |
|||||||
928 | { |
||||||
929 | $query->where(['activate_code' => 0]) |
||||||
930 | ->order(['id' => 'DESC']) |
||||||
931 | ->limit(1); |
||||||
932 | |||||||
933 | return $query; |
||||||
934 | } |
||||||
935 | } |
||||||
936 |
This function has been deprecated. The supplier of the function has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.