Parcourir la source

Cart thumbnail for block cart, session persistence, server-side blocking — v1.1.1 -> v1.1.5

- Add woocommerce_product_get_image_id and
  woocommerce_product_variation_get_image_id filters to override
  product image with uploaded photo when in cart (block cart / Store API)
- Keep classic cart filters (woocommerce_cart_item_thumbnail) as fallback
- Store thumb_url in session and cart item data for reliable display
- Session not cleared after add-to-cart (reusable for multiple variants)
- Server-side add-to-cart validation with safety net
- Remove client-side button disabling (server handles blocking)
- Add cart preview CSS, woocommerce_after_cart_item_name action
- Update documentation and translations

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dalibor Votruba il y a 3 mois
Parent
commit
d117e841ec

+ 4 - 3
studiou-wc-free-photo-product/CLAUDE.md

@@ -2,7 +2,7 @@
 
 ## Project Overview
 
-WordPress/WooCommerce plugin called **studiou-wc-free-photo-product** (QDR - Studiou WC Free Photo Product) v1.1.1.
+WordPress/WooCommerce plugin called **studiou-wc-free-photo-product** (QDR - Studiou WC Free Photo Product) v1.1.5.
 Allows publishing special variable products where customers upload custom raw images to be printed for a price.
 
 ## Initial Requirements
@@ -38,7 +38,7 @@ Allows publishing special variable products where customers upload custom raw im
 - `Studiou_WC_FPP_Upload` - Chunked upload AJAX, file assembly, attachment creation
 - `Studiou_WC_FPP_Single_Product` - Upload area on product page (`woocommerce_single_product_summary` priority 35), WC session storage, server-side validation (`woocommerce_add_to_cart_validation` priority 1) + safety net (`woocommerce_add_to_cart` removes items without photo)
 - `Studiou_WC_FPP_Shortcode` - `[studiou_free_photo_product id="X"]` renders product with WC native variation form
-- `Studiou_WC_FPP_Cart` - Reads file data from WC session via `woocommerce_add_cart_item_data`, replaces cart/order item thumbnails with uploaded photo, order item meta, order linking
+- `Studiou_WC_FPP_Cart` - Reads file data from WC session via `woocommerce_add_cart_item_data`, replaces cart/order item thumbnails (classic cart via `woocommerce_cart_item_thumbnail`, block cart via `woocommerce_product_get_image_id`/`woocommerce_product_variation_get_image_id`), order item meta, order linking
 - `Studiou_WC_FPP_Admin` - Summary page, status management, ZIP download
 
 ## Frontend Flow
@@ -52,7 +52,8 @@ Allows publishing special variable products where customers upload custom raw im
 7. `woocommerce_add_to_cart_validation` (priority 1) checks WC session — blocks with error notice if no photo
 8. `woocommerce_add_to_cart` safety net — removes item if it was added without photo data
 9. `woocommerce_add_cart_item_data` reads file data from WC session (session NOT cleared — same photo reusable for multiple variants)
-10. Cart/order item thumbnails replaced with uploaded photo (`woocommerce_cart_item_thumbnail`, `woocommerce_admin_order_item_thumbnail`)
+10. Cart thumbnails: classic cart via `woocommerce_cart_item_thumbnail`, block cart via `woocommerce_product_get_image_id`/`woocommerce_product_variation_get_image_id`
+11. Order admin thumbnails via `woocommerce_admin_order_item_thumbnail`
 11. On checkout, file record is linked to order via `woocommerce_checkout_order_processed`
 12. Session only cleared when user clicks Remove
 

+ 13 - 0
studiou-wc-free-photo-product/assets/css/frontend.css

@@ -236,6 +236,19 @@
     cursor: not-allowed;
 }
 
