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 librariran
then 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:
edit.php?post_type=book
as an administrator
, then I see the list page without the Add New button, as desired.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.
'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.
It appears that this is a bug in WordPress. I have found the source of the problem and a 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.
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.