'GET', 'callback' => [__CLASS__, 'get_products'], 'permission_callback' => '__return_true', 'args' => [ 'page' => [ 'default' => 1, 'sanitize_callback' => 'absint', ], 'per_page' => [ 'default' => 12, 'sanitize_callback' => 'absint', ], 'category' => [ 'default' => '', 'sanitize_callback' => 'sanitize_text_field', ], 'search' => [ 'default' => '', 'sanitize_callback' => 'sanitize_text_field', ], 'orderby' => [ 'default' => 'date', 'sanitize_callback' => 'sanitize_text_field', ], 'order' => [ 'default' => 'DESC', 'sanitize_callback' => 'sanitize_text_field', ], 'slug' => [ 'default' => '', 'sanitize_callback' => 'sanitize_text_field', ], ], ]); // Get single product (public) register_rest_route($namespace, '/shop/products/(?P\d+)', [ 'methods' => 'GET', 'callback' => [__CLASS__, 'get_product'], 'permission_callback' => '__return_true', 'args' => [ 'id' => [ 'validate_callback' => function($param) { return is_numeric($param); }, ], ], ]); // Get categories (public) register_rest_route($namespace, '/shop/categories', [ 'methods' => 'GET', 'callback' => [__CLASS__, 'get_categories'], 'permission_callback' => '__return_true', ]); // Search products (public) register_rest_route($namespace, '/shop/search', [ 'methods' => 'GET', 'callback' => [__CLASS__, 'search_products'], 'permission_callback' => '__return_true', 'args' => [ 's' => [ 'required' => true, 'sanitize_callback' => 'sanitize_text_field', ], ], ]); } /** * Get products list */ public static function get_products(WP_REST_Request $request) { $page = $request->get_param('page'); $per_page = $request->get_param('per_page'); $category = $request->get_param('category'); $search = $request->get_param('search'); $orderby = $request->get_param('orderby'); $order = $request->get_param('order'); $slug = $request->get_param('slug'); $include = $request->get_param('include'); $exclude = $request->get_param('exclude'); $args = [ 'post_type' => 'product', 'post_status' => 'publish', 'posts_per_page' => $per_page, 'paged' => $page, 'orderby' => $orderby, 'order' => $order, ]; // Add slug filter (for single product lookup) if (!empty($slug)) { $args['name'] = $slug; } // Add include filter (specific product IDs) if (!empty($include)) { $ids = array_map('intval', explode(',', $include)); $args['post__in'] = $ids; $args['orderby'] = 'post__in'; // Maintain order of IDs } // Add exclude filter (exclude specific product IDs) if (!empty($exclude)) { $ids = array_map('intval', explode(',', $exclude)); $args['post__not_in'] = $ids; } // Add category filter if (!empty($category)) { // Check if category is numeric (ID) or string (slug) $field = is_numeric($category) ? 'term_id' : 'slug'; $args['tax_query'] = [ [ 'taxonomy' => 'product_cat', 'field' => $field, 'terms' => $category, ], ]; } // Add search if (!empty($search)) { $args['s'] = $search; } $query = new \WP_Query($args); // Check if this is a single product request (by slug) $is_single = !empty($slug); $products = []; if ($query->have_posts()) { while ($query->have_posts()) { $query->the_post(); $product = wc_get_product(get_the_ID()); if ($product) { // Return detailed data for single product requests $products[] = self::format_product($product, $is_single); } } wp_reset_postdata(); } return new WP_REST_Response([ 'products' => $products, 'total' => $query->found_posts, 'total_pages' => $query->max_num_pages, 'page' => $page, 'per_page' => $per_page, ], 200); } /** * Get single product */ public static function get_product(WP_REST_Request $request) { $product_id = $request->get_param('id'); $product = wc_get_product($product_id); if (!$product) { return new WP_Error('product_not_found', 'Product not found', ['status' => 404]); } return new WP_REST_Response(self::format_product($product, true), 200); } /** * Get categories */ public static function get_categories(WP_REST_Request $request) { $terms = get_terms([ 'taxonomy' => 'product_cat', 'hide_empty' => true, ]); if (is_wp_error($terms)) { return new WP_Error('categories_error', 'Failed to get categories', ['status' => 500]); } $categories = []; foreach ($terms as $term) { $thumbnail_id = get_term_meta($term->term_id, 'thumbnail_id', true); $categories[] = [ 'id' => $term->term_id, 'name' => $term->name, 'slug' => $term->slug, 'count' => $term->count, 'image' => $thumbnail_id ? wp_get_attachment_url($thumbnail_id) : '', ]; } return new WP_REST_Response($categories, 200); } /** * Search products */ public static function search_products(WP_REST_Request $request) { $search = $request->get_param('s'); $args = [ 'post_type' => 'product', 'post_status' => 'publish', 'posts_per_page' => 10, 's' => $search, ]; $query = new \WP_Query($args); $products = []; if ($query->have_posts()) { while ($query->have_posts()) { $query->the_post(); $product = wc_get_product(get_the_ID()); if ($product) { $products[] = self::format_product($product); } } wp_reset_postdata(); } return new WP_REST_Response($products, 200); } /** * Format product data for API response */ private static function format_product($product, $detailed = false) { $data = [ 'id' => $product->get_id(), 'name' => $product->get_name(), 'slug' => $product->get_slug(), 'price' => $product->get_price(), 'regular_price' => $product->get_regular_price(), 'sale_price' => $product->get_sale_price(), 'price_html' => $product->get_price_html(), 'on_sale' => $product->is_on_sale(), 'in_stock' => $product->is_in_stock(), 'stock_status' => $product->get_stock_status(), 'stock_quantity' => $product->get_stock_quantity(), 'type' => $product->get_type(), 'image' => wp_get_attachment_url($product->get_image_id()), 'permalink' => get_permalink($product->get_id()), 'categories' => wp_get_post_terms($product->get_id(), 'product_cat', ['fields' => 'all']), 'virtual' => $product->is_virtual(), 'downloadable' => $product->is_downloadable(), ]; // Add detailed info if requested if ($detailed) { $data['description'] = $product->get_description(); $data['short_description'] = $product->get_short_description(); $data['sku'] = $product->get_sku(); $data['tags'] = wp_get_post_terms($product->get_id(), 'product_tag', ['fields' => 'names']); // Gallery images $gallery_ids = $product->get_gallery_image_ids(); $data['gallery'] = array_map('wp_get_attachment_url', $gallery_ids); // Images array (featured + gallery) for frontend $images = []; if ($data['image']) { $images[] = $data['image']; } $images = array_merge($images, $data['gallery']); $data['images'] = $images; // Attributes and Variations for variable products if ($product->is_type('variable')) { $data['attributes'] = self::get_product_attributes($product); $data['variations'] = self::get_product_variations($product); } // Related products $related_ids = wc_get_related_products($product->get_id(), 4); $data['related_products'] = array_map(function($id) { $related = wc_get_product($id); return $related ? self::format_product($related) : null; }, $related_ids); $data['related_products'] = array_filter($data['related_products']); } return $data; } /** * Get product attributes */ private static function get_product_attributes($product) { $attributes = []; foreach ($product->get_attributes() as $attribute) { $attribute_data = [ 'name' => wc_attribute_label($attribute->get_name()), 'options' => [], 'visible' => $attribute->get_visible(), 'variation' => $attribute->get_variation(), ]; // Get attribute options if ($attribute->is_taxonomy()) { $terms = wc_get_product_terms($product->get_id(), $attribute->get_name(), ['fields' => 'names']); $attribute_data['options'] = $terms; } else { $attribute_data['options'] = $attribute->get_options(); } $attributes[] = $attribute_data; } return $attributes; } /** * Get product variations */ private static function get_product_variations($product) { $variations = []; foreach ($product->get_available_variations() as $variation) { $variation_obj = wc_get_product($variation['variation_id']); if ($variation_obj) { // Get attributes directly from post meta (most reliable) $attributes = []; $variation_id = $variation['variation_id']; // Query all post meta for this variation global $wpdb; $meta_rows = $wpdb->get_results($wpdb->prepare( "SELECT meta_key, meta_value FROM {$wpdb->postmeta} WHERE post_id = %d AND meta_key LIKE 'attribute_%%'", $variation_id )); foreach ($meta_rows as $row) { $attributes[$row->meta_key] = $row->meta_value; } $variations[] = [ 'id' => $variation_id, 'attributes' => $attributes, 'price' => $variation_obj->get_price(), 'regular_price' => $variation_obj->get_regular_price(), 'sale_price' => $variation_obj->get_sale_price(), 'in_stock' => $variation_obj->is_in_stock(), 'stock_quantity' => $variation_obj->get_stock_quantity(), 'image' => wp_get_attachment_url($variation_obj->get_image_id()), ]; } } return $variations; } }