Ver código fonte

Add import / export products (tested) feature

Dalibor Votruba 9 meses atrás
pai
commit
5cc6deab62

+ 174 - 11
studiou-wc-product-cat-manage/CLAUDE.md

@@ -4,9 +4,9 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
 
 ## Project Overview
 
-**QDR - Studiou WC Export/Import Product Categories** is a WordPress plugin that extends WooCommerce to provide batch export/import functionality for product categories and category manipulation tools.
+**QDR - Studiou WC Export/Import Product Categories** is a WordPress plugin that extends WooCommerce to provide batch export/import functionality for both product categories and products, along with category manipulation tools.
 
-**Current Version:** 1.3.0
+**Current Version:** 1.4.0
 
 ## Requirements
 
@@ -54,14 +54,54 @@ The plugin follows a modular class-based architecture:
   - Uses direct database queries for reliable product detection
   - Batch processing for delete operations (10 products per batch)
 
+- **class-studiou-wc-product-manage-product-db.php** - Product database operations (NEW in v1.4)
+  - Retrieves product categories with hierarchy
+  - Gets products by category IDs
+  - Validates product SKU uniqueness
+  - Validates image URLs
+  - Manages product attributes (create if not exists)
+
+- **class-studiou-wc-product-manage-product-import.php** - Product import functionality (NEW in v1.4)
+  - **Batch processing** - Processes 5 products per batch to avoid PHP timeout (60 second limit)
+  - Parses CSV and stores in WordPress transient for batch processing
+  - Creates/updates variable products via WooCommerce API
+  - Creates/updates product variations via WooCommerce API
+  - Enforces 7 comprehensive import rules
+  - **Auto-creates missing categories** - Creates categories if they don't exist
+  - Handles product attributes, images, categories
+  - Image download with 30-second timeout, intermediate sizes disabled
+  - Generates failed products CSV for retry
+  - Supports Min/Max quantity metadata
+  - Comprehensive error logging (always enabled, prefix: "STUDIOU WC IMPORT:")
+  - Logs only skipped/failed items (not successful ones)
+
+- **class-studiou-wc-product-manage-product-export.php** - Product export functionality (NEW in v1.4)
+  - Exports products by category selection
+  - Includes variable products and variations
+  - Generates CSV with 18 columns of product data
+  - **Exports full category paths** for subcategories (format: "Parent > Child")
+  - Handles product attributes, images, pricing
+  - Creates downloadable export files
+
 ### Views (views/)
 
-- **import.php** - Import page UI
-- **export.php** - Export page UI
+- **import.php** - Category import page UI
+- **export.php** - Category export page UI
 - **manipulations.php** - Category manipulations page UI with three operations:
   - Move products between categories (two dropdowns + "Do" button)
   - Batch description between categories (checkbox list + textarea + "Do" button)
   - Delete products in category (checkbox list + "Delete" button + progress bar)
+- **product-import.php** - Product import page UI (NEW in v1.4)
+  - File upload for CSV import
+  - Progress bar for import operations
+  - Import results with statistics
+  - Failed products download button
+  - Import rules documentation
+- **product-export.php** - Product export page UI (NEW in v1.4)
+  - Category hierarchy selection with checkboxes
+  - Select All/Deselect All buttons
+  - Export results with download link
+  - Export information documentation
 
 ### Assets
 
@@ -70,10 +110,13 @@ The plugin follows a modular class-based architecture:
   - Warning message styling (yellow background for destructive operations)
   - Danger button styling (red for delete operations)
 - **assets/js/admin.js** - Admin JavaScript including:
-  - AJAX handlers for all operations
+  - AJAX handlers for all category operations
+  - AJAX handlers for product import/export operations (NEW in v1.4)
   - Batch processing logic for delete operations
   - Real-time progress bar updates
   - Confirmation dialogs for destructive operations
+  - Failed products CSV download handler
+  - Select All/Deselect All handlers for category selection
 
 ### Translations
 
@@ -81,7 +124,9 @@ The plugin follows a modular class-based architecture:
 - Text domain: `studiou-wc-product-cat-manage`
 - All user-facing strings must be wrapped in translation functions: `__()`, `_e()`, `sprintf()`
 
-## CSV Format
+## CSV Formats
+
+### Product Category CSV Format
 
 CSV files use comma separators, double-quote string isolation, UTF-8 encoding:
 
@@ -96,6 +141,31 @@ CSV files use comma separators, double-quote string isolation, UTF-8 encoding:
 | protected-passwords | Pipe-separated passwords (|) if visibility='protected' |
 | operation | 'A' (add), 'U' (update), 'D' (delete) |
 
+### Product CSV Format (NEW in v1.4)
+
+CSV files use comma separators, double-quote string isolation, UTF-8 encoding:
+
+| Column | Description |
+|--------|-------------|
+| ID | Product ID (0 for new products) |
+| Typ | Product type (only "variable" or "variation" allowed) |
+| Katalogové číslo | SKU (must be unique) |
+| Jméno | Product name |
+| Nadřazené | Parent product SKU (for variations only) |
+| Krátký popis | Short description |
+| Popis | Description |
+| Název 1 vlastnosti | Attribute 1 name |
+| Hodnota(y) 1 vlastnosti | Attribute 1 value(s) (pipe-separated) |
+| Kategorie | Categories (pipe-separated) |
+| Obrázky | Image URL (must be valid) |
+| Běžná cena | Regular price (required for variations) |
+| Vlastnost 1 viditelnost | Attribute 1 visibility (1 or 0) |
+| Vlastnost 1 globální | Attribute 1 global (1 or 0) |
+| Vlastnost 1 varianta | Attribute 1 used for variations (1 or 0) |
+| Povolit zákaznické recenze? | Enable reviews (1 or 0) |
+| Minimum Quantity | Minimum order quantity |
+| Maximum Quantity | Maximum order quantity |
+
 ## Development Guidelines
 
 ### When Modifying Code
@@ -128,6 +198,8 @@ All pages are added under **Products** menu:
   - Move products between categories
   - Batch description between categories
   - Delete products in category
+- **Product Import (slug: `studiou-product-import`)** - NEW in v1.4
+- **Product Export (slug: `studiou-product-export`)** - NEW in v1.4
 
 ### Key Integration Points
 
@@ -138,9 +210,17 @@ All pages are added under **Products** menu:
   - Automatically handles variable products by deleting all variations first
   - Force delete (true parameter) ensures permanent removal bypassing trash
   - Processes in batches to prevent PHP timeouts and memory issues
+- **Product Import/Export (NEW in v1.4)**: All operations performed via WooCommerce API
+  - Uses `WC_Product_Variable` and `WC_Product_Variation` classes
+  - Uses `WC_Product_Attribute` for attribute management
+  - Image uploads via `media_handle_sideload()`
+  - SKU validation via `wc_get_product_id_by_sku()`
+  - Attribute taxonomy creation via `wc_create_attribute()`
+  - 7 comprehensive import rules enforced
 
 ## Testing Notes
 
+### Category Operations
 - Test import/export with categories that have special characters, quotes, commas
 - Test category hierarchy (parent/child relationships)
 - Test moving products between categories when products have multiple category assignments
@@ -151,23 +231,106 @@ All pages are added under **Products** menu:
   - Products in multiple categories
   - Large numbers of products (test batch processing)
   - Edge cases: empty categories, categories with only variations
-- Verify translations are properly loaded
-- Verify progress bar updates correctly during delete operations
+
+### Product Import/Export Operations (NEW in v1.4)
+- Test product import with:
+  - Variable products with multiple attributes
+  - Product variations with valid parent references
+  - Invalid SKUs (duplicate, empty)
+  - Invalid image URLs (404, malformed)
+  - Invalid parent references
+  - Missing required fields (price for variations)
+  - Special characters in product names and descriptions
+  - Large CSV files (test progress bar)
+  - Failed products download and re-import
+- Test product export with:
+  - Multiple category selections
+  - Category hierarchy display
+  - Empty categories
+  - Categories with only variable products
+  - Categories with only variations
+  - Select All/Deselect All functionality
+- Test all 7 import rules:
+  1. WooCommerce API usage (no direct SQL)
+  2. Create vs. Update based on ID
+  3. Type validation (only variable/variation)
+  4. SKU uniqueness
+  5. Image URL validation
+  6. Parent reference validation
+  7. Variation price requirement
+
+### General
+- Verify translations are properly loaded (English/Czech)
+- Verify progress bars update correctly
 - Verify confirmation dialogs appear for destructive operations
+- Test with WP_DEBUG enabled to catch errors
 
 ## AJAX Handlers
 
 The plugin registers the following AJAX actions:
 
-1. **studiou_wcpcm_import** - Handles CSV import
-2. **studiou_wcpcm_export** - Generates CSV export
+### Category Operations
+1. **studiou_wcpcm_import** - Handles category CSV import
+2. **studiou_wcpcm_export** - Generates category CSV export
 3. **studiou_wcpcm_move_products** - Moves products between categories
 4. **studiou_wcpcm_batch_description** - Updates descriptions for multiple categories
 5. **studiou_wcpcm_delete_products** - Initial request to get product IDs for deletion
 6. **studiou_wcpcm_delete_products_batch** - Processes product deletion in batches
 
+### Product Operations (NEW in v1.4)
+7. **studiou_wcpcm_product_import_parse** - Parses CSV file and stores in transient (Step 1)
+8. **studiou_wcpcm_product_import_batch** - Processes batch of 5 products (Step 2, called recursively)
+9. **studiou_wcpcm_product_export** - Generates product CSV export by category selection
+
 All AJAX handlers include:
 - Nonce verification (`studiou-wcpcm-nonce`)
 - Permission checks (`manage_woocommerce` capability)
 - Output buffer cleaning to prevent JSON corruption
-- Comprehensive error logging when WP_DEBUG is enabled
+- Comprehensive error logging (always enabled for product import)
+
+## Product Import Rules (v1.4)
+
+The product import functionality enforces 7 comprehensive rules:
+
+1. **Rule 1**: All operations performed via WooCommerce API (not direct SQL)
+2. **Rule 2**: If ID = 0, item will be created; otherwise updated
+3. **Rule 3**: Only "variable" or "variation" types are allowed
+4. **Rule 4**: SKU (Katalogové číslo) must be unique in WooCommerce database
+5. **Rule 5**: Image URL must be valid format (actual accessibility checked during download)
+6. **Rule 6**: Parent reference (Nadřazené) must point to an existing variable product SKU
+7. **Rule 7**: Variations must have a regular price (Běžná cena)
+
+## Product Import Technical Details (v1.4)
+
+### Batch Processing Architecture
+To avoid PHP timeout issues (typically 60 seconds on production servers):
+
+1. **Parse Phase**: CSV file is read entirely and stored in WordPress transient (expires in 1 hour)
+2. **Batch Phase**: Products are processed in batches of 5 via recursive AJAX calls
+3. **Progress Tracking**: Real-time progress bar shows "Processing: X / Y products"
+4. **Result Accumulation**: Success/failed/skipped counts are accumulated across batches
+
+### Category Handling
+- **Simple name lookup only** - Categories have unique names throughout the system
+- **No hierarchy detection** - Dashes and other characters are part of category names, not separators
+- **Auto-create**: If category doesn't exist, it's automatically created during import
+- **Lookup methods**: Tries exact name match, slug match, then case-insensitive search
+
+### Image Processing
+- **30-second timeout** per image download
+- **Intermediate sizes disabled** during import for speed
+- **Graceful failure**: Failed image downloads don't stop import, just logged
+
+### Error Logging
+- **Always enabled** (no WP_DEBUG required)
+- **Prefix**: "STUDIOU WC IMPORT:" for easy filtering
+- **Only logs failures**: Success messages not logged to reduce noise
+- **Detailed debugging**: Shows category lookup attempts, creation, and assignment
+
+Items that fail validation are:
+- Skipped during import
+- Logged with SKIPPED or FAILED prefix
+- Added to failed items list
+- Available for download via "Save Failed Products" button
+- Can be corrected and re-imported
+- new Rule is that product category has unique name through whole product categories

+ 322 - 0
studiou-wc-product-cat-manage/assets/js/admin.js

@@ -46,6 +46,18 @@
         if ($('#studiou-wcpcm-download-sample').length) {
             initSampleDownload();
         }
