WordPress: Capabilities for custom post types
Asked Answered
N

2

15

I'm writing a plugin which creates a custom post_type. I'd also like the plugin to create a custom role which can only add/edit/delete the new post_type. I've tried several plugins (Role Scoper, Advanced Access manager) and they allow me to redefine or create new roles, but they don't allow me to assign capabilities specific to the new post_type. For example, I want to allow the ability to add/edit my new post_type but NOT normal posts/pages.

From what I've read, I can add new roles with the add_role() function. One of the parameters of this function is an array of "capabilities" which appear to be defined here. I think what I need is to be able to add my capabilities that are specific to MY post_type. Is this possible?

Nette answered 19/11, 2011 at 23:3 Comment(0)
R
30

Capabilities for Custom Post Types

The function register_post_type() takes a $capabilities array as one of its (optional) arguments.

It could look like so:

$capabilities = array(
    'publish_posts' => 'publish_ypts',
    'edit_posts' => 'edit_ypts',
    'edit_others_posts' => 'edit_others_ypts',
    'delete_posts' => 'delete_ypts',
    'delete_others_posts' => 'delete_others_ypts',
    'read_private_posts' => 'read_private_ypts',
    'edit_post' => 'edit_ypt',
    'delete_post' => 'delete_ypt',
    'read_post' => 'read_ypt'
);

where "ypt" stands for "your post type".

Thereafter you could add a new role to your WordPress that has these exact capabilities (and possibly some more of the standard WordPress capabilities):

add_role(
    'ypt_author',
    'Author of your post type',
    array(
        'publish_ypts' => true,
        'edit_ypts' => true,
        'edit_others_ypts' => true,
        'delete_ypts' => true,
        'delete_others_ypts' => true,
        'read_private_ypts' => true,
        'edit_ypt' => true,
        'delete_ypt' => true,
        'read_ypt' => true,
        // more standard capabilities here
    )
);

The latter can be done using plugins though, check out the Members plugin by Justin Tadlock, for instance.

Thorough Example

To give you a more concrete example:

/* REGISTER POST TYPE */

add_action('init', 'ypt_register');

function ypt_register()
{

    $labels = array(
        'name' => _x('YPTs', 'post type general name'),
        'singular_name' => _x('YPT', 'post type singular name'),
        'add_new' => _x('Add New YPT', 'Team item'),
        'add_new_item' => __('Add a new post of type YPT'),
        'edit_item' => __('Edit YPT'),
        'new_item' => __('New YPT'),
        'view_item' => __('View YPT'),
        'search_items' => __('Search YPTs'),
        'not_found' =>  __('No YPTs found'),
        'not_found_in_trash' => __('No YPTs currently trashed'),
        'parent_item_colon' => ''
    );

    $capabilities = array(
        // this is where the first code block from above goes
    );

    $args = array(
        'labels' => $labels,
        'public' => true,
        'publicly_queryable' => true,
        'show_ui' => true,
        'query_var' => true,
        'rewrite' => true,
        'capability_type' => 'ypt',
        'capabilities' => $capabilities,
        'hierarchical' => false,
        'menu_position' => null,
        'supports' => array( 'title', 'author', 'thumbnail' )
    ); 

    register_post_type( 'ypt' , $args );

    flush_rewrite_rules( false );
}


/* MAP META CAPABILITIES */

add_filter( 'map_meta_cap', 'ypt_map_meta_cap', 10, 4 );

