From cf7401dd73402d1311b9873a81fc1f1dbfd54c9a Mon Sep 17 00:00:00 2001
From: Matthew Reishus <mreishus@users.noreply.github.com>
Date: Thu, 12 Jan 2023 13:08:54 -0600
Subject: [PATCH] respond_to_request: store matched handlers across other
 methods

---
 src/wp-includes/rest-api.php                  |  8 ++++--
 .../rest-api/class-wp-rest-response.php       | 27 +++++++++++++++++++
 .../rest-api/class-wp-rest-server.php         | 20 +++++++-------
 3 files changed, 44 insertions(+), 11 deletions(-)

diff --git a/src/wp-includes/rest-api.php b/src/wp-includes/rest-api.php
index 9898501cba..d7af8aec84 100644
--- a/src/wp-includes/rest-api.php
+++ b/src/wp-includes/rest-api.php
@@ -808,12 +808,16 @@ function rest_send_allow_header( $response, $server, $request ) {
 		return $response;
 	}
 
-	$routes = $server->get_routes();
+	$all_handlers = $response->get_all_methods_matched_handlers();
+	if ( empty( $all_handlers ) ) {
+		$routes = $server->get_routes();
+		$all_handlers = $routes[ $matched_route ];
+	}
 
 	$allowed_methods = array();
 
 	// Get the allowed methods across the routes.
-	foreach ( $routes[ $matched_route ] as $_handler ) {
+	foreach ( $all_handlers as $_handler ) {
 		foreach ( $_handler['methods'] as $handler_method => $value ) {
 
 			if ( ! empty( $_handler['permission_callback'] ) ) {
diff --git a/src/wp-includes/rest-api/class-wp-rest-response.php b/src/wp-includes/rest-api/class-wp-rest-response.php
index c6ea11be83..0c90f98ca5 100644
--- a/src/wp-includes/rest-api/class-wp-rest-response.php
+++ b/src/wp-includes/rest-api/class-wp-rest-response.php
@@ -40,6 +40,15 @@ class WP_REST_Response extends WP_HTTP_Response {
 	 */
 	protected $matched_handler = null;
 
+	/**
+	 * All handlers that match the request, even if the method doesn't match.
+	 * For example, this could include both GET and POST handlers for a route.
+	 * Used when calculating the Allow header.
+	 *
+	 * @var null|array
+	 */
+	protected $all_methods_matched_handlers = null;
+
 	/**
 	 * Adds a link to the response.
 	 *
@@ -204,6 +213,24 @@ class WP_REST_Response extends WP_HTTP_Response {
 		$this->matched_handler = $handler;
 	}
 
+	/**
+	 * Retrieves all possible handlers that match the route, even for other HTTP methods.
+	 *
+	 * @return null|array Handlers that could be used to
+	 */
+	public function get_all_methods_matched_handlers() {
+		return $this->all_methods_matched_handlers;
+	}
+
+	/**
+	 * Sets all possible handlers that match the route.
+	 *
+	 * @param array $handlers An array of handlers matching the route across all HTTP methods.
+	 */
+	public function set_all_methods_matched_handlers( $handlers ) {
+		$this->all_methods_matched_handlers = $handlers;
+	}
+
 	/**
 	 * Checks if the response is an error, i.e. >= 400 response code.
 	 *
diff --git a/src/wp-includes/rest-api/class-wp-rest-server.php b/src/wp-includes/rest-api/class-wp-rest-server.php
index 00c34cd621..b8b8d216ad 100644
--- a/src/wp-includes/rest-api/class-wp-rest-server.php
+++ b/src/wp-includes/rest-api/class-wp-rest-server.php
@@ -994,7 +994,7 @@ class WP_REST_Server {
 			return $this->error_to_response( $matched );
 		}
 
-		list( $route, $handler ) = $matched;
+		list( $route, $handler, $handlers_all_methods ) = $matched;
 
 		if ( ! is_callable( $handler['callback'] ) ) {
 			$error = new WP_Error(
@@ -1016,7 +1016,7 @@ class WP_REST_Server {
 			}
 		}
 
-		return $this->respond_to_request( $request, $route, $handler, $error );
+		return $this->respond_to_request( $request, $route, $handler, $error, $handlers_all_methods );
 	}
 
 	/**
@@ -1075,7 +1075,7 @@ class WP_REST_Server {
 				}
 
 				if ( ! is_callable( $callback ) ) {
-					return array( $route, $handler );
+					return array( $route, $handler, $handlers );
 				}
 
 				$request->set_url_params( $args );
@@ -1091,7 +1091,7 @@ class WP_REST_Server {
 
 				$request->set_default_params( $defaults );
 
-				return array( $route, $handler );
+				return array( $route, $handler, $handlers );
 			}
 		}
 
@@ -1108,13 +1108,14 @@ class WP_REST_Server {
 	 * @access private
 	 * @since 5.6.0
 	 *
-	 * @param WP_REST_Request $request  The request object.
-	 * @param string          $route    The matched route regex.
-	 * @param array           $handler  The matched route handler.
-	 * @param WP_Error|null   $response The current error object if any.
+	 * @param WP_REST_Request $request               The request object.
+	 * @param string          $route                 The matched route regex.
+	 * @param array           $handler               The matched route handler.
+	 * @param WP_Error|null   $response              The current error object if any.
+	 * @param array           $all_methods_handlers  The matched route handlers including other methods.
 	 * @return WP_REST_Response
 	 */
-	protected function respond_to_request( $request, $route, $handler, $response ) {
+	protected function respond_to_request( $request, $route, $handler, $response, $all_methods_handlers ) {
 		/**
 		 * Filters the response before executing any REST API callbacks.
 		 *
@@ -1204,6 +1205,7 @@ class WP_REST_Server {
 
 		$response->set_matched_route( $route );
 		$response->set_matched_handler( $handler );
+		$response->set_all_methods_matched_handlers( $all_methods_handlers );
 
 		return $response;
 	}
-- 
2.39.0

