具有自定义角色的 WordPress 用户无法在没有 "create_posts" 权限的情况下查看自定义 post 类型的列表页面
WordPress user with custom role cannot view list page for custom post types without the "create_posts" capabililty
我是 运行 一个 WordPress 5.2.3 站点,在管理面板中遇到问题。
我有一个自定义角色,我们称之为 librarian
,还有一个自定义 post 类型,我们称之为 book
。
我想做到 librarian
可以编辑 book
但不能创建新的。
按照另一个问题的建议(WordPress: Disable “Add New” on Custom Post Type) and WordPress documentation,我得到了这个代码:
// 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
));
我原以为当我以 librariran
的身份访问 edit.php?post_type=book
时,我会看到 books
的列表进行编辑,但我不会看到 添加新 按钮。但是,我实际得到的是 403
响应:
Sorry, you are not allowed to access this page.
我认为这可能是 WordPress 中的一个错误,原因如下:
- 如果我以
administrator
的身份访问 edit.php?post_type=book
,那么我会看到列表页面没有 添加新的 按钮,如我所愿。
- 如果我给
librarian
角色赋予 edit_posts
能力,那么我会看到没有 Add New 按钮的列表页面,如我所愿(但我不想给他们 edit_posts
能力!)。
这些让我认为这不是一般设置的自定义 post 类型的问题。
- 如果我从
book
类型注册中删除 'create_posts' => 'do_not_allow'
,librarian
可以 看到列表页面,但它包括添加新 按钮。
这让我觉得这不是一般设置自定义角色的问题。
有没有人遇到过这个问题?我是否遗漏了配置中的任何内容?或者有简单的补丁或解决方法吗?
如有任何帮助,我们将不胜感激!谢谢
这似乎是 WordPress 中的一个错误。我已经找到问题的根源和解决方法。
解决方法
如果您对原因不感兴趣,解决方法是在 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] ] );
}
}
这意味着一些以前不显示子菜单的菜单项现在会显示(单个项目与主菜单项相同),但这只是一个表面的 UI 更改。
原因
对于那些想了解细节的人……
访问 edit.php?post_type=book
时检查失败 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 );
}
对user_can_access_admin_page()
calls through to get_admin_page_parent()
的调用。
如果子菜单已被删除,get_admin_page_parent()
return 是一个空的父菜单,最终导致 user_can_access_admin_page()
错误地 return false
librarian
角色(administrator
角色因不同原因而被淘汰)。
如果子菜单保留在原位,get_admin_page_parent()
return 是一个非空父菜单,访问检查从那里正确进行。
因此,根本问题是全局 $submenu
被用于确定 UI 以及对权限层次结构做出决定。除了上述解决方法外,我没有看到对整个 WordPress 代码的其他地方不会产生副作用的此问题的立即快速修复。
我想我已经找到了解决方法,无需编辑核心文件。
正如您所说,这个错误的原因是没有子菜单项的空菜单会导致访问检查错误地 return false。
所以解决方法是不要让列表页面成为顶级菜单。相反,将其设为现有菜单下的子菜单。这样就不会有空的顶级菜单,您可以在没有 'Add New' 按钮的情况下正确看到列表页面。
这只是一种解决方法,并不理想。但也许它比编辑核心文件更好。
创建自定义 post 类型并将其列表页面作为子菜单项的方法很简单。 register_post_type
的参数有一个名为 show_in_menu
的参数。正如 the docs 所说:
If a string of an existing top level menu (eg. 'tools.php' or 'edit.php?post_type=page'), the post type will be placed as a sub-menu of that.
所以代码将是:
register_post_type('book',
array(
'labels' => array(
'name' => __( 'book' ),
'singular_name' => __( 'Book' )
),
'capability_type' => array('book', 'books'),
'capabilities' => array(
'create_posts' => 'do_not_allow'
),
'map_meta_cap' => true,
'show_ui' => true,
'show_in_menu' => 'tools.php', //or whatever top-level menu you'd like
//... other args omitted
));
我找到了 2 个解决此问题的方法:
首先,我发现为 custom_post_type create_posts 分配特定功能比使用 do_not_allow
更好
所以我使用:
...
'capability_type' => 'books',
'capabilities' => array(
'create_posts' => 'add_new_books'
),
'map_meta_cap' => true,
...
这样我可以为管理员或其他经理分配 add_new_books 能力,但不允许其他角色使用它。
选项 1:
(过滤 user_has_cap 以假装用户拥有 'edit_posts' 但随后删除帖子菜单项)
add_filter(
'user_has_cap',
function( $all_caps, $caps ) {
global $typenow, $menu;
if ( is_admin() && ! empty( $typenow ) && stripos( $_SERVER['REQUEST_URI'], 'edit.php' ) && stripos( $_SERVER['REQUEST_URI'], 'post_type=' . $typenow ) && in_array( 'edit_posts', $caps, true ) ) {
// Temporarily assign the user the edit_posts capability
$all_caps['edit_posts'] = true;
// Now Remove any menu items with edit_posts besides the custom post type pages.
if ( ! empty( $menu ) ) {
foreach ( $menu as $menu_key => $menu_item ) {
if ( ! empty( $menu_item[1] ) && ( $menu_item[1] === 'edit_posts' || $menu_item[2] === 'edit.php' ) ) {
remove_menu_page( $menu_item[2] );
}
}
}
}
return $all_caps;
},
10,
2
);
虽然这有效,但它确实添加了一些 PHP 未定义索引的通知。
选项 2:
(过滤 $pagenow 变量)
add_action(
'admin_menu',
function () {
global $pagenow, $typenow;
if ( is_admin() && ! empty( $typenow ) && ! empty( $pagenow ) && $pagenow === 'edit.php' && stripos( $_SERVER['REQUEST_URI'], 'edit.php' ) && stripos( $_SERVER['REQUEST_URI'], 'post_type=' . $typenow ) ) {
$pagenow = 'custom_post_type_edit.php';
}
}
);
这可以在不添加任何 PHP 通知的情况下工作,但可能会出现不可预见的问题,因为它正在更改 $pagenow 变量,但仅限于该页面。
到目前为止,我使用选项 2 没有任何问题。
我是 运行 一个 WordPress 5.2.3 站点,在管理面板中遇到问题。
我有一个自定义角色,我们称之为 librarian
,还有一个自定义 post 类型,我们称之为 book
。
我想做到 librarian
可以编辑 book
但不能创建新的。
按照另一个问题的建议(WordPress: Disable “Add New” on Custom Post Type) and WordPress documentation,我得到了这个代码:
// 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
));
我原以为当我以 librariran
的身份访问 edit.php?post_type=book
时,我会看到 books
的列表进行编辑,但我不会看到 添加新 按钮。但是,我实际得到的是 403
响应:
Sorry, you are not allowed to access this page.
我认为这可能是 WordPress 中的一个错误,原因如下:
- 如果我以
administrator
的身份访问edit.php?post_type=book
,那么我会看到列表页面没有 添加新的 按钮,如我所愿。 - 如果我给
librarian
角色赋予edit_posts
能力,那么我会看到没有 Add New 按钮的列表页面,如我所愿(但我不想给他们edit_posts
能力!)。
这些让我认为这不是一般设置的自定义 post 类型的问题。
- 如果我从
book
类型注册中删除'create_posts' => 'do_not_allow'
,librarian
可以 看到列表页面,但它包括添加新 按钮。
这让我觉得这不是一般设置自定义角色的问题。
有没有人遇到过这个问题?我是否遗漏了配置中的任何内容?或者有简单的补丁或解决方法吗?
如有任何帮助,我们将不胜感激!谢谢
这似乎是 WordPress 中的一个错误。我已经找到问题的根源和解决方法。
解决方法
如果您对原因不感兴趣,解决方法是在 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] ] );
}
}
这意味着一些以前不显示子菜单的菜单项现在会显示(单个项目与主菜单项相同),但这只是一个表面的 UI 更改。
原因
对于那些想了解细节的人……
访问 edit.php?post_type=book
时检查失败 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 );
}
对user_can_access_admin_page()
calls through to get_admin_page_parent()
的调用。
如果子菜单已被删除,get_admin_page_parent()
return 是一个空的父菜单,最终导致 user_can_access_admin_page()
错误地 return false
librarian
角色(administrator
角色因不同原因而被淘汰)。
如果子菜单保留在原位,get_admin_page_parent()
return 是一个非空父菜单,访问检查从那里正确进行。
因此,根本问题是全局 $submenu
被用于确定 UI 以及对权限层次结构做出决定。除了上述解决方法外,我没有看到对整个 WordPress 代码的其他地方不会产生副作用的此问题的立即快速修复。
我想我已经找到了解决方法,无需编辑核心文件。
正如您所说,这个错误的原因是没有子菜单项的空菜单会导致访问检查错误地 return false。
所以解决方法是不要让列表页面成为顶级菜单。相反,将其设为现有菜单下的子菜单。这样就不会有空的顶级菜单,您可以在没有 'Add New' 按钮的情况下正确看到列表页面。
这只是一种解决方法,并不理想。但也许它比编辑核心文件更好。
创建自定义 post 类型并将其列表页面作为子菜单项的方法很简单。 register_post_type
的参数有一个名为 show_in_menu
的参数。正如 the docs 所说:
If a string of an existing top level menu (eg. 'tools.php' or 'edit.php?post_type=page'), the post type will be placed as a sub-menu of that.
所以代码将是:
register_post_type('book',
array(
'labels' => array(
'name' => __( 'book' ),
'singular_name' => __( 'Book' )
),
'capability_type' => array('book', 'books'),
'capabilities' => array(
'create_posts' => 'do_not_allow'
),
'map_meta_cap' => true,
'show_ui' => true,
'show_in_menu' => 'tools.php', //or whatever top-level menu you'd like
//... other args omitted
));
我找到了 2 个解决此问题的方法:
首先,我发现为 custom_post_type create_posts 分配特定功能比使用 do_not_allow
所以我使用:
...
'capability_type' => 'books',
'capabilities' => array(
'create_posts' => 'add_new_books'
),
'map_meta_cap' => true,
...
这样我可以为管理员或其他经理分配 add_new_books 能力,但不允许其他角色使用它。
选项 1:
(过滤 user_has_cap 以假装用户拥有 'edit_posts' 但随后删除帖子菜单项)
add_filter(
'user_has_cap',
function( $all_caps, $caps ) {
global $typenow, $menu;
if ( is_admin() && ! empty( $typenow ) && stripos( $_SERVER['REQUEST_URI'], 'edit.php' ) && stripos( $_SERVER['REQUEST_URI'], 'post_type=' . $typenow ) && in_array( 'edit_posts', $caps, true ) ) {
// Temporarily assign the user the edit_posts capability
$all_caps['edit_posts'] = true;
// Now Remove any menu items with edit_posts besides the custom post type pages.
if ( ! empty( $menu ) ) {
foreach ( $menu as $menu_key => $menu_item ) {
if ( ! empty( $menu_item[1] ) && ( $menu_item[1] === 'edit_posts' || $menu_item[2] === 'edit.php' ) ) {
remove_menu_page( $menu_item[2] );
}
}
}
}
return $all_caps;
},
10,
2
);
虽然这有效,但它确实添加了一些 PHP 未定义索引的通知。
选项 2:
(过滤 $pagenow 变量)
add_action(
'admin_menu',
function () {
global $pagenow, $typenow;
if ( is_admin() && ! empty( $typenow ) && ! empty( $pagenow ) && $pagenow === 'edit.php' && stripos( $_SERVER['REQUEST_URI'], 'edit.php' ) && stripos( $_SERVER['REQUEST_URI'], 'post_type=' . $typenow ) ) {
$pagenow = 'custom_post_type_edit.php';
}
}
);
这可以在不添加任何 PHP 通知的情况下工作,但可能会出现不可预见的问题,因为它正在更改 $pagenow 变量,但仅限于该页面。
到目前为止,我使用选项 2 没有任何问题。