4

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!!

  • 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.