Completed
Pull Request — master (#23)
by ARCANEDEV
08:45
created

User::detachAllRoles()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 10
ccs 6
cts 6
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 6
nc 1
nop 1
crap 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 75
        }
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 13
        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 13
            )
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
            ->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|\Illuminate\Support\Collection  $slugs
277
     * @param  bool                                  $reload
278
     *
279
     * @return array
280
     */
281 3
    public function syncRoles($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 6
            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 4
            $code = $code->confirmation_code;
363
364 6
        return (new UserConfirmator)->confirm(
365 6
            $this->findUnconfirmed($code)
366 2
        );
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  \Illuminate\Support\Collection|array  $permissions
401
     * @param  \Illuminate\Support\Collection        &$failed
402
     *
403
     * @return bool
404
     */
405 6
    public function mayOne($permissions, &$failed = null)
406
    {
407 6
        $permissions = is_array($permissions) ? collect($permissions) : $permissions;
408
409 6
        $failed = $permissions->reject(function ($permission) {
410 6
            return $this->may($permission);
411 6
        })->values();
412
413 6
        return $permissions->count() !== $failed->count();
414
    }
415
416
    /**
417
     * Check if the user has all permissions.
418
     *
419
     * @param  \Illuminate\Support\Collection|array  $permissions
420
     * @param  \Illuminate\Support\Collection        &$failed
421
     *
422
     * @return bool
423
     */
424 3
    public function mayAll($permissions, &$failed = null)
425
    {
426 3
        $this->mayOne($permissions, $failed);
427
428 3
        return $failed->isEmpty();
0 ignored issues
show
Bug introduced by
The method isEmpty cannot be called on $failed (of type array|null).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
429
    }
430
431
    /* -----------------------------------------------------------------
432
     |  Check Methods
433
     | -----------------------------------------------------------------
434
     */
435
    /**
436
     * Check if user is an administrator.
437
     *
438
     * @return bool
439
     */
440 18
    public function isAdmin()
441
    {
442 18
        return $this->is_admin;
443
    }
444
445
    /**
446
     * Check if user is a moderator.
447
     *
448
     * @return bool
449
     */
450 6
    public function isModerator()
451
    {
452
        // Override this method to give more privileges than members.
453 6
        return false;
454
    }
455
456
    /**
457
     * Check if user is a member.
458
     *
459
     * @return bool
460
     */
461 9
    public function isMember()
462
    {
463 9
        return ! $this->isAdmin();
464
    }
465
466
    /**
467
     * Check if user has a confirmed account.
468
     *
469
     * @return bool
470
     */
471 9
    public function isConfirmed()
472
    {
473 9
        return $this->is_confirmed;
474
    }
475
476
    /**
477
     * Check if user can be impersonated.
478
     *
479
     * @return bool
480
     */
481 3
    public function canBeImpersonated()
482
    {
483 3
        return $this->isMember();
484
    }
485
486
    /* ------------------------------------------------------------------------------------------------
487
     |  Other Functions
488
     | ------------------------------------------------------------------------------------------------
489
     */
490
    /**
491
     * Slugify the value.
492
     *
493
     * @param  string  $value
494
     *
495
     * @return string
496
     */
497 69
    protected function slugify($value)
498
    {
499 69
        return Str::slug($value, config('laravel-auth.users.slug-separator', '.'));
500
    }
501
}
502