0

I have created a custom role that only has access to certain pages and their children/parents. I have used map_meta_cap filter to solve this. However even though it goes through my function correctly it will not function properly, return ['do_not_allow']; is not working as intended and I don't know why, it seemingly does not do anything.

//Custom Roles (stored to db on theme activations)
add_action( 'load-themes.php', 'staby_add_theme_roles');
function staby_add_theme_roles() {
    global $pagenow;

    if ( 'themes.php' == $pagenow && isset($_GET['activated'])) {
        add_role(
            'test',
            __( 'Test', 'staby' ),
            array(
                'pages_id' => array(6911),
            )
        );
        $role = get_role('test');
        $role->add_cap('read');
        $role->add_cap('edit_pages');
        $role->add_cap('edit_published_pages');
        $role->add_cap('edit_others_pages');
        $role->add_cap('publish_pages');
        $role->add_cap('pages_restriction');
    }
}
    //Role cap for different pages
function staby_map_meta_cap( $caps, $cap, $user_id, $args ) {
    // // If the capability being filtered isn't of our interest, just return current value
    
    if ( in_array($cap, ['edit_pages']) ) {
        
        // First item in $args array should be page ID
        if (!staby_role_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;
}
add_filter( 'map_meta_cap', 'staby_map_meta_cap', 10, 4 );

//See if role can edit correspondent page 
function staby_role_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;
   }

   $user = new WP_User($user_id);
   if ($user->allcaps['pages_id']) {
       $user_pages = $user->allcaps['pages_id'];
       if (!in_array($page->ID, $user_pages)) {
            return false;
       }
   }
   
   return true;

}

The only thing I have noticed is that the map_meta_cap filter runs multiple times, and the $args variable is always empty but it somehow still retrieves the $args[0] (page id) in my custom function staby_role_can_edit( $user_id, $page_id ) (but when I call it in that function it's null?). I have no idea why it's not working at all.

Any help is very appreciated! Thanks.

Update:

Tried with user_has_cap filter

    function staby_cap_filter( $allcaps, $cap, $args ) {
    
    //Bail out if role does not have restriction
    // Bail out if we're not asking about a post:
    if ( 'edit_post' != $args[0] || !$allcaps['pages_restriction']){
        $allcaps[$cap[0]] = false;
        return $allcaps;
    }
    if (!staby_role_can_edit( $args[1], $args[2] ) ) {
        $allcaps[$cap[0]] = false;
        return $allcaps;
    }
    return $allcaps;
 
}
add_filter( 'user_has_cap', 'staby_cap_filter', 10, 3 );

However even though it finds the correct page I don't know how to make it restrict access correctly.

$allcaps[$cap[0]] = false;
return $allcaps;

As you can see it sets the primitive capability to false if the user doesn't have access to the page.

9
  • I don't think that's the correct hook that you should use. Have you tried using the user_has_cap filter instead?
    – Sally CJ
    Aug 6 at 10:23
  • @SallyCJ I tried this and got it to work, kinda. It's really buggy, can't access admin dashboard, publish button almost works but it says "Submit for review" instead and no feedback that page has been updated. For some reason when you refresh the editorpage is completely blank. Basically it finds the correct page that you can edit but I don't know how to make it restrict access.
    – Ulvar
    Aug 6 at 12:08
  • Your first if doesn't look right, so what are the pages_restriction and pages_id? Are they an array of post (parent Page) IDs? How are you setting the values and are you sure the allcaps array actually contains those keys?
    – Sally CJ
    Aug 6 at 12:55
  • @SallyCJ pages_restriction are a custom check with add_cap() and pages_id are the accessible pages for that role. I copied the answer on user_has_cap. var_dump($allcaps) shows all the correct capabilities for my role. I'm uncertain how to use $cap though.
    – Ulvar
    Aug 6 at 13:46
  • So you're doing this just for the test role and Pages (i.e. posts of the page post type), and that the pages_id would only contain top-level Pages where their post_parent is 0? And I suggest you to use the switch_theme action instead of load-themes.php, to add the custom role, or do something like this.
    – Sally CJ
    Aug 6 at 14:57

1 Answer 1

1

I don't know how to make it restrict access correctly.

Try this, which uses get_post_ancestors():

function staby_cap_filter( $allcaps, $cap, $args, $user ) {
    // Get the current user's roles.
    $roles = (array) $user->roles;

    /* Do nothing if:
       1. The above roles doesn't include 'test', or
       2. edit_post is not the capability being checked, or
       3. The post ID was not specified, or
       4. pages_id is empty.
    */
    if ( ! in_array( 'test', $roles ) ||
        ( 'edit_post' !== $args[0] ) || empty( $args[2] ) ||
        empty( $allcaps['pages_id'] )
    ) {
        return $allcaps;
    }

    // Get the first ancestor of the current post that's being checked.
    $parent_ids   = get_post_ancestors( $args[2] );
    $first_parent = array_pop( $parent_ids );

    $allowed_ids = array_intersect(
        array( $first_parent, $args[2] ),
        $allcaps['pages_id']
    );

    // Disable the capability if the post ID is not in the allowed list.
    if ( empty( $allowed_ids ) ) {
        $allcaps[ $cap[0] ] = false;
    }

    return $allcaps;
}
add_filter( 'user_has_cap', 'staby_cap_filter', 10, 4 );

Additionally, I suggest you to use the switch_theme action instead of load-themes.php, to add the custom role, or create a plugin and then do something like this.

2
  • Wow, thanks so much! Works better than I expected it too, even in the admin pages! Would upvote if I could but need to get better rep. By the way, is there any difference in creating a plugin for things like add_role? I have that set up in my theme's functions.php at the moment.
    – Ulvar
    Aug 7 at 15:35
  • I'm glad it worked well for you! As for add_role(), I was just telling you to ensure not to unnecessarily re-add the role when it's already added, but the function itself works just the same whether you called it from within a standalone plugin or as part of a theme, just like register_post_type(). But see this and you would know if you should create a plugin or not :)
    – Sally CJ
    Aug 7 at 16:02

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.