Commit fafb0960ab2 for woocommerce
commit fafb0960ab2325534583502a0d588bb49ecc807c
Author: SH Sajal Chowdhury <72102985+shsajalchowdhury@users.noreply.github.com>
Date: Thu Jul 23 22:05:24 2026 +0600
Fix: Prevent auto-including variations in product export when type filter excludes them (#66292)
* Fix: Prevent auto-including variations in export when type filter excludes them (#53155)
When exporting products with a type filter set to 'variable' only,
variations were still auto-included if a category filter was also active.
Now variations are only auto-exported when no type filter is set or when
the type filter explicitly includes variation type.
* Add test
* Simplify logic
---------
Co-authored-by: Albert Juhé Lluveras <contact@albertjuhe.com>
diff --git a/plugins/woocommerce/changelog/fix-53155-export-variable-products-includes-variations b/plugins/woocommerce/changelog/fix-53155-export-variable-products-includes-variations
new file mode 100644
index 00000000000..8673e83766c
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-53155-export-variable-products-includes-variations
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Prevent variations from being automatically included in product exports when a specific type filter (e.g. variable only) is set.
diff --git a/plugins/woocommerce/includes/export/class-wc-product-csv-exporter.php b/plugins/woocommerce/includes/export/class-wc-product-csv-exporter.php
index 1cceaf2ca53..45903b00e55 100644
--- a/plugins/woocommerce/includes/export/class-wc-product-csv-exporter.php
+++ b/plugins/woocommerce/includes/export/class-wc-product-csv-exporter.php
@@ -225,14 +225,16 @@ class WC_Product_CSV_Exporter extends WC_CSV_Batch_Exporter {
$products = wc_get_products( $args );
- $this->total_rows = $products->total;
- $this->row_data = array();
- $variable_products = array();
+ $this->total_rows = $products->total;
+ $this->row_data = array();
+ $variable_products = array();
+ $include_variations = ! isset( $args['type'] ) || in_array( ProductType::VARIATION, (array) $args['type'], true );
foreach ( $products->products as $product ) {
// Check if the product is variable and if either the include or category filter is active.
// This is to ensure that product variations are only included if they are being selectively exported or if they are part of a category.
- if ( ( ! empty( $args['include'] ) || ! empty( $args['category'] ) ) &&
+ if ( $include_variations &&
+ ( ! empty( $args['include'] ) || ! empty( $args['category'] ) ) &&
$product->is_type( ProductType::VARIABLE ) &&
! in_array( $product->get_id(), $variable_products, true ) ) {
$variable_products[] = $product->get_id();
diff --git a/plugins/woocommerce/tests/php/includes/exporter/class-wc-product-csv-exporter-test.php b/plugins/woocommerce/tests/php/includes/exporter/class-wc-product-csv-exporter-test.php
index 7d9e5bc4aeb..57454ccc2b1 100644
--- a/plugins/woocommerce/tests/php/includes/exporter/class-wc-product-csv-exporter-test.php
+++ b/plugins/woocommerce/tests/php/includes/exporter/class-wc-product-csv-exporter-test.php
@@ -6,6 +6,7 @@
*/
use Automattic\WooCommerce\Enums\ProductStatus;
+use Automattic\WooCommerce\Enums\ProductType;
/**
* Class WC_Product_CSV_Exporter_Test
@@ -40,6 +41,41 @@ class WC_Product_CSV_Exporter_Test extends \WC_Unit_Test_Case {
return $args;
}
+ /**
+ * Get prepared export row data via reflection.
+ *
+ * @param WC_Product_CSV_Exporter $exporter Exporter instance.
+ * @return array
+ */
+ private function get_exported_data( WC_Product_CSV_Exporter $exporter ): array {
+ $reflected_exporter = new ReflectionClass( WC_Product_CSV_Exporter::class );
+ $get_data_to_export = $reflected_exporter->getMethod( 'get_data_to_export' );
+ $get_data_to_export->setAccessible( true );
+
+ return $get_data_to_export->invoke( $exporter );
+ }
+
+ /**
+ * Create a variable product assigned to a product category.
+ *
+ * @return array{product: WC_Product_Variable, category_slug: string}
+ */
+ private function create_categorized_variation_product(): array {
+ $term = wp_insert_term( 'Export Test Category', 'product_cat' );
+ $this->assertIsArray( $term, 'Failed to create product category for export test.' );
+
+ $product = WC_Helper_Product::create_variation_product();
+ $product->set_category_ids( array( $term['term_id'] ) );
+ $product->save();
+
+ $category = get_term( $term['term_id'], 'product_cat' );
+
+ return array(
+ 'product' => $product,
+ 'category_slug' => $category->slug,
+ );
+ }
+
/**
* @testdox variations should use draft status from parent product
*/
@@ -48,25 +84,21 @@ class WC_Product_CSV_Exporter_Test extends \WC_Unit_Test_Case {
$product->set_status( ProductStatus::DRAFT );
$product->save();
- $reflected_exporter = new ReflectionClass( WC_Product_CSV_Exporter::class );
- $get_data_to_export = $reflected_exporter->getMethod( 'get_data_to_export' );
- $get_data_to_export->setAccessible( true );
-
$this->product_ids = array_merge( array( $product->get_id() ), $product->get_children( 'edit' ) );
add_filter( 'woocommerce_product_export_product_query_args', array( $this, 'set_export_product_query_args' ) );
- // Required for brands to be registered because wc-admin-brands.php adds a filter that depends on it.
- WC_Brands::init_taxonomy();
-
- $exporter = new WC_Product_CSV_Exporter();
- $exporter->prepare_data_to_export();
- $data = $get_data_to_export->invoke( $exporter );
+ try {
+ $exporter = new WC_Product_CSV_Exporter();
+ $exporter->prepare_data_to_export();
+ $data = $this->get_exported_data( $exporter );
- foreach ( $data as $row ) {
- $this->assertEquals( -1, $row['published'] );
+ foreach ( $data as $row ) {
+ $this->assertEquals( -1, $row['published'] );
+ }
+ } finally {
+ remove_filter( 'woocommerce_product_export_product_query_args', array( $this, 'set_export_product_query_args' ) );
}
- remove_filter( 'woocommerce_product_export_product_query_args', array( $this, 'set_export_product_query_args' ) );
}
/**
@@ -77,10 +109,6 @@ class WC_Product_CSV_Exporter_Test extends \WC_Unit_Test_Case {
$product->set_status( ProductStatus::PENDING );
$product->save();
- $reflected_exporter = new ReflectionClass( WC_Product_CSV_Exporter::class );
- $get_data_to_export = $reflected_exporter->getMethod( 'get_data_to_export' );
- $get_data_to_export->setAccessible( true );
-
$this->product_ids = array( $product->get_id() );
add_filter( 'woocommerce_product_export_product_query_args', array( $this, 'set_export_product_query_args' ) );
@@ -88,7 +116,7 @@ class WC_Product_CSV_Exporter_Test extends \WC_Unit_Test_Case {
try {
$exporter = new WC_Product_CSV_Exporter();
$exporter->prepare_data_to_export();
- $data = $get_data_to_export->invoke( $exporter );
+ $data = $this->get_exported_data( $exporter );
$this->assertNotEmpty( $data, 'Pending review product should be included in the export.' );
foreach ( $data as $row ) {
@@ -98,4 +126,39 @@ class WC_Product_CSV_Exporter_Test extends \WC_Unit_Test_Case {
remove_filter( 'woocommerce_product_export_product_query_args', array( $this, 'set_export_product_query_args' ) );
}
}
+
+ /**
+ * @testdox exporting variable products with a category filter should not auto-include variations.
+ *
+ * See: https://github.com/woocommerce/woocommerce/issues/53155
+ */
+ public function test_category_export_with_variable_type_excludes_variations(): void {
+ $fixture = $this->create_categorized_variation_product();
+ $product = $fixture['product'];
+
+ $exporter = new WC_Product_CSV_Exporter();
+ $exporter->set_product_types_to_export( array( ProductType::VARIABLE ) );
+ $exporter->set_product_category_to_export( array( $fixture['category_slug'] ) );
+ $exporter->prepare_data_to_export();
+
+ $exported_ids = wp_list_pluck( $this->get_exported_data( $exporter ), 'id' );
+
+ $this->assertContains(
+ $product->get_id(),
+ $exported_ids,
+ 'Variable parent should be included when exporting variable products by category.'
+ );
+ $this->assertCount(
+ 1,
+ $exported_ids,
+ 'Only the variable parent should be exported when variation is excluded from the type filter.'
+ );
+ foreach ( $product->get_children( 'edit' ) as $variation_id ) {
+ $this->assertNotContains(
+ $variation_id,
+ $exported_ids,
+ 'Variations should not be auto-included when the type filter is variable only.'
+ );
+ }
+ }
}