Completed
Push — master ( 9fbd7a...7c0b5e )
by Arjay
14:02
created

HasRole::scopeHavingRolesBySlugs()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 0
cts 4
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
crap 2
1
<?php
2
3
namespace Yajra\Acl\Traits;
4
5
use Yajra\Acl\Models\Role;
6
use Illuminate\Support\Str;
7
8
/**
9
 * @property \Illuminate\Database\Eloquent\Collection roles
10
 * @method static \Illuminate\Database\Eloquent\Builder havingRoles(array $roleIds)
11
 * @method static \Illuminate\Database\Eloquent\Builder havingRolesBySlugs(array $slugs)
12
 */
13
trait HasRole
14
{
15
    private $roleClass;
16
17
    /**
18
     * Check if user have access using any of the acl.
19
     *
20
     * @param array $acl
21
     * @return boolean
22
     */
23
    public function canAccess(array $acl)
24
    {
25
        return $this->canAtLeast($acl) || $this->hasRole($acl);
26
    }
27
28
    /**
29
     * Check if user has at least one of the given permissions
30
     *
31
     * @param array $permissions
32
     * @return bool
33
     */
34
    public function canAtLeast(array $permissions)
35
    {
36
        $can = false;
37
38
        if (auth()->check()) {
0 ignored issues
show
Bug introduced by
The method check does only exist in Illuminate\Contracts\Auth\Guard, but not in Illuminate\Contracts\Auth\Factory.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
39
            foreach ($this->roles as $role) {
40
                if ($role->canAtLeast($permissions)) {
41
                    $can = true;
42
                }
43
            }
44
        } else {
45
            $guest = $this->getRoleClass()->whereSlug('guest')->first();
46
47
            if ($guest) {
48
                return $guest->canAtLeast($permissions);
49
            }
50
        }
51
52
        return $can;
53
    }
54
55
    /**
56
     * Get Role class.
57
     *
58
     * @return Role
59
     */
60
    public function getRoleClass()
61
    {
62
        if (! isset($this->roleClass)) {
63
            $this->roleClass = resolve(config('acl.role'));
64
        }
65
66
        return $this->roleClass;
67
    }
68
69
    /**
70
     * Check if user has the given role.
71
     *
72
     * @param string|array $role
73
     * @return bool
74
     */
75
    public function hasRole($role)
76
    {
77
        if (is_string($role)) {
78
            return $this->roles->contains('slug', $role);
79
        }
80
81
        if (is_array($role)) {
82
            $roles = $this->getRoleSlugs();
83
84
            $intersection      = array_intersect($roles, (array) $role);
85
            $intersectionCount = count($intersection);
86
87
            return ($intersectionCount > 0) ? true : false;
88
        }
89
90
        return ! ! $role->intersect($this->roles)->count();
91
    }
92
93
    /**
94
     * Get all user roles.
95
     *
96
     * @return array|null
97
     */
98
    public function getRoleSlugs()
99
    {
100
        if (! is_null($this->roles)) {
101
            return $this->roles->pluck('slug')->toArray();
102
        }
103
104
        return null;
105
    }
106
107
    /**
108
     * Attach a role to user using slug.
109
     *
110
     * @param $slug
111
     * @return bool
112
     */
113
    public function attachRoleBySlug($slug)
114
    {
115
        $role = $this->getRoleClass()->where('slug', $slug)->first();
116
117
        return $this->attachRole($role);
118
    }
119
120
    /**
121
     * Attach a role to user
122
     *
123
     * @param Role $role
124
     * @return boolean
125
     */
126
    public function attachRole(Role $role)
127
    {
128
        return $this->assignRole($role->id);
129
    }
130
131
    /**
132
     * Assigns the given role to the user.
133
     *
134
     * @param int $roleId
135
     * @return bool
136
     */
137
    public function assignRole($roleId = null)
138
    {
139
        $roles = $this->roles;
140
141
        if (! $roles->contains($roleId)) {
142
            return $this->roles()->attach($roleId);
143
        }
144
145
        return false;
146
    }
147
148
    /**
149
     * Model can have many roles.
150
     *
151
     * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
152
     */
153
    public function roles()
154
    {
155
        return $this->belongsToMany(config('acl.role', Role::class))->withTimestamps();
0 ignored issues
show
Documentation Bug introduced by
The method belongsToMany does not exist on object<Yajra\Acl\Traits\HasRole>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
156
    }
157
158
    /**
159
     * Query scope for user having the given roles.
160
     *
161
     * @param \Illuminate\Database\Eloquent\Builder $query
162
     * @param array $roles
163
     * @return mixed
164
     */
165
    public function scopeHavingRoles($query, array $roles)
166
    {
167
        return $query->whereExists(function ($query) use ($roles) {
168
            $query->selectRaw('1')
169
                  ->from('role_user')
170
                  ->whereRaw('role_user.user_id = users.id')
171
                  ->whereIn('role_id', $roles);
172
        });
173
    }
174
175
    /**
176
     * Query scope for user having the given roles by slugs.
177
     *
178
     * @param \Illuminate\Database\Eloquent\Builder $query
179
     * @param array $roles
0 ignored issues
show
Bug introduced by
There is no parameter named $roles. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
180
     * @return mixed
181
     */
182
    public function scopeHavingRolesBySlugs($query, array $slugs)
183
    {
184
        return $query->whereHas('roles', function ($query) use ($slugs) {
185
            $query->whereIn('roles.slug', $slugs);
186
        });
187
    }
188
189
    /**
190
     * Revokes the given role from the user using slug.
191
     *
192
     * @param string $slug
193
     * @return bool
194
     */
195
    public function revokeRoleBySlug($slug)
196
    {
197
        $role = $this->getRoleClass()->where('slug', $slug)->first();
198
199
        return $this->roles()->detach($role);
200
    }
201
202
    /**
203
     * Revokes the given role from the user.
204
     *
205
     * @param mixed $role
206
     * @return bool
207
     */
208
    public function revokeRole($role = "")
209
    {
210
        return $this->roles()->detach($role);
211
    }
212
213
    /**
214
     * Syncs the given role(s) with the user.
215
     *
216
     * @param array $roles
217
     * @return bool
218
     */
219
    public function syncRoles(array $roles)
220
    {
221
        return $this->roles()->sync($roles);
222
    }
223
224
    /**
225
     * Revokes all roles from the user.
226
     *
227
     * @return bool
228
     */
229
    public function revokeAllRoles()
230
    {
231
        return $this->roles()->detach();
232
    }
233
234
    /**
235
     * Get all user role permissions.
236
     *
237
     * @return array|null
238
     */
239
    public function getPermissions()
240
    {
241
        $permissions = [[], []];
242
243
        foreach ($this->roles as $role) {
244
            $permissions[] = $role->getPermissions();
245
        }
246
247
        return call_user_func_array('array_merge', $permissions);
248
    }
249
250
    /**
251
     * Magic __call method to handle dynamic methods.
252
     *
253
     * @param string $method
254
     * @param array $arguments
255
     * @return mixed
256
     */
257
    public function __call($method, $arguments = [])
258
    {
259
        // Handle isRoleSlug() methods
260
        if (Str::startsWith($method, 'is') and $method !== 'is') {
261
            $role = substr($method, 2);
262
263
            return $this->isRole($role);
264
        }
265
266
        // Handle canDoSomething() methods
267
        if (Str::startsWith($method, 'can') and $method !== 'can') {
268
            $permission = substr($method, 3);
269
270
            return $this->can($permission);
0 ignored issues
show
Documentation Bug introduced by
The method can does not exist on object<Yajra\Acl\Traits\HasRole>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
271
        }
272
273
        return parent::__call($method, $arguments);
274
    }
275
276
    /**
277
     * Checks if the user has the given role.
278
     *
279
     * @param string $slug
280
     * @return bool
281
     */
282
    public function isRole($slug)
283
    {
284
        $slug = Str::lower($slug);
285
286
        foreach ($this->roles as $role) {
287
            if ($role->slug == $slug) {
288
                return true;
289
            }
290
        }
291
292
        return false;
293
    }
294
295
    /**
296
     * Check if the given entity/model is owned by the user.
297
     *
298
     * @param \Illuminate\Database\Eloquent\Model $entity
299
     * @param string $relation
300
     * @return bool
301
     */
302
    public function owns($entity, $relation = 'user_id')
303
    {
304
        return $this->id === $entity->{$relation};
0 ignored issues
show
Bug introduced by
The property id 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...
305
    }
306
}
307