From e19c18cba94edea039c8b137138ba94df0097d06 Mon Sep 17 00:00:00 2001 From: Felix Arntz Date: Fri, 19 Jan 2024 17:40:09 +0000 Subject: [PATCH] Bootstrap/Load: Introduce functions to check whether WordPress is serving a REST API request. This changeset introduces two functions: * `wp_is_serving_rest_request()` returns a boolean for whether WordPress is serving an actual REST API request. * `wp_is_rest_endpoint()` returns a boolean for whether a WordPress REST API endpoint is currently being used. While this is always the case if `wp_is_serving_rest_request()` returns `true`, the function additionally covers the scenario of internal REST API requests, i.e. where WordPress calls a REST API endpoint within the same request. Both functions should only be used after the `parse_request` action. All relevant manual checks have been adjusted to use one of the new functions, depending on the use-case. They were all using the same constant check so far, while in fact some of them were intending to check for an actual REST API request while others were intending to check for REST endpoint usage. A new filter `wp_is_rest_endpoint` can be used to alter the return value of the `wp_is_rest_endpoint()` function. Props lots.0.logs, TimothyBlynJacobs, flixos90, joehoyle, peterwilsoncc, swissspidy, SergeyBiryukov, pento, mikejolley, iandunn, hellofromTonya, Cybr, petitphp. Fixes #42061. Built from https://develop.svn.wordpress.org/trunk@57312 git-svn-id: http://core.svn.wordpress.org/trunk@56818 1a063a9b-81f0-0310-95a4-ce76da25c4cd --- wp-includes/class-wp-query.php | 2 +- wp-includes/deprecated.php | 2 +- wp-includes/functions.php | 21 ++++++++++- wp-includes/load.php | 4 ++ wp-includes/rest-api.php | 37 ++++++++++++++++++- wp-includes/rest-api/class-wp-rest-server.php | 32 +++++++++++++++- .../class-wp-rest-attachments-controller.php | 4 +- .../class-wp-rest-url-details-controller.php | 2 +- wp-includes/script-loader.php | 2 +- wp-includes/user.php | 1 + wp-includes/version.php | 2 +- 11 files changed, 97 insertions(+), 12 deletions(-) diff --git a/wp-includes/class-wp-query.php b/wp-includes/class-wp-query.php index e6a3e6b743..f683a52bd1 100644 --- a/wp-includes/class-wp-query.php +++ b/wp-includes/class-wp-query.php @@ -1027,7 +1027,7 @@ class WP_Query { } if ( ! ( $this->is_singular || $this->is_archive || $this->is_search || $this->is_feed - || ( defined( 'REST_REQUEST' ) && REST_REQUEST && $this->is_main_query() ) + || ( wp_is_serving_rest_request() && $this->is_main_query() ) || $this->is_trackback || $this->is_404 || $this->is_admin || $this->is_robots || $this->is_favicon ) ) { $this->is_home = true; } diff --git a/wp-includes/deprecated.php b/wp-includes/deprecated.php index e63708f91b..ec7b33f360 100644 --- a/wp-includes/deprecated.php +++ b/wp-includes/deprecated.php @@ -5436,7 +5436,7 @@ function _wp_theme_json_webfonts_handler() { $settings = WP_Theme_JSON_Resolver::get_merged_data()->get_settings(); // If in the editor, add webfonts defined in variations. - if ( is_admin() || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ) { + if ( is_admin() || wp_is_rest_endpoint() ) { $variations = WP_Theme_JSON_Resolver::get_style_variations(); foreach ( $variations as $variation ) { // Skip if fontFamilies are not defined in the variation. diff --git a/wp-includes/functions.php b/wp-includes/functions.php index a3a5e56bfa..ff55251d7d 100644 --- a/wp-includes/functions.php +++ b/wp-includes/functions.php @@ -3718,7 +3718,7 @@ function wp_die( $message = '', $title = '', $args = array() ) { * @param callable $callback Callback function name. */ $callback = apply_filters( 'wp_die_json_handler', '_json_wp_die_handler' ); - } elseif ( defined( 'REST_REQUEST' ) && REST_REQUEST && wp_is_jsonp_request() ) { + } elseif ( wp_is_serving_rest_request() && wp_is_jsonp_request() ) { /** * Filters the callback for killing WordPress execution for JSONP REST requests. * @@ -4441,7 +4441,7 @@ function _wp_json_prepare_data( $value ) { * @param int $flags Optional. Options to be passed to json_encode(). Default 0. */ function wp_send_json( $response, $status_code = null, $flags = 0 ) { - if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) { + if ( wp_is_serving_rest_request() ) { _doing_it_wrong( __FUNCTION__, sprintf( @@ -4697,6 +4697,23 @@ function _mce_set_direction( $mce_init ) { return $mce_init; } +/** + * Determines whether WordPress is currently serving a REST API request. + * + * The function relies on the 'REST_REQUEST' global. As such, it only returns true when an actual REST _request_ is + * being made. It does not return true when a REST endpoint is hit as part of another request, e.g. for preloading a + * REST response. See {@see wp_is_rest_endpoint()} for that purpose. + * + * This function should not be called until the {@see 'parse_request'} action, as the constant is only defined then, + * even for an actual REST request. + * + * @since 6.5.0 + * + * @return bool True if it's a WordPress REST API request, false otherwise. + */ +function wp_is_serving_rest_request() { + return defined( 'REST_REQUEST' ) && REST_REQUEST; +} /** * Converts smiley code to the icon graphic file equivalent. diff --git a/wp-includes/load.php b/wp-includes/load.php index 520902cdd6..0dbbc187cf 100644 --- a/wp-includes/load.php +++ b/wp-includes/load.php @@ -598,6 +598,10 @@ function wp_debug_mode() { error_reporting( E_CORE_ERROR | E_CORE_WARNING | E_COMPILE_ERROR | E_ERROR | E_WARNING | E_PARSE | E_USER_ERROR | E_USER_WARNING | E_RECOVERABLE_ERROR ); } + /* + * The 'REST_REQUEST' check here is optimistic as the constant is most + * likely not set at this point even if it is in fact a REST request. + */ if ( defined( 'XMLRPC_REQUEST' ) || defined( 'REST_REQUEST' ) || defined( 'MS_FILES_REQUEST' ) || ( defined( 'WP_INSTALLING' ) && WP_INSTALLING ) || wp_doing_ajax() || wp_is_json_request() diff --git a/wp-includes/rest-api.php b/wp-includes/rest-api.php index 61e324e801..2631a6663f 100644 --- a/wp-includes/rest-api.php +++ b/wp-includes/rest-api.php @@ -209,7 +209,7 @@ function rest_api_register_rewrites() { * @since 4.4.0 */ function rest_api_default_filters() { - if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) { + if ( wp_is_serving_rest_request() ) { // Deprecated reporting. add_action( 'deprecated_function_run', 'rest_handle_deprecated_function', 10, 3 ); add_filter( 'deprecated_function_trigger_error', '__return_false' ); @@ -3389,3 +3389,38 @@ function rest_convert_error_to_response( $error ) { return new WP_REST_Response( $data, $status ); } + +/** + * Checks whether a REST API endpoint request is currently being handled. + * + * This may be a standalone REST API request, or an internal request dispatched from within a regular page load. + * + * @since 6.5.0 + * + * @global WP_REST_Server $wp_rest_server REST server instance. + * + * @return bool True if a REST endpoint request is currently being handled, false otherwise. + */ +function wp_is_rest_endpoint() { + /* @var WP_REST_Server $wp_rest_server */ + global $wp_rest_server; + + // Check whether this is a standalone REST request. + $is_rest_endpoint = wp_is_serving_rest_request(); + if ( ! $is_rest_endpoint ) { + // Otherwise, check whether an internal REST request is currently being handled. + $is_rest_endpoint = isset( $wp_rest_server ) + && $wp_rest_server->is_dispatching(); + } + + /** + * Filters whether a REST endpoint request is currently being handled. + * + * This may be a standalone REST API request, or an internal request dispatched from within a regular page load. + * + * @since 6.5.0 + * + * @param bool $is_request_endpoint Whether a REST endpoint request is currently being handled. + */ + return (bool) apply_filters( 'wp_is_rest_endpoint', $is_rest_endpoint ); +} diff --git a/wp-includes/rest-api/class-wp-rest-server.php b/wp-includes/rest-api/class-wp-rest-server.php index 6838579ce8..861f5115e6 100644 --- a/wp-includes/rest-api/class-wp-rest-server.php +++ b/wp-includes/rest-api/class-wp-rest-server.php @@ -87,6 +87,14 @@ class WP_REST_Server { */ protected $embed_cache = array(); + /** + * Stores request objects that are currently being handled. + * + * @since 6.5.0 + * @var array + */ + protected $dispatching_requests = array(); + /** * Instantiates the REST server. * @@ -983,6 +991,8 @@ class WP_REST_Server { * @return WP_REST_Response Response returned by the callback. */ public function dispatch( $request ) { + $this->dispatching_requests[] = $request; + /** * Filters the pre-calculated result of a REST API dispatch request. * @@ -1008,6 +1018,7 @@ class WP_REST_Server { $result = $this->error_to_response( $result ); } + array_pop( $this->dispatching_requests ); return $result; } @@ -1015,7 +1026,9 @@ class WP_REST_Server { $matched = $this->match_request_to_handler( $request ); if ( is_wp_error( $matched ) ) { - return $this->error_to_response( $matched ); + $response = $this->error_to_response( $matched ); + array_pop( $this->dispatching_requests ); + return $response; } list( $route, $handler ) = $matched; @@ -1040,7 +1053,22 @@ class WP_REST_Server { } } - return $this->respond_to_request( $request, $route, $handler, $error ); + $response = $this->respond_to_request( $request, $route, $handler, $error ); + array_pop( $this->dispatching_requests ); + return $response; + } + + /** + * Returns whether the REST server is currently dispatching / responding to a request. + * + * This may be a standalone REST API request, or an internal request dispatched from within a regular page load. + * + * @since 6.5.0 + * + * @return bool Whether the REST server is currently handling a request. + */ + public function is_dispatching() { + return (bool) $this->dispatching_requests; } /** diff --git a/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php b/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php index 7367c0fc57..c7da6d068b 100644 --- a/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php +++ b/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php @@ -201,7 +201,7 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller { wp_after_insert_post( $attachment, false, null ); - if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) { + if ( wp_is_serving_rest_request() ) { /* * Set a custom header with the attachment_id. * Used by the browser/client to resume creating image sub-sizes after a PHP fatal error. @@ -630,7 +630,7 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller { update_post_meta( $new_attachment_id, '_wp_attachment_image_alt', wp_slash( $image_alt ) ); } - if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) { + if ( wp_is_serving_rest_request() ) { /* * Set a custom header with the attachment_id. * Used by the browser/client to resume creating image sub-sizes after a PHP fatal error. diff --git a/wp-includes/rest-api/endpoints/class-wp-rest-url-details-controller.php b/wp-includes/rest-api/endpoints/class-wp-rest-url-details-controller.php index c9ac6675d0..7dcc4d7923 100644 --- a/wp-includes/rest-api/endpoints/class-wp-rest-url-details-controller.php +++ b/wp-includes/rest-api/endpoints/class-wp-rest-url-details-controller.php @@ -128,7 +128,7 @@ class WP_REST_URL_Details_Controller extends WP_REST_Controller { * * @since 5.9.0 * - * @param WP_REST_REQUEST $request Full details about the request. + * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error The parsed details as a response object. WP_Error if there are errors. */ public function parse_url_details( $request ) { diff --git a/wp-includes/script-loader.php b/wp-includes/script-loader.php index 8886cb587b..6425afe874 100644 --- a/wp-includes/script-loader.php +++ b/wp-includes/script-loader.php @@ -2586,7 +2586,7 @@ function wp_should_load_block_editor_scripts_and_styles() { * @return bool Whether separate assets will be loaded. */ function wp_should_load_separate_core_block_assets() { - if ( is_admin() || is_feed() || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ) { + if ( is_admin() || is_feed() || wp_is_rest_endpoint() ) { return false; } diff --git a/wp-includes/user.php b/wp-includes/user.php index 5dbff2b929..5d8cd9f57c 100644 --- a/wp-includes/user.php +++ b/wp-includes/user.php @@ -333,6 +333,7 @@ function wp_authenticate_application_password( $input_user, $username, $password return $input_user; } + // The 'REST_REQUEST' check here may happen too early for the constant to be available. $is_api_request = ( ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST ) || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ); /** diff --git a/wp-includes/version.php b/wp-includes/version.php index 9e43c965d3..205deb7271 100644 --- a/wp-includes/version.php +++ b/wp-includes/version.php @@ -16,7 +16,7 @@ * * @global string $wp_version */ -$wp_version = '6.5-alpha-57311'; +$wp_version = '6.5-alpha-57312'; /** * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.