PluginProbe
404 Solution / 4.3.0
404 Solution v4.3.0
4.3.5 4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 4.2.0 4.1.19 4.1.18 4.1.17 4.1.16 4.1.15 4.1.13 4.1.12 4.1.11 4.1.10 4.1.9 4.1.8 4.1.7 4.1.6 4.1.5 4.1.4 4.1.3 trunk 2.30.0 All 109 releases
404-solution / includes / frontend / FrontendDbVersionRecovery.php

FrontendDbVersionRecovery.php in 404 Solution 4.3.0, at includes/frontend/FrontendDbVersionRecovery.php

87 lines 3.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if (!defined('ABSPATH')) {
4 exit;
5 }
6
7 /**
8 * Self-heals a stale DB_VERSION on the frontend so end users get redirects
9 * without needing an admin visit (task 233). Throttled by a transient so
10 * concurrent 404s don't all queue on the synchronizer lock inside
11 * PluginLogicVersionUpgrader::upgradeIfNeeded(). If recovery cannot close
12 * the gap (lock held, cooldown active, migration repeatedly throws), the
13 * caller falls through to a degraded redirect lookup (task 234) so manual
14 * redirects keep serving instead of every 404 falling to the theme 404 page.
15 */
16 class ABJ_404_Solution_FrontendDbVersionRecovery {
17
18 /** @var ABJ_404_Solution_PluginLogic */
19 private $logic;
20
21 /** @var ABJ_404_Solution_Logging */
22 private $logger;
23
24 /**
25 * @param ABJ_404_Solution_PluginLogic $logic
26 * @param ABJ_404_Solution_Logging $logger
27 */
28 function __construct($logic, $logger) {
29 $this->logic = $logic;
30 $this->logger = $logger;
31 }
32
33 /**
34 * @param array<string, mixed> $options Current options as returned by getOptions(true).
35 * @return array<string, mixed> Options after attempted recovery.
36 */
37 function recoverIfStale(array $options): array {
38 $cooldownKey = 'abj404_frontend_db_recovery_cooldown';
39
40 if (function_exists('get_transient') && get_transient($cooldownKey)) {
41 return $options;
42 }
43
44 // Cooldown is set BEFORE attempting recovery so concurrent requests
45 // bail immediately rather than piling onto the lock.
46 if (function_exists('set_transient')) {
47 set_transient($cooldownKey, '1', 5 * 60);
48 }
49
50 try {
51 $upgraded = $this->logic->versionUpgrader()->upgradeIfNeeded($options);
52 if (is_array($upgraded)) {
53 $options = $upgraded;
54 }
55 } catch (\Throwable $e) {
56 $this->logger->warn('Frontend DB version recovery failed: ' . $e->getMessage());
57 return $options;
58 }
59
60 // upgradeIfNeeded ends in updateOptions() which clears the resolved-
61 // options cache, so getOptions(true) returns fresh values from the DB.
62 $fresh = $this->getOptions();
63 if (isset($fresh['DB_VERSION']) && defined('ABJ404_VERSION') && $fresh['DB_VERSION'] == ABJ404_VERSION) {
64 return $fresh;
65 }
66
67 $observed = (isset($fresh['DB_VERSION']) && is_scalar($fresh['DB_VERSION']))
68 ? (string)$fresh['DB_VERSION']
69 : '(missing)';
70 $expected = defined('ABJ404_VERSION') ? ABJ404_VERSION : '(unknown)';
71 $this->logger->warn(sprintf(
72 'Frontend DB_VERSION still stale after recovery attempt: have=%s expected=%s',
73 $observed,
74 $expected
75 ));
76 return $fresh;
77 }
78
79 /**
80 * @return array<string, mixed>
81 */
82 private function getOptions(): array {
83 $options = $this->logic->optionsResolver()->getOptions(true);
84 return is_array($options) ? $options : array();
85 }
86 }
87