+
+        // Product import form handling
+        if ($('#studiou-wcpcm-product-import-form').length) {
+            console.log('STUDIOU WC: Product import form found');
+            initProductImportForm();
+        }
+
+        // Product export form handling
+        if ($('#studiou-wcpcm-product-export-form').length) {
+            console.log('STUDIOU WC: Product export form found');
+            initProductExportForm();
+        }
     });
     
     /**
@@ -627,6 +639,314 @@
         }, 500);
     }
 
+    /**
+     * Initialize product import form
+     */
+    function initProductImportForm() {
+        console.log('STUDIOU WC: Initializing product import form handlers');
+
+        // Global variables for batch processing
+        var importTransientKey = null;
+        var importTotal = 0;
+        var importProcessed = 0;
+        var importSuccess = 0;
+        var importFailed = 0;
+        var importSkipped = 0;
+        var importMessages = [];
+        var importFailedCsv = '';
+
+        $('#studiou-wcpcm-product-import-form').on('submit', function(e) {
+            e.preventDefault();
+
+            console.log('STUDIOU WC: Product import form submitted');
+
+            // Validate file is selected
+            var fileInput = $('#import_file')[0];
+            if (!fileInput.files || !fileInput.files[0]) {
+                showNotice('error', 'Please select a CSV file to import');
+                return;
+            }
+
+            // Show spinner and progress
+            var $submitBtn = $(this).find('#studiou-wcpcm-product-import-button');
+            var $spinner = $(this).find('.spinner');
+
+            $submitBtn.prop('disabled', true);
+            $spinner.css('visibility', 'visible');
+
+            // Clear previous results
+            $('.studiou-wcpcm-notice-area').empty();
+            $('.studiou-wcpcm-product-import-results').hide();
+            $('.studiou-wcpcm-product-import-failed').hide();
+
+            // Reset counters
+            importTransientKey = null;
+            importTotal = 0;
+            importProcessed = 0;
+            importSuccess = 0;
+            importFailed = 0;
+            importSkipped = 0;
+            importMessages = [];
+            importFailedCsv = '';
+
+            // Show progress bar
+            $('.studiou-wcpcm-product-import-progress').show();
+            $('#studiou-wcpcm-product-import-progress-text').text('Parsing CSV file...');
+            updateProductImportProgressBar(0);
+
+            // Step 1: Parse CSV file
+            var formData = new FormData(this);
+            formData.append('action', 'studiou_wcpcm_product_import_parse');
+            formData.append('nonce', studiouWcpcm.nonce);
+
+            $.ajax({
+                url: studiouWcpcm.ajaxUrl,
+                type: 'POST',
+                data: formData,
+                processData: false,
+                contentType: false,
+                success: function(response) {
+                    console.log('STUDIOU WC: Product import parse response:', response);
+
+                    if (response.success) {
+                        importTransientKey = response.data.transient_key;
+                        importTotal = response.data.total;
+
+                        // Start batch processing
+                        processBatch(0, $submitBtn, $spinner);
+                    } else {
+                        showNotice('error', response.data.message || 'Parse failed');
+                        $('.studiou-wcpcm-product-import-progress').hide();
+                        $submitBtn.prop('disabled', false);
+                        $spinner.css('visibility', 'hidden');
+                    }
+                },
+                error: function(xhr, status, error) {
+                    console.error('STUDIOU WC: Product import parse error:', status, error);
+                    console.error('STUDIOU WC: Response text:', xhr.responseText);
+                    showNotice('error', 'Product import error: ' + error);
+                    $('.studiou-wcpcm-product-import-progress').hide();
+                    $submitBtn.prop('disabled', false);
+                    $spinner.css('visibility', 'hidden');
+                }
+            });
+        });
+
+        // Process batch of products
+        function processBatch(offset, $submitBtn, $spinner) {
+            console.log('STUDIOU WC: Processing batch at offset', offset);
+
+            $.ajax({
+                url: studiouWcpcm.ajaxUrl,
+                type: 'POST',
+                data: {
+                    action: 'studiou_wcpcm_product_import_batch',
+                    nonce: studiouWcpcm.nonce,
+                    transient_key: importTransientKey,
+                    offset: offset
+                },
+                success: function(response) {
+                    console.log('STUDIOU WC: Batch response:', response);
+
+                    if (response.success) {
+                        // Accumulate results
+                        importSuccess += response.data.success;
+                        importFailed += response.data.failed;
+                        importSkipped += response.data.skipped;
+                        importProcessed = response.data.processed;
+                        importTotal = response.data.total;
+                        importMessages = importMessages.concat(response.data.messages);
+
+                        if (response.data.failed_csv) {
+                            importFailedCsv = response.data.failed_csv;
+                        }
+
+                        // Update progress
+                        var percent = (importProcessed / importTotal) * 100;
+                        updateProductImportProgressBar(percent);
+                        $('#studiou-wcpcm-product-import-progress-text').text(
+                            'Processing: ' + importProcessed + ' / ' + importTotal + ' products'
+                        );
+
+                        // Check if complete
+                        if (importProcessed >= importTotal) {
+                            // Import complete
+                            $('#studiou-wcpcm-product-import-progress-text').text('Complete!');
+                            showNotice('success', 'Product import completed!');
+
+                            // Show results
+                            var message = '<p><strong>Import Summary:</strong></p>';
+                            message += '<ul>';
+                            message += '<li>Successful: ' + importSuccess + '</li>';
+                            message += '<li>Failed: ' + importFailed + '</li>';
+                            message += '<li>Skipped: ' + importSkipped + '</li>';
+                            message += '</ul>';
+
+                            $('#studiou-wcpcm-product-import-message').html(message);
+
+                            // Show detailed messages
+                            if (importMessages.length > 0) {
+                                var details = '<pre>' + importMessages.join('\n') + '</pre>';
+                                $('#studiou-wcpcm-product-import-details-content').html(details);
+                            }
+
+                            $('.studiou-wcpcm-product-import-results').show();
+
+                            // Show save failed button if there are failed items
+                            if (importFailed > 0 && importFailedCsv) {
+                                window.studiouWcpcmFailedCsv = importFailedCsv;
+                                $('.studiou-wcpcm-product-import-failed').show();
+                            }
+
+                            // Hide progress after delay
+                            setTimeout(function() {
+                                $('.studiou-wcpcm-product-import-progress').fadeOut();
+                            }, 2000);
+
+                            $submitBtn.prop('disabled', false);
+                            $spinner.css('visibility', 'hidden');
+                        } else {
+                            // Process next batch
+                            processBatch(importProcessed, $submitBtn, $spinner);
+                        }
+                    } else {
+                        showNotice('error', response.data.message || 'Batch processing failed');
+                        $('.studiou-wcpcm-product-import-progress').hide();
+                        $submitBtn.prop('disabled', false);
+                        $spinner.css('visibility', 'hidden');
+                    }
+                },
+                error: function(xhr, status, error) {
+                    console.error('STUDIOU WC: Batch processing error:', status, error);
+                    showNotice('error', 'Batch processing error: ' + error);
+                    $('.studiou-wcpcm-product-import-progress').hide();
+                    $submitBtn.prop('disabled', false);
+                    $spinner.css('visibility', 'hidden');
+                }
+            });
+        }
+
+        // Save failed products button handler
+        $('#studiou-wcpcm-save-failed-products').on('click', function(e) {
+            e.preventDefault();
+
+            if (window.studiouWcpcmFailedCsv) {
+                var blob = new Blob([window.studiouWcpcmFailedCsv], { type: 'text/csv;charset=utf-8;' });
+                var url = URL.createObjectURL(blob);
+                var link = document.createElement('a');
+
+                link.setAttribute('href', url);
+                link.setAttribute('download', 'failed-products-' + Date.now() + '.csv');
+                link.style.visibility = 'hidden';
+
+                document.body.appendChild(link);
+                link.click();
+                document.body.removeChild(link);
+            }
+        });
+    }
+
+    /**
+     * Initialize product export form
+     */
+    function initProductExportForm() {
+        console.log('STUDIOU WC: Initializing product export form handlers');
+
+        // Select all button handler
+        $('#studiou-wcpcm-export-select-all').on('click', function(e) {
+            e.preventDefault();
+            console.log('STUDIOU WC: Export select all button clicked');
+            $('#export_categories_list input[type="checkbox"]').prop('checked', true);
+        });
+
+        // Deselect all button handler
+        $('#studiou-wcpcm-export-deselect-all').on('click', function(e) {
+            e.preventDefault();
+            console.log('STUDIOU WC: Export deselect all button clicked');
+            $('#export_categories_list input[type="checkbox"]').prop('checked', false);
+        });
+
+        // Form submit handler
+        $('#studiou-wcpcm-product-export-form').on('submit', function(e) {
+            e.preventDefault();
+
+            console.log('STUDIOU WC: Product export form submitted');
+
+            // Get selected categories
+            var selectedCategories = [];
+            $('#export_categories_list input[type="checkbox"]:checked').each(function() {
+                selectedCategories.push($(this).val());
+            });
+
+            console.log('STUDIOU WC: Selected categories:', selectedCategories);
+
+            // Validate input
+            if (selectedCategories.length === 0) {
+                console.log('STUDIOU WC: No categories selected');
+                showNotice('error', 'Please select at least one category');
+                return;
+            }
+
+            // Show spinner
+            var $submitBtn = $(this).find('#studiou-wcpcm-product-export-button');
+            var $spinner = $(this).find('.spinner');
+
+            $submitBtn.prop('disabled', true);
+            $spinner.css('visibility', 'visible');
+
+            // Clear previous notices
+            $('.studiou-wcpcm-notice-area').empty();
+            $('.studiou-wcpcm-product-export-results').hide();
+
+            // Debug: Log AJAX request details
+            console.log('STUDIOU WC: Sending product export AJAX request to:', studiouWcpcm.ajaxUrl);
+            console.log('STUDIOU WC: Nonce:', studiouWcpcm.nonce);
+
+            // Send AJAX request
+            $.ajax({
+                url: studiouWcpcm.ajaxUrl,
+                type: 'POST',
+                data: {
+                    action: 'studiou_wcpcm_product_export',
+                    export_categories: selectedCategories,
+                    nonce: studiouWcpcm.nonce
+                },
+                success: function(response) {
+                    console.log('STUDIOU WC: Product export AJAX response:', response);
+
+                    if (response.success) {
+                        showNotice('success', 'Export successful!');
+
+                        // Show results
+                        $('#studiou-wcpcm-product-export-message').html('<p>' + response.data.message + '</p>');
+                        $('#studiou-wcpcm-download-product-export').attr('href', response.data.download_url);
+                        $('.studiou-wcpcm-product-export-results').show();
+                    } else {
+                        showNotice('error', response.data.message || 'Export failed');
+                    }
+                },
+                error: function(xhr, status, error) {
+                    console.error('STUDIOU WC: Product export AJAX error:', status, error);
+                    console.error('STUDIOU WC: Response text:', xhr.responseText);
+                    showNotice('error', 'Product export error: ' + error);
+                },
+                complete: function() {
+                    // Hide spinner
+                    $submitBtn.prop('disabled', false);
+                    $spinner.css('visibility', 'hidden');
+                }
+            });
+        });
+    }
+
+    /**
+     * Update product import progress bar
+     */
+    function updateProductImportProgressBar(percentage) {
+        $('#studiou-wcpcm-product-import-progress-bar').css('width', percentage + '%');
+        $('#studiou-wcpcm-product-import-progress-bar').attr('data-progress', Math.round(percentage) + '%');
+    }
+
     // Additional debug: Check if elements exist after DOM load
     $(window).on('load', function() {
         console.log('STUDIOU WC: Window loaded');
@@ -637,6 +957,8 @@
         console.log('STUDIOU WC: Batch description form exists:', $('#studiou-wcpcm-batch-description-form').length > 0);
         console.log('STUDIOU WC: Batch description button exists:', $('#studiou-wcpcm-batch-description-button').length > 0);
         console.log('STUDIOU WC: Delete products form exists:', $('#studiou-wcpcm-delete-products-form').length > 0);
+        console.log('STUDIOU WC: Product import form exists:', $('#studiou-wcpcm-product-import-form').length > 0);
+        console.log('STUDIOU WC: Product export form exists:', $('#studiou-wcpcm-product-export-form').length > 0);
     });
 
 })(jQuery);

+ 239 - 0
studiou-wc-product-cat-manage/includes/class-studiou-wc-product-manage-product-db.php

@@ -0,0 +1,239 @@
+<?php
+/**
+ * Product database operations class
+ *
+ * Handles database operations related to products
+ *
+ * @since      1.4.0
+ * @package    Studiou_WC_Product_Cat_Manage
+ * @subpackage Studiou_WC_Product_Cat_Manage/includes
+ */
+
+// If this file is called directly, abort.
+if (!defined('WPINC')) {
+    die;
+}
+
+class Studiou_WC_Product_Manage_Product_DB {
+
+    /**
+     * Constructor
+     */
+    public function __construct() {
+    }
+
+    /**
+     * Get all product categories with hierarchy and product counts
+     *
+     * @return array Array of product categories with hierarchy
+     */
+    public function get_categories_with_hierarchy() {
+        $args = array(
+            'taxonomy'   => 'product_cat',
+            'orderby'    => 'name',
+            'hide_empty' => false,
+            'hierarchical' => true,
+        );
+
+        $categories = get_terms($args);
+        $formatted_categories = array();
+
+        if (!is_wp_error($categories) && !empty($categories)) {
+            // Build hierarchy
+            $formatted_categories = $this->build_category_hierarchy($categories);
+        }
+
+        return $formatted_categories;
+    }
+
+    /**
+     * Build category hierarchy with indentation
+     *
+     * @param array $categories Array of WP_Term objects
+     * @param int $parent_id Parent category ID
+     * @param string $prefix Indentation prefix
+     * @return array Formatted category hierarchy
+     */
+    private function build_category_hierarchy($categories, $parent_id = 0, $prefix = '') {
+        $result = array();
+
+        foreach ($categories as $category) {
+            if ($category->parent == $parent_id) {
+                $result[] = array(
+                    'term_id' => $category->term_id,
+                    'name' => $prefix . $category->name,
+                    'count' => $category->count
+                );
+
+                // Recursively add children with increased indentation
+                $children = $this->build_category_hierarchy($categories, $category->term_id, $prefix . '— ');
+                $result = array_merge($result, $children);
+            }
+        }
+
+        return $result;
+    }
+
+    /**
+     * Get products by category IDs
+     *
+     * @param array $category_ids Array of category IDs
+     * @return array Array of product IDs
+     */
+    public function get_products_by_categories($category_ids) {
+        if (empty($category_ids)) {
+            return array();
+        }
+
+        $args = array(
+            'post_type' => 'product',
+            'post_status' => 'any',
+            'posts_per_page' => -1,
+            'fields' => 'ids',
+            'tax_query' => array(
+                array(
+                    'taxonomy' => 'product_cat',
+                    'field' => 'term_id',
+                    'terms' => $category_ids,
+                    'operator' => 'IN'
+                )
+            )
+        );
+
+        $query = new WP_Query($args);
+        $product_ids = $query->posts;
+
+        // Also get variations
+        foreach ($product_ids as $product_id) {
+            $product = wc_get_product($product_id);
+            if ($product && $product->is_type('variable')) {
+                $variations = $product->get_children();
+                $product_ids = array_merge($product_ids, $variations);
+            }
+        }
+
+        return array_unique($product_ids);
+    }
+
+    /**
+     * Get product by SKU
+     *
+     * @param string $sku Product SKU
+     * @return WC_Product|null Product object or null if not found
+     */
+    public function get_product_by_sku($sku) {
+        $product_id = wc_get_product_id_by_sku($sku);
+        if ($product_id) {
+            return wc_get_product($product_id);
+        }
+        return null;
+    }
+
+    /**
+     * Check if SKU is unique (excluding specific product ID)
+     *
+     * @param string $sku Product SKU
+     * @param int $exclude_id Product ID to exclude from check
+     * @return bool True if unique, false otherwise
+     */
+    public function is_sku_unique($sku, $exclude_id = 0) {
+        $product_id = wc_get_product_id_by_sku($sku);
+
+        if (!$product_id) {
+            return true; // SKU doesn't exist
+        }
+
+        if ($exclude_id > 0 && $product_id == $exclude_id) {
+            return true; // SKU belongs to the product we're updating
+        }
+
+        return false; // SKU exists and belongs to another product
+    }
+
+    /**
+     * Validate image URL
+     *
+     * @param string $url Image URL
+     * @return bool True if valid, false otherwise
+     */
+    public function validate_image_url($url) {
+        if (empty($url)) {
+            return false;
+        }
+
+        // Check if URL is valid format
+        if (!filter_var($url, FILTER_VALIDATE_URL)) {
+            return false;
+        }
+
+        // Check if URL has common image extensions (optional check)
+        $valid_extensions = array('jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp');
+        $path_info = pathinfo(parse_url($url, PHP_URL_PATH));
+        $extension = isset($path_info['extension']) ? strtolower($path_info['extension']) : '';
+
+        // If URL has extension, verify it's an image extension
+        // If no extension, allow it (might be dynamic URL)
+        if (!empty($extension) && !in_array($extension, $valid_extensions)) {
+            return false;
+        }
+
+        // Don't make HTTP request during validation - let the actual download handle failures
+        // This prevents timeout issues during validation phase
+        return true;
+    }
+
+    /**
+     * Get product attribute by name
+     *
+     * @param string $attribute_name Attribute name
+     * @return int|null Attribute ID or null if not found
+     */
+    public function get_attribute_by_name($attribute_name) {
+        global $wpdb;
+
+        $attribute_id = $wpdb->get_var($wpdb->prepare(
+            "SELECT attribute_id FROM {$wpdb->prefix}woocommerce_attribute_taxonomies WHERE attribute_name = %s",
+            wc_sanitize_taxonomy_name($attribute_name)
+        ));
+
+        return $attribute_id ? intval($attribute_id) : null;
+    }
+
+    /**
+     * Create product attribute if it doesn't exist
+     *
+     * @param string $attribute_name Attribute name
+     * @return int Attribute ID
+     */
+    public function create_attribute_if_not_exists($attribute_name) {
+        $attribute_id = $this->get_attribute_by_name($attribute_name);
+
+        if ($attribute_id) {
+            return $attribute_id;
+        }
+
+        // Create attribute
+        $attribute = array(
+            'slug' => wc_sanitize_taxonomy_name($attribute_name),
+            'name' => $attribute_name,
+            'type' => 'select',
+            'order_by' => 'menu_order',
+            'has_archives' => false,
+        );
+
+        $attribute_id = wc_create_attribute($attribute);
+
+        if (is_wp_error($attribute_id)) {
+            return 0;
+        }
+
+        // Register taxonomy
+        register_taxonomy(
+            wc_attribute_taxonomy_name($attribute_name),
+            array('product'),
+            array()
+        );
+
+        return $attribute_id;
+    }
+}

