diff --git a/assets/css/routes/profile/layout.css b/assets/css/routes/profile/layout.css index 366e8244..daacaed8 100644 --- a/assets/css/routes/profile/layout.css +++ b/assets/css/routes/profile/layout.css @@ -401,7 +401,8 @@ padding-bottom: 12px; border-bottom: 2px solid var(--color-line-soft, rgba(0,0,0,0.06)); } -.dashboard--public .dashboard-tabs button { +.dashboard--public .dashboard-tabs button, +.dashboard--public .dashboard-tabs a { padding: 8px 16px; border-radius: 8px; font-size: 14px; @@ -413,16 +414,19 @@ transition: all 0.2s ease; position: relative; } -.dashboard--public .dashboard-tabs button:hover { +.dashboard--public .dashboard-tabs button:hover, +.dashboard--public .dashboard-tabs a:hover { color: var(--color-ink); background: var(--surface-2); } -.dashboard--public .dashboard-tabs button.is-active { +.dashboard--public .dashboard-tabs button.is-active, +.dashboard--public .dashboard-tabs a.is-active { color: var(--color-primary); background: var(--color-primary-soft); font-weight: 600; } -.dashboard--public .dashboard-tabs button.is-active::after { +.dashboard--public .dashboard-tabs button.is-active::after, +.dashboard--public .dashboard-tabs a.is-active::after { content: ''; position: absolute; bottom: -14px; @@ -433,7 +437,8 @@ background: var(--color-primary); border-radius: 1px; } -.dashboard--public .dashboard-tabs button.is-active .dashboard-tabs__count { +.dashboard--public .dashboard-tabs button.is-active .dashboard-tabs__count, +.dashboard--public .dashboard-tabs a.is-active .dashboard-tabs__count { color: var(--color-primary); background: var(--color-primary-soft); } @@ -565,3 +570,43 @@ color: #fff; } + +/* 用户中心复用全站 content-layout,避免嵌套网格与双卡片负边距抵消。 */ +.aksrt-profile__layout { + margin-top: 20px; +} +.aksrt-profile__aside.page-owned { + position: static; +} +@media (min-width: 992px) { + .aksrt-profile__aside.page-owned { + position: sticky; + } +} +.aksrt-user-center__notice { + margin-bottom: 16px; + padding: 12px 14px; + border-radius: 8px; + color: #166534; + background: rgba(34, 197, 94, 0.12); +} +.aksrt-user-center__notice--error { + color: var(--color-error, #b91c1c); + background: rgba(239, 68, 68, 0.12); +} +.aksrt-profile .uc-mobile-tabs a { + display: inline-flex; + align-items: center; + padding: 8px 14px; + border-radius: 8px; + color: var(--color-ink-3); + text-decoration: none; +} +.aksrt-profile .uc-mobile-tabs a.is-active { + color: var(--color-primary); + background: var(--color-primary-soft); +} +.aksrt-profile .dashboard-panel > .widget-card:first-child, +.aksrt-profile .profile-form > .widget-card:first-child { + margin-top: 0; +} diff --git a/assets/css/routes/user-center/part-1.css b/assets/css/routes/user-center/part-1.css index 04a667b9..e3788e15 100644 --- a/assets/css/routes/user-center/part-1.css +++ b/assets/css/routes/user-center/part-1.css @@ -17,16 +17,6 @@ padding: 0 0 40px; } -/* ==================== 两栏布局 — 对齐 zibll: 311px 侧栏 ==================== */ -.uc-layout { - display: grid; - grid-template-columns: 311px 1fr; - gap: 20px; - align-items: start; - width: 100%; -} - - .uc-header__actions { position: relative; } diff --git a/assets/css/routes/user-center/part-4.css b/assets/css/routes/user-center/part-4.css index 15e4ecbe..5fdf1ed7 100644 --- a/assets/css/routes/user-center/part-4.css +++ b/assets/css/routes/user-center/part-4.css @@ -82,10 +82,6 @@ /* ==================== 响应式 ==================== */ @media (max-width: 767px) { - .uc-layout { - grid-template-columns: 1fr; - } - .uc-sidebar { position: fixed; top: 0; diff --git a/assets/js/routes/user-center.js b/assets/js/routes/user-center.js index a707044e..76cc7984 100644 --- a/assets/js/routes/user-center.js +++ b/assets/js/routes/user-center.js @@ -18,6 +18,8 @@ var settingsNonce = cfg.settingsNonce || ''; var userId = cfg.userId || 0; var i18n = cfg.i18n || {}; + var tabAliases = cfg.tabAliases || {}; + var tabUrls = cfg.tabUrls || {}; /* ==================== Dashboard 导航 ==================== */ function initDashboardNavigation() { @@ -54,6 +56,10 @@ function switchTo(name) { if (!panels[name]) name = defaultTab; + if (panels[name].panel.dataset.panelLoaded === 'false') { + window.location.assign(tabUrls[name] || panels[name].panel.dataset.tabUrl || ('?tab=' + encodeURIComponent(name))); + return; + } Object.keys(panels).forEach(function(key) { var active = key === name; @@ -72,10 +78,8 @@ function tabFromUrl() { var m = window.location.search.match(/[?&]tab=([^&]+)/); - var name = m ? decodeURIComponent(m[1]) : defaultTab; - if (name === 'signin') return 'credits'; - if (name === 'invite') return 'settings'; - return name; + var name = m ? decodeURIComponent(m[1]) : (cfg.activeTab || defaultTab); + return tabAliases[name] || name; } function updateUrl(name) { @@ -90,16 +94,20 @@ } tabs.forEach(function(tab) { - tab.addEventListener('click', function() { + tab.addEventListener('click', function(e) { var name = this.getAttribute('data-tab'); + if (!panels[name] || panels[name].panel.dataset.panelLoaded === 'false') return; + e.preventDefault(); switchTo(name); updateUrl(name); }); }); sidebarBtns.forEach(function(btn) { - btn.addEventListener('click', function() { + btn.addEventListener('click', function(e) { var name = this.getAttribute('data-tab'); + if (!panels[name] || panels[name].panel.dataset.panelLoaded === 'false') return; + e.preventDefault(); switchTo(name); updateUrl(name); }); diff --git a/author.php b/author.php index c4cf50fb..fe6519f4 100644 --- a/author.php +++ b/author.php @@ -71,7 +71,13 @@ require_once AKSRT_DIR . '/templates/parts/user-shared-helpers.php';
- + [ + 'type' => 'view', + 'url' => get_permalink(), + 'label' => __('阅读', 'aksrt'), + ], + ]); ?>
diff --git a/inc/bootstrap.php b/inc/bootstrap.php index e57c0a12..45ca1a49 100644 --- a/inc/bootstrap.php +++ b/inc/bootstrap.php @@ -43,6 +43,7 @@ require_once AKSRT_DIR . '/inc/helpers/menu-decoration.php'; require_once AKSRT_DIR . '/inc/helpers/class-aksrt-nav-walker.php'; require_once AKSRT_DIR . '/inc/helpers/aksrt-transactions.php'; require_once AKSRT_DIR . '/inc/helpers/aksrt-user-dashboard.php'; +require_once AKSRT_DIR . '/inc/helpers/aksrt-user-center.php'; require_once AKSRT_DIR . '/inc/helpers/aksrt-shortcodes.php'; // ============================================================ diff --git a/inc/classes/class-aksrt-user.php b/inc/classes/class-aksrt-user.php index 2674847b..3b99c586 100644 --- a/inc/classes/class-aksrt-user.php +++ b/inc/classes/class-aksrt-user.php @@ -880,60 +880,20 @@ add_action('template_redirect', function () { // AJAX: 保存用户个人资料(显示名称、简介、联系方式) // ============================================================ add_action('wp_ajax_aksrt_save_profile', function () { - // 验证 nonce - if ( ! isset( $_POST['_profile_nonce'] ) || ! wp_verify_nonce( $_POST['_profile_nonce'], 'aksrt_profile_save' ) ) { - wp_send_json_error( array( 'message' => __( '安全验证失败,请刷新页面重试', 'aksrt' ) ) ); + if (!isset($_POST['_profile_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['_profile_nonce'])), 'aksrt_profile_save')) { + wp_send_json_error(['message' => __('安全验证失败,请刷新页面重试', 'aksrt')]); } - $user_id = isset( $_POST['user_id'] ) ? (int) $_POST['user_id'] : 0; - if ( ! $user_id || $user_id !== get_current_user_id() ) { - wp_send_json_error( array( 'message' => __( '无权操作', 'aksrt' ) ) ); + $user_id = isset($_POST['user_id']) ? (int) $_POST['user_id'] : 0; + $result = function_exists('aksrt_save_user_center_profile') + ? aksrt_save_user_center_profile($user_id, $_POST) + : new WP_Error('aksrt_profile_unavailable', __('保存失败', 'aksrt')); + + if (is_wp_error($result)) { + wp_send_json_error(['message' => $result->get_error_message()]); } - $errors = array(); - - // === 更新昵称(display_name) === - if ( isset( $_POST['nickname'] ) ) { - $name = sanitize_text_field( $_POST['nickname'] ); - if ( mb_strlen( $name ) < 1 || mb_strlen( $name ) > 50 ) { - $errors[] = __( '昵称长度需在 1-50 字之间', 'aksrt' ); - } else { - wp_update_user( array( 'ID' => $user_id, 'display_name' => $name ) ); - } - } - - // === 更新个人简介 === - if ( isset( $_POST['description'] ) ) { - $desc = sanitize_textarea_field( $_POST['description'] ); - if ( mb_strlen( $desc ) > 500 ) { - $errors[] = __( '个人简介不能超过 500 字', 'aksrt' ); - } else { - wp_update_user( array( 'ID' => $user_id, 'description' => $desc ) ); - } - } - - // === 更新联系方式 === - $contacts = array(); - if ( isset( $_POST['contacts'] ) && is_array( $_POST['contacts'] ) ) { - foreach ( $_POST['contacts'] as $c ) { - $url = isset( $c['url'] ) ? esc_url_raw( $c['url'] ) : ''; - $icon = isset( $c['icon'] ) ? sanitize_key( $c['icon'] ) : ''; - $icon = preg_replace('/^(fab|fas|far)[\s-]*/', '', $icon); - $icon = preg_replace('/^fa[\s-]*/', '', $icon); - $icon = preg_replace('/^[\s-]+/', '', $icon); - $icon = trim( $icon, '- ' ); - if ( ! $url || ! $icon ) continue; - $name = aksrt_icon_key_to_name( $icon ); - $contacts[] = array( 'name' => $name, 'url' => $url, 'icon' => $icon ); - } - } - update_user_meta( $user_id, 'aksrt_user_contacts', $contacts ); - - if ( ! empty( $errors ) ) { - wp_send_json_error( array( 'message' => implode( '
', $errors ) ) ); - } - - wp_send_json_success( array( 'message' => __( '保存成功!', 'aksrt' ) ) ); + wp_send_json_success(['message' => __('保存成功!', 'aksrt')]); }); // ============================================================ diff --git a/inc/helpers/aksrt-user-center.php b/inc/helpers/aksrt-user-center.php new file mode 100644 index 00000000..c97505ab --- /dev/null +++ b/inc/helpers/aksrt-user-center.php @@ -0,0 +1,412 @@ + + */ + function aksrt_normalize_user_contacts($raw_contacts): array { + $contacts = []; + if (!is_array($raw_contacts)) { + return $contacts; + } + + foreach ($raw_contacts as $contact) { + if (!is_array($contact)) { + continue; + } + + $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_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; + } + } + + 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_user_contacts', aksrt_normalize_user_contacts($input['contacts'] ?? [])); + 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 { + $url = remove_query_arg([ + 'tab', 'posts_page', 'comments_page', 'favorites_page', 'credits_page', + 'post_status', 'saved', 'profile_error', + ], aksrt_get_user_center_url()); + + return add_query_arg('tab', sanitize_key($tab), $url); + } +} + +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 { + $path = AKSRT_DIR . '/inc/helpers/user-center-renderers/profile-' . sanitize_file_name($view) . '.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', 'posts', 'comments', 'favorites', 'credits', '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_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') + ? AKSRT_Settings::get_user_center_settings() + : [ + 'tabs' => ['profile', 'posts', 'comments', 'favorites'], + 'avatar_upload' => true, + 'cover_upload' => true, + 'social_links' => true, + ]; + $dashboard_tabs = class_exists('AKSRT_User_Center') + ? AKSRT_User_Center::get_instance()->get_dashboard_tabs() + : [ + 'profile' => ['label' => __('个人信息', 'aksrt'), 'icon' => 'user'], + 'posts' => ['label' => __('我的文章', 'aksrt'), 'icon' => 'file-alt'], + 'comments' => ['label' => __('我的评论', 'aksrt'), 'icon' => 'comment'], + 'favorites' => ['label' => __('我的收藏', 'aksrt'), 'icon' => 'star'], + 'settings' => ['label' => __('账号设置', 'aksrt'), 'icon' => 'cog'], + ]; + $dashboard_tabs = aksrt_normalize_user_center_tabs($dashboard_tabs); + $dashboard_tab_keys = array_keys($dashboard_tabs); + $default_dashboard_tab = in_array('profile', $dashboard_tab_keys, true) ? 'profile' : ($dashboard_tab_keys[0] ?? 'profile'); + $active_tab = isset($_GET['tab']) ? sanitize_key(wp_unslash($_GET['tab'])) : $default_dashboard_tab; + if ('signin' === $active_tab) { + $active_tab = 'credits'; + } elseif ('invite' === $active_tab) { + $active_tab = 'settings'; + } + if (!in_array($active_tab, $dashboard_tab_keys, true)) { + $active_tab = $default_dashboard_tab; + } + + $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' => false, + ]) + : []; + $target_user = $current_user; + $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_contacts = $dashboard['user_contacts'] ?? get_user_meta($user_id, 'aksrt_user_contacts', true); + $user_contacts = is_array($user_contacts) ? $user_contacts : []; + $profile_signals = $dashboard['profile_signals'] ?? []; + $user_background_url = (string) ($dashboard['background_url'] ?? get_user_meta($user_id, 'aksrt_background_image', true)); + $allow_bg_upload = !empty($user_center_settings['cover_upload']) && class_exists('AKSRT_Settings') && aksrt_get_setting('allow_background_upload', false); + $show_avatar_edit = !empty($user_center_settings['avatar_upload']); + $avatar_settings_url = admin_url('profile.php'); + $notification_unread_count = function_exists('aksrt_get_user_unread_notification_count') ? aksrt_get_user_unread_notification_count($user_id) : 0; + $messages_url = function_exists('aksrt_get_messages_url') + ? aksrt_get_messages_url() + : aksrt_get_user_center_url('notif'); + $per_page = 10; + $posts_page = max(1, isset($_GET['posts_page']) ? (int) $_GET['posts_page'] : 1); + $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); + $post_status_filter = isset($_GET['post_status']) ? sanitize_key(wp_unslash($_GET['post_status'])) : 'publish'; + if (!in_array($post_status_filter, ['all', 'publish', 'draft', 'pending', 'private'], true)) { + $post_status_filter = 'publish'; + } + $user_status_counts = ['publish' => 0, 'draft' => 0, 'pending' => 0, 'private' => 0]; + if ('posts' === $active_tab) { + global $wpdb; + $rows = $wpdb->get_results($wpdb->prepare( + "SELECT post_status, COUNT(*) AS num FROM {$wpdb->posts} WHERE post_author = %d AND post_type = 'post' AND post_status IN ('publish','draft','pending','private') GROUP BY post_status", + $user_id + )); + foreach ((array) $rows as $row) { + $user_status_counts[$row->post_status] = (int) $row->num; + } + $post_count = (int) array_sum($user_status_counts); + } + + $svg_empty_post = ''; + $svg_empty_comment = ''; + $svg_empty_favorite = ''; + require_once AKSRT_DIR . '/templates/parts/user-shared-helpers.php'; + + $vip_settings = class_exists('AKSRT_Settings') ? AKSRT_Settings::get_payment_vip_settings() : []; + $affiliate_settings = class_exists('AKSRT_Settings') ? AKSRT_Settings::get_payment_affiliate_settings() : []; + $orders_url = function_exists('aksrt_get_orders_page_url') ? aksrt_get_orders_page_url() : ''; + $show_vip = !empty($vip_settings['enabled']); + $show_credit_tabs = (bool) ($dashboard['credits_enabled'] ?? false); + $affiliate_enabled = $show_credit_tabs && !empty($affiliate_settings['enabled']); + $currency_name = (string) ($dashboard['currency_name'] ?? __('积分', 'aksrt')); + $tab_urls = []; + foreach ($dashboard_tab_keys as $tab_key) { + $tab_urls[$tab_key] = aksrt_get_user_center_tab_url($tab_key); + } + + $render_context = [ + 'user_center_settings' => $user_center_settings, + 'dashboard_tabs' => $dashboard_tabs, + 'dashboard_tab_keys' => $dashboard_tab_keys, + 'default_dashboard_tab' => $default_dashboard_tab, + 'active_tab' => $active_tab, + 'dashboard' => $dashboard, + 'target_user' => $target_user, + 'user_id' => $user_id, + 'post_count' => $post_count, + 'comment_count' => $comment_count, + 'favorite_post_ids' => $favorite_post_ids, + 'favorite_count' => $favorite_count, + 'user_contacts' => $user_contacts, + 'profile_signals' => $profile_signals, + 'user_background_url' => $user_background_url, + 'allow_bg_upload' => $allow_bg_upload, + 'show_avatar_edit' => $show_avatar_edit, + 'avatar_settings_url' => $avatar_settings_url, + 'notification_unread_count' => $notification_unread_count, + 'messages_url' => $messages_url, + 'per_page' => $per_page, + 'posts_page' => $posts_page, + 'comments_page' => $comments_page, + 'favorites_page' => $favorites_page, + 'post_status_filter' => $post_status_filter, + 'user_status_counts' => $user_status_counts, + 'svg_empty_post' => $svg_empty_post, + 'svg_empty_comment' => $svg_empty_comment, + 'svg_empty_favorite' => $svg_empty_favorite, + 'orders_url' => $orders_url, + 'show_vip' => $show_vip, + 'show_credit_tabs' => $show_credit_tabs, + 'affiliate_enabled' => $affiliate_enabled, + 'currency_name' => $currency_name, + 'credit_balance' => (float) ($dashboard['credit_balance'] ?? 0.0), + 'credits_enabled' => $show_credit_tabs, + 'vip_settings' => $vip_settings, + 'show_invite_section' => $show_credit_tabs + && class_exists('AKSRT_Settings') + && AKSRT_Settings::invite_enabled() + && AKSRT_Settings::invite_registration_enabled(), + 'user_email' => (string) $current_user->user_email, + ]; + $render_context['runtime_config'] = [ + 'ajaxUrl' => admin_url('admin-ajax.php'), + 'publicNonce' => wp_create_nonce('aksrt_public_nonce'), + 'settingsNonce' => wp_create_nonce('aksrt_account_settings'), + 'userId' => $user_id, + 'defaultTab' => $default_dashboard_tab, + 'activeTab' => $active_tab, + 'tabAliases' => ['signin' => 'credits', 'invite' => 'settings'], + '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'), + ], + ]; + $render_context['cover_upload_config'] = $allow_bg_upload ? [ + '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' => 2097152, + 'fallbackImage' => AKSRT_URI . '/assets/img/person_background.png', + 'idleText' => __('编辑', 'aksrt'), + 'loadingText' => __('上传中…', 'aksrt'), + 'tooLargeText' => __('图片大小不能超过 2MB', 'aksrt'), + 'successText' => __('背景图已更新', 'aksrt'), + 'errorText' => __('上传失败', 'aksrt'), + 'networkErrorText' => __('网络错误,请重试', 'aksrt'), + ] : []; + $render_context = aksrt_build_user_center_render_context($render_context); + + return $render_context; + } +} diff --git a/inc/helpers/aksrt-user-dashboard.php b/inc/helpers/aksrt-user-dashboard.php index 23389c74..ef8ddee8 100644 --- a/inc/helpers/aksrt-user-dashboard.php +++ b/inc/helpers/aksrt-user-dashboard.php @@ -14,7 +14,10 @@ defined('ABSPATH') || exit; * Get aggregated dashboard view data for a user. * * @param int $user_id Target user ID. - * @param array $args Optional. 'viewer_id' => current viewer ID. + * @param array $args Optional. Supported keys: + * - viewer_id: Current viewer ID. + * - include_recent_posts: Whether to query recent posts. + * - include_governance: Whether to resolve notice/report data. * @return array { * @type int $post_count Published post count. * @type int $comment_count Approved comment count. @@ -31,8 +34,10 @@ defined('ABSPATH') || exit; * } */ function aksrt_get_user_dashboard_view(int $user_id, array $args = []): array { - $viewer_id = (int) ($args['viewer_id'] ?? get_current_user_id()); - $is_own_profile = is_user_logged_in() && $viewer_id === $user_id; + $viewer_id = (int) ($args['viewer_id'] ?? get_current_user_id()); + $include_recent_posts = !array_key_exists('include_recent_posts', $args) || (bool) $args['include_recent_posts']; + $include_governance = !array_key_exists('include_governance', $args) || (bool) $args['include_governance']; + $is_own_profile = is_user_logged_in() && $viewer_id === $user_id; $post_count = (int) count_user_posts($user_id, 'post', true); $comment_count = (int) get_comments([ @@ -45,15 +50,17 @@ function aksrt_get_user_dashboard_view(int $user_id, array $args = []): array { ? aksrt_get_user_interaction_posts('_aksrt_favorite_posts', $user_id) : []; $favorite_count = count($favorite_post_ids); - $recent_posts = get_posts([ - 'author' => $user_id, - 'post_type' => 'post', - 'post_status' => 'publish', - 'posts_per_page' => 5, - 'orderby' => 'date', - 'order' => 'DESC', - 'no_found_rows' => true, - ]); + $recent_posts = $include_recent_posts + ? get_posts([ + 'author' => $user_id, + 'post_type' => 'post', + 'post_status' => 'publish', + 'posts_per_page' => 5, + 'orderby' => 'date', + 'order' => 'DESC', + 'no_found_rows' => true, + ]) + : []; $user_contacts = get_user_meta($user_id, 'aksrt_user_contacts', true) ?: []; if (!is_array($user_contacts)) { @@ -74,7 +81,9 @@ function aksrt_get_user_dashboard_view(int $user_id, array $args = []): array { ? AKSRT_Settings::get_currency_name() : __('积分', 'aksrt'); - $user_home_manager = class_exists('AKSRT_User_Home') ? AKSRT_User_Home::get_instance() : null; + $user_home_manager = $include_governance && class_exists('AKSRT_User_Home') + ? AKSRT_User_Home::get_instance() + : null; $profile_signals = $user_home_manager ? $user_home_manager->get_profile_signals($user_id) : []; $profile_notice = $user_home_manager ? $user_home_manager->get_profile_notice($user_id) : []; $show_report = $user_home_manager ? $user_home_manager->show_report_entry() : false; diff --git a/inc/helpers/user-center-renderers/profile-account-modals.php b/inc/helpers/user-center-renderers/profile-account-modals.php new file mode 100644 index 00000000..029c8304 --- /dev/null +++ b/inc/helpers/user-center-renderers/profile-account-modals.php @@ -0,0 +1,98 @@ + + + + + + diff --git a/inc/helpers/user-center-renderers/profile-comments.php b/inc/helpers/user-center-renderers/profile-comments.php new file mode 100644 index 00000000..f2b976b6 --- /dev/null +++ b/inc/helpers/user-center-renderers/profile-comments.php @@ -0,0 +1,46 @@ + + +
+
+

+ +

+
+ $user_id, + 'number' => $per_page, + 'offset' => ( $comments_page - 1 ) * $per_page, + 'status' => 'approve', + 'update_comment_meta_cache' => false, + ] ); + if ( $cs ) : ?> +
+ $c]); + endforeach; ?> +
+ + +
diff --git a/inc/helpers/user-center-renderers/profile-credits.php b/inc/helpers/user-center-renderers/profile-credits.php new file mode 100644 index 00000000..048a14a7 --- /dev/null +++ b/inc/helpers/user-center-renderers/profile-credits.php @@ -0,0 +1,177 @@ + + + + + +
+
+
+
+ + +
+
+ +
+ +
+ + +
+

