diff --git a/functions.php b/functions.php index 7c817caf..21ca21fb 100644 --- a/functions.php +++ b/functions.php @@ -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(); } diff --git a/inc/helpers/aksrt-avatar.php b/inc/helpers/aksrt-avatar.php index 17b317c7..2fc58aa2 100644 --- a/inc/helpers/aksrt-avatar.php +++ b/inc/helpers/aksrt-avatar.php @@ -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; - } - ?> -

- - - - - - - $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. diff --git a/inc/helpers/aksrt-user-center.php b/inc/helpers/aksrt-user-center.php index 529dba05..5d55aa6a 100644 --- a/inc/helpers/aksrt-user-center.php +++ b/inc/helpers/aksrt-user-center.php @@ -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) { diff --git a/inc/helpers/aksrt-user-dashboard.php b/inc/helpers/aksrt-user-dashboard.php index 729e08fe..0a4d3d88 100644 --- a/inc/helpers/aksrt-user-dashboard.php +++ b/inc/helpers/aksrt-user-dashboard.php @@ -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) : ''; diff --git a/inc/helpers/user-center-renderers/profile-form.php b/inc/helpers/user-center-renderers/profile-form.php index 97e1b065..79d14408 100644 --- a/inc/helpers/user-center-renderers/profile-form.php +++ b/inc/helpers/user-center-renderers/profile-form.php @@ -55,7 +55,9 @@ $profile_details = isset($profile_details) && is_array($profile_details) ? $prof + + diff --git a/inc/helpers/user-center-renderers/settings-account.php b/inc/helpers/user-center-renderers/settings-account.php index b30a4334..1bf58e22 100644 --- a/inc/helpers/user-center-renderers/settings-account.php +++ b/inc/helpers/user-center-renderers/settings-account.php @@ -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'); diff --git a/src/Admin/UserControllerAuthPhoneOperations.php b/src/Admin/UserControllerAuthPhoneOperations.php index 96c289ed..f378673c 100644 --- a/src/Admin/UserControllerAuthPhoneOperations.php +++ b/src/Admin/UserControllerAuthPhoneOperations.php @@ -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, ]); diff --git a/src/Auth/Service.php b/src/Auth/Service.php index 07ad8973..c454b0f0 100644 --- a/src/Auth/Service.php +++ b/src/Auth/Service.php @@ -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, ]); diff --git a/src/Auth/ServicePart1b.php b/src/Auth/ServicePart1b.php index 15735eb3..b0732e04 100644 --- a/src/Auth/ServicePart1b.php +++ b/src/Auth/ServicePart1b.php @@ -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); - update_user_meta($user_id, 'aksrt_phone_verified', '1'); - do_action('aksrt_user_phone_changed', $user_id, $phone); - - return [ + $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, $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', diff --git a/src/User/ProfileAdmin.php b/src/User/ProfileAdmin.php new file mode 100644 index 00000000..7e0cc5e6 --- /dev/null +++ b/src/User/ProfileAdmin.php @@ -0,0 +1,282 @@ +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); + ?> +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + 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; + ?> + + 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 !=
-
+
__('男', 'aksrt'), 'female' => __('女', 'aksrt'), 'secret' => __('保密', 'aksrt')][$profile_details['gender']] ?? __('保密', 'aksrt')); ?>
+
GitHub
QQ
+
diff --git a/tests/widget-system/user-features-contract.test.cjs b/tests/widget-system/user-features-contract.test.cjs index 536d3fc3..19d71500 100644 --- a/tests/widget-system/user-features-contract.test.cjs +++ b/tests/widget-system/user-features-contract.test.cjs @@ -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, / { + 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/); +}); diff --git a/tests/widget-system/user-profile-phone-harness.php b/tests/widget-system/user-profile-phone-harness.php new file mode 100644 index 00000000..31c4ed1a --- /dev/null +++ b/tests/widget-system/user-profile-phone-harness.php @@ -0,0 +1,238 @@ +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 ''; } + 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"; +} diff --git a/uninstall.php b/uninstall.php index d561209a..38c1b706 100644 --- a/uninstall.php +++ b/uninstall.php @@ -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',