Add setting validation to the REST API

This commit is contained in:
Justin Shreve 2016-09-08 15:14:40 -07:00
parent b665f5e1c6
commit a36b25a68f
8 changed files with 418 additions and 24 deletions

View File

@ -185,6 +185,140 @@ abstract class WC_REST_Controller extends WP_REST_Controller {
return $response;
}
/**
* Validate a text value for a text based setting.
*
* @since 2.7.0
* @param string $value
* @param array $setting
* @return string
*/
public function validate_setting_text_field( $value, $setting ) {
$value = is_null( $value ) ? '' : $value;
return wp_kses_post( trim( stripslashes( $value ) ) );
return $value;
}
/**
* Validate select based settings.
*
* @since 2.7.0
* @param string $value
* @param array $setting
* @return string|WP_Error
*/
public function validate_setting_select_field( $value, $setting ) {
if ( array_key_exists( $value, $setting['options'] ) ) {
return $value;
} else {
return new WP_Error( 'rest_setting_value_invalid', __( 'An invalid setting value was passed.', 'woocommerce' ), array( 'status' => 400 ) );
}
}
/**
* Validate multiselect based settings.
*
* @since 2.7.0
* @param array $values
* @param array $setting
* @return string|WP_Error
*/
public function validate_setting_multiselect_field( $values, $setting ) {
if ( empty( $values ) ) {
return array();
}
if ( ! is_array( $values ) ) {
return new WP_Error( 'rest_setting_value_invalid', __( 'An invalid setting value was passed.', 'woocommerce' ), array( 'status' => 400 ) );
}
$final_values = array();
foreach ( $values as $value ) {
if ( array_key_exists( $value, $setting['options'] ) ) {
$final_values[] = $value;
}
}
return $final_values;
}
/**
* Validate image_width based settings.
*
* @since 2.7.0
* @param array $value
* @param array $setting
* @return string|WP_Error
*/
public function validate_setting_image_width_field( $values, $setting ) {
if ( ! is_array( $values ) ) {
return new WP_Error( 'rest_setting_value_invalid', __( 'An invalid setting value was passed.', 'woocommerce' ), array( 'status' => 400 ) );
}
$current = $setting['value'];
if ( isset( $values['width'] ) ) {
$current['width'] = intval( $values['width'] );
}
if ( isset( $values['height'] ) ) {
$current['height'] = intval( $values['height'] );
}
if ( isset( $values['crop'] ) ) {
$current['crop'] = (bool) $values['crop'];
}
return $current;
}
/**
* Validate radio based settings.
*
* @since 2.7.0
* @param string $value
* @param array $setting
* @return string|WP_Error
*/
public function validate_setting_radio_field( $value, $setting ) {
return $this->validate_setting_select_field( $value, $setting );
}
/**
* Validate checkbox based settings.
*
* @since 2.7.0
* @param string $value
* @param array $setting
* @return string|WP_Error
*/
public function validate_setting_checkbox_field( $value, $setting ) {
if ( in_array( $value, array( 'yes', 'no' ) ) ) {
return $value;
} elseif ( empty( $value ) ) {
$value = isset( $setting['default'] ) ? $setting['default'] : 'no';
return $value;
} else {
return new WP_Error( 'rest_setting_value_invalid', __( 'An invalid setting value was passed.', 'woocommerce' ), array( 'status' => 400 ) );
}
}
/**
* Validate textarea based settings.
*
* @since 2.7.0
* @param string $value
* @param array $setting
* @return string
*/
public function validate_setting_textarea_field( $value, $setting ) {
$value = is_null( $value ) ? '' : $value;
return wp_kses( trim( stripslashes( $value ) ),
array_merge(
array(
'iframe' => array( 'src' => true, 'style' => true, 'id' => true, 'class' => true ),
),
wp_kses_allowed_html( 'post' )
)
);
}
/**
* Get the batch schema, conforming to JSON Schema.
*

View File

@ -156,12 +156,27 @@ class WC_REST_Payment_Gateways_Controller extends WC_REST_Controller {
// Update settings if present
if ( isset( $request['settings'] ) ) {
$gateway->init_form_fields();
$settings = $gateway->settings;
$settings = $gateway->settings;
$errors_found = false;
foreach ( $gateway->form_fields as $key => $field ) {
if ( isset( $request['settings'][ $key ] ) ) {
$settings[ $key ] = $request['settings'][ $key ];
if ( is_callable( array( $this, 'validate_setting_' . $field['type'] . '_field' ) ) ) {
$value = $this->{'validate_setting_' . $field['type'] . '_field'}( $request['settings'][ $key ], $field );
} else {
$value = $this->validate_setting_text_field( $request['settings'][ $key ], $field );
}
if ( is_wp_error( $value ) ) {
$errors_found = true;
break;
}
$settings[ $key ] = $value;
}
}
if ( $errors_found ) {
return new WP_Error( 'rest_setting_value_invalid', __( 'An invalid setting value was passed.', 'woocommerce' ), array( 'status' => 400 ) );
}
$gateway->settings = $settings;
update_option( $gateway->get_option_key(), apply_filters( 'woocommerce_gateway_' . $gateway->id . '_settings_values', $settings, $gateway ) );
}

View File

@ -136,13 +136,21 @@ class WC_REST_Settings_Options_Controller extends WC_REST_Controller {
foreach ( $settings as $setting ) {
$option_key = $setting['option_key'];
$setting = $this->filter_setting( $setting );
$default = isset( $setting['default'] ) ? $setting['default'] : '';
// Get the option value
if ( is_array( $option_key ) ) {
$option = get_option( $option_key[0] );
$setting['value'] = isset( $option[ $option_key[1] ] ) ? $option[ $option_key[1] ] : '';
$setting['value'] = isset( $option[ $option_key[1] ] ) ? $option[ $option_key[1] ] : $default;
} else {
$setting['value'] = WC_Admin_Settings::get_option( $option_key );
$admin_setting_value = WC_Admin_Settings::get_option( $option_key );
$setting['value'] = empty( $admin_setting_value ) ? $default : $admin_setting_value;
}
if ( 'multi_select_countries' === $setting['type'] ) {
$setting['options'] = WC()->countries->get_countries();
$setting['type'] = 'multiselect';
}
$filtered_settings[] = $setting;
}
@ -224,16 +232,26 @@ class WC_REST_Settings_Options_Controller extends WC_REST_Controller {
return $setting;
}
if ( is_callable( array( $this, 'validate_setting_' . $setting['type'] . '_field' ) ) ) {
$value = $this->{'validate_setting_' . $setting['type'] . '_field'}( $request['value'], $setting );
} else {
$value = $this->validate_setting_text_field( $request['value'], $setting );
}
if ( is_wp_error( $value ) ) {
return $value;
}
if ( is_array( $setting['option_key'] ) ) {
$setting['value'] = $request['value'];
$setting['value'] = $value;
$option_key = $setting['option_key'];
$prev = get_option( $option_key[0] );
$prev[ $option_key[1] ] = $request['value'];
update_option( $option_key[0], $prev );
} else {
$update_data = array();
$update_data[ $setting['option_key'] ] = $request['value'];
$setting['value'] = $request['value'];
$update_data[ $setting['option_key'] ] = $value;
$setting['value'] = $value;
WC_Admin_Settings::save_fields( array( $setting ), $update_data );
}
@ -330,6 +348,29 @@ class WC_REST_Settings_Options_Controller extends WC_REST_Controller {
unset( $setting['options'] );
}
if ( 'image_width' === $setting['type'] ) {
$setting = $this->cast_image_width( $setting );
}
return $setting;
}
/**
* For image_width, Crop can return "0" instead of false -- so we want
* to make sure we return these consistently the same we accept them.
*
* @since 2.7.0
* @param array $setting
* @return array
*/
public function cast_image_width( $setting ) {
foreach ( array( 'default', 'value' ) as $key ) {
if ( isset( $setting[ $key ] ) ) {
$setting[ $key ]['width'] = intval( $setting[ $key ]['width'] );
$setting[ $key ]['height'] = intval( $setting[ $key ]['height'] );
$setting[ $key ]['crop'] = (bool) $setting[ $key ]['crop'];
}
}
return $setting;
}
@ -364,18 +405,17 @@ class WC_REST_Settings_Options_Controller extends WC_REST_Controller {
*/
public function is_setting_type_valid( $type ) {
return in_array( $type, array(
'text',
'email',
'number',
'color',
'password',
'textarea',
'select',
'multiselect',
'radio',
'checkbox',
'multi_select_countries',
'image_width',
'text', // validates with validate_setting_text_field
'email', // validates with validate_setting_text_field
'number', // validates with validate_setting_text_field
'color', // validates with validate_setting_text_field
'password', // validates with validate_setting_text_field
'textarea', // validates with validate_setting_textarea_field
'select', // validates with validate_setting_select_field
'multiselect', // validates with validate_setting_multiselect_field
'radio', // validates with validate_setting_radio_field (-> validate_setting_select_field)
'checkbox', // validates with validate_setting_checkbox_field
'image_width', // validates with validate_setting_image_width_field
) );
}

View File

@ -155,6 +155,9 @@ class WC_REST_Shipping_Zone_Methods_Controller extends WC_REST_Shipping_Zones_Co
}
$method = $this->update_fields( $instance_id, $method, $request );
if ( is_wp_error( $method ) ) {
return $method;
}
$data = $this->prepare_item_for_response( $method, $request );
return rest_ensure_response( $data );
@ -192,6 +195,10 @@ class WC_REST_Shipping_Zone_Methods_Controller extends WC_REST_Shipping_Zones_Co
}
$method = $this->update_fields( $instance_id, $method, $request );
if ( is_wp_error( $method ) ) {
return $method;
}
$request->set_param( 'context', 'view' );
$response = $this->prepare_item_for_response( $method, $request );
@ -244,6 +251,9 @@ class WC_REST_Shipping_Zone_Methods_Controller extends WC_REST_Shipping_Zones_Co
}
$method = $this->update_fields( $instance_id, $method, $request );
if ( is_wp_error( $method ) ) {
return $method;
}
$data = $this->prepare_item_for_response( $method, $request );
return rest_ensure_response( $data );
@ -264,11 +274,26 @@ class WC_REST_Shipping_Zone_Methods_Controller extends WC_REST_Shipping_Zones_Co
if ( isset( $request['settings'] ) ) {
$method->init_instance_settings();
$instance_settings = $method->instance_settings;
$errors_found = false;
foreach ( $method->get_instance_form_fields() as $key => $field ) {
if ( isset( $request['settings'][ $key ] ) ) {
$instance_settings[ $key ] = $request['settings'][ $key ];
if ( is_callable( array( $this, 'validate_setting_' . $field['type'] . '_field' ) ) ) {
$value = $this->{'validate_setting_' . $field['type'] . '_field'}( $request['settings'][ $key ], $field );
} else {
$value = $this->validate_setting_text_field( $request['settings'][ $key ], $field );
}
if ( is_wp_error( $value ) ) {
$errors_found = true;
break;
}
$instance_settings[ $key ] = $value;
}
}
if ( $errors_found ) {
return new WP_Error( 'rest_setting_value_invalid', __( 'An invalid setting value was passed.', 'woocommerce' ), array( 'status' => 400 ) );
}
update_option( $method->get_instance_option_key(), apply_filters( 'woocommerce_shipping_' . $method->id . '_instance_settings_values', $instance_settings, $method ) );
}