+ 289 - 0
studiou-wc-product-cat-manage/includes/class-studiou-wc-product-manage-product-export.php

@@ -0,0 +1,289 @@
+<?php
+/**
+ * Product export class
+ *
+ * Handles product export to CSV files
+ *
+ * @since      1.4.0
+ * @package    Studiou_WC_Product_Cat_Manage
+ * @subpackage Studiou_WC_Product_Cat_Manage/includes
+ */
+
+// If this file is called directly, abort.
+if (!defined('WPINC')) {
+    die;
+}
+
+class Studiou_WC_Product_Manage_Product_Export {
+
+    /**
+     * @var Studiou_WC_Product_Manage_Product_DB
+     */
+    private $db;
+
+    /**
+     * Constructor
+     *
+     * @param Studiou_WC_Product_Manage_Product_DB $db Database instance
+     */
+    public function __construct($db) {
+        $this->db = $db;
+    }
+
+    /**
+     * Export products by category IDs
+     *
+     * @param array $category_ids Array of category IDs
+     * @return string CSV content
+     */
+    public function export_products($category_ids) {
+        $product_ids = $this->db->get_products_by_categories($category_ids);
+
+        if (empty($product_ids)) {
+            return '';
+        }
+
+        // Define CSV header
+        $header = array(
+            'ID', 'Typ', 'Katalogové číslo', 'Jméno', 'Nadřazené', 'Krátký popis', 'Popis',
+            'Název 1 vlastnosti', 'Hodnota(y) 1 vlastnosti', 'Kategorie', 'Obrázky', 'Běžná cena',
+            'Vlastnost 1 viditelnost', 'Vlastnost 1 globální', 'Vlastnost 1 varianta',
+            'Povolit zákaznické recenze?', 'Minimum Quantity', 'Maximum Quantity'
+        );
+
+        // Start CSV output
+        $csv = '';
+        $csv .= '"' . implode('","', $header) . '"' . "\n";
+
+        // Process each product
+        foreach ($product_ids as $product_id) {
+            $product = wc_get_product($product_id);
+            if (!$product) {
+                continue;
+            }
+
+            // Only export variable products and variations
+            if (!$product->is_type('variable') && !$product->is_type('variation')) {
+                continue;
+            }
+
+            $csv .= $this->format_product_row($product);
+        }
+
+        return $csv;
+    }
+
+    /**
+     * Format product data as CSV row
+     *
+     * @param WC_Product $product Product object
+     * @return string CSV row
+     */
+    private function format_product_row($product) {
+        $row = array();
+
+        // ID
+        $row[] = $product->get_id();
+
+        // Typ (Type)
+        $row[] = $product->is_type('variation') ? 'variation' : 'variable';
+
+        // Katalogové číslo (SKU)
+        $row[] = $product->get_sku();
+
+        // Jméno (Name)
+        $row[] = $product->get_name();
+
+        // Nadřazené (Parent) - only for variations
+        if ($product->is_type('variation')) {
+            $parent_id = $product->get_parent_id();
+            if ($parent_id) {
+                $parent = wc_get_product($parent_id);
+                $row[] = $parent ? $parent->get_sku() : '';
+            } else {
+                $row[] = '';
+            }
+        } else {
+            $row[] = '';
+        }
+
+        // Krátký popis (Short description)
+        $row[] = $product->get_short_description();
+
+        // Popis (Description)
+        $row[] = $product->get_description();
+
+        // Název 1 vlastnosti (Attribute 1 name)
+        // Hodnota(y) 1 vlastnosti (Attribute 1 value(s))
+        $attributes = $product->get_attributes();
+        $attribute_name = '';
+        $attribute_values = '';
+        $attribute_visible = '0';
+        $attribute_global = '0';
+        $attribute_variation = '0';
+
+        if (!empty($attributes)) {
+            $first_attribute = reset($attributes);
+
+            if ($first_attribute instanceof WC_Product_Attribute) {
+                $attribute_name = $first_attribute->get_name();
+
+                // Remove 'pa_' prefix if present
+                if (strpos($attribute_name, 'pa_') === 0) {
+                    $attribute_name = substr($attribute_name, 3);
+                }
+
+                // Get attribute values
+                if ($product->is_type('variation')) {
+                    // For variations, get the specific value
+                    $variation_attributes = $product->get_variation_attributes();
+                    if (!empty($variation_attributes)) {
+                        $attribute_values = reset($variation_attributes);
+                    }
+                } else {
+                    // For variable products, get all options
+                    $options = $first_attribute->get_options();
+                    if (is_array($options)) {
+                        $attribute_values = implode('|', $options);
+                    }
+                }
+
+                $attribute_visible = $first_attribute->get_visible() ? '1' : '0';
+                $attribute_global = $first_attribute->is_taxonomy() ? '1' : '0';
+                $attribute_variation = $first_attribute->get_variation() ? '1' : '0';
+            }
+        }
+
+        $row[] = $attribute_name;
+        $row[] = $attribute_values;
+
+        // Kategorie (Categories) - with full hierarchy for subcategories
+        $category_ids = $product->get_category_ids();
+        $category_names = array();
+        foreach ($category_ids as $cat_id) {
+            $category_path = $this->get_category_path($cat_id);
+            if ($category_path) {
+                $category_names[] = $category_path;
+            }
+        }
+        $row[] = implode('|', $category_names);
+
+        // Obrázky (Images)
+        $image_id = $product->get_image_id();
+        $row[] = $image_id ? wp_get_attachment_url($image_id) : '';
+
+        // Běžná cena (Regular price)
+        $row[] = $product->get_regular_price();
+
+        // Vlastnost 1 viditelnost (Attribute 1 visibility)
+        $row[] = $attribute_visible;
+
+        // Vlastnost 1 globální (Attribute 1 global)
+        $row[] = $attribute_global;
+
+        // Vlastnost 1 varianta (Attribute 1 variation)
+        $row[] = $attribute_variation;
+
+        // Povolit zákaznické recenze? (Enable reviews)
+        $row[] = $product->get_reviews_allowed() ? '1' : '0';
+
+        // Minimum Quantity
+        $min_qty = $product->get_meta('_minimum_quantity');
+        $row[] = $min_qty ? $min_qty : '';
+
+        // Maximum Quantity
+        $max_qty = $product->get_meta('_maximum_quantity');
+        $row[] = $max_qty ? $max_qty : '';
+
+        // Format as CSV row
+        $formatted_row = array();
+        foreach ($row as $value) {
+            // Escape double quotes
+            $value = str_replace('"', '""', $value);
+            $formatted_row[] = '"' . $value . '"';
+        }
+
+        return implode(',', $formatted_row) . "\n";
+    }
+
+    /**
+     * Get full category path (Parent > Child format for subcategories)
+     *
+     * @param int $category_id Category term ID
+     * @return string Category path
+     */
+    private function get_category_path($category_id) {
+        $term = get_term($category_id, 'product_cat');
+
+        if (!$term || is_wp_error($term)) {
+            return '';
+        }
+
+        // Build path from child to root
+        $path = array($term->name);
+        $current_term = $term;
+
+        while ($current_term->parent != 0) {
+            $parent_term = get_term($current_term->parent, 'product_cat');
+            if (!$parent_term || is_wp_error($parent_term)) {
+                break;
+            }
+            array_unshift($path, $parent_term->name);
+            $current_term = $parent_term;
+        }
+
+        // Return full path with ">" separator if subcategory, otherwise just the name
+        return count($path) > 1 ? implode(' > ', $path) : $path[0];
+    }
+
+    /**
+     * Generate CSV file and return download URL
+     *
+     * @param array $category_ids Array of category IDs
+     * @return array Result with download URL or error message
+     */
+    public function generate_export_file($category_ids) {
+        $csv_content = $this->export_products($category_ids);
+
+        if (empty($csv_content)) {
+            return array(
+                'success' => false,
+                'message' => __('No products found in selected categories', 'studiou-wc-product-cat-manage')
+            );
+        }
+
+        // Create uploads directory if it doesn't exist
+        $upload_dir = wp_upload_dir();
+        $export_dir = $upload_dir['basedir'] . '/studiou-wc-product-exports';
+
+        if (!file_exists($export_dir)) {
+            wp_mkdir_p($export_dir);
+        }
+
+        // Generate filename with timestamp
+        $filename = 'product-export-' . date('Y-m-d-H-i-s') . '.csv';
+        $file_path = $export_dir . '/' . $filename;
+
+        // Write CSV content to file
+        $result = file_put_contents($file_path, $csv_content);
+
+        if ($result === false) {
+            return array(
+                'success' => false,
+                'message' => __('Failed to create export file', 'studiou-wc-product-cat-manage')
+            );
+        }
+
+        // Generate download URL
+        $download_url = $upload_dir['baseurl'] . '/studiou-wc-product-exports/' . $filename;
+
+        return array(
+            'success' => true,
+            'message' => sprintf(
+                __('Export successful! %d products exported.', 'studiou-wc-product-cat-manage'),
+                substr_count($csv_content, "\n") - 1 // Subtract header row
+            ),
+            'download_url' => $download_url
+        );
+    }
+}

+ 627 - 0
studiou-wc-product-cat-manage/includes/class-studiou-wc-product-manage-product-import.php

