阐述 `current_user_can()` 函数的源码,它是如何通过 `map_meta_cap` 过滤器将元权限映射到具体权限的?

各位听众,晚上好!我是今天的主讲人,咱们今天来聊聊 WordPress 权限系统里的一个核心函数 current_user_can(),以及它背后的功臣——map_meta_cap 过滤器。

别担心,咱们不搞枯燥的理论,尽量用大白话把这些技术概念给掰开了揉碎了讲清楚。准备好了吗?Let’s dive in!

current_user_can():权限判断的入口

首先,current_user_can() 函数,顾名思义,就是用来判断当前用户是否有某个权限的。 这玩意儿在 WordPress 开发中简直是家常便饭,比如,你想让只有管理员才能看到某个菜单项,或者只有作者才能编辑自己的文章,都得靠它。

函数原型很简单:

/**
 * Checks whether the current user has the specified capability.
 *
 * @param string $capability Capability name.
 * @param mixed  ...$args Optional list of parameters to pass to the capability.
 * @return bool True if the current user has the capability, false if not.
 */
function current_user_can( $capability, ...$args ) {
    global $wp_current_filter;

    $args = array_merge( array( $capability ), $args );

    /**
     * Filters the capabilities of the current user.
     *
     * @since 3.1.0
     *
     * @param bool   $allcaps     An array of all the user's capabilities.
     * @param array  $caps        Actual capabilities for meta capability.
     * @param array  $args        Adds the capability to check.
     * @param object $user        The user object.
     * @param array  $wp_current_filter  Array of the current filter.
     */
    $allcaps = apply_filters( 'user_has_cap', array(), $args[0], $args, null );

    if ( ! empty( $allcaps[ $capability ] ) ) {
        return (bool) $allcaps[ $capability ];
    }

    $user = wp_get_current_user();

    if ( empty( $user->ID ) ) {
        return apply_filters( 'current_user_can', false, $capability, ...$args );
    }

    $map_meta_cap_result = map_meta_cap( $capability, $user->ID, ...array_slice( $args, 1 ) );

    $map_meta_cap_result = array_unique( $map_meta_cap_result );

    // Filter out any caps that are empty.
    $map_meta_cap_result = array_filter( $map_meta_cap_result );

    if ( empty( $map_meta_cap_result ) ) {
        return apply_filters( 'current_user_can', false, $capability, ...$args );
    }

    $primitive_result = true;
    foreach ( $map_meta_cap_result as $required_cap ) {
        if ( ! isset( $allcaps[ $required_cap ] ) || ! $allcaps[ $required_cap ] ) {
            $primitive_result = false;
            break;
        }
    }

    return apply_filters( 'current_user_can', $primitive_result, $capability, ...$args );
}

参数说明:

  • $capability: 要检查的权限名称,比如 edit_postsdelete_users 等。这就像你要进一家公司,需要一个通行证,$capability 就是通行证的名字。
  • ...$args: 可选参数,通常是与 $capability 相关的对象 ID,比如文章 ID。 这就像通行证可能需要绑定你的身份信息,以便确定你是否有权访问特定的资源。

简单来说,current_user_can() 的工作流程是这样的:

  1. 检查用户是否已经拥有该权限:它首先会查看用户是否直接拥有 $capability 这个权限。如果用户的角色或自定义权限中已经包含了这个权限,那就直接返回 true
  2. 如果没有直接拥有,就交给 map_meta_cap() 处理: 如果用户没有直接拥有 $capability,事情就变得有趣起来了。current_user_can() 会把这个权限交给 map_meta_cap() 函数去处理。
  3. map_meta_cap() 返回实际需要的权限列表map_meta_cap() 会根据 $capability$args,返回一个或多个实际需要拥有的权限列表。这就像 map_meta_cap() 是一个翻译官,它把一个高级权限(元权限)翻译成一个或多个低级权限(基本权限)。
  4. 检查用户是否拥有 map_meta_cap() 返回的所有权限current_user_can() 拿到 map_meta_cap() 返回的权限列表后,会再次检查用户是否拥有这些权限。只有当用户拥有 map_meta_cap() 返回的所有权限时,current_user_can() 才会返回 true

map_meta_cap():元权限到基本权限的翻译官

map_meta_cap() 函数才是真正的幕后英雄。它负责将一些“元权限”(meta capabilities)映射到实际需要的基本权限(primitive capabilities)。

