各位观众老爷,大家好!今天咱们来聊聊WordPress里一个“老朋友”——wp_update_user()
函数。这哥们儿,负责更新用户信息,看似简单,实则内部乾坤很大。不搞清楚它的来龙去脉,以后遇到用户数据相关的bug,那可就抓瞎了。
咱们这次就来扒一扒 wp_update_user()
的源码,看看它到底做了些什么,又埋了哪些“雷”。
一、wp_update_user()
的基本用法:简单粗暴的更新
首先,我们先来回顾一下 wp_update_user()
的基本用法。这个函数接受一个数组作为参数,数组里包含了要更新的用户数据。最关键的是,必须包含一个 ID
字段,用来指定要更新哪个用户。
<?php
$user_data = array(
'ID' => 1, // 用户ID,必须有!
'first_name' => '张',
'last_name' => '三',
'user_email' => '[email protected]'
);
$user_id = wp_update_user( $user_data );
if ( is_wp_error( $user_id ) ) {
echo '更新失败:' . $user_id->get_error_message();
} else {
echo '更新成功!用户ID:' . $user_id;
}
?>
上面的代码片段展示了如何更新用户的名字和邮箱。注意,如果更新成功,wp_update_user()
会返回用户ID;如果失败,会返回一个 WP_Error
对象,包含了错误信息。
二、源码剖析:一层一层扒开它的皮
现在,咱们进入正题,开始扒 wp_update_user()
的源码。为了方便理解,我把代码拆解成了几个关键步骤,并加上了详细的注释。
- 参数校验和准备工作
/**
* Updates user data.
*
* @since 2.0.0
*
* @param array|object $userdata User's data. Accepts either a user object or an array of user data.
* The following keys are valid:
* 'ID' - (int) Required. User ID.
* 'user_pass' - (string) The user's password.
* 'user_login' - (string) The user's login name.
* 'user_nicename' - (string) The user's nice name.
* 'user_url' - (string) The user's URL.
* 'user_email' - (string) The user's email address.
* 'display_name' - (string) The user's display name.
* 'nickname' - (string) The user's nickname.
* 'first_name' - (string) The user's first name.
* 'last_name' - (string) The user's last name.
* 'description' - (string) The user's description.
* 'rich_editing' - (string) Whether to enable the rich editor.
* 'syntax_highlighting' - (string) Whether to enable syntax highlighting.
* 'comment_shortcuts' - (string) Whether to enable comment shortcuts.
* 'admin_color' - (string) The user's admin color scheme.
* 'use_ssl' - (string) Whether to force SSL for the user's admin screens.
* 'show_admin_bar_front' - (string) Whether to show the admin bar on the front end.
* 'locale' - (string) The user's locale.
* 'wp_capabilities' - (array) The user's capabilities.
* 'wp_user_level' - (int) The user's user level.
* 'user_activation_key' - (string) The user's activation key.
* 'spam' - (string) Whether the user is marked as spam.
* 'deleted' - (string) Whether the user is marked as deleted.
* @return int|WP_Error WP_Error on failure, User ID on success.
*/
function wp_update_user( $userdata ) {
global $wpdb;
// 首先,确保传入的是数组或者对象
if ( ! is_array( $userdata ) && ! is_object( $userdata ) ) {
return new WP_Error( 'invalid_user_data', __( 'Invalid user data.' ) );
}
// 如果传入的是对象,转换成数组
if ( is_object( $userdata ) ) {
$userdata = get_object_vars( $userdata );
}
// 必须要有用户ID
if ( ! isset( $userdata['ID'] ) ) {
return new WP_Error( 'no_user_id', __( 'You must include a user ID.' ) );
}
$user_id = (int) $userdata['ID']; // 强制转换成整数,防止SQL注入
// 获取旧的用户数据
$old_user = get_userdata( $user_id );
if ( false === $old_user ) {
return new WP_Error( 'invalid_user', __( 'Invalid user ID.' ) );
}
// 移除不应该直接更新的字段
$illegal_names = array( 'user_login', 'user_pass', 'user_nicename' );
foreach ( $illegal_names as $illegal_name ) {
if ( isset( $userdata[ $illegal_name ] ) ) {
unset( $userdata[ $illegal_name ] );
}
}
// 过滤用户数据,允许修改,`pre_user_update` 钩子
$userdata = apply_filters( 'pre_user_update', $userdata, $user_id );
}
这一步主要做了以下几件事:
- 类型检查: 确保传入的是数组或对象。
- ID验证: 检查
ID
字段是否存在,这是更新用户的关键。 - 获取旧数据: 通过
get_userdata()
获取旧的用户数据,后续会用到。 - 移除非法字段: 移除
user_login
、user_pass
、user_nicename
这些不应该直接更新的字段,如果需要修改这些字段,应该使用专门的函数。 pre_user_update
钩子: 这是第一个钩子,允许我们在更新用户数据之前,修改userdata
数组。
- 数据清洗和准备
// 数据清洗,使用 wp_unslash() 函数
$userdata = wp_unslash( $userdata );
// 准备更新的数据数组
$data = array();
$update = array();
$update['ID'] = $user_id;
// 定义允许更新的字段
$updateable_keys = array(
'user_url',
'user_email',
'display_name',
'nickname',
'first_name',
'last_name',
'description',
'rich_editing',
'syntax_highlighting',
'comment_shortcuts',
'admin_color',
'use_ssl',
'show_admin_bar_front',
'locale',
);
// 循环遍历,将需要更新的字段添加到 $data 数组中
foreach ( $updateable_keys as $field ) {
if ( isset( $userdata[ $field ] ) ) {
$data[ $field ] = $userdata[ $field ];
$update[ $field ] = $userdata[ $field ];
}
}
// 处理 user_email 的情况
if ( isset( $userdata['user_email'] ) ) {
$user_email = sanitize_email( $userdata['user_email'] );
if ( ! is_email( $user_email ) ) {
return new WP_Error( 'invalid_email', __( 'The email address is invalid.' ) );
}
if ( email_exists( $user_email, $user_id ) ) {
return new WP_Error( 'email_exists', __( 'The email address is already in use.' ) );
}
$data['user_email'] = $user_email;
$update['user_email'] = $user_email;
}
这一步主要做了以下几件事:
- 数据清洗: 使用
wp_unslash()
函数移除反斜杠,防止转义问题。 - 准备更新数组: 创建
$data
数组,用于存储要更新的字段和值。 - 定义可更新字段:
$updateable_keys
数组定义了允许直接更新的字段。 - 字段过滤: 循环遍历
$updateable_keys
,将$userdata
中存在的字段添加到$data
数组中。 user_email
特殊处理: 对user_email
进行了特殊处理,包括验证邮箱格式、检查邮箱是否已存在。
- 更新用户元数据(User Meta)
// 处理用户元数据
$meta_update = array();
$meta_keys = array(
'nickname',
'first_name',
'last_name',
'description'
);
foreach ( $meta_keys as $meta_key ) {
if ( isset( $userdata[ $meta_key ] ) ) {
$meta_update[ $meta_key ] = $userdata[ $meta_key ];
}
}
foreach ( $meta_update as $meta_key => $meta_value ) {
update_user_meta( $user_id, $meta_key, $meta_value );
}
这一步专门处理用户元数据。虽然 nickname
、first_name
、last_name
、description
也在 $updateable_keys
中,但是它们的值实际上是存储在 wp_usermeta
表中的。所以这里需要单独调用 update_user_meta()
函数来更新这些字段。
- 更新用户角色和权限
// 处理用户角色
if ( isset( $userdata['role'] ) ) {
$role = $userdata['role'];
// 如果当前用户没有编辑用户的权限,并且尝试修改自己的角色,则返回错误
if ( ! current_user_can( 'edit_users' ) && $user_id == get_current_user_id() ) {
return new WP_Error( 'edit_users', __( 'Sorry, you are not allowed to edit users.' ) );
}
// 删除旧的角色
$old_user->remove_role( $old_user->roles[0] );
// 添加新的角色
$old_user->add_role( $role );
}
// 处理用户权限
if ( isset( $userdata['wp_capabilities'] ) && is_array( $userdata['wp_capabilities'] ) ) {
$capabilities = $userdata['wp_capabilities'];
// 更新用户权限
foreach ( $capabilities as $cap => $grant ) {
$old_user->add_cap( $cap, $grant );
}
}
这一步处理用户角色和权限。
role
处理: 如果传入了role
字段,会先移除旧的角色,然后添加新的角色。wp_capabilities
处理: 如果传入了wp_capabilities
字段,会循环遍历,更新用户的权限。
- 执行数据库更新
// 如果有需要更新的数据,则执行数据库更新
if ( ! empty( $data ) ) {
$where = array( 'ID' => $user_id );
$result = $wpdb->update( $wpdb->users, $data, $where );
if ( false === $result ) {
return new WP_Error( 'db_update_error', __( 'Could not update user in database.' ), $wpdb->last_error );
}
}
这一步真正执行数据库更新操作。使用 $wpdb->update()
函数更新 wp_users
表中的数据。
- 触发 Action 钩子
// 触发 action 钩子
do_action( 'profile_update', $user_id, $old_user->data );
do_action( 'edit_user_profile_update', $user_id );
do_action( 'personal_options_update', $user_id );
wp_cache_delete( $user_id, 'users' );
wp_cache_delete( 'user_email_' . $data['user_email'], 'useremail' );
return $user_id;
}
最后,触发一系列的 action 钩子,并清理缓存。
profile_update
钩子: 在用户资料更新后触发,传递用户ID和旧的用户数据。edit_user_profile_update
钩子: 在编辑用户资料页面更新后触发,传递用户ID。personal_options_update
钩子: 在个人选项更新后触发,传递用户ID。- 清理缓存: 使用
wp_cache_delete()
函数清理用户缓存,确保下次获取到的是最新的数据。
三、wp_update_user()
流程总结
为了更清晰地理解 wp_update_user()
的流程,我用表格来总结一下:
步骤 | 描述 | 涉及的函数/钩子 |
---|---|---|
1 | 参数校验和准备工作: 验证输入参数,获取旧的用户数据,移除非法字段,触发 pre_user_update 钩子。 |
is_array() , is_object() , get_object_vars() , get_userdata() , apply_filters( 'pre_user_update' ) |
2 | 数据清洗和准备: 清洗数据,定义可更新字段,过滤需要更新的字段,对 user_email 进行特殊处理。 |
wp_unslash() , sanitize_email() , is_email() , email_exists() |
3 | 更新用户元数据: 更新存储在 wp_usermeta 表中的用户元数据,例如 nickname 、first_name 、last_name 、description 。 |
update_user_meta() |
4 | 更新用户角色和权限: 更新用户的角色和权限。 | remove_role() , add_role() , add_cap() |
5 | 执行数据库更新: 使用 $wpdb->update() 函数更新 wp_users 表中的数据。 |
$wpdb->update() |
6 | 触发 Action 钩子和清理缓存: 触发一系列的 action 钩子,例如 profile_update 、edit_user_profile_update 、personal_options_update ,并清理用户缓存。 |
do_action() , wp_cache_delete() |
四、重点关注的钩子:pre_user_update
和 profile_update
wp_update_user()
函数中,有两个钩子尤其值得关注:
pre_user_update
: 这个钩子允许我们在更新用户数据之前,修改$userdata
数组。这给了我们很大的灵活性,可以自定义一些验证规则,或者添加一些额外的字段。
<?php
add_filter( 'pre_user_update', 'my_pre_user_update', 10, 2 );
function my_pre_user_update( $userdata, $user_id ) {
// 检查用户是否尝试修改自己的邮箱
if ( isset( $userdata['user_email'] ) && $user_id == get_current_user_id() ) {
// 验证新的邮箱是否符合自定义的规则
if ( ! my_validate_email( $userdata['user_email'] ) ) {
// 如果不符合规则,则返回错误
add_filter( 'wp_kses_allowed_html', 'wpse_kses_allowed_html', 10, 2 );
function wpse_kses_allowed_html( $tags, $context ) {
$tags['p']['class'] = true;
$tags['a']['class'] = true;
return $tags;
}
$error = new WP_Error( 'invalid_email', __('<strong>ERROR</strong>: The email address is invalid according to our custom rules.', 'textdomain') );
return $error;
}
}
return $userdata;
}
// 自定义邮箱验证规则
function my_validate_email( $email ) {
// 这里可以添加你自己的验证逻辑
// 例如,检查邮箱是否包含特定的域名
if ( strpos( $email, '@example.com' ) !== false ) {
return true;
} else {
return false;
}
}
?>
profile_update
: 这个钩子在用户资料更新后触发,可以用来执行一些后续操作,例如发送邮件通知、记录日志等。
<?php
add_action( 'profile_update', 'my_profile_update', 10, 2 );
function my_profile_update( $user_id, $old_user_data ) {
// 获取新的用户数据
$new_user_data = get_userdata( $user_id );
// 比较新的和旧的用户数据,找出修改的字段
$changed_fields = array();
foreach ( (array) $new_user_data->data as $key => $value ) {
if ( $value != $old_user_data->$key ) {
$changed_fields[ $key ] = array(
'old' => $old_user_data->$key,
'new' => $value
);
}
}
// 如果有修改的字段,则发送邮件通知
if ( ! empty( $changed_fields ) ) {
$message = "用户ID: " . $user_id . "n";
$message .= "以下字段被修改:n";
foreach ( $changed_fields as $field => $values ) {
$message .= " " . $field . ": " . $values['old'] . " -> " . $values['new'] . "n";
}
wp_mail( '[email protected]', '用户资料更新通知', $message );
}
}
?>
五、注意事项和常见问题
- 必须包含
ID
字段: 这是wp_update_user()
的前提条件,否则会报错。 - 不要直接更新
user_login
、user_pass
、user_nicename
: 这些字段应该使用专门的函数来修改,例如wp_update_user_login()
、wp_set_password()
。 - 注意数据清洗和验证: 特别是
user_email
字段,要进行格式验证和重复性检查,防止出现脏数据。 - 合理利用钩子:
pre_user_update
和profile_update
钩子可以帮助我们实现自定义的逻辑,增强wp_update_user()
的功能。 - 角色权限问题: 更新用户角色时,需要确保当前用户有足够的权限。
- 缓存问题: 修改用户信息后,注意清理缓存,避免显示旧的数据。
六、总结
wp_update_user()
函数是 WordPress 中更新用户数据的重要工具。通过深入了解它的源码和流程,我们可以更好地掌握用户数据更新的机制,避免踩坑,并利用钩子机制扩展其功能。希望今天的分享能帮助大家更好地理解和使用这个“老朋友”。
今天的讲座就到这里,谢谢大家! 如果有问题,欢迎提问。