Make WordPress Core


Ignore:
Timestamp:
01/09/2019 08:04:55 PM (8 years ago)
Author:
flixos90
Message:

Bootstrap/Load: Introduce fatal error recovery mechanism allowing users to still log in to their admin dashboard.

This changeset introduces a WP_Shutdown_Handler class that detects fatal errors and which extension (plugin or theme) causes them. Such an error is then recorded, and an error message is displayed. Subsequently, in certain protected areas, for example the admin, the broken extension will be paused, ensuring that the website is still usable in the respective area. The major benefit is that this mechanism allows site owners to still log in to their website, to fix the problem by either disabling the extension or solving the bug and then resuming the extension.

Extensions are only paused in certain designated areas. The frontend for example stays unaffected, as it is impossible to know what pausing the extension would cause to be missing, so it might be preferrable to clearly see that the website is temporarily not accessible instead.

The fatal error recovery is especially important in scope of encouraging the switch to a maintained PHP version, as not necessarily every WordPress extension is compatible with all PHP versions. If problems occur now, non-technical site owners that do not have immediate access to the codebase are not locked out of their site and can at least temporarily solve the problem quickly.

Websites that have custom requirements in that regard can implement their own shutdown handler by adding a shutdown-handler.php drop-in that returns the handler instance to use, which must be based on a class that inherits WP_Shutdown_Handler. That handler will then be used in place of the default one.

Websites that would like to modify specifically the error template displayed in the frontend can add a php-error.php drop-in that works similarly to the existing db-error.php drop-in.

Props afragen, bradleyt, flixos90, ocean90, schlessera, SergeyBiryukov, spacedmonkey.
Fixes #44458.

File:
1 edited

Legend:

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

    r43571 r44524  
    764764        <?php
    765765}
     766
     767/**
     768 * Determines whether a theme is technically active but was paused while
     769 * loading.
     770 *
     771 * For more information on this and similar theme functions, check out
     772 * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
     773 * Conditional Tags} article in the Theme Developer Handbook.
     774 *
     775 * @since 5.1.0
     776 *
     777 * @param string $theme Path to the theme directory relative to the themes directory.
     778 * @return bool True, if in the list of paused themes. False, not in the list.
     779 */
     780function is_theme_paused( $theme ) {
     781        if ( ! isset( $GLOBALS['_paused_themes'] ) ) {
     782                return false;
     783        }
     784
     785        if ( $theme !== get_stylesheet() && $theme !== get_template() ) {
     786                return false;
     787        }
     788
     789        return array_key_exists( $theme, $GLOBALS['_paused_themes'] );
     790}
     791
     792/**
     793 * Gets the error that was recorded for a paused theme.
     794 *
     795 * @since 5.1.0
     796 *
     797 * @param string $theme Path to the theme directory relative to the themes
     798 *                      directory.
     799 * @return array|false Array of error information as it was returned by
     800 *                     `error_get_last()`, or false if none was recorded.
     801 */
     802function wp_get_theme_error( $theme ) {
     803        if ( ! isset( $GLOBALS['_paused_themes'] ) ) {
     804                return false;
     805        }
     806
     807        if ( ! array_key_exists( $theme, $GLOBALS['_paused_themes'] ) ) {
     808                return false;
     809        }
     810
     811        return $GLOBALS['_paused_themes'][ $theme ];
     812}
     813
     814/**
     815 * Gets the number of sites on which a specific theme is paused.
     816 *
     817 * @since 5.1.0
     818 *
     819 * @param string $theme Path to the theme directory relative to the themes directory.
     820 * @return int Site count.
     821 */
     822function count_paused_theme_sites_for_network( $theme ) {
     823        if ( ! is_multisite() ) {
     824                return is_theme_paused( $theme ) ? 1 : 0;
     825        }
     826
     827        $query_args = array(
     828                'count'      => true,
     829                'number'     => 0,
     830                'network_id' => get_current_network_id(),
     831                'meta_query' => array(
     832                        wp_paused_themes()->get_site_meta_query_clause( $theme ),
     833                ),
     834        );
     835
     836        return get_sites( $query_args );
     837}
     838
     839/**
     840 * Tries to resume a single theme.
     841 *
     842 * @since 5.1.0
     843 *
     844 * @param string $theme Single theme to resume.
     845 * @return bool|WP_Error True on success, false if `$theme` was not paused,
     846 *                       `WP_Error` on failure.
     847 */
     848function resume_theme( $theme ) {
     849        $result = wp_forget_extension_error( 'themes', $theme );
     850
     851        if ( ! $result ) {
     852                return new WP_Error(
     853                        'could_not_resume_theme',
     854                        __( 'Could not resume the theme.' )
     855                );
     856        }
     857
     858        return true;
     859}
     860
     861/**
     862 * Renders an admin notice in case some themes have been paused due to errors.
     863 *
     864 * @since 5.1.0
     865 */
     866function paused_themes_notice() {
     867        if ( 'themes.php' === $GLOBALS['pagenow'] ) {
     868                return;
     869        }
     870
     871        if ( ! current_user_can( 'switch_themes' ) ) {
     872                return;
     873        }
     874
     875        if ( ! isset( $GLOBALS['_paused_themes'] ) || empty( $GLOBALS['_paused_themes'] ) ) {
     876                return;
     877        }
     878
     879        printf(
     880                '<div class="notice notice-error"><p><strong>%s</strong><br>%s</p><p>%s</p></div>',
     881                __( 'One or more themes failed to load properly.' ),
     882                __( 'You can find more details and make changes on the Themes screen.' ),
     883                sprintf(
     884                        '<a href="%s">%s</a>',
     885                        admin_url( 'themes.php' ),
     886                        'Go to the Themes screen'
     887                )
     888        );
     889}
Note: See TracChangeset for help on using the changeset viewer.