详解 WordPress `current_user_can()` 函数源码:如何通过 `map_meta_cap` 过滤器实现权限映射。

各位观众老爷,晚上好!我是今晚的讲解员,江湖人称“代码搬运工”,今天给大家伙儿唠唠 WordPress 权限系统里一个非常重要的函数——current_user_can()。这玩意儿就像 WordPress 的安检门,检查你有没有资格进入某些区域,或者执行某些操作。

咱们今天要深入源码,扒开它的底裤,看看它到底是怎么工作的。重点是map_meta_cap这个过滤器,它可是权限映射的幕后推手,能把一些抽象的“元能力”翻译成具体的、用户组拥有的能力。

一、current_user_can():你的权限够不够?

首先,咱们先来简单了解一下 current_user_can() 这个函数。它的作用很简单:判断当前用户是否拥有某个指定的权限。

<?php
/**
 * Checks the current user's permissions to perform a specific action.
 *
 * @param string $capability The capability to check.
 * @param mixed ...$args Optional. Additional arguments for the capability check.
 * @return bool True if the current user has the capability, false otherwise.
 */
function current_user_can( string $capability, ...$args ): bool {
    global $wp_current_filter;

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

    /**
     * Filters the capability being checked against.
     *
     * @since 2.0.0
     *
     * @param string   $capability Name of capability being checked.
     * @param int|null $user_id     ID of the user being checked.
     * @param array    $args       Array of the parameters passed (to current_user_can()).
     */
    $capability = apply_filters( 'user_has_cap', $capability, get_current_user_id(), $args );

    if ( ! is_scalar( $capability ) || empty( $capability ) ) {
        return false;
    }

    $wp_roles = wp_roles();

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

    $roles = wp_get_current_user()->roles;

    // If the user has no roles, return false.
    if ( empty( $roles ) ) {
        return false;
    }

    // Iterate through each role that the user has.
    foreach ( $roles as $role ) {

        if ( isset( $wp_roles->role_objects[ $role ] ) ) {
            $role_obj = $wp_roles->role_objects[ $role ];
        } else {
            continue;
        }

        if ( isset( $role_obj->capabilities[ $capability ] ) && true === $role_obj->capabilities[ $capability ] ) {
            return true;
        }
    }

    // Second, site specific caps.
    if ( is_multisite() ) {
        foreach ( $roles as $role ) {
            $ms_users = get_site_option( 'wp_user_roles' );
            if ( isset( $ms_users[ $role ]['capabilities'][ $capability ] ) && true === $ms_users[ $role ]['capabilities'][ $capability ] ) {
                return true;
            }
        }
    }

    // Finally, super admin.
    if ( is_super_admin() ) {
        return true;
    }

    return false;
}

简单来说,你调用 current_user_can('edit_posts'),它就会检查当前用户是否拥有编辑文章的权限。 如果有,返回 true,否则返回 false

参数解释:

  • $capability: (string) 必须。要检查的权限(能力)名称。比如 'edit_posts''manage_options' 等等。
  • ...$args: (mixed) 可选。传递给权限检查的其他参数。这些参数会被传递给 map_meta_cap 过滤器,用于更细致的权限判断。 例如,current_user_can('edit_post', $post_id),这里的 $post_id 就是额外的参数。

工作流程:

  1. 参数准备:capabilityargs 合并成一个数组。
  2. user_has_cap 过滤器: 应用 user_has_cap 过滤器,允许修改要检查的 capability。
  3. 角色检查: 获取当前用户的角色,并遍历这些角色,检查这些角色是否直接拥有 $capability 权限。
  4. 多站点检查(如果是多站点): 检查用户是否在当前站点拥有 $capability 权限。
  5. 超级管理员检查: 如果用户是超级管理员,直接返回 true
  6. 返回结果: 如果以上都没有找到 $capability 权限,返回 false

举个栗子:

<?php
// 检查当前用户是否可以编辑 ID 为 123 的文章
if ( current_user_can('edit_post', 123) ) {
    echo '当前用户可以编辑该文章!';
} else {
    echo '当前用户无权编辑该文章!';
}
?>

二、map_meta_cap:权限映射的幕后推手

current_user_can() 内部依赖于 WordPress 的角色和权限系统。 但是,有些权限是不能直接通过角色来定义的,比如“编辑某个特定的文章”这种权限。 这时候,map_meta_cap 过滤器就派上用场了。

