Edit File by line
/home/zeestwma/richards.../wp-conte.../plugins/woocomme.../src/Blocks/Domain/Services
File: Hydration.php
<?php
[0] Fix | Delete
namespace Automattic\WooCommerce\Blocks\Domain\Services;
[1] Fix | Delete
[2] Fix | Delete
use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry;
[3] Fix | Delete
use Automattic\WooCommerce\StoreApi\RoutesController;
[4] Fix | Delete
use Automattic\WooCommerce\StoreApi\SchemaController;
[5] Fix | Delete
use Automattic\WooCommerce\StoreApi\StoreApi;
[6] Fix | Delete
[7] Fix | Delete
/**
[8] Fix | Delete
* Service class that handles hydration of API data for blocks.
[9] Fix | Delete
*/
[10] Fix | Delete
class Hydration {
[11] Fix | Delete
/**
[12] Fix | Delete
* Instance of the asset data registry.
[13] Fix | Delete
*
[14] Fix | Delete
* @var AssetDataRegistry
[15] Fix | Delete
*/
[16] Fix | Delete
protected $asset_data_registry;
[17] Fix | Delete
[18] Fix | Delete
/**
[19] Fix | Delete
* Cached notices to restore after hydrating the API.
[20] Fix | Delete
*
[21] Fix | Delete
* @var array
[22] Fix | Delete
*/
[23] Fix | Delete
protected $cached_store_notices = array();
[24] Fix | Delete
[25] Fix | Delete
/**
[26] Fix | Delete
* Constructor.
[27] Fix | Delete
*
[28] Fix | Delete
* @param AssetDataRegistry $asset_data_registry Instance of the asset data registry.
[29] Fix | Delete
*/
[30] Fix | Delete
public function __construct( AssetDataRegistry $asset_data_registry ) {
[31] Fix | Delete
$this->asset_data_registry = $asset_data_registry;
[32] Fix | Delete
}
[33] Fix | Delete
[34] Fix | Delete
/**
[35] Fix | Delete
* Hydrates the asset data registry with data from the API. Disables notices and nonces so requests contain valid
[36] Fix | Delete
* data that is not polluted by the current session.
[37] Fix | Delete
*
[38] Fix | Delete
* @param array $path API paths to hydrate e.g. '/wc/store/v1/cart'.
[39] Fix | Delete
* @return array Response data.
[40] Fix | Delete
*/
[41] Fix | Delete
public function get_rest_api_response_data( $path = '' ) {
[42] Fix | Delete
if ( ! str_starts_with( $path, '/wc/store' ) ) {
[43] Fix | Delete
return array();
[44] Fix | Delete
}
[45] Fix | Delete
[46] Fix | Delete
// Allow-list only store API routes. No other request can be hydrated for safety.
[47] Fix | Delete
$available_routes = StoreApi::container()->get( RoutesController::class )->get_all_routes( 'v1', true );
[48] Fix | Delete
$controller_class = $this->match_route_to_handler( $path, $available_routes );
[49] Fix | Delete
[50] Fix | Delete
/**
[51] Fix | Delete
* We disable nonce check to support endpoints such as checkout. The caveat here is that we need to be careful to only support GET requests. No other request type should be processed without nonce check. Additionally, no GET request can modify data as part of hydration request, for example adding items to cart.
[52] Fix | Delete
*
[53] Fix | Delete
* Long term, we should consider validating nonce here, instead of disabling it temporarily.
[54] Fix | Delete
*/
[55] Fix | Delete
$this->disable_nonce_check();
[56] Fix | Delete
[57] Fix | Delete
$this->cache_store_notices();
[58] Fix | Delete
[59] Fix | Delete
$preloaded_data = array();
[60] Fix | Delete
[61] Fix | Delete
if ( null !== $controller_class ) {
[62] Fix | Delete
try {
[63] Fix | Delete
$response = $this->get_response_from_controller( $controller_class, $path );
[64] Fix | Delete
if ( $response ) {
[65] Fix | Delete
$preloaded_data = array(
[66] Fix | Delete
'body' => $response->get_data(),
[67] Fix | Delete
'headers' => $response->get_headers(),
[68] Fix | Delete
);
[69] Fix | Delete
}
[70] Fix | Delete
} catch ( \Exception $e ) {
[71] Fix | Delete
// This is executing in frontend of the site, a failure in hydration should not stop the site from working.
[72] Fix | Delete
wc_get_logger()->warning(
[73] Fix | Delete
'Error in hydrating REST API request: ' . $e->getMessage(),
[74] Fix | Delete
array(
[75] Fix | Delete
'source' => 'blocks-hydration',
[76] Fix | Delete
'data' => array(
[77] Fix | Delete
'path' => $path,
[78] Fix | Delete
'controller' => $controller_class,
[79] Fix | Delete
),
[80] Fix | Delete
'backtrace' => true,
[81] Fix | Delete
)
[82] Fix | Delete
);
[83] Fix | Delete
}
[84] Fix | Delete
} else {
[85] Fix | Delete
// Preload the request and add it to the array. It will be $preloaded_requests['path'] and contain 'body' and 'headers'.
[86] Fix | Delete
$preloaded_requests = rest_preload_api_request( array(), $path );
[87] Fix | Delete
$preloaded_data = $preloaded_requests[ $path ] ?? array();
[88] Fix | Delete
}
[89] Fix | Delete
[90] Fix | Delete
$this->restore_cached_store_notices();
[91] Fix | Delete
$this->restore_nonce_check();
[92] Fix | Delete
[93] Fix | Delete
// Returns just the single preloaded request, or an empty array if it doesn't exist.
[94] Fix | Delete
return $preloaded_data;
[95] Fix | Delete
}
[96] Fix | Delete
[97] Fix | Delete
/**
[98] Fix | Delete
* Helper method to generate GET response from a controller. Also fires the `rest_request_after_callbacks` for backward compatibility.
[99] Fix | Delete
*
[100] Fix | Delete
* @param string $controller_class Controller class FQN that will respond to the request.
[101] Fix | Delete
* @param string $path Request path regex.
[102] Fix | Delete
*
[103] Fix | Delete
* @return false|mixed|null Response
[104] Fix | Delete
*/
[105] Fix | Delete
private function get_response_from_controller( $controller_class, $path ) {
[106] Fix | Delete
if ( null === $controller_class ) {
[107] Fix | Delete
return false;
[108] Fix | Delete
}
[109] Fix | Delete
[110] Fix | Delete
$request = new \WP_REST_Request( 'GET', $path );
[111] Fix | Delete
$schema_controller = StoreApi::container()->get( SchemaController::class );
[112] Fix | Delete
$controller = new $controller_class(
[113] Fix | Delete
$schema_controller,
[114] Fix | Delete
$schema_controller->get( $controller_class::SCHEMA_TYPE, $controller_class::SCHEMA_VERSION )
[115] Fix | Delete
);
[116] Fix | Delete
[117] Fix | Delete
$controller_args = is_callable( array( $controller, 'get_args' ) ) ? $controller->get_args() : array();
[118] Fix | Delete
[119] Fix | Delete
if ( empty( $controller_args ) ) {
[120] Fix | Delete
return false;
[121] Fix | Delete
}
[122] Fix | Delete
[123] Fix | Delete
// Get the handler that responds to read request.
[124] Fix | Delete
$handler = current(
[125] Fix | Delete
array_filter(
[126] Fix | Delete
$controller_args,
[127] Fix | Delete
function ( $method_handler ) {
[128] Fix | Delete
return is_array( $method_handler ) && isset( $method_handler['methods'] ) && \WP_REST_Server::READABLE === $method_handler['methods'];
[129] Fix | Delete
}
[130] Fix | Delete
)
[131] Fix | Delete
);
[132] Fix | Delete
[133] Fix | Delete
if ( ! $handler ) {
[134] Fix | Delete
return false;
[135] Fix | Delete
}
[136] Fix | Delete
[137] Fix | Delete
/**
[138] Fix | Delete
* Similar to WP core's `rest_dispatch_request` filter, this allows plugin to override hydrating the request.
[139] Fix | Delete
* Allows backward compatibility with the `rest_dispatch_request` filter by providing the same arguments.
[140] Fix | Delete
*
[141] Fix | Delete
* @since 8.9.0
[142] Fix | Delete
*
[143] Fix | Delete
* @param mixed $hydration_result Result of the hydration. If not null, this will be used as the response.
[144] Fix | Delete
* @param WP_REST_Request $request Request used to generate the response.
[145] Fix | Delete
* @param string $path Request path matched for the request..
[146] Fix | Delete
* @param array $handler Route handler used for the request.
[147] Fix | Delete
*/
[148] Fix | Delete
$hydration_result = apply_filters( 'woocommerce_hydration_dispatch_request', null, $request, $path, $handler );
[149] Fix | Delete
[150] Fix | Delete
if ( null !== $hydration_result ) {
[151] Fix | Delete
$response = $hydration_result;
[152] Fix | Delete
} else {
[153] Fix | Delete
$response = call_user_func_array( $handler['callback'], array( $request ) );
[154] Fix | Delete
}
[155] Fix | Delete
[156] Fix | Delete
/**
[157] Fix | Delete
* Similar to WP core's `rest_request_after_callbacks` filter, this allows to modify the response after it has been generated.
[158] Fix | Delete
* Allows backward compatibility with the `rest_request_after_callbacks` filter by providing the same arguments.
[159] Fix | Delete
*
[160] Fix | Delete
* @since 8.9.0
[161] Fix | Delete
*
[162] Fix | Delete
* @param WP_REST_Response|WP_HTTP_Response|WP_Error|mixed $response Result to send to the client.
[163] Fix | Delete
* Usually a WP_REST_Response or WP_Error.
[164] Fix | Delete
* @param array $handler Route handler used for the request.
[165] Fix | Delete
* @param WP_REST_Request $request Request used to generate the response.
[166] Fix | Delete
*/
[167] Fix | Delete
$response = apply_filters( 'woocommerce_hydration_request_after_callbacks', $response, $handler, $request );
[168] Fix | Delete
[169] Fix | Delete
return $response;
[170] Fix | Delete
}
[171] Fix | Delete
[172] Fix | Delete
/**
[173] Fix | Delete
* Inspired from WP core's `match_request_to_handler`, this matches a given path from available route regexes.
[174] Fix | Delete
* However, unlike WP core, this does not check against query params, request method etc.
[175] Fix | Delete
*
[176] Fix | Delete
* @param string $path The path to match.
[177] Fix | Delete
* @param array $available_routes Available routes in { $regex1 => $contoller_class1, ... } format.
[178] Fix | Delete
*
[179] Fix | Delete
* @return string|null
[180] Fix | Delete
*/
[181] Fix | Delete
private function match_route_to_handler( $path, $available_routes ) {
[182] Fix | Delete
$matched_route = null;
[183] Fix | Delete
foreach ( $available_routes as $route_path => $controller ) {
[184] Fix | Delete
$match = preg_match( '@^' . $route_path . '$@i', $path );
[185] Fix | Delete
if ( $match ) {
[186] Fix | Delete
$matched_route = $controller;
[187] Fix | Delete
break;
[188] Fix | Delete
}
[189] Fix | Delete
}
[190] Fix | Delete
return $matched_route;
[191] Fix | Delete
}
[192] Fix | Delete
[193] Fix | Delete
/**
[194] Fix | Delete
* Disable the nonce check temporarily.
[195] Fix | Delete
*/
[196] Fix | Delete
protected function disable_nonce_check() {
[197] Fix | Delete
add_filter( 'woocommerce_store_api_disable_nonce_check', array( $this, 'disable_nonce_check_callback' ) );
[198] Fix | Delete
}
[199] Fix | Delete
[200] Fix | Delete
/**
[201] Fix | Delete
* Callback to disable the nonce check. While we could use `__return_true`, we use a custom named callback so that
[202] Fix | Delete
* we can remove it later without affecting other filters.
[203] Fix | Delete
*/
[204] Fix | Delete
public function disable_nonce_check_callback() {
[205] Fix | Delete
return true;
[206] Fix | Delete
}
[207] Fix | Delete
[208] Fix | Delete
/**
[209] Fix | Delete
* Restore the nonce check.
[210] Fix | Delete
*/
[211] Fix | Delete
protected function restore_nonce_check() {
[212] Fix | Delete
remove_filter( 'woocommerce_store_api_disable_nonce_check', array( $this, 'disable_nonce_check_callback' ) );
[213] Fix | Delete
}
[214] Fix | Delete
[215] Fix | Delete
/**
[216] Fix | Delete
* Cache notices before hydrating the API if the customer has a session.
[217] Fix | Delete
*/
[218] Fix | Delete
protected function cache_store_notices() {
[219] Fix | Delete
if ( ! did_action( 'woocommerce_init' ) || null === WC()->session ) {
[220] Fix | Delete
return;
[221] Fix | Delete
}
[222] Fix | Delete
$this->cached_store_notices = wc_get_notices();
[223] Fix | Delete
wc_clear_notices();
[224] Fix | Delete
}
[225] Fix | Delete
[226] Fix | Delete
/**
[227] Fix | Delete
* Restore notices into current session from cache.
[228] Fix | Delete
*/
[229] Fix | Delete
protected function restore_cached_store_notices() {
[230] Fix | Delete
if ( ! did_action( 'woocommerce_init' ) || null === WC()->session ) {
[231] Fix | Delete
return;
[232] Fix | Delete
}
[233] Fix | Delete
[234] Fix | Delete
wc_set_notices( $this->cached_store_notices );
[235] Fix | Delete
$this->cached_store_notices = array();
[236] Fix | Delete
}
[237] Fix | Delete
}
[238] Fix | Delete
[239] Fix | Delete
It is recommended that you Edit text format, this type of Fix handles quite a lot in one request
Function