, evaluatedAt?: string, catalogVersion?: string} */ public static function snapshot(): array { $stored = get_option(self::OPTION_SNAPSHOT, null); if (is_array($stored) && isset($stored['planId'], $stored['capabilities']) && is_array($stored['capabilities'])) { return [ 'planId' => (string) $stored['planId'], 'capabilities' => array_map('boolval', $stored['capabilities']), 'evaluatedAt' => isset($stored['evaluatedAt']) ? (string) $stored['evaluatedAt'] : '', 'catalogVersion' => isset($stored['catalogVersion']) ? (string) $stored['catalogVersion'] : '', ]; } return self::free_snapshot(); } /** @return array{planId: string, capabilities: array, evaluatedAt: string, catalogVersion: string} */ public static function free_snapshot(): array { return [ 'planId' => self::PLAN_FREE, 'capabilities' => [ 'localReadinessScan' => true, 'topIssuesSummary' => true, 'basicAgentReadyScore' => true, 'artifactPreviews' => true, 'localArtifacts' => true, 'localMetadataControls' => true, 'killSwitchAndLkg' => true, 'hostedPairing' => true, 'pagePostScore' => true, 'llmsAgentMapWorkflow' => true, 'suggestedFixes' => true, 'continuousMonitoring' => false, 'monthlyReport' => false, 'readinessWorkflows' => false, 'basicHistory' => false, 'hostedDashboard' => false, ], 'evaluatedAt' => '', 'catalogVersion' => '', ]; } public static function plan_id(): string { $plan = self::snapshot()['planId']; return in_array($plan, [self::PLAN_FREE, self::PLAN_PRO, self::PLAN_GROWTH, self::PLAN_AGENCY], true) ? $plan : self::PLAN_FREE; } public static function is_paid(): bool { return self::plan_id() !== self::PLAN_FREE; } public static function can(string $capability): bool { $caps = self::snapshot()['capabilities']; return !empty($caps[$capability]); } public static function can_cloud_sync(): bool { return self::can('continuousMonitoring'); } public static function can_hosted_report(): bool { return self::can('monthlyReport') || self::can('continuousMonitoring'); } public static function can_suggested_fixes(): bool { return self::can('suggestedFixes') || self::can('readinessWorkflows'); } /** * @param array $payload */ public static function store_snapshot(array $payload): void { $planId = isset($payload['planId']) && is_string($payload['planId']) ? $payload['planId'] : self::PLAN_FREE; $capabilities = []; if (isset($payload['capabilities']) && is_array($payload['capabilities'])) { foreach ($payload['capabilities'] as $key => $value) { if (is_string($key)) { $capabilities[$key] = (bool) $value; } } } $merged = self::free_snapshot(); $merged['planId'] = in_array($planId, [self::PLAN_FREE, self::PLAN_PRO, self::PLAN_GROWTH, self::PLAN_AGENCY], true) ? $planId : self::PLAN_FREE; $merged['capabilities'] = array_merge($merged['capabilities'], $capabilities); // Connect must remain available even when server sends hostedPairing false historically. $merged['capabilities']['hostedPairing'] = true; $merged['evaluatedAt'] = isset($payload['evaluatedAt']) && is_string($payload['evaluatedAt']) ? $payload['evaluatedAt'] : gmdate('c'); $merged['catalogVersion'] = isset($payload['catalogVersion']) && is_string($payload['catalogVersion']) ? $payload['catalogVersion'] : ''; update_option(self::OPTION_SNAPSHOT, $merged, false); } public static function clear_snapshot(): void { delete_option(self::OPTION_SNAPSHOT); } public static function upgrade_url(): string { return self::CHECKOUT_URL; } public static function upgrade_cta_label(): string { if (!TheDotDevAgenticBridge_Storage::is_connected()) { return __('Connect this site', 'agentic-bridge'); } return __('Choose a paid package on console', 'agentic-bridge'); } public static function upgrade_notice_text(): string { if (!TheDotDevAgenticBridge_Storage::is_connected()) { return __('Local Agent Ready stays free. Connect this site, then request Pro / Growth / Agency on the console. Only hosted cloud sync and hosted recommendations unlock after Dot Dev approves payment.', 'agentic-bridge'); } return __('Connected on Free. Local scan, findings, metadata, and artifacts stay free. Request Pro / Growth / Agency on the console — hosted cloud sync unlocks after Dot Dev approves payment.', 'agentic-bridge'); } }