Is it possible to restrict multiple users to only be able to edit a
post (or posts) where those users have a custom meta key matching the
custom post’s custom taxonomy or meta key?
Yes, you can use user_has_cap
which is a filter applied when current_user_can()
or user_can()
is called — these two functions are used for checking whether the current user or a specific user has the permission to do things like, editing posts, deleting terms, changing site options, etc.
So here’s a working example, tried with a custom post type with capability_type
set to org_profile
which is also the post type slug:
Note: Replace the org_profile
with the correct post type, org_id
with the correct meta key, and organization
with the correct taxonomy slug, and if the user meta stores the term ID instead of slug or name, make sure you pass an integer to has_term()
, e.g. has_term( (int) $user_org_id, ... )
. ( PS: If you remove the comments, the function is actually pretty simple.. 🙂 )
add_filter( 'user_has_cap', 'wpse_393410', 10, 4 );
function wpse_393410( $allcaps, $caps, $args, $user ) {
/* With the following, and assuming the current user's ID is 123:
current_user_can( 'edit_post', 456 );
user_can( 123, 'edit_post', 456 );
$args would be array( 'edit_post', 123, 456 ).
i.e. array( 'edit_post', <user ID>, <post ID> )
*/
if ( ! empty( $args[2] ) && 'edit_post' === $args[0] &&
'org_profile' === get_post_type( $args[2] )
) {
// If the current user does NOT have any of the roles in the include list, we
// do nothing.
$include = array( 'org_user' );
if ( empty( array_intersect( (array) $user->roles, $include ) ) ) {
return $allcaps;
}
$user_org_id = get_user_meta( $args[1], 'org_id', true );
// If the user meta is empty, we do nothing. Else, check if the post has a term
// with the same SLUG OR NAME in the 'organization' taxonomy.
// Note: We just need to disable one of the caps (in $caps).
if ( $user_org_id && ! has_term( $user_org_id, 'organization', $args[2] ) ) {
$allcaps[ $caps[0] ] = false;
}
}
return $allcaps;
}
If you want to use the post meta instead, then replace this:
$user_org_id = get_user_meta( $args[1], 'org_id', true );
// If the user meta is empty, we do nothing. Else, check if the post has a term
// with the same SLUG OR NAME in the 'organization' taxonomy.
// Note: We just need to disable one of the caps (in $caps).
if ( $user_org_id && ! has_term( $user_org_id, 'organization', $args[2] ) ) {
$allcaps[ $caps[0] ] = false;
}
With this:
$user_org_id = get_user_meta( $args[1], 'org_id', true );
$post_org_id = get_post_meta( $args[2], 'org_id', true );
// If the post OR user meta is empty, we do nothing. Else, check if the post meta
// matches the user meta.
// Note: We just need to disable one of the caps (in $caps).
if ( $post_org_id && $user_org_id && $user_org_id !== $post_org_id ) {
$allcaps[ $caps[0] ] = false;
}
Additionally, in the above function, I used an include list ($include
), but you can instead use an exclude list, if you want to, e.g. $exclude = array( 'administrator', 'custom' );
and then do ! empty( array_intersect( $roles, $exclude ) )
.