29

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?
1

4 Answers 4

4

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' );
20

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
  • 1
    Always use get_editable_roles() to fetch roles you want to edit. You will break plugins otherwise.
    – fuxia
    Apr 28, 2013 at 17:38
  • 1
    @toscho Well, okay, I suppose that's one of these functions even the Codex doesn't know about... ;) Of course, this function has its right to exist, however, I don't see using the global WP_Roles array breaking any plugins in my case.
    – tfrommen
    Apr 28, 2013 at 17:44
  • 2
    Some plugins create specialized user roles and rely on the exact set of capabilities. In some cases one capability excludes the usage of another in the program logic. You cannot know when that’s the case.
    – fuxia
    Apr 28, 2013 at 17:54
12

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.

1

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 );
      }
    }
  }
}
1
  • 2
    Do not ever modify the globals of roles! Never. Don't! You will not trigger any hooks and deny filters and make your code a moving target. No one will ever know when and where you registered that role (you did not, you just stuffed it in there somewhere, somewhen, somehow). Please: Do not ever do that. Especially not with roles.
    – kaiser
    Oct 13, 2019 at 16:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.