深入解读 WordPress `wpdb` 类源码:`has_cap()` 方法的数据库权限判断。

各位观众,晚上好!(敲黑板)

今天咱们来聊聊 WordPress 里的“权限管理”,更具体地说,深入 wpdb 类,扒一扒 has_cap() 这个方法,看看它是怎么在数据库层面判断用户有没有权限干点啥的。别担心,咱们尽量用大白话,配合代码,保证你听完之后也能对着源码指点江山。

一、权限是什么?为什么要管它?

想象一下,你开了一家网站,有人是管理员,有人是编辑,有人是读者。管理员可以删文章、改配置,编辑只能写文章,读者只能看看。这就是权限!

权限管理的目的很简单:

  • 安全: 防止恶意用户搞破坏。
  • 控制: 保证不同角色只能做他们该做的事。
  • 组织: 让网站管理更有条理。

WordPress 的权限系统是基于 Capabilities(能力)的概念。一个 Capability 就像一张许可证,拥有这张许可证的用户就能执行特定的操作。 比如 edit_posts 表示编辑文章的权限,delete_posts 表示删除文章的权限。

二、wpdb 是什么鬼?跟权限有什么关系?

wpdb 类是 WordPress 里的数据库操作核心类。 所有的数据库查询、更新、删除操作,基本都得通过它。

你可能会问:权限判断不是应该在 PHP 代码里搞吗?怎么跟数据库扯上关系了?

别急,WordPress 的权限信息(哪些用户拥有哪些 Capability)实际上是存储在数据库里的。所以,wpdb 就成了判断权限的“中间人”。 当我们调用 has_cap() 判断用户是否有某个权限时,最终还是要通过 wpdb 去数据库里查一下才能知道答案。

三、has_cap() 的源码剖析:一步步追踪权限的足迹

