shop = $shop; } /* ============================================================ * Product meta helpers * ============================================================ */ public function get_term_options(string $taxonomy): array { $terms = get_terms([ 'taxonomy' => $taxonomy, 'hide_empty' => false, 'orderby' => 'name', 'order' => 'ASC', ]); if (is_wp_error($terms) || empty($terms)) { return []; } return array_map(static function ($term) { return [ 'id' => (int) $term->term_id, 'name' => (string) $term->name, ]; }, $terms); } public function get_product_gallery(int $product_id): array { $gallery = get_post_meta($product_id, '_aksrt_product_gallery', true); if (!is_array($gallery)) { return []; } return array_values(array_filter(array_map('esc_url_raw', $gallery))); } public function get_product_shipping_profile(int $product_id): array { return [ 'mode' => sanitize_key((string) get_post_meta($product_id, '_aksrt_shipping_mode', true)) ?: 'express', 'fee' => (float) get_post_meta($product_id, '_aksrt_shipping_fee', true), 'delivery_note' => (string) get_post_meta($product_id, '_aksrt_delivery_note', true), ]; } public function get_product_after_sale_profile(int $product_id): array { return [ 'policy' => (string) get_post_meta($product_id, '_aksrt_after_sale_policy', true), 'support_refund' => get_post_meta($product_id, '_aksrt_support_refund', true) !== '0', ]; } public function get_product_marketing_profile(int $product_id): array { return [ 'headline' => (string) get_post_meta($product_id, '_aksrt_marketing_headline', true), 'shipping_tip' => (string) get_post_meta($product_id, '_aksrt_shipping_tip', true), 'service_tip' => (string) get_post_meta($product_id, '_aksrt_service_tip', true), ]; } public function get_payment_method_options(): array { $methods = []; if (class_exists('AKSRT_Payment_Gateway')) { foreach (AKSRT_Payment_Gateway::get_instance()->get_all() as $id => $gateway) { if (!method_exists($gateway, 'is_configured') || !$gateway->is_configured()) { continue; } $methods[$id] = [ 'value' => $id, 'label' => method_exists($gateway, 'get_name') ? (string) $gateway->get_name() : $id, 'description' => $this->gateway_method_description((string) $id), ]; } } if (class_exists('AKSRT_Settings') && AKSRT_Settings::credits_enabled()) { $methods['points'] = [ 'value' => 'points', 'label' => __('积分兑换', 'aksrt'), 'description' => __('使用积分购买积分商品。', 'aksrt'), ]; } $manual = true; if (class_exists('AKSRT_Settings')) { $gw = AKSRT_Settings::get_payment_gateway_settings(); $manual = !empty($gw['manual_review']) || ($gw['mode'] ?? '') === 'manual_review'; } if ($manual) { $methods['manual_review'] = [ 'value' => 'manual_review', 'label' => __('人工确认', 'aksrt'), 'description' => __('保留待支付订单,后续由管理员确认。', 'aksrt'), ]; } if (empty($methods)) { $methods['manual_review'] = [ 'value' => 'manual_review', 'label' => __('人工确认', 'aksrt'), 'description' => __('保留待支付订单,后续由管理员确认。', 'aksrt'), ]; } return $methods; } private function gateway_method_description(string $id): string { switch ($id) { case 'balance': return __('即时完成支付并写回订单状态。', 'aksrt'); case 'alipay': return __('跳转支付宝完成支付。', 'aksrt'); case 'wechatpay': return __('微信扫码或跳转完成支付。', 'aksrt'); case 'paypal': return __('跳转 PayPal 完成支付。', 'aksrt'); case 'stripe': return __('跳转 Stripe Checkout 完成支付。', 'aksrt'); default: return ''; } } public function get_shipping_method_options(): array { return [ 'express' => [ 'value' => 'express', 'label' => __('快递配送', 'aksrt'), 'description' => __('标准快递,支持物流跟踪。', 'aksrt'), 'fee' => 12.0, ], 'ems' => [ 'value' => 'ems', 'label' => __('EMS', 'aksrt'), 'description' => __('偏远地区可选。', 'aksrt'), 'fee' => 18.0, ], 'download' => [ 'value' => 'download', 'label' => __('自动发货', 'aksrt'), 'description' => __('适用于数字商品。', 'aksrt'), 'fee' => 0.0, ], ]; } public function get_limit_buy_config(int $product_id): array { $raw = get_post_meta($product_id, '_aksrt_limit_buy_config', true); if (!is_array($raw)) { $legacy = (int) get_post_meta($product_id, '_aksrt_limit_buy', true); return ['default' => $legacy > 0 ? $legacy : -1, 'desc' => '']; } return [ 'default' => (int) ($raw['default'] ?? -1), 'vip' => (int) ($raw['vip'] ?? -1), 'vip2' => (int) ($raw['vip2'] ?? -1), 'auth' => (int) ($raw['auth'] ?? -1), 'desc' => sanitize_text_field((string) ($raw['desc'] ?? '')), ]; } public function get_product_services(int $product_id): array { $raw = get_post_meta($product_id, '_aksrt_services', true); if (!is_array($raw)) return []; return array_values(array_filter(array_map(static function ($s) { $name = sanitize_text_field((string) ($s['name'] ?? '')); if ($name === '') return null; return [ 'name' => $name, 'desc' => sanitize_text_field((string) ($s['desc'] ?? '')), 'icon' => esc_url_raw((string) ($s['icon'] ?? '')), ]; }, $raw))); } public function get_product_params(int $product_id): array { $raw = get_post_meta($product_id, '_aksrt_product_params', true); if (!is_array($raw)) return []; return array_values(array_filter(array_map(static function ($p) { $name = sanitize_text_field((string) ($p['name'] ?? '')); if ($name === '') return null; return [ 'name' => $name, 'value' => sanitize_text_field((string) ($p['value'] ?? '')), ]; }, $raw))); } /** * Resolve product main image with 3-tier fallback: * 1. _aksrt_product_image (custom main image) * 2. WordPress featured image (post thumbnail) * 3. First gallery image from _aksrt_product_gallery */ public function resolve_main_image(int $product_id): string { $custom = (string) get_post_meta($product_id, '_aksrt_product_image', true); if ($custom !== '') { return esc_url_raw($custom); } $thumb = get_the_post_thumbnail_url($product_id, 'large'); if ($thumb) { return (string) $thumb; } $gallery = get_post_meta($product_id, '_aksrt_product_gallery', true); if (is_array($gallery) && !empty($gallery)) { $first = reset($gallery); if ($first) { return esc_url_raw((string) $first); } } return ''; } public function build_product_card_view(int $product_id): array { $post = get_post($product_id); if (!$post || $post->post_type !== 'aksrt_product') { return []; } $stock_raw = (int) get_post_meta($product_id, '_aksrt_stock', true); $is_unlimited = $stock_raw <= -1; return [ 'id' => $product_id, 'title' => (string) get_the_title($product_id), 'excerpt' => (string) get_the_excerpt($product_id), 'price' => (float) get_post_meta($product_id, '_aksrt_price', true), 'image' => $this->resolve_main_image($product_id), 'stock' => $stock_raw, 'is_unlimited' => $is_unlimited, 'sales' => (int) get_post_meta($product_id, '_aksrt_sales', true), 'status' => (string) get_post_status($product_id), 'created' => get_the_date('Y-m-d H:i', $product_id), 'permalink' => get_permalink($product_id) ?: '', ]; } /* ============================================================ * Product Variants / SKU * ============================================================ */ /** * Get variants table name. */ public function variants_table(): string { global $wpdb; return $wpdb->prefix . 'aksrt_product_variant'; } /** * Get all variants for a product. */ public function get_variants(int $product_id): array { global $wpdb; $rows = $wpdb->get_results($wpdb->prepare( "SELECT * FROM {$this->variants_table()} WHERE product_id = %d ORDER BY sort_order ASC, id ASC", $product_id ), ARRAY_A); if (!$rows) return []; return array_map(function ($row) { $row['id'] = (int) $row['id']; $row['product_id'] = (int) $row['product_id']; $row['price'] = (float) $row['price']; $row['stock'] = (int) $row['stock']; $row['sort_order'] = (int) $row['sort_order']; $row['attributes'] = json_decode($row['attributes'] ?? '[]', true) ?: []; return $row; }, $rows); } /** * Get a single variant by ID. */ public function get_variant(int $variant_id): ?array { global $wpdb; $row = $wpdb->get_row($wpdb->prepare( "SELECT * FROM {$this->variants_table()} WHERE id = %d", $variant_id ), ARRAY_A); if (!$row) return null; $row['id'] = (int) $row['id']; $row['product_id'] = (int) $row['product_id']; $row['price'] = (float) $row['price']; $row['stock'] = (int) $row['stock']; $row['attributes'] = json_decode($row['attributes'] ?? '[]', true) ?: []; return $row; } /** * Save variants for a product (replace all). */ public function save_variants(int $product_id, array $variants): array { global $wpdb; $table = $this->variants_table(); // Delete existing variants $wpdb->delete($table, ['product_id' => $product_id]); $saved = []; foreach ($variants as $index => $v) { $data = [ 'product_id' => $product_id, 'sku' => sanitize_text_field((string) ($v['sku'] ?? '')), 'attributes' => wp_json_encode($v['attributes'] ?? []), 'price' => max(0, (float) ($v['price'] ?? 0)), 'stock' => max(0, (int) ($v['stock'] ?? 0)), 'image' => esc_url_raw((string) ($v['image'] ?? '')), 'sort_order' => $index, 'created_at' => current_time('mysql', true), ]; $wpdb->insert($table, $data); $saved[] = array_merge($data, ['id' => (int) $wpdb->insert_id]); } // Update product meta flag update_post_meta($product_id, '_aksrt_has_variants', count($saved) > 0 ? '1' : '0'); return $saved; } /** * Delete a single variant. */ public function delete_variant(int $variant_id): bool { global $wpdb; $variant = $this->get_variant($variant_id); if (!$variant) return false; $result = $wpdb->delete($this->variants_table(), ['id' => $variant_id]); if ($result) { // Update product meta flag $remaining = $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM {$this->variants_table()} WHERE product_id = %d", $variant['product_id'] )); update_post_meta($variant['product_id'], '_aksrt_has_variants', $remaining > 0 ? '1' : '0'); } return (bool) $result; } /** * Check if a product has variants. */ public function has_variants(int $product_id): bool { return get_post_meta($product_id, '_aksrt_has_variants', true) === '1'; } }