*/ private array $types = []; public function __construct( private readonly ProPresence $pro, private readonly SitePresence $site, ) { } public function register(DestinationType $type): self { $this->types[$type->id()] = $type; return $this; } public function find(string $typeId): ?DestinationType { return $this->types[$typeId] ?? null; } /** * @return array */ public function all(): array { return $this->types; } /** * One type's Availability on this install. * * A type that is **not registered at all** is `locked` or `unavailable` * depending on why, and this cannot tell which — the code that did not run * cannot say why it did not. So an unregistered type reads as `locked` * only on a free install, and `unavailable` on any paid one: a broken Pro * plugin on a valid licence is not something to sell a licence for. * * Asked as "is this install free", the one question {@see ProPresence} * answers. */ public function availabilityOf(string $typeId): Availability { $type = $this->find($typeId); if ($type === null) { return $this->pro->installedTier() === Tier::Free ? Availability::Locked : Availability::Unavailable; } $requires = $type->requires(); return Availability::of( $requires === null || $this->site->has($requires), $type->tier()->isSuppliedBy($this->pro) ); } /** * Whether a Destination of this type may be dispatched to right now. * * **Checked at DISPATCH time**, which is an ordinary server-side runtime * check — deliberately unlike the enqueue-time asset gating the rule * engine needs, which exists only because rules are browser-evaluated. * * A Destination whose type is not `ready` is **skipped and recorded, never * enqueued**: an Action Scheduler job whose handler is unregistered * retries and fails forever, silently. The Optin keeps showing and keeps * capturing, because losing captures when a licence lapses would be the * one genuinely unrecoverable failure available here (#4). * * The recording is {@see HealthStore::skipped()}, written by * {@see PushDispatcher}. This method only answers the question. */ public function isDispatchable(string $typeId): bool { return $this->availabilityOf($typeId) === Availability::Ready; } }