问题描述

我想添加几个编辑 can edit_others_posts,但我不希望他们能够发布其他帖子,只保存发布,点击提交审查按钮。

我该怎么做?

编辑:详细解释这一点。目前,我们无法允许用户通过仅保存帖子来编辑其他帖子。如果为用户启用了 edit_others_post,则可以发布该帖子。

工作流程我的目标是:

  1. 编辑者只能编辑其他待处理的帖子 (solved here) 。

  2. 编辑可以保存挂起的帖子,但不发布。所以提交审查按钮将可用于他们 (这是一个职位待处理模式下的”update post” 按钮)

最佳解决方案

如果我理解得很好,用户在您的站点中有特殊角色应该:

  • 能够编辑自己的所有状态的帖子,但’publish’ 并不能发布,只需发送修改

  • 只能在待机时编辑其他帖子,但无法发布,只需发送修改

  • 永远不能删除其他职位,无论状态如何

如果是这样,在我看来似乎更像’author’ 那么’editor’ 。

只有与作者的区别才是

  • 您的角色的用户无法编辑已发布的帖子,即使他们是作者

  • 您角色的用户可以编辑其他待处理的帖子,但不会发布

所以我可以给出的第一个建议是创建自定义角色,使用角色’author’ 作为点,删除 3 个不需要的上限并添加自定义的一个,一个简单的类:

class CustomEditorRole {

  private static $role = 'authorplus';
  private $role_label;

  function __construct() {
    // here we need a real, loaded, text domain
    $this->role_label = __( 'Author +', 'yout-txt-dmn' );
  }

  function addRole() {
    global $wp_roles;
    if ( ! $wp_roles instanceof WP_Roles ) {
      $wp_roles = new WP_Roles;
    }
    $author = get_role( 'author' );
    $caps = $author->capabilities; // start with author capabilities
    $caps['publish_posts'] = FALSE;
    $caps['edit_published_posts'] = FALSE;
    $caps['delete_published_posts'] = FALSE;
    $caps['edit_others_pending_posts'] = TRUE; // custom cap
    // create new role with custom caps
    add_role( self::$role, $this->role_label, $caps );
  }

  function removeRole() {
    global $wp_roles;
    if ( ! $wp_roles instanceof WP_Roles ) {
      $wp_roles = new WP_Roles;
    }
    remove_role(self::$role);
  }

}

让我们添加插件激活/停用操作:

register_activation_hook( __FILE__, array( new CustomEditorRole, 'addRole' ) );
register_deactivation_hook( __FILE__, array( new CustomEditorRole, 'removeRole' ) );

这里我假设以前的代码是在主要的插件文件。

我们上面设置的功能对于每个帖子都是有效的,无论发布作者还是帖子状态。现在,我们必须允许用户使用自定义角色来编辑其他的帖子。

我们遇到的第一个问题是,在帖子列表屏幕 (edit.php) 上,如果功能 edit_others_posts 没有为用户启用 (而且对于我们的自定义角色而言),则其他用户的帖子不会显示在列表中,因为已删除从查询出发,当发生查询时,我们无法访问帖子数据,因此我们必须至少分配能力,mo 事件状态,至少直到查询运行。

第二个问题是,在保存之前,给予用户自定义角色的 edit_others_posts 上限,我们不仅要检查当前的状态是”pending”,而且用户还没有尝试改变它。可以在 $_POST 数据中查看信息。这意味着我们需要 2 个”routines”,一个在管理屏幕 (edit.phppost.php) 上运行,在后期保存期间运行。

给我们的自定义角色用户的方式只有待处理的帖子 edit_others_post 功能是添加一个过滤器到'user_has_cap'

在过滤器回调中,我们可以实现此工作流程:

  1. 检查过滤功能是否为我们要管理的 2('edit-post''edit-others-posts'),检查我们是否在管理员中,检查用户是否具有我们的自定义功能,而不是编辑器或管理员,如果所有条件都为真我们可以继续,否则我们不得不做任何事情,即返回原始能力

  2. 检查我们是否保存,并运行 2 个不同的例程:

    • 保存时常常

    • 不保存时的例行程序

保存时常常:

  1. 检查当前操作是否是编辑帖子

  2. 从 $ _POST 数据获取帖子信息,检查帖子是否具有正确的帖子类型并处于待处理状态

  3. 检查挂起状态只能由管理员或”real” 编辑器更改

  4. 如果所有以前的检查通过,分配给用户'edit-others-posts'功能 ('edit-post'将自动映射)

不保存的例程

  1. 检查我们是否在两个感兴趣的屏幕之一,如果不是,什么都不做

  2. 不同的行为取决于过滤能力:

    • 当过滤能力是'edit-others-posts'时,我们没有发布数据,所以只需要分配它,但只有在主查询之前还没有发生,只有在 edit.php 屏幕上

    • 当过滤能力是'edit-post'获取帖子数据,并且如果帖子待处理分配给用户'edit-others-posts'上限 ('edit-post'将被自动映射)

有最后要做的事情。使用所描述的工作流自定义角色,用户将无法预览其他待处理的帖子,即使他们能够编辑它们。

我们可以再次过滤功能,但是有一个更简单的方法:在主查询 (使用 WP_Query 触发的十几个钩子中的一个) 时,我们可以在 $wp_post_statuses['pending']对象中设置 public 属性为 true,当前用户具有自定义角色:唯一的效果是待处理的帖子是可预览的,一旦我们不改变任何能力,我们就能保持安全。

