activeEntitledTenant($request); if ($activeTenant instanceof Tenant) { return 'Tenant scope: '.$activeTenant->name; } return 'All tenants'; } /** * @return array{label: string, url: string}|null */ public function returnAffordance(?Request $request = null): ?array { $activeTenant = $this->activeEntitledTenant($request); if ($activeTenant instanceof Tenant) { return [ 'label' => 'Back to '.$activeTenant->name, 'url' => TenantDashboard::getUrl(panel: 'tenant', tenant: $activeTenant), ]; } return null; } /** * @return array */ public function headerActions( string $scopeActionName = 'operate_hub_scope', string $returnActionName = 'operate_hub_return', ?Request $request = null, ): array { $actions = [ Action::make($scopeActionName) ->label($this->scopeLabel($request)) ->color('gray') ->disabled(), ]; $returnAffordance = $this->returnAffordance($request); if (is_array($returnAffordance)) { $actions[] = Action::make($returnActionName) ->label($returnAffordance['label']) ->icon('heroicon-o-arrow-left') ->color('gray') ->url($returnAffordance['url']); } return $actions; } public function activeEntitledTenant(?Request $request = null): ?Tenant { return $this->resolvedContext($request)->tenant; } public function tenantOwnedPanelContext(?Request $request = null): ?Tenant { return $this->activeEntitledTenant($request); } public function resolvedContext(?Request $request = null): ResolvedShellContext { $request ??= request(); if ($request instanceof Request) { $cached = $request->attributes->get(self::REQUEST_ATTRIBUTE); if ($cached instanceof ResolvedShellContext) { return $cached; } } $resolved = $this->buildResolvedContext($request); if ($request instanceof Request) { $request->attributes->set(self::REQUEST_ATTRIBUTE, $resolved); } return $resolved; } private function buildResolvedContext(?Request $request = null): ResolvedShellContext { $pageCategory = $this->pageCategory($request); $routeTenantCandidate = $this->resolveRouteTenantCandidate($request, $pageCategory); $sessionWorkspace = $this->workspaceContext->currentWorkspace($request); $workspace = $this->workspaceContext->currentWorkspaceOrTenantWorkspace($routeTenantCandidate, $request); $workspaceSource = match (true) { $workspace instanceof Workspace && $sessionWorkspace instanceof Workspace && $workspace->is($sessionWorkspace) => 'session_workspace', $workspace instanceof Workspace && $routeTenantCandidate instanceof Tenant && (int) $routeTenantCandidate->workspace_id === (int) $workspace->getKey() => 'route', default => 'none', }; if (! $workspace instanceof Workspace) { return new ResolvedShellContext( workspace: null, tenant: null, pageCategory: $pageCategory, state: 'missing_workspace', displayMode: 'recovery', recoveryAction: $pageCategory === TenantPageCategory::WorkspaceChooserException ? 'none' : 'redirect_choose_workspace', recoveryDestination: '/admin/choose-workspace', recoveryReason: 'missing_workspace', ); } $routeTenant = $this->resolveValidatedRouteTenant($routeTenantCandidate, $workspace, $request, $pageCategory); if ($routeTenant['tenant'] instanceof Tenant) { return new ResolvedShellContext( workspace: $workspace, tenant: $routeTenant['tenant'], pageCategory: $pageCategory, state: 'tenant_scoped', displayMode: 'tenant_scoped', workspaceSource: $workspaceSource, tenantSource: 'route', ); } $recoveryReason = $routeTenant['reason']; if ($pageCategory === TenantPageCategory::TenantBound && $recoveryReason !== null) { return new ResolvedShellContext( workspace: $workspace, tenant: null, pageCategory: $pageCategory, state: 'invalid_tenant', displayMode: 'recovery', workspaceSource: $workspaceSource, recoveryAction: 'abort_not_found', recoveryReason: $recoveryReason, ); } $queryHintTenant = $this->resolveValidatedQueryHintTenant($request, $workspace, $pageCategory); if ($queryHintTenant['tenant'] instanceof Tenant) { return new ResolvedShellContext( workspace: $workspace, tenant: $queryHintTenant['tenant'], pageCategory: $pageCategory, state: 'tenant_scoped', displayMode: 'tenant_scoped', workspaceSource: $workspaceSource, tenantSource: 'query_hint', ); } $recoveryReason ??= $queryHintTenant['reason']; $tenant = $this->resolveValidatedFilamentTenant($request, $pageCategory, $workspace); if ($tenant instanceof Tenant) { return new ResolvedShellContext( workspace: $workspace, tenant: $tenant, pageCategory: $pageCategory, state: 'tenant_scoped', displayMode: 'tenant_scoped', workspaceSource: $workspaceSource, tenantSource: 'filament_tenant', ); } if ($pageCategory->allowsRememberedTenantRestore()) { $rememberedTenant = $this->workspaceContext->rememberedTenant($request); if ($rememberedTenant instanceof Tenant) { return new ResolvedShellContext( workspace: $workspace, tenant: $rememberedTenant, pageCategory: $pageCategory, state: 'tenant_scoped', displayMode: 'tenant_scoped', workspaceSource: $workspaceSource, tenantSource: 'remembered', ); } } if ($pageCategory->requiresExplicitTenant()) { return new ResolvedShellContext( workspace: $workspace, tenant: null, pageCategory: $pageCategory, state: 'missing_tenant', displayMode: 'recovery', workspaceSource: $workspaceSource, recoveryAction: $pageCategory === TenantPageCategory::TenantScopedEvidence ? 'redirect_evidence_overview' : 'abort_not_found', recoveryDestination: $pageCategory === TenantPageCategory::TenantScopedEvidence ? '/admin/evidence/overview' : null, recoveryReason: $recoveryReason ?? 'missing_tenant', ); } return new ResolvedShellContext( workspace: $workspace, tenant: null, pageCategory: $pageCategory, state: 'tenantless_workspace', displayMode: 'tenantless', workspaceSource: $workspaceSource, recoveryReason: $recoveryReason, ); } private function resolveValidatedFilamentTenant( ?Request $request = null, ?TenantPageCategory $pageCategory = null, ?Workspace $workspace = null, ): ?Tenant { $tenant = Filament::getTenant(); if (! $tenant instanceof Tenant) { return null; } $pageCategory ??= $this->pageCategory($request); $workspace ??= $this->workspaceContext->currentWorkspaceOrTenantWorkspace($tenant, $request); if ($workspace instanceof Workspace && $this->tenantValidationReason($tenant, $workspace, $request, $pageCategory) === null) { return $tenant; } Filament::setTenant(null, true); return null; } private function resolveValidatedRouteTenant( ?Tenant $tenant, Workspace $workspace, ?Request $request = null, ?TenantPageCategory $pageCategory = null, ): array { $pageCategory ??= $this->pageCategory($request); if (! $tenant instanceof Tenant) { return ['tenant' => null, 'reason' => null]; } $reason = $this->tenantValidationReason($tenant, $workspace, $request, $pageCategory); if ($reason !== null) { return ['tenant' => null, 'reason' => $reason]; } return ['tenant' => $tenant, 'reason' => null]; } private function resolveValidatedQueryHintTenant( ?Request $request, Workspace $workspace, TenantPageCategory $pageCategory, ): array { if (! $pageCategory->allowsQueryTenantHints()) { return ['tenant' => null, 'reason' => null]; } $queryTenant = $this->resolveQueryTenantHint($request); if (! $queryTenant instanceof Tenant) { return ['tenant' => null, 'reason' => null]; } $reason = $this->tenantValidationReason($queryTenant, $workspace, $request, $pageCategory); if ($reason !== null) { return ['tenant' => null, 'reason' => $reason]; } return ['tenant' => $queryTenant, 'reason' => null]; } private function resolveRouteTenantCandidate(?Request $request = null, ?TenantPageCategory $pageCategory = null): ?Tenant { $route = $request?->route(); $pageCategory ??= $this->pageCategory($request); if ($route?->hasParameter('tenant')) { return $this->resolveTenantIdentifier($route->parameter('tenant')); } if ( $pageCategory !== TenantPageCategory::TenantBound || ! $route?->hasParameter('record') || ! str_starts_with((string) ($route->getName() ?? ''), 'filament.admin.resources.tenants.') ) { return null; } return $this->resolveTenantIdentifier($route->parameter('record')); } private function resolveQueryTenantHint(?Request $request = null): ?Tenant { $queryTenant = $request?->query('tenant'); if (filled($queryTenant)) { return $this->resolveTenantIdentifier($queryTenant); } $queryTenantId = $request?->query('tenant_id'); if (filled($queryTenantId)) { return $this->resolveTenantIdentifier($queryTenantId); } return null; } private function resolveTenantIdentifier(mixed $tenantIdentifier): ?Tenant { if ($tenantIdentifier instanceof Tenant) { return $tenantIdentifier; } $tenantIdentifier = trim((string) $tenantIdentifier); if ($tenantIdentifier === '') { return null; } $tenantKeyColumn = (new Tenant)->getQualifiedKeyName(); return Tenant::query() ->withTrashed() ->where(static function ($query) use ($tenantIdentifier, $tenantKeyColumn): void { $query->where('external_id', $tenantIdentifier); if (ctype_digit($tenantIdentifier)) { $query->orWhere($tenantKeyColumn, (int) $tenantIdentifier); } }) ->first(); } private function tenantValidationReason( Tenant $tenant, Workspace $workspace, ?Request $request = null, ?TenantPageCategory $pageCategory = null, ): ?string { $pageCategory ??= $this->pageCategory($request); if ((int) $tenant->workspace_id !== (int) $workspace->getKey()) { return 'mismatched_workspace'; } $user = auth()->user(); if (! $user instanceof User) { return 'not_member'; } if (! $this->capabilityResolver->isMember($user, $tenant)) { return 'not_member'; } $question = $pageCategory === TenantPageCategory::TenantBound ? TenantOperabilityQuestion::TenantBoundViewability : TenantOperabilityQuestion::AdministrativeDiscoverability; $allowed = $this->tenantOperabilityService->outcomeFor( tenant: $tenant, question: $question, actor: $user, workspaceId: (int) $workspace->getKey(), lane: $pageCategory->lane(), selectedTenant: Filament::getTenant() instanceof Tenant ? Filament::getTenant() : null, )->allowed; if ($allowed) { return null; } return $pageCategory === TenantPageCategory::TenantBound ? 'inaccessible' : 'not_operable'; } private function pageCategory(?Request $request = null): TenantPageCategory { return TenantPageCategory::fromRequest($request); } }