|
1 <?php |
|
2 /** |
|
3 * Error Protection API: WP_Fatal_Error_Handler class |
|
4 * |
|
5 * @package WordPress |
|
6 * @since 5.2.0 |
|
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 |
|
33 try { |
|
34 // Bail if no error found. |
|
35 $error = $this->detect_error(); |
|
36 if ( ! $error ) { |
|
37 return; |
|
38 } |
|
39 |
|
40 if ( ! isset( $GLOBALS['wp_locale'] ) && function_exists( 'load_default_textdomain' ) ) { |
|
41 load_default_textdomain(); |
|
42 } |
|
43 |
|
44 if ( ! is_multisite() && wp_recovery_mode()->is_initialized() ) { |
|
45 wp_recovery_mode()->handle_error( $error ); |
|
46 } |
|
47 |
|
48 // Display the PHP error template if headers not sent. |
|
49 if ( is_admin() || ! headers_sent() ) { |
|
50 $this->display_error_template( $error ); |
|
51 } |
|
52 } catch ( Exception $e ) { |
|
53 // Catch exceptions and remain silent. |
|
54 } |
|
55 } |
|
56 |
|
57 /** |
|
58 * Detects the error causing the crash if it should be handled. |
|
59 * |
|
60 * @since 5.2.0 |
|
61 * |
|
62 * @return array|null Error that was triggered, or null if no error received or if the error should not be handled. |
|
63 */ |
|
64 protected function detect_error() { |
|
65 $error = error_get_last(); |
|
66 |
|
67 // No error, just skip the error handling code. |
|
68 if ( null === $error ) { |
|
69 return null; |
|
70 } |
|
71 |
|
72 // Bail if this error should not be handled. |
|
73 if ( ! $this->should_handle_error( $error ) ) { |
|
74 return null; |
|
75 } |
|
76 |
|
77 return $error; |
|
78 } |
|
79 |
|
80 /** |
|
81 * Determines whether we are dealing with an error that WordPress should handle |
|
82 * in order to protect the admin backend against WSODs. |
|
83 * |
|
84 * @since 5.2.0 |
|
85 * |
|
86 * @param array $error Error information retrieved from error_get_last(). |
|
87 * @return bool Whether WordPress should handle this error. |
|
88 */ |
|
89 protected function should_handle_error( $error ) { |
|
90 $error_types_to_handle = array( |
|
91 E_ERROR, |
|
92 E_PARSE, |
|
93 E_USER_ERROR, |
|
94 E_COMPILE_ERROR, |
|
95 E_RECOVERABLE_ERROR, |
|
96 ); |
|
97 |
|
98 if ( isset( $error['type'] ) && in_array( $error['type'], $error_types_to_handle, true ) ) { |
|
99 return true; |
|
100 } |
|
101 |
|
102 /** |
|
103 * Filters whether a given thrown error should be handled by the fatal error handler. |
|
104 * |
|
105 * This filter is only fired if the error is not already configured to be handled by WordPress core. As such, |
|
106 * it exclusively allows adding further rules for which errors should be handled, but not removing existing |
|
107 * ones. |
|
108 * |
|
109 * @since 5.2.0 |
|
110 * |
|
111 * @param bool $should_handle_error Whether the error should be handled by the fatal error handler. |
|
112 * @param array $error Error information retrieved from error_get_last(). |
|
113 */ |
|
114 return (bool) apply_filters( 'wp_should_handle_php_error', false, $error ); |
|
115 } |
|
116 |
|
117 /** |
|
118 * Displays the PHP error template and sends the HTTP status code, typically 500. |
|
119 * |
|
120 * A drop-in 'php-error.php' can be used as a custom template. This drop-in should control the HTTP status code and |
|
121 * print the HTML markup indicating that a PHP error occurred. Note that this drop-in may potentially be executed |
|
122 * very early in the WordPress bootstrap process, so any core functions used that are not part of |
|
123 * `wp-includes/load.php` should be checked for before being called. |
|
124 * |
|
125 * If no such drop-in is available, this will call {@see WP_Fatal_Error_Handler::display_default_error_template()}. |
|
126 * |
|
127 * @since 5.2.0 |
|
128 * |
|
129 * @param array $error Error information retrieved from `error_get_last()`. |
|
130 */ |
|
131 protected function display_error_template( $error ) { |
|
132 if ( defined( 'WP_CONTENT_DIR' ) ) { |
|
133 // Load custom PHP error template, if present. |
|
134 $php_error_pluggable = WP_CONTENT_DIR . '/php-error.php'; |
|
135 if ( is_readable( $php_error_pluggable ) ) { |
|
136 require_once $php_error_pluggable; |
|
137 |
|
138 return; |
|
139 } |
|
140 } |
|
141 |
|
142 // Otherwise, display the default error template. |
|
143 $this->display_default_error_template( $error ); |
|
144 } |
|
145 |
|
146 /** |
|
147 * Displays the default PHP error template. |
|
148 * |
|
149 * This method is called conditionally if no 'php-error.php' drop-in is available. |
|
150 * |
|
151 * It calls {@see wp_die()} with a message indicating that the site is experiencing technical difficulties and a |
|
152 * login link to the admin backend. The {@see 'wp_php_error_message'} and {@see 'wp_php_error_args'} filters can |
|
153 * be used to modify these parameters. |
|
154 * |
|
155 * @since 5.2.0 |
|
156 * |
|
157 * @param array $error Error information retrieved from `error_get_last()`. |
|
158 */ |
|
159 protected function display_default_error_template( $error ) { |
|
160 if ( ! function_exists( '__' ) ) { |
|
161 wp_load_translations_early(); |
|
162 } |
|
163 |
|
164 if ( ! function_exists( 'wp_die' ) ) { |
|
165 require_once ABSPATH . WPINC . '/functions.php'; |
|
166 } |
|
167 |
|
168 if ( ! class_exists( 'WP_Error' ) ) { |
|
169 require_once ABSPATH . WPINC . '/class-wp-error.php'; |
|
170 } |
|
171 |
|
172 if ( is_protected_endpoint() ) { |
|
173 $message = __( 'The site is experiencing technical difficulties. Please check your site admin email inbox for instructions.' ); |
|
174 } else { |
|
175 $message = __( 'The site is experiencing technical difficulties.' ); |
|
176 } |
|
177 |
|
178 $args = array( |
|
179 'response' => 500, |
|
180 'exit' => false, |
|
181 ); |
|
182 |
|
183 /** |
|
184 * Filters the message that the default PHP error template displays. |
|
185 * |
|
186 * @since 5.2.0 |
|
187 * |
|
188 * @param string $message HTML error message to display. |
|
189 * @param array $error Error information retrieved from `error_get_last()`. |
|
190 */ |
|
191 $message = apply_filters( 'wp_php_error_message', $message, $error ); |
|
192 |
|
193 /** |
|
194 * Filters the arguments passed to {@see wp_die()} for the default PHP error template. |
|
195 * |
|
196 * @since 5.2.0 |
|
197 * |
|
198 * @param array $args Associative array of arguments passed to `wp_die()`. By default these contain a |
|
199 * 'response' key, and optionally 'link_url' and 'link_text' keys. |
|
200 * @param array $error Error information retrieved from `error_get_last()`. |
|
201 */ |
|
202 $args = apply_filters( 'wp_php_error_args', $args, $error ); |
|
203 |
|
204 $wp_error = new WP_Error( |
|
205 'internal_server_error', |
|
206 $message, |
|
207 array( |
|
208 'error' => $error, |
|
209 ) |
|
210 ); |
|
211 |
|
212 wp_die( $wp_error, '', $args ); |
|
213 } |
|
214 } |