From c7a79b0e6a1b2aa215efcecbc96245076614b6aa Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Tue, 22 Aug 2017 14:02:44 +0000 Subject: [PATCH] Plugins: Introduce singular capabilities for activating and deactivating individual plugins. This introduces the following meta capabilities: * `activate_plugin` * `deactivate_plugin` * `deactivate_plugins` The singular `activate_plugin` and `deactivate_plugin` capabilities are used along with the corresponding plugin name when determining whether or not a user can activate or deactivate an individual plugin. The plural `deactivate_plugins` capability is used in place of the existing `activate_plugins` capability when determining whether a user can deactivate plugins. Each of these new meta capabilities map to the existing `activate_plugins` primitive capability, which means there is no change in existing behaviour, but plugins can now filter the capabilities required to activate and deactivate individual plugins. Fixes #38652 Built from https://develop.svn.wordpress.org/trunk@41290 git-svn-id: http://core.svn.wordpress.org/trunk@41130 1a063a9b-81f0-0310-95a4-ce76da25c4cd --- wp-admin/includes/ajax-actions.php | 2 +- .../includes/class-plugin-installer-skin.php | 2 +- .../includes/class-plugin-upgrader-skin.php | 2 +- .../class-wp-plugin-install-list-table.php | 2 +- .../includes/class-wp-plugins-list-table.php | 12 +++++--- wp-admin/plugins.php | 30 ++++++++++++++----- wp-includes/capabilities.php | 5 +++- wp-includes/version.php | 2 +- 8 files changed, 40 insertions(+), 17 deletions(-) diff --git a/wp-admin/includes/ajax-actions.php b/wp-admin/includes/ajax-actions.php index 6f33849fd2..4952415149 100644 --- a/wp-admin/includes/ajax-actions.php +++ b/wp-admin/includes/ajax-actions.php @@ -3704,7 +3704,7 @@ function wp_ajax_install_plugin() { // If installation request is coming from import page, do not return network activation link. $plugins_url = ( 'import' === $pagenow ) ? admin_url( 'plugins.php' ) : network_admin_url( 'plugins.php' ); - if ( current_user_can( 'activate_plugins' ) && is_plugin_inactive( $install_status['file'] ) ) { + if ( current_user_can( 'activate_plugin', $install_status['file'] ) && is_plugin_inactive( $install_status['file'] ) ) { $status['activateUrl'] = add_query_arg( array( '_wpnonce' => wp_create_nonce( 'activate-plugin_' . $install_status['file'] ), 'action' => 'activate', diff --git a/wp-admin/includes/class-plugin-installer-skin.php b/wp-admin/includes/class-plugin-installer-skin.php index 778313f421..d179fde67f 100644 --- a/wp-admin/includes/class-plugin-installer-skin.php +++ b/wp-admin/includes/class-plugin-installer-skin.php @@ -71,7 +71,7 @@ class Plugin_Installer_Skin extends WP_Upgrader_Skin { if ( ! $this->result || is_wp_error($this->result) ) { unset( $install_actions['activate_plugin'], $install_actions['network_activate'] ); - } elseif ( ! current_user_can( 'activate_plugins' ) ) { + } elseif ( ! current_user_can( 'activate_plugin', $plugin_file ) ) { unset( $install_actions['activate_plugin'] ); } diff --git a/wp-admin/includes/class-plugin-upgrader-skin.php b/wp-admin/includes/class-plugin-upgrader-skin.php index 105a6b9752..ba8c30fbc4 100644 --- a/wp-admin/includes/class-plugin-upgrader-skin.php +++ b/wp-admin/includes/class-plugin-upgrader-skin.php @@ -51,7 +51,7 @@ class Plugin_Upgrader_Skin extends WP_Upgrader_Skin { 'activate_plugin' => '' . __( 'Activate Plugin' ) . '', 'plugins_page' => '' . __( 'Return to Plugins page' ) . '' ); - if ( $this->plugin_active || ! $this->result || is_wp_error( $this->result ) || ! current_user_can( 'activate_plugins' ) ) + if ( $this->plugin_active || ! $this->result || is_wp_error( $this->result ) || ! current_user_can( 'activate_plugin', $this->plugin ) ) unset( $update_actions['activate_plugin'] ); /** diff --git a/wp-admin/includes/class-wp-plugin-install-list-table.php b/wp-admin/includes/class-wp-plugin-install-list-table.php index bf6cf3d71b..46eca16143 100644 --- a/wp-admin/includes/class-wp-plugin-install-list-table.php +++ b/wp-admin/includes/class-wp-plugin-install-list-table.php @@ -468,7 +468,7 @@ class WP_Plugin_Install_List_Table extends WP_List_Table { case 'newer_installed': if ( is_plugin_active( $status['file'] ) ) { $action_links[] = ''; - } elseif ( current_user_can( 'activate_plugins' ) ) { + } elseif ( current_user_can( 'activate_plugin', $status['file'] ) ) { $button_text = __( 'Activate' ); /* translators: %s: Plugin name */ $button_label = _x( 'Activate %s', 'plugin' ); diff --git a/wp-admin/includes/class-wp-plugins-list-table.php b/wp-admin/includes/class-wp-plugins-list-table.php index 41b36d45d7..7073e79051 100644 --- a/wp-admin/includes/class-wp-plugins-list-table.php +++ b/wp-admin/includes/class-wp-plugins-list-table.php @@ -620,11 +620,15 @@ class WP_Plugins_List_Table extends WP_List_Table { 'network_only' => __( 'Network Only' ), ); } elseif ( $is_active ) { - /* translators: %s: plugin name */ - $actions['deactivate'] = '' . __( 'Deactivate' ) . ''; + if ( current_user_can( 'deactivate_plugin', $plugin_file ) ) { + /* translators: %s: plugin name */ + $actions['deactivate'] = '' . __( 'Deactivate' ) . ''; + } } else { - /* translators: %s: plugin name */ - $actions['activate'] = '' . __( 'Activate' ) . ''; + if ( current_user_can( 'activate_plugin', $plugin_file ) ) { + /* translators: %s: plugin name */ + $actions['activate'] = '' . __( 'Activate' ) . ''; + } if ( ! is_multisite() && current_user_can( 'delete_plugins' ) ) { /* translators: %s: plugin name */ diff --git a/wp-admin/plugins.php b/wp-admin/plugins.php index 3d7ff016e5..e19f5849e9 100644 --- a/wp-admin/plugins.php +++ b/wp-admin/plugins.php @@ -29,8 +29,9 @@ if ( $action ) { switch ( $action ) { case 'activate': - if ( ! current_user_can('activate_plugins') ) - wp_die(__('Sorry, you are not allowed to activate plugins for this site.')); + if ( ! current_user_can( 'activate_plugin', $plugin ) ) { + wp_die( __( 'Sorry, you are not allowed to activate 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") ); @@ -88,6 +89,10 @@ if ( $action ) { if ( is_plugin_active( $plugin ) || ( is_multisite() && is_network_only_plugin( $plugin ) ) ) { unset( $plugins[ $i ] ); } + // Only activate plugins which the user can activate. + if ( ! current_user_can( 'activate_plugin', $plugin ) ) { + unset( $plugins[ $i ] ); + } } } @@ -146,8 +151,9 @@ if ( $action ) { exit; case 'error_scrape': - if ( ! current_user_can('activate_plugins') ) - wp_die(__('Sorry, you are not allowed to activate plugins for this site.')); + if ( ! current_user_can( 'activate_plugin', $plugin ) ) { + wp_die( __( 'Sorry, you are not allowed to activate this plugin.' ) ); + } check_admin_referer('plugin-activation-error_' . $plugin); @@ -167,8 +173,9 @@ if ( $action ) { exit; case 'deactivate': - if ( ! current_user_can('activate_plugins') ) - wp_die(__('Sorry, you are not allowed to deactivate plugins for this site.')); + if ( ! current_user_can( 'deactivate_plugin', $plugin ) ) { + wp_die( __( 'Sorry, you are not allowed to deactivate this plugin.' ) ); + } check_admin_referer('deactivate-plugin_' . $plugin); @@ -192,8 +199,9 @@ if ( $action ) { exit; case 'deactivate-selected': - if ( ! current_user_can('activate_plugins') ) + if ( ! current_user_can( 'deactivate_plugins' ) ) { wp_die(__('Sorry, you are not allowed to deactivate plugins for this site.')); + } check_admin_referer('bulk-plugins'); @@ -204,6 +212,14 @@ if ( $action ) { } else { $plugins = array_filter( $plugins, 'is_plugin_active' ); $plugins = array_diff( $plugins, array_filter( $plugins, 'is_plugin_active_for_network' ) ); + + foreach ( $plugins as $i => $plugin ) { + // Only deactivate plugins which the user can deactivate. + if ( ! current_user_can( 'deactivate_plugin', $plugin ) ) { + unset( $plugins[ $i ] ); + } + } + } if ( empty($plugins) ) { wp_redirect( self_admin_url("plugins.php?plugin_status=$status&paged=$page&s=$s") ); diff --git a/wp-includes/capabilities.php b/wp-includes/capabilities.php index 5993a77caf..0db4c425a6 100644 --- a/wp-includes/capabilities.php +++ b/wp-includes/capabilities.php @@ -407,7 +407,10 @@ function map_meta_cap( $cap, $user_id ) { } break; case 'activate_plugins': - $caps[] = $cap; + case 'deactivate_plugins': + case 'activate_plugin': + case 'deactivate_plugin': + $caps[] = 'activate_plugins'; if ( is_multisite() ) { // update_, install_, and delete_ are handled above with is_super_admin(). $menu_perms = get_site_option( 'menu_items', array() ); diff --git a/wp-includes/version.php b/wp-includes/version.php index e7b9a54372..7a106bf554 100644 --- a/wp-includes/version.php +++ b/wp-includes/version.php @@ -4,7 +4,7 @@ * * @global string $wp_version */ -$wp_version = '4.9-alpha-41289'; +$wp_version = '4.9-alpha-41290'; /** * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.