Commit 5a7f984ccf5 for woocommerce
commit 5a7f984ccf536dbb5eafdca424d99a0cedfbfd34
Author: Ahmed Ismail <105542236+ahmedevsaad@users.noreply.github.com>
Date: Thu Nov 5 17:13:47 2026 +0200
Fix extra backslashes when updating a review through the REST API (#69295)
* Fix extra backslashes when updating a review through the REST API
* Keep backslashes when a review is updated through the v1 and v2 REST API
update_item() passed unslashed data to wp_update_comment(), which unslashes
it again, so backslashes were stripped. Slash it like create_item() does.
This also covers the v2 route and batch endpoint, which inherit the method.
diff --git a/plugins/woocommerce/changelog/34193-fix-rest-review-update-slashes b/plugins/woocommerce/changelog/34193-fix-rest-review-update-slashes
new file mode 100644
index 00000000000..a7a7321d3ea
--- /dev/null
+++ b/plugins/woocommerce/changelog/34193-fix-rest-review-update-slashes
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Keep quotes and backslashes as entered when a product review is edited through the REST API.
diff --git a/plugins/woocommerce/includes/rest-api/Controllers/Version1/class-wc-rest-product-reviews-v1-controller.php b/plugins/woocommerce/includes/rest-api/Controllers/Version1/class-wc-rest-product-reviews-v1-controller.php
index 9eaa821ac3b..ff07b391db8 100644
--- a/plugins/woocommerce/includes/rest-api/Controllers/Version1/class-wc-rest-product-reviews-v1-controller.php
+++ b/plugins/woocommerce/includes/rest-api/Controllers/Version1/class-wc-rest-product-reviews-v1-controller.php
@@ -399,8 +399,9 @@ class WC_REST_Product_Reviews_V1_Controller extends WC_REST_Controller {
/*
* Zero means no comment-table row changed; false is the actual update failure. A rating-only
* request legitimately returns zero while still storing comment_meta and refreshing counts.
+ * wp_update_comment() expects slashed data, like wp_insert_comment() in create_item().
*/
- $updated = wp_update_comment( $prepared_review );
+ $updated = wp_update_comment( wp_slash( $prepared_review ) );
if ( false === $updated ) {
return new WP_Error( 'rest_product_review_failed_edit', __( 'Updating product review failed.', 'woocommerce' ), array( 'status' => 500 ) );
}
diff --git a/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-product-reviews-controller.php b/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-product-reviews-controller.php
index 168880ad192..27a8a2fa426 100644
--- a/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-product-reviews-controller.php
+++ b/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-product-reviews-controller.php
@@ -949,7 +949,7 @@ class WC_REST_Product_Reviews_Controller extends WC_REST_Controller {
'type' => 'string',
'context' => array( 'view', 'edit' ),
'arg_options' => array(
- 'sanitize_callback' => 'wp_filter_post_kses',
+ 'sanitize_callback' => 'wp_kses_post',
),
),
'rating' => array(
diff --git a/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version2/class-wc-rest-product-reviews-v2-controller-tests.php b/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version2/class-wc-rest-product-reviews-v2-controller-tests.php
index 2000d4c7de7..fa40e55e828 100644
--- a/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version2/class-wc-rest-product-reviews-v2-controller-tests.php
+++ b/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version2/class-wc-rest-product-reviews-v2-controller-tests.php
@@ -87,6 +87,89 @@ class WC_REST_Product_Reviews_V2_Controller_Test extends WC_REST_Unit_Test_case
$this->assertSame( 3, (int) get_comment_meta( $review_id, 'rating', true ) );
}
+ /**
+ * Review content that must be stored exactly as sent.
+ *
+ * @return array
+ */
+ public function provide_review_content_with_slashes(): array {
+ return array(
+ 'quotes' => array( 'It\'s "great"' ),
+ 'backslash' => array( 'Cable 1\\2 inch' ),
+ 'mixed' => array( 'It\'s a 1\\2" cable' ),
+ );
+ }
+
+ /**
+ * @testdox The wc/v2 update route stores quotes and backslashes in review content as sent.
+ * @dataProvider provide_review_content_with_slashes
+ *
+ * @param string $content Review content.
+ */
+ public function test_wc_v2_update_item_stores_review_content_as_sent( string $content ): void {
+ wp_set_current_user( $this->shop_manager_id );
+ $product_id = ProductHelper::create_simple_product()->get_id();
+ $review_id = $this->create_v2_review( $product_id );
+
+ $update = new WP_REST_Request( 'PUT', '/wc/v2/products/' . $product_id . '/reviews/' . $review_id );
+ $update->set_body_params( array( 'review' => $content ) );
+ $response = $this->server->dispatch( $update );
+
+ $this->assertSame( 200, $response->get_status() );
+ $this->assertSame( $content, get_comment( $review_id )->comment_content );
+ }
+
+ /**
+ * @testdox The wc/v2 batch route stores quotes and backslashes in updated review content as sent.
+ * @dataProvider provide_review_content_with_slashes
+ *
+ * @param string $content Review content.
+ */
+ public function test_wc_v2_batch_update_stores_review_content_as_sent( string $content ): void {
+ wp_set_current_user( $this->shop_manager_id );
+ $product_id = ProductHelper::create_simple_product()->get_id();
+ $review_id = $this->create_v2_review( $product_id );
+
+ $batch = new WP_REST_Request( 'POST', '/wc/v2/products/' . $product_id . '/reviews/batch' );
+ $batch->set_body_params(
+ array(
+ 'update' => array(
+ array(
+ 'id' => $review_id,
+ 'review' => $content,
+ ),
+ ),
+ )
+ );
+ $response = $this->server->dispatch( $batch );
+
+ $this->assertSame( 200, $response->get_status() );
+ $this->assertSame( $content, get_comment( $review_id )->comment_content );
+ }
+
+ /**
+ * Create a review through the wc/v2 route.
+ *
+ * @param int $product_id Product ID.
+ * @return int Review ID.
+ */
+ private function create_v2_review( int $product_id ): int {
+ $create = new WP_REST_Request( 'POST', '/wc/v2/products/' . $product_id . '/reviews' );
+ $create->set_body_params(
+ array(
+ 'review' => 'First draft.',
+ 'name' => 'Jane Smith',
+ 'email' => 'jane.smith@example.org',
+ 'rating' => 5,
+ )
+ );
+ $created = $this->server->dispatch( $create );
+
+ $this->assertSame( 201, $created->get_status() );
+
+ return (int) $created->get_data()['id'];
+ }
+
/**
* @testdox Creating a review eagerly populates the verified-owner meta at creation time, not lazily during response preparation.
*/
diff --git a/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-product-reviews-controller-tests.php b/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-product-reviews-controller-tests.php
index 75f7762bfa7..2695cdc3b4b 100644
--- a/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-product-reviews-controller-tests.php
+++ b/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-product-reviews-controller-tests.php
@@ -300,6 +300,38 @@ class WC_REST_Product_Reviews_Controller_Tests extends WC_REST_Unit_Test_Case {
$this->assertEquals( 2, $product->get_review_count(), 'An unrated review is still counted as a review.' );
}
+ /**
+ * Review content that must be stored exactly as sent.
+ *
+ * @return array
+ */
+ public function provide_review_content_with_slashes(): array {
+ return array(
+ 'quotes' => array( 'It\'s "great"' ),
+ 'backslash' => array( 'Cable 1\\2 inch' ),
+ 'mixed' => array( 'It\'s a 1\\2" cable' ),
+ );
+ }
+
+ /**
+ * @testdox Updating review content stores quotes and backslashes as sent.
+ * @dataProvider provide_review_content_with_slashes
+ *
+ * @param string $content Review content.
+ */
+ public function test_update_item_stores_review_content_as_sent( string $content ): void {
+ wp_set_current_user( $this->shop_manager_id );
+ $product_id = ProductHelper::create_simple_product()->get_id();
+ $review_id = $this->create_review( $product_id, 'First draft.', 5 )->get_data()['id'];
+
+ $request = new WP_REST_Request( 'PUT', '/wc/v3/products/reviews/' . $review_id );
+ $request->set_body_params( array( 'review' => $content ) );
+ $response = $this->server->dispatch( $request );
+
+ $this->assertEquals( 200, $response->get_status() );
+ $this->assertSame( $content, get_comment( $review_id )->comment_content );
+ }
+
/**
* @testdox A rating-only edit stores the rating and refreshes aggregates in one product save.
*/