+ +
+
+ + + +
+
+
+
+
+ +
+
+ + + + + + +
+
+ +
+ + +
+ +
+ + +
+ + + + + + +
+
+ + + +
+ +
+ + + get_recent_signins($user_id, 30); + $signin_dates = array_map(function ($entry) { + return $entry['signin_date'] ?? ''; + }, $signin_log); + $signin_dates = array_filter($signin_dates); + } + ?> + +
+

+ + +

+
+
+ + + +
+
+

+ + +

+
+ $user_id, 'page' => $credits_page, 'per_page' => 10 ] ); + } else { + $log = [ 'items' => [], 'total' => 0 ]; + } + if ( ! empty( $log['items'] ) ) : + ?> +
+ = 0 ? 'is-positive' : 'is-negative'; + ?> +
+
+ +
+
+
+ + + () + +
+
+ +
+
+
+ + 0 ? '+' : '' ) . $delta ); ?> + + + + +
+
+ +
+ 1 ) { + aksrt_render_profile_pagination( $total_pages, $credits_page, 'credits_page' ); + } + ?> + +
+
+ +
+

+
+ +
diff --git a/inc/helpers/user-center-renderers/profile-favorites.php b/inc/helpers/user-center-renderers/profile-favorites.php new file mode 100644 index 00000000..efc10fe0 --- /dev/null +++ b/inc/helpers/user-center-renderers/profile-favorites.php @@ -0,0 +1,65 @@ + + +
+
+

