Skip to main content

LicenseManager

\Benecaster\License\LicenseManager

Class Free

The plugin’s cached view of what the license server has said: plan, entitlements, and add-on access. Reads options only — no API call per request. LicenseValidationCron is what writes them.

The license getters take an optional show. Each one documented below with a $show_context argument accepts a show post ID or _benecaster_show_uuid and answers for that show’s license; with no show it reads the license of the site’s first connected show. Every show on a site shares one license, so the answers match, but pass the show when you have one. The values are license state, not standalone wp_options: read them through these methods.

Call addon_is_active() with a show context. The one-argument form is fatal. The method takes a required int|string $show_context, and a one-argument call raises ArgumentCountError. Add-on code that passes a slug alone does not degrade — it fatals.

The add-on gates accept either slug form. addon_is_active(), addon_is_granted() and any_show_addon_is_active() normalise the slug they are given, so a bare catalog slug (analytics-dashboard) and a prefixed entitlement slug (benecaster-addon-analytics-dashboard) resolve identically. The conversion belongs in this class because this is where the pricing catalogue’s vocabulary meets the entitlement catalogue’s.

Constructor Dependencies

Type Description
\Benecaster\License\ShowAddonRepository Per-show grant cache and local activation set. Defaulted, so existing construction still works.

Methods

