Replace the pipe-delimited VIP configuration with structured groups and accordions, matching the reference theme (Zibll) 1:1. Settings: - 会员等级 keeps only the nav buttons and the two level switches, plus the "must enable level 1 first" guard. Level identity fields (display name, intro, benefits HTML, icon) and 会员商品 move into the 一级会员/二级会员 tabs of 会员购买选项, so each level is configured in one place. - Level tab titles bind to the configured display name via a new titleKey/titleFallback hook in AksFormRenderer. - 会员商品 is a repeater with exactly the reference fields: 执行价/原价/ 促销标签/标签颜色/会员有效时间/时长单位. The tag colour uses the full b+jb palette and now reaches the product card instead of a hardcoded jb-yellow. - 会员续费 gains the 自定义商品 type backed by per-level renew-product groups; the discount becomes a 0.1-9.9 slider with float sanitizing. - 会员升级 becomes the reference three-block accordion (month-to-month day-rate, jump to permanent, permanent to permanent), each with its own price, strikethrough price, promo tag and tag colour. - 积分兑换明细 is a repeater (points/level/duration/unit) where the duration accepts the literal Permanent. Runtime: - PaymentRuntime normalizes the structured groups into purchasable rows (money normalization, palette validation, zero-price and out-of-range rows dropped) and no longer emits any *_raw strings. - The catalog builder carries tag_class through and routes renewals to the renew-product group under 自定义商品. - The points-exchange order note carries time/unit so month durations use calendar math instead of a 30-day approximation. Dead code removed: parse_level_products, parse_packages, parse_upgrade_product_line and their pipe-splitting, plus nine zero-call PaymentRuntime proxy methods in HomeCms. No dual-format or migration path was added; SchemaProvider already falls back to defaults when a stored group value is not an array, so existing pipe strings degrade safely. Verified: php -l on every changed PHP file, 655 Node contract tests passing (14 new ones in vip-settings-structured-contract.test.cjs exercising the real schema build and runtime normalization via a PHP harness), and the admin SPA builds.
321 lines
11 KiB
PHP
321 lines
11 KiB
PHP
<?php
|
|
/**
|
|
* @package AKSRT
|
|
*/
|
|
|
|
namespace AKSRT\Settings;
|
|
|
|
defined('ABSPATH') || exit;
|
|
|
|
class HomeCms {
|
|
|
|
public static function get_footer_settings(): array {
|
|
return Settings::read_domain('page_display', 'footer');
|
|
}
|
|
|
|
public static function get_footer_template(): string {
|
|
$template = sanitize_key((string) (self::get_footer_settings()['fcode_template'] ?? 'template_1'));
|
|
return $template === 'template_1' ? $template : 'template_1';
|
|
}
|
|
|
|
public static function get_footer_brand_settings(): array {
|
|
$f = self::get_footer_settings();
|
|
return [
|
|
'm_s' => !empty($f['footer_t1_m_s']) ? ['pc', 'mobile'] : ['pc'],
|
|
'img' => (string) ($f['footer_t1_img'] ?? ''),
|
|
'img_dark' => (string) ($f['footer_t1_img_dark'] ?? ''),
|
|
'title' => (string) ($f['footer_t1_t'] ?? ''),
|
|
'custom_code' => (string) ($f['fcode_t1_code'] ?? ''),
|
|
];
|
|
}
|
|
|
|
public static function get_footer_contact_extended_settings(): array {
|
|
$f = self::get_footer_settings();
|
|
return [
|
|
'm_s' => !empty($f['footer_contact_m_s']) ? ['pc', 'mobile'] : ['pc'],
|
|
'wechat_img' => (string) ($f['footer_contact_wechat_img'] ?? ''),
|
|
'qq' => (string) ($f['footer_contact_qq'] ?? ''),
|
|
'weibo' => (string) ($f['footer_contact_weibo'] ?? ''),
|
|
'email' => (string) ($f['footer_contact_email'] ?? ''),
|
|
'more' => is_array($f['footer_contact_more'] ?? null) ? $f['footer_contact_more'] : [],
|
|
'code_1' => (string) ($f['fcode_t2_code_1'] ?? ''),
|
|
'code_2' => (string) ($f['fcode_t2_code_2'] ?? ''),
|
|
];
|
|
}
|
|
|
|
public static function get_footer_mini_img_settings(): array {
|
|
$f = self::get_footer_settings();
|
|
return [
|
|
'm_s' => !empty($f['footer_mini_img_m_s']) ? ['pc', 'mobile'] : ['pc'],
|
|
'items' => self::normalize_footer_images($f['footer_mini_img'] ?? []),
|
|
'items_2' => self::normalize_footer_images($f['footer_mini_img_2'] ?? []),
|
|
];
|
|
}
|
|
|
|
public static function get_footer_customize_code(): string {
|
|
$f = self::get_footer_settings();
|
|
return (string) ($f['fcode_customize_code'] ?? '');
|
|
}
|
|
|
|
public static function get_footer_theme_config(): array {
|
|
$s = self::get_footer_settings();
|
|
return [
|
|
'custom' => !empty($s['footer_theme_custom']),
|
|
'bg_custom' => sanitize_hex_color((string) ($s['footer_theme_bg_custom'] ?? '')) ?: '',
|
|
'bg' => self::normalize_palette_hex((string) ($s['footer_theme_bg'] ?? '')),
|
|
'color_custom' => sanitize_hex_color((string) ($s['footer_theme_color_custom'] ?? '')) ?: '',
|
|
];
|
|
}
|
|
|
|
public static function get_site_profile(): string {
|
|
return 'cms';
|
|
}
|
|
|
|
public static function get_cms_features(): array {
|
|
$defaults = [
|
|
'resource_cpt_enabled' => true,
|
|
'search_include_resource' => true,
|
|
];
|
|
$s = Settings::get_all();
|
|
$saved = isset($s['cms_features']) && is_array($s['cms_features']) ? $s['cms_features'] : [];
|
|
return array_merge($defaults, $saved);
|
|
}
|
|
|
|
public static function cms_feature(string $key, $default = null) {
|
|
$f = self::get_cms_features();
|
|
return array_key_exists($key, $f) ? $f[$key] : $default;
|
|
}
|
|
|
|
public static function homepage_allowed_sorts(): array {
|
|
return ['modified', 'date', 'views', 'like', 'comment_count', 'favorite', 'rand'];
|
|
}
|
|
|
|
public static function homepage_sort_labels(): array {
|
|
return [
|
|
'modified' => __('更新', 'aksrt'),
|
|
'date' => __('发布', 'aksrt'),
|
|
'views' => __('浏览', 'aksrt'),
|
|
'like' => __('点赞', 'aksrt'),
|
|
'comment_count' => __('评论', 'aksrt'),
|
|
'favorite' => __('收藏', 'aksrt'),
|
|
'rand' => __('随机', 'aksrt'),
|
|
];
|
|
}
|
|
|
|
public static function get_homepage_fixed_settings(): array {
|
|
$s = Settings::read_domain('page_display', 'homepage');
|
|
$allowed_sorts = self::homepage_allowed_sorts();
|
|
|
|
// Canonical legacy-aligned primary list contract.
|
|
$main_list_enabled = !array_key_exists('home_posts_list_s', $s) || !empty($s['home_posts_list_s']);
|
|
$sort_bar_enabled = !array_key_exists('home_list1_orderby_s', $s) || !empty($s['home_list1_orderby_s']);
|
|
|
|
$orderby_option = isset($s['home_list1_orderby_option']) && is_array($s['home_list1_orderby_option'])
|
|
? $s['home_list1_orderby_option']
|
|
: [];
|
|
$sort_options_raw = $orderby_option['lists'] ?? [];
|
|
if (!is_array($sort_options_raw)) {
|
|
$sort_options_raw = [];
|
|
}
|
|
$sort_options = self::normalize_sort_options($sort_options_raw);
|
|
$default_sort = sanitize_key((string) ($sort_options[0] ?? 'modified'));
|
|
if (!in_array($default_sort, $allowed_sorts, true)) {
|
|
$default_sort = 'modified';
|
|
}
|
|
if (!in_array($default_sort, $sort_options, true)) {
|
|
array_unshift($sort_options, $default_sort);
|
|
$sort_options = array_values(array_unique($sort_options));
|
|
}
|
|
|
|
$list_title = wp_kses_post((string) ($s['index_list_title'] ?? ''));
|
|
|
|
$exclude_posts = self::normalize_int_array($s['home_exclude_posts'] ?? []);
|
|
$exclude_cats = self::normalize_int_array($s['home_exclude_cats'] ?? []);
|
|
$tabs_raw = isset($s['home_lists']) && is_array($s['home_lists']) ? $s['home_lists'] : [];
|
|
|
|
$settings = [
|
|
// Canonical legacy keys.
|
|
'home_posts_list_s' => $main_list_enabled,
|
|
'home_list1_orderby_s' => $sort_bar_enabled,
|
|
'home_list1_orderby_option' => ['lists' => $sort_options, 'dropdown' => !empty($orderby_option['dropdown'])],
|
|
'index_list_title' => $list_title,
|
|
'home_exclude_posts' => $exclude_posts,
|
|
'home_exclude_cats' => $exclude_cats,
|
|
'home_lists' => self::normalize_homepage_tabs($tabs_raw),
|
|
];
|
|
|
|
return $settings;
|
|
}
|
|
|
|
/**
|
|
* Homepage always uses ordinary posts. Kept for API stability.
|
|
*
|
|
* @return string Always 'post'.
|
|
*/
|
|
public static function homepage_content_model(): string {
|
|
return 'post';
|
|
}
|
|
|
|
public static function homepage_tabs(): array {
|
|
$settings = self::get_homepage_fixed_settings();
|
|
return $settings['home_lists'];
|
|
}
|
|
|
|
/** @return array<int, array{term_id:int,title:string,orderby:string}> */
|
|
public static function home_lists(): array {
|
|
return self::homepage_tabs();
|
|
}
|
|
|
|
public static function homepage_sidebar_mini_enabled(): bool {
|
|
return true;
|
|
}
|
|
|
|
public static function homepage_default_sort(): string {
|
|
$settings = self::get_homepage_fixed_settings();
|
|
return $settings['home_list1_orderby_option']['lists'][0] ?? 'modified';
|
|
}
|
|
|
|
public static function home_posts_list_enabled(): bool {
|
|
$settings = self::get_homepage_fixed_settings();
|
|
return !empty($settings['home_posts_list_s']);
|
|
}
|
|
|
|
public static function home_list_title(): string {
|
|
$settings = self::get_homepage_fixed_settings();
|
|
return (string) $settings['index_list_title'];
|
|
}
|
|
|
|
public static function home_list_sort_enabled(): bool {
|
|
$settings = self::get_homepage_fixed_settings();
|
|
return !empty($settings['home_list1_orderby_s']);
|
|
}
|
|
|
|
public static function home_list_sort_options(): array {
|
|
$settings = self::get_homepage_fixed_settings();
|
|
return $settings['home_list1_orderby_option']['lists'] ?? ['modified', 'views', 'like', 'comment_count'];
|
|
}
|
|
|
|
public static function home_list_sort_dropdown(): bool {
|
|
$settings = self::get_homepage_fixed_settings();
|
|
return !empty($settings['home_list1_orderby_option']['dropdown']);
|
|
}
|
|
|
|
public static function home_exclude_post_ids(): array {
|
|
$settings = self::get_homepage_fixed_settings();
|
|
return $settings['home_exclude_posts'];
|
|
}
|
|
|
|
public static function home_exclude_category_ids(): array {
|
|
$settings = self::get_homepage_fixed_settings();
|
|
return $settings['home_exclude_cats'];
|
|
}
|
|
|
|
private static function normalize_sort_options(array $raw): array {
|
|
$allowed = self::homepage_allowed_sorts();
|
|
$result = [];
|
|
$seen = [];
|
|
|
|
foreach ($raw as $key) {
|
|
$key = sanitize_key((string) $key);
|
|
if (in_array($key, $allowed, true) && !isset($seen[$key])) {
|
|
$result[] = $key;
|
|
$seen[$key] = true;
|
|
}
|
|
}
|
|
|
|
if (empty($result)) {
|
|
$result = ['modified', 'views', 'like', 'comment_count'];
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
private static function normalize_int_array($raw): array {
|
|
if (!is_array($raw)) {
|
|
return [];
|
|
}
|
|
return array_values(array_filter(array_map('absint', $raw), static fn($v) => $v > 0));
|
|
}
|
|
|
|
private static function normalize_homepage_tabs(array $raw_tabs): array {
|
|
$allowed_orderby = self::homepage_allowed_sorts();
|
|
$tabs = [];
|
|
|
|
foreach ($raw_tabs as $tab) {
|
|
if (!is_array($tab)) {
|
|
continue;
|
|
}
|
|
|
|
$term_id = absint($tab['term_id'] ?? 0);
|
|
if ($term_id <= 0) {
|
|
continue;
|
|
}
|
|
|
|
$orderby = sanitize_key((string) ($tab['orderby'] ?? ''));
|
|
if ($orderby !== '' && !in_array($orderby, $allowed_orderby, true)) {
|
|
$orderby = '';
|
|
}
|
|
|
|
$tabs[] = [
|
|
'term_id' => $term_id,
|
|
'title' => wp_kses_post((string) ($tab['title'] ?? '')),
|
|
'orderby' => $orderby,
|
|
];
|
|
}
|
|
|
|
return $tabs;
|
|
}
|
|
|
|
private static function normalize_footer_images($raw_items): array {
|
|
if (!is_array($raw_items)) {
|
|
return [];
|
|
}
|
|
|
|
$items = [];
|
|
foreach ($raw_items as $item) {
|
|
if (!is_array($item) || empty($item['image'])) {
|
|
continue;
|
|
}
|
|
$items[] = [
|
|
'image' => (string) $item['image'],
|
|
'text' => (string) ($item['text'] ?? ''),
|
|
'link' => is_array($item['link'] ?? null) ? $item['link'] : [],
|
|
];
|
|
}
|
|
|
|
return $items;
|
|
}
|
|
|
|
private static function normalize_palette_hex(string $value): string {
|
|
$value = ltrim(trim($value), '#');
|
|
return preg_match('/^[0-9a-f]{6}$/i', $value) ? '#' . strtolower($value) : '';
|
|
}
|
|
|
|
private static function parse_slug_list(string $raw, array $fallback = []): array {
|
|
$items = preg_split('/[\s,]+/', $raw, -1, PREG_SPLIT_NO_EMPTY);
|
|
$normalized = [];
|
|
|
|
foreach ((array) $items as $item) {
|
|
$slug = sanitize_key((string) $item);
|
|
if ($slug !== '' && !in_array($slug, $normalized, true)) {
|
|
$normalized[] = $slug;
|
|
}
|
|
}
|
|
|
|
return !empty($normalized) ? $normalized : $fallback;
|
|
}
|
|
|
|
private static function count_structured_lines(string $raw): int {
|
|
$lines = preg_split('/[\r\n]+/', $raw, -1, PREG_SPLIT_NO_EMPTY);
|
|
$count = 0;
|
|
|
|
foreach ((array) $lines as $line) {
|
|
if (trim((string) $line) !== '') {
|
|
$count++;
|
|
}
|
|
}
|
|
|
|
return $count;
|
|
}
|
|
}
|