STATE_FREE_MISSING * - Free incompatible -> STATE_FREE_INCOMPATIBLE * - Pro inactive -> STATE_PRO_INACTIVE * - marketplace signal unavailable -> STATE_MARKETPLACE_UNAVAILABLE * - entitlement invalid/expired -> STATE_ENTITLEMENT_INVALID * - entitlement valid -> STATE_ENTITLEMENT_VALID * - temporary provider failure -> STATE_PROVIDER_TEMP_FAIL * * Fail-closed invariant: only an available, healthy provider that explicitly * allows Pro execution yields STATE_ENTITLEMENT_VALID. Every other combination * stays non-valid; a missing/failed signal never becomes a Safe/active state. * * @package Kayvandra\MarginBeacon\Application\Entitlement\Providers */ declare(strict_types=1); namespace Kayvandra\MarginBeacon\Application\Entitlement\Providers; /** * Maps provider signals (allows_pro_execution, is_available, is_unavailable) plus * the plugin lifecycle to the canonical presentation state used by the * UpgradeStateResolver and the License/Overview UI. */ final class EntitlementProviderState { const STATE_FREE_MISSING = 'free_missing'; const STATE_FREE_INCOMPATIBLE = 'free_incompatible'; const STATE_PRO_INACTIVE = 'pro_inactive'; const STATE_MARKETPLACE_UNAVAILABLE = 'marketplace_unavailable'; const STATE_ENTITLEMENT_INVALID = 'entitlement_invalid'; const STATE_ENTITLEMENT_VALID = 'entitlement_valid'; const STATE_PROVIDER_TEMP_FAIL = 'provider_temp_fail'; /** * Resolve the entitlement provider outcome to a presentation state. * * @param EntitlementProvider $provider Provider signal. * @param bool $pro_installed Whether the Pro companion is installed (any state). * @param bool $pro_active Whether the Pro companion is active on this site. * @return string One of the STATE_* constants above. */ public static function resolve( EntitlementProvider $provider, bool $pro_installed = false, bool $pro_active = false ): string { // Lifecycle first: installed-but-inactive outranks any entitlement signal. if ( $pro_installed && ! $pro_active ) { return self::STATE_PRO_INACTIVE; } // The only "up" state: available, healthy, and explicitly allowed. if ( self::provider_allows_execution( $provider ) ) { return self::STATE_ENTITLEMENT_VALID; } // Pro active but the channel is not wired at all. if ( $pro_active && ! $provider->is_available() ) { return self::STATE_MARKETPLACE_UNAVAILABLE; } // Temporary provider failure (channel wired but transiently unreachable). if ( $provider->is_available() && $provider->is_unavailable() ) { return self::STATE_PROVIDER_TEMP_FAIL; } // Pro active, channel healthy, but entitlement denied/expired. if ( $pro_active ) { return self::STATE_ENTITLEMENT_INVALID; } // Not installed: distinguish incompatible (channel reachable) from missing. if ( $provider->is_available() ) { return self::STATE_FREE_INCOMPATIBLE; } return self::STATE_FREE_MISSING; } /** * Shared fail-closed predicate for runtime execution and UI presentation. * * Conflicting signals (available + unavailable) never yield execution. * * @param EntitlementProvider $provider Provider. * @return bool */ public static function provider_allows_execution( EntitlementProvider $provider ) { return $provider->allows_pro_execution() && $provider->is_available() && ! $provider->is_unavailable(); } /** * Human-readable label for a state (for tests / diagnostics). * * @param string $state State constant. * @return string */ public static function label( $state ): string { switch ( $state ) { case self::STATE_FREE_MISSING: return __( 'Free missing', 'marginbeacon' ); case self::STATE_FREE_INCOMPATIBLE: return __( 'Free incompatible', 'marginbeacon' ); case self::STATE_PRO_INACTIVE: return __( 'Pro inactive', 'marginbeacon' ); case self::STATE_MARKETPLACE_UNAVAILABLE: return __( 'Marketplace signal unavailable', 'marginbeacon' ); case self::STATE_ENTITLEMENT_INVALID: return __( 'Entitlement invalid/expired', 'marginbeacon' ); case self::STATE_ENTITLEMENT_VALID: return __( 'Entitlement valid', 'marginbeacon' ); case self::STATE_PROVIDER_TEMP_FAIL: return __( 'Temporary provider failure', 'marginbeacon' ); default: return __( 'Unknown', 'marginbeacon' ); } } /** * Prevent instantiation. */ private function __construct() {} }