+ +

+
+ 'post', + 'post_status' => 'publish', + 'post__in' => $favorite_post_ids, + 'orderby' => 'post__in', + 'posts_per_page' => $per_page, + 'paged' => $favorites_page, + 'ignore_sticky_posts' => true, + 'no_found_rows' => false, + 'update_post_meta_cache' => false, + ] ); + if ( $favorites_query->have_posts() ) : ?> +
+ have_posts() ) : $favorites_query->the_post(); + get_template_part('templates/parts/content', 'profile-list-item', [ + 'action' => [ + 'type' => 'unfavorite', + 'url' => '#', + 'label' => __('取消收藏', 'aksrt'), + ], + ]); + endwhile; ?> +
+ max_num_pages, $favorites_page, 'favorites_page' ); ?> + +
diff --git a/inc/helpers/user-center-renderers/profile-form.php b/inc/helpers/user-center-renderers/profile-form.php new file mode 100644 index 00000000..b49cc8c1 --- /dev/null +++ b/inc/helpers/user-center-renderers/profile-form.php @@ -0,0 +1,66 @@ + + +
+ + + + + +
+

+ +

+
+ + +
+

+ +

+
+ + + +
+

+

+
+ $contact ) : ?> +
+ + + + +
+ +
+ +
+ + + +
+ + +
+
diff --git a/inc/helpers/user-center-renderers/profile-header.php b/inc/helpers/user-center-renderers/profile-header.php new file mode 100644 index 00000000..34741bb5 --- /dev/null +++ b/inc/helpers/user-center-renderers/profile-header.php @@ -0,0 +1,79 @@ + + +
+
+ +
+ + + + +
+
+
+ display_name ); ?> +
+
+

