具有自定义角色的WordPress用户无法在没有“ create_posts”功能的情况下查看自定义帖子类型的列表页面

时间:2019-10-03 11:46:21

标签: wordpress wordpress-capabilities wordpress-roles

我正在运行WordPress 5.2.3网站,并且在管理面板中遇到问题。

我有一个自定义角色,我们将其称为librarian,而一个自定义帖子类型,我们将其称为book

我想这样做,以便librarian可以编辑book但不能创建一个新的

按照另一个问题(WordPress: Disable “Add New” on Custom Post Type)和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
));

我期望当我以edit.php?post_type=book的身份访问librariran时,会看到books的列表进行编辑,但看不到添加新内容按钮。但是,我实际上得到的是一个403响应:

  

对不起,您不能访问此页面。

由于以下情况,我认为这可能是WordPress中的错误:

  • 如果我以edit.php?post_type=book的身份访问administrator,那么我会看到列表页面中没有添加新按钮,
  • 如果我为librarian角色提供了edit_posts功能,那么我会看到列表页面没有所需的 Add New 按钮(但我不想这样做)赋予他们edit_posts的能力!)。

这些使我觉得一般设置的自定义帖子类型不是问题。

  • 如果我从'create_posts' => 'do_not_allow'类型注册中删除了book,则librarian 可以看到列表页面,但是它包含添加新内容按钮。

这使我认为一般设置的自定义角色不是问题。

以前有人遇到过此问题吗?我错过了配置中的任何内容吗?还是有简单的补丁或解决方法?

任何帮助将不胜感激!谢谢。

3 个答案:

答案 0 :(得分:3)

看来这是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的{​​{1}}检查失败:

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

wp-admin/includes/menu.php

打到user_can_access_admin_page()的电话一直打到get_admin_page_parent()

如果子菜单已删除,则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 ); } 返回一个空的父级,这最终导致get_admin_page_parent()user_can_access_admin_page()角色的情况下错误地返回false({{ 1}}角色通过的原因有所不同。

如果子菜单留在原处,librarian将返回一个非空的父级,并且访问检查将从此处正确进行。

因此,根本问题是全局administrator既用于确定UI,也用于决策权限层次结构。除了上面的解决方法之外,我没有看到针对此问题的快速解决方案,该问题不会在整个WordPress代码的其他地方产生副作用。

答案 1 :(得分:0)

我想我已经找到了解决此问题的方法,而无需编辑核心文件。

正如您所说,此错误的原因是没有子菜单项的空白菜单将导致访问检查错误地返回false。

因此,解决方法是不要使列表页面成为顶层菜单。而是将其设为现有菜单下的子菜单。这样,顶层菜单将不会出现空白,并且您可以在没有“添加新”按钮的情况下正确看到列表页面。

这只是一种解决方法,并不理想。但是也许比编辑核心文件要好。

在将自定义帖子类型放入列表页面作为子菜单项时创建自定义帖子类型的方法很简单。 register_post_type的arg有一个名为show_in_menu的arg。正如the docs所说:

如果现有顶级菜单的字符串(例如'tools.php'或'edit.php?post_type = page'),则帖子类型将作为该菜单的子菜单放置。

因此代码将是:

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 :(得分:0)

我找到了 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',然后删除 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 没有任何问题。