解释 `current_user_can()` 函数的源码,它是如何检查用户是否拥有特定权限的?

好吧,各位听众,今天咱们就来扒一扒 WordPress 权限系统里那个至关重要的函数:current_user_can()。 这家伙可是个“保安”,负责检查当前用户是不是有资格做某些事情,比如发帖子、编辑用户、安装插件等等。 咱们要深入源码,看看它到底是怎么运作的,让大家以后都能像驾驭自己的老伙计一样驾驭它。

开场白:权限的重要性

想象一下,你家小区没保安,那岂不是谁都能进进出出?WordPress 也是一样。如果没有权限系统,随便一个游客都能删除你的文章,甚至把你的网站搞瘫痪,那还得了? 所以,权限系统是安全的基础,而 current_user_can() 就是这个安全体系的关键守门人。

current_user_can() 的基本用法

首先,我们先熟悉一下 current_user_can() 的基本用法。这个函数接受一个或多个参数,第一个参数是要检查的权限(capability),后面的参数是可选的,可以用来传递一些上下文信息。

if ( current_user_can( 'edit_posts' ) ) {
  // 当前用户有编辑文章的权限,可以执行某些操作
  echo '你拥有编辑文章的权限!';
} else {
  echo '抱歉,你没有编辑文章的权限。';
}

这里 'edit_posts' 就是一个 capability。 WordPress 内置了很多 capability,比如 'publish_posts'(发布文章)、'delete_posts'(删除文章)、'manage_options'(管理选项)等等。 你也可以自定义 capability,让你的插件或主题更加灵活。

深入源码:current_user_can() 的真面目

现在,让我们一起跳进 current_user_can() 的源码里,看看它到底是怎么工作的。 在 WordPress 的 pluggable.php 文件中,你会找到这个函数的定义:

function current_user_can( $capability ) {
  $args = array_slice( func_get_args(), 1 );
  return apply_filters( 'current_user_can', get_current_user_id() ? user_can( get_current_user_id(), $capability, ...$args ) : false, $capability, get_current_user_id(), $args );
}

看起来有点吓人,是不是? 别怕,我们一步一步来拆解。

  1. $args = array_slice( func_get_args(), 1 );: 这行代码获取传递给 current_user_can() 函数的所有参数,除了第一个参数(capability 本身)。 func_get_args() 获取所有参数, array_slice() 从索引 1 开始(也就是第二个参数)提取出一个新的数组,存储在 $args 变量中。 这些参数会在后面传递给 user_can() 函数,用于更精细的权限检查。

  2. get_current_user_id(): 这个函数获取当前用户的 ID。 如果用户未登录,它会返回 0。

  3. user_can( get_current_user_id(), $capability, ...$args ): 这才是权限检查的核心! user_can() 函数负责判断用户是否拥有指定的 capability。 它接收用户 ID、capability 和额外的参数($args)作为输入。 ...$args 是 PHP 的“解包”操作符,它会将 $args 数组中的元素展开,作为 user_can() 函数的单独参数传递进去。

  4. apply_filters( 'current_user_can', ... ): 这是一个 WordPress 的过滤器(filter)。 它允许你通过插件或主题来修改 current_user_can() 的返回值。 也就是说,你可以自定义权限检查的逻辑,覆盖 WordPress 默认的行为。

    • 第一个参数 'current_user_can' 是过滤器的名称。
    • 第二个参数是 user_can() 的返回值(truefalse),也就是默认的权限判断结果。
    • 后面的参数 $capability, get_current_user_id(), $args 会被传递给过滤器函数,方便你进行更复杂的权限检查。
  5. get_current_user_id() ? ... : false: 这是一个三元运算符。 如果 get_current_user_id() 返回一个非零值(表示用户已登录),则执行 user_can() 函数并返回其结果;否则(用户未登录),直接返回 false,表示用户没有权限。

user_can() 函数的奥秘

current_user_can() 本身只是个“门卫”,真正的权限检查是由 user_can() 完成的。 user_can() 的定义在 wp-includes/capabilities.php 文件中。

