Completed
Push — master ( d698f3...7f8a39 )
by Freek
01:44
created

HasRoles::getAllPermissions()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 7
c 0
b 0
f 0
rs 9.4285
cc 1
eloc 5
nc 1
nop 0
1
<?php
2
3
namespace Spatie\Permission\Traits;
4
5
use Illuminate\Support\Collection;
6
use Spatie\Permission\Contracts\Role;
7
use Illuminate\Database\Eloquent\Builder;
8
use Spatie\Permission\Contracts\Permission;
9
use Illuminate\Database\Eloquent\Relations\MorphToMany;
10
11
trait HasRoles
12
{
13
    use HasPermissions;
14
15
    public static function bootHasRoles()
16
    {
17
        // Delete associated relations roles/permissions
18
        static::deleting(function ($model) {
19
            $model->roles()->detach();
20
            $model->permissions()->detach();
21
        });
22
23
        // Eager load relations by default
24
        static::addGlobalScope('relations', function (Builder $builder) {
25
            $builder->with(['roles', 'permissions']);
26
        });
27
    }
28
29
    /**
30
     * A model may have multiple roles.
31
     */
32
    public function roles(): MorphToMany
33
    {
34
        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...
35
            config('permission.models.role'),
36
            'model',
37
            config('permission.table_names.model_has_roles'),
38
            'model_id',
39
            'role_id'
40
        );
41
    }
42
43
    /**
44
     * A model may have multiple direct permissions.
45
     */
46
    public function permissions(): MorphToMany
47
    {
48
        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...
49
            config('permission.models.permission'),
50
            'model',
51
            config('permission.table_names.model_has_permissions'),
52
            'model_id',
53
            'permission_id'
54
        );
55
    }
56
57
    /**
58
     * Scope the model query to certain roles only.
59
     *
60
     * @param \Illuminate\Database\Eloquent\Builder $query
61
     * @param string|array|Role|\Illuminate\Support\Collection $roles
62
     *
63
     * @return \Illuminate\Database\Eloquent\Builder
64
     */
65
    public function scopeRole(Builder $query, $roles): Builder
66
    {
67
        if ($roles instanceof Collection) {
68
            $roles = $roles->toArray();
69
        }
70
71
        if (! is_array($roles)) {
72
            $roles = [$roles];
73
        }
74
75 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...
76
            if ($role instanceof Role) {
77
                return $role;
78
            }
79
80
            return app(Role::class)->findByName($role, $this->getDefaultGuardName());
81
        }, $roles);
82
83
        return $query->whereHas('roles', function ($query) use ($roles) {
84
            $query->where(function ($query) use ($roles) {
85
                foreach ($roles as $role) {
86
                    $query->orWhere(config('permission.table_names.roles').'.id', $role->id);
87
                }
88
            });
89
        });
90
    }
91
92
    /**
93
     * Assign the given role to the model.
94
     *
95
     * @param array|string|\Spatie\Permission\Contracts\Role ...$roles
96
     *
97
     * @return $this
98
     */
99
    public function assignRole(...$roles)
100
    {
101
        $roles = collect($roles)
102
            ->flatten()
103
            ->map(function ($role) {
104
                return $this->getStoredRole($role);
105
            })
106
            ->each(function ($role) {
107
                $this->ensureModelSharesGuard($role);
108
            })
109
            ->all();
110
111
        $this->roles()->saveMany($roles);
112
113
        $this->forgetCachedPermissions();
114
115
        return $this;
116
    }
117
118
    /**
119
     * Revoke the given role from the model.
120
     *
121
     * @param string|\Spatie\Permission\Contracts\Role $role
122
     */
123
    public function removeRole($role)
124
    {
125
        $this->roles()->detach($this->getStoredRole($role));
126
    }
127
128
    /**
129
     * Remove all current roles and set the given ones.
130
     *
131
     * @param array ...$roles
132
     *
133
     * @return $this
134
     */
135
    public function syncRoles(...$roles)
136
    {
137
        $this->roles()->detach();
138
139
        return $this->assignRole($roles);
140
    }
141
142
    /**
143
     * Determine if the model has (one of) the given role(s).
144
     *
145
     * @param string|array|\Spatie\Permission\Contracts\Role|\Illuminate\Support\Collection $roles
146
     *
147
     * @return bool
148
     */
149
    public function hasRole($roles): bool
150
    {
151 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...
152
            $roles = $this->convertPipeToArray($roles);
153
        }
154
155
        if (is_string($roles)) {
156
            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...
157
        }
158
159
        if ($roles instanceof Role) {
160
            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...
161
        }
162
163
        if (is_array($roles)) {
164
            foreach ($roles as $role) {
165
                if ($this->hasRole($role)) {
166
                    return true;
167
                }
168
            }
169
170
            return false;
171
        }
