delete posts and pages WordPress user capabilities set

Delete posts and pages capabilities

Delete posts and pages capabilities

Delete posts and pages. Who can make these critical operation at your blog? It is worth a lot of efforts to build good content for your blog. But some time we delete old and unneeded posts or pages. Whom WordPress allows to make that? What capabilities user should have to be powerful enough, in order reduce you site content?

If you read WordPress Codex “Roles and Capabilities” page or use plugin like User Role Editor to manage WordPress roles and user capabilities, you saw there this capabilities set, in alphabetical order:
delete_others_pages, delete_others_posts, delete_pages, delete_posts, delete_private_pages, delete_private_posts, delete_published_pages, delete_published_posts.

While these capabilities names are self-explained, it’s interesting, how and where WordPress checks them.

Going through WordPress core source code I discovered that these capabilities are not used directly. The only place where you can find capability name string like ‘delete_others_posts’ is wp-admin/includes/schema.php file, where these capabilities set was defined for WordPress version 2.1.

669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
/**
 * Create and modify WordPress roles for WordPress 2.1.
 *
 * @since 2.1.0
 */
function populate_roles_210() {
	$roles = array('administrator', 'editor');
	foreach ($roles as $role) {
		$role =& get_role($role);
		if ( empty($role) )
			continue;
 
		$role->add_cap('edit_others_pages');
		$role->add_cap('edit_published_pages');
		$role->add_cap('publish_pages');
		$role->add_cap('delete_pages');
		$role->add_cap('delete_others_pages');
		$role->add_cap('delete_published_pages');
		$role->add_cap('delete_posts');
		$role->add_cap('delete_others_posts');
		$role->add_cap('delete_published_posts');
		$role->add_cap('delete_private_posts');
		$role->add_cap('edit_private_posts');
		$role->add_cap('read_private_posts');
		$role->add_cap('delete_private_pages');
		$role->add_cap('edit_private_pages');
		$role->add_cap('read_private_pages');
	}
 
	$role =& get_role('administrator');
	if ( ! empty($role) ) {
		$role->add_cap('delete_users');
		$role->add_cap('create_users');
	}
 
	$role =& get_role('author');
	if ( ! empty($role) ) {
		$role->add_cap('delete_posts');
		$role->add_cap('delete_published_posts');
	}
 
	$role =& get_role('contributor');
	if ( ! empty($role) ) {
		$role->add_cap('delete_posts');
	}
}

Thus, according to the code above, starting from WordPress version 2.1 ‘administrator’ and ‘editor’ roles can delete all WordPress posts and pages, despite its status and author.Author can delete own posts only including published posts. Contributor can delete own unpublished posts only.

Does the fact, that these capabilities are not called by WordPress directly, mean, that they just added to WordPress capabilities list, but they are not in use yet? No, they are in use. WordPress uses them indirectly, via sophisticated mechanism of mapping so-called meta capabilities, like ‘edit_post’, ‘edit_page’ to the primitive capabilities, like deletion capabilities list mentioned above.

Is it interesting for you? Let’s see together, how does WordPress realize that.
When user are going to move a post or page to trash or delete it forever, wp-admin/edit.php script is called. WordPress checks if user has necessary capabilities set for deletion of selected post of page this way:
For moving post or page to Trash:

