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 / root-boot / RuntimeHelpers.php

RuntimeHelpers.php in 404 Solution 4.3.0, at includes/root-boot/RuntimeHelpers.php

190 lines 6.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if (!defined('ABSPATH')) {
3 exit;
4 }
5
6 /**
7 * Runtime helpers for root-file callbacks (logging, clock, admin-policy gate).
8 *
9 * The plugin entry point owns early boot, shutdown, and cron callbacks where the
10 * service container may not be available yet. These helpers keep those callbacks
11 * on the same logging/clock/authorization abstractions the rest of the plugin
12 * uses, while degrading safely when the container or its classes are missing.
13 */
14 // allow-no-test-found: boot-time global helper functions (logging/clock/admin-policy gate) required directly by 404-solution.php before the service container exists; no isolated unit-file seam. abj404_logRuntimeWarning is exercised in CatchExceptionCoverageAuditTest and OpcacheStaleAfterUpgradeTest; the clock helpers back the Clock-injection tests.
15
16 if (!function_exists('abj404_record_missing_file')) {
17 /**
18 * Append a missing/corrupt plugin file path to the boot-state missing-files
19 * list. Centralizes the typed write to $GLOBALS['abj404_missing_files'] so
20 * callers (the autoloader, the boot sequence) don't each have to re-assert the
21 * global's array shape.
22 *
23 * @param string $path
24 * @return void
25 */
26 function abj404_record_missing_file(string $path): void {
27 if (!isset($GLOBALS['abj404_missing_files']) || !is_array($GLOBALS['abj404_missing_files'])) {
28 $GLOBALS['abj404_missing_files'] = array();
29 }
30 $GLOBALS['abj404_missing_files'][] = $path;
31 }
32 }
33
34 if (!function_exists('abj404_logRuntimeWarning')) {
35 /**
36 * Route root-file runtime warnings through the plugin logger when available.
37 *
38 * The root plugin file owns early boot, shutdown, and cron callbacks where the
39 * service container may not be available yet. This helper keeps normal runtime
40 * failures in the plugin log while preserving a last-resort PHP error log
41 * breadcrumb if logger resolution itself fails.
42 *
43 * @param string $context
44 * @param \Throwable|null $throwable
45 * @return void
46 */
47 function abj404_logRuntimeWarning(string $context, ?\Throwable $throwable = null): void {
48 $line = $context;
49 if ($throwable !== null) {
50 $line .= ' (code ' . (string)$throwable->getCode() . ') at ' .
51 $throwable->getFile() . ':' . (string)$throwable->getLine() .
52 ': ' . $throwable->getMessage();
53 }
54
55 $loggerFailure = null;
56 try {
57 $logger = null;
58 if (function_exists('abj_service_optional')) {
59 $logger = abj_service_optional('logging');
60 }
61 if (!is_object($logger) && class_exists('ABJ_404_Solution_Logging', false)) {
62 $logger = ABJ_404_Solution_Logging::getInstance();
63 }
64 if (is_object($logger) && method_exists($logger, 'warn')) {
65 $logger->warn($line);
66 return;
67 }
68 if (is_object($logger) && method_exists($logger, 'errorMessage')) {
69 $logger->errorMessage($line, $throwable instanceof Exception ? $throwable : null);
70 return;
71 }
72 } catch (\Throwable $loggingError) {
73 $loggerFailure = $loggingError;
74 }
75
76 $fallback = $line;
77 if ($loggerFailure !== null) {
78 $fallback .= ' | logger failure (code ' . (string)$loggerFailure->getCode() . ') at ' .
79 $loggerFailure->getFile() . ':' . (string)$loggerFailure->getLine() .
80 ': ' . $loggerFailure->getMessage();
81 }
82 abj404_logPhpFallback('early-boot', $fallback);
83 }
84 }
85
86 if (!function_exists('abj404_resolve_clock')) {
87 /**
88 * Resolve the project clock from the root plugin bootstrap.
89 *
90 * The normal service helper is not available until Loader.php pulls in the
91 * bootstrap files, but root-file callbacks also run before or after that
92 * boundary. This keeps those callbacks on the same clock abstraction without
93 * making early boot depend on the fully initialized container.
94 *
95 * @return object|null
96 */
97 function abj404_resolve_clock() {
98 static $fallbackClock = null;
99
100 try {
101 if (function_exists('abj_clock')) {
102 return abj_clock();
103 }
104 if (is_object($fallbackClock)) {
105 return $fallbackClock;
106 }
107 if (class_exists('ABJ_404_Solution_SystemClock')) {
108 $fallbackClock = new ABJ_404_Solution_SystemClock();
109 return $fallbackClock;
110 }
111 } catch (\Throwable $e) {
112 abj404_logRuntimeWarning('Clock resolution failed in root bootstrap', $e);
113 }
114
115 return null;
116 }
117 }
118
119 if (!function_exists('abj404_now')) {
120 /**
121 * Current epoch seconds for root-file callbacks.
122 *
123 * @return int
124 */
125 function abj404_now(): int {
126 $clock = abj404_resolve_clock();
127 if (is_object($clock) && method_exists($clock, 'now')) {
128 try {
129 return (int)$clock->now();
130 } catch (\Throwable $e) {
131 abj404_logRuntimeWarning('Clock now() failed in root bootstrap', $e);
132 }
133 }
134
135 static $reportedUnavailable = false;
136 if (!$reportedUnavailable) {
137 abj404_logRuntimeWarning('Clock unavailable in root bootstrap; using zero epoch fallback');
138 $reportedUnavailable = true;
139 }
140 return 0;
141 }
142 }
143
144 if (!function_exists('abj404_now_float')) {
145 /**
146 * Current epoch seconds with sub-second precision for root-file callbacks.
147 *
148 * @return float
149 */
150 function abj404_now_float(): float {
151 $clock = abj404_resolve_clock();
152 if (is_object($clock) && method_exists($clock, 'nowFloat')) {
153 try {
154 return (float)$clock->nowFloat();
155 } catch (\Throwable $e) {
156 abj404_logRuntimeWarning('Clock nowFloat() failed in root bootstrap', $e);
157 }
158 }
159
160 return (float)abj404_now();
161 }
162 }
163
164 if (!function_exists('abj404_current_user_is_plugin_admin')) {
165 /**
166 * Root-file policy gate for admin callbacks declared before normal classes run.
167 *
168 * Degraded boot screens still use direct WordPress capabilities because the
169 * policy class can be among the missing files. Runtime admin callbacks should
170 * call this helper so delegated plugin admins follow the same authorization
171 * decision everywhere.
172 *
173 * @return bool
174 */
175 function abj404_current_user_is_plugin_admin(): bool {
176 try {
177 if (class_exists('ABJ_404_Solution_PluginAdminAccessPolicy')) {
178 return ABJ_404_Solution_PluginAdminAccessPolicy::currentUserCanAccessPluginAdmin();
179 }
180 abj404_logRuntimeWarning(
181 'plugin admin access policy resolution failed because PluginAdminAccessPolicy is unavailable.'
182 );
183 } catch (\Throwable $e) {
184 abj404_logRuntimeWarning('plugin admin access policy resolution failed', $e);
185 }
186
187 return false;
188 }
189 }
190