Commit f3feeb81b91 for woocommerce

commit f3feeb81b9170d76cb10032f78c9a13c7511bd60
Author: Chris Lilitsas <1105590+xristos3490@users.noreply.github.com>
Date:   Wed Jul 15 11:34:08 2026 +0300

    Fix CSV import dropping gallery images after a leading empty value (#66586)

    * Fix CSV import dropping gallery images after a leading empty value

    * Add changelog entry for CSV import gallery fix

    * fix: treat separators-only images cell as empty to preserve gallery

diff --git a/plugins/woocommerce/changelog/66583-fix-csv-import-leading-empty-gallery-image b/plugins/woocommerce/changelog/66583-fix-csv-import-leading-empty-gallery-image
new file mode 100644
index 00000000000..74c2cdb791e
--- /dev/null
+++ b/plugins/woocommerce/changelog/66583-fix-csv-import-leading-empty-gallery-image
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Import gallery images from a CSV Images cell whose first value is empty (e.g. a value starting with a separator) instead of silently dropping them and leaving the previous gallery in place.
diff --git a/plugins/woocommerce/includes/import/class-wc-product-csv-importer.php b/plugins/woocommerce/includes/import/class-wc-product-csv-importer.php
index 0e506672e9b..4d2c76e1e0e 100644
--- a/plugins/woocommerce/includes/import/class-wc-product-csv-importer.php
+++ b/plugins/woocommerce/includes/import/class-wc-product-csv-importer.php
@@ -884,14 +884,24 @@ class WC_Product_CSV_Importer extends WC_Product_Importer {
 		if ( isset( $data['images'] ) ) {
 			$images               = $data['images'];
 			$data['raw_image_id'] = array_shift( $images );
+			$gallery              = array_filter(
+				$images,
+				function ( $image ) {
+					return '' !== $image;
+				}
+			);

-			// When a featured image is provided, treat the remaining values as the full
+			// When any image value is provided, treat the remaining values as the full
 			// gallery. Setting the key even when no gallery images remain ensures that
 			// reducing the number of images in the CSV clears previously imported gallery
-			// images instead of leaving them in place.
-			// See https://github.com/woocommerce/woocommerce/issues/34839.
-			if ( ! empty( $data['raw_image_id'] ) ) {
-				$data['raw_gallery_image_ids'] = $images;
+			// images instead of leaving them in place, while a fully empty images cell
+			// (including one containing only separators) still leaves existing images
+			// untouched. Gating on the gallery values too keeps them imported when the
+			// featured-image slot is empty (e.g. a cell starting with a separator).
+			// See https://github.com/woocommerce/woocommerce/issues/34839
+			// and https://github.com/woocommerce/woocommerce/issues/66583.
+			if ( ! empty( $data['raw_image_id'] ) || ! empty( $gallery ) ) {
+				$data['raw_gallery_image_ids'] = $gallery;
 			}
 			unset( $data['images'] );
 		}
diff --git a/plugins/woocommerce/tests/legacy/unit-tests/importer/product.php b/plugins/woocommerce/tests/legacy/unit-tests/importer/product.php
index 0254d59ed8c..33436c6dbc9 100644
--- a/plugins/woocommerce/tests/legacy/unit-tests/importer/product.php
+++ b/plugins/woocommerce/tests/legacy/unit-tests/importer/product.php
@@ -245,7 +245,7 @@ class WC_Tests_Product_CSV_Importer extends WC_Unit_Test_Case {
 		$product->set_gallery_image_ids( array( $gallery_id ) );
 		$product->save();

-		$importer = new WC_Product_CSV_Importer( $this->csv_file, array( 'parse' => false ) );
+		$importer = $this->get_importer();
 		$expanded = $this->invoke_protected( $importer, 'expand_data', array( array( 'images' => array( $new_url ) ) ) );
 		$this->invoke_protected( $importer, 'set_image_data', array( &$product, $expanded ) );

@@ -268,7 +268,7 @@ class WC_Tests_Product_CSV_Importer extends WC_Unit_Test_Case {
 		$product->set_gallery_image_ids( array( $gallery_id ) );
 		$product->save();

-		$importer = new WC_Product_CSV_Importer( $this->csv_file, array( 'parse' => false ) );
+		$importer = $this->get_importer();
 		$expanded = $this->invoke_protected( $importer, 'expand_data', array( array( 'images' => array() ) ) );
 		$this->invoke_protected( $importer, 'set_image_data', array( &$product, $expanded ) );

@@ -276,6 +276,53 @@ class WC_Tests_Product_CSV_Importer extends WC_Unit_Test_Case {
 		$this->assertEquals( array( $gallery_id ), $product->get_gallery_image_ids(), 'Existing gallery should be preserved.' );
 	}

+	/**
+	 * @testdox A leading empty value in the images column still imports the remaining gallery images.
+	 *
+	 * Regression test for https://github.com/woocommerce/woocommerce/issues/66583: an Images cell
+	 * like ",gallery.jpg" parses to an empty featured-image value followed by gallery URLs. The
+	 * gallery URLs must still be imported (and replace the existing gallery) even though the
+	 * featured-image slot is empty.
+	 */
+	public function test_leading_empty_image_value_still_imports_gallery_images() {
+		$gallery_url = 'http://example.com/gallery.jpg';
+		$gallery_id  = $this->create_sourced_attachment( $gallery_url );
+		$stale_id    = $this->create_sourced_attachment( 'http://example.com/stale.jpg' );
+
+		$product = WC_Helper_Product::create_simple_product();
+		$product->set_image_id( $this->create_sourced_attachment( 'http://example.com/featured.jpg' ) );
+		$product->set_gallery_image_ids( array( $stale_id ) );
+		$product->save();
+
+		$importer = $this->get_importer();
+		$expanded = $this->invoke_protected( $importer, 'expand_data', array( array( 'images' => array( '', $gallery_url ) ) ) );
+		$this->invoke_protected( $importer, 'set_image_data', array( &$product, $expanded ) );
+
+		$this->assertEmpty( $product->get_image_id(), 'An empty featured-image value should clear the featured image.' );
+		$this->assertEquals( array( $gallery_id ), $product->get_gallery_image_ids(), 'Gallery images after a leading empty value should replace the existing gallery.' );
+	}
+
+	/**
+	 * @testdox An images cell containing only separators leaves the existing gallery untouched.
+	 *
+	 * An Images cell like "," parses to an array of empty strings. It carries no image values,
+	 * so it must behave like a fully empty cell and not wipe the existing gallery.
+	 */
+	public function test_separators_only_images_cell_preserves_existing_gallery() {
+		$gallery_id = $this->create_sourced_attachment( 'http://example.com/gallery.jpg' );
+
+		$product = WC_Helper_Product::create_simple_product();
+		$product->set_image_id( $this->create_sourced_attachment( 'http://example.com/featured.jpg' ) );
+		$product->set_gallery_image_ids( array( $gallery_id ) );
+		$product->save();
+
+		$importer = $this->get_importer();
+		$expanded = $this->invoke_protected( $importer, 'expand_data', array( array( 'images' => array( '', '' ) ) ) );
+		$this->invoke_protected( $importer, 'set_image_data', array( &$product, $expanded ) );
+
+		$this->assertEquals( array( $gallery_id ), $product->get_gallery_image_ids(), 'A separators-only images cell should leave the existing gallery untouched.' );
+	}
+
 	/**
 	 * Create an attachment whose source URL is recorded, so the importer can resolve it by URL.
 	 *