Commit 7d1e5e233e5 for woocommerce
commit 7d1e5e233e53decdb047e01f8c0263f18d15d9f4
Author: Liam Sarsfield <43409125+LiamSarsfield@users.noreply.github.com>
Date: Mon Aug 10 14:22:41 2026 +0100
Update external product button label handling (#67538)
Co-authored-by: Karol Manijak <20098064+kmanijak@users.noreply.github.com>
diff --git a/plugins/woocommerce/changelog/fix-WOO6-78 b/plugins/woocommerce/changelog/fix-WOO6-78
new file mode 100644
index 00000000000..9fe6d488c8f
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-WOO6-78
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Update product button label checks.
diff --git a/plugins/woocommerce/includes/rest-api/Controllers/Version1/class-wc-rest-products-v1-controller.php b/plugins/woocommerce/includes/rest-api/Controllers/Version1/class-wc-rest-products-v1-controller.php
index d742f7c2d87..143ef5dd4d4 100644
--- a/plugins/woocommerce/includes/rest-api/Controllers/Version1/class-wc-rest-products-v1-controller.php
+++ b/plugins/woocommerce/includes/rest-api/Controllers/Version1/class-wc-rest-products-v1-controller.php
@@ -1354,7 +1354,7 @@ class WC_REST_Products_V1_Controller extends WC_REST_Posts_Controller {
}
if ( isset( $request['button_text'] ) ) {
- $product->set_button_text( $request['button_text'] );
+ $product->set_button_text( sanitize_text_field( $request['button_text'] ) );
}
}
diff --git a/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-products-v2-controller.php b/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-products-v2-controller.php
index 66d114d0f59..17b9f0ed43c 100644
--- a/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-products-v2-controller.php
+++ b/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-products-v2-controller.php
@@ -1416,7 +1416,7 @@ class WC_REST_Products_V2_Controller extends WC_REST_CRUD_Controller {
}
if ( isset( $request['button_text'] ) ) {
- $product->set_button_text( $request['button_text'] );
+ $product->set_button_text( sanitize_text_field( $request['button_text'] ) );
}
}
diff --git a/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller.php b/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller.php
index cd2e44c5207..fc3034341c2 100644
--- a/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller.php
+++ b/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller.php
@@ -1125,7 +1125,7 @@ class WC_REST_Products_Controller extends WC_REST_Products_V2_Controller {
}
if ( isset( $request['button_text'] ) ) {
- $product->set_button_text( $request['button_text'] );
+ $product->set_button_text( sanitize_text_field( $request['button_text'] ) );
}
}
diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/ProductButton.php b/plugins/woocommerce/src/Blocks/BlockTypes/ProductButton.php
index e865ec849f4..29c1f13a92b 100644
--- a/plugins/woocommerce/src/Blocks/BlockTypes/ProductButton.php
+++ b/plugins/woocommerce/src/Blocks/BlockTypes/ProductButton.php
@@ -296,7 +296,7 @@ class ProductButton extends AbstractBlock {
'{context_directives}' => $context_directives,
'{button_styles}' => esc_attr( $styles_and_classes['styles'] ),
'{attributes}' => isset( $args['attributes'] ) ? wc_implode_html_attributes( $args['attributes'] ) : '',
- '{add_to_cart_text}' => $is_ajax_button ? '' : $add_to_cart_text,
+ '{add_to_cart_text}' => $is_ajax_button ? '' : esc_html( $add_to_cart_text ),
'{div_directives}' => $is_ajax_button ? $div_directives : '',
'{button_directives}' => $is_ajax_button ? $button_directives : $anchor_directive,
'{span_button_directives}' => $is_ajax_button ? $span_button_directives : '',
diff --git a/plugins/woocommerce/src/Internal/RestApi/Routes/V4/Products/Controller.php b/plugins/woocommerce/src/Internal/RestApi/Routes/V4/Products/Controller.php
index 31c7a09f5b3..d4bcdeb2f88 100644
--- a/plugins/woocommerce/src/Internal/RestApi/Routes/V4/Products/Controller.php
+++ b/plugins/woocommerce/src/Internal/RestApi/Routes/V4/Products/Controller.php
@@ -1397,7 +1397,7 @@ class Controller extends WC_REST_Products_V2_Controller {
}
if ( isset( $request['button_text'] ) ) {
- $product->set_button_text( $request['button_text'] );
+ $product->set_button_text( sanitize_text_field( $request['button_text'] ) );
}
}
diff --git a/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller-tests.php b/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller-tests.php
index f20b9ea1e34..e56ed02e009 100644
--- a/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller-tests.php
+++ b/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-products-controller-tests.php
@@ -1945,6 +1945,28 @@ class WC_REST_Products_Controller_Tests extends WC_Unit_Test_Case {
$this->assertEquals( 200, $response->get_status() );
}
+ /**
+ * @testdox Should sanitize external product button text.
+ */
+ public function test_update_external_product_sanitizes_button_text(): void {
+ $shop_manager = self::factory()->user->create( array( 'role' => 'shop_manager' ) );
+ wp_set_current_user( $shop_manager );
+
+ $product = WC_Helper_Product::create_external_product();
+ $this->update_product_via_post_request(
+ $product,
+ array(
+ 'button_text' => 'Buy now<style>.hidden { display: none; }</style>',
+ )
+ );
+
+ $updated_product = wc_get_product( $product->get_id() );
+
+ $this->assertSame( 'Buy now', $updated_product->get_button_text(), 'HTML should be removed from the button text.' );
+
+ $product->delete( true );
+ }
+
/**
* Test that batch create operations update term counts correctly.
*
diff --git a/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/ProductButtonTest.php b/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/ProductButtonTest.php
new file mode 100644
index 00000000000..c7d0fea7266
--- /dev/null
+++ b/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/ProductButtonTest.php
@@ -0,0 +1,34 @@
+<?php
+
+declare( strict_types = 1 );
+
+namespace Automattic\WooCommerce\Tests\Blocks\BlockTypes;
+
+use WC_Helper_Product;
+use WC_Unit_Test_Case;
+
+/**
+ * Tests for the ProductButton block type.
+ */
+class ProductButtonTest extends WC_Unit_Test_Case {
+
+ /**
+ * @testdox Should escape an external product's button text.
+ */
+ public function test_render_escapes_external_product_button_text(): void {
+ $product = WC_Helper_Product::create_external_product();
+ $product->set_button_text( 'Buy now<style>.hidden { display: none; }</style>' );
+ $product->save();
+
+ $markup = do_blocks(
+ '<!-- wp:woocommerce/single-product {"productId":' . $product->get_id() . '} -->' .
+ '<!-- wp:woocommerce/product-button /-->' .
+ '<!-- /wp:woocommerce/single-product -->'
+ );
+
+ $this->assertStringContainsString( 'Buy now<style>.hidden { display: none; }</style>', $markup, 'The button text should be escaped.' );
+ $this->assertStringNotContainsString( 'Buy now<style>.hidden { display: none; }</style>', $markup, 'The button text should not render as HTML.' );
+
+ $product->delete( true );
+ }
+}