详细阐述 WordPress 的角色(Roles)与权限(Capabilities)体系,并分析其底层源码实现。

WordPress 角色与权限体系解剖:一场源码级的角色扮演

大家好,我是今天的主讲人,大家可以叫我“代码搬运工”。今天咱们不聊风花雪月,直奔主题,深入WordPress的骨髓,扒一扒它的角色(Roles)与权限(Capabilities)体系。这玩意儿就像WordPress的户籍制度,决定了谁能干什么,谁只能看热闹。

第一幕:角色与权限,傻傻分不清?

首先,咱们得搞清楚两个概念:角色(Roles)和权限(Capabilities)。

  • 角色(Roles): 简单来说,角色就是一群权限的集合。比如“管理员”、“编辑”、“作者”等,每个角色都预设了一堆权限。你可以把角色想象成一个职业,比如“医生”,医生这个职业就自带了诊断、开药等技能。

  • 权限(Capabilities): 权限才是真正干活的东西,它定义了用户能做什么。比如“edit_posts”(编辑文章)、“delete_posts”(删除文章)等等。你可以把权限想象成一个个具体的技能,比如“手术”、“打针”等。

所以,角色是权限的“打包套餐”,用户被赋予某个角色,就相当于获得了这个角色套餐里包含的所有权限。

举个例子:

角色(Role) 包含的权限(Capabilities)
管理员 (Administrator) manage_options, edit_users, delete_users, edit_posts, delete_posts, … (基本上所有权限)
编辑 (Editor) edit_posts, publish_posts, delete_posts, edit_others_posts, …
作者 (Author) edit_posts, publish_posts, delete_posts, … (只能编辑自己的文章)
投稿者 (Contributor) edit_posts (只能编辑自己的草稿), read
订阅者 (Subscriber) read

第二幕:WordPress 如何管理角色和权限?

WordPress使用 $wp_roles 全局对象来管理角色和权限。这个对象是 WP_Roles 类的一个实例。咱们来扒一下这个类的一些核心方法:

  • add_role( $role, $display_name, $capabilities = array() ) 添加一个新角色。

    // 源码简化版
    public function add_role( $role, $display_name, $capabilities = array() ) {
        if ( isset( $this->roles[$role] ) ) {
            return false; // 角色已存在
        }
    
        $this->roles[$role] = array(
            'name'         => $display_name,
            'capabilities' => $capabilities,
        );
    
        $this->role_names[$role] = $display_name;
    
        // 更新数据库,存储角色信息
        update_option( $this->role_key, $this->roles );
    
        $this->reinit(); // 重新初始化角色信息
    
        return true;
    }

    这个方法就是简单地把角色名、显示名和权限数组存到 $this->roles 数组里,然后更新数据库的 wp_options 表里的 wp_user_roles 选项。

  • remove_role( $role ) 删除一个角色。

    // 源码简化版
    public function remove_role( $role ) {
        if ( ! isset( $this->roles[$role] ) ) {
            return; // 角色不存在
        }
    
        unset( $this->roles[$role] );
        unset( $this->role_names[$role] );
    
        // 更新数据库
        update_option( $this->role_key, $this->roles );
    
        $this->reinit();
    }

    add_role() 类似,就是把 $this->roles 数组里对应的角色删掉,然后更新数据库。

  • get_role( $role ) 获取一个角色对象。

    // 源码简化版
    public function get_role( $role ) {
        if ( isset( $this->roles[$role] ) ) {
            return new WP_Role( $role, $this->roles[$role]['capabilities'] );
        }
    
        return null;
    }

    这个方法会返回一个 WP_Role 类的实例,这个类包含了角色的权限信息。

  • add_cap( $role, $cap, $grant = true ) 给某个角色添加权限。

    // 源码简化版
    public function add_cap( $role, $cap, $grant = true ) {
        if ( ! isset( $this->roles[$role] ) ) {
            return; // 角色不存在
        }
    
        $this->roles[$role]['capabilities'][$cap] = $grant;
    
        // 更新数据库
        update_option( $this->role_key, $this->roles );
    
        $this->reinit();
    }

    这个方法会把权限添加到对应角色的 $this->roles 数组里,然后更新数据库。

  • remove_cap( $role, $cap ) 从某个角色移除权限。

    // 源码简化版
    public function remove_cap( $role, $cap ) {
        if ( ! isset( $this->roles[$role] ) ) {
            return; // 角色不存在
        }
    
        unset( $this->roles[$role]['capabilities'][$cap] );
    
        // 更新数据库
        update_option( $this->role_key, $this->roles );
    
        $this->reinit();
    }

    这个方法会把权限从对应角色的 $this->roles 数组里移除,然后更新数据库。

