39 * |
39 * |
40 * @since 5.2.0 |
40 * @since 5.2.0 |
41 * |
41 * |
42 * @param int $rate_limit Number of seconds before another email can be sent. |
42 * @param int $rate_limit Number of seconds before another email can be sent. |
43 * @param array $error Error details from {@see error_get_last()} |
43 * @param array $error Error details from {@see error_get_last()} |
44 * @param array $extension The extension that caused the error. { |
44 * @param array $extension { |
45 * @type string $slug The extension slug. The plugin or theme's directory. |
45 * The extension that caused the error. |
46 * @type string $type The extension type. Either 'plugin' or 'theme'. |
46 * |
|
47 * @type string $slug The extension slug. The plugin or theme's directory. |
|
48 * @type string $type The extension type. Either 'plugin' or 'theme'. |
47 * } |
49 * } |
48 * @return true|WP_Error True if email sent, WP_Error otherwise. |
50 * @return true|WP_Error True if email sent, WP_Error otherwise. |
49 */ |
51 */ |
50 public function maybe_send_recovery_mode_email( $rate_limit, $error, $extension ) { |
52 public function maybe_send_recovery_mode_email( $rate_limit, $error, $extension ) { |
51 |
53 |
60 |
62 |
61 if ( $sent ) { |
63 if ( $sent ) { |
62 return true; |
64 return true; |
63 } |
65 } |
64 |
66 |
65 return new WP_Error( 'email_failed', __( 'The email could not be sent. Possible reason: your host may have disabled the mail() function.' ) ); |
67 return new WP_Error( |
|
68 'email_failed', |
|
69 sprintf( |
|
70 /* translators: %s: mail() */ |
|
71 __( 'The email could not be sent. Possible reason: your host may have disabled the %s function.' ), |
|
72 'mail()' |
|
73 ) |
|
74 ); |
66 } |
75 } |
67 |
76 |
68 $err_message = sprintf( |
77 $err_message = sprintf( |
69 /* translators: 1. Last sent as a human time diff 2. Wait time as a human time diff. */ |
78 /* translators: 1. Last sent as a human time diff, 2. Wait time as a human time diff. */ |
70 __( 'A recovery link was already sent %1$s ago. Please wait another %2$s before requesting a new email.' ), |
79 __( 'A recovery link was already sent %1$s ago. Please wait another %2$s before requesting a new email.' ), |
71 human_time_diff( $last_sent ), |
80 human_time_diff( $last_sent ), |
72 human_time_diff( $last_sent + $rate_limit ) |
81 human_time_diff( $last_sent + $rate_limit ) |
73 ); |
82 ); |
74 |
83 |
92 * @since 5.2.0 |
101 * @since 5.2.0 |
93 * |
102 * |
94 * @param int $rate_limit Number of seconds before another email can be sent. |
103 * @param int $rate_limit Number of seconds before another email can be sent. |
95 * @param array $error Error details from {@see error_get_last()} |
104 * @param array $error Error details from {@see error_get_last()} |
96 * @param array $extension Extension that caused the error. |
105 * @param array $extension Extension that caused the error. |
97 * |
|
98 * @return bool Whether the email was sent successfully. |
106 * @return bool Whether the email was sent successfully. |
99 */ |
107 */ |
100 private function send_recovery_mode_email( $rate_limit, $error, $extension ) { |
108 private function send_recovery_mode_email( $rate_limit, $error, $extension ) { |
101 |
109 |
102 $url = $this->link_service->generate_url(); |
110 $url = $this->link_service->generate_url(); |
125 /** |
133 /** |
126 * Filters the support message sent with the the fatal error protection email. |
134 * Filters the support message sent with the the fatal error protection email. |
127 * |
135 * |
128 * @since 5.2.0 |
136 * @since 5.2.0 |
129 * |
137 * |
130 * @param $message string The Message to include in the email. |
138 * @param string $message The Message to include in the email. |
131 */ |
139 */ |
132 $support = apply_filters( 'recovery_email_support_info', __( 'Please contact your host for assistance with investigating this issue further.' ) ); |
140 $support = apply_filters( 'recovery_email_support_info', __( 'Please contact your host for assistance with investigating this issue further.' ) ); |
133 |
141 |
134 /* translators: Do not translate LINK, EXPIRES, CAUSE, DETAILS, SITEURL, PAGEURL, SUPPORT: those are placeholders. */ |
142 /** |
|
143 * Filters the debug information included in the fatal error protection email. |
|
144 * |
|
145 * @since 5.3.0 |
|
146 * |
|
147 * @param array $message An associative array of debug information. |
|
148 */ |
|
149 $debug = apply_filters( 'recovery_email_debug_info', $this->get_debug( $extension ) ); |
|
150 |
|
151 /* translators: Do not translate LINK, EXPIRES, CAUSE, DETAILS, SITEURL, PAGEURL, SUPPORT. DEBUG: those are placeholders. */ |
135 $message = __( |
152 $message = __( |
136 'Howdy! |
153 'Howdy! |
137 |
154 |
138 Since WordPress 5.2 there is a built-in feature that detects when a plugin or theme causes a fatal error on your site, and notifies you with this automated email. |
155 Since WordPress 5.2 there is a built-in feature that detects when a plugin or theme causes a fatal error on your site, and notifies you with this automated email. |
139 ###CAUSE### |
156 ###CAUSE### |
156 '###CAUSE###', |
176 '###CAUSE###', |
157 '###DETAILS###', |
177 '###DETAILS###', |
158 '###SITEURL###', |
178 '###SITEURL###', |
159 '###PAGEURL###', |
179 '###PAGEURL###', |
160 '###SUPPORT###', |
180 '###SUPPORT###', |
|
181 '###DEBUG###', |
161 ), |
182 ), |
162 array( |
183 array( |
163 $url, |
184 $url, |
164 human_time_diff( time() + $rate_limit ), |
185 human_time_diff( time() + $rate_limit ), |
165 $cause ? "\n{$cause}\n" : "\n", |
186 $cause ? "\n{$cause}\n" : "\n", |
166 $details, |
187 $details, |
167 home_url( '/' ), |
188 home_url( '/' ), |
168 home_url( $_SERVER['REQUEST_URI'] ), |
189 home_url( $_SERVER['REQUEST_URI'] ), |
169 $support, |
190 $support, |
|
191 implode( "\r\n", $debug ), |
170 ), |
192 ), |
171 $message |
193 $message |
172 ); |
194 ); |
173 |
195 |
174 $email = array( |
196 $email = array( |
175 'to' => $this->get_recovery_mode_email_address(), |
197 'to' => $this->get_recovery_mode_email_address(), |
176 /* translators: %s: site name */ |
198 /* translators: %s: Site title. */ |
177 'subject' => __( '[%s] Your Site is Experiencing a Technical Issue' ), |
199 'subject' => __( '[%s] Your Site is Experiencing a Technical Issue' ), |
178 'message' => $message, |
200 'message' => $message, |
179 'headers' => '', |
201 'headers' => '', |
180 ); |
202 ); |
181 |
203 |
227 * @return string Message about which extension caused the error. |
249 * @return string Message about which extension caused the error. |
228 */ |
250 */ |
229 private function get_cause( $extension ) { |
251 private function get_cause( $extension ) { |
230 |
252 |
231 if ( 'plugin' === $extension['type'] ) { |
253 if ( 'plugin' === $extension['type'] ) { |
232 if ( ! function_exists( 'get_plugins' ) ) { |
254 $plugin = $this->get_plugin( $extension ); |
233 require_once ABSPATH . 'wp-admin/includes/plugin.php'; |
255 |
234 } |
256 if ( false === $plugin ) { |
235 |
257 $name = $extension['slug']; |
236 $plugins = get_plugins(); |
|
237 |
|
238 $name = ''; |
|
239 |
|
240 // Assume plugin main file name first since it is a common convention. |
|
241 if ( isset( $plugins[ "{$extension['slug']}/{$extension['slug']}.php" ] ) ) { |
|
242 $name = $plugins[ "{$extension['slug']}/{$extension['slug']}.php" ]['Name']; |
|
243 } else { |
258 } else { |
244 foreach ( $plugins as $file => $plugin_data ) { |
259 $name = $plugin['Name']; |
245 if ( 0 === strpos( $file, "{$extension['slug']}/" ) || $file === $extension['slug'] ) { |
260 } |
246 $name = $plugin_data['Name']; |
261 |
247 break; |
262 /* translators: %s: Plugin name. */ |
248 } |
|
249 } |
|
250 } |
|
251 |
|
252 if ( empty( $name ) ) { |
|
253 $name = $extension['slug']; |
|
254 } |
|
255 |
|
256 /* translators: %s: plugin name */ |
|
257 $cause = sprintf( __( 'In this case, WordPress caught an error with one of your plugins, %s.' ), $name ); |
263 $cause = sprintf( __( 'In this case, WordPress caught an error with one of your plugins, %s.' ), $name ); |
258 } else { |
264 } else { |
259 $theme = wp_get_theme( $extension['slug'] ); |
265 $theme = wp_get_theme( $extension['slug'] ); |
260 $name = $theme->exists() ? $theme->display( 'Name' ) : $extension['slug']; |
266 $name = $theme->exists() ? $theme->display( 'Name' ) : $extension['slug']; |
261 |
267 |
262 /* translators: %s: theme name */ |
268 /* translators: %s: Theme name. */ |
263 $cause = sprintf( __( 'In this case, WordPress caught an error with your theme, %s.' ), $name ); |
269 $cause = sprintf( __( 'In this case, WordPress caught an error with your theme, %s.' ), $name ); |
264 } |
270 } |
265 |
271 |
266 return $cause; |
272 return $cause; |
267 } |
273 } |
|
274 |
|
275 /** |
|
276 * Return the details for a single plugin based on the extension data from an error. |
|
277 * |
|
278 * @since 5.3.0 |
|
279 * |
|
280 * @param array $extension The extension that caused the error. |
|
281 * @return bool|array A plugin array {@see get_plugins()} or `false` if no plugin was found. |
|
282 */ |
|
283 private function get_plugin( $extension ) { |
|
284 if ( ! function_exists( 'get_plugins' ) ) { |
|
285 require_once ABSPATH . 'wp-admin/includes/plugin.php'; |
|
286 } |
|
287 |
|
288 $plugins = get_plugins(); |
|
289 |
|
290 // Assume plugin main file name first since it is a common convention. |
|
291 if ( isset( $plugins[ "{$extension['slug']}/{$extension['slug']}.php" ] ) ) { |
|
292 return $plugins[ "{$extension['slug']}/{$extension['slug']}.php" ]; |
|
293 } else { |
|
294 foreach ( $plugins as $file => $plugin_data ) { |
|
295 if ( 0 === strpos( $file, "{$extension['slug']}/" ) || $file === $extension['slug'] ) { |
|
296 return $plugin_data; |
|
297 } |
|
298 } |
|
299 } |
|
300 |
|
301 return false; |
|
302 } |
|
303 |
|
304 /** |
|
305 * Return debug information in an easy to manipulate format. |
|
306 * |
|
307 * @since 5.3.0 |
|
308 * |
|
309 * @param array $extension The extension that caused the error. |
|
310 * @return array An associative array of debug information. |
|
311 */ |
|
312 private function get_debug( $extension ) { |
|
313 $theme = wp_get_theme(); |
|
314 $wp_version = get_bloginfo( 'version' ); |
|
315 |
|
316 if ( $extension ) { |
|
317 $plugin = $this->get_plugin( $extension ); |
|
318 } else { |
|
319 $plugin = null; |
|
320 } |
|
321 |
|
322 $debug = array( |
|
323 /* translators: %s: Current WordPress version number. */ |
|
324 'wp' => sprintf( |
|
325 __( 'WordPress version %s' ), |
|
326 $wp_version |
|
327 ), |
|
328 'theme' => sprintf( |
|
329 /* translators: 1: Current active theme name. 2: Current active theme version. */ |
|
330 __( 'Current theme: %1$s (version %2$s)' ), |
|
331 $theme->get( 'Name' ), |
|
332 $theme->get( 'Version' ) |
|
333 ), |
|
334 ); |
|
335 |
|
336 if ( null !== $plugin ) { |
|
337 $debug['plugin'] = sprintf( |
|
338 /* translators: 1: The failing plugins name. 2: The failing plugins version. */ |
|
339 __( 'Current plugin: %1$s (version %2$s)' ), |
|
340 $plugin['Name'], |
|
341 $plugin['Version'] |
|
342 ); |
|
343 } |
|
344 |
|
345 $debug['php'] = sprintf( |
|
346 /* translators: %s: The currently used PHP version. */ |
|
347 __( 'PHP version %s' ), |
|
348 PHP_VERSION |
|
349 ); |
|
350 |
|
351 return $debug; |
|
352 } |
268 } |
353 } |