Steve Taylor photo

Custom post types, authors, and custom roles

When you create a custom post type in WordPress, you can set it to ‘support’ a number of things: a featured image, trackbacks, revisions… and author. You might want to use it in the way author is used for core posts, literally the credited author of a post. Or maybe you could use it to give a bit more granularity to user permissions. The ‘owner’ of a post, if you will. Great!

Well, that’s what I wanted to do. Turns out it’s a little more complicated…

I set up my custom post types with their own capabilities, and assigned them to the right roles using Justin Tadlock’s trusty Members plugin. (This process might form another post if I get time. This post of Justin’s definitely helps. My code based on his code might help if you’re stuck. The sticking point for me was the fact that you have to manually add your CPT capabilities to at least one role in the Members role manager, using the little input at the bottom of the role edit screen. NOTE: In the new version 1.x.x of the plugin, the input is at the side, and it seems so far that you have to add them while the Custom tab is selected down the left – even if you can already see the caps in the CPT’s own tab. Anyway…)

The idea is that since you’ve got something like edit_others_posts for your specific CPT, a user in a role without that capability can be set by a user in a role with it as the ‘author’. Then the former user, as long as they’ve got the more general edit_posts equivalent for that CPT, will be able to edit that post, but not others. Great!

Except, when the core authors drop-down is output, you probably won’t see any users who are in custom roles. That’s a big problem if you’re making much use of Members. Why is this?

Turns out it’s a kind of simple, kind of knotty legacy issue related to user levels. These are a deprecated system from WordPress’s early days. The core authors drop-down is populated using who => 'authors' in a call to get_users(). And what does that do? It matches against the user level stored in the user meta table. And it seems that non-core roles, because they don’t have a mapping to the primitive user levels, are set to user level zero. Doesn’t matter how many impressive capabilities they have, to who => 'authors', they’re as puny as subscribers.

This is a 4-year-old ‘known issue’, summed up by the most recent comment on its Trac ticket as ‘a mess’.

I originally thought of solving it by making sure that when a user is assigned to a custom role, they get a user level higher than zero. Sorted, right? Not quite. If you want to use capabilities specific to each custom post type, this is too broad (hence why the whole user levels system was deprecated ages ago). That user level, marking a user as an ‘author’, would apply for all post types.

I imagine there’s probably improvements to be made, but here’s my first stab at a workaround. This approach uses the CMB2 custom fields plugin, though it should be possible to adapt to use other custom field plugins. The basic idea involves:

  • Using a custom field instead of the core author drop-down
  • Overriding the saving of the field meta, to save it as the post_author (and vice versa with the meta retrieval)
  • A few other tricks

1. Registering support

First off, when you register your custom post type, don’t add author to the supports parameter array, add your own version. I use pilau-author.

Obviously, it’ll need to support custom-fields too!

2. Registering the custom field

The basic code for this is straight-forward CMB2 code. However, it depends on a few extra functions to do these things:

  1. To only show the custom field for people who can edit posts of this type authored / owned by someone else
  2. To get a list of users for the drop-down based on capability rather than role or user level
  3. To filter the saving and retrieval of the custom field value to use post_author in the main posts table

There’s a gist with everything in it:

Here’s what’s happening in the code:

  • When registering custom fields, there’s a loop through every custom post type.
  • Each CPT is checked to see if it supports our custom author feature.
  • The options for the drop-down are built up using a custom function which gets all users by capability. There’s no simple way of doing this – you have to get all users, then check each user for the capability. At least, this function makes sure it only gets all users once per request – but watch out if you’ve a lot of users in your system!
  • Each of these CPTs has a meta box registered with the author drop-down.
  • I have my own system using show_on_cb for custom management of conditional meta box display. In the callback function, pilau_cmb2_show_on_custom, I’ve left some other stuff unrelated to this task in there – it might be useful / interesting. But the main thing here is the bit which maps edit_others_post_types (where the post_types bit is a placeholder for the CPT on the current screen) to the right capability for that post type.
  • The author drop-down defaults to the current user’s ID.
  • Right at the end there’s a couple of things hooked to CMB2’s filters for saving and retrieving the meta value for this field. The standard meta procedures are short-circuited and the core post_author field is used instead. (With a bit of fiddling about with the save to avoid an infinite loop…)

Er… that’s all there is to it! ;-)

Leave a comment

Your email address will not be published. Required fields are marked *