Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Catch WSODs and provide a means for recovery for end users #3

Closed
wants to merge 73 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
bc7c925
Port-over 44458.6.diff from Trac.
Jul 30, 2018
e2621f2
Merge branch 'master' into 44458
schlessera Aug 19, 2018
421ac71
Fix erroneous info in is_plaugin_paused() @return doc tag
schlessera Aug 19, 2018
15a1dc2
Die with a 500 instead of a 200
schlessera Aug 19, 2018
e472c52
Protect specific AJAX actions from WSODs
schlessera Aug 19, 2018
9ca6a03
Add multiple types of errors to be caught
schlessera Aug 20, 2018
128dca1
Wrap the pluggable into a try/catch block
schlessera Aug 20, 2018
80eee40
Adapt pluggable template handling
schlessera Aug 20, 2018
4852467
Sandbox resuming a plugin to turn a new error into a dashboard notice…
schlessera Aug 21, 2018
fc7263a
Extract endpoint criteria to protect into separate function
schlessera Aug 21, 2018
741f614
Fix typo in comment
schlessera Aug 21, 2018
63ff519
Add redirection on protected endpoints
schlessera Aug 21, 2018
0d3a399
Combine two conditionals
schlessera Aug 21, 2018
f870c66
Merge branch 'master' into 44458
schlessera Aug 27, 2018
6341d89
Merge branch 'master' into 44458
schlessera Oct 1, 2018
d762d2e
Fix `'shutdownhandler.php'` pluggable file name, which was missing th…
schlessera Oct 1, 2018
dbe5bc0
Still show custom error page if detected error cannot be persisted
schlessera Oct 1, 2018
17a4bec
Merge branch 'master' into 44458
felixarntz Oct 2, 2018
ffde485
Introduce filter to allow providing further protected endpoints.
felixarntz Oct 2, 2018
8e69ea3
Fix WP Coding Standards violations.
felixarntz Oct 2, 2018
e028d5e
Remove unnecessary function_exists() checks.
felixarntz Oct 2, 2018
1fc6ea1
Move check for whether get_option() exists into wp_record_extension_e…
felixarntz Oct 2, 2018
671d27a
Move redirection logic for multiple errors above custom php-error.php…
felixarntz Oct 2, 2018
48c4b18
Fix typo.
felixarntz Oct 2, 2018
20ed46d
Merge branch 'master' into 44458
felixarntz Oct 8, 2018
7a9ac8e
Update version numbers to 5.1.0.
felixarntz Oct 8, 2018
1ca98a9
Allow regular administrators to resume a plugin in multisite.
felixarntz Oct 8, 2018
ea160ac
Skip loading network-active plugins that are paused.
felixarntz Oct 8, 2018
4a1e0e1
Do not handle MU-plugins as these should never be paused.
felixarntz Oct 8, 2018
6637b5c
Introduce a simple class for storing paused extensions and store erro…
felixarntz Oct 8, 2018
fa7211f
Move essential error-protection functions to a new error-protection.p…
felixarntz Oct 8, 2018
fe38caf
Fix bug with retrieving stored extension errors in multisite.
felixarntz Oct 8, 2018
bd8fae2
Fix a possible PHP notice in the network admin.
felixarntz Oct 8, 2018
47d386b
Adjust check in is_plugin_paused() to account for multisite.
felixarntz Oct 8, 2018
3e8bfe7
Introduce support for resuming plugins network-wide and displaying in…
felixarntz Oct 8, 2018
4c02213
Merge branch 'master' into 44458
felixarntz Dec 17, 2018
3145368
Merge branch 'master' into 44458
felixarntz Dec 27, 2018
7cbb4e3
Use plugin_sandbox_scrape() instead of rewriting identical functional…
felixarntz Dec 27, 2018
42b1205
Add missing is_site_meta_supported() check.
felixarntz Dec 27, 2018
2c7810b
Fix incorrect doc.
felixarntz Dec 27, 2018
11b6deb
Introduce method to retrieve a single paused extension error.
felixarntz Dec 27, 2018
52aea23
Introduce function to retrieve active themes, filtering out paused th…
felixarntz Dec 27, 2018
d7003fb
Introduce wp_using_themes() function and filter it to false when them…
felixarntz Dec 27, 2018
fb60bea
Show an admin notice when a theme is paused.
felixarntz Dec 27, 2018
d1cae9c
Add admin functions for detecting whether a theme is paused, retrievi…
felixarntz Dec 27, 2018
ad88019
Include error in theme instance when it is paused, so that it renders…
felixarntz Dec 27, 2018
4f84f08
Introduce new capability to resume themes.
felixarntz Dec 27, 2018
489e43e
Allow resuming a paused theme.
felixarntz Dec 27, 2018
5220f85
Remove unused LIKE meta query that might cause headaches in the future.
felixarntz Dec 27, 2018
54e9d0b
Replace echo sprintf occurrences with printf.
felixarntz Dec 28, 2018
f147375
Merge branch 'master' into 44458
felixarntz Jan 7, 2019
851cfe7
Introduce WP_Shutdown_Handler class for the WSOD protection and allow…
felixarntz Jan 7, 2019
9753e40
Add two missing periods.
felixarntz Jan 7, 2019
3809a1c
Don't throw errors on missing constants
schlessera Jan 9, 2019
2b73834
Ensure passed-in $error information is valid
schlessera Jan 9, 2019
3061589
Be extra safe with multisite load status
schlessera Jan 9, 2019
bd47606
Revert "Be extra safe with multisite load status"
schlessera Jan 9, 2019
0a595a5
Change storage methods from set/unset to add/remove to avoid conflict…
schlessera Jan 9, 2019
ea95b60
Remove unrelated translator comment additions which should go into a …
schlessera Jan 9, 2019
885bae6
Make $extension argument for query clause required for now to avoid s…
schlessera Jan 9, 2019
6c63be1
Prefer less precise isset() to array_key_exists() to match rest of code
schlessera Jan 9, 2019
9bfabcc
Another safeguard for missing WP_CONTENT_DIR constant
schlessera Jan 9, 2019
0528ff5
Use stub for __() if it does not exist yet
schlessera Jan 9, 2019
e9f6b84
Get rid of unneeded i18n method
schlessera Jan 9, 2019
f0c175e
Reorder AJAX protected endpoint detection
schlessera Jan 9, 2019
ce30fc3
Add 'wp_protected_ajax_actions' filter
schlessera Jan 9, 2019
c433600
Fix bug in isset logic
schlessera Jan 9, 2019
5d38162
Skip pausing of security-related plugins to avoid opening up an attac…
schlessera Jan 9, 2019
489495f
Revert "Skip pausing of security-related plugins to avoid opening up …
felixarntz Jan 9, 2019
d9e59f9
Rename WP_Paused_Extensions_Storage methods more accurately and adjus…
felixarntz Jan 9, 2019
d3ede87
Fix coding standards violation.
felixarntz Jan 9, 2019
9c078c1
Adjust translation strings to get rid of the term execution and match…
felixarntz Jan 9, 2019
ecfd731
Merge branch 'master' into 44458
felixarntz Jan 9, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions src/wp-admin/css/list-tables.css
Original file line number Diff line number Diff line change
Expand Up @@ -1301,6 +1301,31 @@ ul.cat-checklist {
text-decoration: underline;
}

