Make WordPress Core


Ignore:
Timestamp:
03/21/2019 09:52:07 PM (5 years ago)
Author:
flixos90
Message:

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.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-admin/includes/theme.php

    r44717 r44973  
    769769    <?php
    770770}
     771
     772/**
     773 * Determines whether a theme is technically active but was paused while
     774 * loading.
     775 *
     776 * For more information on this and similar theme functions, check out
     777 * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
     778 * Conditional Tags} article in the Theme Developer Handbook.
     779 *
     780 * @since 5.2.0
     781 *
     782 * @param string $theme Path to the theme directory relative to the themes directory.
     783 * @return bool True, if in the list of paused themes. False, not in the list.
     784 */
     785function is_theme_paused( $theme ) {
     786    if ( ! isset( $GLOBALS['_paused_themes'] ) ) {
     787        return false;
     788    }
     789
     790    if ( get_stylesheet() !== $theme && get_template() !== $theme ) {
     791        return false;
     792    }
     793
     794    return array_key_exists( $theme, $GLOBALS['_paused_themes'] );
     795}
     796
     797/**
     798 * Gets the error that was recorded for a paused theme.
     799 *
     800 * @since 5.2.0
     801 *
     802 * @param string $theme Path to the theme directory relative to the themes
     803 *                      directory.
     804 * @return array|false Array of error information as it was returned by
     805 *                     `error_get_last()`, or false if none was recorded.
     806 */
     807function wp_get_theme_error( $theme ) {
     808    if ( ! isset( $GLOBALS['_paused_themes'] ) ) {
     809        return false;
     810    }
     811
     812    if ( ! array_key_exists( $theme, $GLOBALS['_paused_themes'] ) ) {
     813        return false;
     814    }
     815
     816    return $GLOBALS['_paused_themes'][ $theme ];
     817}
     818
     819/**
     820 * Tries to resume a single theme.
     821 *
     822 * If a redirect was provided and a functions.php file was found, we first ensure that
     823 * functions.php file does not throw fatal errors anymore.
     824 *
     825 * The way it works is by setting the redirection to the error before trying to
     826 * include the file. If the theme fails, then the redirection will not be overwritten
     827 * with the success message and the theme will not be resumed.
     828 *
     829 * @since 5.2.0
     830 *
     831 * @param string $theme    Single theme to resume.
     832 * @param string $redirect Optional. URL to redirect to. Default empty string.
     833 * @return bool|WP_Error True on success, false if `$theme` was not paused,
     834 *                       `WP_Error` on failure.
     835 */
     836function resume_theme( $theme, $redirect = '' ) {
     837    list( $extension ) = explode( '/', $theme );
     838
     839    /*
     840     * We'll override this later if the theme could be resumed without
     841     * creating a fatal error.
     842     */
     843    if ( ! empty( $redirect ) ) {
     844        $functions_path = '';
     845        if ( strpos( STYLESHEETPATH, $extension ) ) {
     846            $functions_path = STYLESHEETPATH . '/functions.php';
     847        } elseif ( strpos( TEMPLATEPATH, $extension ) ) {
     848            $functions_path = TEMPLATEPATH . '/functions.php';
     849        }
     850
     851        if ( ! empty( $functions_path ) ) {
     852            wp_redirect(
     853                add_query_arg(
     854                    '_error_nonce',
     855                    wp_create_nonce( 'theme-resume-error_' . $theme ),
     856                    $redirect
     857                )
     858            );
     859
     860            // Load the theme's functions.php to test whether it throws a fatal error.
     861            ob_start();
     862            include $functions_path;
     863            ob_clean();
     864        }
     865    }
     866
     867    $result = wp_paused_themes()->delete( $extension );
     868
     869    if ( ! $result ) {
     870        return new WP_Error(
     871            'could_not_resume_theme',
     872            __( 'Could not resume the theme.' )
     873        );
     874    }
     875
     876    return true;
     877}
     878
     879/**
     880 * Renders an admin notice in case some themes have been paused due to errors.
     881 *
     882 * @since 5.2.0
     883 */
     884function paused_themes_notice() {
     885    if ( 'themes.php' === $GLOBALS['pagenow'] ) {
     886        return;
     887    }
     888
     889    if ( ! current_user_can( 'resume_themes' ) ) {
     890        return;
     891    }
     892
     893    if ( ! isset( $GLOBALS['_paused_themes'] ) || empty( $GLOBALS['_paused_themes'] ) ) {
     894        return;
     895    }
     896
     897    printf(
     898        '<div class="notice notice-error"><p><strong>%s</strong><br>%s</p><p><a href="%s">%s</a></p></div>',
     899        __( 'One or more themes failed to load properly.' ),
     900        __( 'You can find more details and make changes on the Themes screen.' ),
     901        esc_url( admin_url( 'themes.php' ) ),
     902        __( 'Go to the Themes screen' )
     903    );
     904}
Note: See TracChangeset for help on using the changeset viewer.