action = $action; } /** * Create enforcement for a table action. * * @param Action $action The Filament action to wrap * @param Model|Closure $record The owner record or a closure that returns it */ public static function forTableAction(Action $action, Model|Closure $record): self { $instance = new self($action); $instance->record = $record; return $instance; } public function requireMembership(bool $require = true): self { $this->requireMembership = $require; return $this; } /** * @throws \InvalidArgumentException If capability is not in the canonical registry */ public function requireCapability(string $capability): self { if (! Capabilities::isKnown($capability)) { throw new \InvalidArgumentException( "Unknown capability: {$capability}. Use constants from ".Capabilities::class ); } $this->capability = $capability; return $this; } public function destructive(): self { $this->isDestructive = true; return $this; } public function tooltip(string $message): self { $this->customTooltip = $message; return $this; } public function apply(): Action { $this->applyVisibility(); $this->applyDisabledState(); $this->applyDestructiveConfirmation(); $this->applyServerSideGuard(); return $this->action; } private function applyVisibility(): void { if (! $this->requireMembership) { return; } $this->action->visible(function (?Model $record = null): bool { $context = $this->resolveContextWithRecord($record); return $context->isMember; }); } private function applyDisabledState(): void { if ($this->capability === null) { return; } $tooltip = $this->customTooltip ?? AuthUiTooltips::insufficientPermission(); $this->action->disabled(function (?Model $record = null): bool { $context = $this->resolveContextWithRecord($record); if (! $context->isMember) { return true; } return ! $context->hasCapability; }); $this->action->tooltip(function (?Model $record = null) use ($tooltip): ?string { $context = $this->resolveContextWithRecord($record); if ($context->isMember && ! $context->hasCapability) { return $tooltip; } return null; }); } private function applyDestructiveConfirmation(): void { if (! $this->isDestructive) { return; } $this->action->requiresConfirmation(); $this->action->modalHeading(UiTooltips::DESTRUCTIVE_CONFIRM_TITLE); $this->action->modalDescription(UiTooltips::DESTRUCTIVE_CONFIRM_DESCRIPTION); } private function applyServerSideGuard(): void { $this->action->before(function (?Model $record = null): void { $context = $this->resolveContextWithRecord($record); if ($context->shouldDenyAsNotFound()) { abort(404); } if ($context->shouldDenyAsForbidden()) { abort(403); } }); } private function resolveContextWithRecord(?Model $record = null): WorkspaceAccessContext { $user = auth()->user(); $workspace = $this->resolveWorkspaceWithRecord($record); if (! $user instanceof User || ! $workspace instanceof Workspace) { return new WorkspaceAccessContext( user: null, workspace: null, isMember: false, hasCapability: false, ); } /** @var WorkspaceCapabilityResolver $resolver */ $resolver = app(WorkspaceCapabilityResolver::class); $isMember = $resolver->isMember($user, $workspace); $hasCapability = true; if ($this->capability !== null && $isMember) { $hasCapability = $resolver->can($user, $workspace, $this->capability); } return new WorkspaceAccessContext( user: $user, workspace: $workspace, isMember: $isMember, hasCapability: $hasCapability, ); } private function resolveWorkspaceWithRecord(?Model $record = null): ?Workspace { if ($record instanceof Workspace) { return $record; } if ($this->record !== null) { try { $resolved = $this->record instanceof Closure ? ($this->record)() : $this->record; if ($resolved instanceof Workspace) { return $resolved; } } catch (Throwable) { return null; } } return null; } }