feat(users): govern profiles and access

Normalize fixed profile fields, profile actions, and the five-column capability matrix for comments, uploads, and downloads.
This commit is contained in:
kate522 committed 2026-08-13 00:24:54 +08:00
1 parent 0b06b2c7df
commit 8241570ca2
67 files changed
+780 -1127

No files matched your search

+70 -43
View File
@@ -7,41 +7,48 @@
defined('ABSPATH') || exit;
if (!function_exists('aksrt_normalize_user_contacts')) {
if (!function_exists('aksrt_get_user_profile_details')) {
/**
* Sanitize the editable public contact list.
* Read the fixed profile details. Social fields are deliberately kept in one
* structured meta value so public rendering can filter them server-side.
*
* @param mixed $raw_contacts Submitted contact rows.
* @return array<int, array{name:string,url:string,icon:string}>
* @return array{privacy:string,gender:string,website:string,qq:string,wechat:string,github:string}
*/
function aksrt_normalize_user_contacts($raw_contacts): array {
$contacts = [];
if (!is_array($raw_contacts)) {
return $contacts;
function aksrt_get_user_profile_details(int $user_id): array {
$raw = get_user_meta($user_id, 'aksrt_profile_details', true);
$raw = is_array($raw) ? $raw : [];
$privacy = (string) ($raw['privacy'] ?? 'private');
if (!in_array($privacy, ['private', 'public', 'members'], true)) {
$privacy = 'private';
}
$gender = (string) ($raw['gender'] ?? 'secret');
if (!in_array($gender, ['secret', 'male', 'female'], true)) {
$gender = 'secret';
}
foreach ($raw_contacts as $contact) {
if (!is_array($contact)) {
continue;
}
return [
'privacy' => $privacy,
'gender' => $gender,
'website' => esc_url_raw((string) ($raw['website'] ?? '')),
'qq' => sanitize_text_field((string) ($raw['qq'] ?? '')),
'wechat' => sanitize_text_field((string) ($raw['wechat'] ?? '')),
'github' => esc_url_raw((string) ($raw['github'] ?? '')),
];
}
}
$url = isset($contact['url']) ? esc_url_raw(wp_unslash($contact['url'])) : '';
$icon = isset($contact['icon']) ? sanitize_key(wp_unslash($contact['icon'])) : '';
$icon = preg_replace('/^(fab|fas|far)[\s-]*/', '', $icon);
$icon = preg_replace('/^fa[\s-]*/', '', (string) $icon);
$icon = trim(preg_replace('/^[\s-]+/', '', (string) $icon), '- ');
if (!$url || !$icon) {
continue;
}
$contacts[] = [
'name' => aksrt_icon_key_to_name($icon),
'url' => $url,
'icon' => $icon,
];
}
return $contacts;
if (!function_exists('aksrt_get_visible_user_profile_details')) {
/** @return array{gender:string,website:string,qq:string,wechat:string,github: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);
return [
'gender' => $details['gender'] === 'secret' ? '' : $details['gender'],
'website' => $visible ? $details['website'] : '',
'qq' => $visible ? $details['qq'] : '',
'wechat' => $visible ? $details['wechat'] : '',
'github' => $visible ? $details['github'] : '',
];
}
}
@@ -79,6 +86,30 @@ if (!function_exists('aksrt_save_user_center_profile')) {
}
}
$privacy = isset($input['privacy']) ? sanitize_key(wp_unslash($input['privacy'])) : 'private';
if (!in_array($privacy, ['private', 'public', 'members'], true)) {
$errors[] = __('无效的隐私设置。', 'aksrt');
}
$gender = isset($input['gender']) ? sanitize_key(wp_unslash($input['gender'])) : 'secret';
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'])) : '',
];
foreach (['website', 'github'] as $field) {
if (!empty($input[$field]) && $details[$field] === '') {
$errors[] = sprintf(__('无效的%s链接。', 'aksrt'), $field === 'website' ? __('个人网站', 'aksrt') : 'GitHub');
}
}
if (mb_strlen($details['qq']) > 50 || mb_strlen($details['wechat']) > 100) {
$errors[] = __('社交资料长度超过限制。', 'aksrt');
}
if ($errors) {
return new WP_Error('aksrt_profile_invalid', implode('<br>', $errors));
}
@@ -89,8 +120,7 @@ if (!function_exists('aksrt_save_user_center_profile')) {
return $result;
}
}
update_user_meta($user_id, 'aksrt_user_contacts', aksrt_normalize_user_contacts($input['contacts'] ?? []));
update_user_meta($user_id, 'aksrt_profile_details', $details);
return true;
@@ -388,6 +418,8 @@ if (!function_exists('aksrt_user_center_cover_upload_config')) {
if (!$allow_bg_upload) {
return [];
}
$settings = class_exists('\\AKSRT\\Settings\\Settings') ? \AKSRT\Settings\Settings::get_all() : [];
$max_size = max(1, min(10, (int) ($settings['avatar_max_size'] ?? 2))) * MB_IN_BYTES;
return [
'enabled' => true,
'restUrl' => rest_url('aksrt/v1/user/background'),
@@ -396,11 +428,11 @@ if (!function_exists('aksrt_user_center_cover_upload_config')) {
'inputId' => 'aksrt-bg-file-input',
'imageId' => 'aksrt-cover-img',
'fieldName' => 'background',
'maxSize' => 2097152,
'maxSize' => $max_size,
'fallbackImage' => AKSRT_URI . '/assets/img/person_background.png',
'idleText' => __('编辑', 'aksrt'),
'idleText' => __('修改背景图', 'aksrt'),
'loadingText' => __('上传中…', 'aksrt'),
'tooLargeText' => __('图片大小不能超过 2MB', 'aksrt'),
'tooLargeText' => sprintf(__('图片大小不能超过 %dMB', 'aksrt'), (int) ($max_size / MB_IN_BYTES)),
'successText' => __('背景图已更新', 'aksrt'),
'errorText' => __('上传失败', 'aksrt'),
'networkErrorText' => __('网络错误,请重试', 'aksrt'),
@@ -418,10 +450,7 @@ if (!function_exists('aksrt_get_user_center_render_context')) {
$user_center_settings = class_exists('\AKSRT\Settings\Settings')
? \AKSRT\Settings\Settings::get_user_center_settings()
: [
'tabs' => ['profile', 'comments', 'favorites'],
'avatar_upload' => true,
'cover_upload' => true,
'social_links' => true,
'tabs' => ['profile', 'comments', 'favorites'],
];
$tabs = aksrt_user_center_resolve_tabs();
@@ -439,11 +468,9 @@ if (!function_exists('aksrt_get_user_center_render_context')) {
$comment_count = (int) ($dashboard['comment_count'] ?? get_comments(['user_id' => $user_id, 'count' => true, 'status' => 'approve']));
$favorite_post_ids = $dashboard['favorite_post_ids'] ?? [];
$favorite_count = (int) ($dashboard['favorite_count'] ?? count($favorite_post_ids));
$user_contacts = $dashboard['user_contacts'] ?? get_user_meta($user_id, 'aksrt_user_contacts', true);
$user_contacts = is_array($user_contacts) ? $user_contacts : [];
$user_background_url = (string) ($dashboard['background_url'] ?? get_user_meta($user_id, 'aksrt_background_image', true));
$allow_avatar_upload = !empty($user_center_settings['avatar_upload']);
$allow_bg_upload = !empty($user_center_settings['cover_upload']) && class_exists('\AKSRT\Settings\Settings') && aksrt_get_setting('allow_background_upload', false);
$allow_avatar_upload = function_exists('aksrt_user_can') && aksrt_user_can('upload_avatar', $user_id);
$allow_bg_upload = function_exists('aksrt_user_can') && aksrt_user_can('upload_profile_background', $user_id);
$messages_enabled = !class_exists('\AKSRT\Settings\Settings') || \AKSRT\Settings\Settings::message_enabled();
$follower_count = class_exists('\AKSRT\User\Follow')
? \AKSRT\User\Follow::get_instance()->get_follower_count($user_id)
@@ -501,7 +528,7 @@ if (!function_exists('aksrt_get_user_center_render_context')) {
'favorite_count' => $favorite_count,
'follower_count' => $follower_count,
'following_count' => $following_count,
'user_contacts' => $user_contacts,
'profile_details' => aksrt_get_user_profile_details($user_id),
'profile_signals' => $dashboard['profile_signals'] ?? [],
'user_background_url' => $user_background_url,
'allow_bg_upload' => $allow_bg_upload,