Commit 0145034204f for woocommerce

commit 0145034204f9c23576c09d2efab14d650bc5a5c9
Author: Bogdan Ungureanu <bogdanungureanu21@gmail.com>
Date:   Fri Sep 18 12:59:04 2026 +0300

    Revert "Fix missing shipping rates when extensions remove free shipping" (#68854)

    Revert "Fix missing shipping rates when extensions remove free shipping (#68707)"

    This reverts commit b922f99f2354c29620c37ee9225c8cb743c1fc31.

diff --git a/plugins/woocommerce/changelog/63300-hide-rates-when-free-shipping-filtered b/plugins/woocommerce/changelog/63300-hide-rates-when-free-shipping-filtered
deleted file mode 100644
index c9965bd0cac..00000000000
--- a/plugins/woocommerce/changelog/63300-hide-rates-when-free-shipping-filtered
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: fix
-
-Apply "Hide shipping rates when free shipping is available" after the woocommerce_package_rates filter, so extensions that remove free shipping no longer leave the customer with no rates.
diff --git a/plugins/woocommerce/includes/class-wc-shipping-rate.php b/plugins/woocommerce/includes/class-wc-shipping-rate.php
index 1e79770d548..c49b44e27ca 100644
--- a/plugins/woocommerce/includes/class-wc-shipping-rate.php
+++ b/plugins/woocommerce/includes/class-wc-shipping-rate.php
@@ -16,9 +16,6 @@ use Automattic\WooCommerce\Enums\ProductTaxStatus;

 /**
  * Shipping rate class.
- *
- * @property string $id        Shipping rate ID.
- * @property string $method_id Shipping method ID.
  */
 class WC_Shipping_Rate implements JsonSerializable {

diff --git a/plugins/woocommerce/includes/class-wc-shipping.php b/plugins/woocommerce/includes/class-wc-shipping.php
index 6bcd3fafc53..1c9e335f90e 100644
--- a/plugins/woocommerce/includes/class-wc-shipping.php
+++ b/plugins/woocommerce/includes/class-wc-shipping.php
@@ -358,27 +358,7 @@ class WC_Shipping {
 				}
 			}

-			/**
-			 * Filter the calculated shipping rates.
-			 *
-			 * @see https://gist.github.com/woogists/271654709e1d27648546e83253c1a813 for cache invalidation methods.
-			 * @since 2.0.0
-			 * @param array $package['rates'] Package rates.
-			 * @param array $package Package of cart items.
-			 */
-			$package['rates'] = apply_filters( 'woocommerce_package_rates', $package['rates'], $package );
-
-			// Package rates should be an array, if it was filtered into a non-array, reset it. Don't reset to the
-			// unfiltered value, as e.g. a 3pd could have set it to "false" to remove rates.
-			if ( ! is_array( $package['rates'] ) ) {
-				$package['rates'] = array();
-			}
-
-			$package['rates'] = array_filter( $package['rates'], static fn( $rate ) => $rate instanceof WC_Shipping_Rate );
-
-			// Hide shipping rates when free shipping is available. Runs after the woocommerce_package_rates filter
-			// so that free shipping only counts as available if it survived filtering; otherwise an extension that
-			// removes free shipping would leave the customer with no rates at all.
+			// Hide shipping rates when free shipping is available.
 			if ( 'yes' === get_option( 'woocommerce_shipping_hide_rates_when_free', 'no' ) ) {
 				$free_shipping = array();
 				$local_pickup  = array();
@@ -389,10 +369,7 @@ class WC_Shipping {
 						continue;
 					}

-					// The rate may have been added by a filter callback, so its method is not necessarily registered here.
-					$rate_method = $this->shipping_methods[ $rate->method_id ] ?? null;
-
-					if ( 'local_pickup' === $rate->method_id || ( $rate_method && $rate_method->supports( 'local-pickup' ) ) ) {
+					if ( $this->shipping_methods[ $rate->method_id ]->supports( 'local-pickup' ) || 'local_pickup' === $rate->method_id ) {
 						$local_pickup[ $rate->id ] = $rate;
 					}
 				}
@@ -402,6 +379,22 @@ class WC_Shipping {
 				}
 			}

+			/**
+			 * Filter the calculated shipping rates.
+			 *
+			 * @see https://gist.github.com/woogists/271654709e1d27648546e83253c1a813 for cache invalidation methods.
+			 * @since 2.0.0
+			 * @param array $package['rates'] Package rates.
+			 * @param array $package Package of cart items.
+			 */
+			$package['rates'] = apply_filters( 'woocommerce_package_rates', $package['rates'], $package );
+
+			// Package rates should be an array, if it was filtered into a non-array, reset it. Don't reset to the
+			// unfiltered value, as e.g. a 3pd could have set it to "false" to remove rates.
+			if ( ! is_array( $package['rates'] ) ) {
+				$package['rates'] = array();
+			}
+
 			// Store in session to avoid recalculation.
 			WC()->session->set(
 				$wc_session_key,
diff --git a/plugins/woocommerce/phpstan-baseline.neon b/plugins/woocommerce/phpstan-baseline.neon
index 0c2f7717aab..11cc1713ef3 100644
--- a/plugins/woocommerce/phpstan-baseline.neon
+++ b/plugins/woocommerce/phpstan-baseline.neon
@@ -14911,6 +14911,12 @@ parameters:
 			count: 1
 			path: includes/class-wc-shipping.php

+		-
+			message: '#^Offset mixed might not exist on array\|null\.$#'
+			identifier: offsetAccess.notFound
+			count: 1
+			path: includes/class-wc-shipping.php
+
 		-
 			message: '#^One or more @param tags has an invalid name or invalid syntax\.$#'
 			identifier: phpDoc.parseError
diff --git a/plugins/woocommerce/tests/php/includes/class-wc-shipping-test.php b/plugins/woocommerce/tests/php/includes/class-wc-shipping-test.php
index afd60123b46..535bf37d22e 100644
--- a/plugins/woocommerce/tests/php/includes/class-wc-shipping-test.php
+++ b/plugins/woocommerce/tests/php/includes/class-wc-shipping-test.php
@@ -42,9 +42,8 @@ class WC_Shipping_Test extends WC_Unit_Test_Case {
 	 * @param string $option_value Option value for woocommerce_shipping_hide_rates_when_free.
 	 * @param array  $shipping_methods Available shipping methods.
 	 * @param array  $expected_rates Expected rates.
-	 * @param array  $hidden_rates Rates expected to be hidden.
 	 */
-	public function test_calculate_shipping_for_hide_rates_when_free( string $option_value, array $shipping_methods, array $expected_rates, array $hidden_rates ) {
+	public function test_calculate_shipping_for_hide_rates_when_free( string $option_value, array $shipping_methods, array $expected_rates ) {
 		update_option( 'woocommerce_shipping_hide_rates_when_free', $option_value );

 		$shipping_methods_hook = fn () => $shipping_methods;
@@ -67,100 +66,7 @@ class WC_Shipping_Test extends WC_Unit_Test_Case {
 			$this->assertArrayHasKey( $rate, $result['rates'] );
 		}

-		foreach ( $hidden_rates as $rate ) {
-			$this->assertArrayNotHasKey( $rate, $result['rates'] );
-		}
-
-		remove_action( 'woocommerce_shipping_methods', $shipping_methods_hook );
-	}
-
-	/**
-	 * @testdox paid rates stay visible when the package rates filter removes free shipping.
-	 */
-	public function test_hide_rates_when_free_respects_free_shipping_removed_by_filter() {
-		update_option( 'woocommerce_shipping_hide_rates_when_free', 'yes' );
-
-		$shipping_methods_hook = fn () => array( new WC_Shipping_Flat_Rate( 1 ), new WC_Shipping_Free_Shipping( 1 ) );
-		$remove_free_shipping  = function ( $rates ) {
-			unset( $rates['free_shipping:1'] );
-			return $rates;
-		};
-
-		add_action( 'woocommerce_shipping_methods', $shipping_methods_hook );
-		add_filter( 'woocommerce_package_rates', $remove_free_shipping );
-
-		$result = $this->sut->calculate_shipping_for_package( $this->get_hide_rates_test_package() );
-
-		remove_filter( 'woocommerce_package_rates', $remove_free_shipping );
 		remove_action( 'woocommerce_shipping_methods', $shipping_methods_hook );
-
-		$this->assertArrayHasKey( 'flat_rate:1', $result['rates'], 'Paid rates should remain when a filter removes free shipping.' );
-	}
-
-	/**
-	 * @testdox a paid rate added by the package rates filter for an unregistered method is hidden without errors.
-	 */
-	public function test_hide_rates_when_free_handles_filter_added_rate_for_unregistered_method() {
-		update_option( 'woocommerce_shipping_hide_rates_when_free', 'yes' );
-
-		$shipping_methods_hook = fn () => array( new WC_Shipping_Free_Shipping( 1 ) );
-		$add_unregistered_rate = function ( $rates ) {
-			$rates['custom_carrier:1'] = new WC_Shipping_Rate( 'custom_carrier:1', 'Custom Carrier', 5, array(), 'custom_carrier' );
-			return $rates;
-		};
-
-		add_action( 'woocommerce_shipping_methods', $shipping_methods_hook );
-		add_filter( 'woocommerce_package_rates', $add_unregistered_rate );
-
-		$result = $this->sut->calculate_shipping_for_package( $this->get_hide_rates_test_package() );
-
-		remove_filter( 'woocommerce_package_rates', $add_unregistered_rate );
-		remove_action( 'woocommerce_shipping_methods', $shipping_methods_hook );
-
-		$this->assertSame(
-			array( 'free_shipping:1' ),
-			array_keys( $result['rates'] ),
-			'Free shipping should hide a paid rate added by a filter for a method that is not registered.'
-		);
-	}
-
-	/**
-	 * @testdox filtered rates that are not WC_Shipping_Rate instances are removed.
-	 *
-	 * @dataProvider provide_invalid_filtered_rates
-	 * @param mixed $invalid_rate Invalid filtered rate.
-	 */
-	public function test_calculate_shipping_rejects_invalid_filtered_rates( $invalid_rate ): void {
-		$valid_rate = new WC_Shipping_Rate( 'flat_rate:1', 'Flat rate', 5, array(), 'flat_rate' );
-
-		add_filter(
-			'woocommerce_package_rates',
-			fn () => array(
-				'flat_rate:1' => $valid_rate,
-				'invalid'     => $invalid_rate,
-			)
-		);
-
-		$result = $this->sut->calculate_shipping_for_package( $this->get_hide_rates_test_package() );
-
-		$this->assertSame( array( 'flat_rate:1' => $valid_rate ), $result['rates'], 'Only WC_Shipping_Rate instances should remain.' );
-	}
-
-	/**
-	 * Invalid values returned by the package rates filter.
-	 *
-	 * @return array
-	 */
-	public static function provide_invalid_filtered_rates(): array {
-		return array(
-			'null'     => array( null ),
-			'stdClass' => array(
-				(object) array(
-					'id'        => 'local_pickup:1',
-					'method_id' => 'local_pickup',
-				),
-			),
-		);
 	}

 	/**
@@ -429,23 +335,6 @@ class WC_Shipping_Test extends WC_Unit_Test_Case {
 		};
 	}

-	/**
-	 * Get a package for hide-rates-when-free tests.
-	 *
-	 * @return array
-	 */
-	private function get_hide_rates_test_package(): array {
-		return array(
-			'contents'      => array(),
-			'contents_cost' => 10,
-			'destination'   => array(
-				'country'  => 'US',
-				'state'    => 'CA',
-				'postcode' => '00000',
-			),
-		);
-	}
-
 	/**
 	 * Get a package for shipping hash tests.
 	 *
@@ -503,19 +392,16 @@ class WC_Shipping_Test extends WC_Unit_Test_Case {
 				'no',
 				array( $flat_rate, $free_shipping, $local_pickup, $custom_pickup ),
 				array( 'flat_rate:1', 'free_shipping:1', 'local_pickup:1', 'custom_pickup:1' ),
-				array(),
 			),
 			'hide enabled - with free shipping'    => array(
 				'yes',
 				array( $flat_rate, $free_shipping, $local_pickup, $custom_pickup ),
 				array( 'free_shipping:1', 'local_pickup:1', 'custom_pickup:1' ),
-				array( 'flat_rate:1' ),
 			),
 			'hide enabled - without free shipping' => array(
 				'yes',
 				array( $flat_rate, $local_pickup, $custom_pickup ),
 				array( 'flat_rate:1', 'local_pickup:1', 'custom_pickup:1' ),
-				array(),
 			),
 		);
 	}