Method Visibility Since Description
addon_is_active( string $addon_slug, int|string $show_context ): bool Public — Resolves two questions in order: granted (does this show's license entitle the add-on?) then enabled (has the podcaster left it switched on for this show?). $show_context is REQUIRED — a show post ID or _benecaster_show_uuid. Callers that genuinely mean anywhere on this install want any_show_addon_is_active().
addon_is_granted( string $addon_slug, int|string $show_context ): bool Public — The grant half alone, ignoring the podcaster's on/off switch — does this show's license entitle this add-on? The Add-ons screen needs both halves separately so it can render an owned-but-switched-off add-on rather than hiding it. Falls through to the site-wide benecaster_active_addons list when no per-show grant cache exists, so a license server predating the shows[] field behaves exactly as before. An empty shows array means we know nothing per show, not entitled to nothing — reading it as the latter would blank a healthy license's add-ons, the very failure this storage exists to prevent. Studio's all-add-ons plan short-circuits to true. false for every add-on while hard_limit_in_force() is true, whatever the caches hold.
hard_limit_in_force(): bool Public — True while the license server has been unreachable for 30 days or more. Every add-on gate on this class then answers false for every add-on on every show, the all-add-ons plan and site-wide free add-ons included, and registered add-ons do not boot. It is site-wide, because there is one license server, and it reads the unreachable streak rather than the license status, so a license that simply ran out does not set it. The next successful license check clears it, and the stored grants apply again.
any_show_addon_is_active( string $addon_slug ): bool Public — True when the add-on is active on at least one connected show — the site-wide counterpart, mirroring any_show_is_premium(). For consumers with no show in hand: telemetry, boot-time registration, an admin asset decision. Never use it to gate content a subscriber sees. Some show on this install has the add-on is not an answer to may this show use it, and substituting one for the other re-opens the cross-license leak this feature closed.
catalog_slug_to_entitlement_slug( string $catalog_slug ): string — — Converts a pricing-catalog slug (email-editor) to the entitlement-system slug (benecaster-addon-email-editor). Idempotent — an already-prefixed slug is returned unchanged. The three add-on gates call it on the way in, so you do not need it to query them; reach for it when you are matching slugs between the two catalogues yourself.
entitlement_slug_to_catalog_slug( string $entitlement_slug ): string — — The inverse — benecaster-addon-email-editor to email-editor. Also idempotent. Use it when matching an entitlement back to a pricing-catalog entry for display.
get_downgrade_eligible( int|string|null $show_context = null ): bool Public — The cached downgrade_eligible flag from /validate, defaulting to false. DISPLAY ONLY. It is named get_ rather than is_ deliberately, so it does not read like permission at a call site — it is one character away from the license server's real gate, PlanChangeOptions::is_downgrade_eligible(), which is the one that decides. An add-on gating behaviour on this gets a cached opinion that can be a day old.
get_enforcement_preference( string $type, int|string|null $show_context = null ): string Public — The enforcement preference for one limit: pass 'subscriber' or 'token'; returns 'auto_upgrade' or 'cap'. This is the whole answer to whether going over that limit upgrades the plan — one preference per limit, with no account-wide switch beside it. The subscriber limit defaults to auto_upgrade and the token cap to cap; any other stored value falls back to the default. Both are changed only from Settings → Account → Limit Enforcement, which pushes them to the license server through POST /license/preferences.
get_referral_code( int|string|null $show_context = null ): ?string Public — The podcaster's referral code from the last daily /validate, or null when the license server has not supplied one (an unactivated install, or a server that predates referrals). The Refer a Friend page uses null to show its "your link will appear once your license is fully activated" message instead of the link. Reads the cache only, so there is no HTTP call. Also available as benecaster_get_referral_code().
get_referral_link( int|string|null $show_context = null ): ?string Public — The shareable referral URL (https://staging.benecaster.com/ref/{code}) from the last daily /validate, or null when the server has not supplied one. It never changes, so the cached copy is always right. Reads the cache only. Also available as benecaster_get_referral_link(), and printed by the [benecaster_referral_link] shortcode.
get_referral_credit_cents( int|string|null $show_context = null ): int Public — The referral credit figure the daily /validate last stored, in whole cents. A cache, never to be shown as the podcaster's balance: it can be a day old, stays stale while the license server is unreachable, and the server sends 0 when it cannot read the figure, so 0 cannot tell "no credit" from "the lookup failed". For display use benecaster_get_live_referral_balance(), which wraps LiveReferralFigures.
get_referral_conversion_count( int|string|null $show_context = null ): ?int Public — How many people have signed up, meaning converted and not merely clicked, through the podcaster's referral link, from the last daily /validate. null when the license server has not sent the field, and null is not zero: the Refer a Friend page hides its "N people have signed up" line rather than claim none. Reads the cache only.
get_notify_referral_updates( int|string|null $show_context = null ): bool Public — Whether the podcaster wants the license server's referral-credit emails (credit earned, credit applied). Returns true when nothing has been answered yet, matching the server's own default. The plugin only caches the value, from /validate and from POST /license/preferences responses, because the license server owns it and sends the emails. Reads the cache only. Also available as benecaster_get_notify_referral_updates().
get_cached_notify_referral_updates( int|string|null $show_context = null ): ?bool Public — The three-state form of get_notify_referral_updates(): true or false once the license server has answered, null when it never has. The admin uses null to hide the checkbox rather than show a default nobody confirmed. The value is deliberately not cleared when a license lapses or a show is disconnected: it is the customer's choice, and must survive both rather than reset to "on".
get_enabled_addons_site_wide(): ?array Public — A sorted list of the add-on slugs the license grants that are not switched off on at least one connected show, in entitlement-slug form (benecaster-addon-email-editor), deduplicated across shows. It is what the site uses, as opposed to what it holds, and it feeds the opted-in enabled_addons telemetry field. Returns null (unknown, not "none") when there is no connected show, or on an all-add-ons plan whose server sent no slug list, because such a plan grants every slug and there is nothing local to enumerate. An empty array means every held add-on is switched off everywhere. Install-wide by design, like any_show_addon_is_active(). Never use it to gate anything a subscriber sees.
get_payment_method_valid( int|string|null $show_context = null ): ?bool Public — Reads a three-valued cache of the license server's payment_method_valid field on /validate: '1' maps to true, '0' to false, and an unset or empty stored value (the pre-feature default, and every non-Launch plan today) maps to null for "no answer." First consumer: UpgradePlanDialog's one-click upgrade confirm button, which disables only on an explicit false — null must be treated the same as a good card, never as a bad one. It is an internal license-state cache, not a public API surface: read it through this method.

Constants

Name Value Description
ACCOUNT_URL 'https://staging.benecaster.com/my-account/' The single definition of the customer's account destination, read by InvalidLicenseNotice, Notices\HealthChecks\LicenseServerCheck and the GET /license payload. It is NOT the upgrade destination. Several different upgrade URLs still exist elsewhere in the plugin and consolidating them is separate, still-open work — a reader who assumes this constant settled that question will think a live inconsistency is fixed.
REFERRAL_DASHBOARD_URL 'https://staging.benecaster.com/my-account/#benecaster-account-referral' Where a customer sees who signed up through their referral link: the referral section of their benecaster.com account page, which lists each sign-up by month, plan and credit. The plugin deliberately never shows those details itself. It is ACCOUNT_URL plus the anchor benecaster-account-referral, the license server's ReferralPanel section id, so renaming that id breaks the link.

Notes

Migrating a one-argument addon_is_active() call. Most hooks that could need a show context already pass one — check the callback signature before concluding you have none. Where the context is genuinely absent, ask whether the decision is about the install (registration, assets, telemetry — use any_show_addon_is_active()) or about entitlement (anything a subscriber sees — thread the show through instead).

**benecaster_active_addons exists and is written, but it is not the source of truth** — only a fall-through for shows with no cached grant. Documentation and code that treat it as authoritative are wrong.

Convert at the boundary, not at the call site. Doing it per caller invites a bug: a caller that skips the conversion reports the Transcription add-on inactive on every install that has it. Because the gates normalise, a caller has nothing to convert.

The two halves of addon_is_active() fail in opposite directions, which is why the normalisation matters on both. The grant check tests membership of an entitlement list, so an unrecognised slug fails closed. The local switch-off check tests membership of the podcaster's disabled list — an exclusion — so an unrecognised slug reads as not-disabled and fails open, ignoring a switch-off the podcaster had made.