Commit 2c6e30aa7db for woocommerce
commit 2c6e30aa7db7282aa34ab061826e6523215df099
Author: Jorge A. Torres <jorge.torres@automattic.com>
Date: Mon Aug 17 19:44:00 2026 +0100
Fix non-scalar classic cart item data (redux) (#67781)
diff --git a/plugins/woocommerce/changelog/65484-fix-cart-item-data-non-scalar b/plugins/woocommerce/changelog/65484-fix-cart-item-data-non-scalar
new file mode 100644
index 00000000000..cd4f90db7dc
--- /dev/null
+++ b/plugins/woocommerce/changelog/65484-fix-cart-item-data-non-scalar
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Prevent non-scalar cart item data provided by extensions from causing a fatal error when rendering the classic cart.
diff --git a/plugins/woocommerce/includes/wc-template-functions.php b/plugins/woocommerce/includes/wc-template-functions.php
index c2d95781cc8..4deae04646e 100644
--- a/plugins/woocommerce/includes/wc-template-functions.php
+++ b/plugins/woocommerce/includes/wc-template-functions.php
@@ -4553,13 +4553,28 @@ function wc_get_formatted_cart_item_data( $cart_item, $flat = false ) {
if ( is_array( $item_data ) ) {
// Format item data ready to display.
foreach ( $item_data as $key => $data ) {
+ if ( ! is_array( $data ) ) {
+ unset( $item_data[ $key ] );
+ continue;
+ }
// Set hidden to true to not display meta on cart.
if ( ! empty( $data['hidden'] ) ) {
unset( $item_data[ $key ] );
continue;
}
- $item_data[ $key ]['key'] = ! empty( $data['key'] ) ? $data['key'] : $data['name'];
- $item_data[ $key ]['display'] = ! empty( $data['display'] ) ? $data['display'] : $data['value'];
+ $label = ! empty( $data['key'] ) ? $data['key'] : ( $data['name'] ?? '' );
+ $display = ! empty( $data['display'] ) ? $data['display'] : ( $data['value'] ?? '' );
+
+ $label_is_renderable = is_scalar( $label ) || ( is_object( $label ) && method_exists( $label, '__toString' ) );
+ $display_is_renderable = is_scalar( $display ) || ( is_object( $display ) && method_exists( $display, '__toString' ) );
+
+ if ( ! $label_is_renderable || ! $display_is_renderable ) {
+ unset( $item_data[ $key ] );
+ continue;
+ }
+
+ $item_data[ $key ]['key'] = (string) $label;
+ $item_data[ $key ]['display'] = (string) $display;
}
// Output flat or in list format.
diff --git a/plugins/woocommerce/tests/legacy/unit-tests/templates/functions.php b/plugins/woocommerce/tests/legacy/unit-tests/templates/functions.php
index 88be12c0e4f..079abf55d3b 100644
--- a/plugins/woocommerce/tests/legacy/unit-tests/templates/functions.php
+++ b/plugins/woocommerce/tests/legacy/unit-tests/templates/functions.php
@@ -292,6 +292,106 @@ class WC_Tests_Template_Functions extends WC_Unit_Test_Case {
$this->assertEquals( $expected_html, $actual_html );
}
+ /**
+ * Item data used by the wc_get_formatted_cart_item_data tests, mixing rows
+ * that should render (scalar or stringable rendered fields) with rows that
+ * should be dropped (non-scalar rendered fields or malformed entries).
+ *
+ * @param array $item_data Existing item data.
+ * @return array
+ */
+ public function get_cart_item_data_fixture( $item_data ) {
+ $item_data[] = array(
+ 'key' => 'Gift wrap',
+ 'value' => 'Included',
+ );
+ $item_data[] = array(
+ 'key' => 'Attachments',
+ 'value' => array( 'file-a.pdf', 'file-b.pdf' ),
+ 'display' => '2 files',
+ );
+ $item_data[] = array(
+ 'key' => 'Custom',
+ 'value' => 'Hidden value',
+ 'display' => 'Shown',
+ '_private' => array( 'internal' => 'data' ),
+ );
+ $item_data[] = array(
+ 'key' => 'Note',
+ 'display' => new class() {
+ public function __toString() { // phpcs:ignore Squiz.Commenting.FunctionComment.Missing
+ return 'From object';
+ }
+ },
+ );
+ $item_data[] = array(
+ 'key' => 'Files',
+ 'value' => array( 'file-c.pdf' ),
+ );
+ $item_data[] = array(
+ 'key' => 'Extra',
+ 'value' => 'Details',
+ 'display' => new stdClass(),
+ );
+ $item_data[] = 'Malformed item data';
+
+ return $item_data;
+ }
+
+ /**
+ * Calls wc_get_formatted_cart_item_data with the shared fixture hooked into
+ * the woocommerce_get_item_data filter.
+ *
+ * @param bool $flat Whether to request flat output.
+ * @return string
+ */
+ private function get_formatted_cart_item_data_with_fixture( $flat ) {
+ $filter = array( $this, 'get_cart_item_data_fixture' );
+ add_filter( 'woocommerce_get_item_data', $filter );
+
+ try {
+ return wc_get_formatted_cart_item_data(
+ array(
+ 'data' => new WC_Product_Simple(),
+ 'variation' => array(),
+ ),
+ $flat
+ );
+ } finally {
+ remove_filter( 'woocommerce_get_item_data', $filter );
+ }
+ }
+
+ /**
+ * @testdox 'wc_get_formatted_cart_item_data' renders rows whose label and display value are stringable and drops the rest.
+ */
+ public function test_wc_get_formatted_cart_item_data_skips_non_renderable_item_data() {
+ $html = $this->get_formatted_cart_item_data_with_fixture( false );
+
+ $this->assertStringContainsString( 'Gift wrap', $html );
+ $this->assertStringContainsString( 'Included', $html );
+ $this->assertStringContainsString( 'Attachments', $html );
+ $this->assertStringContainsString( '2 files', $html );
+ $this->assertStringContainsString( 'Custom', $html );
+ $this->assertStringContainsString( 'Shown', $html );
+ $this->assertStringContainsString( 'Note', $html );
+ $this->assertStringContainsString( 'From object', $html );
+ $this->assertStringNotContainsString( 'Hidden value', $html );
+ $this->assertStringNotContainsString( 'file-a.pdf', $html );
+ $this->assertStringNotContainsString( 'Files', $html );
+ $this->assertStringNotContainsString( 'Extra', $html );
+ $this->assertStringNotContainsString( 'Malformed item data', $html );
+ }
+
+ /**
+ * @testdox 'wc_get_formatted_cart_item_data' renders the same rows in flat output.
+ */
+ public function test_wc_get_formatted_cart_item_data_skips_non_renderable_item_data_in_flat_output() {
+ $output = $this->get_formatted_cart_item_data_with_fixture( true );
+
+ $this->assertSame( "Gift wrap: Included\nAttachments: 2 files\nCustom: Shown\nNote: From object\n", $output );
+ }
+
public function test_hidden_field() {
$actual_html = woocommerce_form_field(
'test',