Commit 99a48e6860b for woocommerce
commit 99a48e6860b26f83f07a58470f98a2da48d5471b
Author: Darren Ethier <darren@roughsmootheng.in>
Date: Wed Sep 2 15:10:53 2026 -0400
Replace mixed WPCS suppressions with exact PHPCS annotations (#68266)
diff --git a/plugins/woocommerce/changelog/fix-woo6-116-mixed-request-validation-suppressions b/plugins/woocommerce/changelog/fix-woo6-116-mixed-request-validation-suppressions
new file mode 100644
index 00000000000..83ad45ecb9c
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-woo6-116-mixed-request-validation-suppressions
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Fix a fatal error on the order received page when the order key is passed as an array, and replace legacy WPCS suppression comments in the settings save handler, thanks page, conditional tag helpers and terms template with exact PHPCS annotations.
diff --git a/plugins/woocommerce/includes/admin/class-wc-admin-menus.php b/plugins/woocommerce/includes/admin/class-wc-admin-menus.php
index 5fb6a9f545c..9dd03b80fe9 100644
--- a/plugins/woocommerce/includes/admin/class-wc-admin-menus.php
+++ b/plugins/woocommerce/includes/admin/class-wc-admin-menus.php
@@ -186,9 +186,31 @@ class WC_Admin_Menus {
$current_section = empty( $_REQUEST['section'] ) ? '' : sanitize_title( wp_unslash( $_REQUEST['section'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only selector; settings mutations verify capability and nonce.
// Save settings if data has been posted.
- if ( '' !== $current_section && apply_filters( "woocommerce_save_settings_{$current_tab}_{$current_section}", ! empty( $_POST['save'] ) ) ) { // WPCS: input var okay, CSRF ok.
+ /**
+ * Filters whether the posted data for a settings section should be saved.
+ *
+ * The dynamic portions of the hook name, `$current_tab` and `$current_section`, are the
+ * sanitized `tab` and `section` request values of the settings screen being viewed.
+ *
+ * @since 3.3.0
+ * @param bool $save Whether to save. Defaults to true when the settings form was submitted.
+ */
+ if ( '' !== $current_section && apply_filters( "woocommerce_save_settings_{$current_tab}_{$current_section}", ! empty( $_POST['save'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Presence check only; WC_Admin_Settings::save() verifies the manage_woocommerce capability and the woocommerce-settings nonce before anything is written.
WC_Admin_Settings::save();
- } elseif ( '' === $current_section && apply_filters( "woocommerce_save_settings_{$current_tab}", ! empty( $_POST['save'] ) ) ) { // WPCS: input var okay, CSRF ok.
+ } elseif (
+ '' === $current_section
+ /**
+ * Filters whether the posted data for a settings tab should be saved.
+ *
+ * The dynamic portion of the hook name, `$current_tab`, is the sanitized `tab` request
+ * value of the settings screen being viewed. This variant fires only when no section is
+ * selected.
+ *
+ * @since 3.3.0
+ * @param bool $save Whether to save. Defaults to true when the settings form was submitted.
+ */
+ && apply_filters( "woocommerce_save_settings_{$current_tab}", ! empty( $_POST['save'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Presence check only; WC_Admin_Settings::save() verifies the manage_woocommerce capability and the woocommerce-settings nonce before anything is written.
+ ) {
WC_Admin_Settings::save();
}
}
diff --git a/plugins/woocommerce/includes/shortcodes/class-wc-shortcode-checkout.php b/plugins/woocommerce/includes/shortcodes/class-wc-shortcode-checkout.php
index f56d1460e61..098997e2d97 100644
--- a/plugins/woocommerce/includes/shortcodes/class-wc-shortcode-checkout.php
+++ b/plugins/woocommerce/includes/shortcodes/class-wc-shortcode-checkout.php
@@ -265,8 +265,20 @@ class WC_Shortcode_Checkout {
$order = false;
// Get the order.
- $order_id = apply_filters( 'woocommerce_thankyou_order_id', absint( $order_id ) );
- $order_key = apply_filters( 'woocommerce_thankyou_order_key', empty( $_GET['key'] ) ? '' : wc_clean( wp_unslash( $_GET['key'] ) ) ); // WPCS: input var ok, CSRF ok.
+ /**
+ * Filters the ID of the order shown on the thanks page.
+ *
+ * @since 2.0.0
+ * @param int $order_id The order ID resolved from the request.
+ */
+ $order_id = apply_filters( 'woocommerce_thankyou_order_id', absint( $order_id ) );
+ /**
+ * Filters the order key used to validate the order shown on the thanks page.
+ *
+ * @since 2.0.0
+ * @param string $order_key The order key read from the request, or an empty string.
+ */
+ $order_key = apply_filters( 'woocommerce_thankyou_order_key', ( empty( $_GET['key'] ) || ! is_string( $_GET['key'] ) ) ? '' : sanitize_text_field( wp_unslash( $_GET['key'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Bearer-style order-key lookup; the value is compared with hash_equals() below and a nonce cannot apply to links emailed to the customer.
if ( $order_id > 0 ) {
$order = wc_get_order( $order_id );
diff --git a/plugins/woocommerce/includes/wc-conditional-functions.php b/plugins/woocommerce/includes/wc-conditional-functions.php
index 4925b28b205..964aa9947ab 100644
--- a/plugins/woocommerce/includes/wc-conditional-functions.php
+++ b/plugins/woocommerce/includes/wc-conditional-functions.php
@@ -245,6 +245,12 @@ if ( ! function_exists( 'is_order_received_page' ) ) {
$page_id = wc_get_page_id( 'checkout' );
+ /**
+ * Filters whether the order received (thanks) page is being viewed.
+ *
+ * @since 3.0.0
+ * @param bool $is_order_received_page Whether the order received page is being viewed.
+ */
return apply_filters( 'woocommerce_is_order_received_page', ( $page_id && is_page( $page_id ) && isset( $wp->query_vars['order-received'] ) ) );
}
}
@@ -343,7 +349,13 @@ if ( ! function_exists( 'is_filtered' ) ) {
* @return bool
*/
function is_filtered() {
- return apply_filters( 'woocommerce_is_filtered', ( count( WC_Query::get_layered_nav_chosen_attributes() ) > 0 || isset( $_GET['max_price'] ) || isset( $_GET['min_price'] ) || isset( $_GET['rating_filter'] ) ) ); // WPCS: CSRF ok.
+ /**
+ * Filters whether products are currently being filtered by layered nav, price or rating.
+ *
+ * @since 2.1.0
+ * @param bool $is_filtered Whether a product filter is active.
+ */
+ return apply_filters( 'woocommerce_is_filtered', ( count( WC_Query::get_layered_nav_chosen_attributes() ) > 0 || isset( $_GET['max_price'] ) || isset( $_GET['min_price'] ) || isset( $_GET['rating_filter'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only presence checks on public catalog filter parameters; nothing is written.
}
}
@@ -399,6 +411,12 @@ if ( ! function_exists( 'wc_tax_enabled' ) ) {
* @return bool
*/
function wc_tax_enabled() {
+ /**
+ * Filters whether store-wide taxes are enabled.
+ *
+ * @since 2.4.0
+ * @param bool $tax_enabled Whether taxes are enabled.
+ */
return apply_filters( 'wc_tax_enabled', get_option( 'woocommerce_calc_taxes' ) === 'yes' );
}
}
@@ -411,6 +429,12 @@ if ( ! function_exists( 'wc_shipping_enabled' ) ) {
* @return bool
*/
function wc_shipping_enabled() {
+ /**
+ * Filters whether shipping is enabled.
+ *
+ * @since 2.6.0
+ * @param bool $shipping_enabled Whether shipping is enabled.
+ */
return apply_filters( 'wc_shipping_enabled', get_option( 'woocommerce_ship_to_countries' ) !== 'disabled' );
}
}
diff --git a/plugins/woocommerce/templates/checkout/terms.php b/plugins/woocommerce/templates/checkout/terms.php
index e2c385c6333..b4c6b631e96 100644
--- a/plugins/woocommerce/templates/checkout/terms.php
+++ b/plugins/woocommerce/templates/checkout/terms.php
@@ -27,7 +27,7 @@ if ( apply_filters( 'woocommerce_checkout_show_terms', true ) && function_exists
<?php if ( wc_terms_and_conditions_checkbox_enabled() ) : ?>
<p class="form-row validate-required">
<label class="woocommerce-form__label woocommerce-form__label-for-checkbox checkbox">
- <input type="checkbox" class="woocommerce-form__input woocommerce-form__input-checkbox input-checkbox" name="terms" <?php checked( apply_filters( 'woocommerce_terms_is_checked_default', isset( $_POST['terms'] ) ), true ); // WPCS: input var ok, csrf ok. ?> id="terms" />
+ <input type="checkbox" class="woocommerce-form__input woocommerce-form__input-checkbox input-checkbox" name="terms" <?php checked( apply_filters( 'woocommerce_terms_is_checked_default', isset( $_POST['terms'] ) ), true ); // phpcs:ignore WordPress.Security.NonceVerification.Missing, WooCommerce.Commenting.CommentHooks.MissingHookComment -- Presence check only, used to re-render the checkbox; WC_Checkout::process_checkout() verifies the checkout nonce. A docblock cannot precede this inline call without changing the rendered markup. ?> id="terms" />
<span class="woocommerce-terms-and-conditions-checkbox-text"><?php wc_terms_and_conditions_checkbox_text(); ?></span> <abbr class="required" title="<?php esc_attr_e( 'required', 'woocommerce' ); ?>">*</abbr>
</label>
<input type="hidden" name="terms-field" value="1" />
diff --git a/plugins/woocommerce/tests/php/includes/shortcodes/class-wc-shortcode-checkout-test.php b/plugins/woocommerce/tests/php/includes/shortcodes/class-wc-shortcode-checkout-test.php
new file mode 100644
index 00000000000..246e7569fb7
--- /dev/null
+++ b/plugins/woocommerce/tests/php/includes/shortcodes/class-wc-shortcode-checkout-test.php
@@ -0,0 +1,45 @@
+<?php
+declare( strict_types = 1 );
+
+/**
+ * Tests for WC_Shortcode_Checkout.
+ *
+ * @package WooCommerce\Tests\Shortcodes
+ */
+
+/**
+ * Class WC_Shortcode_Checkout_Test.
+ */
+class WC_Shortcode_Checkout_Test extends WC_Unit_Test_Case {
+
+ /**
+ * Restore the request and query state touched by this test.
+ */
+ public function tearDown(): void {
+ global $wp;
+
+ unset( $_GET['key'] );
+ unset( $wp->query_vars['order-received'] );
+
+ parent::tearDown();
+ }
+
+ /**
+ * An array `key` must be treated as absent rather than reaching hash_equals().
+ */
+ public function test_order_received_treats_array_order_key_as_absent() {
+ global $wp;
+
+ $order = WC_Helper_Order::create_order( 0 );
+
+ $wp->query_vars['order-received'] = $order->get_id();
+ $_GET['key'] = array( $order->get_order_key() );
+
+ ob_start();
+ WC_Shortcode_Checkout::output( array() );
+ $output = (string) ob_get_clean();
+
+ $this->assertStringNotContainsString( 'woocommerce-thankyou-order-details', $output );
+ $this->assertStringNotContainsString( (string) $order->get_order_number(), $output );
+ }
+}