+/* Cart uploaded photo preview */
+.studiou-fpp-cart-preview {
+    margin-top: 8px;
+}
+
+.studiou-fpp-cart-preview img {
+    max-width: 70px;
+    max-height: 70px;
+    object-fit: cover;
+    border-radius: 4px;
+    border: 1px solid #ddd;
+}
+
 /* Messages */
 .studiou-fpp-msg {
     padding: 10px 15px;

+ 90 - 6
studiou-wc-free-photo-product/includes/class-studiou-wc-fpp-cart.php

@@ -17,9 +17,16 @@ class Studiou_WC_FPP_Cart {
         // Display uploaded file info in cart
         add_filter('woocommerce_get_item_data', array($this, 'display_cart_item_data'), 10, 2);
 
-        // Replace cart item thumbnail with uploaded image
+        // Replace cart item thumbnail with uploaded image (classic cart)
         add_filter('woocommerce_cart_item_thumbnail', array($this, 'cart_item_thumbnail'), 10, 3);
 
+        // Show uploaded photo preview directly after item name in cart (classic cart)
+        add_action('woocommerce_after_cart_item_name', array($this, 'show_cart_item_photo_preview'), 10, 2);
+
+        // Override product image for block cart (Store API) — returns uploaded photo as product image
+        add_filter('woocommerce_product_get_image_id', array($this, 'override_product_image_for_cart'), 10, 2);
+        add_filter('woocommerce_product_variation_get_image_id', array($this, 'override_product_image_for_cart'), 10, 2);
+
         // Save file reference to order item
         add_action('woocommerce_checkout_create_order_line_item', array($this, 'save_order_item_meta'), 10, 4);
 
@@ -50,6 +57,7 @@ class Studiou_WC_FPP_Cart {
         $attachment_id = WC()->session->get('studiou_fpp_attachment_id');
         $file_record_id = WC()->session->get('studiou_fpp_file_record_id');
         $file_name = WC()->session->get('studiou_fpp_file_name');
+        $thumb_url = WC()->session->get('studiou_fpp_thumb_url');
 
         if (!$attachment_id || !$file_record_id) {
             return $cart_item_data;
@@ -64,6 +72,7 @@ class Studiou_WC_FPP_Cart {
         $cart_item_data['studiou_fpp_attachment_id'] = $attachment_id;
         $cart_item_data['studiou_fpp_file_record_id'] = $file_record_id;
         $cart_item_data['studiou_fpp_file_name'] = $file_name ?: $record->file_name;
+        $cart_item_data['studiou_fpp_thumb_url'] = $thumb_url ?: '';
 
         // Update file record with variation
         $actual_variation_id = $variation_id ?: $product_id;
@@ -76,15 +85,45 @@ class Studiou_WC_FPP_Cart {
     }
 
     public function cart_item_thumbnail($thumbnail, $cart_item, $cart_item_key) {
-        if (!empty($cart_item['studiou_fpp_attachment_id'])) {
-            $image = wp_get_attachment_image($cart_item['studiou_fpp_attachment_id'], 'woocommerce_thumbnail');
-            if ($image) {
-                return $image;
-            }
+        $url = $this->get_cart_item_thumb_url($cart_item);
+        if ($url) {
+            $alt = isset($cart_item['studiou_fpp_file_name']) ? esc_attr($cart_item['studiou_fpp_file_name']) : '';
+            return '<img src="' . esc_url($url) . '" alt="' . $alt . '" class="attachment-woocommerce_thumbnail" />';
         }
         return $thumbnail;
     }
 
+    /**
+     * Override product/variation image_id when rendered in cart context (block cart / Store API).
+     * Returns the uploaded photo attachment_id so the block cart shows it instead of placeholder.
+     */
+    public function override_product_image_for_cart($image_id, $product) {
+        if (!WC()->cart) {
+            return $image_id;
+        }
+
+        $product_id = $product->get_id();
+
+        foreach (WC()->cart->get_cart() as $cart_item) {
+            if (empty($cart_item['studiou_fpp_attachment_id'])) {
+                continue;
+            }
+
+            $match = false;
+            if (isset($cart_item['variation_id']) && $cart_item['variation_id'] == $product_id) {
+                $match = true;
+            } elseif ($cart_item['product_id'] == $product_id) {
+                $match = true;
+            }
+
+            if ($match) {
+                return (int) $cart_item['studiou_fpp_attachment_id'];
+            }
+        }
+
+        return $image_id;
+    }
+
     public function display_cart_item_data($item_data, $cart_item) {
         if (isset($cart_item['studiou_fpp_file_name'])) {
             $item_data[] = array(
@@ -96,6 +135,37 @@ class Studiou_WC_FPP_Cart {
         return $item_data;
     }
 
+    public function show_cart_item_photo_preview($cart_item, $cart_item_key) {
+        $url = $this->get_cart_item_thumb_url($cart_item);
+        if ($url) {
+            echo '<div class="studiou-fpp-cart-preview">';
+            echo '<img src="' . esc_url($url) . '" alt="' . esc_attr($cart_item['studiou_fpp_file_name'] ?? '') . '" />';
+            echo '</div>';
+        }
+    }
+
+    private function get_cart_item_thumb_url($cart_item) {
+        // 1. Use stored URL from session (most reliable)
+        if (!empty($cart_item['studiou_fpp_thumb_url'])) {
+            return $cart_item['studiou_fpp_thumb_url'];
+        }
+
+        // 2. Try WP attachment lookup
+        if (!empty($cart_item['studiou_fpp_attachment_id'])) {
+            $att_id = (int) $cart_item['studiou_fpp_attachment_id'];
+            $thumb = wp_get_attachment_image_src($att_id, 'thumbnail');
+            if ($thumb) {
+                return $thumb[0];
+            }
+            $url = wp_get_attachment_url($att_id);
+            if ($url) {
+                return $url;
+            }
+        }
+
+        return '';
+    }
+
     public function save_order_item_meta($item, $cart_item_key, $values, $order) {
         if (isset($values['studiou_fpp_attachment_id'])) {
             $item->add_meta_data('_studiou_fpp_attachment_id', $values['studiou_fpp_attachment_id']);
@@ -107,6 +177,9 @@ class Studiou_WC_FPP_Cart {
             $item->add_meta_data('_studiou_fpp_file_name', $values['studiou_fpp_file_name']);
             $item->add_meta_data(__('Uploaded Photo', 'studiou-wc-free-photo-product'), $values['studiou_fpp_file_name']);
         }
+        if (isset($values['studiou_fpp_thumb_url'])) {
+            $item->add_meta_data('_studiou_fpp_thumb_url', $values['studiou_fpp_thumb_url']);
+        }
     }
 
     public function link_files_to_order($order_id, $posted_data, $order) {
@@ -119,12 +192,23 @@ class Studiou_WC_FPP_Cart {
     }
 
     public function admin_order_item_thumbnail($thumbnail, $item_id, $item) {
+        // Try stored URL first
+        $thumb_url = $item->get_meta('_studiou_fpp_thumb_url');
+        if ($thumb_url) {
+            return '<img src="' . esc_url($thumb_url) . '" alt="" class="wc-block-components-product-image" width="50" height="50" />';
+        }
+
+        // Fallback to attachment lookup
         $attachment_id = $item->get_meta('_studiou_fpp_attachment_id');
         if ($attachment_id) {
             $image = wp_get_attachment_image($attachment_id, 'thumbnail');
             if ($image) {
                 return $image;
             }
+            $url = wp_get_attachment_url($attachment_id);
+            if ($url) {
+                return '<img src="' . esc_url($url) . '" alt="" width="50" height="50" />';
+            }
         }
         return $thumbnail;
     }

+ 10 - 0
studiou-wc-free-photo-product/includes/class-studiou-wc-fpp-single-product.php

@@ -136,6 +136,16 @@ class Studiou_WC_FPP_Single_Product {
             WC()->session->set('studiou_fpp_attachment_id', $attachment_id);
             WC()->session->set('studiou_fpp_file_record_id', $file_record_id);
             WC()->session->set('studiou_fpp_file_name', $file_name);
+
+            // Store thumbnail URL directly so it can be used in cart without wp_get_attachment lookup
+            $thumb_url = '';
+            $thumb = wp_get_attachment_image_src($attachment_id, 'thumbnail');
+            if ($thumb) {
+                $thumb_url = $thumb[0];
+            } else {
+                $thumb_url = wp_get_attachment_url($attachment_id);
+            }
+            WC()->session->set('studiou_fpp_thumb_url', $thumb_url ?: '');
         }
 
         wp_send_json_success();

+ 1 - 1
studiou-wc-free-photo-product/languages/studiou-wc-free-photo-product-cs_CZ.po

@@ -3,7 +3,7 @@
 # This file is distributed under the GPL v2 or later.
 msgid ""
 msgstr ""
-"Project-Id-Version: QDR - Studiou WC Free Photo Product 1.1.1\n"
+"Project-Id-Version: QDR - Studiou WC Free Photo Product 1.1.5\n"
 "Report-Msgid-Bugs-To: https://www.quadarax.com\n"
 "POT-Creation-Date: 2026-04-02 00:00+0000\n"
 "PO-Revision-Date: 2026-04-02 00:00+0000\n"

+ 1 - 1
studiou-wc-free-photo-product/languages/studiou-wc-free-photo-product.pot

@@ -2,7 +2,7 @@
 # This file is distributed under the GPL v2 or later.
 msgid ""
 msgstr ""
-"Project-Id-Version: QDR - Studiou WC Free Photo Product 1.1.1\n"
+"Project-Id-Version: QDR - Studiou WC Free Photo Product 1.1.5\n"
 "Report-Msgid-Bugs-To: https://www.quadarax.com\n"
 "POT-Creation-Date: 2026-04-02 00:00+0000\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"

+ 4 - 2
studiou-wc-free-photo-product/readme.md

@@ -95,7 +95,7 @@ Navigate to **Products > Free Photo Files** to manage uploaded files.
 The plugin integrates with WooCommerce's native add-to-cart flow using WC session storage. When a photo is uploaded, the file reference is stored in the WC session via AJAX. When any add-to-cart button is clicked, `woocommerce_add_cart_item_data` reads the file data from the session and attaches it to the cart item. The session data is cleared after use. The `woocommerce_add_to_cart_validation` filter ensures a photo is uploaded before the item can be added. This approach works with any theme or plugin that handles variable product display (individual variation buttons, dropdowns, tables, etc.).
 
 When a customer adds a photo product to the cart:
-- The cart item thumbnail is replaced with the uploaded photo image
+- The cart item thumbnail shows the uploaded photo — works with both **classic cart** (`woocommerce_cart_item_thumbnail` filter) and **block cart** (`woocommerce_product_get_image_id` / `woocommerce_product_variation_get_image_id` filters that override the product image for the Store API)
 - The uploaded file name is shown as "Uploaded Photo" in the cart
 - On checkout, the file record is linked to the order and order item
 - In the order admin, order item thumbnails show the uploaded photo instead of the product image
@@ -209,7 +209,9 @@ studiou-wc-free-photo-product/
 | `woocommerce_single_product_summary` (priority 35) | Action | Renders upload area below the variation table |
 | `woocommerce_add_to_cart_validation` | Filter | Validates photo is uploaded |
 | `woocommerce_add_cart_item_data` | Filter | Captures file data into cart item |
-| `woocommerce_cart_item_thumbnail` | Filter | Replaces cart item thumbnail with uploaded photo |
+| `woocommerce_cart_item_thumbnail` | Filter | Replaces cart item thumbnail (classic cart) |
+| `woocommerce_product_get_image_id` | Filter | Returns uploaded photo as product image (block cart) |
+| `woocommerce_product_variation_get_image_id` | Filter | Returns uploaded photo as variation image (block cart) |
 | `woocommerce_get_item_data` | Filter | Displays file name in cart |
 | `woocommerce_checkout_create_order_line_item` | Action | Saves file meta to order item |
 | `woocommerce_checkout_order_processed` | Action | Links file record to order |

+ 2 - 2
studiou-wc-free-photo-product/studiou-wc-free-photo-product.php

@@ -3,7 +3,7 @@
  * Plugin Name: QDR - Studiou WC Free Photo Product
  * Plugin URI: https://www.quadarax.com/plugins/studiou-wc-free-photo-product
  * Description: Allows publishing special WooCommerce products with variants and custom raw image upload for photo printing
- * Version: 1.1.1
+ * Version: 1.1.5
  * Requires at least: 6.9.4
  * Requires PHP: 8.2
  * Author: Dalibor Votruba
@@ -21,7 +21,7 @@ if (!defined('WPINC')) {
     die;
 }
 
-if (!defined('STUDIOU_WCFPP_VERSION')) define('STUDIOU_WCFPP_VERSION', '1.1.1');
+if (!defined('STUDIOU_WCFPP_VERSION')) define('STUDIOU_WCFPP_VERSION', '1.1.5');
 if (!defined('STUDIOU_WCFPP_PLUGIN_DIR')) define('STUDIOU_WCFPP_PLUGIN_DIR', plugin_dir_path(__FILE__));
 if (!defined('STUDIOU_WCFPP_PLUGIN_URL')) define('STUDIOU_WCFPP_PLUGIN_URL', plugin_dir_url(__FILE__));
 if (!defined('STUDIOU_WCFPP_PLUGIN_BASENAME')) define('STUDIOU_WCFPP_PLUGIN_BASENAME', plugin_basename(__FILE__));