Completed
Pull Request — master (#494)
by Param
01:57
created

HasRoles::hasRole()   C

Complexity

Conditions 8
Paths 12

Size

Total Lines 26
Code Lines 13

Duplication

Lines 3
Ratio 11.54 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 8
eloc 13
nc 12
nop 1
dl 3
loc 26
rs 5.3846
c 1
b 0
f 1
1
<?php
2
3
namespace Spatie\Permission\Traits;
4
5
use Throwable;
6
use Illuminate\Support\Collection;
7
use Illuminate\Support\Facades\DB;
8
use Illuminate\Support\Facades\Log;
9
use Spatie\Permission\Contracts\Role;
10
use Illuminate\Database\Eloquent\Builder;
11
use Spatie\Permission\Contracts\Permission;
12
use Illuminate\Database\Eloquent\Relations\MorphToMany;
13
14
trait HasRoles
15
{
16
    use HasPermissions;
17
18
    public static function bootHasRoles()
19
    {
20
        static::deleting(function ($model) {
21
            DB::beginTransaction();
22
            try {
23
                $model->roles()->detach();
24
                $model->permissions()->detach();
25
26
                DB::commit();
27
            } catch (Throwable $e) {
28
                DB::rollback();
29
                Log::error($e->getMessage());
30
            }
31
        });
32
    }
33
34
    /**
35
     * A model may have multiple roles.
36
     */
37
    public function roles(): MorphToMany
38
    {
39
        return $this->morphToMany(
0 ignored issues
show
Bug introduced by
It seems like morphToMany() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
40
            config('permission.models.role'),
41
            'model',
42
            config('permission.table_names.model_has_roles'),
43
            'model_id',
44
            'role_id'
45
        );
46
    }
47
48
    /**
49
     * A model may have multiple direct permissions.
50
     */
51
    public function permissions(): MorphToMany
52
    {
53
        return $this->morphToMany(
0 ignored issues
show
Bug introduced by
It seems like morphToMany() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
54
            config('permission.models.permission'),
55
            'model',
56
            config('permission.table_names.model_has_permissions'),
57
            'model_id',
58
            'permission_id'
59
        );
60
    }
61
62
    /**
63
     * Scope the model query to certain roles only.
64
     *
65
     * @param \Illuminate\Database\Eloquent\Builder $query
66
     * @param string|array|\Spatie\Permission\Contracts\Role|\Illuminate\Support\Collection $roles
67
     *
68
     * @return \Illuminate\Database\Eloquent\Builder
69
     */
70
    public function scopeRole(Builder $query, $roles): Builder
71
    {
72
        if ($roles instanceof Collection) {
73
            $roles = $roles->toArray();
74
        }
75
76
        if (! is_array($roles)) {
77
            $roles = [$roles];
78
        }
79
80 View Code Duplication
        $roles = array_map(function ($role) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
81
            if ($role instanceof Role) {
82
                return $role;
83
            }
84
85
            return app(Role::class)->findByName($role, $this->getDefaultGuardName());
86
        }, $roles);
87
88
        return $query->whereHas('roles', function ($query) use ($roles) {
89
            $query->where(function ($query) use ($roles) {
90
                foreach ($roles as $role) {
91
                    $query->orWhere(config('permission.table_names.roles').'.id', $role->id);
92
                }
93
            });
94
        });
95
    }
96
97
    /**
98
     * @param string|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection $permissions
99
     *
100
     * @return array
101
     */
102
    protected function convertToPermissionModels($permissions): array
103
    {
104
        if ($permissions instanceof Collection) {
105
            $permissions = $permissions->toArray();
106
        }
107
108
        $permissions = array_wrap($permissions);
109
110
        return array_map(function ($permission) {
111
            if ($permission instanceof Permission) {
112
                return $permission;
113
            }
114
115
            return app(Permission::class)->findByName($permission, $this->getDefaultGuardName());
116
        }, $permissions);
117
    }
118
119
    /**
120
     * Scope the model query to certain permissions only.
121
     *
122
     * @param \Illuminate\Database\Eloquent\Builder $query
123
     * @param string|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection $permissions
124
     *
125
     * @return \Illuminate\Database\Eloquent\Builder
126
     */
127
    public function scopePermission(Builder $query, $permissions): Builder
128
    {
129
        $permissions = $this->convertToPermissionModels($permissions);
130
131
        $rolesWithPermissions = array_unique(array_reduce($permissions, function ($result, $permission) {
132
            return array_merge($result, $permission->roles->all());
133
        }, []));
134
135
        return $query->
136
            where(function ($query) use ($permissions, $rolesWithPermissions) {
137
                $query->whereHas('permissions', function ($query) use ($permissions) {
138
                    $query->where(function ($query) use ($permissions) {
139
                        foreach ($permissions as $permission) {
140
                            $query->orWhere(config('permission.table_names.permissions').'.id', $permission->id);
141
                        }
142
                    });
143
                });
144
                if (count($rolesWithPermissions) > 0) {
145
                    $query->orWhereHas('roles', function ($query) use ($rolesWithPermissions) {
146
                        $query->where(function ($query) use ($rolesWithPermissions) {
147
                            foreach ($rolesWithPermissions as $role) {
148
                                $query->orWhere(config('permission.table_names.roles').'.id', $role->id);
149
                            }
150
                        });
151
                    });
152
                }
153
            });
154
    }
155
156
    /**
157
     * Assign the given role to the model.
158
     *
159
     * @param array|string|\Spatie\Permission\Contracts\Role ...$roles
160
     *
161
     * @return $this
162
     */
163 View Code Duplication
    public function assignRole(...$roles)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
164
    {
165
        $roles = collect($roles)
166
            ->flatten()
167
            ->map(function ($role) {
168
                return $this->getStoredRole($role);
169
            })
170
            ->each(function ($role) {
171
                $this->ensureModelSharesGuard($role);
172
            })
173
            ->all();
174
175
        $this->roles()->saveMany($roles);
176
177
        $this->forgetCachedPermissions();
178
179
        return $this;
180
    }
181
182
    /**
183
     * Revoke the given role from the model.
184
     *
185
     * @param string|\Spatie\Permission\Contracts\Role $role
186
     */
187
    public function removeRole($role)
188
    {
189
        $this->roles()->detach($this->getStoredRole($role));
190
    }
191
192
    /**
193
     * Remove all current roles and set the given ones.
194
     *
195
     * @param array|\Spatie\Permission\Contracts\Role|string ...$roles
196
     *
197
     * @return $this
198
     */
199
    public function syncRoles(...$roles)
200
    {
201
        $this->roles()->detach();
202
203
        return $this->assignRole($roles);
204
    }
205
206
    /**
207
     * Determine if the model has (one of) the given role(s).
208
     *
209
     * @param string|array|\Spatie\Permission\Contracts\Role|\Illuminate\Support\Collection $roles
210
     *
211
     * @return bool
212
     */
213
    public function hasRole($roles): bool
214
    {
215 View Code Duplication
        if (is_string($roles) && false !== strpos($roles, '|')) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
216
            $roles = $this->convertPipeToArray($roles);
217
        }
218
219
        if (is_string($roles)) {
220
            return $this->roles->contains('name', $roles);
0 ignored issues
show
Bug introduced by
The property roles does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
221
        }
222
223
        if ($roles instanceof Role) {
224
            return $this->roles->contains('id', $roles->id);
0 ignored issues
show
Bug introduced by
Accessing id on the interface Spatie\Permission\Contracts\Role 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...
225
        }
226
227
        if (is_array($roles)) {
228
            foreach ($roles as $role) {
229
                if ($this->hasRole($role)) {
230
                    return true;
231
                }
232
            }
233
234
            return false;
235
        }
236
237
        return $roles->intersect($this->roles)->isNotEmpty();
238
    }
