Commit 7ddffb082c7 for woocommerce
commit 7ddffb082c7a5f7a97b3e8376dd51e025b7f7de3
Author: Chris Lilitsas <1105590+xristos3490@users.noreply.github.com>
Date: Fri Jul 24 12:31:39 2026 +0300
Fix CSV re-imports deleting terms attached to existing variations (#66946)
* Stop CSV re-imports deleting terms attached to existing variations
The variation-placeholder cleanup added for issue #31815 ran
unconditionally for every variation row with a valid ID, so re-imports
with update_existing enabled stripped product_cat/product_tag terms
attached to pre-existing variations (e.g. by an extension). Only delete
terms when the post was just converted into a variation.
* Add changelog entry for variation re-import term deletion fix
diff --git a/plugins/woocommerce/changelog/66940-fix-csv-import-variation-term-deletion b/plugins/woocommerce/changelog/66940-fix-csv-import-variation-term-deletion
new file mode 100644
index 00000000000..30ecb4f7f1b
--- /dev/null
+++ b/plugins/woocommerce/changelog/66940-fix-csv-import-variation-term-deletion
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Preserve category and tag terms attached to existing variations when re-importing them via CSV with "update existing" enabled, instead of unconditionally deleting them on every variation row.
diff --git a/plugins/woocommerce/includes/import/abstract-wc-product-importer.php b/plugins/woocommerce/includes/import/abstract-wc-product-importer.php
index a30d06361ab..aede47e4730 100644
--- a/plugins/woocommerce/includes/import/abstract-wc-product-importer.php
+++ b/plugins/woocommerce/includes/import/abstract-wc-product-importer.php
@@ -184,6 +184,8 @@ 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 );
+
$id = wp_update_post(
array(
'ID' => $id,
@@ -195,8 +197,11 @@ abstract class WC_Product_Importer implements WC_Importer_Interface {
// simple product, so the product data store may have assigned it
// the default product category. Variations must not carry
// product_cat/product_tag terms, and WordPress does not clear
- // them when the post type changes, so remove them here.
- if ( $id && ! is_wp_error( $id ) ) {
+ // them when the post type changes, so remove them here — but
+ // only when the post was just converted into a variation.
+ // Pre-existing variations (e.g. re-imports with "update
+ // existing" enabled) must keep any terms attached to them.
+ if ( $id && ! is_wp_error( $id ) && ! $was_variation ) {
wp_delete_object_term_relationships( $id, array( 'product_cat', 'product_tag' ) );
}
}
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 fc1ba774760..e56e4985db9 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
@@ -317,6 +317,67 @@ class WC_Product_CSV_Importer_Test extends \WC_Unit_Test_Case {
}
}
+ /**
+ * @testdox Re-importing an existing variation with update_existing enabled preserves taxonomy terms attached to it.
+ */
+ public function test_reimporting_existing_variation_preserves_attached_terms() {
+ // Term creation during import requires the manage_product_terms capability.
+ wp_set_current_user( self::factory()->user->create( array( 'role' => 'administrator' ) ) );
+
+ $imported_ids = array();
+ $term_id = 0;
+
+ try {
+ // Build the header-to-field mapping the way the admin import UI does.
+ $csv_file = __DIR__ . '/variation-category-31815.csv';
+ $headers = ( new WC_Product_CSV_Importer( $csv_file, array( 'parse' => false ) ) )->get_raw_keys();
+ $controller = new WC_Product_CSV_Importer_Controller();
+ $auto_map = new ReflectionMethod( $controller, 'auto_map_columns' );
+ $auto_map->setAccessible( true );
+ $mapping = array_combine( $headers, $auto_map->invoke( $controller, $headers ) );
+
+ $data = ( new WC_Product_CSV_Importer(
+ $csv_file,
+ array(
+ 'parse' => true,
+ 'mapping' => $mapping,
+ )
+ ) )->import();
+
+ $imported_ids = array_merge( $data['imported'], $data['imported_variations'] );
+ $this->assertCount( 2, $data['imported_variations'], 'Expected 2 variations to be imported.' );
+
+ // Simulate an extension attaching a category to an existing variation.
+ $inserted = wp_insert_term( 'Variation Extension Category', 'product_cat' );
+ $term_id = is_wp_error( $inserted ) ? (int) $inserted->get_error_data( 'term_exists' ) : $inserted['term_id'];
+ $variation_id = $data['imported_variations'][0];
+ wp_set_object_terms( $variation_id, array( $term_id ), 'product_cat' );
+
+ $update = ( new WC_Product_CSV_Importer(
+ $csv_file,
+ array(
+ 'parse' => true,
+ 'mapping' => $mapping,
+ 'update_existing' => true,
+ )
+ ) )->import();
+
+ $this->assertContains( $variation_id, $update['updated'], 'Expected the existing variation to be updated by the re-import.' );
+ $this->assertSame(
+ array( $term_id ),
+ wp_get_object_terms( $variation_id, 'product_cat', array( 'fields' => 'ids' ) ),
+ 'Terms attached to an existing variation must survive a re-import that updates it.'
+ );
+ } finally {
+ foreach ( $imported_ids as $id ) {
+ WC_Helper_Product::delete_product( $id );
+ }
+ if ( $term_id ) {
+ wp_delete_term( $term_id, 'product_cat' );
+ }
+ }
+ }
+
/**
* @testdox parse_float_field should respect the store's decimal separator setting (issue #38116).
* @dataProvider provider_parse_float_field_decimal_separator