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.