*/ public function get_modal_view_model(int $user_id, int $prefer_level = 1): array { $catalog = $this->get_catalog($user_id); $vip = $this->get_vip_settings_context(); $member = $this->get_vip_member_context($user_id); $levels = is_array($vip['levels'] ?? null) ? $vip['levels'] : []; $prefer_level = max(1, min(2, $prefer_level)); $level_view = []; foreach ([1, 2] as $level) { if (empty($levels[$level]['enabled'])) { continue; } $equity = (string) ($levels[$level]['equity'] ?? ''); $equity = function_exists('aksrt_format_vip_equity_html') ? (string) aksrt_format_vip_equity_html($equity) : wp_kses_post($equity); $level_view[] = [ 'level' => $level, 'name' => wp_kses_post((string) ($levels[$level]['name_html'] ?? $levels[$level]['name'] ?? ('VIP' . $level))), 'name_text' => (string) ($levels[$level]['name'] ?? ('VIP' . $level)), 'desc' => wp_kses_post((string) ($levels[$level]['desc'] ?? '')), 'equity' => $equity, 'img_icon' => esc_url_raw((string) ($levels[$level]['img_icon'] ?? '')), ]; } $package_ids = []; $has_renew = false; $has_upgrade = false; $groups = is_array($catalog['level_packages'] ?? null) ? $catalog['level_packages'] : []; foreach ($groups as $group) { if (!is_array($group)) { continue; } $action = sanitize_key((string) ($group['action'] ?? 'pay')); $has_renew = $has_renew || $action === 'renew'; $has_upgrade = $has_upgrade || $action === 'upgrade'; foreach ((array) ($group['items'] ?? []) as $item) { if (is_array($item) && (int) ($item['package_id'] ?? 0) > 0) { $package_ids[] = (int) $item['package_id']; } } } $points_exchange = is_array($catalog['points_exchange'] ?? null) ? $catalog['points_exchange'] : []; $methods = is_array($catalog['payment_methods'] ?? null) ? $catalog['payment_methods'] : []; // 支付方式 logo(与充值/积分弹窗的 payment-method-radio 组件同源)。 $methods = array_map(function ($m) { $id = sanitize_key((string) ($m['id'] ?? '')); return [ 'id' => $id, 'name' => (string) ($m['name'] ?? $id), 'logo' => $this->payment_method_logo_url($id), ]; }, $methods); $card_pass = is_array($catalog['card_pass'] ?? null) ? array_merge(['enabled' => false, 'desc' => '', 'only_password' => false], $catalog['card_pass']) : ['enabled' => false, 'desc' => '', 'only_password' => false]; $no_payment_method = $methods === []; $action_reasons = [ 'purchase' => $no_payment_method ? 'no_payment_method' : ($package_ids === [] ? 'no_package' : ''), 'renew' => $no_payment_method ? 'no_payment_method' : ($has_renew ? '' : 'not_available'), 'upgrade' => $no_payment_method ? 'no_payment_method' : ($has_upgrade ? '' : 'not_available'), 'points_exchange' => !empty($points_exchange['enabled']) && !empty($points_exchange['packages']) ? '' : 'not_available', ]; $member_state = (string) ($member['state'] ?? 'unopened'); $member_version = substr(hash('sha256', (string) wp_json_encode([ 'level' => (int) ($member['level'] ?? 0), 'expires' => (int) ($member['expires'] ?? 0), 'state' => $member_state, ])), 0, 16); $catalog_version = substr(hash('sha256', (string) wp_json_encode([ 'packages' => $groups, 'points' => $points_exchange, 'methods' => $methods, 'levels' => $level_view, ])), 0, 16); $user = get_userdata($user_id); $avatar_url = function_exists('get_avatar_url') ? (string) get_avatar_url($user_id, ['size' => 42]) : ''; return [ 'enabled' => !empty($catalog['enabled']), 'prefer_level' => $prefer_level, 'catalog' => [ 'desc' => (string) ($catalog['desc'] ?? ''), 'more' => (string) ($vip['more'] ?? ''), 'currency_name' => (string) ($catalog['currency_name'] ?? ''), 'level_packages' => $groups, 'points_exchange' => $points_exchange, 'wallet_balance' => (string) ($catalog['wallet_balance'] ?? '0.00'), ], 'member' => [ 'id' => $user_id, 'name' => $user ? (string) $user->display_name : '', 'avatar_url' => $avatar_url, 'level' => (int) ($member['level'] ?? 0), 'expires' => (int) ($member['expires'] ?? 0), 'expires_label' => (string) ($member['expires_label'] ?? ''), 'state' => (string) ($member['state'] ?? 'unopened'), 'is_permanent' => !empty($member['is_permanent']), 'logged_in' => $user_id > 0, ], 'levels' => $level_view, 'payment_methods' => $methods, 'card_pass' => $card_pass, 'currency_mark' => (string) (\AKSRT\Settings\Settings::get_payment_gateway_settings()['currency']['mark'] ?? '¥'), 'allowed_actions' => [ 'purchase' => $package_ids !== [] && $methods !== [], 'renew' => $has_renew && $methods !== [], 'upgrade' => $has_upgrade && $methods !== [], 'points_exchange' => !empty($points_exchange['enabled']) && !empty($points_exchange['packages']), ], 'action_reasons' => $action_reasons, 'version' => [ 'schema' => 1, 'member' => $member_version, 'catalog' => $catalog_version, // Stable revision token; do not use request time and bust short cache. 'updated_at' => $member_version . ':' . $catalog_version, 'tags' => ['vip', 'orders'], ], ]; } /** * 积分兑换会员弹窗(参考主题 zibpay_get_vip_points_exchange_modal 1:1 移植)。 * * 服务端静态输出参考主题 refresh_modal 同构外壳: * .modal.flex.jc.fade > .modal-dialog.modal-mini.full-sm * > .modal-content > .modal-body * 与余额充值/购买积分弹窗同构(静止态由 vip-modal.css 显式隐藏,打开态由 * pay-vip.js 的自包含开关以内联 display:flex 驱动)。 * * 入口有两处(均带 data-aksrt-vip-points-open,pay-vip.js 文档级委托开合): * 会员购买弹窗内的「积分兑换会员」按钮,以及 /user/vip/ 档位购买卡下方的 * 入口横条(aksrt_get_vip_points_exchange_entry_html)。.pay-vip 触发点 * 全站可见,因此由 aksrt_render_vip_points_exchange_modal() 在 wp_footer * 全局输出一次。 * * @return string 空串表示积分兑换不可用(不输出弹窗壳)。 */ public function get_points_exchange_modal_html(int $user_id = 0): string { $user_id = max(0, $user_id); if ($user_id <= 0 || !class_exists('\AKSRT\Settings\Settings')) { return ''; } // 先做零成本门控,避免每次页面渲染都去查积分余额。 $vip = $this->get_vip_settings_context(); if (empty($vip['enabled']) || empty($vip['points_exchange']['enabled']) || !\AKSRT\Settings\Settings::credits_enabled()) { return ''; } $catalog = $this->get_catalog($user_id); $points_ex = is_array($catalog['points_exchange'] ?? null) ? $catalog['points_exchange'] : []; $packages = is_array($points_ex['packages'] ?? null) ? $points_ex['packages'] : []; if ($packages === []) { return ''; } $levels = is_array($vip['levels'] ?? null) ? $vip['levels'] : []; $user_points = max(0, (int) ($catalog['user_points'] ?? 0)); $currency = (string) ($catalog['currency_name'] ?? __('积分', 'aksrt')); $points_center_url = function_exists('aksrt_get_user_center_tab_url') ? aksrt_get_user_center_tab_url('credits') : home_url('/'); // 会员卡片(参考主题 data-for/data-value 选中模型;data-value 为本主题的套餐 ID)。 $cards = ''; $min_points = 0; $first_package_id = 0; foreach ($packages as $index => $package) { if (!is_array($package)) { continue; } $package_id = max(0, (int) ($package['package_id'] ?? 0)); if ($package_id <= 0) { continue; } if ($first_package_id === 0) { $first_package_id = $package_id; } $level = max(1, min(2, (int) ($package['level'] ?? 1))); $points = max(0, (int) ($package['points'] ?? 0)); // 参考主题时长行:30天 / 永久;time 为 Permanent 时按永久渲染。 $is_permanent = !empty($package['is_permanent']); $pkg_time = max(0, (int) ($package['time'] ?? 0)); $pkg_unit = ((string) ($package['unit'] ?? 'day')) === 'month' ? 'month' : 'day'; $duration = ($is_permanent || $pkg_time <= 0) ? esc_html__('永久', 'aksrt') : (string) $pkg_time . '' . esc_html($pkg_unit === 'month' ? __('个月', 'aksrt') : __('天', 'aksrt')) . ''; $level_name = (string) ($levels[$level]['name_html'] ?? $levels[$level]['name'] ?? ('VIP' . $level)); $cards .= '