第三幕:用户如何获得角色和权限?

用户的信息存储在 wp_users 表里,而用户的角色信息存储在 wp_usermeta 表里。wp_usermeta 表里有一条记录,meta_keywp_capabilitieswp_ 是表前缀),meta_value 是一个序列化的数组,包含了用户拥有的所有权限。

当用户登录时,WordPress会创建一个 WP_User 类的实例来代表这个用户。这个类有一些方法来判断用户是否拥有某个权限:

  • has_cap( $capability ) 判断用户是否拥有某个权限。

    // 源码简化版
    public function has_cap( $capability ) {
        global $wp_roles;
    
        $capabilities = $this->get_caps(); // 获取用户的所有权限
    
        // 如果用户拥有该权限,直接返回 true
        if ( isset( $capabilities[$capability] ) && $capabilities[$capability] ) {
            return true;
        }
    
        // 如果用户是管理员,直接返回 true
        if ( is_super_admin( $this->ID ) ) {
            return true;
        }
    
        // 检查角色是否拥有该权限
        if ( is_array( $this->roles ) ) {
            foreach ( $this->roles as $role ) {
                if ( isset( $wp_roles->roles[$role]['capabilities'][$capability] ) && $wp_roles->roles[$role]['capabilities'][$capability] ) {
                    return true;
                }
            }
        }
    
        // 权限不存在,返回 false
        return apply_filters( 'user_has_cap', false, $capability, $this->ID, $capabilities );
    }

    这个方法会先检查用户是否直接拥有该权限,如果没有,再检查用户所属的角色是否拥有该权限。如果都没有,就返回 false。

  • get_caps() 获取用户的所有权限。

    // 源码简化版
    public function get_caps() {
        if ( isset( $this->allcaps ) ) {
            return $this->allcaps; // 如果已经缓存了,直接返回
        }
    
        global $wp_roles;
    
        $capabilities = array();
    
        // 获取角色拥有的权限
        if ( is_array( $this->roles ) ) {
            foreach ( $this->roles as $role ) {
                if ( isset( $wp_roles->roles[$role]['capabilities'] ) ) {
                    $capabilities = array_merge( $capabilities, $wp_roles->roles[$role]['capabilities'] );
                }
            }
        }
    
        // 获取用户直接拥有的权限
        if ( isset( $this->caps ) && is_array( $this->caps ) ) {
            $capabilities = array_merge( $capabilities, $this->caps );
        }
    
        // 对权限进行过滤
        $capabilities = apply_filters( 'user_caps', $capabilities, $this->caps, $this );
    
        // 缓存权限
        $this->allcaps = $capabilities;
    
        return $capabilities;
    }

    这个方法会先获取用户所属的角色拥有的所有权限,然后再获取用户直接拥有的所有权限,最后合并成一个数组。

第四幕:权限检查,无处不在

WordPress在很多地方都会进行权限检查,比如:

  • 编辑文章: 在编辑文章的页面,WordPress会检查用户是否拥有 edit_post 权限。
  • 删除文章: 在删除文章的操作中,WordPress会检查用户是否拥有 delete_post 权限。
  • 管理主题: 在管理主题的页面,WordPress会检查用户是否拥有 switch_themes 权限。

WordPress提供了一些函数来进行权限检查,比如:

  • current_user_can( $capability ) 检查当前用户是否拥有某个权限。

    // 源码简化版
    function current_user_can( $capability ) {
        $args = array_slice( func_get_args(), 1 );
        global $wp_current_user;
    
        return apply_filters( 'current_user_can', call_user_func_array( array( $wp_current_user, 'has_cap' ), array_merge( (array) $capability, $args ) ), $capability, $args );
    }

    这个函数会调用 $wp_current_user 对象的 has_cap() 方法来判断当前用户是否拥有某个权限。

  • is_super_admin( $user_id = false ) 检查用户是否是超级管理员(在多站点模式下)。

    // 源码简化版
    function is_super_admin( $user_id = false ) {
        global $current_site, $super_admins;
    
        if ( ! is_multisite() ) {
            return apply_filters( 'is_super_admin', false, $user_id ); // 单站点模式下,默认没有超级管理员
        }
    
        if ( empty( $super_admins ) ) {
            return apply_filters( 'is_super_admin', false, $user_id );
        }
    
        if ( ! $user_id ) {
            $user = wp_get_current_user();
            if ( empty( $user->ID ) ) {
                return apply_filters( 'is_super_admin', false, $user_id );
            }
            $user_id = $user->ID;
        }
    
        $super_admins = apply_filters( 'super_admins', $super_admins );
    
        return apply_filters( 'is_super_admin', in_array( (string) $user_id, (array) $super_admins, true ), $user_id );
    }

    这个函数会检查用户是否在 $super_admins 数组里。