接下来,咱们就来深入研究 has_cap() 方法的源码。 准备好,我们要开始“解剖”了!

  1. current_user_can() 函数:权限判断的入口

    在 WordPress 里,我们通常使用 current_user_can() 函数来判断当前用户是否有某个权限。这个函数其实是对 WP_User 类的 has_cap() 方法的封装。

    // wp-includes/capabilities.php
    function current_user_can( $capability ) {
       $args = array_slice( func_get_args(), 1 );
       return apply_filters( 'current_user_can', get_current_user_id() ? wp_get_current_user()->has_cap( $capability, $args ) : false, $capability, $args );
    }

    可以看到,它获取当前用户对象(wp_get_current_user()),然后调用这个对象的 has_cap() 方法。

  2. WP_User::has_cap() 方法:能力验证的核心

    WP_User 类的 has_cap() 方法负责实际的能力验证。它会检查用户是否直接拥有该 Capability,或者是否拥有某个角色,而该角色又拥有该 Capability。

    // wp-includes/class-wp-user.php
    function has_cap( $capability ) {
       $args = array_slice( func_get_args(), 1 );
    
       // 如果需要,获取用户元数据
       if ( empty( $this->caps ) ) {
           $this->get_caps_from_db();
       }
    
       // 如果用户是超级管理员,直接返回 true
       if ( is_multisite() && is_super_admin( $this->ID ) ) {
           return apply_filters( 'user_has_cap', true, $capability, $this->ID, $args );
       }
    
       // 检查用户是否直接拥有该 Capability
       if ( isset( $this->caps[ $capability ] ) && $this->caps[ $capability ] ) {
           return apply_filters( 'user_has_cap', true, $capability, $this->ID, $args );
       }
    
       // 检查用户是否拥有某个角色,而该角色又拥有该 Capability
       foreach ( (array) $this->roles as $role ) {
           if ( isset( $this->wp_roles->roles[ $role ]['capabilities'][ $capability ] ) && $this->wp_roles->roles[ $role ]['capabilities'][ $capability ] ) {
               return apply_filters( 'user_has_cap', true, $capability, $this->ID, $args );
           }
       }
    
       // 如果以上条件都不满足,则返回 false
       return apply_filters( 'user_has_cap', false, $capability, $this->ID, $args );
    }

    这个方法做了这些事:

    • 获取用户元数据: 如果用户的 Capability 信息($this->caps)为空,就调用 get_caps_from_db() 方法从数据库里加载。
    • 超级管理员判断: 如果用户是超级管理员(在多站点环境中),直接返回 true
    • 直接 Capability 检查: 检查用户的 $caps 数组里是否直接拥有该 Capability。
    • 角色 Capability 检查: 遍历用户的角色,检查每个角色是否拥有该 Capability。
  3. WP_User::get_caps_from_db() 方法:从数据库加载 Capability

    这个方法是重点,它负责从数据库里加载用户的 Capability 信息。 这里就用到了 wpdb 类。

    // wp-includes/class-wp-user.php
    function get_caps_from_db() {
       global $wpdb, $wp_roles;
    
       $this->caps = apply_filters( 'user_capabilities', (array) get_user_meta( $this->ID, $wpdb->prefix . 'capabilities', true ) );
       $this->roles = apply_filters( 'user_roles', (array) get_user_meta( $this->ID, $wpdb->prefix . 'user_roles', true ) );
    
       $this->wp_roles = $wp_roles;
       return;
    }

    看到了吗?这里调用了 get_user_meta() 函数两次:

    • get_user_meta( $this->ID, $wpdb->prefix . 'capabilities', true ) 获取用户的 Capability 信息。 这个信息以数组的形式存储在 wp_usermeta 表里,键名为 wp_capabilitieswp_ 是表前缀,可以自定义)。
    • get_user_meta( $this->ID, $wpdb->prefix . 'user_roles', true ) 获取用户的角色信息。 这个信息也以数组的形式存储在 wp_usermeta 表里,键名为 wp_user_roles

    get_user_meta() 函数内部会使用 wpdb 类来执行数据库查询。

  4. get_user_meta() 函数:封装数据库查询

    get_user_meta() 函数负责从 wp_usermeta 表里获取用户元数据。

    // wp-includes/user.php
    function get_user_meta( $user_id, $key = '', $single = false ) {
       return get_metadata( 'user', $user_id, $key, $single );
    }

    它实际上是对 get_metadata() 函数的封装,指定了元数据类型为 user

  5. get_metadata() 函数:通用的元数据获取函数

    get_metadata() 函数是一个通用的元数据获取函数,可以用于获取用户、文章、分类等各种类型的元数据。

    // wp-includes/meta.php
    function get_metadata( $meta_type, $object_id, $meta_key = '', $single = false ) {
       global $wpdb;
    
       if ( ! is_numeric( $object_id ) ) {
           return false;
       }
    
       $table = _get_meta_table( $meta_type );
    
       if ( ! $table ) {
           return false;
       }
    
       $meta_key = sanitize_key( $meta_key );
       $cache_key = sanitize_key( $meta_key );
       $id_and_key = "$object_id:$cache_key";
    
       $cache = wp_cache_get( $id_and_key, $meta_type . '_meta' );
    
       if ( false !== $cache ) {
           if ( $meta_key && ! $single ) {
               $cache = array_map( 'maybe_unserialize', $cache );
           } elseif ( $meta_key && $single ) {
               if ( isset( $cache[0] ) ) {
                   $cache = maybe_unserialize( $cache[0] );
               } else {
                   $cache = '';
               }
           } elseif ( ! $meta_key ) {
               $cache = array_map( 'maybe_unserialize', $cache );
           }
    
           return $cache;
       }
    
       $meta_key_clause = $wpdb->prepare( "AND meta_key = %s", $meta_key );
    
       $sql = "SELECT meta_value FROM $table WHERE meta_key != '_edit_lock' AND meta_key != '_edit_last' AND meta_id != 0 AND object_id = %d $meta_key_clause";
    
       if ( $single ) {
           $sql .= " LIMIT 1";
       }
    
       $cache = array();
       $results = $wpdb->get_col( $wpdb->prepare( $sql, $object_id ) );
    
       if ( ! empty( $results ) ) {
           if ( $single ) {
               $cache[0] = $results[0];
           } else {
               $cache = $results;
           }
       }
    
       wp_cache_set( $id_and_key, $cache, $meta_type . '_meta' );
    
       if ( $meta_key && ! $single ) {
           $cache = array_map( 'maybe_unserialize', $cache );
       } elseif ( $meta_key && $single ) {
           if ( isset( $cache[0] ) ) {
               $cache = maybe_unserialize( $cache[0] );
           } else {
               $cache = '';
           }
       } elseif ( ! $meta_key ) {
           $cache = array_map( 'maybe_unserialize', $cache );
       }
    
       return $cache;
    }

    这里,我们终于看到了 wpdb 的身影!

    • 获取表名: 通过 _get_meta_table() 函数获取元数据表名(例如 wp_usermeta)。
    • 构建 SQL 查询: 使用 $wpdb->prepare() 方法构建 SQL 查询语句,查询指定用户的指定元数据。
    • 执行查询: 使用 $wpdb->get_col() 方法执行 SQL 查询,获取查询结果。
    • 缓存结果: 将查询结果缓存起来,下次可以直接从缓存中获取,提高性能。

