Your browser doesn't support the features required by impress.js, so you are presented with a simplified version of this presentation.

For the best experience please use the latest Chrome, Safari or Firefox browser.

Use a spacebar or arrow keys to navigate

The Power of WordPress’ Roles and Capabilities: Understanding map_meta_cap

Erick Hitter

Design Engineer @ Automattic

@ethitter

About Me

Myth I Hope To (Continue) Dispelling

 

WordPress, as a CMS, lacks adequate controls over user actions, making CMS ____?____ a better choice.

 

 

By show of hands, who has heard this refrain?

Roles and Capabilities

http://codex.wordpress.org/Roles_and_Capabilities

 

This Codex article is your friend!

Default Roles

Native Capabilities

 

This list is far from complete; see http://codex.wordpress.org/Roles_and_Capabilities#Capabilities

Key Functions

Checking capabilities

Both return a boolean indicating whether or not the user has the specified capability.

Checking user roles

Don't

current_user_can() and user_can() will accept a role in place of the capability, but it's unsafe to do so.

A user could have the role specified, but a critical capability could be removed.

Also, this breaks much of what I'll cover today.

How does WordPress leverages capabilities?

In Core, when creating a new post or page:

<?php
// wp-admin/post-new.php, line 39-40

if ( ! current_user_can( $post_type_object->cap->edit_posts ) )
	wp_die( __( 'Cheatin’ uh?' ) );
?>

 

In Core, deterining if the current user can manage widgets:

<?php
// wp-admin/widgets.php, line 15-16

if ( ! current_user_can('edit_theme_options') )
	wp_die( __( 'Cheatin’ uh?' ) );
?>

Need more granular control?

 

map_meta_cap is your friend

But why?

With map_meta_cap, you can:

 

In short, this provides a level of control beyond just the roles themselves.

But how?

There are really two types of capabilities in WordPress:

 

map_meta_cap translates a user's primitive capabilities to his/her meta capabilities.

How are the two different?

Contextual determination? Say what?

Intent: to publish a post you wrote

Need: publish_posts, edit_posts

Contextual determination? Say what?

Intent: to publish a post someone else wrote

Need: publish_posts, edit_posts, edit_others_posts

Contextual determination? Say what?

Intent: to moderate a comment

Need: moderate_comments, edit_posts

In other words, map_meta_cap introduces dependencies within WordPress' capabilities system.

Questions before we move into code?

map_meta_cap

map_meta_cap() is in wp-includes/capabilities.php.

 

Within that function is the map_meta_cap filter.

 

WordPress handles the function, we really care about the filter.

map_meta_cap

apply_filters('map_meta_cap', $caps, $cap, $user_id, $args)

$args

This is important!

 

The $args parameter is both incredibly useful, and annoyingly vague.

 

$args is a numerically-indexed array whose values contain data relevant to the current capabilities check.

 

Unfortunately, isn't always populated, forcing one to use globals in some cases.

 

Clear as mud, right?

Block deletion

<?php
/**
 * Prevent a post from being deleted if a meta key is present
 *
 * @param array $caps
 * @param string $cap
 * @param int $user_id
 * @param array $args
 * @uses get_post_meta
 * @filter map_meta_cap
 * @return array
 */
function eth_block_post_deletion( $caps, $cap, $user_id, $args ) {
	if (
		'delete_post' == $cap
		&& get_post_meta( (int) $args[0], '_block_deletion', true )
	)
		$caps[] = 'do_not_allow';

	return $caps;
}
add_filter( 'map_meta_cap', 'eth_block_post_deletion', 10, 4 );
?>

Block file uploads

<?php
/**
 * Prevent specific users from uploading files
 *
 * @param array $caps
 * @param string $cap
 * @param int $user_id
 * @param array $args
 * @uses get_user_meta
 * @filter map_meta_cap
 * @return array
 */
function eth_limit_uploads( $caps, $cap, $user_id, $args ) {
	if (
		'upload_files' == $cap
		&& (bool) get_user_meta( $user_id, 'disallow_uploads', true )
	)
		$caps[] = 'do_not_allow';

	return $caps;
}
add_filter( 'map_meta_cap', 'eth_limit_uploads', 10, 4 );
?>

Magic of do_not_allow

When preventing something, you don't need to remove the capabilities that let a user perform an action.

unset( $caps['edit_posts'] );

 

Instead, add do_not_allow to the $caps array and WP ignores the other meta capabilities it determined.

 

It's worth noting that you don't have to use do_not_allow, but Core does and so should you; that said, you can use any string that isn't a capability used by WordPress.

Allow editing

<?php
/**
 * Allow a contributor to edit a particular published post
 *
 * @param array $caps
 * @param string $cap
 * @param int $user_id
 * @param array $args
 * @uses get_post_meta
 * @filter map_meta_cap
 * @return array
 */
function eth_allow_contrib_edit_post( $caps, $cap, $user_id, $args ) {
	if (
		'edit_post' == $cap
		&& isset( $args[0] )
		&& (bool) get_post_meta( (int) $args[0], '_allow_contribs_to_edit', true )
		&& get_post_field( 'post_author', (int) $args[0] ) == $user_id
	)
		$caps = array( 'edit_posts' );

	return $caps;
}
add_filter( 'map_meta_cap', 'eth_allow_contrib_edit_post', 10, 4 );
?>

Note about overriding

When overriding to grant a capability, you often must clear the $caps array of the capabilities you're overridding.

In other words, there is no allow cap that has the opposite effect as do_not_allow.

Questions?

Slides are available at http://www.ethitter.com/blog/.

Check Twitter for a link as well: @ethitter.