+ display_name ); ?> + +

+ description ) : ?> +

description ); ?>

+ + +
+ + + + + + +
+ +
+ + user_login ); ?> + + user_email ) : ?> + + user_email ); ?> + + +
+
+ +
+
diff --git a/inc/helpers/user-center-renderers/profile-posts.php b/inc/helpers/user-center-renderers/profile-posts.php new file mode 100644 index 00000000..f8c7f1db --- /dev/null +++ b/inc/helpers/user-center-renderers/profile-posts.php @@ -0,0 +1,101 @@ + + +
+
+

+ +

+ __( '已发布', 'aksrt' ), + 'draft' => __( '草稿', 'aksrt' ), + 'pending' => __( '待审', 'aksrt' ), + 'private' => __( '私密', 'aksrt' ), + 'all' => __( '全部', 'aksrt' ), + ]; + ?> +
+ $status_text ) : + $count_for_chip = ( 'all' === $status_key ) + ? $post_count + : ( $user_status_counts[ $status_key ] ?? 0 ); + if ( 'publish' !== $status_key && 'all' !== $status_key && $count_for_chip < 1 ) { + continue; + } + $chip_url = add_query_arg( [ + 'tab' => 'posts', + 'post_status' => $status_key, + 'posts_page' => 1, + ], remove_query_arg( [ 'posts_page' ] ) ); + $chip_active = ( $post_status_filter === $status_key ); + ?> + + + + + +
+
+ + $user_id, + 'post_status' => ( 'all' === $post_status_filter ) + ? [ 'publish', 'draft', 'pending', 'private' ] + : $post_status_filter, + 'posts_per_page' => $per_page, + 'paged' => $posts_page, + 'no_found_rows' => false, + 'update_post_meta_cache' => false, + 'update_post_term_cache' => true, + ]; + $q = new WP_Query( $ps_args ); + if ( $q->have_posts() ) : ?> +
+ have_posts() ) : $q->the_post(); + $st = get_post_status(); + $status_label = ''; + if ( 'publish' !== $st ) { + $st_map = [ + 'draft' => __( '草稿', 'aksrt' ), + 'pending' => __( '待审', 'aksrt' ), + 'private' => __( '私密', 'aksrt' ), + 'future' => __( '定时', 'aksrt' ), + ]; + $status_label = $st_map[ $st ] ?? $st; + } + + $edit_link = get_edit_post_link(); + get_template_part('templates/parts/content', 'profile-list-item', [ + 'action' => $edit_link ? ['type' => 'edit', 'url' => $edit_link, 'label' => __('编辑', 'aksrt')] : [], + 'status' => $status_label, + ]); + endwhile; ?> +
+ max_num_pages, $posts_page, 'posts_page' ); ?> + +
diff --git a/inc/helpers/user-center-renderers/profile-settings.php b/inc/helpers/user-center-renderers/profile-settings.php new file mode 100644 index 00000000..dc6e74a6 --- /dev/null +++ b/inc/helpers/user-center-renderers/profile-settings.php @@ -0,0 +1,191 @@ + + + get_frontend_bindings_data($user_id); + $user_email = $target_user->user_email ?: ''; + $binding_name_map = [ + 'github' => 'GitHub', + 'gitee' => 'Gitee', + 'qq' => 'QQ', + 'google' => 'Google', + ]; + $binding_rows = []; + foreach ( (array) ( $oauth_data['bindings'] ?? [] ) as $binding ) { + $key = $binding['key'] ?? ''; + if ( ! $key ) continue; + $binding_rows[ $key ] = [ + 'key' => $key, + 'name' => $binding_name_map[ $key ] ?? ( $binding['name'] ?? ucfirst( $key ) ), + 'icon' => $binding['icon'] ?? '', + 'is_bound' => true, + 'url' => '', + ]; + } + foreach ( (array) ( $oauth_data['available'] ?? [] ) as $binding ) { + $key = $binding['key'] ?? ''; + if ( ! $key ) continue; + $binding_rows[ $key ] = [ + 'key' => $key, + 'name' => $binding_name_map[ $key ] ?? ( $binding['name'] ?? ucfirst( $key ) ), + 'icon' => $binding['icon'] ?? '', + 'is_bound' => false, + 'url' => $binding['url'] ?? '', + ]; + } + ?> + + +
+
+ +