@@ -0,0 +1,627 @@
+<?php
+/**
+ * Product import class
+ *
+ * Handles product import from CSV files
+ *
+ * @since      1.4.0
+ * @package    Studiou_WC_Product_Cat_Manage
+ * @subpackage Studiou_WC_Product_Cat_Manage/includes
+ */
+
+// If this file is called directly, abort.
+if (!defined('WPINC')) {
+    die;
+}
+
+class Studiou_WC_Product_Manage_Product_Import {
+
+    /**
+     * @var Studiou_WC_Product_Manage_Product_DB
+     */
+    private $db;
+
+    /**
+     * Constructor
+     *
+     * @param Studiou_WC_Product_Manage_Product_DB $db Database instance
+     */
+    public function __construct($db) {
+        $this->db = $db;
+    }
+
+    /**
+     * Parse CSV file and store data for batch processing
+     *
+     * @param string $file_path Path to CSV file
+     * @return array Result with total count and transient key
+     */
+    public function parse_csv_for_batch($file_path) {
+        if (!file_exists($file_path)) {
+            return array('error' => __('File not found', 'studiou-wc-product-cat-manage'));
+        }
+
+        // Open CSV file
+        $handle = fopen($file_path, 'r');
+        if (!$handle) {
+            return array('error' => __('Could not open file', 'studiou-wc-product-cat-manage'));
+        }
+
+        // Read header row
+        $header = fgetcsv($handle, 0, ',', '"');
+        if (!$header) {
+            fclose($handle);
+            return array('error' => __('Invalid CSV format', 'studiou-wc-product-cat-manage'));
+        }
+
+        // Read all data rows
+        $rows = array();
+        $row_number = 1;
+        while (($row = fgetcsv($handle, 0, ',', '"')) !== false) {
+            $row_number++;
+
+            // Skip empty rows
+            if (empty(array_filter($row))) {
+                continue;
+            }
+
+            // Combine header with row data
+            $data = array_combine($header, $row);
+            $rows[] = array(
+                'row_number' => $row_number,
+                'data' => $data
+            );
+        }
+
+        fclose($handle);
+
+        // Store data in transient (expires in 1 hour)
+        $transient_key = 'studiou_wcpcm_import_' . uniqid();
+        set_transient($transient_key, $rows, 3600);
+
+        return array(
+            'transient_key' => $transient_key,
+            'total' => count($rows)
+        );
+    }
+
+    /**
+     * Process batch of products
+     *
+     * @param string $transient_key Transient key with CSV data
+     * @param int $offset Starting offset
+     * @param int $batch_size Number of items to process
+     * @return array Result with statistics
+     */
+    public function process_batch($transient_key, $offset = 0, $batch_size = 5) {
+        $rows = get_transient($transient_key);
+
+        if (!$rows) {
+            return array('error' => __('Import data not found or expired', 'studiou-wc-product-cat-manage'));
+        }
+
+        $result = array(
+            'success' => 0,
+            'failed' => 0,
+            'skipped' => 0,
+            'failed_items' => array(),
+            'messages' => array(),
+            'processed' => 0,
+            'total' => count($rows)
+        );
+
+        // Get batch of rows to process
+        $batch = array_slice($rows, $offset, $batch_size);
+
+        foreach ($batch as $item) {
+            $row_number = $item['row_number'];
+            $data = $item['data'];
+
+            // Process the product
+            $process_result = $this->process_product_row($data, $row_number);
+
+            if ($process_result['status'] === 'success') {
+                $result['success']++;
+                // Don't add success messages to log
+            } elseif ($process_result['status'] === 'skipped') {
+                $result['skipped']++;
+                $result['messages'][] = sprintf(
+                    __('Row %d: SKIPPED - %s', 'studiou-wc-product-cat-manage'),
+                    $row_number,
+                    $process_result['message']
+                );
+                // Add to failed items for re-processing
+                $result['failed_items'][] = $data;
+            } else {
+                $result['failed']++;
+                $result['messages'][] = sprintf(
+                    __('Row %d: FAILED - %s', 'studiou-wc-product-cat-manage'),
+                    $row_number,
+                    $process_result['message']
+                );
+                // Add to failed items
+                $result['failed_items'][] = $data;
+            }
+        }
+
+        $result['processed'] = $offset + count($batch);
+
+        return $result;
+    }
+
+    /**
+     * Process single product row
+     *
+     * @param array $data Product data
+     * @param int $row_number Row number for error reporting
+     * @return array Result with status and message
+     */
+    private function process_product_row($data, $row_number) {
+        // Validate required fields
+        if (!isset($data['Typ']) || empty($data['Typ'])) {
+            return array(
+                'status' => 'skipped',
+                'message' => __('Missing product type', 'studiou-wc-product-cat-manage')
+            );
+        }
+
+        // Rule 3: Only allow "variable" or "variation" types
+        $type = trim($data['Typ']);
+        if (!in_array($type, array('variable', 'variation'))) {
+            return array(
+                'status' => 'skipped',
+                'message' => sprintf(
+                    __('Invalid product type: %s (only "variable" or "variation" allowed)', 'studiou-wc-product-cat-manage'),
+                    $type
+                )
+            );
+        }
+
+        // Get product ID
+        $product_id = isset($data['ID']) ? intval($data['ID']) : 0;
+        $sku = isset($data['Katalogové číslo']) ? trim($data['Katalogové číslo']) : '';
+
+        // Rule 4: SKU must be unique
+        if (empty($sku)) {
+            return array(
+                'status' => 'skipped',
+                'message' => __('Missing SKU (Katalogové číslo)', 'studiou-wc-product-cat-manage')
+            );
+        }
+
+        if (!$this->db->is_sku_unique($sku, $product_id)) {
+            return array(
+                'status' => 'skipped',
+                'message' => sprintf(
+                    __('SKU "%s" already exists', 'studiou-wc-product-cat-manage'),
+                    $sku
+                )
+            );
+        }
+
+        // Rule 5: Validate image URL if provided
+        $image_url = isset($data['Obrázky']) ? trim($data['Obrázky']) : '';
+        if (!empty($image_url) && !$this->db->validate_image_url($image_url)) {
+            return array(
+                'status' => 'skipped',
+                'message' => sprintf(
+                    __('Invalid image URL: %s', 'studiou-wc-product-cat-manage'),
+                    $image_url
+                )
+            );
+        }
+
+        // Process based on product type
+        if ($type === 'variation') {
+            return $this->process_variation($data, $product_id);
+        } else {
+            return $this->process_variable_product($data, $product_id);
+        }
+    }
+
+    /**
+     * Process variable product
+     *
+     * @param array $data Product data
+     * @param int $product_id Product ID (0 for new product)
+     * @return array Result with status and message
+     */
+    private function process_variable_product($data, $product_id) {
+        try {
+            // Create or update product
+            if ($product_id == 0) {
+                $product = new WC_Product_Variable();
+            } else {
+                $product = wc_get_product($product_id);
+                if (!$product || !$product->is_type('variable')) {
+                    return array(
+                        'status' => 'error',
+                        'message' => sprintf(
+                            __('Product ID %d is not a variable product', 'studiou-wc-product-cat-manage'),
+                            $product_id
+                        )
+                    );
+                }
+            }
+
+            // Set basic data
+            $product->set_name(isset($data['Jméno']) ? $data['Jméno'] : '');
+            $product->set_sku(isset($data['Katalogové číslo']) ? $data['Katalogové číslo'] : '');
+            $product->set_short_description(isset($data['Krátký popis']) ? $data['Krátký popis'] : '');
+            $product->set_description(isset($data['Popis']) ? $data['Popis'] : '');
+
+            // Set categories
+            if (isset($data['Kategorie']) && !empty($data['Kategorie'])) {
+                $categories = array_map('trim', explode('|', $data['Kategorie']));
+                $category_ids = array();
+
+                error_log('STUDIOU WC IMPORT: Processing categories for product: ' . $product->get_name());
+                error_log('STUDIOU WC IMPORT: Categories string from CSV: ' . $data['Kategorie']);
+
+                foreach ($categories as $category_name) {
+                    if (empty($category_name)) {
+                        continue;
+                    }
+
+                    $category_id = $this->find_category_by_name($category_name);
+                    error_log('STUDIOU WC IMPORT: Looking for category "' . $category_name . '" - Result: ' . ($category_id ? 'FOUND ID=' . $category_id : 'NOT FOUND'));
+
+                    if ($category_id) {
+                        $category_ids[] = $category_id;
+                    }
+                }
+
+                error_log('STUDIOU WC IMPORT: Total category IDs found: ' . count($category_ids) . ' - IDs: ' . implode(',', $category_ids));
+
+                if (!empty($category_ids)) {
+                    $product->set_category_ids($category_ids);
+                    error_log('STUDIOU WC IMPORT: Categories assigned to product via set_category_ids()');
+                } else {
+                    error_log('STUDIOU WC IMPORT: WARNING - No valid category IDs found for: ' . $data['Kategorie']);
+                }
+            } else {
+                error_log('STUDIOU WC IMPORT: No categories in CSV for product: ' . $product->get_name());
+            }
+
+            // Handle image
+            if (isset($data['Obrázky']) && !empty($data['Obrázky'])) {
+                $image_id = $this->upload_image_from_url($data['Obrázky']);
+                if ($image_id) {
+                    $product->set_image_id($image_id);
+                }
+            }
+
+            // Set reviews enabled
+            $reviews_enabled = isset($data['Povolit zákaznické recenze?']) && $data['Povolit zákaznické recenze?'] === '1';
+            $product->set_reviews_allowed($reviews_enabled);
+
+            // Handle product attribute
+            if (isset($data['Název 1 vlastnosti']) && !empty($data['Název 1 vlastnosti'])) {
+                $attribute_name = $data['Název 1 vlastnosti'];
+                $attribute_values = isset($data['Hodnota(y) 1 vlastnosti']) ? $data['Hodnota(y) 1 vlastnosti'] : '';
+                $is_visible = isset($data['Vlastnost 1 viditelnost']) && $data['Vlastnost 1 viditelnost'] === '1';
+                $is_variation = isset($data['Vlastnost 1 varianta']) && $data['Vlastnost 1 varianta'] === '1';
+
+                // Create attribute taxonomy if it doesn't exist
+                $this->db->create_attribute_if_not_exists($attribute_name);
+
+                $attribute = new WC_Product_Attribute();
+                $attribute->set_name(wc_attribute_taxonomy_name($attribute_name));
+                $attribute->set_visible($is_visible);
+                $attribute->set_variation($is_variation);
+
+                // Set attribute options (terms)
+                $values = array_map('trim', explode('|', $attribute_values));
+                $attribute->set_options($values);
+
+                $product->set_attributes(array($attribute));
+            }
+
+            // Handle minimum/maximum quantity
+            if (isset($data['Minimum Quantity']) && !empty($data['Minimum Quantity'])) {
+                $product->update_meta_data('_minimum_quantity', intval($data['Minimum Quantity']));
+            }
+            if (isset($data['Maximum Quantity']) && !empty($data['Maximum Quantity'])) {
+                $product->update_meta_data('_maximum_quantity', intval($data['Maximum Quantity']));
+            }
+
+            // Save product
+            $saved_id = $product->save();
+
+            $message = $product_id == 0 ?
+                sprintf(__('Created variable product: %s', 'studiou-wc-product-cat-manage'), $product->get_name()) :
+                sprintf(__('Updated variable product: %s', 'studiou-wc-product-cat-manage'), $product->get_name());
+
+            return array(
+                'status' => 'success',
+                'message' => $message
+            );
+
+        } catch (Exception $e) {
+            return array(
+                'status' => 'error',
+                'message' => $e->getMessage()
+            );
+        }
+    }
+
+    /**
+     * Process product variation
+     *
+     * @param array $data Product data
+     * @param int $variation_id Variation ID (0 for new variation)
+     * @return array Result with status and message
+     */
+    private function process_variation($data, $variation_id) {
+        try {
+            // Rule 6: Must have parent reference
+            if (!isset($data['Nadřazené']) || empty($data['Nadřazené'])) {
+                return array(
+                    'status' => 'skipped',
+                    'message' => __('Missing parent product reference (Nadřazené)', 'studiou-wc-product-cat-manage')
+                );
+            }
+
+            // Rule 7: Must have regular price
+            if (!isset($data['Běžná cena']) || empty($data['Běžná cena'])) {
+                return array(
+                    'status' => 'skipped',
+                    'message' => __('Missing regular price (Běžná cena)', 'studiou-wc-product-cat-manage')
+                );
+            }
+
+            // Find parent product by SKU
+            $parent_sku = trim($data['Nadřazené']);
+            $parent_product = $this->db->get_product_by_sku($parent_sku);
+
+            if (!$parent_product || !$parent_product->is_type('variable')) {
+                return array(
+                    'status' => 'skipped',
+                    'message' => sprintf(
+                        __('Parent product with SKU "%s" not found or not variable', 'studiou-wc-product-cat-manage'),
+                        $parent_sku
+                    )
+                );
+            }
+
+            // Create or update variation
+            if ($variation_id == 0) {
+                $variation = new WC_Product_Variation();
+                $variation->set_parent_id($parent_product->get_id());
+            } else {
+                $variation = wc_get_product($variation_id);
+                if (!$variation || !$variation->is_type('variation')) {
+                    return array(
+                        'status' => 'error',
+                        'message' => sprintf(
+                            __('Product ID %d is not a variation', 'studiou-wc-product-cat-manage'),
+                            $variation_id
+                        )
+                    );
+                }
+            }
+
+            // Set basic data
+            $variation->set_name(isset($data['Jméno']) ? $data['Jméno'] : '');
+            $variation->set_sku(isset($data['Katalogové číslo']) ? $data['Katalogové číslo'] : '');
+            $variation->set_description(isset($data['Popis']) ? $data['Popis'] : '');
+            $variation->set_regular_price($data['Běžná cena']);
+
+            // Handle image
+            if (isset($data['Obrázky']) && !empty($data['Obrázky'])) {
+                $image_id = $this->upload_image_from_url($data['Obrázky']);
+                if ($image_id) {
+                    $variation->set_image_id($image_id);
+                }
+            }
+
+            // Set variation attributes
+            if (isset($data['Název 1 vlastnosti']) && !empty($data['Název 1 vlastnosti']) &&
+                isset($data['Hodnota(y) 1 vlastnosti']) && !empty($data['Hodnota(y) 1 vlastnosti'])) {
+
+                $attribute_name = wc_attribute_taxonomy_name($data['Název 1 vlastnosti']);
+                $attribute_value = $data['Hodnota(y) 1 vlastnosti'];
+
+                $variation->set_attributes(array(
+                    $attribute_name => $attribute_value
+                ));
+            }
+
+            // Handle minimum/maximum quantity
+            if (isset($data['Minimum Quantity']) && !empty($data['Minimum Quantity'])) {
+                $variation->update_meta_data('_minimum_quantity', intval($data['Minimum Quantity']));
+            }
+            if (isset($data['Maximum Quantity']) && !empty($data['Maximum Quantity'])) {
+                $variation->update_meta_data('_maximum_quantity', intval($data['Maximum Quantity']));
+            }
+
+            // Save variation
+            $saved_id = $variation->save();
+
+            $message = $variation_id == 0 ?
+                sprintf(__('Created variation: %s', 'studiou-wc-product-cat-manage'), $variation->get_name()) :
+                sprintf(__('Updated variation: %s', 'studiou-wc-product-cat-manage'), $variation->get_name());
+
+            return array(
+                'status' => 'success',
+                'message' => $message
+            );
+
+        } catch (Exception $e) {
+            return array(
+                'status' => 'error',
+                'message' => $e->getMessage()
+            );
+        }
+    }
+
+    /**
+     * Upload image from URL
+     *
+     * @param string $url Image URL
+     * @return int|null Attachment ID or null on failure
+     */
+    private function upload_image_from_url($url) {
+        require_once(ABSPATH . 'wp-admin/includes/media.php');
+        require_once(ABSPATH . 'wp-admin/includes/file.php');
+        require_once(ABSPATH . 'wp-admin/includes/image.php');
+
+        // Set shorter timeout for image download (30 seconds)
+        add_filter('http_request_timeout', function() { return 30; });
+
+        try {
+            $tmp = download_url($url, 30);
+
+            if (is_wp_error($tmp)) {
+                if (defined('WP_DEBUG') && WP_DEBUG) {
+                    error_log('STUDIOU WC: Image download failed for URL: ' . $url . ' - ' . $tmp->get_error_message());
+                }
+                return null;
+            }
+
+            $file_array = array(
+                'name' => basename($url),
+                'tmp_name' => $tmp
+            );
+
+            // Disable image processing to speed up import
+            add_filter('intermediate_image_sizes_advanced', function() { return array(); });
+
+            $id = media_handle_sideload($file_array, 0);
+
+            // Re-enable image processing
+            remove_filter('intermediate_image_sizes_advanced', function() { return array(); });
+
+            if (is_wp_error($id)) {
+                @unlink($file_array['tmp_name']);
+                if (defined('WP_DEBUG') && WP_DEBUG) {
+                    error_log('STUDIOU WC: Image upload failed for URL: ' . $url . ' - ' . $id->get_error_message());
+                }
+                return null;
+            }
+
+            return $id;
+
+        } catch (Exception $e) {
+            if (defined('WP_DEBUG') && WP_DEBUG) {
+                error_log('STUDIOU WC: Image upload exception for URL: ' . $url . ' - ' . $e->getMessage());
+            }
+            return null;
+        }
+    }
+
+    /**
+     * Find category by name (simple lookup, categories have unique names)
+     *
+     * @param string $category_name Category name
+     * @return int|null Category ID or null if not found
+     */
+    private function find_category_by_name($category_name) {
+        // Trim and normalize the category name
+        $category_name = trim($category_name);
+
+        if (empty($category_name)) {
+            return null;
+        }
+
+        // Simple name lookup only (no hierarchy detection)
+        // Categories have unique names throughout the system
+
+        error_log('STUDIOU WC IMPORT: find_category_by_name() called for: "' . $category_name . '"');
+
+        // Method 1: Exact name match
+        $term = get_term_by('name', $category_name, 'product_cat');
+        if ($term) {
+            error_log('STUDIOU WC IMPORT: Found by exact name match - ID: ' . $term->term_id);
+            return $term->term_id;
+        }
+
+        // Method 2: Try by slug
+        $slug = sanitize_title($category_name);
+        error_log('STUDIOU WC IMPORT: Exact name failed, trying slug: "' . $slug . '"');
+        $term = get_term_by('slug', $slug, 'product_cat');
+        if ($term) {
+            error_log('STUDIOU WC IMPORT: Found by slug match - ID: ' . $term->term_id);
+            return $term->term_id;
+        }
+
+        // Method 3: Search case-insensitively through all categories
+        $all_terms = get_terms(array(
+            'taxonomy' => 'product_cat',
+            'hide_empty' => false
+        ));
+
+        error_log('STUDIOU WC IMPORT: Slug failed, searching ' . count($all_terms) . ' categories case-insensitively');
+
+        foreach ($all_terms as $t) {
+            if (strcasecmp($t->name, $category_name) === 0) {
+                error_log('STUDIOU WC IMPORT: Found by case-insensitive match - ID: ' . $t->term_id . ', name: "' . $t->name . '"');
+                return $t->term_id;
+            }
+        }
+
+        // Not found - try to create it
+        error_log('STUDIOU WC IMPORT: Category NOT FOUND: "' . $category_name . '" - Attempting to create it');
+
+        $new_term = wp_insert_term(
+            $category_name,
+            'product_cat',
+            array(
+                'slug' => $slug
+            )
+        );
+
+        if (is_wp_error($new_term)) {
+            error_log('STUDIOU WC IMPORT: Failed to create category "' . $category_name . '": ' . $new_term->get_error_message());
+            error_log('STUDIOU WC IMPORT: Total available categories: ' . count($all_terms));
+
+            // Show first 20 categories for debugging
+            $sample_cats = array_slice($all_terms, 0, 20);
+            foreach ($sample_cats as $cat) {
+                error_log('STUDIOU WC IMPORT: Available: "' . $cat->name . '" (ID: ' . $cat->term_id . ', slug: ' . $cat->slug . ')');
+            }
+
+            return null;
+        } else {
+            $term_id = $new_term['term_id'];
+            error_log('STUDIOU WC IMPORT: Successfully created category "' . $category_name . '" with ID: ' . $term_id);
+            return $term_id;
+        }
+    }
+
+    /**
+     * Generate CSV with failed items
+     *
+     * @param array $failed_items Array of failed product data
+     * @return string CSV content
+     */
+    public function generate_failed_csv($failed_items) {
+        if (empty($failed_items)) {
+            return '';
+        }
+
+        // Define CSV header
+        $header = array(
+            'ID', 'Typ', 'Katalogové číslo', 'Jméno', 'Nadřazené', 'Krátký popis', 'Popis',
+            'Název 1 vlastnosti', 'Hodnota(y) 1 vlastnosti', 'Kategorie', 'Obrázky', 'Běžná cena',
+            'Vlastnost 1 viditelnost', 'Vlastnost 1 globální', 'Vlastnost 1 varianta',
+            'Povolit zákaznické recenze?', 'Minimum Quantity', 'Maximum Quantity'
+        );
+
+        // Start CSV output
+        $csv = '';
+        $csv .= '"' . implode('","', $header) . '"' . "\n";
+
+        // Add failed items
+        foreach ($failed_items as $item) {
+            $row = array();
+            foreach ($header as $column) {
+                $value = isset($item[$column]) ? $item[$column] : '';
+                $row[] = '"' . str_replace('"', '""', $value) . '"';
+            }
+            $csv .= implode(',', $row) . "\n";
+        }
+
+        return $csv;
+    }
+}

