diff --git a/assets/admin/src/schema/sections/payment/vip.js b/assets/admin/src/schema/sections/payment/vip.js index f1b97026..54699f7e 100644 --- a/assets/admin/src/schema/sections/payment/vip.js +++ b/assets/admin/src/schema/sections/payment/vip.js @@ -208,7 +208,7 @@ export const vipSchema = { dependsOn: { key: 'pay_vip_points_exchange_s', value: true }, help: '每行:等级|积分|天数|标题(天数 0=永久;仅未开通会员可兑)', }, - { key: 'pay_vip_points_exchange_desc', label: '兑换说明', type: 'textarea', default: '', dependsOn: { key: 'pay_vip_points_exchange_s', value: true } }, + { 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代码' }, ], }, { diff --git a/src/Admin/Schema/SchemaPayment.php b/src/Admin/Schema/SchemaPayment.php index e12d665a..bd8c0ad1 100644 --- a/src/Admin/Schema/SchemaPayment.php +++ b/src/Admin/Schema/SchemaPayment.php @@ -243,7 +243,7 @@ class SchemaPayment { ['key' => 'points-exchange', 'title' => '积分兑换会员', 'fields' => [ ['key' => 'pay_vip_points_exchange_s', 'label' => '启用积分兑换', 'type' => 'switch', 'default' => false], ['key' => 'payment_vip_points_packages', 'label' => '兑换套餐', 'type' => 'textarea', 'default' => "1|500|30|积分月卡\n1|5000|0|积分永久", 'dependsOn' => ['key' => 'pay_vip_points_exchange_s', 'value' => true], 'help' => '每行:等级|积分|天数|标题(天数 0=永久;普通用户可开通,已有会员可续费同等级或升级更高等级)'], - ['key' => 'pay_vip_points_exchange_desc', 'label' => '兑换说明', 'type' => 'textarea', 'default' => '', 'dependsOn' => ['key' => 'pay_vip_points_exchange_s', 'value' => true]], + ['key' => 'pay_vip_points_exchange_desc', 'label' => '兑换说明', 'type' => 'textarea', 'sanitize_type' => 'html', 'default' => '', 'rows' => 2, 'dependsOn' => ['key' => 'pay_vip_points_exchange_s', 'value' => true]], ]], ['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]]]], diff --git a/src/Content/VipServiceModal.php b/src/Content/VipServiceModal.php index df38e60d..73fda631 100644 --- a/src/Content/VipServiceModal.php +++ b/src/Content/VipServiceModal.php @@ -238,7 +238,9 @@ trait VipServiceModal { $desc_text = trim((string) ($points_ex['desc'] ?? '')); $desc = $desc_text !== '' - ? '
' . esc_html($desc_text) . '
' + // 管理员编辑的富文本说明:保存端已 wp_kses_post,这里同级别输出 + // (参考主题 zibpay_get_vip_points_exchange_modal 原样拼接)。 + ? '
' . wp_kses_post($desc_text) . '
' : ''; // 最低价套餐仍超出用户积分 → 参考主题的 badg c-red btn-block 提示。 diff --git a/src/Settings/PaymentRuntime.php b/src/Settings/PaymentRuntime.php index 9bf3e887..3f083e25 100644 --- a/src/Settings/PaymentRuntime.php +++ b/src/Settings/PaymentRuntime.php @@ -261,7 +261,7 @@ class PaymentRuntime { ], 'points_exchange' => [ 'enabled' => !empty($settings['pay_vip_points_exchange_s']), - 'desc' => sanitize_textarea_field((string) ($settings['pay_vip_points_exchange_desc'] ?? '')), + 'desc' => wp_kses_post((string) ($settings['pay_vip_points_exchange_desc'] ?? '')), 'packages_raw' => sanitize_textarea_field((string) ($settings['payment_vip_points_packages'] ?? "1|500|30|积分月卡\n1|5000|0|积分永久")), ], ]; diff --git a/tests/widget-system/vip-modal-copy-fields.test.cjs b/tests/widget-system/vip-modal-copy-fields.test.cjs index 6051b538..7614a95b 100644 --- a/tests/widget-system/vip-modal-copy-fields.test.cjs +++ b/tests/widget-system/vip-modal-copy-fields.test.cjs @@ -172,3 +172,15 @@ test('弹窗内 more 与卡密说明按 HTML 渲染而非纯文本', () => { assert.ok(payVipJs.includes('htmlNode("div", "muted-2-color mb10 em09 muted-box padding-10 mini-radius", catalog.more)')); assert.ok(payVipJs.includes('htmlNode("div", "muted-box muted-2-color padding-10 mb10 em09", cardPass.desc)')); }); + +test('积分兑换说明按 post 级 HTML 放行并原样渲染', () => { + // 参考主题该字段 sanitize=false 且弹窗内原样拼接;AKSRT 对齐为保存/输出 + // 双端 wp_kses_post + 弹窗 wp_kses_post 渲染。 + assert.match(vipSchemaJs, /key: 'pay_vip_points_exchange_desc', label: '兑换说明', type: 'textarea', sanitize_type: 'html'/); + assert.match(schemaPhp, /'key' => 'pay_vip_points_exchange_desc', 'label' => '兑换说明', 'type' => 'textarea', 'sanitize_type' => 'html'/); + const vipSettings = slice(paymentRuntime, 'public static function get_payment_vip_settings', 'public static function get_payment_revenue_settings'); + assert.match(vipSettings, /'desc'\s+=> wp_kses_post\(\(string\) \(\$settings\['pay_vip_points_exchange_desc'\]/); + // 弹窗渲染端不再 esc_html 剥标签。 + assert.match(modalSource, /
' \. wp_kses_post\(\$desc_text\)/); + assert.doesNotMatch(modalSource, /esc_html\(\$desc_text\)/); +});