Commit 169672a256b for woocommerce

commit 169672a256b54c47a20726ab06fe9d40bd4ec321
Author: Vladimir Reznichenko <kalessil@gmail.com>
Date:   Tue Sep 22 06:18:34 2026 +0200

    [Performance] Enable optimized variations price hashing for new stores by default (#68910)

diff --git a/plugins/woocommerce/changelog/performance-66883-disable-legacy-variable-products-price-hashing b/plugins/woocommerce/changelog/performance-66883-disable-legacy-variable-products-price-hashing
new file mode 100644
index 00000000000..6d73a064deb
--- /dev/null
+++ b/plugins/woocommerce/changelog/performance-66883-disable-legacy-variable-products-price-hashing
@@ -0,0 +1,4 @@
+Significance: minor
+Type: performance
+
+Enabled optimized variations price hashing for new stores by default.
diff --git a/plugins/woocommerce/includes/class-wc-install.php b/plugins/woocommerce/includes/class-wc-install.php
index b72653019d5..d4b6233ce15 100644
--- a/plugins/woocommerce/includes/class-wc-install.php
+++ b/plugins/woocommerce/includes/class-wc-install.php
@@ -362,6 +362,7 @@ class WC_Install {
 		),
 		'11.3.0'   => array(
 			'wc_update_1130_repair_hpos_order_dates_from_posts',
+			'wc_update_1130_set_legacy_variation_price_hash_option',
 		),
 	);