BIN
studiou-wc-product-cat-manage/languages/studiou-wc-product-cat-manage-cs_CZ.mo


+ 286 - 5
studiou-wc-product-cat-manage/languages/studiou-wc-product-cat-manage-cs_CZ.po

@@ -388,10 +388,6 @@ msgstr ""
 msgid "Choose CSV file"
 msgstr "Vyberte CSV soubor"
 
-#: views/import.php:48
-msgid "Import Results"
-msgstr "Výsledky importu"
-
 #: views/import.php:54
 msgid "Sample CSV Format"
 msgstr "Vzorový formát CSV"
@@ -522,4 +518,289 @@ msgstr ""
 
 #: views/manipulations.php:142
 msgid "The batch description operation will overwrite the existing descriptions for all selected categories."
-msgstr "Hromadná operace popisu přepíše stávající popisy všech vybraných kategorií."
+msgstr "Hromadná operace popisu přepíše stávající popisy všech vybraných kategorií."
+
+#: views/product-import.php:17
+msgid "Product Import"
+msgstr "Import produktů"
+
+#: views/product-import.php:24
+msgid "Import Products from CSV"
+msgstr "Import produktů z CSV"
+
+#: views/product-import.php:26
+msgid "Upload a CSV file to import products. The CSV file must follow the required format with the following columns:"
+msgstr "Nahrajte CSV soubor pro import produktů. CSV soubor musí odpovídat požadovanému formátu s následujícími sloupci:"
+
+#: views/product-import.php:29
+msgid "Product ID (0 for new products)"
+msgstr "ID produktu (0 pro nové produkty)"
+
+#: views/product-import.php:30
+msgid "Product type (only \"variable\" or \"variation\" allowed)"
+msgstr "Typ produktu (povoleno pouze \"variable\" nebo \"variation\")"
+
+#: views/product-import.php:31
+msgid "SKU (must be unique)"
+msgstr "SKU (musí být jedinečné)"
+
+#: views/product-import.php:32
+msgid "Product name"
+msgstr "Název produktu"
+
+#: views/product-import.php:33
+msgid "Parent product SKU (for variations only)"
+msgstr "SKU nadřazeného produktu (pouze pro varianty)"
+
+#: views/product-import.php:34
+msgid "Short description"
+msgstr "Krátký popis"
+
+#: views/product-import.php:35
+msgid "Description"
+msgstr "Popis"
+
+#: views/product-import.php:36
+msgid "Attribute 1 name"
+msgstr "Název 1. vlastnosti"
+
+#: views/product-import.php:37
+msgid "Attribute 1 value(s) (pipe-separated)"
+msgstr "Hodnota(y) 1. vlastnosti (oddělené rourou)"
+
+#: views/product-import.php:38
+msgid "Categories (pipe-separated)"
+msgstr "Kategorie (oddělené rourou)"
+
+#: views/product-import.php:39
+msgid "Image URL (must be valid)"
+msgstr "URL obrázku (musí být platná)"
+
+#: views/product-import.php:40
+msgid "Regular price (required for variations)"
+msgstr "Běžná cena (povinná pro varianty)"
+
+#: views/product-import.php:41
+msgid "Attribute 1 visibility (1 or 0)"
+msgstr "Viditelnost 1. vlastnosti (1 nebo 0)"
+
+#: views/product-import.php:42
+msgid "Attribute 1 global (1 or 0)"
+msgstr "1. vlastnost globální (1 nebo 0)"
+
+#: views/product-import.php:43
+msgid "Attribute 1 used for variations (1 or 0)"
+msgstr "1. vlastnost použitá pro varianty (1 nebo 0)"
+
+#: views/product-import.php:44
+msgid "Enable reviews (1 or 0)"
+msgstr "Povolit recenze (1 nebo 0)"
+
+#: views/product-import.php:45
+msgid "Minimum order quantity"
+msgstr "Minimální objednané množství"
+
+#: views/product-import.php:46
+msgid "Maximum order quantity"
+msgstr "Maximální objednané množství"
+
+#: views/product-import.php:51
+msgid "Select Import File"
+msgstr "Vybrat soubor pro import"
+
+#: views/product-import.php:53
+msgid "Select a CSV file to import (UTF-8 encoding)"
+msgstr "Vyberte CSV soubor pro import (kódování UTF-8)"
+
+#: views/product-import.php:58
+msgid "Process Import File"
+msgstr "Zpracovat soubor pro import"
+
+#: views/product-import.php:66
+msgid "Import Progress"
+msgstr "Průběh importu"
+
+#: views/product-import.php:71
+msgid "Initializing..."
+msgstr "Inicializace..."
+
+#: views/import.php:48
+#: views/product-import.php:77
+msgid "Import Results"
+msgstr "Výsledky importu"
+
+#: views/product-import.php:81
+msgid "Import Details"
+msgstr "Detaily importu"
+
+#: views/product-import.php:89
+msgid "Save Failed Products"
+msgstr "Uložit neúspěšné produkty"
+
+#: views/product-import.php:91
+msgid "Download a CSV file with failed products to fix and re-import"
+msgstr "Stáhnout CSV soubor s neúspěšnými produkty k opravě a opětovnému importu"
+
+#: views/product-import.php:97
+msgid "Import Rules"
+msgstr "Pravidla importu"
+
+#: views/product-import.php:99
+msgid "Rule 1:"
+msgstr "Pravidlo 1:"
+
+#: views/product-import.php:99
+msgid "All operations are performed via WooCommerce API (not direct SQL)"
+msgstr "Všechny operace jsou prováděny přes WooCommerce API (ne přímým SQL)"
+
+#: views/product-import.php:100
+msgid "Rule 2:"
+msgstr "Pravidlo 2:"
+
+#: views/product-import.php:100
+msgid "If ID = 0, item will be created; otherwise updated"
+msgstr "Pokud ID = 0, položka bude vytvořena; jinak aktualizována"
+
+#: views/product-import.php:101
+msgid "Rule 3:"
+msgstr "Pravidlo 3:"
+
+#: views/product-import.php:101
+msgid "Only \"variable\" or \"variation\" types are allowed"
+msgstr "Povoleny jsou pouze typy \"variable\" nebo \"variation\""
+
+#: views/product-import.php:102
+msgid "Rule 4:"
+msgstr "Pravidlo 4:"
+
+#: views/product-import.php:102
+msgid "SKU (Katalogové číslo) must be unique"
+msgstr "SKU (Katalogové číslo) musí být jedinečné"
+
+#: views/product-import.php:103
+msgid "Rule 5:"
+msgstr "Pravidlo 5:"
+
+#: views/product-import.php:103
+msgid "Image URL must be valid and accessible"
+msgstr "URL obrázku musí být platná a přístupná"
+
+#: views/product-import.php:104
+msgid "Rule 6:"
+msgstr "Pravidlo 6:"
+
+#: views/product-import.php:104
+msgid "Parent reference (Nadřazené) must point to an existing variable product SKU"
+msgstr "Odkaz na nadřazený (Nadřazené) musí ukazovat na existující SKU variabilního produktu"
+
+#: views/product-import.php:105
+msgid "Rule 7:"
+msgstr "Pravidlo 7:"
+
+#: views/product-import.php:105
+msgid "Variations must have a regular price (Běžná cena)"
+msgstr "Varianty musí mít běžnou cenu (Běžná cena)"
+
+#: views/product-export.php:22
+msgid "Product Export"
+msgstr "Export produktů"
+
+#: views/product-export.php:29
+msgid "Export Products to CSV"
+msgstr "Export produktů do CSV"
+
+#: views/product-export.php:31
+msgid "Select product categories to export. All products in the selected categories will be exported to a CSV file."
+msgstr "Vyberte kategorie produktů pro export. Všechny produkty ve vybraných kategoriích budou exportovány do CSV souboru."
+
+#: views/product-export.php:35
+msgid "Select Product Categories"
+msgstr "Vybrat kategorie produktů"
+
+#: views/product-export.php:41
+msgid "Select All"
+msgstr "Vybrat vše"
+
+#: views/product-export.php:44
+msgid "Deselect All"
+msgstr "Zrušit výběr všech"
+
+#: views/product-export.php:51
+msgid "No product categories found."
+msgstr "Nebyly nalezeny žádné kategorie produktů."
+
+#: views/product-export.php:63
+msgid "Categories are shown with product count in parentheses"
+msgstr "Kategorie jsou zobrazeny s počtem produktů v závorkách"
+
+#: views/product-export.php:68
+msgid "Export Selected Products"
+msgstr "Exportovat vybrané produkty"
+
+#: views/product-export.php:76
+msgid "Export Results"
+msgstr "Výsledky exportu"
+
+#: views/product-export.php:79
+msgid "Download Export File"
+msgstr "Stáhnout soubor exportu"
+
+#: views/product-export.php:86
+msgid "Export Information"
+msgstr "Informace o exportu"
+
+#: views/product-export.php:87
+msgid "The exported CSV file will contain the following columns:"
+msgstr "Exportovaný CSV soubor bude obsahovat následující sloupce:"
+
+#: views/product-export.php:89
+msgid "Product ID"
+msgstr "ID produktu"
+
+#: views/product-export.php:90
+msgid "Product type (variable or variation)"
+msgstr "Typ produktu (variable nebo variation)"
+
+#: views/product-export.php:91
+msgid "SKU"
+msgstr "SKU"
+
+#: views/product-export.php:93
+msgid "Parent product SKU (for variations)"
+msgstr "SKU nadřazeného produktu (pro varianty)"
+
+#: views/product-export.php:97
+msgid "Attribute 1 value(s)"
+msgstr "Hodnota(y) 1. vlastnosti"
+
+#: views/product-export.php:98
+msgid "Categories"
+msgstr "Kategorie"
+
+#: views/product-export.php:99
+msgid "Image URL"
+msgstr "URL obrázku"
+
+#: views/product-export.php:100
+msgid "Regular price"
+msgstr "Běžná cena"
+
+#: views/product-export.php:101
+msgid "Attribute 1 visibility"
+msgstr "Viditelnost 1. vlastnosti"
+
+#: views/product-export.php:102
+msgid "Attribute 1 global"
+msgstr "1. vlastnost globální"
+
+#: views/product-export.php:103
+msgid "Attribute 1 used for variations"
+msgstr "1. vlastnost použitá pro varianty"
+
+#: views/product-export.php:104
+msgid "Enable reviews"
+msgstr "Povolit recenze"
+
+#: views/product-export.php:108
+msgid "The exported file can be modified and re-imported using the Product Import feature."
+msgstr "Exportovaný soubor může být upraven a znovu importován pomocí funkce Import produktů."

+ 284 - 0
studiou-wc-product-cat-manage/languages/studiou-wc-product-cat-manage.po

@@ -485,4 +485,288 @@ msgstr ""
 
 #: views/manipulations.php:142
 msgid "The batch description operation will overwrite the existing descriptions for all selected categories."
