getEffectivePermissions($user)->get($permission); return $effect === 'allow'; } public function canAny(User $user, array $permissions): bool { foreach ($permissions as $permission) { if ($this->can($user, trim((string) $permission))) { return true; } } return false; } public function visibilityScope(User $user, string $module): ?string { $scopes = config("access_scopes.{$module}", []); if ($scopes === []) { return null; } $bestScope = null; $bestPriority = PHP_INT_MIN; foreach ($scopes as $scope => $priority) { if (!$this->can($user, "{$module}.scope.{$scope}")) { continue; } if ((int) $priority > $bestPriority) { $bestScope = (string) $scope; $bestPriority = (int) $priority; } } return $bestScope; } public function hasVisibilityScope(User $user, string $module, string $scope): bool { return $this->visibilityScope($user, $module) === $scope; } public function canViewField(User $user, string $module, string $field, ?string $entity = null): bool { return $this->can($user, $this->fieldPermissionSlug($module, $field, 'view')); } public function canUpdateField(User $user, string $module, string $field, ?string $entity = null): bool { return $this->can($user, $this->fieldPermissionSlug($module, $field, 'update')); } public function filterReadableFields(User $user, string $module, array $fields, ?string $entity = null): array { return array_values(array_filter( $fields, fn (string $field): bool => $this->canViewField($user, $module, $field, $entity) )); } public function filterWritableData(User $user, string $module, array $data, ?string $entity = null): array { return array_filter( $data, fn (string $field): bool => $this->canUpdateField($user, $module, $field, $entity), ARRAY_FILTER_USE_KEY ); } public function assertCan(User $user, string $permission): void { abort_unless($this->can($user, $permission), 403); } public function routePermission(?string $routeName): array|string|bool|null { if (!$routeName) { return null; } $exact = config('access_routes.exact', [])[$routeName] ?? null; if ($exact) { return $exact; } foreach (config('access_routes.prefixes', []) as $prefix => $permissions) { if (!str_starts_with($routeName, $prefix)) { continue; } $suffix = substr($routeName, strlen($prefix)); return $permissions[$suffix] ?? $permissions['*'] ?? null; } return null; } public function canAccessRoute(User $user, ?string $routeName): bool { $permission = $this->routePermission($routeName); if ($permission === true) { return true; } if ($permission === null) { return false; } return is_array($permission) ? $this->canAny($user, $permission) : $this->can($user, $permission); } public function roleHasPermission(Role $role, string $permission): bool { $permissionModel = $role->permissions() ->where('slug', $permission) ->first(); return $permissionModel?->pivot?->effect === 'allow'; } public function getEffectivePermissions(User $user): Collection { return Cache::remember( $this->cacheKey($user), now()->addHour(), fn (): Collection => $this->resolveEffectivePermissions($user) ); } public function bumpCacheVersion(): void { Cache::forever(self::CACHE_VERSION_KEY, $this->cacheVersion() + 1); } private function resolveEffectivePermissions(User $user): Collection { if (!$this->tablesExist()) { return collect(); } $effects = collect(); $denied = collect(); $role = $user->roleModel()->with('permissions')->first(); if (!$role && $user->resolvedRoleSlug()) { $role = Role::query() ->where('slug', $user->resolvedRoleSlug()) ->with('permissions') ->first(); } if ($role) { foreach ($role->permissions as $permission) { $effect = $permission->pivot->effect; if ($effect === 'deny') { $denied->put($permission->slug, true); $effects->put($permission->slug, 'deny'); continue; } if (!$denied->has($permission->slug)) { $effects->put($permission->slug, $effect); } } } $userPermissions = $user->permissions() ->where(function ($query) { $query->whereNull('expires_at') ->orWhere('expires_at', '>', now()); }) ->get(); foreach ($userPermissions as $permission) { $effect = $permission->pivot->effect; if ($effect === 'deny') { $denied->put($permission->slug, true); $effects->put($permission->slug, 'deny'); continue; } if (!$denied->has($permission->slug)) { $effects->put($permission->slug, $effect); } } return $effects; } private function fieldPermissionSlug(string $module, string $field, string $action): string { return "{$module}.fields.{$field}.{$action}"; } private function cacheKey(User $user): string { $role = $user->relationLoaded('roleModel') ? $user->roleModel : $user->roleModel()->first(); return implode(':', [ 'permissions', 'user', $user->id, sha1((string) $user->email), $user->getAttribute('role_id') ?: 'legacy', $user->resolvedRoleSlug() ?: 'none', optional($user->updated_at)->timestamp ?: 0, optional($role?->updated_at)->timestamp ?: 0, 'v' . $this->cacheVersion(), ]); } private function cacheVersion(): int { return (int) Cache::get(self::CACHE_VERSION_KEY, 1); } private function tablesExist(): bool { return Schema::hasTable('roles') && Schema::hasTable('permissions') && Schema::hasTable('role_permissions') && Schema::hasTable('user_permissions'); } }