order ), self::EXTENDING_KEY => $this->get_extended_data( self::IDENTIFIER ), ]; } /** * This prepares the payment details for the response so it's following the * schema where it's an array of objects. * * @param array $payment_details An array of payment details from the processed payment. * * @return array An array of objects where each object has the key and value * as distinct properties. */ protected function prepare_payment_details_for_response( array $payment_details ) { return array_map( function( $key, $value ) { return (object) [ 'key' => $key, 'value' => $value, ]; }, array_keys( $payment_details ), $payment_details ); } /** * Get the additional fields response. * * @param \WC_Order $order Order object. * @return array */ protected function get_additional_fields_response( \WC_Order $order ) { $fields = wp_parse_args( $this->additional_fields_controller->get_all_fields_from_order( $order ), $this->additional_fields_controller->get_all_fields_from_customer( wc()->customer ) ); $response = []; foreach ( $fields as $key => $value ) { if ( 0 === strpos( $key, '/billing/' ) || 0 === strpos( $key, '/shipping/' ) ) { continue; } $response[ $key ] = $value; } return $response; } /** * Get the schema for additional fields. * * @return array */ protected function get_additional_fields_schema() { return $this->generate_additional_fields_schema( $this->additional_fields_controller->get_fields_for_location( 'contact' ), $this->additional_fields_controller->get_fields_for_location( 'additional' ) ); } /** * Generate the schema for additional fields. * * @param array[] ...$args One or more arrays of additional fields. * @return array */ protected function generate_additional_fields_schema( ...$args ) { $additional_fields = array_merge( ...$args ); $schema = []; foreach ( $additional_fields as $key => $field ) { $field_schema = [ 'description' => $field['label'], 'type' => 'string', 'context' => [ 'view', 'edit' ], 'required' => $field['required'], ]; if ( 'select' === $field['type'] ) { $field_schema['enum'] = array_map( function( $option ) { return $option['value']; }, $field['options'] ); } if ( 'checkbox' === $field['type'] ) { $field_schema['type'] = 'boolean'; } $schema[ $key ] = $field_schema; } return $schema; } /** * Check if any additional field is required, so that the parent item is required as well. * * @param array $additional_fields_schema Additional fields schema. * @return bool */ protected function schema_has_required_property( $additional_fields_schema ) { return array_reduce( array_keys( $additional_fields_schema ), function( $carry, $key ) use ( $additional_fields_schema ) { return $carry || $additional_fields_schema[ $key ]['required']; }, false ); } /** * Sanitize and format additional fields object. * * @param array $fields Values being sanitized. * @return array */ public function sanitize_additional_fields( $fields ) { $properties = $this->get_additional_fields_schema(); $sanitization_utils = new SanitizationUtils(); $fields = $sanitization_utils->wp_kses_array( array_reduce( array_keys( $fields ), function( $carry, $key ) use ( $fields, $properties ) { if ( ! isset( $properties[ $key ] ) ) { return $carry; } $field_schema = $properties[ $key ]; $rest_sanitized = rest_sanitize_value_from_schema( wp_unslash( $fields[ $key ] ), $field_schema, $key ); $rest_sanitized = $this->additional_fields_controller->sanitize_field( $key, $rest_sanitized ); $carry[ $key ] = $rest_sanitized; return $carry; }, [] ) ); return $sanitization_utils->wp_kses_array( $fields ); } /** * Validate additional fields object. * * @see rest_validate_value_from_schema * * @param array $fields Value being sanitized. * @param \WP_REST_Request $request The Request. * @return true|\WP_Error */ public function validate_additional_fields( $fields, $request ) { $errors = new \WP_Error(); $fields = $this->sanitize_additional_fields( $fields ); $additional_field_schema = $this->get_additional_fields_schema(); // Validate individual properties first. foreach ( $fields as $key => $field_value ) { if ( ! isset( $additional_field_schema[ $key ] ) ) { continue; } $result = rest_validate_value_from_schema( $field_value, $additional_field_schema[ $key ], $key ); // Only allow custom validation on fields that pass the schema validation. if ( true === $result ) { $result = $this->additional_fields_controller->validate_field( $key, $field_value ); } if ( is_wp_error( $result ) && $result->has_errors() ) { $location = $this->additional_fields_controller->get_field_location( $key ); foreach ( $result->get_error_codes() as $code ) { $result->add_data( array( 'location' => $location, 'key' => $key, ), $code ); } $errors->merge_from( $result ); } } // Validate groups of properties per registered location. $locations = array( 'contact', 'additional' ); foreach ( $locations as $location ) { $location_fields = $this->additional_fields_controller->filter_fields_for_location( $fields, $location ); $result = $this->additional_fields_controller->validate_fields_for_location( $location_fields, $location ); if ( is_wp_error( $result ) && $result->has_errors() ) { $errors->merge_from( $result ); } } return $errors->has_errors( $errors ) ? $errors : true; } }