好的,只是在代码中翻译单词:

class CustomEditorCaps {

  function manageCaps( $allcaps, $caps, $args, $user ) {
    if ( ! $this->shouldManage( $args[0], $user ) ) {
      return $allcaps;
    }
    // Are we saving?
    $action = filter_input( INPUT_POST, 'action', FILTER_SANITIZE_STRING );
    $method = strtoupper(filter_var($_SERVER['REQUEST_METHOD'], FILTER_SANITIZE_STRING ));
    if ( $method !== 'POST' ) { // not saving
      global $pagenow;
      // we are interested only on post list and  post edit screens
      if (
        is_admin()
        && in_array( $pagenow, array( 'post.php', 'post-new.php', 'edit.php' ), TRUE
      ) ) {
        $screen_id = $pagenow === 'edit.php' ? 'edit-post' : 'post';
        $allcaps = $this->maybeAllow( $args, $allcaps, $user, $screen_id );
      }
    } elseif ( $action === 'editpost' ) { // saving and right action
      $allcaps = $this->maybeAllowOnSave( $args, $allcaps, $user  );
    }
    return $allcaps; // always return: it's a filter
  }

  function lockPendingStatus( $data, $postarr ) {
    if (
       isset( $postarr['ID'] )
       && ! empty($postarr['ID'])
       && $data['post_type'] === 'post' // 'post' post type
       && $data['post_status'] !== 'pending' // a non pending status
       && ! current_user_can( 'delete_others_posts' ) // current user is not an admin
    ) {
       $orig = get_post_status( $postarr['ID'] );
       if ( $orig === 'pending' ) { // hey post was pending!
          $data['post_status'] = 'pending'; // let's restore pending status
       }
    }
    return $data; // always return: it's a filter
  }

  function allowPreview( $posts, $query ) {
    if ( is_admin()
      || ! $query->is_main_query()
      || empty( $posts )
      || ! $query->is_single
      || $posts[0]->post_type !== 'post'
    ) {
      return $posts; // return first argument: it's a filter
    }
    $status = get_post_status( $posts[0] );
    $post_status_obj = get_post_status_object( $status );
    if (
      ! $post_status_obj->public
      && $status === 'pending'
      && current_user_can('edit_others_pending_posts')
    ) {
      // post is pending and our user has our special role
      // allow preview
      global $wp_post_statuses;
      $wp_post_statuses[$status]->public = TRUE;
    }
    return $posts; // return first argument: it's a filter
  }

  private function maybeAllow( $args, $allcaps, $user, $screen ) {
    if ( $args[0] === 'edit_others_posts' ) {
      // if filtering 'edit_others_posts' we have no access to single post data
      // allow cap only on post list screen and before querying posts
      $allcaps['edit_others_posts'] = ! did_action('pre_get_posts')
        && $screen === 'edit-post';
      return $allcaps;
    }
    $post = get_post( $args[2] );
    if (  $post->post_status === 'pending' ) {
      $allcaps['edit_others_posts'] = TRUE;
    }
    return $allcaps; // always return: it's a filter
  }

  private function maybeAllowOnSave( $args, $allcaps, $user ) {
    $data = $this->getPostedData();
    if ( $data['post_type'] !== 'post' || (int) $data['post_ID'] <= 0 ) {
      return $allcaps;
    }
    $post = get_post( $data['post_ID'] );
    if (
      $post->post_status === 'pending'
      && $data['original_post_status'] === 'pending'
      && ( empty( $data['post_status'] ) || $data['post_status'] === 'pending' )
    ) {
      // if post is pending and will stay pending allow editing
      $allcaps['edit_others_posts'] = true;
    }
    return $allcaps;
  }

  private function shouldManage( $cap, $user ) {
    return is_admin() // not affect frontend
      && in_array( $cap, array( 'edit_others_posts', 'edit_post' ), TRUE )
      && ! $user->has_cap( 'delete_others_posts' ) // real editor or more
      && $user->has_cap( 'edit_others_pending_posts' ) // our role
      && ! defined( 'DOING_AJAX' ); // does not affect ajax
  }

  private function getPostedData() {
    return filter_input_array( INPUT_POST, array(
      'post_type'            => FILTER_SANITIZE_STRING,
      'post_ID'              => FILTER_SANITIZE_NUMBER_INT,
      'original_post_status' => FILTER_SANITIZE_STRING,
      'post_status'          => FILTER_SANITIZE_STRING,
    ) );
  }

}

并添加 2 个相关的钩子:一个用于过滤'user_has_cap',一个用于确保挂起状态只能由管理员或实际编辑者更改,最后一次过滤'posts_results'以允许预览:

$cap_manager = new CustomEditorCaps;
add_filter( 'user_has_cap', array( $cap_manager, 'manageCaps' ), PHP_INT_MAX, 4 );
add_filter( 'posts_results', array( $cap_manager, 'allowPreview' ), 10, 2 );
add_filter( 'wp_insert_post_data', array( $cap_manager, 'lockPendingStatus' ), 10, 2 );

一旦将所有这些代码插入到插件中并激活它,您只需要为自定义角色插件创建的用户分配。


所有代码可用作插件,在 Gist here 中。

参考文献

注:本文内容整合自 Google/Baidu/Bing 辅助翻译的英文资料结果。如果您对结果不满意,可以加入我们改善翻译效果:薇晓朵技术论坛。