Completed
Pull Request — master (#23)
by ARCANEDEV
07:14
created

User   B

Complexity

Total Complexity 33

Size/Duplication

Total Lines 446
Duplicated Lines 0 %

Coupling/Cohesion

Components 7
Dependencies 15

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 33
lcom 7
cbo 15
dl 0
loc 446
ccs 99
cts 99
cp 1
rs 7.8333
c 0
b 0
f 0

25 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A setupModel() 0 9 2
A roles() 0 10 1
A getPermissionsAttribute() 0 8 1
A scopeUnconfirmed() 0 6 1
A scopeLastActive() 0 8 2
A setUsernameAttribute() 0 4 1
A getFullNameAttribute() 0 4 1
A setPasswordAttribute() 0 4 1
A attachRole() 0 10 2
A syncRoles() 0 13 1
A detachRole() 0 10 1
A detachAllRoles() 0 10 1
A findUnconfirmed() 0 10 2
A confirm() 0 9 2
A updateLastActivity() 0 6 2
A may() 0 4 1
A mayOne() 0 8 3
A mayAll() 0 6 1
A isAdmin() 0 4 1
A isModerator() 0 5 1
A isMember() 0 4 1
A isConfirmed() 0 4 1
A canBeImpersonated() 0 4 1
A slugify() 0 4 1
1
<?php namespace Arcanedev\LaravelAuth\Models;
2
3
use Arcanedev\LaravelAuth\Events\Users as UserEvents;
4
use Arcanedev\LaravelAuth\Exceptions\UserConfirmationException;
5
use Arcanedev\LaravelAuth\Models\Traits\Activatable;
6
use Arcanedev\LaravelAuth\Models\Traits\AuthRoleTrait;
7
use Arcanedev\LaravelAuth\Services\SocialAuthenticator;
8
use Arcanedev\LaravelAuth\Services\UserConfirmator;
9
use Arcanesoft\Contracts\Auth\Models\Permission as PermissionContract;
10
use Arcanesoft\Contracts\Auth\Models\Role as RoleContract;
11
use Arcanesoft\Contracts\Auth\Models\User as UserContract;
12
use Carbon\Carbon;
13
use Illuminate\Auth\Authenticatable;
14
use Illuminate\Auth\Passwords\CanResetPassword;
15
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
16
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
17
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
18
use Illuminate\Database\Eloquent\SoftDeletes;
19
use Illuminate\Foundation\Auth\Access\Authorizable;
20
use Illuminate\Support\Str;
21
22
/**
23
 * Class     User
24
 *
25
 * @package  Arcanedev\LaravelAuth\Models
26
 * @author   ARCANEDEV <[email protected]>
27
 *
28
 * @property  int                                       id
29
 * @property  string                                    username
30
 * @property  string                                    first_name
31
 * @property  string                                    last_name
32
 * @property  string                                    full_name
33
 * @property  string                                    email
34
 * @property  string                                    password
35
 * @property  string                                    remember_token
36
 * @property  bool                                      is_admin
37
 * @property  bool                                      is_active
38
 * @property  bool                                      is_confirmed       (Optional)
39
 * @property  string                                    confirmation_code  (Optional)
40
 * @property  \Carbon\Carbon                            confirmed_at       (Optional)
41
 * @property  \Carbon\Carbon                            last_activity
42
 * @property  \Carbon\Carbon                            created_at
43
 * @property  \Carbon\Carbon                            updated_at
44
 * @property  \Carbon\Carbon                            deleted_at
45
 *
46
 * @property  \Illuminate\Database\Eloquent\Collection       roles
47
 * @property  \Illuminate\Support\Collection                 permissions
48
 * @property  \Arcanedev\LaravelAuth\Models\Pivots\RoleUser  pivot
49
 *
50
 * @method  static  bool                                   insert(array $values)
51
 * @method          \Illuminate\Database\Eloquent\Builder  unconfirmed(string $code)
52
 * @method          \Illuminate\Database\Eloquent\Builder  lastActive(int $minutes = null)
53
 */
54
class User
0 ignored issues
show
Bug introduced by
There is at least one abstract method in this class. Maybe declare it as abstract, or implement the remaining methods: can, forceFill, getAuthIdentifier, getAuthIdentifierName, getAuthPassword, getEmailForPasswordReset, getRememberToken, getRememberTokenName, sendPasswordResetNotification, setAttribute, setRememberToken
Loading history...
55
    extends AbstractModel
56
    implements UserContract, AuthenticatableContract, AuthorizableContract, CanResetPasswordContract
57
{
58
    /* ------------------------------------------------------------------------------------------------
59
     |  Traits
60
     | ------------------------------------------------------------------------------------------------
61
     */
62
    use AuthRoleTrait,
63
        Authenticatable,
64
        Authorizable,
65
        CanResetPassword,
66
        Activatable,
67
        SoftDeletes;
68
69
    /* ------------------------------------------------------------------------------------------------
70
     |  Properties
71
     | ------------------------------------------------------------------------------------------------
72
     */
73
    /**
74
     * The attributes that are mass assignable.
75
     *
76
     * @var array
77
     */
78
    protected $fillable = [
79
        'username',
80
        'first_name',
81
        'last_name',
82
        'email',
83
        'password',
84
    ];
85
86
    /**
87
     * The attributes excluded from the model's JSON form.
88
     *
89
     * @var array
90
     */
91
    protected $hidden   = [
92
        'password',
93
        'remember_token',
94
        'confirmation_code',
95
    ];
96
97
    /**
98
     * The attributes that should be casted to native types.
99
     *
100
     * @var array
101
     */
102
    protected $casts = [
103
        'is_admin'     => 'boolean',
104
        'is_active'    => 'boolean',
105
        'is_confirmed' => 'boolean',
106
    ];
107
108
    /**
109
     * The attributes that should be mutated to dates.
110
     *
111
     * @var array
112
     */
113
    protected $dates = [
114
        'confirmed_at',
115
        'last_activity',
116
        'deleted_at',
117
    ];
118
119
    /* ------------------------------------------------------------------------------------------------
120
     |  Constructor
121
     | ------------------------------------------------------------------------------------------------
122
     */
123
    /**
124
     * Create a new Eloquent model instance.
125
     *
126
     * @param  array  $attributes
127
     */
128 225
    public function __construct(array $attributes = [])
129
    {
130 225
        parent::__construct($attributes);
131
132 225
        $this->setupModel();
133 225
    }
134
135
    /**
136
     * Setup the model.
137
     */
138 225
    protected function setupModel()
139
    {
140 225
        $this->setTable(config('laravel-auth.users.table', 'users'));
141
142 225
        if (SocialAuthenticator::isEnabled()) {
143 225
            $this->hidden   = array_merge($this->hidden, ['social_provider_id']);
144 225
            $this->fillable = array_merge($this->fillable, ['social_provider', 'social_provider_id']);
145
        }
146 225
    }
147
148
    /* ------------------------------------------------------------------------------------------------
149
     |  Relationships
150
     | ------------------------------------------------------------------------------------------------
151
     */
152
    /**
153
     * User belongs to many roles.
154
     *
155
     * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
156
     */
157 39
    public function roles()
158
    {
159
        return $this
160 39
            ->belongsToMany(
161 39
                config('laravel-auth.roles.model', Role::class),
162 39
                $this->getPrefix().config('laravel-auth.role-user.table', 'role_user')
163
            )
164 39
            ->using(Pivots\RoleUser::class)
165 39
            ->withTimestamps();
166
    }
167
168
    /**
169
     * Get all user permissions.
170
     *
171
     * @return \Illuminate\Support\Collection
172
     */
173 9
    public function getPermissionsAttribute()
174
    {
175 9
        return $this->roles->pluck('permissions')
176 9
            ->flatten()
177 9
            ->unique(function (PermissionContract $permission) {
178 9
                return $permission->id;
0 ignored issues
show
Bug introduced by
Accessing id on the interface Arcanesoft\Contracts\Auth\Models\Permission suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
179 9
            });
180
    }
181
182
    /* ------------------------------------------------------------------------------------------------
183
     |  Scopes
184
     | ------------------------------------------------------------------------------------------------
185
     */
186
    /**
187
     * Scope unconfirmed users by code.
188
     *
189
     * @param  \Illuminate\Database\Eloquent\Builder  $query
190
     * @param  string                                 $code
191
     *
192
     * @return \Illuminate\Database\Eloquent\Builder
193
     */
194 12
    public function scopeUnconfirmed($query, $code)
195
    {
196 12
        return $query->where('is_confirmed', false)
197 12
                     ->where('confirmation_code', $code)
198 12
                     ->whereNull('confirmed_at');
199
    }
200
201
    /**
202
     * Scope last active users.
203
     *
204
     * @param  \Illuminate\Database\Eloquent\Builder  $query
205
     * @param  int|null                               $minutes
206
     *
207
     * @return \Illuminate\Database\Eloquent\Builder
208
     */
209 3
    public function scopeLastActive($query, $minutes = null)
210
    {
211 3
        $minutes = $minutes ?: config('laravel_auth.track-activity.minutes', 5);
212
213 3
        $date = Carbon::now()->subMinutes($minutes);
214
215 3
        return $query->where('last_activity', '>=', $date->toDateTimeString());
216
    }
217
218
    /* ------------------------------------------------------------------------------------------------
219
     |  Getters & Setters
220
     | ------------------------------------------------------------------------------------------------
221
     */
222
    /**
223
     * Set the `username` attribute.
224
     *
225
     * @param  string  $username
226
     */
227 69
    public function setUsernameAttribute($username)
228
    {
229 69
        $this->attributes['username'] = $this->slugify($username);
230 69
    }
231
232
    /**
233
     * Get the `full_name` attribute.
234
     *
235
     * @return string
236
     */
237 3
    public function getFullNameAttribute()
238
    {
239 3
        return $this->first_name.' '.$this->last_name;
240
    }
241
242
    /**
243
     * Set the `password` attribute.
244
     *
245
     * @param  string  $password
246
     */
247 69
    public function setPasswordAttribute($password)
248
    {
249 69
        $this->attributes['password'] = bcrypt($password);
250 69
    }
251
252
    /* -----------------------------------------------------------------
253
     |  Main Methods
254
     | -----------------------------------------------------------------
255
     */
256
    /**
257
     * Attach a role to a user.
258
     *
259
     * @param  \Arcanesoft\Contracts\Auth\Models\Role|int  $role
260
     * @param  bool                                       $reload
261
     */
262 27
    public function attachRole($role, $reload = true)
263
    {
264 27
        if ($this->hasRole($role)) return;
265
266 27
        event(new UserEvents\AttachingRoleToUser($this, $role));
0 ignored issues
show
Bug introduced by
It seems like $role defined by parameter $role on line 262 can also be of type integer; however, Arcanedev\LaravelAuth\Ev...leToUser::__construct() does only seem to accept object<Arcanesoft\Contracts\Auth\Models\Role>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and 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...
267 27
        $this->roles()->attach($role);
268 27
        event(new UserEvents\AttachedRoleToUser($this, $role));
0 ignored issues
show
Bug introduced by
It seems like $role defined by parameter $role on line 262 can also be of type integer; however, Arcanedev\LaravelAuth\Ev...leToUser::__construct() does only seem to accept object<Arcanesoft\Contracts\Auth\Models\Role>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and 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...
269
270 27
        $this->loadRoles($reload);
271 27
    }
272
273
    /**
274
     * Sync the roles by its slugs.
275
     *
276
     * @param  array  $slugs
277
     * @param  bool   $reload
278
     *
279
     * @return array
280
     */
281 3
    public function syncRoles(array $slugs, $reload = true)
282
    {
283
        /** @var  \Illuminate\Database\Eloquent\Collection  $roles */
284 3
        $roles = app(RoleContract::class)->whereIn('slug', $slugs)->get();
285
286 3
        event(new UserEvents\SyncingUserWithRoles($this, $roles));
287 3
        $synced = $this->roles()->sync($roles->pluck('id'));
288 3
        event(new UserEvents\SyncedUserWithRoles($this, $roles, $synced));
289
290 3
        $this->loadRoles($reload);
291
292 3
        return $synced;
293
    }
294
295
    /**
296
     * Detach a role from a user.
297
     *
298
     * @param  \Arcanesoft\Contracts\Auth\Models\Role|int  $role
299
     * @param  bool                                        $reload
300
     *
301
     * @return int
302
     */
303 3
    public function detachRole($role, $reload = true)
304
    {
305 3
        event(new UserEvents\DetachingRole($this, $role));
0 ignored issues
show
Bug introduced by
It seems like $role defined by parameter $role on line 303 can also be of type integer; however, Arcanedev\LaravelAuth\Ev...hingRole::__construct() does only seem to accept object<Arcanesoft\Contracts\Auth\Models\Role>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and 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...
306 3
        $results = $this->roles()->detach($role);
307 3
        event(new UserEvents\DetachedRole($this, $role, $results));
0 ignored issues
show
Bug introduced by
It seems like $role defined by parameter $role on line 303 can also be of type integer; however, Arcanedev\LaravelAuth\Ev...chedRole::__construct() does only seem to accept object<Arcanesoft\Contracts\Auth\Models\Role>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and 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...
308
309 3
        $this->loadRoles($reload);
310
311 3
        return $results;
312
    }
313
314
    /**
315
     * Detach all roles from a user.
316
     *
317
     * @param  bool  $reload
318
     *
319
     * @return int
320
     */
321 3
    public function detachAllRoles($reload = true)
322
    {
323 3
        event(new UserEvents\DetachingRoles($this));
324 3
        $results = $this->roles()->detach();
325 3
        event(new UserEvents\DetachedRoles($this, $results));
326
327 3
        $this->loadRoles($reload);
328
329 3
        return $results;
330
    }
331
332
    /**
333
     * Confirm the unconfirmed user account by confirmation code.
334
     *
335
     * @param  string  $code
336
     *
337
     * @return \Arcanesoft\Contracts\Auth\Models\User
338
     *
339
     * @throws \Arcanedev\LaravelAuth\Exceptions\UserConfirmationException
340
     */
341 12
    public function findUnconfirmed($code)
342
    {
343
        /** @var  \Arcanesoft\Contracts\Auth\Models\User|null  $unconfirmed */
344 12
        $unconfirmed = static::unconfirmed($code)->first();
345
346 12
        if ( ! $unconfirmed instanceof self)
347 3
            throw (new UserConfirmationException)->setModel(static::class);
348
349 9
        return $unconfirmed;
350
    }
351
352
    /**
353
     * Confirm the new user account.
354
     *
355
     * @param  \Arcanesoft\Contracts\Auth\Models\User|string  $code
356
     *
357
     * @return \Arcanesoft\Contracts\Auth\Models\User
358
     */
359 6
    public function confirm($code)
360
    {
361 6
        if ($code instanceof self)
362 3
            $code = $code->confirmation_code;
363
364 6
        return (new UserConfirmator)->confirm(
365 6
            $this->findUnconfirmed($code)
366
        );
367
    }
368
369
    /**
370
     * Update the user's last activity.
371
     *
372
     * @param  bool  $save
373
     */
374 6
    public function updateLastActivity($save = true)
375
    {
376 6
        $this->forceFill(['last_activity' => Carbon::now()]);
377
378 6
        if ($save) $this->save();
379 6
    }
380
381
    /* -----------------------------------------------------------------
382
     |  Permission Check Methods
383
     | -----------------------------------------------------------------
384
     */
385
    /**
386
     * Check if the user has a permission.
387
     *
388
     * @param  string  $slug
389
     *
390
     * @return bool
391
     */
392 9
    public function may($slug)
393
    {
394 9
        return ! $this->permissions->filter->hasSlug($slug)->isEmpty();
395
    }
396
397
    /**
398
     * Check if the user has at least one permission.
399
     *
400
     * @param  array  $permissions
401
     * @param  array  $failedPermissions
402
     *
403
     * @return bool
404
     */
405 6
    public function mayOne(array $permissions, array &$failedPermissions = [])
406
    {
407 6
        foreach ($permissions as $permission) {
408 6
            if ( ! $this->may($permission)) $failedPermissions[] = $permission;
409
        }
410
411 6
        return count($permissions) !== count($failedPermissions);
412
    }
413
414
    /**
415
     * Check if the user has all permissions.
416
     *
417
     * @param  array  $permissions
418
     * @param  array  $failedPermissions
419
     *
420
     * @return bool
421
     */
422 3
    public function mayAll(array $permissions, array &$failedPermissions = [])
423
    {
424 3
        $this->mayOne($permissions, $failedPermissions);
425
426 3
        return count($failedPermissions) === 0;
427
    }
428
429
    /* -----------------------------------------------------------------
430
     |  Check Methods
431
     | -----------------------------------------------------------------
432
     */
433
    /**
434
     * Check if user is an administrator.
435
     *
436
     * @return bool
437
     */
438 18
    public function isAdmin()
439
    {
440 18
        return $this->is_admin;
441
    }
442
443
    /**
444
     * Check if user is a moderator.
445
     *
446
     * @return bool
447
     */
448 6
    public function isModerator()
449
    {
450
        // Override this method to give more privileges than members.
451 6
        return false;
452
    }
453
454
    /**
455
     * Check if user is a member.
456
     *
457
     * @return bool
458
     */
459 9
    public function isMember()
460
    {
461 9
        return ! $this->isAdmin();
462
    }
463
464
    /**
465
     * Check if user has a confirmed account.
466
     *
467
     * @return bool
468
     */
469 9
    public function isConfirmed()
470
    {
471 9
        return $this->is_confirmed;
472
    }
473
474
    /**
475
     * Check if user can be impersonated.
476
     *
477
     * @return bool
478
     */
479 3
    public function canBeImpersonated()
480
    {
481 3
        return $this->isMember();
482
    }
483
484
    /* ------------------------------------------------------------------------------------------------
485
     |  Other Functions
486
     | ------------------------------------------------------------------------------------------------
487
     */
488
    /**
489
     * Slugify the value.
490
     *
491
     * @param  string  $value
492
     *
493
     * @return string
494
     */
495 69
    protected function slugify($value)
496
    {
497 69
        return Str::slug($value, config('laravel-auth.users.slug-separator', '.'));
498
    }
499
}
500