Prevent Store API orders being placed with empty/invalid fields (#50028)
* Fix spacing/capitalisation on comments. * Use get_fields_for_location to prevent numeric keys going into locale * Merge default locale with current locale Required because the current locale only contains a diff against the default * Iterate over current locale to get validate required fields * Add unit tests to cover the state/postcode checks * Add changelog * Fix lint errors * Ensure locale filter runs when tests need it. * Update tests to reset locale after running * Add validate_required_additional_fields function * Add test to ensure required additional fields are passed * Add throws tag to docblock
This commit is contained in:
parent
02c639c2a3
commit
e2bd308e8e
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
|
||||
Prevent Store API orders being placed with empty state
|
|
@ -780,7 +780,7 @@ class CheckoutFields {
|
|||
* @return mixed
|
||||
*/
|
||||
public function update_default_locale_with_fields( $locale ) {
|
||||
foreach ( $this->fields_locations['address'] as $field_id => $additional_field ) {
|
||||
foreach ( $this->get_fields_for_location( 'address' ) as $field_id => $additional_field ) {
|
||||
if ( empty( $locale[ $field_id ] ) ) {
|
||||
$locale[ $field_id ] = $additional_field;
|
||||
}
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
<?php
|
||||
namespace Automattic\WooCommerce\StoreApi\Routes\V1;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFields;
|
||||
use Automattic\WooCommerce\StoreApi\Payments\PaymentResult;
|
||||
use Automattic\WooCommerce\StoreApi\Exceptions\InvalidCartException;
|
||||
use Automattic\WooCommerce\StoreApi\Exceptions\RouteException;
|
||||
use Automattic\WooCommerce\StoreApi\Utilities\DraftOrderTrait;
|
||||
use Automattic\WooCommerce\Checkout\Helpers\ReserveStockException;
|
||||
use Automattic\WooCommerce\StoreApi\Utilities\CheckoutTrait;
|
||||
use Automattic\WooCommerce\Tests\Blocks\StoreApi\Routes\AdditionalFields;
|
||||
use Automattic\WooCommerce\Utilities\RestApiUtil;
|
||||
|
||||
/**
|
||||
|
@ -174,6 +176,54 @@ class Checkout extends AbstractCartRoute {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate required additional fields on request.
|
||||
*
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
*
|
||||
* @throws RouteException When a required additional field is missing.
|
||||
*/
|
||||
public function validate_required_additional_fields( \WP_REST_Request $request ) {
|
||||
$contact_fields = $this->additional_fields_controller->get_fields_for_location( 'contact' );
|
||||
$order_fields = $this->additional_fields_controller->get_fields_for_location( 'order' );
|
||||
$order_and_contact_fields = array_merge( $contact_fields, $order_fields );
|
||||
|
||||
if ( ! empty( $order_and_contact_fields ) ) {
|
||||
foreach ( $order_and_contact_fields as $field_key => $order_and_contact_field ) {
|
||||
if ( $order_and_contact_field['required'] && ! isset( $request['additional_fields'][ $field_key ] ) ) {
|
||||
throw new RouteException(
|
||||
'woocommerce_rest_checkout_missing_required_field',
|
||||
/* translators: %s: is the field label */
|
||||
esc_html( sprintf( __( 'There was a problem with the provided additional fields: %s is required', 'woocommerce' ), $order_and_contact_field['label'] ) ),
|
||||
400
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$address_fields = $this->additional_fields_controller->get_fields_for_location( 'address' );
|
||||
if ( ! empty( $address_fields ) ) {
|
||||
foreach ( $address_fields as $field_key => $address_field ) {
|
||||
if ( $address_field['required'] && ! isset( $request['billing_address'][ $field_key ] ) ) {
|
||||
throw new RouteException(
|
||||
'woocommerce_rest_checkout_missing_required_field',
|
||||
/* translators: %s: is the field label */
|
||||
esc_html( sprintf( __( 'There was a problem with the provided billing address: %s is required', 'woocommerce' ), $address_field['label'] ) ),
|
||||
400
|
||||
);
|
||||
}
|
||||
if ( $address_field['required'] && ! isset( $request['shipping_address'][ $field_key ] ) ) {
|
||||
throw new RouteException(
|
||||
'woocommerce_rest_checkout_missing_required_field',
|
||||
/* translators: %s: is the field label */
|
||||
esc_html( sprintf( __( 'There was a problem with the provided shipping address: %s is required', 'woocommerce' ), $address_field['label'] ) ),
|
||||
400
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an order.
|
||||
*
|
||||
|
@ -201,6 +251,11 @@ class Checkout extends AbstractCartRoute {
|
|||
*/
|
||||
$this->cart_controller->validate_cart();
|
||||
|
||||
/**
|
||||
* Validate additional fields on request.
|
||||
*/
|
||||
$this->validate_required_additional_fields( $request );
|
||||
|
||||
/**
|
||||
* Persist customer session data from the request first so that OrderController::update_addresses_from_cart
|
||||
* uses the up to date customer address.
|
||||
|
|
|
@ -164,7 +164,8 @@ abstract class AbstractAddressSchema extends AbstractSchema {
|
|||
$address = (array) $address;
|
||||
$validation_util = new ValidationUtils();
|
||||
$schema = $this->get_properties();
|
||||
// omit all keys from address that are not in the schema. This should account for email.
|
||||
|
||||
// Omit all keys from address that are not in the schema. This should account for email.
|
||||
$address = array_intersect_key( $address, $schema );
|
||||
|
||||
// The flow is Validate -> Sanitize -> Re-Validate
|
||||
|
@ -179,6 +180,7 @@ abstract class AbstractAddressSchema extends AbstractSchema {
|
|||
if ( empty( $schema[ $key ] ) || empty( $address[ $key ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( is_wp_error( rest_validate_value_from_schema( $value, $schema[ $key ], $key ) ) ) {
|
||||
$errors->add(
|
||||
'invalid_' . $key,
|
||||
|
|
|
@ -381,30 +381,16 @@ class OrderController {
|
|||
$address = $order->get_address( $address_type );
|
||||
$current_locale = isset( $all_locales[ $address['country'] ] ) ? $all_locales[ $address['country'] ] : array();
|
||||
|
||||
foreach ( $all_locales['default'] as $key => $value ) {
|
||||
$default_value = empty( $current_locale[ $key ] ) ? [] : $current_locale[ $key ];
|
||||
$current_locale[ $key ] = wp_parse_args( $default_value, $value );
|
||||
}
|
||||
|
||||
$additional_fields = $this->additional_fields_controller->get_all_fields_from_object( $order, $address_type );
|
||||
|
||||
$address = array_merge( $address, $additional_fields );
|
||||
|
||||
$fields = $this->additional_fields_controller->get_additional_fields();
|
||||
$address_fields_keys = $this->additional_fields_controller->get_address_fields_keys();
|
||||
$address_fields = array_filter(
|
||||
$fields,
|
||||
function ( $key ) use ( $address_fields_keys ) {
|
||||
return in_array( $key, $address_fields_keys, true );
|
||||
},
|
||||
ARRAY_FILTER_USE_KEY
|
||||
);
|
||||
|
||||
if ( $current_locale ) {
|
||||
foreach ( $current_locale as $key => $field ) {
|
||||
if ( isset( $address_fields[ $key ] ) ) {
|
||||
$address_fields[ $key ]['label'] = isset( $field['label'] ) ? $field['label'] : $address_fields[ $key ]['label'];
|
||||
$address_fields[ $key ]['required'] = isset( $field['required'] ) ? $field['required'] : $address_fields[ $key ]['required'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ( $address_fields as $address_field_key => $address_field ) {
|
||||
foreach ( $current_locale as $address_field_key => $address_field ) {
|
||||
if ( empty( $address[ $address_field_key ] ) && $address_field['required'] ) {
|
||||
/* translators: %s Field label. */
|
||||
$errors->add( $address_type, sprintf( __( '%s is required', 'woocommerce' ), $address_field['label'] ), $address_field_key );
|
||||
|
|
|
@ -81,6 +81,8 @@ class AdditionalFields extends MockeryTestCase {
|
|||
*/
|
||||
protected function tearDown(): void {
|
||||
parent::tearDown();
|
||||
unset( wc()->countries->locale );
|
||||
remove_all_filters( 'woocommerce_get_country_locale' );
|
||||
global $wp_rest_server;
|
||||
$wp_rest_server = null;
|
||||
$this->unregister_fields();
|
||||
|
@ -1545,18 +1547,65 @@ class AdditionalFields extends MockeryTestCase {
|
|||
$request->set_body_params(
|
||||
array(
|
||||
'billing_address' => (object) array(
|
||||
'first_name' => 'test',
|
||||
'last_name' => 'test',
|
||||
'company' => '',
|
||||
'address_1' => 'test',
|
||||
'address_2' => '',
|
||||
'city' => 'test',
|
||||
'state' => '',
|
||||
'postcode' => 'cb241ab',
|
||||
'country' => 'GB',
|
||||
'phone' => '',
|
||||
'email' => 'testaccount@test.com',
|
||||
'plugin-namespace/gov-id' => 'gov id',
|
||||
'first_name' => 'test',
|
||||
'last_name' => 'test',
|
||||
'company' => '',
|
||||
'address_1' => 'test',
|
||||
'address_2' => '',
|
||||
'city' => 'test',
|
||||
'state' => '',
|
||||
'postcode' => 'cb241ab',
|
||||
'country' => 'GB',
|
||||
'phone' => '',
|
||||
'email' => 'testaccount@test.com',
|
||||
'plugin-namespace/gov-id' => 'gov id',
|
||||
'plugin-namespace/my-required-field' => 'req. field',
|
||||
),
|
||||
'shipping_address' => (object) array(
|
||||
'first_name' => 'test',
|
||||
'last_name' => 'test',
|
||||
'company' => '',
|
||||
'address_1' => 'test',
|
||||
'address_2' => '',
|
||||
'city' => 'test',
|
||||
'state' => '',
|
||||
'postcode' => 'cb241ab',
|
||||
'country' => 'GB',
|
||||
'phone' => '',
|
||||
'plugin-namespace/gov-id' => 'gov id',
|
||||
'plugin-namespace/my-required-field' => 'req. field',
|
||||
),
|
||||
'payment_method' => 'bacs',
|
||||
'additional_fields' => array(
|
||||
'plugin-namespace/job-function' => 'engineering',
|
||||
),
|
||||
)
|
||||
);
|
||||
$response = rest_get_server()->dispatch( $request );
|
||||
$data = $response->get_data();
|
||||
$this->assertEquals( 200, $response->get_status(), print_r( $data, true ) );
|
||||
|
||||
WC()->cart->add_to_cart( $this->products[0]->get_id(), 2 );
|
||||
WC()->cart->add_to_cart( $this->products[1]->get_id(), 1 );
|
||||
|
||||
$request = new \WP_REST_Request( 'POST', '/wc/store/v1/checkout' );
|
||||
$request->set_header( 'Nonce', wp_create_nonce( 'wc_store_api' ) );
|
||||
$request->set_body_params(
|
||||
array(
|
||||
'billing_address' => (object) array(
|
||||
'first_name' => 'test',
|
||||
'last_name' => 'test',
|
||||
'company' => '',
|
||||
'address_1' => 'test',
|
||||
'address_2' => '',
|
||||
'city' => 'test',
|
||||
'state' => '',
|
||||
'postcode' => 'cb241ab',
|
||||
'country' => 'GB',
|
||||
'phone' => '',
|
||||
'email' => 'testaccount@test.com',
|
||||
'plugin-namespace/gov-id' => 'gov id',
|
||||
'plugin-namespace/my-required-field' => 'gov id',
|
||||
),
|
||||
'shipping_address' => (object) array(
|
||||
'first_name' => 'test',
|
||||
|
@ -1583,6 +1632,50 @@ class AdditionalFields extends MockeryTestCase {
|
|||
$this->assertEquals( 400, $response->get_status(), print_r( $data, true ) );
|
||||
$this->assertEquals( \sprintf( 'There was a problem with the provided shipping address: %s is required', $label ), $data['message'], print_r( $data, true ) );
|
||||
|
||||
$request = new \WP_REST_Request( 'POST', '/wc/store/v1/checkout' );
|
||||
$request->set_header( 'Nonce', wp_create_nonce( 'wc_store_api' ) );
|
||||
$request->set_body_params(
|
||||
array(
|
||||
'billing_address' => (object) array(
|
||||
'first_name' => 'test',
|
||||
'last_name' => 'test',
|
||||
'company' => '',
|
||||
'address_1' => 'test',
|
||||
'address_2' => '',
|
||||
'city' => 'test',
|
||||
'state' => '',
|
||||
'postcode' => 'cb241ab',
|
||||
'country' => 'GB',
|
||||
'phone' => '',
|
||||
'email' => 'testaccount@test.com',
|
||||
'plugin-namespace/gov-id' => 'gov id',
|
||||
'plugin-namespace/my-required-field' => 'gov id',
|
||||
),
|
||||
'shipping_address' => (object) array(
|
||||
'first_name' => 'test',
|
||||
'last_name' => 'test',
|
||||
'company' => '',
|
||||
'address_1' => 'test',
|
||||
'address_2' => '',
|
||||
'city' => 'test',
|
||||
'state' => '',
|
||||
'postcode' => 'cb241ab',
|
||||
'country' => 'GB',
|
||||
'phone' => '',
|
||||
'plugin-namespace/gov-id' => 'gov id',
|
||||
'plugin-namespace/my-required-field' => 'gov id',
|
||||
),
|
||||
'payment_method' => 'bacs',
|
||||
'additional_fields' => array(
|
||||
'plugin-namespace/job-function' => '',
|
||||
),
|
||||
)
|
||||
);
|
||||
$response = rest_get_server()->dispatch( $request );
|
||||
$data = $response->get_data();
|
||||
|
||||
$this->assertEquals( 400, $response->get_status(), print_r( $data, true ) );
|
||||
|
||||
\__internal_woocommerce_blocks_deregister_checkout_field( $id );
|
||||
|
||||
// Ensures the field isn't registered.
|
||||
|
|
|
@ -102,17 +102,271 @@ class Checkout extends MockeryTestCase {
|
|||
*/
|
||||
protected function tearDown(): void {
|
||||
parent::tearDown();
|
||||
unset( wc()->countries->locale );
|
||||
$default_zone = \WC_Shipping_Zones::get_zone( 0 );
|
||||
$shipping_methods = $default_zone->get_shipping_methods();
|
||||
foreach ( $shipping_methods as $method ) {
|
||||
$default_zone->delete_shipping_method( $method->instance_id );
|
||||
}
|
||||
$default_zone->save();
|
||||
remove_all_filters( 'woocommerce_get_country_locale' );
|
||||
|
||||
global $wp_rest_server;
|
||||
$wp_rest_server = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that orders can be placed.
|
||||
*/
|
||||
public function test_post_data() {
|
||||
$request = new \WP_REST_Request( 'POST', '/wc/store/v1/checkout' );
|
||||
$request->set_header( 'Nonce', wp_create_nonce( 'wc_store_api' ) );
|
||||
$request->set_body_params(
|
||||
array(
|
||||
'billing_address' => (object) array(
|
||||
'first_name' => 'test',
|
||||
'last_name' => 'test',
|
||||
'company' => '',
|
||||
'address_1' => 'test',
|
||||
'address_2' => '',
|
||||
'city' => 'test',
|
||||
'state' => '',
|
||||
'postcode' => 'cb241ab',
|
||||
'country' => 'GB',
|
||||
'phone' => '',
|
||||
'email' => 'testaccount@test.com',
|
||||
),
|
||||
'shipping_address' => (object) array(
|
||||
'first_name' => 'test',
|
||||
'last_name' => 'test',
|
||||
'company' => '',
|
||||
'address_1' => 'test',
|
||||
'address_2' => '',
|
||||
'city' => 'test',
|
||||
'state' => '',
|
||||
'postcode' => 'cb241ab',
|
||||
'country' => 'GB',
|
||||
'phone' => '',
|
||||
),
|
||||
'payment_method' => 'bacs',
|
||||
)
|
||||
);
|
||||
$response = rest_get_server()->dispatch( $request );
|
||||
$this->assertEquals( 200, $response->get_status() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that orders cannot be placed with invalid data.
|
||||
*/
|
||||
public function test_invalid_post_data() {
|
||||
$request = new \WP_REST_Request( 'POST', '/wc/store/v1/checkout' );
|
||||
$request->set_header( 'Nonce', wp_create_nonce( 'wc_store_api' ) );
|
||||
|
||||
// Test with empty state.
|
||||
$request->set_body_params(
|
||||
array(
|
||||
'billing_address' => (object) array(
|
||||
'first_name' => 'test',
|
||||
'last_name' => 'test',
|
||||
'company' => '',
|
||||
'address_1' => '',
|
||||
'address_2' => '',
|
||||
'city' => 'test',
|
||||
'state' => '',
|
||||
'postcode' => '90210',
|
||||
'country' => 'US',
|
||||
'phone' => '',
|
||||
'email' => 'testaccount@test.com',
|
||||
),
|
||||
'shipping_address' => (object) array(
|
||||
'first_name' => 'test',
|
||||
'last_name' => '',
|
||||
'company' => '',
|
||||
'address_1' => 'test',
|
||||
'address_2' => '',
|
||||
'city' => 'test',
|
||||
'state' => '',
|
||||
'postcode' => '90210',
|
||||
'country' => 'US',
|
||||
'phone' => '',
|
||||
),
|
||||
'payment_method' => 'bacs',
|
||||
)
|
||||
);
|
||||
$response = rest_get_server()->dispatch( $request );
|
||||
$this->assertEquals( 400, $response->get_status() );
|
||||
|
||||
// Test with invalid state.
|
||||
$request->set_body_params(
|
||||
array(
|
||||
'billing_address' => (object) array(
|
||||
'first_name' => 'test',
|
||||
'last_name' => 'test',
|
||||
'company' => '',
|
||||
'address_1' => '',
|
||||
'address_2' => '',
|
||||
'city' => 'test',
|
||||
'state' => 'GG',
|
||||
'postcode' => '90210',
|
||||
'country' => 'US',
|
||||
'phone' => '',
|
||||
'email' => 'testaccount@test.com',
|
||||
),
|
||||
'shipping_address' => (object) array(
|
||||
'first_name' => 'test',
|
||||
'last_name' => '',
|
||||
'company' => '',
|
||||
'address_1' => 'test',
|
||||
'address_2' => '',
|
||||
'city' => 'test',
|
||||
'state' => 'GG',
|
||||
'postcode' => '90210',
|
||||
'country' => 'US',
|
||||
'phone' => '',
|
||||
),
|
||||
'payment_method' => 'bacs',
|
||||
)
|
||||
);
|
||||
$response = rest_get_server()->dispatch( $request );
|
||||
$this->assertEquals( 400, $response->get_status() );
|
||||
|
||||
// Test with no state passed.
|
||||
$request->set_body_params(
|
||||
array(
|
||||
'billing_address' => (object) array(
|
||||
'first_name' => 'test',
|
||||
'last_name' => 'test',
|
||||
'company' => '',
|
||||
'address_1' => '',
|
||||
'address_2' => '',
|
||||
'city' => 'test',
|
||||
'postcode' => '90210',
|
||||
'country' => 'US',
|
||||
'phone' => '',
|
||||
'email' => 'testaccount@test.com',
|
||||
),
|
||||
'shipping_address' => (object) array(
|
||||
'first_name' => 'test',
|
||||
'last_name' => '',
|
||||
'company' => '',
|
||||
'address_1' => 'test',
|
||||
'address_2' => '',
|
||||
'city' => 'test',
|
||||
'postcode' => '90210',
|
||||
'country' => 'US',
|
||||
'phone' => '',
|
||||
),
|
||||
'payment_method' => 'bacs',
|
||||
)
|
||||
);
|
||||
$response = rest_get_server()->dispatch( $request );
|
||||
$this->assertEquals( 400, $response->get_status() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that validation respects locale filtering.
|
||||
*/
|
||||
public function test_locale_required_filtering_post_data() {
|
||||
add_filter(
|
||||
'woocommerce_get_country_locale',
|
||||
function ( $locale ) {
|
||||
$locale['US']['state']['required'] = false;
|
||||
return $locale;
|
||||
}
|
||||
);
|
||||
|
||||
$request = new \WP_REST_Request( 'POST', '/wc/store/v1/checkout' );
|
||||
$request->set_header( 'Nonce', wp_create_nonce( 'wc_store_api' ) );
|
||||
|
||||
// Test that a country that usually requires state can be overridden with woocommerce_get_country_locale filter.
|
||||
$request->set_body_params(
|
||||
array(
|
||||
'billing_address' => (object) array(
|
||||
'first_name' => 'test',
|
||||
'last_name' => 'test',
|
||||
'company' => '',
|
||||
'address_1' => 'test lane',
|
||||
'address_2' => '',
|
||||
'city' => 'test',
|
||||
'state' => '',
|
||||
'postcode' => '90210',
|
||||
'country' => 'US',
|
||||
'phone' => '123456',
|
||||
'email' => 'testaccount@test.com',
|
||||
),
|
||||
'shipping_address' => (object) array(
|
||||
'first_name' => 'test',
|
||||
'last_name' => 'test',
|
||||
'company' => '',
|
||||
'address_1' => 'test',
|
||||
'address_2' => '',
|
||||
'city' => 'test',
|
||||
'state' => '',
|
||||
'postcode' => '90210',
|
||||
'country' => 'US',
|
||||
'phone' => '123456',
|
||||
),
|
||||
'payment_method' => 'bacs',
|
||||
)
|
||||
);
|
||||
$response = rest_get_server()->dispatch( $request );
|
||||
$this->assertEquals( 200, $response->get_status() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that labels respect locale filtering.
|
||||
*/
|
||||
public function test_locale_label_filtering_post_data() {
|
||||
add_filter(
|
||||
'woocommerce_get_country_locale',
|
||||
function ( $locale ) {
|
||||
$locale['FR']['state']['label'] = 'French state';
|
||||
$locale['FR']['state']['required'] = true;
|
||||
$locale['DE']['state']['label'] = 'German state';
|
||||
$locale['DE']['state']['required'] = true;
|
||||
return $locale;
|
||||
}
|
||||
);
|
||||
|
||||
$request = new \WP_REST_Request( 'POST', '/wc/store/v1/checkout' );
|
||||
$request->set_header( 'Nonce', wp_create_nonce( 'wc_store_api' ) );
|
||||
|
||||
// Test that a country that usually requires state can be overridden with woocommerce_get_country_locale filter.
|
||||
$request->set_body_params(
|
||||
array(
|
||||
'billing_address' => (object) array(
|
||||
'first_name' => 'test',
|
||||
'last_name' => 'test',
|
||||
'company' => '',
|
||||
'address_1' => 'test lane',
|
||||
'address_2' => '',
|
||||
'city' => 'test',
|
||||
'state' => '',
|
||||
'postcode' => '90210',
|
||||
'country' => 'FR',
|
||||
'phone' => '123456',
|
||||
'email' => 'testaccount@test.com',
|
||||
),
|
||||
'shipping_address' => (object) array(
|
||||
'first_name' => 'test',
|
||||
'last_name' => 'test',
|
||||
'company' => '',
|
||||
'address_1' => 'test',
|
||||
'address_2' => '',
|
||||
'city' => 'test',
|
||||
'state' => '',
|
||||
'postcode' => '90210',
|
||||
'country' => 'DE',
|
||||
'phone' => '123456',
|
||||
),
|
||||
'payment_method' => 'bacs',
|
||||
)
|
||||
);
|
||||
$response = rest_get_server()->dispatch( $request );
|
||||
$this->assertEquals( 'French state is required', $response->get_data()['data']['errors']['billing'][0] );
|
||||
$this->assertEquals( 'German state is required', $response->get_data()['data']['errors']['shipping'][0] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that registered extension data is correctly shown on options requests.
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue