Custom user role that can only edit specific (non-custom-type) page and all child pages [duplicate]

There’s no way in WordPress to assign the capability for editing (or any action) a specific post to a role.

However, you can filter capabilities checks and change them on the fly using the map_meta_cap.

When handling post permissions, WordPress ultimately deals in just 4 capabilties:

  • edit_post
  • read_post
  • delete_post
  • publish_post

Then whenever an action is performed on a post it maps these capabilities to the ‘primitive’ capabilities. These are the capabilities you’ll be more familiar with:

  • publish_posts
  • edit_posts
  • edit_others_posts
  • edit_private_posts
  • edit_published_posts
  • read
  • read_private_posts
  • delete_posts
  • delete_private_posts
  • delete_published_posts
  • delete_others_posts

There is also create_posts, but as far as I can tell this is only used in some REST endpoints and for controlling whether some UI appears. When saving a post create_posts is mapped to edit_posts.

What map_meta_cap() does is that when someone tries to edit a post it determines which primitive capability is required.

So if a user tries to edit a post, map_meta_cap() checks if they are the author of that post. If they are then the edit_post meta capability will be mapped to edit_posts. If they are not the author it will be mapped to edit_others_posts. WordPress will then check if the user has the mapped capability and respond accordingly.

So if you want to change permissions on a per-page basis, you need to filter map_meta_cap to change the way it assigns the meta capabilities.

In your example, you want to let users edit_page for the Internal Resources page (and others), but not edit any other pages. This is a little bit tricky because to do this they need access to the Pages menu in the Dashboard. So you’ll need to grant your student role the edit_pages and publish_pages capabilities, and then use the filter to revoke those capabilities on a page-by-page basis:

function wpse_293259_map_meta_cap( $required_caps, $cap, $user_id, $args ) {
    if ( in_array( $cap, ['edit_post', 'publish_post'] ) ) {
        $page_id = $args[0]; // The ID of the post being edited.
        $student_pages = [1,2,3]; // The IDs of the pages students are allowed to edit.

        /**
         * If the page being edited is not one students can edit, check if the user
         * is a student. If they are, set the required capabilities to 'do_not_allow'
         * to prevent them editing.
         */
        if ( ! in_array( $page_id, $student_pages ) ) {
            $user = new WP_User( $user_id );

            if ( in_array( 'students', $user->roles ) ) {
                $required_caps = ['do_not_allow'];
            }
        }
    }

    return $required_caps;
}
add_filter( 'map_meta_cap', 'wpse_293259_map_meta_cap', 10, 4 );

This will prevent publishing or editing of pages that aren’t in the $student_pages.

I have not been able to figure out a good way to allow users to publish pages but only if they’re a child of a particular page. Every mix of editing and publishing capabilities I’ve tried has resulted in weird behaviour. I don’t think child pages are a good way to manage permissions because they are a thing that can be changed on the page editor. This means that you would be changing permissions between publishing a post and being redirected back to edit it.

You might be best off using the technique I described to allow editing of the Internal Resources page, but then break the sub-pages out into a separate post type with its own permissions.

Leave a Comment