当指向CPT用户界面的链接被放置为子菜单时,如何允许CPT的“添加新的”功能?

时间:2020-05-29 作者:Mort 1305

编辑:去拿“学生”徽章,想知道是否有人可以投票支持这个问题?)

我回答说a question 关于能力,但现在我需要这方面的帮助。在审查之后the manual, 我更困惑了。

我有两种自定义帖子类型,管理员都可以完全访问。我有订阅者,有些人可以访问其中一个CPT,它是第一个CPT的“子”并将其父ID存储在_adm_id 元数据。这些“特殊”订阅者可以访问父CPT admin表,因此他们可以单击链接来创建具有特殊状态的父CPT帖子。接下来,允许订阅者编辑子帖子(包括其自己的帖子和其他人创建的帖子),但前提是它具有特定的自定义帖子状态。最后,特别订阅者不允许删除帖子(或编辑已删除的帖子),甚至不允许删除自己的帖子。

以下是我得到的(工作代码)。。。

// Setup custom post types and statuses
add_action(\'init\', function() {
    // Custom Post Types
    register_post_type(\'adm-cpt\', array(
        \'label\'             => __(\'Admin Only CPT\'),
        \'show_ui\'           => TRUE,
        \'show_in_menu\'      => \'my-menu-item\',
        \'show_in_admin_bar\' => FALSE,
        \'capability_type\'   => \'adm\',
        \'map_meta_cap\'      => TRUE,
        \'capabilities\' => array(
            \'create_posts\'  => \'administrator\', // Only admin can create, not special Subscribers
        ),
    ));
    register_post_type(\'sub-cpt\', array(
        \'label\'             => __(\'Subscriber/Admin CPT\'),
        \'show_ui\'           => TRUE,
        \'show_in_menu\'      => \'my-menu-item\',
        \'show_in_admin_bar\' => FALSE,
        \'capability_type\'   => \'sub\',
        \'map_meta_cap\'      => TRUE,
    ));
    // Custom Post Statuses
    foreach(array(
        \'adm-childable\'     => __(\'Can Create Children\'),
        \'sub-editable\'      => __(\'Any Subscriber Can Edit\'),
    ) as $slug => $label) {
        register_post_status($slug, array(
            \'label\'         => _x($label, \'post\'),
            \'label_count\'   => _n_noop($label .\' <span class="count">(%s)</span>\', $label .\' <span class="count">(%s)</span>\' ),
            \'public\'        => TRUE,
        ));
    }
});

