Very interesting question. It's a bit outside the scope of the typical rolls/capabilities functionality as it's more fine grained (unless I'm wrong -- very possible).
The first step would be to put some sort of way in that you can assign which posts a user can edit.
It makes the most sense to stick that the user's profile page. So you can hook into edit_user_profile
, get all the pages on the site, and stick them in a multi select box. The code comments should explain the step by step a bit. edit_user_profile
only shows up when editing other users' profiles.
<?php
add_action( 'edit_user_profile', 'wpse30211_user_profile' );
function wpse30211_user_profile( $user )
{
// only show this on editor pages
if( ! in_array( 'editor', $user->roles ) ) return;
// get the pages.
$pages = get_posts(
array(
'post_type' => 'page',
'numberposts' => -1,
'post_status' => 'any',
)
);
// Bail if we don't have pages.
if( ! $pages ) return;
// Which pages can our user edit?
$allowed = get_user_meta( $user->ID, 'wpse30211_pages', true );
if( ! is_array( $allowed ) || empty( $allowed ) ) $allowed = array();
// nonce-i-fy things
wp_nonce_field( 'wpse30211_nonce', 'wpse30211_nonce' );
// section heading...
echo '<h3>' . __( 'Grant this User permission to edit...' ) . '</h3>';
echo '<select multiple="multiple" name="wpse30211[]">';
echo '<option value="0">None</option>';
foreach( $pages as $p )
{
// for use in checked() later...
$selected = in_array( $p->ID, $allowed ) ? 'on' : 'off';
echo '<option ' . selected( 'on', $selected, false ) . ' value="' . esc_attr( $p->ID ) . '">' . esc_html( $p->post_title ) . '</option>';
}
echo '</select>';
}
Next you need to save the extra data you added to peoples profile. To do that, you hook into edit_user_profile_update
. Verify the nonce set in the above function, then check if the wpse30211
field is set and save the stuff with update_user_meta
.
<?php
add_action( 'edit_user_profile_update', 'wpse30211_user_save' );
function wpse30211_user_save( $user_id )
{
// verify our nonce
if( ! isset( $_POST['wpse30211_nonce'] ) || ! wp_verify_nonce( $_POST['wpse30211_nonce'], 'wpse30211_nonce' ) )
return;
// make sure our fields are set
if( ! isset( $_POST['wpse30211'] ) )
return;
$save = array();
foreach( $_POST['wpse30211'] as $p )
{
$save[] = $p;
}
update_user_meta( $user_id, 'wpse30211_pages', $save );
}
Now the fun part: disallowing access to certain pages. To do that, you can hook into load-post.php
, which fires when wp-admin/post.php
, the post editing screen, is loaded. Grab the the post id being editted, check to make sure it's a page. Then, grab the current user with wp_get_current_user
and get the pages they are allowed to edit with get_user_meta
. If the current post ID isn't in the array of pages they're allowed to edit, call wp_die
and kill the page.
<?php
add_action( 'load-post.php', 'wpse30211_kill_edit' );
function wpse30211_kill_edit()
{
$post_id = isset( $_REQUEST['post'] ) ? absint( $_REQUEST['post'] ) : 0;
if( ! $post_id ) return;
// bail if this isn't a page
if( 'page' !== get_post_type( $post_id ) ) return;
$user = wp_get_current_user();
$allowed = get_user_meta( $user->ID, 'wpse30211_pages', true );
if( ! is_array( $allowed ) || empty( $allowed ) ) $allowed = array();
// if the user can't edit this page, stop the loading...
if( ! in_array( $post_id, $allowed ) )
{
wp_die(
__( 'User cannot edit this page' ),
__( "You can't edit this post" ),
array( 'response' => 403 )
);
}
}
Finally, you can hook into pre_update_post
and stop that from updating of the user can't edit the page.
<?php
add_action( 'pre_post_update', 'wpse30211_stop_update' );
function wpse30211_stop_update( $post_id )
{
// not a page? bail.
if( 'page' !== get_post_type( $post_id ) ) return;
$user = wp_get_current_user();
$allowed = get_user_meta( $user->ID, 'wpse30211_pages', true );
if( ! is_array( $allowed ) || empty( $allowed ) ) $allowed = array();
if( ! in_array( $post_id, $allowed ) )
{
wp_die(
__( 'User cannot edit this page' ),
__( "You can't edit this post" ),
array( 'response' => 403 )
);
}
}
The above works, but I suspect there may be a way to do this a bit easier with WP's built in role & capabilities. Here's the whole thing as a plugin.