@@ -404,6 +405,7 @@ class WC_Install {
 		add_action( 'woocommerce_newly_installed', array( __CLASS__, 'enable_customer_stock_notifications_signups' ), 20 );
 		add_action( 'woocommerce_newly_installed', array( __CLASS__, 'enable_analytics_scheduled_import' ), 20 );
 		add_action( 'woocommerce_newly_installed', array( __CLASS__, 'enable_product_instance_caching_for_newly_installed' ), 20 );
+		add_action( 'woocommerce_newly_installed', array( __CLASS__, 'disable_legacy_variation_price_hash_for_newly_installed' ), 20 );
 		add_action( 'woocommerce_updated', array( __CLASS__, 'enable_email_improvements_for_existing_merchants' ), 20 );
 		add_action( 'woocommerce_run_update_callback', array( __CLASS__, 'run_update_callback' ) );
 		add_action( 'woocommerce_update_db_to_current_version', array( __CLASS__, 'update_db_version' ) );
@@ -1376,6 +1378,17 @@ class WC_Install {
 		$feature_controller->change_feature_enable( ProductCacheController::FEATURE_NAME, true );
 	}

+	/**
+	 * Disable the legacy variations price hash algorithm for new stores.
+	 *
+	 * @since 11.3.0
+	 *
+	 * @return void
+	 */
+	public static function disable_legacy_variation_price_hash_for_newly_installed(): void {
+		add_option( 'woocommerce_use_legacy_get_variations_price_hash', 'no', '', true );
+	}
+
 	/**
 	 * Enable email improvements by default for existing shops if conditions are met.
 	 *
diff --git a/plugins/woocommerce/includes/data-stores/class-wc-product-variable-data-store-cpt.php b/plugins/woocommerce/includes/data-stores/class-wc-product-variable-data-store-cpt.php
index 8d11ae54915..c771eda9880 100644
--- a/plugins/woocommerce/includes/data-stores/class-wc-product-variable-data-store-cpt.php
+++ b/plugins/woocommerce/includes/data-stores/class-wc-product-variable-data-store-cpt.php
@@ -669,13 +669,13 @@ class WC_Product_Variable_Data_Store_CPT extends WC_Product_Data_Store_CPT imple
 			);
 		}

-		$filter_names = array( 'woocommerce_variation_prices_price', 'woocommerce_variation_prices_regular_price', 'woocommerce_variation_prices_sale_price' );
+		$filter_names         = array( 'woocommerce_variation_prices_price', 'woocommerce_variation_prices_regular_price', 'woocommerce_variation_prices_sale_price' );
+		$use_legacy_algorithm = 'yes' === get_option( 'woocommerce_use_legacy_get_variations_price_hash', 'yes' );

 		/**
 		 * Filters whether to use the legacy callback serialization algorithm.
 		 *
-		 * By default, WooCommerce will use the legacy algorithm to get the callback signatures
-		 * for variation price hash calculation. That algorithm includes the callback array as it
+		 * The legacy algorithm for variation price hash calculation includes the callback array as it
 		 * comes from $wp_filter in the hashed data, which is then JSON encoded. For callbacks that
 		 * are class methods, JSON encoding captures the object's PUBLIC property values only;
 		 * private and protected properties are not captured. Note that dynamically created
@@ -690,13 +690,15 @@ class WC_Product_Variable_Data_Store_CPT extends WC_Product_Data_Store_CPT imple
 		 *
 		 * IMPORTANT: see also the documentation for the 'woocommerce_variation_prices_price' filter.
 		 *
-		 * @since 10.5.0
+		 * @since 10.5.0 the hook is introduced and all stores use the legacy algorith by default.
+		 * @since 11.3.0 the new stores use optimized algorithm by default.
 		 *
-		 * @param bool       $use_legacy  True to use the legacy algorithm (default), false to use CallbackUtil
-		 * @param WC_Product $product     The product object.
-		 * @param bool       $for_display If taxes should be calculated or not.
+		 * @param bool       $use_legacy_algorithm True to use the legacy algorithm, false to use CallbackUtil.
+		 * @param WC_Product $product              The product object.
+		 * @param bool       $for_display          If taxes should be calculated or not.
+		 * @return bool
 		 */
-		$use_legacy_algorithm = apply_filters( 'woocommerce_use_legacy_get_variations_price_hash', true, $product, $for_display );
+		$use_legacy_algorithm = (bool) apply_filters( 'woocommerce_use_legacy_get_variations_price_hash', $use_legacy_algorithm, $product, $for_display );

 		if ( $use_legacy_algorithm ) {
 			global $wp_filter;
diff --git a/plugins/woocommerce/includes/wc-update-functions.php b/plugins/woocommerce/includes/wc-update-functions.php
index 43b066bd32a..b7d19919e03 100644
--- a/plugins/woocommerce/includes/wc-update-functions.php
+++ b/plugins/woocommerce/includes/wc-update-functions.php
@@ -4171,3 +4171,14 @@ function wc_update_1130_repair_hpos_order_dates_from_posts() {

 	return false;
 }
+
+/**
+ * Persist the legacy variation price hash option for existing stores so get_option returns an explicit value.
+ *
+ * @return void
+ */
+function wc_update_1130_set_legacy_variation_price_hash_option() {
+	if ( false === get_option( 'woocommerce_use_legacy_get_variations_price_hash' ) ) {
+		add_option( 'woocommerce_use_legacy_get_variations_price_hash', 'yes', '', true );
+	}
+}
diff --git a/plugins/woocommerce/tests/php/includes/data-stores/class-wc-product-variable-data-store-cpt-test.php b/plugins/woocommerce/tests/php/includes/data-stores/class-wc-product-variable-data-store-cpt-test.php
index 31ff1ca3a1d..284d1e6215d 100644
--- a/plugins/woocommerce/tests/php/includes/data-stores/class-wc-product-variable-data-store-cpt-test.php
+++ b/plugins/woocommerce/tests/php/includes/data-stores/class-wc-product-variable-data-store-cpt-test.php
@@ -1,4 +1,4 @@
-<?php
+<?php declare( strict_types = 1 );

 use Automattic\WooCommerce\Enums\ProductStatus;
 use Automattic\WooCommerce\Enums\ProductStockStatus;
@@ -773,10 +773,25 @@ class WC_Product_Variable_Data_Store_CPT_Test extends WC_Unit_Test_Case {
 	}

 	/**
-	 * @testdox get_price_hash includes callback signatures via CallbackUtil when the legacy algorithm is disabled.
+	 * Data provider for falsy values that should disable the legacy algorithm.
 	 */
-	public function test_get_price_hash_uses_callback_util_when_legacy_algorithm_is_disabled(): void {
-		add_filter( 'woocommerce_use_legacy_get_variations_price_hash', '__return_false' );
+	public function woocommerce_use_legacy_get_variations_price_hash_disabled_values_provider(): array {
+		return array(
+			'false'        => array( false ),
+			'zero'         => array( 0 ),
+			'empty_string' => array( '' ),
+			'empty_array'  => array( array() ),
+		);
+	}
+
+	/**
+	 * @testdox get_price_hash includes callback signatures via CallbackUtil when the filter returns $falsy_value.
+	 * @dataProvider woocommerce_use_legacy_get_variations_price_hash_disabled_values_provider
+	 * @param mixed $falsy_value The falsy value to inject via the filter.
+	 */
+	public function test_get_price_hash_uses_callback_util_when_legacy_algorithm_is_disabled( $falsy_value ): void {
+		$filter = fn() => $falsy_value;
+		add_filter( 'woocommerce_use_legacy_get_variations_price_hash', $filter );

 		$product             = WC_Helper_Product::create_variation_product();
 		$extended_data_store = $this->get_data_store_with_public_get_price_hash();
@@ -793,7 +808,7 @@ class WC_Product_Variable_Data_Store_CPT_Test extends WC_Unit_Test_Case {
 		$this->assertNotSame( $hash_without_callback, $hash_with_callback );

 		remove_filter( 'woocommerce_variation_prices_price', $callback );
-		remove_filter( 'woocommerce_use_legacy_get_variations_price_hash', '__return_false' );
+		remove_filter( 'woocommerce_use_legacy_get_variations_price_hash', $filter );

 		$product->delete();
 	}
@@ -2212,4 +2227,36 @@ class WC_Product_Variable_Data_Store_CPT_Test extends WC_Unit_Test_Case {
 		$this->assertSame( self::$product_id, $logger->errors[0][1]['product_id'] );
 		$this->assertSame( array(), $result, 'A failed read defers every variation rather than promoting it.' );
 	}
+
+	/**
+	 * @testdox get_price_hash respects the woocommerce_use_legacy_get_variations_price_hash option.
+	 */
+	public function test_get_price_hash_respects_legacy_price_hash_option(): void {
+		$product = WC_Helper_Product::create_variation_product();
+
+		$captured       = null;
+		$capture_filter = function ( $use_legacy ) use ( &$captured ) {
+			$captured = $use_legacy;
+			return $use_legacy;
+		};
+		add_filter( 'woocommerce_use_legacy_get_variations_price_hash', $capture_filter );
+
+		// Option 'yes' → legacy enabled → filter receives true.
+		update_option( 'woocommerce_use_legacy_get_variations_price_hash', 'yes' );
+		$this->get_data_store_with_public_get_price_hash()->get_price_hash( $product, false );
+		$this->assertTrue( $captured, 'Option "yes" must dispatch true (legacy enabled) into the filter.' );
+
+		// Option 'no' → legacy disabled → filter receives false.
+		update_option( 'woocommerce_use_legacy_get_variations_price_hash', 'no' );
+		$this->get_data_store_with_public_get_price_hash()->get_price_hash( $product, false );
+		$this->assertFalse( $captured, 'Option "no" must dispatch false (legacy disabled) into the filter.' );
+
+		delete_option( 'woocommerce_use_legacy_get_variations_price_hash' );
+		$this->get_data_store_with_public_get_price_hash()->get_price_hash( $product, false );
+		$this->assertTrue( $captured, 'Missing option must dispatch true (legacy enabled) into the filter.' );
+
+		remove_filter( 'woocommerce_use_legacy_get_variations_price_hash', $capture_filter );
+
+		$product->delete();
+	}
 }
diff --git a/plugins/woocommerce/tests/php/includes/wc-update-functions-test.php b/plugins/woocommerce/tests/php/includes/wc-update-functions-test.php
index d83e01e5576..3a7c144b8f0 100644
--- a/plugins/woocommerce/tests/php/includes/wc-update-functions-test.php
+++ b/plugins/woocommerce/tests/php/includes/wc-update-functions-test.php
@@ -1024,4 +1024,23 @@ class WC_Update_Functions_Test extends \WC_Unit_Test_Case {

 		$this->assertSame( array( 'First@Example.com', 'Second@Example.com' ), $emails, 'No row should be rewritten after the write fails' );
 	}
+
+	/**
+	 * @testdox wc_update_1130_set_legacy_variation_price_hash_option sets the option when absent and skips when already set.
+	 */
+	public function test_wc_update_1130_set_legacy_variation_price_hash_option(): void {
+		include_once WC_ABSPATH . 'includes/wc-update-functions.php';
+
+		// When the option does not exist, the migration must create it with 'yes'.
+		delete_option( 'woocommerce_use_legacy_get_variations_price_hash' );
+		wc_update_1130_set_legacy_variation_price_hash_option();
+		$this->assertSame( 'yes', get_option( 'woocommerce_use_legacy_get_variations_price_hash' ), 'Migration must set "yes" for existing stores.' );
+
+		// When the option already exists (e.g. new store set to 'no'), the migration must not overwrite it.
+		update_option( 'woocommerce_use_legacy_get_variations_price_hash', 'no' );
+		wc_update_1130_set_legacy_variation_price_hash_option();
+		$this->assertSame( 'no', get_option( 'woocommerce_use_legacy_get_variations_price_hash' ), 'Migration must not overwrite an existing option.' );
+
+		delete_option( 'woocommerce_use_legacy_get_variations_price_hash' );
+	}
 }