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' => '', + ]]; + } + ?> + +

+ +

+ + + + + + + + + + + + $row) : + $attrs = $row['attributes'] ?? []; + $attrs_text = ''; + if (is_array($attrs)) { + $parts = []; + foreach ($attrs as $k => $v) { + if (is_int($k) && is_array($v)) { + $parts[] = ($v['name'] ?? $v['key'] ?? '') . ':' . ($v['value'] ?? ''); + } else { + $parts[] = $k . ':' . $v; + } + } + $attrs_text = implode(';', array_filter($parts)); + } elseif (is_string($attrs)) { + $attrs_text = $attrs; + } + ?> + + + + + + + + + +
+

+ +

+ + 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); + } +}