feat: Add product images support with WP Media Library integration
- Add WP Media Library integration for product and variation images - Support images array (URLs) conversion to attachment IDs - Add images array to API responses (Admin & Customer SPA) - Implement drag-and-drop sortable images in Admin product form - Add image gallery thumbnails in Customer SPA product page - Initialize WooCommerce session for guest cart operations - Fix product variations and attributes display in Customer SPA - Add variation image field in Admin SPA Changes: - includes/Api/ProductsController.php: Handle images array, add to responses - includes/Frontend/ShopController.php: Add images array for customer SPA - includes/Frontend/CartController.php: Initialize WC session for guests - admin-spa/src/lib/wp-media.ts: Add openWPMediaGallery function - admin-spa/src/routes/Products/partials/tabs/GeneralTab.tsx: WP Media + sortable images - admin-spa/src/routes/Products/partials/tabs/VariationsTab.tsx: Add variation image field - customer-spa/src/pages/Product/index.tsx: Add gallery thumbnails display
This commit is contained in:
306
includes/Frontend/CartController.php
Normal file
306
includes/Frontend/CartController.php
Normal file
@@ -0,0 +1,306 @@
|
||||
<?php
|
||||
namespace WooNooW\Frontend;
|
||||
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* Cart Controller - Customer-facing cart API
|
||||
* Handles cart operations for customer-spa
|
||||
*/
|
||||
class CartController {
|
||||
|
||||
/**
|
||||
* Register REST API routes
|
||||
*/
|
||||
public static function register_routes() {
|
||||
$namespace = 'woonoow/v1';
|
||||
|
||||
// Get cart
|
||||
register_rest_route($namespace, '/cart', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [__CLASS__, 'get_cart'],
|
||||
'permission_callback' => '__return_true',
|
||||
]);
|
||||
|
||||
// Add to cart
|
||||
register_rest_route($namespace, '/cart/add', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'add_to_cart'],
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => [
|
||||
'product_id' => [
|
||||
'required' => true,
|
||||
'validate_callback' => function($param) {
|
||||
return is_numeric($param);
|
||||
},
|
||||
],
|
||||
'quantity' => [
|
||||
'default' => 1,
|
||||
'sanitize_callback' => 'absint',
|
||||
],
|
||||
'variation_id' => [
|
||||
'default' => 0,
|
||||
'sanitize_callback' => 'absint',
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
// Update cart item
|
||||
register_rest_route($namespace, '/cart/update', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'update_cart'],
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => [
|
||||
'cart_item_key' => [
|
||||
'required' => true,
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
],
|
||||
'quantity' => [
|
||||
'required' => true,
|
||||
'sanitize_callback' => 'absint',
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
// Remove from cart
|
||||
register_rest_route($namespace, '/cart/remove', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'remove_from_cart'],
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => [
|
||||
'cart_item_key' => [
|
||||
'required' => true,
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
// Apply coupon
|
||||
register_rest_route($namespace, '/cart/apply-coupon', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'apply_coupon'],
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => [
|
||||
'coupon_code' => [
|
||||
'required' => true,
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
// Remove coupon
|
||||
register_rest_route($namespace, '/cart/remove-coupon', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'remove_coupon'],
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => [
|
||||
'coupon_code' => [
|
||||
'required' => true,
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cart contents
|
||||
*/
|
||||
public static function get_cart(WP_REST_Request $request) {
|
||||
if (!WC()->cart) {
|
||||
return new WP_Error('cart_error', 'Cart not initialized', ['status' => 500]);
|
||||
}
|
||||
|
||||
return new WP_REST_Response(self::format_cart(), 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add item to cart
|
||||
*/
|
||||
public static function add_to_cart(WP_REST_Request $request) {
|
||||
$product_id = $request->get_param('product_id');
|
||||
$quantity = $request->get_param('quantity');
|
||||
$variation_id = $request->get_param('variation_id');
|
||||
|
||||
// Initialize WooCommerce session for guest users
|
||||
if (!WC()->session->has_session()) {
|
||||
WC()->session->set_customer_session_cookie(true);
|
||||
}
|
||||
|
||||
if (!WC()->cart) {
|
||||
return new WP_Error('cart_error', 'Cart not initialized', ['status' => 500]);
|
||||
}
|
||||
|
||||
// Validate product
|
||||
$product = wc_get_product($product_id);
|
||||
if (!$product) {
|
||||
return new WP_Error('invalid_product', 'Product not found', ['status' => 404]);
|
||||
}
|
||||
|
||||
// Add to cart
|
||||
$cart_item_key = WC()->cart->add_to_cart($product_id, $quantity, $variation_id);
|
||||
|
||||
if (!$cart_item_key) {
|
||||
return new WP_Error('add_to_cart_failed', 'Failed to add product to cart', ['status' => 400]);
|
||||
}
|
||||
|
||||
return new WP_REST_Response([
|
||||
'message' => 'Product added to cart',
|
||||
'cart_item_key' => $cart_item_key,
|
||||
'cart' => self::format_cart(),
|
||||
], 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update cart item quantity
|
||||
*/
|
||||
public static function update_cart(WP_REST_Request $request) {
|
||||
$cart_item_key = $request->get_param('cart_item_key');
|
||||
$quantity = $request->get_param('quantity');
|
||||
|
||||
if (!WC()->cart) {
|
||||
return new WP_Error('cart_error', 'Cart not initialized', ['status' => 500]);
|
||||
}
|
||||
|
||||
// Update quantity
|
||||
$updated = WC()->cart->set_quantity($cart_item_key, $quantity);
|
||||
|
||||
if (!$updated) {
|
||||
return new WP_Error('update_failed', 'Failed to update cart item', ['status' => 400]);
|
||||
}
|
||||
|
||||
return new WP_REST_Response([
|
||||
'message' => 'Cart updated',
|
||||
'cart' => self::format_cart(),
|
||||
], 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove item from cart
|
||||
*/
|
||||
public static function remove_from_cart(WP_REST_Request $request) {
|
||||
$cart_item_key = $request->get_param('cart_item_key');
|
||||
|
||||
if (!WC()->cart) {
|
||||
return new WP_Error('cart_error', 'Cart not initialized', ['status' => 500]);
|
||||
}
|
||||
|
||||
// Remove item
|
||||
$removed = WC()->cart->remove_cart_item($cart_item_key);
|
||||
|
||||
if (!$removed) {
|
||||
return new WP_Error('remove_failed', 'Failed to remove cart item', ['status' => 400]);
|
||||
}
|
||||
|
||||
return new WP_REST_Response([
|
||||
'message' => 'Item removed from cart',
|
||||
'cart' => self::format_cart(),
|
||||
], 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply coupon to cart
|
||||
*/
|
||||
public static function apply_coupon(WP_REST_Request $request) {
|
||||
$coupon_code = $request->get_param('coupon_code');
|
||||
|
||||
if (!WC()->cart) {
|
||||
return new WP_Error('cart_error', 'Cart not initialized', ['status' => 500]);
|
||||
}
|
||||
|
||||
// Apply coupon
|
||||
$applied = WC()->cart->apply_coupon($coupon_code);
|
||||
|
||||
if (!$applied) {
|
||||
return new WP_Error('coupon_failed', 'Failed to apply coupon', ['status' => 400]);
|
||||
}
|
||||
|
||||
return new WP_REST_Response([
|
||||
'message' => 'Coupon applied',
|
||||
'cart' => self::format_cart(),
|
||||
], 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove coupon from cart
|
||||
*/
|
||||
public static function remove_coupon(WP_REST_Request $request) {
|
||||
$coupon_code = $request->get_param('coupon_code');
|
||||
|
||||
if (!WC()->cart) {
|
||||
return new WP_Error('cart_error', 'Cart not initialized', ['status' => 500]);
|
||||
}
|
||||
|
||||
// Remove coupon
|
||||
$removed = WC()->cart->remove_coupon($coupon_code);
|
||||
|
||||
if (!$removed) {
|
||||
return new WP_Error('remove_coupon_failed', 'Failed to remove coupon', ['status' => 400]);
|
||||
}
|
||||
|
||||
return new WP_REST_Response([
|
||||
'message' => 'Coupon removed',
|
||||
'cart' => self::format_cart(),
|
||||
], 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format cart data for API response
|
||||
*/
|
||||
private static function format_cart() {
|
||||
$cart = WC()->cart;
|
||||
|
||||
if (!$cart) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$items = [];
|
||||
foreach ($cart->get_cart() as $cart_item_key => $cart_item) {
|
||||
$product = $cart_item['data'];
|
||||
|
||||
$items[] = [
|
||||
'key' => $cart_item_key,
|
||||
'product_id' => $cart_item['product_id'],
|
||||
'variation_id' => $cart_item['variation_id'] ?? 0,
|
||||
'quantity' => $cart_item['quantity'],
|
||||
'name' => $product->get_name(),
|
||||
'price' => $product->get_price(),
|
||||
'subtotal' => $cart_item['line_subtotal'],
|
||||
'total' => $cart_item['line_total'],
|
||||
'image' => wp_get_attachment_url($product->get_image_id()),
|
||||
'permalink' => get_permalink($cart_item['product_id']),
|
||||
'attributes' => $cart_item['variation'] ?? [],
|
||||
];
|
||||
}
|
||||
|
||||
// Get applied coupons
|
||||
$coupons = [];
|
||||
foreach ($cart->get_applied_coupons() as $coupon_code) {
|
||||
$coupon = new \WC_Coupon($coupon_code);
|
||||
$coupons[] = [
|
||||
'code' => $coupon_code,
|
||||
'discount' => $cart->get_coupon_discount_amount($coupon_code),
|
||||
'type' => $coupon->get_discount_type(),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'items' => $items,
|
||||
'subtotal' => $cart->get_subtotal(),
|
||||
'subtotal_tax' => $cart->get_subtotal_tax(),
|
||||
'discount_total' => $cart->get_discount_total(),
|
||||
'discount_tax' => $cart->get_discount_tax(),
|
||||
'shipping_total' => $cart->get_shipping_total(),
|
||||
'shipping_tax' => $cart->get_shipping_tax(),
|
||||
'cart_contents_tax' => $cart->get_cart_contents_tax(),
|
||||
'fee_total' => $cart->get_fee_total(),
|
||||
'fee_tax' => $cart->get_fee_tax(),
|
||||
'total' => $cart->get_total('edit'),
|
||||
'total_tax' => $cart->get_total_tax(),
|
||||
'coupons' => $coupons,
|
||||
'needs_shipping' => $cart->needs_shipping(),
|
||||
'needs_payment' => $cart->needs_payment(),
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user