fix(vip): align modal copy fields with reference theme backend

Rename and reposition the two VIP modal copy settings to match the zibll reference backend, drop dead renew/upgrade desc config, and align copy defaults.

- pay_user_vip_desc: modal top intro -> 会员介绍, textarea -> input (one-line), zibll help text, default copy from zibll

- pay_user_vip_more: bottom note -> 开通会员更多内容, rows 4 + sanitize_type html (HTML was stripped on save), zibll help text and three-div default

- Both fields move from the member-level group to a new 界面文案 (modal-copy) group at the end of the VIP section; dependsOn any-of-two-levels adapted from zibll's pay_user_vip_1_s dependency

- Copy defaults synced in three places (SPA schema, PHP schema, PaymentRuntime fallbacks, since Settings::get_all() does not merge schema defaults): 黄金会员/钻石会员, 尊享专属特权, zibll four-item equity list

- Remove vip_renew_desc / vip_upgrade_desc: AKSRT has no renew/upgrade card render surface (render_vip_card takes no type) and zero consumers repo-wide; zibll renders them on page-level cards only

- pay_user_vip_1/2_equity and pay_vip_pass_charge_desc gain sanitize_type html so admins no longer lose list/HTML markup on save

Verification: new tests/widget-system/vip-modal-copy-fields.test.cjs (6 contract tests); node --test tests/**/*.test.cjs 634/634 pass; php -l clean on both changed PHP files; assets/admin npm run build succeeds
This commit is contained in:
kate522 committed 2026-10-18 14:30:15 +08:00
1 parent cb76b95b01
commit 7f556892ad
5 files changed
+204 -42

No files matched your search

