Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions src/wp-admin/includes/admin-filters.php
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@
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', 'maintenance_nag', 10 );

add_filter( 'update_footer', 'core_update_footer' );
Expand Down
65 changes: 60 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 @@ -436,6 +443,9 @@ 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':
$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 @@ -647,6 +657,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 execution of %s', 'plugin' ), $plugin_data['Name'] ) ) . '">' . __( 'Resume execution' ) . '</a>';
}
} else {
if ( current_user_can( 'activate_plugin', $plugin_file ) ) {
/* translators: %s: plugin name */
Expand Down Expand Up @@ -753,6 +767,11 @@ public function single_row( $item ) {
$class .= ' update';
}

$paused = is_plugin_paused( $plugin_file );
if ( $paused ) {
$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 @@ -831,12 +850,48 @@ 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 ) {
echo sprintf(
'<p><span class="dashicons dashicons-warning"></span> <strong>%s</strong></p>',
__( 'This plugin failed to load properly and was paused within the admin backend.' )
);

$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'] ];

echo sprintf(
'<div class="error-display"><p>%s</p></div>',
sprintf(
__( '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 @@ -869,7 +924,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 @@ -885,7 +940,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
117 changes: 117 additions & 0 deletions src/wp-admin/includes/plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,8 @@ function _get_dropins() {
'install.php' => array( __( 'Custom installation script.' ), true ), // auto on installation
'maintenance.php' => array( __( 'Custom maintenance message.' ), true ), // auto on maintenance
'object-cache.php' => array( __( 'External object cache.' ), true ), // auto on load
'php-error.php' => array( __( 'Custom PHP error message.' ), true ), // auto on error
'shutdown-handler' => array( __( 'Custom PHP shutdown handler.' ), true ), // auto on error
);

if ( is_multisite() ) {
Expand Down Expand Up @@ -496,6 +498,57 @@ function is_plugin_inactive( $plugin ) {
return ! is_plugin_active( $plugin );
}

/**
* Determines whether a plugin is technically active but was paused while
* loading.
*
* For more information on this and similar theme functions, check out
* the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
* Conditional Tags} article in the Theme Developer Handbook.
*
* @since 5.0.0
*
* @param string $plugin Path to the plugin file relative to the plugins directory.
* @return bool True, if in the active plugins list. False, not in the list.
*/
function is_plugin_paused( $plugin ) {
if ( ! isset( $GLOBALS['_paused_plugins'] ) ) {
return false;
}

if ( ! is_plugin_active( $plugin ) || is_plugin_active_for_network( $plugin ) ) {
return false;
}

list( $plugin ) = explode( '/', $plugin );

return array_key_exists( $plugin, $GLOBALS['_paused_plugins'] );
}

/**
* Gets the error that was recorded for a paused plugin.
*
* @since 5.0.0
*
* @param string $plugin Path to the plugin file relative to the plugins
* directory.
* @return array|false Array of error information as it was returned by
* `error_get_last()`, or false if none was recorded.
*/
function wp_get_plugin_error( $plugin ) {
if ( ! isset( $GLOBALS['_paused_plugins'] ) ) {
return false;
}

list( $plugin ) = explode( '/', $plugin );

if ( ! array_key_exists( $plugin, $GLOBALS['_paused_plugins'] ) ) {
return false;
}

return $GLOBALS['_paused_plugins'][ $plugin ];
}

/**
* Determines whether the plugin is active for the entire network.
*
Expand Down Expand Up @@ -693,6 +746,11 @@ function deactivate_plugins( $plugins, $silent = false, $network_wide = null ) {
continue;
}

// Clean up the database before deactivating the plugin.
if ( is_plugin_paused( $plugin ) ) {
resume_plugin( $plugin );
}

$network_deactivating = false !== $network_wide && is_plugin_active_for_network( $plugin );

if ( ! $silent ) {
Expand Down Expand Up @@ -887,6 +945,11 @@ function delete_plugins( $plugins, $deprecated = '' ) {
uninstall_plugin( $plugin_file );
}

// Clean up the database before removing the plugin.
if ( is_plugin_paused( $plugin_file ) ) {
resume_plugin( $plugin_file );
}

/**
* Fires immediately before a plugin deletion attempt.
*
Expand Down Expand Up @@ -959,6 +1022,28 @@ function delete_plugins( $plugins, $deprecated = '' ) {
return true;
}

/**
* Resumes a single plugin.
*
* Resuming the plugin basically means removing its entry from the
* `pause_on_admin` database option.
*
* @since 5.0.0
*
* @param string $plugin Single plugin to resume.
*
* @return bool|WP_Error True on success, false if `$plugin` was not paused, `WP_Error` on failure.
*/
function resume_plugin( $plugin ) {
$result = wp_forget_extension_error( 'plugins', $plugin );

if ( ! $result ) {
return new WP_Error( 'could_not_resume_plugin', __( 'Could not resume execution of the plugin.' ) );
}

return true;
}

/**
* Validate active plugins
*
Expand Down Expand Up @@ -2066,3 +2151,35 @@ function wp_add_privacy_policy_content( $plugin_name, $policy_text ) {

WP_Privacy_Policy_Content::add( $plugin_name, $policy_text );
}

/**
* Renders an admin notice in case some plugins have been paused due to errors.
*
* @since 5.0.0
*
* @return void
*/
function paused_plugins_notice() {
if ( 'plugins.php' === $GLOBALS['pagenow'] ) {
return;
}

if ( ! current_user_can( 'deactivate_plugins' ) ) {
return;
}

if ( ! isset( $GLOBALS['_paused_plugins'] ) || empty( $GLOBALS['_paused_plugins'] ) ) {
return;
}

echo sprintf(
'<div class="notice notice-error"><p><strong>%s</strong><br>%s</p><p>%s</p></div>',
__( 'One or more plugins failed to load properly.' ),
__( 'You can find more details and make changes on the Plugins screen.' ),
sprintf(
'<a href="%s">%s</a>',
admin_url( 'plugins.php?plugin_status=paused' ),
'Go to the Plugins screen'
)
);
}
23 changes: 23 additions & 0 deletions src/wp-admin/plugins.php
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,27 @@
}
break;

case 'resume':
if ( ! current_user_can( 'resume_plugin', $plugin ) ) {
wp_die( __( 'Sorry, you are not allowed to resume execution of this plugin.' ) );
}

if ( is_multisite() && ! is_network_admin() && is_network_only_plugin( $plugin ) ) {
wp_redirect( self_admin_url( "plugins.php?plugin_status=$status&paged=$page&s=$s" ) );
exit;
}

check_admin_referer( 'resume-plugin_' . $plugin );

$result = resume_plugin( $plugin );

if ( is_wp_error( $result ) ) {
wp_die( $result );
}

wp_redirect( self_admin_url( "plugins.php?resume=true&plugin_status=$status&paged=$page&s=$s" ) );
exit;

default:
if ( isset( $_POST['checked'] ) ) {
check_admin_referer( 'bulk-plugins' );
Expand Down Expand Up @@ -532,6 +553,8 @@
<div id="message" class="updated notice is-dismissible"><p><?php _e( 'Selected plugins <strong>deactivated</strong>.' ); ?></p></div>
<?php elseif ( 'update-selected' == $action ) : ?>
<div id="message" class="updated notice is-dismissible"><p><?php _e( 'All selected plugins are up to date.' ); ?></p></div>
<?php elseif ( isset( $_GET['resume'] ) ) : ?>
<div id="message" class="updated notice is-dismissible"><p><?php _e( 'Execution of plugin <strong>resumed</strong>.' ); ?></p></div>
<?php endif; ?>

<div class="wrap">
Expand Down
1 change: 1 addition & 0 deletions src/wp-includes/capabilities.php
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,7 @@ function map_meta_cap( $cap, $user_id ) {
case 'deactivate_plugins':
case 'activate_plugin':
case 'deactivate_plugin':
case 'resume_plugin':
$caps[] = 'activate_plugins';
if ( is_multisite() ) {
// update_, install_, and delete_ are handled above with is_super_admin().
Expand Down
Loading