Files
wordpress-theme-aksrt/inc/helpers/header/aksrt-header-navbar.php
T
kate522 6f815678ec feat(frontend): replicate reference header user box 1:1
顶导航点击小人弹出的用户信息浮层,从 AKSRT 定制结构改为与参考主题 zib_header_user_box() 逐行同构:头像链接包裹、钱包双瓷砖、会员条、签到、消息图标、统计徽章行全部改用参考主题遗留类,渲染交回 main.css(.sub-user-box 242px、.avatar-img 38px、.msg-news-icon、badge.top、.payvip-icon 深色渐变加 gem 水印、.vip-expdate-tag、.muted-box)。

头像改走 aksrt_get_avatar_box(用户ID, avatar-img, true, false),得到参考的 a 链接包裹 avatar-img 结构。消息图标改参考标记 msg-news-icon abs-right 加 toggle-radius msg-icon,未读徽章用 badge.top,avatar_dropdown 位置门控契约保留。管理端行在「后台管理」前插入「支付中心」,跳 admin.php?page=aksrt_pay,即参考主题「商城中心」槽位,图标 fa-credit-card。

钱包重写为参考双瓷砖:余额在前(c-blue-2 加 fa-money-bill-wave)、积分在后(c-yellow 加 fa-coins),分别按充值开关与积分总开关门控,两块皆关则不渲染,并保留 data-aksrt-wallet-value 与 data-aksrt-points-value 两个 JS 刷新钩子。会员条去掉 AKSRT 自创的 vipbg-v2 level-N 金色背景,改参考的 payvip-icon 加 badg jb-yellow vip-expdate-tag,到期文案按参考语义分为已过期、永久会员、裸日期三种。签到入口由 button 改为 a.but block c-yellow,并去掉随之失效的 compact 形参,data-signin 挂载与 wp_footer 验证码不变。统计行删掉文章徽章(本站不做帖子),图标由 SVG sprite 换 FA5+ 的 fa-comment-dots、fa-thumbs-up、fa-fire。

会员等级图标契约不动:aksrt_get_vip_icon 与 User\Level::get_badge_html 的调用与参数保持原样。

顺带清死代码:aksrt_header_user_badges_row 原本取到函数后直接返回空串,现在真正输出统计行;删掉 header-userbox.css 里整套 bespoke 规则与 vip-modal.css 的 vipbg-v2 死规则。

显式偏差:统计行链接保持扁平用户主页 URL(AKSRT 用户主页是干净 URL 加 JS tab,没有 query tab 路由);签到入口沿用积分签到总开关门控,不新增管理端可配置项。

public.css 与 public.min.css 用 tools/build-public-assets.mjs 重建:该 bundle 打包了 header-userbox.css 且作为依赖先于单模块入队,旧的 width 260px 会压过新源码、把卡片钉在 260px 而不是参考的 242px。

验证:node --test tests/**/*.test.cjs 573 项全通过;六个改动 PHP 文件 php -l 无语法错误;卡片用到的 FA5 token 逐个核对 5.15.4 全部命中。未做真实视觉效果验收。
2026-10-10 16:17:01 +08:00

386 lines
18 KiB
PHP

