Skip to main content

LicenseValidationCron

\Benecaster\License\LicenseValidationCron

Class Premium

Daily WP-Cron job on the benecaster_license_validation hook. Validates every connected show against the license server, records telemetry, and updates the plugin’s license state options. run() is also called synchronously by POST /license/validate when an admin triggers an immediate validation cycle.

Validation is per show. run() reads the connected show UUIDs from ShowTokenRepository; if none is connected it fires benecaster_license_cron_complete and returns without any network call. Each connected show is validated in turn and carries its own 401 strike counter and reconnect flag, so a revoked token on one show does not lock out another.

Response handling — status: valid maps to Active or Expiring depending on expires_at and resets the failure counter; status: invalid maps to Cancelled (reason: cancelled) or Suspended and also resets the counter, because the server was reachable; a null or malformed response fails open, leaving current state and incrementing the consecutive-failure counter.

Failure thresholds — days 1 to 14 are silent. Days 15 to 29 carry a 1-in-10 daily chance of dispatching the operator alert. Day 30 and beyond forces status to Expired, restricting premium features until the server is reachable again.

Constructor Dependencies

Type Description
\Benecaster\License\LicenseClient Issues /validate and /alert, and exposes last_http_status() so a 401 can be told from an outage.
\Benecaster\License\ValidationPayloadBuilder Builds the outbound /validate body.
\Benecaster\License\LicensePricingFetcher After each valid response, maybe_refresh() fetches pricing when the 24-hour pricing cache is empty. This cron path has none of the admin-request gates, and ignores the failed-fetch backoff.
\Benecaster\License\ShowTokenRepository Connected show UUIDs, 401 strike counters, the time of each show's last counted strike, and reconnect state. Defaults to a new instance.

Methods

Method Visibility Since Description
register(): void Public — Binds run() to the benecaster_license_validation hook and schedules the daily event if it is not already scheduled.
run(): void Public — Executes the validation cycle for every connected show, then fires benecaster_license_cron_complete once at the end of every path — valid, invalid, unreachable, or nothing-connected.
derive_active_status( string $expires_at ): LicenseStatus Protected — Given expires_at from a valid response, returns Expiring when expiry is within 30 days, otherwise Active. An empty or unparseable value returns Active — this is how the Studio unlimited plan resolves. Protected so tests can subclass.
now(): int Protected — Current Unix timestamp. Protected so tests can override it for deterministic time-sensitive assertions.
roll_alert_dice(): bool Protected — Returns true on a 1-in-10 roll, gating the Day 15 to 29 operator alert. Protected so tests can subclass with a deterministic result.

Hooks Fired

Notes

send_vendor_alert() has no license-key check. On an OAuth-connected install benecaster_license_key is always empty, so a gate on it would mean **the Day 15 to 29 operator alert could not fire on any correctly-connected site.** The alert dispatches for any connected show, authenticated by that show's bearer token. The alert body carries no license key — site_url, failure_days and plugin_version only.

BenchmarkFetcher::should_fetch() and the diagnostic log sender follow the same rule.

Two-strike 401 handling. The license server returns 401 only when the site token is missing, unrecognised or revoked; outages return 5xx or a WP_Error and continue down the fail-open path. A first 401 records a strike and fires benecaster_license_unauthorized_strike without blocking anything, so a one-off Authorization-header-stripping incident on a managed host does not lock a paying customer out for a day. A second strike counts only when its 401 arrives at least 12 hours after the first counted one; a 401 inside that window is the same strike again, with no counter change and no benecaster_license_unauthorized_strike. The second counted strike is authoritative: reconnect state is set for that show, the same license state fields handle_invalid() clears are zeroed, and benecaster_license_reconnect_required fires. Strikes reset on the next 2xx, valid or invalid. The show's token is deliberately not cleared — the prefix stays visible in Settings, License so the operator can cross-reference the revoked token in Customer Detail, Connected Sites.

The plugin sends no auto-upgrade admin email, and none should be added. An email about the podcaster's Benecaster bill would duplicate the license server's upgrade_confirmed email, and it would contradict the split: the server emails about our relationship with the podcaster, the plugin shows notices about the podcaster's relationship with their listeners.

handle_invalid() does not clear the license's push secret — pushes must stay verifiable through a temporary validation failure.