Commit daae10db1a for woocommerce
commit daae10db1a9dcb58470ba0ff33b1927b5f21a9d8
Author: Mike Jolley <mike.jolley@me.com>
Date: Tue Dec 16 13:59:03 2025 +0000
Store API - Move `package_id` and `package_name` logic to core cart class (#62393)
* Move package ID and Name creation to cart class
* Use package name from package in cart template
* Add test
* Changelog
* Inner package must be array
* Remove get_package_name
* Simplify package id indexing
diff --git a/plugins/woocommerce/changelog/wooplug-5959-storeapi-request-modifying-shipping-packages-might-cause b/plugins/woocommerce/changelog/wooplug-5959-storeapi-request-modifying-shipping-packages-might-cause
new file mode 100644
index 0000000000..1aa63ea766
--- /dev/null
+++ b/plugins/woocommerce/changelog/wooplug-5959-storeapi-request-modifying-shipping-packages-might-cause
@@ -0,0 +1,4 @@
+Significance: patch
+Type: tweak
+
+Moved package_id and package_name generation from CartController to WC_Cart::get_shipping_packages() method to ensure these fields are always present in shipping packages, regardless of how they are accessed.
diff --git a/plugins/woocommerce/includes/class-wc-cart.php b/plugins/woocommerce/includes/class-wc-cart.php
index 5381183765..c0d8659bd6 100644
--- a/plugins/woocommerce/includes/class-wc-cart.php
+++ b/plugins/woocommerce/includes/class-wc-cart.php
@@ -1579,7 +1579,14 @@ class WC_Cart extends WC_Legacy_Cart {
* @return array of cart items
*/
public function get_shipping_packages() {
- return apply_filters(
+ /**
+ * Filters the shipping packages for the cart.
+ *
+ * @since 1.5.4
+ * @param array $packages The shipping packages.
+ * @return array The shipping packages.
+ */
+ $shipping_packages = apply_filters(
'woocommerce_cart_shipping_packages',
array(
array(
@@ -1594,14 +1601,66 @@ class WC_Cart extends WC_Legacy_Cart {
'state' => $this->get_customer()->get_shipping_state(),
'postcode' => $this->get_customer()->get_shipping_postcode(),
'city' => $this->get_customer()->get_shipping_city(),
- 'address' => $this->get_customer()->get_shipping_address(),
- 'address_1' => $this->get_customer()->get_shipping_address(), // Provide both address and address_1 for backwards compatibility.
+ 'address' => $this->get_customer()->get_shipping_address(), // This is an alias of address_1, provided for backwards compatibility.
+ 'address_1' => $this->get_customer()->get_shipping_address_1(),
'address_2' => $this->get_customer()->get_shipping_address_2(),
),
'cart_subtotal' => $this->get_displayed_subtotal(),
),
)
);
+
+ // Return empty array if invalid object supplied by the filter or no packages.
+ if ( ! is_array( $shipping_packages ) || empty( $shipping_packages ) ) {
+ return array();
+ }
+
+ // Remove any invalid packages before adding package IDs.
+ $shipping_packages = array_filter(
+ $shipping_packages,
+ function ( $package ) {
+ return ! empty( $package ) && is_array( $package );
+ }
+ );
+
+ // Add package ID and package name to each package after the filter is applied.
+ $index = 1;
+ foreach ( $shipping_packages as $key => $package ) {
+ $shipping_packages[ $key ]['package_id'] = $package['package_id'] ?? $key;
+ $shipping_packages[ $key ]['package_name'] = $this->get_shipping_package_name( $shipping_packages[ $key ], $index );
+ ++$index;
+ }
+
+ return $shipping_packages;
+ }
+
+ /**
+ * Get the package name.
+ *
+ * @param array $package Shipping package data.
+ * @param int $index Package number.
+ * @return string
+ */
+ private function get_shipping_package_name( $package, $index ) {
+ /**
+ * Filters the shipping package name.
+ *
+ * @since 4.3.0
+ * @param string $shipping_package_name Shipping package name.
+ * @param string $package_id Shipping package ID.
+ * @param array $package Shipping package from WooCommerce.
+ * @return string Shipping package name.
+ */
+ return apply_filters(
+ 'woocommerce_shipping_package_name',
+ sprintf(
+ /* translators: %d: shipping package number */
+ _x( 'Shipment %d', 'shipping packages', 'woocommerce' ),
+ $index
+ ),
+ $package['package_id'],
+ $package
+ );
}
/**
diff --git a/plugins/woocommerce/includes/wc-cart-functions.php b/plugins/woocommerce/includes/wc-cart-functions.php
index ffa7feeb8c..f5081cc5e9 100644
--- a/plugins/woocommerce/includes/wc-cart-functions.php
+++ b/plugins/woocommerce/includes/wc-cart-functions.php
@@ -257,8 +257,7 @@ function wc_cart_totals_shipping_html() {
'show_package_details' => count( $packages ) > 1,
'show_shipping_calculator' => is_cart() && apply_filters( 'woocommerce_shipping_show_shipping_calculator', $first, $i, $package ),
'package_details' => implode( ', ', $product_names ),
- /* translators: %d: shipping package number */
- 'package_name' => apply_filters( 'woocommerce_shipping_package_name', ( ( $i + 1 ) > 1 ) ? sprintf( _x( 'Shipping %d', 'shipping packages', 'woocommerce' ), ( $i + 1 ) ) : _x( 'Shipping', 'shipping packages', 'woocommerce' ), $i, $package ),
+ 'package_name' => $package['package_name'],
'index' => $i,
'chosen_method' => $chosen_method,
'formatted_destination' => WC()->countries->get_formatted_address( $package['destination'], ', ' ),
diff --git a/plugins/woocommerce/src/StoreApi/Utilities/CartController.php b/plugins/woocommerce/src/StoreApi/Utilities/CartController.php
index a076254d15..a2120758e8 100644
--- a/plugins/woocommerce/src/StoreApi/Utilities/CartController.php
+++ b/plugins/woocommerce/src/StoreApi/Utilities/CartController.php
@@ -905,60 +905,14 @@ class CartController {
$packages = $cart->get_shipping_packages();
- // Return early if invalid object supplied by the filter or no packages.
- if ( ! is_array( $packages ) || empty( $packages ) ) {
+ // Return early if no packages.
+ if ( empty( $packages ) ) {
return [];
}
- // Add extra package data to array.
- $packages = array_map(
- function ( $key, $package, $index ) {
- $package['package_id'] = isset( $package['package_id'] ) ? $package['package_id'] : $key;
- $package['package_name'] = isset( $package['package_name'] ) ? $package['package_name'] : $this->get_package_name( $package, $index );
- return $package;
- },
- array_keys( $packages ),
- $packages,
- range( 1, count( $packages ) )
- );
-
return $calculate_rates ? wc()->shipping()->calculate_shipping( $packages ) : $packages;
}
- /**
- * Creates a name for a package.
- *
- * @param array $package Shipping package from WooCommerce.
- * @param int $index Package number.
- * @return string
- */
- protected function get_package_name( $package, $index ) {
- /**
- * Filters the shipping package name.
- *
- * @since 4.3.0
- *
- * @internal Matches filter name in WooCommerce core.
- *
- * @param string $shipping_package_name Shipping package name.
- * @param string $package_id Shipping package ID.
- * @param array $package Shipping package from WooCommerce.
- * @return string Shipping package name.
- */
- return apply_filters(
- 'woocommerce_shipping_package_name',
- $index > 1 ?
- sprintf(
- /* translators: %d: shipping package number */
- _x( 'Shipment %d', 'shipping packages', 'woocommerce' ),
- $index
- ) :
- _x( 'Shipment 1', 'shipping packages', 'woocommerce' ),
- $package['package_id'],
- $package
- );
- }
-
/**
* Selects a shipping rate.
*
diff --git a/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Utilities/CartControllerTests.php b/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Utilities/CartControllerTests.php
index 580ead4fc2..729d6af767 100644
--- a/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Utilities/CartControllerTests.php
+++ b/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Utilities/CartControllerTests.php
@@ -18,6 +18,7 @@ class CartControllerTests extends TestCase {
public function tearDown(): void {
parent::tearDown();
WC()->cart->empty_cart();
+ remove_all_filters( 'woocommerce_cart_shipping_packages' );
}
/**
@@ -158,4 +159,128 @@ class CartControllerTests extends TestCase {
$this->assertContains( $expected_error, $error_codes );
}
}
+
+ /**
+ * Test that get_shipping_packages returns packages with package_id and package_name.
+ */
+ public function test_get_shipping_packages_includes_package_id_and_package_name() {
+ $class = new CartController();
+ $fixtures = new FixtureData();
+ $fixtures->shipping_add_flat_rate();
+
+ $product = $fixtures->get_simple_product(
+ array(
+ 'name' => 'Test Product',
+ 'regular_price' => 10,
+ 'weight' => 10,
+ )
+ );
+
+ wc()->cart->add_to_cart( $product->get_id(), 1 );
+
+ // Set shipping address so packages are generated.
+ wc()->customer->set_shipping_country( 'US' );
+ wc()->customer->set_shipping_state( 'CA' );
+ wc()->customer->set_shipping_postcode( '90210' );
+
+ $packages = $class->get_shipping_packages( false );
+
+ $this->assertNotEmpty( $packages, 'Should have at least one shipping package.' );
+ $this->assertArrayHasKey( 'package_id', $packages[0], 'Package should have package_id.' );
+ $this->assertArrayHasKey( 'package_name', $packages[0], 'Package should have package_name.' );
+ $this->assertEquals( 0, $packages[0]['package_id'], 'First package should have package_id of 0 (array key).' );
+ $this->assertStringContainsString( 'Shipment 1', $packages[0]['package_name'], 'First package should have package_name containing "Shipment 1".' );
+ }
+
+ /**
+ * Test that get_shipping_packages handles multiple packages correctly.
+ */
+ public function test_get_shipping_packages_handles_multiple_packages() {
+ $class = new CartController();
+ $fixtures = new FixtureData();
+ $fixtures->shipping_add_flat_rate();
+
+ $product = $fixtures->get_simple_product(
+ array(
+ 'name' => 'Test Product',
+ 'regular_price' => 10,
+ 'weight' => 10,
+ )
+ );
+
+ wc()->cart->add_to_cart( $product->get_id(), 1 );
+
+ // Set shipping address.
+ wc()->customer->set_shipping_country( 'US' );
+ wc()->customer->set_shipping_state( 'CA' );
+ wc()->customer->set_shipping_postcode( '90210' );
+
+ // Filter to create multiple packages.
+ add_filter(
+ 'woocommerce_cart_shipping_packages',
+ function ( $packages ) {
+ $packages[] = $packages[0];
+ return $packages;
+ }
+ );
+
+ $packages = $class->get_shipping_packages( false );
+
+ $this->assertCount( 2, $packages, 'Should have two shipping packages.' );
+
+ // First package.
+ $this->assertArrayHasKey( 'package_id', $packages[0], 'First package should have package_id.' );
+ $this->assertArrayHasKey( 'package_name', $packages[0], 'First package should have package_name.' );
+ $this->assertEquals( 0, $packages[0]['package_id'], 'First package should have package_id of 0.' );
+ $this->assertStringContainsString( 'Shipment 1', $packages[0]['package_name'], 'First package should have package_name containing "Shipment 1".' );
+
+ // Second package.
+ $this->assertArrayHasKey( 'package_id', $packages[1], 'Second package should have package_id.' );
+ $this->assertArrayHasKey( 'package_name', $packages[1], 'Second package should have package_name.' );
+ $this->assertEquals( 1, $packages[1]['package_id'], 'Second package should have package_id of 1.' );
+ $this->assertStringContainsString( 'Shipment 2', $packages[1]['package_name'], 'Second package should have package_name containing "Shipment 2".' );
+
+ remove_all_filters( 'woocommerce_cart_shipping_packages' );
+ }
+
+ /**
+ * Test that get_shipping_packages respects custom package_id from filter.
+ */
+ public function test_get_shipping_packages_respects_custom_package_id() {
+ $class = new CartController();
+ $fixtures = new FixtureData();
+ $fixtures->shipping_add_flat_rate();
+
+ $product = $fixtures->get_simple_product(
+ array(
+ 'name' => 'Test Product',
+ 'regular_price' => 10,
+ 'weight' => 10,
+ )
+ );
+
+ wc()->cart->add_to_cart( $product->get_id(), 1 );
+
+ // Set shipping address.
+ wc()->customer->set_shipping_country( 'US' );
+ wc()->customer->set_shipping_state( 'CA' );
+ wc()->customer->set_shipping_postcode( '90210' );
+
+ // Filter to add custom package_id.
+ add_filter(
+ 'woocommerce_cart_shipping_packages',
+ function ( $packages ) {
+ $packages[0]['package_id'] = 'custom-package-123';
+ return $packages;
+ }
+ );
+
+ $packages = $class->get_shipping_packages( false );
+
+ $this->assertNotEmpty( $packages, 'Should have at least one shipping package.' );
+ $this->assertEquals( 'custom-package-123', $packages[0]['package_id'], 'Package should use custom package_id from filter.' );
+ $this->assertArrayHasKey( 'package_name', $packages[0], 'Package should still have package_name.' );
+
+ remove_all_filters( 'woocommerce_cart_shipping_packages' );
+ }
}