How do I create a custom role capability?

I wish to create a custom capability for accessing the interface of my plugin.

  • Should the plugin manage adding this capability to all the administrator accounts on activation?
  • If so: Does WordPress manage adding the capability to all administrators of sub blogs and super administrators in multisite installations, or does that function need to be handled by the plugin?

Related posts

Leave a Reply

4 comments

  1. Tested with WordPress 6.0, and it works:

    add_action( 'activated_plugin',function( $plugin,$network_activation ){
        add_role( 'your_custom_role_slug', __( 'Your custom role title','your-plugin-domain' ),
            array(
                'read' => true,
                'view_admin_dashboard' => true,
                'activate_plugins' => false,
                'deactivate_plugins' => false,
                'your_custom_capability' => true
            )
        );
    }, 10, 2 );
    

    In the array, you have just some examples of capabilities. And you can add your custom capability without any problem. The custom capability will be stored together with the other capabilities.

    With this method, you will not be able to say what you can do with “your_custom_capability”, but most of the time like in the case of this question, you need nothing else than checking if the current user can “your_custom_capability”.
    If you want to show your plugin setting pages only to the users who have the capability “your_custom_capability”, you can do it without problems.
    Use the inbuilt capabilities when you create the role to decide what are the other things that the user with the role “your_custom_role_slug” should be able to see and do.

    This will return true if the user has the role “your_custom_role_slug”:

    current_user_can( 'your_custom_capability' );
    

    And this will show the settings page only if the user has the capability “your_custom_capability”:

    add_menu_page( __( 'Your title','eos-dp' ),__( 'Your title','your-domain' ),'your_custom_capability,'your_page_slug','your_function_callback','dashicons-your-icon',20 );
    

    Then on plugin deletion, I would remove the user role with:

    remove_role( 'your_custom_role_slug' );
    
  2. For a plugin I’m currently working on, I wanted to grant/restrict access to the plugin settings (i.e., the according admin menu pages) on a per role base.
    Therefore, I had to add a new plugin-specific capability to the user roles.

    Unfortunately, kaiser’s answer seems to be not working anymore, so I spent some time trying to figure out how to allow for the above mentioned functionality.


    The Schedule

    Before I share my code with you, here is what it’s all about, in plain text:

    1. On plugin activation, add the new capability THE_NEW_CAP to roles having a certain built-in capability BUILT_IN_CAP (in my case: edit_pages).
    2. On each page load, do 1. (i.e., add the capability, again). This is only necessary if you want to account for possible new roles that have been created after the activation of the plugin. Hence, these new roles don’t have the plugin-specific capability, even if they have the required built-in capability.
    3. Use the new capability for whatever you want. As explained before, I use it for granting/restricting access to the plugin’s admin menu pages, so that is how it is done in the following code example.
    4. On plugin deactivation, remove the capability. Of course, you could also do this when the plugin is being uninstalled. Either way, do it eventually.

    The Code

    And here is the above list converted into code:

    » Setting It Up

    class WPSE35165Plugin {
    
        public function __construct() {
            // Register hooks
            register_activation_hook(__FILE__, array(__CLASS__, 'activation'));
            register_deactivation_hook(__FILE__, array(__CLASS__, 'deactivation'));
    
            // Add actions
            add_action('admin_menu', array(__CLASS__, 'admin_menu'));
        }
    
        public function activation() {
            self::add_cap();
        }
    
        // Add the new capability to all roles having a certain built-in capability
        private static function add_cap() {
            $roles = get_editable_roles();
            foreach ($GLOBALS['wp_roles']->role_objects as $key => $role) {
                if (isset($roles[$key]) && $role->has_cap('BUILT_IN_CAP')) {
                    $role->add_cap('THE_NEW_CAP');
                }
            }
        }
    

    » Using It

        // Add plugin menu pages to admin menu
        public function admin_menu() {
            // Remove the following line if you don't care about new roles
            // that have been created after plugin activation
            self::add_cap();
    
            // Set up the plugin admin menu
            add_menu_page('Menu', 'Menu', 'THE_NEW_CAP', …);
            add_submenu_page('wpse35165', 'Submenu', 'Submenu', 'THE_NEW_CAP', ...);
        }
    

    » Cleaning It Up

        public function deactivation() {
            self::remove_cap();
        }
    
        // Remove the plugin-specific custom capability
        private static function remove_cap() {
            $roles = get_editable_roles();
            foreach ($GLOBALS['wp_roles']->role_objects as $key => $role) {
                if (isset($roles[$key]) && $role->has_cap('THE_NEW_CAP')) {
                    $role->remove_cap('THE_NEW_CAP');
                }
            }
        }
    
    }
    

    Note: Please do not use upper case capabilities. This is just for readability.

  3. Remove what you add

    First, please make sure that everything you add on activation also gets removed on uninstall. I got a short tutorial including example code for you.

    Test with a small plugin:

    I really don’t know much about MU, but as far as I can tell, the roles object is global across all blogs. Just try this little plugin and see what you can get:

    <?php
    /*
    Plugin Name:    MU Roles check
    Plugin URI:     https://github.com/franz-josef-kaiser/
    Description:    Check roles during viewing a blog
    Author:     Franz Josef Kaiser
    Author URI:     https://plus.google.com/u/0/107110219316412982437
    Version:        0.1
    Text Domain:    murc
    License:        GPL v2 - http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
    */
    
    /**
     * Show the blog data and the role names in this blog
     * Also shows if the custom capability was successfully added, or displays n/a for the role
     * 
     * @return void
     */
    function wpse35165_role_check()
    {
        $blog = get_current_site();
        $custom_cap = 'name_of_your_custom_capability';
    
        $html = "<hr /><table>";
        $html .= "<caption>List roles in (Blog) {$blog->site_name} / ID#{$blog->id}</caption>"
        $html .= "<thead><tr><th>Role Name</th><th>Capabilties</th></tr></thead><tbody>";
        foreach ( $GLOBALS['wp_roles'] as $name => $role_obj )
        {
            $cap = in_array( $custom_cap, $role_obj->caps ) ? $custom_cap : 'n/a';
            $cap = $cap OR in_array( $custom_cap, $role_obj->allcaps ) ? $custom_cap : 'n/a';
            $html .= "<tr><td>{$name}</td><td>{$cap}</td></tr>";
        }
        $html .= '</tbody></table>';
    
        print $html;
    }
    add_action( 'shutdown', 'wpse35165_role_check' );
    

    Adding Capabilities

    /**
     * Add the capability to the role objects
     * Should be in your activation function and done before you inspect with your plugin
     * 
     * @return void
     */
    function wpse35165_add_cap()
    {
        $custom_cap = 'name_of_your_custom_capability';
        $min_cap    = 'the_minimum_required_built_in_cap'; // Check "Roles and objects table in codex!
        $grant      = true; 
    
        foreach ( $GLOBALS['wp_roles'] as $role_obj )
        {
            if ( 
                ! $role_obj->has_cap( $custom_cap ) 
                AND $role_obj->has_cap( $min_cap )
            )
                $role_obj->add_cap( $custom_cap, $grant );
        }
    }
    

    Note: You can add the capability to the role without granting access to it – just set the second argument $grant = false;. This allows whitelisting single users with simply adding the cap including the last argument as true.

  4. This works for me:

    add_action('admin_init', 'add_custom_cap');
    function add_custom_cap()
    {
      $custom_cap = 'test_cap';
      $min_cap    = 'read';
      $grant      = true;
      $to_role = 'your_user_role';
      $role = 'user_role';
    
      foreach ( $GLOBALS['wp_roles'] as $role_obj )
      {
        if (is_object($role_obj[$role])) {
          if (!$role_obj[$role]->has_cap( $custom_cap ) && $role_obj[$role]->has_cap( $min_cap )) {
            $role_obj[$role]->add_cap( $custom_cap, $grant );
          }
        }
      }
    }