function user_can( $user, $capability ) {
  $args = array_slice( func_get_args(), 2 );
  return call_user_func_array( array( wp_roles(), 'user_has_cap' ), array_merge( array( $user, $capability ), $args ) );
}

让我们来分析一下这段代码:

  1. $args = array_slice( func_get_args(), 2 );: 和 current_user_can() 类似,这行代码获取传递给 user_can() 函数的所有参数,除了前两个参数(用户 ID 和 capability 本身)。

  2. call_user_func_array( array( wp_roles(), 'user_has_cap' ), array_merge( array( $user, $capability ), $args ) ): 这行代码比较复杂,它使用了 call_user_func_array() 函数来动态调用 WP_Roles 类的 user_has_cap() 方法。

    • wp_roles() 返回全局的 WP_Roles 对象,这个对象负责管理 WordPress 的角色和权限。
    • array( wp_roles(), 'user_has_cap' ) 创建了一个数组,包含了要调用的对象(wp_roles())和方法名('user_has_cap')。
    • array_merge( array( $user, $capability ), $args ) 将用户 ID、capability 和额外的参数合并成一个数组,作为 user_has_cap() 方法的参数。
    • call_user_func_array() 使用合并后的参数数组来调用 user_has_cap() 方法,并返回其结果。

WP_Roles::user_has_cap():终极裁判

现在,我们终于来到了权限检查的最后一站:WP_Roles::user_has_cap()。 这个方法才是真正的“终极裁判”,它会根据用户的角色和权限配置,判断用户是否拥有指定的 capability。 这个方法同样在 wp-includes/capabilities.php 文件中。 由于代码较长,我们只提取关键部分进行分析。

