# QDR - Studiou WC Free Photo Product WordPress (6.9.4+) / WooCommerce (10.6.2+) plugin that allows publishing special variable products with custom raw image upload for photo printing. Customers select a product variant using WooCommerce's native variation selector and upload their photo file, which gets stored in the WordPress media library under a configurable media category. ## Requirements - WordPress 6.9.4+ - WooCommerce 10.0+ - PHP 8.2+ - PHP ZipArchive extension (for admin ZIP download) ## Installation 1. Upload the `studiou-wc-free-photo-product` folder to `/wp-content/plugins/` 2. Activate the plugin through the Plugins menu in WordPress 3. The plugin creates a custom database table `{prefix}studiou_fpp_files` on activation ## Configuration ### Creating a Free Photo Product 1. Create or edit a WooCommerce product and set its type to **Variable** 2. Add variations with attributes (e.g. size: 10x15, 20x30, A4) and prices 3. Go to the **Free Photo** tab in the product data panel (visible for Variable products) 4. Check **Enable Free Photo** 5. Select or create a **Media Category** - uploaded files will be assigned to this category 6. Set **Max File Size (MB)** - maximum allowed upload size per file (1-500 MB, default 50) 7. Optionally set **Min Image Resolution (px)** and **Max Image Resolution (px)** - width and height are interchangeable (portrait/landscape auto-detected). Leave empty to skip validation. RAW files that cannot be measured are skipped. 8. Copy the displayed **Shortcode** for use on custom pages ### Product Detail Page When a variable product has Free Photo enabled, the upload area automatically appears on the WooCommerce single product page **below** the product summary (below the variation table and Add to Cart buttons). The customer workflow is: 1. Upload a photo file via drag-and-drop or file browser (below the variation table) 2. The product gallery image updates to show the uploaded photo preview 3. Select a variant from the product variation table and click its **Add to Cart** button 4. If no photo is uploaded, the server blocks the add-to-cart and shows an error notice 5. The uploaded photo stays — the same photo can be used for multiple variants without re-uploading The uploaded file reference is stored in the WooCommerce session, so it works with any add-to-cart method (form POST, AJAX buttons, individual variation buttons). The session persists across add-to-cart actions, allowing the same photo to be used for multiple variants. The session is only cleared when the user explicitly removes the uploaded photo. Add-to-cart is blocked server-side until a photo is uploaded: - **Validation**: `woocommerce_add_to_cart_validation` (priority 1) checks the WC session for uploaded file data and blocks with an error notice if missing. - **Safety net**: `woocommerce_add_to_cart` action removes any FPP cart item that was added without a photo (catches edge cases where validation was bypassed). - Product detection uses `resolve_fpp_product_id()` which checks the product itself, its parent, and the variation's parent to handle all add-to-cart methods. On page reload, existing session state is restored — the upload preview and product gallery image are reconstructed from the server-side session data. When the photo is removed, the original product image is restored. Add-to-cart buttons always look and behave normally — blocking is entirely server-side. ### Shortcode The shortcode can be used to embed the product with its variation selector and upload area on any page: ``` [studiou_free_photo_product id="123"] ``` Replace `123` with your product ID. The shortcode renders: - Product image, name, and short description - WooCommerce native variation selector and Add to Cart form - Upload area with drag-and-drop, progress bar, and preview (injected into the form) ### Accepted File Formats JPEG, PNG, TIFF, BMP, PSD, WebP, and camera RAW formats: CR2 (Canon), NEF (Nikon), ARW (Sony), DNG (Adobe), ORF (Olympus), RW2 (Panasonic), RAF (Fuji). ## Chunked Upload Files are uploaded in 1 MB chunks via AJAX to bypass PHP `upload_max_filesize` and `post_max_size` limits. The server assembles chunks into the final file, creates a WordPress media attachment, and assigns it to the configured media category. The chunk size is defined by the `STUDIOU_WCFPP_CHUNK_SIZE` constant. ## Admin Summary Page Navigate to **Products > Free Photo Files** to manage uploaded files. ### Features - Filter by status (Ready / Downloaded / Solved), product, or file name search - Inline status change per file via dropdown - Single file download (automatically marks status as "downloaded" if currently "ready") - Single file delete with media attachment cleanup - Bulk actions with confirmation dialogs: Download as ZIP, Set Status (Ready / Downloaded / Solved), Delete - Order number links (HPOS-compatible) - Customer name display - Pagination (30 files per page, preserves active filters across pages) ### File Statuses | Status | Meaning | |---|---| | Ready | File uploaded, awaiting processing | | Downloaded | File has been downloaded by admin (set automatically on download) | | Solved | File has been printed / order fulfilled | ## Cart and Order Integration 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 persists so the same photo can be reused for multiple variants. 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 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 - The uploaded photo file name appears as order item metadata - A **Download uploaded photo** button with the original file name is shown below the order item meta in admin (downloading automatically marks the file status as "downloaded") - Files linked to orders cannot be removed by the customer ## Media Categories The plugin registers a custom taxonomy `studiou_media_category` on the `attachment` post type. Categories are visible in the Media Library admin column and can be managed from: - **Free Photo** product tab (inline add via "+" button) - Media Library sidebar Each product has one assigned media category. All files uploaded for that product are tagged with it. ## HPOS Support The plugin declares full compatibility with WooCommerce High-Performance Order Storage (HPOS / Custom Order Tables). Order edit links in the admin summary page use the correct URL format based on whether HPOS is enabled. ## Localization Supported languages: - English (default) - Czech (cs_CZ) Translation files are in the `languages/` directory. To compile the `.mo` file from `.po`: ```bash # Using the included helper script php languages/generate-mo.php # Or using WP-CLI wp i18n make-mo languages/ # Or using gettext msgfmt languages/studiou-wc-free-photo-product-cs_CZ.po -o languages/studiou-wc-free-photo-product-cs_CZ.mo ``` Text domain: `studiou-wc-free-photo-product` ## Database The plugin creates one custom table on activation: **`{prefix}studiou_fpp_files`** | Column | Type | Description | |---|---|---| | id | bigint | Primary key | | product_id | bigint | WC product ID | | variation_id | bigint | WC variation ID | | order_id | bigint | WC order ID (set on checkout) | | order_item_id | bigint | WC order item ID (set on checkout) | | attachment_id | bigint | WP media attachment ID | | customer_id | bigint | WP user ID (0 for guests) | | session_key | varchar(64) | WC session key for guest users | | file_name | varchar(255) | Original uploaded file name | | status | varchar(20) | ready / downloaded / solved | | created_at | datetime | Upload timestamp | | updated_at | datetime | Last update timestamp | ## File Structure ``` studiou-wc-free-photo-product/ ├── studiou-wc-free-photo-product.php Main plugin file ├── includes/ │ ├── class-studiou-wc-fpp-db.php Database + media taxonomy │ ├── class-studiou-wc-fpp-product.php Product data tab │ ├── class-studiou-wc-fpp-upload.php Chunked upload handler │ ├── class-studiou-wc-fpp-shortcode.php Shortcode renderer │ ├── class-studiou-wc-fpp-single-product.php Product page upload area │ ├── class-studiou-wc-fpp-cart.php Cart/order integration (native WC flow) │ └── class-studiou-wc-fpp-admin.php Admin summary page ├── views/ │ ├── frontend-product.php Shortcode template │ ├── single-product-upload.php Upload area for product page │ └── admin-summary.php Admin file list template ├── assets/ │ ├── css/admin.css Admin styles │ ├── css/frontend.css Frontend styles │ ├── js/admin.js Admin JS │ └── js/frontend.js Frontend JS (chunked upload) └── languages/ ├── studiou-wc-free-photo-product.pot Translation template ├── studiou-wc-free-photo-product-cs_CZ.po Czech translation └── generate-mo.php MO file generator ``` ## Hooks and Filters ### AJAX Actions | Action | Auth | Description | |---|---|---| | `studiou_wcfpp_upload_chunk` | Both | Upload a file chunk | | `studiou_wcfpp_remove_upload` | Both | Remove an uploaded file (pre-order) | | `studiou_wcfpp_set_upload_session` | Both | Store uploaded file reference in WC session | | `studiou_wcfpp_clear_upload_session` | Both | Clear uploaded file reference from WC session | | `studiou_wcfpp_add_media_category` | Admin | Create a new media category | | `studiou_wcfpp_update_status` | Admin | Update single file status | | `studiou_wcfpp_bulk_status` | Admin | Bulk update file statuses | | `studiou_wcfpp_delete_files` | Admin | Delete files (single or bulk) | | `studiou_wcfpp_download_zip` | Admin | Generate ZIP of selected files | ### WooCommerce Hooks Used | Hook | Type | Description | |---|---|---| | `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 (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 (classic checkout) | | `woocommerce_store_api_checkout_order_processed` | Action | Links file record to order (block checkout) | | `woocommerce_thankyou` | Action | Fallback linking on thank-you page | | `woocommerce_admin_order_item_thumbnail` | Filter | Replaces order item thumbnail with uploaded photo | | `woocommerce_after_order_itemmeta` | Action | Shows download link for uploaded photo in order admin | | `woocommerce_product_data_tabs` | Filter | Adds Free Photo product tab | | `woocommerce_product_data_panels` | Action | Renders Free Photo settings panel | | `woocommerce_process_product_meta` | Action | Saves Free Photo product meta | ### Product Meta Keys | Key | Description | |---|---| | `_studiou_fpp_enabled` | "yes" / "no" | | `_studiou_fpp_media_category` | Term ID of media category | | `_studiou_fpp_max_file_size` | Max upload size in MB | | `_studiou_fpp_min_width` | Min image width in px (0 = no limit) | | `_studiou_fpp_min_height` | Min image height in px (0 = no limit) | | `_studiou_fpp_max_width` | Max image width in px (0 = no limit) | | `_studiou_fpp_max_height` | Max image height in px (0 = no limit) | ### Order Item Meta Keys | Key | Description | |---|---| | `_studiou_fpp_attachment_id` | WP attachment ID | | `_studiou_fpp_file_record_id` | File record ID in custom table | | `_studiou_fpp_file_name` | Original file name | | `_studiou_fpp_thumb_url` | Thumbnail URL (for reliable display) | ## License GPL v2 or later ## Author Dalibor Votruba - [quadarax.com](https://www.quadarax.com)