fix(user): repair phone binding and profile fields
This commit is contained in:
@@ -374,6 +374,9 @@ if (class_exists('\AKSRT\User\Level')) {
|
||||
if (class_exists('\AKSRT\User\LevelAdmin')) {
|
||||
\AKSRT\User\LevelAdmin::get_instance()->init();
|
||||
}
|
||||
if (class_exists('\AKSRT\User\ProfileAdmin')) {
|
||||
\AKSRT\User\ProfileAdmin::init();
|
||||
}
|
||||
if (class_exists('\AKSRT\User\Metrics')) {
|
||||
\AKSRT\User\Metrics::get_instance()->init();
|
||||
}
|
||||
|
||||
@@ -267,8 +267,6 @@ if (!function_exists('aksrt_get_avatar_box')) {
|
||||
}
|
||||
}
|
||||
|
||||
add_action('show_user_profile', 'aksrt_render_admin_avatar_field');
|
||||
add_action('edit_user_profile', 'aksrt_render_admin_avatar_field');
|
||||
add_action('user_edit_form_tag', static function (): void {
|
||||
echo ' enctype="multipart/form-data"';
|
||||
});
|
||||
@@ -312,137 +310,6 @@ if (!function_exists('aksrt_get_user_description')) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_render_admin_avatar_field')) {
|
||||
/**
|
||||
* Render local avatar controls on the native WordPress user profile screen.
|
||||
*/
|
||||
function aksrt_render_admin_avatar_field(WP_User $user): void {
|
||||
if (!current_user_can('edit_user', $user->ID) || !aksrt_is_avatar_upload_enabled()) {
|
||||
return;
|
||||
}
|
||||
?>
|
||||
<h2><?php esc_html_e('AKSRT 头像', 'aksrt'); ?></h2>
|
||||
<table class="form-table" role="presentation">
|
||||
<tr>
|
||||
<th scope="row"><?php esc_html_e('当前头像', 'aksrt'); ?></th>
|
||||
<td>
|
||||
<?php echo get_avatar($user->ID, 96, '', $user->display_name, ['loading' => 'eager']); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
<p><input type="file" name="aksrt_avatar_file" accept="image/jpeg,image/png,image/webp,image/gif"></p>
|
||||
<?php if (aksrt_get_custom_avatar_url((int) $user->ID, 96) !== '') : ?>
|
||||
<label><input type="checkbox" name="aksrt_avatar_reset" value="1"> <?php esc_html_e('恢复项目默认头像并删除当前头像文件', 'aksrt'); ?></label>
|
||||
<?php endif; ?>
|
||||
<p class="description"><?php esc_html_e('支持 JPG、PNG、GIF、WebP;大小上限由“权限 → 附件权限”控制。', 'aksrt'); ?></p>
|
||||
<?php wp_nonce_field('aksrt_save_admin_avatar_' . $user->ID, 'aksrt_admin_avatar_nonce'); ?>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
|
||||
add_filter('user_profile_update_errors', static function (WP_Error $errors, bool $update): void {
|
||||
if (empty($_FILES['aksrt_avatar_file']['name']) || !aksrt_is_avatar_upload_enabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$file = $_FILES['aksrt_avatar_file'];
|
||||
// 头像上限并入「前端图片上传限制」统一治理(参考主题同源)。
|
||||
$max_size = class_exists('\AKSRT\Attachment\UploadLimits') ? \AKSRT\Attachment\UploadLimits::limit_bytes('image') : (int) wp_max_upload_size();
|
||||
$actual_size = !empty($file['tmp_name']) && is_readable($file['tmp_name']) ? (int) filesize($file['tmp_name']) : 0;
|
||||
if ((int) ($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK || $actual_size <= 0 || $actual_size > $max_size) {
|
||||
$errors->add('aksrt_avatar_size', sprintf(__('头像文件无效或超过 %s', 'aksrt'), size_format($max_size)));
|
||||
return;
|
||||
}
|
||||
|
||||
require_once ABSPATH . 'wp-admin/includes/file.php';
|
||||
$checked = wp_check_filetype_and_ext($file['tmp_name'], $file['name'], [
|
||||
'jpg|jpeg|jpe' => 'image/jpeg',
|
||||
'png' => 'image/png',
|
||||
'gif' => 'image/gif',
|
||||
'webp' => 'image/webp',
|
||||
]);
|
||||
$image_size = wp_getimagesize($file['tmp_name']);
|
||||
if (empty($checked['type']) || empty($checked['ext']) || !wp_match_mime_types('image', $checked['type']) || !$image_size || ((int) $image_size[0] * (int) $image_size[1]) > 40000000) {
|
||||
$errors->add('aksrt_avatar_type', __('头像必须是有效的 JPG、PNG、GIF 或 WebP 图片,且总像素不能超过 4000 万。', 'aksrt'));
|
||||
}
|
||||
}, 10, 2);
|
||||
|
||||
add_action('personal_options_update', 'aksrt_save_admin_avatar_field');
|
||||
add_action('edit_user_profile_update', 'aksrt_save_admin_avatar_field');
|
||||
|
||||
if (!function_exists('aksrt_save_admin_avatar_field')) {
|
||||
/**
|
||||
* Save local avatar changes from the native WordPress profile screen.
|
||||
*/
|
||||
function aksrt_save_admin_avatar_field(int $user_id): void {
|
||||
if (!current_user_can('edit_user', $user_id) || !aksrt_is_avatar_upload_enabled()) {
|
||||
return;
|
||||
}
|
||||
$nonce = isset($_POST['aksrt_admin_avatar_nonce']) ? sanitize_text_field(wp_unslash($_POST['aksrt_admin_avatar_nonce'])) : '';
|
||||
if (!wp_verify_nonce($nonce, 'aksrt_save_admin_avatar_' . $user_id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$previous_id = (int) get_user_meta($user_id, 'aksrt_avatar_attachment_id', true);
|
||||
if (!empty($_POST['aksrt_avatar_reset'])) {
|
||||
delete_user_meta($user_id, 'aksrt_avatar_attachment_id');
|
||||
aksrt_delete_owned_avatar_attachment($previous_id, $user_id);
|
||||
do_action('aksrt_user_avatar_changed', $user_id, 0, '');
|
||||
return;
|
||||
}
|
||||
|
||||
if (empty($_FILES['aksrt_avatar_file']['name'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 头像上限并入「前端图片上传限制」统一治理(参考主题同源)。
|
||||
$max_size = class_exists('\AKSRT\Attachment\UploadLimits') ? \AKSRT\Attachment\UploadLimits::limit_bytes('image') : (int) wp_max_upload_size();
|
||||
$file = $_FILES['aksrt_avatar_file'];
|
||||
$actual_size = !empty($file['tmp_name']) && is_readable($file['tmp_name']) ? (int) filesize($file['tmp_name']) : 0;
|
||||
if ((int) ($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK || $actual_size <= 0 || $actual_size > $max_size) {
|
||||
return;
|
||||
}
|
||||
|
||||
require_once ABSPATH . 'wp-admin/includes/file.php';
|
||||
$checked = wp_check_filetype_and_ext($file['tmp_name'], $file['name'], [
|
||||
'jpg|jpeg|jpe' => 'image/jpeg',
|
||||
'png' => 'image/png',
|
||||
'gif' => 'image/gif',
|
||||
'webp' => 'image/webp',
|
||||
]);
|
||||
$image_size = wp_getimagesize($file['tmp_name']);
|
||||
if (empty($checked['type']) || empty($checked['ext']) || !wp_match_mime_types('image', $checked['type']) || !$image_size || ((int) $image_size[0] * (int) $image_size[1]) > 40000000) {
|
||||
return;
|
||||
}
|
||||
|
||||
require_once ABSPATH . 'wp-admin/includes/media.php';
|
||||
require_once ABSPATH . 'wp-admin/includes/image.php';
|
||||
$attachment_id = media_handle_upload('aksrt_avatar_file', 0, [], [
|
||||
'test_form' => false,
|
||||
'mimes' => [
|
||||
'jpg|jpeg|jpe' => 'image/jpeg',
|
||||
'png' => 'image/png',
|
||||
'gif' => 'image/gif',
|
||||
'webp' => 'image/webp',
|
||||
],
|
||||
]);
|
||||
if (is_wp_error($attachment_id) || !wp_attachment_is_image((int) $attachment_id)) {
|
||||
if (!is_wp_error($attachment_id)) {
|
||||
wp_delete_attachment((int) $attachment_id, true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$attachment_id = (int) $attachment_id;
|
||||
wp_update_post(['ID' => $attachment_id, 'post_author' => $user_id]);
|
||||
update_post_meta($attachment_id, '_aksrt_profile_media_user_id', $user_id);
|
||||
update_post_meta($attachment_id, '_aksrt_profile_media_type', 'avatar');
|
||||
update_user_meta($user_id, 'aksrt_avatar_attachment_id', $attachment_id);
|
||||
aksrt_delete_owned_avatar_attachment($previous_id, $user_id);
|
||||
do_action('aksrt_user_avatar_changed', $user_id, $attachment_id, (string) wp_get_attachment_image_url($attachment_id, 'thumbnail'));
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_delete_owned_avatar_attachment')) {
|
||||
/**
|
||||
* Delete an avatar attachment only when it belongs to the target user.
|
||||
|
||||
@@ -12,7 +12,7 @@ if (!function_exists('aksrt_get_user_profile_details')) {
|
||||
* Read the fixed profile details. Social fields are deliberately kept in one
|
||||
* structured meta value so public rendering can filter them server-side.
|
||||
*
|
||||
* @return array{privacy:string,gender:string,website:string,qq:string,wechat:string,github:string}
|
||||
* @return array{privacy:string,gender:string,website:string,qq:string,wechat:string,weibo:string,github:string,address:string}
|
||||
*/
|
||||
function aksrt_get_user_profile_details(int $user_id): array {
|
||||
$raw = get_user_meta($user_id, 'aksrt_profile_details', true);
|
||||
@@ -32,13 +32,15 @@ if (!function_exists('aksrt_get_user_profile_details')) {
|
||||
'website' => esc_url_raw((string) ($raw['website'] ?? '')),
|
||||
'qq' => sanitize_text_field((string) ($raw['qq'] ?? '')),
|
||||
'wechat' => sanitize_text_field((string) ($raw['wechat'] ?? '')),
|
||||
'weibo' => esc_url_raw((string) ($raw['weibo'] ?? '')),
|
||||
'github' => esc_url_raw((string) ($raw['github'] ?? '')),
|
||||
'address' => sanitize_text_field((string) ($raw['address'] ?? '')),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_get_visible_user_profile_details')) {
|
||||
/** @return array{gender:string,website:string,qq:string,wechat:string,github:string} */
|
||||
/** @return array{gender:string,website:string,qq:string,wechat:string,weibo:string,github:string,address:string} */
|
||||
function aksrt_get_visible_user_profile_details(int $user_id, int $viewer_id = 0): array {
|
||||
$details = aksrt_get_user_profile_details($user_id);
|
||||
$visible = $viewer_id === $user_id || $details['privacy'] === 'public' || ($details['privacy'] === 'members' && $viewer_id > 0);
|
||||
@@ -47,7 +49,9 @@ if (!function_exists('aksrt_get_visible_user_profile_details')) {
|
||||
'website' => $visible ? $details['website'] : '',
|
||||
'qq' => $visible ? $details['qq'] : '',
|
||||
'wechat' => $visible ? $details['wechat'] : '',
|
||||
'weibo' => $visible ? $details['weibo'] : '',
|
||||
'github' => $visible ? $details['github'] : '',
|
||||
'address' => $visible ? $details['address'] : '',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -86,28 +90,32 @@ if (!function_exists('aksrt_save_user_center_profile')) {
|
||||
}
|
||||
}
|
||||
|
||||
$privacy = isset($input['privacy']) ? sanitize_key(wp_unslash($input['privacy'])) : 'private';
|
||||
$existing_details = aksrt_get_user_profile_details($user_id);
|
||||
$privacy = isset($input['privacy']) ? sanitize_key(wp_unslash($input['privacy'])) : $existing_details['privacy'];
|
||||
if (!in_array($privacy, ['private', 'public', 'members'], true)) {
|
||||
$errors[] = __('无效的隐私设置。', 'aksrt');
|
||||
}
|
||||
$gender = isset($input['gender']) ? sanitize_key(wp_unslash($input['gender'])) : 'secret';
|
||||
$gender = isset($input['gender']) ? sanitize_key(wp_unslash($input['gender'])) : $existing_details['gender'];
|
||||
if (!in_array($gender, ['secret', 'male', 'female'], true)) {
|
||||
$errors[] = __('无效的性别设置。', 'aksrt');
|
||||
}
|
||||
$details = [
|
||||
'privacy' => $privacy,
|
||||
'gender' => $gender,
|
||||
'website' => isset($input['website']) ? esc_url_raw(wp_unslash($input['website'])) : '',
|
||||
'qq' => isset($input['qq']) ? sanitize_text_field(wp_unslash($input['qq'])) : '',
|
||||
'wechat' => isset($input['wechat']) ? sanitize_text_field(wp_unslash($input['wechat'])) : '',
|
||||
'github' => isset($input['github']) ? esc_url_raw(wp_unslash($input['github'])) : '',
|
||||
'website' => isset($input['website']) ? esc_url_raw(wp_unslash($input['website'])) : $existing_details['website'],
|
||||
'qq' => isset($input['qq']) ? sanitize_text_field(wp_unslash($input['qq'])) : $existing_details['qq'],
|
||||
'wechat' => isset($input['wechat']) ? sanitize_text_field(wp_unslash($input['wechat'])) : $existing_details['wechat'],
|
||||
'weibo' => isset($input['weibo']) ? esc_url_raw(wp_unslash($input['weibo'])) : $existing_details['weibo'],
|
||||
'github' => isset($input['github']) ? esc_url_raw(wp_unslash($input['github'])) : $existing_details['github'],
|
||||
'address' => isset($input['address']) ? sanitize_text_field(wp_unslash($input['address'])) : $existing_details['address'],
|
||||
];
|
||||
foreach (['website', 'github'] as $field) {
|
||||
foreach (['website', 'weibo', 'github'] as $field) {
|
||||
if (!empty($input[$field]) && $details[$field] === '') {
|
||||
$errors[] = sprintf(__('无效的%s链接。', 'aksrt'), $field === 'website' ? __('个人网站', 'aksrt') : 'GitHub');
|
||||
$field_label = ['website' => __('个人网站', 'aksrt'), 'weibo' => __('微博', 'aksrt'), 'github' => 'GitHub'][$field];
|
||||
$errors[] = sprintf(__('无效的%s链接。', 'aksrt'), $field_label);
|
||||
}
|
||||
}
|
||||
if (mb_strlen($details['qq']) > 50 || mb_strlen($details['wechat']) > 100) {
|
||||
if (mb_strlen($details['qq']) > 50 || mb_strlen($details['wechat']) > 100 || mb_strlen($details['address']) > 200) {
|
||||
$errors[] = __('社交资料长度超过限制。', 'aksrt');
|
||||
}
|
||||
if ($errors) {
|
||||
|
||||
@@ -66,7 +66,7 @@ function aksrt_get_user_dashboard_view(int $user_id, array $args = []): array {
|
||||
|
||||
$profile_details = function_exists('aksrt_get_visible_user_profile_details')
|
||||
? aksrt_get_visible_user_profile_details($user_id, $viewer_id)
|
||||
: ['gender' => '', 'website' => '', 'qq' => '', 'wechat' => '', 'github' => ''];
|
||||
: ['gender' => '', 'website' => '', 'qq' => '', 'wechat' => '', 'weibo' => '', 'github' => '', 'address' => ''];
|
||||
|
||||
$background_url = function_exists('aksrt_get_user_cover_url') ? aksrt_get_user_cover_url($user_id) : '';
|
||||
|
||||
|
||||
@@ -55,7 +55,9 @@ $profile_details = isset($profile_details) && is_array($profile_details) ? $prof
|
||||
<label><span><?php esc_html_e('个人网站', 'aksrt'); ?></span><input type="url" name="website" class="profile-input" value="<?php echo esc_attr($profile_details['website']); ?>" placeholder="https://"></label>
|
||||
<label><span>QQ</span><input type="text" name="qq" class="profile-input" maxlength="50" value="<?php echo esc_attr($profile_details['qq']); ?>"></label>
|
||||
<label><span><?php esc_html_e('微信', 'aksrt'); ?></span><input type="text" name="wechat" class="profile-input" maxlength="100" value="<?php echo esc_attr($profile_details['wechat']); ?>"></label>
|
||||
<label><span><?php esc_html_e('微博', 'aksrt'); ?></span><input type="url" name="weibo" class="profile-input" value="<?php echo esc_attr($profile_details['weibo']); ?>" placeholder="https://"></label>
|
||||
<label><span>GitHub</span><input type="url" name="github" class="profile-input" value="<?php echo esc_attr($profile_details['github']); ?>" placeholder="https://github.com/"></label>
|
||||
<label><span><?php esc_html_e('住址', 'aksrt'); ?></span><input type="text" name="address" class="profile-input" maxlength="200" value="<?php echo esc_attr($profile_details['address']); ?>"></label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ $account_user = isset($target_user) && $target_user instanceof WP_User
|
||||
: wp_get_current_user();
|
||||
$account_user_id = $account_user instanceof WP_User ? (int) $account_user->ID : (int) ($user_id ?? 0);
|
||||
$account_email = $account_user instanceof WP_User ? (string) $account_user->user_email : '';
|
||||
$account_phone = $account_user_id > 0 ? (string) get_user_meta($account_user_id, 'aksrt_phone', true) : '';
|
||||
$account_phone = $account_user_id > 0 ? (string) get_user_meta($account_user_id, 'phone_number', true) : '';
|
||||
$user_settings = class_exists('\\AKSRT\\Settings\\Settings') ? \AKSRT\Settings\Settings::get_all() : [];
|
||||
$sms_settings = class_exists('\\AKSRT\\Sms\\SMS') ? \AKSRT\Sms\SMS::get_instance()->get_provider_config() : [];
|
||||
$phone_available = !empty($user_settings['bind_phone']) && (($sms_settings['sms_sdk'] ?? 'null') !== 'null');
|
||||
|
||||
@@ -131,7 +131,7 @@ trait UserControllerAuthPhoneOperations {
|
||||
private function auth_find_user(string $target, string $channel) {
|
||||
if ($channel === 'phone') {
|
||||
$users = get_users([
|
||||
'meta_key' => 'aksrt_phone',
|
||||
'meta_key' => 'phone_number',
|
||||
'meta_value' => $target,
|
||||
'number' => 1,
|
||||
]);
|
||||
|
||||
@@ -50,7 +50,7 @@ final class Service {
|
||||
&& \AKSRT\Settings\Settings::signin_phone_enabled()
|
||||
) {
|
||||
$users = get_users([
|
||||
'meta_key' => 'aksrt_phone',
|
||||
'meta_key' => 'phone_number',
|
||||
'meta_value' => $login,
|
||||
'number' => 1,
|
||||
]);
|
||||
|
||||
@@ -127,7 +127,7 @@ trait ServicePart1b {
|
||||
}
|
||||
|
||||
// 绑定手机号
|
||||
update_user_meta($user_id, 'aksrt_phone', $phone);
|
||||
update_user_meta($user_id, 'phone_number', $phone);
|
||||
update_user_meta($user_id, 'aksrt_phone_verified', '1');
|
||||
|
||||
// 自动登录
|
||||
@@ -173,21 +173,23 @@ trait ServicePart1b {
|
||||
return ['success' => false, 'error' => __('验证码错误或已过期', 'aksrt')];
|
||||
}
|
||||
|
||||
// 检查手机号是否已被其他用户绑定
|
||||
$user_id = get_current_user_id();
|
||||
$existing = self::get_user_by_phone($phone);
|
||||
if ($existing && (int) $existing->ID !== get_current_user_id()) {
|
||||
if ($existing && (int) $existing->ID !== $user_id) {
|
||||
return ['success' => false, 'error' => __('该手机号已被其他账号绑定', 'aksrt')];
|
||||
}
|
||||
if ($existing && (int) $existing->ID === $user_id) {
|
||||
return ['success' => false, 'error' => __('手机号不能与当前绑定号码相同', 'aksrt')];
|
||||
}
|
||||
|
||||
$user_id = get_current_user_id();
|
||||
update_user_meta($user_id, 'aksrt_phone', $phone);
|
||||
$old_phone = (string) get_user_meta($user_id, 'phone_number', true);
|
||||
update_user_meta($user_id, 'phone_number', $phone);
|
||||
update_user_meta($user_id, 'aksrt_phone_verified', '1');
|
||||
do_action('aksrt_user_phone_changed', $user_id, $phone);
|
||||
do_action('aksrt_user_phone_changed', $user_id, $phone, $old_phone);
|
||||
|
||||
return [
|
||||
|
||||
'success' => true,
|
||||
'message' => __('手机号绑定成功', 'aksrt'),
|
||||
'message' => $old_phone !== '' ? __('手机号修改成功', 'aksrt') : __('手机号绑定成功', 'aksrt'),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -196,7 +198,7 @@ trait ServicePart1b {
|
||||
*/
|
||||
private static function get_user_by_phone(string $phone): ?\WP_User {
|
||||
$users = get_users([
|
||||
'meta_key' => 'aksrt_phone',
|
||||
'meta_key' => 'phone_number',
|
||||
'meta_value' => $phone,
|
||||
'number' => 1,
|
||||
'fields' => 'all',
|
||||
|
||||
@@ -0,0 +1,282 @@
|
||||
<?php
|
||||
/** Native WordPress profile fields for AKSRT user data. */
|
||||
|
||||
namespace AKSRT\User;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
final class ProfileAdmin {
|
||||
|
||||
private const PROFILE_NONCE = 'aksrt_profile_admin_nonce';
|
||||
private const PHONE_META = 'phone_number';
|
||||
|
||||
public static function init(): void {
|
||||
if (!is_admin()) {
|
||||
return;
|
||||
}
|
||||
add_action('show_user_profile', [self::class, 'render']);
|
||||
add_action('edit_user_profile', [self::class, 'render']);
|
||||
add_action('personal_options_update', [self::class, 'save']);
|
||||
add_action('edit_user_profile_update', [self::class, 'save']);
|
||||
add_filter('user_profile_update_errors', [self::class, 'validate'], 10, 3);
|
||||
add_action('admin_footer-user-edit.php', [self::class, 'unlock_script']);
|
||||
add_action('admin_footer-profile.php', [self::class, 'unlock_script']);
|
||||
}
|
||||
|
||||
public static function render(\WP_User $user): void {
|
||||
if (!current_user_can('edit_user', (int) $user->ID)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$user_id = (int) $user->ID;
|
||||
$details = function_exists('aksrt_get_user_profile_details')
|
||||
? aksrt_get_user_profile_details($user_id)
|
||||
: ['gender' => 'secret', 'website' => '', 'qq' => '', 'wechat' => '', 'weibo' => '', 'github' => '', 'address' => ''];
|
||||
$avatar_url = function_exists('aksrt_get_custom_avatar_url') ? aksrt_get_custom_avatar_url($user_id, 96) : '';
|
||||
$cover_id = (int) get_user_meta($user_id, 'aksrt_background_attachment_id', true);
|
||||
$cover_url = esc_url_raw((string) get_user_meta($user_id, 'aksrt_background_image', true));
|
||||
if ($cover_url === '' && $cover_id > 0) $cover_url = (string) wp_get_attachment_image_url($cover_id, 'full');
|
||||
$phone = (string) get_user_meta($user_id, self::PHONE_META, true);
|
||||
?>
|
||||
<h2 id="aksrt-more-profile-data"><?php esc_html_e('更多资料(AKSRT)', 'aksrt'); ?></h2>
|
||||
<table class="form-table" role="presentation">
|
||||
<?php if (!function_exists('aksrt_is_avatar_upload_enabled') || aksrt_is_avatar_upload_enabled()) : ?><tr>
|
||||
<th scope="row"><label for="aksrt_avatar_file"><?php esc_html_e('自定义头像', 'aksrt'); ?></label></th>
|
||||
<td>
|
||||
<?php echo get_avatar($user_id, 96, '', $user->display_name, ['loading' => 'eager']); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
<p><input id="aksrt_avatar_file" type="file" name="aksrt_avatar_file" accept="image/jpeg,image/png,image/webp,image/gif"></p>
|
||||
<?php if ($avatar_url !== '') : ?><label><input type="checkbox" name="aksrt_avatar_reset" value="1"> <?php esc_html_e('恢复项目默认头像并删除当前头像文件', 'aksrt'); ?></label><?php endif; ?>
|
||||
<p class="description"><?php esc_html_e('支持 JPG、PNG、GIF、WebP;大小上限由“权限 → 附件权限”控制。', 'aksrt'); ?></p>
|
||||
<?php wp_nonce_field('aksrt_save_admin_avatar_' . $user_id, 'aksrt_admin_avatar_nonce'); ?>
|
||||
</td>
|
||||
</tr><?php endif; ?>
|
||||
<?php if (!function_exists('aksrt_is_user_cover_upload_enabled') || aksrt_is_user_cover_upload_enabled()) : ?>
|
||||
<tr>
|
||||
<th scope="row"><label for="aksrt_cover_file"><?php esc_html_e('自定义封面', 'aksrt'); ?></label></th>
|
||||
<td>
|
||||
<?php if ($cover_url !== '') : ?><img src="<?php echo esc_url($cover_url); ?>" alt="" style="display:block;max-width:360px;height:auto;margin-bottom:8px;border-radius:4px;"><?php endif; ?>
|
||||
<p><input id="aksrt_cover_file" type="file" name="aksrt_cover_file" accept="image/jpeg,image/png,image/webp,image/gif"></p>
|
||||
<?php if ($cover_url !== '') : ?><label><input type="checkbox" name="aksrt_background_reset" value="1"> <?php esc_html_e('恢复默认封面并删除当前封面文件', 'aksrt'); ?></label><?php endif; ?>
|
||||
<p class="description"><?php esc_html_e('用户主页资料卡使用的背景图。', 'aksrt'); ?></p>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
<tr>
|
||||
<th scope="row"><label for="aksrt_profile_gender"><?php esc_html_e('性别', 'aksrt'); ?></label></th>
|
||||
<td><select id="aksrt_profile_gender" name="aksrt_profile_gender"><option value="secret" <?php selected($details['gender'], 'secret'); ?>><?php esc_html_e('保密', 'aksrt'); ?></option><option value="male" <?php selected($details['gender'], 'male'); ?>><?php esc_html_e('男', 'aksrt'); ?></option><option value="female" <?php selected($details['gender'], 'female'); ?>><?php esc_html_e('女', 'aksrt'); ?></option></select></td>
|
||||
</tr>
|
||||
<tr><th scope="row"><label for="aksrt_profile_qq">QQ</label></th><td><input id="aksrt_profile_qq" class="regular-text" type="text" name="aksrt_profile_qq" maxlength="50" value="<?php echo esc_attr($details['qq']); ?>"></td></tr>
|
||||
<tr><th scope="row"><label for="aksrt_profile_wechat"><?php esc_html_e('微信', 'aksrt'); ?></label></th><td><input id="aksrt_profile_wechat" class="regular-text" type="text" name="aksrt_profile_wechat" maxlength="100" value="<?php echo esc_attr($details['wechat']); ?>"></td></tr>
|
||||
<tr><th scope="row"><label for="aksrt_profile_weibo"><?php esc_html_e('微博', 'aksrt'); ?></label></th><td><input id="aksrt_profile_weibo" class="regular-text" type="url" name="aksrt_profile_weibo" value="<?php echo esc_attr($details['weibo']); ?>" placeholder="https://"></td></tr>
|
||||
<tr><th scope="row"><label for="aksrt_profile_github">GitHub</label></th><td><input id="aksrt_profile_github" class="regular-text" type="url" name="aksrt_profile_github" value="<?php echo esc_attr($details['github']); ?>" placeholder="https://github.com/"></td></tr>
|
||||
<tr><th scope="row"><label for="aksrt_profile_website"><?php esc_html_e('个人网站', 'aksrt'); ?></label></th><td><input id="aksrt_profile_website" class="regular-text" type="url" name="aksrt_profile_website" value="<?php echo esc_attr($details['website']); ?>" placeholder="https://"></td></tr>
|
||||
<tr><th scope="row"><label for="aksrt_profile_address"><?php esc_html_e('住址', 'aksrt'); ?></label></th><td><input id="aksrt_profile_address" class="regular-text" type="text" name="aksrt_profile_address" maxlength="200" value="<?php echo esc_attr($details['address']); ?>"></td></tr>
|
||||
<tr>
|
||||
<th scope="row"><label for="user_phone_number"><?php esc_html_e('绑定手机号', 'aksrt'); ?></label></th>
|
||||
<td>
|
||||
<input id="user_phone_number" class="regular-text" type="text" name="phone_number" value="<?php echo esc_attr($phone); ?>" readonly>
|
||||
<input type="hidden" name="aksrt_phone_edit_unlocked" id="aksrt_phone_edit_unlocked" value="0">
|
||||
<p class="description"><?php esc_html_e('手机号涉及登录功能,默认只读。管理员确认后可直接修改。', 'aksrt'); ?></p>
|
||||
<button type="button" class="button" data-aksrt-unlock-phone><?php esc_html_e('修改用户手机号', 'aksrt'); ?></button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<?php wp_nonce_field(self::PROFILE_NONCE . '_' . $user_id, self::PROFILE_NONCE); ?>
|
||||
<?php
|
||||
}
|
||||
|
||||
public static function validate(\WP_Error $errors, bool $update, ?\WP_User $user = null): void {
|
||||
$user_id = $user instanceof \WP_User ? (int) $user->ID : (int) ($_POST['user_id'] ?? 0);
|
||||
if ($user_id <= 0 || !current_user_can('edit_user', $user_id) || empty($_POST[self::PROFILE_NONCE])) {
|
||||
return;
|
||||
}
|
||||
$nonce = sanitize_text_field(wp_unslash($_POST[self::PROFILE_NONCE]));
|
||||
if (!wp_verify_nonce($nonce, self::PROFILE_NONCE . '_' . $user_id)) {
|
||||
$errors->add('aksrt_profile_nonce', __('更多资料安全验证失败,请刷新页面重试。', 'aksrt'));
|
||||
return;
|
||||
}
|
||||
$phone = trim(sanitize_text_field(wp_unslash($_POST['phone_number'] ?? '')));
|
||||
$old_phone = (string) get_user_meta($user_id, self::PHONE_META, true);
|
||||
$unlocked = !empty($_POST['aksrt_phone_edit_unlocked']);
|
||||
if ($phone !== '' && $phone !== $old_phone && $unlocked) {
|
||||
if (!class_exists('AKSRT\\Sms\\SMS') || !\AKSRT\Sms\SMS::is_phonenumber($phone)) {
|
||||
$errors->add('aksrt_profile_phone', __('绑定手机号格式不正确。', 'aksrt'));
|
||||
} else {
|
||||
$users = get_users(['meta_key' => self::PHONE_META, 'meta_value' => $phone, 'exclude' => [$user_id], 'number' => 1, 'fields' => 'ids']);
|
||||
if (!empty($users)) {
|
||||
$errors->add('aksrt_profile_phone_exists', __('该手机号已被其他账号绑定。', 'aksrt'));
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (['aksrt_profile_weibo' => '微博', 'aksrt_profile_github' => 'GitHub', 'aksrt_profile_website' => '个人网站'] as $key => $label) {
|
||||
$value = trim((string) wp_unslash($_POST[$key] ?? ''));
|
||||
if ($value !== '' && !esc_url_raw($value)) {
|
||||
$errors->add('aksrt_profile_' . sanitize_key($label), sprintf(__('%s地址格式不正确。', 'aksrt'), $label));
|
||||
}
|
||||
}
|
||||
$gender = sanitize_key(wp_unslash($_POST['aksrt_profile_gender'] ?? 'secret'));
|
||||
if (!in_array($gender, ['secret', 'male', 'female'], true)) {
|
||||
$errors->add('aksrt_profile_gender', __('性别选项无效。', 'aksrt'));
|
||||
}
|
||||
$details_limits = ['aksrt_profile_qq' => 50, 'aksrt_profile_wechat' => 100, 'aksrt_profile_address' => 200];
|
||||
foreach ($details_limits as $key => $limit) {
|
||||
if (mb_strlen((string) wp_unslash($_POST[$key] ?? '')) > $limit) {
|
||||
$errors->add('aksrt_profile_' . $key, sprintf(__('资料字段不能超过 %d 个字符。', 'aksrt'), $limit));
|
||||
}
|
||||
}
|
||||
if (!function_exists('aksrt_is_avatar_upload_enabled') || aksrt_is_avatar_upload_enabled()) {
|
||||
self::validate_image_upload($errors, 'aksrt_avatar_file', __('头像', 'aksrt'), $user_id);
|
||||
}
|
||||
if (!function_exists('aksrt_is_user_cover_upload_enabled') || aksrt_is_user_cover_upload_enabled()) {
|
||||
self::validate_image_upload($errors, 'aksrt_cover_file', __('封面', 'aksrt'), $user_id);
|
||||
}
|
||||
}
|
||||
|
||||
public static function save(int $user_id): void {
|
||||
if ($user_id <= 0 || !current_user_can('edit_user', $user_id)) {
|
||||
return;
|
||||
}
|
||||
$nonce = sanitize_text_field(wp_unslash($_POST[self::PROFILE_NONCE] ?? ''));
|
||||
if (!wp_verify_nonce($nonce, self::PROFILE_NONCE . '_' . $user_id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$details = function_exists('aksrt_get_user_profile_details') ? aksrt_get_user_profile_details($user_id) : [];
|
||||
if (isset($_POST['aksrt_profile_gender'])) $details['gender'] = sanitize_key(wp_unslash($_POST['aksrt_profile_gender']));
|
||||
if (isset($_POST['aksrt_profile_qq'])) $details['qq'] = sanitize_text_field(wp_unslash($_POST['aksrt_profile_qq']));
|
||||
if (isset($_POST['aksrt_profile_wechat'])) $details['wechat'] = sanitize_text_field(wp_unslash($_POST['aksrt_profile_wechat']));
|
||||
if (isset($_POST['aksrt_profile_weibo'])) $details['weibo'] = esc_url_raw(wp_unslash($_POST['aksrt_profile_weibo']));
|
||||
if (isset($_POST['aksrt_profile_github'])) $details['github'] = esc_url_raw(wp_unslash($_POST['aksrt_profile_github']));
|
||||
if (isset($_POST['aksrt_profile_website'])) $details['website'] = esc_url_raw(wp_unslash($_POST['aksrt_profile_website']));
|
||||
if (isset($_POST['aksrt_profile_address'])) $details['address'] = sanitize_text_field(wp_unslash($_POST['aksrt_profile_address']));
|
||||
if (function_exists('aksrt_get_user_profile_details')) update_user_meta($user_id, 'aksrt_profile_details', $details);
|
||||
|
||||
$old_phone = (string) get_user_meta($user_id, self::PHONE_META, true);
|
||||
$new_phone = trim(sanitize_text_field(wp_unslash($_POST['phone_number'] ?? '')));
|
||||
if (!empty($_POST['aksrt_phone_edit_unlocked']) && $new_phone !== '' && $new_phone !== $old_phone && class_exists('AKSRT\\Sms\\SMS') && \AKSRT\Sms\SMS::is_phonenumber($new_phone)) {
|
||||
$users = get_users(['meta_key' => self::PHONE_META, 'meta_value' => $new_phone, 'exclude' => [$user_id], 'number' => 1, 'fields' => 'ids']);
|
||||
if (empty($users)) {
|
||||
update_user_meta($user_id, self::PHONE_META, $new_phone);
|
||||
update_user_meta($user_id, 'aksrt_phone_verified', '1');
|
||||
do_action('aksrt_user_phone_changed', $user_id, $new_phone, $old_phone);
|
||||
}
|
||||
}
|
||||
|
||||
self::save_avatar($user_id);
|
||||
self::save_cover($user_id);
|
||||
}
|
||||
|
||||
private static function validate_image_upload(\WP_Error $errors, string $field, string $label, int $user_id): void {
|
||||
if (empty($_FILES[$field]['name'])) return;
|
||||
$file = $_FILES[$field];
|
||||
$max_size = class_exists('AKSRT\\Attachment\\UploadLimits')
|
||||
? \AKSRT\Attachment\UploadLimits::limit_bytes('image', $user_id)
|
||||
: (int) wp_max_upload_size();
|
||||
$actual_size = !empty($file['tmp_name']) && is_readable($file['tmp_name']) ? (int) filesize($file['tmp_name']) : 0;
|
||||
if ((int) ($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK || $actual_size <= 0 || $actual_size > $max_size) {
|
||||
$errors->add('aksrt_profile_' . sanitize_key($field) . '_size', sprintf(__('%1$s文件无效或超过 %2$s。', 'aksrt'), $label, size_format($max_size)));
|
||||
return;
|
||||
}
|
||||
require_once ABSPATH . 'wp-admin/includes/file.php';
|
||||
$checked = wp_check_filetype_and_ext($file['tmp_name'], $file['name'], ['jpg|jpeg|jpe' => 'image/jpeg', 'png' => 'image/png', 'gif' => 'image/gif', 'webp' => 'image/webp']);
|
||||
$image_size = wp_getimagesize($file['tmp_name']);
|
||||
if (empty($checked['type']) || empty($checked['ext']) || !wp_match_mime_types('image', $checked['type']) || !$image_size || ((int) $image_size[0] * (int) $image_size[1]) > 40000000) {
|
||||
$errors->add('aksrt_profile_' . sanitize_key($field) . '_type', sprintf(__('%s必须是有效的 JPG、PNG、GIF 或 WebP 图片,且总像素不能超过 4000 万。', 'aksrt'), $label));
|
||||
}
|
||||
}
|
||||
|
||||
private static function save_avatar(int $user_id): void {
|
||||
if (function_exists('aksrt_is_avatar_upload_enabled') && !aksrt_is_avatar_upload_enabled()) return;
|
||||
$nonce = sanitize_text_field(wp_unslash($_POST['aksrt_admin_avatar_nonce'] ?? ''));
|
||||
if (!wp_verify_nonce($nonce, 'aksrt_save_admin_avatar_' . $user_id)) return;
|
||||
$previous_id = (int) get_user_meta($user_id, 'aksrt_avatar_attachment_id', true);
|
||||
if (!empty($_POST['aksrt_avatar_reset'])) {
|
||||
delete_user_meta($user_id, 'aksrt_avatar_attachment_id');
|
||||
if (function_exists('aksrt_delete_owned_avatar_attachment')) aksrt_delete_owned_avatar_attachment($previous_id, $user_id);
|
||||
do_action('aksrt_user_avatar_changed', $user_id, 0, '');
|
||||
return;
|
||||
}
|
||||
if (empty($_FILES['aksrt_avatar_file']['name'])) return;
|
||||
require_once ABSPATH . 'wp-admin/includes/file.php';
|
||||
require_once ABSPATH . 'wp-admin/includes/media.php';
|
||||
require_once ABSPATH . 'wp-admin/includes/image.php';
|
||||
$attachment_id = media_handle_upload('aksrt_avatar_file', 0, [], ['test_form' => false, 'mimes' => ['jpg|jpeg|jpe' => 'image/jpeg', 'png' => 'image/png', 'gif' => 'image/gif', 'webp' => 'image/webp']]);
|
||||
if (is_wp_error($attachment_id) || !wp_attachment_is_image((int) $attachment_id)) {
|
||||
if (!is_wp_error($attachment_id)) wp_delete_attachment((int) $attachment_id, true);
|
||||
return;
|
||||
}
|
||||
$attachment_id = (int) $attachment_id;
|
||||
wp_update_post(['ID' => $attachment_id, 'post_author' => $user_id]);
|
||||
update_post_meta($attachment_id, '_aksrt_profile_media_user_id', $user_id);
|
||||
update_post_meta($attachment_id, '_aksrt_profile_media_type', 'avatar');
|
||||
update_user_meta($user_id, 'aksrt_avatar_attachment_id', $attachment_id);
|
||||
if (function_exists('aksrt_delete_owned_avatar_attachment')) aksrt_delete_owned_avatar_attachment($previous_id, $user_id);
|
||||
do_action('aksrt_user_avatar_changed', $user_id, $attachment_id, (string) wp_get_attachment_image_url($attachment_id, 'thumbnail'));
|
||||
}
|
||||
|
||||
private static function save_cover(int $user_id): void {
|
||||
if (function_exists('aksrt_is_user_cover_upload_enabled') && !aksrt_is_user_cover_upload_enabled()) return;
|
||||
$old_id = (int) get_user_meta($user_id, 'aksrt_background_attachment_id', true);
|
||||
if (!empty($_POST['aksrt_background_reset'])) {
|
||||
delete_user_meta($user_id, 'aksrt_background_attachment_id');
|
||||
delete_user_meta($user_id, 'aksrt_background_image');
|
||||
self::delete_attachment($old_id, $user_id);
|
||||
return;
|
||||
}
|
||||
if (empty($_FILES['aksrt_cover_file']['name'])) return;
|
||||
$file = $_FILES['aksrt_cover_file'];
|
||||
$max_size = class_exists('AKSRT\\Attachment\\UploadLimits') ? \AKSRT\Attachment\UploadLimits::limit_bytes('image', $user_id) : (int) wp_max_upload_size();
|
||||
if (($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK || empty($file['tmp_name']) || filesize($file['tmp_name']) > $max_size) return;
|
||||
require_once ABSPATH . 'wp-admin/includes/file.php';
|
||||
require_once ABSPATH . 'wp-admin/includes/media.php';
|
||||
require_once ABSPATH . 'wp-admin/includes/image.php';
|
||||
$checked = wp_check_filetype_and_ext($file['tmp_name'], $file['name'], ['jpg|jpeg|jpe' => 'image/jpeg', 'png' => 'image/png', 'gif' => 'image/gif', 'webp' => 'image/webp']);
|
||||
$image_size = wp_getimagesize($file['tmp_name']);
|
||||
if (empty($checked['type']) || empty($checked['ext']) || !$image_size || ((int) $image_size[0] * (int) $image_size[1]) > 40000000) return;
|
||||
$attachment_id = media_handle_upload('aksrt_cover_file', 0, [], ['test_form' => false, 'mimes' => ['jpg|jpeg|jpe' => 'image/jpeg', 'png' => 'image/png', 'gif' => 'image/gif', 'webp' => 'image/webp']]);
|
||||
if (is_wp_error($attachment_id) || !wp_attachment_is_image((int) $attachment_id)) {
|
||||
if (!is_wp_error($attachment_id)) wp_delete_attachment((int) $attachment_id, true);
|
||||
return;
|
||||
}
|
||||
$attachment_id = (int) $attachment_id;
|
||||
wp_update_post(['ID' => $attachment_id, 'post_author' => $user_id]);
|
||||
update_post_meta($attachment_id, '_aksrt_profile_media_user_id', $user_id);
|
||||
update_post_meta($attachment_id, '_aksrt_profile_media_type', 'background');
|
||||
$url = wp_get_attachment_image_url($attachment_id, 'full');
|
||||
if (!$url) {
|
||||
wp_delete_attachment($attachment_id, true);
|
||||
return;
|
||||
}
|
||||
update_user_meta($user_id, 'aksrt_background_attachment_id', $attachment_id);
|
||||
update_user_meta($user_id, 'aksrt_background_image', esc_url_raw($url));
|
||||
self::delete_attachment($old_id, $user_id);
|
||||
}
|
||||
|
||||
private static function delete_attachment(int $attachment_id, int $user_id): void {
|
||||
$attachment = $attachment_id > 0 ? get_post($attachment_id) : null;
|
||||
$managed_user_id = $attachment_id > 0 ? (int) get_post_meta($attachment_id, '_aksrt_profile_media_user_id', true) : 0;
|
||||
$managed_type = $attachment_id > 0 ? (string) get_post_meta($attachment_id, '_aksrt_profile_media_type', true) : '';
|
||||
if ($attachment instanceof \WP_Post && $attachment->post_type === 'attachment' && (int) $attachment->post_author === $user_id && $managed_user_id === $user_id && $managed_type === 'background') {
|
||||
wp_delete_attachment($attachment_id, true);
|
||||
}
|
||||
}
|
||||
|
||||
public static function unlock_script(): void {
|
||||
if (!get_current_screen() || !in_array(get_current_screen()->base, ['profile', 'user-edit'], true)) return;
|
||||
?>
|
||||
<script>
|
||||
document.addEventListener('click', function (event) {
|
||||
var button = event.target.closest('[data-aksrt-unlock-phone]');
|
||||
if (!button) return;
|
||||
var field = document.getElementById('user_phone_number');
|
||||
var flag = document.getElementById('aksrt_phone_edit_unlocked');
|
||||
if (!field || !flag) return;
|
||||
field.removeAttribute('readonly');
|
||||
field.focus();
|
||||
flag.value = '1';
|
||||
button.disabled = true;
|
||||
});
|
||||
</script>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,7 @@ $profile_details = is_array($dashboard['profile_details'] ?? null) ? $dashboard[
|
||||
$registered_time = get_date_from_gmt((string) $user->user_registered, 'Y-m-d H:i:s');
|
||||
$last_login_ts = (int) get_user_meta($user_id, 'aksrt_last_login', true);
|
||||
$last_login_time = $last_login_ts > 0 ? wp_date('Y-m-d H:i:s', $last_login_ts) : '';
|
||||
$has_more_details = $registered_time !== '' || $last_login_time !== '' || !empty($profile_details['website']) || !empty($profile_details['qq']) || !empty($profile_details['wechat']) || !empty($profile_details['github']);
|
||||
$has_more_details = $registered_time !== '' || $last_login_time !== '' || !empty($profile_details['website']) || !empty($profile_details['qq']) || !empty($profile_details['wechat']) || !empty($profile_details['weibo']) || !empty($profile_details['github']) || !empty($profile_details['address']);
|
||||
$signals = $dashboard['profile_signals'] ?? [];
|
||||
$show_report = !empty($dashboard['show_report']);
|
||||
$bg_url = function_exists('aksrt_get_user_cover_url') ? aksrt_get_user_cover_url($user_id) : '';
|
||||
@@ -214,11 +214,13 @@ $show_cover_menu = $has_more_details || $show_cover_report || $cover_ban_html !=
|
||||
<dl>
|
||||
<?php $verify_state = is_array($dashboard['verify_state'] ?? null) ? $dashboard['verify_state'] : []; ?>
|
||||
<?php if (($verify_state['state'] ?? '') !== '' && ($verify_state['label'] ?? '') !== '') : ?><div><dt><?php esc_html_e('认证', 'aksrt'); ?></dt><dd><?php echo esc_html((string) $verify_state['label']); ?></dd></div><?php endif; ?>
|
||||
<?php if (!empty($profile_details['gender'])) : ?><div><dt><?php esc_html_e('性别', 'aksrt'); ?></dt><dd><?php echo esc_html($profile_details['gender'] === 'male' ? __('男', 'aksrt') : __('女', 'aksrt')); ?></dd></div><?php endif; ?>
|
||||
<?php if (!empty($profile_details['gender'])) : ?><div><dt><?php esc_html_e('性别', 'aksrt'); ?></dt><dd><?php echo esc_html(['male' => __('男', 'aksrt'), 'female' => __('女', 'aksrt'), 'secret' => __('保密', 'aksrt')][$profile_details['gender']] ?? __('保密', 'aksrt')); ?></dd></div><?php endif; ?>
|
||||
<?php if (!empty($profile_details['website'])) : ?><div><dt><?php esc_html_e('个人网站', 'aksrt'); ?></dt><dd><a href="<?php echo esc_url($profile_details['website']); ?>" target="_blank" rel="noopener noreferrer"><?php echo esc_html($profile_details['website']); ?></a></dd></div><?php endif; ?>
|
||||
<?php if (!empty($profile_details['weibo'])) : ?><div><dt><?php esc_html_e('微博', 'aksrt'); ?></dt><dd><a href="<?php echo esc_url($profile_details['weibo']); ?>" target="_blank" rel="noopener noreferrer"><?php echo esc_html($profile_details['weibo']); ?></a></dd></div><?php endif; ?>
|
||||
<?php if (!empty($profile_details['github'])) : ?><div><dt>GitHub</dt><dd><a href="<?php echo esc_url($profile_details['github']); ?>" target="_blank" rel="noopener noreferrer"><?php echo esc_html($profile_details['github']); ?></a></dd></div><?php endif; ?>
|
||||
<?php if (!empty($profile_details['qq'])) : ?><div><dt>QQ</dt><dd><?php echo esc_html($profile_details['qq']); ?></dd></div><?php endif; ?>
|
||||
<?php if (!empty($profile_details['wechat'])) : ?><div><dt><?php esc_html_e('微信', 'aksrt'); ?></dt><dd><?php echo esc_html($profile_details['wechat']); ?></dd></div><?php endif; ?>
|
||||
<?php if (!empty($profile_details['address'])) : ?><div><dt><?php esc_html_e('住址', 'aksrt'); ?></dt><dd><?php echo esc_html($profile_details['address']); ?></dd></div><?php endif; ?>
|
||||
<?php if ($registered_time !== '') : ?><div><dt><?php esc_html_e('注册时间', 'aksrt'); ?></dt><dd><?php echo esc_html($registered_time); ?></dd></div><?php endif; ?>
|
||||
<?php if ($last_login_time !== '') : ?><div><dt><?php esc_html_e('最后登录', 'aksrt'); ?></dt><dd><?php echo esc_html($last_login_time); ?></dd></div><?php endif; ?>
|
||||
</dl>
|
||||
|
||||
@@ -184,13 +184,14 @@ test('native user profile omits ledger tables and renders canonical images eager
|
||||
const credits = read('src/User/CreditsAdmin.php');
|
||||
const balance = read('src/User/BalanceAdmin.php');
|
||||
const avatar = read('inc/helpers/aksrt-avatar.php');
|
||||
const profileAdmin = read('src/User/ProfileAdmin.php');
|
||||
const level = read('src/User/LevelAdmin.php');
|
||||
|
||||
assert.doesNotMatch(credits, /LedgerQuery::get_instance\(\)->history/);
|
||||
assert.doesNotMatch(credits, /近期%s流水|近期积分流水/);
|
||||
assert.doesNotMatch(balance, /LedgerQuery::get_instance\(\)->history/);
|
||||
assert.doesNotMatch(balance, /近期流水/);
|
||||
assert.match(avatar, /get_avatar\(\$user->ID, 96, '', \$user->display_name, \['loading' => 'eager'\]\)/);
|
||||
assert.match(profileAdmin, /get_avatar\(\$user_id, 96, '', \$user->display_name, \['loading' => 'eager'\]\)/);
|
||||
assert.doesNotMatch(avatar, /aksrt_get_avatar_html\(\$user->ID, 96/);
|
||||
assert.match(level, /Level::get_level_icon_url\(\$level\)/);
|
||||
assert.match(level, /<img class="aksrt-user-level-icon aksrt-user-level-icon--admin" src="/);
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const test = require('node:test');
|
||||
const { execFileSync } = require('node:child_process');
|
||||
|
||||
const root = path.resolve(__dirname, '../..');
|
||||
const read = (file) => fs.readFileSync(path.join(root, file), 'utf8');
|
||||
|
||||
test('phone persistence, uniqueness, and native profile saves pass the PHP harness', () => {
|
||||
const output = execFileSync('php', [path.join(root, 'tests/widget-system/user-profile-phone-harness.php')], { encoding: 'utf8' });
|
||||
assert.match(output, /User profile phone harness passed/);
|
||||
});
|
||||
|
||||
test('phone binding, registration, and sign-in share phone_number as the only phone value key', () => {
|
||||
const service = read('src/Auth/Service.php');
|
||||
const registrationAndBinding = read('src/Auth/ServicePart1b.php');
|
||||
const handlers = read('src/Admin/UserControllerAuthPhoneOperations.php');
|
||||
const account = read('inc/helpers/user-center-renderers/settings-account.php');
|
||||
const admin = read('src/User/ProfileAdmin.php');
|
||||
const runtime = [service, registrationAndBinding, handlers, account, admin].join('\n');
|
||||
|
||||
assert.match(service, /'meta_key'\s*=>\s*'phone_number'/);
|
||||
assert.match(registrationAndBinding, /update_user_meta\(\$user_id, 'phone_number', \$phone\)/);
|
||||
assert.match(registrationAndBinding, /'meta_key'\s*=>\s*'phone_number'/);
|
||||
assert.match(handlers, /'meta_key'\s*=>\s*'phone_number'/);
|
||||
assert.match(account, /get_user_meta\(\$account_user_id, 'phone_number', true\)/);
|
||||
assert.doesNotMatch(runtime, /['"]aksrt_phone['"]/);
|
||||
assert.match(admin, /aksrt_phone_verified/);
|
||||
assert.match(admin, /wp_verify_nonce/);
|
||||
assert.match(admin, /current_user_can\('edit_user'/);
|
||||
});
|
||||
|
||||
test('native profile exposes the reference fields and continues using canonical media/profile metadata', () => {
|
||||
const admin = read('src/User/ProfileAdmin.php');
|
||||
const avatar = read('inc/helpers/aksrt-avatar.php');
|
||||
const profile = read('inc/helpers/aksrt-user-center.php');
|
||||
const form = read('inc/helpers/user-center-renderers/profile-form.php');
|
||||
const publicProfile = read('templates/parts/user/author-hero.php');
|
||||
|
||||
['自定义头像', '自定义封面', '性别', 'QQ', '微信', '微博', 'GitHub', '住址', '绑定手机号'].forEach((label) => assert.ok(admin.includes(label), `missing ${label}`));
|
||||
assert.match(admin, /'aksrt_avatar_attachment_id'/);
|
||||
assert.match(admin, /'aksrt_background_attachment_id'/);
|
||||
assert.match(admin, /'aksrt_profile_details'/);
|
||||
assert.match(profile, /'weibo'\s*=>/);
|
||||
assert.match(profile, /'address'\s*=>/);
|
||||
assert.match(profile, /\$existing_details\['weibo'\]/);
|
||||
assert.match(profile, /\$existing_details\['address'\]/);
|
||||
assert.match(form, /name="weibo"/);
|
||||
assert.match(form, /name="address"/);
|
||||
assert.match(publicProfile, /\$profile_details\['weibo'\]/);
|
||||
assert.match(publicProfile, /\$profile_details\['address'\]/);
|
||||
assert.doesNotMatch(avatar, /aksrt_render_admin_avatar_field|aksrt_save_admin_avatar_field/);
|
||||
});
|
||||
@@ -0,0 +1,238 @@
|
||||
<?php
|
||||
/** Phone binding and native profile persistence contract harness. */
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace {
|
||||
define('ABSPATH', __DIR__ . '/');
|
||||
$GLOBALS['aksrt_profile_meta'] = [];
|
||||
$GLOBALS['aksrt_profile_users'] = [];
|
||||
$GLOBALS['aksrt_nonce_valid'] = true;
|
||||
$GLOBALS['aksrt_can_edit_user'] = true;
|
||||
$GLOBALS['aksrt_transients'] = [];
|
||||
$GLOBALS['aksrt_expected_sms_code'] = '246810';
|
||||
|
||||
final class WP_User {
|
||||
public function __construct(public int $ID) {}
|
||||
}
|
||||
|
||||
final class WP_Error {
|
||||
private array $errors = [];
|
||||
public function add(string $code, string $message): void { $this->errors[$code][] = $message; }
|
||||
public function get_error_codes(): array { return array_keys($this->errors); }
|
||||
}
|
||||
final class WP_REST_Request {
|
||||
public function __construct(private array $params = [], private string $nonce = 'ok') {}
|
||||
public function get_json_params(): array { return $this->params; }
|
||||
public function get_header(string $name): string { return $name === 'X-WP-Nonce' ? $this->nonce : ''; }
|
||||
}
|
||||
final class WP_REST_Response {
|
||||
public function __construct(public array $data, public int $status = 200) {}
|
||||
}
|
||||
function is_wp_error($value): bool { return $value instanceof WP_Error; }
|
||||
|
||||
function is_admin(): bool { return true; }
|
||||
function current_user_can(string $capability, ...$args): bool { return $GLOBALS['aksrt_can_edit_user']; }
|
||||
function is_user_logged_in(): bool { return true; }
|
||||
function get_current_user_id(): int { return 7; }
|
||||
function get_user_meta(int $user_id, string $key, bool $single = false) {
|
||||
$value = $GLOBALS['aksrt_profile_meta'][$user_id][$key] ?? '';
|
||||
return $single ? $value : ($value === '' ? [] : [$value]);
|
||||
}
|
||||
function update_user_meta(int $user_id, string $key, $value): bool {
|
||||
$GLOBALS['aksrt_profile_meta'][$user_id][$key] = $value;
|
||||
return true;
|
||||
}
|
||||
function get_users(array $args = []): array {
|
||||
$results = [];
|
||||
foreach ($GLOBALS['aksrt_profile_meta'] as $user_id => $meta) {
|
||||
if (in_array((int) $user_id, array_map('intval', $args['exclude'] ?? []), true)) continue;
|
||||
if (($meta[$args['meta_key'] ?? ''] ?? null) !== ($args['meta_value'] ?? null)) continue;
|
||||
$results[] = ($args['fields'] ?? '') === 'ids' ? (int) $user_id : new WP_User((int) $user_id);
|
||||
if (count($results) >= (int) ($args['number'] ?? 1)) break;
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
function sanitize_text_field($value): string { return trim(strip_tags((string) $value)); }
|
||||
function sanitize_textarea_field($value): string { return sanitize_text_field($value); }
|
||||
function sanitize_key($value): string { return preg_replace('/[^a-z0-9_\-]/', '', strtolower((string) $value)) ?? ''; }
|
||||
function wp_unslash($value) { return $value; }
|
||||
function esc_url_raw($value): string {
|
||||
$value = trim((string) $value);
|
||||
return $value === '' || filter_var($value, FILTER_VALIDATE_URL) ? $value : '';
|
||||
}
|
||||
function wp_verify_nonce(string $nonce, string $action): bool { return $GLOBALS['aksrt_nonce_valid'] && $nonce === 'ok'; }
|
||||
function aksrt_require_captcha(string $scene, array $payload): bool { return true; }
|
||||
function get_transient(string $key) { return $GLOBALS['aksrt_transients'][$key] ?? false; }
|
||||
function set_transient(string $key, $value, int $expiration = 0): bool { $GLOBALS['aksrt_transients'][$key] = $value; return true; }
|
||||
function delete_transient(string $key): bool { unset($GLOBALS['aksrt_transients'][$key]); return true; }
|
||||
function wp_check_password(string $password, string $hash): bool { return $hash === 'fixture-hash' && $password === $GLOBALS['aksrt_expected_sms_code']; }
|
||||
function wp_update_user(array $data) { return (int) ($data['ID'] ?? 0); }
|
||||
function do_action(...$args): void {}
|
||||
function add_action(...$args): void {}
|
||||
function add_filter(...$args): void {}
|
||||
function __(string $text, string $domain = ''): string { return $text; }
|
||||
function esc_attr(string $value): string { return htmlspecialchars($value, ENT_QUOTES); }
|
||||
function esc_html_e(string $text, string $domain = ''): void { echo htmlspecialchars($text, ENT_QUOTES); }
|
||||
function esc_attr_e(string $text, string $domain = ''): void { echo htmlspecialchars($text, ENT_QUOTES); }
|
||||
function esc_url(string $value): string { return $value; }
|
||||
function selected($selected, $current = true, bool $echo = true): string { return (string) $selected === (string) $current ? 'selected="selected"' : ''; }
|
||||
function wp_nonce_field(string $action, string $name): void {}
|
||||
function get_avatar(...$args): string { return '<img alt="">'; }
|
||||
function wp_max_upload_size(): int { return 2097152; }
|
||||
function size_format(int $bytes): string { return (string) $bytes; }
|
||||
function wp_check_filetype_and_ext(...$args): array { return []; }
|
||||
function wp_match_mime_types(...$args): bool { return true; }
|
||||
function wp_getimagesize(...$args): array|false { return false; }
|
||||
function wp_get_attachment_image_url(...$args): string|false { return false; }
|
||||
function get_post(...$args) { return null; }
|
||||
function get_post_meta(...$args) { return ''; }
|
||||
function wp_delete_attachment(...$args): bool { return true; }
|
||||
function get_current_screen() { return null; }
|
||||
|
||||
require dirname(__DIR__, 2) . '/inc/helpers/aksrt-user-center.php';
|
||||
}
|
||||
|
||||
namespace AKSRT\Sms {
|
||||
final class SMS {
|
||||
public static function is_phonenumber(string $phone): bool { return (bool) preg_match('/^1[3-9]\d{9}$/', $phone); }
|
||||
}
|
||||
}
|
||||
|
||||
namespace AKSRT\Auth {
|
||||
require dirname(__DIR__, 2) . '/src/Auth/ServicePart1b.php';
|
||||
final class PhoneHarnessService { use ServicePart1b; }
|
||||
final class Service { use ServicePart1b; }
|
||||
}
|
||||
|
||||
namespace AKSRT\Admin {
|
||||
require dirname(__DIR__, 2) . '/src/Admin/UserControllerAuthPhoneOperations.php';
|
||||
final class PhoneOperationsHarness {
|
||||
use UserControllerAuthPhoneOperations;
|
||||
public function get_json_params(\WP_REST_Request $request): array { return $request->get_json_params(); }
|
||||
public function error(string $message, int $status = 400): \WP_REST_Response { return new \WP_REST_Response(['success' => false, 'message' => $message], $status); }
|
||||
public function success(array $data): \WP_REST_Response { return new \WP_REST_Response(array_merge(['success' => true], $data), 200); }
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
require dirname(__DIR__, 2) . '/src/User/ProfileAdmin.php';
|
||||
|
||||
function check(bool $condition, string $message): void {
|
||||
if (!$condition) throw new RuntimeException($message);
|
||||
}
|
||||
|
||||
$GLOBALS['aksrt_profile_meta'][7] = [
|
||||
'aksrt_phone' => '13900000000',
|
||||
'aksrt_profile_details' => [
|
||||
'privacy' => 'public', 'gender' => 'female', 'website' => 'https://example.test',
|
||||
'qq' => '123456', 'wechat' => 'wxid', 'weibo' => 'https://weibo.com/user',
|
||||
'github' => 'https://github.com/user', 'address' => 'Shanghai',
|
||||
],
|
||||
];
|
||||
|
||||
$bad_format = \AKSRT\Auth\PhoneHarnessService::bind_phone(['phone' => 'bad'], true);
|
||||
check(empty($bad_format['success']), 'phone binding accepted malformed number');
|
||||
$unverified = \AKSRT\Auth\PhoneHarnessService::bind_phone(['phone' => '13800000007'], false);
|
||||
check(empty($unverified['success']), 'phone binding accepted missing verification');
|
||||
|
||||
$GLOBALS['aksrt_profile_meta'][8]['phone_number'] = '13800000008';
|
||||
$duplicate = \AKSRT\Auth\PhoneHarnessService::bind_phone(['phone' => '13800000008'], true);
|
||||
check(empty($duplicate['success']), 'phone binding accepted a number owned by another user');
|
||||
|
||||
$bound = \AKSRT\Auth\PhoneHarnessService::bind_phone(['phone' => '13800000007'], true);
|
||||
check(!empty($bound['success']), 'verified phone binding failed');
|
||||
check(get_user_meta(7, 'phone_number', true) === '13800000007', 'verified phone was not stored in canonical meta');
|
||||
check(get_user_meta(7, 'aksrt_phone_verified', true) === '1', 'verified marker was not stored');
|
||||
check(get_user_meta(7, 'aksrt_phone', true) === '13900000000', 'legacy phone key was unexpectedly migrated or rewritten');
|
||||
|
||||
$same_phone = \AKSRT\Auth\PhoneHarnessService::bind_phone(['phone' => '13800000007'], true);
|
||||
check(empty($same_phone['success']), 'binding accepted the current phone as a change');
|
||||
|
||||
$phone_to_bind = '13800000010';
|
||||
$otp_key = 'aksrt_auth_code_' . md5('bind_phone|' . strtolower(trim($phone_to_bind)));
|
||||
$operations = new \AKSRT\Admin\PhoneOperationsHarness();
|
||||
$GLOBALS['aksrt_transients'][$otp_key] = ['hash' => 'fixture-hash', 'attempts' => 0, 'expires' => time() + 300];
|
||||
$GLOBALS['aksrt_expected_sms_code'] = '246810';
|
||||
$wrong_code = $operations->user_bind_phone(new WP_REST_Request(['phone' => $phone_to_bind, 'code' => '000000']));
|
||||
check(empty($wrong_code->data['success']), 'REST phone binding accepted an incorrect SMS code');
|
||||
check(($GLOBALS['aksrt_transients'][$otp_key]['attempts'] ?? 0) === 1, 'incorrect SMS code attempt was not recorded');
|
||||
$GLOBALS['aksrt_transients'][$otp_key] = ['hash' => 'fixture-hash', 'attempts' => 0, 'expires' => time() - 1];
|
||||
$expired_code = $operations->user_bind_phone(new WP_REST_Request(['phone' => $phone_to_bind, 'code' => '246810']));
|
||||
check(empty($expired_code->data['success']), 'REST phone binding accepted an expired SMS code');
|
||||
check(!isset($GLOBALS['aksrt_transients'][$otp_key]), 'expired SMS code was not discarded');
|
||||
$GLOBALS['aksrt_transients'][$otp_key] = ['hash' => 'fixture-hash', 'attempts' => 0, 'expires' => time() + 300];
|
||||
$valid_code = $operations->user_bind_phone(new WP_REST_Request(['phone' => $phone_to_bind, 'code' => '246810']));
|
||||
check(!empty($valid_code->data['success']), 'REST phone binding rejected the correct SMS code');
|
||||
check(get_user_meta(7, 'phone_number', true) === $phone_to_bind, 'REST phone binding did not store the canonical phone meta');
|
||||
check(!isset($GLOBALS['aksrt_transients'][$otp_key]), 'valid SMS code was not consumed');
|
||||
|
||||
$_POST = [
|
||||
'aksrt_profile_admin_nonce' => 'ok',
|
||||
'aksrt_admin_avatar_nonce' => 'ok',
|
||||
'aksrt_profile_gender' => 'male',
|
||||
'aksrt_profile_qq' => '7654321',
|
||||
'aksrt_profile_wechat' => 'new-wechat',
|
||||
'aksrt_profile_weibo' => 'https://weibo.com/new-user',
|
||||
'aksrt_profile_github' => 'https://github.com/new-user',
|
||||
'aksrt_profile_website' => 'https://new.example.test',
|
||||
'aksrt_profile_address' => 'Beijing',
|
||||
'phone_number' => '13800000007',
|
||||
'aksrt_phone_edit_unlocked' => '0',
|
||||
];
|
||||
$_FILES = [];
|
||||
\AKSRT\User\ProfileAdmin::save(7);
|
||||
$details = aksrt_get_user_profile_details(7);
|
||||
check($details['privacy'] === 'public', 'admin profile update erased social privacy setting');
|
||||
check($details['gender'] === 'male' && $details['weibo'] === 'https://weibo.com/new-user' && $details['address'] === 'Beijing', 'admin profile fields did not persist');
|
||||
check($details['website'] === 'https://new.example.test', 'personal website field did not persist');
|
||||
|
||||
$front_save = aksrt_save_user_center_profile(7, ['nickname' => 'Profile User']);
|
||||
check($front_save === true, 'user-center profile save failed');
|
||||
$details = aksrt_get_user_profile_details(7);
|
||||
check($details['weibo'] === 'https://weibo.com/new-user' && $details['address'] === 'Beijing', 'front profile save erased admin-added fields');
|
||||
$details['privacy'] = 'private';
|
||||
update_user_meta(7, 'aksrt_profile_details', $details);
|
||||
$private_view = aksrt_get_visible_user_profile_details(7, 9);
|
||||
check($private_view['weibo'] === '' && $private_view['address'] === '', 'private social details leaked to another viewer');
|
||||
$owner_view = aksrt_get_visible_user_profile_details(7, 7);
|
||||
check($owner_view['weibo'] === 'https://weibo.com/new-user' && $owner_view['address'] === 'Beijing', 'owner cannot read private social details');
|
||||
$details['privacy'] = 'public';
|
||||
update_user_meta(7, 'aksrt_profile_details', $details);
|
||||
$public_view = aksrt_get_visible_user_profile_details(7, 9);
|
||||
check($public_view['weibo'] === 'https://weibo.com/new-user' && $public_view['address'] === 'Beijing', 'public social details are not visible to another viewer');
|
||||
|
||||
$_POST = ['aksrt_profile_admin_nonce' => 'ok', 'phone_number' => 'not-a-phone', 'aksrt_phone_edit_unlocked' => '1'];
|
||||
$invalid_errors = new WP_Error();
|
||||
\AKSRT\User\ProfileAdmin::validate($invalid_errors, true, new WP_User(7));
|
||||
check(in_array('aksrt_profile_phone', $invalid_errors->get_error_codes(), true), 'invalid admin phone format was not rejected');
|
||||
|
||||
$_POST = ['aksrt_profile_admin_nonce' => 'ok', 'phone_number' => '13800000008', 'aksrt_phone_edit_unlocked' => '1'];
|
||||
$duplicate_errors = new WP_Error();
|
||||
\AKSRT\User\ProfileAdmin::validate($duplicate_errors, true, new WP_User(7));
|
||||
check(in_array('aksrt_profile_phone_exists', $duplicate_errors->get_error_codes(), true), 'admin profile accepted a duplicate phone');
|
||||
|
||||
$_POST = ['aksrt_profile_admin_nonce' => 'wrong', 'phone_number' => '13800000009', 'aksrt_phone_edit_unlocked' => '1'];
|
||||
$before_phone = get_user_meta(7, 'phone_number', true);
|
||||
$before_details = get_user_meta(7, 'aksrt_profile_details', true);
|
||||
\AKSRT\User\ProfileAdmin::save(7);
|
||||
check(get_user_meta(7, 'phone_number', true) === $before_phone, 'invalid admin nonce changed the phone');
|
||||
check(get_user_meta(7, 'aksrt_profile_details', true) === $before_details, 'invalid admin nonce changed profile details');
|
||||
|
||||
$_POST = ['aksrt_profile_admin_nonce' => 'ok', 'phone_number' => '13800000009', 'aksrt_phone_edit_unlocked' => '1'];
|
||||
\AKSRT\User\ProfileAdmin::save(7);
|
||||
check(get_user_meta(7, 'phone_number', true) === '13800000009', 'unlocked admin phone change was not saved');
|
||||
check(get_user_meta(7, 'aksrt_phone_verified', true) === '1', 'admin phone change cleared its verified marker');
|
||||
$_POST['phone_number'] = '';
|
||||
\AKSRT\User\ProfileAdmin::save(7);
|
||||
check(get_user_meta(7, 'phone_number', true) === '13800000009', 'blank admin phone unexpectedly cleared the binding');
|
||||
|
||||
$before_details = get_user_meta(7, 'aksrt_profile_details', true);
|
||||
$GLOBALS['aksrt_can_edit_user'] = false;
|
||||
$_POST = ['aksrt_profile_admin_nonce' => 'ok', 'aksrt_profile_address' => 'Unauthorized change'];
|
||||
\AKSRT\User\ProfileAdmin::save(7);
|
||||
$GLOBALS['aksrt_can_edit_user'] = true;
|
||||
check(get_user_meta(7, 'aksrt_profile_details', true) === $before_details, 'user without edit permission changed profile details');
|
||||
|
||||
echo "User profile phone harness passed\n";
|
||||
}
|
||||
@@ -63,6 +63,10 @@ $meta_keys = [
|
||||
'aksrt_avatar_attachment_id',
|
||||
'aksrt_background_attachment_id',
|
||||
'aksrt_background_image',
|
||||
'phone_number',
|
||||
'aksrt_phone_verified',
|
||||
'aksrt_profile_details',
|
||||
'aksrt_phone',
|
||||
'aksrt_email_verified',
|
||||
'aksrt_unread_message_count',
|
||||
'_aksrt_active_restrictions',
|
||||
|
||||
Reference in New Issue
Block a user