Commit ca2caf5c45d for woocommerce
commit ca2caf5c45ddac262916adc4ed9367e836788572
Author: Mike Jolley <mike.jolley@me.com>
Date: Fri Jul 3 11:08:35 2026 +0100
Fix My Account endpoint titles with a non-empty cart in block themes (#65949)
* Fix My Account endpoint titles when cart is not empty in block themes
* Add tests for wc_page_endpoint_title() endpoint-page guard
* Fix PHPCS errors on phpcs:ignore reason annotation
The inline reason was placed before the phpcs:ignore directive, which
turned the line into a regular comment (tripping the InlineComment and
PostStatementComment sniffs) and stopped the directive from suppressing
the GlobalVariablesOverride error. Move the reason into the documented
-- annotation suffix so it stays a pure directive.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Document intentional $post_id = 0 default on wc_page_endpoint_title()
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
diff --git a/plugins/woocommerce/changelog/fix-orders-endpoint-title-cart b/plugins/woocommerce/changelog/fix-orders-endpoint-title-cart
new file mode 100644
index 00000000000..31b7fdf51e6
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-orders-endpoint-title-cart
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Fix My Account endpoint page titles showing the page title instead of the endpoint heading (e.g. "Orders") in block themes when the cart is not empty.
diff --git a/plugins/woocommerce/includes/wc-page-functions.php b/plugins/woocommerce/includes/wc-page-functions.php
index a0d433e280d..62cdef429b7 100644
--- a/plugins/woocommerce/includes/wc-page-functions.php
+++ b/plugins/woocommerce/includes/wc-page-functions.php
@@ -13,13 +13,18 @@ defined( 'ABSPATH' ) || exit;
/**
* Replace a page title with the endpoint title.
*
- * @param string $title Post title.
+ * @param string $title Post title.
+ * @param int $post_id Optional. The post ID the title belongs to. Passed by the `the_title` filter. Defaults to 0 for backwards compatibility with callers that pass only the title, in which case the queried-object check is skipped (matching the original single-argument behaviour).
* @return string
*/
-function wc_page_endpoint_title( $title ) {
+function wc_page_endpoint_title( $title, $post_id = 0 ) {
global $wp_query;
- if ( ! is_null( $wp_query ) && ! is_admin() && is_main_query() && in_the_loop() && is_page() && is_wc_endpoint_url() ) {
+ // In block themes the whole template (header, footer, content) renders inside the main
+ // loop, so `the_title` fires for any post title rendered on the page (e.g. a product in a
+ // server-rendered mini-cart) - not just the page's own heading. Only replace the title of
+ // the queried page so an earlier title doesn't consume this one-shot filter.
+ if ( ! is_null( $wp_query ) && ! is_admin() && is_main_query() && in_the_loop() && is_page() && is_wc_endpoint_url() && ( ! $post_id || get_queried_object_id() === $post_id ) ) {
$endpoint = WC()->query->get_current_endpoint();
$action = isset( $_GET['action'] ) ? sanitize_text_field( wp_unslash( $_GET['action'] ) ) : '';
$endpoint_title = WC()->query->get_endpoint_title( $endpoint, $action );
@@ -31,7 +36,7 @@ function wc_page_endpoint_title( $title ) {
return $title;
}
-add_filter( 'the_title', 'wc_page_endpoint_title' );
+add_filter( 'the_title', 'wc_page_endpoint_title', 10, 2 );
/**
* Replace the title part of the document title.
diff --git a/plugins/woocommerce/tests/legacy/unit-tests/page-functions/class-wc-tests-page-functions.php b/plugins/woocommerce/tests/legacy/unit-tests/page-functions/class-wc-tests-page-functions.php
index ef1109b8d4d..509babf506b 100644
--- a/plugins/woocommerce/tests/legacy/unit-tests/page-functions/class-wc-tests-page-functions.php
+++ b/plugins/woocommerce/tests/legacy/unit-tests/page-functions/class-wc-tests-page-functions.php
@@ -43,4 +43,76 @@ class WC_Tests_Page_Functions extends WC_Unit_Test_Case {
$url = wc_get_endpoint_url( 'customer-logout', '', 'https://' . WP_TESTS_DOMAIN . '/' );
$this->assertEquals( 'https://' . WP_TESTS_DOMAIN . '/customer-logout', $url );
}
+
+ /**
+ * Reset the query globals mutated by the endpoint-title tests and restore the filter
+ * that wc_page_endpoint_title() removes from `the_title` once it matches.
+ */
+ public function tearDown(): void {
+ global $wp, $wp_query, $wp_the_query, $post;
+ $wp = null; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
+ $wp_query = null; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
+ $wp_the_query = null; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
+ $post = null; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
+ delete_option( 'woocommerce_myaccount_page_id' );
+ add_filter( 'the_title', 'wc_page_endpoint_title', 10, 2 );
+
+ parent::tearDown();
+ }
+
+ /**
+ * Set up the globals for a My Account "orders" endpoint request rendered inside the main
+ * loop, so the conditional tags wc_page_endpoint_title() relies on are all genuinely true.
+ *
+ * @return int The My Account page ID, which is also the queried object.
+ */
+ private function set_up_orders_endpoint_in_loop() {
+ $page_id = wp_insert_post(
+ array(
+ 'post_type' => 'page',
+ 'post_status' => 'publish',
+ 'post_title' => 'My account',
+ 'post_name' => 'my-account',
+ )
+ );
+ update_option( 'woocommerce_myaccount_page_id', $page_id );
+
+ global $post, $wp, $wp_query, $wp_the_query;
+ $post = get_post( $page_id ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
+
+ $wp = new stdClass(); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
+ $wp->query_vars = array( 'orders' => '' );
+
+ $wp_query = new WP_Query(); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
+ $wp_query->is_page = true;
+ $wp_query->in_the_loop = true;
+ $wp_query->queried_object = $post;
+ $wp_query->queried_object_id = $page_id;
+ $wp_the_query = $wp_query; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- is_main_query() compares against this.
+
+ return $page_id;
+ }
+
+ /**
+ * @testdox wc_page_endpoint_title() replaces the queried page's title with the endpoint title.
+ */
+ public function test_wc_page_endpoint_title_replaces_queried_page_title() {
+ $page_id = $this->set_up_orders_endpoint_in_loop();
+
+ $this->assertSame( 'Orders', wc_page_endpoint_title( 'My account', $page_id ) );
+ }
+
+ /**
+ * @testdox wc_page_endpoint_title() leaves titles of other posts untouched so an earlier title does not consume the one-shot the_title filter before the page heading renders.
+ */
+ public function test_wc_page_endpoint_title_ignores_non_queried_titles() {
+ $this->set_up_orders_endpoint_in_loop();
+ $product = WC_Helper_Product::create_simple_product();
+
+ $this->assertSame(
+ $product->get_name(),
+ wc_page_endpoint_title( $product->get_name(), $product->get_id() ),
+ 'A title belonging to another post (e.g. a product in a server-rendered mini-cart) must not be replaced with the endpoint title.'
+ );
+ }
}