Make WordPress Core


Ignore:
Timestamp:
03/21/2019 09:52:07 PM (8 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.