阐述 `current_user_can()` 函数的源码,它如何通过 `WP_User` 类的 `has_cap()` 方法来检查权限?

各位朋友,大家好!今天咱们来聊聊WordPress里一个非常重要的函数:current_user_can()。这玩意儿就像WordPress世界里的安检员,负责审查当前用户有没有权限干某件事。别看它名字挺长,其实原理并不复杂,核心就是调用WP_User类的has_cap()方法。接下来,咱们深入剖析一下这个“安检员”的工作原理。

一、current_user_can():权限审查官

首先,咱们来看看current_user_can()函数的基本用法。它长这样:

<?php
/**
 * Checks whether the current user has the specified capability.
 *
 * @since 2.0.0
 *
 * @param string $capability Capability name.
 * @param mixed  ...$args Optional arguments.
 * @return bool True if the current user has the capability, false if not.
 */
function current_user_can( string $capability, ...$args ): bool {
    global $wp_roles, $current_user;

    if ( is_user_logged_in() ) {
        $current_user = wp_get_current_user();

        if ( is_multisite() && ! is_user_member_of_blog( $current_user->ID, get_current_blog_id() ) && ! apply_filters( 'map_meta_cap', array( $capability ), $current_user->ID, ...$args ) ) {
            return false;
        }
    } else {
        // If the user is not logged in, then they're not an admin.
        if ( 'administrator' === $capability ) {
            return false;
        }

        /**
         * Filters whether an unauthenticated user has the given capability.
         *
         * @since 5.1.0
         *
         * @param bool   $unauthenticated Whether the user has the given capability. Default false.
         * @param string $capability      Capability name.
         * @param mixed  ...$args       Optional arguments.
         */
        return apply_filters( 'unauthenticated_user_has_capability', false, $capability, ...$args );
    }

    if ( is_null( $current_user ) ) {
        return false;
    }

    if ( isset( $wp_roles ) && is_object( $wp_roles ) ) {
        if ( is_a( $current_user, 'WP_User' ) ) {
            return $current_user->has_cap( $capability, ...$args );
        }
    }

    return false;
}

简单来说,它接受一个能力名($capability)作为参数,以及可选的参数...$args,然后返回truefalse,表示当前用户是否拥有该能力。

二、current_user_can() 的工作流程

让我们逐步分析一下current_user_can()函数的工作流程:

  1. 全局变量准备:

    • 首先,它从全局作用域中获取 $wp_roles$current_user 变量。$wp_roles 用于访问角色信息,$current_user 用于获取当前用户的信息。
  2. 用户登录状态检查:

    • 使用 is_user_logged_in() 函数检查用户是否已登录。
    • 如果用户已登录:
      • 使用 wp_get_current_user() 函数获取当前用户信息,并将其赋值给 $current_user 变量。
      • 检查是否是多站点环境,并且当前用户不是当前站点的成员。如果是,则使用 apply_filters( 'map_meta_cap', array( $capability ), $current_user->ID, ...$args ) 过滤,决定是否拥有权限。如果过滤结果为空,则返回 false,表示没有权限。
    • 如果用户未登录:
      • 如果尝试检查管理员权限 ('administrator' === $capability),则直接返回 false,因为未登录用户不可能拥有管理员权限。
      • 触发 unauthenticated_user_has_capability 过滤器,允许插件或主题自定义未登录用户是否具有特定权限。
  3. 当前用户对象检查:

    • 检查 $current_user 变量是否为 null。如果为 null,则返回 false,表示没有权限。
  4. WP_User 对象和角色信息检查:

    • 检查 $wp_roles 是否已设置且是一个对象。并且 $current_userWP_User 类的实例。 如果满足这些条件,则调用 $current_user->has_cap( $capability, ...$args ) 方法来检查用户是否具有指定的权限。
  5. 默认返回:

    • 如果以上条件都不满足,则默认返回 false,表示没有权限。

三、WP_User::has_cap():权限判断的核心

current_user_can() 的核心在于调用了 WP_User 类的 has_cap() 方法。这个方法负责真正判断用户是否拥有某个能力。下面是 WP_User::has_cap() 的简化版代码(完整代码在 wp-includes/class-wp-user.php 中):

<?php
/**
 * Checks whether the user has the specified capability.
 *
 * @since 2.0.0
 *
 * @param string $capability Capability name.
 * @param mixed  ...$args Optional parameters passed to the capability check.
 * @return bool Whether the user has the given capability.
 */
