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;
+	}
 }