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:
224
includes/Frontend/HookBridge.php
Normal file
224
includes/Frontend/HookBridge.php
Normal file
@@ -0,0 +1,224 @@
|
||||
<?php
|
||||
namespace WooNooW\Frontend;
|
||||
|
||||
/**
|
||||
* WooCommerce Hook Bridge
|
||||
* Captures WooCommerce action hook output and makes it available to the SPA
|
||||
*/
|
||||
class HookBridge {
|
||||
|
||||
/**
|
||||
* Common WooCommerce hooks to capture
|
||||
*/
|
||||
private static $hooks = [
|
||||
// Single Product Hooks
|
||||
'woocommerce_before_single_product',
|
||||
'woocommerce_before_single_product_summary',
|
||||
'woocommerce_single_product_summary',
|
||||
'woocommerce_before_add_to_cart_form',
|
||||
'woocommerce_before_add_to_cart_button',
|
||||
'woocommerce_after_add_to_cart_button',
|
||||
'woocommerce_after_add_to_cart_form',
|
||||
'woocommerce_product_meta_start',
|
||||
'woocommerce_product_meta_end',
|
||||
'woocommerce_after_single_product_summary',
|
||||
'woocommerce_after_single_product',
|
||||
|
||||
// Shop/Archive Hooks
|
||||
'woocommerce_before_shop_loop',
|
||||
'woocommerce_after_shop_loop',
|
||||
'woocommerce_before_shop_loop_item',
|
||||
'woocommerce_after_shop_loop_item',
|
||||
'woocommerce_before_shop_loop_item_title',
|
||||
'woocommerce_shop_loop_item_title',
|
||||
'woocommerce_after_shop_loop_item_title',
|
||||
|
||||
// Cart Hooks
|
||||
'woocommerce_before_cart',
|
||||
'woocommerce_before_cart_table',
|
||||
'woocommerce_before_cart_contents',
|
||||
'woocommerce_cart_contents',
|
||||
'woocommerce_after_cart_contents',
|
||||
'woocommerce_after_cart_table',
|
||||
'woocommerce_cart_collaterals',
|
||||
'woocommerce_after_cart',
|
||||
|
||||
// Checkout Hooks
|
||||
'woocommerce_before_checkout_form',
|
||||
'woocommerce_checkout_before_customer_details',
|
||||
'woocommerce_checkout_after_customer_details',
|
||||
'woocommerce_checkout_before_order_review',
|
||||
'woocommerce_checkout_after_order_review',
|
||||
'woocommerce_after_checkout_form',
|
||||
];
|
||||
|
||||
/**
|
||||
* Capture hook output for a specific context
|
||||
*
|
||||
* @param string $context 'product', 'shop', 'cart', 'checkout'
|
||||
* @param array $args Context-specific arguments (e.g., product_id)
|
||||
* @return array Associative array of hook_name => html_output
|
||||
*/
|
||||
public static function capture_hooks($context, $args = []) {
|
||||
$captured = [];
|
||||
|
||||
// Filter hooks based on context
|
||||
$context_hooks = self::get_context_hooks($context);
|
||||
|
||||
foreach ($context_hooks as $hook) {
|
||||
// Start output buffering
|
||||
ob_start();
|
||||
|
||||
// Setup context (e.g., global $product for product hooks)
|
||||
self::setup_context($context, $args);
|
||||
|
||||
// Execute the hook
|
||||
do_action($hook);
|
||||
|
||||
// Capture output
|
||||
$output = ob_get_clean();
|
||||
|
||||
// Only include hooks that have output
|
||||
if (!empty(trim($output))) {
|
||||
$captured[$hook] = $output;
|
||||
}
|
||||
|
||||
// Cleanup context
|
||||
self::cleanup_context($context);
|
||||
}
|
||||
|
||||
return $captured;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get hooks for a specific context
|
||||
*/
|
||||
private static function get_context_hooks($context) {
|
||||
$context_map = [
|
||||
'product' => [
|
||||
'woocommerce_before_single_product',
|
||||
'woocommerce_before_single_product_summary',
|
||||
'woocommerce_single_product_summary',
|
||||
'woocommerce_before_add_to_cart_form',
|
||||
'woocommerce_before_add_to_cart_button',
|
||||
'woocommerce_after_add_to_cart_button',
|
||||
'woocommerce_after_add_to_cart_form',
|
||||
'woocommerce_product_meta_start',
|
||||
'woocommerce_product_meta_end',
|
||||
'woocommerce_after_single_product_summary',
|
||||
'woocommerce_after_single_product',
|
||||
],
|
||||
'shop' => [
|
||||
'woocommerce_before_shop_loop',
|
||||
'woocommerce_after_shop_loop',
|
||||
'woocommerce_before_shop_loop_item',
|
||||
'woocommerce_after_shop_loop_item',
|
||||
],
|
||||
'cart' => [
|
||||
'woocommerce_before_cart',
|
||||
'woocommerce_before_cart_table',
|
||||
'woocommerce_cart_collaterals',
|
||||
'woocommerce_after_cart',
|
||||
],
|
||||
'checkout' => [
|
||||
'woocommerce_before_checkout_form',
|
||||
'woocommerce_checkout_before_customer_details',
|
||||
'woocommerce_checkout_after_customer_details',
|
||||
'woocommerce_after_checkout_form',
|
||||
],
|
||||
];
|
||||
|
||||
return $context_map[$context] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup context for hook execution
|
||||
*/
|
||||
private static function setup_context($context, $args) {
|
||||
global $product, $post;
|
||||
|
||||
switch ($context) {
|
||||
case 'product':
|
||||
if (isset($args['product_id'])) {
|
||||
$product = wc_get_product($args['product_id']);
|
||||
$post = get_post($args['product_id']);
|
||||
setup_postdata($post);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'shop':
|
||||
// Setup shop context if needed
|
||||
break;
|
||||
|
||||
case 'cart':
|
||||
// Ensure cart is loaded
|
||||
if (!WC()->cart) {
|
||||
WC()->cart = new \WC_Cart();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'checkout':
|
||||
// Ensure checkout is loaded
|
||||
if (!WC()->checkout()) {
|
||||
WC()->checkout = new \WC_Checkout();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup context after hook execution
|
||||
*/
|
||||
private static function cleanup_context($context) {
|
||||
global $product, $post;
|
||||
|
||||
switch ($context) {
|
||||
case 'product':
|
||||
wp_reset_postdata();
|
||||
$product = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register REST API endpoint for hook capture
|
||||
*/
|
||||
public static function register_routes() {
|
||||
register_rest_route('woonoow/v1', '/hooks/(?P<context>[a-z]+)', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [__CLASS__, 'get_hooks'],
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => [
|
||||
'context' => [
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
'enum' => ['product', 'shop', 'cart', 'checkout'],
|
||||
],
|
||||
'product_id' => [
|
||||
'type' => 'integer',
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* REST API callback to get hooks
|
||||
*/
|
||||
public static function get_hooks($request) {
|
||||
$context = $request->get_param('context');
|
||||
$args = [];
|
||||
|
||||
// Get context-specific args
|
||||
if ($context === 'product') {
|
||||
$args['product_id'] = $request->get_param('product_id');
|
||||
}
|
||||
|
||||
$hooks = self::capture_hooks($context, $args);
|
||||
|
||||
return rest_ensure_response([
|
||||
'success' => true,
|
||||
'context' => $context,
|
||||
'hooks' => $hooks,
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user