4

This question already has an answer here:

My site has several static pages and several custom post types. I'm trying to create a custom user role called students and give students access to ONLY certain custom post types and certain specific static pages.

I understand how to do this with custom post types by using add_cap() and populating the 'capability_type' and 'map_meta_cap' fields passed to register_post_type for that custom post type.

However, I don't understand how to do this for generic pages (which are not custom, but have been populated with different content). Specifically, I would like to take a parent page called internal-resources and give the student users editing capability for that specific page. There are some child pages of the internal-resources page they should be able to edit too. Finally, I would like them to be able to create new child pages under internal-resources. However, other static pages like Research and People they should NOT be able to edit. This shouldn't be too difficult, right?

Thanks for the help!!

marked as duplicate by Nicolai, Nathan Johnson, EAMann Feb 15 '18 at 15:27

This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.

  • 1
    The cleanest solution would indeed be to make the specific page trees, that you want to be edited by the student users, a custom post type. So there is a point to be made to just do that. – Nicolai Feb 6 '18 at 9:56
2

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.

  • 2
    I hadn't seen the possible duplicate comment when posting (I had been writing this for, er, a while). – Jacob Peattie Feb 6 '18 at 11:30

Not the answer you're looking for? Browse other questions tagged or ask your own question.