Roles, caps,
and meta caps

harnessing the
WordPress capabilities API

Cahir Castle Portcullis

Jonathan Brinley

@jbrinley • jonathan@tri.be

Modern Tribe

The Editorial Process
You Think You Have

slightly more advanced editorial flowchart

The Editorial Process
You Really Have

Intersections / from Giphy
https://codex.wordpress.org/Roles_and_Capabilities

Capabilities

current_user_can()


add_options_page(
  'Super Secret Settings Page', // Page Title
  'Secret Settings',            // Menu Title
  'manage_secret_settings',     // Capability
  'secret-settings',            // Slug
  [ $this, 'render' ]           // Callback function
)
			

current_user_can( 'manage_secret_settings' )

current_user_can( 'edit_post', $post_id )


$current_user = wp_get_current_user();
return call_user_func_array( [ $current_user, 'has_cap' ], $args );
			

WP_User::has_cap()


class WP_User {
	public function has_cap( $cap ) {
		// ...
		$args = array_slice( func_get_args(), 1 );
		$args = array_merge( array( $cap, $this->ID ), $args );

		$caps = call_user_func_array( 'map_meta_cap', $args );

		if ( is_multisite() && is_super_admin( $this->ID ) ) {
			// ...
		}

		// ...
	}
}
			

current_user_can( 'edit_post', $post_id )


case 'edit_post':
  if ( in_array( $post->post_status, [ 'publish', 'future' ], true ) ) {
    $caps[] = $post_type->cap->edit_published_posts;
  } elseif ( 'private' == $post->post_status ) {
    $caps[] = $post_type->cap->edit_private_posts;
  }
			

map_meta_cap()

diagram showing how meta caps map to caps

map_meta_cap()


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

function add_approval_requirement( $required_caps, $requested_cap, $user_id, $args ) {
  if ( $requested_cap == 'publish_post' ) {
    if ( ! is_approved( $args[0] ) ) {
      $required_caps[] = 'publish_unapproved_posts';
    }
  }
  return $required_caps;
}
			

add_role()


$role = add_role( 'page_author', __( 'Like an author, but for pages' ) );

$role->add_cap( 'read' );
$role->add_cap( 'edit_pages' );
$role->add_cap( 'publish_pages' );
$role->add_cap( 'delete_pages' );
$role->add_cap( 'edit_published_pages' );
$role->add_cap( 'delete_published_pages' );
$role->add_cap( 'upload_files' );
$role->add_cap( 'level_1' );
			

add_role()


add_action( 'admin_init', function() {
  if ( get_option( 'my_user_roles_version', 0 ) < 1 ) {
    my_register_roles_version_1();
    update_option( 'my_user_roles_version', 1 );
  }
} );
			

get_role()


$role = get_role( 'author' );

$role->add_cap( 'edit_pages' );
$role->add_cap( 'publish_pages' );
$role->add_cap( 'delete_pages' );
$role->remove_cap( 'edit_published_posts' );
$role->remove_cap( 'delete_published_posts' );
			

Custom Post Types

register_post_type( 'story' )

Custom Post Types

capability_type


register_post_type( 'story', [
	'label'           => __( 'Stories' ),
	// ... many more args
	'capability_type' => [ 'story', 'stories' ],
] );
			

current_user_can( 'edit_stories' )

Custom Post Types

capabilities


register_post_type( 'story', [
	'label'        => __( 'Stories' ),
	// ... many more args
	'capabilities' => [
		'publish_posts' => 'special_publish_posts',
	],
] );
			

current_user_can( 'special_publish_posts' )

Custom Post Types

map_meta_cap


register_post_type( 'story', [
	'label'           => __( 'Stories' ),
	// ... many more args
	'capability_type' => [ 'story', 'stories' ],
	'map_meta_cap'    => true,
] );
			

current_user_can( 'edit_post', $story_id )

map_meta_cap()

diagram showing how meta caps map to caps for a cpt

Custom Post Types

map_meta_cap


register_post_type( 'story', [
	'label'           => __( 'Stories' ),
	// ... many more args
	'capability_type' => [ 'story', 'stories' ],
	'map_meta_cap'    => false,
] );
			

add_filter( 'map_meta_cap', 'your_mapping_method', 10, 4 )

Custom Post Types


$caps = [
	'read',                    'edit_posts',
	'edit_others_posts',       'edit_private_posts',
	'edit_published_posts',    'publish_posts',
	'read_private_posts',      'delete_posts',
	'delete_private_posts',    'delete_published_posts',
	'delete_others_posts',     'create_posts',
];

$pto = get_post_type_object( 'story' );

foreach ( [ 'administrator', 'editor' ] as $role_name ) {
	$role = get_role( $role_name );
	foreach ( $caps as $cap ) {
		$role->add_cap( $pto->cap->{ $cap } );
	}
}
			
venn diagram showing two non-overlapping roles
venn diagram showing the overlap of conditions for a role to edit a story
venn diagram showing the overlap of conditions for a role to edit a story

user_has_cap


add_filter( 'user_has_cap', 'filter_user_caps', 10, 4 );
function filter_user_caps( $caps_the_user_has, $caps_the_user_needs, $args, $user ) {
	$originally_requested_cap = $args[0];
	$user_id = $args[1];
	$other_args = array_slice( $args, 2 ); // varies per $arg[0]
}
			

user_has_cap


foreach ( $groups as $group ) {
  $access_level = $group->get_permissions( $post_type );
  if (
       $access_level == Group::PERMISSION_EDITOR
    || $group->is_assigned_to_post( $post_id )
  ) {
    $user_caps[ $post_type_object->cap->edit_others_posts ]      = true;
    $user_caps[ $post_type_object->cap->read_private_posts ]     = true;
    $user_caps[ $post_type_object->cap->delete_posts ]           = true;
    $user_caps[ $post_type_object->cap->delete_private_posts ]   = true;
    $user_caps[ $post_type_object->cap->delete_published_posts ] = true;
    $user_caps[ $post_type_object->cap->delete_others_posts ]    = true;
    $user_caps[ $post_type_object->cap->edit_private_posts ]     = true;
  }
}
			

Jonathan Brinley

@jbrinley • jonathan@tri.be

Modern Tribe