.plugins tr.paused th.check-column {
border-left: 4px solid #d54e21;
}

.plugins tr.paused th,
.plugins tr.paused td {
background-color: #fef7f1;
}

.plugins tr.paused .plugin-title,
.plugins .paused .dashicons-warning {
color: #dc3232;
}

.plugins .paused .error-display p,
.plugins .paused .error-display code {
font-size: 90%;
font-style: italic;
color: rgb( 0, 0, 0, 0.7 );
}

.plugins .resume-link {
color: #dc3232;
}

.plugin-card .update-now:before {
color: #f56e28;
content: "\f463";
Expand Down
2 changes: 2 additions & 0 deletions src/wp-admin/includes/admin-filters.php
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@
add_action( 'load-themes.php', 'wp_theme_update_rows', 20 ); // After wp_update_themes() is called.

add_action( 'admin_notices', 'update_nag', 3 );
add_action( 'admin_notices', 'paused_plugins_notice', 5 );
add_action( 'admin_notices', 'paused_themes_notice', 5 );
add_action( 'admin_notices', 'maintenance_nag', 10 );

add_filter( 'update_footer', 'core_update_footer' );
Expand Down
85 changes: 80 additions & 5 deletions src/wp-admin/includes/class-wp-plugins-list-table.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public function __construct( $args = array() ) {
);

$status = 'all';
if ( isset( $_REQUEST['plugin_status'] ) && in_array( $_REQUEST['plugin_status'], array( 'active', 'inactive', 'recently_activated', 'upgrade', 'mustuse', 'dropins', 'search' ) ) ) {
if ( isset( $_REQUEST['plugin_status'] ) && in_array( $_REQUEST['plugin_status'], array( 'active', 'inactive', 'recently_activated', 'upgrade', 'mustuse', 'dropins', 'search', 'paused' ) ) ) {
$status = $_REQUEST['plugin_status'];
}

Expand Down Expand Up @@ -99,6 +99,7 @@ public function prepare_items() {
'upgrade' => array(),
'mustuse' => array(),
'dropins' => array(),
'paused' => array(),
);

$screen = $this->screen;
Expand Down Expand Up @@ -209,6 +210,9 @@ public function prepare_items() {
if ( $show_network_active ) {
// On the non-network screen, show network-active plugins if allowed
$plugins['active'][ $plugin_file ] = $plugin_data;
if ( is_plugin_paused( $plugin_file ) ) {
$plugins['paused'][ $plugin_file ] = $plugin_data;
}
} else {
// On the non-network screen, filter out network-active plugins
unset( $plugins['all'][ $plugin_file ] );
Expand All @@ -218,6 +222,9 @@ public function prepare_items() {
// On the non-network screen, populate the active list with plugins that are individually activated
// On the network-admin screen, populate the active list with plugins that are network activated
$plugins['active'][ $plugin_file ] = $plugin_data;
if ( is_plugin_paused( $plugin_file ) ) {
$plugins['paused'][ $plugin_file ] = $plugin_data;
}
} else {
if ( isset( $recently_activated[ $plugin_file ] ) ) {
// Populate the recently activated list with plugins that have been recently activated
Expand Down Expand Up @@ -438,6 +445,10 @@ protected function get_views() {
case 'dropins':
$text = _n( 'Drop-ins <span class="count">(%s)</span>', 'Drop-ins <span class="count">(%s)</span>', $count );
break;
case 'paused':
/* translators: %s: plugin count */
$text = _n( 'Paused <span class="count">(%s)</span>', 'Paused <span class="count">(%s)</span>', $count );
break;
case 'upgrade':
$text = _n( 'Update Available <span class="count">(%s)</span>', 'Update Available <span class="count">(%s)</span>', $count );
break;
Expand Down Expand Up @@ -625,11 +636,19 @@ public function single_row( $item ) {
/* translators: %s: plugin name */
$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>';
}
if ( current_user_can( 'manage_network_plugins' ) && count_paused_plugin_sites_for_network( $plugin_file ) ) {
/* translators: %s: plugin name */
$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>';
}
} else {
if ( current_user_can( 'manage_network_plugins' ) ) {
/* translators: %s: plugin name */
$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>';
}
if ( current_user_can( 'manage_network_plugins' ) && count_paused_plugin_sites_for_network( $plugin_file ) ) {
/* translators: %s: plugin name */
$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>';
}
if ( current_user_can( 'delete_plugins' ) && ! is_plugin_active( $plugin_file ) ) {
/* translators: %s: plugin name */
$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>';
Expand All @@ -640,6 +659,10 @@ public function single_row( $item ) {
$actions = array(
'network_active' => __( 'Network Active' ),
);
if ( ! $restrict_network_only && current_user_can( 'resume_plugin' ) && is_plugin_paused( $plugin_file ) ) {
/* translators: %s: plugin name */
$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>';
}
} elseif ( $restrict_network_only ) {
$actions = array(
'network_only' => __( 'Network Only' ),
Expand All @@ -649,6 +672,10 @@ public function single_row( $item ) {
/* translators: %s: plugin name */
$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>';
}
if ( current_user_can( 'resume_plugin' ) && is_plugin_paused( $plugin_file ) ) {
/* translators: %s: plugin name */
$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>';
}
} else {
if ( current_user_can( 'activate_plugin', $plugin_file ) ) {
/* translators: %s: plugin name */
Expand Down Expand Up @@ -755,6 +782,12 @@ public function single_row( $item ) {
$class .= ' update';
}

$paused = is_plugin_paused( $plugin_file );
$paused_on_network_sites_count = $screen->in_admin( 'network' ) ? count_paused_plugin_sites_for_network( $plugin_file ) : 0;
if ( $paused || $paused_on_network_sites_count ) {
$class .= ' paused';
}

$plugin_slug = isset( $plugin_data['slug'] ) ? $plugin_data['slug'] : sanitize_title( $plugin_name );
printf(
'<tr class="%s" data-slug="%s" data-plugin="%s">',
Expand Down Expand Up @@ -833,12 +866,54 @@ public function single_row( $item ) {
* @param array $plugin_data An array of plugin data.
* @param string $status Status of the plugin. Defaults are 'All', 'Active',
* 'Inactive', 'Recently Activated', 'Upgrade', 'Must-Use',
* 'Drop-ins', 'Search'.
* 'Drop-ins', 'Search', 'Paused'.
*/
$plugin_meta = apply_filters( 'plugin_row_meta', $plugin_meta, $plugin_file, $plugin_data, $status );
echo implode( ' | ', $plugin_meta );

echo '</div></td>';
echo '</div>';

if ( $paused || $paused_on_network_sites_count ) {
$notice_text = __( 'This plugin failed to load properly and was paused within the admin backend.' );
if ( $screen->in_admin( 'network' ) && $paused_on_network_sites_count ) {
$notice_text = sprintf(
/* translators: %s: number of sites */
_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 ),
number_format_i18n( $paused_on_network_sites_count )
);
}

printf( '<p><span class="dashicons dashicons-warning"></span> <strong>%s</strong></p>', $notice_text );

$error = wp_get_plugin_error( $plugin_file );

if ( false !== $error ) {
$constants = get_defined_constants( true );
$constants = isset( $constants['Core'] ) ? $constants['Core'] : $constants['internal'];

foreach ( $constants as $constant => $value ) {
if ( 0 === strpos( $constant, 'E_' ) ) {
$core_errors[ $value ] = $constant;
}
}

$error['type'] = $core_errors[ $error['type'] ];

printf(
'<div class="error-display"><p>%s</p></div>',
sprintf(
/* translators: 1: error type, 2: error line number, 3: error file name, 4: error message */
__( 'The plugin caused an error of type %1$s in line %2$s of the file %3$s. Error message: %4$s' ),
"<code>{$error['type']}</code>",
"<code>{$error['line']}</code>",
"<code>{$error['file']}</code>",
"<code>{$error['message']}</code>"
)
);
}
}

echo '</td>';
break;
default:
$classes = "$column_name column-$column_name $class";
Expand Down Expand Up @@ -871,7 +946,7 @@ public function single_row( $item ) {
* @param array $plugin_data An array of plugin data.
* @param string $status Status of the plugin. Defaults are 'All', 'Active',
* 'Inactive', 'Recently Activated', 'Upgrade', 'Must-Use',
* 'Drop-ins', 'Search'.
* 'Drop-ins', 'Search', 'Paused'.
*/
do_action( 'after_plugin_row', $plugin_file, $plugin_data, $status );

Expand All @@ -887,7 +962,7 @@ public function single_row( $item ) {
* @param array $plugin_data An array of plugin data.
* @param string $status Status of the plugin. Defaults are 'All', 'Active',
* 'Inactive', 'Recently Activated', 'Upgrade', 'Must-Use',
* 'Drop-ins', 'Search'.
* 'Drop-ins', 'Search', 'Paused'.
*/
do_action( "after_plugin_row_{$plugin_file}", $plugin_file, $plugin_data, $status );
}
Expand Down
Loading