Commit 898e3cde33a for woocommerce
commit 898e3cde33a23ba6c850b214da08feb3b7b9d051
Author: Albert Juhé Lluveras <contact@albertjuhe.com>
Date: Fri Sep 18 11:09:27 2026 +0200
Fix Product Rating linking to the wrong page when used inside the Product block in product catalog pages (#68829)
* Fix Product Rating linking to the wrong page when used inside the Product block in product catalog pages
* Add changelog
* Add test
* Improve tests
* Remove unnecessary guards in tests
* Update changelog
* Make sure we restore the iAPI state
diff --git a/plugins/woocommerce/changelog/fix-WOOAIRR-369-product-rating-wrong-is-descendent-checks b/plugins/woocommerce/changelog/fix-WOOAIRR-369-product-rating-wrong-is-descendent-checks
new file mode 100644
index 00000000000..e282e493ff6
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-WOOAIRR-369-product-rating-wrong-is-descendent-checks
@@ -0,0 +1,3 @@
+Significance: patch
+Type: fix
+Comment: Fix Product Rating linking to the wrong page when used inside the Product block in product catalog pages
diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/ProductRating.php b/plugins/woocommerce/src/Blocks/BlockTypes/ProductRating.php
index 0df1a87e945..f00023abdbb 100644
--- a/plugins/woocommerce/src/Blocks/BlockTypes/ProductRating.php
+++ b/plugins/woocommerce/src/Blocks/BlockTypes/ProductRating.php
@@ -70,9 +70,8 @@ class ProductRating extends AbstractBlock {
&& wc_reviews_enabled() ) {
$product_reviews_count = $product->get_review_count();
$product_rating = $product->get_average_rating();
- $global_product = $GLOBALS['product'] ?? null;
- $is_descendent_of_single_product_block = ! isset( $block->context['queryId'] ) && ( ! $global_product instanceof \WC_Product || (int) $post_id !== $global_product->get_id() );
- $is_descendent_of_single_product_template = ! isset( $block->context['queryId'] ) && ! $is_descendent_of_single_product_block;
+ $is_descendent_of_single_product_template = ! isset( $block->context['queryId'] ) && is_product() && get_queried_object_id() === (int) $post_id;
+ $is_descendent_of_single_product_block = ! isset( $block->context['queryId'] ) && ! $is_descendent_of_single_product_template;
$styles_and_classes = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
$text_align_styles_and_classes = StyleAttributesUtils::get_text_align_class_and_style( $attributes );
diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/ProductRatingCounter.php b/plugins/woocommerce/src/Blocks/BlockTypes/ProductRatingCounter.php
index 051a876c0e6..683fb0df5da 100644
--- a/plugins/woocommerce/src/Blocks/BlockTypes/ProductRatingCounter.php
+++ b/plugins/woocommerce/src/Blocks/BlockTypes/ProductRatingCounter.php
@@ -68,9 +68,8 @@ class ProductRatingCounter extends AbstractBlock {
if ( $product && $product->get_review_count() > 0 ) {
$product_reviews_count = $product->get_review_count();
$product_rating = $product->get_average_rating();
- $global_product = $GLOBALS['product'] ?? null;
- $is_descendent_of_single_product_block = ! isset( $block->context['queryId'] ) && ( ! $global_product instanceof \WC_Product || (int) $post_id !== $global_product->get_id() );
- $is_descendent_of_single_product_template = ! isset( $block->context['queryId'] ) && ! $is_descendent_of_single_product_block;
+ $is_descendent_of_single_product_template = ! isset( $block->context['queryId'] ) && is_product() && get_queried_object_id() === (int) $post_id;
+ $is_descendent_of_single_product_block = ! isset( $block->context['queryId'] ) && ! $is_descendent_of_single_product_template;
$styles_and_classes = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
$text_align_styles_and_classes = StyleAttributesUtils::get_text_align_class_and_style( $attributes );
diff --git a/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/ProductRatingTest.php b/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/ProductRatingTest.php
index 1f25dc4576a..c60e1e9f1c0 100644
--- a/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/ProductRatingTest.php
+++ b/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/ProductRatingTest.php
@@ -178,4 +178,109 @@ class ProductRatingTest extends WC_Unit_Test_Case {
'global reviews disabled' => array( true, 'no', false ),
);
}
+
+ /**
+ * @testdox Single Product block review links follow the current page and ignore $GLOBALS['product'].
+ *
+ * @testWith ["woocommerce/product-rating", false, false]
+ * ["woocommerce/product-rating", true, false]
+ * ["woocommerce/product-rating", false, true]
+ * ["woocommerce/product-rating", true, true]
+ * ["woocommerce/product-rating-counter", false, false]
+ * ["woocommerce/product-rating-counter", true, false]
+ * ["woocommerce/product-rating-counter", false, true]
+ * ["woocommerce/product-rating-counter", true, true]
+ *
+ * @param string $inner_block_name Inner rating block to render.
+ * @param bool $global_product_matches Whether to set $GLOBALS['product'] to the block product.
+ * @param bool $on_product_page Whether to render on the product permalink.
+ */
+ public function test_single_product_block_review_link_ignores_matching_global_product( string $inner_block_name, bool $global_product_matches, bool $on_product_page ): void {
+ $had_global_product = array_key_exists( 'product', $GLOBALS );
+ $previous_global_product = $GLOBALS['product'] ?? null;
+ $products_store_state = $this->snapshot_products_store_static_state();
+ $interactivity_state = $this->snapshot_interactivity_state();
+
+ try {
+ $product = $this->create_rated_product();
+ $permalink_href = 'href="' . esc_url( get_permalink( $product->get_id() ) ) . '#reviews"';
+
+ if ( $on_product_page ) {
+ $this->go_to( get_permalink( $product->get_id() ) );
+ }
+
+ if ( $global_product_matches ) {
+ // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
+ $GLOBALS['product'] = $product;
+ } else {
+ unset( $GLOBALS['product'] );
+ }
+
+ $markup = do_blocks(
+ '<!-- wp:woocommerce/single-product {"productId":' . $product->get_id() . '} -->' .
+ '<!-- wp:' . $inner_block_name . ' /-->' .
+ '<!-- /wp:woocommerce/single-product -->'
+ );
+
+ $context = ( $on_product_page ? 'on the product page' : 'off the product page' ) . ' ' . ( $global_product_matches ? 'when the global product matches' : 'when the global product is not set' );
+
+ if ( $on_product_page ) {
+ $this->assertStringContainsString(
+ 'href="#reviews"',
+ $markup,
+ $inner_block_name . ' inside a Single Product block should emit a same-page #reviews anchor ' . $context . '.'
+ );
+ $this->assertStringNotContainsString(
+ $permalink_href,
+ $markup,
+ $inner_block_name . ' inside a Single Product block should not link to the product permalink ' . $context . '.'
+ );
+ } else {
+ $this->assertStringContainsString(
+ $permalink_href,
+ $markup,
+ $inner_block_name . ' inside a Single Product block should link to the product permalink plus #reviews ' . $context . '.'
+ );
+ $this->assertStringNotContainsString(
+ 'href="#reviews"',
+ $markup,
+ $inner_block_name . ' inside a Single Product block should not emit a bare #reviews anchor ' . $context . '.'
+ );
+ }
+ } finally {
+ try {
+ if ( $had_global_product ) {
+ $GLOBALS['product'] = $previous_global_product;
+ } else {
+ unset( $GLOBALS['product'] );
+ }
+ } finally {
+ try {
+ $this->restore_products_store_static_state( $products_store_state );
+ } finally {
+ $this->restore_interactivity_state( $interactivity_state );
+ }
+ }
+ }
+ }
+
+ /**
+ * Create a published product that has one approved review.
+ *
+ * @return \WC_Product
+ */
+ private function create_rated_product(): \WC_Product {
+ $fixtures = new FixtureData();
+ $product = $fixtures->get_simple_product(
+ array(
+ 'name' => 'Rated product',
+ 'regular_price' => '10',
+ 'status' => 'publish',
+ 'reviews_allowed' => true,
+ )
+ );
+ $fixtures->add_product_review( $product->get_id(), 5 );
+
+ return $product;
+ }
}