+msgstr ""
+
+#: views/product-import.php:17
+msgid "Product Import"
+msgstr ""
+
+#: views/product-import.php:24
+msgid "Import Products from CSV"
+msgstr ""
+
+#: views/product-import.php:26
+msgid "Upload a CSV file to import products. The CSV file must follow the required format with the following columns:"
+msgstr ""
+
+#: views/product-import.php:29
+msgid "Product ID (0 for new products)"
+msgstr ""
+
+#: views/product-import.php:30
+msgid "Product type (only \"variable\" or \"variation\" allowed)"
+msgstr ""
+
+#: views/product-import.php:31
+msgid "SKU (must be unique)"
+msgstr ""
+
+#: views/product-import.php:32
+msgid "Product name"
+msgstr ""
+
+#: views/product-import.php:33
+msgid "Parent product SKU (for variations only)"
+msgstr ""
+
+#: views/product-import.php:34
+msgid "Short description"
+msgstr ""
+
+#: views/product-import.php:35
+msgid "Description"
+msgstr ""
+
+#: views/product-import.php:36
+msgid "Attribute 1 name"
+msgstr ""
+
+#: views/product-import.php:37
+msgid "Attribute 1 value(s) (pipe-separated)"
+msgstr ""
+
+#: views/product-import.php:38
+msgid "Categories (pipe-separated)"
+msgstr ""
+
+#: views/product-import.php:39
+msgid "Image URL (must be valid)"
+msgstr ""
+
+#: views/product-import.php:40
+msgid "Regular price (required for variations)"
+msgstr ""
+
+#: views/product-import.php:41
+msgid "Attribute 1 visibility (1 or 0)"
+msgstr ""
+
+#: views/product-import.php:42
+msgid "Attribute 1 global (1 or 0)"
+msgstr ""
+
+#: views/product-import.php:43
+msgid "Attribute 1 used for variations (1 or 0)"
+msgstr ""
+
+#: views/product-import.php:44
+msgid "Enable reviews (1 or 0)"
+msgstr ""
+
+#: views/product-import.php:45
+msgid "Minimum order quantity"
+msgstr ""
+
+#: views/product-import.php:46
+msgid "Maximum order quantity"
+msgstr ""
+
+#: views/product-import.php:51
+msgid "Select Import File"
+msgstr ""
+
+#: views/product-import.php:53
+msgid "Select a CSV file to import (UTF-8 encoding)"
+msgstr ""
+
+#: views/product-import.php:58
+msgid "Process Import File"
+msgstr ""
+
+#: views/product-import.php:66
+msgid "Import Progress"
+msgstr ""
+
+#: views/product-import.php:71
+msgid "Initializing..."
+msgstr ""
+
+#: views/product-import.php:77
+msgid "Import Results"
+msgstr ""
+
+#: views/product-import.php:81
+msgid "Import Details"
+msgstr ""
+
+#: views/product-import.php:89
+msgid "Save Failed Products"
+msgstr ""
+
+#: views/product-import.php:91
+msgid "Download a CSV file with failed products to fix and re-import"
+msgstr ""
+
+#: views/product-import.php:97
+msgid "Import Rules"
+msgstr ""
+
+#: views/product-import.php:99
+msgid "Rule 1:"
+msgstr ""
+
+#: views/product-import.php:99
+msgid "All operations are performed via WooCommerce API (not direct SQL)"
+msgstr ""
+
+#: views/product-import.php:100
+msgid "Rule 2:"
+msgstr ""
+
+#: views/product-import.php:100
+msgid "If ID = 0, item will be created; otherwise updated"
+msgstr ""
+
+#: views/product-import.php:101
+msgid "Rule 3:"
+msgstr ""
+
+#: views/product-import.php:101
+msgid "Only \"variable\" or \"variation\" types are allowed"
+msgstr ""
+
+#: views/product-import.php:102
+msgid "Rule 4:"
+msgstr ""
+
+#: views/product-import.php:102
+msgid "SKU (Katalogové číslo) must be unique"
+msgstr ""
+
+#: views/product-import.php:103
+msgid "Rule 5:"
+msgstr ""
+
+#: views/product-import.php:103
+msgid "Image URL must be valid and accessible"
+msgstr ""
+
+#: views/product-import.php:104
+msgid "Rule 6:"
+msgstr ""
+
+#: views/product-import.php:104
+msgid "Parent reference (Nadřazené) must point to an existing variable product SKU"
+msgstr ""
+
+#: views/product-import.php:105
+msgid "Rule 7:"
+msgstr ""
+
+#: views/product-import.php:105
+msgid "Variations must have a regular price (Běžná cena)"
+msgstr ""
+
+#: views/product-export.php:22
+msgid "Product Export"
+msgstr ""
+
+#: views/product-export.php:29
+msgid "Export Products to CSV"
+msgstr ""
+
+#: views/product-export.php:31
+msgid "Select product categories to export. All products in the selected categories will be exported to a CSV file."
+msgstr ""
+
+#: views/product-export.php:35
+msgid "Select Product Categories"
+msgstr ""
+
+#: views/product-export.php:41
+msgid "Select All"
+msgstr ""
+
+#: views/product-export.php:44
+msgid "Deselect All"
+msgstr ""
+
+#: views/product-export.php:51
+msgid "No product categories found."
+msgstr ""
+
+#: views/product-export.php:63
+msgid "Categories are shown with product count in parentheses"
+msgstr ""
+
+#: views/product-export.php:68
+msgid "Export Selected Products"
+msgstr ""
+
+#: views/product-export.php:76
+msgid "Export Results"
+msgstr ""
+
+#: views/product-export.php:79
+msgid "Download Export File"
+msgstr ""
+
+#: views/product-export.php:86
+msgid "Export Information"
+msgstr ""
+
+#: views/product-export.php:87
+msgid "The exported CSV file will contain the following columns:"
+msgstr ""
+
+#: views/product-export.php:89
+msgid "Product ID"
+msgstr ""
+
+#: views/product-export.php:90
+msgid "Product type (variable or variation)"
+msgstr ""
+
+#: views/product-export.php:91
+msgid "SKU"
+msgstr ""
+
+#: views/product-export.php:93
+msgid "Parent product SKU (for variations)"
+msgstr ""
+
+#: views/product-export.php:97
+msgid "Attribute 1 value(s)"
+msgstr ""
+
+#: views/product-export.php:98
+msgid "Categories"
+msgstr ""
+
+#: views/product-export.php:99
+msgid "Image URL"
+msgstr ""
+
+#: views/product-export.php:100
+msgid "Regular price"
+msgstr ""
+
+#: views/product-export.php:101
+msgid "Attribute 1 visibility"
+msgstr ""
+
+#: views/product-export.php:102
+msgid "Attribute 1 global"
+msgstr ""
+
+#: views/product-export.php:103
+msgid "Attribute 1 used for variations"
+msgstr ""
+
+#: views/product-export.php:104
+msgid "Enable reviews"
+msgstr ""
+
+#: views/product-export.php:108
+msgid "The exported file can be modified and re-imported using the Product Import feature."
 msgstr ""

+ 149 - 11
studiou-wc-product-cat-manage/readme.md

@@ -1,17 +1,23 @@
 # QDR - Studiou WC Export/Import Product Categories
 
-**Version: 1.3**
+**Version: 1.4**
 
-A WordPress plugin that extends WooCommerce to provide batch export and import functionality for product categories, along with category manipulation tools.
+A WordPress plugin that extends WooCommerce to provide batch export and import functionality for both product categories and products, along with category manipulation tools.
 
 ## Description
 
-QDR - Studiou WC Export/Import Product Categories is a powerful WordPress plugin for WooCommerce store administrators who need to manage product categories in bulk. This plugin allows you to export your existing product categories to a CSV file, make modifications, and import them back into your store. Additionally, it provides tools for manipulating categories, moving products between categories, and batch updating category descriptions.
+QDR - Studiou WC Export/Import Product Categories is a powerful WordPress plugin for WooCommerce store administrators who need to manage product categories and products in bulk. This plugin allows you to:
 
-The plugin also supports the WooCommerce Protected Categories extension, allowing you to manage visibility settings and passwords for your product categories.
+- Export and import product categories with all their settings
+- Export and import variable products and their variations
+- Manipulate categories (move products, batch update descriptions, delete products)
+- Support for WooCommerce Protected Categories extension
+
+The plugin provides a complete solution for bulk product and category management with comprehensive validation, error reporting, and progress tracking.
 
 ## Features
 
+### Category Management
 - Export all product categories to a CSV file
 - Import product categories from a CSV file
 - Add, update, or delete product categories in batch
@@ -19,9 +25,21 @@ The plugin also supports the WooCommerce Protected Categories extension, allowin
 - **Move products between categories** in bulk
 - **Batch description update** for multiple categories
 - **Delete products in category** - permanently delete all products and variants from selected categories
+
+### Product Management (NEW in v1.4)
+- **Export products to CSV** - Export variable products and variations by category selection
+- **Import products from CSV** - Batch create/update variable products and variations
+- Full support for product attributes and variations
+- Image URL handling with validation
+- Min/Max quantity support
+- Comprehensive import rules with validation
+- Failed products download for retry
+
+### General Features
 - User-friendly interface integrated with WooCommerce admin
 - Real-time progress tracking for bulk operations
 - Comprehensive validation and error reporting
+- Full translation support (English/Czech)
 
 ## Requirements
 
@@ -38,9 +56,11 @@ The plugin also supports the WooCommerce Protected Categories extension, allowin
 4. Click "Install Now"
 5. Activate the plugin
 
-## CSV Format
+## CSV Formats
 
-The plugin uses CSV files with the following columns:
+### Product Category CSV Format
+
+The plugin uses CSV files with the following columns for categories:
 
 | Column | Description |
 |--------|-------------|
