Completed
Push — master ( 137cbe...efa073 )
by Arjay
03:16
created

HasRole::canAtLeast()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
dl 0
loc 20
rs 8.8571
c 0
b 0
f 0
ccs 0
cts 17
cp 0
cc 5
eloc 11
nc 4
nop 1
crap 30
1
<?php
2
3
namespace Yajra\Acl\Traits;
4
5
use Illuminate\Support\Str;
6
use Yajra\Acl\Models\Role;
7
8
trait HasRole
9
{
10
    /**
11
     * Check if user have access using any of the acl.
12
     *
13
     * @param  string|array $acl
14
     * @return boolean
15
     */
16
    public function canAccess($acl)
17
    {
18
        return $this->canAtLeast($acl) || $this->hasRole($acl);
0 ignored issues
show
Bug introduced by
It seems like $acl defined by parameter $acl on line 16 can also be of type string; however, Yajra\Acl\Traits\HasRole::canAtLeast() does only seem to accept array, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
19
    }
20
21
    /**
22
     * Check if user has at least one of the given permissions
23
     *
24
     * @param  array $permissions
25
     * @return bool
26
     */
27
    public function canAtLeast(array $permissions)
28
    {
29
        $can = false;
30
31
        if (auth()->check()) {
32
            foreach ($this->roles as $role) {
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...
33
                if ($role->canAtLeast($permissions)) {
34
                    $can = true;
35
                }
36
            }
37
        } else {
38
            $guest = Role::whereSlug('guest')->first();
39
40
            if ($guest) {
41
                return $guest->canAtLeast($permissions);
42
            }
43
        }
44
45
        return $can;
46
    }
47
48
    /**
49
     * Check if user has the given role.
50
     *
51
     * @param $role
52
     * @return bool
53
     */
54
    public function hasRole($role)
55
    {
56
        if (is_string($role)) {
57
            return $this->roles->contains('name', $role);
58
        }
59
60
        if (is_array($role)) {
61
            $roles = $this->getRoleSlugs();
62
63
            $intersection      = array_intersect($roles, (array) $role);
64
            $intersectionCount = count($intersection);
65
66
            return ($intersectionCount > 0) ? true : false;
67
        }
68
69
        return ! ! $role->intersect($this->roles)->count();
70
    }
71
72
    /**
73
     * Get all user roles.
74
     *
75
     * @return array|null
76
     */
77
    public function getRoleSlugs()
78
    {
79
        if (! is_null($this->roles)) {
80
            return $this->roles->pluck('slug')->toArray();
81
        }
82
83
        return null;
84
    }
85
86
    /**
87
     * Attach a role to user using slug.
88
     *
89
     * @param $slug
90
     * @return bool
91
     */
92
    public function attachRoleBySlug($slug)
93
    {
94
        $role = Role::where('slug', $slug)->first();
95
96
        return $this->attachRole($role);
97
    }
98
99
    /**
100
     * Attach a role to user
101
     *
102
     * @param  Role $role
103
     * @return boolean
104
     */
105
    public function attachRole(Role $role)
106
    {
107
        return $this->assignRole($role->id);
0 ignored issues
show
Documentation introduced by
The property id does not exist on object<Yajra\Acl\Models\Role>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
108
    }
109
110
    /**
111
     * Assigns the given role to the user.
112
     *
113
     * @param  int $roleId
114
     * @return bool
115
     */
116
    public function assignRole($roleId = null)
117
    {
118
        $roles = $this->roles;
119
120
        if (! $roles->contains($roleId)) {
121
            return $this->roles()->attach($roleId);
122
        }
123
124
        return false;
125
    }
126
127
    /**
128
     * Model can have many roles.
129
     *
130
     * @return \Illuminate\Database\Eloquent\Model
131
     */
132
    public function roles()
133
    {
134
        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...
135
    }
136
137
    /**
138
     * @param $query
139
     * @param array $roles
140
     * @return mixed
141
     */
142
    public function scopeHavingRoles($query, array $roles)
143
    {
144
        return $query->whereExists(function ($query) use ($roles) {
145
            $query->selectRaw('1')
146
                  ->from('role_user')
147
                  ->whereRaw('role_user.user_id = users.id')
148
                  ->whereIn('role_id', $roles);
149
        });
150
    }
151
152
    /**
153
     * Revokes the given role from the user.
154
     *
155
     * @param $role
156
     * @return bool
157
     */
158
    public function revokeRole($role = "")
159
    {
160
        return $this->roles()->detach($role);
161
    }
162
163
    /**
164
     * Syncs the given role(s) with the user.
165
     *
166
     * @param  array $roles
167
     * @return bool
168
     */
169
    public function syncRoles(array $roles)
170
    {
171
        return $this->roles()->sync($roles);
172
    }
173
174
    /**
175
     * Revokes all roles from the user.
176
     *
177
     * @return bool
178
     */
179
    public function revokeAllRoles()
180
    {
181
        return $this->roles()->detach();
182
    }
183
184
    /**
185
     * Get all user role permissions.
186
     *
187
     * @return array|null
188
     */
189
    public function getPermissions()
190
    {
191
        $permissions = [[], []];
192
193
        foreach ($this->roles as $role) {
194
            $permissions[] = $role->getPermissions();
195
        }
196
197
        return call_user_func_array('array_merge', $permissions);
198
    }
199
200
    /**
201
     * Magic __call method to handle dynamic methods.
202
     *
203
     * @param  string $method
204
     * @param  array $arguments
205
     * @return mixed
206
     */
207
    public function __call($method, $arguments = [])
208
    {
209
        // Handle isRoleSlug() methods
210
        if (Str::startsWith($method, 'is') and $method !== 'is') {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
211
            $role = substr($method, 2);
212
213
            return $this->is($role);
0 ignored issues
show
Documentation Bug introduced by
The method is 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...
214
        }
215
216
        // Handle canDoSomething() methods
217
        if (Str::startsWith($method, 'can') and $method !== 'can') {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
218
            $permission = substr($method, 3);
219
220
            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...
221
        }
222
223
        return parent::__call($method, $arguments);
224
    }
225
226
    /**
227
     * Checks if the user has the given role.
228
     *
229
     * @param  string $slug
230
     * @return bool
231
     */
232
    public function isRole($slug)
233
    {
234
        $slug = Str::lower($slug);
235
236
        foreach ($this->roles as $role) {
237
            if ($role->slug == $slug) {
238
                return true;
239
            }
240
        }
241
242
        return false;
243
    }
244
245
    /**
246
     * Check if the given entity/model is owned by the user.
247
     *
248
     * @param \Illuminate\Database\Eloquent\Model $entity
249
     * @param string $relation
250
     * @return bool
251
     */
252
    public function owns($entity, $relation = 'user_id')
253
    {
254
        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...
255
    }
256
}
257