$parameters */ public static function getUrl(array $parameters = [], bool $isAbsolute = true, ?string $panel = null, ?Model $tenant = null): string { $panelId = $panel ?? Filament::getCurrentOrDefaultPanel()?->getId() ?? 'admin'; if ($panelId !== 'admin') { return parent::getUrl($parameters, $isAbsolute, $panelId, $tenant); } $environment = static::resolveAdminUrlEnvironment($parameters, $tenant); if (! $environment instanceof ManagedEnvironment) { return url('/admin'); } $workspace = static::resolveAdminUrlWorkspace($environment, $parameters); if (! $workspace instanceof Workspace && ! is_string($workspace) && ! is_int($workspace)) { return url('/admin'); } $parameters = static::withoutLegacyScopeQuery($parameters); $parameters['environment'] = $environment; $parameters['workspace'] = $workspace instanceof Workspace ? static::workspaceRouteKey($workspace) : $workspace; return parent::getUrl($parameters, $isAbsolute, $panelId, null); } public static function canAccess(): bool { $environment = static::resolveRouteOwnedEnvironment(); $user = auth()->user(); if (! $environment instanceof ManagedEnvironment || ! $user instanceof User) { return false; } if (! static::routeWorkspaceMatchesEnvironment($environment)) { return false; } return app(ManagedEnvironmentAccessScopeResolver::class) ->decision($user, $environment, Capabilities::WORKSPACE_BASELINES_VIEW) ->allowed(); } public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration { return ActionSurfaceDeclaration::forPage(ActionSurfaceProfile::CrudListAndView) ->withListRowPrimaryActionLimit(1) ->satisfy(ActionSurfaceSlot::ListHeader, 'The page header exposes only the delegated compare rerun action and neutral navigation back to compare.') ->satisfy(ActionSurfaceSlot::ListRowMoreMenu, 'Row decisions are constrained to one primary binding action plus confirmed secondary decision/revoke actions.') ->satisfy(ActionSurfaceSlot::ListEmptyState, 'The table distinguishes no compare context from no decisions required.') ->exempt(ActionSurfaceSlot::DetailHeader, 'Spec 384 V1 does not add a separate detail route; subject context is shown in the row and confirmed action modals.') ->exempt(ActionSurfaceSlot::InspectAffordance, 'Rows expose compact candidate and decision context inline through the table and modal copy instead of a second detail route in v1.') ->exempt(ActionSurfaceSlot::ListBulkMoreGroup, 'Baseline subject decisions are high-impact and intentionally have no bulk action in v1.'); } public function mount(ManagedEnvironment|string|null $environment = null): void { $tenant = static::resolveRouteOwnedEnvironment($environment); $this->authorizeEnvironmentOrAbort($tenant, Capabilities::WORKSPACE_BASELINES_VIEW); $this->scopedEnvironmentId = (int) $tenant->getKey(); $this->focusedOperationRunId = $this->scopedOperationRunIdFromQuery($tenant); $this->heading = $tenant->getFilamentName(); $this->subheading = 'Baseline subject resolution'; $this->mountInteractsWithTable(); } public function table(Table $table): Table { return $table ->queryStringIdentifier('baselineSubjectResolution') ->defaultSort('readiness_impact') ->defaultPaginationPageOption(25) ->paginated(TablePaginationProfiles::customPage()) ->searchable() ->searchPlaceholder('Search subject, reason, provider, decision') ->records(function ( ?string $sortColumn, ?string $sortDirection, ?string $search, array $filters, int $page, int $recordsPerPage ): LengthAwarePaginator { $rows = $this->filteredRows($filters, $search); $rows = $this->sortRows($rows, $sortColumn, $sortDirection); return $this->paginateRows($rows, $page, $recordsPerPage); }) ->filters([ SelectFilter::make('provider') ->label('Provider') ->options(fn (): array => $this->filterOptions('provider')), SelectFilter::make('subject_class') ->label('Class') ->options(fn (): array => $this->filterOptions('subject_class')), SelectFilter::make('resource_type') ->label('Resource type') ->options(fn (): array => $this->filterOptions('resource_type')), SelectFilter::make('actionability') ->label('Actionability') ->options(fn (): array => $this->filterOptions('actionability')), SelectFilter::make('readiness_impact') ->label('Readiness') ->options(fn (): array => $this->filterOptions('readiness_impact')), SelectFilter::make('reason') ->label('Reason') ->options(fn (): array => $this->filterOptions('reason')), SelectFilter::make('active_binding') ->label('Decision') ->options([ 'yes' => 'Decision recorded', 'no' => 'No decision', ]), SelectFilter::make('candidates') ->label('Candidates') ->options([ 'yes' => 'Has candidates', 'no' => 'No candidates', ]), ]) ->columns([ TextColumn::make('subject_label') ->label('Subject') ->description(fn (Model $record): string => (string) $record->getAttribute('resource_type_label')) ->searchable() ->sortable() ->wrap(), TextColumn::make('reason_label') ->label('Problem') ->badge() ->color('warning') ->searchable() ->sortable() ->wrap(), TextColumn::make('readiness_label') ->label('Readiness') ->badge() ->color(fn (Model $record): string => $this->readinessColor((string) $record->getAttribute('readiness_impact'))) ->sortable() ->wrap(), TextColumn::make('actionability_label') ->label('Actionability') ->badge() ->color('gray') ->sortable() ->wrap(), TextColumn::make('candidate_count') ->label('Candidates') ->numeric() ->sortable(), TextColumn::make('current_decision_label') ->label('Current decision') ->badge() ->color(fn (Model $record): string => $record->getAttribute('active_binding_id') === null ? 'gray' : 'success') ->sortable() ->wrap(), TextColumn::make('source_operation_run_id') ->label('Source') ->formatStateUsing(fn (mixed $state): string => is_numeric($state) ? 'Operation #'.(int) $state : 'Latest compare') ->sortable(), TextColumn::make('provider_label') ->label('Provider') ->toggleable(isToggledHiddenByDefault: true) ->sortable(), TextColumn::make('subject_class_label') ->label('Class') ->toggleable(isToggledHiddenByDefault: true) ->sortable(), TextColumn::make('trust_level') ->label('Trust') ->toggleable(isToggledHiddenByDefault: true) ->formatStateUsing(fn (?string $state): string => $state !== null ? Str::of($state)->replace('_', ' ')->headline()->toString() : 'Unknown'), ]) ->actions([ $this->bindSubjectAction(), $this->recordDecisionAction(), $this->revokeDecisionAction(), ]) ->bulkActions([]) ->emptyStateHeading(fn (): string => $this->emptyStateHeading()) ->emptyStateDescription(fn (): string => $this->emptyStateDescription()) ->emptyStateActions([ $this->runCompareEmptyStateAction(), ]); } /** * @return array */ protected function getViewData(): array { $tenant = $this->currentEnvironment(); $summary = $tenant instanceof ManagedEnvironment ? $this->query()->summary($tenant, $this->focusedOperationRunId) : [ 'has_run' => false, 'actionable_count' => 0, 'source_operation_run_id' => null, 'by_actionability' => [], 'by_readiness_impact' => [], 'by_reason' => [], 'legacy_payload_only' => false, ]; return [ 'summary' => $summary, 'compareUrl' => $tenant instanceof ManagedEnvironment ? ManagedEnvironmentLinks::baselineCompareUrl($tenant) : null, 'sourceRunUrl' => $tenant instanceof ManagedEnvironment && is_numeric($summary['source_operation_run_id'] ?? null) ? OperationRunLinks::view((int) $summary['source_operation_run_id'], $tenant) : null, ]; } /** * @return array */ protected function getHeaderActions(): array { $actions = []; $tenant = $this->currentEnvironment(); if ($tenant instanceof ManagedEnvironment) { $actions[] = Action::make('openBaselineCompare') ->label('Open baseline compare') ->icon('heroicon-o-arrow-left') ->color('gray') ->url(ManagedEnvironmentLinks::baselineCompareUrl($tenant)); } $actions[] = $this->runCompareAction('runComparisonAgain'); return $actions; } public function currentEnvironment(): ?ManagedEnvironment { $tenant = $this->scopedEnvironmentId === null ? static::resolveRouteOwnedEnvironment() : ManagedEnvironment::query() ->withTrashed() ->whereKey($this->scopedEnvironmentId) ->first(); if (! $tenant instanceof ManagedEnvironment) { return null; } $this->authorizeEnvironmentOrAbort($tenant, Capabilities::WORKSPACE_BASELINES_VIEW); return $tenant; } private function bindSubjectAction(): Action { $action = Action::make('bindSubject') ->label('Bind subject') ->icon('heroicon-o-link') ->visible(fn (Model $record): bool => (int) $record->getAttribute('candidate_count') > 0) ->form([ Select::make('candidate_key') ->label('Provider resource') ->required() ->native(false) ->options(fn (Model $record): array => collect($record->getAttribute('candidates') ?? []) ->mapWithKeys(fn (array $candidate): array => [ (string) $candidate['candidate_key'] => trim(implode(' - ', array_filter([ (string) ($candidate['display_label'] ?? 'Provider resource'), (string) ($candidate['provider_label'] ?? ''), (string) ($candidate['stable_identity_preview'] ?? ''), ]))), ]) ->all()), Textarea::make('operator_note') ->label('Operator note') ->required() ->minLength(8) ->rows(4) ->helperText('TenantPilot decision only. This does not mutate the provider tenant.'), ]) ->requiresConfirmation() ->modalHeading('Bind subject') ->modalDescription('Record a TenantPilot-only subject binding. Future baseline compares can consume this active decision.') ->modalSubmitActionLabel('Bind subject') ->action(function (Model $record, array $data): void { $tenant = $this->authorizedMutationEnvironment(); $actor = $this->actorOrAbort(); $row = $this->freshRowOrAbort($record); $candidate = $this->candidateFromRow($row, (string) ($data['candidate_key'] ?? '')); app(ProviderResourceBindingService::class)->createManualBinding( actor: $actor, environment: $tenant, identity: ResourceIdentity::fromArray($candidate['identity']), attributes: $this->bindingAttributes($row, $data, $candidate), ); Notification::make() ->success() ->title('Subject binding recorded') ->body('Run baseline compare again to validate the decision against current evidence.') ->send(); $this->resetTable(); }); return UiEnforcement::forTableAction($action, fn (): ?ManagedEnvironment => $this->currentEnvironment()) ->requireCapability(Capabilities::WORKSPACE_BASELINES_MANAGE) ->preserveVisibility() ->apply(); } private function recordDecisionAction(): Action { $action = Action::make('recordDecision') ->label('Record decision') ->icon('heroicon-o-check-circle') ->color('gray') ->visible(fn (Model $record): bool => is_array($record->getAttribute('decision_identity')) && $record->getAttribute('active_binding_id') === null) ->form([ Select::make('decision') ->label('Decision') ->required() ->native(false) ->options([ 'excluded_non_governed' => 'Exclude subject', 'accepted_limitation' => 'Accept limitation', 'unsupported_coverage' => 'Mark unsupported', 'missing_expected' => 'Mark missing expected', ]), Textarea::make('operator_note') ->label('Operator note') ->required() ->minLength(8) ->rows(4) ->helperText('TenantPilot decision only. This does not mutate the provider tenant.'), ]) ->requiresConfirmation() ->modalHeading('Record subject decision') ->modalDescription('Record a TenantPilot-only decision for this baseline subject. Future baseline compares can consume the active decision.') ->modalSubmitActionLabel('Record decision') ->action(function (Model $record, array $data): void { $tenant = $this->authorizedMutationEnvironment(); $actor = $this->actorOrAbort(); $row = $this->freshRowOrAbort($record); $identityPayload = $row['decision_identity'] ?? null; if (! is_array($identityPayload)) { throw new InvalidArgumentException('A valid provider resource identity is required for this decision.'); } $identity = ResourceIdentity::fromArray($identityPayload); $attributes = $this->bindingAttributes($row, $data); match ((string) ($data['decision'] ?? '')) { 'excluded_non_governed' => app(ProviderResourceBindingService::class)->createExclusion($actor, $tenant, $identity, $attributes), 'accepted_limitation' => app(ProviderResourceBindingService::class)->createAcceptedLimitation($actor, $tenant, $identity, $attributes), 'unsupported_coverage' => app(ProviderResourceBindingService::class)->markUnsupported($actor, $tenant, $identity, $attributes), 'missing_expected' => app(ProviderResourceBindingService::class)->markMissingExpected($actor, $tenant, $identity, $attributes), default => throw new InvalidArgumentException('Unsupported baseline subject decision.'), }; Notification::make() ->success() ->title('Subject decision recorded') ->body('Run baseline compare again to validate the decision against current evidence.') ->send(); $this->resetTable(); }); return UiEnforcement::forTableAction($action, fn (): ?ManagedEnvironment => $this->currentEnvironment()) ->requireCapability(Capabilities::WORKSPACE_BASELINES_MANAGE) ->preserveVisibility() ->apply(); } private function revokeDecisionAction(): Action { $action = Action::make('revokeDecision') ->label('Revoke decision') ->icon('heroicon-o-no-symbol') ->color('danger') ->visible(fn (Model $record): bool => is_numeric($record->getAttribute('active_binding_id'))) ->form([ Textarea::make('operator_note') ->label('Operator note') ->required() ->minLength(8) ->rows(4) ->helperText('TenantPilot decision only. This does not mutate the provider tenant.'), ]) ->requiresConfirmation() ->modalHeading('Revoke subject decision') ->modalDescription('Revoke the active TenantPilot subject decision. Future baseline compares will no longer consume this decision.') ->modalSubmitActionLabel('Revoke decision') ->action(function (Model $record, array $data): void { $this->authorizedMutationEnvironment(); $actor = $this->actorOrAbort(); $row = $this->freshRowOrAbort($record); $bindingId = $row['active_binding_id'] ?? null; if (! is_numeric($bindingId)) { throw new NotFoundHttpException; } $binding = ProviderResourceBinding::query() ->whereKey((int) $bindingId) ->firstOrFail(); Gate::forUser($actor)->authorize('revoke', $binding); app(ProviderResourceBindingService::class)->revoke( actor: $actor, binding: $binding, operatorNote: (string) ($data['operator_note'] ?? ''), resolutionReason: 'baseline_subject_resolution_revoked', ); Notification::make() ->success() ->title('Subject decision revoked') ->body('Run baseline compare again to validate the updated decision state.') ->send(); $this->resetTable(); }); return UiEnforcement::forTableAction($action, fn (): ?ManagedEnvironment => $this->currentEnvironment()) ->requireCapability(Capabilities::WORKSPACE_BASELINES_MANAGE) ->destructive() ->preserveVisibility() ->apply(); } private function runCompareAction(string $name): Action { $label = 'Run comparison again'; $action = Action::make($name) ->label($label) ->icon('heroicon-o-arrow-path') ->requiresConfirmation() ->modalHeading($label) ->modalDescription('This delegates to the existing baseline compare start flow and queues a compare operation for the current environment.') ->modalSubmitActionLabel($label) ->action(function (): void { $this->startBaselineCompare(); }); return UiEnforcement::forAction($action, fn (): ?ManagedEnvironment => $this->currentEnvironment()) ->requireCapability(Capabilities::TENANT_SYNC) ->apply(); } private function runCompareEmptyStateAction(): Action { return $this->runCompareAction('runComparisonAgainFromEmptyState') ->visible(fn (): bool => ! $this->summary()['has_run']); } private function startBaselineCompare(): void { $tenant = $this->currentEnvironment(); $user = auth()->user(); if (! $tenant instanceof ManagedEnvironment) { Notification::make()->title('Open an environment to compare baselines')->danger()->send(); return; } if (! $user instanceof User) { Notification::make()->title('Not authenticated')->danger()->send(); return; } $result = app(BaselineCompareService::class)->startCompare($tenant, $user); if (! ($result['ok'] ?? false)) { $reasonCode = is_string($result['reason_code'] ?? null) ? (string) $result['reason_code'] : 'unknown'; $translation = is_array($result['reason_translation'] ?? null) ? $result['reason_translation'] : []; $message = is_string($translation['short_explanation'] ?? null) && trim((string) $translation['short_explanation']) !== '' ? trim((string) $translation['short_explanation']) : 'Reason: '.$reasonCode; Notification::make() ->title('Cannot start comparison') ->body($message) ->danger() ->send(); return; } $run = $result['run'] ?? null; if ($run instanceof OperationRun) { $this->focusedOperationRunId = (int) $run->getKey(); } OpsUxBrowserEvents::dispatchRunEnqueued($this); OperationUxPresenter::queuedToast($run instanceof OperationRun ? (string) $run->type : OperationRunType::BaselineCompare->value) ->actions($run instanceof OperationRun ? [ Action::make('view_run') ->label('Open operation') ->url(OperationRunLinks::view($run, $tenant)), ] : []) ->send(); $this->resetTable(); } /** * @return array */ private function bindingAttributes(array $row, array $data, ?array $candidate = null): array { return [ 'subject_domain' => (string) ($row['subject_domain'] ?? 'baseline'), 'subject_class' => (string) ($row['subject_class'] ?? 'policy_backed'), 'subject_type_key' => (string) ($row['subject_type_key'] ?? 'unknown'), 'canonical_subject_key' => is_string($row['canonical_subject_key'] ?? null) ? (string) $row['canonical_subject_key'] : null, 'display_label' => is_string($candidate['display_label'] ?? null) ? (string) $candidate['display_label'] : (string) ($row['subject_label'] ?? ''), 'resolution_reason' => (string) ($row['reason'] ?? 'baseline_subject_resolution'), 'operator_note' => (string) ($data['operator_note'] ?? ''), 'source_operation_run_id' => is_numeric($row['source_operation_run_id'] ?? null) ? (int) $row['source_operation_run_id'] : null, 'source_baseline_snapshot_id' => is_numeric($row['source_baseline_snapshot_id'] ?? null) ? (int) $row['source_baseline_snapshot_id'] : null, 'source_inventory_item_id' => is_numeric($candidate['source_inventory_item_id'] ?? $row['source_inventory_item_id'] ?? null) ? (int) ($candidate['source_inventory_item_id'] ?? $row['source_inventory_item_id']) : null, 'source_policy_version_id' => is_numeric($candidate['source_policy_version_id'] ?? $row['source_policy_version_id'] ?? null) ? (int) ($candidate['source_policy_version_id'] ?? $row['source_policy_version_id']) : null, ]; } private function candidateFromRow(array $row, string $candidateKey): array { $candidate = collect($row['candidates'] ?? []) ->first(fn (array $candidate): bool => (string) ($candidate['candidate_key'] ?? '') === $candidateKey); if (! is_array($candidate) || ! is_array($candidate['identity'] ?? null)) { throw new InvalidArgumentException('Select a valid provider resource candidate.'); } return $candidate; } private function freshRowOrAbort(Model $record): array { $tenant = $this->currentEnvironment(); if (! $tenant instanceof ManagedEnvironment) { throw new NotFoundHttpException; } $row = $this->query()->row( environment: $tenant, rowId: (string) $record->getKey(), filters: [ 'operation_run_id' => $this->focusedOperationRunId, 'include_resolved' => true, ], ); if (! is_array($row)) { throw new NotFoundHttpException; } return $row; } private function authorizedMutationEnvironment(): ManagedEnvironment { $tenant = $this->currentEnvironment(); $this->authorizeEnvironmentOrAbort($tenant, Capabilities::WORKSPACE_BASELINES_MANAGE); return $tenant; } private function actorOrAbort(): User { $user = auth()->user(); if (! $user instanceof User) { abort(403); } return $user; } private function authorizeEnvironmentOrAbort(?ManagedEnvironment $environment, string $capability): void { $user = auth()->user(); if (! $environment instanceof ManagedEnvironment || ! $user instanceof User) { abort(404); } if (! static::routeWorkspaceMatchesEnvironment($environment)) { abort(404); } $decision = app(ManagedEnvironmentAccessScopeResolver::class) ->decision($user, $environment, $capability); if ($decision->shouldDenyAsNotFound()) { abort(404); } if ($decision->shouldDenyAsForbidden()) { abort(403); } } /** * @param array $filters * @return Collection> */ private function filteredRows(array $filters, ?string $search): Collection { $tenant = $this->currentEnvironment(); if (! $tenant instanceof ManagedEnvironment) { return collect(); } $normalizedFilters = [ 'operation_run_id' => $this->focusedOperationRunId, 'provider' => data_get($filters, 'provider.value'), 'subject_class' => data_get($filters, 'subject_class.value'), 'resource_type' => data_get($filters, 'resource_type.value'), 'actionability' => data_get($filters, 'actionability.value'), 'readiness_impact' => data_get($filters, 'readiness_impact.value'), 'reason' => data_get($filters, 'reason.value'), 'active_binding' => data_get($filters, 'active_binding.value'), 'candidates' => data_get($filters, 'candidates.value'), ]; $rows = collect($this->query()->rows($tenant, $normalizedFilters)); $normalizedSearch = Str::lower(trim((string) $search)); if ($normalizedSearch === '') { return $rows; } return $rows ->filter(fn (array $row): bool => str_contains((string) ($row['search_text'] ?? ''), $normalizedSearch)) ->values(); } /** * @param Collection> $rows * @return Collection> */ private function sortRows(Collection $rows, ?string $sortColumn, ?string $sortDirection): Collection { $allowed = [ 'subject_label', 'reason_label', 'readiness_label', 'readiness_impact', 'actionability_label', 'candidate_count', 'current_decision_label', 'source_operation_run_id', 'provider_label', 'subject_class_label', ]; $sortColumn = in_array($sortColumn, $allowed, true) ? $sortColumn : 'readiness_impact'; $descending = Str::lower((string) ($sortDirection ?? 'asc')) === 'desc'; return $rows->sortBy( fn (array $row): string|int => $sortColumn === 'candidate_count' || $sortColumn === 'source_operation_run_id' ? (int) ($row[$sortColumn] ?? 0) : (string) ($row[$sortColumn] ?? ''), SORT_NATURAL | SORT_FLAG_CASE, $descending, )->values(); } /** * @param Collection> $rows */ private function paginateRows(Collection $rows, int $page, int $recordsPerPage): LengthAwarePaginator { $perPage = max(1, $recordsPerPage); $currentPage = max(1, $page); $items = $rows->forPage($currentPage, $perPage) ->values() ->map(fn (array $row): Model => $this->toTableRecord($row)); return new LengthAwarePaginator( $items, $rows->count(), $perPage, $currentPage, ); } /** * @param array $row */ private function toTableRecord(array $row): Model { $record = new class extends Model { public $timestamps = false; public $incrementing = false; protected $keyType = 'string'; protected $guarded = []; protected $table = 'baseline_subject_resolution_rows'; }; $record->forceFill($row); $record->exists = true; return $record; } /** * @return array */ private function filterOptions(string $key): array { $tenant = $this->currentEnvironment(); if (! $tenant instanceof ManagedEnvironment) { return []; } return $this->query()->filterOptions($tenant, $this->focusedOperationRunId, $key); } private function emptyStateHeading(): string { $summary = $this->summary(); if (! $summary['has_run']) { return 'Run baseline compare first'; } if ($summary['legacy_payload_only']) { return 'Structured subject decisions unavailable'; } return 'No baseline subject decisions required'; } private function emptyStateDescription(): string { return match ($this->emptyStateHeading()) { 'Run baseline compare first' => 'No baseline compare run exists for this environment yet.', 'Structured subject decisions unavailable' => 'The latest compare run does not contain Spec 383 subject outcome semantics. Run baseline compare again to refresh the decision worklist.', default => 'The selected compare context has no unresolved or decision-required baseline subjects.', }; } /** * @return array */ private function summary(): array { $tenant = $this->currentEnvironment(); if (! $tenant instanceof ManagedEnvironment) { return ['has_run' => false, 'legacy_payload_only' => false, 'actionable_count' => 0]; } return $this->query()->summary($tenant, $this->focusedOperationRunId); } private function readinessColor(string $readinessImpact): string { return match ($readinessImpact) { 'customer_blocker', 'internal_blocker' => 'danger', 'customer_limitation', 'internal_limitation' => 'warning', 'no_impact' => 'success', default => 'gray', }; } private function scopedOperationRunIdFromQuery(ManagedEnvironment $tenant): ?int { $operationRunId = request()->query('operation_run_id'); if (! is_numeric($operationRunId)) { return null; } $run = $this->query()->resolveRun($tenant, (int) $operationRunId); return $run instanceof OperationRun ? (int) $run->getKey() : null; } protected static function resolveRouteOwnedEnvironment(ManagedEnvironment|string|null $environment = null): ?ManagedEnvironment { if ($environment instanceof ManagedEnvironment) { return $environment; } if (is_string($environment) && $environment !== '') { return ManagedEnvironment::query() ->where('slug', $environment) ->first(); } $routeEnvironment = request()->route('environment') ?? request()->route('tenant'); if ($routeEnvironment instanceof ManagedEnvironment) { return $routeEnvironment; } if (is_string($routeEnvironment) && $routeEnvironment !== '') { return ManagedEnvironment::query() ->where('slug', $routeEnvironment) ->first(); } $refererEnvironment = static::resolveRefererOwnedEnvironment(); if ($refererEnvironment instanceof ManagedEnvironment) { return $refererEnvironment; } $filamentTenant = Filament::getTenant(); return $filamentTenant instanceof ManagedEnvironment ? $filamentTenant : null; } private static function resolveRefererOwnedEnvironment(): ?ManagedEnvironment { $referer = request()->headers->get('referer'); if (! is_string($referer) || $referer === '') { return null; } $path = parse_url($referer, PHP_URL_PATH); if (! is_string($path)) { return null; } if (preg_match('#^/admin/workspaces/([^/]+)/environments/([^/]+)/baseline-subject-resolution$#', $path, $matches) !== 1) { return null; } $workspaceRouteKey = rawurldecode($matches[1]); $environmentRouteKey = rawurldecode($matches[2]); $environment = ManagedEnvironment::query() ->where('slug', $environmentRouteKey) ->first(); if (! $environment instanceof ManagedEnvironment) { return null; } $workspace = $environment->workspace instanceof Workspace ? $environment->workspace : $environment->workspace()->first(); if (! $workspace instanceof Workspace) { return null; } if ($workspaceRouteKey !== static::workspaceRouteKey($workspace) && $workspaceRouteKey !== (string) $workspace->getKey()) { return null; } return $environment; } private static function routeWorkspaceMatchesEnvironment(ManagedEnvironment $environment): bool { $routeWorkspace = request()->route('workspace'); if ($routeWorkspace instanceof Workspace) { return (int) $routeWorkspace->getKey() === (int) $environment->workspace_id; } if (! is_string($routeWorkspace) && ! is_int($routeWorkspace)) { return true; } $routeWorkspace = trim((string) $routeWorkspace); if ($routeWorkspace === '') { return true; } $workspace = $environment->workspace instanceof Workspace ? $environment->workspace : $environment->workspace()->first(); if (! $workspace instanceof Workspace) { return false; } return $routeWorkspace === static::workspaceRouteKey($workspace) || $routeWorkspace === (string) $workspace->getKey(); } /** * @param array $parameters */ protected static function resolveAdminUrlEnvironment(array $parameters, ?Model $tenant = null): ?ManagedEnvironment { if ($tenant instanceof ManagedEnvironment) { return $tenant; } foreach (['environment', 'tenant', 'managed_environment_id', 'environment_id', 'tenant_id'] as $key) { $value = $parameters[$key] ?? null; if ($value instanceof ManagedEnvironment) { return $value; } if (is_numeric($value)) { return ManagedEnvironment::query()->whereKey((int) $value)->first(); } if (is_string($value) && trim($value) !== '') { return ManagedEnvironment::query()->where('slug', trim($value))->first(); } } return Filament::getTenant() instanceof ManagedEnvironment ? Filament::getTenant() : null; } /** * @param array $parameters */ protected static function resolveAdminUrlWorkspace(ManagedEnvironment $environment, array $parameters = []): Workspace|string|int|null { $workspace = $parameters['workspace'] ?? null; if ($workspace instanceof Workspace || is_string($workspace) || is_int($workspace)) { return $workspace; } return Workspace::query()->whereKey((int) $environment->workspace_id)->first(); } protected static function workspaceRouteKey(Workspace $workspace): string { $slug = $workspace->getAttribute('slug'); return is_string($slug) && $slug !== '' ? $slug : (string) $workspace->getKey(); } /** * @param array $parameters * @return array */ protected static function withoutLegacyScopeQuery(array $parameters): array { foreach (self::LEGACY_SCOPE_QUERY_KEYS as $key) { unset($parameters[$key]); } return $parameters; } private function query(): BaselineSubjectResolutionQuery { return app(BaselineSubjectResolutionQuery::class); } }