Access on specific pages in wordpress for a specific user

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.