LicenseValidationCron
\Benecaster\License\LicenseValidationCron
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.