Completed
Push — master ( 8f5508...0e0fba )
by Chris
01:32
created

HasPermissions::ensureModelSharesGuard()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 1
dl 0
loc 6
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
    public function hasAnyPermission(...$permissions): bool
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, via roles, the given permission.
146
     *
147
     * @param \Spatie\Permission\Contracts\Permission $permission
148
     *
149
     * @return bool
150
     */
151
    protected function hasPermissionViaRole(Permission $permission): bool
152
    {
153
        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...
154
    }
155
156
    /**
157
     * Determine if the model has the given permission.
158
     *
159
     * @param string|\Spatie\Permission\Contracts\Permission $permission
160
     *
161
     * @return bool
162
     */
163
    public function hasDirectPermission($permission): bool
164
    {
165 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...
166
            $permission = app(Permission::class)->findByName($permission, $this->getDefaultGuardName());
167
            if (! $permission) {
168
                return false;
169
            }
170
        }
171
172
        if (is_int($permission)) {
173
            $permission = app(Permission::class)->findById($permission, $this->getDefaultGuardName());
174
            if (! $permission) {
175
                return false;
176
            }
177
        }
178
179
        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...
180
    }
181
182
    /**
183
     * Return all the permissions the model has via roles.
184
     */
185
    public function getPermissionsViaRoles(): Collection
186
    {
187
        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...
188
            ->roles->flatMap(function ($role) {
189
                return $role->permissions;
190
            })->sort()->values();
191
    }
192
193
    /**
194
     * Return all the permissions the model has, both directly and via roles.
195
     */
196
    public function getAllPermissions(): Collection
197
    {
198
        return $this->permissions
199
            ->merge($this->getPermissionsViaRoles())
200
            ->sort()
201
            ->values();
202
    }
203
204
    /**
205
     * Grant the given permission(s) to a role.
206
     *
207
     * @param string|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection $permissions
208
     *
209
     * @return $this
210
     */
211 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...
212
    {
213
        $permissions = collect($permissions)
214
            ->flatten()
215
            ->map(function ($permission) {
216
                return $this->getStoredPermission($permission);
217
            })
218
            ->each(function ($permission) {
219
                $this->ensureModelSharesGuard($permission);
220
            })
221
            ->all();
222
223
        $this->permissions()->saveMany($permissions);
224
225
        $this->forgetCachedPermissions();
226
227
        return $this;
228
    }
229
230
    /**
231
     * Remove all current permissions and set the given ones.
232
     *
233
     * @param string|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection $permissions
234
     *
235
     * @return $this
236
     */
237
    public function syncPermissions(...$permissions)
238
    {
239
        $this->permissions()->detach();
240
241
        return $this->givePermissionTo($permissions);
242
    }
243
244
    /**
245
     * Revoke the given permission.
246
     *
247
     * @param \Spatie\Permission\Contracts\Permission|\Spatie\Permission\Contracts\Permission[]|string|string[] $permission
248
     *
249
     * @return $this
250
     */
251
    public function revokePermissionTo($permission)
252
    {
253
        $this->permissions()->detach($this->getStoredPermission($permission));
254
255
        $this->forgetCachedPermissions();
256
257
        return $this;
258
    }
259
260
    /**
261
     * @param string|array|\Spatie\Permission\Contracts\Permission|\Illuminate\Support\Collection $permissions
262
     *
263
     * @return \Spatie\Permission\Contracts\Permission|\Spatie\Permission\Contracts\Permission[]|\Illuminate\Support\Collection
264
     */
265
    protected function getStoredPermission($permissions)
266
    {
267
        if (is_numeric($permissions)) {
268
            return app(Permission::class)->findById($permissions, $this->getDefaultGuardName());
269
        }
270
271
        if (is_string($permissions)) {
272
            return app(Permission::class)->findByName($permissions, $this->getDefaultGuardName());
273
        }
274
275
        if (is_array($permissions)) {
276
            return app(Permission::class)
277
                ->whereIn('name', $permissions)
278
                ->whereIn('guard_name', $this->getGuardNames())
279
                ->get();
280
        }
281
282
        return $permissions;
283
    }
284
285
    /**
286
     * @param \Spatie\Permission\Contracts\Permission|\Spatie\Permission\Contracts\Role $roleOrPermission
287
     *
288
     * @throws \Spatie\Permission\Exceptions\GuardDoesNotMatch
289
     */
290
    protected function ensureModelSharesGuard($roleOrPermission)
291
    {
292
        if (! $this->getGuardNames()->contains($roleOrPermission->guard_name)) {
293
            throw GuardDoesNotMatch::create($roleOrPermission->guard_name, $this->getGuardNames());
294
        }
295
    }
296
297
    protected function getGuardNames(): Collection
298
    {
299
        return Guard::getNames($this);
300
    }
301
302
    protected function getDefaultGuardName(): string
303
    {
304
        return Guard::getDefaultName($this);
305
    }
306
307
    /**
308
     * Forget the cached permissions.
309
     */
310
    public function forgetCachedPermissions()
311
    {
312
        app(PermissionRegistrar::class)->forgetCachedPermissions();
313
    }
314
}
315