I’m building a bulk upload feature in WordPress for WooCommerce. The challenge I’m facing is with importing product images.
The image data comes from a CSV file, where each row represents a product.
The images column contains one or more image filenames, separated by a pipe (|) character (e.g., image1.jpg|image2.jpg).
These images are included in a ZIP folder uploaded alongside the CSV.
The goal is to match each filename in the CSV with its corresponding file from the ZIP, then upload and attach those images to the WooCommerce product.
Below is the function that processes the CSV and creates products. I’d appreciate help refining this code to better handle the image import logic and ensure best practices.
public function bulkUpload($csvBase64,$bId)
{
// Accept CSV as base64 string from POST body
$csvBase64 = isset($_POST['csvBase64']) ? $_POST['csvBase64'] : null;
$imagesDir = func_num_args() > 2 ? func_get_arg(2) : null;
if (!$csvBase64) {
return [
'success' => false,
'message' => 'No CSV file provided',
'code' => 400,
'data' => [],
];
}
// Decode base64
$csvData = base64_decode($csvBase64);
if ($csvData === false) {
return [
'success' => false,
'message' => 'Failed to decode CSV',
'code' => 400,
'data' => [],
];
}
$rows = array_map('str_getcsv', preg_split('/rn|n|r/', $csvData));
if (count($rows) < 2) {
return [
'success' => false,
'message' => 'CSV must have at least one data row',
'code' => 400,
'data' => [],
];
}
$header = array_map('trim', $rows[0]);
$productsCreated = [];
$errors = [];
for ($i = 1; $i < count($rows); $i++) {
$row = $rows[$i];
if (count($row) < count($header)) {
continue; // skip incomplete rows
}
$data = array_combine($header, $row);
error_log("BulkUpload row $i images: " . (isset($data['images']) ? $data['images'] : 'NONE'));
// Required fields: title, price
$title = isset($data['title']) ? sanitize_text_field($data['title']) : '';
$regularPrice = isset($data['regular_price']) ? $data['regular_price'] : '';
$salePrice = isset($data['sale_price']) ? $data['sale_price'] : '';
$description = isset($data['description']) ? sanitize_text_field($data['description']) : '';
$condition = isset($data['condition']) ? sanitize_text_field($data['condition']) : '';
$categories = isset($data['categories']) ? explode('|', $data['categories']) : [];
$post_status = isset($data['post_status']) ? sanitize_text_field($data['post_status']) : '';
$availability = isset($data['availability']) ? sanitize_text_field($data['availability']) : '';
$quantity = isset($data['quantity']) ? $data['quantity'] : 0;
$backOrders = isset($data['backOrders']) ? $data['backOrders'] : 0;
$product = new WC_Product_Simple();
$product->set_name($title);
$product->set_description($description);
$product->set_status('publish');
if ($availability === false) {
$product->set_stock_status('outofstock');
} else {
$product->set_stock_status('instock');
}
$post_data = [
'post_author' => get_current_user_id(),
'post_title' => $title,
'post_type' => 'product',
'post_status' => 'publish',
];
$product->set_stock_quantity((int)$quantity);
$product->set_backorders($backOrders);
$product->set_manage_stock(true);
$post_id = wp_insert_post($post_data);
$product->set_id($post_id);
if (!empty($categories)) {
$product->set_category_ids($categories);
}
$product->set_sale_price($salePrice);
$product->set_regular_price($regularPrice);
$productId = $product->save();
update_post_meta($productId, 'bID', $bId);
update_post_meta($productId, '_condition', $condition);
$productsCreated[] = $productId;
// Handle attribute_name_X and new_attribute_name_X pairs
$attributesArray = [];
foreach ($data as $key => $value) {
if (preg_match('/^(attribute_name_|new_attribute_name_)(d+)$/', $key, $matches)) {
$index = $matches[2];
$attrName = sanitize_text_field($value);
$attrValueKey1 = 'attribute_value_' . $index;
$attrValueKey2 = 'new_attribute_value_' . $index;
if (isset($data[$attrValueKey1])) {
$attrValue = sanitize_text_field($data[$attrValueKey1]);
} elseif (isset($data[$attrValueKey2])) {
$attrValue = sanitize_text_field($data[$attrValueKey2]);
} else {
continue;
}
$attributesArray[] = [
'name' => $attrName,
'value' => $attrValue,
];
}
}
// Set all attributes at once
$attributes = [];
foreach ($attributesArray as $attribute) {
$taxonomy = sanitize_title($attribute['name']);
$attributeObj = new WC_Product_Attribute();
$attributeObj->set_name($taxonomy);
$attributeObj->set_position(0);
$attributeObj->set_options([$attribute['value']]);
$attributeObj->set_visible(true);
$attributeObj->set_variation(false);
$attributes[$taxonomy] = $attributeObj;
}
if (!empty($attributes)) {
$product->set_attributes($attributes);
$product->save();
}
// Handle images from zip
if ($imagesDir && isset($data['images']) && !empty($data['images'])) {
$imageFiles = explode('|', $data['images']); // e.g. "img1.jpg|img2.png
$images = [];
foreach ($imageFiles as $fileName) {
$filePath = $imagesDir . '/' . trim($fileName);
if (file_exists($filePath)) {
$base64 = 'data:image/' . pathinfo($filePath, PATHINFO_EXTENSION) . ';base64,' . base64_encode(file_get_contents($filePath));
$images[] = ['url' => $base64];
}
}
if (!empty($images)) {
$this->uploadImages($images, $productId, true);
}
}
}
return [
'success' => true,
'message' => 'Bulk upload completed',
'code' => 200,
'data' => [
'products' => $productsCreated,
'errors' => $errors,
],
];
}