@@ -111,7 +111,7 @@ export const rechargeSchema = {
{ key: 'points_pass_exchange_desc', label: '卡密兑换积分说明', type: 'textarea', default: '', rows: 2, dependsOn: { key: 'points_pass_exchange_s', value: true }, help: '用户使用卡密兑换积分时显示的内容,建议为提醒事项、兑换协议等,支持HTML代码' },
{ key: 'points_pass_exchange_only_password', label: '积分卡密单密码模式', type: 'switch', default: false, dependsOn: { key: 'points_pass_exchange_s', value: true }, help: '积分卡密仅需输入密码,不需要卡号' },
{ key: 'pay_vip_pass_charge_s', label: '启用卡密兑换会员功能', type: 'switch', default: false, help: '开启后用户可在会员购买/续费弹窗选择「卡密」渠道,使用会员卡密直接兑换会员' },
{ key: 'pay_vip_pass_charge_desc', label: '卡密兑换会员说明', type: 'textarea', default: '', rows: 2, dependsOn: { key: 'pay_vip_pass_charge_s', value: true }, help: '用户使用卡密兑换会员时显示的内容,建议为提醒事项、兑换协议等,支持HTML代码' },
{ key: 'pay_vip_pass_charge_desc', label: '卡密兑换会员说明', type: 'textarea', sanitize_type: 'html', default: '', rows: 2, dependsOn: { key: 'pay_vip_pass_charge_s', value: true }, help: '用户使用卡密兑换会员时显示的内容,建议为提醒事项、兑换协议等,支持HTML代码' },
{ key: 'pay_vip_pass_charge_only_password', label: '会员卡密单密码模式', type: 'switch', default: false, dependsOn: { key: 'pay_vip_pass_charge_s', value: true }, help: '会员卡密仅需输入密码,不需要卡号;默认关闭,会员卡需输入卡号和密码(与其他卡密兑换一致)' },
],
},
+39 -20
View File
@@ -11,17 +11,17 @@ export const vipSchema = {
nav_pay_vip: true,
nav_user_pay_vip: true,
pay_user_vip_1_s: true,
pay_user_vip_1_name: '一级会员',
pay_user_vip_1_desc: '尊享一级会员特权',
pay_user_vip_1_equity: '<li>免费下载部分资源</li><li>会员专属身份标识</li><li>付费内容会员价</li>',
pay_user_vip_1_name: '黄金会员',
pay_user_vip_1_desc: '尊享专属特权',
pay_user_vip_1_equity: '<li>全站资源折扣购买</li><li>部分内容免费阅读</li><li>一对一技术指导</li><li>VIP用户专属QQ群</li>',
vip_1_img_icon: '',
pay_user_vip_2_s: true,
pay_user_vip_2_name: '二级会员',
pay_user_vip_2_desc: '尊享二级会员特权',
pay_user_vip_2_equity: '<li>更高免费下载额度</li><li>更高等级身份标识</li><li>更多付费内容会员价</li>',
pay_user_vip_2_name: '钻石会员',
pay_user_vip_2_desc: '尊享专属特权',
pay_user_vip_2_equity: '<li>全站资源折扣购买</li><li>部分内容免费阅读</li><li>一对一技术指导</li><li>VIP用户专属QQ群</li>',
vip_2_img_icon: '',
pay_user_vip_desc: '',
pay_user_vip_more: '',
pay_user_vip_desc: '开通VIP会员,享受会员专属折扣以及多项特权',
pay_user_vip_more: '<div>购买后不支持退款</div><div>VIP权益仅适用于本站</div><div>欢迎与站长联系</div>',
pay_download_limit: 0,
pay_download_speed: 0,
pay_download_limit_vip_1: 0,
@@ -33,12 +33,10 @@ export const vipSchema = {
vip_2_product_s: true,
payment_vip_2_products: '58|99|1|month|限时|月卡\n168|299|3|month|特惠|季卡\n598|999|12|month|超值|年卡\n1299|1999|0|day|永久|永久卡',
vip_renew: true,
vip_renew_desc: '',
vip_renew_price_type: 'discount',
vip_renew_discount: 8,
vip_renew_reduce: '20.00',
vip_upgrade: true,
vip_upgrade_desc: '',
vip_upgrade_product: 'unit|1|按剩余天数升级\njump|199|升级永久二级会员\npermanent|299|永久一级升永久二级',
pay_vip_points_exchange_s: false,
payment_vip_points_packages: '1|500|30|积分月卡\n1|5000|0|积分永久',
@@ -67,17 +65,15 @@ export const vipSchema = {
{ key: 'nav_pay_vip', label: '导航栏购买按钮', type: 'switch', default: true, help: '在顶部导航栏显示开通会员按钮;请注意顶部导航的整体宽度。' },
{ key: 'nav_user_pay_vip', label: '用户框购买按钮', type: 'switch', default: true, help: '在导航栏用户框内显示开通会员按钮。' },
{ key: 'pay_user_vip_1_s', label: '启用一级会员', type: 'switch', default: true },
{ key: 'pay_user_vip_1_name', label: '一级会员显示名称', type: 'input', default: '一级会员', dependsOn: { key: 'pay_user_vip_1_s', value: true } },
{ key: 'pay_user_vip_1_desc', label: '一级会员一句话简介', type: 'input', default: '尊享一级会员特权', dependsOn: { key: 'pay_user_vip_1_s', value: true } },
{ key: 'pay_user_vip_1_equity', label: '一级会员权益 HTML', type: 'textarea', default: '<li>免费下载部分资源</li><li>会员专属身份标识</li><li>付费内容会员价</li>', dependsOn: { key: 'pay_user_vip_1_s', value: true }, help: '建议使用 <li> 列表项' },
{ key: 'pay_user_vip_1_name', label: '一级会员显示名称', type: 'input', default: '黄金会员', dependsOn: { key: 'pay_user_vip_1_s', value: true } },
{ key: 'pay_user_vip_1_desc', label: '一级会员一句话简介', type: 'input', default: '尊享专属特权', dependsOn: { key: 'pay_user_vip_1_s', value: true } },
{ key: 'pay_user_vip_1_equity', label: '一级会员权益 HTML', type: 'textarea', sanitize_type: 'html', default: '<li>全站资源折扣购买</li><li>部分内容免费阅读</li><li>一对一技术指导</li><li>VIP用户专属QQ群</li>', rows: 4, dependsOn: { key: 'pay_user_vip_1_s', value: true }, help: '建议使用 <li> 列表项' },
{ key: 'vip_1_img_icon', label: '一级会员图标 URL', type: 'input', default: '', dependsOn: { key: 'pay_user_vip_1_s', value: true }, help: '留空使用内置 vip-1.svg 图标' },
{ key: 'pay_user_vip_2_s', label: '启用二级会员', type: 'switch', default: true },
{ key: 'pay_user_vip_2_name', label: '二级会员显示名称', type: 'input', default: '二级会员', dependsOn: { key: 'pay_user_vip_2_s', value: true } },
{ key: 'pay_user_vip_2_desc', label: '二级会员一句话简介', type: 'input', default: '尊享二级会员特权', dependsOn: { key: 'pay_user_vip_2_s', value: true } },
{ key: 'pay_user_vip_2_equity', label: '二级会员权益 HTML', type: 'textarea', default: '<li>更高免费下载额度</li><li>更高等级身份标识</li><li>更多付费内容会员价</li>', dependsOn: { key: 'pay_user_vip_2_s', value: true } },
{ key: 'pay_user_vip_2_name', label: '二级会员显示名称', type: 'input', default: '钻石会员', dependsOn: { key: 'pay_user_vip_2_s', value: true } },
{ key: 'pay_user_vip_2_desc', label: '二级会员一句话简介', type: 'input', default: '尊享专属特权', dependsOn: { key: 'pay_user_vip_2_s', value: true } },
{ key: 'pay_user_vip_2_equity', label: '二级会员权益 HTML', type: 'textarea', sanitize_type: 'html', default: '<li>全站资源折扣购买</li><li>部分内容免费阅读</li><li>一对一技术指导</li><li>VIP用户专属QQ群</li>', rows: 4, dependsOn: { key: 'pay_user_vip_2_s', value: true } },
{ key: 'vip_2_img_icon', label: '二级会员图标 URL', type: 'input', default: '', dependsOn: { key: 'pay_user_vip_2_s', value: true }, help: '留空使用内置 vip-2.svg 图标' },
{ key: 'pay_user_vip_desc', label: '弹窗顶部会员说明', type: 'textarea', default: '' },
{ key: 'pay_user_vip_more', label: '弹窗底部补充说明 HTML', type: 'textarea', default: '' },
],
},
{
@@ -163,7 +159,6 @@ export const vipSchema = {
title: '会员续费',
fields: [
{ key: 'vip_renew', label: '允许续费', type: 'switch', default: true, help: '关闭后则不能续费,只能会员到期之后再购买。永久会员不需要续费' },
{ key: 'vip_renew_desc', label: '续费说明', type: 'textarea', default: '', dependsOn: { key: 'vip_renew', value: true } },
{
key: 'vip_renew_price_type',
label: '续费价格类型',
@@ -185,7 +180,6 @@ export const vipSchema = {
title: '会员升级',
fields: [
{ key: 'vip_upgrade', label: '允许升级', type: 'switch', default: true },
{ key: 'vip_upgrade_desc', label: '升级说明', type: 'textarea', default: '', dependsOn: { key: 'vip_upgrade', value: true } },
{
key: 'vip_upgrade_product',
label: '升级产品',
@@ -217,5 +211,30 @@ export const vipSchema = {
{ key: 'pay_vip_points_exchange_desc', label: '兑换说明', type: 'textarea', default: '', dependsOn: { key: 'pay_vip_points_exchange_s', value: true } },
],
},
{
key: 'modal-copy',
title: '界面文案',
icon: 'fa fas fa-align-left',
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 }] },
help: '显示在开通界面顶部一句话简介,可以为会员权益简介或者活动介绍',
},
{
key: 'pay_user_vip_more',
label: '开通会员更多内容',
type: 'textarea',
sanitize_type: 'html',
default: '<div>购买后不支持退款</div><div>VIP权益仅适用于本站</div><div>欢迎与站长联系</div>',
rows: 4,
dependsOn: { any: [{ key: 'pay_user_vip_1_s', value: true }, { key: 'pay_user_vip_2_s', value: true }] },
help: '显示在开通界面底部位置,可以为提醒事项、用户协议等,支持HTML代码',
},
],
},
],
}
+11 -11
View File
@@ -159,7 +159,7 @@ class SchemaPayment {
['key' => 'points_pass_exchange_desc', 'label' => '卡密兑换积分说明', 'type' => 'textarea', 'default' => '', 'rows' => 2, 'dependsOn' => ['key' => 'points_pass_exchange_s', 'value' => true], 'help' => '用户使用卡密兑换积分时显示的内容,建议为提醒事项、兑换协议等,支持HTML代码'],
['key' => 'points_pass_exchange_only_password', 'label' => '积分卡密单密码模式', 'type' => 'switch', 'default' => false, 'dependsOn' => ['key' => 'points_pass_exchange_s', 'value' => true], 'help' => '积分卡密仅需输入密码,不需要卡号'],
['key' => 'pay_vip_pass_charge_s', 'label' => '启用卡密兑换会员功能', 'type' => 'switch', 'default' => false, 'help' => '开启后用户可在会员购买/续费弹窗选择「卡密」渠道,使用会员卡密直接兑换会员'],
['key' => 'pay_vip_pass_charge_desc', 'label' => '卡密兑换会员说明', 'type' => 'textarea', 'default' => '', 'rows' => 2, 'dependsOn' => ['key' => 'pay_vip_pass_charge_s', 'value' => true], 'help' => '用户使用卡密兑换会员时显示的内容,建议为提醒事项、兑换协议等,支持HTML代码'],
['key' => 'pay_vip_pass_charge_desc', 'label' => '卡密兑换会员说明', 'type' => 'textarea', 'sanitize_type' => 'html', 'default' => '', 'rows' => 2, 'dependsOn' => ['key' => 'pay_vip_pass_charge_s', 'value' => true], 'help' => '用户使用卡密兑换会员时显示的内容,建议为提醒事项、兑换协议等,支持HTML代码'],
['key' => 'pay_vip_pass_charge_only_password', 'label' => '会员卡密单密码模式', 'type' => 'switch', 'default' => false, 'dependsOn' => ['key' => 'pay_vip_pass_charge_s', 'value' => true], 'help' => '会员卡密仅需输入密码,不需要卡号;默认关闭,会员卡需输入卡号和密码(与其他卡密兑换一致)'],
]],
['key' => 'points-exchange', 'title' => '余额兑换积分', 'fields' => [
@@ -188,17 +188,15 @@ class SchemaPayment {
['key' => 'nav_pay_vip', 'label' => '导航栏购买按钮', 'type' => 'switch', 'default' => true, 'help' => '在顶部导航栏显示开通会员按钮;请注意顶部导航的整体宽度。'],
['key' => 'nav_user_pay_vip', 'label' => '用户框购买按钮', 'type' => 'switch', 'default' => true, 'help' => '在导航栏用户框内显示开通会员按钮。'],
['key' => 'pay_user_vip_1_s', 'label' => '启用一级会员', 'type' => 'switch', 'default' => true],
['key' => 'pay_user_vip_1_name', 'label' => '一级会员显示名称', 'type' => 'input', 'default' => '一级会员', 'dependsOn' => ['key' => 'pay_user_vip_1_s', 'value' => true]],
['key' => 'pay_user_vip_1_desc', 'label' => '一级会员一句话简介', 'type' => 'input', 'default' => '尊享一级会员特权', 'dependsOn' => ['key' => 'pay_user_vip_1_s', 'value' => true]],
['key' => 'pay_user_vip_1_equity', 'label' => '一级会员权益 HTML', 'type' => 'textarea', 'default' => '<li>免费下载部分资源</li><li>会员专属身份标识</li><li>付费内容会员价</li>', 'dependsOn' => ['key' => 'pay_user_vip_1_s', 'value' => true]],
['key' => 'pay_user_vip_1_name', 'label' => '一级会员显示名称', 'type' => 'input', 'default' => '黄金会员', 'dependsOn' => ['key' => 'pay_user_vip_1_s', 'value' => true]],
['key' => 'pay_user_vip_1_desc', 'label' => '一级会员一句话简介', 'type' => 'input', 'default' => '尊享专属特权', 'dependsOn' => ['key' => 'pay_user_vip_1_s', 'value' => true]],
['key' => 'pay_user_vip_1_equity', 'label' => '一级会员权益 HTML', 'type' => 'textarea', 'sanitize_type' => 'html', 'default' => '<li>全站资源折扣购买</li><li>部分内容免费阅读</li><li>一对一技术指导</li><li>VIP用户专属QQ群</li>', 'rows' => 4, 'dependsOn' => ['key' => 'pay_user_vip_1_s', 'value' => true]],
['key' => 'vip_1_img_icon', 'label' => '一级会员图标 URL', 'type' => 'input', 'default' => '', 'dependsOn' => ['key' => 'pay_user_vip_1_s', 'value' => true], 'help' => '留空使用内置 vip-1.svg 图标'],
['key' => 'pay_user_vip_2_s', 'label' => '启用二级会员', 'type' => 'switch', 'default' => true],
['key' => 'pay_user_vip_2_name', 'label' => '二级会员显示名称', 'type' => 'input', 'default' => '二级会员', 'dependsOn' => ['key' => 'pay_user_vip_2_s', 'value' => true]],
['key' => 'pay_user_vip_2_desc', 'label' => '二级会员一句话简介', 'type' => 'input', 'default' => '尊享二级会员特权', 'dependsOn' => ['key' => 'pay_user_vip_2_s', 'value' => true]],
['key' => 'pay_user_vip_2_equity', 'label' => '二级会员权益 HTML', 'type' => 'textarea', 'default' => '<li>更高免费下载额度</li><li>更高等级身份标识</li><li>更多付费内容会员价</li>', 'dependsOn' => ['key' => 'pay_user_vip_2_s', 'value' => true]],
['key' => 'pay_user_vip_2_name', 'label' => '二级会员显示名称', 'type' => 'input', 'default' => '钻石会员', 'dependsOn' => ['key' => 'pay_user_vip_2_s', 'value' => true]],
['key' => 'pay_user_vip_2_desc', 'label' => '二级会员一句话简介', 'type' => 'input', 'default' => '尊享专属特权', 'dependsOn' => ['key' => 'pay_user_vip_2_s', 'value' => true]],
['key' => 'pay_user_vip_2_equity', 'label' => '二级会员权益 HTML', 'type' => 'textarea', 'sanitize_type' => 'html', 'default' => '<li>全站资源折扣购买</li><li>部分内容免费阅读</li><li>一对一技术指导</li><li>VIP用户专属QQ群</li>', 'rows' => 4, 'dependsOn' => ['key' => 'pay_user_vip_2_s', 'value' => true]],
['key' => 'vip_2_img_icon', 'label' => '二级会员图标 URL', 'type' => 'input', 'default' => '', 'dependsOn' => ['key' => 'pay_user_vip_2_s', 'value' => true], 'help' => '留空使用内置 vip-2.svg 图标'],
['key' => 'pay_user_vip_desc', 'label' => '弹窗顶部会员说明', 'type' => 'textarea', 'default' => ''],
['key' => 'pay_user_vip_more', 'label' => '弹窗底部补充说明 HTML', 'type' => 'textarea', 'default' => ''],
]],
['key' => 'benefit', 'title' => '会员权益', 'fields' => [
['key' => 'vip_benefit', 'label' => '会员权益', 'type' => 'tabbed', 'tabs' => [
@@ -228,7 +226,6 @@ class SchemaPayment {
]],
['key' => 'renew', 'title' => '会员续费', 'fields' => [
['key' => 'vip_renew', 'label' => '允许续费', 'type' => 'switch', 'default' => true, 'help' => '关闭后则不能续费,只能会员到期之后再购买。永久会员不需要续费'],
['key' => 'vip_renew_desc', 'label' => '续费说明', 'type' => 'textarea', 'default' => '', 'dependsOn' => ['key' => 'vip_renew', 'value' => true]],
['key' => 'vip_renew_price_type', 'label' => '续费价格类型', 'type' => 'radio', 'default' => 'discount', 'dependsOn' => ['key' => 'vip_renew', 'value' => true], 'options' => [
['label' => '相同价格', 'value' => 'same'],
['label' => '折扣价格', 'value' => 'discount'],
@@ -239,7 +236,6 @@ class SchemaPayment {
]],
['key' => 'upgrade', 'title' => '会员升级', 'fields' => [
['key' => 'vip_upgrade', 'label' => '允许升级', 'type' => 'switch', 'default' => true],
['key' => 'vip_upgrade_desc', 'label' => '升级说明', 'type' => 'textarea', 'default' => '', 'dependsOn' => ['key' => 'vip_upgrade', 'value' => true]],
['key' => 'vip_upgrade_product', 'label' => '升级产品', 'type' => 'textarea', 'default' => "unit|1|按剩余天数升级\njump|199|升级永久二级会员\npermanent|299|永久一级升永久二级", 'dependsOn' => ['key' => 'vip_upgrade', 'value' => true], 'help' => '每行一条。永久VIP1:permanent|价格|标题;剩余天:unit|每日单价|标题;跳转永久:jump|价格|标题'],
]],
]],
@@ -249,6 +245,10 @@ class SchemaPayment {
['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' => '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_more', 'label' => '开通会员更多内容', 'type' => 'textarea', 'sanitize_type' => 'html', 'default' => '<div>购买后不支持退款</div><div>VIP权益仅适用于本站</div><div>欢迎与站长联系</div>', 'rows' => 4, 'dependsOn' => ['any' => [['key' => 'pay_user_vip_1_s', 'value' => true], ['key' => 'pay_user_vip_2_s', 'value' => true]]]],
]],
],
],
+8 -10
View File
@@ -171,10 +171,10 @@ class PaymentRuntime {
$name1 = trim((string) ($settings['pay_user_vip_1_name'] ?? ''));
$name2 = trim((string) ($settings['pay_user_vip_2_name'] ?? ''));
if ($name1 === '') {
$name1 = __('一级会员', 'aksrt');
$name1 = __('黄金会员', 'aksrt');
}
if ($name2 === '') {
$name2 = __('二级会员', 'aksrt');
$name2 = __('钻石会员', 'aksrt');
}
$badge = $vip2_enabled ? $name2 : ($vip1_enabled ? $name1 : __('VIP', 'aksrt'));
@@ -196,8 +196,8 @@ class PaymentRuntime {
$products_2 = $default_l2;
}
$desc = sanitize_textarea_field((string) ($settings['pay_user_vip_desc'] ?? ''));
$more = (string) ($settings['pay_user_vip_more'] ?? '');
$desc = sanitize_text_field((string) ($settings['pay_user_vip_desc'] ?? __('开通VIP会员,享受会员专属折扣以及多项特权', 'aksrt')));
$more = (string) ($settings['pay_user_vip_more'] ?? '<div>购买后不支持退款</div><div>VIP权益仅适用于本站</div><div>欢迎与站长联系</div>');
$allowed_equity = [
'li' => [],
'ul' => [],
@@ -223,8 +223,8 @@ 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'] : '<li>免费下载部分资源</li><li>会员专属身份标识</li><li>付费内容会员价</li>'), $allowed_equity),
'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'] : '<li>全站资源折扣购买</li><li>部分内容免费阅读</li><li>一对一技术指导</li><li>VIP用户专属QQ群</li>'), $allowed_equity),
'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_raw' => $products_1,
@@ -233,8 +233,8 @@ 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'] : '<li>更高免费下载额度</li><li>更高等级身份标识</li><li>更多付费内容会员价</li>'), $allowed_equity),
'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'] : '<li>全站资源折扣购买</li><li>部分内容免费阅读</li><li>一对一技术指导</li><li>VIP用户专属QQ群</li>'), $allowed_equity),
'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_raw' => $products_2,
@@ -246,14 +246,12 @@ class PaymentRuntime {
],
'renew' => [
'enabled' => !isset($settings['vip_renew']) || !empty($settings['vip_renew']),
'desc' => sanitize_textarea_field((string) ($settings['vip_renew_desc'] ?? '')),
'price_type' => $renew_price_type,
'discount' => max(1, min(100, (int) ($settings['vip_renew_discount'] ?? 8))),
'reduce' => self::money_decimal($settings['vip_renew_reduce'] ?? '20.00', '20.00', 999900),
],
'upgrade' => [
'enabled' => !isset($settings['vip_upgrade']) || !empty($settings['vip_upgrade']),
'desc' => sanitize_textarea_field((string) ($settings['vip_upgrade_desc'] ?? '')),
// Multi-line upgrade product rules — must not collapse newlines.
'product' => sanitize_textarea_field((string) ($settings['vip_upgrade_product'] ?? '')),
],
@@ -0,0 +1,145 @@
/**
* VIP 后台设置弹窗文案字段契约(对照参考主题 zibll 修正)。
*
* 硬约束:
* 1) 弹窗文案字段更名为「会员介绍」(pay_user_vip_desc, input) 与
* 「开通会员更多内容」(pay_user_vip_more, textarea rows 4 +
* sanitize_type html),并从「会员等级」组移到分区末尾的
* 「界面文案」(modal-copy) 独立分组;
* 2) 文案默认值对齐 zibll(黄金会员/钻石会员、尊享专属特权、权益四项、
* 会员介绍与开通会员更多内容默认值),SPA schema / PHP schema /
* PaymentRuntime 回退三处一致——Settings::get_all() 不合并
* schema 默认值,运行时回退即实际默认值;
* 3) vip_renew_desc / vip_upgrade_desc 为死配置(AKSRT 无续费/升级
* 卡渲染面,全仓零消费方),两份 schema 与 PaymentRuntime 均已删除;
* 4) 权益与卡密说明字段保存时保留 HTML(sanitize_type html),
* 不再被 sanitize_textarea_field 剥掉标签。
*/
const test = require('node:test');
const assert = require('node:assert');
const fs = require('node:fs');
const path = require('node:path');
const root = path.join(__dirname, '..', '..');
const read = (...seg) => fs.readFileSync(path.join(root, ...seg), 'utf8');
const vipSchemaJs = read('assets', 'admin', 'src', 'schema', 'sections', 'payment', 'vip.js');
const schemaPhp = read('src', 'Admin', 'Schema', 'SchemaPayment.php');
const paymentRuntime = read('src', 'Settings', 'PaymentRuntime.php');
const rechargeJs = read('assets', 'admin', 'src', 'schema', 'sections', 'payment', 'recharge.js');
const DESC_DEFAULT = '开通VIP会员,享受会员专属折扣以及多项特权';
const MORE_DEFAULT = '<div>购买后不支持退款</div><div>VIP权益仅适用于本站</div><div>欢迎与站长联系</div>';
const EQUITY_DEFAULT = '<li>全站资源折扣购买</li><li>部分内容免费阅读</li><li>一对一技术指导</li><li>VIP用户专属QQ群</li>';
// 取两个标记之间的源码段(分组切片,避免断言误伤其它功能的同名键)。
function slice(source, from, to) {
const start = source.indexOf(from);
assert.notStrictEqual(start, -1, `missing ${from}`);
const end = source.indexOf(to, start + from.length);
return source.slice(start, end === -1 ? undefined : end);
}
test('弹窗文案字段更名为会员介绍/开通会员更多内容并改为正确类型', () => {
for (const [name, src] of [['vip.js', vipSchemaJs], ['SchemaPayment.php', schemaPhp]]) {
assert.ok(!src.includes('弹窗顶部会员说明') && !src.includes('弹窗底部补充说明 HTML'), `${name} 旧名称应已移除`);
assert.ok(src.includes('会员介绍') && src.includes('开通会员更多内容'), `${name} 应含新名称`);
}
// 会员介绍为单行 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会员,享受会员专属折扣以及多项特权'/
);
assert.match(
vipSchemaJs,
/key: 'pay_user_vip_more',\s*\n\s*label: '开通会员更多内容',\s*\n\s*type: 'textarea',\s*\n\s*sanitize_type: 'html'[\s\S]*?rows: 4/
);
assert.match(
schemaPhp,
/'key' => 'pay_user_vip_desc', 'label' => '会员介绍', 'type' => 'input', 'default' => '开通VIP会员,享受会员专属折扣以及多项特权'/
);
assert.match(
schemaPhp,
/'key' => 'pay_user_vip_more', 'label' => '开通会员更多内容', 'type' => 'textarea', 'sanitize_type' => 'html'[\s\S]*?'rows' => 4/
);
// 帮助文本对齐 zibll 的 desc。
assert.ok(vipSchemaJs.includes('显示在开通界面顶部一句话简介,可以为会员权益简介或者活动介绍'));
assert.ok(vipSchemaJs.includes('显示在开通界面底部位置,可以为提醒事项、用户协议等,支持HTML代码'));
});
test('两个文案字段位于分区末尾的界面文案分组且不再属于会员等级组', () => {
const modalJs = vipSchemaJs.indexOf("key: 'modal-copy'");
const modalPhp = schemaPhp.indexOf("'key' => 'modal-copy'");
assert.notStrictEqual(modalJs, -1, 'vip.js 应含 modal-copy 分组');
assert.notStrictEqual(modalPhp, -1, 'SchemaPayment.php 应含 modal-copy 分组');
assert.ok(modalJs > vipSchemaJs.indexOf("key: 'points-exchange'"), 'vip.js modal-copy 应在分区末尾');
assert.ok(modalPhp > schemaPhp.indexOf("'key' => 'points-exchange'"), 'SchemaPayment.php modal-copy 应在分区末尾');
// modal-copy 之后不再出现其它分组/字段 key。
// modal-copy 之后(至分区末尾)不再出现其它分组/字段 key;dependsOn 引用的等级开关除外。
assert.doesNotMatch(vipSchemaJs.slice(modalJs), /key: '(?!modal-copy|pay_user_vip_desc|pay_user_vip_more|pay_user_vip_1_s|pay_user_vip_2_s)/);
const phpTail = slice(schemaPhp, "'key' => 'modal-copy'", "'revenue'");
assert.doesNotMatch(phpTail, /'key' => '(?!modal-copy|pay_user_vip_desc|pay_user_vip_more|pay_user_vip_1_s|pay_user_vip_2_s)/);
// 「会员等级」组不再包含这两个字段。
assert.doesNotMatch(slice(vipSchemaJs, "key: 'basic'", "key: 'benefit'"), /pay_user_vip_desc|pay_user_vip_more/);
assert.doesNotMatch(slice(schemaPhp, "'key' => 'basic'", "'key' => 'benefit'"), /pay_user_vip_desc|pay_user_vip_more/);
// 依赖:任一等级启用时显示(zibll pay_user_vip_1_s 依赖的双等级适配)。
assert.ok(
vipSchemaJs.includes("dependsOn: { any: [{ key: 'pay_user_vip_1_s', value: true }, { key: 'pay_user_vip_2_s', value: true }] }"),
'vip.js 应为 any 双等级依赖'
);
assert.ok(
schemaPhp.includes("'dependsOn' => ['any' => [['key' => 'pay_user_vip_1_s', 'value' => true], ['key' => 'pay_user_vip_2_s', 'value' => true]]]"),
'SchemaPayment.php 应为 any 双等级依赖'
);
});
test('续费/升级说明死配置已从两份 schema 与运行时删除', () => {
for (const [name, src] of [['vip.js', vipSchemaJs], ['SchemaPayment.php', schemaPhp], ['PaymentRuntime.php', paymentRuntime]]) {
assert.ok(!src.includes('vip_renew_desc'), `${name} 不应再含 vip_renew_desc`);
assert.ok(!src.includes('vip_upgrade_desc'), `${name} 不应再含 vip_upgrade_desc`);
}
});
test('文案默认值三处一致并对齐 zibll', () => {
// SPA defaults 块与字段级 default。
assert.ok(vipSchemaJs.includes("pay_user_vip_1_name: '黄金会员'"));
assert.ok(vipSchemaJs.includes("pay_user_vip_2_name: '钻石会员'"));
assert.ok(vipSchemaJs.includes("pay_user_vip_1_desc: '尊享专属特权'"));
assert.ok(vipSchemaJs.includes("pay_user_vip_2_desc: '尊享专属特权'"));
assert.ok(vipSchemaJs.includes(`pay_user_vip_desc: '${DESC_DEFAULT}'`));
assert.ok(vipSchemaJs.includes(`pay_user_vip_more: '${MORE_DEFAULT}'`));
assert.ok(vipSchemaJs.includes(`default: '${EQUITY_DEFAULT}'`));
// PHP schema 字段 default。
assert.ok(schemaPhp.includes("'default' => '黄金会员'"));
assert.ok(schemaPhp.includes("'default' => '钻石会员'"));
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(`$more = (string) ($settings['pay_user_vip_more'] ?? '${MORE_DEFAULT}')`));
assert.ok(paymentRuntime.includes("$name1 = __('黄金会员', 'aksrt');"));
assert.ok(paymentRuntime.includes("$name2 = __('钻石会员', 'aksrt');"));
assert.ok(paymentRuntime.includes("$settings['pay_user_vip_1_desc'] ?? __('尊享专属特权', 'aksrt')"));
assert.ok(paymentRuntime.includes("$settings['pay_user_vip_2_desc'] ?? __('尊享专属特权', 'aksrt')"));
assert.ok(paymentRuntime.includes(EQUITY_DEFAULT));
// 旧默认值不再出现在任何一处。
for (const [name, src] of [['vip.js', vipSchemaJs], ['SchemaPayment.php', schemaPhp], ['PaymentRuntime.php', paymentRuntime]]) {
assert.ok(!/尊享一级会员特权|尊享二级会员特权|免费下载部分资源|更高免费下载额度/.test(src), `${name} 旧默认值应已移除`);
}
});
test('权益与卡密说明字段保存时保留 HTML', () => {
assert.ok(vipSchemaJs.includes("key: 'pay_user_vip_1_equity', label: '一级会员权益 HTML', type: 'textarea', sanitize_type: 'html'"));
assert.ok(vipSchemaJs.includes("key: 'pay_user_vip_2_equity', label: '二级会员权益 HTML', type: 'textarea', sanitize_type: 'html'"));
assert.ok(schemaPhp.includes("'key' => 'pay_user_vip_1_equity', 'label' => '一级会员权益 HTML', 'type' => 'textarea', 'sanitize_type' => 'html'"));
assert.ok(schemaPhp.includes("'key' => 'pay_user_vip_2_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('会员介绍运行时按单行文本净化', () => {
// 只收敛 VIP 设置函数:充值等其它设置的 $desc 与本文无关。
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'));
});