239
240
    /**
241
     * Determine if the model has any of the given role(s).
242
     *
243
     * @param string|array|\Spatie\Permission\Contracts\Role|\Illuminate\Support\Collection $roles
244
     *
245
     * @return bool
246
     */
247
    public function hasAnyRole($roles): bool
248
    {
249
        return $this->hasRole($roles);
250
    }
251
252
    /**
253
     * Determine if the model has all of the given role(s).
254
     *
255
     * @param string|\Spatie\Permission\Contracts\Role|\Illuminate\Support\Collection $roles
256
     *
257
     * @return bool
258
     */
259
    public function hasAllRoles($roles): bool
260
    {
261 View Code Duplication
        if (is_string($roles) && false !== strpos($roles, '|')) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
262
            $roles = $this->convertPipeToArray($roles);
263
        }
264
265
        if (is_string($roles)) {
266
            return $this->roles->contains('name', $roles);
267
        }
268
269
        if ($roles instanceof Role) {
270
            return $this->roles->contains('id', $roles->id);
0 ignored issues
show
Bug introduced by
Accessing id on the interface Spatie\Permission\Contracts\Role 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...
271
        }
272
273
        $roles = collect()->make($roles)->map(function ($role) {
274
            return $role instanceof Role ? $role->name : $role;
0 ignored issues
show
Bug introduced by
Accessing name on the interface Spatie\Permission\Contracts\Role 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...
275
        });
