9
|
1 |
<?php |
|
2 |
/** |
|
3 |
* Error Protection API: WP_Fatal_Error_Handler class |
|
4 |
* |
|
5 |
* @package WordPress |
16
|
6 |
* @since 5.2.0 |
9
|
7 |
*/ |
|
8 |
|
|
9 |
/** |
|
10 |
* Core class used as the default shutdown handler for fatal errors. |
|
11 |
* |
|
12 |
* A drop-in 'fatal-error-handler.php' can be used to override the instance of this class and use a custom |
|
13 |
* implementation for the fatal error handler that WordPress registers. The custom class should extend this class and |
|
14 |
* can override its methods individually as necessary. The file must return the instance of the class that should be |
|
15 |
* registered. |
|
16 |
* |
|
17 |
* @since 5.2.0 |
|
18 |
*/ |
|
19 |
class WP_Fatal_Error_Handler { |
|
20 |
|
|
21 |
/** |
|
22 |
* Runs the shutdown handler. |
|
23 |
* |
|
24 |
* This method is registered via `register_shutdown_function()`. |
|
25 |
* |
|
26 |
* @since 5.2.0 |
|
27 |
*/ |
|
28 |
public function handle() { |
|
29 |
if ( defined( 'WP_SANDBOX_SCRAPING' ) && WP_SANDBOX_SCRAPING ) { |
|
30 |
return; |
|
31 |
} |
|
32 |
|
16
|
33 |
// Do not trigger the fatal error handler while updates are being installed. |
|
34 |
if ( wp_is_maintenance_mode() ) { |
|
35 |
return; |
|
36 |
} |
|
37 |
|
9
|
38 |
try { |
|
39 |
// Bail if no error found. |
|
40 |
$error = $this->detect_error(); |
|
41 |
if ( ! $error ) { |
|
42 |
return; |
|
43 |
} |
|
44 |
|
|
45 |
if ( ! isset( $GLOBALS['wp_locale'] ) && function_exists( 'load_default_textdomain' ) ) { |
|
46 |
load_default_textdomain(); |
|
47 |
} |
|
48 |
|
16
|
49 |
$handled = false; |
|
50 |
|
9
|
51 |
if ( ! is_multisite() && wp_recovery_mode()->is_initialized() ) { |
16
|
52 |
$handled = wp_recovery_mode()->handle_error( $error ); |
9
|
53 |
} |
|
54 |
|
|
55 |
// Display the PHP error template if headers not sent. |
|
56 |
if ( is_admin() || ! headers_sent() ) { |
16
|
57 |
$this->display_error_template( $error, $handled ); |
9
|
58 |
} |
|
59 |
} catch ( Exception $e ) { |
|
60 |
// Catch exceptions and remain silent. |
|
61 |
} |
|
62 |
} |
|
63 |
|
|
64 |
/** |
|
65 |
* Detects the error causing the crash if it should be handled. |
|
66 |
* |
|
67 |
* @since 5.2.0 |
|
68 |
* |
|
69 |
* @return array|null Error that was triggered, or null if no error received or if the error should not be handled. |
|
70 |
*/ |
|
71 |
protected function detect_error() { |
|
72 |
$error = error_get_last(); |
|
73 |
|
|
74 |
// No error, just skip the error handling code. |
|
75 |
if ( null === $error ) { |
|
76 |
return null; |
|
77 |
} |
|
78 |
|
|
79 |
// Bail if this error should not be handled. |
|
80 |
if ( ! $this->should_handle_error( $error ) ) { |
|
81 |
return null; |
|
82 |
} |
|
83 |
|
|
84 |
return $error; |
|
85 |
} |
|
86 |
|
|
87 |
/** |
|
88 |
* Determines whether we are dealing with an error that WordPress should handle |
|
89 |
* in order to protect the admin backend against WSODs. |
|
90 |
* |
|
91 |
* @since 5.2.0 |
|
92 |
* |
|
93 |
* @param array $error Error information retrieved from error_get_last(). |
|
94 |
* @return bool Whether WordPress should handle this error. |
|
95 |
*/ |
|
96 |
protected function should_handle_error( $error ) { |
|
97 |
$error_types_to_handle = array( |
|
98 |
E_ERROR, |
|
99 |
E_PARSE, |
|
100 |
E_USER_ERROR, |
|
101 |
E_COMPILE_ERROR, |
|
102 |
E_RECOVERABLE_ERROR, |
|
103 |
); |
|
104 |
|
|
105 |
if ( isset( $error['type'] ) && in_array( $error['type'], $error_types_to_handle, true ) ) { |
|
106 |
return true; |
|
107 |
} |
|
108 |
|
|
109 |
/** |
|
110 |
* Filters whether a given thrown error should be handled by the fatal error handler. |
|
111 |
* |
|
112 |
* This filter is only fired if the error is not already configured to be handled by WordPress core. As such, |
|
113 |
* it exclusively allows adding further rules for which errors should be handled, but not removing existing |
|
114 |
* ones. |
|
115 |
* |
|
116 |
* @since 5.2.0 |
|
117 |
* |
|
118 |
* @param bool $should_handle_error Whether the error should be handled by the fatal error handler. |
|
119 |
* @param array $error Error information retrieved from error_get_last(). |
|
120 |
*/ |
|
121 |
return (bool) apply_filters( 'wp_should_handle_php_error', false, $error ); |
|
122 |
} |
|
123 |
|
|
124 |
/** |
|
125 |
* Displays the PHP error template and sends the HTTP status code, typically 500. |
|
126 |
* |
|
127 |
* A drop-in 'php-error.php' can be used as a custom template. This drop-in should control the HTTP status code and |
|
128 |
* print the HTML markup indicating that a PHP error occurred. Note that this drop-in may potentially be executed |
|
129 |
* very early in the WordPress bootstrap process, so any core functions used that are not part of |
|
130 |
* `wp-includes/load.php` should be checked for before being called. |
|
131 |
* |
|
132 |
* If no such drop-in is available, this will call {@see WP_Fatal_Error_Handler::display_default_error_template()}. |
|
133 |
* |
|
134 |
* @since 5.2.0 |
16
|
135 |
* @since 5.3.0 The `$handled` parameter was added. |
9
|
136 |
* |
16
|
137 |
* @param array $error Error information retrieved from `error_get_last()`. |
|
138 |
* @param true|WP_Error $handled Whether Recovery Mode handled the fatal error. |
9
|
139 |
*/ |
16
|
140 |
protected function display_error_template( $error, $handled ) { |
9
|
141 |
if ( defined( 'WP_CONTENT_DIR' ) ) { |
|
142 |
// Load custom PHP error template, if present. |
|
143 |
$php_error_pluggable = WP_CONTENT_DIR . '/php-error.php'; |
|
144 |
if ( is_readable( $php_error_pluggable ) ) { |
|
145 |
require_once $php_error_pluggable; |
|
146 |
|
|
147 |
return; |
|
148 |
} |
|
149 |
} |
|
150 |
|
|
151 |
// Otherwise, display the default error template. |
16
|
152 |
$this->display_default_error_template( $error, $handled ); |
9
|
153 |
} |
|
154 |
|
|
155 |
/** |
|
156 |
* Displays the default PHP error template. |
|
157 |
* |
|
158 |
* This method is called conditionally if no 'php-error.php' drop-in is available. |
|
159 |
* |
|
160 |
* It calls {@see wp_die()} with a message indicating that the site is experiencing technical difficulties and a |
|
161 |
* login link to the admin backend. The {@see 'wp_php_error_message'} and {@see 'wp_php_error_args'} filters can |
|
162 |
* be used to modify these parameters. |
|
163 |
* |
|
164 |
* @since 5.2.0 |
16
|
165 |
* @since 5.3.0 The `$handled` parameter was added. |
9
|
166 |
* |
16
|
167 |
* @param array $error Error information retrieved from `error_get_last()`. |
|
168 |
* @param true|WP_Error $handled Whether Recovery Mode handled the fatal error. |
9
|
169 |
*/ |
16
|
170 |
protected function display_default_error_template( $error, $handled ) { |
9
|
171 |
if ( ! function_exists( '__' ) ) { |
|
172 |
wp_load_translations_early(); |
|
173 |
} |
|
174 |
|
|
175 |
if ( ! function_exists( 'wp_die' ) ) { |
|
176 |
require_once ABSPATH . WPINC . '/functions.php'; |
|
177 |
} |
|
178 |
|
|
179 |
if ( ! class_exists( 'WP_Error' ) ) { |
|
180 |
require_once ABSPATH . WPINC . '/class-wp-error.php'; |
|
181 |
} |
|
182 |
|
16
|
183 |
if ( true === $handled && wp_is_recovery_mode() ) { |
18
|
184 |
$message = __( 'There has been a critical error on this website, putting it in recovery mode. Please check the Themes and Plugins screens for more details. If you just installed or updated a theme or plugin, check the relevant page for that first.' ); |
|
185 |
} elseif ( is_protected_endpoint() && wp_recovery_mode()->is_initialized() ) { |
|
186 |
$message = __( 'There has been a critical error on this website. Please check your site admin email inbox for instructions.' ); |
9
|
187 |
} else { |
18
|
188 |
$message = __( 'There has been a critical error on this website.' ); |
9
|
189 |
} |
|
190 |
|
16
|
191 |
$message = sprintf( |
|
192 |
'<p>%s</p><p><a href="%s">%s</a></p>', |
|
193 |
$message, |
18
|
194 |
/* translators: Documentation about troubleshooting. */ |
|
195 |
__( 'https://wordpress.org/support/article/faq-troubleshooting/' ), |
|
196 |
__( 'Learn more about troubleshooting WordPress.' ) |
16
|
197 |
); |
|
198 |
|
9
|
199 |
$args = array( |
|
200 |
'response' => 500, |
|
201 |
'exit' => false, |
|
202 |
); |
|
203 |
|
|
204 |
/** |
|
205 |
* Filters the message that the default PHP error template displays. |
|
206 |
* |
|
207 |
* @since 5.2.0 |
|
208 |
* |
|
209 |
* @param string $message HTML error message to display. |
|
210 |
* @param array $error Error information retrieved from `error_get_last()`. |
|
211 |
*/ |
|
212 |
$message = apply_filters( 'wp_php_error_message', $message, $error ); |
|
213 |
|
|
214 |
/** |
|
215 |
* Filters the arguments passed to {@see wp_die()} for the default PHP error template. |
|
216 |
* |
|
217 |
* @since 5.2.0 |
|
218 |
* |
|
219 |
* @param array $args Associative array of arguments passed to `wp_die()`. By default these contain a |
|
220 |
* 'response' key, and optionally 'link_url' and 'link_text' keys. |
|
221 |
* @param array $error Error information retrieved from `error_get_last()`. |
|
222 |
*/ |
|
223 |
$args = apply_filters( 'wp_php_error_args', $args, $error ); |
|
224 |
|
|
225 |
$wp_error = new WP_Error( |
|
226 |
'internal_server_error', |
|
227 |
$message, |
|
228 |
array( |
|
229 |
'error' => $error, |
|
230 |
) |
|
231 |
); |
|
232 |
|
|
233 |
wp_die( $wp_error, '', $args ); |
|
234 |
} |
|
235 |
} |