什么是元权限? 你可以把元权限理解为一种抽象的、高级的权限,它本身并不能直接用于判断用户是否有权执行某个操作。 比如 edit_post 就是一个元权限,它表示“编辑文章”的权限,但具体是编辑哪篇文章,谁可以编辑,这些都需要进一步确定。

基本权限则是具体的、可以直接用于判断用户是否有权执行某个操作的权限,比如 edit_others_postsedit_published_posts 等。

map_meta_cap() 的作用就是根据上下文(比如用户 ID 和文章 ID),将元权限转换为一个或多个基本权限。

函数原型:

/**
 * Maps meta capabilities to primitive capabilities.
 *
 * @param string $cap     Capability name.
 * @param int    $user_id User ID.
 * @param int    ...$args Arguments.
 * @return array Array of required primitive capabilities.
 */
function map_meta_cap( $cap, $user_id, ...$args ) {
    $caps = array( $cap );

    /**
     * Filters the primitive capabilities required to map a meta capability.
     *
     * @since 2.8.0
     *
     * @param array  $caps    Returns the primitive capabilities required to accomplish the meta capability.
     * @param string $cap     Capability name.
     * @param int    $user_id User ID.
     * @param array  $args    Arguments.
     */
    return apply_filters( 'map_meta_cap', $caps, $cap, $user_id, $args );
}

参数说明:

  • $cap: 要映射的元权限名称,比如 edit_postdelete_page 等。
  • $user_id: 用户 ID。
  • ...$args: 可选参数,通常是与 $cap 相关的对象 ID,比如文章 ID。

map_meta_cap() 的核心在于 apply_filters( 'map_meta_cap', ... ) 这一行代码。它使用 map_meta_cap 过滤器,允许开发者自定义元权限到基本权限的映射规则。

map_meta_cap 过滤器:自定义权限映射的利器

map_meta_cap 过滤器是 WordPress 权限系统中最灵活的部分。 它允许你根据自己的需求,自定义元权限到基本权限的映射规则。

没有这个过滤器,WordPress 权限系统就会变得死板,只能使用内置的权限规则。 有了它,你可以轻松地实现各种复杂的权限控制逻辑。

举个例子:edit_post 元权限的映射

让我们以 edit_post 元权限为例,看看 map_meta_cap 过滤器是如何工作的。

WordPress 核心代码中,就有一个针对 edit_post 元权限的 map_meta_cap 过滤器回调函数,定义在 wp-includes/capabilities.php 文件中。 这个函数会根据用户的角色、文章作者和文章状态等因素,将 edit_post 映射到不同的基本权限。

以下是简化后的代码片段,更便于理解:

add_filter( 'map_meta_cap', 'map_edit_post_caps', 10, 4 );

function map_edit_post_caps( $caps, $cap, $user_id, $args ) {
    if ( 'edit_post' !== $cap ) {
        return $caps;
    }

    $post_id = isset( $args[0] ) ? (int) $args[0] : 0;
    if ( ! $post_id ) {
        return array( 'do_not_allow' ); // 没有文章ID,直接不允许
    }

    $post = get_post( $post_id );
    if ( ! $post ) {
        return array( 'do_not_allow' ); // 文章不存在,直接不允许
    }

    if ( get_current_user_id() == $post->post_author ) {
        // 如果是文章作者,则根据文章状态判断权限
        if ( 'publish' == $post->post_status ) {
            $caps = array( 'edit_published_posts' ); // 编辑已发布的文章
        } elseif ( 'future' == $post->post_status ) {
            $caps = array( 'edit_future_posts' ); // 编辑计划发布的文章
        } else {
            $caps = array( 'edit_posts' ); // 编辑其他状态的文章
        }
    } else {
        // 如果不是文章作者,则判断是否有编辑他人文章的权限
        $caps = array( 'edit_others_posts' );
    }

    return $caps;
}

这段代码做了什么?

  1. 判断是否是 edit_post 元权限: 首先,它会检查传入的 $cap 是否是 edit_post。如果不是,就直接返回原始的 $caps 数组,不做任何处理。
  2. 获取文章 ID 和文章对象: 然后,它会从 $args 数组中获取文章 ID,并根据文章 ID 获取文章对象。
  3. 判断是否是文章作者: 接着,它会判断当前用户是否是文章的作者。
  4. 根据文章状态和用户角色映射到不同的基本权限
    • 如果是文章作者,则根据文章的状态(publishfuture 或其他状态)映射到不同的基本权限,比如 edit_published_postsedit_future_postsedit_posts
    • 如果不是文章作者,则映射到 edit_others_posts 基本权限。

