Просмотр исходного кода

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 3 месяцев назад
Родитель
Сommit
d117e841ec

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

@@ -2,7 +2,7 @@
 
 
 ## Project Overview
 ## 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.
 Allows publishing special variable products where customers upload custom raw images to be printed for a price.
 
 
 ## Initial Requirements
 ## 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_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_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_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
 - `Studiou_WC_FPP_Admin` - Summary page, status management, ZIP download
 
 
 ## Frontend Flow
 ## 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
 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
 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)
 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`
 11. On checkout, file record is linked to order via `woocommerce_checkout_order_processed`
 12. Session only cleared when user clicks Remove
 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;
     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 */
 /* Messages */
 .studiou-fpp-msg {
 .studiou-fpp-msg {
     padding: 10px 15px;
     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
         // Display uploaded file info in cart
         add_filter('woocommerce_get_item_data', array($this, 'display_cart_item_data'), 10, 2);
         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);
         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
         // Save file reference to order item
         add_action('woocommerce_checkout_create_order_line_item', array($this, 'save_order_item_meta'), 10, 4);
         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');
         $attachment_id = WC()->session->get('studiou_fpp_attachment_id');
         $file_record_id = WC()->session->get('studiou_fpp_file_record_id');
         $file_record_id = WC()->session->get('studiou_fpp_file_record_id');
         $file_name = WC()->session->get('studiou_fpp_file_name');
         $file_name = WC()->session->get('studiou_fpp_file_name');
+        $thumb_url = WC()->session->get('studiou_fpp_thumb_url');
 
 
         if (!$attachment_id || !$file_record_id) {
         if (!$attachment_id || !$file_record_id) {
             return $cart_item_data;
             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_attachment_id'] = $attachment_id;
         $cart_item_data['studiou_fpp_file_record_id'] = $file_record_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_file_name'] = $file_name ?: $record->file_name;
+        $cart_item_data['studiou_fpp_thumb_url'] = $thumb_url ?: '';
 
 
         // Update file record with variation
         // Update file record with variation
         $actual_variation_id = $variation_id ?: $product_id;
         $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) {
     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;
         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) {
     public function display_cart_item_data($item_data, $cart_item) {
         if (isset($cart_item['studiou_fpp_file_name'])) {
         if (isset($cart_item['studiou_fpp_file_name'])) {
             $item_data[] = array(
             $item_data[] = array(
@@ -96,6 +135,37 @@ class Studiou_WC_FPP_Cart {
         return $item_data;
         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) {
     public function save_order_item_meta($item, $cart_item_key, $values, $order) {
         if (isset($values['studiou_fpp_attachment_id'])) {
         if (isset($values['studiou_fpp_attachment_id'])) {
             $item->add_meta_data('_studiou_fpp_attachment_id', $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('_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']);
             $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) {
     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) {
     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');
         $attachment_id = $item->get_meta('_studiou_fpp_attachment_id');
         if ($attachment_id) {
         if ($attachment_id) {
             $image = wp_get_attachment_image($attachment_id, 'thumbnail');
             $image = wp_get_attachment_image($attachment_id, 'thumbnail');
             if ($image) {
             if ($image) {
                 return $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;
         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_attachment_id', $attachment_id);
             WC()->session->set('studiou_fpp_file_record_id', $file_record_id);
             WC()->session->set('studiou_fpp_file_record_id', $file_record_id);
             WC()->session->set('studiou_fpp_file_name', $file_name);
             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();
         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.
 # This file is distributed under the GPL v2 or later.
 msgid ""
 msgid ""
 msgstr ""
 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"
 "Report-Msgid-Bugs-To: https://www.quadarax.com\n"
 "POT-Creation-Date: 2026-04-02 00:00+0000\n"
 "POT-Creation-Date: 2026-04-02 00:00+0000\n"
 "PO-Revision-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.
 # This file is distributed under the GPL v2 or later.
 msgid ""
 msgid ""
 msgstr ""
 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"
 "Report-Msgid-Bugs-To: https://www.quadarax.com\n"
 "POT-Creation-Date: 2026-04-02 00:00+0000\n"
 "POT-Creation-Date: 2026-04-02 00:00+0000\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\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.).
 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:
 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
 - 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
 - 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
 - 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_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_to_cart_validation` | Filter | Validates photo is uploaded |
 | `woocommerce_add_cart_item_data` | Filter | Captures file data into cart item |
 | `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_get_item_data` | Filter | Displays file name in cart |
 | `woocommerce_checkout_create_order_line_item` | Action | Saves file meta to order item |
 | `woocommerce_checkout_create_order_line_item` | Action | Saves file meta to order item |
 | `woocommerce_checkout_order_processed` | Action | Links file record to order |
 | `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 Name: QDR - Studiou WC Free Photo Product
  * Plugin URI: https://www.quadarax.com/plugins/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
  * 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 at least: 6.9.4
  * Requires PHP: 8.2
  * Requires PHP: 8.2
  * Author: Dalibor Votruba
  * Author: Dalibor Votruba
@@ -21,7 +21,7 @@ if (!defined('WPINC')) {
     die;
     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_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_URL')) define('STUDIOU_WCFPP_PLUGIN_URL', plugin_dir_url(__FILE__));
 if (!defined('STUDIOU_WCFPP_PLUGIN_BASENAME')) define('STUDIOU_WCFPP_PLUGIN_BASENAME', plugin_basename(__FILE__));
 if (!defined('STUDIOU_WCFPP_PLUGIN_BASENAME')) define('STUDIOU_WCFPP_PLUGIN_BASENAME', plugin_basename(__FILE__));