$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'] ?? '')), ]; } } 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'] : '', ]; } } if (!function_exists('aksrt_save_user_center_profile')) { /** * Save the user-center profile form for both AJAX and no-JS requests. * * @param int $user_id User being updated. * @param array $input Untrusted request data. * @return true|WP_Error */ function aksrt_save_user_center_profile(int $user_id, array $input) { if (!$user_id || $user_id !== get_current_user_id()) { return new WP_Error('aksrt_profile_forbidden', __('无权操作', 'aksrt')); } $errors = []; $update = ['ID' => $user_id]; if (isset($input['nickname'])) { $name = sanitize_text_field(wp_unslash($input['nickname'])); if (mb_strlen($name) < 1 || mb_strlen($name) > 50) { $errors[] = __('昵称长度需在 1-50 字之间', 'aksrt'); } else { $update['display_name'] = $name; } } if (isset($input['description'])) { $description = sanitize_textarea_field(wp_unslash($input['description'])); if (mb_strlen($description) > 500) { $errors[] = __('个人简介不能超过 500 字', 'aksrt'); } else { $update['description'] = $description; } } $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('
', $errors)); } if (count($update) > 1) { $result = wp_update_user($update); if (is_wp_error($result)) { return $result; } } update_user_meta($user_id, 'aksrt_profile_details', $details); return true; } } if (!function_exists('aksrt_get_user_center_tab_url')) { /** * Build a canonical full-page user-center tab URL. */ function aksrt_get_user_center_tab_url(string $tab): string { $tab = sanitize_key($tab); return aksrt_get_user_center_url($tab); } } if (!function_exists('aksrt_normalize_user_center_tabs')) { /** * Normalize the dashboard tab contract. */ function aksrt_normalize_user_center_tabs(array $tabs): array { $normalized = []; foreach ($tabs as $key => $tab) { $key = sanitize_key((string) $key); if (!$key || !is_array($tab)) { continue; } $normalized[$key] = [ 'label' => (string) ($tab['label'] ?? $key), 'icon' => sanitize_html_class((string) ($tab['icon'] ?? 'circle')), 'template' => isset($tab['template']) ? (string) $tab['template'] : '', 'context_callback' => $tab['context_callback'] ?? null, ]; } return $normalized; } } if (!function_exists('aksrt_capture_user_center_view')) { /** * Render an internal user-center view into an inert HTML payload. */ function aksrt_capture_user_center_view(string $view, array $context): string { // Built-in panels map to renderer files; "profile" content lives in profile-form.php. $map = [ 'level' => 'level', 'profile' => 'form', 'creator' => 'creator', 'posts' => 'posts', 'follow' => 'follow', 'comments' => 'comments', 'favorites' => 'favorites', 'credits' => 'credits', 'vip' => 'vip', 'orders' => 'orders', 'auth' => 'auth', 'revenue' => 'revenue', 'settings' => 'settings', 'header' => 'header', 'sidebar' => 'sidebar', 'account-modals' => 'account-modals', ]; $view = sanitize_file_name($view); $renderer = $map[$view] ?? $view; $path = AKSRT_DIR . '/inc/helpers/user-center-renderers/profile-' . $renderer . '.php'; if (!is_file($path)) { return ''; } ob_start(); $args = ['context' => $context]; require $path; return (string) ob_get_clean(); } } if (!function_exists('aksrt_capture_user_center_panel')) { /** * Capture a built-in or registered user-center panel body. */ function aksrt_capture_user_center_panel(string $tab_key, array $tab, array $context): string { $built_in_tabs = ['profile', 'level', 'creator', 'posts', 'follow', 'comments', 'favorites', 'credits', 'vip', 'orders', 'auth', 'revenue', 'settings']; if (in_array($tab_key, $built_in_tabs, true)) { return aksrt_capture_user_center_view($tab_key, $context); } $panel_context = $context; if (is_callable($tab['context_callback'] ?? null)) { $custom_context = call_user_func($tab['context_callback'], $context, $tab_key); if (is_array($custom_context)) { $panel_context = array_merge($panel_context, $custom_context); } } $template = ltrim((string) ($tab['template'] ?? ''), '/'); if ($template === '') { return ''; } $template_path = locate_template([$template . '.php', $template], false, false); if (!$template_path) { return ''; } ob_start(); load_template($template_path, false, ['context' => $panel_context]); return (string) ob_get_clean(); } } if (!function_exists('aksrt_build_user_center_render_context')) { /** * Assemble all runtime data before template parts are entered. */ function aksrt_build_user_center_render_context(array $context): array { $active_tab = (string) ($context['active_tab'] ?? 'profile'); $context['header_html'] = aksrt_capture_user_center_view('header', $context); $context['sidebar_html'] = aksrt_capture_user_center_view('sidebar', $context); $context['mobile_tabs'] = []; foreach ((array) ($context['dashboard_tabs'] ?? []) as $tab_key => $tab) { $context['mobile_tabs'][] = [ 'key' => (string) $tab_key, 'label' => (string) ($tab['label'] ?? $tab_key), 'url' => aksrt_get_user_center_tab_url((string) $tab_key), 'is_active' => $active_tab === $tab_key, ]; } $context['panels'] = []; foreach ((array) ($context['dashboard_tabs'] ?? []) as $tab_key => $tab) { $context['panels'][$tab_key] = [ 'key' => (string) $tab_key, 'url' => aksrt_get_user_center_tab_url((string) $tab_key), 'is_active' => $active_tab === $tab_key, 'is_loaded' => $active_tab === $tab_key, 'html' => $active_tab === $tab_key ? aksrt_capture_user_center_panel((string) $tab_key, (array) $tab, $context) : '', ]; } $context['account_modals_html'] = 'settings' === $active_tab ? aksrt_capture_user_center_view('account-modals', $context) : ''; return $context; } } if (!function_exists('aksrt_user_center_empty_svgs')) { /** * Shared empty-state icons for user-center panels. * * @return array{post:string,comment:string,favorite:string} */ function aksrt_user_center_empty_svgs(): array { return [ 'post' => '', 'comment' => '', 'favorite' => '', ]; } } if (!function_exists('aksrt_user_center_resolve_tabs')) { /** * Resolve dashboard tabs + active tab for the private user center. * * @return array{tabs:array,keys:array,default:string,active:string,urls:array} */ function aksrt_user_center_resolve_tabs(): array { $tabs = class_exists('\AKSRT\User\Center') ? \AKSRT\User\Center::get_instance()->get_dashboard_tabs() : [ 'profile' => ['label' => __('个人信息', 'aksrt'), 'icon' => 'user'], 'comments' => ['label' => __('我的评论', 'aksrt'), 'icon' => 'comment'], 'favorites' => ['label' => __('我的收藏', 'aksrt'), 'icon' => 'star'], 'settings' => ['label' => __('账号设置', 'aksrt'), 'icon' => 'cog'], ]; $tabs = aksrt_normalize_user_center_tabs($tabs); $keys = array_keys($tabs); $default = in_array('vip', $keys, true) ? 'vip' : (in_array('profile', $keys, true) ? 'profile' : ($keys[0] ?? 'profile')); $active = (string) get_query_var('aksrt_user_center_tab') ?: $default; if ($active === 'balance') { $active = 'credits'; } if ('signin' === $active || 'assets' === $active) { $active = 'credits'; } elseif ('vip' === $active) { // Prefer dedicated vip tab when registered; else credits (VIP shop). $active = in_array('vip', $keys, true) ? 'vip' : 'credits'; } if (!in_array($active, $keys, true)) { $active = $default; } $urls = []; foreach ($keys as $key) { $urls[$key] = aksrt_get_user_center_tab_url($key); } return [ 'tabs' => $tabs, 'keys' => $keys, 'default' => $default, 'active' => $active, 'urls' => $urls, ]; } } if (!function_exists('aksrt_user_center_runtime_config')) { /** * Frontend runtime config injected into the private user center page. */ function aksrt_user_center_runtime_config(int $user_id, string $default_tab, string $active_tab, array $tab_urls): array { return [ 'ajaxUrl' => admin_url('admin-ajax.php'), 'publicNonce' => wp_create_nonce('aksrt_public_nonce'), 'settingsNonce' => wp_create_nonce('aksrt_account_settings'), 'userId' => $user_id, 'restUrl' => rest_url('aksrt/v1'), 'restNonce' => wp_create_nonce('wp_rest'), 'submitNonce' => wp_create_nonce('aksrt_submit'), 'defaultTab' => $default_tab, 'activeTab' => $active_tab, 'tabAliases' => [ 'signin' => 'credits', 'revenue' => 'revenue', 'assets' => 'credits', // vip → vip when panel exists; SPA may still map missing keys via server resolve. 'vip' => 'vip', ], 'tabUrls' => $tab_urls, 'i18n' => [ 'contactUrlPlaceholder' => __('链接 URL', 'aksrt'), 'deleteLabel' => __('删除', 'aksrt'), 'saving' => __('保存中…', 'aksrt'), 'saveBtn' => __('保存修改', 'aksrt'), 'saved' => __('已保存', 'aksrt'), 'saveFailed' => __('保存失败', 'aksrt'), 'networkError' => __('网络错误,请重试', 'aksrt'), 'confirmUnfavorite' => __('确定取消收藏这篇文章?', 'aksrt'), 'unfavorited' => __('已取消收藏', 'aksrt'), 'operationFailed' => __('操作失败', 'aksrt'), 'confirmUnbind' => __('确定解除绑定?', 'aksrt'), 'unbinding' => __('解除中...', 'aksrt'), 'unbindSuccess' => __('解除成功', 'aksrt'), 'unbindFailed' => __('解除失败', 'aksrt'), 'unbind' => __('解除', 'aksrt'), ], ]; } } if (!function_exists('aksrt_user_center_avatar_upload_config')) { /** * Avatar upload config for the private user center. */ function aksrt_user_center_avatar_upload_config(bool $enabled): array { if (!$enabled) { 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/avatar'), 'restNonce' => wp_create_nonce('wp_rest'), 'buttonId' => 'aksrt-upload-avatar-btn', 'inputId' => 'aksrt-avatar-file-input', 'imageId' => 'aksrt-user-avatar-image', 'fieldName' => 'avatar', 'maxSize' => $max_size, 'idleText' => __('更换头像', 'aksrt'), 'loadingText' => __('上传中…', 'aksrt'), 'tooLargeText' => sprintf(__('图片大小不能超过 %dMB', 'aksrt'), (int) ($max_size / MB_IN_BYTES)), 'successText' => __('头像已更新', 'aksrt'), 'errorText' => __('头像上传失败', 'aksrt'), ]; } } if (!function_exists('aksrt_user_center_cover_upload_config')) { /** * Cover upload config for the private user-center hero. */ function aksrt_user_center_cover_upload_config(bool $allow_bg_upload): array { 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'), 'restNonce' => wp_create_nonce('wp_rest'), 'buttonId' => 'aksrt-upload-bg-btn', 'inputId' => 'aksrt-bg-file-input', 'imageId' => 'aksrt-cover-img', 'fieldName' => 'background', 'maxSize' => $max_size, 'fallbackImage' => AKSRT_URI . '/assets/img/person_background.png', 'idleText' => __('修改背景图', 'aksrt'), 'loadingText' => __('上传中…', 'aksrt'), 'tooLargeText' => sprintf(__('图片大小不能超过 %dMB', 'aksrt'), (int) ($max_size / MB_IN_BYTES)), 'successText' => __('背景图已更新', 'aksrt'), 'errorText' => __('上传失败', 'aksrt'), 'networkErrorText' => __('网络错误,请重试', 'aksrt'), ]; } } if (!function_exists('aksrt_get_user_center_render_context')) { /** * Resolve the current user's complete render context outside template files. */ function aksrt_get_user_center_render_context(WP_User $current_user): array { $user_id = (int) $current_user->ID; $user_center_settings = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_user_center_settings() : [ 'tabs' => ['profile', 'comments', 'favorites'], ]; $tabs = aksrt_user_center_resolve_tabs(); $active_tab = $tabs['active']; $dashboard = function_exists('aksrt_get_user_dashboard_view') ? aksrt_get_user_dashboard_view($user_id, [ 'viewer_id' => $user_id, 'include_recent_posts' => false, 'include_governance' => true, ]) : []; $post_count = (int) ($dashboard['post_count'] ?? count_user_posts($user_id, 'post', true)); $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_background_url = (string) ($dashboard['background_url'] ?? get_user_meta($user_id, 'aksrt_background_image', true)); $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) : 0; $following_count = class_exists('\AKSRT\User\Follow') ? \AKSRT\User\Follow::get_instance()->get_following_count($user_id) : 0; $allowed_post_statuses = ['publish', 'draft', 'pending', 'private']; $post_status_filter = isset($_GET['post_status']) ? sanitize_key(wp_unslash($_GET['post_status'])) : 'publish'; if (!in_array($post_status_filter, array_merge($allowed_post_statuses, ['all']), true)) { $post_status_filter = 'publish'; } $user_status_counts = []; foreach ($allowed_post_statuses as $status) { $status_query = new WP_Query([ 'author' => $user_id, 'post_type' => 'post', 'post_status' => $status, 'posts_per_page' => 1, 'fields' => 'ids', 'no_found_rows' => false, ]); $user_status_counts[$status] = (int) $status_query->found_posts; } $svgs = aksrt_user_center_empty_svgs(); require_once AKSRT_DIR . '/templates/parts/user-shared-helpers.php'; $vip_settings = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_payment_vip_settings() : []; $affiliate_settings = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_payment_affiliate_settings() : []; $show_credit_tabs = (bool) ($dashboard['credits_enabled'] ?? false); $currency_name = (string) ($dashboard['currency_name'] ?? __('积分', 'aksrt')); $balance_enabled = (bool) ($dashboard['balance_enabled'] ?? false); $wallet_balance = (float) ($dashboard['wallet_balance'] ?? 0.0); $runtime_config = aksrt_user_center_runtime_config($user_id, $tabs['default'], $active_tab, $tabs['urls']); $runtime_config['avatarUpload'] = aksrt_user_center_avatar_upload_config($allow_avatar_upload); $render_context = [ 'user_center_settings' => $user_center_settings, 'dashboard_tabs' => $tabs['tabs'], 'dashboard_tab_keys' => $tabs['keys'], 'default_dashboard_tab' => $tabs['default'], 'active_tab' => $active_tab, 'dashboard' => $dashboard, 'target_user' => $current_user, 'user_id' => $user_id, 'post_count' => $post_count, 'comment_count' => $comment_count, 'favorite_post_ids' => $favorite_post_ids, 'favorite_count' => $favorite_count, 'follower_count' => $follower_count, 'following_count' => $following_count, '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, 'show_avatar_edit' => $allow_avatar_upload, 'notification_unread_count' => function_exists('aksrt_get_user_unread_notification_count') ? aksrt_get_user_unread_notification_count($user_id) : 0, 'messages_url' => $messages_enabled && function_exists('aksrt_get_messages_page_url') ? aksrt_get_messages_page_url() : '', 'per_page' => 10, 'posts_page' => max(1, isset($_GET['posts_page']) ? (int) $_GET['posts_page'] : 1), 'post_status_filter' => $post_status_filter, 'user_status_counts' => $user_status_counts, 'new_post_url' => function_exists('aksrt_get_new_post_url') ? aksrt_get_new_post_url() : '', 'svg_empty_post' => $svgs['post'], 'comments_page' => max(1, isset($_GET['comments_page']) ? (int) $_GET['comments_page'] : 1), 'favorites_page' => max(1, isset($_GET['favorites_page']) ? (int) $_GET['favorites_page'] : 1), 'svg_empty_comment' => $svgs['comment'], 'svg_empty_favorite' => $svgs['favorite'], 'orders_url' => isset($tabs['tabs']['orders']) ? aksrt_get_user_center_tab_url('orders') : '', 'show_vip' => !empty($vip_settings['enabled']), 'show_credit_tabs' => $show_credit_tabs, 'affiliate_enabled' => $show_credit_tabs && !empty($affiliate_settings['enabled']), 'currency_name' => $currency_name, 'credit_balance' => (float) ($dashboard['credit_balance'] ?? 0.0), 'credits_enabled' => $show_credit_tabs, 'balance_enabled' => $balance_enabled, 'wallet_balance' => $wallet_balance, 'vip_settings' => $vip_settings, 'user_email' => (string) $current_user->user_email, 'runtime_config' => $runtime_config, 'cover_upload_config' => aksrt_user_center_cover_upload_config($allow_bg_upload), ]; return aksrt_build_user_center_render_context($render_context); } }