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/class-wp-plugins-list-table.php

    r44937 r44973  
    4141
    4242        $status = 'all';
    43         if ( isset( $_REQUEST['plugin_status'] ) && in_array( $_REQUEST['plugin_status'], array( 'active', 'inactive', 'recently_activated', 'upgrade', 'mustuse', 'dropins', 'search' ) ) ) {
     43        if ( isset( $_REQUEST['plugin_status'] ) && in_array( $_REQUEST['plugin_status'], array( 'active', 'inactive', 'recently_activated', 'upgrade', 'mustuse', 'dropins', 'search', 'paused' ) ) ) {
    4444            $status = $_REQUEST['plugin_status'];
    4545        }
     
    100100            'mustuse'            => array(),
    101101            'dropins'            => array(),
     102            'paused'             => array(),
    102103        );
    103104
     
    184185            // Extra info if known. array_merge() ensures $plugin_data has precedence if keys collide.
    185186            if ( isset( $plugin_info->response[ $plugin_file ] ) ) {
    186                 $plugins['all'][ $plugin_file ] = $plugin_data = array_merge( (array) $plugin_info->response[ $plugin_file ], $plugin_data );
     187                $plugin_data                    = array_merge( (array) $plugin_info->response[ $plugin_file ], $plugin_data );
     188                $plugins['all'][ $plugin_file ] = $plugin_data;
    187189                // Make sure that $plugins['upgrade'] also receives the extra info since it is used on ?plugin_status=upgrade
    188190                if ( isset( $plugins['upgrade'][ $plugin_file ] ) ) {
    189                     $plugins['upgrade'][ $plugin_file ] = $plugin_data = array_merge( (array) $plugin_info->response[ $plugin_file ], $plugin_data );
     191                    $plugins['upgrade'][ $plugin_file ] = $plugin_data;
    190192                }
    191193            } elseif ( isset( $plugin_info->no_update[ $plugin_file ] ) ) {
    192                 $plugins['all'][ $plugin_file ] = $plugin_data = array_merge( (array) $plugin_info->no_update[ $plugin_file ], $plugin_data );
     194                $plugin_data                    = array_merge( (array) $plugin_info->no_update[ $plugin_file ], $plugin_data );
     195                $plugins['all'][ $plugin_file ] = $plugin_data;
    193196                // Make sure that $plugins['upgrade'] also receives the extra info since it is used on ?plugin_status=upgrade
    194197                if ( isset( $plugins['upgrade'][ $plugin_file ] ) ) {
    195                     $plugins['upgrade'][ $plugin_file ] = $plugin_data = array_merge( (array) $plugin_info->no_update[ $plugin_file ], $plugin_data );
     198                    $plugins['upgrade'][ $plugin_file ] = $plugin_data;
    196199                }
    197200            }
     
    219222                // On the network-admin screen, populate the active list with plugins that are network activated
    220223                $plugins['active'][ $plugin_file ] = $plugin_data;
     224
     225                if ( ! $screen->in_admin( 'network' ) && is_plugin_paused( $plugin_file ) ) {
     226                    $plugins['paused'][ $plugin_file ] = $plugin_data;
     227                }
    221228            } else {
    222229                if ( isset( $recently_activated[ $plugin_file ] ) ) {
     
    445452                    /* translators: %s: plugin count */
    446453                    $text = _n( 'Drop-ins <span class="count">(%s)</span>', 'Drop-ins <span class="count">(%s)</span>', $count );
     454                    break;
     455                case 'paused':
     456                    /* translators: %s: plugin count */
     457                    $text = _n( 'Paused <span class="count">(%s)</span>', 'Paused <span class="count">(%s)</span>', $count );
    447458                    break;
    448459                case 'upgrade':
     
    658669                        $actions['deactivate'] = '<a href="' . wp_nonce_url( 'plugins.php?action=deactivate&amp;plugin=' . urlencode( $plugin_file ) . '&amp;plugin_status=' . $context . '&amp;paged=' . $page . '&amp;s=' . $s, 'deactivate-plugin_' . $plugin_file ) . '" aria-label="' . esc_attr( sprintf( _x( 'Deactivate %s', 'plugin' ), $plugin_data['Name'] ) ) . '">' . __( 'Deactivate' ) . '</a>';
    659670                    }
     671                    if ( current_user_can( 'resume_plugin', $plugin_file ) && is_plugin_paused( $plugin_file ) ) {
     672                        /* translators: %s: plugin name */
     673                        $actions['resume'] = '<a class="resume-link" href="' . wp_nonce_url( 'plugins.php?action=resume&amp;plugin=' . urlencode( $plugin_file ) . '&amp;plugin_status=' . $context . '&amp;paged=' . $page . '&amp;s=' . $s, 'resume-plugin_' . $plugin_file ) . '" aria-label="' . esc_attr( sprintf( _x( 'Resume %s', 'plugin' ), $plugin_data['Name'] ) ) . '">' . __( 'Resume' ) . '</a>';
     674                    }
    660675                } else {
    661676                    if ( current_user_can( 'activate_plugin', $plugin_file ) ) {
     
    764779        if ( ! empty( $totals['upgrade'] ) && ! empty( $plugin_data['update'] ) ) {
    765780            $class .= ' update';
     781        }
     782
     783        $paused = ! $screen->in_admin( 'network' ) && is_plugin_paused( $plugin_file );
     784
     785        if ( $paused ) {
     786            $class .= ' paused';
    766787        }
    767788
     
    847868                     * @param string   $status      Status of the plugin. Defaults are 'All', 'Active',
    848869                     *                              'Inactive', 'Recently Activated', 'Upgrade', 'Must-Use',
    849                      *                              'Drop-ins', 'Search'.
     870                     *                              'Drop-ins', 'Search', 'Paused'.
    850871                     */
    851872                    $plugin_meta = apply_filters( 'plugin_row_meta', $plugin_meta, $plugin_file, $plugin_data, $status );
     
    853874
    854875                    echo '</div>';
     876
     877                    if ( $paused ) {
     878                        $notice_text = __( 'This plugin failed to load properly and is paused during recovery mode.' );
     879
     880                        printf( '<p><span class="dashicons dashicons-warning"></span> <strong>%s</strong></p>', $notice_text );
     881
     882                        $error = wp_get_plugin_error( $plugin_file );
     883
     884                        if ( false !== $error ) {
     885                            printf( '<div class="error-display"><p>%s</p></div>', wp_get_extension_error_description( $error ) );
     886                        }
     887                    }
    855888
    856889                    echo '</td>';
     
    887920         * @param string $status      Status of the plugin. Defaults are 'All', 'Active',
    888921         *                            'Inactive', 'Recently Activated', 'Upgrade', 'Must-Use',
    889          *                            'Drop-ins', 'Search'.
     922         *                            'Drop-ins', 'Search', 'Paused'.
    890923         */
    891924        do_action( 'after_plugin_row', $plugin_file, $plugin_data, $status );
     
    903936         * @param string $status      Status of the plugin. Defaults are 'All', 'Active',
    904937         *                            'Inactive', 'Recently Activated', 'Upgrade', 'Must-Use',
    905          *                            'Drop-ins', 'Search'.
     938         *                            'Drop-ins', 'Search', 'Paused'.
    906939         */
    907940        do_action( "after_plugin_row_{$plugin_file}", $plugin_file, $plugin_data, $status );
Note: See TracChangeset for help on using the changeset viewer.