Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WordPress user with custom role cannot view list page for custom post types without the "create_posts" capabililty

I am running a WordPress 5.2.3 site and having trouble with something in the admin panel.

I have a custom role, let's call it librarian, and a custom post type, let's call it book.

I want to make it so that a librarian can edit a book but not create a new one.

Following the advice in another question (WordPress: Disable “Add New” on Custom Post Type) and WordPress documentation, I have ended up with this code:

// Custom post type.
register_post_type('book',
    array(
        'labels'                => array(
            'name' => __( 'book' ),
            'singular_name' => __( 'Book' )
        ),
        'capability_type'       => array('book', 'books'),
        'capabilities'          => array(
            'create_posts' => 'do_not_allow' // <-- The important bit.
        ),
        'map_meta_cap'          => true,
        'description'           => 'Book full of pages',
        'exclude_from_search'   => true,
        'publicly_queryable'    => false,
        'show_in_nav_menus'     => false,
        'show_ui'               => true,
        'show_in_menu'          => true,
        'show_in_rest'          => true,
        'menu_icon'             => 'dashicons-location',
        'menu_position'         => 5,
        'supports'              => array('title', 'revisions')
    ));
// Custom role.
add_role('librarian', 'Librarian', array(
    'read'                  => true,
    'edit_books'            => true,
    'edit_published_books'  => true
));

I was expecting that when I visited edit.php?post_type=book as a librariranthen I would see the list of books for editing, but I would not see the Add New button. However, what I actually get is a 403 response:

Sorry, you are not allowed to access this page.

I think this may be a bug in WordPress, because of the following cases:

  • If I visit edit.php?post_type=book as an administrator, then I see the list page without the Add New button, as desired.
  • If I give the librarian role the edit_posts capability, then I see the list page without the Add New button, as desired (but I don't want to give them the edit_posts capability!).

These make me think that it isn't a problem with the custom post type set up in general.

  • If I remove the 'create_posts' => 'do_not_allow' from the book type registration, the librarian can see the list page, but it includes the Add New button.

This makes me think that it isn't a problem with the custom role set up in general.

Has anyone encountered this issue before? Have I missed anything from my configuration? Or is there an easy patch or workaround?

Any help would be appreciated! Thanks.

like image 667
Jamie Humphries Avatar asked Oct 03 '19 11:10

Jamie Humphries


1 Answers

It appears that this is a bug in WordPress. I have found the source of the problem and a workaround.

Workaround

If you're not interested in the cause, the workaround is to comment out this bit of cosmetic code in wp-admin/includes/menu.php:

https://github.com/WordPress/WordPress/blob/master/wp-admin/includes/menu.php#L168

/*
 * If there is only one submenu and it is has same destination as the parent,
 * remove the submenu.
 */
if ( ! empty( $submenu[ $data[2] ] ) && 1 == count( $submenu[ $data[2] ] ) ) {
    $subs      = $submenu[ $data[2] ];
    $first_sub = reset( $subs );
    if ( $data[2] == $first_sub[2] ) {
        unset( $submenu[ $data[2] ] );
    }
}

This will mean that some menu items that previously didn't show a submenu now will (with a single item the same as the main menu item), but that is only a cosmetic UI change.

Cause

For those of you that want to know the detail…

Accessing edit.php?post_type=book was failing this check in wp-admin/includes/menu.php:

https://github.com/WordPress/WordPress/blob/master/wp-admin/includes/menu.php#L341

if ( ! user_can_access_admin_page() ) {

    /**
     * Fires when access to an admin page is denied.
     *
     * @since 2.5.0
     */
    do_action( 'admin_page_access_denied' );

    wp_die( __( 'Sorry, you are not allowed to access this page.' ), 403 );
}

The call to user_can_access_admin_page() calls through to get_admin_page_parent().

If the submenu has been removed, get_admin_page_parent() returns an empty parent which ultimately causes user_can_access_admin_page() to erroneously return false in the case of the librarian role (the administrator role passes for a different reason).

If the submenu is left in place, get_admin_page_parent() returns a non-empty parent and the access check proceeds correctly from there.

So the root issue is that the global $submenu is being used to both determine the UI and also to make decisions on the permissions hierarchy. I don't see an immediate quick fix for this problem that wouldn't have side effects elsewhere throughout the WordPress code, other than the workaround above.

like image 57
Jamie Humphries Avatar answered Nov 15 '22 14:11

Jamie Humphries