WordPress.org

Make WordPress Core

Ticket #44458: 44458-2.diff

File 44458-2.diff, 16.0 KB (added by schlessera, 2 years ago)

Second iteration, adding all the basic UI integration (for plugins only for now) [Updated]

  • src/wp-admin/css/list-tables.css

    diff --git src/wp-admin/css/list-tables.css src/wp-admin/css/list-tables.css
    index 8d78983993..8bef97bae2 100644
    ul.cat-checklist { 
    13011301        text-decoration: underline;
    13021302}
    13031303
     1304.plugins tr.paused th.check-column {
     1305        border-left: 4px solid #d54e21;
     1306}
     1307
     1308.plugins tr.paused th,
     1309.plugins tr.paused td {
     1310        background-color: #fef7f1;
     1311}
     1312
     1313.plugins tr.paused .plugin-title,
     1314.plugins .paused .dashicons-warning {
     1315        color: #dc3232;
     1316}
     1317
     1318.plugins .resume-link {
     1319        color: #dc3232;
     1320}
     1321
    13041322.plugin-card .update-now:before {
    13051323        color: #f56e28;
    13061324        content: "\f463";
  • src/wp-admin/includes/admin-filters.php

    diff --git src/wp-admin/includes/admin-filters.php src/wp-admin/includes/admin-filters.php
    index bfc9f51cfc..0686b9b8c2 100644
    add_action( 'load-plugins.php', 'wp_plugin_update_rows', 20 ); // After wp_updat 
    119119add_action( 'load-themes.php', 'wp_theme_update_rows', 20 ); // After wp_update_themes() is called.
    120120
    121121add_action( 'admin_notices', 'update_nag', 3 );
     122add_action( 'admin_notices', 'paused_plugins_notice', 5 );
    122123add_action( 'admin_notices', 'maintenance_nag', 10 );
    123124
    124125add_filter( 'update_footer', 'core_update_footer' );
  • src/wp-admin/includes/class-wp-plugins-list-table.php

    diff --git src/wp-admin/includes/class-wp-plugins-list-table.php src/wp-admin/includes/class-wp-plugins-list-table.php
    index 6e130f94b4..e169a8f245 100644
    class WP_Plugins_List_Table extends WP_List_Table { 
    9999                        'upgrade'            => array(),
    100100                        'mustuse'            => array(),
    101101                        'dropins'            => array(),
     102                        'paused'            => isset( $GLOBALS['_paused_plugins'] ) ? $GLOBALS['_paused_plugins'] : array(),
    102103                );
    103104
    104105                $screen = $this->screen;
    class WP_Plugins_List_Table extends WP_List_Table { 
    647648                                                /* translators: %s: plugin name */
    648649                                                $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>';
    649650                                        }
     651                                        if ( current_user_can( 'resume_plugin' ) && is_plugin_paused( $plugin_file ) ) {
     652                                                /* translators: %s: plugin name */
     653                                                $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 execution of %s', 'plugin' ), $plugin_data['Name'] ) ) . '">' . __( 'Resume execution' ) . '</a>';
     654                                        }
    650655                                } else {
    651656                                        if ( current_user_can( 'activate_plugin', $plugin_file ) ) {
    652657                                                /* translators: %s: plugin name */
    class WP_Plugins_List_Table extends WP_List_Table { 
    753758                        $class .= ' update';
    754759                }
    755760
     761                $paused = is_plugin_paused( $plugin_file );
     762                if ( $paused ) {
     763                        $class .= ' paused';
     764                }
     765
    756766                $plugin_slug = isset( $plugin_data['slug'] ) ? $plugin_data['slug'] : sanitize_title( $plugin_name );
    757767                printf(
    758768                        '<tr class="%s" data-slug="%s" data-plugin="%s">',
    class WP_Plugins_List_Table extends WP_List_Table { 
    836846                                        $plugin_meta = apply_filters( 'plugin_row_meta', $plugin_meta, $plugin_file, $plugin_data, $status );
    837847                                        echo implode( ' | ', $plugin_meta );
    838848
    839                                         echo '</div></td>';
     849                                        echo '</div>';
     850
     851                                        if ( $paused ) {
     852                                                echo sprintf(
     853                                                        '<p><span class="dashicons dashicons-warning"></span> <strong>%s</strong></p>',
     854                                                        __( 'This plugin failed to load properly and was paused within the admin backend.' )
     855                                                );
     856                                        }
     857
     858                                        echo '</td>';
    840859                                        break;
    841860                                default:
    842861                                        $classes = "$column_name column-$column_name $class";
  • src/wp-admin/includes/plugin.php

    diff --git src/wp-admin/includes/plugin.php src/wp-admin/includes/plugin.php
    index c898fc5169..4acf694d0f 100644
    function _get_dropins() { 
    444444                'install.php'        => array( __( 'Custom installation script.' ), true ), // auto on installation
    445445                'maintenance.php'    => array( __( 'Custom maintenance message.' ), true ), // auto on maintenance
    446446                'object-cache.php'   => array( __( 'External object cache.' ), true ), // auto on load
     447                'php-error.php'      => array( __( 'Custom PHP error message.' ), true ), // auto on error
     448                'shutdown-handler'   => array( __( 'Custom PHP shutdown handler.' ), true ), // auto on error
    447449        );
    448450
    449451        if ( is_multisite() ) {
    function is_plugin_inactive( $plugin ) { 
    496498        return ! is_plugin_active( $plugin );
    497499}
    498500
     501/**
     502 * Determines whether a plugin is technically active but was paused while
     503 * loading.
     504 *
     505 * For more information on this and similar theme functions, check out
     506 * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
     507 * Conditional Tags} article in the Theme Developer Handbook.
     508 *
     509 * @since 5.0.0
     510 *
     511 * @param string $plugin Path to the plugin file relative to the plugins directory.
     512 *
     513 * @return bool True, if in the active plugins list. False, not in the list.
     514 */
     515function is_plugin_paused( $plugin ) {
     516        if ( ! isset( $GLOBALS['_paused_plugins'] ) ) {
     517                return false;
     518        }
     519
     520        if ( ! is_plugin_active( $plugin ) || is_plugin_active_for_network( $plugin ) ) {
     521                return false;
     522        }
     523
     524        $plugin_path = WP_CONTENT_DIR . '/plugins/' . $plugin;
     525
     526        return in_array( $plugin_path, $GLOBALS['_paused_plugins'], true );
     527}
     528
    499529/**
    500530 * Determines whether the plugin is active for the entire network.
    501531 *
    function deactivate_plugins( $plugins, $silent = false, $network_wide = null ) { 
    693723                        continue;
    694724                }
    695725
     726                // Clean up the database before deactivating the plugin.
     727                if ( is_plugin_paused( $plugin ) ) {
     728                        resume_plugin( $plugin );
     729                }
     730
    696731                $network_deactivating = false !== $network_wide && is_plugin_active_for_network( $plugin );
    697732
    698733                if ( ! $silent ) {
    function delete_plugins( $plugins, $deprecated = '' ) { 
    887922                        uninstall_plugin( $plugin_file );
    888923                }
    889924
     925                // Clean up the database before removing the plugin.
     926                if ( is_plugin_paused( $plugin_file ) ) {
     927                        resume_plugin( $plugin_file );
     928                }
     929
    890930                /**
    891931                 * Fires immediately before a plugin deletion attempt.
    892932                 *
    function delete_plugins( $plugins, $deprecated = '' ) { 
    959999        return true;
    9601000}
    9611001
     1002/**
     1003 * Resumes a single plugin.
     1004 *
     1005 * Resuming the plugin basically means removing its entry from the
     1006 * `pause_on_admin` database option.
     1007 *
     1008 * @since 5.0.0
     1009 *
     1010 * @param string $plugin Single plugin to resume.
     1011 *
     1012 * @return bool|WP_Error True on success, false if `$plugin` was not paused, `WP_Error` on failure.
     1013 */
     1014function resume_plugin( $plugin ) {
     1015        $pause_on_admin = get_option( 'pause_on_admin', array() );
     1016
     1017        if ( ! array_key_exists( 'plugin', $pause_on_admin ) ) {
     1018                return false;
     1019        }
     1020
     1021        list( $plugin ) = explode( '/', $plugin );
     1022
     1023        if ( ( $index = array_search( $plugin, $pause_on_admin['plugin'], true ) ) !== false ) {
     1024                unset( $pause_on_admin['plugin'][ $index ] );
     1025        }
     1026
     1027        if ( count( $pause_on_admin['plugin'] ) === 0 ) {
     1028                unset( $pause_on_admin['plugin'] );
     1029        }
     1030
     1031        $result = update_option( 'pause_on_admin', $pause_on_admin );
     1032
     1033        if ( ! $result ) {
     1034                return new WP_Error( 'could_not_resume_plugin', __( 'Could not resume execution of the plugin.' ) );
     1035        }
     1036
     1037        return true;
     1038}
     1039
    9621040/**
    9631041 * Validate active plugins
    9641042 *
    function wp_add_privacy_policy_content( $plugin_name, $policy_text ) { 
    20662144
    20672145        WP_Privacy_Policy_Content::add( $plugin_name, $policy_text );
    20682146}
     2147
     2148/**
     2149 * Renders an admin notice in case some plugins have been paused due to errors.
     2150 *
     2151 * @since 5.0.0
     2152 *
     2153 * @return void
     2154 */
     2155function paused_plugins_notice() {
     2156        if ( 'plugins.php' === $GLOBALS['pagenow'] ) {
     2157                return;
     2158        }
     2159
     2160        if ( ! current_user_can( 'deactivate_plugins' ) ) {
     2161                return;
     2162        }
     2163
     2164        if ( ! isset( $GLOBALS['_paused_plugins'] ) || empty( $GLOBALS['_paused_plugins'] ) ) {
     2165                return;
     2166        }
     2167
     2168
     2169        echo sprintf(
     2170                '<div class="notice notice-error"><p><strong>%s</strong><br>%s</p><p>%s</p></div>',
     2171                __( 'One or more plugins failed to load properly.' ),
     2172                __( 'You can find more details and make changes on the Plugins screen.' ),
     2173                sprintf(
     2174                        '<a href="%s">%s</a>',
     2175                        admin_url( 'plugins.php' ),
     2176                        'Go to the Plugins screen'
     2177                )
     2178        );
     2179}
  • src/wp-admin/plugins.php

    diff --git src/wp-admin/plugins.php src/wp-admin/plugins.php
    index b482f474c0..b21254adc9 100644
    if ( $action ) { 
    389389                        }
    390390                        break;
    391391
     392                case 'resume':
     393                        if ( ! current_user_can( 'resume_plugin', $plugin ) ) {
     394                                wp_die( __( 'Sorry, you are not allowed to resume execution of this plugin.' ) );
     395                        }
     396
     397                        if ( is_multisite() && ! is_network_admin() && is_network_only_plugin( $plugin ) ) {
     398                                wp_redirect( self_admin_url( "plugins.php?plugin_status=$status&paged=$page&s=$s" ) );
     399                                exit;
     400                        }
     401
     402                        check_admin_referer( 'resume-plugin_' . $plugin );
     403
     404                        $result = resume_plugin( $plugin );
     405
     406                        if ( is_wp_error( $result ) ) {
     407                                wp_die( $result );
     408                        }
     409
     410                        wp_redirect( self_admin_url( "plugins.php?resume=true&plugin_status=$status&paged=$page&s=$s" ) );
     411                        exit;
     412
    392413                default:
    393414                        if ( isset( $_POST['checked'] ) ) {
    394415                                check_admin_referer( 'bulk-plugins' );
    elseif ( isset( $_GET['deleted'] ) ) : 
    532553        <div id="message" class="updated notice is-dismissible"><p><?php _e( 'Selected plugins <strong>deactivated</strong>.' ); ?></p></div>
    533554<?php elseif ( 'update-selected' == $action ) : ?>
    534555        <div id="message" class="updated notice is-dismissible"><p><?php _e( 'All selected plugins are up to date.' ); ?></p></div>
     556<?php elseif ( isset( $_GET['resume'] ) ) : ?>
     557        <div id="message" class="updated notice is-dismissible"><p><?php _e( 'Execution of plugin <strong>resumed</strong>.' ); ?></p></div>
    535558<?php endif; ?>
    536559
    537560<div class="wrap">
  • src/wp-includes/capabilities.php

    diff --git src/wp-includes/capabilities.php src/wp-includes/capabilities.php
    index 592995a120..315ae6e556 100644
    function map_meta_cap( $cap, $user_id ) { 
    455455                case 'deactivate_plugins':
    456456                case 'activate_plugin':
    457457                case 'deactivate_plugin':
     458                case 'resume_plugin':
    458459                        $caps[] = 'activate_plugins';
    459460                        if ( is_multisite() ) {
    460461                                // update_, install_, and delete_ are handled above with is_super_admin().
  • src/wp-includes/load.php

    diff --git src/wp-includes/load.php src/wp-includes/load.php
    index 81014fdde8..f4ee1a3dad 100644
    function wp_get_active_and_valid_plugins() { 
    687687                        $plugins[] = WP_PLUGIN_DIR . '/' . $plugin;
    688688                }
    689689        }
     690
     691        /*
     692         * Remove plugins from the list of active plugins when we're on an admin or
     693         * login screen and the plugin appears in the `pause_on_admin` list.
     694         */
     695        if ( 'wp-login.php' === $GLOBALS['pagenow']
     696             || ( is_admin() && ! wp_doing_ajax() ) ) {
     697                $pause_on_admin  = (array) get_option( 'pause_on_admin', array() );
     698
     699                if ( ! array_key_exists( 'plugin', $pause_on_admin ) ) {
     700                        return $plugins;
     701                }
     702
     703                foreach ( $plugins as $index => $plugin ) {
     704                        $parts = explode(
     705                                '/',
     706                                str_replace( WP_CONTENT_DIR . '/', '', $plugin )
     707                        );
     708
     709                        if ( in_array( $parts[1], $pause_on_admin['plugin'], true ) ) {
     710                                unset( $plugins[ $index ] );
     711                                // Store list of paused plugins for displaying an admin notice.
     712                                $GLOBALS['_paused_plugins'][] = $plugin;
     713                        }
     714                }
     715        }
     716
    690717        return $plugins;
    691718}
    692719
    function wp_finalize_scraping_edited_file_errors( $scrape_key ) { 
    12501277        }
    12511278        echo "\n###### wp_scraping_result_end:$scrape_key ######\n";
    12521279}
     1280
     1281/**
     1282 * Wraps the shutdown handler function so it can be made pluggable at a later
     1283 * stage.
     1284 *
     1285 * @since 5.0.0
     1286 *
     1287 * @return void
     1288 */
     1289function wp_shutdown_handler_wrapper() {
     1290        if ( defined( 'WP_EXECUTION_SUCCEEDED' ) && WP_EXECUTION_SUCCEEDED ) {
     1291                return;
     1292        }
     1293
     1294        // Load the pluggable shutdown handler in case we found one.
     1295        if ( function_exists( 'wp_handle_shutdown' ) ) {
     1296                $stop_propagation = (bool) wp_handle_shutdown();
     1297
     1298                if ( $stop_propagation ) {
     1299                        return;
     1300                }
     1301        }
     1302
     1303        $error = error_get_last();
     1304
     1305        // No error, just skip the error handling code.
     1306        if ( null === $error ) {
     1307                return;
     1308        }
     1309
     1310        /*
     1311         * If the option API has not been loaded yet, we cannot persist our
     1312         * discovery, so there's no point in moving forward.
     1313         */
     1314        if ( ! function_exists( 'get_option' ) ) {
     1315                return;
     1316        }
     1317
     1318        // For now, we only trigger our safe mode on parse errors.
     1319        if ( ! isset( $error['type'] ) || E_PARSE !== $error['type'] ) {
     1320                return;
     1321        }
     1322
     1323        try {
     1324                $path = str_replace( WP_CONTENT_DIR . '/', '', $error['file'] );
     1325
     1326                $parts     = explode( '/', $path );
     1327                $type      = rtrim( array_shift( $parts ), 's' );
     1328                $extension = array_shift( $parts );
     1329
     1330                $pause_on_admin   = get_option( 'pause_on_admin', array() );
     1331
     1332                if ( ! array_key_exists( $type, $pause_on_admin ) ) {
     1333                        $pause_on_admin[ $type ] = array();
     1334                }
     1335
     1336                if ( ! in_array( $extension, $pause_on_admin[ $type ], true ) ) {
     1337                        $pause_on_admin[ $type ][] = $extension;
     1338                }
     1339
     1340                update_option( 'pause_on_admin', $pause_on_admin );
     1341
     1342                // Load custom PHP error template, if present.
     1343                if ( is_readable( WP_CONTENT_DIR . '/php-error.php' ) ) {
     1344                        include WP_CONTENT_DIR . '/php-error.php';
     1345                        die();
     1346                }
     1347
     1348                $message = sprintf(
     1349                        '<p>%s</p>',
     1350                        __( 'The site is experiencing technical difficulties.' )
     1351                );
     1352
     1353                if ( function_exists( 'get_admin_url' ) ) {
     1354                        $url = get_admin_url();
     1355                        $message .= sprintf(
     1356                                '<hr><p><em>%s <a href="%s">%s</a></em></p>',
     1357                                __( 'Are you the site owner?' ),
     1358                                $url,
     1359                                __( 'Log into the admin backend to fix this.' )
     1360                        );
     1361                }
     1362
     1363                if ( function_exists( 'apply_filters' ) ) {
     1364                        /**
     1365                         * Filters the message that the default PHP error page displays.
     1366                         *
     1367                         * @since 5.0.0
     1368                         *
     1369                         * @param string $message HTML error message to display.
     1370                         */
     1371                        $message = apply_filters( 'wp_technical_issues_display', $message );
     1372                }
     1373
     1374                wp_die( $message );
     1375        } catch ( Exception $exception ) {
     1376                // Catch exceptions and remain silent.
     1377        }
     1378}
     1379
     1380/**
     1381 * Registers the WordPress premature shutdown handler.
     1382 *
     1383 * @since 5.0.0
     1384 *
     1385 * @return void
     1386 */
     1387function wp_register_premature_shutdown_handler() {
     1388        register_shutdown_function( 'wp_shutdown_handler_wrapper' );
     1389}
  • src/wp-settings.php

    diff --git src/wp-settings.php src/wp-settings.php
    index eb632238f0..c633f05b1d 100644
    require( ABSPATH . WPINC . '/load.php' ); 
    2020require( ABSPATH . WPINC . '/default-constants.php' );
    2121require_once( ABSPATH . WPINC . '/plugin.php' );
    2222
     23// Make sure we register the premature shutdown handler as soon as possible.
     24wp_register_premature_shutdown_handler();
     25
    2326/*
    2427 * These can't be directly globalized in version.php. When updating,
    2528 * we're including version.php from another installation and don't want
    global $blog_id; 
    4043// Set initial default constants including WP_MEMORY_LIMIT, WP_MAX_MEMORY_LIMIT, WP_DEBUG, SCRIPT_DEBUG, WP_CONTENT_DIR and WP_CACHE.
    4144wp_initial_constants();
    4245
     46/*
     47 * Allow an optional shutdown handler to be included through a pluggable file.
     48 * This file should register a function `wp_handle_shutdown( $context )` that
     49 * returns a boolean value. If the return value evaluates to false, the default
     50 * shutdown handler will not be executed.
     51 */
     52if ( is_readable( WP_CONTENT_DIR . '/shutdown-handler.php' ) ) {
     53        include WP_CONTENT_DIR . '/shutdown-handler.php';
     54}
     55
    4356// Check for the required PHP version and for the MySQL extension or a database drop-in.
    4457wp_check_php_mysql_versions();
    4558
    if ( is_multisite() ) { 
    482495 * @since 3.0.0
    483496 */
    484497do_action( 'wp_loaded' );
     498
     499/*
     500 * Store the fact that we could successfully execute the entire WordPress
     501 * lifecycle. This is used to skip the premature shutdown handler, as it cannot
     502 * be unregistered.
     503 */
     504if ( ! defined( 'WP_EXECUTION_SUCCEEDED' ) ) {
     505        define( 'WP_EXECUTION_SUCCEEDED', true );
     506}