plugin updates

This commit is contained in:
Tony Volpe
2024-06-17 14:48:11 -04:00
parent ecc5fbf831
commit 3751a5a1a6
1318 changed files with 91130 additions and 52250 deletions

View File

@@ -32,7 +32,7 @@ class CheckoutFieldsAdmin {
add_filter( 'woocommerce_admin_billing_fields', array( $this, 'admin_address_fields' ), 10, 3 );
add_filter( 'woocommerce_admin_billing_fields', array( $this, 'admin_contact_fields' ), 10, 3 );
add_filter( 'woocommerce_admin_shipping_fields', array( $this, 'admin_address_fields' ), 10, 3 );
add_filter( 'woocommerce_admin_shipping_fields', array( $this, 'admin_additional_fields' ), 10, 3 );
add_filter( 'woocommerce_admin_shipping_fields', array( $this, 'admin_order_fields' ), 10, 3 );
}
/**
@@ -73,7 +73,9 @@ class CheckoutFieldsAdmin {
* @param \WC_Order $order The order to update the field for.
*/
public function update_callback( $key, $value, $order ) {
$this->checkout_fields_controller->persist_field_for_order( $key, $value, $order, false );
list( $group, $key ) = explode( '/', $key, 2 );
$group = CheckoutFields::get_group_name( $group );
$this->checkout_fields_controller->persist_field_for_order( $key, $value, $order, $group, false );
}
/**
@@ -89,11 +91,11 @@ class CheckoutFieldsAdmin {
return $fields;
}
$group = doing_action( 'woocommerce_admin_billing_fields' ) ? 'billing' : 'shipping';
$additional_fields = $this->checkout_fields_controller->get_order_additional_fields_with_values( $order, 'address', $group, $context );
$group_name = doing_action( 'woocommerce_admin_billing_fields' ) ? 'billing' : 'shipping';
$additional_fields = $this->checkout_fields_controller->get_order_additional_fields_with_values( $order, 'address', $group_name, $context );
foreach ( $additional_fields as $key => $field ) {
$group_key = '/' . $group . '/' . $key;
$additional_fields[ $key ] = $this->format_field_for_meta_box( $field, $group_key );
$prefixed_key = CheckoutFields::get_group_key( $group_name ) . $key;
$additional_fields[ $key ] = $this->format_field_for_meta_box( $field, $prefixed_key );
}
array_splice(
@@ -123,16 +125,14 @@ class CheckoutFieldsAdmin {
return $fields;
}
$additional_fields = $this->checkout_fields_controller->get_order_additional_fields_with_values( $order, 'contact', '', $context );
$additional_fields = $this->checkout_fields_controller->get_order_additional_fields_with_values( $order, 'contact', 'other', $context );
return array_merge(
$fields,
array_map(
array( $this, 'format_field_for_meta_box' ),
$additional_fields,
array_keys( $additional_fields )
)
);
foreach ( $additional_fields as $key => $field ) {
$prefixed_key = CheckoutFields::get_group_key( 'other' ) . $key;
$additional_fields[ $key ] = $this->format_field_for_meta_box( $field, $prefixed_key );
}
return array_merge( $fields, $additional_fields );
}
/**
@@ -143,20 +143,18 @@ class CheckoutFieldsAdmin {
* @param string $context The context to show the fields for.
* @return array
*/
public function admin_additional_fields( $fields, $order = null, $context = 'edit' ) {
public function admin_order_fields( $fields, $order = null, $context = 'edit' ) {
if ( ! $order instanceof \WC_Order ) {
return $fields;
}
$additional_fields = $this->checkout_fields_controller->get_order_additional_fields_with_values( $order, 'additional', '', $context );
$additional_fields = $this->checkout_fields_controller->get_order_additional_fields_with_values( $order, 'order', 'other', $context );
return array_merge(
$fields,
array_map(
array( $this, 'format_field_for_meta_box' ),
$additional_fields,
array_keys( $additional_fields )
)
);
foreach ( $additional_fields as $key => $field ) {
$prefixed_key = CheckoutFields::get_group_key( 'other' ) . $key;
$additional_fields[ $key ] = $this->format_field_for_meta_box( $field, $prefixed_key );
}
return array_merge( $fields, $additional_fields );
}
}

View File

