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.'
+			);
+		}
+	}
 }