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; } }