function ypt_map_meta_cap( $caps, $cap, $user_id, $args )
{

    if ( 'edit_ypt' == $cap || 'delete_ypt' == $cap || 'read_ypt' == $cap ) {
        $post = get_post( $args[0] );
        $post_type = get_post_type_object( $post->post_type );
        $caps = array();
    }

    if ( 'edit_ypt' == $cap ) {
        if ( $user_id == $post->post_author )
            $caps[] = $post_type->cap->edit_posts;
        else
            $caps[] = $post_type->cap->edit_others_posts;
    }

    elseif ( 'delete_ypt' == $cap ) {
        if ( $user_id == $post->post_author )
            $caps[] = $post_type->cap->delete_posts;
        else
            $caps[] = $post_type->cap->delete_others_posts;
    }

    elseif ( 'read_ypt' == $cap ) {
        if ( 'private' != $post->post_status )
            $caps[] = 'read';
        elseif ( $user_id == $post->post_author )
            $caps[] = 'read';
        else
            $caps[] = $post_type->cap->read_private_posts;
    }

    return $caps;
}
Ramp answered 19/11, 2011 at 23:27 Comment(14)
How does publish_ypt or any of the other post_type-specific capabilities get defined?Nette
@Emerson The first part of my answer is that definition. The $capabilities array is one of the arguments you put into the register_post_type() function. In it, you map the new capabilities to the equivalent ones of regular posts. If register_post_type() is used without this optional argument, the regular capabilities apply. If it is used, the pubish_posts capability does not include your post type - the author has to have the publish_ypt capability instead.Ramp
Oh I get it. I got confused because the third parameter for add_role() in the codex is called $capabilities. So I was using that array in your second function. I'll fix it report back.Nette
@Emerson See my above edit or check my version of your pastebin. Note that since you want parents and children apparently, 'hierarchical' needs to be set to true. Otherwise, my example applies to your dictionary entries as well.Ramp
I edited my code, but the only thing I could find that was missing is: 'capability_type' => 'dictionary_entry', It still doesn't work but I think there must be something else I'm missing.Nette
Aha. I had a couple things out of place! I got the capabilities array mixed with the labels and I'd also missed the flush_rewrite_rules(). But now it appears to work! For anyone following, my current code is here: pastebin.com/5JtW17gwNette
Follow-up: The code is working exactly as it should w.r.t. editing my custom post_type. However, my new role doesn't have sufficient permissions to view their own profile, which means they get an "insufficient privileges" error as soon as they log in. Can you point me in the right direction to fixing this? Should I add another capability or two to let them log in smoothly?Nette
I scoured the codex and it seems like the 'read' capability is the basic access required for subscriber to edit their own profile (and not do much else). I've tried to add this nto the array of capabilities, but it still throws an "insufficient privileges" error when my custom role signs in.Nette
I tried to paste the block of code but it gets messy. I'm adding 'read' => true, to the end of the array that's the third parameter of the add_role() function. I think this is right, but it doesn't seem to be working. It doesn't result in the basic access to the dashboard. Perhaps I need to map the standard read capability? Any advice would be much appreciated.Nette
If the CPT works and you are just experiencing problems with adding the role, I suggest you check out the plugin I proposed to use for that part (last line before the edit). Powerful well written plugin.Ramp
The plugin works great. I've used it in the past, but I don't want my plugin to be dependent on another plugin to work. I got this to work by using: $role =& get_role('dictionary_entry_author'); $role->add_cap('read'); I have no idea why this worked instead of adding 'read' => true to the array of capabilities.Nette
UPDATE: Don't forget the new 'edit_published_posts', if you don't add this a user can't even edit his/her own post after publishing!Acidity
You forgot to pluralize the primitive capabilities e.g. 'edit_post' => 'edit_ypt' (meta cap) vs 'edit_posts' => 'edit_ypts' (primitive cap).Feretory
Thanks, @Jorge. I think I fixed it. But feel free to edit the answer yourself, if you care (You could have done that in the first place).Ramp
K
22

Nowadays (WP 3.5+), it's much easier. Just set the map_meta_cap argument to TRUE and choose a string (typically the post type name) for the capability_type argument when registering the post type.

A simple var_dump( $GLOBALS['wp_post_types']['new_custom_post_type'] ) ); will show you something like the following.

[cap] => stdClass Object
(
    [edit_post]      => "edit_{$capability_type}"
    [read_post]      => "read_{$capability_type}"
    [delete_post]        => "delete_{$capability_type}"
    [edit_posts]         => "edit_{$capability_type}s"
    [edit_others_posts]  => "edit_others_{$capability_type}s"
    [publish_posts]      => "publish_{$capability_type}s"
    [read_private_posts]     => "read_private_{$capability_type}s"
        [delete_posts]           => "delete_{$capability_type}s"
        [delete_private_posts]   => "delete_private_{$capability_type}s"
        [delete_published_posts] => "delete_published_{$capability_type}s"
        [delete_others_posts]    => "delete_others_{$capability_type}s"
        [edit_private_posts]     => "edit_private_{$capability_type}s"
        [edit_published_posts]   => "edit_published_{$capability_type}s"
)

The more intended array part are the seven other primitive capabilities that are not checked by core, but mapped by map_meta_caps() during post type registration.

Kymric answered 20/5, 2013 at 18:36 Comment(1)
For the noobs still learning, setting map_meta_cap to true and capability_type to your content type, will automatically generate the capabilities for you, so you don't have to specify them.Alexia

© 2022 - 2024 — McMap. All rights reserved.