Files
wordpress-theme-aksrt/inc/classes/class-aksrt-shop-term-meta.php
T
kate522 1770e8e0db feat(admin): topic governance meta box and product category term fields
Add native side meta box for forum topics (plate/sticky/lock/review/pay
visibility) and taxonomy form fields for aksrt_product_cat inheritance
settings. Source-only changes; no SPA build artifacts.
2026-07-15 02:24:00 +08:00

681 lines
28 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* AKSRT Shop Term Meta
*
* 为商品分类(aksrt_product_cat)、商品标签(aksrt_product_tag)和
* 优惠活动(shop_discount)注册 term meta 并提供 getter/setter。
*
* @package AKSRT
*/
defined('ABSPATH') || exit;
class AKSRT_Shop_Term_Meta {
private static $instance = null;
/** @var string Meta key for product_cat/product_tag unified storage */
const META_KEY = 'aksrt_shop_term_meta';
/** @var string Meta key for shop_discount unified storage */
const DISCOUNT_META_KEY = 'aksrt_shop_discount_meta';
public static function get_instance(): self {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Register hooks.
*/
public function init(): void {
add_action('init', [$this, 'register_discount_taxonomy'], 5);
add_action('init', [$this, 'register_term_meta_fields'], 10);
// Native term UI for shop_discount (zibll-style admin ops).
add_action('shop_discount_add_form_fields', [$this, 'render_discount_add_fields']);
add_action('shop_discount_edit_form_fields', [$this, 'render_discount_edit_fields'], 10, 1);
add_action('created_shop_discount', [$this, 'save_discount_term_fields']);
add_action('edited_shop_discount', [$this, 'save_discount_term_fields']);
// Product category inheritance fields.
add_action('aksrt_product_cat_add_form_fields', [$this, 'render_cat_add_fields']);
add_action('aksrt_product_cat_edit_form_fields', [$this, 'render_cat_edit_fields'], 10, 1);
add_action('created_aksrt_product_cat', [$this, 'save_cat_term_fields']);
add_action('edited_aksrt_product_cat', [$this, 'save_cat_term_fields']);
}
// ─── Taxonomy Registration ───────────────────────────────────
/**
* Register the shop_discount taxonomy for discount campaigns.
*/
public function register_discount_taxonomy(): void {
if (taxonomy_exists('shop_discount')) {
return;
}
$shop_enabled = true;
if (class_exists('AKSRT_Settings')) {
$shop_settings = AKSRT_Settings::get_shop_settings();
$shop_enabled = !empty($shop_settings['enabled']);
}
$shop_show_ui = true;
$shop_show_menu = is_admin() ? $shop_enabled : false;
register_taxonomy('shop_discount', 'aksrt_product', [
'labels' => [
'name' => __('优惠活动', 'aksrt'),
'singular_name' => __('优惠活动', 'aksrt'),
'search_items' => __('搜索优惠活动', 'aksrt'),
'all_items' => __('所有优惠活动', 'aksrt'),
'edit_item' => __('编辑优惠活动', 'aksrt'),
'add_new_item' => __('新增优惠活动', 'aksrt'),
'menu_name' => __('优惠活动', 'aksrt'),
],
'public' => true,
'hierarchical' => false,
'show_ui' => $shop_show_ui,
'show_in_menu' => $shop_show_menu,
'show_in_rest' => true,
'show_admin_column' => true,
'rewrite' => ['slug' => 'shop-discount', 'with_front' => false],
]);
}
// ─── Term Meta Registration ──────────────────────────────────
/**
* Register all term meta fields for REST API exposure.
*/
public function register_term_meta_fields(): void {
$auth = function () {
return current_user_can('manage_categories');
};
// ── aksrt_product_cat ──
$cat_fields = $this->get_cat_field_definitions();
foreach ($cat_fields as $key => $def) {
register_term_meta('aksrt_product_cat', $key, [
'type' => $def['type'],
'description' => $def['description'] ?? $key,
'single' => true,
'default' => $def['default'] ?? '',
'show_in_rest' => true,
'sanitize_callback' => $def['sanitize'] ?? 'sanitize_text_field',
'auth_callback' => $auth,
]);
}
// ── aksrt_product_tag ──
$tag_fields = $this->get_tag_field_definitions();
foreach ($tag_fields as $key => $def) {
register_term_meta('aksrt_product_tag', $key, [
'type' => $def['type'],
'description' => $def['description'] ?? $key,
'single' => true,
'default' => $def['default'] ?? '',
'show_in_rest' => true,
'sanitize_callback' => $def['sanitize'] ?? 'sanitize_text_field',
'auth_callback' => $auth,
]);
}
// ── shop_discount ──
$discount_fields = $this->get_discount_field_definitions();
foreach ($discount_fields as $key => $def) {
register_term_meta('shop_discount', $key, [
'type' => $def['type'],
'description' => $def['description'] ?? $key,
'single' => true,
'default' => $def['default'] ?? '',
'show_in_rest' => true,
'sanitize_callback' => $def['sanitize'] ?? 'sanitize_text_field',
'auth_callback' => $auth,
]);
}
}
// ─── Field Definitions ───────────────────────────────────────
/**
* Product category term meta field definitions.
*
* @return array<string, array{type:string, description:string, default:mixed, sanitize?:callable}>
*/
private function get_cat_field_definitions(): array {
return [
'shop_cat_limit_buy' => [
'type' => 'integer', 'description' => '分类级限购 (-1 不限)', 'default' => -1,
'sanitize' => function ($v) { return max(-1, min(9999, (int) $v)); },
],
'shop_cat_shipping_fee' => [
'type' => 'string', 'description' => '分类级运费类型', 'default' => '',
'sanitize' => function ($v) {
$v = sanitize_key((string) $v);
return in_array($v, ['', 'free', 'fixed', 'amount'], true) ? $v : '';
},
],
'shop_cat_guest_buy' => [
'type' => 'string', 'description' => '分类级免登录购买', 'default' => '',
],
'shop_cat_email_fill' => [
'type' => 'string', 'description' => '分类级邮箱必留', 'default' => '',
],
'shop_cat_after_sale' => [
'type' => 'string', 'description' => '分类级售后政策', 'default' => '',
'sanitize' => function ($v) {
$v = sanitize_key((string) $v);
return in_array($v, ['', 'refund', 'refund_return', 'none'], true) ? $v : '';
},
],
'shop_cat_rebate' => [
'type' => 'string', 'description' => '分类级返佣覆盖', 'default' => '',
],
'shop_cat_service' => [
'type' => 'string', 'description' => '分类级服务保障', 'default' => '',
'sanitize' => 'sanitize_textarea_field',
],
'shop_cat_single_tab' => [
'type' => 'string', 'description' => '分类级自定义 TAB', 'default' => '',
'sanitize' => 'sanitize_textarea_field',
],
'shop_cat_content_after' => [
'type' => 'string', 'description' => '分类级详情页底部内容', 'default' => '',
'sanitize' => 'wp_kses_post',
],
'shop_cat_content_layout' => [
'type' => 'string', 'description' => '分类级内容布局', 'default' => '',
'sanitize' => function ($v) {
$v = sanitize_key((string) $v);
return in_array($v, ['', 'adapt', 'full', 'sidebar'], true) ? $v : '';
},
],
'shop_cat_content_show_bg' => [
'type' => 'string', 'description' => '分类级详情背景盒子', 'default' => '',
],
'shop_cat_list_style' => [
'type' => 'string', 'description' => '分类页列表样式', 'default' => '',
'sanitize' => function ($v) {
$v = sanitize_key((string) $v);
return in_array($v, ['', 'default', 'card-lg', 'card-sm'], true) ? $v : '';
},
],
];
}
/**
* Product tag term meta field definitions.
*
* @return array<string, array{type:string, description:string, default:mixed, sanitize?:callable}>
*/
private function get_tag_field_definitions(): array {
return [
'shop_tag_priority' => [
'type' => 'integer', 'description' => '标签优先级(影响显示顺序)', 'default' => 0,
'sanitize' => function ($v) { return max(0, min(9999, (int) $v)); },
],
'shop_tag_class' => [
'type' => 'string', 'description' => '标签颜色 class', 'default' => '',
'sanitize' => 'sanitize_html_class',
],
'shop_tag_is_important' => [
'type' => 'string', 'description' => '重点标签(商品价格位着重显示)', 'default' => '',
],
];
}
/**
* Shop discount term meta field definitions.
*
* @return array<string, array{type:string, description:string, default:mixed, sanitize?:callable}>
*/
private function get_discount_field_definitions(): array {
return [
'shop_discount_small_badge' => [
'type' => 'string', 'description' => '小标签文案', 'default' => '',
],
'shop_discount_priority' => [
'type' => 'integer', 'description' => '优先级(数字越大越优先)', 'default' => 0,
'sanitize' => function ($v) { return max(0, min(9999, (int) $v)); },
],
'shop_discount_is_important' => [
'type' => 'string', 'description' => '重点活动', 'default' => '',
],
'shop_discount_important_class' => [
'type' => 'string', 'description' => '重点活动背景色 class', 'default' => '',
'sanitize' => 'sanitize_html_class',
],
'shop_discount_scope' => [
'type' => 'string', 'description' => '优惠范围', 'default' => 'item',
'sanitize' => function ($v) {
$v = sanitize_key((string) $v);
return in_array($v, ['item', 'product', 'author', 'order'], true) ? $v : 'item';
},
],
'shop_discount_type' => [
'type' => 'string', 'description' => '优惠类型', 'default' => 'reduction',
'sanitize' => function ($v) {
$v = sanitize_key((string) $v);
return in_array($v, ['reduction', 'discount', 'gift'], true) ? $v : 'reduction';
},
],
'shop_discount_value' => [
'type' => 'string', 'description' => '优惠数值(立减金额/折扣 0.01-9.99)', 'default' => '0',
'sanitize' => function ($v) { return sanitize_text_field((string) $v); },
],
'shop_discount_gift_config' => [
'type' => 'string', 'description' => '赠品配置 JSON', 'default' => '',
'sanitize' => 'sanitize_textarea_field',
],
'shop_discount_price_limit' => [
'type' => 'string', 'description' => '金额门槛', 'default' => '0',
'sanitize' => function ($v) { return max(0, (float) $v); },
],
'shop_discount_user_limit' => [
'type' => 'string', 'description' => '用户身份限制', 'default' => '',
'sanitize' => function ($v) {
$v = sanitize_key((string) $v);
return in_array($v, ['', 'vip', 'vip_2', 'auth'], true) ? $v : '';
},
],
'shop_discount_start' => [
'type' => 'string', 'description' => '活动开始时间', 'default' => '',
'sanitize' => 'sanitize_text_field',
],
'shop_discount_end' => [
'type' => 'string', 'description' => '活动结束时间', 'default' => '',
'sanitize' => 'sanitize_text_field',
],
'shop_discount_countdown' => [
'type' => 'string', 'description' => '倒计时显示', 'default' => '',
],
];
}
// ─── Getters ─────────────────────────────────────────────────
/**
* Get all shop term meta for a product category.
*
* @param int $term_id
* @return array
*/
public static function get_cat_meta(int $term_id): array {
$defaults = [
'limit_buy' => -1,
'shipping_fee' => '',
'guest_buy' => '',
'email_fill' => '',
'after_sale' => '',
'rebate' => '',
'service' => '',
'single_tab' => '',
'content_after' => '',
'content_layout' => '',
'content_show_bg' => '',
'list_style' => '',
];
$result = [];
foreach ($defaults as $key => $default) {
$meta_key = "shop_cat_{$key}";
$val = get_term_meta($term_id, $meta_key, true);
$result[$key] = ($val !== '' && $val !== false) ? $val : $default;
}
return $result;
}
/**
* Get all shop term meta for a product tag.
*
* @param int $term_id
* @return array
*/
public static function get_tag_meta(int $term_id): array {
$priority = get_term_meta($term_id, 'shop_tag_priority', true);
$class = get_term_meta($term_id, 'shop_tag_class', true);
$is_important = get_term_meta($term_id, 'shop_tag_is_important', true);
return [
'priority' => $priority !== '' ? (int) $priority : 0,
'class' => is_string($class) ? $class : '',
'is_important' => !empty($is_important),
];
}
/**
* Get all shop discount term meta.
*
* @param int $term_id
* @return array
*/
public static function get_discount_meta(int $term_id): array {
$defaults = [
'small_badge' => '',
'priority' => 0,
'is_important' => false,
'important_class' => '',
'scope' => 'item',
'type' => 'reduction',
'value' => '0',
'gift_config' => '',
'price_limit' => '0',
'user_limit' => '',
'start' => '',
'end' => '',
'countdown' => '',
];
$result = [];
foreach ($defaults as $key => $default) {
$meta_key = "shop_discount_{$key}";
$val = get_term_meta($term_id, $meta_key, true);
$result[$key] = ($val !== '' && $val !== false) ? $val : $default;
}
$result['priority'] = (int) $result['priority'];
$result['is_important'] = !empty($result['is_important']);
$result['price_limit'] = (float) $result['price_limit'];
return $result;
}
/**
* Resolve parameter inheritance: product → category → theme settings.
*
* Returns the effective value for a given parameter key by checking:
* 1. Product-level override (from post meta)
* 2. Category-level override (from term meta)
* 3. Theme-level default (from AKSRT_Settings::get_shop_settings())
*
* @param int $product_id Product post ID
* @param string $param_key Parameter key (e.g. 'limit_buy', 'shipping_fee')
* @param mixed $product_value Product-level value (null if not set)
* @return mixed
*/
public static function resolve_inheritance(int $product_id, string $param_key, $product_value = null) {
// 1. Product-level override
if ($product_value !== null && $product_value !== '' && $product_value !== -1) {
return $product_value;
}
// 2. Category-level override
$terms = get_the_terms($product_id, 'aksrt_product_cat');
if (is_array($terms) && !empty($terms)) {
$cat_meta_key = "shop_cat_{$param_key}";
foreach ($terms as $term) {
$val = get_term_meta($term->term_id, $cat_meta_key, true);
if ($val !== '' && $val !== false && $val !== -1) {
return $val;
}
}
}
// 3. Theme-level default
$shop = AKSRT_Settings::get_shop_settings();
$defaults = $shop['product_defaults'] ?? [];
return $defaults[$param_key] ?? $product_value;
}
/**
* Add form fields for shop_discount taxonomy.
*/
public function render_discount_add_fields(): void {
$fields = $this->get_discount_field_definitions();
foreach ($fields as $key => $def) {
$label = (string) ($def['description'] ?? $key);
$default = $def['default'] ?? '';
echo '<div class="form-field">';
echo '<label for="' . esc_attr($key) . '">' . esc_html($label) . '</label>';
$this->render_discount_field_control($key, $default, false);
echo '</div>';
}
}
/**
* Edit form fields for shop_discount taxonomy.
*/
public function render_discount_edit_fields($term): void {
if (!($term instanceof WP_Term)) {
return;
}
$fields = $this->get_discount_field_definitions();
foreach ($fields as $key => $def) {
$label = (string) ($def['description'] ?? $key);
$value = get_term_meta($term->term_id, $key, true);
if ($value === '' || $value === false) {
$value = $def['default'] ?? '';
}
echo '<tr class="form-field">';
echo '<th scope="row"><label for="' . esc_attr($key) . '">' . esc_html($label) . '</label></th>';
echo '<td>';
$this->render_discount_field_control($key, $value, true);
echo '</td></tr>';
}
}
/**
* @param string|int|float $value
*/
private function render_discount_field_control(string $key, $value, bool $edit): void {
$id = esc_attr($key);
$name = esc_attr($key);
$val = (string) $value;
if ($key === 'shop_discount_type') {
$opts = [
'reduction' => __('立减', 'aksrt'),
'discount' => __('折扣', 'aksrt'),
'gift' => __('赠品', 'aksrt'),
];
echo '<select name="' . $name . '" id="' . $id . '">';
foreach ($opts as $k => $label) {
printf('<option value="%s"%s>%s</option>', esc_attr($k), selected($val, $k, false), esc_html($label));
}
echo '</select>';
return;
}
if ($key === 'shop_discount_scope') {
$opts = [
'item' => __('单品', 'aksrt'),
'product' => __('商品', 'aksrt'),
'author' => __('商家', 'aksrt'),
'order' => __('整单', 'aksrt'),
];
echo '<select name="' . $name . '" id="' . $id . '">';
foreach ($opts as $k => $label) {
printf('<option value="%s"%s>%s</option>', esc_attr($k), selected($val, $k, false), esc_html($label));
}
echo '</select>';
return;
}
if ($key === 'shop_discount_user_limit') {
$opts = [
'' => __('不限', 'aksrt'),
'vip' => __('VIP1', 'aksrt'),
'vip_2' => __('VIP2', 'aksrt'),
'auth' => __('已认证', 'aksrt'),
];
echo '<select name="' . $name . '" id="' . $id . '">';
foreach ($opts as $k => $label) {
printf('<option value="%s"%s>%s</option>', esc_attr($k), selected($val, $k, false), esc_html($label));
}
echo '</select>';
return;
}
if (in_array($key, ['shop_discount_is_important', 'shop_discount_countdown'], true)) {
echo '<label><input type="checkbox" name="' . $name . '" id="' . $id . '" value="1" ' . checked($val, '1', false) . '> ' . esc_html__('启用', 'aksrt') . '</label>';
return;
}
if ($key === 'shop_discount_gift_config') {
echo '<textarea name="' . $name . '" id="' . $id . '" rows="3" class="large-text">' . esc_textarea($val) . '</textarea>';
return;
}
$type = in_array($key, ['shop_discount_priority'], true) ? 'number' : 'text';
if (in_array($key, ['shop_discount_start', 'shop_discount_end'], true)) {
$type = 'datetime-local';
// Convert mysql-ish values to datetime-local if needed.
if ($val !== '' && str_contains($val, ' ')) {
$val = str_replace(' ', 'T', substr($val, 0, 16));
}
}
printf(
'<input type="%s" name="%s" id="%s" value="%s" class="%s" />',
esc_attr($type),
$name,
$id,
esc_attr($val),
$edit ? 'regular-text' : ''
);
}
public function save_discount_term_fields(int $term_id): void {
if (!current_user_can('manage_categories')) {
return;
}
$fields = $this->get_discount_field_definitions();
foreach ($fields as $key => $def) {
if (in_array($key, ['shop_discount_is_important', 'shop_discount_countdown'], true)) {
$raw = isset($_POST[$key]) ? '1' : '';
} else {
if (!isset($_POST[$key])) {
continue;
}
$raw = wp_unslash($_POST[$key]);
}
if (isset($def['sanitize']) && is_callable($def['sanitize'])) {
$value = call_user_func($def['sanitize'], $raw);
} else {
$value = sanitize_text_field((string) $raw);
}
if (in_array($key, ['shop_discount_start', 'shop_discount_end'], true) && is_string($value) && str_contains($value, 'T')) {
$value = str_replace('T', ' ', $value);
if (strlen($value) === 16) {
$value .= ':00';
}
}
update_term_meta($term_id, $key, $value);
}
}
public function render_cat_add_fields(): void {
foreach ($this->get_cat_field_definitions() as $key => $def) {
echo '<div class="form-field">';
echo '<label for="' . esc_attr($key) . '">' . esc_html((string) ($def['description'] ?? $key)) . '</label>';
$this->render_cat_field_control($key, $def['default'] ?? '', false);
echo '</div>';
}
}
public function render_cat_edit_fields($term): void {
if (!($term instanceof WP_Term)) {
return;
}
foreach ($this->get_cat_field_definitions() as $key => $def) {
$value = get_term_meta($term->term_id, $key, true);
if ($value === '' || $value === false) {
$value = $def['default'] ?? '';
}
echo '<tr class="form-field"><th scope="row"><label for="' . esc_attr($key) . '">' . esc_html((string) ($def['description'] ?? $key)) . '</label></th><td>';
$this->render_cat_field_control($key, $value, true);
echo '</td></tr>';
}
}
/**
* @param mixed $value
*/
private function render_cat_field_control(string $key, $value, bool $edit): void {
$val = (string) $value;
if (in_array($key, ['shop_cat_guest_buy', 'shop_cat_email_fill', 'shop_cat_content_show_bg'], true)) {
echo '<label><input type="checkbox" name="' . esc_attr($key) . '" value="1" ' . checked($val, '1', false) . '> ' . esc_html__('启用', 'aksrt') . '</label>';
return;
}
if (in_array($key, ['shop_cat_service', 'shop_cat_single_tab', 'shop_cat_content_after'], true)) {
echo '<textarea class="large-text" rows="3" name="' . esc_attr($key) . '" id="' . esc_attr($key) . '">' . esc_textarea($val) . '</textarea>';
return;
}
if ($key === 'shop_cat_shipping_fee') {
$opts = ['' => __('继承', 'aksrt'), 'free' => __('包邮', 'aksrt'), 'fixed' => __('固定运费', 'aksrt'), 'amount' => __('满额包邮', 'aksrt')];
echo '<select name="' . esc_attr($key) . '" id="' . esc_attr($key) . '">';
foreach ($opts as $k => $label) {
printf('<option value="%s"%s>%s</option>', esc_attr($k), selected($val, $k, false), esc_html($label));
}
echo '</select>';
return;
}
if ($key === 'shop_cat_after_sale') {
$opts = ['' => __('继承', 'aksrt'), 'refund' => __('仅退款', 'aksrt'), 'refund_return' => __('退货退款', 'aksrt'), 'none' => __('不支持', 'aksrt')];
echo '<select name="' . esc_attr($key) . '" id="' . esc_attr($key) . '">';
foreach ($opts as $k => $label) {
printf('<option value="%s"%s>%s</option>', esc_attr($k), selected($val, $k, false), esc_html($label));
}
echo '</select>';
return;
}
if ($key === 'shop_cat_content_layout') {
$opts = ['' => __('继承', 'aksrt'), 'adapt' => __('自适应', 'aksrt'), 'full' => __('通栏', 'aksrt'), 'sidebar' => __('侧栏', 'aksrt')];
echo '<select name="' . esc_attr($key) . '" id="' . esc_attr($key) . '">';
foreach ($opts as $k => $label) {
printf('<option value="%s"%s>%s</option>', esc_attr($k), selected($val, $k, false), esc_html($label));
}
echo '</select>';
return;
}
if ($key === 'shop_cat_list_style') {
$opts = ['' => __('继承', 'aksrt'), 'default' => __('默认', 'aksrt'), 'card-lg' => __('大卡片', 'aksrt'), 'card-sm' => __('小卡片', 'aksrt')];
echo '<select name="' . esc_attr($key) . '" id="' . esc_attr($key) . '">';
foreach ($opts as $k => $label) {
printf('<option value="%s"%s>%s</option>', esc_attr($k), selected($val, $k, false), esc_html($label));
}
echo '</select>';
return;
}
$type = $key === 'shop_cat_limit_buy' ? 'number' : 'text';
printf(
'<input type="%s" class="%s" name="%s" id="%s" value="%s" />',
esc_attr($type),
$edit ? 'regular-text' : '',
esc_attr($key),
esc_attr($key),
esc_attr($val)
);
}
public function save_cat_term_fields(int $term_id): void {
if (!current_user_can('manage_categories')) {
return;
}
foreach ($this->get_cat_field_definitions() as $key => $def) {
if (in_array($key, ['shop_cat_guest_buy', 'shop_cat_email_fill', 'shop_cat_content_show_bg'], true)) {
$raw = isset($_POST[$key]) ? '1' : '';
} else {
if (!isset($_POST[$key])) {
continue;
}
$raw = wp_unslash($_POST[$key]);
}
if (isset($def['sanitize']) && is_callable($def['sanitize'])) {
$value = call_user_func($def['sanitize'], $raw);
} else {
$value = sanitize_text_field((string) $raw);
}
update_term_meta($term_id, $key, $value);
}
}
}