diff --git a/functions.php b/functions.php
index ecaea40f..f2bffc70 100644
--- a/functions.php
+++ b/functions.php
@@ -298,6 +298,16 @@ if (is_admin() && class_exists('AKSRT_Admin_Shop_Controller')) {
if (is_admin() && class_exists('AKSRT_Product_Meta_Box')) {
AKSRT_Product_Meta_Box::get_instance()->init();
}
+// 商品原生变体 meta box
+if (is_admin() && class_exists('AKSRT_Product_Variants_Meta_Box')) {
+ AKSRT_Product_Variants_Meta_Box::get_instance()->init();
+}
+
+// 论坛版块版主 meta box
+if (is_admin() && class_exists('AKSRT_Plate_Meta_Box')) {
+ AKSRT_Plate_Meta_Box::get_instance()->init();
+}
+
// 商品评价/评分系统
if (class_exists('AKSRT_Product_Review')) {
diff --git a/inc/bootstrap.php b/inc/bootstrap.php
index 271719cc..0cb88453 100644
--- a/inc/bootstrap.php
+++ b/inc/bootstrap.php
@@ -321,6 +321,8 @@ require_once AKSRT_DIR . '/inc/classes/shop/class-aksrt-shop-checkout-service.ph
require_once AKSRT_DIR . '/inc/classes/shop/class-aksrt-shop-address-service.php';
require_once AKSRT_DIR . '/inc/classes/shop/class-aksrt-shop-product-service.php';
require_once AKSRT_DIR . '/inc/classes/shop/class-aksrt-product-meta-box.php';
+require_once AKSRT_DIR . '/inc/classes/shop/class-aksrt-product-variants-meta-box.php';
+require_once AKSRT_DIR . '/inc/classes/forum/class-aksrt-plate-meta-box.php';
require_once AKSRT_DIR . '/inc/classes/shop/shop-traits/trait-aksrt-shop-part-1.php';
require_once AKSRT_DIR . '/inc/classes/shop/shop-traits/trait-aksrt-shop-part-2.php';
require_once AKSRT_DIR . '/inc/classes/shop/shop-traits/trait-aksrt-shop-part-3.php';
diff --git a/inc/classes/class-aksrt-shop-term-meta.php b/inc/classes/class-aksrt-shop-term-meta.php
index 9db2a11d..3f1a7568 100644
--- a/inc/classes/class-aksrt-shop-term-meta.php
+++ b/inc/classes/class-aksrt-shop-term-meta.php
@@ -33,6 +33,12 @@ class AKSRT_Shop_Term_Meta {
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']);
}
// ─── Taxonomy Registration ───────────────────────────────────
@@ -408,4 +414,155 @@ class AKSRT_Shop_Term_Meta {
$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 '
';
+ echo '';
+ $this->render_discount_field_control($key, $default, false);
+ echo '
';
+ }
+ }
+
+ /**
+ * 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 '';
+ echo ' | ';
+ echo '';
+ $this->render_discount_field_control($key, $value, true);
+ echo ' |
';
+ }
+ }
+
+ /**
+ * @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 '';
+ return;
+ }
+
+ if ($key === 'shop_discount_scope') {
+ $opts = [
+ 'item' => __('单品', 'aksrt'),
+ 'product' => __('商品', 'aksrt'),
+ 'author' => __('商家', 'aksrt'),
+ 'order' => __('整单', 'aksrt'),
+ ];
+ echo '';
+ return;
+ }
+
+ if ($key === 'shop_discount_user_limit') {
+ $opts = [
+ '' => __('不限', 'aksrt'),
+ 'vip' => __('VIP1', 'aksrt'),
+ 'vip_2' => __('VIP2', 'aksrt'),
+ 'auth' => __('已认证', 'aksrt'),
+ ];
+ echo '';
+ return;
+ }
+
+ if (in_array($key, ['shop_discount_is_important', 'shop_discount_countdown'], true)) {
+ echo '';
+ return;
+ }
+
+ if ($key === 'shop_discount_gift_config') {
+ echo '';
+ 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(
+ '',
+ 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);
+ }
+ }
}
diff --git a/inc/classes/forum/class-aksrt-plate-meta-box.php b/inc/classes/forum/class-aksrt-plate-meta-box.php
new file mode 100644
index 00000000..dbd2ff95
--- /dev/null
+++ b/inc/classes/forum/class-aksrt-plate-meta-box.php
@@ -0,0 +1,140 @@
+get_plate_moderators((int) $post->ID);
+ $applications = $forum->get_plate_moderator_applications((int) $post->ID);
+ } else {
+ $raw = get_post_meta($post->ID, '_aksrt_moderators', true);
+ $moderator_ids = is_array($raw) ? array_map('intval', $raw) : [];
+ $applications = get_post_meta($post->ID, '_aksrt_moderator_applications', true);
+ $applications = is_array($applications) ? $applications : [];
+ }
+
+ $moderator_labels = [];
+ foreach ($moderator_ids as $uid) {
+ $user = get_userdata((int) $uid);
+ $moderator_labels[] = $user
+ ? sprintf('%s (#%d)', $user->user_login, (int) $uid)
+ : (string) (int) $uid;
+ }
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+ ' . esc_html__('暂无待处理申请。', 'aksrt') . '';
+ return;
+ }
+
+ echo '';
+ }
+
+ public function save(int $post_id, WP_Post $post): void {
+ if (!isset($_POST['aksrt_plate_meta_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash((string) $_POST['aksrt_plate_meta_nonce'])), 'aksrt_plate_meta_save')) {
+ return;
+ }
+ if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
+ return;
+ }
+ if ($post->post_type !== 'aksrt_forum_plate' || !current_user_can('edit_post', $post_id)) {
+ return;
+ }
+
+ $raw = isset($_POST['aksrt_plate_moderators']) ? (string) wp_unslash($_POST['aksrt_plate_moderators']) : '';
+ $ids = preg_split('/[\s,;]+/', $raw) ?: [];
+ $ids = array_values(array_unique(array_filter(array_map('intval', $ids))));
+ update_post_meta($post_id, '_aksrt_moderators', $ids);
+
+ $actions = isset($_POST['aksrt_plate_app']) && is_array($_POST['aksrt_plate_app'])
+ ? wp_unslash($_POST['aksrt_plate_app'])
+ : [];
+ if (!$actions || !class_exists('AKSRT_Forum')) {
+ return;
+ }
+
+ $forum = AKSRT_Forum::get_instance();
+ foreach ($actions as $application_id => $action) {
+ $application_id = sanitize_text_field((string) $application_id);
+ $action = sanitize_key((string) $action);
+ if ($application_id === '' || $action === 'keep') {
+ continue;
+ }
+ if ($action === 'approved') {
+ $forum->review_moderator_application($application_id, AKSRT_Forum::APPLICATION_APPROVED, '', get_current_user_id());
+ } elseif ($action === 'rejected') {
+ $forum->review_moderator_application($application_id, AKSRT_Forum::APPLICATION_REJECTED, '', get_current_user_id());
+ }
+ }
+ }
+}
diff --git a/inc/classes/shop/class-aksrt-product-variants-meta-box.php b/inc/classes/shop/class-aksrt-product-variants-meta-box.php
new file mode 100644
index 00000000..f79c1331
--- /dev/null
+++ b/inc/classes/shop/class-aksrt-product-variants-meta-box.php
@@ -0,0 +1,188 @@
+get_variants((int) $post->ID);
+ }
+
+ if (!$variants) {
+ $variants = [[
+ 'sku' => '',
+ 'attributes' => [],
+ 'price' => 0,
+ 'stock' => 0,
+ 'image' => '',
+ ]];
+ }
+ ?>
+
+
+
+
+
+
+
+
+
+ post_type !== 'aksrt_product' || !current_user_can('edit_post', $post_id)) {
+ return;
+ }
+ if (!class_exists('AKSRT_Shop')) {
+ return;
+ }
+
+ $skus = isset($_POST['aksrt_variant_sku']) && is_array($_POST['aksrt_variant_sku']) ? wp_unslash($_POST['aksrt_variant_sku']) : [];
+ $attrs = isset($_POST['aksrt_variant_attrs']) && is_array($_POST['aksrt_variant_attrs']) ? wp_unslash($_POST['aksrt_variant_attrs']) : [];
+ $prices = isset($_POST['aksrt_variant_price']) && is_array($_POST['aksrt_variant_price']) ? wp_unslash($_POST['aksrt_variant_price']) : [];
+ $stocks = isset($_POST['aksrt_variant_stock']) && is_array($_POST['aksrt_variant_stock']) ? wp_unslash($_POST['aksrt_variant_stock']) : [];
+ $images = isset($_POST['aksrt_variant_image']) && is_array($_POST['aksrt_variant_image']) ? wp_unslash($_POST['aksrt_variant_image']) : [];
+
+ $variants = [];
+ $count = max(count($skus), count($attrs), count($prices), count($stocks), count($images));
+ for ($i = 0; $i < $count; $i++) {
+ $sku = sanitize_text_field((string) ($skus[$i] ?? ''));
+ $attr_raw = sanitize_text_field((string) ($attrs[$i] ?? ''));
+ $price = max(0, (float) ($prices[$i] ?? 0));
+ $stock = max(0, (int) ($stocks[$i] ?? 0));
+ $image = esc_url_raw((string) ($images[$i] ?? ''));
+
+ if ($sku === '' && $attr_raw === '' && $price <= 0 && $stock <= 0 && $image === '') {
+ continue;
+ }
+
+ $attributes = [];
+ if ($attr_raw !== '') {
+ foreach (preg_split('/\s*;\s*/', $attr_raw) ?: [] as $pair) {
+ if ($pair === '' || !str_contains($pair, ':')) {
+ continue;
+ }
+ [$k, $v] = array_map('trim', explode(':', $pair, 2));
+ if ($k !== '') {
+ $attributes[$k] = $v;
+ }
+ }
+ }
+
+ $variants[] = [
+ 'sku' => $sku,
+ 'attributes' => $attributes,
+ 'price' => $price,
+ 'stock' => $stock,
+ 'image' => $image,
+ ];
+ }
+
+ AKSRT_Shop::get_instance()->save_variants($post_id, $variants);
+ }
+}