public function has_cap( string $capability, ...$args ): bool {
    global $wp_roles;

    if ( ! is_array( $this->caps ) ) {
        $this->caps = array();
    }

    if ( isset( $this->caps[$capability] ) ) {
        $has_cap = $this->caps[$capability];
    } else {
        $has_cap = false;
    }

    /**
     * Filters whether the user has the given capability.
     *
     * @since 2.8.0
     *
     * @param bool   $has_cap    Whether the user has the given capability.
     * @param string $capability Capability name.
     * @param int    $user_id    User ID.
     * @param array  $args       Arguments passed to the capability check.
     */
    $has_cap = apply_filters( 'user_has_cap', $has_cap, $capability, $this->ID, $args );

    // Must have the cap, so quit early.
    if ( true === $has_cap ) {
        return true;
    }

    if ( 'do_not_allow' === $has_cap ) {
        return false;
    }

    if ( empty( $wp_roles ) ) {
        return apply_filters( 'map_meta_cap', array( $capability ), $this->ID, ...$args );
    }

    $role_caps = array();
    foreach ( (array) $this->roles as $role ) {
        if ( isset( $wp_roles->role_objects[ $role ] ) ) {
            $the_role = $wp_roles->role_objects[ $role ];
        } else {
            $the_role = get_role( $role );
        }

        if ( empty( $the_role ) ) {
            continue;
        }

        $role_caps = array_merge( $role_caps, $the_role->capabilities );
    }

    $role_caps = array_unique( $role_caps );

    // Grant $capability if it's a meta cap and the unfiltered map_meta_cap() function returns true.
    if ( apply_filters( 'map_meta_cap', array( $capability ), $this->ID, ...$args ) ) {
        return true;
    }

    /**
     * Filters the user's capabilities.
     *
     * @since 3.1.0
     *
     * @param array  $allcaps    An array of all the user's capabilities.
     * @param array  $caps       Actual capabilities for the user.
     * @param array  $args       Optional parameters passed to the capability check.
     * @param WP_User $user       The user object.
     */
    $allcaps = apply_filters( 'user_has_cap_allcaps', array_merge( (array) $this->caps, $role_caps ), (array) $this->caps, $args, $this );

    if ( isset( $allcaps[$capability] ) ) {
        $has_cap = $allcaps[$capability];
    } else {
        $has_cap = false;
    }

    return $has_cap;
}

这个方法的逻辑稍微复杂一些,咱们逐步拆解:

  1. 检查用户是否直接拥有该能力:

    • WP_User 对象有一个 $caps 属性,它是一个关联数组,存储了用户直接拥有的能力。
    • 方法首先检查 $this->caps 中是否存在 $capability。如果存在,则直接返回对应的值(truefalse)。
  2. user_has_cap 过滤器:

    • 允许通过 user_has_cap 过滤器修改用户的权限。这是一个非常强大的扩展点,可以根据自定义逻辑动态地赋予或剥夺用户的能力。
  3. 早期退出:

    • 如果经过过滤,$has_captrue,直接返回 true
    • 如果 $has_capdo_not_allow,则直接返回 false
  4. 角色权限检查:

    • 如果用户没有直接拥有该能力,则需要检查用户所属的角色是否拥有该能力。
    • 方法遍历用户的所有角色(存储在 $this->roles 属性中),并从 $wp_roles 全局对象中获取每个角色的权限列表。
    • 将所有角色拥有的权限合并到一个数组 $role_caps 中。
  5. 元能力检查:

    • 接下来,它会调用 apply_filters( 'map_meta_cap', array( $capability ), $this->ID, ...$args )map_meta_cap 过滤器用于将“元能力”(meta capabilities)映射到实际的能力。
    • 什么是元能力呢? 比如 edit_post 编辑文章 这个能力, 具体到某个post id=123 的文章, 如果当前用户有编辑这个文章的权限,那么就返回true,否则返回false. 这个post id就是 $args 传递过来的参数。
    • 如果 apply_filters( 'map_meta_cap', array( $capability ), $this->ID, ...$args ) 返回 true,则表示用户拥有该能力。
  6. user_has_cap_allcaps 过滤器:

    • 调用 user_has_cap_allcaps 过滤器,允许修改用户的所有能力。这个过滤器可以用来添加、删除或修改用户的权限。
  7. 最终判断:

    • 将用户直接拥有的能力和角色拥有的能力合并到一个数组 $allcaps 中。
    • 再次检查 $allcaps 中是否存在 $capability。如果存在,则返回对应的值。否则,返回 false

四、能力(Capabilities):WordPress 的权限基石

在WordPress中,能力是权限的基础。它们是一些字符串,代表了用户可以执行的操作。例如:

  • read:阅读文章
  • edit_posts:编辑文章
  • publish_posts:发布文章
  • delete_posts:删除文章
  • manage_options:管理选项

这些能力可以分配给角色(Roles),也可以直接分配给用户。

五、角色(Roles):权限的集合

角色是能力的集合。WordPress预定义了几个角色,例如:

  • Administrator:拥有所有权限
  • Editor:可以管理和发布文章,包括其他用户的文章
  • Author:可以撰写、编辑和发布自己的文章
  • Contributor:可以撰写自己的文章,但需要经过审核才能发布
  • Subscriber:只能阅读文章

你可以自定义角色,并为它们分配特定的能力。