第五幕:自定义角色和权限

WordPress允许开发者自定义角色和权限。你可以使用 $wp_roles 对象来添加、删除角色和权限。

例如,你可以创建一个名为“内容审核员”的角色,拥有审核文章的权限:

global $wp_roles;

// 添加角色
$result = add_role(
    'content_reviewer',
    '内容审核员',
    array(
        'read'         => true,  // 允许阅读
        'moderate_comments' => true, // 允许审核评论
        'edit_posts'       => false, // 不允许编辑文章
        'publish_posts'    => false, // 不允许发布文章
    )
);

if($result){
    echo '角色创建成功!';
}
else{
    echo '角色创建失败!';
}

// 给角色添加权限(如果角色已经存在)
$role = get_role( 'content_reviewer' );
if ( ! empty( $role ) ) {
    $role->add_cap( 'moderate_comments' ); // 允许审核评论
}

你也可以给用户添加自定义的权限:

$user_id = 1; // 用户ID
$user = new WP_User( $user_id );

// 添加权限
$user->add_cap( 'can_view_sensitive_data' );

// 检查权限
if ( user_can( $user_id, 'can_view_sensitive_data' ) ) {
    echo '用户可以查看敏感数据';
} else {
    echo '用户不能查看敏感数据';
}

第六幕:插件开发中的角色与权限应用

在插件开发中,合理利用角色与权限体系至关重要。比如,你可以创建一个插件,只允许管理员才能访问插件的设置页面:

add_action( 'admin_menu', 'my_plugin_menu' );

function my_plugin_menu() {
    // 只有管理员才能看到菜单项
    add_menu_page(
        'My Plugin Settings',
        'My Plugin',
        'manage_options', // 需要 manage_options 权限
        'my-plugin-settings',
        'my_plugin_settings_page'
    );
}

function my_plugin_settings_page() {
    // 插件设置页面内容
    echo '<h1>My Plugin Settings</h1>';
    echo '<p>管理员才能看到这个页面</p>';
}

或者,你可以创建一个自定义文章类型,只有特定角色才能编辑:

add_action( 'init', 'my_custom_post_type' );

function my_custom_post_type() {
    $args = array(
        'public'    => true,
        'label'     => 'My Custom Post',
        'capability_type' => 'my_custom_post', // 自定义权限类型
        'map_meta_cap' => true, // 启用自定义权限映射
    );

    register_post_type( 'my_custom_post', $args );

    // 添加自定义权限
    $roles = array( 'administrator', 'editor' ); // 允许管理员和编辑
    foreach ( $roles as $role_name ) {
        $role = get_role( $role_name );
        if ( ! empty( $role ) ) {
            $role->add_cap( 'edit_my_custom_post' );
            $role->add_cap( 'read_my_custom_post' );
            $role->add_cap( 'delete_my_custom_post' );
            $role->add_cap( 'edit_others_my_custom_posts' );
            $role->add_cap( 'publish_my_custom_posts' );
            $role->add_cap( 'read_private_my_custom_posts' );
            $role->add_cap( 'delete_posts' );
            $role->add_cap( 'delete_private_posts' );
        }
    }
}

第七幕:权限映射(Capability Mapping)

WordPress的权限映射机制可以将一些通用的权限(比如 edit_post)映射到更具体的权限(比如 edit_my_custom_post)。这使得你可以更灵活地控制用户对不同类型内容的访问权限。

map_meta_cap 参数就是用来启用权限映射的。当 map_meta_cap 设置为 true 时,WordPress会根据文章的类型、作者等信息,将通用的权限映射到更具体的权限。

例如,对于自定义文章类型 my_custom_post,WordPress会自动创建以下权限:

  • edit_my_custom_post:编辑单个文章
  • read_my_custom_post:读取单个文章
  • delete_my_custom_post:删除单个文章
  • edit_posts:编辑所有文章
  • edit_others_posts:编辑其他人的文章
  • publish_posts:发布文章
  • read_private_posts:读取私有文章

通过自定义权限映射,你可以更精细地控制用户对不同文章的访问权限。

总结陈词

今天,我们深入探讨了WordPress的角色与权限体系,从概念到源码,再到实际应用,希望能帮助大家更好地理解和利用这个强大的功能。记住,角色和权限就像WordPress的“安保系统”,合理配置它们,才能确保网站的安全和稳定。

最后,给大家留个思考题:如何创建一个角色,允许用户只能编辑特定分类的文章? 欢迎大家在评论区留言讨论。 祝大家编程愉快,下次再见!

发表回复

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