Commit 39a33686334 for woocommerce
commit 39a33686334fc32beb0554534cf9172a0e0bc5e7
Author: Brian <brian@brianhaas.li>
Date: Sun May 3 01:17:14 2026 +0200
Fix: Array to string conversion warning in wc_query_string_form_fields() with nested query params (#64010)
* fix Array to string conversion php8 message
* Add changefile(s) from automation for the following project(s): woocommerce
* fix placeholders leak
* add tests
* add prettyboymp suggestion
Co-authored-by: Michael Pretty <prettyboymp@users.noreply.github.com>
* remove part from phpstan baseline
---------
Co-authored-by: woocommercebot <woocommercebot@users.noreply.github.com>
Co-authored-by: Michael Pretty <prettyboymp@users.noreply.github.com>
diff --git a/plugins/woocommerce/changelog/64010-30819-php8-warning b/plugins/woocommerce/changelog/64010-30819-php8-warning
new file mode 100644
index 00000000000..35a676e0cb5
--- /dev/null
+++ b/plugins/woocommerce/changelog/64010-30819-php8-warning
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Fix array to string conversion warning in wc_query_string_form_fields() when URL contains nested query string parameters.
\ No newline at end of file
diff --git a/plugins/woocommerce/includes/wc-template-functions.php b/plugins/woocommerce/includes/wc-template-functions.php
index dcbdeeb72d8..4b20e650060 100644
--- a/plugins/woocommerce/includes/wc-template-functions.php
+++ b/plugins/woocommerce/includes/wc-template-functions.php
@@ -807,12 +807,24 @@ function wc_query_string_form_fields( $values = null, $exclude = array(), $curre
// Parse the string.
parse_str( $query_string, $parsed_query_string );
- // Convert the full-stops, pluses and spaces back and add to values array.
- foreach ( $parsed_query_string as $key => $value ) {
- $new_key = str_replace( array_values( $replace_chars ), array_keys( $replace_chars ), $key );
- $new_value = str_replace( array_values( $replace_chars ), array_keys( $replace_chars ), $value );
- $values[ $new_key ] = $new_value;
- }
+ // Convert the full-stops, pluses and spaces back in all scalar values (any depth).
+ array_walk_recursive(
+ $parsed_query_string,
+ function ( &$value ) use ( $replace_chars ) {
+ $value = str_replace( array_values( $replace_chars ), array_keys( $replace_chars ), $value );
+ }
+ );
+
+ // Restore placeholders in keys at every depth, then add to values array.
+ $restore_keys = function ( $items ) use ( &$restore_keys, $replace_chars ) {
+ $out = array();
+ foreach ( $items as $key => $value ) {
+ $key = str_replace( array_values( $replace_chars ), array_keys( $replace_chars ), $key );
+ $out[ $key ] = is_array( $value ) ? $restore_keys( $value ) : $value;
+ }
+ return $out;
+ };
+ $values = $restore_keys( $parsed_query_string );
}
}
$html = '';
diff --git a/plugins/woocommerce/phpstan-baseline.neon b/plugins/woocommerce/phpstan-baseline.neon
index 18433e313e0..cd38064367e 100644
--- a/plugins/woocommerce/phpstan-baseline.neon
+++ b/plugins/woocommerce/phpstan-baseline.neon
@@ -36363,12 +36363,6 @@ parameters:
count: 2
path: includes/wc-template-functions.php
- -
- message: '#^Parameter \#3 \$subject of function str_replace expects array\<string\>\|string, int\|string given\.$#'
- identifier: argument.type
- count: 1
- path: includes/wc-template-functions.php
-
-
message: '#^Property WooCommerce\:\:\$cart \(WC_Cart\) in isset\(\) is not nullable\.$#'
identifier: isset.property
diff --git a/plugins/woocommerce/tests/legacy/unit-tests/templates/functions.php b/plugins/woocommerce/tests/legacy/unit-tests/templates/functions.php
index b41e3d3b9c4..88be12c0e4f 100644
--- a/plugins/woocommerce/tests/legacy/unit-tests/templates/functions.php
+++ b/plugins/woocommerce/tests/legacy/unit-tests/templates/functions.php
@@ -217,6 +217,45 @@ class WC_Tests_Template_Functions extends WC_Unit_Test_Case {
$this->assertEquals( $expected_html, $actual_html );
}
+ /**
+ * Test wc_query_string_form_fields with nested array params.
+ *
+ * @dataProvider provide_nested_array_cases
+ *
+ * @param string $url URL to parse.
+ * @param string $expected_name Expected hidden field name attribute.
+ * @param string $expected_value Expected hidden field value attribute.
+ * @return void
+ */
+ public function test_wc_query_string_form_fields_nested_arrays( string $url, string $expected_name, string $expected_value ): void {
+ $html = wc_query_string_form_fields( $url, array(), '', true );
+
+ $this->assertStringContainsString( 'name="' . $expected_name . '"', $html );
+ $this->assertStringContainsString( 'value="' . $expected_value . '"', $html );
+ $this->assertStringNotContainsString( '{dot}', $html );
+ $this->assertStringNotContainsString( '{plus}', $html );
+ }
+
+ /**
+ * Data provider for test_wc_query_string_form_fields_nested_arrays.
+ *
+ * @return array[]
+ */
+ public function provide_nested_array_cases(): array {
+ return array(
+ // Baseline: nested params without any special chars.
+ 'nested baseline' => array( 'https://x/?products[1][id]=12345', 'products[1][id]', '12345' ),
+ // Nested params with dots in nested keys.
+ 'dot in nested key' => array( 'https://x/?products[1.5][id]=12345', 'products[1.5][id]', '12345' ),
+ // Nested params with dots in nested values.
+ 'dot in nested value' => array( 'https://x/?products[1][price]=12.50', 'products[1][price]', '12.50' ),
+ // Same as dot-in-key case but with + instead of .
+ 'plus in nested key' => array( 'https://x/?products[a+b][id]=12345', 'products[a+b][id]', '12345' ),
+ // Same as dot-in-value case but with + instead of .
+ 'plus in nested value' => array( 'https://x/?products[1][label]=hello+world', 'products[1][label]', 'hello+world' ),
+ );
+ }
+
/**
* Test test_wc_get_pay_buttons().
*/