@@ -53,6 +73,31 @@ The plugin uses CSV files with the following columns:
 | protected-passwords | Pipe (&#124;) separated passwords if visibility is 'protected' |
 | operation | Record operation: 'A' - add, 'U' - update, 'D' - delete |
 
+### Product CSV Format (NEW in v1.4)
+
+The plugin uses CSV files with the following columns for products:
+
+| Column | Description |
+|--------|-------------|
+| ID | Product ID (0 for new products) |
+| Typ | Product type (only "variable" or "variation" allowed) |
+| Katalogové číslo | SKU (must be unique) |
+| Jméno | Product name |
+| Nadřazené | Parent product SKU (for variations only) |
+| Krátký popis | Short description |
+| Popis | Description |
+| Název 1 vlastnosti | Attribute 1 name |
+| Hodnota(y) 1 vlastnosti | Attribute 1 value(s) (pipe-separated for variable products) |
+| Kategorie | Categories (pipe-separated) |
+| Obrázky | Image URL (must be valid and accessible) |
+| Běžná cena | Regular price (required for variations) |
+| Vlastnost 1 viditelnost | Attribute 1 visibility (1 or 0) |
+| Vlastnost 1 globální | Attribute 1 global (1 or 0) |
+| Vlastnost 1 varianta | Attribute 1 used for variations (1 or 0) |
+| Povolit zákaznické recenze? | Enable reviews (1 or 0) |
+| Minimum Quantity | Minimum order quantity |
+| Maximum Quantity | Maximum order quantity |
+
 ## Usage
 
 ### Exporting Product Categories
@@ -68,6 +113,53 @@ The plugin uses CSV files with the following columns:
 3. Click "Import Product Categories"
 4. Review the import results
 
+### Exporting Products (NEW in v1.4)
+
+1. Go to Products > Product Export
+2. Select categories using checkboxes
+   - Use "Select All" to select all categories
+   - Use "Deselect All" to deselect all categories
+3. Click "Export Selected Products"
+4. Download the generated CSV file
+
+### Importing Products (NEW in v1.4)
+
+1. Go to Products > Product Import
+2. Select your CSV file (UTF-8 encoding)
+3. Click "Process Import File"
+4. Watch the progress bar as products are being imported in batches
+   - Products are processed 5 at a time to avoid timeout issues
+   - Progress shows "Processing: X / Y products"
+5. Review the import results showing:
+   - Successful imports
+   - Failed imports
+   - Skipped items
+   - Detailed messages for skipped/failed items only
+6. If there are failed items, click "Save Failed Products" to download a CSV file with failed items for correction and re-import
+
+**Product Import Features:**
+- **Batch Processing**: Processes 5 products per batch to avoid PHP timeouts
+- **Auto-Create Categories**: Missing categories are automatically created during import
+- **Real-Time Progress**: Shows exact progress (e.g., "Processing: 15 / 50 products")
+- **Smart Category Matching**: Tries exact name, slug, and case-insensitive matching
+- **Image Optimization**: 30-second timeout per image, intermediate sizes disabled for speed
+- **Detailed Logging**: All operations logged for debugging (prefix: "STUDIOU WC IMPORT:")
+
+**Product Import Rules:**
+- Rule 1: All operations performed via WooCommerce API (not direct SQL)
+- Rule 2: If ID = 0, item will be created; otherwise updated
+- Rule 3: Only "variable" or "variation" types are allowed
+- Rule 4: SKU (Katalogové číslo) must be unique in WooCommerce database
+- Rule 5: Image URL must be valid format (accessibility checked during download)
+- Rule 6: Parent reference (Nadřazené) must point to an existing variable product SKU
+- Rule 7: Variations must have a regular price (Běžná cena)
+
+**Important Notes:**
+- Categories have unique names throughout the system (no hierarchy path needed)
+- Category names can contain dashes, dots, and special characters
+- Failed image downloads don't stop the import, they're just logged
+- Import can handle large files thanks to batch processing
+
 ### Category Manipulations
 
 #### Moving Products Between Categories
@@ -125,7 +217,9 @@ The plugin uses CSV files with the following columns:
 - If a product belongs to multiple categories, it will still be deleted entirely from the system
 - The page will automatically reload after successful deletion to update category counts
 
-## Sample CSV
+## Sample CSVs
+
+### Sample Product Category CSV
 
 ```
 name,url-name,parent-name,description,display-type,visibility,protected-passwords,operation
@@ -136,22 +230,35 @@ name,url-name,parent-name,description,display-type,visibility,protected-password
 "Accessories","accessories","","All accessories","default","public","","D"
 ```
 
-## Import Operations
-
+**Category Import Operations:**
 - `A` - Add a new product category
 - `U` - Update an existing product category (identified by name)
 - `D` - Delete an existing product category (identified by name)
 
+### Sample Product CSV (NEW in v1.4)
+
+```
+ID,Typ,Katalogové číslo,Jméno,Nadřazené,Krátký popis,Popis,Název 1 vlastnosti,Hodnota(y) 1 vlastnosti,Kategorie,Obrázky,Běžná cena,Vlastnost 1 viditelnost,Vlastnost 1 globální,Vlastnost 1 varianta,Povolit zákaznické recenze?,Minimum Quantity,Maximum Quantity
+0,variable,T-SHIRT-001,"Basic T-Shirt","","Comfortable cotton t-shirt","High-quality cotton t-shirt in various sizes",Size,"S|M|L|XL","Clothing|Men","https://example.com/tshirt.jpg","",1,1,1,1,1,10
+0,variation,T-SHIRT-001-S,"Basic T-Shirt - S",T-SHIRT-001,"","",Size,S,"","https://example.com/tshirt-s.jpg",19.99,0,1,0,0,1,10
+0,variation,T-SHIRT-001-M,"Basic T-Shirt - M",T-SHIRT-001,"","",Size,M,"","https://example.com/tshirt-m.jpg",19.99,0,1,0,0,1,10
+```
+
 ## Menu Structure
 
 After activation, the plugin adds the following menu items under **Products** in the WordPress admin:
 
+### Category Management
 - Import Product Categories
 - Export Product Categories
-- **Category Manipulations**
+- Category Manipulations
   - Move products between categories
   - Batch description between categories
-  - **Delete products in category**
+  - Delete products in category
+
+### Product Management (NEW in v1.4)
+- **Product Import** - Batch import variable products and variations
+- **Product Export** - Export products by category selection
 
 ## License
 
@@ -168,6 +275,37 @@ For support, please visit [https://www.quadarax.com/plugins/studiou-wc-product-c
 
 ## Changelog
 
+### Version 1.4.0
+- **NEW: Product Import** - Batch import variable products and variations from CSV
+  - **Batch processing** - Processes 5 products per batch to avoid PHP timeout (60 sec limit)
+  - **Auto-create categories** - Missing categories automatically created during import
+  - Support for variable products with attributes
+  - Support for product variations with parent references
+  - Image URL handling with 30-second timeout, intermediate sizes disabled
+  - Min/Max quantity support
+  - 7 comprehensive import rules with validation
+  - Failed products download for retry
+  - Real-time progress bar showing "Processing: X / Y products"
+  - Comprehensive error logging (always enabled, prefix: "STUDIOU WC IMPORT:")
+  - Logs only skipped/failed items (not successful ones)
+- **NEW: Product Export** - Export products by category selection
+  - Export variable products and their variations
+  - **Full category paths** exported for subcategories (format: "Parent > Child")
+  - Category hierarchy selection with product counts
+  - Select All/Deselect All functionality
+  - Full product data export (18 columns)
+- **Technical improvements**:
+  - Two-phase import: Parse CSV to transient, then batch process
+  - Smart category matching: exact name, slug, case-insensitive
+  - Simple category lookup only (categories have unique names)
+  - Image validation only checks format, not HTTP accessibility (prevents timeouts)
+- Added 3 new database/import/export classes for product management
+- Added 2 new view templates for product import/export
+- Enhanced JavaScript with recursive batch processing handlers
+- Added 71 new translation strings (English/Czech)
+- Updated plugin description to reflect product management capabilities
+- All operations use WooCommerce API (no direct SQL)
+
 ### Version 1.3.0
 - **New feature: Delete products in category** - permanently delete all products and variants from selected categories
 - Real-time progress bar for deletion operations

+ 227 - 5
studiou-wc-product-cat-manage/studiou-wc-product-cat-manage.php

@@ -2,8 +2,8 @@
 /**
  * Plugin Name: QDR - Studiou WC Export/Import Product Categories
  * Plugin URI: https://www.quadarax.com/plugins/studiou-wc-product-cat-manage
- * Description: Allows batch import and export WooCommerce product categories
- * Version: 1.3.0
+ * Description: Allows batch import and export WooCommerce product categories and products
+ * Version: 1.4.0
  * Requires at least: 6.8.1
  * Requires PHP: 8.2
  * Author: Dalibor Votruba
@@ -23,7 +23,7 @@ if (!defined('WPINC')) {
 }
 
 // Define plugin constants
-if (!defined('STUDIOU_WCPCM_VERSION')) define('STUDIOU_WCPCM_VERSION', '1.3.0');
+if (!defined('STUDIOU_WCPCM_VERSION')) define('STUDIOU_WCPCM_VERSION', '1.4.0');
 if (!defined('STUDIOU_WCPCM_PLUGIN_DIR')) define('STUDIOU_WCPCM_PLUGIN_DIR', plugin_dir_path(__FILE__));
 if (!defined('STUDIOU_WCPCM_PLUGIN_URL')) define('STUDIOU_WCPCM_PLUGIN_URL', plugin_dir_url(__FILE__));
 if (!defined('STUDIOU_WCPCM_PLUGIN_BASENAME')) define('STUDIOU_WCPCM_PLUGIN_BASENAME', plugin_basename(__FILE__));
@@ -52,6 +52,21 @@ class Studiou_WC_Product_Cat_Manage {
      * @var Studiou_WC_Product_Cat_Manage_Manipulator
      */
     private $manipulator;
+
+    /**
+     * @var Studiou_WC_Product_Manage_Product_DB
+     */
+    private $product_db;
+
+    /**
+     * @var Studiou_WC_Product_Manage_Product_Import
+     */
+    private $product_importer;
+
+    /**
+     * @var Studiou_WC_Product_Manage_Product_Export
+     */
+    private $product_exporter;
     
     /**
      * Initialize the plugin
@@ -97,6 +112,9 @@ class Studiou_WC_Product_Cat_Manage {
         require_once STUDIOU_WCPCM_PLUGIN_DIR . 'includes/class-studiou-wc-product-cat-manage-export.php';
         require_once STUDIOU_WCPCM_PLUGIN_DIR . 'includes/class-studiou-wc-product-cat-manage-import.php';
         require_once STUDIOU_WCPCM_PLUGIN_DIR . 'includes/class-studiou-wc-product-cat-manage-manipulator.php';
+        require_once STUDIOU_WCPCM_PLUGIN_DIR . 'includes/class-studiou-wc-product-manage-product-db.php';
+        require_once STUDIOU_WCPCM_PLUGIN_DIR . 'includes/class-studiou-wc-product-manage-product-import.php';
+        require_once STUDIOU_WCPCM_PLUGIN_DIR . 'includes/class-studiou-wc-product-manage-product-export.php';
     }
     
     /**
@@ -107,6 +125,14 @@ class Studiou_WC_Product_Cat_Manage {
         $this->exporter = new Studiou_WC_Product_Cat_Manage_Export($this->db);
         $this->importer = new Studiou_WC_Product_Cat_Manage_Import($this->db);
         $this->manipulator = new Studiou_WC_Product_Cat_Manage_Manipulator($this->db);
+        $this->product_db = new Studiou_WC_Product_Manage_Product_DB();
+        $this->product_importer = new Studiou_WC_Product_Manage_Product_Import($this->product_db);
+        $this->product_exporter = new Studiou_WC_Product_Manage_Product_Export($this->product_db);
+
+        // Register AJAX handlers
+        add_action('wp_ajax_studiou_wcpcm_product_import_parse', array($this, 'handle_product_import_parse'));
+        add_action('wp_ajax_studiou_wcpcm_product_import_batch', array($this, 'handle_product_import_batch'));
+        add_action('wp_ajax_studiou_wcpcm_product_export', array($this, 'handle_product_export'));
     }
     
     /**
@@ -174,6 +200,24 @@ class Studiou_WC_Product_Cat_Manage {
                 'studiou-category-manipulations',
                 array($this, 'render_manipulations_page')
             );
+
+            add_submenu_page(
+                'edit.php?post_type=product',
+                __('Product Import', 'studiou-wc-product-cat-manage'),
+                __('Product Import', 'studiou-wc-product-cat-manage'),
+                'manage_woocommerce',
+                'studiou-product-import',
+                array($this, 'render_product_import_page')
+            );
+
+            add_submenu_page(
+                'edit.php?post_type=product',
+                __('Product Export', 'studiou-wc-product-cat-manage'),
+                __('Product Export', 'studiou-wc-product-cat-manage'),
+                'manage_woocommerce',
+                'studiou-product-export',
+                array($this, 'render_product_export_page')
+            );
         }
     }
     
@@ -186,9 +230,11 @@ class Studiou_WC_Product_Cat_Manage {
             error_log('STUDIOU WC: Checking admin assets for hook: ' . $hook);
         }
         
-        if ('product_page_studiou-import-product-categories' === $hook || 
+        if ('product_page_studiou-import-product-categories' === $hook ||
             'product_page_studiou-export-product-categories' === $hook ||
-            'product_page_studiou-category-manipulations' === $hook) {
+            'product_page_studiou-category-manipulations' === $hook ||
+            'product_page_studiou-product-import' === $hook ||
+            'product_page_studiou-product-export' === $hook) {
             
             if (defined('WP_DEBUG') && WP_DEBUG) {
                 error_log('STUDIOU WC: Loading admin assets for hook: ' . $hook);
@@ -256,6 +302,182 @@ class Studiou_WC_Product_Cat_Manage {
     public function render_manipulations_page() {
         include STUDIOU_WCPCM_PLUGIN_DIR . 'views/manipulations.php';
     }
+
+    /**
+     * Render product import page
+     */
+    public function render_product_import_page() {
+        include STUDIOU_WCPCM_PLUGIN_DIR . 'views/product-import.php';
+    }
+
+    /**
+     * Render product export page
+     */
+    public function render_product_export_page() {
+        include STUDIOU_WCPCM_PLUGIN_DIR . 'views/product-export.php';
+    }
+
+    /**
+     * Handle product import parse AJAX request (step 1: parse CSV)
+     */
+    public function handle_product_import_parse() {
+        // Clean any output buffers
+        while (ob_get_level()) {
+            ob_end_clean();
+        }
+
+        // Verify nonce
+        if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'studiou-wcpcm-nonce')) {
+            wp_send_json_error(array('message' => __('Security check failed', 'studiou-wc-product-cat-manage')));
+            return;
+        }
+
+        // Check permissions
+        if (!current_user_can('manage_woocommerce')) {
+            wp_send_json_error(array('message' => __('Insufficient permissions', 'studiou-wc-product-cat-manage')));
+            return;
+        }
+
+        // Check if file was uploaded
+        if (!isset($_FILES['import_file']) || $_FILES['import_file']['error'] !== UPLOAD_ERR_OK) {
+            wp_send_json_error(array('message' => __('No file uploaded or upload error', 'studiou-wc-product-cat-manage')));
+            return;
+        }
+
+        try {
+            // Parse CSV and store in transient
+            $result = $this->product_importer->parse_csv_for_batch($_FILES['import_file']['tmp_name']);
+
+            if (isset($result['error'])) {
+                wp_send_json_error(array('message' => $result['error']));
+                return;
+            }
+
+            wp_send_json_success(array(
+                'transient_key' => $result['transient_key'],
+                'total' => $result['total']
+            ));
+
+        } catch (Exception $e) {
+            if (defined('WP_DEBUG') && WP_DEBUG) {
+                error_log('STUDIOU WC Product Import Parse Error: ' . $e->getMessage());
+            }
+            wp_send_json_error(array('message' => $e->getMessage()));
+        }
+    }
+
+    /**
+     * Handle product import batch AJAX request (step 2: process batch)
+     */
+    public function handle_product_import_batch() {
+        // Clean any output buffers
+        while (ob_get_level()) {
+            ob_end_clean();
+        }
+
+        // Verify nonce
+        if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'studiou-wcpcm-nonce')) {
+            wp_send_json_error(array('message' => __('Security check failed', 'studiou-wc-product-cat-manage')));
+            return;
+        }
+
+        // Check permissions
+        if (!current_user_can('manage_woocommerce')) {
+            wp_send_json_error(array('message' => __('Insufficient permissions', 'studiou-wc-product-cat-manage')));
+            return;
+        }
+
+        // Get parameters
+        $transient_key = isset($_POST['transient_key']) ? sanitize_text_field($_POST['transient_key']) : '';
+        $offset = isset($_POST['offset']) ? intval($_POST['offset']) : 0;
+
+        if (empty($transient_key)) {
+            wp_send_json_error(array('message' => __('Invalid request', 'studiou-wc-product-cat-manage')));
+            return;
+        }
+
+        try {
+            // Process batch
+            $result = $this->product_importer->process_batch($transient_key, $offset, 5);
+
+            if (isset($result['error'])) {
+                wp_send_json_error(array('message' => $result['error']));
+                return;
+            }
+
+            // Generate failed CSV if this is the last batch and there are failed items
+            $failed_csv = '';
+            if ($result['processed'] >= $result['total'] && !empty($result['failed_items'])) {
+                $failed_csv = $this->product_importer->generate_failed_csv($result['failed_items']);
+            }
+
+            wp_send_json_success(array(
+                'success' => $result['success'],
+                'failed' => $result['failed'],
+                'skipped' => $result['skipped'],
+                'messages' => $result['messages'],
+                'processed' => $result['processed'],
+                'total' => $result['total'],
+                'failed_csv' => $failed_csv
+            ));
+
+        } catch (Exception $e) {
+            if (defined('WP_DEBUG') && WP_DEBUG) {
+                error_log('STUDIOU WC Product Import Batch Error: ' . $e->getMessage());
+            }
+            wp_send_json_error(array('message' => $e->getMessage()));
+        }
+    }
+
+    /**
+     * Handle product export AJAX request
+     */
+    public function handle_product_export() {
+        // Clean any output buffers
+        while (ob_get_level()) {
+            ob_end_clean();
+        }
+
+        // Verify nonce
+        if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'studiou-wcpcm-nonce')) {
+            wp_send_json_error(array('message' => __('Security check failed', 'studiou-wc-product-cat-manage')));
+            return;
+        }
+
+        // Check permissions
+        if (!current_user_can('manage_woocommerce')) {
+            wp_send_json_error(array('message' => __('Insufficient permissions', 'studiou-wc-product-cat-manage')));
+            return;
+        }
+
+        // Get selected categories
+        $category_ids = isset($_POST['export_categories']) ? array_map('intval', $_POST['export_categories']) : array();
+
+        if (empty($category_ids)) {
+            wp_send_json_error(array('message' => __('No categories selected', 'studiou-wc-product-cat-manage')));
+            return;
+        }
+
+        try {
+            // Generate export file
+            $result = $this->product_exporter->generate_export_file($category_ids);
+
+            if ($result['success']) {
+                wp_send_json_success(array(
+                    'message' => $result['message'],
+                    'download_url' => $result['download_url']
+                ));
+            } else {
+                wp_send_json_error(array('message' => $result['message']));
+            }
+
+        } catch (Exception $e) {
+            if (defined('WP_DEBUG') && WP_DEBUG) {
+                error_log('STUDIOU WC Product Export Error: ' . $e->getMessage());
+            }
+            wp_send_json_error(array('message' => $e->getMessage()));
+        }
+    }
 }
 
 /**

+ 110 - 0
studiou-wc-product-cat-manage/views/product-export.php

@@ -0,0 +1,110 @@
+<?php
+/**
+ * Product Export page view
+ *
+ * @since      1.4.0
+ * @package    Studiou_WC_Product_Cat_Manage
+ * @subpackage Studiou_WC_Product_Cat_Manage/views
+ */
+
+// If this file is called directly, abort.
+if (!defined('WPINC')) {
+    die;
+}
+
+// Get database instance to fetch categories
+require_once STUDIOU_WCPCM_PLUGIN_DIR . 'includes/class-studiou-wc-product-manage-product-db.php';
+$product_db = new Studiou_WC_Product_Manage_Product_DB();
+$categories = $product_db->get_categories_with_hierarchy();
+?>
+
+<div class="wrap studiou-wcpcm-wrap">
+    <h1><?php echo esc_html(__('Product Export', 'studiou-wc-product-cat-manage')); ?></h1>
+
+    <!-- Notice Area -->
+    <div class="studiou-wcpcm-notice-area"></div>
+
+    <!-- Export Form -->
+    <div class="studiou-wcpcm-card">
+        <h2><?php _e('Export Products to CSV', 'studiou-wc-product-cat-manage'); ?></h2>
+
+        <p><?php _e('Select product categories to export. All products in the selected categories will be exported to a CSV file.', 'studiou-wc-product-cat-manage'); ?></p>
+
+        <form id="studiou-wcpcm-product-export-form" method="post">
+            <div class="form-field">
+                <label><?php _e('Select Product Categories', 'studiou-wc-product-cat-manage'); ?></label>
+
+                <div class="studiou-wcpcm-categories-list-container">
+                    <!-- List Actions -->
+                    <div class="studiou-wcpcm-list-actions">
+                        <button type="button" id="studiou-wcpcm-export-select-all" class="button button-secondary">
+                            <?php _e('Select All', 'studiou-wc-product-cat-manage'); ?>
+                        </button>
+                        <button type="button" id="studiou-wcpcm-export-deselect-all" class="button button-secondary">
+                            <?php _e('Deselect All', 'studiou-wc-product-cat-manage'); ?>
+                        </button>
+                    </div>
+
+                    <!-- Categories List -->
+                    <div id="export_categories_list" class="studiou-wcpcm-categories-list">
+                        <?php if (empty($categories)): ?>
+                            <p><?php _e('No product categories found.', 'studiou-wc-product-cat-manage'); ?></p>
+                        <?php else: ?>
+                            <?php foreach ($categories as $category): ?>
+                                <label class="studiou-wcpcm-category-item">
+                                    <input type="checkbox" name="export_categories[]" value="<?php echo esc_attr($category['term_id']); ?>" />
+                                    <span><?php echo esc_html($category['name']); ?> (<?php echo esc_html($category['count']); ?>)</span>
+                                </label>
+                            <?php endforeach; ?>
+                        <?php endif; ?>
+                    </div>
+                </div>
+
+                <p class="description"><?php _e('Categories are shown with product count in parentheses', 'studiou-wc-product-cat-manage'); ?></p>
+            </div>
+
+            <div class="submit-buttons">
+                <button type="submit" id="studiou-wcpcm-product-export-button" class="button button-primary">
+                    <?php _e('Export Selected Products', 'studiou-wc-product-cat-manage'); ?>
+                </button>
+                <span class="spinner"></span>
+            </div>
+        </form>
+
+        <!-- Export Results -->
+        <div class="studiou-wcpcm-product-export-results" style="display: none;">
+            <h3><?php _e('Export Results', 'studiou-wc-product-cat-manage'); ?></h3>
+            <div id="studiou-wcpcm-product-export-message"></div>
+            <a id="studiou-wcpcm-download-product-export" href="#" class="button button-primary" style="margin-top: 10px;">
+                <?php _e('Download Export File', 'studiou-wc-product-cat-manage'); ?>
+            </a>
+        </div>
+    </div>
+
+    <!-- Export Information -->
+    <div class="studiou-wcpcm-card">
+        <h2><?php _e('Export Information', 'studiou-wc-product-cat-manage'); ?></h2>
+        <p><?php _e('The exported CSV file will contain the following columns:', 'studiou-wc-product-cat-manage'); ?></p>
+        <ul>
+            <li><strong>ID</strong> - <?php _e('Product ID', 'studiou-wc-product-cat-manage'); ?></li>
+            <li><strong>Typ</strong> - <?php _e('Product type (variable or variation)', 'studiou-wc-product-cat-manage'); ?></li>
+            <li><strong>Katalogové číslo</strong> - <?php _e('SKU', 'studiou-wc-product-cat-manage'); ?></li>
+            <li><strong>Jméno</strong> - <?php _e('Product name', 'studiou-wc-product-cat-manage'); ?></li>
+            <li><strong>Nadřazené</strong> - <?php _e('Parent product SKU (for variations)', 'studiou-wc-product-cat-manage'); ?></li>
+            <li><strong>Krátký popis</strong> - <?php _e('Short description', 'studiou-wc-product-cat-manage'); ?></li>
+            <li><strong>Popis</strong> - <?php _e('Description', 'studiou-wc-product-cat-manage'); ?></li>
+            <li><strong>Název 1 vlastnosti</strong> - <?php _e('Attribute 1 name', 'studiou-wc-product-cat-manage'); ?></li>
+            <li><strong>Hodnota(y) 1 vlastnosti</strong> - <?php _e('Attribute 1 value(s)', 'studiou-wc-product-cat-manage'); ?></li>
+            <li><strong>Kategorie</strong> - <?php _e('Categories', 'studiou-wc-product-cat-manage'); ?></li>
+            <li><strong>Obrázky</strong> - <?php _e('Image URL', 'studiou-wc-product-cat-manage'); ?></li>
+            <li><strong>Běžná cena</strong> - <?php _e('Regular price', 'studiou-wc-product-cat-manage'); ?></li>
+            <li><strong>Vlastnost 1 viditelnost</strong> - <?php _e('Attribute 1 visibility', 'studiou-wc-product-cat-manage'); ?></li>
+            <li><strong>Vlastnost 1 globální</strong> - <?php _e('Attribute 1 global', 'studiou-wc-product-cat-manage'); ?></li>
+            <li><strong>Vlastnost 1 varianta</strong> - <?php _e('Attribute 1 used for variations', 'studiou-wc-product-cat-manage'); ?></li>
+            <li><strong>Povolit zákaznické recenze?</strong> - <?php _e('Enable reviews', 'studiou-wc-product-cat-manage'); ?></li>
+            <li><strong>Minimum Quantity</strong> - <?php _e('Minimum order quantity', 'studiou-wc-product-cat-manage'); ?></li>
+            <li><strong>Maximum Quantity</strong> - <?php _e('Maximum order quantity', 'studiou-wc-product-cat-manage'); ?></li>
+        </ul>
+        <p><?php _e('The exported file can be modified and re-imported using the Product Import feature.', 'studiou-wc-product-cat-manage'); ?></p>
+    </div>
+</div>

