From 573040381d09a044644b32edf20434e2e7ea8d39 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 19 Oct 2026 12:52:23 +0800 Subject: [PATCH] fix(vip): preserve safe HTML in VIP copy Co-Authored-By: Claude Opus 5 (1M context) --- .../admin/src/schema/sections/payment/vip.js | 14 +++--- assets/js/pay-vip.js | 22 ++++----- inc/helpers/aksrt-header-userbox.php | 4 +- inc/helpers/aksrt-pay.php | 8 +++- inc/helpers/aksrt-vip.php | 18 +------ .../user-center-renderers/sidebar-vip.php | 8 ++-- inc/helpers/user-center-renderers/vip-tab.php | 28 +++++------ src/Admin/Schema/SchemaPayment.php | 12 ++--- src/Content/VipServiceModal.php | 28 ++++++----- src/Content/VipServicePurchase.php | 2 +- src/Settings/PaymentRuntime.php | 36 ++++++-------- .../page-display-contract.test.cjs | 3 +- .../vip-modal-copy-fields.test.cjs | 48 ++++++++++++++----- .../vip-settings-structured-contract.test.cjs | 19 +++++++- .../vip-settings-structured-harness.php | 23 +++++++-- 15 files changed, 155 insertions(+), 118 deletions(-) diff --git a/assets/admin/src/schema/sections/payment/vip.js b/assets/admin/src/schema/sections/payment/vip.js index 334dbda7..5c1930ec 100644 --- a/assets/admin/src/schema/sections/payment/vip.js +++ b/assets/admin/src/schema/sections/payment/vip.js @@ -69,9 +69,9 @@ function vipProductFields() { function vipLevelTabFields(level) { const dep = { key: `pay_user_vip_${level}_s`, value: true }; return [ - { key: `pay_user_vip_${level}_name`, label: '会员显示名称', type: 'input', default: level === 1 ? '黄金会员' : '钻石会员', dependsOn: dep, help: '会员名称(例如“黄金会员”、“超级会员”)' }, - { key: `pay_user_vip_${level}_desc`, label: '一句话简介', type: 'input', default: '尊享专属特权', dependsOn: dep }, - { key: `pay_user_vip_${level}_equity`, label: '会员权益 HTML', type: 'textarea', sanitize_type: 'html', default: VIP_EQUITY_DEFAULT, rows: 4, dependsOn: dep, help: '建议使用
  • 列表项' }, + { key: `pay_user_vip_${level}_name`, label: '会员显示名称', type: 'input', sanitize_type: 'html', default: level === 1 ? '黄金会员' : '钻石会员', dependsOn: dep, help: '支持HTML,请注意控制长度' }, + { key: `pay_user_vip_${level}_desc`, label: '一句话简介', type: 'input', sanitize_type: 'html', default: '尊享专属特权', dependsOn: dep, help: '支持HTML' }, + { key: `pay_user_vip_${level}_equity`, label: '会员权益 HTML', type: 'textarea', sanitize_type: 'html', default: VIP_EQUITY_DEFAULT, rows: 4, dependsOn: dep, help: '支持安全HTML,建议使用
  • 列表项' }, { key: `vip_${level}_product_s`, label: '购买会员', type: 'switch', default: true, dependsOn: dep, help: '关闭后则不能付费购买会员,但可通过积分兑换会员,请确保已开启积分以及积分兑换会员功能' }, { key: `vip_${level}_product`, @@ -290,9 +290,10 @@ export const vipSchema = { key: 'vip_renew_desc', label: '续费介绍', type: 'input', + sanitize_type: 'html', default: '立即续费会员,畅享VIP权益', dependsOn: { key: 'vip_renew', value: true }, - help: '续费会员的一句话简介', + help: '续费会员的一句话简介,支持HTML', }, { key: 'vip_renew_price_type', @@ -371,7 +372,7 @@ export const vipSchema = { title: '会员升级', fields: [ { key: 'vip_upgrade', label: '允许升级', type: 'switch', default: true }, - { key: 'vip_upgrade_desc', label: '升级介绍', type: 'input', default: '升级VIP会员,享更多会员权益', dependsOn: { key: 'vip_upgrade', value: true }, help: '升级会员的一句话简介' }, + { key: 'vip_upgrade_desc', label: '升级介绍', type: 'input', sanitize_type: 'html', default: '升级VIP会员,享更多会员权益', dependsOn: { key: 'vip_upgrade', value: true }, help: '升级会员的一句话简介,支持HTML' }, { key: 'vip_upgrade_product', label: '升级价格', @@ -478,9 +479,10 @@ export const vipSchema = { key: 'pay_user_vip_desc', label: '会员介绍', type: 'input', + sanitize_type: 'html', default: '开通VIP会员,享受会员专属折扣以及多项特权', dependsOn: { any: [{ key: 'pay_user_vip_1_s', value: true }, { key: 'pay_user_vip_2_s', value: true }] }, - help: '显示在开通界面顶部一句话简介,可以为会员权益简介或者活动介绍', + help: '显示在开通界面顶部一句话简介,可以为会员权益简介或者活动介绍,支持HTML', }, { key: 'pay_user_vip_more', diff --git a/assets/js/pay-vip.js b/assets/js/pay-vip.js index 8d3a8b58..78c29f11 100644 --- a/assets/js/pay-vip.js +++ b/assets/js/pay-vip.js @@ -17,8 +17,7 @@ return el; } - // 服务端已按 post 级 HTML 净化的富文本字段(more / 卡密说明)走 innerHTML; - // node() 的 textContent 会把标签当字面文本显示。 + // Server-side schema sanitization limits these configured rich-text fields to post-safe HTML. function htmlNode(tag, className, value) { var el = document.createElement(tag); if (className) el.className = className; @@ -70,7 +69,7 @@ if (levelInfo && levelInfo.img_icon) { var img = node("img", "img-icon"); img.src = text(levelInfo.img_icon); - img.alt = text(levelInfo.name || ""); + img.alt = text(levelInfo.name_text || levelInfo.name || ""); img.decoding = "async"; return img; } @@ -125,7 +124,7 @@ // (右上 tag + 价格行 + 时长 badge,无标题行)。 var product = node("div", "zib-widget vip-product relative text-center product-box"); // 标签颜色取后台「标签颜色」配置,未配置时回退参考主题默认的 jb-yellow。 - if (item.discount_tag) append(product, node("div", "abs-right vip-tag badg " + (item.tag_class || "jb-yellow"), item.discount_tag)); + if (item.discount_tag) append(product, htmlNode("div", "abs-right vip-tag badg " + (item.tag_class || "jb-yellow"), item.discount_tag)); append(product, priceRow(mark, item.price_label || "", item.show_price_label || "")); if (item.days_label) append(product, node("div", "muted-2-color c-blue em09 p2-10 badg radius", item.days_label)); append(label, product); @@ -408,7 +407,7 @@ } else if (member.state === "expired") { append(userInfo, node("div", "muted-2-color em09", "已过期")); } else if (catalog.desc) { - append(userInfo, node("div", "muted-2-color em09", catalog.desc)); + append(userInfo, htmlNode("div", "muted-2-color em09", catalog.desc)); } append(userBox, userInfo); append(modal, userBox); @@ -444,8 +443,8 @@ append(miniIcon, vipIcon(levelInfo)); append(miniIconbox, miniIcon); var miniInfo = node("div", "v-info"); - append(miniInfo, node("div", "v-name", actionLabel(action) + (levelInfo.name || group.name || "VIP" + level))); - append(miniInfo, node("div", "v-desc em09", levelInfo.desc || "")); + append(miniInfo, htmlNode("div", "v-name", actionLabel(action) + (levelInfo.name || group.name || "VIP" + level))); + append(miniInfo, htmlNode("div", "v-desc em09", levelInfo.desc || "")); append(miniIconbox, miniInfo); append(miniCard, miniIconbox); append(tab, miniCard); @@ -471,15 +470,12 @@ append(vicon, vipIcon(levelInfo)); append(iconbox, vicon); var vinfo = node("div", "v-info"); - append(vinfo, node("div", "v-name", levelInfo.name || group.name || "VIP" + level)); - append(vinfo, node("div", "v-desc em09", levelInfo.desc || "")); + append(vinfo, htmlNode("div", "v-name", levelInfo.name || group.name || "VIP" + level)); + append(vinfo, htmlNode("div", "v-desc em09", levelInfo.desc || "")); append(iconbox, vinfo); append(info, iconbox); if (levelInfo.equity) { - var equity = document.createElement("ul"); - equity.className = "v-equity"; - equity.innerHTML = text(levelInfo.equity); - append(info, equity); + append(info, htmlNode("div", "", levelInfo.equity)); } var infoBa = node("div", "abs-center vip-baicon"); append(infoBa, vipIcon(levelInfo)); diff --git a/inc/helpers/aksrt-header-userbox.php b/inc/helpers/aksrt-header-userbox.php index 1213e503..e75fbb23 100644 --- a/inc/helpers/aksrt-header-userbox.php +++ b/inc/helpers/aksrt-header-userbox.php @@ -200,7 +200,7 @@ if (!function_exists('aksrt_header_payvip_bar')) : $level = function_exists('aksrt_get_user_vip_level') ? (int) aksrt_get_user_vip_level($user_id) : 0; if ($level > 0) { $levels = is_array($vip['levels'] ?? null) ? $vip['levels'] : []; - $name = (string) ($levels[$level]['name'] ?? ($level === 2 ? __('二级会员', 'aksrt') : __('一级会员', 'aksrt'))); + $name = (string) ($levels[$level]['name_html'] ?? $levels[$level]['name'] ?? ($level === 2 ? __('二级会员', 'aksrt') : __('一级会员', 'aksrt'))); // 到期徽章语义对齐参考主题 zib_get_user_vip_exp_date_text():只有真的拿到 // 到期时间才渲染这枚徽章,已过期显示「会员已过期」否则显示裸日期;显式永久 // 会员显示「永久会员」;拿不到到期数据时参考主题返回 false,整枚徽章不渲染。 @@ -219,7 +219,7 @@ if (!function_exists('aksrt_header_payvip_bar')) : $html = '
    '; $html .= ''; $html .= function_exists('aksrt_get_vip_icon') ? aksrt_get_vip_icon($user_id, 'em12 mr6', false) : ''; - $html .= '' . esc_html($name) . ''; + $html .= '' . wp_kses_post($name) . ''; if ($exp_label !== '') { $html .= '' . '' diff --git a/inc/helpers/aksrt-pay.php b/inc/helpers/aksrt-pay.php index 7b83f092..672f3890 100644 --- a/inc/helpers/aksrt-pay.php +++ b/inc/helpers/aksrt-pay.php @@ -1309,6 +1309,7 @@ if (!function_exists('aksrt_get_posts_vip_price')) { } $level_name = (string) ($level_settings['name'] ?? sprintf(__('VIP%d', 'aksrt'), $level)); + $level_name_html = (string) ($level_settings['name_html'] ?? $level_name); $price = $vip_minor === 0 ? esc_html__('免费', 'aksrt') : $mark . esc_html(aksrt_format_price($value)); @@ -1319,7 +1320,7 @@ if (!function_exists('aksrt_get_posts_vip_price')) { ? ' data-toggle="tooltip" title="' . esc_attr($title) . '"' : ''; - $items[] = '' . $icon . esc_html($level_name) . '' . $price . ''; + $items[] = '' . $icon . wp_kses_post($level_name_html) . '' . $price . ''; } $html = implode('', $items); @@ -1384,10 +1385,13 @@ if (!function_exists('aksrt_get_paywall_html')) { $vip_action = $vip_level > 0 ? sprintf(__('升级%s', 'aksrt'), $level_name) : __('开通会员', 'aksrt'); + $vip_action_html = $vip_level > 0 + ? __('升级', 'aksrt') . wp_kses_post((string) ($level_settings['name_html'] ?? $level_name)) + : esc_html__('开通会员', 'aksrt'); $action_class = is_user_logged_in() ? 'pay-vip' : 'signin-loader'; $icon = aksrt_get_pay_vip_icon_html($level_settings); $button = '
    ' . esc_html(sprintf(__('您暂无购买权限,请先%s', 'aksrt'), $vip_action)) . '
    '; - $button .= '' . $icon . esc_html($vip_action) . ''; + $button .= '' . $icon . $vip_action_html . ''; } elseif (!is_user_logged_in()) { if ($amount_minor > 0 && $modo === '0' && aksrt_allow_guest_content_pay($post)) { $button = ''; diff --git a/inc/helpers/aksrt-vip.php b/inc/helpers/aksrt-vip.php index 1d664ab5..62355d11 100644 --- a/inc/helpers/aksrt-vip.php +++ b/inc/helpers/aksrt-vip.php @@ -481,7 +481,7 @@ endif; * or Chinese keyword heuristics — that mangles intentional HTML. * * Rules: - * - Sanitize with a small allowlist. + * - Sanitize with WordPress post-safe HTML. * - If markup already has
  • , wrap the items in
      . * - Otherwise output the sanitized HTML inside
      . * - Plain multi-line text: each non-empty line becomes one
    • (only \n split). @@ -497,21 +497,7 @@ if (!function_exists('aksrt_format_vip_equity_html')) { return ''; } - $allowed = [ - 'li' => ['class' => true], - 'ul' => ['class' => true], - 'ol' => ['class' => true], - 'strong' => [], - 'b' => [], - 'em' => [], - 'i' => ['class' => true], - 'span' => ['class' => true], - 'br' => [], - 'p' => ['class' => true], - 'div' => ['class' => true], - 'a' => ['href' => true, 'target' => true, 'rel' => true, 'class' => true], - ]; - $clean = trim(wp_kses($raw, $allowed)); + $clean = trim(wp_kses_post($raw)); if ($clean === '') { return ''; } diff --git a/inc/helpers/user-center-renderers/sidebar-vip.php b/inc/helpers/user-center-renderers/sidebar-vip.php index 43ec5176..b38cbd5f 100644 --- a/inc/helpers/user-center-renderers/sidebar-vip.php +++ b/inc/helpers/user-center-renderers/sidebar-vip.php @@ -29,7 +29,7 @@ if ($status === '') { } if ($vip_level > 0) { - $title = (string) ($vip_levels[$vip_level]['name'] ?? ('VIP' . $vip_level)); + $title = (string) ($vip_levels[$vip_level]['name_html'] ?? $vip_levels[$vip_level]['name'] ?? ('VIP' . $vip_level)); if ($expires <= 0 && ($vip_state['source'] ?? 'none') !== 'order') { $status = __('永久会员', 'aksrt'); } else { @@ -41,7 +41,7 @@ if ($vip_level > 0) { ); } } elseif ($expired_level > 0) { - $title = (string) ($vip_levels[$expired_level]['name'] ?? ('VIP' . $expired_level)); + $title = (string) ($vip_levels[$expired_level]['name_html'] ?? $vip_levels[$expired_level]['name'] ?? ('VIP' . $expired_level)); $status = __('已过期', 'aksrt'); } @@ -55,8 +55,8 @@ $sidebar_arrow = aksrt_icon_markup('zibsvg-right', 'uc-sidebar-vip__arrow'); - - + + diff --git a/inc/helpers/user-center-renderers/vip-tab.php b/inc/helpers/user-center-renderers/vip-tab.php index 2f62383d..ca22ca66 100644 --- a/inc/helpers/user-center-renderers/vip-tab.php +++ b/inc/helpers/user-center-renderers/vip-tab.php @@ -46,7 +46,7 @@ $btn_type = 'pay'; if ($level > 0) { $vip_level_number = $level; - $vip_level_name = (string) ($levels[$level]['name'] ?? ('VIP' . $level)); + $vip_level_name = (string) ($levels[$level]['name_html'] ?? $levels[$level]['name'] ?? ('VIP' . $level)); $level_desc = (string) ($levels[$level]['desc'] ?? ''); $vip_description = $level_desc !== '' ? $level_desc : __('尊享专属特权', 'aksrt'); @@ -63,7 +63,7 @@ if ($level > 0) { $vip_state_label = __('生效中', 'aksrt'); $vip_expiry_text = sprintf(__('到期时间:%s', 'aksrt'), wp_date('Y年m月d日', $expires)); if ($base_level > 0 && $base_level < $level) { - $base_name = (string) ($levels[$base_level]['name'] ?? ('VIP' . $base_level)); + $base_name = wp_strip_all_tags((string) ($levels[$base_level]['name'] ?? ('VIP' . $base_level))); $base_expiry_text = $base_expires > 0 ? wp_date('Y年m月d日', $base_expires) : __('永久', 'aksrt'); @@ -75,7 +75,7 @@ if ($level > 0) { $vip_state = 'expired'; $vip_state_label = __('已过期', 'aksrt'); $vip_level_number = $expired_level; - $vip_level_name = (string) ($levels[$expired_level]['name'] ?? ('VIP' . $expired_level)); + $vip_level_name = (string) ($levels[$expired_level]['name_html'] ?? $levels[$expired_level]['name'] ?? ('VIP' . $expired_level)); $expired_desc = (string) ($levels[$expired_level]['desc'] ?? ''); $vip_description = $expired_desc !== '' ? $expired_desc @@ -97,9 +97,9 @@ $status_html = '
      ' . '
      ' // 未开通(level 0)不输出图标位:名称前不放通用证书图标。 . ($vip_level_number > 0 ? '' : '') - . '' . esc_html($vip_level_name) . '' + . '' . wp_kses_post($vip_level_name) . '' . '
      ' - . '
      ' . esc_html($vip_description) . '
      ' + . '
      ' . wp_kses_post($vip_description) . '
      ' . ($vip_expiry_text !== '' ? '
      ' . esc_html($vip_expiry_text) . '
      ' : '') . '
      '; @@ -109,14 +109,14 @@ foreach ([1, 2] as $lv) { if (empty($levels[$lv]['enabled'])) { continue; } - $lv_name = (string) ($levels[$lv]['name'] ?? ('VIP' . $lv)); + $lv_name = (string) ($levels[$lv]['name_html'] ?? $levels[$lv]['name'] ?? ('VIP' . $lv)); $lv_desc = (string) ($levels[$lv]['desc'] ?? ''); // Preserve admin equity HTML as-is (sanitize/wrap only; never re-split into items). $equity_raw = (string) ($levels[$lv]['equity'] ?? ''); $equity = function_exists('aksrt_format_vip_equity_html') ? aksrt_format_vip_equity_html($equity_raw) : ''; - $cta = sprintf(__('开通%s', 'aksrt'), $lv_name); + $cta = __('开通', 'aksrt') . wp_kses_post($lv_name); $vip_attr = $lv; // 参考主题 zibpay_get_vip_card:续费/升级档位卡展示各自的一句话简介。 $card_action = 'pay'; @@ -128,10 +128,10 @@ foreach ([1, 2] as $lv) { if (str_starts_with($btn_type, 'renew_')) { $renew_lv = (int) substr($btn_type, 6); if ($lv === $renew_lv) { - $cta = sprintf(__('续费%s', 'aksrt'), $lv_name); + $cta = __('续费', 'aksrt') . wp_kses_post($lv_name); $card_action = 'renew'; } elseif ($lv > $renew_lv) { - $cta = sprintf(__('升级%s', 'aksrt'), $lv_name); + $cta = __('升级', 'aksrt') . wp_kses_post($lv_name); $card_action = 'upgrade'; } else { continue; @@ -140,13 +140,13 @@ foreach ([1, 2] as $lv) { if ($lv <= $level) { continue; } - $cta = sprintf(__('升级%s', 'aksrt'), $lv_name); + $cta = __('升级', 'aksrt') . wp_kses_post($lv_name); $card_action = 'upgrade'; $vip_attr = 2; } elseif ($level > 0 && $lv < $level) { continue; } elseif ($level > 0 && $lv === $level) { - $cta = sprintf(__('续费%s', 'aksrt'), $lv_name); + $cta = __('续费', 'aksrt') . wp_kses_post($lv_name); $card_action = 'renew'; } if ($card_action === 'renew' && $renew_desc !== '') { @@ -158,14 +158,14 @@ foreach ([1, 2] as $lv) { $cards .= '
      '; $cards .= '
      '; $cards .= '
      '; - $cards .= '
      ' . $lv_icon . '' . esc_html($lv_name) . '
      '; + $cards .= '
      ' . $lv_icon . '' . wp_kses_post($lv_name) . '
      '; if ($card_desc !== '') { - $cards .= '
      ' . esc_html($card_desc) . '
      '; + $cards .= '
      ' . wp_kses_post($card_desc) . '
      '; } $cards .= '
      '; $cards .= $equity; $cards .= '' - . '' . esc_html($cta) . '' + . '' . $cta . '' . ' '; $cards .= '
      '; } diff --git a/src/Admin/Schema/SchemaPayment.php b/src/Admin/Schema/SchemaPayment.php index ae6074c9..58e60a14 100644 --- a/src/Admin/Schema/SchemaPayment.php +++ b/src/Admin/Schema/SchemaPayment.php @@ -213,7 +213,7 @@ class SchemaPayment { ['key' => 'vip2', 'title' => '二级会员', 'titleKey' => 'pay_user_vip_2_name', 'titleFallback' => '二级会员', 'fields' => self::vip_level_tab_fields(2)], ['key' => 'renew', 'title' => '会员续费', 'fields' => [ ['key' => 'vip_renew', 'label' => '允许续费', 'type' => 'switch', 'default' => true, 'help' => '关闭后则不能续费,只能会员到期之后再购买。永久会员不需要续费'], - ['key' => 'vip_renew_desc', 'label' => '续费介绍', 'type' => 'input', 'default' => '立即续费会员,畅享VIP权益', 'dependsOn' => ['key' => 'vip_renew', 'value' => true], 'help' => '续费会员的一句话简介'], + ['key' => 'vip_renew_desc', 'label' => '续费介绍', 'type' => 'input', 'sanitize_type' => 'html', 'default' => '立即续费会员,畅享VIP权益', 'dependsOn' => ['key' => 'vip_renew', 'value' => true], 'help' => '续费会员的一句话简介,支持HTML'], ['key' => 'vip_renew_price_type', 'label' => '续费价格类型', 'type' => 'select', 'default' => 'discount', 'dependsOn' => ['key' => 'vip_renew', 'value' => true], 'options' => [ ['label' => '相同价格', 'value' => 'same'], ['label' => '折扣价格', 'value' => 'discount'], @@ -247,7 +247,7 @@ class SchemaPayment { ]], ['key' => 'upgrade', 'title' => '会员升级', 'fields' => [ ['key' => 'vip_upgrade', 'label' => '允许升级', 'type' => 'switch', 'default' => true], - ['key' => 'vip_upgrade_desc', 'label' => '升级介绍', 'type' => 'input', 'default' => '升级VIP会员,享更多会员权益', 'dependsOn' => ['key' => 'vip_upgrade', 'value' => true], 'help' => '升级会员的一句话简介'], + ['key' => 'vip_upgrade_desc', 'label' => '升级介绍', 'type' => 'input', 'sanitize_type' => 'html', 'default' => '升级VIP会员,享更多会员权益', 'dependsOn' => ['key' => 'vip_upgrade', 'value' => true], 'help' => '升级会员的一句话简介,支持HTML'], [ 'key' => 'vip_upgrade_product', 'label' => '升级价格', 'type' => 'accordion', 'default' => [ 'month_to_month_s' => true, 'unit_price' => '10.00', 'unit_tag' => ' 站长推荐', 'unit_tag_class' => 'jb-yellow', @@ -311,7 +311,7 @@ class SchemaPayment { ['key' => 'pay_vip_points_exchange_desc', 'label' => '兑换说明', 'type' => 'textarea', 'sanitize_type' => 'html', 'default' => '', 'rows' => 2, 'dependsOn' => ['key' => 'pay_vip_points_exchange_s', 'value' => true], 'help' => '用户使用积分兑换时显示的内容,建议为提醒事项、兑换协议等,支持HTML代码'], ]], ['key' => 'modal-copy', 'title' => '界面文案', 'fields' => [ - ['key' => 'pay_user_vip_desc', 'label' => '会员介绍', 'type' => 'input', 'default' => '开通VIP会员,享受会员专属折扣以及多项特权', 'dependsOn' => ['any' => [['key' => 'pay_user_vip_1_s', 'value' => true], ['key' => 'pay_user_vip_2_s', 'value' => true]]]], + ['key' => 'pay_user_vip_desc', 'label' => '会员介绍', 'type' => 'input', 'sanitize_type' => 'html', 'default' => '开通VIP会员,享受会员专属折扣以及多项特权', 'dependsOn' => ['any' => [['key' => 'pay_user_vip_1_s', 'value' => true], ['key' => 'pay_user_vip_2_s', 'value' => true]]], 'help' => '显示在开通界面顶部的一句话简介,支持HTML'], ['key' => 'pay_user_vip_more', 'label' => '开通会员更多内容', 'type' => 'textarea', 'sanitize_type' => 'html', 'default' => '
      购买后不支持退款
      VIP权益仅适用于本站
      欢迎与站长联系
      ', 'rows' => 4, 'dependsOn' => ['any' => [['key' => 'pay_user_vip_1_s', 'value' => true], ['key' => 'pay_user_vip_2_s', 'value' => true]]]], ]], ], @@ -684,9 +684,9 @@ class SchemaPayment { $dep = ['key' => 'pay_user_vip_' . $level . '_s', 'value' => true]; $equity = '
    • 全站资源折扣购买
    • 部分内容免费阅读
    • 一对一技术指导
    • VIP用户专属QQ群
    • '; return [ - ['key' => 'pay_user_vip_' . $level . '_name', 'label' => '会员显示名称', 'type' => 'input', 'default' => $level === 1 ? '黄金会员' : '钻石会员', 'dependsOn' => $dep, 'help' => '会员名称(例如“黄金会员”、“超级会员”)'], - ['key' => 'pay_user_vip_' . $level . '_desc', 'label' => '一句话简介', 'type' => 'input', 'default' => '尊享专属特权', 'dependsOn' => $dep], - ['key' => 'pay_user_vip_' . $level . '_equity', 'label' => '会员权益 HTML', 'type' => 'textarea', 'sanitize_type' => 'html', 'default' => $equity, 'rows' => 4, 'dependsOn' => $dep, 'help' => '建议使用
    • 列表项'], + ['key' => 'pay_user_vip_' . $level . '_name', 'label' => '会员显示名称', 'type' => 'input', 'sanitize_type' => 'html', 'default' => $level === 1 ? '黄金会员' : '钻石会员', 'dependsOn' => $dep, 'help' => '会员名称,支持HTML'], + ['key' => 'pay_user_vip_' . $level . '_desc', 'label' => '一句话简介', 'type' => 'input', 'sanitize_type' => 'html', 'default' => '尊享专属特权', 'dependsOn' => $dep, 'help' => '支持HTML'], + ['key' => 'pay_user_vip_' . $level . '_equity', 'label' => '会员权益 HTML', 'type' => 'textarea', 'sanitize_type' => 'html', 'default' => $equity, 'rows' => 4, 'dependsOn' => $dep, 'help' => '支持安全HTML,建议使用
    • 列表项'], ['key' => 'vip_' . $level . '_product_s', 'label' => '购买会员', 'type' => 'switch', 'default' => true, 'dependsOn' => $dep, 'help' => '关闭后则不能付费购买会员,但可通过积分兑换会员,请确保已开启积分以及积分兑换会员功能'], [ 'key' => 'vip_' . $level . '_product', 'label' => '会员商品', 'type' => 'group', 'max' => 8, diff --git a/src/Content/VipServiceModal.php b/src/Content/VipServiceModal.php index a9dd88df..ee809ce6 100644 --- a/src/Content/VipServiceModal.php +++ b/src/Content/VipServiceModal.php @@ -38,8 +38,9 @@ trait VipServiceModal { : wp_kses_post($equity); $level_view[] = [ 'level' => $level, - 'name' => (string) ($levels[$level]['name'] ?? ('VIP' . $level)), - 'desc' => (string) ($levels[$level]['desc'] ?? ''), + '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'] ?? '')), ]; @@ -221,10 +222,10 @@ trait VipServiceModal { $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'] ?? ('VIP' . $level)); + $level_name = (string) ($levels[$level]['name_html'] ?? $levels[$level]['name'] ?? ('VIP' . $level)); $cards .= '
      ' - . '' . $this->points_exchange_card_icon($levels[$level] ?? []) . esc_html($level_name) . '' + . '' . $this->points_exchange_card_icon($levels[$level] ?? []) . wp_kses_post($level_name) . '' . '
      ' . '' . $duration . '' . '' . $points . '' . esc_html($currency) . '' @@ -292,14 +293,14 @@ trait VipServiceModal { * @param array $level_settings 等级设置(含 name / img_icon)。 */ private function points_exchange_card_icon(array $level_settings): string { - $name = (string) ($level_settings['name'] ?? ''); + $name_text = (string) ($level_settings['name'] ?? ''); $icon_url = (string) ($level_settings['img_icon'] ?? ''); if ($icon_url === '') { return ''; } return ''; + . ' title="' . esc_attr($name_text) . '" alt="' . esc_attr($name_text) . '">'; } /** @@ -330,26 +331,27 @@ trait VipServiceModal { if (empty($levels[$level]['enabled'])) { return ''; } - $name = (string) ($levels[$level]['name'] ?? ('VIP' . $level)); + $name = wp_kses_post((string) ($levels[$level]['name_html'] ?? $levels[$level]['name'] ?? ('VIP' . $level))); + $name_text = (string) ($levels[$level]['name'] ?? ('VIP' . $level)); $equity = (string) ($levels[$level]['equity'] ?? ''); $cls = $user_id > 0 ? ' pay-vip' : ' signin-loader'; - $equity_html = $equity !== '' - ? '
        ' . wp_kses_post($equity) . '
      ' - : ''; + $equity_html = $equity !== '' && function_exists('aksrt_format_vip_equity_html') + ? aksrt_format_vip_equity_html($equity, 'mb10 relative') + : ($equity !== '' ? '
      ' . wp_kses_post($equity) . '
      ' : ''); $icon_url = esc_url((string) ($levels[$level]['img_icon'] ?? '')); $icon = $icon_url !== '' - ? '' + ? '' : ''; $html = '
      '; $html .= ''; $html .= '
      ' . $icon . '
      '; $html .= '
      '; - $html .= '
      ' . $icon . '' . esc_html($name) . '
      '; + $html .= '
      ' . $icon . '' . wp_kses_post($name) . '
      '; $html .= $equity_html; $html .= '' - . esc_html(sprintf(__('开通%s', 'aksrt'), $name)) + . wp_kses_post(sprintf(__('开通%s', 'aksrt'), $name)) . ''; $html .= '
      '; return $html; diff --git a/src/Content/VipServicePurchase.php b/src/Content/VipServicePurchase.php index 9a2a5338..5a1bf017 100644 --- a/src/Content/VipServicePurchase.php +++ b/src/Content/VipServicePurchase.php @@ -137,7 +137,7 @@ trait VipServicePurchase { continue; } - $name = (string) ($levels[$lv]['name'] ?? ('VIP' . $lv)); + $name = trim(wp_strip_all_tags((string) ($levels[$lv]['name'] ?? ('VIP' . $lv)))); $items = []; // 自定义续费商品时按续费商品组计价,否则按会员商品组计价。 $level_action = 'pay'; diff --git a/src/Settings/PaymentRuntime.php b/src/Settings/PaymentRuntime.php index c1eb33f7..b6d9bf0d 100644 --- a/src/Settings/PaymentRuntime.php +++ b/src/Settings/PaymentRuntime.php @@ -170,13 +170,17 @@ class PaymentRuntime { $vip2_enabled = !empty($settings['pay_user_vip_2_s']); $enabled = $vip1_enabled || $vip2_enabled; - $name1 = trim((string) ($settings['pay_user_vip_1_name'] ?? '')); - $name2 = trim((string) ($settings['pay_user_vip_2_name'] ?? '')); + $name1_html = trim(wp_kses_post((string) ($settings['pay_user_vip_1_name'] ?? ''))); + $name2_html = trim(wp_kses_post((string) ($settings['pay_user_vip_2_name'] ?? ''))); + $name1 = sanitize_text_field(wp_strip_all_tags($name1_html)); + $name2 = sanitize_text_field(wp_strip_all_tags($name2_html)); if ($name1 === '') { $name1 = __('黄金会员', 'aksrt'); + $name1_html = $name1; } if ($name2 === '') { $name2 = __('钻石会员', 'aksrt'); + $name2_html = $name2; } $badge = $vip2_enabled ? $name2 : ($vip1_enabled ? $name1 : __('VIP', 'aksrt')); @@ -186,20 +190,8 @@ class PaymentRuntime { $renew_price_type = 'discount'; } - $desc = sanitize_text_field((string) ($settings['pay_user_vip_desc'] ?? __('开通VIP会员,享受会员专属折扣以及多项特权', 'aksrt'))); + $desc = wp_kses_post((string) ($settings['pay_user_vip_desc'] ?? __('开通VIP会员,享受会员专属折扣以及多项特权', 'aksrt'))); $more = (string) ($settings['pay_user_vip_more'] ?? '
      购买后不支持退款
      VIP权益仅适用于本站
      欢迎与站长联系
      '); - $allowed_equity = [ - 'li' => [], - 'ul' => [], - 'ol' => [], - 'strong' => [], - 'b' => [], - 'em' => [], - 'i' => ['class' => true], - 'span' => ['class' => true], - 'br' => [], - 'a' => ['href' => true, 'target' => true, 'rel' => true, 'class' => true], - ]; return [ 'enabled' => $enabled, @@ -215,8 +207,9 @@ class PaymentRuntime { 1 => [ 'enabled' => $vip1_enabled, 'name' => $name1, - 'desc' => sanitize_text_field((string) ($settings['pay_user_vip_1_desc'] ?? __('尊享专属特权', 'aksrt'))), - 'equity' => wp_kses((string) (($settings['pay_user_vip_1_equity'] ?? '') !== '' ? $settings['pay_user_vip_1_equity'] : '
    • 全站资源折扣购买
    • 部分内容免费阅读
    • 一对一技术指导
    • VIP用户专属QQ群
    • '), $allowed_equity), + 'name_html' => $name1_html, + 'desc' => wp_kses_post((string) ($settings['pay_user_vip_1_desc'] ?? __('尊享专属特权', 'aksrt'))), + 'equity' => wp_kses_post((string) (($settings['pay_user_vip_1_equity'] ?? '') !== '' ? $settings['pay_user_vip_1_equity'] : '
    • 全站资源折扣购买
    • 部分内容免费阅读
    • 一对一技术指导
    • VIP用户专属QQ群
    • ')), 'img_icon' => self::get_vip_icon_url(1, (string) ($settings['vip_1_img_icon'] ?? '')), 'product_s' => !isset($settings['vip_1_product_s']) || !empty($settings['vip_1_product_s']), 'products' => self::normalize_vip_product_items($settings['vip_1_product'] ?? null, self::vip_product_defaults()), @@ -226,8 +219,9 @@ class PaymentRuntime { 2 => [ 'enabled' => $vip2_enabled, 'name' => $name2, - 'desc' => sanitize_text_field((string) ($settings['pay_user_vip_2_desc'] ?? __('尊享专属特权', 'aksrt'))), - 'equity' => wp_kses((string) (($settings['pay_user_vip_2_equity'] ?? '') !== '' ? $settings['pay_user_vip_2_equity'] : '
    • 全站资源折扣购买
    • 部分内容免费阅读
    • 一对一技术指导
    • VIP用户专属QQ群
    • '), $allowed_equity), + 'name_html' => $name2_html, + 'desc' => wp_kses_post((string) ($settings['pay_user_vip_2_desc'] ?? __('尊享专属特权', 'aksrt'))), + 'equity' => wp_kses_post((string) (($settings['pay_user_vip_2_equity'] ?? '') !== '' ? $settings['pay_user_vip_2_equity'] : '
    • 全站资源折扣购买
    • 部分内容免费阅读
    • 一对一技术指导
    • VIP用户专属QQ群
    • ')), 'img_icon' => self::get_vip_icon_url(2, (string) ($settings['vip_2_img_icon'] ?? '')), 'product_s' => !isset($settings['vip_2_product_s']) || !empty($settings['vip_2_product_s']), 'products' => self::normalize_vip_product_items($settings['vip_2_product'] ?? null, self::vip_product_defaults()), @@ -240,7 +234,7 @@ class PaymentRuntime { ], 'renew' => [ 'enabled' => !isset($settings['vip_renew']) || !empty($settings['vip_renew']), - 'desc' => sanitize_text_field((string) ($settings['vip_renew_desc'] ?? '立即续费会员,畅享VIP权益')), + 'desc' => wp_kses_post((string) ($settings['vip_renew_desc'] ?? '立即续费会员,畅享VIP权益')), 'price_type' => $renew_price_type, 'discount' => max(0.1, min(9.9, (float) ($settings['vip_renew_discount'] ?? 8))), 'reduce' => self::money_decimal($settings['vip_renew_reduce'] ?? '20.00', '20.00', 50000), @@ -834,7 +828,7 @@ class PaymentRuntime { return [ 'enabled' => !isset($settings['vip_upgrade']) || !empty($settings['vip_upgrade']), - 'desc' => sanitize_text_field((string) ($settings['vip_upgrade_desc'] ?? '升级VIP会员,享更多会员权益')), + 'desc' => wp_kses_post((string) ($settings['vip_upgrade_desc'] ?? '升级VIP会员,享更多会员权益')), 'month_to_month_s' => $switch($raw['month_to_month_s'] ?? null, true), 'unit_price' => $money($raw['unit_price'] ?? null, '10.00'), 'unit_tag' => $tag($raw['unit_tag'] ?? null, ' 站长推荐'), diff --git a/tests/widget-system/page-display-contract.test.cjs b/tests/widget-system/page-display-contract.test.cjs index 7e11554a..33485941 100644 --- a/tests/widget-system/page-display-contract.test.cjs +++ b/tests/widget-system/page-display-contract.test.cjs @@ -156,9 +156,8 @@ test('header VIP hover cards match the legacy markup and style contract', () => assert.match(vipCardRenderer, /
      ]*>/) assert.match(vipCardRenderer, /
      /) assert.match(vipCardRenderer, /
      /) - assert.match(vipCardRenderer, /
        /) + assert.match(vipCardRenderer, /aksrt_format_vip_equity_html\(\$equity, 'mb10 relative'\)/) assert.doesNotMatch(vipCardRenderer, /vip-name__(?:icon|label)/) - assert.doesNotMatch(vipCardRenderer, /aksrt_format_vip_equity_html\(\$equity, 'mb10 relative'\)/) assert.doesNotMatch(vipModalCss, /Header hover VIP cards/) assert.doesNotMatch(vipModalCss, /\.navbar-vip-hover\.hover-show/) diff --git a/tests/widget-system/vip-modal-copy-fields.test.cjs b/tests/widget-system/vip-modal-copy-fields.test.cjs index bfd2feba..d724d5d7 100644 --- a/tests/widget-system/vip-modal-copy-fields.test.cjs +++ b/tests/widget-system/vip-modal-copy-fields.test.cjs @@ -52,7 +52,7 @@ test('弹窗文案字段更名为会员介绍/开通会员更多内容并改为 // 会员介绍为单行 input(zibll text「一句话简介」);开通会员更多内容为 rows 4 的 HTML textarea。 assert.match( vipSchemaJs, - /key: 'pay_user_vip_desc',\s*\n\s*label: '会员介绍',\s*\n\s*type: 'input',\s*\n\s*default: '开通VIP会员,享受会员专属折扣以及多项特权'/ + /key: 'pay_user_vip_desc',\s*\n\s*label: '会员介绍',\s*\n\s*type: 'input',\s*\n\s*sanitize_type: 'html',\s*\n\s*default: '开通VIP会员,享受会员专属折扣以及多项特权'/ ); assert.match( vipSchemaJs, @@ -60,7 +60,7 @@ test('弹窗文案字段更名为会员介绍/开通会员更多内容并改为 ); assert.match( schemaPhp, - /'key' => 'pay_user_vip_desc', 'label' => '会员介绍', 'type' => 'input', 'default' => '开通VIP会员,享受会员专属折扣以及多项特权'/ + /'key' => 'pay_user_vip_desc', 'label' => '会员介绍', 'type' => 'input', 'sanitize_type' => 'html', 'default' => '开通VIP会员,享受会员专属折扣以及多项特权'/ ); assert.match( schemaPhp, @@ -141,7 +141,7 @@ test('文案默认值三处一致并对齐 zibll', () => { assert.ok(schemaPhp.includes(`'default' => '${DESC_DEFAULT}'`)); assert.ok(schemaPhp.includes(`'default' => '${MORE_DEFAULT}'`)); // PaymentRuntime 运行时回退(get_all 不合并 schema 默认值,回退即实际默认值)。 - assert.ok(paymentRuntime.includes(`$desc = sanitize_text_field((string) ($settings['pay_user_vip_desc'] ?? __('${DESC_DEFAULT}', 'aksrt')))`)); + assert.ok(paymentRuntime.includes(`$desc = wp_kses_post((string) ($settings['pay_user_vip_desc'] ?? __('${DESC_DEFAULT}', 'aksrt')))`)); assert.ok(paymentRuntime.includes(`$more = (string) ($settings['pay_user_vip_more'] ?? '${MORE_DEFAULT}')`)); assert.ok(paymentRuntime.includes("$name1 = __('黄金会员', 'aksrt');")); assert.ok(paymentRuntime.includes("$name2 = __('钻石会员', 'aksrt');")); @@ -154,19 +154,41 @@ test('文案默认值三处一致并对齐 zibll', () => { } }); -test('权益与卡密说明字段保存时保留 HTML', () => { - // 权益字段已并入「会员购买选项」的等级 Tab,标签统一为「会员权益 HTML」。 - assert.ok(vipSchemaJs.includes("key: `pay_user_vip_${level}_equity`, label: '会员权益 HTML', type: 'textarea', sanitize_type: 'html'")); - assert.ok(schemaPhp.includes("['key' => 'pay_user_vip_' . $level . '_equity', 'label' => '会员权益 HTML', 'type' => 'textarea', 'sanitize_type' => 'html'")); - assert.ok(rechargeJs.includes("key: 'pay_vip_pass_charge_desc', label: '卡密兑换会员说明', type: 'textarea', sanitize_type: 'html'")); - assert.ok(schemaPhp.includes("'key' => 'pay_vip_pass_charge_desc', 'label' => '卡密兑换会员说明', 'type' => 'textarea', 'sanitize_type' => 'html'")); +test('会员等级名称、简介、续费和升级介绍都声明 HTML 净化', () => { + const levelFieldsJs = slice(vipSchemaJs, 'function vipLevelTabFields', 'export const vipSchema'); + const levelFieldsPhp = slice(schemaPhp, 'private static function vip_level_tab_fields', 'private static function vip_product_defaults'); + assert.match(levelFieldsJs, /pay_user_vip_\$\{level\}_name[^\n]*sanitize_type: 'html'/); + assert.match(levelFieldsJs, /pay_user_vip_\$\{level\}_desc[^\n]*sanitize_type: 'html'/); + assert.match(levelFieldsPhp, /pay_user_vip_' \. \$level \. '_name'[^\n]*'sanitize_type' => 'html'/); + assert.match(levelFieldsPhp, /pay_user_vip_' \. \$level \. '_desc'[^\n]*'sanitize_type' => 'html'/); + assert.match(vipSchemaJs, /key: 'vip_renew_desc',[\s\S]*?sanitize_type: 'html'/); + assert.match(schemaPhp, /'key' => 'vip_renew_desc'[^\n]*'sanitize_type' => 'html'/); + assert.match(vipSchemaJs, /key: 'vip_upgrade_desc',[^\n]*sanitize_type: 'html'/); + assert.match(schemaPhp, /'key' => 'vip_upgrade_desc'[^\n]*'sanitize_type' => 'html'/); + assert.match(vipSchemaJs, /key: 'pay_user_vip_desc',[\s\S]*?sanitize_type: 'html'/); + assert.match(schemaPhp, /'key' => 'pay_user_vip_desc'[^\n]*'sanitize_type' => 'html'/); }); -test('会员介绍运行时按单行文本净化', () => { - // 只收敛 VIP 设置函数:充值等其它设置的 $desc 与本文无关。 +test('权益和促销标签 HTML 通过安全净化及 HTML 渲染保留', () => { const vipSettings = slice(paymentRuntime, 'public static function get_payment_vip_settings', 'public static function get_payment_revenue_settings'); - assert.ok(vipSettings.includes("$desc = sanitize_text_field((string) ($settings['pay_user_vip_desc']")); - assert.ok(!vipSettings.includes('$desc = sanitize_textarea_field')); + assert.match(vipSettings, /'equity'\s*=> wp_kses_post\(/); + assert.doesNotMatch(vipSettings, /\$allowed_equity/); + const equityHelper = read('inc', 'helpers', 'aksrt-vip.php'); + assert.match(equityHelper, /\$clean = trim\(wp_kses_post\(\$raw\)\)/); + assert.match(payVipJs, /if \(item\.discount_tag\) append\(product, htmlNode\(/); + assert.match(payVipJs, /append\(info, htmlNode\("div", "", levelInfo\.equity\)\)/); + assert.match(payVipJs, /htmlNode\("div", "v-name", levelInfo\.name \|\| group\.name/); + assert.match(payVipJs, /htmlNode\("div", "v-desc em09", levelInfo\.desc/); + assert.match(vipTabPhp, /wp_kses_post\(\$card_desc\)/); +}); + +test('会员介绍按 post 级 HTML 安全净化并在弹窗与用户中心渲染', () => { + const vipSettings = slice(paymentRuntime, 'public static function get_payment_vip_settings', 'public static function get_payment_revenue_settings'); + assert.match(vipSettings, /\$desc = wp_kses_post\(\(string\) \(\$settings\['pay_user_vip_desc'\]/); + assert.match(vipSettings, /'desc'\s+=> wp_kses_post\(\(string\) \(\$settings\['pay_user_vip_1_desc'\]/); + assert.match(vipSettings, /'desc'\s+=> wp_kses_post\(\(string\) \(\$settings\['pay_user_vip_2_desc'\]/); + assert.match(vipTabPhp, /wp_kses_post\(\$vip_description\)/); + assert.match(payVipJs, /htmlNode\("div", "muted-2-color em09", catalog\.desc\)/); }); test('开通会员更多内容只在弹窗内渲染,不裸露在用户中心页面', () => { diff --git a/tests/widget-system/vip-settings-structured-contract.test.cjs b/tests/widget-system/vip-settings-structured-contract.test.cjs index 01c0797a..5698cd44 100644 --- a/tests/widget-system/vip-settings-structured-contract.test.cjs +++ b/tests/widget-system/vip-settings-structured-contract.test.cjs @@ -307,9 +307,21 @@ test('积分兑换明细.level 枚举净化后仍能通过校验,非法值依 assert.strictEqual(sanitize.invalid_unit.valid, false); assert.deepStrictEqual(sanitize.invalid_unit.errors, ['积分兑换明细[0].unit has an invalid value']); + // VIP's HTML-capable fields pass the schema save sanitizer intact. + assert.strictEqual(sanitize.vip_html.valid, true); + assert.strictEqual(sanitize.vip_html.fields.pay_user_vip_1_name, '会员'); + assert.strictEqual(sanitize.vip_html.fields.pay_user_vip_1_desc, '简介'); + assert.strictEqual(sanitize.vip_html.fields.pay_user_vip_1_equity, '

        权益

        '); + assert.strictEqual(sanitize.vip_html.fields.pay_user_vip_desc, '会员介绍'); + assert.strictEqual(sanitize.vip_html.fields.vip_renew_desc, '续费'); + assert.strictEqual(sanitize.vip_html.fields.vip_upgrade_desc, '升级'); + assert.strictEqual(sanitize.vip_html.fields.vip_1_product[0].tag, '推荐'); + // 字符串型枚举(palette)不受影响。 assert.strictEqual(sanitize.invalid_tag_class.valid, false); assert.deepStrictEqual(sanitize.invalid_tag_class.errors, ['会员商品[0].tag_class has an invalid value']); + + }); test('运行时归一化:默认值、价格净化、零价与非法行剔除', () => { @@ -345,7 +357,12 @@ test('运行时归一化:默认值、价格净化、零价与非法行剔除', // 自定义续费价格类型与折扣。 assert.strictEqual(custom.renew.price_type, 'customize'); assert.strictEqual(custom.renew.discount, 7.5); - assert.strictEqual(custom.renew.desc, '续费有礼'); + assert.strictEqual(custom.renew.desc, '续费有礼'); + assert.strictEqual(custom.upgrade.desc, '升级王者'); + assert.strictEqual(custom.levels['1'].name, '青铜会员'); + assert.strictEqual(custom.levels['1'].name_html, '青铜会员'); + assert.strictEqual(custom.levels['1'].desc, '专属权益'); + assert.strictEqual(custom.levels['1'].equity, '

        一对一支持

        '); assert.strictEqual(custom.levels['1'].renew_products[0].tag_class, 'b-red'); // 升级三块参数透传。 assert.strictEqual(custom.upgrade.unit_price, '12.50'); diff --git a/tests/widget-system/vip-settings-structured-harness.php b/tests/widget-system/vip-settings-structured-harness.php index 0de00ada..ada3658e 100644 --- a/tests/widget-system/vip-settings-structured-harness.php +++ b/tests/widget-system/vip-settings-structured-harness.php @@ -23,6 +23,7 @@ namespace { function sanitize_text_field($value): string { return trim(strip_tags((string) $value)); } function sanitize_textarea_field($value): string { return trim((string) $value); } function wp_kses_post($value): string { return (string) $value; } + function wp_strip_all_tags($value): string { return trim(strip_tags((string) $value)); } function wp_kses($value, $allowed = null): string { return (string) $value; } function trailingslashit($value): string { return rtrim((string) $value, '/\\') . '/'; } function get_template_directory_uri(): string { return 'https://example.test/wp-content/themes/aksrt'; } @@ -115,15 +116,17 @@ namespace { Settings::$snapshot = [ 'pay_user_vip_1_s' => true, 'pay_user_vip_2_s' => true, - 'pay_user_vip_1_name' => '青铜会员', - 'pay_user_vip_2_name' => '王者会员', + 'pay_user_vip_1_name' => '青铜会员', + 'pay_user_vip_2_name' => '王者会员', + 'pay_user_vip_1_desc' => '专属权益', + 'pay_user_vip_1_equity' => '

        一对一支持

        ', 'vip_renew' => true, - 'vip_renew_desc' => '续费有礼', + 'vip_renew_desc' => '续费有礼', 'vip_renew_price_type' => 'customize', 'vip_renew_discount' => 7.5, 'vip_renew_reduce' => '15.00', 'vip_upgrade' => true, - 'vip_upgrade_desc' => '升级王者', + 'vip_upgrade_desc' => '升级王者', 'vip_upgrade_product' => [ 'month_to_month_s' => true, 'unit_price' => '12.50', @@ -179,6 +182,17 @@ namespace { $sanitize = (static function (): array { $provider = \AKSRT\Admin\SchemaProvider::get_instance(); $cases = [ + 'vip_html' => [ + 'pay_user_vip_1_name' => '会员', + 'pay_user_vip_1_desc' => '简介', + 'pay_user_vip_1_equity' => '

        权益

        ', + 'pay_user_vip_desc' => '会员介绍', + 'vip_renew_desc' => '续费', + 'vip_upgrade_desc' => '升级', + 'vip_1_product' => [ + ['price' => '88.80', 'show_price' => '99.00', 'tag' => '推荐', 'tag_class' => 'jb-yellow', 'time' => 1, 'unit' => 'month'], + ], + ], 'default_rows' => [ 'pay_vip_points_exchange_s' => true, 'pay_vip_points_exchange_product' => [ @@ -215,6 +229,7 @@ namespace { $clean = $provider->sanitize_payload('payment', $payload, 'vip'); $result = $provider->validate('payment', $clean, 'vip'); $out[$name] = [ + 'fields' => $clean, 'points_rows' => $clean['pay_vip_points_exchange_product'] ?? null, 'valid' => $result['valid'], 'errors' => $result['errors'],