map_meta_cap 的作用是将一些“元能力”(meta capabilities)映射到实际的角色能力上。 所谓“元能力”,就是一些抽象的、需要根据具体情况才能判断的权限。 比如 'edit_post''delete_post''read_post' 等等。

map_meta_cap 过滤器的定义:

<?php
/**
 * Filters the capabilities required for a specific action.
 *
 * @since 2.8.0
 *
 * @param string[] $caps    Returns the user's actual capabilities.
 * @param string   $cap     Capability name.
 * @param int      $user_id User ID.
 * @param array    $args    Adds the context to the cap. Typically the object ID.
 */
function map_meta_cap( array $caps, string $cap, int $user_id, array $args ): array;

参数解释:

  • $caps: (string[]) 数组。 初始值为一个包含 $cap 的数组。 这个数组最终会被修改,返回用户实际需要的权限。
  • $cap: (string) 要映射的元能力名称。 比如 'edit_post'
  • $user_id: (int) 用户 ID。
  • $args: (array) 传递给 current_user_can() 的额外参数。 对于 'edit_post' 来说,通常是文章 ID。

工作流程:

  1. current_user_can() 调用 apply_filters('map_meta_cap', $caps, $capability, $user_id, $args)
  2. 注册到 map_meta_cap 过滤器的函数会被依次执行。
  3. 这些函数根据 $cap$args 的值,判断用户是否应该拥有该权限。
  4. 如果用户应该拥有该权限,则修改 $caps 数组,将其替换为实际的角色能力。
  5. 如果用户不应该拥有该权限,则 $caps 数组保持不变。
  6. current_user_can() 最终根据 $caps 数组中的权限来判断用户是否有权执行操作。

举个栗子:edit_post 权限的映射

WordPress 核心代码中,对 edit_post 权限的映射大致如下(简化版):

<?php
add_filter( 'map_meta_cap', 'my_map_meta_cap', 10, 4 );

function my_map_meta_cap( $caps, $cap, $user_id, $args ) {
    // 如果不是 edit_post 权限,直接返回
    if ( 'edit_post' !== $cap ) {
        return $caps;
    }

    // 确保有文章 ID
    if ( empty( $args[0] ) ) {
        return array( 'do_not_allow' ); // 不允许
    }

    $post_id = (int) $args[0];
    $post = get_post( $post_id );

    if ( empty( $post ) ) {
        return array( 'do_not_allow' ); // 文章不存在,不允许
    }

    // 获取文章作者 ID
    $author_id = (int) $post->post_author;

    // 获取当前用户的信息
    $user = get_userdata( $user_id );

    // 如果当前用户是作者,并且允许编辑自己的文章
    if ( $user_id === $author_id ) {
        if ( 'publish' === $post->post_status ) {
            $caps = array( 'edit_published_posts' ); // 允许编辑已发布的文章
        } else {
            $caps = array( 'edit_posts' ); // 允许编辑未发布的文章
        }
    } else {
        // 如果不是作者,则需要 edit_others_posts 权限
        $caps = array( 'edit_others_posts' );
    }

    return $caps;
}
?>

代码解释:

  1. 注册过滤器: 使用 add_filter('map_meta_cap', 'my_map_meta_cap', 10, 4) 注册 my_map_meta_cap 函数到 map_meta_cap 过滤器。
  2. 判断 $cap 首先判断 $cap 是否为 'edit_post',如果不是,则直接返回 $caps,不做任何修改。
  3. 获取文章信息:$args 中获取文章 ID,并使用 get_post() 函数获取文章对象。如果文章不存在,则返回 array('do_not_allow'),表示不允许该用户编辑该文章。
  4. 判断用户是否为作者: 判断当前用户是否为文章的作者。
    • 如果是作者,则根据文章的状态(已发布或未发布),返回 'edit_published_posts''edit_posts' 权限。
    • 如果不是作者,则返回 'edit_others_posts' 权限。
  5. 返回 $caps 返回修改后的 $caps 数组。

总结一下,这个例子说明了 map_meta_cap 如何将抽象的 'edit_post' 权限映射到具体的角色权限上:

  • 如果用户是文章作者, 则需要 'edit_posts''edit_published_posts' 权限。
  • 如果用户不是文章作者, 则需要 'edit_others_posts' 权限。

