| @@ -136,8 +136,14 @@ | ||
| 136 | 136 | 'registered_at' => time(), |
| 137 | 137 | ) |
| 138 | 138 | ); |
| 139 | 139 | delete_transient( self::DOWN_TRANSIENT ); |
| 140 | + // status() consults the per-printer negative cache before DOWN_TRANSIENT, | |
| 141 | + // so clearing the site-wide marker alone is not enough: a printer that hit | |
| 142 | + // an "unknown site" 404 backed off for REREGISTER_GUARD, and without this | |
| 143 | + // it would keep returning null for the full hour even though the site_key | |
| 144 | + // is valid again. Dropping those entries lets status resume on the next call. | |
| 145 | + self::clear_status_cache(); | |
| 140 | 146 | |
| 141 | 147 | // The printer URL is always rebuilt from the validated site_key — |
| 142 | 148 | // never from the relay response — so a compromised relay cannot |
| 143 | 149 | // point printers (and their tokens) at another host. |
| @@ -197,9 +203,12 @@ | ||
| 197 | 203 | $state = array( |
| 198 | 204 | 'enabled' => false, |
| 199 | 205 | 'available' => true, |
| 200 | 206 | ); |
| 201 | - if ( 1 === preg_match( '/^[a-f0-9]{32}$/', (string) ( $relay['site_key'] ?? '' ) ) ) { | |
| 207 | + // Case-insensitive to match valid_credentials(): registration lowercases | |
| 208 | + // on write, but a legacy uppercase key still signs hints and status | |
| 209 | + // calls fine, so it must not report itself disabled to the settings app. | |
| 210 | + if ( 1 === preg_match( '/^[a-f0-9]{32}$/i', (string) ( $relay['site_key'] ?? '' ) ) ) { | |
| 202 | 211 | $state['enabled'] = true; |
| 203 | 212 | $state['printer_base_url'] = self::printer_base_url( (string) $relay['site_key'] ); |
| 204 | 213 | } |
| 205 | 214 | |
| @@ -286,8 +295,23 @@ | ||
| 286 | 295 | // registry was rebuilt, so a guarded re-registration restores the |
| 287 | 296 | // same deterministic site key. |
| 288 | 297 | if ( 404 === $code && \is_array( $data ) && 'unknown site' === ( $data['error'] ?? '' ) ) { |
| 289 | 298 | self::schedule_reregistration(); |
| 299 | + | |
| 300 | + // Back off for the re-registration window, not the cache window. | |
| 301 | + // This 404 is not a transient relay hiccup: the stored site_key is | |
| 302 | + // not in the relay's registry, and nothing about that changes until | |
| 303 | + // a re-registration succeeds — which is itself rate-limited to once | |
| 304 | + // per REREGISTER_GUARD. Falling through to the 30s failure window | |
| 305 | + // would replay the identical 404 twice a minute forever whenever a | |
| 306 | + // site cannot re-register (the relay cannot reach its verification | |
| 307 | + // endpoint, say), which is exactly what one site was doing: ~2,000 | |
| 308 | + // pointless requests a day. A successful registration deletes | |
| 309 | + // DOWN_TRANSIENT, so this self-heals the moment re-registration | |
| 310 | + // works rather than pinning the site down for the full hour. | |
| 311 | + self::note_status_failure( $key, self::REREGISTER_GUARD ); | |
| 312 | + | |
| 313 | + return null; | |
| 290 | 314 | } |
| 291 | 315 | if ( 200 !== $code || ! \is_array( $data ) ) { |
| 292 | 316 | self::note_status_failure( $key ); |
| 293 | 317 | |
| @@ -405,13 +429,35 @@ | ||
| 405 | 429 | /** |
| 406 | 430 | * Record a failed status call: per-printer negative cache plus the |
| 407 | 431 | * site-wide down marker so other printers skip their calls entirely. |
| 408 | 432 | * |
| 409 | - * @param string $transient_key Per-printer status transient key. | |
| 433 | + * @param string $transient_key Per-printer status transient key. | |
| 434 | + * @param int|null $ttl Backoff seconds; defaults to STATUS_CACHE_TTL. | |
| 435 | + * Callers pass a longer window when the | |
| 436 | + * failure cannot clear on its own within it. | |
| 410 | 437 | */ |
| 411 | - private static function note_status_failure( string $transient_key ): void { | |
| 412 | - set_transient( $transient_key, array( 'failed' => true ), self::STATUS_CACHE_TTL ); | |
| 413 | - set_transient( self::DOWN_TRANSIENT, true, self::STATUS_CACHE_TTL ); | |
| 438 | + private static function note_status_failure( string $transient_key, ?int $ttl = null ): void { | |
| 439 | + $ttl = null === $ttl ? self::STATUS_CACHE_TTL : max( 1, $ttl ); | |
| 440 | + set_transient( $transient_key, array( 'failed' => true ), $ttl ); | |
| 441 | + set_transient( self::DOWN_TRANSIENT, true, $ttl ); | |
| 442 | + } | |
| 443 | + | |
| 444 | + /** | |
| 445 | + * Drop every per-printer status cache. | |
| 446 | + * | |
| 447 | + * Because status() checks the per-printer negative cache before DOWN_TRANSIENT, | |
| 448 | + * a successful (re-)registration must clear these entries too — otherwise a | |
| 449 | + * printer that backed off on an "unknown site" 404 keeps returning null for | |
| 450 | + * the full REREGISTER_GUARD window despite the site_key being valid again. | |
| 451 | + * Dropping any live positive caches is harmless: the next call re-polls. | |
| 452 | + */ | |
| 453 | + private static function clear_status_cache(): void { | |
| 454 | + foreach ( ( new Cloud_Print_Registry() )->get_printers() as $printer ) { | |
| 455 | + $printer_id = (string) ( $printer['id'] ?? '' ); | |
| 456 | + if ( '' !== $printer_id ) { | |
| 457 | + delete_transient( self::STATUS_TRANSIENT_PREFIX . $printer_id ); | |
| 458 | + } | |
| 459 | + } | |
| 414 | 460 | } |
| 415 | 461 | |
| 416 | 462 | /** |
| 417 | 463 | * Schedule one guarded background re-registration. |