Bootstrap/Load: Introduce a recovery mode for fixing fatal errors.

Using the new fatal handler introduced in [44962], an email is sent to the admin when a fatal error occurs. This email includes a secret link to enter recovery mode. When clicked, the link will be validated and on success a cookie will be placed on the client, enabling recovery mode for that user. This functionality is executed early before plugins and themes are loaded, in order to be unaffected by potential fatal errors these might be causing.

When in recovery mode, broken plugins and themes will be paused for that client, so that they are able to access the admin backend despite of these errors. They are notified about the broken extensions and the errors caused, and can then decide whether they would like to temporarily deactivate the extension or fix the problem and resume the extension.

A link in the admin bar allows the client to exit recovery mode.

Props timothyblynjacobs, afragen, flixos90, nerrad, miss_jwo, schlessera, spacedmonkey, swissspidy.
Fixes #46130, #44458.


git-svn-id: https://develop.svn.wordpress.org/trunk@44973 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
Felix Arntz
2019-03-21 21:52:07 +00:00
parent 57a38bed47
commit 1f3912e9bf
27 changed files with 2361 additions and 16 deletions
+134
View File
@@ -768,3 +768,137 @@ function customize_themes_print_templates() {
</script>
<?php
}
/**
* Determines whether a theme is technically active but was paused while
* loading.
*
* For more information on this and similar theme functions, check out
* the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
* Conditional Tags} article in the Theme Developer Handbook.
*
* @since 5.2.0
*
* @param string $theme Path to the theme directory relative to the themes directory.
* @return bool True, if in the list of paused themes. False, not in the list.
*/
function is_theme_paused( $theme ) {
if ( ! isset( $GLOBALS['_paused_themes'] ) ) {
return false;
}
if ( get_stylesheet() !== $theme && get_template() !== $theme ) {
return false;
}
return array_key_exists( $theme, $GLOBALS['_paused_themes'] );
}
/**
* Gets the error that was recorded for a paused theme.
*
* @since 5.2.0
*
* @param string $theme Path to the theme directory relative to the themes
* directory.
* @return array|false Array of error information as it was returned by
* `error_get_last()`, or false if none was recorded.
*/
function wp_get_theme_error( $theme ) {
if ( ! isset( $GLOBALS['_paused_themes'] ) ) {
return false;
}
if ( ! array_key_exists( $theme, $GLOBALS['_paused_themes'] ) ) {
return false;
}
return $GLOBALS['_paused_themes'][ $theme ];
}
/**
* Tries to resume a single theme.
*
* If a redirect was provided and a functions.php file was found, we first ensure that
* functions.php file does not throw fatal errors anymore.
*
* The way it works is by setting the redirection to the error before trying to
* include the file. If the theme fails, then the redirection will not be overwritten
* with the success message and the theme will not be resumed.
*
* @since 5.2.0
*
* @param string $theme Single theme to resume.
* @param string $redirect Optional. URL to redirect to. Default empty string.
* @return bool|WP_Error True on success, false if `$theme` was not paused,
* `WP_Error` on failure.
*/
function resume_theme( $theme, $redirect = '' ) {
list( $extension ) = explode( '/', $theme );
/*
* We'll override this later if the theme could be resumed without
* creating a fatal error.
*/
if ( ! empty( $redirect ) ) {
$functions_path = '';
if ( strpos( STYLESHEETPATH, $extension ) ) {
$functions_path = STYLESHEETPATH . '/functions.php';
} elseif ( strpos( TEMPLATEPATH, $extension ) ) {
$functions_path = TEMPLATEPATH . '/functions.php';
}
if ( ! empty( $functions_path ) ) {
wp_redirect(
add_query_arg(
'_error_nonce',
wp_create_nonce( 'theme-resume-error_' . $theme ),
$redirect
)
);
// Load the theme's functions.php to test whether it throws a fatal error.
ob_start();
include $functions_path;
ob_clean();
}
}
$result = wp_paused_themes()->delete( $extension );
if ( ! $result ) {
return new WP_Error(
'could_not_resume_theme',
__( 'Could not resume the theme.' )
);
}
return true;
}
/**
* Renders an admin notice in case some themes have been paused due to errors.
*
* @since 5.2.0
*/
function paused_themes_notice() {
if ( 'themes.php' === $GLOBALS['pagenow'] ) {
return;
}
if ( ! current_user_can( 'resume_themes' ) ) {
return;
}
if ( ! isset( $GLOBALS['_paused_themes'] ) || empty( $GLOBALS['_paused_themes'] ) ) {
return;
}
printf(
'<div class="notice notice-error"><p><strong>%s</strong><br>%s</p><p><a href="%s">%s</a></p></div>',
__( 'One or more themes failed to load properly.' ),
__( 'You can find more details and make changes on the Themes screen.' ),
esc_url( admin_url( 'themes.php' ) ),
__( 'Go to the Themes screen' )
);
}