这意味着:

  • 如果一个用户拥有 'edit_others_posts' 权限,那么他就可以编辑所有人的文章。
  • 如果一个用户只拥有 'edit_posts' 权限,那么他只能编辑自己的文章。

三、map_meta_cap 的应用场景

map_meta_cap 过滤器在 WordPress 中被广泛使用,常见的应用场景包括:

  • 自定义文章类型权限: 为自定义文章类型设置独立的编辑、删除、阅读权限。
  • 用户角色权限细化: 根据用户的角色和具体情况,动态调整用户的权限。
  • 插件权限控制: 插件可以使用 map_meta_cap 来控制用户对插件功能的访问权限。

举个栗子:自定义文章类型的权限控制

假设我们创建了一个名为 book 的自定义文章类型,我们希望只有具有 'manage_books' 权限的用户才能管理所有书籍,而作者只能编辑自己发布的书籍。

<?php
// 注册自定义文章类型
function my_register_post_type() {
    $args = array(
        'public'    => true,
        'label'     => '书籍',
        'supports'  => array( 'title', 'editor', 'author' ),
        'capability_type' => 'book', // 设置 capability_type
        'map_meta_cap' => true, // 启用 map_meta_cap
    );
    register_post_type( 'book', $args );
}
add_action( 'init', 'my_register_post_type' );

// 添加权限
function my_add_role_caps() {
    // 获取管理员角色
    $role = get_role( 'administrator' );

    // 为管理员添加 manage_books 权限
    $role->add_cap( 'manage_books' );
}
add_action( 'admin_init', 'my_add_role_caps' );

// 权限映射
add_filter( 'map_meta_cap', 'my_map_book_caps', 10, 4 );

function my_map_book_caps( $caps, $cap, $user_id, $args ) {
    if ( 'book' !== get_post_type( $args[0] ) ) {
        return $caps;
    }

    switch ( $cap ) {
        case 'edit_book':
            $post = get_post( $args[0] );
            if ( $user_id == $post->post_author ) {
                $caps = array( 'edit_posts' );
            } else {
                $caps = array( 'manage_books' );
            }
            break;
        case 'read_book':
            $caps = array( 'read' );
            break;
        case 'delete_book':
            $post = get_post( $args[0] );
            if ( $user_id == $post->post_author ) {
                $caps = array( 'delete_posts' );
            } else {
                $caps = array( 'manage_books' );
            }
            break;
        case 'edit_books':
        case 'edit_others_books':
        case 'publish_books':
        case 'read_private_books':
        case 'delete_books':
        case 'delete_private_books':
        case 'delete_published_books':
        case 'delete_others_books':
            $caps = array( 'manage_books' );
            break;
        default:
            $caps = array( 'manage_books' );
            break;
    }

    return $caps;
}
?>

代码解释:

  1. 注册自定义文章类型: 使用 register_post_type() 函数注册 book 自定义文章类型。 关键在于设置了 'capability_type' => 'book''map_meta_cap' => truecapability_type 定义了权限的前缀,map_meta_cap 启用了权限映射。
  2. 添加权限: 为管理员角色添加 'manage_books' 权限。
  3. 权限映射: 使用 map_meta_cap 过滤器,将 edit_bookread_bookdelete_book 等权限映射到实际的角色权限上。
    • edit_book:如果是作者,则需要 'edit_posts' 权限,否则需要 'manage_books' 权限。
    • read_book:需要 'read' 权限。
    • delete_book:如果是作者,则需要 'delete_posts' 权限,否则需要 'manage_books' 权限。
    • 其他的 books 相关权限,都需要 'manage_books' 权限。

四、总结

current_user_can() 函数是 WordPress 权限系统的核心,而 map_meta_cap 过滤器则是实现灵活权限控制的关键。 通过 map_meta_cap,我们可以将抽象的元能力映射到具体的角色权限上,从而实现更精细化的权限管理。

核心要点:

  • current_user_can():检查用户是否拥有指定权限。
  • map_meta_cap:将元能力映射到角色权限。
  • $capability_type:定义自定义文章类型的权限前缀。
  • $args:传递给 map_meta_cap 的额外参数,通常是文章 ID。

掌握了 current_user_can()map_meta_cap,你就能更好地理解 WordPress 的权限系统,并能根据自己的需求,定制出更安全、更灵活的网站。

好了,今天的讲解就到这里,希望对大家有所帮助! 如果有什么疑问,欢迎提问。 拜拜!

发表回复

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