fix(vip): preserve safe HTML in VIP copy
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
7a0445fc4a
commit
573040381d
15 files changed
+155
-118
No files matched your search
@@ -156,9 +156,8 @@ test('header VIP hover cards match the legacy markup and style contract', () =>
|
||||
assert.match(vipCardRenderer, /<div class=\"abs-center vip-baicon\"[^>]*>/)
|
||||
assert.match(vipCardRenderer, /<div class=\"vip-img abs-right\">/)
|
||||
assert.match(vipCardRenderer, /<div class=\"vip-name mb10\"><span class=\"mr6\">/)
|
||||
assert.match(vipCardRenderer, /<ul class=\"mb10 relative\">/)
|
||||
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/)
|
||||
|
||||
@@ -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('开通会员更多内容只在弹窗内渲染,不裸露在用户中心页面', () => {
|
||||
|
||||
@@ -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, '<b>会员</b>');
|
||||
assert.strictEqual(sanitize.vip_html.fields.pay_user_vip_1_desc, '<em>简介</em>');
|
||||
assert.strictEqual(sanitize.vip_html.fields.pay_user_vip_1_equity, '<p>权益</p>');
|
||||
assert.strictEqual(sanitize.vip_html.fields.pay_user_vip_desc, '<span>会员介绍</span>');
|
||||
assert.strictEqual(sanitize.vip_html.fields.vip_renew_desc, '<strong>续费</strong>');
|
||||
assert.strictEqual(sanitize.vip_html.fields.vip_upgrade_desc, '<i>升级</i>');
|
||||
assert.strictEqual(sanitize.vip_html.fields.vip_1_product[0].tag, '<b>推荐</b>');
|
||||
|
||||
// 字符串型枚举(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, '<b>续费有礼</b>');
|
||||
assert.strictEqual(custom.upgrade.desc, '<i>升级王者</i>');
|
||||
assert.strictEqual(custom.levels['1'].name, '青铜会员');
|
||||
assert.strictEqual(custom.levels['1'].name_html, '<strong>青铜会员</strong>');
|
||||
assert.strictEqual(custom.levels['1'].desc, '<em>专属权益</em>');
|
||||
assert.strictEqual(custom.levels['1'].equity, '<p>一对一支持</p>');
|
||||
assert.strictEqual(custom.levels['1'].renew_products[0].tag_class, 'b-red');
|
||||
// 升级三块参数透传。
|
||||
assert.strictEqual(custom.upgrade.unit_price, '12.50');
|
||||
|
||||
@@ -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' => '<strong>青铜会员</strong>',
|
||||
'pay_user_vip_2_name' => '<span>王者会员</span>',
|
||||
'pay_user_vip_1_desc' => '<em>专属权益</em>',
|
||||
'pay_user_vip_1_equity' => '<p>一对一支持</p>',
|
||||
'vip_renew' => true,
|
||||
'vip_renew_desc' => '续费有礼',
|
||||
'vip_renew_desc' => '<b>续费有礼</b>',
|
||||
'vip_renew_price_type' => 'customize',
|
||||
'vip_renew_discount' => 7.5,
|
||||
'vip_renew_reduce' => '15.00',
|
||||
'vip_upgrade' => true,
|
||||
'vip_upgrade_desc' => '升级王者',
|
||||
'vip_upgrade_desc' => '<i>升级王者</i>',
|
||||
'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' => '<b>会员</b>',
|
||||
'pay_user_vip_1_desc' => '<em>简介</em>',
|
||||
'pay_user_vip_1_equity' => '<p>权益</p>',
|
||||
'pay_user_vip_desc' => '<span>会员介绍</span>',
|
||||
'vip_renew_desc' => '<strong>续费</strong>',
|
||||
'vip_upgrade_desc' => '<i>升级</i>',
|
||||
'vip_1_product' => [
|
||||
['price' => '88.80', 'show_price' => '99.00', 'tag' => '<b>推荐</b>', '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'],
|
||||
|
||||
Reference in new issue
Block a user