六、map_meta_cap 过滤器:元能力映射

map_meta_cap 过滤器是一个非常重要的机制,用于将元能力映射到实际的能力。元能力是那些需要根据上下文才能确定的能力。例如,edit_post 是一个元能力,因为它需要知道用户是否有权编辑 特定的 文章。

map_meta_cap 过滤器接受以下参数:

  • $caps:需要检查的能力数组
  • $user_id:用户ID
  • ...$args:传递给能力检查的参数

过滤器函数应该返回一个能力数组。如果用户拥有所有返回的能力,则认为用户拥有原始的元能力。

七、实例演示:检查用户是否可以编辑特定文章

假设我们要检查当前用户是否可以编辑ID为123的文章。我们可以这样写:

<?php
$post_id = 123;
if ( current_user_can( 'edit_post', $post_id ) ) {
    echo '当前用户可以编辑ID为' . $post_id . '的文章';
} else {
    echo '当前用户没有权限编辑ID为' . $post_id . '的文章';
}
?>

在这个例子中,edit_post 是一个元能力。current_user_can() 函数会将 edit_post$post_id 传递给 map_meta_cap 过滤器。map_meta_cap 过滤器会根据文章的作者、用户的角色等信息,返回一个或多个实际的能力,例如 edit_others_postsedit_published_posts。如果用户拥有所有返回的能力,则 current_user_can() 函数返回 true

八、user_has_capuser_has_cap_allcaps 过滤器:动态权限控制

这两个过滤器提供了强大的动态权限控制能力。

  • user_has_cap 过滤器允许你在用户进行能力检查时,修改用户的权限。例如,你可以根据用户的IP地址、时间等因素动态地赋予或剥夺用户的能力。

  • user_has_cap_allcaps 过滤器允许你修改用户的所有能力。你可以添加、删除或修改用户的权限。

九、总结:current_user_can() 的重要性

current_user_can() 函数是WordPress权限管理的核心。它提供了一种简单而强大的方式来检查用户是否拥有执行特定操作的权限。理解 current_user_can() 函数的工作原理,可以帮助你更好地控制WordPress网站的权限,并创建更安全、更灵活的应用程序。

十、代码示例:使用过滤器修改用户权限

以下是一个使用 user_has_cap 过滤器修改用户权限的示例:

<?php
/**
 * 根据用户IP地址动态赋予用户 edit_posts 能力
 *
 * @param bool   $has_cap    Whether the user has the given capability.
 * @param string $capability Capability name.
 * @param int    $user_id    User ID.
 * @param array  $args       Arguments passed to the capability check.
 *
 * @return bool
 */
function my_custom_user_has_cap( $has_cap, $capability, $user_id, $args ) {
    if ( 'edit_posts' === $capability ) {
        $user_ip = $_SERVER['REMOTE_ADDR'];
        // 假设只有特定IP地址的用户才能编辑文章
        if ( '127.0.0.1' === $user_ip ) {
            $has_cap = true;
        }
    }
    return $has_cap;
}
add_filter( 'user_has_cap', 'my_custom_user_has_cap', 10, 4 );
?>

这个例子中,我们定义了一个名为 my_custom_user_has_cap 的函数,它会在用户尝试检查 edit_posts 能力时被调用。如果用户的IP地址是 127.0.0.1,则该函数会将 $has_cap 设置为 true,从而赋予用户编辑文章的权限。

十一、常见问题解答

  • Q: current_user_can() 函数的性能如何?

    A: current_user_can() 函数的性能通常是可以接受的。但是,如果你的网站需要进行大量的权限检查,或者你的权限逻辑非常复杂,那么你可能需要考虑使用缓存或其他优化技术来提高性能。

  • Q: 我应该如何选择能力?

    A: 选择能力时,应该尽量选择最具体的能力。例如,如果你只想允许用户编辑自己的文章,那么你应该使用 edit_posts 能力,而不是 manage_options 能力。

  • Q: 我应该如何测试我的权限逻辑?

    A: 测试你的权限逻辑非常重要。你可以使用 WordPress 的测试框架或其他测试工具来测试你的代码。

十二、表格总结:关键函数和过滤器

为了方便大家理解,我用一个表格总结一下今天讲到的关键函数和过滤器:

函数/过滤器 描述
current_user_can() 检查当前用户是否拥有指定的权限。
WP_User::has_cap() WP_User 类的方法,负责真正判断用户是否拥有某个能力。
map_meta_cap 过滤器,用于将元能力映射到实际的能力。
user_has_cap 过滤器,允许你在用户进行能力检查时,修改用户的权限。
user_has_cap_allcaps 过滤器,允许你修改用户的所有能力。

希望今天的讲座对大家有所帮助!权限管理是WordPress开发中一个非常重要的方面,掌握 current_user_can() 函数及其相关机制,可以帮助你构建更安全、更可靠的应用程序。 谢谢大家!

发表回复

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