+
+ + +

+
+ + +
+ + + + + + + + + +
+ + + + + + + + + + + +
+ +

+ +
+ + +
+
+ +

+
+
+ +
+
+ +
+ + +
+
+ +
+ +
+
+ +
+ + +
+
+ +
+
+
+ + +
+
+ +

+
+
+
+
+ +

+ +

+
+
+ + -- +
+
+ +
+
+ + 0 + +
+
+ + 0 + +
+
+ + 0 + +
+
+
+
+

+ +
+
+
+ +
+
+
+
+
+ + + + + + + + + diff --git a/inc/helpers/user-center-renderers/profile-sidebar.php b/inc/helpers/user-center-renderers/profile-sidebar.php new file mode 100644 index 00000000..ca89d22c --- /dev/null +++ b/inc/helpers/user-center-renderers/profile-sidebar.php @@ -0,0 +1,94 @@ + + +
+
+
+ + + + + + + + + + + + +
+
+ + +
+
+
+ $tab ) : ?> + + + + + + + + + 0 ) : ?> + + + +
+
+ + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + diff --git a/templates/pages/page-profile.php b/templates/pages/page-profile.php index c0e08c0a..e9b2be44 100644 --- a/templates/pages/page-profile.php +++ b/templates/pages/page-profile.php @@ -3,1422 +3,77 @@ * Template Name: 用户中心 * Template Post Type: page * - * 单一页面模板,根据场景显示不同内容: - * - 登录用户查看自己 → 仪表盘面板(个人信息 / 文章 / 评论 / 收藏 / 通知 / 设置) - * - 查看其他用户主页 → 公开用户主页(头像 / 简介 / 文章 / 评论) + * Logged-in users manage their own account here. Public profiles are served by + * page-user-home.php and author.php. * * @package AKSRT */ -// 加载共享辅助函数(分页、空状态) -require_once AKSRT_DIR . '/templates/parts/user-shared-helpers.php'; +defined('ABSPATH') || exit; -get_header(); ?> - -
- - - $uid, 'display_name' => sanitize_text_field( $_POST['nickname'] ) ] ); - } - if ( isset( $_POST['description'] ) ) { - wp_update_user( [ 'ID' => $uid, 'description' => sanitize_textarea_field( $_POST['description'] ) ] ); - } - $contacts = []; - if ( isset( $_POST['contacts'] ) && is_array( $_POST['contacts'] ) ) { - foreach ( $_POST['contacts'] as $c ) { - $u = isset( $c['url'] ) ? esc_url_raw( $c['url'] ) : ''; - $i = isset( $c['icon'] ) ? sanitize_key( $c['icon'] ) : ''; - // 清理 FA5 类名前缀:fab fa-xxx / fabfa-xxx / fa-xxx → xxx - $i = preg_replace('/^(fab|fas|far)[\s-]*/', '', $i); - $i = preg_replace('/^fa[\s-]*/', '', $i); - $i = preg_replace('/^[\s-]+/', '', $i); - $i = trim( $i, '- ' ); - if ( ! $u || ! $i ) continue; - $n = aksrt_icon_key_to_name( $i ); - $contacts[] = [ 'name' => $n, 'url' => $u, 'icon' => $i ]; - } - } - update_user_meta( $uid, 'aksrt_user_contacts', $contacts ); - } - wp_safe_redirect( add_query_arg( 'saved', '1', aksrt_get_user_center_url() ) ); +if (!is_user_logged_in()) { + wp_safe_redirect(aksrt_get_login_url(aksrt_get_user_center_url())); exit; } -// ─── 确定目标用户 ───────────────────────────────────────────── -// 来源: /user/{昵称} (自定义重写规则) -// 注意: get_queried_object_id() 在页面中返回的是页面 ID 而非用户 ID -$target_user_id = 0; -$target_user_nicename = get_query_var('user_nicename'); +$current_user = wp_get_current_user(); +$user_id = (int) $current_user->ID; -if ( $target_user_nicename ) { - $user = get_user_by( 'slug', $target_user_nicename ); - if ( $user ) { - $target_user_id = $user->ID; - } +$requested_nicename = get_query_var('user_nicename'); +if ($requested_nicename && $requested_nicename !== $current_user->user_nicename) { + $requested_user = get_user_by('slug', $requested_nicename); + wp_safe_redirect($requested_user && function_exists('aksrt_get_user_home_url') + ? aksrt_get_user_home_url($requested_user) + : aksrt_get_user_center_url()); + exit; } -// 兜底:检查 queried object -if ( ! $target_user_id ) { - $qid = get_queried_object_id(); - if ( $qid && get_userdata( $qid ) ) { - $target_user_id = $qid; - } -} - -$current_user = wp_get_current_user(); -$target_user = $target_user_id ? get_userdata( $target_user_id ) : null; -$is_own_profile = false; - -if ( ! $target_user ) { - if ( is_user_logged_in() ) { - $target_user = $current_user; - $target_user_id = $current_user->ID; - $is_own_profile = true; +if ('POST' === ($_SERVER['REQUEST_METHOD'] ?? '') && isset($_POST['action']) && 'aksrt_save_profile' === sanitize_key(wp_unslash($_POST['action']))) { + $nonce = isset($_POST['_profile_nonce']) ? sanitize_text_field(wp_unslash($_POST['_profile_nonce'])) : ''; + if (!wp_verify_nonce($nonce, 'aksrt_profile_save')) { + $result = new WP_Error('aksrt_profile_nonce', __('安全验证失败,请刷新页面重试', 'aksrt')); } else { - wp_safe_redirect( aksrt_get_login_url( aksrt_get_user_center_url() ) ); - exit; + $result = aksrt_save_user_center_profile($user_id, $_POST); } -} elseif ( is_user_logged_in() && (int) $current_user->ID === (int) $target_user->ID ) { - $is_own_profile = true; -} -if ( $target_user_nicename && ! $is_own_profile && function_exists( 'aksrt_get_user_home_url' ) ) { - wp_safe_redirect( aksrt_get_user_home_url( $target_user ) ); + $redirect_args = is_wp_error($result) + ? ['profile_error' => $result->get_error_message()] + : ['saved' => '1']; + wp_safe_redirect(add_query_arg($redirect_args, aksrt_get_user_center_tab_url('profile'))); exit; } -$user_id = $target_user->ID; -$post_count = (int) count_user_posts( $user_id, 'post', true ); -$comment_count = (int) get_comments( [ 'user_id' => $user_id, 'count' => true, 'status' => 'approve' ] ); +$render_context = aksrt_get_user_center_render_context($current_user); -// 加载用户自定义联系方式 -$user_contacts = get_user_meta( $user_id, 'aksrt_user_contacts', true ) ?: []; -if ( ! is_array( $user_contacts ) ) $user_contacts = []; -$user_center_settings = class_exists( 'AKSRT_Settings' ) - ? AKSRT_Settings::get_user_center_settings() - : [ - 'tabs' => [ 'profile', 'posts', 'comments', 'favorites' ], - 'avatar_upload' => true, - 'cover_upload' => true, - 'social_links' => true, - ]; -$user_home_settings = class_exists( 'AKSRT_Settings' ) - ? AKSRT_Settings::get_user_home_settings() - : [ - 'tabs' => [ 'posts', 'comments', 'favorites' ], - 'sidebar' => true, - 'cover' => true, - 'follow' => true, - 'posts_per_page' => 10, - ]; -$user_home_manager = class_exists( 'AKSRT_User_Home' ) ? AKSRT_User_Home::get_instance() : null; -$profile_signals = $user_home_manager ? $user_home_manager->get_profile_signals( $user_id ) : []; -$profile_notice = $user_home_manager ? $user_home_manager->get_profile_notice( $user_id ) : []; -$show_report_button = $user_home_manager ? $user_home_manager->show_report_entry() : false; -$public_visible_tabs = $user_home_manager - ? $user_home_manager->get_visible_tabs( false ) - : [ - 'posts' => [ 'label' => __( '文章', 'aksrt' ) ], - 'comments' => [ 'label' => __( '评论', 'aksrt' ) ], - 'favorites' => [ 'label' => __( '收藏', 'aksrt' ) ], - ]; - -// 用户自定义背景图 -$user_background_url = get_user_meta( $user_id, 'aksrt_background_image', true ) ?: ''; -$allow_bg_upload = $is_own_profile - && ! empty( $user_center_settings['cover_upload'] ) - && class_exists( 'AKSRT_Settings' ) - && aksrt_get_setting( 'allow_background_upload', false ); -$show_avatar_edit = $is_own_profile && ! empty( $user_center_settings['avatar_upload'] ); -$avatar_settings_url = admin_url( 'profile.php' ); -$public_home_url = function_exists( 'aksrt_get_user_home_url' ) ? aksrt_get_user_home_url( $target_user ) : home_url( '/' ); - -$favorite_post_ids = function_exists( 'aksrt_get_user_interaction_posts' ) - ? aksrt_get_user_interaction_posts( '_aksrt_favorite_posts', $user_id ) - : []; -$favorite_count = count( $favorite_post_ids ); -$notification_unread_count = $is_own_profile && function_exists( 'aksrt_get_user_unread_notification_count' ) - ? aksrt_get_user_unread_notification_count( $user_id ) - : 0; -$dashboard_tabs = class_exists( 'AKSRT_User_Center' ) - ? AKSRT_User_Center::get_instance()->get_dashboard_tabs() - : [ - 'profile' => [ 'label' => __( '个人信息', 'aksrt' ), 'icon' => 'user' ], - 'posts' => [ 'label' => __( '我的文章', 'aksrt' ), 'icon' => 'file-alt' ], - 'comments' => [ 'label' => __( '我的评论', 'aksrt' ), 'icon' => 'comment' ], - 'favorites' => [ 'label' => __( '我的收藏', 'aksrt' ), 'icon' => 'star' ], - 'settings' => [ 'label' => __( '账号设置', 'aksrt' ), 'icon' => 'cog' ], - ]; -$dashboard_tab_keys = array_keys( $dashboard_tabs ); -$public_tab_keys = array_keys( $public_visible_tabs ); -if ( empty( $public_tab_keys ) ) { - $public_visible_tabs = [ - 'posts' => [ 'label' => __( '文章', 'aksrt' ) ], - 'comments' => [ 'label' => __( '评论', 'aksrt' ) ], - 'favorites' => [ 'label' => __( '收藏', 'aksrt' ) ], - ]; - $public_tab_keys = array_keys( $public_visible_tabs ); -} -$public_tab_labels = [ - 'posts' => __( '文章', 'aksrt' ), - 'comments' => __( '评论', 'aksrt' ), - 'favorites' => __( '收藏', 'aksrt' ), -]; -foreach ( $public_visible_tabs as $public_tab_key => $public_tab_config ) { - if ( ! empty( $public_tab_config['label'] ) ) { - $public_tab_labels[ $public_tab_key ] = (string) $public_tab_config['label']; - } -} -$public_tab_counts = [ - 'posts' => $post_count, - 'comments' => $comment_count, - 'favorites' => $favorite_count, -]; - -// 消息中心页面 URL -$messages_page = get_pages( [ 'meta_key' => '_wp_page_template', 'meta_value' => 'templates/pages/page-messages.php', 'number' => 1 ] ); -$messages_url = ! empty( $messages_page ) ? get_permalink( $messages_page[0] ) : aksrt_get_user_center_url( 'notif' ); - -// ─── 文章状态计数(仅自己模式;用于状态筛选 chip) ──────────── -$user_status_counts = [ - 'publish' => 0, - 'draft' => 0, - 'pending' => 0, - 'private' => 0, -]; -if ( $is_own_profile ) { - global $wpdb; - $rows = $wpdb->get_results( $wpdb->prepare( - "SELECT post_status, COUNT(*) AS num FROM {$wpdb->posts} - WHERE post_author = %d AND post_type = 'post' - AND post_status IN ('publish','draft','pending','private') - GROUP BY post_status", - $user_id - ) ); - foreach ( (array) $rows as $row ) { - $user_status_counts[ $row->post_status ] = (int) $row->num; - } - $post_count = (int) array_sum( $user_status_counts ); -} - -// ─── 分页与筛选参数(自己 / 他人 共享) ──────────────────────── -$per_page = $is_own_profile ? 10 : max( 5, (int) ( $user_home_settings['posts_per_page'] ?? 10 ) ); -$default_dashboard_tab = in_array( 'profile', $dashboard_tab_keys, true ) ? 'profile' : ( $dashboard_tab_keys[0] ?? 'profile' ); -$active_tab = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : $default_dashboard_tab; -$public_default_tab = $public_tab_keys[0] ?? 'posts'; -$public_active_tab = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : $public_default_tab; -// 兼容旧链接:签到 / 邀请已经并入新面板 -if ( 'signin' === $active_tab ) { - $active_tab = 'credits'; -} elseif ( 'invite' === $active_tab ) { - $active_tab = 'settings'; -} -if ( ! in_array( $active_tab, $dashboard_tab_keys, true ) ) { - $active_tab = $default_dashboard_tab; -} -if ( ! in_array( $public_active_tab, $public_tab_keys, true ) ) { - $public_active_tab = $public_default_tab; -} -$posts_page = max( 1, isset( $_GET['posts_page'] ) ? (int) $_GET['posts_page'] : 1 ); -$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 ); - -$valid_post_status = [ 'all', '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, $valid_post_status, true ) ) { - $post_status_filter = 'publish'; -} - -// 空状态图标 SVG(提取以减少重复) -$svg_empty_post = ''; -$svg_empty_comment = ''; -$svg_empty_favorite = ''; +get_header(); ?> +
+ -
- - - -
- - 'GitHub', - 'gitee' => 'Gitee', - 'qq' => 'QQ', - 'google' => 'Google', - ]; - ?> - - - - - -
-
- -
- - - - -
-
-
- display_name ); ?> -
-
-