172
173
        return $roles->intersect($this->roles)->isNotEmpty();
174
    }
175
176
    /**
177
     * Determine if the model has any of the given role(s).
178
     *
179
     * @param string|array|\Spatie\Permission\Contracts\Role|\Illuminate\Support\Collection $roles
180
     *
181
     * @return bool
182
     */
183
    public function hasAnyRole($roles): bool
184
    {
185
        return $this->hasRole($roles);
186
    }
187
188
    /**
189
     * Determine if the model has all of the given role(s).
190
     *
191
     * @param string|\Spatie\Permission\Contracts\Role|\Illuminate\Support\Collection $roles
192
     *
193
     * @return bool
194
     */
195
    public function hasAllRoles($roles): bool
196
    {
197 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...
198
            $roles = $this->convertPipeToArray($roles);
199
        }
200
201
        if (is_string($roles)) {
202
            return $this->roles->contains('name', $roles);
203
        }
204
205
        if ($roles instanceof Role) {
206
            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...
207
        }
208
209
        $roles = collect()->make($roles)->map(function ($role) {
210
            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...
211
        });
212
213
        return $roles->intersect($this->roles->pluck('name')) == $roles;
214
    }
215
216
    /**
217
     * Determine if the model may perform the given permission.
218
     *
219
     * @param string|\Spatie\Permission\Contracts\Permission $permission
220
     * @param string|null $guardName
221
     *
222
     * @return bool
223
     */
224
    public function hasPermissionTo($permission, $guardName = null): bool
225
    {
226 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...
227
            $permission = app(Permission::class)->findByName(
228
                $permission,
229
                $guardName ?? $this->getDefaultGuardName()
230
            );
231
        }
232
233
        return $this->hasDirectPermission($permission) || $this->hasPermissionViaRole($permission);
234
    }
235
236
    /**
237
     * Determine if the model has any of the given permissions.
238
     *
239
     * @param array ...$permissions
240
     *
241
     * @return bool
242
     */
243
    public function hasAnyPermission(...$permissions): bool
244
    {
245
        if (is_array($permissions[0])) {
246
            $permissions = $permissions[0];
247
        }
248
249
        foreach ($permissions as $permission) {
250
            if ($this->hasPermissionTo($permission)) {
251
                return true;
252
            }
253
        }
254
255
        return false;
256
    }
257
258
    /**
259
     * Determine if the model has, via roles, the given permission.
260
     *
261
     * @param \Spatie\Permission\Contracts\Permission $permission
262
     *
263
     * @return bool
264
     */
265
    protected function hasPermissionViaRole(Permission $permission): bool
266
    {
267
        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...
268
    }
269
270
    /**
271
     * Determine if the model has the given permission.
272
     *
273
     * @param string|\Spatie\Permission\Contracts\Permission $permission
274
     *
275
     * @return bool
276
     */
277
    public function hasDirectPermission($permission): bool
278
    {
279 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...
280
            $permission = app(Permission::class)->findByName($permission, $this->getDefaultGuardName());
281
282
            if (! $permission) {
283
                return false;
284
            }
285
        }
286
287
        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...
288
    }
289
290
    /**
291
     * Return all permissions the directory coupled to the model.
292
     */
293
    public function getDirectPermissions(): Collection
294
    {
295
        return $this->permissions;
296
    }
297
298
    /**
299
     * Return all the permissions the model has via roles.
300
     */
301
    public function getPermissionsViaRoles(): Collection
302
    {
303
        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...
304
            ->roles->flatMap(function ($role) {
305
                return $role->permissions;
306
            })->sort()->values();
307
    }
308
309
    /**
310
     * Return all the permissions the model has, both directly and via roles.
311
     */
312
    public function getAllPermissions(): Collection
313
    {
314
        return $this->permissions
315
            ->merge($this->getPermissionsViaRoles())
316
            ->sort()
317
            ->values();
318
    }
319
320 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...
321
    {
322
        if (is_string($role)) {
323
            return app(Role::class)->findByName($role, $this->getDefaultGuardName());
324
        }
325
326
        return $role;
327
    }
328
329
    protected function convertPipeToArray(string $pipeString)
330
    {
331
        $pipeString = trim($pipeString);
332
333
        if (strlen($pipeString) <= 2) {
334
            return $pipeString;
335
        }
336
337
        $quoteCharacter = substr($pipeString, 0, 1);
338
        $endCharacter = substr($quoteCharacter, -1, 1);
339
340
        if ($quoteCharacter !== $endCharacter) {
341
            return explode('|', $pipeString);
342
        }
343
344
        if (! in_array($quoteCharacter, ["'", '"'])) {
345
            return explode('|', $pipeString);
346
        }
347
348
        return explode('|', trim($pipeString, $quoteCharacter));
349
    }
350
}
351