From e971c2b1da4f5cac5ba007b6ca3072cf7a16954c Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 21 Aug 2026 13:42:59 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BA=86=E4=B8=80=E4=BA=9B?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/src/schema/sections/payment/vip.js | 4 +++ inc/helpers/aksrt-header-menu-buttons.php | 2 +- inc/helpers/aksrt-header-userbox.php | 3 +++ inc/helpers/header/aksrt-header-navbar.php | 2 +- src/Admin/Schema/SchemaPayment.php | 2 ++ src/Settings/PaymentRuntime.php | 2 ++ .../page-display-contract.test.cjs | 27 ++++++++++++++++++- .../page-display-save-harness.php | 16 +++++++++++ 8 files changed, 55 insertions(+), 3 deletions(-) diff --git a/assets/admin/src/schema/sections/payment/vip.js b/assets/admin/src/schema/sections/payment/vip.js index b6d147fa..600161eb 100644 --- a/assets/admin/src/schema/sections/payment/vip.js +++ b/assets/admin/src/schema/sections/payment/vip.js @@ -4,6 +4,8 @@ export const vipSchema = { icon: 'fa fas fa-gem', description: '对齐 Zibll 双级会员:分档套餐、权益、续费/升级与积分兑换。', defaults: { + 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: '尊享一级会员特权', @@ -44,6 +46,8 @@ export const vipSchema = { title: '会员等级', icon: 'fa fas fa-user', fields: [ + { 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 } }, diff --git a/inc/helpers/aksrt-header-menu-buttons.php b/inc/helpers/aksrt-header-menu-buttons.php index 4207adb1..64b90188 100644 --- a/inc/helpers/aksrt-header-menu-buttons.php +++ b/inc/helpers/aksrt-header-menu-buttons.php @@ -173,7 +173,7 @@ if (!function_exists('aksrt_render_header_menu_buttons')) : $show_vip = false; if (class_exists('\AKSRT\Settings\Settings')) { $vs = \AKSRT\Settings\Settings::get_payment_vip_settings(); - $show_vip = !empty($vs['enabled']); + $show_vip = !empty($vs['enabled']) && !empty($vs['nav_pay_vip']); } if ($show_vip) { $is_vip = false; diff --git a/inc/helpers/aksrt-header-userbox.php b/inc/helpers/aksrt-header-userbox.php index 71a030f9..5a004607 100644 --- a/inc/helpers/aksrt-header-userbox.php +++ b/inc/helpers/aksrt-header-userbox.php @@ -156,6 +156,9 @@ if (!function_exists('aksrt_header_payvip_bar')) : $html .= ''; return $html; } + if (empty($vip['nav_user_pay_vip'])) { + return ''; + } // Match check-in CTA geometry (full width, 6px radius) — not top-nav navbar-cta chips. return '
' diff --git a/inc/helpers/header/aksrt-header-navbar.php b/inc/helpers/header/aksrt-header-navbar.php index 0f550efc..96256838 100644 --- a/inc/helpers/header/aksrt-header-navbar.php +++ b/inc/helpers/header/aksrt-header-navbar.php @@ -242,7 +242,7 @@ if (!function_exists('aksrt_header_user_box_html')) : $vip_guest = ''; if (class_exists('\AKSRT\Settings\Settings')) { $vs = \AKSRT\Settings\Settings::get_payment_vip_settings(); - if (!empty($vs['enabled'])) { + if (!empty($vs['enabled']) && !empty($vs['nav_user_pay_vip'])) { $vip_guest = '
'; diff --git a/src/Admin/Schema/SchemaPayment.php b/src/Admin/Schema/SchemaPayment.php index 6a68134b..9acb94f4 100644 --- a/src/Admin/Schema/SchemaPayment.php +++ b/src/Admin/Schema/SchemaPayment.php @@ -62,6 +62,8 @@ class SchemaPayment { 'option_key' => 'aksrt_settings', 'groups' => [ ['key' => 'basic', 'title' => '会员等级', 'fields' => [ + ['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]], diff --git a/src/Settings/PaymentRuntime.php b/src/Settings/PaymentRuntime.php index 5e73643f..c65eece2 100644 --- a/src/Settings/PaymentRuntime.php +++ b/src/Settings/PaymentRuntime.php @@ -194,6 +194,8 @@ class PaymentRuntime { return [ 'enabled' => $enabled, + 'nav_pay_vip' => !isset($settings['nav_pay_vip']) || !empty($settings['nav_pay_vip']), + 'nav_user_pay_vip' => !isset($settings['nav_user_pay_vip']) || !empty($settings['nav_user_pay_vip']), 'name' => $vip1_enabled ? $name1 : $badge, 'badge_text' => $badge, 'desc' => $desc, diff --git a/tests/widget-system/page-display-contract.test.cjs b/tests/widget-system/page-display-contract.test.cjs index 52e369cd..e27709fb 100644 --- a/tests/widget-system/page-display-contract.test.cjs +++ b/tests/widget-system/page-display-contract.test.cjs @@ -7,6 +7,9 @@ const { spawnSync } = require('node:child_process') const root = path.resolve(__dirname, '../..') const read = file => fs.readFileSync(path.join(root, file), 'utf8') const schema = read('src/Admin/Schema/SchemaPageDisplay.php') +const paymentSchema = read('src/Admin/Schema/SchemaPayment.php') +const adminVipSchema = read('assets/admin/src/schema/sections/payment/vip.js') +const paymentRuntime = read('src/Settings/PaymentRuntime.php') const routes = read('assets/admin/src/constants/sections/page-display.js') const controller = read('src/Admin/PageDisplayController.php') const controllerBase = read('src/Admin/ControllerBase.php') @@ -103,13 +106,26 @@ test('defaults, nested structures, dependencies, and excluded product families a assert.match(schema, /'header_slider_card_s'.*?'dependsOn' => self::not_empty\('header_slider_show'\)/) assert.match(schema, /'header_slider_filter_blur'.*?'dependsOn' => self::not_empty\('header_slider_show'\)/) assert.match(schema, /'all' => \[self::enabled\('home_posts_list_s'\), self::enabled\('home_list1_orderby_s'\)\]/) - for (const dead of ['nav_newposts_btns', 'nav_pay_vip', 'nav_user_pay_vip', 'forum_home', 'forum_plate', 'forum_post', 'ajax_list_cat_topics', 'ajax_list_tag_topics']) { + for (const dead of ['nav_newposts_btns', 'forum_home', 'forum_plate', 'forum_post', 'ajax_list_cat_topics', 'ajax_list_tag_topics']) { assert.equal(schema.includes(dead), false, `legacy/excluded field remains: ${dead}`) } assert.equal(headerMenuButtons.includes('nav_newposts_btns'), false) assert.match(headerMenuButtons, /aksrt_render_new_add_btns\(\['post'\], \$class, \$label\)/) }) +test('VIP navigation purchase switches are saved and consumed end to end', () => { + for (const key of ['nav_pay_vip', 'nav_user_pay_vip']) { + assert.match(paymentSchema, new RegExp(`'key' => '${key}'[^\\n]+?'type' => 'switch'[^\\n]+?'default' => true`)) + assert.match(adminVipSchema, new RegExp(`${key}: true`)) + assert.match(adminVipSchema, new RegExp(`key: '${key}'[^\\n]+type: 'switch'[^\\n]+default: true`)) + assert.match(paymentRuntime, new RegExp(`'${key}'[^\\n]+!isset\\(\\$settings\\['${key}'\\]\\)[^\\n]+!empty\\(\\$settings\\['${key}'\\]\\)`)) + } + + assert.match(headerMenuButtons, /!empty\(\$vs\['enabled'\]\) && !empty\(\$vs\['nav_pay_vip'\]\)/) + assert.match(headerUserbox, /empty\(\$vip\['nav_user_pay_vip'\]\)[\s\S]*?return '';/) + assert.match(headerNavbar, /!empty\(\$vs\['enabled'\]\) && !empty\(\$vs\['nav_user_pay_vip'\]\)/) +}) + test('schema REST saves are privileged, section-whitelisted, and deeply sanitized', () => { assert.match(controllerBase, /current_user_can\('manage_options'\)/) assert.match(controllerBase, /sanitize_payload\(\$domain, \$payload, \$section\)/) @@ -266,6 +282,15 @@ test('save contract rejects invalid input and round-trips canonical nested value assert.equal(data.invalid_enum_rejected, true) assert.equal(data.nested_multiselect_valid, true) assert.equal(data.footer_valid, true) + assert.equal(data.vip_switches_valid, true) + assert.deepEqual(data.vip_switches_clean, { + nav_pay_vip: false, + nav_user_pay_vip: true, + }) + assert.deepEqual(data.vip_switches_readback, { + nav_pay_vip: false, + nav_user_pay_vip: true, + }) assert.equal(data.homepage_defaults.home_list1_orderby_option, '') assert.equal(data.homepage_defaults.home_exclude_posts, '') assert.equal(data.homepage_defaults.home_exclude_cats, '') diff --git a/tests/widget-system/page-display-save-harness.php b/tests/widget-system/page-display-save-harness.php index 84cdf6f4..84bc9416 100644 --- a/tests/widget-system/page-display-save-harness.php +++ b/tests/widget-system/page-display-save-harness.php @@ -137,6 +137,7 @@ namespace AKSRT\Settings { namespace { require_once dirname(__DIR__, 2) . '/src/Admin/Schema/SchemaPageDisplay.php'; + require_once dirname(__DIR__, 2) . '/src/Admin/Schema/SchemaPayment.php'; require_once dirname(__DIR__, 2) . '/src/Admin/SchemaProvider.php'; require_once dirname(__DIR__, 2) . '/src/Admin/ControllerBase.php'; require_once dirname(__DIR__, 2) . '/inc/helpers/homepage/header-components.php'; @@ -184,6 +185,15 @@ namespace { $provider->write_settings('page_display', $footer_clean, 'footer'); $footer_readback = $provider->read_settings('page_display', 'footer'); + $vip_clean = $provider->sanitize_payload('payment', [ + 'nav_pay_vip' => false, + 'nav_user_pay_vip' => true, + 'unknown_vip_key' => true, + ], 'vip'); + $vip_validation = $provider->validate('payment', $vip_clean, 'vip'); + $provider->write_settings('payment', $vip_clean, 'vip'); + $vip_readback = $provider->read_settings('payment', 'vip'); + $category_payload = [ 'cat_orderby_option' => [ 'lists' => ['modified', 'views', 'like', 'comment_count'], @@ -239,6 +249,12 @@ namespace { 'footer_valid' => $footer_validation['valid'], 'footer_clean' => $footer_clean, 'footer_readback' => $footer_readback, + 'vip_switches_valid' => $vip_validation['valid'], + 'vip_switches_clean' => $vip_clean, + 'vip_switches_readback' => [ + 'nav_pay_vip' => $vip_readback['nav_pay_vip'] ?? null, + 'nav_user_pay_vip' => $vip_readback['nav_user_pay_vip'] ?? null, + ], 'visibility' => $visibility, ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); }