咳咳,各位观众老爷们,晚上好!今天咱们聊点WordPress里面有点意思的东西,就是current_user_can()
这个函数,以及它背后的一个大功臣——map_meta_cap
钩子。
想象一下,你开了一家面包店,你想让不同的人干不同的活儿。老板娘可以啥都干,收银员只能收钱,面包师傅只能烤面包。这权限控制,在WordPress里,current_user_can()
就是负责问:“这个人能不能干这个事儿?”的。而map_meta_cap
,就是那个帮你细化规则,让权限控制更精准的幕后高手。
一、current_user_can()
:权限判断的门卫
先来看看current_user_can()
是干嘛的。这函数接受一个或多个参数:
- 第一个参数: 你要判断的“能力”(capability),比如
edit_posts
(编辑文章),delete_pages
(删除页面)等等。 - 后面的参数: 可选参数,通常是你要操作的对象的ID,比如文章ID,页面ID。这玩意儿很重要,因为权限判断有时候需要根据对象来决定。
<?php
if ( current_user_can( 'edit_post', 123 ) ) {
echo '用户有权限编辑ID为123的文章';
} else {
echo '用户没有权限编辑ID为123的文章';
}
?>
这段代码的意思是:如果当前用户有编辑ID为123的文章的权限,就显示“用户有权限编辑ID为123的文章”,否则显示“用户没有权限编辑ID为123的文章”。
二、Capability:能力的标签
Capability,咱们可以把它理解为“能力的标签”。WordPress自带了很多capability,比如:
Capability | 描述 |
---|---|
read |
阅读 |
edit_posts |
编辑文章 |
publish_posts |
发布文章 |
delete_posts |
删除文章 |
edit_pages |
编辑页面 |
delete_pages |
删除页面 |
manage_options |
管理选项(通常只有管理员才有) |
upload_files |
上传文件 |
moderate_comments |
审核评论 |
这些capability都是比较笼统的。比如edit_posts
,它指的是“编辑文章”的权限,但它没有区分是“编辑自己的文章”还是“编辑别人的文章”。 这时候,map_meta_cap
就派上用场了。
三、map_meta_cap
:权限映射的魔术师
map_meta_cap
是一个过滤器钩子。它的作用是:把一些“抽象的”capability(也就是所谓的“meta capability”)映射到具体的capability上。
Meta Capability vs. Primitive Capability
- Meta Capability: 抽象的,需要根据上下文才能确定的capability。比如
edit_post
(编辑文章),delete_post
(删除文章)。 - Primitive Capability: 具体的,可以直接分配给角色的capability。比如
edit_others_posts
(编辑别人的文章),delete_published_posts
(删除已发布的文章)。
map_meta_cap
的作用,就是把像edit_post
这样的meta capability,根据文章的作者、状态等信息,映射到像edit_others_posts
、edit_published_posts
这样的primitive capability上。
工作原理
当current_user_can()
被调用时,如果传入的capability是一个meta capability,那么WordPress会触发map_meta_cap
钩子。所有注册到这个钩子上的函数都会被执行,它们负责根据传入的参数(用户ID、capability、对象ID等),来决定应该返回哪些具体的capability。
举个栗子:edit_post
的映射
假设我们要判断用户是否有编辑ID为123的文章的权限:
<?php
if ( current_user_can( 'edit_post', 123 ) ) {
echo '用户有权限编辑ID为123的文章';
} else {
echo '用户没有权限编辑ID为123的文章';
}
?>
在这个例子中,edit_post
就是一个meta capability。当current_user_can()
被调用时,WordPress会触发map_meta_cap
钩子。WordPress内部已经有一个默认的函数注册到了map_meta_cap
钩子上,它会根据以下规则进行映射:
- 如果用户是管理员, 那么直接返回
do_not_allow
(表示不允许),因为管理员默认拥有所有权限,不需要进行额外的判断。(这里的设计有点反直觉,返回do_not_allow
表示“不需要进一步检查,允许通过”,而不是“不允许通过”。) - 如果文章的作者是当前用户, 那么会检查用户是否拥有
edit_posts
的权限。如果有,则返回edit_posts
。 - 如果文章的作者不是当前用户, 那么会检查用户是否拥有
edit_others_posts
的权限。如果有,则返回edit_others_posts
。 - 如果文章是已发布的, 那么还会检查用户是否拥有
edit_published_posts
的权限。
通过这些映射,edit_post
这个meta capability就被转化成了具体的primitive capability,current_user_can()
就可以根据这些primitive capability来判断用户是否有权限编辑文章了。
四、自定义map_meta_cap
:打造你的专属权限系统
WordPress默认的map_meta_cap
已经很强大了,但有时候我们还需要自定义权限规则。比如,我们想实现以下功能:
- 只有“编辑”角色的用户才能编辑某个特定分类的文章。
要实现这个功能,我们需要自定义一个函数,注册到map_meta_cap
钩子上。
<?php
/**
* 自定义权限映射函数
*
* @param array $caps Required primitive capabilities for the requested action.
* @param string $cap Capability being checked.
* @param int $user_id User ID.
* @param array $args Adds the context to the cap. Typically the object ID.
* @return array Actual capabilities for performing the action.
*/
function my_custom_map_meta_cap( $caps, $cap, $user_id, $args ) {
// 检查是否是编辑文章的请求
if ( 'edit_post' === $cap ) {
// 获取文章ID
$post_id = $args[0] ?? 0; // 使用空合并运算符,避免未定义索引错误
if ( ! empty( $post_id ) ) {
// 获取文章对象
$post = get_post( $post_id );
if ( $post ) {
// 获取文章的分类ID
$categories = wp_get_post_categories( $post_id );
// 检查文章是否属于特定分类(这里假设分类ID为10)
if ( in_array( 10, $categories, true ) ) {
// 获取用户对象
$user = get_user_by( 'id', $user_id );
// 检查用户是否是“编辑”角色
if ( in_array( 'editor', (array) $user->roles, true ) ) {
// 如果是,则允许编辑
return array( 'edit_posts' ); // 返回 edit_posts 而不是 do_not_allow,因为我们只是限制了特定分类的文章
} else {
// 如果不是,则拒绝编辑
return array( 'do_not_allow' ); //明确拒绝,防止其他capability允许
}
}
} else {
// 如果文章不存在,拒绝编辑
return array( 'do_not_allow' );
}
} else {
// 如果文章ID为空,拒绝编辑
return array( 'do_not_allow' );
}
}
// 如果不是编辑文章的请求,则返回原始的capabilities
return $caps;
}
// 注册钩子
add_filter( 'map_meta_cap', 'my_custom_map_meta_cap', 10, 4 );
?>
这段代码做了以下几件事:
- 定义了一个函数
my_custom_map_meta_cap
, 这个函数接收四个参数:$caps
:原始的capabilities数组。$cap
:要检查的capability(比如edit_post
)。$user_id
:用户ID。$args
:传递给current_user_can()
的额外参数(比如文章ID)。
- 检查
$cap
是否是edit_post
。 如果不是,则直接返回原始的$caps
,不做任何修改。 - 如果
$cap
是edit_post
, 则获取文章ID,然后获取文章对象。 - 获取文章的分类ID, 然后检查文章是否属于特定分类(这里假设分类ID为10)。
- 如果文章属于特定分类, 则获取用户对象,然后检查用户是否是“编辑”角色。
- 如果是“编辑”角色, 则返回
array( 'edit_posts' )
,表示允许编辑。 - 如果不是“编辑”角色, 则返回
array( 'do_not_allow' )
,表示拒绝编辑。 - 使用
add_filter()
函数, 将my_custom_map_meta_cap
函数注册到map_meta_cap
钩子上。
代码解释
$args[0]
:current_user_can()
的第二个参数(文章ID)会作为$args
数组的第一个元素传递给map_meta_cap
钩子。get_post( $post_id )
:根据文章ID获取文章对象。wp_get_post_categories( $post_id )
:根据文章ID获取文章的分类ID数组。in_array( 10, $categories, true )
:检查分类ID数组中是否包含ID为10的分类。true
表示进行严格类型比较。get_user_by( 'id', $user_id )
:根据用户ID获取用户对象。(array) $user->roles
:将用户角色属性转换为数组。in_array( 'editor', (array) $user->roles, true )
:检查用户角色数组中是否包含“editor”角色。
注意事项
map_meta_cap
钩子非常强大,但也需要谨慎使用。不正确的映射可能会导致权限混乱,甚至安全问题。- 在自定义
map_meta_cap
函数时,一定要考虑各种情况,确保你的权限规则是正确的、完整的。 - 尽量避免在
map_meta_cap
函数中执行复杂的数据库查询或其他耗时操作,因为这会影响网站的性能。 - 确保你的代码具有良好的可读性和可维护性,方便以后进行修改和调试。
- 返回
do_not_allow
时要谨慎,确保没有其他capability会覆盖你的拒绝。 - 在 WordPress 5.9 及更高版本中,
map_meta_cap
钩子还可以传递$object_id
参数,如果你只需要对象 ID,可以使用$args[0]
或者直接使用$object_id
。
五、更复杂的栗子:基于文章状态的权限控制
假设我们想实现以下功能:
- 只有“管理员”才能删除已发布的文章。
- “编辑”可以删除草稿文章或者自己发布的文章。
<?php
/**
* 自定义权限映射函数
*
* @param array $caps Required primitive capabilities for the requested action.
* @param string $cap Capability being checked.
* @param int $user_id User ID.
* @param array $args Adds the context to the cap. Typically the object ID.
* @return array Actual capabilities for performing the action.
*/
function my_custom_delete_post_cap( $caps, $cap, $user_id, $args ) {
if ( 'delete_post' === $cap ) {
$post_id = $args[0] ?? 0;
if ( ! empty( $post_id ) ) {
$post = get_post( $post_id );
if ( $post ) {
$user = get_user_by( 'id', $user_id );
if ( 'publish' === $post->post_status ) {
// 已发布的文章
if ( in_array( 'administrator', (array) $user->roles, true ) ) {
// 只有管理员才能删除
return array( 'delete_published_posts' ); // 使用已有的capability
} else {
return array( 'do_not_allow' );
}
} else {
// 草稿或其他状态的文章
if ( in_array( 'editor', (array) $user->roles, true ) || $post->post_author == $user_id ) {
// 编辑可以删除,或者作者自己可以删除
return array( 'delete_posts' ); // 使用已有的capability
} else {
return array( 'do_not_allow' );
}
}
} else {
return array( 'do_not_allow' );
}
} else {
return array( 'do_not_allow' );
}
}
return $caps;
}
add_filter( 'map_meta_cap', 'my_custom_delete_post_cap', 10, 4 );
?>
这个例子更复杂一些,它根据文章的状态和用户的角色,来决定是否允许删除文章。
六、总结
current_user_can()
和 map_meta_cap
是 WordPress 权限控制的核心。current_user_can()
负责发起权限检查,map_meta_cap
负责将抽象的权限映射到具体的权限。通过自定义 map_meta_cap
钩子,我们可以实现各种复杂的权限控制需求,打造一个安全、灵活的网站。
记住,权限控制是一个需要仔细考虑的问题。在实现自定义权限规则时,一定要充分测试,确保你的规则是正确的、完整的,并且不会对网站的性能产生负面影响。
好了,今天的讲座就到这里。希望大家有所收获!如果有什么问题,欢迎提问。 散会!