// Setup parent page in admin menu
add_action(\'admin_menu\', function() {
    // Add menu item
    if(current_user_can(\'administrator\')
    || current_user_can(\'special-subscriber\')
    ) {
        // Admin menu header
        add_menu_page(
            NULL,
            \'CPTs\',
            \'exist\',
            \'my-menu-item\',
            \'\'
        );
    }
});

// Set up role
add_action(\'wp_roles_init\', function($wp_roles){
    // Prepare
    $role = \'special-subscriber\';
    $caps = array(
        \'delete_subs\'           => FALSE,   // No trashing ...
        \'delete_others_subs\'    => FALSE,
        \'delete_published_subs\' => FALSE,
        \'delete_private_subs\'   => FALSE,
        \'edit_published_subs\'   => FALSE,   // And no editing published/private posts ...
        \'edit_private_subs\'     => FALSE,
        \'edit_adms\'             => TRUE,    // Allow viewing of adm-cpt table
        \'edit_posts\'            => TRUE,    // WARNING:  Here\'s the permission that is causing the problems!
    );
    $name = __(\'"Special" Subscriber\');
    // Update role in database, if needed
    if($wp_roles->get_role($role) === NULL
    || $wp_roles->get_role($role)->capabilities != $caps
    || $wp_roles->roles[$role][\'name\'] !== $name
    ) {
        $wp_roles->remove_role($role);
        $wp_roles->add_role($role, $name, $caps);
    }
});

// Dynamicly set capabilities
add_action(\'user_has_cap\', function($allcaps, $caps, $args, $user) {
    foreach($caps as $cap) {
        $perm = substr($cap, 0, strrpos($cap, \'_\'));
        $type = substr($cap, strlen($perm)+1);
        if(in_array($type, array(\'adm\', \'adms\')) && in_array(\'administrator\', $user->roles)
        || in_array($type, array(\'sub\', \'subs\')) && !empty(array_intersect(array(\'administrator\', \'special-subscriber\'), $user->roles))
        ) {
            // Check Subscriber if post is editable
            if(in_array($cap, array(\'edit_subs\', \'edit_others_subs\'))
            && in_array(\'special-subscriber\', $user->roles)
            && !in_array(\'administrator\', $user->roles)
            && !empty($args[2])
            &&  (   !in_array(get_post_status($args[2]), array(\'sub-editable\'))
                        && !in_array($_REQUEST[\'original_post_status\'], array(\'sub-editable\', \'auto-draft\'))    // Creating
                ||  get_post_status(get_post_meta($args[2], \'_adm_id\', TRUE)) === \'trash\'
                )
            ) {
                $allcaps[$cap] = FALSE;
            }
            // Add the cap
            if(!isset($allcaps[$cap])
            ) {
                $allcaps[$cap] = TRUE;  // All the _adm and _sub capabilities are made available.
            }
        }
    }
    return $allcaps;
}, 10, 4);

// Add stuff to force proper navigation
add_action(\'post_row_actions\', function($actions, $post) {
    // Add link to adm-cpt table entries to create child
    if(get_post_type($post) === \'adm-cpt\'
    && get_post_status($post) === \'adm-childable\'
    && current_user_can(\'edit_subs\')
    ) {
        $lbl = __(\'New \'). get_post_type_object(\'sub-cpt\')->labels->name;
        $actions[\'adm-cpt-create-sub-cpt\'] = sprintf(
            \'<a href="%s" aria-label="%s">%s</a>\',
            admin_url(\'post-new.php?post_type=sub-cpt&adm_id=\'. $post->ID),
            esc_attr(\'“\'. $lbl .\'”\'),
            $lbl
        );
    }
    // Return
    return $actions;
}, 10, 2);

// Modify publish metabox
add_action(\'post_submitbox_misc_actions\', function($post) {
    $arr = array();
    switch(get_post_type($post)) {
        case \'adm-cpt\':
            $arr = array(\'adm-childable\');
            break;
        case \'sub-cpt\':
            $arr = array(\'sub-editable\');
            break;
        default:
            return;
    }
    // Check that parent exists -- Should be in an init hook, but it\'s prettier here.
    if($_REQUEST[\'post_type\'] === \'sub-cpt\'
    &&  (empty($_REQUEST[\'adm_id\']) || get_post_type($_REQUEST[\'adm_id\']) !== \'adm-cpt\')
    &&  (empty($post->_adm_id) || get_post_type($post->_adm_id) !== \'adm-cpt\')
    ){
        ?><script>window.document.location.replace("<?= admin_url(\'edit.php?post_type=adm-cpt\') ?>")</script><?php
        return;
    }
    // Add custom post statuses
    ?><input type=\'hidden\' name=\'adm_id\' value=\'<?= $_REQUEST[\'adm_id\'] ?>\'><?php
    if(count($arr)) {
        ?><script>
        <?php foreach($arr as $k) { $obj = get_post_status_object($k); ?>
            jQuery("select#post_status").append("<option value=\\"<?= $k ?>\\"><?= $obj->label ?></option>");
            <?php if(get_post_status($post) == $k) { ?>
                jQuery("#post-status-display").text("<?= $obj->label ?>");
                jQuery("select#post_status").val("<?= $k ?>");
            <?php } ?>
        <?php } ?>
        </script><?php
    }
    // Display parent -- Informational
    if(!empty($_REQUEST[\'adm_id\'])
    || !empty($post->_adm_id)
    ) {
        $parent_id = $post->_adm_id;
        if(!$parent_id) $parent_id = $_REQUEST[\'adm_id\'];
        ?><div class="misc-pub-section misc-pub-adm-cpt">Parent:  <span id="post-status-display"><?= get_the_title($parent_id) ?></span></div><?php
    }
});

// Save parent ID
add_action(\'save_post_sub-cpt\', function($post_id, $post, $update) {
    // Ensure we continue only id a new child is created
    if(defined(\'DOING_AUTOSAVE\') && DOING_AUTOSAVE
    || get_post_type($post_id) !== \'sub-cpt\'
    || empty($_REQUEST[\'adm_id\'])
    || get_post_type($_REQUEST[\'adm_id\']) !== \'adm-cpt\'
    ) return;
    // Set parent ID
    update_post_meta($post_id, \'_adm_id\', $_REQUEST[\'adm_id\']);
}, 10, 3);

// Navigation when changed to uneditable
add_action(\'load-post.php\', function(){
    if(!empty($_REQUEST[\'post\'])
    && get_post_type($_REQUEST[\'post\']) === \'sub-cpt\'
    && !current_user_can(\'edit_subs\', $_REQUEST[\'post\'])
    ) {
        delete_post_meta($_REQUEST[\'post\'], \'_edit_lock\');
        wp_redirect(\'edit.php?post_type=sub-cpt\');
        die();
    }
});
这里的问题是,特别订阅者可以编辑常规帖子和评论。我知道这是edit_posts 功能,该功能允许编辑/创建all 职位类型。但是,删除它会阻止特殊订阅者创建sub-cpt 职位和授予edit_subs 无法解决问题。设置capabilities->create_post=special-subscriber 注册子CPT时。我可以限制订阅者创建adm-cpt 通过定义capabilities 注册帖子类型时的参数。但我不希望特殊订阅者能够编辑/创建除sub-cpt 键入,但我似乎不知道如何键入。

我找到了a Q&A 与主题相关,但这似乎不起作用。CPT映射到自定义功能,它们存在,并且user_has_cap filter动态授予每个功能。我甚至尝试在special-subscriber 角色定义。不管怎样,我相信变化很简单--“它是什么?”?

(如果您感兴趣,我还有另一个功能问题。当特殊订阅者设置子CPT时post_status 若要发布,将锁定帖子并将其转发到edit.php 但我想解锁帖子,让观众转发到edit.php?post_type=sub-cpt 就像在load-post.php 我的代码的钩子,我似乎不知道怎么做。)

UPDATE: 我已经将其隔离到菜单中CPT的位置。当CPT注册为使用register_post_type 选项show_in_menu=TRUE, 一切正常。但是,当CPT被添加为一个老式管理菜单项的子菜单时,情况就不一样了。添加UI并隐藏它会导致相同的问题,同时添加子页面并将其重定向到CPT的UI。示例:

// 1.)  Works as expected if user has every custom capability
add_action(\'init\', function() {
    register_post_type(\'sub-cpt\', array(
        \'label\'             => __(\'Subscriber/Admin CPT\'),
        \'show_ui\'           => TRUE,
        \'show_in_menu\'      => TRUE,    // Take note of this
        \'show_in_admin_bar\' => FALSE,
        \'capability_type\'   => \'sub\',
        \'map_meta_cap\'      => TRUE,
    ));
}

// 2.)  Same as #1 with the exception that access to \'post-new.php\' when "Add New" button is clicked is prohibited
add_action(\'init\', function() {
    register_post_type(\'sub-cpt\', array(
        \'label\'             => __(\'Subscriber/Admin CPT\'),
        \'show_ui\'           => TRUE,
        \'show_in_menu\'      => \'my-menu-item\',  // Take note of this
        \'show_in_admin_bar\' => FALSE,
        \'capability_type\'   => \'sub\',
        \'map_meta_cap\'      => TRUE,
    ));
}
add_action(\'admin_menu\', function() {
    add_menu_page(
        \'CPT in title bar\',
        \'CPT in menu\',
        \'edit_subs\',
        \'my-menu-item\',
        \'\'
    );
}

// 3.)  Breaks the same as #2
add_action(\'init\', function() {
    register_post_type(\'sub-cpt\', array(
        \'label\'             => __(\'Subscriber/Admin CPT\'),
        \'show_ui\'           => TRUE,
        \'show_in_menu\'      => FALSE,   // Take note of this
        \'show_in_admin_bar\' => FALSE,
        \'capability_type\'   => \'sub\',
        \'map_meta_cap\'      => TRUE,
    ));
}
add_action(\'admin_menu\', function() {
    global $submenu;
    add_menu_page(
        \'CPT in title bar\',
        \'CPT in menu\',
        \'edit_subs\',
        \'my-menu-item\'
    );
    add_submenu_page(
        \'my-menu-item\',
        get_post_type_object(\'sub-cpt\')->label,
        get_post_type_object(\'sub-cpt\')->label,
        \'edit_subs\',
        \'my-menu-item-sub\'
    );
    // Change link
    $url = \'edit.php?post_type=sub-cpt\';
    $submenu[\'my-menu-item\'][1][2] = admin_url($url);   // Set URL to view CPT
    unset($submenu[\'my-menu-item\'][0]);             // Remove WP generated menu item
});
如果我可以将“添加新”功能作为子页面与CPT一起使用,我想我的问题会得到解决,因为edit_posts 给我带来麻烦的能力可以具体映射到edit_subs. 有人知道怎么做吗?

3 个回复
最合适的回答,由SO网友:Mort 1305 整理而成

问题是,当特殊订户试图Add New 子cpt帖子,被拒绝权限。然而,当CPT菜单是一个顶级管理菜单时,一切正常。这个问题与CPT的UI菜单在后端的位置有关:如果是顶级的(show_in_menu=TRUE), 一切都很好;如果是子菜单(show_in_menu=\'my-menu-item\'), 用户无法创建帖子类型,除非它具有edit_posts 权限(即使它具有所有edit_PostType 权限)。从22号开始我就一直在追这个愚蠢的东西。多亏了这场大流行,我没有做太多其他事情。在8天中的每一天12-15个小时后,我终于把这个小家伙挑出来了。

这个问题与post-new.php, 当CPT在post.php 脚本(几乎相同)。首先post-new.php is是否打开admin.php. 在线153, wp-admin/menu.php 被调用到bat,其中包括wp-admin/includes/menu.php 作为其last execution. 在那上面includes/menu.php 文件的line 341, 这个user_can_access_admin_page() 退货FALSE, 触发do_action(\'admin_page_access_denied\') 要发射的挂钩和wp_die(__(\'Sorry, you are not allowed to access this page.\'), 403) 命令终止整个进程。

这个user_can_access_admin_page() 方法定义于line 2042wp-admin/includes/plugin.php 文件Line 2064 通过了检查get_admin_page_parent() 是空的。然后是line 2078 未能签入以下变量:$_wp_submenu_nopriv[\'edit.php\'][\'post-new.php\'] 已设置。这些检查的综合效果FALSE 返回布尔值,WordPress死亡。

我所知的最相关的脚本是post.php, 作为admin.php 立即调用进程并以相同的方式运行,包括调用user_can_access_admin_page(). 调试表明user_can_access_admin_page() 在中通过post.php 脚本,因为post-new.php, 没有$_wp_submenu_nopriv[____][$pagenow] 已设置标志。所以,问题是为什么要为post-new.php 未设置为post.php.

这个global $_wp_submenu_nopriv 第一次设置为online 71 属于wp-admin/includes/menu.php, 其中该变量初始化为空数组。如果current_user_can() 测试未通过line 79, 标志设置为online 81. 在这一点上global $submenu[\'edit.php\'] 初始化到我们关注的点,并包含位于*index=*10处的数组(“Add New”、“edit\\u posts”、“post New.php”)。回顾admin menu positioning) 显示此条目是Add New 系统为标准WP帖子建立链接。发生的检查测试当前用户是否有权限edit_posts. 由于特殊订户用户无法编辑“帖子”,检查失败,系统中断。当我得知这一点时,比赛开始了$submenu[\'edit.php\'][\'post-new.php\'] 之前的条目line 81 属于wp-admin/includes/menu.php 已执行。如果一个人从那条线后退到wp-admin/menu.php, 可以发现,问题标志设置为line 170 执行$submenu[$ptype_file][10] = array($ptype_obj->labels->add_new, $ptype_obj->cap->create_posts, $post_new_file). 因此,代码中这两点之间的钩子将允许我们插入并解除引起我这么多冲突的旗帜。

在此设置之后,使用可用挂钩调用的第一个函数是current_user_can(\'switch_themes\') 在…上line 185. 随后调用user_has_cap 因为这个蠕动的标志出现的次数多得数不清,所以它并不是最好的钩子。在此之后,唯一可用的直接挂钩是_network_admin_menu, _user_admin_menu, 或_admin_menu 在中找到/wp-admin/includes/menu.php 直接在最顶端the file (根据请求是针对网络管理界面、用户管理界面还是两者都不针对,只有其中一个会触发)。由于从一个不相关的函数调用过滤器是一种迂回的处理方式,因此我选择使用这些挂钩,如下所示:

add_action(\'_network_admin_menu\', \'pick_out_the_little_bugger\');
add_action(\'_user_admin_menu\', \'pick_out_the_little_bugger\');
add_action(\'_admin_menu\', \'pick_out_the_little_bugger\');
function pick_out_the_little_bugger() {
    // If the current user can not edit posts, unset the post menu
    if(!current_user_can(\'edit_posts\')) {
        global $submenu;
        $problem_child = remove_menu_page(\'edit.php\');  // Kill its parent and get its lineage.
        unset($submenu[$problem_child[2]]);         // "unset" is just too nice for this wormy thing.
    }
}
Jeezers这是在黑暗中拍摄的,用不到十几行代码就可以完成很多工作!自从我发现一群人有同样的问题,我opened a ticket 修改WordPress核心。

SO网友:Himad

我前段时间遇到过这个问题,我会尽量抽出时间来正确调试问题的根源,但与此同时,请尝试以下方法:

/*
This is due to a bug that doesn\'t grant permission to the post-new.php unless there is a
submenu with the link accesible for the user.
*/
global $submenu;
$submenu[\'your_menu\'][] = array(
    \'Hide me\', # Do something to hide it or just leave it blank.
    \'create_posts\',
    \'post-new.php?post_type=your_post_type\',
);  

SO网友:Dave S

在调试了一个小时后,我很高兴发现了这个问题,并意识到这是一个已知的bug。我采取了一种稍微不同的方法来绕过它。

当CPT UI是一个子菜单页,CPT具有自定义功能或功能类型,并且当前用户没有“edit\\u posts”功能时,就会出现此错误。我创建了一个自定义菜单页面,其中包含几个自定义的帖子类型和分类,避免了管理菜单的混乱。但是当用户没有“edit\\u posts”功能时,我只需要显示一个或两个CPT,因此我并不真正需要自定义菜单页面。为了解决这个问题,我检查当前用户是否可以编辑\\u帖子,并在此基础上在菜单中指定show\\u。

class Admin_Menu_Page {
    const MENU_SLUG = \'my-menu-slug\';
    // ...
    public static function get_CPT_show_in_menu() {
        if ( current_user_can( \'edit_posts\' ) ) {
            $result = self::MENU_SLUG;
        } else {
            $result = TRUE;
        }
        return $result;
    }
    public static function create_menu() {
        if ( self::get_CPT_show_in_menu() === self::MENU_SLUG ) {
            // Create my custom menu page
        }
    }
}

class My_CPT {
    const POST_TYPE = \'my-post-type\';
    // ...
    public static function register() {
        $args = array(
            // ...
            \'show_in_menu\'    => Admin_Menu_Page::get_CPT_show_in_menu(),
            // ...
        );
        register_post_type( self::POST_TYPE, $args );
    }
}