Make WordPress Core


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

    r44717 r44973  
    469469function _get_dropins() {
    470470        $dropins = array(
    471                 'advanced-cache.php' => array( __( 'Advanced caching plugin.' ), 'WP_CACHE' ), // WP_CACHE
    472                 'db.php'             => array( __( 'Custom database class.' ), true ), // auto on load
    473                 'db-error.php'       => array( __( 'Custom database error message.' ), true ), // auto on error
    474                 'install.php'        => array( __( 'Custom installation script.' ), true ), // auto on installation
    475                 'maintenance.php'    => array( __( 'Custom maintenance message.' ), true ), // auto on maintenance
    476                 'object-cache.php'   => array( __( 'External object cache.' ), true ), // auto on load
     471                'advanced-cache.php'      => array( __( 'Advanced caching plugin.' ), 'WP_CACHE' ), // WP_CACHE
     472                'db.php'                  => array( __( 'Custom database class.' ), true ), // auto on load
     473                'db-error.php'            => array( __( 'Custom database error message.' ), true ), // auto on error
     474                'install.php'             => array( __( 'Custom installation script.' ), true ), // auto on installation
     475                'maintenance.php'         => array( __( 'Custom maintenance message.' ), true ), // auto on maintenance
     476                'object-cache.php'        => array( __( 'External object cache.' ), true ), // auto on load
     477                'php-error.php'           => array( __( 'Custom PHP error message.' ), true ), // auto on error
     478                'fatal-error-handler.php' => array( __( 'Custom PHP fatal error handler.' ), true ), // auto on error
    477479        );
    478480
     
    21022104        WP_Privacy_Policy_Content::add( $plugin_name, $policy_text );
    21032105}
     2106
     2107/**
     2108 * Determines whether a plugin is technically active but was paused while
     2109 * loading.
     2110 *
     2111 * For more information on this and similar theme functions, check out
     2112 * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
     2113 * Conditional Tags} article in the Theme Developer Handbook.
     2114 *
     2115 * @since 5.2.0
     2116 *
     2117 * @param string $plugin Path to the plugin file relative to the plugins directory.
     2118 * @return bool True, if in the list of paused plugins. False, not in the list.
     2119 */
     2120function is_plugin_paused( $plugin ) {
     2121        if ( ! isset( $GLOBALS['_paused_plugins'] ) ) {
     2122                return false;
     2123        }
     2124
     2125        if ( ! is_plugin_active( $plugin ) ) {
     2126                return false;
     2127        }
     2128
     2129        list( $plugin ) = explode( '/', $plugin );
     2130
     2131        return array_key_exists( $plugin, $GLOBALS['_paused_plugins'] );
     2132}
     2133
     2134/**
     2135 * Gets the error that was recorded for a paused plugin.
     2136 *
     2137 * @since 5.2.0
     2138 *
     2139 * @param string $plugin Path to the plugin file relative to the plugins
     2140 *                       directory.
     2141 * @return array|false Array of error information as it was returned by
     2142 *                     `error_get_last()`, or false if none was recorded.
     2143 */
     2144function wp_get_plugin_error( $plugin ) {
     2145        if ( ! isset( $GLOBALS['_paused_plugins'] ) ) {
     2146                return false;
     2147        }
     2148
     2149        list( $plugin ) = explode( '/', $plugin );
     2150
     2151        if ( ! array_key_exists( $plugin, $GLOBALS['_paused_plugins'] ) ) {
     2152                return false;
     2153        }
     2154
     2155        return $GLOBALS['_paused_plugins'][ $plugin ];
     2156}
     2157
     2158/**
     2159 * Tries to resume a single plugin.
     2160 *
     2161 * If a redirect was provided, we first ensure the plugin does not throw fatal
     2162 * errors anymore.
     2163 *
     2164 * The way it works is by setting the redirection to the error before trying to
     2165 * include the plugin file. If the plugin fails, then the redirection will not
     2166 * be overwritten with the success message and the plugin will not be resumed.
     2167 *
     2168 * @since 5.2.0
     2169 *
     2170 * @param string $plugin       Single plugin to resume.
     2171 * @param string $redirect     Optional. URL to redirect to. Default empty string.
     2172 * @return bool|WP_Error True on success, false if `$plugin` was not paused,
     2173 *                       `WP_Error` on failure.
     2174 */
     2175function resume_plugin( $plugin, $redirect = '' ) {
     2176        /*
     2177         * We'll override this later if the plugin could be resumed without
     2178         * creating a fatal error.
     2179         */
     2180        if ( ! empty( $redirect ) ) {
     2181                wp_redirect(
     2182                        add_query_arg(
     2183                                '_error_nonce',
     2184                                wp_create_nonce( 'plugin-resume-error_' . $plugin ),
     2185                                $redirect
     2186                        )
     2187                );
     2188
     2189                // Load the plugin to test whether it throws a fatal error.
     2190                ob_start();
     2191                plugin_sandbox_scrape( $plugin );
     2192                ob_clean();
     2193        }
     2194
     2195        list( $extension ) = explode( '/', $plugin );
     2196
     2197        $result = wp_paused_plugins()->delete( $extension );
     2198
     2199        if ( ! $result ) {
     2200                return new WP_Error(
     2201                        'could_not_resume_plugin',
     2202                        __( 'Could not resume the plugin.' )
     2203                );
     2204        }
     2205
     2206        return true;
     2207}
     2208
     2209/**
     2210 * Renders an admin notice in case some plugins have been paused due to errors.
     2211 *
     2212 * @since 5.2.0
     2213 */
     2214function paused_plugins_notice() {
     2215        if ( 'plugins.php' === $GLOBALS['pagenow'] ) {
     2216                return;
     2217        }
     2218
     2219        if ( ! current_user_can( 'resume_plugins' ) ) {
     2220                return;
     2221        }
     2222
     2223        if ( ! isset( $GLOBALS['_paused_plugins'] ) || empty( $GLOBALS['_paused_plugins'] ) ) {
     2224                return;
     2225        }
     2226
     2227        printf(
     2228                '<div class="notice notice-error"><p><strong>%s</strong><br>%s</p><p><a href="%s">%s</a></p></div>',
     2229                __( 'One or more plugins failed to load properly.' ),
     2230                __( 'You can find more details and make changes on the Plugins screen.' ),
     2231                esc_url( admin_url( 'plugins.php?plugin_status=paused' ) ),
     2232                __( 'Go to the Plugins screen' )
     2233        );
     2234}
Note: See TracChangeset for help on using the changeset viewer.