public function user_has_cap( $user_id, $cap, ...$args ) {
  $user = get_userdata( $user_id );

  if ( ! $user ) {
    return false;
  }

  if ( 'administrator' === $user->user_login ) { // 仅供演示,实际情况不应直接使用用户名判断
    $user = new WP_User( $user_id );
    if ( in_array( 'administrator', $user->roles ) ) {
      return true;
    }
  }

  $caps = $this->get_user_caps( $user );

  if ( empty( $caps ) ) {
    return false;
  }

  if ( in_array( 'do_not_allow', $caps, true ) ) {
    return false;
  }

  if ( isset( $caps[$cap] ) ) {
    $check = $caps[$cap];
  } else {
    $check = false;
  }

  /**
   * Filters whether a user has the specified capability.
   *
   * @since 2.8.0
   *
   * @param bool   $check     Whether the user has the capability. Default false.
   * @param string $cap       Capability name.
   * @param int    $user_id   The user ID.
   * @param array  $args      Adds the context to the cap check, which might change
   *                          the result.
   */
  return apply_filters( 'user_has_cap', $check, $cap, $user_id, $args );
}
  1. $user = get_userdata( $user_id );: 根据用户 ID 获取用户数据。 如果用户不存在,返回 false

  2. if ( 'administrator' === $user->user_login ) { ... }: 这是一个不推荐的做法,仅作演示! 这个代码片段用于快速判断用户是否是管理员。 强烈建议不要直接使用用户名进行权限判断,这很不安全! 更好的做法是检查用户是否拥有 administrator 角色。

  3. $caps = $this->get_user_caps( $user );: 获取用户的所有 capability。 get_user_caps() 方法会根据用户的角色,将所有相关的 capability 收集到一个数组中。

  4. if ( empty( $caps ) ) { return false; }if ( in_array( 'do_not_allow', $caps, true ) { return false; }: 如果用户没有任何 capability,或者拥有一个特殊的 'do_not_allow' capability,则表示用户没有权限。

  5. if ( isset( $caps[$cap] ) ) { $check = $caps[$cap]; } else { $check = false; }: 检查用户的 capability 数组中是否包含指定的 $cap。 如果包含,则 $checktrue;否则,$checkfalse

  6. return apply_filters( 'user_has_cap', $check, $cap, $user_id, $args );: 又是一个过滤器! 允许你通过插件或主题来修改 user_has_cap() 的返回值,提供更灵活的权限控制。

总结:权限检查的完整流程

现在,我们来总结一下 current_user_can() 函数的完整工作流程:

  1. current_user_can() 接收 capability 和可选的参数。
  2. current_user_can() 获取当前用户 ID。
  3. current_user_can() 调用 user_can() 函数,并将用户 ID、capability 和参数传递给它。
  4. user_can() 函数调用 WP_Roles::user_has_cap() 方法。
  5. WP_Roles::user_has_cap() 获取用户的所有 capability。
  6. WP_Roles::user_has_cap() 检查用户的 capability 数组中是否包含指定的 capability。
  7. WP_Roles::user_has_cap() 返回检查结果(truefalse)。
  8. current_user_can()user_has_cap 分别应用过滤器,允许修改检查结果。
  9. current_user_can() 返回最终的检查结果。

表格:WordPress 角色和默认 Capability

为了方便大家理解,我整理了一个表格,列出了 WordPress 默认的角色和一些常见的 capability:

角色 默认 Capability
Administrator 所有 Capability (manage_options, install_plugins, delete_users, 等等)
Editor publish_posts, edit_posts, delete_posts, manage_categories, moderate_comments, upload_files, 等等
Author publish_posts, edit_posts, delete_posts, upload_files
Contributor edit_posts, upload_files
Subscriber read

自定义 Capability

除了使用 WordPress 内置的 capability,你还可以自定义 capability,让你的插件或主题更加灵活。 要自定义 capability,你需要使用 add_role()get_role() 函数来添加或修改角色,并使用 add_cap() 方法来给角色添加 capability。

// 创建一个新的角色 "book_editor"
add_role(
  'book_editor',
  'Book Editor',
  array(
    'read'         => true,  // 允许阅读
    'edit_posts'   => true,  // 允许编辑文章
    'publish_posts' => true,  // 允许发布文章
    'edit_books'   => true,  // 允许编辑书籍(自定义 capability)
  )
);

// 给管理员角色添加 "manage_books" capability
$admin_role = get_role( 'administrator' );
$admin_role->add_cap( 'manage_books' );

// 检查当前用户是否拥有 "edit_books" capability
if ( current_user_can( 'edit_books' ) ) {
  echo '你拥有编辑书籍的权限!';
}

传递上下文参数

current_user_can()user_can() 允许你传递额外的参数,用于更精细的权限检查。 这些参数可以用来传递一些上下文信息,比如要编辑的文章 ID,或者要删除的用户 ID。

// 检查当前用户是否有权限编辑 ID 为 123 的文章
if ( current_user_can( 'edit_post', 123 ) ) {
  echo '你拥有编辑 ID 为 123 的文章的权限!';
}

// 在你的插件或主题中,你可以使用过滤器来处理这些参数
add_filter( 'user_has_cap', 'my_custom_user_has_cap', 10, 4 );

function my_custom_user_has_cap( $check, $cap, $user_id, $args ) {
  if ( 'edit_post' === $cap && isset( $args[0] ) ) {
    $post_id = $args[0];

    // 在这里进行更复杂的权限检查,比如检查用户是否是文章的作者
    $post = get_post( $post_id );
    $user = wp_get_current_user();

    if ( $post->post_author == $user->ID ) {
      return true; // 如果用户是文章的作者,则允许编辑
    } else {
      return false; // 否则,拒绝编辑
    }
  }

  return $check; // 返回默认的检查结果
}

安全建议

  • 不要直接使用用户名进行权限判断! 这很不安全,容易被攻击者利用。 应该使用角色或 capability 进行权限判断。
  • 避免硬编码 capability! 将 capability 定义为常量,方便修改和维护。
  • 使用过滤器来扩展权限系统! WordPress 的过滤器提供了一种灵活的方式来定制权限检查的逻辑。
  • 仔细测试你的权限系统! 确保所有权限都按照你的预期工作,防止出现安全漏洞。

总结

current_user_can() 函数是 WordPress 权限系统的核心。 通过深入了解它的源码,我们可以更好地掌握 WordPress 的权限控制机制,为我们的插件和主题提供更安全、更灵活的权限管理。 希望今天的讲解对大家有所帮助! 下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注