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

xdnvmnnf  于 2023-01-04  发布在  WordPress
关注(0)|答案(4)|浏览(107)

我正在运行一个WordPress 5.2.3网站,在管理面板中的东西有麻烦。
我有一个自定义角色,我们称之为librarian,还有一个自定义帖子类型,我们称之为book
我想使librarian可以编辑book,但不能创建新的book
按照另一个问题(WordPress: Disable “Add New” on Custom Post Type)和WordPressdocumentation中的建议,我最终得到了以下代码:

// 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的列表以供编辑,但我看不到Add New按钮。然而,我实际上得到的是403的响应:
对不起,您没有权限访问此页面。
我认为这可能是WordPress中的一个错误,因为以下情况:

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

这些让我认为这不是一个问题,自定义后类型设置一般。

  • 如果我从book类型注册中删除'create_posts' => 'do_not_allow'librarian * 可以 * 看到列表页面,但它包括Add New按钮。

这使我认为,这不是一个问题,与自定义角色设置一般。
以前有没有人遇到过这个问题?我的配置中有没有遗漏什么?或者有没有简单的补丁或变通方法?
任何帮助都将不胜感激!谢谢。

njthzxwz

njthzxwz1#

看来这是WordPress中的一个bug。我已经找到了问题的根源和解决方法。

变通方案

如果您对原因不感兴趣,解决方法是注解掉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()的调用会调用到get_admin_page_parent()
如果子菜单已被删除,则get_admin_page_parent()返回一个空父级,这最终导致user_can_access_admin_page()librarian角色的情况下错误地返回falseadministrator角色由于不同原因而通过)。
如果子菜单保留在原处,get_admin_page_parent()将返回一个非空的父级,访问检查将从那里正确地进行。
所以根本问题是全局的$submenu被用来决定用户界面和权限层次结构。我没有看到一个即时的快速解决这个问题,不会有副作用在整个WordPress代码的其他地方,除了上面的变通办法。

noj0wjuj

noj0wjuj2#

我想我已经找到了一个解决办法,不必编辑核心文件。
正如您所说,此bug的原因是没有子菜单项的空菜单将导致访问检查错误地返回false。
因此,解决方法是不要将列表页设置为顶级菜单。而是将其设置为现有菜单下的子菜单。这样就不会有空的顶级菜单,并且您可以在没有“添加新项”按钮的情况下正确查看列表页。
这只是一种变通方法,并不理想,但也许比编辑核心文件要好。
创建一个自定义帖子类型的方法很简单,同时将其列表页面作为子菜单项。register_post_type的args有一个名为show_in_menu的args。正如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
));
yshpjwxd

yshpjwxd3#

我找到了此问题的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没有任何问题。

3z6pesqy

3z6pesqy4#

这个代码对我有用

/**
 *  Fix Disable add new cpt post (post-new.php?post_type=) - craete_posts
 */

function disable_create_newpost()
{
    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 = 'edit-' . $typenow . '.php';
    }
}

add_action('admin_menu', 'disable_create_newpost');

// END PART

感谢@ggedde

相关问题