View File

@ -109,6 +109,9 @@ class WC_Register_WP_Admin_Settings {
foreach ( $sections as $section => $section_label ) {
$settings_from_section = $this->object->get_settings( $section );
foreach ( $settings_from_section as $setting ) {
if ( ! isset( $setting['id'] ) ) {
continue;
}
$setting['option_key'] = $setting['id'];
$new_setting = $this->register_setting( $setting );
if ( $new_setting ) {
@ -120,12 +123,11 @@ class WC_Register_WP_Admin_Settings {
}
/**
* Register's a specific setting (from WC_Settings_Page::get_settings() )
* into the format expected for the REST API Settings Controller.
* Register a setting into the format expected for the Settings REST API.
*
* @since 2.7.0
* @param array $setting Settings array, as produced by a subclass of WC_Settings_Page.
* @return array|bool boolean False if setting has no ID or converted array.
* @param array $setting
* @return array|bool
*/
public function register_setting( $setting ) {
if ( ! isset( $setting['id'] ) ) {

View File

@ -184,6 +184,26 @@ class Payment_Gateways extends WC_REST_Unit_Test_Case {
$this->assertEquals( 'PayPal - New Title', $paypal['settings']['title']['value'] );
$this->assertEquals( 'woo@woo.local', $paypal['settings']['email']['value'] );
$this->assertEquals( 'yes', $paypal['settings']['testmode']['value'] );
// test bogus
$request = new WP_REST_Request( 'POST', '/wc/v1/payment_gateways/paypal' );
$request->set_body_params( array(
'settings' => array(
'paymentaction' => 'afasfasf',
)
) );
$response = $this->server->dispatch( $request );
$this->assertEquals( 400, $response->get_status() );
$request = new WP_REST_Request( 'POST', '/wc/v1/payment_gateways/paypal' );
$request->set_body_params( array(
'settings' => array(
'paymentaction' => 'authorization',
)
) );
$response = $this->server->dispatch( $request );
$paypal = $response->get_data();
$this->assertEquals( 'authorization', $paypal['settings']['paymentaction']['value'] );
}
/**

View File

@ -628,4 +628,151 @@ class Settings extends WC_REST_Unit_Test_Case {
$this->assertEquals( 'This is my subject', $setting['value'] );
}
/**
* Test validation of checkbox settings.
*
* @since 2.7.0
*/
public function test_validation_checkbox() {
wp_set_current_user( $this->user );
// test bogus value
$request = new WP_REST_Request( 'PUT', sprintf( '/wc/v1/settings/%s/%s', 'email_cancelled_order', 'enabled' ) );
$request->set_body_params( array(
'value' => 'not_yes_or_no',
) );
$response = $this->server->dispatch( $request );
$this->assertEquals( 400, $response->get_status() );
// test yes
$request = new WP_REST_Request( 'PUT', sprintf( '/wc/v1/settings/%s/%s', 'email_cancelled_order', 'enabled' ) );
$request->set_body_params( array(
'value' => 'yes',
) );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
// test no
$request = new WP_REST_Request( 'PUT', sprintf( '/wc/v1/settings/%s/%s', 'email_cancelled_order', 'enabled' ) );
$request->set_body_params( array(
'value' => 'no',
) );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
}
/**
* Test validation of radio settings.
*
* @since 2.7.0
*/
public function test_validation_radio() {
wp_set_current_user( $this->user );
// not a valid option
$request = new WP_REST_Request( 'PUT', sprintf( '/wc/v1/settings/%s/%s', 'shipping', 'woocommerce_ship_to_destination' ) );
$request->set_body_params( array(
'value' => 'billing2',
) );
$response = $this->server->dispatch( $request );
$this->assertEquals( 400, $response->get_status() );
// valid
$request = new WP_REST_Request( 'PUT', sprintf( '/wc/v1/settings/%s/%s', 'shipping', 'woocommerce_ship_to_destination' ) );
$request->set_body_params( array(
'value' => 'billing',
) );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
}
/**
* Test validation of multiselect.
*
* @since 2.7.0
*/
public function test_validation_multiselect() {
wp_set_current_user( $this->user );
$response = $this->server->dispatch( new WP_REST_Request( 'GET', sprintf( '/wc/v1/settings/%s/%s', 'general', 'woocommerce_specific_allowed_countries' ) ) );
$setting = $response->get_data();
$this->assertEmpty( $setting['value'] );
$request = new WP_REST_Request( 'PUT', sprintf( '/wc/v1/settings/%s/%s', 'general', 'woocommerce_specific_allowed_countries' ) );
$request->set_body_params( array(
'value' => array( 'AX', 'DZ', 'MMM' ),
) );
$response = $this->server->dispatch( $request );
$setting = $response->get_data();
$this->assertEquals( array( 'AX', 'DZ' ), $setting['value'] );
}
/**
* Test validation of select.
*
* @since 2.7.0
*/
public function test_validation_select() {
wp_set_current_user( $this->user );
$response = $this->server->dispatch( new WP_REST_Request( 'GET', sprintf( '/wc/v1/settings/%s/%s', 'products', 'woocommerce_weight_unit' ) ) );
$setting = $response->get_data();
$this->assertEquals( 'kg', $setting['value'] );
// invalid
$request = new WP_REST_Request( 'PUT', sprintf( '/wc/v1/settings/%s/%s', 'products', 'woocommerce_weight_unit' ) );
$request->set_body_params( array(
'value' => 'pounds', // invalid, should be lbs
) );
$response = $this->server->dispatch( $request );
$this->assertEquals( 400, $response->get_status() );
// valid
$request = new WP_REST_Request( 'PUT', sprintf( '/wc/v1/settings/%s/%s', 'products', 'woocommerce_weight_unit' ) );
$request->set_body_params( array(
'value' => 'lbs', // invalid, should be lbs
) );
$response = $this->server->dispatch( $request );
$setting = $response->get_data();
$this->assertEquals( 'lbs', $setting['value'] );
}
/**
* Test validation of image_width.
*
* @since 2.7.0
*/
public function test_validation_image_width() {
wp_set_current_user( $this->user );
$response = $this->server->dispatch( new WP_REST_Request( 'GET', sprintf( '/wc/v1/settings/%s/%s', 'products', 'shop_thumbnail_image_size' ) ) );
$setting = $response->get_data();
$this->assertEquals( array( 'width' => 180, 'height' => 180, 'crop' => true ), $setting['value'] );
// test bogus
$request = new WP_REST_Request( 'PUT', sprintf( '/wc/v1/settings/%s/%s', 'products', 'shop_thumbnail_image_size' ) );
$request->set_body_params( array(
'value' => array(
'width' => 400,
'height' => 200,
'crop' => 'asdasdasd',
),
) );
$response = $this->server->dispatch( $request );
$setting = $response->get_data();
$this->assertEquals( array( 'width' => 400, 'height' => 200, 'crop' => true ), $setting['value'] );
$request = new WP_REST_Request( 'PUT', sprintf( '/wc/v1/settings/%s/%s', 'products', 'shop_thumbnail_image_size' ) );
$request->set_body_params( array(
'value' => array(
'width' => 200,
'height' => 100,
'crop' => false,
),
) );
$response = $this->server->dispatch( $request );
$setting = $response->get_data();
$this->assertEquals( array( 'width' => 200, 'height' => 100, 'crop' => false ), $setting['value'] );
}
}

View File

@ -677,6 +677,17 @@ class WC_Tests_API_Shipping_Zones extends WC_REST_Unit_Test_Case {
$this->assertArrayHasKey( 'cost', $data['settings'] );
$this->assertEquals( '10', $data['settings']['cost']['value'] );
// Test bogus
$request = new WP_REST_Request( 'POST', '/wc/v1/shipping/zones/' . $zone->get_id() . '/methods/' . $instance_id );
$request->set_body_params( array(
'settings' => array(
'cost' => 10,
'tax_status' => 'this_is_not_a_valid_option',
),
) );
$response = $this->server->dispatch( $request );
$this->assertEquals( 400, $response->get_status() );
// Test other parameters
$this->assertTrue( $data['enabled'] );
$this->assertEquals( 1, $data['order'] );