+ 108 - 0
studiou-wc-product-cat-manage/views/product-import.php

@@ -0,0 +1,108 @@
+<?php
+/**
+ * Product Import page view
+ *
+ * @since      1.4.0
+ * @package    Studiou_WC_Product_Cat_Manage
+ * @subpackage Studiou_WC_Product_Cat_Manage/views
+ */
+
+// If this file is called directly, abort.
+if (!defined('WPINC')) {
+    die;
+}
+?>
+
+<div class="wrap studiou-wcpcm-wrap">
+    <h1><?php echo esc_html(__('Product Import', 'studiou-wc-product-cat-manage')); ?></h1>
+
+    <!-- Notice Area -->
+    <div class="studiou-wcpcm-notice-area"></div>
+
+    <!-- Import Form -->
+    <div class="studiou-wcpcm-card">
+        <h2><?php _e('Import Products from CSV', 'studiou-wc-product-cat-manage'); ?></h2>
+
+        <p><?php _e('Upload a CSV file to import products. The CSV file must follow the required format with the following columns:', 'studiou-wc-product-cat-manage'); ?></p>
+
+        <ul>
+            <li><strong>ID</strong> - <?php _e('Product ID (0 for new products)', 'studiou-wc-product-cat-manage'); ?></li>
+            <li><strong>Typ</strong> - <?php _e('Product type (only "variable" or "variation" allowed)', 'studiou-wc-product-cat-manage'); ?></li>
+            <li><strong>Katalogové číslo</strong> - <?php _e('SKU (must be unique)', 'studiou-wc-product-cat-manage'); ?></li>
+            <li><strong>Jméno</strong> - <?php _e('Product name', 'studiou-wc-product-cat-manage'); ?></li>
+            <li><strong>Nadřazené</strong> - <?php _e('Parent product SKU (for variations only)', 'studiou-wc-product-cat-manage'); ?></li>
+            <li><strong>Krátký popis</strong> - <?php _e('Short description', 'studiou-wc-product-cat-manage'); ?></li>
+            <li><strong>Popis</strong> - <?php _e('Description', 'studiou-wc-product-cat-manage'); ?></li>
+            <li><strong>Název 1 vlastnosti</strong> - <?php _e('Attribute 1 name', 'studiou-wc-product-cat-manage'); ?></li>
+            <li><strong>Hodnota(y) 1 vlastnosti</strong> - <?php _e('Attribute 1 value(s) (pipe-separated)', 'studiou-wc-product-cat-manage'); ?></li>
+            <li><strong>Kategorie</strong> - <?php _e('Categories (pipe-separated)', 'studiou-wc-product-cat-manage'); ?></li>
+            <li><strong>Obrázky</strong> - <?php _e('Image URL (must be valid)', 'studiou-wc-product-cat-manage'); ?></li>
+            <li><strong>Běžná cena</strong> - <?php _e('Regular price (required for variations)', 'studiou-wc-product-cat-manage'); ?></li>
+            <li><strong>Vlastnost 1 viditelnost</strong> - <?php _e('Attribute 1 visibility (1 or 0)', 'studiou-wc-product-cat-manage'); ?></li>
+            <li><strong>Vlastnost 1 globální</strong> - <?php _e('Attribute 1 global (1 or 0)', 'studiou-wc-product-cat-manage'); ?></li>
+            <li><strong>Vlastnost 1 varianta</strong> - <?php _e('Attribute 1 used for variations (1 or 0)', 'studiou-wc-product-cat-manage'); ?></li>
+            <li><strong>Povolit zákaznické recenze?</strong> - <?php _e('Enable reviews (1 or 0)', 'studiou-wc-product-cat-manage'); ?></li>
+            <li><strong>Minimum Quantity</strong> - <?php _e('Minimum order quantity', 'studiou-wc-product-cat-manage'); ?></li>
+            <li><strong>Maximum Quantity</strong> - <?php _e('Maximum order quantity', 'studiou-wc-product-cat-manage'); ?></li>
+        </ul>
+
+        <form id="studiou-wcpcm-product-import-form" method="post" enctype="multipart/form-data">
+            <div class="form-field">
+                <label for="import_file"><?php _e('Select Import File', 'studiou-wc-product-cat-manage'); ?></label>
+                <input type="file" id="import_file" name="import_file" accept=".csv" required />
+                <p class="description"><?php _e('Select a CSV file to import (UTF-8 encoding)', 'studiou-wc-product-cat-manage'); ?></p>
+            </div>
+
+            <div class="submit-buttons">
+                <button type="submit" id="studiou-wcpcm-product-import-button" class="button button-primary">
+                    <?php _e('Process Import File', 'studiou-wc-product-cat-manage'); ?>
+                </button>
+                <span class="spinner"></span>
+            </div>
+        </form>
+
+        <!-- Progress bar -->
+        <div class="studiou-wcpcm-product-import-progress" style="display: none;">
+            <h3><?php _e('Import Progress', 'studiou-wc-product-cat-manage'); ?></h3>
+            <div class="studiou-wcpcm-progress-bar-container">
+                <div id="studiou-wcpcm-product-import-progress-bar" class="studiou-wcpcm-progress-bar" style="width: 0%;" data-progress="0%"></div>
+            </div>
+            <div id="studiou-wcpcm-product-import-progress-text" class="studiou-wcpcm-progress-text">
+                <?php _e('Initializing...', 'studiou-wc-product-cat-manage'); ?>
+            </div>
+        </div>
+
+        <!-- Import Results -->
+        <div class="studiou-wcpcm-product-import-results" style="display: none;">
+            <h3><?php _e('Import Results', 'studiou-wc-product-cat-manage'); ?></h3>
+            <div id="studiou-wcpcm-product-import-message"></div>
+
+            <div id="studiou-wcpcm-product-import-details" style="margin-top: 20px;">
+                <h4><?php _e('Import Details', 'studiou-wc-product-cat-manage'); ?></h4>
+                <div id="studiou-wcpcm-product-import-details-content"></div>
+            </div>
+        </div>
+
+        <!-- Save Failed Products Button -->
+        <div class="studiou-wcpcm-product-import-failed" style="display: none; margin-top: 20px;">
+            <button id="studiou-wcpcm-save-failed-products" class="button button-secondary">
+                <?php _e('Save Failed Products', 'studiou-wc-product-cat-manage'); ?>
+            </button>
+            <p class="description"><?php _e('Download a CSV file with failed products to fix and re-import', 'studiou-wc-product-cat-manage'); ?></p>
+        </div>
+    </div>
+
+    <!-- Import Rules -->
+    <div class="studiou-wcpcm-card">
+        <h2><?php _e('Import Rules', 'studiou-wc-product-cat-manage'); ?></h2>
+        <ul>
+            <li><strong><?php _e('Rule 1:', 'studiou-wc-product-cat-manage'); ?></strong> <?php _e('All operations are performed via WooCommerce API (not direct SQL)', 'studiou-wc-product-cat-manage'); ?></li>
+            <li><strong><?php _e('Rule 2:', 'studiou-wc-product-cat-manage'); ?></strong> <?php _e('If ID = 0, item will be created; otherwise updated', 'studiou-wc-product-cat-manage'); ?></li>
+            <li><strong><?php _e('Rule 3:', 'studiou-wc-product-cat-manage'); ?></strong> <?php _e('Only "variable" or "variation" types are allowed', 'studiou-wc-product-cat-manage'); ?></li>
+            <li><strong><?php _e('Rule 4:', 'studiou-wc-product-cat-manage'); ?></strong> <?php _e('SKU (Katalogové číslo) must be unique', 'studiou-wc-product-cat-manage'); ?></li>
+            <li><strong><?php _e('Rule 5:', 'studiou-wc-product-cat-manage'); ?></strong> <?php _e('Image URL must be valid and accessible', 'studiou-wc-product-cat-manage'); ?></li>
+            <li><strong><?php _e('Rule 6:', 'studiou-wc-product-cat-manage'); ?></strong> <?php _e('Parent reference (Nadřazené) must point to an existing variable product SKU', 'studiou-wc-product-cat-manage'); ?></li>
+            <li><strong><?php _e('Rule 7:', 'studiou-wc-product-cat-manage'); ?></strong> <?php _e('Variations must have a regular price (Běžná cena)', 'studiou-wc-product-cat-manage'); ?></li>
+        </ul>
+    </div>
+</div>