quota() ); } } /** * Shared by refresh() and the settings screen, which makes this same * call already (to show the quota line) — one request instead of two. */ public static function apply_response( array $response ): void { if ( 200 === $response['status'] && is_array( $response['body'] ) ) { $state = $response['body']['access'] ?? null; if ( in_array( $state, self::STATES, true ) ) { $ends_at = $response['body']['access_ends_at'] ?? null; self::store( $state, is_string( $ends_at ) ? $ends_at : null ); } // A 200 without a recognisable 'access' key (an older service) // leaves the transient untouched rather than guessing. return; } if ( 401 === $response['status'] ) { self::lapse(); } // Any other outcome (network failure, 429/5xx, malformed body): the // checked_at we already have keeps ageing towards STALE_S (P11) // instead of being refreshed by a call that told us nothing. } public static function lapse(): void { self::store( 'lapsed', null ); } private static function store( string $state, ?string $ends_at ): void { // The effective state, not the stored one: a player hidden by // staleness (P11) was cached without the player, so recovering from // it must purge like any other way out of "lapsed". $previous_state = self::state(); set_transient( self::TRANSIENT, array( 'state' => $state, 'ends_at' => $ends_at, 'checked_at' => time() ), self::STORAGE_TTL_S ); // Spec §6.1: only a transition into or out of "lapsed" purges pages — // the same state written again (the common case, once a day) must not. if ( $state !== $previous_state && ( 'lapsed' === $state || 'lapsed' === $previous_state ) ) { Readerr_Cache::purge_all(); } } /** * The effective state, staleness folded in (P11). Defaults to "active": * a fresh install with no check yet must not hide a player that was * never shown to be gone. */ public static function state(): string { $data = get_transient( self::TRANSIENT ); if ( ! is_array( $data ) || ! isset( $data['state'], $data['checked_at'] ) ) { return 'active'; } if ( time() - (int) $data['checked_at'] > self::STALE_S ) { return 'lapsed'; } return in_array( $data['state'], self::STATES, true ) ? $data['state'] : 'active'; } /** The grace period's end (ISO 8601 UTC, as the service sent it), or null. */ public static function ends_at(): ?string { $data = get_transient( self::TRANSIENT ); return is_array( $data ) && is_string( $data['ends_at'] ?? null ) ? $data['ends_at'] : null; } }