Completed
Push — master ( 68774a...bcdaed )
by Chris
02:38 queued 01:03
created

HasPermissions::getPermissionsViaRoles()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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