Commit 67d039b5455 for woocommerce
commit 67d039b5455f81b53a027b4a37f6ce1e98e3d3ec
Author: Michal Iwanow <4765119+mcliwanow@users.noreply.github.com>
Date: Wed Nov 4 13:50:38 2026 +0100
Restrict the product CSV importer to recognised product fields (#70631)
process_item() now passes only the product's data keys to set_props().
Co-authored-by: Chris Lilitsas <1105590+xristos3490@users.noreply.github.com>
diff --git a/plugins/woocommerce/changelog/fix-product-import-column-mapping b/plugins/woocommerce/changelog/fix-product-import-column-mapping
new file mode 100644
index 00000000000..e023a23e8f5
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-product-import-column-mapping
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Improve validation of the column mapping used by the product CSV importer.
diff --git a/plugins/woocommerce/includes/import/abstract-wc-product-importer.php b/plugins/woocommerce/includes/import/abstract-wc-product-importer.php
index 78a5d6dbee4..11e14657104 100644
--- a/plugins/woocommerce/includes/import/abstract-wc-product-importer.php
+++ b/plugins/woocommerce/includes/import/abstract-wc-product-importer.php
@@ -184,7 +184,23 @@ abstract class WC_Product_Importer implements WC_Importer_Interface {
try {
// Prevent getting "variation_invalid_id" error message from Variation Data Store.
if ( ProductType::VARIATION === $data['type'] ) {
- $was_variation = $id && 'product_variation' === get_post_type( $id );
+ $post_type = $id ? get_post_type( $id ) : false;
+
+ // The wp_update_post() below rewrites the post type, so refuse an ID that is
+ // not already a product or variation to avoid converting an arbitrary post.
+ if ( $post_type && ! in_array( $post_type, array( 'product', 'product_variation' ), true ) ) {
+ return new WP_Error(
+ 'woocommerce_product_csv_importer_invalid_id',
+ /* translators: %d: product ID */
+ sprintf( __( 'Invalid product ID %d.', 'woocommerce' ), $id ),
+ array(
+ 'id' => $id,
+ 'status' => 401,
+ )
+ );
+ }
+
+ $was_variation = 'product_variation' === $post_type;
$id = wp_update_post(
array(
@@ -378,7 +394,7 @@ abstract class WC_Product_Importer implements WC_Importer_Interface {
unset( $data['cogs_value'] );
}
- $result = $object->set_props( array_diff_key( $data, array_flip( array( 'meta_data', 'raw_image_id', 'raw_gallery_image_ids', 'raw_attributes' ) ) ) );
+ $result = $object->set_props( array_intersect_key( $data, array_flip( $object->get_data_keys() ) ) );
if ( is_wp_error( $result ) ) {
throw new Exception( $result->get_error_message() );
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 5f925f4d2ff..a9f504170a9 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
@@ -3198,4 +3198,77 @@ class WC_Product_CSV_Importer_Test extends \WC_Unit_Test_Case {
$this->assertSame( array( 'Premium', 'Basic' ), $product->get_attributes()['edition']->get_options() );
$this->assertSame( array( 'edition' => 'Premium' ), $product->get_default_attributes() );
}
+
+ /**
+ * @testdox Only recognised product fields from the column mapping are applied to the imported product.
+ * @testWith ["post_type"]
+ * ["post_author"]
+ * ["ID"]
+ * ["unknown_prop"]
+ *
+ * @param string $key Unrecognised key the first column is mapped to.
+ */
+ public function test_import_applies_only_recognised_product_fields( string $key ): void {
+ $page_id = self::factory()->post->create(
+ array(
+ 'post_type' => 'page',
+ 'post_title' => 'About us',
+ )
+ );
+
+ $file = get_temp_dir() . 'product-import-' . wp_generate_uuid4() . '.csv';
+ file_put_contents( $file, "target,name\n{$page_id},Imported name\n" ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents -- Temporary CSV fixture.
+
+ try {
+ $importer = new WC_Product_CSV_Importer(
+ $file,
+ array(
+ 'parse' => true,
+ 'update_existing' => false,
+ 'mapping' => array(
+ 'target' => $key,
+ 'name' => 'name',
+ ),
+ )
+ );
+ $data = $importer->import();
+ } finally {
+ wp_delete_file( $file );
+ }
+
+ $this->assertCount( 1, $data['imported'], 'The row should import as its own product' );
+ $this->assertEmpty( $data['failed'] );
+ $this->assertNotContains( $page_id, $data['imported'], 'The imported product should not reuse the page ID' );
+ $this->assertSame( 'About us', get_the_title( $page_id ), 'The page title should be unchanged' );
+ $this->assertSame( 'page', get_post_type( $page_id ), 'The page should keep its post type' );
+ }
+
+ /**
+ * @testdox get_product_object() only treats an existing product or variation as the variation to update.
+ */
+ public function test_get_product_object_requires_product_id_for_variation(): void {
+ $page_id = self::factory()->post->create( array( 'post_type' => 'page' ) );
+ $importer = new class( __DIR__ . '/sample.csv' ) extends WC_Product_CSV_Importer {
+ /**
+ * Expose the protected method under test.
+ *
+ * @param array $data Row data.
+ * @return WC_Product|WP_Error
+ */
+ public function product_object_for( array $data ) {
+ return $this->get_product_object( $data );
+ }
+ };
+
+ $result = $importer->product_object_for(
+ array(
+ 'type' => ProductType::VARIATION,
+ 'id' => $page_id,
+ )
+ );
+
+ $this->assertWPError( $result );
+ $this->assertSame( 'woocommerce_product_csv_importer_invalid_id', $result->get_error_code() );
+ $this->assertSame( 'page', get_post_type( $page_id ), 'The page should keep its post type' );
+ }
}