diff --git a/src/Directives/CanAtLeastDirective.php b/src/Directives/CanAtLeastDirective.php index 1492842..72e9634 100644 --- a/src/Directives/CanAtLeastDirective.php +++ b/src/Directives/CanAtLeastDirective.php @@ -9,13 +9,11 @@ class CanAtLeastDirective extends DirectiveAbstract /** * Can at least blade directive compiler. * - * @throws \Exception - * @throws \Throwable + * @param string|string[] $permissions */ public function handle(string|array $permissions): bool { - if ($this->auth->check()) { - // @phpstan-ignore-next-line + if ($this->auth->user() && method_exists($this->auth->user(), 'canAtLeast')) { return $this->auth->user()->canAtLeast((array) $permissions); } diff --git a/src/Directives/RoleDirective.php b/src/Directives/RoleDirective.php index e8120e4..33c288c 100644 --- a/src/Directives/RoleDirective.php +++ b/src/Directives/RoleDirective.php @@ -6,11 +6,12 @@ class RoleDirective extends DirectiveAbstract { /** * Is Role blade directive compiler. + * + * @param string|string[] $role */ public function handle(string|array $role): bool { - if ($this->auth->check()) { - // @phpstan-ignore-next-line + if ($this->auth->user() && method_exists($this->auth->user(), 'hasRole')) { return $this->auth->user()->hasRole($role); } diff --git a/src/GateRegistrar.php b/src/GateRegistrar.php index 0927ac2..d101b77 100644 --- a/src/GateRegistrar.php +++ b/src/GateRegistrar.php @@ -5,29 +5,31 @@ use Exception; use Illuminate\Contracts\Auth\Access\Gate as GateContract; use Illuminate\Contracts\Cache\Repository; -use Illuminate\Support\Collection; +use Illuminate\Database\Eloquent\Collection; +use Illuminate\Foundation\Auth\User; use Illuminate\Support\Str; use Yajra\Acl\Models\Permission; class GateRegistrar { - /** - * GateRegistrar constructor. - */ public function __construct(public GateContract $gate, public Repository $cache) { } - /** - * Handle permission gate registration. - */ public function register(): void { - // @phpstan-ignore-next-line $this->getPermissions()->each(function (Permission $permission) { $ability = $permission->slug; - $policy = fn ($user) => // @phpstan-ignore-next-line -collect($user->getPermissions())->contains($permission->slug); + $policy = function (User $user) use ($permission) { + if (method_exists($user, 'getPermissions')) { + // @phpstan-ignore-next-line + $permissions = collect($user->getPermissions()); + + return $permissions->contains($permission->slug); + } + + return false; + }; if (Str::contains($permission->slug, '@')) { $policy = $permission->slug; @@ -40,6 +42,8 @@ public function register(): void /** * Get all permissions. + * + * @return \Illuminate\Database\Eloquent\Collection */ protected function getPermissions(): Collection { @@ -48,7 +52,6 @@ protected function getPermissions(): Collection try { if (config('acl.cache.enabled', true)) { - // @phpstan-ignore-next-line return $this->cache->rememberForever($key, fn () => $this->getPermissionClass()->with('roles')->get()); } else { return $this->getPermissionClass()->with('roles')->get(); @@ -56,13 +59,13 @@ protected function getPermissions(): Collection } catch (Exception) { $this->cache->forget($key); - return new Collection; + return Collection::make(); } } protected function getPermissionClass(): Permission { - /** @var class-string $class */ + /** @var class-string $class */ $class = config('acl.permission', Permission::class); return resolve($class); diff --git a/src/Middleware/CanAtLeastMiddleware.php b/src/Middleware/CanAtLeastMiddleware.php index 8caf29d..067dae6 100644 --- a/src/Middleware/CanAtLeastMiddleware.php +++ b/src/Middleware/CanAtLeastMiddleware.php @@ -10,16 +10,25 @@ class CanAtLeastMiddleware /** * Handle an incoming request. * - * @return mixed + * @param string|string[] $permissions */ - public function handle(Request $request, Closure $next, array|string $permissions) + public function handle(Request $request, Closure $next, array|string $permissions): mixed { $abilities = is_array($permissions) ? $permissions : explode(',', $permissions); - if (! auth()->check() || ! auth()->user()->canAtLeast($abilities)) { + if ($this->deniesAccessUsing($abilities)) { abort(403, 'You are not allowed to view this content!'); } return $next($request); } + + /** + * @param string[] $abilities + */ + protected function deniesAccessUsing(array $abilities): bool + { + return ! auth()->user() + || (method_exists(auth()->user(), 'canAtLeast') && ! auth()->user()->canAtLeast($abilities)); + } } diff --git a/src/Middleware/RoleMiddleware.php b/src/Middleware/RoleMiddleware.php index e97a471..0dd9080 100644 --- a/src/Middleware/RoleMiddleware.php +++ b/src/Middleware/RoleMiddleware.php @@ -11,13 +11,16 @@ class RoleMiddleware /** * Handle an incoming request. * - * @return mixed + * @param string|string[] $role */ - public function handle(Request $request, Closure $next, string $role) + public function handle(Request $request, Closure $next, string|array $role): mixed { - $role = Str::of($role)->split('/[|,]/')->toArray(); + if (is_string($role)) { + /** @var string[] $role */ + $role = Str::of($role)->split('/[|,]/')->toArray(); + } - if (! auth()->user() || ! auth()->user()->hasRole($role)) { + if ($this->deniesAccessUsing($role)) { if ($request->ajax()) { return response()->json([ 'error' => [ @@ -33,4 +36,13 @@ public function handle(Request $request, Closure $next, string $role) return $next($request); } + + /** + * @param string[] $role + */ + protected function deniesAccessUsing(array $role): bool + { + return ! auth()->user() + || (method_exists(auth()->user(), 'hasRole') && ! auth()->user()->hasRole($role)); + } } diff --git a/src/Models/Permission.php b/src/Models/Permission.php index f22607e..5c28641 100644 --- a/src/Models/Permission.php +++ b/src/Models/Permission.php @@ -3,9 +3,9 @@ namespace Yajra\Acl\Models; use Exception; -use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsToMany; +use Illuminate\Support\Collection; use Illuminate\Support\Str; use Yajra\Acl\Traits\InteractsWithRole; use Yajra\Acl\Traits\RefreshPermissionsCache; @@ -20,29 +20,31 @@ class Permission extends Model { use InteractsWithRole, RefreshPermissionsCache; - /** @var string */ protected $table = 'permissions'; - /** @var string[] */ - protected $fillable = ['name', 'slug', 'resource', 'system']; + protected $fillable = [ + 'name', + 'slug', + 'resource', + 'system', + ]; - /** @var array */ - protected $casts = ['system' => 'bool']; + protected $casts = [ + 'system' => 'bool', + ]; /** * Find a permission by slug. - * - * @return \Illuminate\Database\Eloquent\Model|static - * - * @throws \Illuminate\Database\Eloquent\ModelNotFoundException<\Illuminate\Database\Eloquent\Model> */ - public static function findBySlug(string $slug) + public static function findBySlug(string $slug): Permission { return static::query()->where('slug', $slug)->firstOrFail(); } /** * Create a permissions for a resource. + * + * @return \Illuminate\Support\Collection */ public static function createResource(string $resource, bool $system = false): Collection { @@ -95,10 +97,12 @@ public static function createResource(string $resource, bool $system = false): C /** * Permission can belong to many users. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany<\Illuminate\Foundation\Auth\User> */ public function users(): BelongsToMany { - /** @var class-string $model */ + /** @var class-string<\Illuminate\Foundation\Auth\User> $model */ $model = config('acl.user', config('auth.providers.users.model')); return $this->belongsToMany($model)->withTimestamps(); diff --git a/src/Models/Role.php b/src/Models/Role.php index f9a44f3..9d291d4 100644 --- a/src/Models/Role.php +++ b/src/Models/Role.php @@ -17,35 +17,35 @@ class Role extends Model { use HasPermission, RefreshPermissionsCache; - /** @var string */ protected $table = 'roles'; - /** @var string[] */ - protected $fillable = ['name', 'slug', 'description', 'system']; + protected $fillable = [ + 'name', + 'slug', + 'description', + 'system', + ]; - /** @var array */ protected $casts = [ 'system' => 'bool', ]; /** * Find a role by slug. - * - * @return \Illuminate\Database\Eloquent\Model|static - * - * @throws \Illuminate\Database\Eloquent\ModelNotFoundException */ - public static function findBySlug(string $slug) + public static function findBySlug(string $slug): Role { return static::query()->where('slug', $slug)->firstOrFail(); } /** * Roles can belong to many users. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany<\Illuminate\Foundation\Auth\User> */ public function users(): BelongsToMany { - /** @var class-string $model */ + /** @var class-string<\Illuminate\Foundation\Auth\User> $model */ $model = config('acl.user', config('auth.providers.users.model')); return $this->belongsToMany($model)->withTimestamps(); diff --git a/src/Traits/HasPermission.php b/src/Traits/HasPermission.php index 28898c3..b1f049c 100644 --- a/src/Traits/HasPermission.php +++ b/src/Traits/HasPermission.php @@ -2,14 +2,24 @@ namespace Yajra\Acl\Traits; -use Yajra\Acl\Models\Permission; - trait HasPermission { use InteractsWithPermission; + /** + * Checks if the role does not have the given permission. + * + * @param string|string[] $permission + */ + public function cannot(array|string $permission): bool + { + return ! $this->can($permission); + } + /** * Checks if the role has the given permission. + * + * @param string|string[] $permission */ public function can(array|string $permission): bool { @@ -26,16 +36,10 @@ public function can(array|string $permission): bool return in_array($permission, $permissions); } - /** - * Checks if the role does not have the given permission. - */ - public function cannot(array|string $permission): bool - { - return ! $this->can($permission); - } - /** * Check if the role has at least one of the given permissions. + * + * @param string|string[] $permission */ public function canAtLeast(string|array $permission): bool { diff --git a/src/Traits/InteractsWithPermission.php b/src/Traits/InteractsWithPermission.php index c42b914..91448d7 100644 --- a/src/Traits/InteractsWithPermission.php +++ b/src/Traits/InteractsWithPermission.php @@ -2,23 +2,27 @@ namespace Yajra\Acl\Traits; +use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsToMany; +use Illuminate\Support\Collection; use Yajra\Acl\Models\Permission; /** - * @property \Illuminate\Database\Eloquent\Collection|Permission[] $permissions + * @property \Illuminate\Database\Eloquent\Collection $permissions * * @mixin \Illuminate\Database\Eloquent\Model */ trait InteractsWithPermission { /** - * @var class-string|Permission + * @var class-string|Permission */ public $permissionClass; /** * Grant permissions by slug(/s). + * + * @param string|string[] $slug */ public function grantPermissionBySlug(array|string $slug): void { @@ -46,8 +50,22 @@ public function getPermissionClass(): Permission return $this->permissionClass; } + /** + * Grant the given permission. + * + * @param array $attributes + */ + public function grantPermission(mixed $ids, array $attributes = [], bool $touch = true): void + { + $this->permissions()->attach($ids, $attributes, $touch); + + $this->load('permissions'); + } + /** * Get related permissions. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany */ public function permissions(): BelongsToMany { @@ -59,6 +77,8 @@ public function permissions(): BelongsToMany /** * Grant permissions by resource. + * + * @param string|string[] $resource */ public function grantPermissionByResource(array|string $resource): void { @@ -72,18 +92,10 @@ public function grantPermissionByResource(array|string $resource): void $this->load('permissions'); } - /** - * Grant the given permission. - */ - public function grantPermission(mixed $ids, array $attributes = [], bool $touch = true): void - { - $this->permissions()->attach($ids, $attributes, $touch); - - $this->load('permissions'); - } - /** * Revoke permissions by the given slug(/s). + * + * @param string|string[] $slug */ public function revokePermissionBySlug(array|string $slug): void { @@ -97,8 +109,22 @@ public function revokePermissionBySlug(array|string $slug): void $this->load('permissions'); } + /** + * Revokes the given permission. + */ + public function revokePermission(mixed $ids = null, bool $touch = true): int + { + $detached = $this->permissions()->detach($ids, $touch); + + $this->load('permissions'); + + return $detached; + } + /** * Revoke permissions by resource. + * + * @param string|string[] $resource */ public function revokePermissionByResource(array|string $resource): void { @@ -112,24 +138,13 @@ public function revokePermissionByResource(array|string $resource): void $this->load('permissions'); } - /** - * Revokes the given permission. - */ - public function revokePermission(mixed $ids = null, bool $touch = true): int - { - $detached = $this->permissions()->detach($ids, $touch); - - $this->load('permissions'); - - return $detached; - } - /** * Syncs the given permission. * - * @param \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model|array $ids + * @param Collection|Model|array $ids + * @return array */ - public function syncPermissions(mixed $ids, bool $detaching = true): array + public function syncPermissions(Collection|Model|array $ids, bool $detaching = true): array { $synced = $this->permissions()->sync($ids, $detaching); @@ -152,6 +167,8 @@ public function revokeAllPermissions(): int /** * Get list of permissions slug. + * + * @return array */ public function getPermissions(): array { diff --git a/src/Traits/InteractsWithRole.php b/src/Traits/InteractsWithRole.php index 4bb9ff0..232bd79 100644 --- a/src/Traits/InteractsWithRole.php +++ b/src/Traits/InteractsWithRole.php @@ -3,11 +3,13 @@ namespace Yajra\Acl\Traits; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsToMany; +use Illuminate\Support\Collection; use Yajra\Acl\Models\Role; /** - * @property \Illuminate\Database\Eloquent\Collection|Role[] $roles + * @property \Illuminate\Database\Eloquent\Collection $roles * * @method static Builder havingRoles($roleIds) * @method static Builder havingRolesBySlugs($slugs) @@ -21,13 +23,15 @@ trait InteractsWithRole /** * Check if user has the given role. + * + * @param string|string[] $role */ public function hasRole(string|array $role): bool { if (is_array($role)) { $roles = $this->getRoleSlugs(); - $intersection = array_intersect($roles, (array) $role); + $intersection = array_intersect($roles, $role); $intersectionCount = count($intersection); return $intersectionCount > 0; @@ -38,6 +42,8 @@ public function hasRole(string|array $role): bool /** * Get all user roles. + * + * @return array */ public function getRoleSlugs(): array { @@ -56,6 +62,8 @@ public function attachRoleBySlug(string $slug): void /** * Attach a role to user. + * + * @param array $attributes */ public function attachRole(mixed $role, array $attributes = [], bool $touch = true): void { @@ -66,6 +74,8 @@ public function attachRole(mixed $role, array $attributes = [], bool $touch = tr /** * Model can have many roles. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany */ public function roles(): BelongsToMany { @@ -80,7 +90,7 @@ public function roles(): BelongsToMany * * @throws \Illuminate\Database\Eloquent\ModelNotFoundException */ - protected function findRoleBySlug(string $slug): \Illuminate\Database\Eloquent\Model|static + protected function findRoleBySlug(string $slug): Model|static { return $this->getRoleClass()->newQuery()->where('slug', $slug)->firstOrFail(); } @@ -102,8 +112,12 @@ public function getRoleClass(): Role /** * Query scope for user having the given roles. + * + * @param \Illuminate\Database\Eloquent\Builder<\Yajra\Acl\Models\Permission> $query + * @param array $roles + * @return \Illuminate\Database\Eloquent\Builder<\Yajra\Acl\Models\Permission> */ - public function scopeHavingRoles(Builder $query, mixed $roles): Builder + public function scopeHavingRoles(Builder $query, array $roles): Builder { return $query->whereExists(function ($query) use ($roles) { $query->selectRaw('1') @@ -115,8 +129,12 @@ public function scopeHavingRoles(Builder $query, mixed $roles): Builder /** * Query scope for user having the given roles by slugs. + * + * @param \Illuminate\Database\Eloquent\Builder<\Yajra\Acl\Models\Permission> $query + * @param array $slugs + * @return \Illuminate\Database\Eloquent\Builder<\Yajra\Acl\Models\Permission> */ - public function scopeHavingRolesBySlugs(Builder $query, mixed $slugs): Builder + public function scopeHavingRolesBySlugs(Builder $query, array $slugs): Builder { return $query->whereHas('roles', function ($query) use ($slugs) { $query->whereIn('roles.slug', $slugs); @@ -126,6 +144,7 @@ public function scopeHavingRolesBySlugs(Builder $query, mixed $slugs): Builder /** * Revokes the given role from the user using slug. * + * @param string|string[] $slug * @param bool $touch */ public function revokeRoleBySlug(string|array $slug, $touch = true): int @@ -159,10 +178,10 @@ public function revokeRole(mixed $role, $touch = true): int /** * Syncs the given role(s) with the user. * - * @param \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model|array $roles - * @param bool $detaching + * @param Collection|Model|array $roles + * @return array */ - public function syncRoles($roles, $detaching = true): array + public function syncRoles(Collection|Model|array $roles, bool $detaching = true): array { $synced = $this->roles()->sync($roles, $detaching); diff --git a/src/Traits/RefreshPermissionsCache.php b/src/Traits/RefreshPermissionsCache.php index 471247c..c791a1f 100644 --- a/src/Traits/RefreshPermissionsCache.php +++ b/src/Traits/RefreshPermissionsCache.php @@ -2,6 +2,7 @@ namespace Yajra\Acl\Traits; +use Illuminate\Database\Eloquent\Model; use Yajra\Acl\GateRegistrar; trait RefreshPermissionsCache @@ -9,7 +10,7 @@ trait RefreshPermissionsCache public static function bootRefreshPermissionsCache(): void { static::saved(function () { - if (auth()->check()) { + if (auth()->check() && auth()->user() instanceof Model) { auth()->user()->load('roles'); } @@ -21,7 +22,7 @@ public static function bootRefreshPermissionsCache(): void }); static::deleted(function () { - if (auth()->check()) { + if (auth()->check() && auth()->user() instanceof Model) { auth()->user()->load('roles'); }