通过这个例子,我们可以看到 map_meta_cap 过滤器是如何根据上下文信息,将 edit_post 元权限映射到不同的基本权限的。

自定义 map_meta_cap 过滤器回调函数

现在,让我们来动手写一个自定义的 map_meta_cap 过滤器回调函数。 假设我们想实现这样的权限控制逻辑:

  • 只有管理员才能删除分类目录。
  • 其他用户不能删除分类目录。

我们可以这样写:

add_filter( 'map_meta_cap', 'my_map_delete_category_cap', 10, 4 );

function my_map_delete_category_cap( $caps, $cap, $user_id, $args ) {
    if ( 'delete_category' !== $cap ) {
        return $caps;
    }

    if ( current_user_can( 'manage_options' ) ) {
        // 如果是管理员,则允许删除分类目录
        $caps = array( 'manage_categories' ); // 'manage_categories' 是一个基本权限,表示管理分类目录的权限
    } else {
        // 如果不是管理员,则不允许删除分类目录
        $caps = array( 'do_not_allow' );
    }

    return $caps;
}

这段代码做了什么?

  1. 判断是否是 delete_category 元权限: 首先,它会检查传入的 $cap 是否是 delete_category。如果不是,就直接返回原始的 $caps 数组,不做任何处理。
  2. 判断是否是管理员: 然后,它会使用 current_user_can( 'manage_options' ) 函数判断当前用户是否是管理员。
  3. 根据用户角色映射到不同的基本权限
    • 如果是管理员,则映射到 manage_categories 基本权限。
    • 如果不是管理员,则映射到 do_not_allow,表示不允许执行该操作。

map_meta_cap 的应用场景

map_meta_cap 过滤器的应用场景非常广泛,以下是一些常见的例子:

  • 自定义文章类型权限: 你可以使用 map_meta_cap 过滤器,为自定义文章类型定义自己的权限控制逻辑。 比如,你可以让只有特定角色的用户才能编辑某个自定义文章类型的文章。
  • 自定义分类法权限: 你也可以使用 map_meta_cap 过滤器,为自定义分类法定义自己的权限控制逻辑。 比如,你可以让只有特定角色的用户才能管理某个自定义分类法的分类目录。
  • 插件权限: 如果你开发了一个插件,你可以使用 map_meta_cap 过滤器,为插件的功能定义自己的权限控制逻辑。 比如,你可以让只有特定角色的用户才能使用插件的某个功能。
  • 主题权限: 在主题开发中,你也可以使用 map_meta_cap 过滤器,对主题的某些功能进行权限控制。

current_user_can()map_meta_cap() 的关系总结

为了更好地理解 current_user_can()map_meta_cap() 的关系,我们用一张表格来总结一下:

函数/过滤器 功能 作用 例子
current_user_can() 判断当前用户是否拥有某个权限。 提供一个统一的权限判断入口,简化权限判断逻辑。 current_user_can( 'edit_post', $post_id ):判断当前用户是否有权编辑 ID 为 $post_id 的文章。
map_meta_cap() 将元权限映射到基本权限。 提供一个灵活的权限映射机制,允许开发者自定义权限控制逻辑。 map_meta_cap( 'edit_post', $user_id, $post_id ):将 edit_post 元权限映射到 edit_others_postsedit_published_posts 等基本权限。
map_meta_cap 过滤器 允许开发者自定义元权限到基本权限的映射规则。 为 WordPress 权限系统提供最大的灵活性和可扩展性。 使用 add_filter( 'map_meta_cap', 'my_map_edit_post_cap', 10, 4 ) 注册一个回调函数,自定义 edit_post 元权限的映射规则。

注意事项

  • 权限命名规范: 在定义自己的权限时,最好遵循 WordPress 的命名规范,使用小写字母和下划线,比如 my_custom_capability
  • 性能优化map_meta_cap 过滤器回调函数可能会被频繁调用,因此要注意性能优化,避免执行耗时的操作。
  • 安全: 权限控制是安全的关键,要仔细设计权限控制逻辑,避免出现安全漏洞。

总结

current_user_can()map_meta_cap 过滤器是 WordPress 权限系统中的两个核心组件。 current_user_can() 提供了一个统一的权限判断入口,而 map_meta_cap 过滤器则提供了灵活的权限映射机制。

掌握这两个工具,你就可以轻松地实现各种复杂的权限控制逻辑,为你的 WordPress 网站或插件提供更安全、更灵活的用户体验。

好了,今天的讲座就到这里。 希望大家有所收获,也欢迎大家在实际开发中多多实践,加深理解。 下次再见!

发表回复

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