- display_name ); ?> - -

- description ) : ?> -

description ); ?>

- - -
- - - - - - -
- -
- - user_login ); ?> - - user_email ) : ?> - - user_email ); ?> - - -
-
- -
-
- - -
- - - - -
- +
+
+ $render_context]); ?> +
+
- - -
- $tab ) : ?> - - -
- - - -
> -
- - - - -
-

- -

-
- - -
-

- -

-
- - - -
-

-

-
- $contact ) : ?> -
- - - - -
- -
- -
+ +
+ + - - -
- - -
-
-
- - -
> -
-
-

- -

- __( '已发布', 'aksrt' ), - 'draft' => __( '草稿', 'aksrt' ), - 'pending' => __( '待审', 'aksrt' ), - 'private' => __( '私密', 'aksrt' ), - 'all' => __( '全部', 'aksrt' ), - ]; - ?> -
- $status_text ) : - $count_for_chip = ( 'all' === $status_key ) - ? $post_count - : ( $user_status_counts[ $status_key ] ?? 0 ); - if ( 'publish' !== $status_key && 'all' !== $status_key && $count_for_chip < 1 ) { - continue; - } - $chip_url = add_query_arg( [ - 'tab' => 'posts', - 'post_status' => $status_key, - 'posts_page' => 1, - ], remove_query_arg( [ 'posts_page' ] ) ); - $chip_active = ( $post_status_filter === $status_key ); - ?> - - - - - -
-
- - $user_id, - 'post_status' => ( 'all' === $post_status_filter ) - ? [ 'publish', 'draft', 'pending', 'private' ] - : $post_status_filter, - 'posts_per_page' => $per_page, - 'paged' => $posts_page, - 'no_found_rows' => false, - 'update_post_meta_cache' => false, - 'update_post_term_cache' => true, - ]; - $q = new WP_Query( $ps_args ); - if ( $q->have_posts() ) : ?> -
- have_posts() ) : $q->the_post(); - $st = get_post_status(); - $status_label = ''; - if ( 'publish' !== $st ) { - $st_map = [ - 'draft' => __( '草稿', 'aksrt' ), - 'pending' => __( '待审', 'aksrt' ), - 'private' => __( '私密', 'aksrt' ), - 'future' => __( '定时', 'aksrt' ), - ]; - $status_label = $st_map[ $st ] ?? $st; - } - set_query_var( 'aksrt_profile_item_status', $status_label ); - - $edit_link = get_edit_post_link(); - set_query_var( 'aksrt_profile_item_action', $edit_link ? [ - 'type' => 'edit', - 'url' => $edit_link, - 'label' => __( '编辑', 'aksrt' ), - ] : [] ); - - get_template_part( 'templates/parts/content', 'profile-list-item' ); - endwhile; ?> -
- max_num_pages, $posts_page, 'posts_page' ); ?> - -
-
- - -
> -
-
-

