Completed
Push — develop ( cc9a49...c1329f )
by Abdelrahman
09:49
created

User::boot()   A

Complexity

Conditions 4
Paths 1

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 6
nc 1
nop 0
dl 0
loc 12
rs 9.2
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Cortex\Fort\Models;
6
7
use Rinvex\Country\Country;
8
use Rinvex\Language\Language;
9
use Illuminate\Auth\Authenticatable;
10
use Illuminate\Support\Facades\Hash;
11
use Rinvex\Fort\Traits\HasHashables;
12
use Rinvex\Tenants\Traits\Tenantable;
13
use Rinvex\Fort\Traits\CanVerifyEmail;
14
use Rinvex\Fort\Traits\CanVerifyPhone;
15
use Cortex\Foundation\Traits\Auditable;
16
use Illuminate\Database\Eloquent\Model;
17
use Rinvex\Cacheable\CacheableEloquent;
18
use Illuminate\Notifications\Notifiable;
19
use Rinvex\Fort\Traits\CanResetPassword;
20
use Rinvex\Support\Traits\ValidatingTrait;
21
use Rinvex\Attributes\Traits\Attributable;
22
use Spatie\Activitylog\Traits\HasActivity;
23
use Spatie\MediaLibrary\HasMedia\HasMedia;
24
use Spatie\MediaLibrary\HasMedia\HasMediaTrait;
25
use Rinvex\Fort\Traits\AuthenticatableTwoFactor;
26
use Rinvex\Fort\Contracts\CanVerifyEmailContract;
27
use Rinvex\Fort\Contracts\CanVerifyPhoneContract;
28
use Silber\Bouncer\Database\HasRolesAndAbilities;
29
use Illuminate\Foundation\Auth\Access\Authorizable;
30
use Rinvex\Fort\Contracts\CanResetPasswordContract;
31
use Illuminate\Database\Eloquent\Relations\MorphMany;
32
use Cortex\Fort\Notifications\PasswordResetNotification;
33
use Rinvex\Fort\Contracts\AuthenticatableTwoFactorContract;
34
use Cortex\Fort\Notifications\EmailVerificationNotification;
35
use Cortex\Fort\Notifications\PhoneVerificationNotification;
36
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
37
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
38
39
class User extends Model implements AuthenticatableContract, AuthenticatableTwoFactorContract, AuthorizableContract, CanResetPasswordContract, CanVerifyEmailContract, CanVerifyPhoneContract, HasMedia
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 199 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
40
{
41
    // @TODO: Strangely, this issue happens only here!!!
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
42
    // Duplicate trait usage to fire attached events for cache
43
    // flush before other events in other traits specially LogsActivity,
44
    // otherwise old cached queries used and no changelog recorded on update.
45
    use CacheableEloquent;
46
    use Auditable;
47
    use Tenantable;
48
    use Notifiable;
49
    use HasActivity;
50
    use Attributable;
51
    use Authorizable;
52
    use HasHashables;
53
    use HasMediaTrait;
54
    use CanVerifyEmail;
55
    use CanVerifyPhone;
56
    use Authenticatable;
57
    use ValidatingTrait;
58
    use CanResetPassword;
59
    use CacheableEloquent;
60
    use HasRolesAndAbilities;
61
    use AuthenticatableTwoFactor;
62
63
    /**
64
     * {@inheritdoc}
65
     */
66
    protected $fillable = [
67
        'username',
68
        'password',
69
        'two_factor',
70
        'email',
71
        'email_verified',
72
        'email_verified_at',
73
        'phone',
74
        'phone_verified',
75
        'phone_verified_at',
76
        'name_prefix',
77
        'first_name',
78
        'middle_name',
79
        'last_name',
80
        'name_suffix',
81
        'title',
82
        'country_code',
83
        'language_code',
84
        'birthday',
85
        'gender',
86
        'is_active',
87
        'last_activity',
88
        'abilities',
89
        'roles',
90
    ];
91
92
    /**
93
     * {@inheritdoc}
94
     */
95
    protected $casts = [
96
        'username' => 'string',
97
        'password' => 'string',
98
        'two_factor' => 'json',
99
        'email' => 'string',
100
        'email_verified' => 'boolean',
101
        'email_verified_at' => 'datetime',
102
        'phone' => 'string',
103
        'phone_verified' => 'boolean',
104
        'phone_verified_at' => 'datetime',
105
        'name_prefix' => 'string',
106
        'first_name' => 'string',
107
        'middle_name' => 'string',
108
        'last_name' => 'string',
109
        'name_suffix' => 'string',
110
        'title' => 'string',
111
        'country_code' => 'string',
112
        'language_code' => 'string',
113
        'birthday' => 'string',
114
        'gender' => 'string',
115
        'is_active' => 'boolean',
116
        'last_activity' => 'datetime',
117
        'deleted_at' => 'datetime',
118
    ];
119
120
    /**
121
     * {@inheritdoc}
122
     */
123
    protected $hidden = [
124
        'password',
125
        'two_factor',
126
        'remember_token',
127
    ];
128
129
    /**
130
     * {@inheritdoc}
131
     */
132
    protected $observables = [
133
        'validating',
134
        'validated',
135
    ];
136
137
    /**
138
     * The attributes to be encrypted before saving.
139
     *
140
     * @var array
141
     */
142
    protected $hashables = [
143
        'password',
144
    ];
145
146
    /**
147
     * The default rules that the model will validate against.
148
     *
149
     * @var array
150
     */
151
    protected $rules = [];
152
153
    /**
154
     * Whether the model should throw a
155
     * ValidationException if it fails validation.
156
     *
157
     * @var bool
158
     */
159
    protected $throwValidationExceptions = true;
160
161
    /**
162
     * Indicates whether to log only dirty attributes or all.
163
     *
164
     * @var bool
165
     */
166
    protected static $logOnlyDirty = true;
167
168
    /**
169
     * The attributes that are logged on change.
170
     *
171
     * @var array
172
     */
173
    protected static $logFillable = true;
174
175
    /**
176
     * The attributes that are ignored on change.
177
     *
178
     * @var array
179
     */
180
    protected static $ignoreChangedAttributes = [
181
        'password',
182
        'two_factor',
183
        'email_verified_at',
184
        'phone_verified_at',
185
        'last_activity',
186
        'created_at',
187
        'updated_at',
188
        'deleted_at',
189
    ];
190
191
    /**
192
     * {@inheritdoc}
193
     */
194
    protected $passwordResetNotificationClass = PasswordResetNotification::class;
195
196
    /**
197
     * {@inheritdoc}
198
     */
199
    protected $emailVerificationNotificationClass = EmailVerificationNotification::class;
200
201
    /**
202
     * {@inheritdoc}
203
     */
204
    protected $phoneVerificationNotificationClass = PhoneVerificationNotification::class;
205
206
    /**
207
     * Get the route key for the model.
208
     *
209
     * @return string
210
     */
211
    public function getRouteKeyName(): string
212
    {
213
        return 'username';
214
    }
215
216
    /**
217
     * Register media collections.
218
     *
219
     * @return void
220
     */
221
    public function registerMediaCollections(): void
222
    {
223
        $this->addMediaCollection('profile_picture')->singleFile();
224
        $this->addMediaCollection('cover_photo')->singleFile();
225
    }
226
227
    /**
228
     * Attach the given abilities to the model.
229
     *
230
     * @param mixed $abilities
231
     *
232
     * @return void
233
     */
234
    public function setAbilitiesAttribute($abilities): void
235
    {
236
        static::saved(function (self $model) use ($abilities) {
237
            activity()
238
                ->performedOn($model)
239
                ->withProperties(['attributes' => ['abilities' => $abilities], 'old' => ['abilities' => $model->abilities->pluck('id')->toArray()]])
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 148 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
240
                ->log('updated');
241
242
            $model->abilities()->sync($abilities, true);
243
        });
244
    }
245
246
    /**
247
     * Attach the given roles to the model.
248
     *
249
     * @param mixed $roles
250
     *
251
     * @return void
252
     */
253
    public function setRolesAttribute($roles): void
254
    {
255
        static::saved(function (self $model) use ($roles) {
256
            activity()
257
                ->performedOn($model)
258
                ->withProperties(['attributes' => ['roles' => $roles], 'old' => ['roles' => $model->roles->pluck('id')->toArray()]])
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 132 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
259
                ->log('updated');
260
261
            $model->roles()->sync($roles, true);
262
        });
263
    }
264
265
    /**
266
     * Create a new Eloquent model instance.
267
     *
268
     * @param array $attributes
269
     */
270
    public function __construct(array $attributes = [])
271
    {
272
        parent::__construct($attributes);
273
274
        $this->setTable(config('cortex.fort.tables.users'));
275
        $this->setRules([
276
            'username' => 'required|alpha_dash|min:3|max:150|unique:'.config('cortex.fort.tables.users').',username',
277
            'password' => 'sometimes|required|min:'.config('cortex.fort.password_min_chars'),
278
            'two_factor' => 'nullable|array',
279
            'email' => 'required|email|min:3|max:150|unique:'.config('cortex.fort.tables.users').',email',
280
            'email_verified' => 'sometimes|boolean',
281
            'email_verified_at' => 'nullable|date',
282
            'phone' => 'nullable|numeric|min:4',
283
            'phone_verified' => 'sometimes|boolean',
284
            'phone_verified_at' => 'nullable|date',
285
            'name_prefix' => 'nullable|string|max:150',
286
            'first_name' => 'nullable|string|max:150',
287
            'middle_name' => 'nullable|string|max:150',
288
            'last_name' => 'nullable|string|max:150',
289
            'name_suffix' => 'nullable|string|max:150',
290
            'title' => 'nullable|string|max:150',
291
            'country_code' => 'nullable|alpha|size:2|country',
292
            'language_code' => 'nullable|alpha|size:2|language',
293
            'birthday' => 'nullable|date_format:Y-m-d',
294
            'gender' => 'nullable|string|in:male,female',
295
            'is_active' => 'sometimes|boolean',
296
            'last_activity' => 'nullable|date',
297
        ]);
298
    }
299
300
    /**
301
     * {@inheritdoc}
302
     */
303
    protected static function boot()
304
    {
305
        parent::boot();
306
307
        static::saving(function (self $user) {
308
            foreach (array_intersect($user->getHashables(), array_keys($user->getAttributes())) as $hashable) {
309
                if ($user->isDirty($hashable) && Hash::needsRehash($user->$hashable)) {
310
                    $user->$hashable = Hash::make($user->$hashable);
311
                }
312
            }
313
        });
314
    }
315
316
    /**
317
     * The user may have many sessions.
318
     *
319
     * @return \Illuminate\Database\Eloquent\Relations\MorphMany
320
     */
321
    public function sessions(): MorphMany
322
    {
323
        return $this->morphMany(config('cortex.fort.models.session'), 'user');
324
    }
325
326
    /**
327
     * The user may have many socialites.
328
     *
329
     * @return \Illuminate\Database\Eloquent\Relations\MorphMany
330
     */
331
    public function socialites(): MorphMany
332
    {
333
        return $this->morphMany(config('cortex.fort.models.socialite'), 'user');
334
    }
335
336
    /**
337
     * Get name attribute.
338
     *
339
     * @return string
340
     */
341
    public function getNameAttribute(): string
342
    {
343
        $name = trim(implode(' ', [$this->name_prefix, $this->first_name, $this->middle_name, $this->last_name, $this->name_suffix]));
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 134 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
344
345
        return $name ?: $this->username;
346
    }
347
348
    /**
349
     * Route notifications for the authy channel.
350
     *
351
     * @return int|null
352
     */
353
    public function routeNotificationForAuthy(): ?int
354
    {
355
        if (! ($authyId = array_get($this->getTwoFactor(), 'phone.authy_id')) && $this->getEmailForVerification() && $this->getPhoneForVerification() && $this->getCountryForVerification()) {
0 ignored issues
show
Bug introduced by
It seems like $this->getTwoFactor() targeting Rinvex\Fort\Traits\Authe...oFactor::getTwoFactor() can also be of type null; however, array_get() does only seem to accept object<ArrayAccess>|array, maybe add an additional type check?

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.

Loading history...
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 190 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
356
            $result = app('rinvex.authy.user')->register($this->getEmailForVerification(), preg_replace('/[^0-9]/', '', $this->getPhoneForVerification()), $this->getCountryForVerification());
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 191 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
357
            $authyId = $result->get('user')['id'];
358
359
            // Prepare required variables
360
            $twoFactor = $this->getTwoFactor();
361
362
            // Update user account
363
            array_set($twoFactor, 'phone.authy_id', $authyId);
0 ignored issues
show
Bug introduced by
It seems like $twoFactor defined by $this->getTwoFactor() on line 360 can also be of type null; however, array_set() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
364
365
            $this->fill(['two_factor' => $twoFactor])->forceSave();
366
        }
367
368
        return $authyId;
369
    }
370
371
    /**
372
     * Get the user's country.
373
     *
374
     * @return \Rinvex\Country\Country
375
     */
376
    public function getCountryAttribute(): Country
377
    {
378
        return country($this->country_code);
379
    }
380
381
    /**
382
     * Get the user's language.
383
     *
384
     * @return \Rinvex\Language\Language
385
     */
386
    public function getLanguageAttribute(): Language
387
    {
388
        return language($this->language_code);
389
    }
390
391
    /**
392
     * Activate the user.
393
     *
394
     * @return $this
395
     */
396
    public function activate()
397
    {
398
        $this->update(['is_active' => true]);
399
400
        return $this;
401
    }
402
403
    /**
404
     * Deactivate the user.
405
     *
406
     * @return $this
407
     */
408
    public function deactivate()
409
    {
410
        $this->update(['is_active' => false]);
411
412
        return $this;
413
    }
414
}
415