TenantAtlas/apps/platform/app/Support/Verification/VerificationLinkBehavior.php
ahmido 360d20e881 feat: complete workspace-first environment routing cutover (#340)
## Summary
- retire the tenant panel runtime and converge operator routing on the workspace-first admin shell
- update tenant, operations, and required-permissions navigation helpers to use canonical workspace-scoped URLs
- repair the focused feature coverage, add the Spec 280 browser smoke, and record the implementation close-out in the requirements checklist

## Validation
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/WorkspaceFoundation tests/Feature/Workspaces tests/Feature/ManagedEnvironment tests/Feature/RequiredPermissions tests/Feature/Operations tests/Feature/MonitoringOperationsTest.php`
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Browser/Spec280WorkspaceTenancyEnvironmentRoutingSmokeTest.php`
- `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent`

## Note
- `origin/platform` is not present on the remote; `platform-dev` is the clean base branch that limits this PR to the Spec 280 prep commit plus the implementation commit.

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #340
2026-05-07 21:56:14 +00:00

150 lines
4.1 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Support\Verification;
use App\Support\Providers\ProviderReasonCodes;
final class VerificationLinkBehavior
{
/**
* @return array{
* label:string,
* url:string,
* kind:'external'|'internal-diagnostic'|'internal-inline-safe',
* opens_in_new_tab:bool,
* show_new_tab_hint:bool
* }
*/
public function describe(?string $label, ?string $url): array
{
$normalizedLabel = is_string($label) && trim($label) !== ''
? trim($label)
: 'Open link';
$normalizedUrl = is_string($url) && trim($url) !== ''
? trim($url)
: '';
$kind = $this->classify($normalizedUrl);
$opensInNewTab = $kind !== 'internal-inline-safe' && $normalizedUrl !== '';
return [
'label' => $normalizedLabel,
'url' => $normalizedUrl,
'kind' => $kind,
'opens_in_new_tab' => $opensInNewTab,
'show_new_tab_hint' => $opensInNewTab,
];
}
/**
* @param array<string, mixed> $check
*/
public function shouldRouteThroughAssist(array $check, bool $assistVisible): bool
{
if (! $assistVisible) {
return false;
}
$key = $check['key'] ?? null;
$key = is_string($key) ? trim($key) : '';
if ($key !== '' && str_starts_with($key, 'permissions.')) {
return true;
}
$reasonCode = $check['reason_code'] ?? null;
$reasonCode = is_string($reasonCode) ? trim($reasonCode) : '';
return in_array($reasonCode, [
ProviderReasonCodes::ProviderConsentMissing,
ProviderReasonCodes::ProviderConsentFailed,
ProviderReasonCodes::ProviderConsentRevoked,
ProviderReasonCodes::ProviderPermissionMissing,
ProviderReasonCodes::ProviderPermissionDenied,
ProviderReasonCodes::ProviderPermissionRefreshFailed,
ProviderReasonCodes::IntuneRbacPermissionMissing,
], true);
}
/**
* @return 'external'|'internal-diagnostic'|'internal-inline-safe'
*/
private function classify(string $url): string
{
if ($url === '') {
return 'internal-inline-safe';
}
$path = $this->extractPath($url);
if ($path !== null && $this->isInternalDiagnosticPath($path)) {
return 'internal-diagnostic';
}
if ($this->isExternalUrl($url)) {
return 'external';
}
return 'internal-inline-safe';
}
private function extractPath(string $url): ?string
{
$path = parse_url($url, PHP_URL_PATH);
if (is_string($path) && $path !== '') {
return '/'.ltrim($path, '/');
}
if (str_starts_with($url, '/')) {
return '/'.ltrim($url, '/');
}
return null;
}
private function isExternalUrl(string $url): bool
{
$scheme = parse_url($url, PHP_URL_SCHEME);
if (! is_string($scheme) || ! in_array(strtolower($scheme), ['http', 'https'], true)) {
return false;
}
$host = parse_url($url, PHP_URL_HOST);
if (! is_string($host) || $host === '') {
return true;
}
$applicationHost = parse_url(url('/'), PHP_URL_HOST);
$applicationPort = parse_url(url('/'), PHP_URL_PORT);
$urlPort = parse_url($url, PHP_URL_PORT);
if (! is_string($applicationHost) || $applicationHost === '') {
return true;
}
if (strcasecmp($host, $applicationHost) !== 0) {
return true;
}
if ($applicationPort === null || $urlPort === null) {
return false;
}
return (int) $urlPort !== (int) $applicationPort;
}
private function isInternalDiagnosticPath(string $path): bool
{
return (bool) preg_match(
'/^\/admin\/(?:workspaces\/[^\/]+\/environments\/[^\/]+\/required-permissions|tenants\/[^\/]+\/provider-connections(?:\/create|\/[^\/]+(?:\/edit)?)?|provider-connections(?:\/create|\/[^\/]+(?:\/edit)?)?)$/',
$path,
);
}
}