- -

-
- $user_id, - 'number' => $per_page, - 'offset' => ( $comments_page - 1 ) * $per_page, - 'status' => 'approve', - 'update_comment_meta_cache' => false, - ] ); - if ( $cs ) : ?> -
- -
- - -
-
- - -
> -
-
-

- -

-
- 'post', - 'post_status' => 'publish', - 'post__in' => $favorite_post_ids, - 'orderby' => 'post__in', - 'posts_per_page' => $per_page, - 'paged' => $favorites_page, - 'ignore_sticky_posts' => true, - 'no_found_rows' => false, - 'update_post_meta_cache' => false, - ] ); - if ( $favorites_query->have_posts() ) : ?> -
- have_posts() ) : $favorites_query->the_post(); - set_query_var( 'aksrt_profile_item_action', [ - 'type' => 'unfavorite', - 'url' => '#', - 'label' => __( '取消收藏', 'aksrt' ), - ] ); - set_query_var( 'aksrt_profile_item_status', '' ); - get_template_part( 'templates/parts/content', 'profile-list-item' ); - endwhile; ?> -
- max_num_pages, $favorites_page, 'favorites_page' ); ?> - -
-
- - - -
> - - -
-
-
-
- - -
-
- -
- -
- - -
-

- -
-
- - - -
-
-
-
-
- -
-
- - - - - - -
-
- -
- - -
- -
- - -
- - - - - - -
-
- - - - - - - get_recent_signins($user_id, 30); - $signin_dates = array_map(function ($entry) { - return $entry['signin_date'] ?? ''; - }, $signin_log); - $signin_dates = array_filter($signin_dates); - } - ?> - - - - - -
-
-

- - -

-
- $user_id, 'page' => $credits_page, 'per_page' => 10 ] ); - } else { - $log = [ 'items' => [], 'total' => 0 ]; - } - if ( ! empty( $log['items'] ) ) : - ?> -
- = 0 ? 'is-positive' : 'is-negative'; - ?> -
-
- -
-
-
- - - () - -
-
- -
-
-
- - 0 ? '+' : '' ) . $delta ); ?> - - - - -
-
- -
- 1 ) { - aksrt_render_profile_pagination( $total_pages, $credits_page, 'credits_page' ); - } - ?> - -
-
- -
-

-
- -
-
- - -
> - get_frontend_bindings_data($user_id); - $user_email = $target_user->user_email ?: ''; - $binding_name_map = [ - 'github' => 'GitHub', - 'gitee' => 'Gitee', - 'qq' => 'QQ', - 'google' => 'Google', - ]; - $binding_rows = []; - foreach ( (array) ( $oauth_data['bindings'] ?? [] ) as $binding ) { - $key = $binding['key'] ?? ''; - if ( ! $key ) continue; - $binding_rows[ $key ] = [ - 'key' => $key, - 'name' => $binding_name_map[ $key ] ?? ( $binding['name'] ?? ucfirst( $key ) ), - 'icon' => $binding['icon'] ?? '', - 'is_bound' => true, - 'url' => '', - ]; - } - foreach ( (array) ( $oauth_data['available'] ?? [] ) as $binding ) { - $key = $binding['key'] ?? ''; - if ( ! $key ) continue; - $binding_rows[ $key ] = [ - 'key' => $key, - 'name' => $binding_name_map[ $key ] ?? ( $binding['name'] ?? ucfirst( $key ) ), - 'icon' => $binding['icon'] ?? '', - 'is_bound' => false, - 'url' => $binding['url'] ?? '', - ]; - } - ?> - - -
-
- -

-
- - -

-
- - -
- - - - - - - - - -
- - - - - - - - - - - -
- -

- -
- - -
-
- -

-
-
- -
-
- -
- - -
-
- -
- -
-
- -
- - -
-
- -
-
-
- - -
-
- -

-
-
-
-
- -

- -

-
-
- - -- -
-
- -
-
- - 0 - -
-
- - 0 - -
-
- - 0 - -
-
-
-
-

- -
-
-
- -
-
-
-
-
- - - - - - - - - -
- - - - - - -
-
-
- - - -
> -
- display_name ); ?> -

display_name ); ?>

- - -
- - - - - + $render_context]); ?> + + $panel]); ?> + $render_context]); ?>
- - -
- - - - - - - - - -
- +
- -
- description ) : ?> -

description ) ); ?>

- -