276
277
        return $roles->intersect($this->roles->pluck('name')) == $roles;
278
    }
279
280
    /**
281
     * Determine if the model may perform the given permission.
282
     *
283
     * @param string|\Spatie\Permission\Contracts\Permission $permission
284
     * @param string|null $guardName
285
     *
286
     * @return bool
287
     */
288
    public function hasPermissionTo($permission, $guardName = null): bool
289
    {
290 View Code Duplication
        if (is_string($permission)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
291
            $permission = app(Permission::class)->findByName(
292
                $permission,
293
                $guardName ?? $this->getDefaultGuardName()
294
            );
295
        }
296
297
        return $this->hasDirectPermission($permission) || $this->hasPermissionViaRole($permission);
298
    }
299
300
    /**
301
     * Determine if the model has any of the given permissions.
302
     *
303
     * @param array ...$permissions
304
     *
305
     * @return bool
306
     */
307
    public function hasAnyPermission(...$permissions): bool
308
    {
309
        if (is_array($permissions[0])) {
310
            $permissions = $permissions[0];
311
        }
312
313
        foreach ($permissions as $permission) {
314
            if ($this->hasPermissionTo($permission)) {
315
                return true;
316
            }
317
        }
318
319
        return false;
320
    }
321
322
    /**
323
     * Determine if the model has, via roles, the given permission.
324
     *
325
     * @param \Spatie\Permission\Contracts\Permission $permission
326
     *
327
     * @return bool
328
     */
329
    protected function hasPermissionViaRole(Permission $permission): bool
330
    {
331
        return $this->hasRole($permission->roles);
0 ignored issues
show
Bug introduced by
Accessing roles on the interface Spatie\Permission\Contracts\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...
332
    }
333
334
    /**
335
     * Determine if the model has the given permission.
336
     *
337
     * @param string|\Spatie\Permission\Contracts\Permission $permission
338
     *
339
     * @return bool
340
     */
341
    public function hasDirectPermission($permission): bool
342
    {
343 View Code Duplication
        if (is_string($permission)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
344
            $permission = app(Permission::class)->findByName($permission, $this->getDefaultGuardName());
345
346
            if (! $permission) {
347
                return false;
348
            }
349
        }
350
351
        return $this->permissions->contains('id', $permission->id);
0 ignored issues
show
Bug introduced by
The property permissions does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
352
    }
353
354
    /**
355
     * Return all permissions the directory coupled to the model.
356
     */
357
    public function getDirectPermissions(): Collection
358
    {
359
        return $this->permissions;
360
    }
361
362
    /**
363
     * Return all the permissions the model has via roles.
364
     */
365
    public function getPermissionsViaRoles(): Collection
366
    {
367
        return $this->load('roles', 'roles.permissions')
0 ignored issues
show
Bug introduced by
It seems like load() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
368
            ->roles->flatMap(function ($role) {
369
                return $role->permissions;
370
            })->sort()->values();
371
    }
372
373
    /**
374
     * Return all the permissions the model has, both directly and via roles.
375
     */
376
    public function getAllPermissions(): Collection
377
    {
378
        return $this->permissions
379
            ->merge($this->getPermissionsViaRoles())
380
            ->sort()
381
            ->values();
382
    }
383
384
    public function getRoleNames(): Collection
385
    {
386
        return $this->roles->pluck('name');
387
    }
388
389 View Code Duplication
    protected function getStoredRole($role): Role
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
390
    {
391
        if (is_string($role)) {
392
            return app(Role::class)->findByName($role, $this->getDefaultGuardName());
393
        }
394
395
        return $role;
396
    }
397
398
    protected function convertPipeToArray(string $pipeString)
399
    {
400
        $pipeString = trim($pipeString);
401
402
        if (strlen($pipeString) <= 2) {
403
            return $pipeString;
404
        }
405
406
        $quoteCharacter = substr($pipeString, 0, 1);
407
        $endCharacter = substr($quoteCharacter, -1, 1);
408
409
        if ($quoteCharacter !== $endCharacter) {
410
            return explode('|', $pipeString);
411
        }
412
413
        if (! in_array($quoteCharacter, ["'", '"'])) {
414
            return explode('|', $pipeString);
415
        }
416
417
        return explode('|', trim($pipeString, $quoteCharacter));
418
    }
419
}
420