Commit 9f7f8b6ff49 for woocommerce

commit 9f7f8b6ff495b8a1e746abefb553db16537ee29a
Author: Darren Ethier <darren@roughsmootheng.in>
Date:   Fri Aug 28 15:08:06 2026 -0400

    Fix template markup contracts and shipping label targets (#68020)

diff --git a/plugins/woocommerce/changelog/fix-woo6-126-template-markup-suppressions b/plugins/woocommerce/changelog/fix-woo6-126-template-markup-suppressions
new file mode 100644
index 00000000000..7ed37580a51
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-woo6-126-template-markup-suppressions
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Fix the shipping method label's for attribute so it matches the input ID when a shipping package uses a non-numeric key.
diff --git a/plugins/woocommerce/templates/cart/cart-shipping.php b/plugins/woocommerce/templates/cart/cart-shipping.php
index 08caa6576a2..bfe9c7f3fb6 100644
--- a/plugins/woocommerce/templates/cart/cart-shipping.php
+++ b/plugins/woocommerce/templates/cart/cart-shipping.php
@@ -14,7 +14,7 @@
  *
  * @see https://woocommerce.com/document/template-structure/
  * @package WooCommerce\Templates
- * @version 8.8.0
+ * @version 11.2.0
  */

 defined( 'ABSPATH' ) || exit;
@@ -33,11 +33,11 @@ $calculator_text          = '';
 					<li>
 						<?php
 						if ( 1 < count( $available_methods ) ) {
-							printf( '<input type="radio" name="shipping_method[%1$d]" data-index="%1$d" id="shipping_method_%1$d_%2$s" value="%3$s" class="shipping_method" %4$s />', $index, esc_attr( sanitize_title( $method->id ) ), esc_attr( $method->id ), checked( $method->id, $chosen_method, false ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $index is an integer; the remaining values are escaped or safe attribute markup.
+							printf( '<input type="radio" name="shipping_method[%1$d]" data-index="%1$d" id="shipping_method_%1$d_%2$s" value="%3$s" class="shipping_method" %4$s />', $index, esc_attr( sanitize_title( $method->id ) ), esc_attr( $method->id ), checked( $method->id, $chosen_method, false ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- %1$d normalizes the package index; the remaining values are escaped or safe attribute markup.
 						} else {
-							printf( '<input type="hidden" name="shipping_method[%1$d]" data-index="%1$d" id="shipping_method_%1$d_%2$s" value="%3$s" class="shipping_method" />', $index, esc_attr( sanitize_title( $method->id ) ), esc_attr( $method->id ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $index is an integer and the remaining attribute values are escaped.
+							printf( '<input type="hidden" name="shipping_method[%1$d]" data-index="%1$d" id="shipping_method_%1$d_%2$s" value="%3$s" class="shipping_method" />', $index, esc_attr( sanitize_title( $method->id ) ), esc_attr( $method->id ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- %1$d normalizes the package index; the remaining attribute values are escaped.
 						}
-						printf( '<label for="shipping_method_%1$s_%2$s">%3$s</label>', $index, esc_attr( sanitize_title( $method->id ) ), wc_cart_totals_shipping_method_label( $method ) ); // WPCS: XSS ok.
+						printf( '<label for="shipping_method_%1$d_%2$s">%3$s</label>', $index, esc_attr( sanitize_title( $method->id ) ), wc_cart_totals_shipping_method_label( $method ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- %1$d normalizes the package index; the shipping method label is intentionally filterable HTML.
 						do_action( 'woocommerce_after_shipping_rate', $method, $index );
 						?>
 					</li>
diff --git a/plugins/woocommerce/templates/checkout/payment-method.php b/plugins/woocommerce/templates/checkout/payment-method.php
index 05308d8c091..c9a23905f89 100644
--- a/plugins/woocommerce/templates/checkout/payment-method.php
+++ b/plugins/woocommerce/templates/checkout/payment-method.php
@@ -23,7 +23,7 @@ if ( ! defined( 'ABSPATH' ) ) {
 	<input id="payment_method_<?php echo esc_attr( $gateway->id ); ?>" type="radio" class="input-radio" name="payment_method" value="<?php echo esc_attr( $gateway->id ); ?>" <?php checked( $gateway->chosen, true ); ?> data-order_button_text="<?php echo esc_attr( $gateway->order_button_text ); ?>" />

 	<label for="payment_method_<?php echo esc_attr( $gateway->id ); ?>">
-		<?php echo $gateway->get_title(); /* phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped */ ?> <?php echo $gateway->get_icon(); /* phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped */ ?>
+		<?php echo $gateway->get_title(); /* phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- The base gateway title is sanitized; the public filter intentionally allows HTML. */ ?> <?php echo $gateway->get_icon(); /* phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- The base icon attributes are escaped; the public filter intentionally allows HTML. */ ?>
 	</label>
 	<?php if ( $gateway->has_fields() || $gateway->get_description() ) : ?>
 		<div class="payment_box payment_method_<?php echo esc_attr( $gateway->id ); ?>" <?php if ( ! $gateway->chosen ) : /* phpcs:ignore Squiz.ControlStructures.ControlSignature.NewlineAfterOpenBrace */ ?>style="display:none;"<?php endif; /* phpcs:ignore Squiz.ControlStructures.ControlSignature.NewlineAfterOpenBrace */ ?>>
diff --git a/plugins/woocommerce/templates/loop/rating.php b/plugins/woocommerce/templates/loop/rating.php
index 8d9aeb6e48c..12fde0be1d4 100644
--- a/plugins/woocommerce/templates/loop/rating.php
+++ b/plugins/woocommerce/templates/loop/rating.php
@@ -25,4 +25,4 @@ if ( ! wc_review_ratings_enabled() ) {
 	return;
 }

-echo wc_get_rating_html( $product->get_average_rating() ); // WordPress.XSS.EscapeOutput.OutputNotEscaped.
+echo wc_get_rating_html( $product->get_average_rating() ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- The rating helper returns intentionally filterable HTML.
diff --git a/plugins/woocommerce/templates/single-product/add-to-cart/simple.php b/plugins/woocommerce/templates/single-product/add-to-cart/simple.php
index 1ae8fbb0405..5a82ef6208c 100644
--- a/plugins/woocommerce/templates/single-product/add-to-cart/simple.php
+++ b/plugins/woocommerce/templates/single-product/add-to-cart/simple.php
@@ -23,7 +23,7 @@ if ( ! $product->is_purchasable() ) {
 	return;
 }

-echo wc_get_stock_html( $product ); // WPCS: XSS ok.
+echo wc_get_stock_html( $product ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- The stock helper returns intentionally filterable HTML.

 if ( $product->is_in_stock() ) : ?>

diff --git a/plugins/woocommerce/templates/single-product/add-to-cart/variable.php b/plugins/woocommerce/templates/single-product/add-to-cart/variable.php
index f262ac5c613..9589427ed39 100644
--- a/plugins/woocommerce/templates/single-product/add-to-cart/variable.php
+++ b/plugins/woocommerce/templates/single-product/add-to-cart/variable.php
@@ -35,7 +35,7 @@ do_action( 'woocommerce_before_add_to_cart_form' ); ?>
 			<tbody>
 				<?php foreach ( $attributes as $attribute_name => $options ) : ?>
 					<tr>
-						<th class="label"><label for="<?php echo esc_attr( sanitize_title( $attribute_name ) ); ?>"><?php echo wc_attribute_label( $attribute_name ); // WPCS: XSS ok. ?></label></th>
+						<th class="label"><label for="<?php echo esc_attr( sanitize_title( $attribute_name ) ); ?>"><?php echo wc_attribute_label( $attribute_name ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Attribute names are sanitized on save; the filter output may carry markup by design. ?></label></th>
 						<td class="value">
 							<?php
 								wc_dropdown_variation_attribute_options(
diff --git a/plugins/woocommerce/templates/single-product/rating.php b/plugins/woocommerce/templates/single-product/rating.php
index a7fa24143a2..4a7647e4c5a 100644
--- a/plugins/woocommerce/templates/single-product/rating.php
+++ b/plugins/woocommerce/templates/single-product/rating.php
@@ -32,7 +32,7 @@ $average      = $product->get_average_rating();
 if ( $rating_count > 0 ) : ?>

 	<div class="woocommerce-product-rating">
-		<?php echo wc_get_rating_html( $average, $rating_count ); // WPCS: XSS ok. ?>
+		<?php echo wc_get_rating_html( $average, $rating_count ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- The rating helper returns intentionally filterable HTML. ?>
 		<?php if ( comments_open() ) : ?>
 			<?php //phpcs:disable ?>
 			<a href="#reviews" class="woocommerce-review-link" rel="nofollow">(<?php printf( _n( '%s customer review', '%s customer reviews', $review_count, 'woocommerce' ), '<span class="count">' . esc_html( $review_count ) . '</span>' ); ?>)</a>
diff --git a/plugins/woocommerce/templates/single-product/review-rating.php b/plugins/woocommerce/templates/single-product/review-rating.php
index 244f2867f39..cee7406e432 100644
--- a/plugins/woocommerce/templates/single-product/review-rating.php
+++ b/plugins/woocommerce/templates/single-product/review-rating.php
@@ -23,5 +23,5 @@ global $comment;
 $rating = intval( get_comment_meta( $comment->comment_ID, 'rating', true ) );

 if ( $rating && wc_review_ratings_enabled() ) {
-	echo wc_get_rating_html( $rating ); // WPCS: XSS ok.
+	echo wc_get_rating_html( $rating ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- The rating helper returns intentionally filterable HTML.
 }
diff --git a/plugins/woocommerce/templates/single-product/short-description.php b/plugins/woocommerce/templates/single-product/short-description.php
index 2f7e4c82eac..86c17d55a40 100644
--- a/plugins/woocommerce/templates/single-product/short-description.php
+++ b/plugins/woocommerce/templates/single-product/short-description.php
@@ -29,5 +29,5 @@ if ( ! $short_description ) {

 ?>
 <div class="woocommerce-product-details__short-description">
-	<?php echo $short_description; // WPCS: XSS ok. ?>
+	<?php echo $short_description; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Short descriptions intentionally contain filtered post and extension HTML. ?>
 </div>
diff --git a/plugins/woocommerce/tests/php/includes/templates/class-wc-cart-shipping-template-test.php b/plugins/woocommerce/tests/php/includes/templates/class-wc-cart-shipping-template-test.php
new file mode 100644
index 00000000000..a85eaef746c
--- /dev/null
+++ b/plugins/woocommerce/tests/php/includes/templates/class-wc-cart-shipping-template-test.php
@@ -0,0 +1,41 @@
+<?php
+/**
+ * Tests for the cart shipping template.
+ */
+
+declare( strict_types = 1 );
+
+/**
+ * Cart shipping template test.
+ */
+class WC_Cart_Shipping_Template_Test extends WC_Unit_Test_Case {
+
+	/**
+	 * @testdox Shipping method labels target the rendered radio input when a package has a non-numeric key.
+	 */
+	public function test_shipping_method_label_targets_normalized_package_index(): void {
+		$flat_rate     = new WC_Shipping_Rate( 'flat_rate:1', 'Flat rate', 0, array(), 'flat_rate', 1 );
+		$free_shipping = new WC_Shipping_Rate( 'free_shipping:2', 'Free shipping', 0, array(), 'free_shipping', 2 );
+
+		$markup = $this->capture_output_from(
+			'wc_get_template',
+			'cart/cart-shipping.php',
+			array(
+				'package'                  => array( 'destination' => array() ),
+				'available_methods'        => array( $flat_rate, $free_shipping ),
+				'show_package_details'     => false,
+				'show_shipping_calculator' => false,
+				'package_details'          => '',
+				'package_name'             => 'Package',
+				'index'                    => 'vendor-package',
+				'chosen_method'            => $flat_rate->get_id(),
+				'formatted_destination'    => '',
+				'has_calculated_shipping'  => true,
+			)
+		);
+
+		$this->assertStringContainsString( 'id="shipping_method_0_flat_rate1"', $markup, 'The package index should be normalized in the input ID.' );
+		$this->assertStringContainsString( 'for="shipping_method_0_flat_rate1"', $markup, 'The label should target the normalized input ID.' );
+		$this->assertStringNotContainsString( 'vendor-package', $markup, 'The raw package key should not be rendered in an attribute.' );
+	}
+}