Ticket #44458: 44458.7.diff
File 44458.7.diff, 29.3 KB (added by , 6 years ago) |
---|
-
src/wp-admin/css/list-tables.css
diff --git src/wp-admin/css/list-tables.css src/wp-admin/css/list-tables.css index 893326ba38..0e8891ec0e 100644
ul.cat-checklist { 1301 1301 text-decoration: underline; 1302 1302 } 1303 1303 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 1304 1329 .plugin-card .update-now:before { 1305 1330 color: #f56e28; 1306 1331 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 0816b2420b..bc2f769b64 100644
add_action( 'load-plugins.php', 'wp_plugin_update_rows', 20 ); // After wp_updat 121 121 add_action( 'load-themes.php', 'wp_theme_update_rows', 20 ); // After wp_update_themes() is called. 122 122 123 123 add_action( 'admin_notices', 'update_nag', 3 ); 124 add_action( 'admin_notices', 'paused_plugins_notice', 5 ); 124 125 add_action( 'admin_notices', 'maintenance_nag', 10 ); 125 126 126 127 add_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 1f540c9ed2..9bc91e1019 100644
class WP_Plugins_List_Table extends WP_List_Table { 40 40 ); 41 41 42 42 $status = 'all'; 43 if ( isset( $_REQUEST['plugin_status'] ) && in_array( $_REQUEST['plugin_status'], array( 'active', 'inactive', 'recently_activated', 'upgrade', 'mustuse', 'dropins', 'search' ) ) ) {43 if ( isset( $_REQUEST['plugin_status'] ) && in_array( $_REQUEST['plugin_status'], array( 'active', 'inactive', 'recently_activated', 'upgrade', 'mustuse', 'dropins', 'search', 'paused' ) ) ) { 44 44 $status = $_REQUEST['plugin_status']; 45 45 } 46 46 … … class WP_Plugins_List_Table extends WP_List_Table { 99 99 'upgrade' => array(), 100 100 'mustuse' => array(), 101 101 'dropins' => array(), 102 'paused' => array(), 102 103 ); 103 104 104 105 $screen = $this->screen; … … class WP_Plugins_List_Table extends WP_List_Table { 209 210 if ( $show_network_active ) { 210 211 // On the non-network screen, show network-active plugins if allowed 211 212 $plugins['active'][ $plugin_file ] = $plugin_data; 213 if ( is_plugin_paused( $plugin_file ) ) { 214 $plugins['paused'][ $plugin_file ] = $plugin_data; 215 } 212 216 } else { 213 217 // On the non-network screen, filter out network-active plugins 214 218 unset( $plugins['all'][ $plugin_file ] ); … … class WP_Plugins_List_Table extends WP_List_Table { 218 222 // On the non-network screen, populate the active list with plugins that are individually activated 219 223 // On the network-admin screen, populate the active list with plugins that are network activated 220 224 $plugins['active'][ $plugin_file ] = $plugin_data; 225 if ( is_plugin_paused( $plugin_file ) ) { 226 $plugins['paused'][ $plugin_file ] = $plugin_data; 227 } 221 228 } else { 222 229 if ( isset( $recently_activated[ $plugin_file ] ) ) { 223 230 // Populate the recently activated list with plugins that have been recently activated … … class WP_Plugins_List_Table extends WP_List_Table { 438 445 case 'dropins': 439 446 $text = _n( 'Drop-ins <span class="count">(%s)</span>', 'Drop-ins <span class="count">(%s)</span>', $count ); 440 447 break; 448 case 'paused': 449 $text = _n( 'Paused <span class="count">(%s)</span>', 'Paused <span class="count">(%s)</span>', $count ); 450 break; 441 451 case 'upgrade': 442 452 $text = _n( 'Update Available <span class="count">(%s)</span>', 'Update Available <span class="count">(%s)</span>', $count ); 443 453 break; … … class WP_Plugins_List_Table extends WP_List_Table { 649 659 /* translators: %s: plugin name */ 650 660 $actions['deactivate'] = '<a href="' . wp_nonce_url( 'plugins.php?action=deactivate&plugin=' . urlencode( $plugin_file ) . '&plugin_status=' . $context . '&paged=' . $page . '&s=' . $s, 'deactivate-plugin_' . $plugin_file ) . '" aria-label="' . esc_attr( sprintf( _x( 'Deactivate %s', 'plugin' ), $plugin_data['Name'] ) ) . '">' . __( 'Deactivate' ) . '</a>'; 651 661 } 662 if ( current_user_can( 'resume_plugin' ) && is_plugin_paused( $plugin_file ) ) { 663 /* translators: %s: plugin name */ 664 $actions['resume'] = '<a class="resume-link" href="' . wp_nonce_url( 'plugins.php?action=resume&plugin=' . urlencode( $plugin_file ) . '&plugin_status=' . $context . '&paged=' . $page . '&s=' . $s, 'resume-plugin_' . $plugin_file ) . '" aria-label="' . esc_attr( sprintf( _x( 'Resume execution of %s', 'plugin' ), $plugin_data['Name'] ) ) . '">' . __( 'Resume execution' ) . '</a>'; 665 } 652 666 } else { 653 667 if ( current_user_can( 'activate_plugin', $plugin_file ) ) { 654 668 /* translators: %s: plugin name */ … … class WP_Plugins_List_Table extends WP_List_Table { 755 769 $class .= ' update'; 756 770 } 757 771 772 $paused = is_plugin_paused( $plugin_file ); 773 if ( $paused ) { 774 $class .= ' paused'; 775 } 776 758 777 $plugin_slug = isset( $plugin_data['slug'] ) ? $plugin_data['slug'] : sanitize_title( $plugin_name ); 759 778 printf( 760 779 '<tr class="%s" data-slug="%s" data-plugin="%s">', … … class WP_Plugins_List_Table extends WP_List_Table { 833 852 * @param array $plugin_data An array of plugin data. 834 853 * @param string $status Status of the plugin. Defaults are 'All', 'Active', 835 854 * 'Inactive', 'Recently Activated', 'Upgrade', 'Must-Use', 836 * 'Drop-ins', 'Search' .855 * 'Drop-ins', 'Search', 'Paused' 837 856 */ 838 857 $plugin_meta = apply_filters( 'plugin_row_meta', $plugin_meta, $plugin_file, $plugin_data, $status ); 839 858 echo implode( ' | ', $plugin_meta ); 840 859 841 echo '</div></td>'; 860 echo '</div>'; 861 862 if ( $paused ) { 863 echo sprintf( 864 '<p><span class="dashicons dashicons-warning"></span> <strong>%s</strong></p>', 865 __( 'This plugin failed to load properly and was paused within the admin backend.' ) 866 ); 867 868 $error = wp_get_plugin_error( $plugin_file ); 869 870 if ( false !== $error ) { 871 $constants = get_defined_constants( true ); 872 $constants = isset( $constants['Core'] ) ? $constants['Core'] : $constants['internal']; 873 874 foreach ( $constants as $constant => $value ) { 875 if ( 0 === strpos( $constant, 'E_' ) ) { 876 $core_errors[ $value ] = $constant; 877 } 878 } 879 880 $error['type'] = $core_errors[ $error['type'] ]; 881 882 echo sprintf( 883 '<div class="error-display"><p>%s</p></div>', 884 sprintf( 885 __( 'The plugin caused an error of type %1$s in line %2$s of the file %3$s. Error message: %4$s' ), 886 "<code>{$error['type']}</code>", 887 "<code>{$error['line']}</code>", 888 "<code>{$error['file']}</code>", 889 "<code>{$error['message']}</code>" 890 ) 891 ); 892 } 893 } 894 895 896 echo '</td>'; 842 897 break; 843 898 default: 844 899 $classes = "$column_name column-$column_name $class"; … … class WP_Plugins_List_Table extends WP_List_Table { 871 926 * @param array $plugin_data An array of plugin data. 872 927 * @param string $status Status of the plugin. Defaults are 'All', 'Active', 873 928 * 'Inactive', 'Recently Activated', 'Upgrade', 'Must-Use', 874 * 'Drop-ins', 'Search' .929 * 'Drop-ins', 'Search', 'Paused'. 875 930 */ 876 931 do_action( 'after_plugin_row', $plugin_file, $plugin_data, $status ); 877 932 … … class WP_Plugins_List_Table extends WP_List_Table { 887 942 * @param array $plugin_data An array of plugin data. 888 943 * @param string $status Status of the plugin. Defaults are 'All', 'Active', 889 944 * 'Inactive', 'Recently Activated', 'Upgrade', 'Must-Use', 890 * 'Drop-ins', 'Search' .945 * 'Drop-ins', 'Search', 'Paused' 891 946 */ 892 947 do_action( "after_plugin_row_{$plugin_file}", $plugin_file, $plugin_data, $status ); 893 948 } -
src/wp-admin/includes/plugin.php
diff --git src/wp-admin/includes/plugin.php src/wp-admin/includes/plugin.php index c898fc5169..248df5a675 100644
function _get_dropins() { 444 444 'install.php' => array( __( 'Custom installation script.' ), true ), // auto on installation 445 445 'maintenance.php' => array( __( 'Custom maintenance message.' ), true ), // auto on maintenance 446 446 '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 447 449 ); 448 450 449 451 if ( is_multisite() ) { … … function is_plugin_inactive( $plugin ) { 496 498 return ! is_plugin_active( $plugin ); 497 499 } 498 500 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 * @return bool True, if in the list of paused plugins. False, not in the list. 513 */ 514 function is_plugin_paused( $plugin ) { 515 if ( ! isset( $GLOBALS['_paused_plugins'] ) ) { 516 return false; 517 } 518 519 if ( ! is_plugin_active( $plugin ) || is_plugin_active_for_network( $plugin ) ) { 520 return false; 521 } 522 523 list( $plugin ) = explode( '/', $plugin ); 524 525 return array_key_exists( $plugin, $GLOBALS['_paused_plugins'] ); 526 } 527 528 /** 529 * Gets the error that was recorded for a paused plugin. 530 * 531 * @since 5.0.0 532 * 533 * @param string $plugin Path to the plugin file relative to the plugins 534 * directory. 535 * @return array|false Array of error information as it was returned by 536 * `error_get_last()`, or false if none was recorded. 537 */ 538 function wp_get_plugin_error( $plugin ) { 539 if ( ! isset( $GLOBALS['_paused_plugins'] ) ) { 540 return false; 541 } 542 543 list( $plugin ) = explode( '/', $plugin ); 544 545 if ( ! array_key_exists( $plugin, $GLOBALS['_paused_plugins'] ) ) { 546 return false; 547 } 548 549 return $GLOBALS['_paused_plugins'][ $plugin ]; 550 } 551 499 552 /** 500 553 * Determines whether the plugin is active for the entire network. 501 554 * … … function deactivate_plugins( $plugins, $silent = false, $network_wide = null ) { 693 746 continue; 694 747 } 695 748 749 // Clean up the database before deactivating the plugin. 750 if ( is_plugin_paused( $plugin ) ) { 751 resume_plugin( $plugin ); 752 } 753 696 754 $network_deactivating = false !== $network_wide && is_plugin_active_for_network( $plugin ); 697 755 698 756 if ( ! $silent ) { … … function delete_plugins( $plugins, $deprecated = '' ) { 887 945 uninstall_plugin( $plugin_file ); 888 946 } 889 947 948 // Clean up the database before removing the plugin. 949 if ( is_plugin_paused( $plugin_file ) ) { 950 resume_plugin( $plugin_file ); 951 } 952 890 953 /** 891 954 * Fires immediately before a plugin deletion attempt. 892 955 * … … function delete_plugins( $plugins, $deprecated = '' ) { 959 1022 return true; 960 1023 } 961 1024 1025 /** 1026 * Tries to resume a single plugin. 1027 * 1028 * Resuming the plugin basically means removing its entry from the 1029 * `pause_on_admin` database option. 1030 * 1031 * If a redirect was provided, we first ensure the plugin does not throw fatal 1032 * errors anymore. 1033 * 1034 * The way it works is by setting the redirection to the error before trying to 1035 * include the plugin file. If the plugin fails, then the redirection will not 1036 * be overwritten with the success message and the `pause_on_admin` option 1037 * will not be updated. 1038 * 1039 * @since 5.0.0 1040 * 1041 * @param string $plugin Single plugin to resume. 1042 * @param string $redirect Optional. URL to redirect to. 1043 * 1044 * @return bool|WP_Error True on success, false if `$plugin` was not paused, 1045 * `WP_Error` on failure. 1046 */ 1047 function resume_plugin( $plugin, $redirect = '' ) { 1048 /* 1049 * We'll override this later if the plugin could be included without 1050 * creating a fatal error. 1051 */ 1052 if ( ! empty( $redirect ) ) { 1053 wp_redirect( 1054 add_query_arg( 1055 '_error_nonce', 1056 wp_create_nonce( 'plugin-resume-error_' . $plugin ), 1057 $redirect 1058 ) 1059 ); 1060 1061 // Load the plugin to test whether it throws a fatal error. 1062 ob_start(); 1063 $plugin_path = WP_PLUGIN_DIR . '/' . $plugin; 1064 wp_register_plugin_realpath( $plugin_path ); 1065 include_once $plugin_path; 1066 ob_clean(); 1067 } 1068 1069 $result = wp_forget_extension_error( 'plugins', $plugin ); 1070 1071 if ( ! $result ) { 1072 return new WP_Error( 1073 'could_not_resume_plugin', 1074 __( 'Could not resume execution of the plugin.' ) 1075 ); 1076 } 1077 1078 return true; 1079 } 1080 962 1081 /** 963 1082 * Validate active plugins 964 1083 * … … function wp_add_privacy_policy_content( $plugin_name, $policy_text ) { 2066 2185 2067 2186 WP_Privacy_Policy_Content::add( $plugin_name, $policy_text ); 2068 2187 } 2188 2189 /** 2190 * Renders an admin notice in case some plugins have been paused due to errors. 2191 * 2192 * @since 5.0.0 2193 * 2194 * @return void 2195 */ 2196 function paused_plugins_notice() { 2197 if ( 'plugins.php' === $GLOBALS['pagenow'] ) { 2198 return; 2199 } 2200 2201 if ( ! current_user_can( 'deactivate_plugins' ) ) { 2202 return; 2203 } 2204 2205 if ( ! isset( $GLOBALS['_paused_plugins'] ) || empty( $GLOBALS['_paused_plugins'] ) ) { 2206 return; 2207 } 2208 2209 echo sprintf( 2210 '<div class="notice notice-error"><p><strong>%s</strong><br>%s</p><p>%s</p></div>', 2211 __( 'One or more plugins failed to load properly.' ), 2212 __( 'You can find more details and make changes on the Plugins screen.' ), 2213 sprintf( 2214 '<a href="%s">%s</a>', 2215 admin_url( 'plugins.php?plugin_status=paused' ), 2216 'Go to the Plugins screen' 2217 ) 2218 ); 2219 } -
src/wp-admin/plugins.php
diff --git src/wp-admin/plugins.php src/wp-admin/plugins.php index 4d8c797b04..4a3e8ed77c 100644
if ( $action ) { 389 389 } 390 390 break; 391 391 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, self_admin_url( 'plugins.php?error=resuming' ) ); 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 392 413 default: 393 414 if ( isset( $_POST['checked'] ) ) { 394 415 check_admin_referer( 'bulk-plugins' ); … … if ( isset( $_GET['error'] ) ) : 480 501 $errmsg = __( 'You cannot delete a plugin while it is active on the main site.' ); 481 502 } elseif ( isset( $_GET['charsout'] ) ) { 482 503 $errmsg = sprintf( __( 'The plugin generated %d characters of <strong>unexpected output</strong> during activation. If you notice “headers already sent” messages, problems with syndication feeds or other issues, try deactivating or removing this plugin.' ), $_GET['charsout'] ); 504 } elseif ( 'resuming' === $_GET['error'] ) { 505 $errmsg = __( 'Plugin could not be resumed because it tri§ggered a <strong>fatal error</strong>.' ); 483 506 } else { 484 507 $errmsg = __( 'Plugin could not be activated because it triggered a <strong>fatal error</strong>.' ); 485 508 } … … elseif ( isset( $_GET['deleted'] ) ) : 533 556 <div id="message" class="updated notice is-dismissible"><p><?php _e( 'Selected plugins <strong>deactivated</strong>.' ); ?></p></div> 534 557 <?php elseif ( 'update-selected' == $action ) : ?> 535 558 <div id="message" class="updated notice is-dismissible"><p><?php _e( 'All selected plugins are up to date.' ); ?></p></div> 559 <?php elseif ( isset( $_GET['resume'] ) ) : ?> 560 <div id="message" class="updated notice is-dismissible"><p><?php _e( 'Execution of plugin <strong>resumed</strong>.' ); ?></p></div> 536 561 <?php endif; ?> 537 562 538 563 <div class="wrap"> -
src/wp-includes/capabilities.php
diff --git src/wp-includes/capabilities.php src/wp-includes/capabilities.php index 375648e768..e65ff0b5a2 100644
function map_meta_cap( $cap, $user_id ) { 455 455 case 'deactivate_plugins': 456 456 case 'activate_plugin': 457 457 case 'deactivate_plugin': 458 case 'resume_plugin': 458 459 $caps[] = 'activate_plugins'; 459 460 if ( is_multisite() ) { 460 461 // 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 79e445021b..0a4218681f 100644
function wp_get_active_and_valid_plugins() { 687 687 $plugins[] = WP_PLUGIN_DIR . '/' . $plugin; 688 688 } 689 689 } 690 691 /* 692 * Remove plugins from the list of active plugins when we're on an endpoint 693 * that should be protected against WSODs and the plugin appears in the 694 * `pause_on_admin` list. 695 */ 696 if ( is_protected_endpoint() ) { 697 $pause_on_admin = (array) get_option( 'pause_on_admin', array() ); 698 699 if ( ! array_key_exists( 'plugins', $pause_on_admin ) ) { 700 return $plugins; 701 } 702 703 foreach ( $plugins as $index => $plugin ) { 704 $parts = explode( 705 '/', 706 str_replace( wp_normalize_path( WP_CONTENT_DIR . '/' ), '', wp_normalize_path( $plugin ) ) 707 ); 708 709 $type = array_shift( $parts ); 710 $plugin = array_shift( $parts ); 711 712 if ( array_key_exists( $plugin, $pause_on_admin[ $type ] ) ) { 713 unset( $plugins[ $index ] ); 714 // Store list of paused plugins for displaying an admin notice. 715 $GLOBALS['_paused_plugins'][ $plugin ] = $pause_on_admin[ $type ][ $plugin ]; 716 } 717 } 718 } 719 690 720 return $plugins; 691 721 } 692 722 … … function wp_doing_ajax() { 1154 1184 return apply_filters( 'wp_doing_ajax', defined( 'DOING_AJAX' ) && DOING_AJAX ); 1155 1185 } 1156 1186 1187 /** 1188 * Determines whether we are currently on an endpoint that should be protected 1189 * against WSODs. 1190 * 1191 * @since 5.0.0 1192 * 1193 * @return bool True if the current endpoint should be protected. 1194 */ 1195 function is_protected_endpoint() { 1196 // Protect login pages. 1197 if ( 'wp-login.php' === $GLOBALS['pagenow'] ) { 1198 return true; 1199 } 1200 1201 // Protect the admin backend. 1202 if ( is_admin() && ! wp_doing_ajax() ) { 1203 return true; 1204 } 1205 1206 // Protect AJAX actions that could help resolve a fatal error should be available. 1207 if ( wp_doing_ajax() && is_protected_ajax_action() ) { 1208 return true; 1209 } 1210 1211 return false; 1212 } 1213 1214 /** 1215 * Determines whether we are currently handling an AJAX action that should be 1216 * protected against WSODs. 1217 * 1218 * @since 5.0.0 1219 * 1220 * @return bool True if the current AJAX action should be protected. 1221 */ 1222 function is_protected_ajax_action() { 1223 $actions_to_protect = array( 1224 'edit-theme-plugin-file', // Saving changes in the core code editor. 1225 'heartbeat', // Keep the heart beating. 1226 'install-plugin', // Installing a new plugin. 1227 'install-theme', // Installing a new theme. 1228 'search-plugins', // Searching in the list of plugins. 1229 'search-install-plugins', // Searching for a plugin in the plugin install screen. 1230 'update-plugin', // Update an existing plugin. 1231 'update-theme', // Update an existing theme. 1232 ); 1233 1234 if ( ! wp_doing_ajax() ) { 1235 return false; 1236 } 1237 1238 if ( ! isset( $_REQUEST['action'] ) ) { 1239 return false; 1240 } 1241 1242 if ( ! in_array( $_REQUEST['action'], $actions_to_protect, true ) ) { 1243 return false; 1244 } 1245 1246 return true; 1247 } 1248 1157 1249 /** 1158 1250 * Determines whether the current request is a WordPress cron request. 1159 1251 * … … function wp_finalize_scraping_edited_file_errors( $scrape_key ) { 1250 1342 } 1251 1343 echo "\n###### wp_scraping_result_end:$scrape_key ######\n"; 1252 1344 } 1345 1346 /** 1347 * Prunes the array of recorded extension errors. 1348 * 1349 * @since 5.0.0 1350 * 1351 * @param array $errors Array of errors to prune. 1352 * @return array Pruned array of errors. 1353 */ 1354 function wp_prune_extension_errors( $errors ) { 1355 foreach( array( 'plugins', 'mu-plugins', 'themes' ) as $type ) { 1356 if ( ! array_key_exists( $type, $errors ) ) { 1357 continue; 1358 } 1359 1360 switch( $type ) { 1361 case 'plugins': 1362 $active_plugins = array_merge( 1363 (array) get_option( 'active_plugins', array() ), 1364 (array) get_option( 'active_sitewide_plugins', array() ) 1365 ); 1366 1367 foreach( $errors[ $type ] as $plugin => $error ) { 1368 $found = false; 1369 1370 foreach ( $active_plugins as $active_plugin ) { 1371 list( $active_plugin ) = explode( '/', $active_plugin ); 1372 1373 if ( $active_plugin === $plugin ) { 1374 $found = true; 1375 break; 1376 } 1377 } 1378 1379 if ( ! $found ) { 1380 unset( $errors[ $type ][ $plugin ] ); 1381 } 1382 } 1383 1384 break; 1385 case 'mu-plugins': 1386 // TODO: Implement MU-plugin-specific behavior. 1387 break; 1388 case 'themes': 1389 // TODO: Implement theme-specific behavior. 1390 break; 1391 } 1392 1393 if ( 0 === count( $errors[ $type ] ) ) { 1394 unset( $errors[ $type ] ); 1395 } 1396 } 1397 1398 return $errors; 1399 } 1400 1401 /** 1402 * Records the extension error as a database option. 1403 * 1404 * @since 5.0.0 1405 * 1406 * @global array $wp_theme_directories 1407 * 1408 * @param array $error Error that was triggered. 1409 * @return bool Whether the error was correctly recorded. 1410 */ 1411 function wp_record_extension_error( $error ) { 1412 global $wp_theme_directories; 1413 1414 $path = ''; 1415 1416 $error_file = wp_normalize_path( $error['file'] ); 1417 $wp_plugin_dir = wp_normalize_path( WP_PLUGIN_DIR ); 1418 $wpmu_plugin_dir = wp_normalize_path( WPMU_PLUGIN_DIR ); 1419 1420 if ( 0 === strpos( $error_file, $wp_plugin_dir ) ) { 1421 $type = 'plugins'; 1422 $path = str_replace( $wp_plugin_dir . '/', '', $error_file ); 1423 } elseif ( 0 === strpos( $error_file, $wpmu_plugin_dir ) ) { 1424 $type = 'mu-plugins'; 1425 $path = str_replace( $wpmu_plugin_dir . '/', '', $error_file ); 1426 } else { 1427 foreach ( $wp_theme_directories as $theme_directory ) { 1428 $theme_directory = wp_normalize_path( $theme_directory ); 1429 if ( 0 === strpos( $error_file, $theme_directory ) ) { 1430 $type = 'themes'; 1431 $path = str_replace( $theme_directory . '/', '', $error_file ); 1432 } 1433 } 1434 } 1435 1436 if ( empty( $type ) || empty( $path ) ) { 1437 return false; 1438 } 1439 1440 $parts = explode( '/', $path ); 1441 $extension = array_shift( $parts ); 1442 1443 $errors = (array) get_option( 'pause_on_admin', array() ); 1444 1445 $modified_errors = $errors; 1446 1447 if ( ! array_key_exists( $type, $modified_errors ) ) { 1448 $modified_errors[ $type ] = array(); 1449 } 1450 1451 $modified_errors[ $type ][ $extension ] = $error; 1452 1453 $modified_errors = wp_prune_extension_errors( $modified_errors ); 1454 1455 if ( $modified_errors === $errors ) { 1456 return true; 1457 } 1458 1459 return update_option( 'pause_on_admin', $modified_errors ); 1460 } 1461 1462 /** 1463 * Forgets a previously recorded extension error again. 1464 * 1465 * @since 5.0.0 1466 * 1467 * @param string $type Type of the extension. 1468 * @param string $extension Relative path of the extension. 1469 * @return bool Whether the extension error was successfully forgotten. 1470 */ 1471 function wp_forget_extension_error( $type, $extension ) { 1472 $errors = (array) get_option( 'pause_on_admin', array() ); 1473 1474 if ( ! array_key_exists( $type, $errors ) ) { 1475 return false; 1476 } 1477 1478 $modified_errors = $errors; 1479 1480 switch ( $type ) { 1481 case 'plugins': 1482 list( $extension ) = explode( '/', $extension ); 1483 } 1484 1485 if ( array_key_exists( $extension, $modified_errors[ $type ] ) ) { 1486 unset( $modified_errors[ $type ][ $extension ] ); 1487 } 1488 1489 $modified_errors = wp_prune_extension_errors( $modified_errors ); 1490 1491 if ( $modified_errors === $errors ) { 1492 return true; 1493 } 1494 1495 return update_option( 'pause_on_admin', $modified_errors ); 1496 } 1497 1498 /** 1499 * Determines whether we are dealing with an error that WordPress should handle 1500 * in order to protect the admin backend against WSODs. 1501 * 1502 * @param array $error Error information retrieved from error_get_last(). 1503 * 1504 * @return bool Whether WordPress should handle this error. 1505 */ 1506 function wp_should_handle_error( $error ) { 1507 if ( ! isset( $error['type'] ) ) { 1508 return false; 1509 } 1510 1511 $error_types_to_handle = array( 1512 E_ERROR, 1513 E_PARSE, 1514 E_USER_ERROR, 1515 E_COMPILE_ERROR, 1516 E_RECOVERABLE_ERROR, 1517 ); 1518 1519 return in_array( $error['type'], $error_types_to_handle, true ); 1520 } 1521 1522 /** 1523 * Wraps the shutdown handler function so it can be made pluggable at a later 1524 * stage. 1525 * 1526 * @since 5.0.0 1527 * 1528 * @return void 1529 */ 1530 function wp_shutdown_handler_wrapper() { 1531 if ( defined( 'WP_EXECUTION_SUCCEEDED' ) && WP_EXECUTION_SUCCEEDED ) { 1532 return; 1533 } 1534 1535 // Load the pluggable shutdown handler in case we found one. 1536 if ( function_exists( 'wp_handle_shutdown' ) ) { 1537 $stop_propagation = false; 1538 1539 try { 1540 $stop_propagation = (bool) wp_handle_shutdown(); 1541 } catch ( Exception $exception ) { 1542 // Catch exceptions and remain silent. 1543 } 1544 1545 if ( $stop_propagation ) { 1546 return; 1547 } 1548 } 1549 1550 $error = error_get_last(); 1551 1552 // No error, just skip the error handling code. 1553 if ( null === $error ) { 1554 return; 1555 } 1556 1557 /* 1558 * If the option API has not been loaded yet, we cannot persist our 1559 * discovery, so there's no point in moving forward. 1560 */ 1561 if ( ! function_exists( 'get_option' ) ) { 1562 return; 1563 } 1564 1565 // Bail early if this error should not be handled. 1566 if ( ! wp_should_handle_error( $error ) ) { 1567 return; 1568 } 1569 1570 try { 1571 wp_record_extension_error( $error ); 1572 1573 // Load custom PHP error template, if present. 1574 $php_error_pluggable = WP_CONTENT_DIR . '/php-error.php'; 1575 if ( is_readable( $php_error_pluggable ) ) { 1576 /* 1577 * The pluggable should control the HTTP return code and die itself. 1578 * In case it doesn't, execution is continued here and will show the 1579 * default message and die with a 500. 1580 */ 1581 include $php_error_pluggable; 1582 } 1583 1584 $message = sprintf( 1585 '<p>%s</p>', 1586 __( 'The site is experiencing technical difficulties.' ) 1587 ); 1588 1589 if ( function_exists( 'get_admin_url' ) ) { 1590 $url = get_admin_url(); 1591 $message .= sprintf( 1592 '<hr><p><em>%s <a href="%s">%s</a></em></p>', 1593 __( 'Are you the site owner?' ), 1594 $url, 1595 __( 'Log into the admin backend to fix this.' ) 1596 ); 1597 } 1598 1599 if ( function_exists( 'apply_filters' ) ) { 1600 /** 1601 * Filters the message that the default PHP error page displays. 1602 * 1603 * @since 5.0.0 1604 * 1605 * @param string $message HTML error message to display. 1606 */ 1607 $message = apply_filters( 'wp_technical_issues_display', $message ); 1608 } 1609 1610 /* 1611 * If we happen to be on a protected endpoint, we try to redirect to 1612 * catch multiple errors in one go. 1613 */ 1614 if ( function_exists( 'is_protected_endpoint' ) 1615 && is_protected_endpoint() ) { 1616 /* 1617 * Pluggable is usually loaded after plugins, so we manually 1618 * include it here for redirection functionality. 1619 */ 1620 if ( ! function_exists( 'wp_redirect' ) ) { 1621 include ABSPATH . WPINC . '/pluggable.php'; 1622 } 1623 1624 // Scheme defaults to https:// if is_ssl() is not available. 1625 $scheme = ( ! function_exists( 'is_ssl' ) || is_ssl() ) 1626 ? 'https://' 1627 : 'http://'; 1628 1629 $url = "{$scheme}{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}"; 1630 wp_redirect( $url ); 1631 exit; 1632 } 1633 1634 wp_die( $message, '', 500 ); 1635 1636 } catch ( Exception $exception ) { 1637 // Catch exceptions and remain silent. 1638 } 1639 } 1640 1641 /** 1642 * Registers the WordPress premature shutdown handler. 1643 * 1644 * @since 5.0.0 1645 * 1646 * @return void 1647 */ 1648 function wp_register_premature_shutdown_handler() { 1649 register_shutdown_function( 'wp_shutdown_handler_wrapper' ); 1650 } -
src/wp-settings.php
diff --git src/wp-settings.php src/wp-settings.php index eb632238f0..c633f05b1d 100644
require( ABSPATH . WPINC . '/load.php' ); 20 20 require( ABSPATH . WPINC . '/default-constants.php' ); 21 21 require_once( ABSPATH . WPINC . '/plugin.php' ); 22 22 23 // Make sure we register the premature shutdown handler as soon as possible. 24 wp_register_premature_shutdown_handler(); 25 23 26 /* 24 27 * These can't be directly globalized in version.php. When updating, 25 28 * we're including version.php from another installation and don't want … … global $blog_id; 40 43 // Set initial default constants including WP_MEMORY_LIMIT, WP_MAX_MEMORY_LIMIT, WP_DEBUG, SCRIPT_DEBUG, WP_CONTENT_DIR and WP_CACHE. 41 44 wp_initial_constants(); 42 45 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 */ 52 if ( is_readable( WP_CONTENT_DIR . '/shutdown-handler.php' ) ) { 53 include WP_CONTENT_DIR . '/shutdown-handler.php'; 54 } 55 43 56 // Check for the required PHP version and for the MySQL extension or a database drop-in. 44 57 wp_check_php_mysql_versions(); 45 58 … … if ( is_multisite() ) { 482 495 * @since 3.0.0 483 496 */ 484 497 do_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 */ 504 if ( ! defined( 'WP_EXECUTION_SUCCEEDED' ) ) { 505 define( 'WP_EXECUTION_SUCCEEDED', true ); 506 }