Commit 3f9911b15b9 for woocommerce
commit 3f9911b15b9db6e19371ab058d31ee70588eb739
Author: EazyHood <jhonatandelrio9@gmail.com>
Date: Tue Oct 20 08:54:27 2026 -0500
Fix automatic mapping of exported product GTIN columns (#69947)
* Fix automatic mapping of exported product GTIN columns
* Add changelog entry for CSV identifier mapping
* Add tests for importing rows with a duplicated GTIN
---------
Co-authored-by: Ján Mikláš <neosinner@gmail.com>
diff --git a/plugins/woocommerce/changelog/fix-csv-gtin-auto-mapping b/plugins/woocommerce/changelog/fix-csv-gtin-auto-mapping
new file mode 100644
index 00000000000..f0df23ea68d
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-csv-gtin-auto-mapping
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Automatically match the GTIN, UPC, EAN, or ISBN column when importing exported product CSV files.
diff --git a/plugins/woocommerce/includes/admin/importers/class-wc-product-csv-importer-controller.php b/plugins/woocommerce/includes/admin/importers/class-wc-product-csv-importer-controller.php
index 386b56e7ec4..3ec84e250f8 100644
--- a/plugins/woocommerce/includes/admin/importers/class-wc-product-csv-importer-controller.php
+++ b/plugins/woocommerce/includes/admin/importers/class-wc-product-csv-importer-controller.php
@@ -778,6 +778,7 @@ class WC_Product_CSV_Importer_Controller {
__( 'ID', 'woocommerce' ) => 'id',
__( 'Type', 'woocommerce' ) => 'type',
__( 'SKU', 'woocommerce' ) => 'sku',
+ __( 'GTIN, UPC, EAN, or ISBN', 'woocommerce' ) => 'global_unique_id',
__( 'Name', 'woocommerce' ) => 'name',
__( 'Published', 'woocommerce' ) => 'published',
__( 'Is featured?', 'woocommerce' ) => 'featured',
diff --git a/plugins/woocommerce/includes/admin/importers/mappings/default.php b/plugins/woocommerce/includes/admin/importers/mappings/default.php
index ba06f1f4494..df0977f036d 100644
--- a/plugins/woocommerce/includes/admin/importers/mappings/default.php
+++ b/plugins/woocommerce/includes/admin/importers/mappings/default.php
@@ -44,6 +44,7 @@ function wc_importer_default_english_mappings( $mappings ) {
'ID' => 'id',
'Type' => 'type',
'SKU' => 'sku',
+ 'GTIN, UPC, EAN, or ISBN' => 'global_unique_id',
'Name' => 'name',
'Published' => 'published',
'Is featured?' => 'featured',
diff --git a/plugins/woocommerce/tests/php/includes/admin/importers/class-wc-product-csv-importer-controller-test.php b/plugins/woocommerce/tests/php/includes/admin/importers/class-wc-product-csv-importer-controller-test.php
index 165bc606a3e..e7c1d84064b 100644
--- a/plugins/woocommerce/tests/php/includes/admin/importers/class-wc-product-csv-importer-controller-test.php
+++ b/plugins/woocommerce/tests/php/includes/admin/importers/class-wc-product-csv-importer-controller-test.php
@@ -63,6 +63,41 @@ class WC_Product_CSV_Importer_Controller_Test extends WC_Unit_Test_Case {
);
}
+ /**
+ * @testdox Should map exported global unique ID headers in English and translated stores.
+ * @testWith ["en_US", "GTIN, UPC, EAN, or ISBN"]
+ * ["es_ES", "GTIN, UPC, EAN o ISBN"]
+ * ["es_ES", "GTIN, UPC, EAN, or ISBN"]
+ *
+ * @param string $locale Store locale.
+ * @param string $header CSV column header.
+ */
+ public function test_auto_mapping_global_unique_id( string $locale, string $header ): void {
+ wp_set_current_user( 0 );
+ add_filter(
+ 'locale',
+ static function () use ( $locale ) {
+ return $locale;
+ }
+ );
+ add_filter(
+ 'gettext_woocommerce',
+ static function ( $translation, $text ) use ( $locale ) {
+ return 'es_ES' === $locale && 'GTIN, UPC, EAN, or ISBN' === $text ? 'GTIN, UPC, EAN o ISBN' : $translation;
+ },
+ 10,
+ 2
+ );
+
+ $sut = new WC_Product_CSV_Importer_Controller();
+ // Mappings are included once, but the test framework restores hooks between datasets.
+ add_filter( 'woocommerce_csv_product_import_mapping_default_columns', 'wc_importer_default_english_mappings', 100 );
+ $method = new ReflectionMethod( WC_Product_CSV_Importer_Controller::class, 'auto_map_columns' );
+ $method->setAccessible( true );
+
+ $this->assertSame( array( 'global_unique_id' ), $method->invoke( $sut, array( $header ) ), 'The exported identifier column should be selected for import.' );
+ }
+
/**
* @testdox Should URL-encode request-derived values in the next step link so special characters like '+' survive the round trip.
*/
diff --git a/plugins/woocommerce/tests/php/includes/importer/class-wc-product-csv-importer-test.php b/plugins/woocommerce/tests/php/includes/importer/class-wc-product-csv-importer-test.php
index a415d333ec1..a05c2aec2b2 100644
--- a/plugins/woocommerce/tests/php/includes/importer/class-wc-product-csv-importer-test.php
+++ b/plugins/woocommerce/tests/php/includes/importer/class-wc-product-csv-importer-test.php
@@ -442,6 +442,85 @@ class WC_Product_CSV_Importer_Test extends \WC_Unit_Test_Case {
WC_Helper_Product::delete_product( $product->get_id() );
}
+ /**
+ * @testdox A new product row whose GTIN belongs to another product fails without creating a product.
+ */
+ public function test_import_fails_new_product_rows_with_a_duplicated_global_unique_id() {
+ $existing = WC_Helper_Product::create_simple_product();
+ $existing->set_global_unique_id( '0012345678905' );
+ $existing->save();
+
+ $csv_file = trailingslashit( get_temp_dir() ) . 'import-duplicated-global-unique-id-new.csv';
+ file_put_contents( $csv_file, "SKU,\"GTIN, UPC, EAN, or ISBN\",Name\nnew-sku,0012345678905,Copied product\n" ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents -- Test fixture written to the temp dir.
+
+ $importer = new WC_Product_CSV_Importer(
+ $csv_file,
+ array(
+ 'parse' => true,
+ 'mapping' => array(
+ 'SKU' => 'sku',
+ 'GTIN, UPC, EAN, or ISBN' => 'global_unique_id',
+ 'Name' => 'name',
+ ),
+ )
+ );
+ $data = $importer->import();
+
+ wp_delete_file( $csv_file );
+
+ $this->assertEmpty( $data['imported'], 'A row with a duplicated GTIN should not create a product.' );
+ $this->assertCount( 1, $data['failed'] );
+ $this->assertSame( 'Invalid or duplicated GTIN, UPC, EAN or ISBN.', $data['failed'][0]->get_error_message() );
+ $this->assertSame( 0, wc_get_product_id_by_sku( 'new-sku' ), 'No product should be saved for the failed row.' );
+ $this->assertSame( $existing->get_id(), wc_get_product_id_by_global_unique_id( '0012345678905' ), 'The GTIN should stay with the original product.' );
+
+ WC_Helper_Product::delete_product( $existing->get_id() );
+ }
+
+ /**
+ * @testdox Updating a product with another product's GTIN fails the row and leaves the product unchanged.
+ */
+ public function test_import_fails_update_rows_with_a_duplicated_global_unique_id() {
+ $owner = WC_Helper_Product::create_simple_product();
+ $owner->set_global_unique_id( '0012345678905' );
+ $owner->save();
+
+ $target = WC_Helper_Product::create_simple_product();
+ $target->set_name( 'Original name' );
+ $target->save();
+
+ $csv_file = trailingslashit( get_temp_dir() ) . 'import-duplicated-global-unique-id-update.csv';
+ file_put_contents( $csv_file, sprintf( "ID,\"GTIN, UPC, EAN, or ISBN\",Name\n%d,0012345678905,Updated name\n", $target->get_id() ) ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents -- Test fixture written to the temp dir.
+
+ $importer = new WC_Product_CSV_Importer(
+ $csv_file,
+ array(
+ 'parse' => true,
+ 'update_existing' => true,
+ 'mapping' => array(
+ 'ID' => 'id',
+ 'GTIN, UPC, EAN, or ISBN' => 'global_unique_id',
+ 'Name' => 'name',
+ ),
+ )
+ );
+ $data = $importer->import();
+
+ wp_delete_file( $csv_file );
+
+ $this->assertEmpty( $data['updated'] );
+ $this->assertCount( 1, $data['failed'] );
+ $this->assertSame( 'Invalid or duplicated GTIN, UPC, EAN or ISBN.', $data['failed'][0]->get_error_message() );
+
+ $target = wc_get_product( $target->get_id() );
+ $this->assertSame( 'Original name', $target->get_name(), 'Other fields in the failed row should not be saved.' );
+ $this->assertSame( '', $target->get_global_unique_id() );
+ $this->assertSame( $owner->get_id(), wc_get_product_id_by_global_unique_id( '0012345678905' ) );
+
+ WC_Helper_Product::delete_product( $owner->get_id() );
+ WC_Helper_Product::delete_product( $target->get_id() );
+ }
+
/**
* @testdox A Global Unique ID that matches a variable product fails a row whose blank Type defaults to simple.
*/