四、总结:has_cap() 的数据库权限判断流程

现在,让我们把整个流程串起来:

  1. 调用 current_user_can( $capability ) 函数。
  2. current_user_can() 调用当前用户对象的 has_cap( $capability ) 方法。
  3. WP_User::has_cap() 方法:
    • 如果用户的 Capability 信息为空,则调用 get_caps_from_db() 方法从数据库加载。
    • 检查用户是否是超级管理员。
    • 检查用户是否直接拥有该 Capability。
    • 检查用户是否拥有某个角色,而该角色又拥有该 Capability。
  4. WP_User::get_caps_from_db() 方法:
    • 调用 get_user_meta() 函数两次,分别获取用户的 Capability 和角色信息。
  5. get_user_meta() 函数:
    • 调用 get_metadata() 函数。
  6. get_metadata() 函数:
    • 使用 wpdb 类构建 SQL 查询语句,从元数据表(例如 wp_usermeta)中查询指定用户的指定元数据。
    • 使用 wpdb 类执行 SQL 查询。
    • 将查询结果缓存起来。

用一张表格来概括一下:

步骤 涉及函数/方法 作用 是否涉及 wpdb
1. 入口 current_user_can() 权限判断的入口函数,封装了 WP_User::has_cap() 方法。
2. 能力验证核心 WP_User::has_cap() 实际的能力验证,检查用户是否直接拥有 Capability 或通过角色拥有 Capability。
3. 从数据库加载 Capability WP_User::get_caps_from_db() 从数据库加载用户的 Capability 和角色信息。
4. 获取用户元数据 get_user_meta() 获取指定用户的指定元数据。
5. 通用的元数据获取 get_metadata() 通用的元数据获取函数,负责构建 SQL 查询语句并执行,使用 wpdb 类操作数据库。
6. 数据库查询(在 get_metadata 内部) $wpdb->prepare(), $wpdb->get_col() 构建 SQL 查询语句,并执行查询。

五、wpdb 在权限判断中的具体应用

咱们再具体看看 wpdbget_metadata() 函数里是怎么用的。

// 假设我们要获取用户 ID 为 1 的 'wp_capabilities' 元数据
$user_id = 1;
$meta_key = 'wp_capabilities';
$single = true; // 获取单个值

global $wpdb;

$table = $wpdb->usermeta; // 获取 usermeta 表名

// 构建 SQL 查询语句
$sql = $wpdb->prepare(
    "SELECT meta_value FROM $table WHERE user_id = %d AND meta_key = %s",
    $user_id,
    $meta_key
);

// 执行查询
$result = $wpdb->get_var( $sql );

// 解序列化结果(如果需要)
$value = maybe_unserialize( $result );

echo "用户 ID 为 1 的 wp_capabilities 元数据:";
print_r( $value );

这段代码演示了如何使用 wpdb 类直接从 wp_usermeta 表里获取用户的 Capability 信息。

  • $wpdb->usermeta 获取 wp_usermeta 表的名称。
  • $wpdb->prepare() 用于构建安全的 SQL 查询语句,防止 SQL 注入。
  • $wpdb->get_var() 用于执行查询,并获取单个结果。
  • maybe_unserialize() 用于解序列化存储在数据库里的数组。

六、总结与思考

通过对 has_cap() 方法的源码剖析,我们了解了 WordPress 是如何利用 wpdb 类在数据库层面进行权限判断的。

  • wpdb 类是权限判断的“桥梁”,它负责从数据库里获取用户的 Capability 和角色信息。
  • get_metadata() 函数是关键,它使用 wpdb 类构建 SQL 查询语句并执行,获取元数据。
  • WordPress 的权限系统是基于 Capability 的,通过检查用户是否直接拥有 Capability 或者通过角色拥有 Capability 来判断权限。

理解了这些,你就可以更深入地定制 WordPress 的权限系统,比如:

  • 自定义 Capability:添加新的 Capability,用于控制特定的操作。
  • 自定义角色:创建新的角色,并分配不同的 Capability。
  • 修改数据库查询:如果你对性能有更高的要求,可以优化 get_metadata() 函数里的 SQL 查询语句。

当然,直接操作数据库有风险,一定要小心谨慎!

好了,今天的讲座就到这里。希望大家有所收获! 咱们下次再见!

发表回复

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