16

I would want to allow certain user to edit only one page and it's subpages. How would this be possible? I tried the old Role Scoper, but it seems to have a lot of problems and bugs.

  • 2
    I removed your request for a plugin recommendation as that made the question off-topic. Yes, this should be possible with a plugin but when I see attempts to do things that require this kind of hacking of basic functionality I can't help but think that you are taking the wrong approach. Can you explain the project in more detail? – s_ha_dum Jun 16 '15 at 14:44
14
+100

The first thing to do to implements such task is to be able to recognise which page an user can edit.

There are different ways to do it. It could be a user meta, some configuration value... For the sake of this answer, I will assume that a function lile this exists:

function wpse_user_can_edit( $user_id, $page_id ) {

   $page = get_post( $page_id );

   // let's find the topmost page in the hierarchy
   while( $page && (int) $page->parent ) {
     $page = get_post( $page->parent );
   }

   if ( ! $page ) {
     return false;
   }

   // now $page is the top page in the hierarchy
   // how to know if an user can edit it, it's up to you...

}

Now that we have a way to determine if an user can edit a page we just need to tell WordPress to use this function to check useer capability to edit a page.

That can be done via 'map_meta_cap' filter.

Something like:

add_filter( 'map_meta_cap', function ( $caps, $cap, $user_id, $args ) {

    $to_filter = [ 'edit_post', 'delete_post', 'edit_page', 'delete_page' ];

    // If the capability being filtered isn't of our interest, just return current value
    if ( ! in_array( $cap, $to_filter, true ) ) {
        return $caps;
    }

    // First item in $args array should be page ID
    if ( ! $args || empty( $args[0] ) || ! wpse_user_can_edit( $user_id, $args[0] ) ) {
        // User is not allowed, let's tell that to WP
        return [ 'do_not_allow' ];
    }
    // Otherwise just return current value
    return $caps;

}, 10, 4 );

At this point, we need only a way to connect an user to one or more pages.

There might be different solutions depending on use case.

A flexible solution could be to add a dropdown of "root" pages (see wp_dropdown_pages) to the edit user admin screen, and save selected page(s) as user meta.

We could leverage 'edit_user_profile' to add the pages dropdown field and 'edit_user_profile_update' to store the selected value as user meta.

I am sure that in this website there's enough guidance on how to that in detail.

When page(s) are stored as user meta, the wpse_user_can_edit() function from above can be finished by checking if the page id is part of the user meta value.

Removing capability to edit the page, WordPress will do the rest: will remove any edit link from backend and frontend, will prevent direct access... and so on.

  • 3
    This much better than my answer. Why limit edit links when you can just modify the user's capability, and let WordPress handle the rest? – ricotheque Mar 19 '17 at 17:04
  • you should use "a" before the word "user" not "an" because a long "u" sounds like "yu" which starts with a consonant. – Philip Dec 10 '19 at 19:16
7

It does take a small amount of code to implement this feature, even if you use a PHP class to avoid global variables. I also didn't want to hide the prohibited pages for the user in the Dashboard. What if they added content that was already on the site?

$user_edit_limit = new NS_User_Edit_Limit(
    15,       // User ID we want to limit
    [2, 17]   // Array of parent page IDs user is allowed to edit
                 (also accepts sub-page IDs)
);

class NS_User_Edit_Limit {

    /**
     * Store the ID of the user we want to control, and the
     * posts we will let the user edit.
     */
    private $user_id = 0;
    private $allowed = array();

    public function __construct( $user_id, $allowed ) {

        // Save the ID of the user we want to limit.
        $this->user_id = $user_id;

        // Expand the list of allowed pages to include sub pages
        $all_pages = new WP_Query( array(
            'post_type' => 'page',
            'posts_per_page' => -1,
        ) );            
        foreach ( $allowed as $page ) {
            $this->allowed[] = $page;
            $sub_pages = get_page_children( $page, $all_pages );
            foreach ( $sub_pages as $sub_page ) {
                $this->allowed[] = $sub_page->ID;
            }
        }

        // For the prohibited user...
        // Remove the edit link from the front-end as needed
        add_filter( 'get_edit_post_link', array( $this, 'remove_edit_link' ), 10, 3 );
        add_action( 'admin_bar_menu', array( $this, 'remove_wp_admin_edit_link' ), 10, 1 );
        // Remove the edit link from wp-admin as needed
        add_action( 'page_row_actions', array( $this, 'remove_page_list_edit_link' ), 10, 2 );
    }

    /**
     * Helper functions that check if the current user is the one
     * we want to limit, and check if a specific post is in our
     * list of posts that we allow the user to edit.
     */
    private function is_user_limited() {
        $current_user = wp_get_current_user();
        return ( $current_user->ID == $this->user_id );
    }
    private function is_page_allowed( $post_id ) {
        return in_array( $post_id, $this->allowed );
    }

    /**
     * Removes the edit link from the front-end as needed.
     */
    public function remove_edit_link( $link, $post_id, $test ) {
        /**
         * If...
         * - The limited user is logged in
         * - The page the edit link is being created for is not in the allowed list
         * ...return an empty $link. This also causes edit_post_link() to show nothing.
         *
         * Otherwise, return link as normal.
         */
        if ( $this->is_user_limited() && !$this->is_page_allowed( $post_id ) ) {
            return '';
        }
        return $link;
    }

