WordPress.org

Make WordPress Core

Ticket #46141: 46141.diff

File 46141.diff, 54.9 KB (added by flixos90, 12 months ago)
  • src/wp-admin/css/list-tables.css

     
    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 .paused .error-display p,
    1319 .plugins .paused .error-display code {
    1320         font-size: 90%;
    1321         font-style: italic;
    1322         color: rgb( 0, 0, 0, 0.7 );
    1323 }
    1324 
    1325 .plugins .resume-link {
    1326         color: #dc3232;
    1327 }
    1328 
    13291304.plugin-card .update-now:before {
    13301305        color: #f56e28;
    13311306        content: "\f463";
  • src/wp-admin/includes/admin-filters.php

     
    117117add_action( 'load-themes.php', 'wp_theme_update_rows', 20 ); // After wp_update_themes() is called.
    118118
    119119add_action( 'admin_notices', 'update_nag', 3 );
    120 add_action( 'admin_notices', 'paused_plugins_notice', 5 );
    121 add_action( 'admin_notices', 'paused_themes_notice', 5 );
    122120add_action( 'admin_notices', 'maintenance_nag', 10 );
    123121
    124122add_filter( 'update_footer', 'core_update_footer' );
  • src/wp-admin/includes/class-wp-plugins-list-table.php

     
    4040                );
    4141
    4242                $status = 'all';
    43                 if ( isset( $_REQUEST['plugin_status'] ) && in_array( $_REQUEST['plugin_status'], array( 'active', 'inactive', 'recently_activated', 'upgrade', 'mustuse', 'dropins', 'search', 'paused' ) ) ) {
     43                if ( isset( $_REQUEST['plugin_status'] ) && in_array( $_REQUEST['plugin_status'], array( 'active', 'inactive', 'recently_activated', 'upgrade', 'mustuse', 'dropins', 'search' ) ) ) {
    4444                        $status = $_REQUEST['plugin_status'];
    4545                }
    4646
     
    9999                        'upgrade'            => array(),
    100100                        'mustuse'            => array(),
    101101                        'dropins'            => array(),
    102                         'paused'             => array(),
    103102                );
    104103
    105104                $screen = $this->screen;
     
    210209                                if ( $show_network_active ) {
    211210                                        // On the non-network screen, show network-active plugins if allowed
    212211                                        $plugins['active'][ $plugin_file ] = $plugin_data;
    213                                         if ( is_plugin_paused( $plugin_file ) ) {
    214                                                 $plugins['paused'][ $plugin_file ] = $plugin_data;
    215                                         }
    216212                                } else {
    217213                                        // On the non-network screen, filter out network-active plugins
    218214                                        unset( $plugins['all'][ $plugin_file ] );
     
    222218                                // On the non-network screen, populate the active list with plugins that are individually activated
    223219                                // On the network-admin screen, populate the active list with plugins that are network activated
    224220                                $plugins['active'][ $plugin_file ] = $plugin_data;
    225                                 if ( is_plugin_paused( $plugin_file ) ) {
    226                                         $plugins['paused'][ $plugin_file ] = $plugin_data;
    227                                 }
    228221                        } else {
    229222                                if ( isset( $recently_activated[ $plugin_file ] ) ) {
    230223                                        // Populate the recently activated list with plugins that have been recently activated
     
    452445                                        /* translators: %s: plugin count */
    453446                                        $text = _n( 'Drop-ins <span class="count">(%s)</span>', 'Drop-ins <span class="count">(%s)</span>', $count );
    454447                                        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 );
    458                                         break;
    459448                                case 'upgrade':
    460449                                        /* translators: %s: plugin count */
    461450                                        $text = _n( 'Update Available <span class="count">(%s)</span>', 'Update Available <span class="count">(%s)</span>', $count );
     
    644633                                                /* translators: %s: plugin name */
    645634                                                $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( 'Network Deactivate %s', 'plugin' ), $plugin_data['Name'] ) ) . '">' . __( 'Network Deactivate' ) . '</a>';
    646635                                        }
    647                                         if ( current_user_can( 'manage_network_plugins' ) && count_paused_plugin_sites_for_network( $plugin_file ) ) {
    648                                                 /* translators: %s: plugin name */
    649                                                 $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( 'Network Resume %s', 'plugin' ), $plugin_data['Name'] ) ) . '">' . __( 'Network Resume' ) . '</a>';
    650                                         }
    651636                                } else {
    652637                                        if ( current_user_can( 'manage_network_plugins' ) ) {
    653638                                                /* translators: %s: plugin name */
    654639                                                $actions['activate'] = '<a href="' . wp_nonce_url( 'plugins.php?action=activate&amp;plugin=' . urlencode( $plugin_file ) . '&amp;plugin_status=' . $context . '&amp;paged=' . $page . '&amp;s=' . $s, 'activate-plugin_' . $plugin_file ) . '" class="edit" aria-label="' . esc_attr( sprintf( _x( 'Network Activate %s', 'plugin' ), $plugin_data['Name'] ) ) . '">' . __( 'Network Activate' ) . '</a>';
    655640                                        }
    656                                         if ( current_user_can( 'manage_network_plugins' ) && count_paused_plugin_sites_for_network( $plugin_file ) ) {
    657                                                 /* translators: %s: plugin name */
    658                                                 $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( 'Network Resume %s', 'plugin' ), $plugin_data['Name'] ) ) . '">' . __( 'Network Resume' ) . '</a>';
    659                                         }
    660641                                        if ( current_user_can( 'delete_plugins' ) && ! is_plugin_active( $plugin_file ) ) {
    661642                                                /* translators: %s: plugin name */
    662643                                                $actions['delete'] = '<a href="' . wp_nonce_url( 'plugins.php?action=delete-selected&amp;checked[]=' . urlencode( $plugin_file ) . '&amp;plugin_status=' . $context . '&amp;paged=' . $page . '&amp;s=' . $s, 'bulk-plugins' ) . '" class="delete" aria-label="' . esc_attr( sprintf( _x( 'Delete %s', 'plugin' ), $plugin_data['Name'] ) ) . '">' . __( 'Delete' ) . '</a>';
     
    667648                                        $actions = array(
    668649                                                'network_active' => __( 'Network Active' ),
    669650                                        );
    670                                         if ( ! $restrict_network_only && current_user_can( 'resume_plugin', $plugin_file ) && is_plugin_paused( $plugin_file ) ) {
    671                                                 /* translators: %s: plugin name */
    672                                                 $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>';
    673                                         }
    674651                                } elseif ( $restrict_network_only ) {
    675652                                        $actions = array(
    676653                                                'network_only' => __( 'Network Only' ),
     
    680657                                                /* translators: %s: plugin name */
    681658                                                $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>';
    682659                                        }
    683                                         if ( current_user_can( 'resume_plugin', $plugin_file ) && is_plugin_paused( $plugin_file ) ) {
    684                                                 /* translators: %s: plugin name */
    685                                                 $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>';
    686                                         }
    687660                                } else {
    688661                                        if ( current_user_can( 'activate_plugin', $plugin_file ) ) {
    689662                                                /* translators: %s: plugin name */
     
    791764                        $class .= ' update';
    792765                }
    793766
    794                 $paused                        = is_plugin_paused( $plugin_file );
    795                 $paused_on_network_sites_count = $screen->in_admin( 'network' ) ? count_paused_plugin_sites_for_network( $plugin_file ) : 0;
    796                 if ( $paused || $paused_on_network_sites_count ) {
    797                         $class .= ' paused';
    798                 }
    799 
    800767                $plugin_slug = isset( $plugin_data['slug'] ) ? $plugin_data['slug'] : sanitize_title( $plugin_name );
    801768                printf(
    802769                        '<tr class="%s" data-slug="%s" data-plugin="%s">',
     
    878845                                         * @param array    $plugin_data An array of plugin data.
    879846                                         * @param string   $status      Status of the plugin. Defaults are 'All', 'Active',
    880847                                         *                              'Inactive', 'Recently Activated', 'Upgrade', 'Must-Use',
    881                                          *                              'Drop-ins', 'Search', 'Paused'.
     848                                         *                              'Drop-ins', 'Search'.
    882849                                         */
    883850                                        $plugin_meta = apply_filters( 'plugin_row_meta', $plugin_meta, $plugin_file, $plugin_data, $status );
    884851                                        echo implode( ' | ', $plugin_meta );
     
    885852
    886853                                        echo '</div>';
    887854
    888                                         if ( $paused || $paused_on_network_sites_count ) {
    889                                                 $notice_text = __( 'This plugin failed to load properly and was paused within the admin backend.' );
    890                                                 if ( $screen->in_admin( 'network' ) && $paused_on_network_sites_count ) {
    891                                                         $notice_text = sprintf(
    892                                                                 /* translators: %s: number of sites */
    893                                                                 _n( 'This plugin failed to load properly and was paused within the admin backend for %s site.', 'This plugin failed to load properly and was paused within the admin backend for %s sites.', $paused_on_network_sites_count ),
    894                                                                 number_format_i18n( $paused_on_network_sites_count )
    895                                                         );
    896                                                 }
    897 
    898                                                 printf( '<p><span class="dashicons dashicons-warning"></span> <strong>%s</strong></p>', $notice_text );
    899 
    900                                                 $error = wp_get_plugin_error( $plugin_file );
    901 
    902                                                 if ( false !== $error ) {
    903                                                         $constants = get_defined_constants( true );
    904                                                         $constants = isset( $constants['Core'] ) ? $constants['Core'] : $constants['internal'];
    905 
    906                                                         foreach ( $constants as $constant => $value ) {
    907                                                                 if ( 0 === strpos( $constant, 'E_' ) ) {
    908                                                                         $core_errors[ $value ] = $constant;
    909                                                                 }
    910                                                         }
    911 
    912                                                         $error['type'] = $core_errors[ $error['type'] ];
    913 
    914                                                         printf(
    915                                                                 '<div class="error-display"><p>%s</p></div>',
    916                                                                 sprintf(
    917                                                                         /* translators: 1: error type, 2: error line number, 3: error file name, 4: error message */
    918                                                                         __( 'The plugin caused an error of type %1$s in line %2$s of the file %3$s. Error message: %4$s' ),
    919                                                                         "<code>{$error['type']}</code>",
    920                                                                         "<code>{$error['line']}</code>",
    921                                                                         "<code>{$error['file']}</code>",
    922                                                                         "<code>{$error['message']}</code>"
    923                                                                 )
    924                                                         );
    925                                                 }
    926                                         }
    927 
    928855                                        echo '</td>';
    929856                                        break;
    930857                                default:
     
    958885                 * @param array  $plugin_data An array of plugin data.
    959886                 * @param string $status      Status of the plugin. Defaults are 'All', 'Active',
    960887                 *                            'Inactive', 'Recently Activated', 'Upgrade', 'Must-Use',
    961                  *                            'Drop-ins', 'Search', 'Paused'.
     888                 *                            'Drop-ins', 'Search'.
    962889                 */
    963890                do_action( 'after_plugin_row', $plugin_file, $plugin_data, $status );
    964891
     
    974901                 * @param array  $plugin_data An array of plugin data.
    975902                 * @param string $status      Status of the plugin. Defaults are 'All', 'Active',
    976903                 *                            'Inactive', 'Recently Activated', 'Upgrade', 'Must-Use',
    977                  *                            'Drop-ins', 'Search', 'Paused'.
     904                 *                            'Drop-ins', 'Search'.
    978905                 */
    979906                do_action( "after_plugin_row_{$plugin_file}", $plugin_file, $plugin_data, $status );
    980907        }
  • src/wp-admin/includes/plugin.php

     
    468468 */
    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
    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
     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
    479477        );
    480478
    481479        if ( is_multisite() ) {
     
    529527}
    530528
    531529/**
    532  * Determines whether a plugin is technically active but was paused while
    533  * loading.
    534  *
    535  * For more information on this and similar theme functions, check out
    536  * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
    537  * Conditional Tags} article in the Theme Developer Handbook.
    538  *
    539  * @since 5.1.0
    540  *
    541  * @param string $plugin Path to the plugin file relative to the plugins directory.
    542  * @return bool True, if in the list of paused plugins. False, not in the list.
    543  */
    544 function is_plugin_paused( $plugin ) {
    545         if ( ! isset( $GLOBALS['_paused_plugins'] ) ) {
    546                 return false;
    547         }
    548 
    549         if ( ! is_plugin_active( $plugin ) && ! is_plugin_active_for_network( $plugin ) ) {
    550                 return false;
    551         }
    552 
    553         list( $plugin ) = explode( '/', $plugin );
    554 
    555         return array_key_exists( $plugin, $GLOBALS['_paused_plugins'] );
    556 }
    557 
    558 /**
    559  * Gets the error that was recorded for a paused plugin.
    560  *
    561  * @since 5.1.0
    562  *
    563  * @param string $plugin Path to the plugin file relative to the plugins
    564  *                       directory.
    565  * @return array|false Array of error information as it was returned by
    566  *                     `error_get_last()`, or false if none was recorded.
    567  */
    568 function wp_get_plugin_error( $plugin ) {
    569         if ( ! isset( $GLOBALS['_paused_plugins'] ) ) {
    570                 return false;
    571         }
    572 
    573         list( $plugin ) = explode( '/', $plugin );
    574 
    575         if ( ! array_key_exists( $plugin, $GLOBALS['_paused_plugins'] ) ) {
    576                 return false;
    577         }
    578 
    579         return $GLOBALS['_paused_plugins'][ $plugin ];
    580 }
    581 
    582 /**
    583  * Gets the number of sites on which a specific plugin is paused.
    584  *
    585  * @since 5.1.0
    586  *
    587  * @param string $plugin Path to the plugin file relative to the plugins directory.
    588  * @return int Site count.
    589  */
    590 function count_paused_plugin_sites_for_network( $plugin ) {
    591         if ( ! is_multisite() ) {
    592                 return is_plugin_paused( $plugin ) ? 1 : 0;
    593         }
    594 
    595         list( $plugin ) = explode( '/', $plugin );
    596 
    597         $query_args = array(
    598                 'count'      => true,
    599                 'number'     => 0,
    600                 'network_id' => get_current_network_id(),
    601                 'meta_query' => array(
    602                         wp_paused_plugins()->get_site_meta_query_clause( $plugin ),
    603                 ),
    604         );
    605 
    606         return get_sites( $query_args );
    607 }
    608 
    609 /**
    610530 * Determines whether the plugin is active for the entire network.
    611531 *
    612532 * Only plugins installed in the plugins/ folder can be active.
     
    803723                        continue;
    804724                }
    805725
    806                 // Clean up the database before deactivating the plugin.
    807                 if ( is_plugin_paused( $plugin ) ) {
    808                         resume_plugin( $plugin );
    809                 }
    810 
    811726                $network_deactivating = false !== $network_wide && is_plugin_active_for_network( $plugin );
    812727
    813728                if ( ! $silent ) {
     
    1002917                        uninstall_plugin( $plugin_file );
    1003918                }
    1004919
    1005                 // Clean up the database before removing the plugin.
    1006                 if ( is_plugin_paused( $plugin_file ) ) {
    1007                         resume_plugin( $plugin_file );
    1008                 }
    1009 
    1010920                /**
    1011921                 * Fires immediately before a plugin deletion attempt.
    1012922                 *
     
    1085995}
    1086996
    1087997/**
    1088  * Tries to resume a single plugin.
    1089  *
    1090  * If a redirect was provided, we first ensure the plugin does not throw fatal
    1091  * errors anymore.
    1092  *
    1093  * The way it works is by setting the redirection to the error before trying to
    1094  * include the plugin file. If the plugin fails, then the redirection will not
    1095  * be overwritten with the success message and the plugin will not be resumed.
    1096  *
    1097  * @since 5.1.0
    1098  *
    1099  * @param string $plugin       Single plugin to resume.
    1100  * @param string $redirect     Optional. URL to redirect to. Default empty string.
    1101  * @param bool   $network_wide Optional. Whether to resume the plugin for the entire
    1102  *                             network. Default false.
    1103  * @return bool|WP_Error True on success, false if `$plugin` was not paused,
    1104  *                       `WP_Error` on failure.
    1105  */
    1106 function resume_plugin( $plugin, $redirect = '', $network_wide = false ) {
    1107         /*
    1108          * We'll override this later if the plugin could be included without
    1109          * creating a fatal error.
    1110          */
    1111         if ( ! empty( $redirect ) ) {
    1112                 wp_redirect(
    1113                         add_query_arg(
    1114                                 '_error_nonce',
    1115                                 wp_create_nonce( 'plugin-resume-error_' . $plugin ),
    1116                                 $redirect
    1117                         )
    1118                 );
    1119 
    1120                 // Load the plugin to test whether it throws a fatal error.
    1121                 ob_start();
    1122                 plugin_sandbox_scrape( $plugin );
    1123                 ob_clean();
    1124         }
    1125 
    1126         $result = wp_forget_extension_error( 'plugins', $plugin, $network_wide );
    1127 
    1128         if ( ! $result ) {
    1129                 return new WP_Error(
    1130                         'could_not_resume_plugin',
    1131                         __( 'Could not resume the plugin.' )
    1132                 );
    1133         }
    1134 
    1135         return true;
    1136 }
    1137 
    1138 /**
    1139998 * Validate active plugins
    1140999 *
    11411000 * Validate all active plugins, deactivates invalid and
     
    22422101
    22432102        WP_Privacy_Policy_Content::add( $plugin_name, $policy_text );
    22442103}
    2245 
    2246 /**
    2247  * Renders an admin notice in case some plugins have been paused due to errors.
    2248  *
    2249  * @since 5.1.0
    2250  */
    2251 function paused_plugins_notice() {
    2252         if ( 'plugins.php' === $GLOBALS['pagenow'] ) {
    2253                 return;
    2254         }
    2255 
    2256         if ( ! current_user_can( 'deactivate_plugins' ) ) {
    2257                 return;
    2258         }
    2259 
    2260         if ( ! isset( $GLOBALS['_paused_plugins'] ) || empty( $GLOBALS['_paused_plugins'] ) ) {
    2261                 return;
    2262         }
    2263 
    2264         printf(
    2265                 '<div class="notice notice-error"><p><strong>%s</strong><br>%s</p><p>%s</p></div>',
    2266                 __( 'One or more plugins failed to load properly.' ),
    2267                 __( 'You can find more details and make changes on the Plugins screen.' ),
    2268                 sprintf(
    2269                         '<a href="%s">%s</a>',
    2270                         admin_url( 'plugins.php?plugin_status=paused' ),
    2271                         'Go to the Plugins screen'
    2272                 )
    2273         );
    2274 }
  • src/wp-admin/includes/theme.php

     
    768768        </script>
    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.1.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  */
    785 function is_theme_paused( $theme ) {
    786         if ( ! isset( $GLOBALS['_paused_themes'] ) ) {
    787                 return false;
    788         }
    789 
    790         if ( $theme !== get_stylesheet() && $theme !== get_template() ) {
    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.1.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  */
    807 function 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  * Gets the number of sites on which a specific theme is paused.
    821  *
    822  * @since 5.1.0
    823  *
    824  * @param string $theme Path to the theme directory relative to the themes directory.
    825  * @return int Site count.
    826  */
    827 function count_paused_theme_sites_for_network( $theme ) {
    828         if ( ! is_multisite() ) {
    829                 return is_theme_paused( $theme ) ? 1 : 0;
    830         }
    831 
    832         $query_args = array(
    833                 'count'      => true,
    834                 'number'     => 0,
    835                 'network_id' => get_current_network_id(),
    836                 'meta_query' => array(
    837                         wp_paused_themes()->get_site_meta_query_clause( $theme ),
    838                 ),
    839         );
    840 
    841         return get_sites( $query_args );
    842 }
    843 
    844 /**
    845  * Tries to resume a single theme.
    846  *
    847  * @since 5.1.0
    848  *
    849  * @param string $theme Single theme to resume.
    850  * @return bool|WP_Error True on success, false if `$theme` was not paused,
    851  *                       `WP_Error` on failure.
    852  */
    853 function resume_theme( $theme ) {
    854         $result = wp_forget_extension_error( 'themes', $theme );
    855 
    856         if ( ! $result ) {
    857                 return new WP_Error(
    858                         'could_not_resume_theme',
    859                         __( 'Could not resume the theme.' )
    860                 );
    861         }
    862 
    863         return true;
    864 }
    865 
    866 /**
    867  * Renders an admin notice in case some themes have been paused due to errors.
    868  *
    869  * @since 5.1.0
    870  */
    871 function paused_themes_notice() {
    872         if ( 'themes.php' === $GLOBALS['pagenow'] ) {
    873                 return;
    874         }
    875 
    876         if ( ! current_user_can( 'switch_themes' ) ) {
    877                 return;
    878         }
    879 
    880         if ( ! isset( $GLOBALS['_paused_themes'] ) || empty( $GLOBALS['_paused_themes'] ) ) {
    881                 return;
    882         }
    883 
    884         printf(
    885                 '<div class="notice notice-error"><p><strong>%s</strong><br>%s</p><p>%s</p></div>',
    886                 __( 'One or more themes failed to load properly.' ),
    887                 __( 'You can find more details and make changes on the Themes screen.' ),
    888                 sprintf(
    889                         '<a href="%s">%s</a>',
    890                         admin_url( 'themes.php' ),
    891                         'Go to the Themes screen'
    892                 )
    893         );
    894 }
  • src/wp-admin/plugins.php

     
    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 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, self_admin_url( 'plugins.php?error=resuming' ), is_network_admin() );
    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 
    413392                default:
    414393                        if ( isset( $_POST['checked'] ) ) {
    415394                                check_admin_referer( 'bulk-plugins' );
     
    509488                        $_GET['charsout']
    510489                );
    511490                $errmsg .= ' ' . __( 'If you notice &#8220;headers already sent&#8221; messages, problems with syndication feeds or other issues, try deactivating or removing this plugin.' );
    512         } elseif ( 'resuming' === $_GET['error'] ) {
    513                 $errmsg = __( 'Plugin could not be resumed because it triggered a <strong>fatal error</strong>.' );
    514491        } else {
    515492                $errmsg = __( 'Plugin could not be activated because it triggered a <strong>fatal error</strong>.' );
    516493        }
     
    564541        <div id="message" class="updated notice is-dismissible"><p><?php _e( 'Selected plugins <strong>deactivated</strong>.' ); ?></p></div>
    565542<?php elseif ( 'update-selected' == $action ) : ?>
    566543        <div id="message" class="updated notice is-dismissible"><p><?php _e( 'All selected plugins are up to date.' ); ?></p></div>
    567 <?php elseif ( isset( $_GET['resume'] ) ) : ?>
    568         <div id="message" class="updated notice is-dismissible"><p><?php _e( 'Plugin <strong>resumed</strong>.' ); ?></p></div>
    569544<?php endif; ?>
    570545
    571546<div class="wrap">
  • src/wp-admin/themes.php

     
    3333                switch_theme( $theme->get_stylesheet() );
    3434                wp_redirect( admin_url( 'themes.php?activated=true' ) );
    3535                exit;
    36         } elseif ( 'resume' === $_GET['action'] ) {
    37                 check_admin_referer( 'resume-theme_' . $_GET['stylesheet'] );
    38                 $theme = wp_get_theme( $_GET['stylesheet'] );
    39 
    40                 if ( ! current_user_can( 'resume_themes' ) ) {
    41                         wp_die(
    42                                 '<h1>' . __( 'You need a higher level of permission.' ) . '</h1>' .
    43                                 '<p>' . __( 'Sorry, you are not allowed to resume this theme.' ) . '</p>',
    44                                 403
    45                         );
    46                 }
    47 
    48                 $result = resume_theme( $theme->get_stylesheet() );
    49 
    50                 if ( is_wp_error( $result ) ) {
    51                         wp_die( $result );
    52                 }
    53 
    54                 wp_redirect( admin_url( 'themes.php?resumed=true' ) );
    55                 exit;
    5636        } elseif ( 'delete' == $_GET['action'] ) {
    5737                check_admin_referer( 'delete-theme_' . $_GET['stylesheet'] );
    5838                $theme = wp_get_theme( $_GET['stylesheet'] );
     
    215195        ?>
    216196        <div id="message4" class="error"><p><?php _e( 'You cannot delete a theme while it has an active child theme.' ); ?></p></div>
    217197        <?php
    218 } elseif ( isset( $_GET['resumed'] ) ) {
    219         ?>
    220         <div id="message5" class="updated notice is-dismissible"><p><?php _e( 'Theme resumed.' ); ?></p></div>
    221         <?php
    222198}
    223199
    224200$ct = wp_get_theme();
     
    372348<p><?php _e( 'The following themes are installed but incomplete.' ); ?></p>
    373349
    374350        <?php
    375         $can_resume  = current_user_can( 'resume_themes' );
    376351        $can_delete  = current_user_can( 'delete_themes' );
    377352        $can_install = current_user_can( 'install_themes' );
    378353        ?>
     
    380355        <tr>
    381356                <th><?php _ex( 'Name', 'theme name' ); ?></th>
    382357                <th><?php _e( 'Description' ); ?></th>
    383                 <?php if ( $can_resume ) { ?>
    384                         <td></td>
    385                 <?php } ?>
    386358                <?php if ( $can_delete ) { ?>
    387359                        <td></td>
    388360                <?php } ?>
     
    395367                        <td><?php echo $broken_theme->get( 'Name' ) ? $broken_theme->display( 'Name' ) : $broken_theme->get_stylesheet(); ?></td>
    396368                        <td><?php echo $broken_theme->errors()->get_error_message(); ?></td>
    397369                        <?php
    398                         if ( $can_resume ) {
    399                                 if ( 'theme_paused' === $broken_theme->errors()->get_error_code() ) {
    400                                         $stylesheet = $broken_theme->get_stylesheet();
    401                                         $resume_url = add_query_arg(
    402                                                 array(
    403                                                         'action'     => 'resume',
    404                                                         'stylesheet' => urlencode( $stylesheet ),
    405                                                 ),
    406                                                 admin_url( 'themes.php' )
    407                                         );
    408                                         $resume_url = wp_nonce_url( $resume_url, 'resume-theme_' . $stylesheet );
    409                                         ?>
    410                                         <td><a href="<?php echo esc_url( $resume_url ); ?>" class="button resume-theme"><?php _e( 'Resume' ); ?></a></td>
    411                                         <?php
    412                                 } else {
    413                                         ?>
    414                                         <td></td>
    415                                         <?php
    416                                 }
    417                         }
    418 
    419370                        if ( $can_delete ) {
    420371                                $stylesheet = $broken_theme->get_stylesheet();
    421372                                $delete_url = add_query_arg(
  • src/wp-includes/capabilities.php

     
    464464                                }
    465465                        }
    466466                        break;
    467                 case 'resume_plugin':
    468                         // Even in a multisite, regular administrators should be able to resume a plugin.
    469                         $caps[] = 'activate_plugins';
    470                         break;
    471                 case 'resume_themes':
    472                         // Even in a multisite, regular administrators should be able to resume a theme.
    473                         $caps[] = 'switch_themes';
    474                         break;
    475467                case 'delete_user':
    476468                case 'delete_users':
    477469                        // If multisite only super admins can delete users.
  • src/wp-includes/class-wp-fatal-error-handler.php

     
    1 <?php
    2 /**
    3  * Error Protection API: WP_Fatal_Error_Handler class
    4  *
    5  * @package WordPress
    6  * @since 5.1.0
    7  */
    8 
    9 /**
    10  * Core class used as the default shutdown handler for fatal errors.
    11  *
    12  * A drop-in 'fatal-error-handler.php' can be used to override the instance of this class and use a custom
    13  * implementation for the fatal error handler that WordPress registers. The custom class should extend this class and
    14  * can override its methods individually as necessary. The file must return the instance of the class that should be
    15  * registered.
    16  *
    17  * @since 5.1.0
    18  */
    19 class WP_Fatal_Error_Handler {
    20 
    21         /**
    22          * Runs the shutdown handler.
    23          *
    24          * This method is registered via `register_shutdown_function()`.
    25          *
    26          * @since 5.1.0
    27          */
    28         public function handle() {
    29                 // Bail if WordPress executed successfully.
    30                 if ( defined( 'WP_EXECUTION_SUCCEEDED' ) && WP_EXECUTION_SUCCEEDED ) {
    31                         return;
    32                 }
    33 
    34                 try {
    35                         // Bail if no error found.
    36                         $error = $this->detect_error();
    37                         if ( ! $error ) {
    38                                 return;
    39                         }
    40 
    41                         // If the error was stored and thus the extension paused,
    42                         // redirect the request to catch multiple errors in one go.
    43                         if ( $this->store_error( $error ) ) {
    44                                 $this->redirect_protected();
    45                         }
    46 
    47                         // Display the PHP error template.
    48                         $this->display_error_template();
    49                 } catch ( Exception $e ) {
    50                         // Catch exceptions and remain silent.
    51                 }
    52         }
    53 
    54         /**
    55          * Detects the error causing the crash if it should be handled.
    56          *
    57          * @since 5.1.0
    58          *
    59          * @return array|null Error that was triggered, or null if no error received or if the error should not be handled.
    60          */
    61         protected function detect_error() {
    62                 $error = error_get_last();
    63 
    64                 // No error, just skip the error handling code.
    65                 if ( null === $error ) {
    66                         return null;
    67                 }
    68 
    69                 // Bail if this error should not be handled.
    70                 if ( ! wp_should_handle_error( $error ) ) {
    71                         return null;
    72                 }
    73 
    74                 return $error;
    75         }
    76 
    77         /**
    78          * Stores the given error so that the extension causing it is paused.
    79          *
    80          * @since 5.1.0
    81          *
    82          * @param array $error Error that was triggered.
    83          * @return bool True if the error was stored successfully, false otherwise.
    84          */
    85         protected function store_error( $error ) {
    86                 // Do not pause extensions if they only crash on a non-protected endpoint.
    87                 if ( ! is_protected_endpoint() ) {
    88                         return false;
    89                 }
    90 
    91                 return wp_record_extension_error( $error );
    92         }
    93 
    94         /**
    95          * Redirects the current request to allow recovering multiple errors in one go.
    96          *
    97          * The redirection will only happen when on a protected endpoint.
    98          *
    99          * It must be ensured that this method is only called when an error actually occurred and will not occur on the
    100          * next request again. Otherwise it will create a redirect loop.
    101          *
    102          * @since 5.1.0
    103          */
    104         protected function redirect_protected() {
    105                 // Do not redirect requests on non-protected endpoints.
    106                 if ( ! is_protected_endpoint() ) {
    107                         return;
    108                 }
    109 
    110                 // Pluggable is usually loaded after plugins, so we manually include it here for redirection functionality.
    111                 if ( ! function_exists( 'wp_redirect' ) ) {
    112                         include ABSPATH . WPINC . '/pluggable.php';
    113                 }
    114 
    115                 $scheme = is_ssl() ? 'https://' : 'http://';
    116 
    117                 $url = "{$scheme}{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}";
    118                 wp_redirect( $url );
    119                 exit;
    120         }
    121 
    122         /**
    123          * Displays the PHP error template and sends the HTTP status code, typically 500.
    124          *
    125          * A drop-in 'php-error.php' can be used as a custom template. This drop-in should control the HTTP status code and
    126          * print the HTML markup indicating that a PHP error occurred. Note that this drop-in may potentially be executed
    127          * very early in the WordPress bootstrap process, so any core functions used that are not part of
    128          * `wp-includes/load.php` should be checked for before being called.
    129          *
    130          * If no such drop-in is available, this will call {@see WP_Fatal_Error_Handler::display_default_error_template()}.
    131          *
    132          * @since 5.1.0
    133          */
    134         protected function display_error_template() {
    135                 if ( defined( 'WP_CONTENT_DIR' ) ) {
    136                         // Load custom PHP error template, if present.
    137                         $php_error_pluggable = WP_CONTENT_DIR . '/php-error.php';
    138                         if ( is_readable( $php_error_pluggable ) ) {
    139                                 require_once $php_error_pluggable;
    140                                 return;
    141                         }
    142                 }
    143 
    144                 // Otherwise, display the default error template.
    145                 $this->display_default_error_template();
    146         }
    147 
    148         /**
    149          * Displays the default PHP error template.
    150          *
    151          * This method is called conditionally if no 'php-error.php' drop-in is available.
    152          *
    153          * It calls {@see wp_die()} with a message indicating that the site is experiencing technical difficulties and a
    154          * login link to the admin backend. The {@see 'wp_php_error_message'} and {@see 'wp_php_error_args'} filters can
    155          * be used to modify these parameters.
    156          *
    157          * @since 5.1.0
    158          */
    159         protected function display_default_error_template() {
    160                 if ( ! function_exists( '__' ) ) {
    161                         wp_load_translations_early();
    162                 }
    163 
    164                 if ( ! function_exists( 'wp_die' ) ) {
    165                         require_once ABSPATH . WPINC . '/functions.php';
    166                 }
    167 
    168                 $message = __( 'The site is experiencing technical difficulties.' );
    169 
    170                 $args = array(
    171                         'response' => 500,
    172                         'exit'     => false,
    173                 );
    174                 if ( function_exists( 'admin_url' ) ) {
    175                         $args['link_url']  = admin_url();
    176                         $args['link_text'] = __( 'Log into the admin backend to fix this.' );
    177                 }
    178 
    179                 /**
    180                  * Filters the message that the default PHP error template displays.
    181                  *
    182                  * @since 5.1.0
    183                  *
    184                  * @param string $message HTML error message to display.
    185                  */
    186                 $message = apply_filters( 'wp_php_error_message', $message );
    187 
    188                 /**
    189                  * Filters the arguments passed to {@see wp_die()} for the default PHP error template.
    190                  *
    191                  * @since 5.1.0
    192                  *
    193                  * @param array $args Associative array of arguments passed to `wp_die()`. By default these contain a
    194                  *                    'response' key, and optionally 'link_url' and 'link_text' keys.
    195                  */
    196                 $args = apply_filters( 'wp_php_error_args', $args );
    197 
    198                 wp_die( $message, '', $args );
    199         }
    200 }
  • src/wp-includes/class-wp-paused-extensions-storage.php

     
    1 <?php
    2 /**
    3  * Error Protection API: WP_Paused_Extensions_Storage class
    4  *
    5  * @package WordPress
    6  * @since 5.1.0
    7  */
    8 
    9 /**
    10  * Core class used for storing paused extensions.
    11  *
    12  * @since 5.1.0
    13  */
    14 class WP_Paused_Extensions_Storage {
    15 
    16         /**
    17          * Option name for storing paused extensions.
    18          *
    19          * @since 5.1.0
    20          * @var string
    21          */
    22         protected $option_name;
    23 
    24         /**
    25          * Prefix for paused extensions stored as site metadata.
    26          *
    27          * @since 5.1.0
    28          * @var string
    29          */
    30         protected $meta_prefix;
    31 
    32         /**
    33          * Constructor.
    34          *
    35          * @since 5.1.0
    36          *
    37          * @param string $option_name Option name for storing paused extensions.
    38          * @param string $meta_prefix Prefix for paused extensions stored as site metadata.
    39          */
    40         public function __construct( $option_name, $meta_prefix ) {
    41                 $this->option_name = $option_name;
    42                 $this->meta_prefix = $meta_prefix;
    43         }
    44 
    45         /**
    46          * Records an extension error.
    47          *
    48          * Only one error is stored per extension, with subsequent errors for the same extension overriding the
    49          * previously stored error.
    50          *
    51          * @since 5.1.0
    52          *
    53          * @param string $extension Plugin or theme directory name.
    54          * @param array  $error     {
    55          *     Error that was triggered.
    56          *
    57          *     @type string $type    The error type.
    58          *     @type string $file    The name of the file in which the error occurred.
    59          *     @type string $line    The line number in which the error occurred.
    60          *     @type string $message The error message.
    61          * }
    62          * @return bool True on success, false on failure.
    63          */
    64         public function record( $extension, $error ) {
    65                 if ( ! $this->is_api_loaded() ) {
    66                         return false;
    67                 }
    68 
    69                 if ( is_multisite() && is_site_meta_supported() ) {
    70                         // Do not update if the error is already stored.
    71                         if ( get_site_meta( get_current_blog_id(), $this->meta_prefix . $extension, true ) === $error ) {
    72                                 return true;
    73                         }
    74 
    75                         return (bool) update_site_meta( get_current_blog_id(), $this->meta_prefix . $extension, $error );
    76                 }
    77 
    78                 $paused_extensions = $this->get_all();
    79 
    80                 // Do not update if the error is already stored.
    81                 if ( isset( $paused_extensions[ $extension ] ) && $paused_extensions[ $extension ] === $error ) {
    82                         return true;
    83                 }
    84 
    85                 $paused_extensions[ $extension ] = $error;
    86 
    87                 return update_option( $this->option_name, $paused_extensions );
    88         }
    89 
    90         /**
    91          * Forgets a previously recorded extension error.
    92          *
    93          * @since 5.1.0
    94          *
    95          * @param string $extension Plugin or theme directory name.
    96          * @return bool True on success, false on failure.
    97          */
    98         public function forget( $extension ) {
    99                 if ( ! $this->is_api_loaded() ) {
    100                         return false;
    101                 }
    102 
    103                 if ( is_multisite() && is_site_meta_supported() ) {
    104                         // Do not delete if no error is stored.
    105                         if ( get_site_meta( get_current_blog_id(), $this->meta_prefix . $extension ) === array() ) {
    106                                 return true;
    107                         }
    108 
    109                         return (bool) delete_site_meta( get_current_blog_id(), $this->meta_prefix . $extension );
    110                 }
    111 
    112                 $paused_extensions = $this->get_all();
    113 
    114                 // Do not delete if no error is stored.
    115                 if ( ! isset( $paused_extensions[ $extension ] ) ) {
    116                         return true;
    117                 }
    118 
    119                 // Clean up the entire option if we're removing the only error.
    120                 if ( count( $paused_extensions ) === 1 ) {
    121                         return delete_option( $this->option_name );
    122                 }
    123 
    124                 unset( $paused_extensions[ $extension ] );
    125 
    126                 return update_option( $this->option_name, $paused_extensions );
    127         }
    128 
    129         /**
    130          * Gets the error for an extension, if paused.
    131          *
    132          * @since 5.1.0
    133          *
    134          * @param string $extension Plugin or theme directory name.
    135          * @return array|null Error that is stored, or null if the extension is not paused.
    136          */
    137         public function get( $extension ) {
    138                 if ( ! $this->is_api_loaded() ) {
    139                         return null;
    140                 }
    141 
    142                 if ( is_multisite() && is_site_meta_supported() ) {
    143                         $error = get_site_meta( get_current_blog_id(), $this->meta_prefix . $extension, true );
    144                         if ( ! $error ) {
    145                                 return null;
    146                         }
    147 
    148                         return $error;
    149                 }
    150 
    151                 $paused_extensions = $this->get_all();
    152 
    153                 if ( ! isset( $paused_extensions[ $extension ] ) ) {
    154                         return null;
    155                 }
    156 
    157                 return $paused_extensions[ $extension ];
    158         }
    159 
    160         /**
    161          * Gets the paused extensions with their errors.
    162          *
    163          * @since 5.1.0
    164          *
    165          * @return array Associative array of $extension => $error pairs.
    166          */
    167         public function get_all() {
    168                 if ( ! $this->is_api_loaded() ) {
    169                         return array();
    170                 }
    171 
    172                 if ( is_multisite() && is_site_meta_supported() ) {
    173                         $site_metadata = get_site_meta( get_current_blog_id() );
    174 
    175                         $paused_extensions = array();
    176                         foreach ( $site_metadata as $meta_key => $meta_values ) {
    177                                 if ( 0 !== strpos( $meta_key, $this->meta_prefix ) ) {
    178                                         continue;
    179                                 }
    180 
    181                                 $error = maybe_unserialize( array_shift( $meta_values ) );
    182 
    183                                 $paused_extensions[ substr( $meta_key, strlen( $this->meta_prefix ) ) ] = $error;
    184                         }
    185 
    186                         return $paused_extensions;
    187                 }
    188 
    189                 return (array) get_option( $this->option_name, array() );
    190         }
    191 
    192         /**
    193          * Gets the site meta query clause for querying sites with paused extensions.
    194          *
    195          * @since 5.1.0
    196          *
    197          * @param string $extension Plugin or theme directory name.
    198          * @return array A single clause to add to a meta query.
    199          */
    200         public function get_site_meta_query_clause( $extension ) {
    201                 return array(
    202                         'key'         => $this->meta_prefix . $extension,
    203                         'compare_key' => '=',
    204                 );
    205         }
    206 
    207         /**
    208          * Checks whether the underlying API to store paused extensions is loaded.
    209          *
    210          * @since 5.1.0
    211          *
    212          * @return bool True if the API is loaded, false otherwise.
    213          */
    214         protected function is_api_loaded() {
    215                 if ( is_multisite() ) {
    216                         return function_exists( 'is_site_meta_supported' ) && function_exists( 'get_site_meta' );
    217                 }
    218 
    219                 return function_exists( 'get_option' );
    220         }
    221 }
  • src/wp-includes/class-wp-theme.php

     
    371371                        $this->parent = new WP_Theme( $this->template, isset( $theme_root_template ) ? $theme_root_template : $this->theme_root, $this );
    372372                }
    373373
    374                 if ( wp_paused_themes()->get( $this->stylesheet ) && ( ! is_wp_error( $this->errors ) || ! isset( $this->errors->errors['theme_paused'] ) ) ) {
    375                         $this->errors = new WP_Error( 'theme_paused', __( 'This theme failed to load properly and was paused within the admin backend.' ) );
    376                 }
    377 
    378374                // We're good. If we didn't retrieve from cache, set it.
    379375                if ( ! is_array( $cache ) ) {
    380376                        $cache = array(
  • src/wp-includes/error-protection.php

     
    1 <?php
    2 /**
    3  * Error Protection API: Functions
    4  *
    5  * @package WordPress
    6  * @since 5.1.0
    7  */
    8 
    9 /**
    10  * Gets the instance for storing paused plugins.
    11  *
    12  * @since 5.1.0
    13  *
    14  * @return WP_Paused_Extensions_Storage Paused plugins storage.
    15  */
    16 function wp_paused_plugins() {
    17         static $wp_paused_plugins_storage = null;
    18 
    19         if ( null === $wp_paused_plugins_storage ) {
    20                 $wp_paused_plugins_storage = new WP_Paused_Extensions_Storage( 'paused_plugins', 'paused_plugin_' );
    21         }
    22 
    23         return $wp_paused_plugins_storage;
    24 }
    25 
    26 /**
    27  * Gets the instance for storing paused themes.
    28  *
    29  * @since 5.1.0
    30  *
    31  * @return WP_Paused_Extensions_Storage Paused themes storage.
    32  */
    33 function wp_paused_themes() {
    34         static $wp_paused_themes_storage = null;
    35 
    36         if ( null === $wp_paused_themes_storage ) {
    37                 $wp_paused_themes_storage = new WP_Paused_Extensions_Storage( 'paused_themes', 'paused_theme_' );
    38         }
    39 
    40         return $wp_paused_themes_storage;
    41 }
    42 
    43 /**
    44  * Records the extension error as a database option.
    45  *
    46  * @since 5.1.0
    47  *
    48  * @global array $wp_theme_directories
    49  *
    50  * @param array $error Error that was triggered.
    51  * @return bool Whether the error was correctly recorded.
    52  */
    53 function wp_record_extension_error( $error ) {
    54         global $wp_theme_directories;
    55 
    56         if ( ! isset( $error['file'] ) ) {
    57                 return false;
    58         }
    59 
    60         if ( ! defined( 'WP_PLUGIN_DIR' ) ) {
    61                 return false;
    62         }
    63 
    64         $error_file    = wp_normalize_path( $error['file'] );
    65         $wp_plugin_dir = wp_normalize_path( WP_PLUGIN_DIR );
    66 
    67         if ( 0 === strpos( $error_file, $wp_plugin_dir ) ) {
    68                 $callback = 'wp_paused_plugins';
    69                 $path     = str_replace( $wp_plugin_dir . '/', '', $error_file );
    70         } else {
    71                 if ( empty( $wp_theme_directories ) ) {
    72                         return false;
    73                 }
    74 
    75                 foreach ( $wp_theme_directories as $theme_directory ) {
    76                         $theme_directory = wp_normalize_path( $theme_directory );
    77                         if ( 0 === strpos( $error_file, $theme_directory ) ) {
    78                                 $callback = 'wp_paused_themes';
    79                                 $path     = str_replace( $theme_directory . '/', '', $error_file );
    80                         }
    81                 }
    82         }
    83 
    84         if ( empty( $callback ) || empty( $path ) ) {
    85                 return false;
    86         }
    87 
    88         $parts     = explode( '/', $path );
    89         $extension = array_shift( $parts );
    90 
    91         return call_user_func( $callback )->record( $extension, $error );
    92 }
    93 
    94 /**
    95  * Forgets a previously recorded extension error again.
    96  *
    97  * @since 5.1.0
    98  *
    99  * @param string $type         Type of the extension.
    100  * @param string $extension    Relative path of the extension.
    101  * @param bool   $network_wide Optional. Whether to resume the plugin for the entire
    102  *                             network. Default false.
    103  * @return bool Whether the extension error was successfully forgotten.
    104  */
    105 function wp_forget_extension_error( $type, $extension, $network_wide = false ) {
    106         switch ( $type ) {
    107                 case 'plugins':
    108                         $callback          = 'wp_paused_plugins';
    109                         list( $extension ) = explode( '/', $extension );
    110                         break;
    111                 case 'themes':
    112                         $callback          = 'wp_paused_themes';
    113                         list( $extension ) = explode( '/', $extension );
    114                         break;
    115         }
    116 
    117         if ( empty( $callback ) || empty( $extension ) ) {
    118                 return false;
    119         }
    120 
    121         // Handle manually since the regular APIs do not expose this functionality.
    122         if ( $network_wide && is_site_meta_supported() ) {
    123                 $site_meta_query_clause = call_user_func( $callback )->get_site_meta_query_clause( $extension );
    124                 return delete_metadata( 'blog', 0, $site_meta_query_clause['key'], '', true );
    125         }
    126 
    127         return call_user_func( $callback )->forget( $extension );
    128 }
    129 
    130 /**
    131  * Determines whether we are dealing with an error that WordPress should handle
    132  * in order to protect the admin backend against WSODs.
    133  *
    134  * @param array $error Error information retrieved from error_get_last().
    135  *
    136  * @return bool Whether WordPress should handle this error.
    137  */
    138 function wp_should_handle_error( $error ) {
    139         if ( ! isset( $error['type'] ) ) {
    140                 return false;
    141         }
    142 
    143         $error_types_to_handle = array(
    144                 E_ERROR,
    145                 E_PARSE,
    146                 E_USER_ERROR,
    147                 E_COMPILE_ERROR,
    148                 E_RECOVERABLE_ERROR,
    149         );
    150 
    151         return in_array( $error['type'], $error_types_to_handle, true );
    152 }
    153 
    154 /**
    155  * Registers the shutdown handler for fatal errors.
    156  *
    157  * The handler will only be registered if {@see wp_is_fatal_error_handler_enabled()} returns true.
    158  *
    159  * @since 5.1.0
    160  */
    161 function wp_register_fatal_error_handler() {
    162         if ( ! wp_is_fatal_error_handler_enabled() ) {
    163                 return;
    164         }
    165 
    166         $handler = null;
    167         if ( defined( 'WP_CONTENT_DIR' ) && is_readable( WP_CONTENT_DIR . '/fatal-error-handler.php' ) ) {
    168                 $handler = include WP_CONTENT_DIR . '/fatal-error-handler.php';
    169         }
    170 
    171         if ( ! is_object( $handler ) || ! is_callable( array( $handler, 'handle' ) ) ) {
    172                 $handler = new WP_Fatal_Error_Handler();
    173         }
    174 
    175         register_shutdown_function( array( $handler, 'handle' ) );
    176 }
    177 
    178 /**
    179  * Checks whether the fatal error handler is enabled.
    180  *
    181  * A constant `WP_DISABLE_FATAL_ERROR_HANDLER` can be set in `wp-config.php` to disable it, or alternatively the
    182  * {@see 'wp_fatal_error_handler_enabled'} filter can be used to modify the return value.
    183  *
    184  * @since 5.1.0
    185  *
    186  * @return bool True if the fatal error handler is enabled, false otherwise.
    187  */
    188 function wp_is_fatal_error_handler_enabled() {
    189         $enabled = ! defined( 'WP_DISABLE_FATAL_ERROR_HANDLER' ) || ! WP_DISABLE_FATAL_ERROR_HANDLER;
    190 
    191         /**
    192          * Filters whether the fatal error handler is enabled.
    193          *
    194          * @since 5.1.0
    195          *
    196          * @param bool $enabled True if the fatal error handler is enabled, false otherwise.
    197          */
    198         return apply_filters( 'wp_fatal_error_handler_enabled', $enabled );
    199 }
  • src/wp-includes/load.php

     
    697697                }
    698698        }
    699699
    700         /*
    701          * Remove plugins from the list of active plugins when we're on an endpoint
    702          * that should be protected against WSODs and the plugin is paused.
    703          */
    704         if ( is_protected_endpoint() ) {
    705                 $plugins = wp_skip_paused_plugins( $plugins );
    706         }
    707 
    708700        return $plugins;
    709701}
    710702
    711703/**
    712  * Filters a given list of plugins, removing any paused plugins from it.
    713  *
    714  * @since 5.1.0
    715  *
    716  * @param array $plugins List of absolute plugin main file paths.
    717  * @return array Filtered value of $plugins, without any paused plugins.
    718  */
    719 function wp_skip_paused_plugins( array $plugins ) {
    720         $paused_plugins = wp_paused_plugins()->get_all();
    721 
    722         if ( empty( $paused_plugins ) ) {
    723                 return $plugins;
    724         }
    725 
    726         foreach ( $plugins as $index => $plugin ) {
    727                 list( $plugin ) = explode( '/', plugin_basename( $plugin ) );
    728 
    729                 if ( array_key_exists( $plugin, $paused_plugins ) ) {
    730                         unset( $plugins[ $index ] );
    731 
    732                         // Store list of paused plugins for displaying an admin notice.
    733                         $GLOBALS['_paused_plugins'][ $plugin ] = $paused_plugins[ $plugin ];
    734                 }
    735         }
    736 
    737         return $plugins;
    738 }
    739 
    740 /**
    741704 * Retrieves an array of active and valid themes.
    742705 *
    743706 * While upgrading or installing WordPress, no themes are returned.
     
    762725
    763726        $themes[] = TEMPLATEPATH;
    764727
    765         /*
    766          * Remove themes from the list of active themes when we're on an endpoint
    767          * that should be protected against WSODs and the theme is paused.
    768          */
    769         if ( is_protected_endpoint() ) {
    770                 $themes = wp_skip_paused_themes( $themes );
    771 
    772                 // If no active and valid themes exist, skip loading themes.
    773                 if ( empty( $themes ) ) {
    774                         add_filter( 'wp_using_themes', '__return_false' );
    775                 }
    776         }
    777 
    778728        return $themes;
    779729}
    780730
    781731/**
    782  * Filters a given list of themes, removing any paused themes from it.
    783  *
    784  * @since 5.1.0
    785  *
    786  * @param array $themes List of absolute theme directory paths.
    787  * @return array Filtered value of $themes, without any paused themes.
    788  */
    789 function wp_skip_paused_themes( array $themes ) {
    790         $paused_themes = wp_paused_themes()->get_all();
    791 
    792         if ( empty( $paused_themes ) ) {
    793                 return $themes;
    794         }
    795 
    796         foreach ( $themes as $index => $theme ) {
    797                 $theme = basename( $theme );
    798 
    799                 if ( array_key_exists( $theme, $paused_themes ) ) {
    800                         unset( $themes[ $index ] );
    801 
    802                         // Store list of paused themes for displaying an admin notice.
    803                         $GLOBALS['_paused_themes'][ $theme ] = $paused_themes[ $theme ];
    804                 }
    805         }
    806 
    807         return $themes;
    808 }
    809 
    810 /**
    811732 * Set internal encoding.
    812733 *
    813734 * In most cases the default internal encoding is latin1, which is
     
    12901211}
    12911212
    12921213/**
    1293  * Determines whether we are currently on an endpoint that should be protected against WSODs.
    1294  *
    1295  * @since 5.1.0
    1296  *
    1297  * @return bool True if the current endpoint should be protected.
    1298  */
    1299 function is_protected_endpoint() {
    1300         // Protect login pages.
    1301         if ( isset( $GLOBALS['pagenow'] ) && 'wp-login.php' === $GLOBALS['pagenow'] ) {
    1302                 return true;
    1303         }
    1304 
    1305         // Protect the admin backend.
    1306         if ( is_admin() && ! wp_doing_ajax() ) {
    1307                 return true;
    1308         }
    1309 
    1310         // Protect AJAX actions that could help resolve a fatal error should be available.
    1311         if ( is_protected_ajax_action() ) {
    1312                 return true;
    1313         }
    1314 
    1315         /**
    1316          * Filters whether the current request is against a protected endpoint.
    1317          *
    1318          * This filter is only fired when an endpoint is requested which is not already protected by
    1319          * WordPress core. As such, it exclusively allows providing further protected endpoints in
    1320          * addition to the admin backend, login pages and protected AJAX actions.
    1321          *
    1322          * @since 5.1.0
    1323          *
    1324          * @param bool $is_protected_endpoint Whether the currently requested endpoint is protected. Default false.
    1325          */
    1326         return (bool) apply_filters( 'is_protected_endpoint', false );
    1327 }
    1328 
    1329 /**
    1330  * Determines whether we are currently handling an AJAX action that should be protected against WSODs.
    1331  *
    1332  * @since 5.1.0
    1333  *
    1334  * @return bool True if the current AJAX action should be protected.
    1335  */
    1336 function is_protected_ajax_action() {
    1337         if ( ! wp_doing_ajax() ) {
    1338                 return false;
    1339         }
    1340 
    1341         if ( ! isset( $_REQUEST['action'] ) ) {
    1342                 return false;
    1343         }
    1344 
    1345         $actions_to_protect = array(
    1346                 'edit-theme-plugin-file', // Saving changes in the core code editor.
    1347                 'heartbeat',              // Keep the heart beating.
    1348                 'install-plugin',         // Installing a new plugin.
    1349                 'install-theme',          // Installing a new theme.
    1350                 'search-plugins',         // Searching in the list of plugins.
    1351                 'search-install-plugins', // Searching for a plugin in the plugin install screen.
    1352                 'update-plugin',          // Update an existing plugin.
    1353                 'update-theme',           // Update an existing theme.
    1354         );
    1355 
    1356         /**
    1357          * Filters the array of protected AJAX actions.
    1358          *
    1359          * This filter is only fired when doing AJAX and the AJAX request has an 'action' property.
    1360          *
    1361          * @since 5.1.0
    1362          *
    1363          * @param array $actions_to_protect Array of strings with AJAX actions to protect.
    1364          */
    1365         $actions_to_protect = (array) apply_filters( 'wp_protected_ajax_actions', $actions_to_protect );
    1366 
    1367         if ( ! in_array( $_REQUEST['action'], $actions_to_protect, true ) ) {
    1368                 return false;
    1369         }
    1370 
    1371         return true;
    1372 }
    1373 
    1374 /**
    13751214 * Determines whether the current request is a WordPress cron request.
    13761215 *
    13771216 * @since 4.8.0
  • src/wp-includes/ms-load.php

     
    5353                }
    5454        }
    5555
    56         /*
    57          * Remove plugins from the list of active plugins when we're on an endpoint
    58          * that should be protected against WSODs and the plugin is paused.
    59          */
    60         if ( is_protected_endpoint() ) {
    61                 $plugins = wp_skip_paused_plugins( $plugins );
    62         }
    63 
    6456        return $plugins;
    6557}
    6658
  • src/wp-settings.php

     
    1717
    1818// Include files required for initialization.
    1919require( ABSPATH . WPINC . '/load.php' );
    20 require( ABSPATH . WPINC . '/class-wp-paused-extensions-storage.php' );
    21 require( ABSPATH . WPINC . '/class-wp-fatal-error-handler.php' );
    22 require( ABSPATH . WPINC . '/error-protection.php' );
    2320require( ABSPATH . WPINC . '/default-constants.php' );
    2421require_once( ABSPATH . WPINC . '/plugin.php' );
    2522
    26 // Make sure we register the shutdown handler for fatal errors as soon as possible.
    27 wp_register_fatal_error_handler();
    28 
    2923/*
    3024 * These can't be directly globalized in version.php. When updating,
    3125 * we're including version.php from another installation and don't want
     
    530524 * @since 3.0.0
    531525 */
    532526do_action( 'wp_loaded' );
    533 
    534 /*
    535  * Store the fact that we could successfully execute the entire WordPress
    536  * lifecycle. This is used to skip the premature shutdown handler, as it cannot
    537  * be unregistered.
    538  */
    539 if ( ! defined( 'WP_EXECUTION_SUCCEEDED' ) ) {
    540         define( 'WP_EXECUTION_SUCCEEDED', true );
    541 }
  • tests/phpunit/tests/user/capabilities.php

     
    257257                        'export_others_personal_data' => array( 'administrator' ),
    258258                        'erase_others_personal_data'  => array( 'administrator' ),
    259259                        'manage_privacy_options'      => array( 'administrator' ),
    260                         'resume_themes'               => array( 'administrator' ),
    261260
    262261                        'edit_categories'             => array( 'administrator', 'editor' ),
    263262                        'delete_categories'           => array( 'administrator', 'editor' ),
     
    297296                        'customize'                   => array( 'administrator' ),
    298297                        'delete_site'                 => array( 'administrator' ),
    299298                        'add_users'                   => array( 'administrator' ),
    300                         'resume_themes'               => array( 'administrator' ),
    301299
    302300                        'edit_categories'             => array( 'administrator', 'editor' ),
    303301                        'delete_categories'           => array( 'administrator', 'editor' ),
     
    456454                        // Singular object meta capabilities (where an object ID is passed) are not tested:
    457455                        $expected['activate_plugin'],
    458456                        $expected['deactivate_plugin'],
    459                         $expected['resume_plugin'],
    460457                        $expected['remove_user'],
    461458                        $expected['promote_user'],
    462459                        $expected['edit_user'],