Completed
Push — master ( a07a51...d6976d )
by Mostafa Abd El-Salam
15s
created

HasPermissions::getPermissionClass()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 0
dl 0
loc 6
ccs 2
cts 2
cp 1
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Maklad\Permission\Traits;
4
5
use Illuminate\Support\Collection;
6
use Jenssegers\Mongodb\Eloquent\Builder;
7
use Jenssegers\Mongodb\Eloquent\Model;
8
use Jenssegers\Mongodb\Relations\BelongsToMany;
9
use Maklad\Permission\Contracts\PermissionInterface as Permission;
10
use Maklad\Permission\Exceptions\GuardDoesNotMatch;
11
use Maklad\Permission\Guard;
12
use Maklad\Permission\Helpers;
13
use Maklad\Permission\Models\Role;
14
use Maklad\Permission\PermissionRegistrar;
15
16
/**
17
 * Trait HasPermissions
18
 * @package Maklad\Permission\Traits
19
 */
20
trait HasPermissions
21
{
22
    private $permissionClass;
23
24 123
    public static function bootHasPermissions()
25 6
    {
26 2
        static::deleting(function (Model $model) {
27
            if (isset($model->forceDeleting) && !$model->forceDeleting) {
0 ignored issues
show
Bug introduced by
The property forceDeleting does not seem to exist on Jenssegers\Mongodb\Eloquent\Model. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
28
                return;
29 4
            }
30 123
31 123
            $model->permissions()->sync([]);
32
        });
33
    }
34
35
    public function getPermissionClass()
36
    {
37 54
        if ($this->permissionClass === null) {
38
            $this->permissionClass = app(PermissionRegistrar::class)->getPermissionClass();
39 54
        }
40
        return $this->permissionClass;
41
    }
42
43
    /**
44
     * A role may be given various permissions.
45 2
     * @return BelongsToMany
46
     */
47 2
    public function permissions(): BelongsToMany
48
    {
49
        return $this->belongsToMany(config('permission.models.permission'));
0 ignored issues
show
Bug introduced by
It seems like belongsToMany() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

49
        return $this->/** @scrutinizer ignore-call */ belongsToMany(config('permission.models.permission'));
Loading history...
50
    }
51
52
    /**
53
     * A role belongs to some users of the model associated with its guard.
54
     */
55
    public function users(): BelongsToMany
56
    {
57
        return $this->belongsToMany($this->helpers->getModelForGuard($this->attributes['guard_name']));
58 46
    }
59
60 46
    /**
61 46
     * Grant the given permission(s) to a role.
62 46
     *
63 44
     * @param string|array|Permission|\Illuminate\Support\Collection $permissions
64 46
     *
65 44
     * @return $this
66 42
     * @throws GuardDoesNotMatch
67 44
     */
68 41
    public function givePermissionTo(...$permissions): self
69
    {
70 41
        $permissions = collect($permissions)
71
            ->flatten()
72 41
            ->map(function ($permission) {
73
                return $this->getStoredPermission($permission);
74 41
            })
75
            ->each(function ($permission) {
76
                $this->ensureModelSharesGuard($permission);
77
            })
78
            ->all();
79
80
        $this->permissions()->saveMany($permissions);
81
82
        $this->forgetCachedPermissions();
83
84
        return $this;
85 4
    }
86
87 4
    /**
88
     * Remove all current permissions and set the given ones.
89 4
     *
90
     * @param string|array|Permission|\Illuminate\Support\Collection $permissions
91
     *
92
     * @return $this
93
     * @throws GuardDoesNotMatch
94
     */
95
    public function syncPermissions(...$permissions): self
96
    {
97
        $this->permissions()->sync([]);
98
99
        return $this->givePermissionTo($permissions);
100 6
    }
101
102 6
    /**
103 6
     * Revoke the given permission.
104 6
     *
105 6
     * @param string|array|Permission|\Illuminate\Support\Collection $permissions
106 6
     *
107
     * @return $this
108 6
     * @throws \Maklad\Permission\Exceptions\GuardDoesNotMatch
109 6
     */
110
    public function revokePermissionTo(...$permissions): self
111 6
    {
112
        collect($permissions)
113 6
            ->flatten()
114
            ->map(function ($permission) {
115
                $permission = $this->getStoredPermission($permission);
116
                $this->permissions()->detach($permission);
117
118
                return $permission;
119
            });
120
121
        $this->forgetCachedPermissions();
122 45
123
        return $this;
124 45
    }
125 32
126
    /**
127
     * @param string|Permission $permission
128 16
     *
129
     * @return Permission
130
     * @throws \ReflectionException
131
     */
132
    protected function getStoredPermission($permission): Permission
133
    {
134
        if (\is_string($permission)) {
135
            return $this->getPermissionClass()->findByName($permission, $this->getDefaultGuardName());
136
        }
137 83
138
        return $permission;
139 83
    }
140 5
141 5
    /**
142 5
     * @param Model $roleOrPermission
143
     *
144 5
     * @throws GuardDoesNotMatch
145
     * @throws \ReflectionException
146 79
     */
147
    protected function ensureModelSharesGuard(Model $roleOrPermission)
148
    {
149
        if (! $this->getGuardNames()->contains($roleOrPermission->guard_name)) {
0 ignored issues
show
Bug introduced by
The property guard_name does not exist on Jenssegers\Mongodb\Eloquent\Model. Did you mean guarded?
Loading history...
150
            $expected = $this->getGuardNames();
151
            $given    = $roleOrPermission->guard_name;
152 86
            $helpers  = new Helpers();
153
154 86
            throw new GuardDoesNotMatch($helpers->getGuardDoesNotMatchMessage($expected, $given));
155
        }
156
    }
157
158
    /**
159
     * @return Collection
160
     * @throws \ReflectionException
161 78
     */
162
    protected function getGuardNames(): Collection
163 78
    {
164
        return (new Guard())->getNames($this);
165
    }
166
167
    /**
168
     * @return string
169 80
     * @throws \ReflectionException
170
     */
171 80
    protected function getDefaultGuardName(): string
172 80
    {
173
        return (new Guard())->getDefaultName($this);
174
    }
175
176
    /**
177
     * Forget the cached permissions.
178
     */
179
    public function forgetCachedPermissions()
180
    {
181 7
        app(PermissionRegistrar::class)->forgetCachedPermissions();
182
    }
183 7
184 3
    /**
185
     * Convert to Permission Models
186
     *
187 7
     * @param string|array|Collection $permissions
188 5
     *
189
     * @return Collection
190
     */
191 7
    private function convertToPermissionModels($permissions): Collection
192 7
    {
193 7
        if (\is_array($permissions)) {
194
            $permissions = collect($permissions);
195 6
        }
196
197
        if (! $permissions instanceof Collection) {
198
            $permissions = collect([$permissions]);
199
        }
200
201
        $permissions = $permissions->map(function ($permission) {
202
            return $this->getStoredPermission($permission);
203 1
        });
204
205 1
        return $permissions;
206
    }
207
208
    /**
209
     * Return a collection of permission names associated with this user.
210
     *
211 3
     * @return Collection
212
     */
213 3
    public function getPermissionNames(): Collection
214 3
    {
215 2
        return $this->getAllPermissions()->pluck('name');
216 3
    }
217
218
    /**
219
     * Return all the permissions the model has via roles.
220
     */
221
    public function getPermissionsViaRoles(): Collection
222 2
    {
223
        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? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

223
        return $this->/** @scrutinizer ignore-call */ load('roles', 'roles.permissions')
Loading history...
224 2
            ->roles->flatMap(function (Role $role) {
225 2
                return $role->permissions;
0 ignored issues
show
Bug Best Practice introduced by
The property permissions does not exist on Maklad\Permission\Models\Role. Since you implemented __get, consider adding a @property annotation.
Loading history...
226 2
            })->sort()->values();
227 2
    }
228
229
    /**
230
     * Return all the permissions the model has, both directly and via roles.
231
     */
232
    public function getAllPermissions(): Collection
233
    {
234
        return $this->permissions
0 ignored issues
show
Bug introduced by
The property permissions does not exist on Maklad\Permission\Traits\HasPermissions. Did you mean permissionClass?
Loading history...
235
            ->merge($this->getPermissionsViaRoles())
236
            ->sort()
237
            ->values();
238
    }
239 24
240
    /**
241 24
     * Determine if the model may perform the given permission.
242 15
     *
243 15
     * @param string|Permission $permission
244 15
     * @param string|null $guardName
245
     *
246
     * @return bool
247
     * @throws \ReflectionException
248 22
     */
249
    public function hasPermissionTo($permission, $guardName = null): bool
250
    {
251
        if (\is_string($permission)) {
252
            $permission = $this->getPermissionClass()->findByName(
253
                $permission,
254
                $guardName ?? $this->getDefaultGuardName()
255
            );
256
        }
257
258
        return $this->hasDirectPermission($permission) || $this->hasPermissionViaRole($permission);
259 8
    }
260
261 8
    /**
262 6
     * Determine if the model has any of the given permissions.
263
     *
264
     * @param array ...$permissions
265 8
     *
266 8
     * @return bool
267 8
     * @throws \ReflectionException
268
     */
269
    public function hasAnyPermission(...$permissions): bool
270
    {
271 5
        if (\is_array($permissions[0])) {
272
            $permissions = $permissions[0];
273
        }
274
275
        foreach ($permissions as $permission) {
276
            if ($this->hasPermissionTo($permission)) {
277
                return true;
278
            }
279
        }
280
281 17
        return false;
282
    }
283 17
284
    /**
285
     * Determine if the model has, via roles, the given permission.
286
     *
287
     * @param Permission $permission
288
     *
289
     * @return bool
290
     */
291
    protected function hasPermissionViaRole(Permission $permission): bool
292
    {
293
        return $this->hasRole($permission->roles);
0 ignored issues
show
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? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

293
        return $this->/** @scrutinizer ignore-call */ hasRole($permission->roles);
Loading history...
Bug introduced by
Accessing roles on the interface Maklad\Permission\Contracts\PermissionInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
294 23
    }
295
296 23
    /**
297 1
     * Determine if the model has the given permission.
298
     *
299
     * @param string|Permission $permission
300 23
     *
301
     * @return bool
302
     * @throws \ReflectionException
303
     */
304
    public function hasDirectPermission($permission): bool
305
    {
306 1
        if (\is_string($permission)) {
307
            $permission = $this->getPermissionClass()->findByName($permission, $this->getDefaultGuardName());
308 1
        }
309
310
        return $this->permissions->contains('id', $permission->id);
0 ignored issues
show
Bug introduced by
The property permissions does not exist on Maklad\Permission\Traits\HasPermissions. Did you mean permissionClass?
Loading history...
311
    }
312
313
    /**
314
     * Return all permissions the directory coupled to the model.
315
     */
316
    public function getDirectPermissions(): Collection
317
    {
318
        return $this->permissions;
0 ignored issues
show
Bug introduced by
The property permissions does not exist on Maklad\Permission\Traits\HasPermissions. Did you mean permissionClass?
Loading history...
319 7
    }
320
321 7
    /**
322
     * Scope the model query to certain permissions only.
323 6
     *
324
     * @param Builder $query
325 6
     * @param string|array|\Maklad\Permission\Contracts\PermissionInterface|Collection $permissions
326 6
     *
327
     * @return Builder
328 6
     */
329
    public function scopePermission(Builder $query, $permissions): Builder
330 6
    {
331 6
        $permissions = $this->convertToPermissionModels($permissions);
0 ignored issues
show
Bug introduced by
It seems like $permissions can also be of type Maklad\Permission\Contracts\PermissionInterface; however, parameter $permissions of Maklad\Permission\Traits...ertToPermissionModels() does only seem to accept Illuminate\Support\Collection|array|string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

331
        $permissions = $this->convertToPermissionModels(/** @scrutinizer ignore-type */ $permissions);
Loading history...
332
333
        $roles = \collect([]);
334
335
        foreach ($permissions as $permission) {
336
            $roles = $roles->merge($permission->roles);
337
        }
338
        $roles = $roles->unique();
339
340
        return $query->orWhereIn('permission_ids', $permissions->pluck('_id'))
341
            ->orWhereIn('role_ids', $roles->pluck('_id'));
342
    }
343
}
344