    /**
     * Removes the edit link from WP Admin Bar
     */
    public function remove_wp_admin_edit_link( $wp_admin_bar ) {
        /**
         *  If:
         *  - We're on a single page
         *  - The limited user is logged in
         *  - The page is not in the allowed list
         *  ...Remove the edit link from the WP Admin Bar
         */
        if ( 
            is_page() &&
            $this->is_user_limited() &&
            !$this->is_page_allowed( get_post()->ID )
        ) {
            $wp_admin_bar->remove_node( 'edit' );
        }
    }

    /**
     * Removes the edit link from WP Admin's edit.php
     */
    public function remove_page_list_edit_link( $actions, $post ) {
        /**
         * If:
         * -The limited user is logged in
         * -The page is not in the allowed list
         * ...Remove the "Edit", "Quick Edit", and "Trash" quick links.
         */
        if ( 
            $this->is_user_limited() &&
            !$this->is_page_allowed( $post->ID )
        ) {
            unset( $actions['edit'] );
            unset( $actions['inline hide-if-no-js']);
            unset( $actions['trash'] );
        }
        return $actions;
    }
}

What the code above does is prevent the following from working or appearing as needed:

  1. get_edit_post_link
  2. Edit Page link on the WP Admin Bar that appears for the Pages
  3. Edit, Quick Edit, and Trash quick links that appear underneath the Pages in /wp-admin/edit.php?post_type=page

This worked on my local WordPress 4.7 install. Assuming that the pages on the site won't change often, it might be better to hardcode the IDs of the page and its sub-pages, and remove the WP_Query inside the __construct method. This will save a lot on database calls.

  • +1 for the more complete answer than @Ben's but the right way to handle the links is by manipulating the capabilities, – Mark Kaplun Mar 19 '17 at 5:35
  • Yeah, when I saw gmazzap's answer I ended up thinking "Now why didn't I think of that?" – ricotheque Mar 19 '17 at 17:05
5

If you wanted to keep away from plugins, you could a variation of the code below in a functions.php file or a custom plugin.

There are 2 seperate parts to this code, You would only need to use 1 of them, but which one depends on the complexity of the requirements.

Part 1 is specifying a single user and restricting them to a specific post.

Part 2 allows you to create a map of users and post ID's and allows multiple posts

The code below is for a page only, but if you wanted to change that to a post, or a custom post type, you would need to change the string in $screen->id == 'page' to something else.

You can find a reference to the screen ID's around wp-admin here

function my_pre_get_posts( $query ){

    $screen = get_current_screen();
    $current_user = wp_get_current_user();

    /**
     * Specify a single user and restrict to a single page
     */
    $restricted_user_id = 10; //User ID of the restricted user
    $allowed_post_id = 1234; //Post ID of the allowed post

    $current_post_id = isset( $_GET['post'] ) ? (int)$_GET['post'] : false ;

    //Only affecting a specific user
    if( $current_user->ID !== $restricted_user_id ){
        return;
    }

    //Only Affecting EDIT page.
    if( ! $current_post_id ){
        return;
    }

    if( $screen->id == 'page' && $current_post_id !== $allowed_post_id ){
        wp_redirect( admin_url( ) );
        exit;
    }

    /**
     * Specify a map of user_id => $allowed_posts
     */
    $restrictions_map = [
        10 => [ 123 ], //Allow user ID to edit Page ID 123
        11 => [ 152, 186 ] //Allow user ID to edit Page ID 123 and 186
    ];

    if( array_key_exists( $current_user->ID, $restrictions_map ) ){

        $allowed_posts = $restrictions_map[$current_user->ID];

        if( $screen->id == 'page' && ! in_array( $current_user->ID, $allowed_posts ) ){
            wp_redirect( admin_url( ) );
            exit;
        }

    }

}
add_action( 'pre_get_posts', 'my_pre_get_posts' );
  • 1
    +1 as it can work to do the core functionality, but this still leaves the links to edit the pages being output even to users that can not edit them which makes a bad UI – Mark Kaplun Mar 19 '17 at 5:29
-4

I used User Role Editor a couple of times and is pretty good. Maybe it could help you too. Here is the link User Role Editor

  • Seems to be a solid plugin, but I cannot find a way how to restrict a user to edit specific pages. – naf Jun 16 '15 at 14:35
  • Make the users you want to limit this way author-level users Add the capability "edit_pages" to the author user level (using User Role Editor) Set the author of a page to the user you want to grant privilege of editing it. An author level user given the capability of edit_pages can see the list of pages in the dashboard, but does not have the option to edit except for pages they are the author of. – user2319361 Jun 16 '15 at 14:46
  • 4
    Thanks, that works to an extent. At some point I might have to have multiple users restricted to modify a specific page, so there would need to be a way to set multiple authors to a page. – naf Jun 16 '15 at 14:59
  • To restrict users to specific pages you will need to purchase the Pro versoin. I am looking for the same thing and found that out. wordpress.stackexchange.com/questions/191658/… – Ricardo Andres Mar 3 '17 at 14:32
  • 1
    while that specific plugin is a solid thing right now, it is probably easier to write code to do it than wade in all the options the plugin offers. (if it even lets you do what the OP asks) – Mark Kaplun Mar 14 '17 at 4:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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