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.
 	 */