Improvements for roles and capabilities in 4.9

Here is an overview of the developer facing changes focused on user roles and capabilities for the 4.9 cycle. If you’re interested in more detail, checkout the full list of tickets.

New Capabilities

Activating and deactivating plugins

It is now possible to manage capabilities for activating and deactivating plugins more granularly through the following new capabilities:

  • activate_plugin checks whether a user can activate a specific plugin. When checking the capability, it gets passed the plugin file (such as current_user_can( 'activate_plugin', 'my-plugin/my-plugin.php' )).
  • deactivate_plugin works similar to activate_plugin, but checks whether a user can deactivate a specific plugin as the name indicates.
  • deactivate_plugins allows to check whether a user can generally deactivate plugins.

By default, all of the above capabilities map to the existing primitive capability activate_plugins, so there is no change in behavior by default. However they make it possible to customize the behavior, for example to prevent specific users from activating or deactivating specific plugins. See #38652 for background discussion.

Installing and updating language files

The other group of new meta capabilities deals with installing and updating language files / translations:

  • install_languages checks whether a user can install new language files.
  • update_languages checks whether a user can apply language file updates.

By default, the capabilities are granted to a user when they have at least one of the existing update_core, install_plugins or install_themes capabilities. In addition, if wp_can_install_language_pack() returns false, the capability checks will return false as well. Again there is no change in behavior, but these capabilities allow customizing permissions more granularly, for example to not allow any updates other than language file updates. See #39677 for background discussion.

Hardening security against prohibited actions

When going through the map_meta_cap() function, several capabilities end up mapping to a value of do_not_allow, which is not an actual capability that should be used, but rather indicate that a user should under no circumstances be allowed to perform the respective action. However, it has historically been possible to manually grant users do_not_allow as an actual capability, which is a bad practice and would cause unexpected behavior. As of 4.9, it is no longer possible to do that. See #41059 for background discussion.

Refactored user capability and role switching in multisite

In multisite, switching the available roles and the current user’s capabilities no longer happens in switch_to_blog() and restore_current_blog(), instead it has been moved to a new function wp_switch_roles_and_user() which is hooked into the site switching process. This allows to improve performance by temporarily unhooking the function in cases where roles and capabilities do not need to be switched.

Furthermore the logic for both switching user capabilities in WP_User and switching available roles in WP_Roles has been refactored to work in a similar manner and provide more granular methods:

  • The WP_User::for_blog() and WP_User::_init_caps() methods have been deprecated in favor of WP_User::for_site().
  • WP_Roles::_init() has been deprecated in favor of WP_Roles::for_site().
  • Both WP_User and WP_Roles now provide a get_site_id() method to retrieve the ID for which the user’s capabilities/available roles respectively are currently initialized.

All these changes heavily benefit the process of switching sites, particularly by fixing a bug where available roles were not switched correctly prior. See #36961 and #38645 for background discussion.

Having a clean foundation now, several areas now deal with the available roles correctly when in a switched state. See #42013, #42014 and #42015 for the individual tickets.


#4-9, #dev-notes