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 / services / FatalErrorProcessor.php

FatalErrorProcessor.php in 404 Solution 4.3.0, at includes/services/FatalErrorProcessor.php

175 lines 6.5 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 * Orchestrates shutdown-time fatal-error handling.
9 */
10 class ABJ_404_Solution_FatalErrorProcessor {
11
12 /** @var ABJ_404_Solution_ErrorTypeClassifier */
13 private $classifier;
14
15 /** @var ABJ_404_Solution_ErrorDiagnosticsReporter */
16 private $diagnostics;
17
18 /** @var ABJ_404_Solution_AdminFatalErrorResponder */
19 private $adminResponder;
20
21 /** @var ABJ_404_Solution_AjaxFatalErrorResponder */
22 private $ajaxResponder;
23
24 /**
25 * @param ABJ_404_Solution_ErrorTypeClassifier|null $classifier
26 * @param ABJ_404_Solution_ErrorDiagnosticsReporter|null $diagnostics
27 * @param ABJ_404_Solution_AdminFatalErrorResponder|null $adminResponder
28 * @param ABJ_404_Solution_AjaxFatalErrorResponder|null $ajaxResponder
29 */
30 public function __construct($classifier = null, $diagnostics = null, $adminResponder = null, $ajaxResponder = null) {
31 $this->classifier = $classifier !== null ? $classifier : new ABJ_404_Solution_ErrorTypeClassifier();
32 $this->diagnostics = $diagnostics !== null ? $diagnostics : new ABJ_404_Solution_ErrorDiagnosticsReporter();
33 $this->adminResponder = $adminResponder !== null ? $adminResponder : new ABJ_404_Solution_AdminFatalErrorResponder();
34 $this->ajaxResponder = $ajaxResponder !== null ? $ajaxResponder : new ABJ_404_Solution_AjaxFatalErrorResponder($this->diagnostics);
35 }
36
37 /**
38 * @param array<string,mixed>|null $lasterror
39 * @return bool
40 */
41 public function process($lasterror): bool {
42 if (!$this->isProcessableFatal($lasterror) || !is_array($lasterror)) {
43 return false;
44 }
45
46 $lasterror = $this->truncateLargeMessage($lasterror);
47 $ctx = $this->currentAjaxContext();
48
49 $isPluginAdminPage = $this->adminResponder->isPluginAdminPageRequest();
50 if ($isPluginAdminPage) {
51 ABJ_404_Solution_ErrorHandler::releaseReservedMemory();
52 $this->adminResponder->stashAdminFatal($lasterror);
53 }
54
55 if ($this->ajaxResponder->isAjaxContext($ctx)) {
56 return $this->ajaxResponder->process($lasterror, is_array($ctx) ? $ctx : array());
57 }
58
59 $this->logDefaultFatal($lasterror, $isPluginAdminPage);
60
61 if ($isPluginAdminPage) {
62 $this->adminResponder->renderAdminFatalFallback($lasterror);
63 }
64
65 return false;
66 }
67
68 /**
69 * @param mixed $lasterror
70 * @return bool
71 */
72 private function isProcessableFatal($lasterror): bool {
73 if ($lasterror == null || !is_array($lasterror) || !array_key_exists('type', $lasterror) ||
74 !array_key_exists('file', $lasterror)) {
75 return false;
76 }
77
78 $errorType = $lasterror['type'];
79 return $this->classifier->isFatalType(is_int($errorType) ? $errorType : (is_scalar($errorType) ? (int)$errorType : 0));
80 }
81
82 /**
83 * @return array<string,mixed>|null
84 */
85 private function currentAjaxContext() {
86 if (!isset($GLOBALS['abj404_ajax_context']) || !is_array($GLOBALS['abj404_ajax_context'])) {
87 return null;
88 }
89
90 $context = array();
91 foreach ($GLOBALS['abj404_ajax_context'] as $key => $value) {
92 if (is_string($key)) {
93 $context[$key] = $value;
94 }
95 }
96 return $context;
97 }
98
99 /**
100 * @param array<string,mixed> $lasterror
101 * @return void
102 */
103 private function logDefaultFatal(array $lasterror, bool $isPluginAdminPage): void {
104 try {
105 $errno = $lasterror['type'];
106 $errfile = is_string($lasterror['file']) ? $lasterror['file'] : '';
107 $pluginFolder = $this->pluginFolder();
108 $isPluginScopeFatal = (strpos($errfile, $pluginFolder) !== false);
109
110 if (!$isPluginScopeFatal && !$isPluginAdminPage) {
111 return;
112 }
113
114 $extraInfo = "(none)";
115 $ctxDebugInfo = abj_service('request_context')->debug_info;
116 if ($ctxDebugInfo !== '') {
117 $extraInfo = stripcslashes(wp_kses_post((string)json_encode($ctxDebugInfo)));
118 }
119 $contextPrefix = $isPluginScopeFatal
120 ? 'ABJ404-SOLUTION Fatal error handler: '
121 : 'ABJ404-SOLUTION Fatal error handler (plugin admin page, foreign scope): ';
122
123 $errmsg = $contextPrefix .
124 stripcslashes(wp_kses_post((string)json_encode($lasterror))) .
125 ", \nAdditional info: " . $extraInfo . ", mbstring: " .
126 (extension_loaded('mbstring') ? 'true' : 'false');
127
128 $abj404logging = abj_service('logging');
129 if ($abj404logging != null) {
130 switch ($errno) {
131 case E_NOTICE:
132 $serverName = array_key_exists('SERVER_NAME', $_SERVER) ? $_SERVER['SERVER_NAME'] : (array_key_exists('HTTP_HOST', $_SERVER) ? $_SERVER['HTTP_HOST'] : '(not found)');
133 $whitelist = isset($GLOBALS['abj404_whitelist']) && is_array($GLOBALS['abj404_whitelist'])
134 ? $GLOBALS['abj404_whitelist'] : array();
135 if (in_array($serverName, $whitelist, true)) {
136 $abj404logging->debugMessage($errmsg);
137 }
138 break;
139
140 default:
141 $abj404logging->errorMessage($errmsg);
142 break;
143 }
144 } else {
145 echo $errmsg;
146 }
147 } catch (Throwable $ex) {
148 abj404_logPhpFallback(
149 'fatal-handler-fallback',
150 'error handler itself failed (code ' . $ex->getCode() . '): ' . $ex->getMessage()
151 );
152 }
153 }
154
155 /**
156 * @param array<string,mixed> $lasterror
157 * @return array<string,mixed>
158 */
159 private function truncateLargeMessage(array $lasterror): array {
160 if (isset($lasterror['message']) && is_string($lasterror['message'])
161 && strlen($lasterror['message']) > 8192) {
162 $lasterror['message'] = substr($lasterror['message'], 0, 8192)
163 . '... (truncated; original length ' . strlen($lasterror['message']) . ' bytes)';
164 }
165 return $lasterror;
166 }
167
168 /** @return string */
169 private function pluginFolder(): string {
170 $slashPos = strpos(ABJ404_NAME, '/');
171 $pluginFolder = substr(ABJ404_NAME, 0, ($slashPos !== false ? $slashPos : strlen(ABJ404_NAME)));
172 return is_string($pluginFolder) ? $pluginFolder : (string)ABJ404_NAME;
173 }
174 }
175