Commit 37f05098d91 for woocommerce
commit 37f05098d9130496021c66655b7aed51947e5698
Author: Sergey Mitroshin <sergeymitr@gmail.com>
Date: Wed Nov 4 21:58:12 2026 -0500
Fix UCP discovery missing Allow header and stale rewrite rules (#70679)
Fix UCP discovery missing Allow header and stale rewrite rules.
diff --git a/plugins/woocommerce/changelog/fix-ucp-discovery-allow-header-and-stale-rewrite b/plugins/woocommerce/changelog/fix-ucp-discovery-allow-header-and-stale-rewrite
new file mode 100644
index 00000000000..3e9d4bcf250
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-ucp-discovery-allow-header-and-stale-rewrite
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+UCP: Send an Allow header with 405 responses from /.well-known/ucp and recover the endpoint when the flag changes without a rewrite flush.
diff --git a/plugins/woocommerce/src/Internal/Ucp/UcpDiscovery.php b/plugins/woocommerce/src/Internal/Ucp/UcpDiscovery.php
index 87752e4ed7b..89d293d6642 100644
--- a/plugins/woocommerce/src/Internal/Ucp/UcpDiscovery.php
+++ b/plugins/woocommerce/src/Internal/Ucp/UcpDiscovery.php
@@ -38,13 +38,20 @@ final class UcpDiscovery implements RegisterHooksInterface {
public function register_feature_hooks(): void {
global $wp_rewrite;
- if ( ! UcpUtils::is_enabled() || ! $wp_rewrite->using_permalinks() ) {
- return;
+ $active = UcpUtils::is_enabled() && $wp_rewrite->using_permalinks();
+
+ if ( $active ) {
+ add_rewrite_rule( self::RULE, 'index.php?' . self::QUERY_VAR . '=1', 'top' );
+ add_filter( 'query_vars', array( $this, 'add_query_var' ), 0 );
+ add_action( 'parse_request', array( $this, 'maybe_render' ), 0 );
}
- add_rewrite_rule( self::RULE, 'index.php?' . self::QUERY_VAR . '=1', 'top' );
- add_filter( 'query_vars', array( $this, 'add_query_var' ), 0 );
- add_action( 'parse_request', array( $this, 'maybe_render' ), 0 );
+ // The flag can change without FEATURE_ENABLED_CHANGED_ACTION firing (direct DB write, option import),
+ // so flush when the stored rules disagree; empty stored rules are regenerated by WP on their own.
+ $rules = get_option( 'rewrite_rules' );
+ if ( is_array( $rules ) && ! empty( $rules ) && isset( $rules[ self::RULE ] ) !== $active ) {
+ update_option( 'woocommerce_queue_flush_rewrite_rules', 'yes' );
+ }
}
/**
@@ -82,6 +89,8 @@ final class UcpDiscovery implements RegisterHooksInterface {
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput
if ( ! in_array( $_SERVER['REQUEST_METHOD'] ?? '', array( 'GET', 'HEAD' ), true ) ) {
+ // RFC 9110 requires a 405 to name the allowed methods.
+ header( 'Allow: GET, HEAD' );
status_header( 405 );
exit;
}
diff --git a/plugins/woocommerce/tests/php/src/Internal/Ucp/UcpDiscoveryTest.php b/plugins/woocommerce/tests/php/src/Internal/Ucp/UcpDiscoveryTest.php
index 62958bc5acb..d9298eeb7ba 100644
--- a/plugins/woocommerce/tests/php/src/Internal/Ucp/UcpDiscoveryTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/Ucp/UcpDiscoveryTest.php
@@ -35,6 +35,7 @@ class UcpDiscoveryTest extends \WC_Unit_Test_Case {
unset( $wp_rewrite->extra_rules_top[ self::RULE ] );
remove_filter( 'query_vars', array( $this->sut, 'add_query_var' ), 0 );
remove_action( 'parse_request', array( $this->sut, 'maybe_render' ), 0 );
+ delete_option( 'woocommerce_queue_flush_rewrite_rules' );
parent::tearDown();
}
@@ -77,6 +78,32 @@ class UcpDiscoveryTest extends \WC_Unit_Test_Case {
$this->assertSame( $expected ? 0 : false, has_action( 'parse_request', array( $this->sut, 'maybe_render' ) ) );
}
+ /**
+ * @testdox register_feature_hooks queues a rewrite flush when the stored rules disagree with the feature flag.
+ * @testWith ["yes", "present", false]
+ * ["yes", "missing", true]
+ * ["yes", "empty", false]
+ * ["no", "present", true]
+ *
+ * @param string $enabled Feature option value.
+ * @param string $stored Whether the stored rules contain the rule, lack it, or are empty.
+ * @param bool $expected Whether a flush should be queued.
+ */
+ public function test_register_feature_hooks_heals_stale_rewrite_rules( string $enabled, string $stored, bool $expected ): void {
+ update_option( 'woocommerce_feature_ucp_enabled', $enabled );
+ $this->set_permalink_structure( '/%postname%/' );
+ $rules = array( '^shop/?$' => 'index.php?post_type=product' );
+ if ( 'present' === $stored ) {
+ $rules[ self::RULE ] = 'index.php?' . UcpDiscovery::QUERY_VAR . '=1';
+ }
+ update_option( 'rewrite_rules', 'empty' === $stored ? array() : $rules );
+ update_option( 'woocommerce_queue_flush_rewrite_rules', 'no' );
+
+ $this->sut->register_feature_hooks();
+
+ $this->assertSame( $expected ? 'yes' : 'no', get_option( 'woocommerce_queue_flush_rewrite_rules' ) );
+ }
+
/**
* @testdox Toggling the UCP feature queues a rewrite rules flush.
*/