Expose POST /shop/products/taxonomy to create category/tag terms, enrich GET term payload with slug/parent/count, and let ProductEditorDrawer create categories and ensure tags without WP native UI.
492 lines
22 KiB
PHP
492 lines
22 KiB
PHP
<?php
|
|
/**
|
|
* AKSRT admin shop product operations.
|
|
*
|
|
* @package AKSRT
|
|
*/
|
|
|
|
defined('ABSPATH') || exit;
|
|
|
|
trait AKSRT_Admin_Shop_Controller_Product_Operations {
|
|
|
|
public function get_overview(): WP_REST_Response {
|
|
$product_count = 0;
|
|
|
|
$post_type_obj = get_post_type_object('aksrt_product');
|
|
if ($post_type_obj) {
|
|
$counts = wp_count_posts('aksrt_product');
|
|
$product_count = (int) ($counts->publish ?? 0);
|
|
}
|
|
|
|
$order_stats = ['total' => 0, 'pending' => 0, 'paid' => 0, 'revenue' => 0];
|
|
if (class_exists('AKSRT_Order_Repository')) {
|
|
$order_stats = AKSRT_Order_Repository::get_instance()->get_stats();
|
|
}
|
|
|
|
$revenue_stats = ['total' => 0, 'pending' => 0, 'settled' => 0];
|
|
if (class_exists('AKSRT_Revenue')) {
|
|
$revenue_stats = AKSRT_Revenue::get_instance()->get_stats();
|
|
}
|
|
|
|
$gateway_summary = [];
|
|
if (class_exists('AKSRT_Payment_Gateway')) {
|
|
$gateway_summary = AKSRT_Payment_Gateway::get_instance()->get_status_summary();
|
|
}
|
|
|
|
return $this->success([
|
|
'data' => [
|
|
'enabled' => true,
|
|
'product_count' => $product_count,
|
|
'order_count' => $order_stats['total'],
|
|
'order_pending' => $order_stats['pending'] ?? 0,
|
|
'revenue_total' => $order_stats['revenue'] ?? 0,
|
|
'revenue_pending' => $revenue_stats['pending'] ?? 0,
|
|
'gateways' => $gateway_summary,
|
|
],
|
|
]);
|
|
}
|
|
|
|
public function get_product_taxonomy(): WP_REST_Response {
|
|
return $this->success([
|
|
'data' => [
|
|
'categories' => $this->format_product_terms('aksrt_product_cat'),
|
|
'tags' => $this->format_product_terms('aksrt_product_tag'),
|
|
],
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* POST /shop/products/taxonomy — create category or tag term.
|
|
*
|
|
* Body: { taxonomy: 'category'|'tag'|'aksrt_product_cat'|'aksrt_product_tag', name: string, parent?: int }
|
|
*/
|
|
public function create_product_taxonomy_term(WP_REST_Request $request): WP_REST_Response {
|
|
$params = $this->get_json_params($request);
|
|
$raw_tax = sanitize_key((string) ($params['taxonomy'] ?? 'category'));
|
|
$name = sanitize_text_field((string) ($params['name'] ?? ''));
|
|
$parent = max(0, (int) ($params['parent'] ?? 0));
|
|
|
|
$taxonomy_map = [
|
|
'category' => 'aksrt_product_cat',
|
|
'tag' => 'aksrt_product_tag',
|
|
'aksrt_product_cat' => 'aksrt_product_cat',
|
|
'aksrt_product_tag' => 'aksrt_product_tag',
|
|
];
|
|
$taxonomy = $taxonomy_map[$raw_tax] ?? '';
|
|
|
|
if ($taxonomy === '' || !taxonomy_exists($taxonomy)) {
|
|
return $this->error(__('无效的分类法', 'aksrt'));
|
|
}
|
|
if ($name === '') {
|
|
return $this->error(__('名称不能为空', 'aksrt'));
|
|
}
|
|
|
|
$existing = term_exists($name, $taxonomy, $taxonomy === 'aksrt_product_cat' ? $parent : 0);
|
|
if (is_array($existing) && !empty($existing['term_id'])) {
|
|
$term = get_term((int) $existing['term_id'], $taxonomy);
|
|
if ($term && !is_wp_error($term)) {
|
|
return $this->success([
|
|
'message' => __('术语已存在', 'aksrt'),
|
|
'data' => [
|
|
'id' => (int) $term->term_id,
|
|
'name' => (string) $term->name,
|
|
'slug' => (string) $term->slug,
|
|
'parent' => (int) $term->parent,
|
|
'taxonomy' => $taxonomy === 'aksrt_product_cat' ? 'category' : 'tag',
|
|
'existing' => true,
|
|
],
|
|
]);
|
|
}
|
|
}
|
|
|
|
$args = [];
|
|
if ($taxonomy === 'aksrt_product_cat' && $parent > 0) {
|
|
$args['parent'] = $parent;
|
|
}
|
|
|
|
$result = wp_insert_term($name, $taxonomy, $args);
|
|
if (is_wp_error($result)) {
|
|
return $this->error($result->get_error_message());
|
|
}
|
|
|
|
$term_id = (int) ($result['term_id'] ?? 0);
|
|
$term = get_term($term_id, $taxonomy);
|
|
if (!$term || is_wp_error($term)) {
|
|
return $this->error(__('创建失败', 'aksrt'));
|
|
}
|
|
|
|
return $this->success([
|
|
'message' => __('术语已创建', 'aksrt'),
|
|
'data' => [
|
|
'id' => (int) $term->term_id,
|
|
'name' => (string) $term->name,
|
|
'slug' => (string) $term->slug,
|
|
'parent' => (int) $term->parent,
|
|
'taxonomy' => $taxonomy === 'aksrt_product_cat' ? 'category' : 'tag',
|
|
'existing' => false,
|
|
],
|
|
], 201);
|
|
}
|
|
|
|
/**
|
|
* @return array<int, array{id:int,name:string,slug:string,parent:int,count:int}>
|
|
*/
|
|
private function format_product_terms(string $taxonomy): array {
|
|
if (!taxonomy_exists($taxonomy)) {
|
|
return [];
|
|
}
|
|
|
|
$terms = get_terms([
|
|
'taxonomy' => $taxonomy,
|
|
'hide_empty' => false,
|
|
]);
|
|
if (is_wp_error($terms) || !is_array($terms)) {
|
|
return [];
|
|
}
|
|
|
|
return array_map(static function (WP_Term $term): array {
|
|
return [
|
|
'id' => (int) $term->term_id,
|
|
'name' => (string) $term->name,
|
|
'slug' => (string) $term->slug,
|
|
'parent' => (int) $term->parent,
|
|
'count' => (int) $term->count,
|
|
];
|
|
}, $terms);
|
|
}
|
|
|
|
/**
|
|
* GET /shop/products — 商品列表,支持 search、status、page、per_page
|
|
*/
|
|
public function get_products(WP_REST_Request $request): WP_REST_Response {
|
|
$page = max(1, (int) $request->get_param('page'));
|
|
$per_page = max(1, min(50, (int) $request->get_param('per_page'))) ?: 20;
|
|
$search = sanitize_text_field((string) $request->get_param('search'));
|
|
$status = sanitize_key((string) $request->get_param('status'));
|
|
|
|
$query_args = [
|
|
'post_type' => 'aksrt_product',
|
|
'posts_per_page' => $per_page,
|
|
'paged' => $page,
|
|
'orderby' => 'date',
|
|
'order' => 'DESC',
|
|
];
|
|
|
|
if ($status !== '') {
|
|
$query_args['post_status'] = $status;
|
|
} else {
|
|
$query_args['post_status'] = ['publish', 'draft', 'pending'];
|
|
}
|
|
|
|
if ($search !== '') {
|
|
$query_args['s'] = $search;
|
|
}
|
|
|
|
$query = new WP_Query($query_args);
|
|
|
|
$products = [];
|
|
while ($query->have_posts()) {
|
|
$query->the_post();
|
|
$id = get_the_ID();
|
|
$products[] = [
|
|
'id' => $id,
|
|
'title' => get_the_title(),
|
|
'price' => (float) get_post_meta($id, '_aksrt_price', true),
|
|
'image' => get_the_post_thumbnail_url($id, 'medium') ?: '',
|
|
'stock' => (int) get_post_meta($id, '_aksrt_stock', true),
|
|
'sales' => (int) get_post_meta($id, '_aksrt_sales', true),
|
|
'status' => get_post_status(),
|
|
'created' => get_the_date('Y-m-d H:i'),
|
|
];
|
|
}
|
|
wp_reset_postdata();
|
|
|
|
return $this->success([
|
|
'data' => [
|
|
'items' => $products,
|
|
'total' => (int) $query->found_posts,
|
|
'total_pages' => (int) $query->max_num_pages,
|
|
'page' => $page,
|
|
],
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* GET /shop/products/{id} — 单个商品详情
|
|
*/
|
|
public function get_product(WP_REST_Request $request): WP_REST_Response {
|
|
$id = (int) $request->get_param('id');
|
|
$post = get_post($id);
|
|
|
|
if (!$post || $post->post_type !== 'aksrt_product') {
|
|
return $this->error('商品不存在', 404);
|
|
}
|
|
|
|
return $this->success([
|
|
'data' => [
|
|
'id' => $post->ID,
|
|
'title' => $post->post_title,
|
|
'content' => $post->post_content,
|
|
'excerpt' => $post->post_excerpt,
|
|
'price' => (float) get_post_meta($post->ID, '_aksrt_price', true),
|
|
'image' => (string) (get_post_meta($post->ID, '_aksrt_product_image', true) ?: get_the_post_thumbnail_url($post->ID, 'medium') ?: ''),
|
|
'stock' => (int) get_post_meta($post->ID, '_aksrt_stock', true),
|
|
'sales' => (int) get_post_meta($post->ID, '_aksrt_sales', true),
|
|
'status' => $post->post_status,
|
|
'created' => get_the_date('Y-m-d H:i', $post),
|
|
'modified' => get_the_modified_date('Y-m-d H:i', $post),
|
|
'categories' => wp_get_post_terms($post->ID, 'aksrt_product_cat', ['fields' => 'names']) ?: [],
|
|
'tags' => wp_get_post_terms($post->ID, 'aksrt_product_tag', ['fields' => 'names']) ?: [],
|
|
'gallery' => get_post_meta($post->ID, '_aksrt_product_gallery', true) ?: [],
|
|
'shipping_mode' => (string) get_post_meta($post->ID, '_aksrt_shipping_mode', true),
|
|
'shipping_fee' => (float) get_post_meta($post->ID, '_aksrt_shipping_fee', true),
|
|
'after_sale_policy' => (string) get_post_meta($post->ID, '_aksrt_after_sale_policy', true),
|
|
'support_refund' => get_post_meta($post->ID, '_aksrt_support_refund', true) !== '0',
|
|
],
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* POST /shop/products — 创建商品
|
|
*/
|
|
public function create_product(WP_REST_Request $request): WP_REST_Response {
|
|
$params = $this->get_json_params($request);
|
|
|
|
$title = sanitize_text_field((string) ($params['title'] ?? ''));
|
|
$content = wp_kses_post((string) ($params['content'] ?? ''));
|
|
$excerpt = sanitize_textarea_field((string) ($params['excerpt'] ?? ''));
|
|
$price = (float) ($params['price'] ?? 0);
|
|
$stock = (int) ($params['stock'] ?? 0);
|
|
$status = sanitize_key((string) ($params['status'] ?? 'publish'));
|
|
$image = esc_url_raw((string) ($params['image'] ?? ''));
|
|
$gallery = is_array($params['gallery'] ?? null) ? array_values(array_filter(array_map('esc_url_raw', $params['gallery']))) : [];
|
|
$shipping_mode = sanitize_key((string) ($params['shipping_mode'] ?? 'express'));
|
|
$shipping_fee = max(0, (float) ($params['shipping_fee'] ?? 0));
|
|
$after_sale_policy = sanitize_textarea_field((string) ($params['after_sale_policy'] ?? ''));
|
|
$support_refund = !isset($params['support_refund']) || !empty($params['support_refund']);
|
|
$author_id = max(0, (int) ($params['author_id'] ?? 0));
|
|
|
|
if ($title === '') {
|
|
return $this->error('商品名称不能为空');
|
|
}
|
|
|
|
$valid_statuses = ['publish', 'draft', 'pending'];
|
|
if (!in_array($status, $valid_statuses, true)) {
|
|
$status = 'draft';
|
|
}
|
|
|
|
$post_id = wp_insert_post([
|
|
'post_type' => 'aksrt_product',
|
|
'post_title' => $title,
|
|
'post_content' => $content,
|
|
'post_excerpt' => $excerpt,
|
|
'post_status' => $status,
|
|
'post_author' => $author_id > 0 ? $author_id : get_current_user_id(),
|
|
'meta_input' => [
|
|
'_aksrt_price' => max(0, $price),
|
|
'_aksrt_stock' => max(0, $stock),
|
|
'_aksrt_sales' => 0,
|
|
'_aksrt_score' => 0,
|
|
'_aksrt_views' => 0,
|
|
'_aksrt_favorite_count' => 0,
|
|
'_aksrt_product_gallery' => $gallery,
|
|
'_aksrt_shipping_mode' => $shipping_mode,
|
|
'_aksrt_shipping_fee' => $shipping_fee,
|
|
'_aksrt_after_sale_policy' => $after_sale_policy,
|
|
'_aksrt_support_refund' => $support_refund ? '1' : '0',
|
|
'_aksrt_product_video' => is_array($params['product_video'] ?? null) ? array_values(array_filter(array_map('esc_url_raw', $params['product_video']))) : [],
|
|
'_aksrt_product_params' => sanitize_textarea_field((string) ($params['product_params'] ?? '')),
|
|
'_aksrt_pay_modo' => sanitize_key((string) ($params['pay_modo'] ?? '0')),
|
|
'_aksrt_start_price' => max(0, (float) ($params['start_price'] ?? 0)),
|
|
'_aksrt_product_rebate' => sanitize_text_field((string) ($params['product_rebate'] ?? '')),
|
|
'_aksrt_limit_buy' => max(-1, min(9999, (int) ($params['limit_buy'] ?? -1))),
|
|
'_aksrt_limit_buy_note' => sanitize_text_field((string) ($params['limit_buy_note'] ?? '')),
|
|
'_aksrt_stock_type' => sanitize_key((string) ($params['stock_type'] ?? 'all')),
|
|
'_aksrt_user_required' => sanitize_textarea_field((string) ($params['user_required'] ?? '')),
|
|
'_aksrt_shipping_type' => sanitize_key((string) ($params['shipping_type'] ?? $shipping_mode)),
|
|
'_aksrt_shipping_delivery_desc' => sanitize_text_field((string) ($params['shipping_delivery_desc'] ?? '')),
|
|
'_aksrt_shipping_fee_type' => sanitize_key((string) ($params['shipping_fee_type'] ?? 'free')),
|
|
'_aksrt_shipping_fee_free_amount'=> max(0, (float) ($params['shipping_fee_free_amount'] ?? 0)),
|
|
'_aksrt_after_sale_matrix' => sanitize_textarea_field((string) ($params['after_sale_matrix'] ?? '')),
|
|
'_aksrt_service' => sanitize_textarea_field((string) ($params['service'] ?? '')),
|
|
'_aksrt_single_tab' => sanitize_textarea_field((string) ($params['single_tab'] ?? '')),
|
|
'_aksrt_content_layout' => sanitize_key((string) ($params['content_layout'] ?? '')),
|
|
'_aksrt_content_show_bg' => sanitize_text_field((string) ($params['content_show_bg'] ?? '')),
|
|
'_aksrt_delivery_note' => sanitize_text_field((string) ($params['delivery_note'] ?? '')),
|
|
'_aksrt_marketing_headline' => sanitize_text_field((string) ($params['marketing_headline'] ?? '')),
|
|
'_aksrt_shipping_tip' => sanitize_text_field((string) ($params['shipping_tip'] ?? '')),
|
|
'_aksrt_service_tip' => sanitize_text_field((string) ($params['service_tip'] ?? '')),
|
|
],
|
|
], true);
|
|
|
|
if (is_wp_error($post_id)) {
|
|
return $this->error($post_id->get_error_message());
|
|
}
|
|
|
|
if (!empty($params['categories']) && is_array($params['categories'])) {
|
|
wp_set_object_terms($post_id, $params['categories'], 'aksrt_product_cat');
|
|
}
|
|
if (!empty($params['tags']) && is_array($params['tags'])) {
|
|
wp_set_object_terms($post_id, $params['tags'], 'aksrt_product_tag');
|
|
}
|
|
if ($image !== '') {
|
|
update_post_meta($post_id, '_aksrt_product_image', $image);
|
|
}
|
|
|
|
return $this->success([
|
|
'message' => __('商品已创建', 'aksrt'),
|
|
'data' => ['id' => $post_id],
|
|
], 201);
|
|
}
|
|
|
|
/**
|
|
* POST /shop/products/{id} — 更新商品(含上下架)
|
|
*/
|
|
public function update_product(WP_REST_Request $request): WP_REST_Response {
|
|
$id = (int) $request->get_param('id');
|
|
$post = get_post($id);
|
|
|
|
if (!$post || $post->post_type !== 'aksrt_product') {
|
|
return $this->error('商品不存在', 404);
|
|
}
|
|
|
|
$params = $this->get_json_params($request);
|
|
$update = ['ID' => $id];
|
|
|
|
if (isset($params['title'])) {
|
|
$update['post_title'] = sanitize_text_field($params['title']);
|
|
}
|
|
if (isset($params['content'])) {
|
|
$update['post_content'] = wp_kses_post($params['content']);
|
|
}
|
|
if (isset($params['excerpt'])) {
|
|
$update['post_excerpt'] = sanitize_textarea_field($params['excerpt']);
|
|
}
|
|
if (isset($params['status'])) {
|
|
$new_status = sanitize_key($params['status']);
|
|
$valid_statuses = ['publish', 'draft', 'pending', 'trash'];
|
|
if (in_array($new_status, $valid_statuses, true)) {
|
|
$update['post_status'] = $new_status;
|
|
}
|
|
}
|
|
|
|
$meta = [];
|
|
if (isset($params['price'])) {
|
|
$meta['_aksrt_price'] = max(0, (float) $params['price']);
|
|
}
|
|
if (isset($params['stock'])) {
|
|
$meta['_aksrt_stock'] = max(0, (int) $params['stock']);
|
|
}
|
|
if (isset($params['gallery']) && is_array($params['gallery'])) {
|
|
$meta['_aksrt_product_gallery'] = array_values(array_filter(array_map('esc_url_raw', $params['gallery'])));
|
|
}
|
|
if (isset($params['shipping_mode'])) {
|
|
$meta['_aksrt_shipping_mode'] = sanitize_key((string) $params['shipping_mode']);
|
|
}
|
|
if (isset($params['shipping_fee'])) {
|
|
$meta['_aksrt_shipping_fee'] = max(0, (float) $params['shipping_fee']);
|
|
}
|
|
if (isset($params['after_sale_policy'])) {
|
|
$meta['_aksrt_after_sale_policy'] = sanitize_textarea_field((string) $params['after_sale_policy']);
|
|
}
|
|
if (isset($params['support_refund'])) {
|
|
$meta['_aksrt_support_refund'] = !empty($params['support_refund']) ? '1' : '0';
|
|
}
|
|
if (isset($params['product_video']) && is_array($params['product_video'])) {
|
|
$meta['_aksrt_product_video'] = array_values(array_filter(array_map('esc_url_raw', $params['product_video'])));
|
|
}
|
|
if (isset($params['product_params'])) {
|
|
$meta['_aksrt_product_params'] = sanitize_textarea_field((string) $params['product_params']);
|
|
}
|
|
if (isset($params['pay_modo'])) {
|
|
$meta['_aksrt_pay_modo'] = sanitize_key((string) $params['pay_modo']);
|
|
}
|
|
if (isset($params['start_price'])) {
|
|
$meta['_aksrt_start_price'] = max(0, (float) $params['start_price']);
|
|
}
|
|
if (isset($params['product_rebate'])) {
|
|
$meta['_aksrt_product_rebate'] = sanitize_text_field((string) $params['product_rebate']);
|
|
}
|
|
if (isset($params['limit_buy'])) {
|
|
$meta['_aksrt_limit_buy'] = max(-1, min(9999, (int) $params['limit_buy']));
|
|
}
|
|
if (isset($params['limit_buy_note'])) {
|
|
$meta['_aksrt_limit_buy_note'] = sanitize_text_field((string) $params['limit_buy_note']);
|
|
}
|
|
if (isset($params['stock_type'])) {
|
|
$meta['_aksrt_stock_type'] = sanitize_key((string) $params['stock_type']);
|
|
}
|
|
if (isset($params['stock_opts']) && is_array($params['stock_opts'])) {
|
|
$meta['_aksrt_stock_opts'] = wp_json_encode($params['stock_opts']);
|
|
}
|
|
if (isset($params['user_required'])) {
|
|
$meta['_aksrt_user_required'] = sanitize_textarea_field((string) $params['user_required']);
|
|
}
|
|
if (isset($params['shipping_type'])) {
|
|
$meta['_aksrt_shipping_type'] = sanitize_key((string) $params['shipping_type']);
|
|
}
|
|
if (isset($params['shipping_delivery_desc'])) {
|
|
$meta['_aksrt_shipping_delivery_desc'] = sanitize_text_field((string) $params['shipping_delivery_desc']);
|
|
}
|
|
if (isset($params['auto_delivery']) && is_array($params['auto_delivery'])) {
|
|
$meta['_aksrt_auto_delivery'] = wp_json_encode($params['auto_delivery']);
|
|
}
|
|
if (isset($params['shipping_fee_type'])) {
|
|
$meta['_aksrt_shipping_fee_type'] = sanitize_key((string) $params['shipping_fee_type']);
|
|
}
|
|
if (isset($params['shipping_fee_free_amount'])) {
|
|
$meta['_aksrt_shipping_fee_free_amount'] = max(0, (float) $params['shipping_fee_free_amount']);
|
|
}
|
|
if (isset($params['after_sale_matrix'])) {
|
|
$meta['_aksrt_after_sale_matrix'] = sanitize_textarea_field((string) $params['after_sale_matrix']);
|
|
}
|
|
if (isset($params['service'])) {
|
|
$meta['_aksrt_service'] = sanitize_textarea_field((string) $params['service']);
|
|
}
|
|
if (isset($params['single_tab'])) {
|
|
$meta['_aksrt_single_tab'] = sanitize_textarea_field((string) $params['single_tab']);
|
|
}
|
|
if (isset($params['content_layout'])) {
|
|
$meta['_aksrt_content_layout'] = sanitize_key((string) $params['content_layout']);
|
|
}
|
|
if (isset($params['content_show_bg'])) {
|
|
$meta['_aksrt_content_show_bg'] = sanitize_text_field((string) $params['content_show_bg']);
|
|
}
|
|
if (isset($params['delivery_note'])) {
|
|
$meta['_aksrt_delivery_note'] = sanitize_text_field((string) $params['delivery_note']);
|
|
}
|
|
if (isset($params['marketing_headline'])) {
|
|
$meta['_aksrt_marketing_headline'] = sanitize_text_field((string) $params['marketing_headline']);
|
|
}
|
|
if (isset($params['shipping_tip'])) {
|
|
$meta['_aksrt_shipping_tip'] = sanitize_text_field((string) $params['shipping_tip']);
|
|
}
|
|
if (isset($params['service_tip'])) {
|
|
$meta['_aksrt_service_tip'] = sanitize_text_field((string) $params['service_tip']);
|
|
}
|
|
if (!empty($meta)) {
|
|
$update['meta_input'] = $meta;
|
|
}
|
|
|
|
$result = wp_update_post($update, true);
|
|
|
|
if (is_wp_error($result)) {
|
|
return $this->error($result->get_error_message());
|
|
}
|
|
|
|
if (isset($params['categories']) && is_array($params['categories'])) {
|
|
wp_set_object_terms($id, $params['categories'], 'aksrt_product_cat');
|
|
}
|
|
if (isset($params['tags']) && is_array($params['tags'])) {
|
|
wp_set_object_terms($id, $params['tags'], 'aksrt_product_tag');
|
|
}
|
|
if (isset($params['image'])) {
|
|
update_post_meta($id, '_aksrt_product_image', esc_url_raw((string) $params['image']));
|
|
}
|
|
|
|
return $this->success([
|
|
'message' => __('商品已更新', 'aksrt'),
|
|
]);
|
|
}
|
|
}
|