Commit e09664501b0 for woocommerce

commit e09664501b06671903b01bbb739c3da32ef80977
Author: Luigi Teschio <gigitux@gmail.com>
Date:   Tue Nov 10 13:24:19 2026 +0100

    Exclude non-indexable WooCommerce pages from core sitemaps (#70712)

    * Exclude non-indexable WooCommerce pages from core sitemaps

    * Add changelog entry for core sitemap exclusions

    * Centralize WooCommerce search engine visibility handling

    * Register wp_robots filter in wc-template-functions.php for better control over search engine visibility

diff --git a/plugins/woocommerce/changelog/fix-core-sitemap-private-pages b/plugins/woocommerce/changelog/fix-core-sitemap-private-pages
new file mode 100644
index 00000000000..5d1bbfe9b53
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-core-sitemap-private-pages
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Exclude Cart, Checkout, My Account, and Review Order pages from the WordPress sitemap.
diff --git a/plugins/woocommerce/includes/class-woocommerce.php b/plugins/woocommerce/includes/class-woocommerce.php
index c946d959a3e..935fa2bb8f8 100644
--- a/plugins/woocommerce/includes/class-woocommerce.php
+++ b/plugins/woocommerce/includes/class-woocommerce.php
@@ -56,6 +56,7 @@ use Automattic\WooCommerce\Internal\Caches\TaxRateVersionStringInvalidator;
 use Automattic\WooCommerce\Internal\CustomerEmailVerification\CustomerEmailVerification;
 use Automattic\WooCommerce\StoreApi\Utilities\Ucp\IdempotencyStore;
 use Automattic\WooCommerce\Internal\StructuredData\OpenGraph;
+use Automattic\WooCommerce\Internal\SearchEngineVisibility;

 /**
  * Main WooCommerce Class.
@@ -425,6 +426,7 @@ final class WooCommerce {
 		$container->get( SettingsScreen::class );
 		$container->get( AbilitiesTracking::class );
 		$container->get( OpenGraph::class );
+		$container->get( SearchEngineVisibility::class );
 		$container->get( MenuIcons::class );

 		/**
diff --git a/plugins/woocommerce/includes/wc-deprecated-functions.php b/plugins/woocommerce/includes/wc-deprecated-functions.php
index 627c73ca24b..a24fb48d8e0 100644
--- a/plugins/woocommerce/includes/wc-deprecated-functions.php
+++ b/plugins/woocommerce/includes/wc-deprecated-functions.php
@@ -1403,3 +1403,14 @@ function get_woocommerce_api_url( $path ) {
 add_filter( 'pre_option_woocommerce_api_enabled', function() {
 	return WC()->legacy_rest_api_is_available() ? 'yes' : 'no';
 } );
+
+/**
+ * Legacy noindex output for transactional pages.
+ *
+ * @since 3.2.0
+ * @deprecated 11.4.0 Use wc_page_no_robots() to filter robots directives instead.
+ * @return void
+ */
+function wc_page_noindex() {
+	wc_deprecated_function( __FUNCTION__, '11.4.0', 'wc_page_no_robots' );
+}
diff --git a/plugins/woocommerce/includes/wc-template-functions.php b/plugins/woocommerce/includes/wc-template-functions.php
index 9a380a4ea8a..7305e8e0663 100644
--- a/plugins/woocommerce/includes/wc-template-functions.php
+++ b/plugins/woocommerce/includes/wc-template-functions.php
@@ -14,6 +14,7 @@ use Automattic\WooCommerce\Enums\OrderStatus;
 use Automattic\WooCommerce\Enums\PaymentGatewayFeature;
 use Automattic\WooCommerce\Enums\ProductType;
 use Automattic\WooCommerce\Admin\Features\Fulfillments\Fulfillment;
+use Automattic\WooCommerce\Internal\SearchEngineVisibility;
 use Automattic\WooCommerce\Internal\Utilities\HtmlSanitizer;
 use Automattic\WooCommerce\Utilities\FeaturesUtil;

@@ -4519,25 +4520,6 @@ function wc_empty_cart_message() {
 	echo '<div class="woocommerce-notices-wrapper wc-empty-cart-message">' . $notice . '</div>';
 }

-/**
- * Disable search engines indexing core, dynamic, cart/checkout pages.
- *
- * @todo Deprecated this function after dropping support for WP 5.6.
- * @since 3.2.0
- * @return void
- */
-function wc_page_noindex() {
-	// wp_no_robots is deprecated since WP 5.7.
-	if ( function_exists( 'wp_robots_no_robots' ) ) {
-		return;
-	}
-
-	if ( is_page( wc_get_page_id( 'cart' ) ) || is_page( wc_get_page_id( 'checkout' ) ) || is_page( wc_get_page_id( 'myaccount' ) ) ) {
-		wp_no_robots();
-	}
-}
-add_action( 'wp_head', 'wc_page_noindex' );
-
 /**
  * Disable search engines indexing core, dynamic, cart/checkout pages.
  * Uses "wp_robots" filter introduced in WP 5.7.
@@ -4547,11 +4529,7 @@ add_action( 'wp_head', 'wc_page_noindex' );
  * @return array Filtered robots directives.
  */
 function wc_page_no_robots( $robots ) {
-	if ( is_page( wc_get_page_id( 'cart' ) ) || is_page( wc_get_page_id( 'checkout' ) ) || is_page( wc_get_page_id( 'myaccount' ) ) ) {
-		return wp_robots_no_robots( $robots );
-	}
-
-	return $robots;
+	return wc_get_container()->get( SearchEngineVisibility::class )->handle_wp_robots( $robots );
 }
 add_filter( 'wp_robots', 'wc_page_no_robots' );

diff --git a/plugins/woocommerce/src/Internal/SearchEngineVisibility.php b/plugins/woocommerce/src/Internal/SearchEngineVisibility.php
new file mode 100644
index 00000000000..ec7279dd514
--- /dev/null
+++ b/plugins/woocommerce/src/Internal/SearchEngineVisibility.php
@@ -0,0 +1,107 @@
+<?php
+/**
+ * Search engine visibility for WooCommerce pages.
+ *
+ * @package WooCommerce
+ */
+
+declare( strict_types = 1 );
+
+namespace Automattic\WooCommerce\Internal;
+
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * Controls robots directives and core sitemap exclusions for WooCommerce pages.
+ *
+ * @since 11.4.0
+ * @internal
+ */
+class SearchEngineVisibility {
+
+	/**
+	 * Register the sitemap filter.
+	 *
+	 * @internal
+	 */
+	final public function init(): void {
+		// Keep wp_robots registered in wc-template-functions.php through wc_page_no_robots() so extensions can remove it.
+		add_filter( 'wp_sitemaps_posts_query_args', array( $this, 'handle_wp_sitemaps_posts_query_args' ), 10, 2 );
+	}
+
+	/**
+	 * Add noindex to the robots directives for transactional pages.
+	 *
+	 * @internal
+	 *
+	 * @param mixed $robots Robots directives from earlier filters.
+	 * @return mixed Filtered robots directives.
+	 */
+	public function handle_wp_robots( $robots ) {
+		return is_array( $robots ) && $this->is_noindex_page() ? wp_robots_no_robots( $robots ) : $robots;
+	}
+
+	/**
+	 * Exclude pages that WooCommerce marks noindex or redirects on direct visits.
+	 *
+	 * @internal
+	 *
+	 * @param mixed  $args      Query arguments from earlier filters.
+	 * @param string $post_type Sitemap post type.
+	 * @return mixed Filtered query arguments.
+	 */
+	public function handle_wp_sitemaps_posts_query_args( $args, $post_type ) {
+		if ( 'page' !== $post_type || ! is_array( $args ) ) {
+			return $args;
+		}
+
+		$excluded_ids = $this->get_excluded_page_ids( true );
+		if ( $excluded_ids ) {
+			$args['post__not_in'] = array_merge( (array) ( $args['post__not_in'] ?? array() ), $excluded_ids );
+		}
+
+		return $args;
+	}
+
+	/**
+	 * Check whether the current page should receive a noindex directive.
+	 *
+	 * @return bool
+	 */
+	private function is_noindex_page(): bool {
+		$page_ids = $this->get_excluded_page_ids();
+
+		return $page_ids && is_page( $page_ids );
+	}
+
+	/**
+	 * Resolve transactional page IDs, optionally including the Review Order host page.
+	 *
+	 * @param bool $include_review_order Whether to include the redirecting host page for sitemap exclusions.
+	 * @return int[]
+	 */
+	private function get_excluded_page_ids( bool $include_review_order = false ): array {
+		$pages = array( 'cart', 'checkout', 'myaccount' );
+		if ( $include_review_order ) {
+			$pages[] = 'review_order';
+		}
+
+		// Prime caches to reduce future queries.
+		wp_prime_option_caches(
+			array_map(
+				static fn( string $page ): string => 'woocommerce_' . $page . '_page_id',
+				$pages
+			)
+		);
+
+		$excluded_ids = array();
+		foreach ( $pages as $page ) {
+			$page_id = wc_get_page_id( $page );
+			if ( $page_id > 0 ) {
+				$excluded_ids[] = $page_id;
+			}
+		}
+
+		return $excluded_ids;
+	}
+}