<?php
/**
* Desktop navbar pieces (legacy navbar header / menu / buttons).
*
* @package AKSRT
*/
defined('ABSPATH') || exit;
if (!function_exists('aksrt_menu_items')) :
/**
* PC top menu (legacy).
*
* @param string $location Menu location.
* @param bool $echo Echo or return.
* @return string|void
*/
function aksrt_menu_items(string $location = 'primary', bool $echo = true) {
$walker = class_exists('\AKSRT\Frontend\NavWalker')
? new \AKSRT\Frontend\NavWalker()
: null;
$args = [
'container' => false,
'echo' => false,
'fallback_cb' => false,
'items_wrap' => '<ul class="nav navbar-nav">%3$s</ul>',
'theme_location' => $location,
];
if ($walker) {
$args['walker'] = $walker;
}
if (!wp_is_mobile()) {
$args['depth'] = 0;
} else {
$args['depth'] = 3;
}
$menu = '';
if (has_nav_menu($location)) {
$menu = (string) wp_nav_menu($args);
}
if ($menu === '' && is_super_admin()) {
$menu = '<ul class="nav navbar-nav"><li><a href="' . esc_url(admin_url('nav-menus.php')) . '">'
. esc_html__('添加导航菜单', 'aksrt') . '</a></li></ul>';
}
if ($echo) {
echo $menu; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
return;
}
return $menu;
}
endif;
if (!function_exists('aksrt_navbar_header')) :
/**
* Logo + mobile toggles (legacy).
*
* @param array<string,mixed> $ctx
*/
function aksrt_navbar_header(array $ctx): void {
$brand_name = (string) ($ctx['brand_name'] ?? get_bloginfo('name'));
$logo_inner = !empty($ctx['logo_html'])
? $ctx['logo_html']
: '<span class="navbar-logo-text">' . esc_html($brand_name) . '</span>';
$logo = '<a class="navbar-logo" href="' . esc_url(home_url('/')) . '">' . $logo_inner . '</a>';
$button = '<button type="button" data-toggle-class="mobile-navbar-show" data-target="body" class="navbar-toggle" aria-label="'
. esc_attr__('打开菜单', 'aksrt') . '">';
$button .= '<i class="em12 css-icon i-menu"><i></i></i></button>';
$button .= '<a href="javascript:;" class="navbar-toggle main-search-btn" aria-label="'
. esc_attr__('打开搜索', 'aksrt') . '">';
$button .= '<svg class="icon" aria-hidden="true"><use xlink:href="#icon-search"></use></svg></a>';
echo '<div class="navbar-header">';
echo '<div class="navbar-brand">' . $logo . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $button; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '</div>';
}
endif;
if (!function_exists('aksrt_get_menu_search')) :
/**
* Layout-2 inline search form (legacy simplified).
*
* @param array<string,mixed> $ctx
*/
function aksrt_get_menu_search(array $ctx): void {
if (is_search()) {
return;
}
$placeholder = (string) ($ctx['search_placeholder'] ?? __('搜索内容', 'aksrt'));
// legacy: form + input + remote-box (action=menu_search).
echo '<form method="get" class="navbar-form navbar-left hover-show" action="' . esc_url(home_url('/')) . '">';
echo '<div class="form-group relative dropdown">';
echo '<input type="text" class="form-control search-input focus-show" name="s" value="'
. esc_attr(get_search_query()) . '" placeholder="' . esc_attr($placeholder) . '" autocomplete="off">';
echo '<div class="abs-right muted-3-color"><button type="submit" class="null" aria-label="'
. esc_attr__('搜索', 'aksrt') . '"><i class="fa fas fa-search" aria-hidden="true"></i></button></div>';
if (function_exists('aksrt_get_remote_box')) {
echo aksrt_get_remote_box([ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
'class' => '',
'loader' => ' ',
'query' => [
'action' => 'menu_search',
'nonce' => wp_create_nonce('aksrt_public_nonce'),
],
]);
} else {
$remote_url = add_query_arg([
'action' => 'menu_search',
'nonce' => wp_create_nonce('aksrt_public_nonce'),
], admin_url('admin-ajax.php'));
echo '<div class="lazyload" remote-box="' . esc_url($remote_url) . '" lazyload-action="ias">';
echo '<i class="loading-spot"><i></i></i></div>';
}
echo '</div></form>';
}
endif;
if (!function_exists('aksrt_header_user_box_html')) :
/**
* User dropdown inner content for nav sub-menu (logged-in / guest).
*
* @param array<string,mixed> $ctx
*/
function aksrt_header_user_box_html(array $ctx): string {
static $html = null;
if ($html !== null) {
return $html;
}
$user_id = (int) ($ctx['user_id'] ?? 0);
$con = '';
if ($user_id > 0) {
$user = $ctx['current_user'] instanceof WP_User ? $ctx['current_user'] : wp_get_current_user();
$display_name = $user->display_name ?: $user->user_login;
$avatar = aksrt_get_avatar_box($user_id, 'avatar-img', true, false);
$desc = (string) ($ctx['user_desc'] ?? '');
$vip_badge = function_exists('aksrt_get_vip_icon') ? aksrt_get_vip_icon($user_id) : '';
// Level and certification icons ride inline to the right of the display name.
$level_badge = (class_exists('\AKSRT\User\Level') && method_exists('\AKSRT\User\Level', 'get_badge_html'))
? \AKSRT\User\Level::get_badge_html($user_id, 'aksrt-user-level-icon--header')
: '';
$medal_badge = function_exists('aksrt_get_worn_medal_icon')
? aksrt_get_worn_medal_icon($user_id, 'aksrt-header-user-medal')
: '';
$certification_badge = '';
if (function_exists('aksrt_get_user_identity_signals')) {
foreach (aksrt_get_user_identity_signals($user_id) as $signal) {
if ((string) ($signal['type'] ?? '') !== 'verify') {
continue;
}
$icon_url = esc_url((string) ($signal['icon_url'] ?? ''));
$title = (string) ($signal['title'] ?? $signal['label'] ?? '');
if ($icon_url !== '' && $title !== '') {
$certification_badge = '<img' . aksrt_get_lazy_attr('lazy_other', $icon_url, 'aksrt-identity-certification-icon img-icon ml3') . ' alt="" title="' . esc_attr($title) . '" decoding="async">';
}
break;
}
}
$payvip = function_exists('aksrt_header_payvip_bar') ? aksrt_header_payvip_bar($user_id) : '';
$checkin = function_exists('aksrt_header_user_checkin_btn') ? aksrt_header_user_checkin_btn($user_id) : '';
$wallet = function_exists('aksrt_header_user_wallet_mini') ? aksrt_header_user_wallet_mini($user_id) : '';
$badges = function_exists('aksrt_header_user_badges_row') ? aksrt_header_user_badges_row($user_id) : '';
$unread = (int) ($ctx['unread_count'] ?? 0);
$msg_url = (string) ($ctx['messages_url'] ?? '');
$positions = (array) ($ctx['message_icon_positions'] ?? []);
$msg_icon = '';
if ($msg_url !== '' && in_array('avatar_dropdown', $positions, true)) {
$msg_icon = '<a class="msg-news-icon abs-right" href="' . esc_url($msg_url) . '" title="' . esc_attr__('消息中心', 'aksrt') . '">';
$msg_icon .= '<span class="toggle-radius msg-icon"><i class="fa fas fa-bell" aria-hidden="true"></i></span>';
if ($unread > 0) {
$msg_icon .= '<badge class="top">' . esc_html((string) min(99, $unread)) . '</badge>';
}
$msg_icon .= '</a>';
}
$href = '<div class="flex jsa header-user-href">';
$href .= '<a rel="nofollow" href="' . esc_url(function_exists('aksrt_get_user_center_url') ? aksrt_get_user_center_url() : home_url('/')) . '">';
$href .= '<div class="badg mb6 toggle-radius c-blue"><i class="fa fas fa-user" aria-hidden="true"></i></div>';
$href .= '<div class="c-blue">' . esc_html__('用户中心', 'aksrt') . '</div></a>';
$orders = (string) ($ctx['orders_url'] ?? '');
if ($orders !== '') {
$href .= '<a rel="nofollow" href="' . esc_url($orders) . '">';
$href .= '<div class="badg mb6 toggle-radius c-purple"><i class="fa fas fa-shopping-bag" aria-hidden="true"></i></div>';
$href .= '<div class="c-purple">' . esc_html__('我的订单', 'aksrt') . '</div></a>';
}
if (!empty($ctx['can_publish'])) {
// legacy:用户卡片发布按钮由 nav_user_newposts_btn 门控,文案可配。
$newposts_type = class_exists('\AKSRT\Settings\Settings')
? \AKSRT\Settings\Settings::get_value('nav_user_newposts_btn', 'post')
: 'post';
if (!empty($newposts_type)) {
$newposts_text = class_exists('\AKSRT\Settings\Settings')
? (string) \AKSRT\Settings\Settings::get_value('nav_user_newposts_btn_text', __('发布内容', 'aksrt'))
: __('发布内容', 'aksrt');
$pub_url = (string) $ctx['publish_url'];
$href .= '<a rel="nofollow" href="' . esc_url($pub_url) . '">';
$href .= '<div class="badg mb6 toggle-radius c-green"><i class="fa fas fa-edit" aria-hidden="true"></i></div>';
$href .= '<div class="c-green">' . esc_html($newposts_text) . '</div></a>';
}
}
$href .= '<a href="javascript:;" data-toggle="modal" data-target="#modal_signout">';
$href .= '<div class="badg mb6 toggle-radius c-red"><i class="fa fas fa-sign-out-alt" aria-hidden="true"></i></div>';
$href .= '<div class="c-red">' . esc_html__('退出登录', 'aksrt') . '</div></a>';
$href .= '</div>';
if (is_super_admin()) {
$href .= '<div class="flex jsa header-user-href">';
$href .= '<a rel="nofollow" target="_blank" href="' . esc_url((string) $ctx['theme_settings_url']) . '">';
$href .= '<div class="badg mb6 toggle-radius c-yellow"><i class="fa fas fa-sliders-h" aria-hidden="true"></i></div>';
$href .= '<div class="c-yellow">' . esc_html__('主题设置', 'aksrt') . '</div></a>';
// legacy: 模块布局 → widgets.
$href .= '<a rel="nofollow" target="_blank" href="' . esc_url(admin_url('widgets.php')) . '">';
$href .= '<div class="badg mb6 toggle-radius c-yellow"><i class="fa fas fa-chart-pie" aria-hidden="true"></i></div>';
$href .= '<div class="c-yellow">' . esc_html__('模块布局', 'aksrt') . '</div></a>';
// legacy: 商城中心 → 支付中心(AKSRT 支付中心后台菜单)。
$href .= '<a rel="nofollow" target="_blank" href="' . esc_url(admin_url('admin.php?page=aksrt_pay')) . '">';
$href .= '<div class="badg mb6 toggle-radius c-yellow"><i class="fa fas fa-credit-card" aria-hidden="true"></i></div>';
$href .= '<div class="c-yellow">' . esc_html__('支付中心', 'aksrt') . '</div></a>';
$href .= '<a rel="nofollow" target="_blank" href="' . esc_url(admin_url()) . '">';
$href .= '<div class="badg mb6 toggle-radius c-yellow"><i class="fa fas fa-cog" aria-hidden="true"></i></div>';
$href .= '<div class="c-yellow">' . esc_html__('后台管理', 'aksrt') . '</div></a>';
$href .= '</div>';
}
$con = '<div class="user-info flex ac relative">';
$con .= $avatar;
$con .= '<div class="user-right flex flex1 ac jsb ml10">';
$con .= '<div class="flex1" style="max-width: calc(100% - 40px);"><b>'
. '<name class="flex ac flex1">'
. '<a class="display-name text-ellipsis" href="' . esc_url(function_exists('aksrt_get_user_home_url') ? aksrt_get_user_home_url($user_id) : get_author_posts_url($user_id)) . '">' . esc_html($display_name) . '</a>'
. $vip_badge . $medal_badge . $certification_badge . $level_badge
. '</name>'
. '</b><div class="px12 muted-2-color text-ellipsis">' . esc_html($desc) . '</div></div>';
$con .= '</div>';
$con .= $msg_icon;
$con .= '</div>';
$con .= $payvip ? $payvip : '';
$con .= $checkin ? '<div class="mt6 em09" style="padding:2px;">' . $checkin . '</div>' : '';
$con .= $wallet ? '<div class="mt6" style="padding:2px;">' . $wallet . '</div>' : '';
$con .= $badges;
$con .= '<div class="relative opacity5"><i class="line-form-line"></i></div>';
$con .= '<div class="mt10 text-center">' . $href . '</div>';
} else {
$vip_guest = '';
if (class_exists('\AKSRT\Settings\Settings')) {
$vs = \AKSRT\Settings\Settings::get_payment_vip_settings();
if (!empty($vs['enabled']) && !empty($vs['nav_user_pay_vip'])) {
$vip_guest = '<div><a class="em09 signin-loader but jb-red radius4 payvip-icon btn-block mt10" href="javascript:;">'
. '<i class="fa fas fa-gem" aria-hidden="true"></i> '
. esc_html__('开通会员 尊享会员权益', 'aksrt') . '</a></div>';
}
}
$href = $vip_guest;
$href .= '<div class="flex jsa header-user-href">';
$href .= '<a href="javascript:;" class="signin-loader"><div class="badg mb6 toggle-radius c-blue"><i class="fa fas fa-sign-in-alt" aria-hidden="true"></i></div><div class="c-blue">'
. esc_html__('登录', 'aksrt') . '</div></a>';
if (get_option('users_can_register')) {
$href .= '<a href="javascript:;" class="signup-loader"><div class="badg mb6 toggle-radius c-green"><i class="fa fas fa-user-plus" aria-hidden="true"></i></div><div class="c-green">'
. esc_html__('注册', 'aksrt') . '</div></a>';
}
// legacy: 找回密码 → 统一登录页 resetpassword 标签(带回跳)。
$reset_url = function_exists('aksrt_get_sign_page_url')
? aksrt_get_sign_page_url('resetpassword', [
'redirect_to' => remove_query_arg(
'redirect_to',
home_url(isset($_SERVER['REQUEST_URI']) ? wp_unslash($_SERVER['REQUEST_URI']) : '/')
),
])
: wp_lostpassword_url(home_url('/'));
$href .= '<a target="_blank" rel="nofollow" href="' . esc_url($reset_url) . '"><div class="badg mb6 toggle-radius c-purple"><i class="fa fas fa-key" aria-hidden="true"></i></div><div class="c-purple">'
. esc_html__('找回密码', 'aksrt') . '</div></a>';
$href .= '</div>';
$con = '<div class="text-center">' . $href . '</div>';
if (class_exists('\AKSRT\Auth\OAuth') && method_exists(\AKSRT\Auth\OAuth::get_instance(), 'render_social_buttons')) {
$oauth_html = (string) \AKSRT\Auth\OAuth::get_instance()->render_social_buttons(__('快速登录', 'aksrt'));
if ($oauth_html !== '') {
$con .= '<p class="social-separator separator muted-3-color em09 mt10">' . esc_html__('快速登录', 'aksrt') . '</p>';
$con .= '<div class="social_loginbar">' . $oauth_html . '</div>';
}
}
}
$html = '<div class="sub-user-box aksrt-header-userbox">' . $con . '</div>';
return $html;
}
endif;
if (!function_exists('aksrt_menu_button')) :
/**
* Right cluster: fold, publish/VIP, theme, user, search (legacy).
*
* @param array<string,mixed> $ctx
*/
function aksrt_menu_button(array $ctx): void {
$layout = (string) ($ctx['layout'] ?? '1');
$user_id = (int) ($ctx['user_id'] ?? 0);
$actions = '';
// Publish and membership actions.
if (function_exists('aksrt_render_header_menu_buttons')) {
$actions = aksrt_render_header_menu_buttons();
}
// Theme toggle.
$utility_buttons = '';
if (in_array('pc_nav', (array) ($ctx['theme_positions'] ?? []), true)) {
$utility_buttons .= '<a href="javascript:;" class="toggle-theme toggle-radius" id="theme-toggle-desktop" title="'
. esc_attr__('切换亮暗色', 'aksrt') . '">';
$utility_buttons .= '<i class="fa fas fa-toggle-theme" aria-hidden="true"></i>';
$utility_buttons .= '</a>';
}
if (function_exists('aksrt_get_translate_language_btn')) {
$utility_buttons .= aksrt_get_translate_language_btn('aksrt-navbar-language');
}
// Notifications, user and search.
$notification_but = '';
$messages_url = (string) ($ctx['messages_url'] ?? '');
$positions = (array) ($ctx['message_icon_positions'] ?? []);
if ($user_id > 0 && $messages_url !== '' && in_array('nav_menu', $positions, true)) {
$unread = (int) ($ctx['unread_count'] ?? 0);
$notification_but = '<li class="relative"><a rel="nofollow" href="' . esc_url($messages_url)
. '" class="aksrt-nav-icon aksrt-nav-notification" aria-label="' . esc_attr__('消息中心', 'aksrt') . '">'
. '<i class="fa fas fa-bell" aria-hidden="true"></i>';
if ($unread > 0) {
$notification_but .= '<span class="aksrt-notification-badge">' . esc_html((string) min(99, $unread)) . '</span>';
}
$notification_but .= '</a></li>';
}
$sign_but = '';
$user_sub = '<div class="padding-10">' . aksrt_header_user_box_html($ctx) . '</div>';
if ('2' === $layout) {
if ($user_id > 0) {
$avatar = aksrt_get_avatar_html($user_id, 36, ['class' => 'avatar']);
$sign_but = '<li><a href="javascript:;" class="navbar-avatar aksrt-nav-avatar">' . $avatar . '</a><ul class="sub-menu">'
. $user_sub . '</ul></li>';
} else {
$sign_but = '<li><a href="javascript:;" class="signin-loader">' . esc_html__('登录', 'aksrt') . '</a></li>';
if (get_option('users_can_register')) {
$sign_but .= '<li><a href="javascript:;" class="signup-loader">' . esc_html__('注册', 'aksrt') . '</a></li>';
}
}
} else {
$cls = 'aksrt-nav-icon' . ($user_id > 0 ? '' : ' signin-loader');
$sign_but = '<li><a href="javascript:;" class="' . esc_attr($cls) . '">'
. '<svg class="icon" aria-hidden="true"><use xlink:href="#icon-user"></use></svg></a>'
. '<ul class="sub-menu">' . $user_sub . '</ul></li>';
}
$search_but = '';
if ('2' !== $layout) {
$search_but = '<li class="relative"><a href="javascript:;" class="aksrt-nav-icon nav-search-btn main-search-btn" aria-label="'
. esc_attr__('打开搜索', 'aksrt') . '"><svg class="icon" aria-hidden="true"><use xlink:href="#icon-search"></use></svg></a></li>';
}
$account = '<ul class="list-inline splitters relative aksrt-navbar-account'
. ($user_id <= 0 && '2' === $layout ? ' navbar-text' : '') . '">'
. $notification_but . $sign_but . $search_but . '</ul>';
$button = '<div class="aksrt-navbar-actions navbar-form navbar-right navbar-but">'
. $actions . $utility_buttons . $account . '</div>';
$fold = '<div class="navbar-form navbar-right hide show-nav-but" style="margin-right:-10px;">'
. '<a data-toggle-class data-target=".nav.navbar-nav" href="javascript:;" class="aksrt-nav-icon" aria-label="'
. esc_attr__('展开菜单', 'aksrt') . '"><svg class="icon" viewBox="0 0 1024 1024" aria-hidden="true"><use xlink:href="#icon-menu_2"></use></svg></a></div>';
$html = $fold . $button;
echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
endif;