- -
- - - - - - - - - - - - - - - - - -
-
-
- - -
- -
- - -
-
- - - -
- - -
> - $user_id, - 'posts_per_page' => $per_page, - 'post_status' => 'publish', - 'paged' => $posts_page, - 'no_found_rows' => false, - 'update_post_meta_cache' => false, - ] ); - if ( $author_posts->have_posts() ) : ?> -
- have_posts() ) : $author_posts->the_post(); - set_query_var( 'aksrt_profile_item_action', [ - 'type' => 'view', - 'url' => get_permalink(), - 'label' => __( '阅读', 'aksrt' ), - ] ); - set_query_var( 'aksrt_profile_item_status', '' ); - get_template_part( 'templates/parts/content', 'profile-list-item' ); - endwhile; ?> -
- max_num_pages, $posts_page, 'posts_page' ); - wp_reset_postdata(); - set_query_var( 'aksrt_profile_item_action', [] ); - else : - aksrt_render_profile_empty( $svg_empty_post, __( 'TA 还没有发布文章', 'aksrt' ) ); - endif; ?> -
- - - -
> - $user_id, - 'status' => 'approve', - 'number' => $per_page, - 'offset' => ( $comments_page - 1 ) * $per_page, - 'type' => 'comment', - 'update_comment_meta_cache' => false, - ] ); - if ( $user_comments ) : ?> -
- -
- - -
- - - -
> - 'post', - 'post__in' => $favorite_post_ids, - 'orderby' => 'post__in', - 'posts_per_page' => $per_page, - 'paged' => $favorites_page, - 'no_found_rows' => false, - 'update_post_meta_cache' => false, - ] ); - } else { - $favorite_query = null; - } - - if ( $favorite_query && $favorite_query->have_posts() ) : - ?> -
- have_posts() ) : $favorite_query->the_post(); - set_query_var( 'aksrt_profile_item_action', [ - 'type' => 'view', - 'url' => get_permalink(), - 'label' => __( '阅读', 'aksrt' ), - ] ); - set_query_var( 'aksrt_profile_item_status', '' ); - get_template_part( 'templates/parts/content', 'profile-list-item' ); - endwhile; ?> -
- max_num_pages, $favorites_page, 'favorites_page' ); - wp_reset_postdata(); - set_query_var( 'aksrt_profile_item_action', [] ); - else : - aksrt_render_profile_empty( $svg_empty_favorite, __( 'TA 还没有公开收藏内容', 'aksrt' ) ); - endif; ?> -
- -
- - - - - - - - - - - + $render_context]); ?>
have_posts()) : $author_posts->the_post(); ?> 'view', - 'url' => get_permalink(), - 'label' => __('阅读', 'aksrt'), - ]); - set_query_var('aksrt_profile_item_status', ''); - get_template_part('templates/parts/content', 'profile-list-item'); + get_template_part('templates/parts/content', 'profile-list-item', [ + 'action' => [ + 'type' => 'view', + 'url' => get_permalink(), + 'label' => __('阅读', 'aksrt'), + ], + ]); ?>
max_num_pages, $posts_page, 'posts_page'); wp_reset_postdata(); - set_query_var('aksrt_profile_item_action', []); else : aksrt_render_profile_empty($svg_empty_post, __('TA 还没有发布文章', 'aksrt')); endif; @@ -211,11 +210,9 @@ $home_url = function_exists('aksrt_get_user_home_url') ? aksrt_get_user_home_url
$c]); ?> -
have_posts()) : $favorites_query->the_post(); ?> 'view', - 'url' => get_permalink(), - 'label' => __('阅读', 'aksrt'), + get_template_part('templates/parts/content', 'profile-list-item', [ + 'action' => [ + 'type' => 'view', + 'url' => get_permalink(), + 'label' => __('阅读', 'aksrt'), + ], ]); - set_query_var('aksrt_profile_item_status', ''); - get_template_part('templates/parts/content', 'profile-list-item'); ?>
max_num_pages, $favorites_page, 'favorites_page'); wp_reset_postdata(); - set_query_var('aksrt_profile_item_action', []); else : aksrt_render_profile_empty($svg_empty_favorite, __('TA 还没有收藏文章', 'aksrt')); endif; diff --git a/templates/parts/content-profile-comment-item.php b/templates/parts/content-profile-comment-item.php index dc54da53..d4c0c4a3 100644 --- a/templates/parts/content-profile-comment-item.php +++ b/templates/parts/content-profile-comment-item.php @@ -2,7 +2,7 @@ /** * Profile Comment List Item — image-less comment row for user center. * - * 通过 set_query_var() 接收: + * 通过 get_template_part() 的 $args 接收(保留 query var 兼容): * - aksrt_profile_comment: WP_Comment 对象 * * @package AKSRT @@ -10,7 +10,7 @@ defined('ABSPATH') || exit; -$comment = get_query_var('aksrt_profile_comment'); +$comment = $args['comment'] ?? get_query_var('aksrt_profile_comment'); if (!$comment instanceof WP_Comment) { return; } diff --git a/templates/parts/content-profile-list-item.php b/templates/parts/content-profile-list-item.php index a0d7c258..fd999ec1 100644 --- a/templates/parts/content-profile-list-item.php +++ b/templates/parts/content-profile-list-item.php @@ -5,7 +5,7 @@ * 用于: * - 我的文章 / 我的收藏 / 公开主页文章列表 * - * 通过 set_query_var() 接收以下参数(可选): + * 通过 get_template_part() 的 $args 接收以下参数(保留 query var 兼容): * - aksrt_profile_item_action: ['type'=>'edit'|'view'|'unfavorite', 'url'=>'...', 'label'=>'...'] * - aksrt_profile_item_status: 文章状态徽章文本(如 "草稿" / "私密") * @@ -14,7 +14,7 @@ defined('ABSPATH') || exit; -$action = get_query_var('aksrt_profile_item_action', []); +$action = isset($args['action']) ? $args['action'] : get_query_var('aksrt_profile_item_action', []); if (!is_array($action)) { $action = []; } @@ -22,7 +22,7 @@ $action_type = isset($action['type']) ? (string) $action['type'] : ''; $action_url = isset($action['url']) ? (string) $action['url'] : ''; $action_label = isset($action['label']) ? (string) $action['label'] : ''; -$status_label = (string) get_query_var('aksrt_profile_item_status', ''); +$status_label = isset($args['status']) ? (string) $args['status'] : (string) get_query_var('aksrt_profile_item_status', ''); $post_id = get_the_ID(); $permalink = get_permalink(); diff --git a/templates/parts/user-center/account-modals.php b/templates/parts/user-center/account-modals.php new file mode 100644 index 00000000..25331fde --- /dev/null +++ b/templates/parts/user-center/account-modals.php @@ -0,0 +1,5 @@ + +
+ + + +
diff --git a/templates/parts/user-center/panel-comments.php b/templates/parts/user-center/panel-comments.php new file mode 100644 index 00000000..33c615ca --- /dev/null +++ b/templates/parts/user-center/panel-comments.php @@ -0,0 +1,8 @@ + +
data-panel-loaded="" data-tab-url=""> + +
diff --git a/templates/parts/user-center/panel-credits.php b/templates/parts/user-center/panel-credits.php new file mode 100644 index 00000000..ed5af753 --- /dev/null +++ b/templates/parts/user-center/panel-credits.php @@ -0,0 +1,8 @@ + +
data-panel-loaded="" data-tab-url=""> + +
diff --git a/templates/parts/user-center/panel-favorites.php b/templates/parts/user-center/panel-favorites.php new file mode 100644 index 00000000..a3ccacfa --- /dev/null +++ b/templates/parts/user-center/panel-favorites.php @@ -0,0 +1,8 @@ + +
data-panel-loaded="" data-tab-url=""> + +
diff --git a/templates/parts/user-center/panel-posts.php b/templates/parts/user-center/panel-posts.php new file mode 100644 index 00000000..9202a5fe --- /dev/null +++ b/templates/parts/user-center/panel-posts.php @@ -0,0 +1,8 @@ + +
data-panel-loaded="" data-tab-url=""> + +
diff --git a/templates/parts/user-center/panel-profile.php b/templates/parts/user-center/panel-profile.php new file mode 100644 index 00000000..caddeb08 --- /dev/null +++ b/templates/parts/user-center/panel-profile.php @@ -0,0 +1,8 @@ + +
data-panel-loaded="" data-tab-url=""> + +
diff --git a/templates/parts/user-center/panel-settings.php b/templates/parts/user-center/panel-settings.php new file mode 100644 index 00000000..f9a765a6 --- /dev/null +++ b/templates/parts/user-center/panel-settings.php @@ -0,0 +1,8 @@ + +
data-panel-loaded="" data-tab-url=""> + +
diff --git a/templates/parts/user-center/panel.php b/templates/parts/user-center/panel.php new file mode 100644 index 00000000..53045092 --- /dev/null +++ b/templates/parts/user-center/panel.php @@ -0,0 +1,14 @@ + +
data-panel-loaded="" data-tab-url=""> + +
diff --git a/templates/parts/user-center/runtime-config.php b/templates/parts/user-center/runtime-config.php new file mode 100644 index 00000000..82c3ebad --- /dev/null +++ b/templates/parts/user-center/runtime-config.php @@ -0,0 +1,9 @@ + + + + + diff --git a/templates/parts/user-center/sidebar.php b/templates/parts/user-center/sidebar.php new file mode 100644 index 00000000..c1dea68d --- /dev/null +++ b/templates/parts/user-center/sidebar.php @@ -0,0 +1,5 @@ +