diff --git a/src/wp-admin/includes/ajax-actions.php b/src/wp-admin/includes/ajax-actions.php
index 90a9dd1..034ec2f 100644
a
|
b
|
function wp_ajax_install_plugin() { |
3643 | 3643 | // If install request is coming from import page, do not return network activation link. |
3644 | 3644 | $plugins_url = ( 'import' === $pagenow ) ? admin_url( 'plugins.php' ) : network_admin_url( 'plugins.php' ); |
3645 | 3645 | |
3646 | | if ( current_user_can( 'activate_plugins' ) && is_plugin_inactive( $install_status['file'] ) ) { |
| 3646 | if ( current_user_can( 'activate_plugin', $install_status['file'] ) && is_plugin_inactive( $install_status['file'] ) ) { |
3647 | 3647 | $status['activateUrl'] = add_query_arg( array( |
3648 | 3648 | '_wpnonce' => wp_create_nonce( 'activate-plugin_' . $install_status['file'] ), |
3649 | 3649 | 'action' => 'activate', |
diff --git a/src/wp-admin/includes/class-plugin-installer-skin.php b/src/wp-admin/includes/class-plugin-installer-skin.php
index 6c55455..247387e 100644
a
|
b
|
class Plugin_Installer_Skin extends WP_Upgrader_Skin { |
73 | 73 | |
74 | 74 | if ( ! $this->result || is_wp_error($this->result) ) { |
75 | 75 | unset( $install_actions['activate_plugin'], $install_actions['network_activate'] ); |
76 | | } elseif ( ! current_user_can( 'activate_plugins' ) ) { |
| 76 | } elseif ( ! current_user_can( 'activate_plugin', $plugin_file ) ) { |
77 | 77 | unset( $install_actions['activate_plugin'] ); |
78 | 78 | } |
79 | 79 | |
diff --git a/src/wp-admin/includes/class-plugin-upgrader-skin.php b/src/wp-admin/includes/class-plugin-upgrader-skin.php
index 1c4da87..b685bca 100644
a
|
b
|
class Plugin_Upgrader_Skin extends WP_Upgrader_Skin { |
52 | 52 | 'activate_plugin' => '<a href="' . wp_nonce_url( 'plugins.php?action=activate&plugin=' . urlencode( $this->plugin ), 'activate-plugin_' . $this->plugin) . '" target="_parent">' . __( 'Activate Plugin' ) . '</a>', |
53 | 53 | 'plugins_page' => '<a href="' . self_admin_url( 'plugins.php' ) . '" target="_parent">' . __( 'Return to Plugins page' ) . '</a>' |
54 | 54 | ); |
55 | | if ( $this->plugin_active || ! $this->result || is_wp_error( $this->result ) || ! current_user_can( 'activate_plugins' ) ) |
| 55 | if ( $this->plugin_active || ! $this->result || is_wp_error( $this->result ) || ! current_user_can( 'activate_plugin', $this->plugin ) ) |
56 | 56 | unset( $update_actions['activate_plugin'] ); |
57 | 57 | |
58 | 58 | /** |
diff --git a/src/wp-admin/includes/class-wp-plugin-install-list-table.php b/src/wp-admin/includes/class-wp-plugin-install-list-table.php
index cd71836..e67ca54 100644
a
|
b
|
class WP_Plugin_Install_List_Table extends WP_List_Table { |
470 | 470 | case 'newer_installed': |
471 | 471 | if ( is_plugin_active( $status['file'] ) ) { |
472 | 472 | $action_links[] = '<button type="button" class="button button-disabled" disabled="disabled">' . _x( 'Active', 'plugin' ) . '</button>'; |
473 | | } elseif ( current_user_can( 'activate_plugins' ) ) { |
| 473 | } elseif ( current_user_can( 'activate_plugin', $status['file'] ) ) { |
474 | 474 | $button_text = __( 'Activate' ); |
475 | 475 | /* translators: %s: Plugin name */ |
476 | 476 | $button_label = _x( 'Activate %s', 'plugin' ); |
diff --git a/src/wp-admin/includes/class-wp-plugins-list-table.php b/src/wp-admin/includes/class-wp-plugins-list-table.php
index 136fbd4..4128edf 100644
a
|
b
|
class WP_Plugins_List_Table extends WP_List_Table { |
623 | 623 | 'network_only' => __( 'Network Only' ), |
624 | 624 | ); |
625 | 625 | } elseif ( $is_active ) { |
626 | | /* translators: %s: plugin name */ |
627 | | $actions['deactivate'] = '<a href="' . wp_nonce_url( 'plugins.php?action=deactivate&plugin=' . $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>'; |
| 626 | if ( current_user_can( 'deactivate_plugin', $plugin_file ) ) { |
| 627 | /* translators: %s: plugin name */ |
| 628 | $actions['deactivate'] = '<a href="' . wp_nonce_url( 'plugins.php?action=deactivate&plugin=' . $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>'; |
| 629 | } |
628 | 630 | } else { |
629 | | /* translators: %s: plugin name */ |
630 | | $actions['activate'] = '<a href="' . wp_nonce_url( 'plugins.php?action=activate&plugin=' . $plugin_file . '&plugin_status=' . $context . '&paged=' . $page . '&s=' . $s, 'activate-plugin_' . $plugin_file ) . '" class="edit" aria-label="' . esc_attr( sprintf( _x( 'Activate %s', 'plugin' ), $plugin_data['Name'] ) ) . '">' . __( 'Activate' ) . '</a>'; |
| 631 | if ( current_user_can( 'activate_plugin', $plugin_file ) ) { |
| 632 | /* translators: %s: plugin name */ |
| 633 | $actions['activate'] = '<a href="' . wp_nonce_url( 'plugins.php?action=activate&plugin=' . $plugin_file . '&plugin_status=' . $context . '&paged=' . $page . '&s=' . $s, 'activate-plugin_' . $plugin_file ) . '" class="edit" aria-label="' . esc_attr( sprintf( _x( 'Activate %s', 'plugin' ), $plugin_data['Name'] ) ) . '">' . __( 'Activate' ) . '</a>'; |
| 634 | } |
631 | 635 | |
632 | 636 | if ( ! is_multisite() && current_user_can( 'delete_plugins' ) ) { |
633 | 637 | /* translators: %s: plugin name */ |
diff --git a/src/wp-admin/plugins.php b/src/wp-admin/plugins.php
index 3d7ff01..e19f584 100644
a
|
b
|
if ( $action ) { |
29 | 29 | |
30 | 30 | switch ( $action ) { |
31 | 31 | case 'activate': |
32 | | if ( ! current_user_can('activate_plugins') ) |
33 | | wp_die(__('Sorry, you are not allowed to activate plugins for this site.')); |
| 32 | if ( ! current_user_can( 'activate_plugin', $plugin ) ) { |
| 33 | wp_die( __( 'Sorry, you are not allowed to activate this plugin.' ) ); |
| 34 | } |
34 | 35 | |
35 | 36 | if ( is_multisite() && ! is_network_admin() && is_network_only_plugin( $plugin ) ) { |
36 | 37 | wp_redirect( self_admin_url("plugins.php?plugin_status=$status&paged=$page&s=$s") ); |
… |
… |
if ( $action ) { |
88 | 89 | if ( is_plugin_active( $plugin ) || ( is_multisite() && is_network_only_plugin( $plugin ) ) ) { |
89 | 90 | unset( $plugins[ $i ] ); |
90 | 91 | } |
| 92 | // Only activate plugins which the user can activate. |
| 93 | if ( ! current_user_can( 'activate_plugin', $plugin ) ) { |
| 94 | unset( $plugins[ $i ] ); |
| 95 | } |
91 | 96 | } |
92 | 97 | } |
93 | 98 | |
… |
… |
if ( $action ) { |
146 | 151 | exit; |
147 | 152 | |
148 | 153 | case 'error_scrape': |
149 | | if ( ! current_user_can('activate_plugins') ) |
150 | | wp_die(__('Sorry, you are not allowed to activate plugins for this site.')); |
| 154 | if ( ! current_user_can( 'activate_plugin', $plugin ) ) { |
| 155 | wp_die( __( 'Sorry, you are not allowed to activate this plugin.' ) ); |
| 156 | } |
151 | 157 | |
152 | 158 | check_admin_referer('plugin-activation-error_' . $plugin); |
153 | 159 | |
… |
… |
if ( $action ) { |
167 | 173 | exit; |
168 | 174 | |
169 | 175 | case 'deactivate': |
170 | | if ( ! current_user_can('activate_plugins') ) |
171 | | wp_die(__('Sorry, you are not allowed to deactivate plugins for this site.')); |
| 176 | if ( ! current_user_can( 'deactivate_plugin', $plugin ) ) { |
| 177 | wp_die( __( 'Sorry, you are not allowed to deactivate this plugin.' ) ); |
| 178 | } |
172 | 179 | |
173 | 180 | check_admin_referer('deactivate-plugin_' . $plugin); |
174 | 181 | |
… |
… |
if ( $action ) { |
192 | 199 | exit; |
193 | 200 | |
194 | 201 | case 'deactivate-selected': |
195 | | if ( ! current_user_can('activate_plugins') ) |
| 202 | if ( ! current_user_can( 'deactivate_plugins' ) ) { |
196 | 203 | wp_die(__('Sorry, you are not allowed to deactivate plugins for this site.')); |
| 204 | } |
197 | 205 | |
198 | 206 | check_admin_referer('bulk-plugins'); |
199 | 207 | |
… |
… |
if ( $action ) { |
204 | 212 | } else { |
205 | 213 | $plugins = array_filter( $plugins, 'is_plugin_active' ); |
206 | 214 | $plugins = array_diff( $plugins, array_filter( $plugins, 'is_plugin_active_for_network' ) ); |
| 215 | |
| 216 | foreach ( $plugins as $i => $plugin ) { |
| 217 | // Only deactivate plugins which the user can deactivate. |
| 218 | if ( ! current_user_can( 'deactivate_plugin', $plugin ) ) { |
| 219 | unset( $plugins[ $i ] ); |
| 220 | } |
| 221 | } |
| 222 | |
207 | 223 | } |
208 | 224 | if ( empty($plugins) ) { |
209 | 225 | wp_redirect( self_admin_url("plugins.php?plugin_status=$status&paged=$page&s=$s") ); |
diff --git a/src/wp-includes/capabilities.php b/src/wp-includes/capabilities.php
index abd8725..387f029 100644
a
|
b
|
function map_meta_cap( $cap, $user_id ) { |
393 | 393 | } |
394 | 394 | break; |
395 | 395 | case 'activate_plugins': |
396 | | $caps[] = $cap; |
| 396 | case 'deactivate_plugins': |
| 397 | case 'activate_plugin': |
| 398 | case 'deactivate_plugin': |
| 399 | $caps[] = 'activate_plugins'; |
397 | 400 | if ( is_multisite() ) { |
398 | 401 | // update_, install_, and delete_ are handled above with is_super_admin(). |
399 | 402 | $menu_perms = get_site_option( 'menu_items', array() ); |
diff --git a/tests/phpunit/tests/user/capabilities.php b/tests/phpunit/tests/user/capabilities.php
index a479011..b2eb3f5 100644
a
|
b
|
class Tests_User_Capabilities extends WP_UnitTestCase { |
233 | 233 | 'upload_themes' => array( 'administrator' ), |
234 | 234 | 'customize' => array( 'administrator' ), |
235 | 235 | 'add_users' => array( 'administrator' ), |
| 236 | 'deactivate_plugins' => array( 'administrator' ), |
236 | 237 | |
237 | 238 | 'edit_categories' => array( 'administrator', 'editor' ), |
238 | 239 | 'delete_categories' => array( 'administrator', 'editor' ), |
… |
… |
class Tests_User_Capabilities extends WP_UnitTestCase { |
261 | 262 | 'upload_themes' => array(), |
262 | 263 | 'edit_css' => array(), |
263 | 264 | 'upgrade_network' => array(), |
| 265 | 'deactivate_plugins' => array(), |
264 | 266 | |
265 | 267 | 'customize' => array( 'administrator' ), |
266 | 268 | 'delete_site' => array( 'administrator' ), |
… |
… |
class Tests_User_Capabilities extends WP_UnitTestCase { |
421 | 423 | $expected['create_users'], |
422 | 424 | $expected['manage_links'], |
423 | 425 | // Singular object meta capabilities (where an object ID is passed) are not tested: |
| 426 | $expected['activate_plugin'], |
| 427 | $expected['deactivate_plugin'], |
424 | 428 | $expected['remove_user'], |
425 | 429 | $expected['promote_user'], |
426 | 430 | $expected['edit_user'], |