77
78
79
80
81
	case 'trash':
		$trashed = 0;
		foreach( (array) $post_ids as $post_id ) {
			if ( !current_user_can($post_type_object->cap->delete_post, $post_id) )
				wp_die( __('You are not allowed to move this item to the Trash.') );

In order to delete post or page permanently:

103
104
105
106
107
108
109
	case 'delete':
		$deleted = 0;
		foreach( (array) $post_ids as $post_id ) {
			$post_del = get_post($post_id);
 
			if ( !current_user_can($post_type_object->cap->delete_post, $post_id) )
				wp_die( __('You are not allowed to delete this item.') );

As you can see, WordPress checks if user has ‘delete_post’ capability. Let’s look to the WordPress roles. No one role contains ‘delete_post’ capability. It simply doesn’t exist, even for ‘Administrator’ role. What’s the puzzle WordPress authors prepared for us? Let’s go further.
Looking at the call stack I see that WordPress calls map_meta_cap() function from capabilities.php file via user object method has_cap().

891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
 /**
 * Whether user has capability or role name.
 *
 * This is useful for looking up whether the user has a specific role
 * assigned to the user. The second optional parameter can also be used to
 * check for capabilities against a specific object, such as a post or user.
 *
 * @since 2.0.0
 * @access public
 *
 * @param string|int $cap Capability or role name to search.
 * @return bool True, if user has capability; false, if user does not have capability.
 */
function has_cap( $cap ) {
	if ( is_numeric( $cap ) ) {
		_deprecated_argument( __FUNCTION__, '2.0', __('Usage of user levels by plugins and themes is deprecated. Use roles and capabilities instead.') );
		$cap = $this->translate_level_to_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 );

What does the function map_meta_cap() do? This is the definition of function map_meta_cap() from capabilities.php file:

964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
 /**
 * Map meta capabilities to primitive capabilities.
 *
 * This does not actually compare whether the user ID has the actual capability,
 * just what the capability or capabilities are. Meta capability list value can
 * be 'delete_user', 'edit_user', 'remove_user', 'promote_user', 'delete_post',
 * 'delete_page', 'edit_post', 'edit_page', 'read_post', or 'read_page'.
 *
 * @since 2.0.0
 *
 * @param string $cap Capability name.
 * @param int $user_id User ID.
 * @return array Actual capabilities for meta capability.
 */
function map_meta_cap( $cap, $user_id ) {

As for this post theme let’s look for ‘delete_post’ capability occurrence:

1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
	case 'delete_post':
	case 'delete_page':
		$post = get_post( $args[0] );
 
		if ( 'revision' == $post->post_type ) {
			$post = get_post( $post->post_parent );
		}
 
		$post_type = get_post_type_object( $post->post_type );
 
		if ( ! $post_type->map_meta_cap ) {
			$caps[] = $post_type->cap->$cap;
			// Prior to 3.1 we would re-call map_meta_cap here.
			if ( 'delete_post' == $cap )
				$cap = $post_type->cap->$cap;
			break;
		}
 
		$post_author_id = $post->post_author;
 
		// If no author set yet, default to current user for cap checks.
		if ( ! $post_author_id )
			$post_author_id = $user_id;
 
		$post_author_data = $post_author_id == get_current_user_id() ? wp_get_current_user() : get_userdata( $post_author_id );
 
		// If the user is the author...
		if ( is_object( $post_author_data ) && $user_id == $post_author_data->ID ) {
			// If the post is published...
			if ( 'publish' == $post->post_status ) {
				$caps[] = $post_type->cap->delete_published_posts;
			} elseif ( 'trash' == $post->post_status ) {
				if ('publish' == get_post_meta($post->ID, '_wp_trash_meta_status', true) )
					$caps[] = $post_type->cap->delete_published_posts;
			} else {
				// If the post is draft...
				$caps[] = $post_type->cap->delete_posts;
			}
		} else {
			// The user is trying to edit someone else's post.
			$caps[] = $post_type->cap->delete_others_posts;
			// The post is published, extra cap required.
			if ( 'publish' == $post->post_status )
				$caps[] = $post_type->cap->delete_published_posts;
			elseif ( 'private' == $post->post_status )
				$caps[] = $post_type->cap->delete_private_posts;
		}
		break;

Meta capabilities delete_post and delete_page are processed here together. Here WordPress checks if current user is the author of this post and according to post status expand one meta capability, like ‘delete_post’ to the whole set of primitive capabilities to check, like ‘delete_others_posts’, ‘delete_published_posts’. How those capabilities are defined, depends from the post type. For ‘post’ post type they are ‘delete_others_posts’, ‘delete_published_posts’, for ‘pages’ post type they are ‘delete_others_pages’, ‘delete_published_pages’, etc.
For advanced or curious PHP developers comment from the get_post_type_capabilities() function definition (file wp-admin/post.php) in relation to the linked register_post_type() function from the same file could help to look on the subject from the wider base:

1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
/**
 * Builds an object with all post type capabilities out of a post type object
 *
 * Post type capabilities use the 'capability_type' argument as a base, if the
 * capability is not set in the 'capabilities' argument array or if the
 * 'capabilities' argument is not supplied.
 *
 * The capability_type argument can optionally be registered as an array, with
 * the first value being singular and the second plural, e.g. array('story, 'stories')
 * Otherwise, an 's' will be added to the value for the plural form. After
 * registration, capability_type will always be a string of the singular value.
 *
 * By default, seven keys are accepted as part of the capabilities array:
 *
 * - edit_post, read_post, and delete_post are meta capabilities, which are then
 *   generally mapped to corresponding primitive capabilities depending on the
 *   context, which would be the post being edited/read/deleted and the user or
 *   role being checked. Thus these capabilities would generally not be granted
 *   directly to users or roles.
 *
 * - edit_posts - Controls whether objects of this post type can be edited.
 * - edit_others_posts - Controls whether objects of this type owned by other users
 *   can be edited. If the post type does not support an author, then this will
 *   behave like edit_posts.
 * - publish_posts - Controls publishing objects of this post type.
 * - read_private_posts - Controls whether private objects can be read.
 *
 * These four primitive capabilities are checked in core in various locations.
 * There are also seven other primitive capabilities which are not referenced
 * directly in core, except in map_meta_cap(), which takes the three aforementioned
 * meta capabilities and translates them into one or more primitive capabilities
 * that must then be checked against the user or role, depending on the context.
 *
 * - read - Controls whether objects of this post type can be read.
 * - delete_posts - Controls whether objects of this post type can be deleted.
 * - delete_private_posts - Controls whether private objects can be deleted.
 * - delete_published_posts - Controls whether published objects can be deleted.
 * - delete_others_posts - Controls whether objects owned by other users can be
 *   can be deleted. If the post type does not support an author, then this will
 *   behave like delete_posts.
 * - edit_private_posts - Controls whether private objects can be edited.
 * - edit_published_posts - Controls whether published objects can be edited.
 *
 * These additional capabilities are only used in map_meta_cap(). Thus, they are
 * only assigned by default if the post type is registered with the 'map_meta_cap'
 * argument set to true (default is false).
 *
 * @see map_meta_cap()
 * @since 3.0.0
 *
 * @param object $args Post type registration arguments
 * @return object object with all the capabilities as member variables
 */
function get_post_type_capabilities( $args ) {

Finally WordPress checks, if user has all primitive capabilities added by map_meta_cap() function and decides to permit or prohibit requested operation.

This post is built on the base of WordPress 3.5 Beta 1 core source code.

Tags: ,