1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Charcoal\User; |
4
|
|
|
|
5
|
|
|
use DateTime; |
6
|
|
|
use DateTimeInterface; |
7
|
|
|
use Exception; |
8
|
|
|
use InvalidArgumentException; |
9
|
|
|
|
10
|
|
|
// From 'charcoal-factory' |
11
|
|
|
use Charcoal\Factory\FactoryInterface; |
12
|
|
|
|
13
|
|
|
// From 'charcoal-core' |
14
|
|
|
use Charcoal\Validator\ValidatorInterface; |
15
|
|
|
|
16
|
|
|
// From 'charcoal-config' |
17
|
|
|
use Charcoal\Config\ConfigurableInterface; |
18
|
|
|
use Charcoal\Config\ConfigurableTrait; |
19
|
|
|
|
20
|
|
|
// From 'charcoal-object' |
21
|
|
|
use Charcoal\Object\Content; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* Full implementation, as abstract class, of the `UserInterface`. |
25
|
|
|
*/ |
26
|
|
|
abstract class AbstractUser extends Content implements |
27
|
|
|
UserInterface, |
28
|
|
|
ConfigurableInterface |
29
|
|
|
{ |
30
|
|
|
use ConfigurableTrait; |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* @var UserInterface $authenticatedUser |
34
|
|
|
*/ |
35
|
|
|
protected static $authenticatedUser; |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* The email address should be unique and mandatory. |
39
|
|
|
* |
40
|
|
|
* It is also used as the login name. |
41
|
|
|
* |
42
|
|
|
* @var string |
43
|
|
|
*/ |
44
|
|
|
private $email; |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* The password is stored encrypted in the (database) storage. |
48
|
|
|
* |
49
|
|
|
* @var string|null |
50
|
|
|
*/ |
51
|
|
|
private $password; |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* The display name serves as a human-readable identifier for the user. |
55
|
|
|
* |
56
|
|
|
* @var string|null |
57
|
|
|
*/ |
58
|
|
|
private $displayName; |
59
|
|
|
|
60
|
|
|
/** |
61
|
|
|
* Roles define a set of tasks a user is allowed or denied from performing. |
62
|
|
|
* |
63
|
|
|
* @var string[] |
64
|
|
|
*/ |
65
|
|
|
private $roles = []; |
66
|
|
|
|
67
|
|
|
/** |
68
|
|
|
* The timestamp of the latest (successful) login. |
69
|
|
|
* |
70
|
|
|
* @var DateTimeInterface|null |
71
|
|
|
*/ |
72
|
|
|
private $lastLoginDate; |
73
|
|
|
|
74
|
|
|
/** |
75
|
|
|
* The IP address during the latest (successful) login. |
76
|
|
|
* |
77
|
|
|
* @var string|null |
78
|
|
|
*/ |
79
|
|
|
private $lastLoginIp; |
80
|
|
|
|
81
|
|
|
/** |
82
|
|
|
* The timestamp of the latest password change. |
83
|
|
|
* |
84
|
|
|
* @var DateTimeInterface|null |
85
|
|
|
*/ |
86
|
|
|
private $lastPasswordDate; |
87
|
|
|
|
88
|
|
|
/** |
89
|
|
|
* The IP address during the latest password change. |
90
|
|
|
* |
91
|
|
|
* @var string|null |
92
|
|
|
*/ |
93
|
|
|
private $lastPasswordIp; |
94
|
|
|
|
95
|
|
|
/** |
96
|
|
|
* Tracks the password reset token. |
97
|
|
|
* |
98
|
|
|
* If the token is set (not empty), then the user should be prompted |
99
|
|
|
* to reset his password after login / enter the token to continue. |
100
|
|
|
* |
101
|
|
|
* @var string|null |
102
|
|
|
*/ |
103
|
|
|
private $loginToken = ''; |
104
|
|
|
|
105
|
|
|
/** |
106
|
|
|
* Structure |
107
|
|
|
* |
108
|
|
|
* Get the user preferences |
109
|
|
|
* |
110
|
|
|
* @var array|mixed |
111
|
|
|
*/ |
112
|
|
|
private $preferences; |
113
|
|
|
|
114
|
|
|
/** |
115
|
|
|
* @param string $email The user email. |
116
|
|
|
* @throws InvalidArgumentException If the email is not a string. |
117
|
|
|
* @return UserInterface Chainable |
118
|
|
|
*/ |
119
|
|
|
public function setEmail($email) |
120
|
|
|
{ |
121
|
|
|
if (!is_string($email)) { |
122
|
|
|
throw new InvalidArgumentException( |
123
|
|
|
'Set user email: Email must be a string' |
124
|
|
|
); |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
$this->email = $email; |
128
|
|
|
|
129
|
|
|
return $this; |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
/** |
133
|
|
|
* @return string |
134
|
|
|
*/ |
135
|
|
|
public function email() |
136
|
|
|
{ |
137
|
|
|
return $this->email; |
138
|
|
|
} |
139
|
|
|
|
140
|
|
|
/** |
141
|
|
|
* @param string|null $password The user password. Encrypted in storage. |
142
|
|
|
* @throws InvalidArgumentException If the password is not a string (or null, to reset). |
143
|
|
|
* @return UserInterface Chainable |
144
|
|
|
*/ |
145
|
|
|
public function setPassword($password) |
146
|
|
|
{ |
147
|
|
|
if ($password === null) { |
148
|
|
|
$this->password = $password; |
149
|
|
|
} elseif (is_string($password)) { |
150
|
|
|
$this->password = $password; |
151
|
|
|
} else { |
152
|
|
|
throw new InvalidArgumentException( |
153
|
|
|
'Set user password: Password must be a string' |
154
|
|
|
); |
155
|
|
|
} |
156
|
|
|
|
157
|
|
|
return $this; |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
/** |
161
|
|
|
* @return string|null |
162
|
|
|
*/ |
163
|
|
|
public function password() |
164
|
|
|
{ |
165
|
|
|
return $this->password; |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
/** |
169
|
|
|
* @param string|null $name The user's display name. |
170
|
|
|
* @return UserInterface Chainable |
171
|
|
|
*/ |
172
|
|
|
public function setDisplayName($name) |
173
|
|
|
{ |
174
|
|
|
$this->displayName = $name; |
175
|
|
|
|
176
|
|
|
return $this; |
177
|
|
|
} |
178
|
|
|
|
179
|
|
|
/** |
180
|
|
|
* @return string|null |
181
|
|
|
*/ |
182
|
|
|
public function displayName() |
183
|
|
|
{ |
184
|
|
|
return $this->displayName; |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
/** |
188
|
|
|
* @param string|string[]|null $roles The ACL roles this user belongs to. |
189
|
|
|
* @throws InvalidArgumentException If the roles argument is invalid. |
190
|
|
|
* @return UserInterface Chainable |
191
|
|
|
*/ |
192
|
|
|
public function setRoles($roles) |
193
|
|
|
{ |
194
|
|
|
if (empty($roles) && !is_numeric($roles)) { |
195
|
|
|
$this->roles = []; |
196
|
|
|
return $this; |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
if (is_string($roles)) { |
200
|
|
|
$roles = explode(',', $roles); |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
if (!is_array($roles)) { |
204
|
|
|
throw new InvalidArgumentException( |
205
|
|
|
'Roles must be a comma-separated string or an array' |
206
|
|
|
); |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
$this->roles = array_filter(array_map('trim', $roles), 'strlen'); |
210
|
|
|
|
211
|
|
|
return $this; |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
/** |
215
|
|
|
* @return string[] |
216
|
|
|
*/ |
217
|
|
|
public function roles() |
218
|
|
|
{ |
219
|
|
|
return $this->roles; |
220
|
|
|
} |
221
|
|
|
|
222
|
|
|
/** |
223
|
|
|
* @param string|DateTimeInterface|null $lastLoginDate The last login date. |
224
|
|
|
* @throws InvalidArgumentException If the ts is not a valid date/time. |
225
|
|
|
* @return UserInterface Chainable |
226
|
|
|
*/ |
227
|
|
View Code Duplication |
public function setLastLoginDate($lastLoginDate) |
|
|
|
|
228
|
|
|
{ |
229
|
|
|
if ($lastLoginDate === null) { |
230
|
|
|
$this->lastLoginDate = null; |
231
|
|
|
return $this; |
232
|
|
|
} |
233
|
|
|
|
234
|
|
|
if (is_string($lastLoginDate)) { |
235
|
|
|
try { |
236
|
|
|
$lastLoginDate = new DateTime($lastLoginDate); |
237
|
|
|
} catch (Exception $e) { |
238
|
|
|
throw new InvalidArgumentException(sprintf( |
239
|
|
|
'Invalid login date (%s)', |
240
|
|
|
$e->getMessage() |
241
|
|
|
), 0, $e); |
242
|
|
|
} |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
if (!($lastLoginDate instanceof DateTimeInterface)) { |
246
|
|
|
throw new InvalidArgumentException( |
247
|
|
|
'Invalid "Last Login Date" value. Must be a date/time string or a DateTime object.' |
248
|
|
|
); |
249
|
|
|
} |
250
|
|
|
|
251
|
|
|
$this->lastLoginDate = $lastLoginDate; |
252
|
|
|
|
253
|
|
|
return $this; |
254
|
|
|
} |
255
|
|
|
|
256
|
|
|
/** |
257
|
|
|
* @return DateTimeInterface|null |
258
|
|
|
*/ |
259
|
|
|
public function lastLoginDate() |
260
|
|
|
{ |
261
|
|
|
return $this->lastLoginDate; |
262
|
|
|
} |
263
|
|
|
|
264
|
|
|
/** |
265
|
|
|
* @param string|integer|null $ip The last login IP address. |
266
|
|
|
* @throws InvalidArgumentException If the IP is not an IP string, an integer, or null. |
267
|
|
|
* @return UserInterface Chainable |
268
|
|
|
*/ |
269
|
|
View Code Duplication |
public function setLastLoginIp($ip) |
|
|
|
|
270
|
|
|
{ |
271
|
|
|
if ($ip === null) { |
272
|
|
|
$this->lastLoginIp = null; |
273
|
|
|
return $this; |
274
|
|
|
} |
275
|
|
|
|
276
|
|
|
if (is_int($ip)) { |
277
|
|
|
$ip = long2ip($ip); |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
if (!is_string($ip)) { |
281
|
|
|
throw new InvalidArgumentException( |
282
|
|
|
'Invalid IP address' |
283
|
|
|
); |
284
|
|
|
} |
285
|
|
|
|
286
|
|
|
$this->lastLoginIp = $ip; |
287
|
|
|
|
288
|
|
|
return $this; |
289
|
|
|
} |
290
|
|
|
|
291
|
|
|
/** |
292
|
|
|
* Get the last login IP in x.x.x.x format |
293
|
|
|
* |
294
|
|
|
* @return string|null |
295
|
|
|
*/ |
296
|
|
|
public function lastLoginIp() |
297
|
|
|
{ |
298
|
|
|
return $this->lastLoginIp; |
299
|
|
|
} |
300
|
|
|
|
301
|
|
|
/** |
302
|
|
|
* @param string|DateTimeInterface|null $lastPasswordDate The last password date. |
303
|
|
|
* @throws InvalidArgumentException If the passsword date is not a valid DateTime. |
304
|
|
|
* @return UserInterface Chainable |
305
|
|
|
*/ |
306
|
|
View Code Duplication |
public function setLastPasswordDate($lastPasswordDate) |
|
|
|
|
307
|
|
|
{ |
308
|
|
|
if ($lastPasswordDate === null) { |
309
|
|
|
$this->lastPasswordDate = null; |
310
|
|
|
return $this; |
311
|
|
|
} |
312
|
|
|
|
313
|
|
|
if (is_string($lastPasswordDate)) { |
314
|
|
|
try { |
315
|
|
|
$lastPasswordDate = new DateTime($lastPasswordDate); |
316
|
|
|
} catch (Exception $e) { |
317
|
|
|
throw new InvalidArgumentException(sprintf( |
318
|
|
|
'Invalid last password date (%s)', |
319
|
|
|
$e->getMessage() |
320
|
|
|
), 0, $e); |
321
|
|
|
} |
322
|
|
|
} |
323
|
|
|
|
324
|
|
|
if (!($lastPasswordDate instanceof DateTimeInterface)) { |
325
|
|
|
throw new InvalidArgumentException( |
326
|
|
|
'Invalid "Last Password Date" value. Must be a date/time string or a DateTime object.' |
327
|
|
|
); |
328
|
|
|
} |
329
|
|
|
|
330
|
|
|
$this->lastPasswordDate = $lastPasswordDate; |
331
|
|
|
|
332
|
|
|
return $this; |
333
|
|
|
} |
334
|
|
|
|
335
|
|
|
/** |
336
|
|
|
* @return DateTimeInterface|null |
337
|
|
|
*/ |
338
|
|
|
public function lastPasswordDate() |
339
|
|
|
{ |
340
|
|
|
return $this->lastPasswordDate; |
341
|
|
|
} |
342
|
|
|
|
343
|
|
|
/** |
344
|
|
|
* @param integer|string|null $ip The last password IP. |
345
|
|
|
* @throws InvalidArgumentException If the IP is not null, an integer or an IP string. |
346
|
|
|
* @return UserInterface Chainable |
347
|
|
|
*/ |
348
|
|
View Code Duplication |
public function setLastPasswordIp($ip) |
|
|
|
|
349
|
|
|
{ |
350
|
|
|
if ($ip === null) { |
351
|
|
|
$this->lastPasswordIp = null; |
352
|
|
|
return $this; |
353
|
|
|
} |
354
|
|
|
|
355
|
|
|
if (is_int($ip)) { |
356
|
|
|
$ip = long2ip($ip); |
357
|
|
|
} |
358
|
|
|
|
359
|
|
|
if (!is_string($ip)) { |
360
|
|
|
throw new InvalidArgumentException( |
361
|
|
|
'Invalid IP address' |
362
|
|
|
); |
363
|
|
|
} |
364
|
|
|
|
365
|
|
|
$this->lastPasswordIp = $ip; |
366
|
|
|
|
367
|
|
|
return $this; |
368
|
|
|
} |
369
|
|
|
|
370
|
|
|
/** |
371
|
|
|
* Get the last password change IP in x.x.x.x format |
372
|
|
|
* |
373
|
|
|
* @return string|null |
374
|
|
|
*/ |
375
|
|
|
public function lastPasswordIp() |
376
|
|
|
{ |
377
|
|
|
return $this->lastPasswordIp; |
378
|
|
|
} |
379
|
|
|
|
380
|
|
|
/** |
381
|
|
|
* @param string|null $token The login token. |
382
|
|
|
* @throws InvalidArgumentException If the token is not a string. |
383
|
|
|
* @return UserInterface Chainable |
384
|
|
|
*/ |
385
|
|
|
public function setLoginToken($token) |
386
|
|
|
{ |
387
|
|
|
if ($token === null) { |
388
|
|
|
$this->loginToken = null; |
389
|
|
|
return $this; |
390
|
|
|
} |
391
|
|
|
|
392
|
|
|
if (!is_string($token)) { |
393
|
|
|
throw new InvalidArgumentException( |
394
|
|
|
'Login Token must be a string' |
395
|
|
|
); |
396
|
|
|
} |
397
|
|
|
|
398
|
|
|
$this->loginToken = $token; |
399
|
|
|
|
400
|
|
|
return $this; |
401
|
|
|
} |
402
|
|
|
|
403
|
|
|
/** |
404
|
|
|
* @return string|null |
405
|
|
|
*/ |
406
|
|
|
public function loginToken() |
407
|
|
|
{ |
408
|
|
|
return $this->loginToken; |
409
|
|
|
} |
410
|
|
|
|
411
|
|
|
/** |
412
|
|
|
* @param array|mixed $preferences Preferences for AbstractUser. |
413
|
|
|
* @return self |
414
|
|
|
*/ |
415
|
|
|
public function setPreferences($preferences) |
416
|
|
|
{ |
417
|
|
|
$this->preferences = $preferences; |
418
|
|
|
|
419
|
|
|
return $this; |
420
|
|
|
} |
421
|
|
|
|
422
|
|
|
/** |
423
|
|
|
* @return array|mixed |
424
|
|
|
*/ |
425
|
|
|
public function preferences() |
426
|
|
|
{ |
427
|
|
|
return $this->preferences; |
428
|
|
|
} |
429
|
|
|
|
430
|
|
|
/** |
431
|
|
|
* @throws Exception If trying to save a user to session without a ID. |
432
|
|
|
* @return UserInterface Chainable |
433
|
|
|
*/ |
434
|
|
|
public function saveToSession() |
435
|
|
|
{ |
436
|
|
|
if (!$this->id()) { |
437
|
|
|
throw new Exception( |
438
|
|
|
'Can not set auth user; no user ID' |
439
|
|
|
); |
440
|
|
|
} |
441
|
|
|
|
442
|
|
|
$_SESSION[static::sessionKey()] = $this->id(); |
443
|
|
|
|
444
|
|
|
return $this; |
445
|
|
|
} |
446
|
|
|
|
447
|
|
|
/** |
448
|
|
|
* Log in the user (in session) |
449
|
|
|
* |
450
|
|
|
* Called when the authentication is successful. |
451
|
|
|
* |
452
|
|
|
* @return boolean Success / Failure |
453
|
|
|
*/ |
454
|
|
|
public function login() |
455
|
|
|
{ |
456
|
|
|
if (!$this->id()) { |
457
|
|
|
return false; |
458
|
|
|
} |
459
|
|
|
|
460
|
|
|
$this->setLastLoginDate('now'); |
461
|
|
|
$ip = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : ''; |
462
|
|
|
if ($ip) { |
463
|
|
|
$this->setLastLoginIp($ip); |
464
|
|
|
} |
465
|
|
|
|
466
|
|
|
$this->update([ 'last_login_ip', 'last_login_date' ]); |
467
|
|
|
|
468
|
|
|
$this->saveToSession(); |
469
|
|
|
|
470
|
|
|
return true; |
471
|
|
|
} |
472
|
|
|
|
473
|
|
|
/** |
474
|
|
|
* Empties the session var associated to the session key. |
475
|
|
|
* |
476
|
|
|
* @return boolean Logged out or not. |
477
|
|
|
*/ |
478
|
|
|
public function logout() |
479
|
|
|
{ |
480
|
|
|
// Irrelevant call... |
481
|
|
|
if (!$this->id()) { |
482
|
|
|
return false; |
483
|
|
|
} |
484
|
|
|
|
485
|
|
|
$key = static::sessionKey(); |
486
|
|
|
|
487
|
|
|
$_SESSION[$key] = null; |
488
|
|
|
unset($_SESSION[$key], static::$authenticatedUser[$key]); |
489
|
|
|
|
490
|
|
|
return true; |
491
|
|
|
} |
492
|
|
|
|
493
|
|
|
/** |
494
|
|
|
* Reset the password. |
495
|
|
|
* |
496
|
|
|
* Encrypt the password and re-save the object in the database. |
497
|
|
|
* Also updates the last password date & ip. |
498
|
|
|
* |
499
|
|
|
* @param string $plainPassword The plain (non-encrypted) password to reset to. |
500
|
|
|
* @throws InvalidArgumentException If the plain password is not a string. |
501
|
|
|
* @return UserInterface Chainable |
502
|
|
|
*/ |
503
|
|
|
public function resetPassword($plainPassword) |
504
|
|
|
{ |
505
|
|
|
if (!is_string($plainPassword)) { |
506
|
|
|
throw new InvalidArgumentException( |
507
|
|
|
'Can not change password: password is not a string.' |
508
|
|
|
); |
509
|
|
|
} |
510
|
|
|
|
511
|
|
|
$hash = password_hash($plainPassword, PASSWORD_DEFAULT); |
512
|
|
|
$this->setPassword($hash); |
513
|
|
|
|
514
|
|
|
$this->setLastPasswordDate('now'); |
515
|
|
|
$ip = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : ''; |
516
|
|
|
if ($ip) { |
517
|
|
|
$this->setLastPasswordIp($ip); |
518
|
|
|
} |
519
|
|
|
|
520
|
|
|
if ($this->id()) { |
521
|
|
|
$this->update([ 'password', 'last_password_date', 'last_password_ip' ]); |
522
|
|
|
} |
523
|
|
|
|
524
|
|
|
return $this; |
525
|
|
|
} |
526
|
|
|
|
527
|
|
|
/** |
528
|
|
|
* Get the currently authenticated user (from session) |
529
|
|
|
* |
530
|
|
|
* Return null if there is no current user in logged into |
531
|
|
|
* |
532
|
|
|
* @param FactoryInterface $factory The factory to create the user object with. |
533
|
|
|
* @throws Exception If the user from session is invalid. |
534
|
|
|
* @return UserInterface|null |
535
|
|
|
*/ |
536
|
|
|
public static function getAuthenticated(FactoryInterface $factory) |
537
|
|
|
{ |
538
|
|
|
$key = static::sessionKey(); |
539
|
|
|
|
540
|
|
|
if (isset(static::$authenticatedUser[$key])) { |
541
|
|
|
return static::$authenticatedUser[$key]; |
542
|
|
|
} |
543
|
|
|
|
544
|
|
|
if (!isset($_SESSION[$key])) { |
545
|
|
|
return null; |
546
|
|
|
} |
547
|
|
|
|
548
|
|
|
$userId = $_SESSION[$key]; |
549
|
|
|
if (!$userId) { |
550
|
|
|
return null; |
551
|
|
|
} |
552
|
|
|
|
553
|
|
|
$userClass = get_called_class(); |
554
|
|
|
$user = $factory->create($userClass); |
555
|
|
|
$user->load($userId); |
556
|
|
|
|
557
|
|
|
// Inactive users can not authenticate |
558
|
|
|
if (!$user->id() || !$user->email() || !$user->active()) { |
559
|
|
|
return null; |
560
|
|
|
} |
561
|
|
|
|
562
|
|
|
static::$authenticatedUser[$key] = $user; |
563
|
|
|
|
564
|
|
|
return $user; |
565
|
|
|
} |
566
|
|
|
|
567
|
|
|
|
568
|
|
|
|
569
|
|
|
// Extends Charcoal\Validator\ValidatableTrait |
570
|
|
|
// ========================================================================= |
571
|
|
|
|
572
|
|
|
/** |
573
|
|
|
* Validate the model. |
574
|
|
|
* |
575
|
|
|
* @see \Charcoal\Validator\ValidatorInterface |
576
|
|
|
* @param ValidatorInterface $v Optional. A custom validator object to use for validation. If null, use object's. |
577
|
|
|
* @return boolean |
578
|
|
|
*/ |
579
|
|
|
public function validate(ValidatorInterface &$v = null) |
580
|
|
|
{ |
581
|
|
|
$result = parent::validate($v); |
582
|
|
|
$previousModel = $this->modelFactory()->create(self::class)->load($this->id()); |
583
|
|
|
|
584
|
|
|
$email = $this->email(); |
585
|
|
|
if (empty($email)) { |
586
|
|
|
$this->validator()->error( |
587
|
|
|
'Email is required.', |
588
|
|
|
'email' |
|
|
|
|
589
|
|
|
); |
590
|
|
|
} elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) { |
591
|
|
|
$this->validator()->error( |
592
|
|
|
'Email format is incorrect.', |
593
|
|
|
'email' |
|
|
|
|
594
|
|
|
); |
595
|
|
|
/** Check if updating/changing email. */ |
596
|
|
|
} elseif ($previousModel->email() !== $email) { |
597
|
|
|
$existingModel = $this->modelFactory()->create(self::class)->loadFrom('email', $email); |
598
|
|
|
/** Check for existing user with given email. */ |
599
|
|
|
if (!empty($existingModel->id())) { |
600
|
|
|
$this->validator()->error( |
601
|
|
|
'This email is not available.', |
602
|
|
|
'email' |
|
|
|
|
603
|
|
|
); |
604
|
|
|
} |
605
|
|
|
} |
606
|
|
|
|
607
|
|
|
return count($this->validator()->errorResults()) === 0 && $result; |
|
|
|
|
608
|
|
|
} |
609
|
|
|
} |
610
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.