@@ -32,7 +32,7 @@ class CheckoutFieldsFrontend {
public function init() {
// Show custom checkout fields on the order details page.
add_action( 'woocommerce_order_details_after_customer_address', array( $this, 'render_order_address_fields' ), 10, 2 );
add_action( 'woocommerce_order_details_after_customer_details', array( $this, 'render_order_additional_fields' ), 10 );
add_action( 'woocommerce_order_details_after_customer_details', array( $this, 'render_order_other_fields' ), 10 );
// Show custom checkout fields on the My Account page.
add_action( 'woocommerce_my_account_after_my_address', array( $this, 'render_address_fields' ), 10, 1 );
@@ -87,10 +87,10 @@ class CheckoutFieldsFrontend {
*
* @param WC_Order $order Order object.
*/
public function render_order_additional_fields( $order ) {
public function render_order_other_fields( $order ) {
$fields = array_merge(
$this->checkout_fields_controller->get_order_additional_fields_with_values( $order, 'contact', '', 'view' ),
$this->checkout_fields_controller->get_order_additional_fields_with_values( $order, 'additional', '', 'view' ),
$this->checkout_fields_controller->get_order_additional_fields_with_values( $order, 'contact', 'other', 'view' ),
$this->checkout_fields_controller->get_order_additional_fields_with_values( $order, 'order', 'other', 'view' ),
);
if ( ! $fields ) {
@@ -122,7 +122,7 @@ class CheckoutFieldsFrontend {
foreach ( $fields as $key => $field ) {
$value = $this->checkout_fields_controller->format_additional_field_value(
$this->checkout_fields_controller->get_field_from_customer( $key, $customer, $address_type ),
$this->checkout_fields_controller->get_field_from_object( $key, $customer, $address_type ),
$field
);
@@ -160,8 +160,10 @@ class CheckoutFieldsFrontend {
$fields = $this->checkout_fields_controller->get_fields_for_location( 'contact' );
foreach ( $fields as $key => $field ) {
$field_key = CheckoutFields::get_group_key( 'other' ) . $key;
$form_field = $field;
$form_field['value'] = $this->checkout_fields_controller->get_field_from_customer( $key, $customer, 'contact' );
$form_field['id'] = $field_key;
$form_field['value'] = $this->checkout_fields_controller->get_field_from_object( $key, $customer, 'contact' );
if ( 'select' === $field['type'] ) {
$form_field['options'] = array_column( $field['options'], 'label', 'value' );
@@ -189,12 +191,13 @@ class CheckoutFieldsFrontend {
$additional_fields = $this->checkout_fields_controller->get_fields_for_location( 'contact' );
$field_values = array();
foreach ( $additional_fields as $key => $field ) {
if ( ! isset( $_POST[ $key ] ) ) {
foreach ( array_keys( $additional_fields ) as $key ) {
$post_key = CheckoutFields::get_group_key( 'other' ) . $key;
if ( ! isset( $_POST[ $post_key ] ) ) {
continue;
}
$field_value = $this->checkout_fields_controller->sanitize_field( $key, wc_clean( wp_unslash( $_POST[ $key ] ) ) );
$field_value = $this->checkout_fields_controller->sanitize_field( $key, wc_clean( wp_unslash( $_POST[ $post_key ] ) ) );
$validation = $this->checkout_fields_controller->validate_field( $key, $field_value );
if ( is_wp_error( $validation ) && $validation->has_errors() ) {
@@ -207,11 +210,11 @@ class CheckoutFieldsFrontend {
// Persist individual additional fields to customer.
foreach ( $field_values as $key => $value ) {
$this->checkout_fields_controller->persist_field_for_customer( $key, $value, $customer );
$this->checkout_fields_controller->persist_field_for_customer( $key, $value, $customer, 'other' );
}
// Validate all fields for this location.
$location_validation = $this->checkout_fields_controller->validate_fields_for_location( $field_values, 'contact' );
$location_validation = $this->checkout_fields_controller->validate_fields_for_location( $field_values, 'contact', 'other' );
if ( is_wp_error( $location_validation ) && $location_validation->has_errors() ) {
wc_add_notice( $location_validation->get_error_message(), 'error' );
@@ -233,9 +236,9 @@ class CheckoutFieldsFrontend {
$fields = $this->checkout_fields_controller->get_fields_for_location( 'address' );
foreach ( $fields as $key => $field ) {
$field_key = "/{$address_type}/{$key}";
$field_key = CheckoutFields::get_group_key( $address_type ) . $key;
$address[ $field_key ] = $field;
$address[ $field_key ]['value'] = $this->checkout_fields_controller->get_field_from_customer( $key, $customer, $address_type );
$address[ $field_key ]['value'] = $this->checkout_fields_controller->get_field_from_object( $key, $customer, $address_type );
if ( 'select' === $field['type'] ) {
$address[ $field_key ]['options'] = array_column( $field['options'], 'label', 'value' );
@@ -266,8 +269,8 @@ class CheckoutFieldsFrontend {
$additional_fields = $this->checkout_fields_controller->get_fields_for_location( 'address' );
$field_values = array();
foreach ( $additional_fields as $key => $field ) {
$post_key = "/{$address_type}/{$key}";
foreach ( array_keys( $additional_fields ) as $key ) {
$post_key = CheckoutFields::get_group_key( $address_type ) . $key;
if ( ! isset( $_POST[ $post_key ] ) ) {
continue;
@@ -286,7 +289,7 @@ class CheckoutFieldsFrontend {
// Persist individual additional fields to customer.
foreach ( $field_values as $key => $value ) {
$this->checkout_fields_controller->persist_field_for_customer( "/{$address_type}/{$key}", $value, $customer );
$this->checkout_fields_controller->persist_field_for_customer( $key, $value, $customer, $address_type );
}
// Validate all fields for this location.

View File

@@ -2,6 +2,9 @@
namespace Automattic\WooCommerce\Blocks\Domain\Services;
use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry;
use Automattic\WooCommerce\StoreApi\RoutesController;
use Automattic\WooCommerce\StoreApi\SchemaController;
use Automattic\WooCommerce\StoreApi\StoreApi;
/**
* Service class that handles hydration of API data for blocks.
@@ -19,7 +22,7 @@ class Hydration {
*
* @var array
*/
protected $cached_store_notices = [];
protected $cached_store_notices = array();
/**
* Constructor.
@@ -38,24 +41,162 @@ class Hydration {
* @return array Response data.
*/
public function get_rest_api_response_data( $path = '' ) {
$this->cache_store_notices();
if ( ! str_starts_with( $path, '/wc/store' ) ) {
return array();
}
// Allow-list only store API routes. No other request can be hydrated for safety.
$available_routes = StoreApi::container()->get( RoutesController::class )->get_all_routes( 'v1', true );
$controller_class = $this->match_route_to_handler( $path, $available_routes );
/**
* 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.
*
* Long term, we should consider validating nonce here, instead of disabling it temporarily.
*/
$this->disable_nonce_check();
// Preload the request and add it to the array. It will be $preloaded_requests['path'] and contain 'body' and 'headers'.
$preloaded_requests = rest_preload_api_request( [], $path );
$this->cache_store_notices();
$preloaded_data = array();
if ( null !== $controller_class ) {
try {
$response = $this->get_response_from_controller( $controller_class, $path );
if ( $response ) {
$preloaded_data = array(
'body' => $response->get_data(),
'headers' => $response->get_headers(),
);
}
} catch ( \Exception $e ) {
// This is executing in frontend of the site, a failure in hydration should not stop the site from working.
wc_get_logger()->warning(
'Error in hydrating REST API request: ' . $e->getMessage(),
array(
'source' => 'blocks-hydration',
'data' => array(
'path' => $path,
'controller' => $controller_class,
),
'backtrace' => true,
)
);
}
} else {
// Preload the request and add it to the array. It will be $preloaded_requests['path'] and contain 'body' and 'headers'.
$preloaded_requests = rest_preload_api_request( array(), $path );
$preloaded_data = $preloaded_requests[ $path ] ?? array();
}
$this->restore_cached_store_notices();
$this->restore_nonce_check();
// Returns just the single preloaded request, or an empty array if it doesn't exist.
return $preloaded_requests[ $path ] ?? [];
return $preloaded_data;
}
/**
* Helper method to generate GET response from a controller. Also fires the `rest_request_after_callbacks` for backward compatibility.
*
* @param string $controller_class Controller class FQN that will respond to the request.
* @param string $path Request path regex.
*
* @return false|mixed|null Response
*/
private function get_response_from_controller( $controller_class, $path ) {
if ( null === $controller_class ) {
return false;
}
$request = new \WP_REST_Request( 'GET', $path );
$schema_controller = StoreApi::container()->get( SchemaController::class );
$controller = new $controller_class(
$schema_controller,
$schema_controller->get( $controller_class::SCHEMA_TYPE, $controller_class::SCHEMA_VERSION )
);
$controller_args = is_callable( array( $controller, 'get_args' ) ) ? $controller->get_args() : array();
if ( empty( $controller_args ) ) {
return false;
}
// Get the handler that responds to read request.
$handler = current(
array_filter(
$controller_args,
function ( $method_handler ) {
return is_array( $method_handler ) && isset( $method_handler['methods'] ) && \WP_REST_Server::READABLE === $method_handler['methods'];
}
)
);
if ( ! $handler ) {
return false;
}
/**
* Similar to WP core's `rest_dispatch_request` filter, this allows plugin to override hydrating the request.
* Allows backward compatibility with the `rest_dispatch_request` filter by providing the same arguments.
*
* @since 8.9.0
*
* @param mixed $hydration_result Result of the hydration. If not null, this will be used as the response.
* @param WP_REST_Request $request Request used to generate the response.
* @param string $path Request path matched for the request..
* @param array $handler Route handler used for the request.
*/
$hydration_result = apply_filters( 'woocommerce_hydration_dispatch_request', null, $request, $path, $handler );
if ( null !== $hydration_result ) {
$response = $hydration_result;
} else {
$response = call_user_func_array( $handler['callback'], array( $request ) );
}
/**
* Similar to WP core's `rest_request_after_callbacks` filter, this allows to modify the response after it has been generated.
* Allows backward compatibility with the `rest_request_after_callbacks` filter by providing the same arguments.
*
* @since 8.9.0
*
* @param WP_REST_Response|WP_HTTP_Response|WP_Error|mixed $response Result to send to the client.
* Usually a WP_REST_Response or WP_Error.
* @param array $handler Route handler used for the request.
* @param WP_REST_Request $request Request used to generate the response.
*/
$response = apply_filters( 'woocommerce_hydration_request_after_callbacks', $response, $handler, $request );
return $response;
}
/**
* Inspired from WP core's `match_request_to_handler`, this matches a given path from available route regexes.
* However, unlike WP core, this does not check against query params, request method etc.
*
* @param string $path The path to match.
* @param array $available_routes Available routes in { $regex1 => $contoller_class1, ... } format.
*
* @return string|null
*/
private function match_route_to_handler( $path, $available_routes ) {
$matched_route = null;
foreach ( $available_routes as $route_path => $controller ) {
$match = preg_match( '@^' . $route_path . '$@i', $path );
if ( $match ) {
$matched_route = $controller;
break;
}
}
return $matched_route;
}
/**
* Disable the nonce check temporarily.
*/
protected function disable_nonce_check() {
add_filter( 'woocommerce_store_api_disable_nonce_check', [ $this, 'disable_nonce_check_callback' ] );
add_filter( 'woocommerce_store_api_disable_nonce_check', array( $this, 'disable_nonce_check_callback' ) );
}
/**
@@ -70,7 +211,7 @@ class Hydration {
* Restore the nonce check.
*/
protected function restore_nonce_check() {
remove_filter( 'woocommerce_store_api_disable_nonce_check', [ $this, 'disable_nonce_check_callback' ] );
remove_filter( 'woocommerce_store_api_disable_nonce_check', array( $this, 'disable_nonce_check_callback' ) );
}
/**
@@ -92,6 +233,6 @@ class Hydration {
return;
}
WC()->session->set( 'wc_notices', $this->cached_store_notices );
$this->cached_store_notices = [];
$this->cached_store_notices = array();
}
}

View File

@@ -3,14 +3,14 @@
use Automattic\WooCommerce\Blocks\Package;
use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFields;
if ( ! function_exists( '__experimental_woocommerce_blocks_register_checkout_field' ) ) {
if ( ! function_exists( 'woocommerce_register_additional_checkout_field' ) ) {
/**
* Register a checkout field.
*
* @param array $options Field arguments. See CheckoutFields::register_checkout_field() for details.
* @throws \Exception If field registration fails.
*/
function __experimental_woocommerce_blocks_register_checkout_field( $options ) { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore,PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore
function woocommerce_register_additional_checkout_field( $options ) { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore,PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore
// Check if `woocommerce_blocks_loaded` ran. If not then the CheckoutFields class will not be available yet.
// In that case, re-hook `woocommerce_blocks_loaded` and try running this again.
@@ -19,7 +19,7 @@ if ( ! function_exists( '__experimental_woocommerce_blocks_register_checkout_fie
add_action(
'woocommerce_blocks_loaded',
function () use ( $options ) {
__experimental_woocommerce_blocks_register_checkout_field( $options );
woocommerce_register_additional_checkout_field( $options );
}
);
return;
@@ -32,6 +32,20 @@ if ( ! function_exists( '__experimental_woocommerce_blocks_register_checkout_fie
}
}
if ( ! function_exists( '__experimental_woocommerce_blocks_register_checkout_field' ) ) {
/**
* Register a checkout field.
*
* @param array $options Field arguments. See CheckoutFields::register_checkout_field() for details.
* @throws \Exception If field registration fails.
* @deprecated 5.6.0 Use woocommerce_register_additional_checkout_field() instead.
*/
function __experimental_woocommerce_blocks_register_checkout_field( $options ) { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore,PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore
wc_deprecated_function( __FUNCTION__, '8.9.0', 'woocommerce_register_additional_checkout_field' );
woocommerce_register_additional_checkout_field( $options );
}
}
if ( ! function_exists( '__internal_woocommerce_blocks_deregister_checkout_field' ) ) {
/**