- : / -
- -- : -
- -- : -
- -- : % -
- - -%s: %s
', - esc_html( - _x('Days to start', 'financoop campaign widget', 'forms-bridge') - ), - (int) $days_to_start - ); - } - - if ($end) { - $end_date = new DateTime($end); - $output .= sprintf( - '%s: %s
', - esc_html( - _x('End date', 'financoop campaign widget', 'forms-bridge') - ), - esc_html(IntlDateFormatter::formatObject($end_date, 'dd/MM/Y')) - ); - - if (!$days_to_start && $days_to_end > 0) { - $output .= sprintf( - '%s: %s
', - esc_html( - _x( - 'Days to end', - 'financoop campaign widget', - 'forms-bridge' - ) - ), - (int) $days_to_end - ); - } - } - - $output .= '+ + + : + + / + + +
+ ++ + + : + + +
+ ++ + + : + + +
+ ++ + + : + + % +
+ + +%s: %s
', + esc_html( + _x( 'Days to start', 'financoop campaign widget', 'forms-bridge' ) + ), + (int) $days_to_start + ); + } + + if ( $end ) { + $end_date = new DateTime( $end ); + $output .= sprintf( + '%s: %s
', + esc_html( + _x( 'End date', 'financoop campaign widget', 'forms-bridge' ) + ), + esc_html( IntlDateFormatter::formatObject( $end_date, 'dd/MM/Y' ) ) + ); + + if ( ! $days_to_start && $days_to_end > 0 ) { + $output .= sprintf( + '%s: %s
', + esc_html( + _x( + 'Days to end', + 'financoop campaign widget', + 'forms-bridge' + ) + ), + (int) $days_to_end + ); + } + } + + $output .= 'Form submitted successfully.
', + 'forms-bridge' + ), + 'email_message_plain' => '', + 'exception_fields' => array(), + ), + ), + ); + } +} + +Ninja_Integration::setup(); diff --git a/forms-bridge/integrations/woo/class-woo-integration.php b/forms-bridge/integrations/woo/class-woo-integration.php new file mode 100644 index 00000000..7bc77028 --- /dev/null +++ b/forms-bridge/integrations/woo/class-woo-integration.php @@ -0,0 +1,839 @@ + 'object', + 'properties' => array( + 'id' => array( 'type' => 'integer' ), + 'parent_id' => array( 'type' => 'integer' ), + 'status' => array( 'type' => 'string' ), + 'currency' => array( 'type' => 'string' ), + 'version' => array( 'type' => 'string' ), + 'prices_include_tax' => array( 'type' => 'boolean' ), + 'date_created' => array( 'type' => 'string' ), + 'date_modified' => array( 'type' => 'string' ), + 'discount_total' => array( 'type' => 'number' ), + 'discount_tax' => array( + 'type' => 'object', + 'properties' => array( + 'amount' => array( 'type' => 'number' ), + 'rate' => array( 'type' => 'number' ), + 'percentage' => array( 'type' => 'number' ), + ), + ), + 'shipping_total' => array( 'type' => 'number' ), + 'shipping_tax' => array( + 'type' => 'object', + 'properties' => array( + 'amount' => array( 'type' => 'number' ), + 'rate' => array( 'type' => 'number' ), + 'percentage' => array( 'type' => 'number' ), + ), + ), + 'cart_total' => array( 'type' => 'number' ), + 'cart_tax' => array( + 'type' => 'object', + 'properties' => array( + 'amount' => array( 'type' => 'number' ), + 'rate' => array( 'type' => 'number' ), + 'percentage' => array( 'type' => 'number' ), + ), + ), + 'total' => array( 'type' => 'number' ), + 'total_tax' => array( + 'type' => 'object', + 'properties' => array( + 'amount' => array( 'type' => 'number' ), + 'rate' => array( 'type' => 'number' ), + 'percentage' => array( 'type' => 'number' ), + ), + ), + 'customer_id' => array( 'type' => 'integer' ), + 'order_key' => array( 'type' => 'string' ), + 'billing' => array( + 'type' => 'object', + 'properties' => array( + 'first_name' => array( 'type' => 'string' ), + 'last_name' => array( 'type' => 'string' ), + 'company' => array( 'type' => 'string' ), + 'address_1' => array( 'type' => 'string' ), + 'address_2' => array( 'type' => 'string' ), + 'city' => array( 'type' => 'string' ), + 'state' => array( 'type' => 'string' ), + 'postcode' => array( 'type' => 'string' ), + 'country' => array( 'type' => 'string' ), + 'email' => array( 'type' => 'string' ), + 'phone' => array( 'type' => 'string' ), + ), + 'additionalProperties' => true, + ), + 'shipping' => array( + 'type' => 'object', + 'properties' => array( + 'first_name' => array( 'type' => 'string' ), + 'last_name' => array( 'type' => 'string' ), + 'company' => array( 'type' => 'string' ), + 'address_1' => array( 'type' => 'string' ), + 'address_2' => array( 'type' => 'string' ), + 'city' => array( 'type' => 'string' ), + 'state' => array( 'type' => 'string' ), + 'postcode' => array( 'type' => 'string' ), + 'country' => array( 'type' => 'string' ), + 'phone' => array( 'type' => 'string' ), + ), + 'additionalProperties' => true, + ), + 'payment_method' => array( 'type' => 'string' ), + 'payment_method_title' => array( 'type' => 'string' ), + 'transaction_id' => array( 'type' => 'string' ), + 'customer_ip_address' => array( 'type' => 'string' ), + 'customer_user_agent' => array( 'type' => 'string' ), + 'created_via' => array( 'type' => 'string' ), + 'customer_note' => array( 'type' => 'string' ), + 'date_completed' => array( 'type' => 'string' ), + 'date_paid' => array( 'type' => 'string' ), + 'cart_hash' => array( 'type' => 'string' ), + 'order_stock_reduced' => array( 'type' => 'boolean' ), + 'download_permissions_granted' => array( 'type' => 'boolean' ), + 'new_order_email_sent' => array( 'type' => 'boolean' ), + 'recorded_sales' => array( 'type' => 'boolean' ), + 'recorded_coupon_usage_counts' => array( 'type' => 'boolean' ), + 'number' => array( 'type' => 'integer' ), + // 'meta_data' => [ + // 'type' => 'array', + // 'items' => [ + // 'type' => 'object', + // 'properties' => [ + // 'id' => ['type' => 'integer'], + // 'key' => ['type' => 'string'], + // 'value' => ['type' => 'string'], + // ], + // 'required' => ['id', 'key', 'value'], + // 'additionalProperties' => false, + // ], + // 'additionalItems' => true, + // ], + 'line_items' => array( + 'type' => 'array', + 'items' => array( + 'type' => 'object', + 'properties' => array( + 'id' => array( 'type' => 'integer' ), + 'order_id' => array( 'type' => 'integer' ), + 'name' => array( 'type' => 'string' ), + 'product_id' => array( 'type' => 'integer' ), + 'variation_id' => array( 'type' => 'integer' ), + 'quantity' => array( 'type' => 'integer' ), + 'tax_class' => array( 'type' => 'string' ), + 'subtotal' => array( 'type' => 'number' ), + 'subtotal_tax' => array( + 'type' => 'object', + 'properties' => array( + 'amount' => array( 'type' => 'number' ), + 'rate' => array( 'type' => 'number' ), + 'percentage' => array( 'type' => 'number' ), + ), + ), + 'total' => array( 'type' => 'number' ), + 'total_tax' => array( + 'type' => 'object', + 'properties' => array( + 'amount' => array( 'type' => 'number' ), + 'rate' => array( 'type' => 'number' ), + 'percentage' => array( 'type' => 'number' ), + ), + ), + 'taxes' => array( + 'type' => 'object', + 'properties' => array( + 'subtotal' => array( + 'type' => 'array', + 'items' => array( 'type' => 'number' ), + 'additionalItems' => true, + ), + 'total' => array( + 'type' => 'array', + 'items' => array( 'type' => 'number' ), + 'additionalItems' => true, + ), + ), + ), + 'product' => array( + 'type' => 'object', + 'properties' => array( + 'id' => array( 'type' => 'integer' ), + 'parent_id' => array( 'type' => 'integer' ), + 'sku' => array( 'type' => 'string' ), + 'name' => array( 'type' => 'string' ), + 'slug' => array( 'type' => 'string' ), + 'price' => array( 'type' => 'number' ), + 'sale_price' => array( 'type' => 'number' ), + 'regular_price' => array( 'type' => 'number' ), + 'stock_quantity' => array( 'type' => 'number' ), + 'stock_status' => array( 'type' => 'string' ), + ), + ), + // 'meta_data' => [ + // 'type' => 'array', + // 'items' => [ + // 'type' => 'object', + // 'properties' => [ + // 'id' => ['type' => 'integer'], + // 'key' => ['type' => 'string'], + // 'value' => ['type' => 'string'], + // ], + // ], + // ], + ), + ), + 'additionalItems' => true, + 'minItems' => 1, + ), + 'tax_lines' => array( + 'type' => 'array', + 'items' => array( + 'type' => 'object', + 'properties' => array( + 'id' => array( 'type' => 'integer' ), + 'order_id' => array( 'type' => 'integer' ), + 'name' => array( 'type' => 'string' ), + 'rate_code' => array( 'type' => 'string' ), + 'rate_id' => array( 'type' => 'integer' ), + 'label' => array( 'type' => 'string' ), + 'compound' => array( 'type' => 'boolean' ), + 'tax_total' => array( 'type' => 'number' ), + 'shipping_tax_total' => array( 'type' => 'number' ), + 'rate_percent' => array( 'type' => 'number' ), + // 'meta_data' => [ + // 'type' => 'array', + // 'items' => [ + // 'type' => 'object', + // 'properties' => [ + // 'id' => ['type' => 'integer'], + // 'key' => ['type' => 'string'], + // 'value' => ['type' => 'string'], + // ], + // ], + // 'additionalItems' => true, + // ], + ), + ), + 'additionalItems' => true, + ), + 'shipping_lines' => array( + 'type' => 'array', + 'items' => array( + 'type' => 'object', + 'properties' => array( + 'id' => array( 'type' => 'integer' ), + 'order_id' => array( 'type' => 'integer' ), + 'name' => array( 'type' => 'string' ), + 'method_id' => array( 'type' => 'string' ), + 'method_title' => array( 'type' => 'string' ), + 'instance_id' => array( 'type' => 'integer' ), + 'total' => array( 'type' => 'number' ), + 'total_tax' => array( + 'type' => 'object', + 'properties' => array( + 'amount' => array( 'type' => 'number' ), + 'rate' => array( 'type' => 'number' ), + 'percentage' => array( 'type' => 'number' ), + ), + ), + 'tax_status' => array( 'type' => 'string' ), + 'taxes' => array( + 'type' => 'object', + 'properties' => array( + 'total' => array( 'type' => 'number' ), + 'subtotal' => array( 'type' => 'number' ), + ), + 'required' => array( 'total' ), + ), + // 'meta_data' => [ + // 'type' => 'array', + // 'items' => [ + // 'type' => 'object', + // 'properties' => [ + // 'id' => ['type' => 'integer'], + // 'key' => ['type' => 'string'], + // 'value' => ['type' => 'string'], + // ], + // ], + // ], + ), + ), + 'additionalItems' => true, + ), + 'fee_lines' => array( + 'type' => 'array', + 'items' => array( + 'type' => 'object', + 'properties' => array( + 'id' => array( 'type' => 'integer' ), + 'order_id' => array( 'type' => 'integer' ), + 'name' => array( 'type' => 'string' ), + 'tax_class' => array( 'type' => 'string' ), + 'tax_status' => array( 'type' => 'string' ), + 'amount' => array( 'type' => 'number' ), + 'total' => array( 'type' => 'number' ), + 'total_tax' => array( + 'type' => 'object', + 'properties' => array( + 'amount' => array( 'type' => 'number' ), + 'rate' => array( 'type' => 'number' ), + 'percentage' => array( 'type' => 'number' ), + ), + ), + 'taxes' => array( + 'type' => 'object', + 'properties' => array( + 'total' => array( + 'type' => 'array', + 'items' => array( 'type' => 'number' ), + 'additionalItems' => true, + ), + ), + 'required' => array( 'total' ), + ), + // 'meta_data' => [ + // 'type' => 'array', + // 'items' => [ + // 'type' => 'object', + // 'properties' => [ + // 'id' => ['type' => 'integer'], + // 'key' => ['type' => 'string'], + // 'value' => ['type' => 'string'], + // ], + // ], + // ], + ), + ), + 'additionalItems' => true, + ), + 'coupon_lines' => array( + 'type' => 'array', + 'items' => array( + 'type' => 'object', + 'properties' => array( + 'id' => array( 'type' => 'integer' ), + 'order_id' => array( 'type' => 'integer' ), + 'name' => array( 'type' => 'string' ), + 'code' => array( 'type' => 'string' ), + 'discount' => array( 'type' => 'number' ), + 'discount_tax' => array( 'type' => 'number' ), + // 'meta_data' => [ + // 'type' => 'array', + // 'items' => [ + // 'type' => 'object', + // 'properties' => [ + // 'id' => ['type' => 'integer'], + // 'key' => ['type' => 'string'], + // 'value' => ['type' => 'string'], + // ], + // ], + // ], + ), + ), + 'additionalItems' => true, + ), + ), + 'additionalProperties' => false, + ); + + /** + * Wraps a tax amount in a tax descriptor array. + * + * @param float $tax Tax amount. + * @param float $total Tax total import. + * + * @return array Tax descriptor. + */ + private static function decorate_tax( $tax, $total ) { + try { + $tax = (float) $tax; + $rate = $tax / $total; + $rate = floor( $rate * 1000 ) / 1000; + + return array( + 'amount' => $tax, + 'rate' => $rate, + 'percentage' => $rate * 100, + ); + } catch ( TypeError | DivisionByZeroError ) { + return array( + 'amount' => 0, + 'rate' => 0, + 'percentage' => 0, + ); + } + } + + /** + * Integration initializer. Hooks the integration to woocommerce events. + */ + public function init() { + add_action( + 'woocommerce_order_status_changed', + static function ( $order_id, $old_status, $new_status ) { + $is_bridged = get_post_meta( + $order_id, + self::ORDER_BRIDGED_CF, + true + ); + + $trigger_submission = apply_filters( + 'forms_bridge_woo_trigger_submission', + '1' !== $is_bridged && 'completed' === $new_status, + $order_id, + $new_status, + $old_status, + $is_bridged + ); + + if ( $trigger_submission ) { + self::$order_id = $order_id; + + add_action( + 'forms_bridge_after_submission', + function () { + update_post_meta( + self::$order_id, + self::ORDER_BRIDGED_CF, + '1' + ); + }, + 90 + ); + + Forms_Bridge::do_submission(); + } + }, + 10, + 3 + ); + } + + /** + * Retrives the checkout form data if an order is being processed. + * + * @return array|null + */ + public function form() { + if ( empty( self::$order_id ) ) { + return; + } + + return $this->get_form_by_id( 1 ); + } + + /** + * Retrives checkout form data by ID. + * + * @param int $form_id Form ID. + * + * @return array|null Form data if ID is 1, null otherwise. + */ + public function get_form_by_id( $form_id ) { + if ( 1 !== +$form_id ) { + return null; + } + + WC()->session = new WC_Session_Handler(); + WC()->customer = new WC_Customer(); + + return apply_filters( + 'forms_bridge_form_data', + array( + '_id' => 'woo:1', + 'id' => 1, + 'title' => __( 'Woo Checkout', 'forms-bridge' ), + 'bridges' => FBAPI::get_form_bridges( 1, 'woo' ), + 'fields' => $this->serialize_form( null ), + ), + WC()->checkout, + 'woo' + ); + } + + /** + * Retrives available forms' data. + * + * @return array Collection of form data array representations. + */ + public function forms() { + return array( $this->get_form_by_id( 1 ) ); + } + + /** + * Skips form creation and return a success result. + * + * @param array $data Form template data, ignored. + * + * @return int 1, the checkout form internal ID. + */ + public function create_form( $data ) { + return 1; + } + + /** + * Skips form removal and return a success result. + * + * @param integer $form_id Form ID, ignored. + * + * @return boolean + */ + public function remove_form( $form_id ) { + return true; + } + + /** + * Retrives the current order ID. + * + * @return string|null + */ + public function submission_id() { + if ( self::$order_id ) { + return (string) self::$order_id; + } + } + + /** + * Retrives the current order data. + * + * @param boolean $raw Control if the order is serialized before exit. + * + * @return array|null + */ + public function submission( $raw ) { + if ( empty( self::$order_id ) ) { + return; + } + + return $this->serialize_order( self::$order_id ); + } + + /** + * Return an empty array as checkout form does not supports file uploads. + * + * @return array + */ + public function uploads() { + return array(); + } + + /** + * Alias to the serialize_order_fields method. + * + * @param mixed $form Ignored argument. + * + * @return array The order fields serialized as array of data. + */ + public function serialize_form( $form ) { + return $this->serialize_order_fields(); + } + + /** + * Serialize the order fields. + * + * @return array Order fields as form data. + */ + private function serialize_order_fields() { + $checkout_fields = WC()->checkout->checkout_fields; + + $fields = array(); + foreach ( + self::$order_data_schema['properties'] + as $name => $field_schema + ) { + $fields[] = self::decorate_order_field( $name, $field_schema ); + } + + foreach ( array_keys( $checkout_fields['billing'] ) as $name ) { + $name = str_replace( 'billing_', '', $name ); + if ( isset( self::$order_data_schema['billing'][ $name ] ) ) { + continue; + } + + $index = array_search( 'billing', array_column( $fields, 'name' ), true ); + + $billing_field = &$fields[ $index ]; + $billing_field['schema']['properties'][ $name ] = array( + 'type' => 'text', + ); + } + + foreach ( array_keys( $checkout_fields['shipping'] ) as $name ) { + $name = str_replace( 'shipping_', '', $name ); + if ( isset( self::$order_data_schema['shipping'][ $name ] ) ) { + continue; + } + + $index = array_search( 'shipping', array_column( $fields, 'name' ), true ); + + $fields[ $index ]['schema']['properties'][ $name ] = array( + 'type' => 'text', + ); + } + + return $fields; + } + + /** + * Decorates order fields as form data fields. + * + * @param string $name Field name. + * @param array $schema Field schema. + * + * @return array Field data. + */ + private function decorate_order_field( $name, $schema ) { + switch ( $schema['type'] ) { + case 'string': + $field_type = 'text'; + break; + case 'number': + case 'integer': + $field_type = 'number'; + break; + case 'boolean': + $field_type = 'boolean'; + break; + case 'array': + $field_type = 'select'; + break; + default: + $field_type = $schema['type']; + } + + return array( + 'id' => null, + 'name' => $name, + 'label' => $name, + 'type' => $field_type, + 'required' => true, + 'is_file' => false, + 'is_multi' => 'array' === $schema['type'], + 'conditional' => false, + 'schema' => $schema, + ); + } + + /** + * Alias to the serialize_order method. + * + * @param array $submission Ignored argument. + * @param array $form_data Ignored argument. + * + * @return array|null + */ + public function serialize_submission( $submission, $form_data ) { + return $this->serialize_order( self::$order_id ); + } + + /** + * Serialize the current WC_Order as a payload array. + * + * @param integer $order_id ID of the order to serialize. + * + * @return array + */ + public function serialize_order( $order_id ) { + $order = wc_get_order( $order_id ); + if ( empty( $order ) ) { + return; + } + + $checkout = WC()->checkout; + + $data = $order->get_data(); + unset( $data['meta_data'] ); + + $checkout_fields = $checkout->checkout_fields; + foreach ( array_keys( $checkout_fields['billing'] ) as $name ) { + $unprefixed = str_replace( 'billing_', '', $name ); + if ( ! isset( $data['billing'][ $unprefixed ] ) ) { + $data['billing'][ $unprefixed ] = $checkout->get_value( $name ); + } + } + + foreach ( array_keys( $checkout_fields['shipping'] ) as $name ) { + $unprefixed = str_replace( 'shipping_', '', $name ); + if ( ! isset( $data['shipping'][ $unprefixed ] ) ) { + $data['shipping'][ $unprefixed ] = $checkout->get_value( $name ); + } + } + + $tax_lines = array(); + foreach ( $data['tax_lines'] as $tax_line ) { + $line_data = $tax_line->get_data(); + unset( $line_data['meta_data'] ); + $tax_lines[] = $line_data; + } + + $data['tax_lines'] = $tax_lines; + + $line_items = array(); + foreach ( $data['line_items'] as $line_item ) { + $item_data = $line_item->get_data(); + unset( $item_data['meta_data'] ); + + $product = $line_item->get_product(); + $item_data['product'] = array( + 'id' => $product->get_id(), + 'parent_id' => $product->get_parent_id(), + 'slug' => $product->get_slug(), + 'sku' => $product->get_sku(), + 'name' => $product->get_name(), + 'price' => $product->get_price(), + 'sale_price' => $product->get_sale_price(), + 'regular_price' => $product->get_regular_price(), + 'stock_quantity' => $product->get_stock_quantity(), + 'stock_status' => $product->get_stock_status(), + ); + + $item_data['total_tax'] = self::decorate_tax( + $line_item['total_tax'], + $line_item['total'] + ); + + $item_data['subtotal_tax'] = self::decorate_tax( + $line_item['subtotal_tax'], + $line_item['subtotal'] + ); + + $line_items[] = $item_data; + } + + $data['line_items'] = $line_items; + + $shipping_lines = array(); + foreach ( $data['shipping_lines'] as $shipping_line ) { + $line_data = $shipping_line->get_data(); + unset( $line_data['meta_data'] ); + + $line_data['total_tax'] = self::decorate_tax( + $line_data['total_tax'], + $line_data['total'] + ); + + $shipping_lines[] = $line_data; + } + + $data['shipping_lines'] = $shipping_lines; + + $coupon_lines = array(); + foreach ( $data['coupon_lines'] ?? array() as $coupon_line ) { + $line_data = $coupon_line->get_data(); + unset( $line_data['meta_data'] ); + + $line_data['discount_tax'] = self::decorate_tax( + $line_data['discount_tax'], + $line_data['discount'] + ); + + $coupon_lines[] = $line_data; + } + + $data['coupon_lines'] = $coupon_lines; + + $fee_lines = array(); + foreach ( $data['fee_lines'] ?? array() as $fee_line ) { + $line_data = $fee_line->get_data(); + unset( $line_data['meta_data'] ); + + $line_data['total_tax'] = self::decorate_tax( + $line_data['total_tax'], + $line_data['total'] + ); + + $fee_lines[] = $line_data; + } + + $data['fee_lines'] = $fee_lines; + + $data['discount_tax'] = self::decorate_tax( + $data['discount_tax'], + $data['discount_total'] + ); + + $data['shipping_tax'] = self::decorate_tax( + $data['shipping_tax'], + $data['shipping_total'] + ); + + $data['total_tax'] = self::decorate_tax( + $data['total_tax'], + $data['total'] + ); + + $cart_total = 0; + foreach ( $data['line_items'] as $line_data ) { + $cart_total += $line_data['total']; + } + + foreach ( $data['fee_lines'] as $line_data ) { + $cart_total += $line_data['total']; + } + + $data['cart_total'] = $cart_total; + $data['cart_tax'] = self::decorate_tax( + $data['cart_tax'], + $data['cart_total'] + ); + + return rest_sanitize_value_from_schema( $data, self::$order_data_schema ); + } +} + +Woo_Integration::setup(); diff --git a/forms-bridge/integrations/wpcf7/class-wpcf7-integration.php b/forms-bridge/integrations/wpcf7/class-wpcf7-integration.php new file mode 100644 index 00000000..cbf2e283 --- /dev/null +++ b/forms-bridge/integrations/wpcf7/class-wpcf7-integration.php @@ -0,0 +1,591 @@ +serialize_form( $form ); + } + + /** + * Retrives a contact form's data by ID. + * + * @param int $form_id Form ID. + * @return array $form_data Form data. + */ + public function get_form_by_id( $form_id ) { + $form = WPCF7_ContactForm::get_instance( $form_id ); + if ( ! $form ) { + return null; + } + + return $this->serialize_form( $form ); + } + + /** + * Retrives available constact forms as form data. + * + * @return array $forms Collection of form data. + */ + public function forms() { + $forms = WPCF7_ContactForm::find( array( 'post_status', 'publish' ) ); + return array_map( + function ( $form ) { + return $this->serialize_form( $form ); + }, + $forms + ); + } + + /** + * Creates a form from the given template fields. + * + * @param array $data Form template data. + * + * @return int|null ID of the new form. + * + * @todo Fix form email attribute. + */ + public function create_form( $data ) { + if ( empty( $data['title'] ) || empty( $data['fields'] ) ) { + return; + } + + $form = $this->fields_to_form( $data['fields'] ); + $email = $this->form_email( $data['title'], $data['fields'] ); + + $contact_form = wpcf7_save_contact_form( + array( + 'title' => $data['title'], + 'locale' => get_locale(), + 'form' => $form, + 'mail' => $email, + ) + ); + + if ( ! $contact_form ) { + return; + } + + return $contact_form->id(); + } + + /** + * Removes a form by ID. + * + * @param integer $form_id Form ID. + * + * @return boolean Removal result. + */ + public function remove_form( $form_id ) { + $result = wp_delete_post( $form_id ); + return (bool) $result; + } + + /** + * Retrives the current submission ID. + * + * @return string|null + */ + public function submission_id() { + $submission = $this->submission( true ); + if ( $submission ) { + return $submission->get_posted_data_hash(); + } + } + + /** + * Retrives the current submission data. + * + * @param boolean $raw Control if the submission is serialized before exit. + * + * @return WPCF7_Submission|array Submission data. + */ + public function submission( $raw = false ) { + $submission = WPCF7_Submission::get_instance(); + + if ( ! $submission ) { + return null; + } elseif ( $raw ) { + return $submission; + } + + $form = $this->form(); + return $this->serialize_submission( $submission, $form ); + } + + /** + * Retrives the current submission uploaded files. + * + * @return array Uploaded files data. + */ + public function uploads() { + $submission = WPCF7_Submission::get_instance(); + if ( ! $submission ) { + return null; + } + + return $this->submission_uploads( $submission ); + } + + /** + * Serializes a contact form instance as array data. + * + * @param WPCF7_ContactForm $form Form instance. + * + * @return array. + */ + public function serialize_form( $form ) { + $form_id = (int) $form->id(); + $fields = array_filter( + array_map( + function ( $field ) { + return $this->serialize_field( $field ); + }, + $form->scan_form_tags() + ) + ); + + return apply_filters( + 'forms_bridge_form_data', + array( + '_id' => 'wpcf7:' . $form_id, + 'id' => $form_id, + 'title' => $form->title(), + 'bridges' => FBAPI::get_form_bridges( $form_id, 'wpcf7' ), + 'fields' => array_values( $fields ), + ), + $form, + 'wpcf7' + ); + } + + /** + * Serializes a form tags as array data. + * + * @param WPCF7_FormTag $field Form tag instance. + * + * @return array. + */ + private function serialize_field( $field ) { + if ( in_array( $field->basetype, array( 'response', 'submit', 'quiz' ), true ) ) { + return; + } + + $basetype = $field->basetype; + $type = $basetype; + + if ( 'conditional' === $basetype ) { + $type = $field->get_option( 'type' )[0]; + } + + switch ( $basetype ) { + case 'radio': + case 'checkbox': + $type = 'select'; + break; + case 'acceptance': + $type = 'checkbox'; + break; + case 'hidden': + $type = 'text'; + break; + } + + $options = array(); + if ( is_array( $field->values ) ) { + $values = $field->pipes->collect_afters(); + + $l = count( $field->raw_values ); + for ( $i = 0; $i < $l; $i++ ) { + $options[] = array( + 'value' => $values[ $i ], + 'label' => $field->labels[ $i ], + ); + } + } + + $format = 'date' === $type ? 'yyyy-mm-dd' : ''; + + return apply_filters( + 'forms_bridge_form_field_data', + array( + 'id' => $field->get_id_option(), + 'type' => $type, + 'name' => $field->raw_name, + 'label' => $field->name, + 'required' => $field->is_required(), + 'options' => $options, + 'is_file' => 'file' === $type, + 'is_multi' => $this->is_multi_field( $field ), + 'conditional' => in_array( $field->basetype, array( 'conditional', 'fileconditional' ), true ), + 'format' => $format, + 'schema' => $this->field_value_schema( $field ), + 'basetype' => $basetype, + ), + $field, + 'wpcf7' + ); + } + + /** + * Checks if a filed is multi value field. + * + * @param WPCF7_FormTag $tag Target tag instance. + * + * @return bool + */ + private function is_multi_field( $tag ) { + $type = str_replace( '*', '', $tag->type ); + + if ( 'checkbox' === $type ) { + return ! $tag->has_option( 'exclusive' ); + } + + if ( 'select' === $type ) { + return $tag->has_option( 'multiple' ); + } + + return false; + } + + /** + * Gets the field value JSON schema. + * + * @param WPCF7_FormTag $tag Tag instance. + * + * @return array JSON schema of the value of the field. + */ + private function field_value_schema( $tag ) { + $type = str_replace( '*', '', $tag->type ); + + switch ( $type ) { + case 'text': + case 'textarea': + case 'date': + case 'email': + case 'url': + case 'quiz': + case 'radio': + return array( 'type' => 'string' ); + case 'select': + if ( $tag->has_option( 'multiple' ) ) { + $items = array(); + + $l = count( $tag->values ); + for ( $i = 0; $i < $l; $i++ ) { + $items[] = array( 'type' => 'string' ); + } + + return array( + 'type' => 'array', + 'items' => $items, + 'additionalItems' => false, + 'minItems' => $tag->is_required() ? 1 : 0, + 'maxItems' => count( $tag->values ), + ); + } + + return array( 'type' => 'string' ); + case 'checkbox': + if ( $tag->has_option( 'exclusive' ) ) { + return array( 'type' => 'string' ); + } + + $items = array(); + + $l = count( $tag->values ); + for ( $i = 0; $i < $l; $i++ ) { + $items[] = array( 'type' => 'string' ); + } + + return array( + 'type' => 'array', + 'items' => $items, + 'additionalItems' => false, + 'minItems' => $tag->is_required() ? 1 : 0, + 'maxItems' => count( $tag->values ), + ); + case 'file': + case 'files': + return; + case 'acceptance': + return array( 'type' => 'boolean' ); + case 'number': + return array( 'type' => 'number' ); + default: + return array( 'type' => 'string' ); + } + } + + /** + * Serializes the form's submission data. + * + * @param WPCF7_Submission $submission Submission instance. + * @param array $form_data Form data. + * + * @return array Submission data. + */ + public function serialize_submission( $submission, $form_data ) { + $data = $submission->get_posted_data(); + + foreach ( $data as $key => $val ) { + $index = array_search( $key, array_column( $form_data['fields'], 'name' ), true ); + $field = $form_data['fields'][ $index ]; + + if ( is_array( $val ) && ! $field['is_multi'] ) { + $data[ $key ] = $val[0]; + $val = $data[ $key ]; + } + + if ( 'hidden' === $field['basetype'] ) { + $number_val = (float) $val; + if ( strval( $number_val ) === $val ) { + $data[ $key ] = $number_val; + } else { + $data[ $key ] = $val; + } + } elseif ( 'number' === $field['basetype'] ) { + $data[ $key ] = (float) $val; + } elseif ( 'file' === $field['basetype'] ) { + unset( $data[ $key ] ); + } elseif ( 'acceptance' === $field['basetype'] ) { + $data[ $key ] = (bool) $val; + } + } + + return $data; + } + + /** + * Gets submission uploaded files. + * + * @param WPCF7_Submission $submission Submission instance. + * + * @return array Uploaded files data. + */ + protected function submission_uploads( $submission ) { + $uploads = array(); + $uploads = $submission->uploaded_files(); + foreach ( $uploads as $file_name => $paths ) { + if ( ! empty( $paths ) ) { + $is_multi = count( $paths ) > 1; + + $uploads[ $file_name ] = array( + 'path' => $is_multi ? $paths : $paths[0], + 'is_multi' => $is_multi, + ); + } + } + + return $uploads; + } + + /** + * Gets form fields from a template and return a contact form content string. + * + * @param array $fields Form data fields array. + * + * @return string Form content. + */ + private function fields_to_form( $fields ) { + $form = ''; + foreach ( $fields as $field ) { + if ( 'hidden' === $field['type'] ) { + if ( isset( $field['value'] ) ) { + if ( is_bool( $field['value'] ) ) { + $field['value'] = $field['value'] ? '1' : '0'; + } + + $form .= $this->field_to_tag( $field ) . "\n\n"; + } + } else { + $form .= "\n\n"; + } + } + + $form .= sprintf( '[submit "%s"]', __( 'Submit', 'forms-bridge' ) ); + return $form; + } + + /** + * Gets a field template data and returns a form tag string. + * + * @param array $field Field data. + * + * @return string. + */ + private function field_to_tag( $field ) { + if ( isset( $field['value'] ) ) { + $type = 'hidden'; + } else { + $type = sanitize_text_field( $field['type'] ); + + switch ( $type ) { + case 'checkbox': + $type = 'acceptance'; + break; + case 'select': + if ( $field['is_multi'] ?? false ) { + $type = 'checkbox'; + } + + break; + } + + if ( ( $field['required'] ?? false ) && 'hidden' !== $type ) { + $type .= '*'; + } + } + + $name = sanitize_text_field( $field['name'] ); + $tag = "[{$type} {$name} "; + + foreach ( $field as $key => $val ) { + if ( ! in_array( $key, array( 'name', 'type', 'value', 'required', 'label' ), true ) ) { + $key = sanitize_text_field( $key ); + $val = trim( sanitize_text_field( $val ) ); + $tag .= "{$key}:{$val} "; + } + } + + $value = null; + + if ( strstr( $type, 'select' ) !== false || strstr( $type, 'checkbox' ) !== false ) { + $options = array(); + foreach ( (array) $field['options'] as $opt ) { + $value = $opt['value']; + $label = $opt['label'] ?? $value; + $options[] = $label . '|' . $value; + } + + $value = implode( '" "', $options ); + } elseif ( ! empty( $field['value'] ) ) { + $value = sanitize_text_field( (string) $field['value'] ); + } + + if ( $value ) { + $tag .= "\"{$value}\""; + } + + return $tag . ']'; + } + + /** + * Serialize the email data of a contact form. + * + * @param string $title Form title. + * @param array $fields Form fields. + */ + private function form_email( $title, $fields ) { + $site_url = get_option( 'siteurl' ); + $host = wp_parse_url( $site_url )['host'] ?? 'example.coop'; + + $email_index = array_search( 'email', array_column( $fields, 'type' ), true ); + if ( $email_index ) { + $replay_to = 'Replay-To: [' . $fields[ $email_index ]['name'] . ']'; + } else { + $replay_to = ''; + } + + $body = __( + "This are the responses to the contact form:\n\n", + 'forms-bridge' + ); + + foreach ( $fields as $field ) { + $label = $field['label'] ?? $field['name']; + $body .= ' * ' . esc_html( $label ) . ': [' . $field['name'] . "]\n"; + } + + $notice = sprintf( + /* translators: 1: blog name, 2: blog URL */ + __( + 'This is a notification that a contact form was submitted on your website (%1$s %2$s).', + 'forms-bridge' + ), + '[_site_title]', + '[_site_url]' + ); + + $body .= "\n---\n{$notice}"; + + return array( + 'recipient' => '[_site_admin_email]', + 'sender' => "[_site_title]Form submitted successfully.
', - 'forms-bridge' - ), - 'email_message_plain' => '', - ], - ], - ]; - } -} - -Integration::setup(); diff --git a/integrations/woo/class-woo-integration.php b/integrations/woo/class-woo-integration.php deleted file mode 100644 index 5a4bd49c..00000000 --- a/integrations/woo/class-woo-integration.php +++ /dev/null @@ -1,735 +0,0 @@ - 'object', - 'properties' => [ - 'id' => ['type' => 'integer'], - 'parent_id' => ['type' => 'integer'], - 'status' => ['type' => 'string'], - 'currency' => ['type' => 'string'], - 'version' => ['type' => 'string'], - 'prices_include_tax' => ['type' => 'boolean'], - 'date_created' => ['type' => 'string'], - 'date_modified' => ['type' => 'string'], - 'discount_total' => ['type' => 'number'], - 'discount_tax' => [ - 'type' => 'object', - 'properties' => [ - 'amount' => ['type' => 'number'], - 'rate' => ['type' => 'number'], - 'percentage' => ['type' => 'number'], - ], - ], - 'shipping_total' => ['type' => 'number'], - 'shipping_tax' => [ - 'type' => 'object', - 'properties' => [ - 'amount' => ['type' => 'number'], - 'rate' => ['type' => 'number'], - 'percentage' => ['type' => 'number'], - ], - ], - 'cart_total' => ['type' => 'number'], - 'cart_tax' => [ - 'type' => 'object', - 'properties' => [ - 'amount' => ['type' => 'number'], - 'rate' => ['type' => 'number'], - 'percentage' => ['type' => 'number'], - ], - ], - 'total' => ['type' => 'number'], - 'total_tax' => [ - 'type' => 'object', - 'properties' => [ - 'amount' => ['type' => 'number'], - 'rate' => ['type' => 'number'], - 'percentage' => ['type' => 'number'], - ], - ], - 'customer_id' => ['type' => 'integer'], - 'order_key' => ['type' => 'string'], - 'billing' => [ - 'type' => 'object', - 'properties' => [ - 'first_name' => ['type' => 'string'], - 'last_name' => ['type' => 'string'], - 'company' => ['type' => 'string'], - 'address_1' => ['type' => 'string'], - 'address_2' => ['type' => 'string'], - 'city' => ['type' => 'string'], - 'state' => ['type' => 'string'], - 'postcode' => ['type' => 'string'], - 'country' => ['type' => 'string'], - 'email' => ['type' => 'string'], - 'phone' => ['type' => 'string'], - ], - 'additionalProperties' => true, - ], - 'shipping' => [ - 'type' => 'object', - 'properties' => [ - 'first_name' => ['type' => 'string'], - 'last_name' => ['type' => 'string'], - 'company' => ['type' => 'string'], - 'address_1' => ['type' => 'string'], - 'address_2' => ['type' => 'string'], - 'city' => ['type' => 'string'], - 'state' => ['type' => 'string'], - 'postcode' => ['type' => 'string'], - 'country' => ['type' => 'string'], - 'phone' => ['type' => 'string'], - ], - 'additionalProperties' => true, - ], - 'payment_method' => ['type' => 'string'], - 'payment_method_title' => ['type' => 'string'], - 'transaction_id' => ['type' => 'string'], - 'customer_ip_address' => ['type' => 'string'], - 'customer_user_agent' => ['type' => 'string'], - 'created_via' => ['type' => 'string'], - 'customer_note' => ['type' => 'string'], - 'date_completed' => ['type' => 'string'], - 'date_paid' => ['type' => 'string'], - 'cart_hash' => ['type' => 'string'], - 'order_stock_reduced' => ['type' => 'boolean'], - 'download_permissions_granted' => ['type' => 'boolean'], - 'new_order_email_sent' => ['type' => 'boolean'], - 'recorded_sales' => ['type' => 'boolean'], - 'recorded_coupon_usage_counts' => ['type' => 'boolean'], - 'number' => ['type' => 'integer'], - // 'meta_data' => [ - // 'type' => 'array', - // 'items' => [ - // 'type' => 'object', - // 'properties' => [ - // 'id' => ['type' => 'integer'], - // 'key' => ['type' => 'string'], - // 'value' => ['type' => 'string'], - // ], - // 'required' => ['id', 'key', 'value'], - // 'additionalProperties' => false, - // ], - // 'additionalItems' => true, - // ], - 'line_items' => [ - 'type' => 'array', - 'items' => [ - 'type' => 'object', - 'properties' => [ - 'id' => ['type' => 'integer'], - 'order_id' => ['type' => 'integer'], - 'name' => ['type' => 'string'], - 'product_id' => ['type' => 'integer'], - 'variation_id' => ['type' => 'integer'], - 'quantity' => ['type' => 'integer'], - 'tax_class' => ['type' => 'string'], - 'subtotal' => ['type' => 'number'], - 'subtotal_tax' => [ - 'type' => 'object', - 'properties' => [ - 'amount' => ['type' => 'number'], - 'rate' => ['type' => 'number'], - 'percentage' => ['type' => 'number'], - ], - ], - 'total' => ['type' => 'number'], - 'total_tax' => [ - 'type' => 'object', - 'properties' => [ - 'amount' => ['type' => 'number'], - 'rate' => ['type' => 'number'], - 'percentage' => ['type' => 'number'], - ], - ], - 'taxes' => [ - 'type' => 'object', - 'properties' => [ - 'subtotal' => [ - 'type' => 'array', - 'items' => ['type' => 'number'], - 'additionalItems' => true, - ], - 'total' => [ - 'type' => 'array', - 'items' => ['type' => 'number'], - 'additionalItems' => true, - ], - ], - ], - 'product' => [ - 'type' => 'object', - 'properties' => [ - 'id' => ['type' => 'integer'], - 'parent_id' => ['type' => 'integer'], - 'sku' => ['type' => 'string'], - 'name' => ['type' => 'string'], - 'slug' => ['type' => 'string'], - 'price' => ['type' => 'number'], - 'sale_price' => ['type' => 'number'], - 'regular_price' => ['type' => 'number'], - 'stock_quantity' => ['type' => 'number'], - 'stock_status' => ['type' => 'string'], - ], - ], - // 'meta_data' => [ - // 'type' => 'array', - // 'items' => [ - // 'type' => 'object', - // 'properties' => [ - // 'id' => ['type' => 'integer'], - // 'key' => ['type' => 'string'], - // 'value' => ['type' => 'string'], - // ], - // ], - // ], - ], - ], - 'additionalItems' => true, - 'minItems' => 1, - ], - 'tax_lines' => [ - 'type' => 'array', - 'items' => [ - 'type' => 'object', - 'properties' => [ - 'id' => ['type' => 'integer'], - 'order_id' => ['type' => 'integer'], - 'name' => ['type' => 'string'], - 'rate_code' => ['type' => 'string'], - 'rate_id' => ['type' => 'integer'], - 'label' => ['type' => 'string'], - 'compound' => ['type' => 'boolean'], - 'tax_total' => ['type' => 'number'], - 'shipping_tax_total' => ['type' => 'number'], - 'rate_percent' => ['type' => 'number'], - // 'meta_data' => [ - // 'type' => 'array', - // 'items' => [ - // 'type' => 'object', - // 'properties' => [ - // 'id' => ['type' => 'integer'], - // 'key' => ['type' => 'string'], - // 'value' => ['type' => 'string'], - // ], - // ], - // 'additionalItems' => true, - // ], - ], - ], - 'additionalItems' => true, - ], - 'shipping_lines' => [ - 'type' => 'array', - 'items' => [ - 'type' => 'object', - 'properties' => [ - 'id' => ['type' => 'integer'], - 'order_id' => ['type' => 'integer'], - 'name' => ['type' => 'string'], - 'method_id' => ['type' => 'string'], - 'method_title' => ['type' => 'string'], - 'instance_id' => ['type' => 'integer'], - 'total' => ['type' => 'number'], - 'total_tax' => [ - 'type' => 'object', - 'properties' => [ - 'amount' => ['type' => 'number'], - 'rate' => ['type' => 'number'], - 'percentage' => ['type' => 'number'], - ], - ], - 'tax_status' => ['type' => 'string'], - 'taxes' => [ - 'type' => 'object', - 'properties' => [ - 'total' => ['type' => 'number'], - 'subtotal' => ['type' => 'number'], - ], - 'required' => ['total'], - ], - // 'meta_data' => [ - // 'type' => 'array', - // 'items' => [ - // 'type' => 'object', - // 'properties' => [ - // 'id' => ['type' => 'integer'], - // 'key' => ['type' => 'string'], - // 'value' => ['type' => 'string'], - // ], - // ], - // ], - ], - ], - 'additionalItems' => true, - ], - 'fee_lines' => [ - 'type' => 'array', - 'items' => [ - 'type' => 'object', - 'properties' => [ - 'id' => ['type' => 'integer'], - 'order_id' => ['type' => 'integer'], - 'name' => ['type' => 'string'], - 'tax_class' => ['type' => 'string'], - 'tax_status' => ['type' => 'string'], - 'amount' => ['type' => 'number'], - 'total' => ['type' => 'number'], - 'total_tax' => [ - 'type' => 'object', - 'properties' => [ - 'amount' => ['type' => 'number'], - 'rate' => ['type' => 'number'], - 'percentage' => ['type' => 'number'], - ], - ], - 'taxes' => [ - 'type' => 'object', - 'properties' => [ - 'total' => [ - 'type' => 'array', - 'items' => ['type' => 'number'], - 'additionalItems' => true, - ], - ], - 'required' => ['total'], - ], - // 'meta_data' => [ - // 'type' => 'array', - // 'items' => [ - // 'type' => 'object', - // 'properties' => [ - // 'id' => ['type' => 'integer'], - // 'key' => ['type' => 'string'], - // 'value' => ['type' => 'string'], - // ], - // ], - // ], - ], - ], - 'additionalItems' => true, - ], - 'coupon_lines' => [ - 'type' => 'array', - 'items' => [ - 'type' => 'object', - 'properties' => [ - 'id' => ['type' => 'integer'], - 'order_id' => ['type' => 'integer'], - 'name' => ['type' => 'string'], - 'code' => ['type' => 'string'], - 'discount' => ['type' => 'number'], - 'discount_tax' => ['type' => 'number'], - // 'meta_data' => [ - // 'type' => 'array', - // 'items' => [ - // 'type' => 'object', - // 'properties' => [ - // 'id' => ['type' => 'integer'], - // 'key' => ['type' => 'string'], - // 'value' => ['type' => 'string'], - // ], - // ], - // ], - ], - ], - 'additionalItems' => true, - ], - ], - 'additionalProperties' => false, - ]; - - private static function decorate_tax($tax, $total) - { - try { - $tax = (float) $tax; - $rate = $tax / $total; - $rate = floor($rate * 1000) / 1000; - - return [ - 'amount' => $tax, - 'rate' => $rate, - 'percentage' => $rate * 100, - ]; - } catch (TypeError | DivisionByZeroError) { - return [ - 'amount' => 0, - 'rate' => 0, - 'percentage' => 0, - ]; - } - } - - public function init() - { - add_action( - 'woocommerce_order_status_changed', - static function ($order_id, $old_status, $new_status) { - $is_bridged = - get_post_meta( - $order_id, - self::is_order_bridged_custom_field, - true - ) === '1'; - - $trigger_submission = apply_filters( - 'forms_bridge_woo_trigger_submission', - !$is_bridged && $new_status === 'completed', - $order_id, - $new_status, - $old_status, - $is_bridged - ); - - if ($trigger_submission) { - self::$order_id = $order_id; - - add_action( - 'forms_bridge_after_submission', - function () { - update_post_meta( - self::$order_id, - self::is_order_bridged_custom_field, - '1' - ); - }, - 90 - ); - - Forms_Bridge::do_submission(); - } - }, - 10, - 3 - ); - } - - public function form() - { - if (empty(self::$order_id)) { - return; - } - - return $this->get_form_by_id(1); - } - - public function get_form_by_id($form_id) - { - if ($form_id != 1) { - return; - } - - WC()->session = new WC_Session_Handler(); - WC()->customer = new WC_Customer(); - - return apply_filters( - 'forms_bridge_form_data', - [ - '_id' => 'woo:1', - 'id' => 1, - 'title' => __('Woo Checkout', 'forms-bridge'), - 'bridges' => FBAPI::get_form_bridges(1, 'woo'), - 'fields' => $this->serialize_order_fields(), - ], - WC()->checkout, - 'woo' - ); - } - - public function forms() - { - return [$this->get_form_by_id(1)]; - } - - public function create_form($data) - { - return 1; - } - - public function remove_form($form_id) - { - return; - } - - public function submission_id() - { - if (self::$order_id) { - return (string) self::$order_id; - } - } - - public function submission($raw) - { - if (empty(self::$order_id)) { - return; - } - - return $this->serialize_order(self::$order_id); - } - - public function uploads() - { - return []; - } - - private function serialize_order_fields() - { - $checkout_fields = WC()->checkout->checkout_fields; - - $fields = []; - foreach ( - self::order_data_schema['properties'] - as $name => $field_schema - ) { - $fields[] = self::decorate_order_field($name, $field_schema); - } - - foreach (array_keys($checkout_fields['billing']) as $name) { - $name = str_replace('billing_', '', $name); - if (isset(self::order_data_schema['billing'][$name])) { - continue; - } - - $index = array_search('billing', array_column($fields, 'name')); - - $billing_field = &$fields[$index]; - $billing_field['schema']['properties'][$name] = [ - 'type' => 'text', - ]; - } - - foreach (array_keys($checkout_fields['shipping']) as $name) { - $name = str_replace('shipping_', '', $name); - if (isset(self::order_data_schema['shipping'][$name])) { - continue; - } - - $index = array_search('shipping', array_column($fields, 'name')); - - $shipping_field = &$fields[$index]; - $shipping_field['schema']['properties'][$name] = [ - 'type' => 'text', - ]; - } - - return $fields; - } - - private function decorate_order_field($name, $schema) - { - switch ($schema['type']) { - case 'string': - $field_type = 'text'; - break; - case 'number': - case 'integer': - $field_type = 'number'; - break; - case 'boolean': - $field_type = 'boolean'; - break; - case 'array': - $field_type = 'select'; - break; - default: - $field_type = $schema['type']; - } - - return [ - 'id' => null, - 'name' => $name, - 'label' => $name, - 'type' => $field_type, - 'required' => true, - 'is_file' => false, - 'is_multi' => $schema['type'] === 'array', - 'conditional' => false, - 'schema' => $schema, - ]; - } - - private function serialize_order($order_id) - { - $order = wc_get_order($order_id); - if (empty($order)) { - return; - } - - $checkout = WC()->checkout; - - $data = $order->get_data(); - unset($data['meta_data']); - - $checkout_fields = $checkout->checkout_fields; - foreach (array_keys($checkout_fields['billing']) as $name) { - $unprefixed = str_replace('billing_', '', $name); - if (!isset($data['billing'][$unprefixed])) { - $data['billing'][$unprefixed] = $checkout->get_value($name); - } - } - - foreach (array_keys($checkout_fields['shipping']) as $name) { - $unprefixed = str_replace('shipping_', '', $name); - if (!isset($data['shipping'][$unprefixed])) { - $data['shipping'][$unprefixed] = $checkout->get_value($name); - } - } - - $tax_lines = []; - foreach ($data['tax_lines'] as $tax_line) { - $line_data = $tax_line->get_data(); - unset($line_data['meta_data']); - $tax_lines[] = $line_data; - } - - $data['tax_lines'] = $tax_lines; - - $line_items = []; - foreach ($data['line_items'] as $line_item) { - $item_data = $line_item->get_data(); - unset($item_data['meta_data']); - - $product = $line_item->get_product(); - $item_data['product'] = [ - 'id' => $product->get_id(), - 'parent_id' => $product->get_parent_id(), - 'slug' => $product->get_slug(), - 'sku' => $product->get_sku(), - 'name' => $product->get_name(), - 'price' => $product->get_price(), - 'sale_price' => $product->get_sale_price(), - 'regular_price' => $product->get_regular_price(), - 'stock_quantity' => $product->get_stock_quantity(), - 'stock_status' => $product->get_stock_status(), - ]; - - $item_data['total_tax'] = self::decorate_tax( - $line_item['total_tax'], - $line_item['total'] - ); - - $item_data['subtotal_tax'] = self::decorate_tax( - $line_item['subtotal_tax'], - $line_item['subtotal'] - ); - - $line_items[] = $item_data; - } - - $data['line_items'] = $line_items; - - $shipping_lines = []; - foreach ($data['shipping_lines'] as $shipping_line) { - $line_data = $shipping_line->get_data(); - unset($line_data['meta_data']); - - $line_data['total_tax'] = self::decorate_tax( - $line_data['total_tax'], - $line_data['total'] - ); - - $shipping_lines[] = $line_data; - } - - $data['shipping_lines'] = $shipping_lines; - - $coupon_lines = []; - foreach ($data['coupon_lines'] ?? [] as $coupon_line) { - $line_data = $coupon_line->get_data(); - unset($line_data['meta_data']); - - $line_data['discount_tax'] = self::decorate_tax( - $line_data['discount_tax'], - $line_data['discount'] - ); - - $coupon_lines[] = $line_data; - } - - $data['coupon_lines'] = $coupon_lines; - - $fee_lines = []; - foreach ($data['fee_lines'] ?? [] as $fee_line) { - $line_data = $fee_line->get_data(); - unset($line_data['meta_data']); - - $line_data['total_tax'] = self::decorate_tax( - $line_data['total_tax'], - $line_data['total'] - ); - - $fee_lines[] = $line_data; - } - - $data['fee_lines'] = $fee_lines; - - $data['discount_tax'] = self::decorate_tax( - $data['discount_tax'], - $data['discount_total'] - ); - - $data['shipping_tax'] = self::decorate_tax( - $data['shipping_tax'], - $data['shipping_total'] - ); - - $data['total_tax'] = self::decorate_tax( - $data['total_tax'], - $data['total'] - ); - - $cart_total = 0; - foreach ($data['line_items'] as $line_data) { - $cart_total += $line_data['total']; - } - - foreach ($data['fee_lines'] as $line_data) { - $cart_total += $line_data['total']; - } - - $data['cart_total'] = $cart_total; - $data['cart_tax'] = self::decorate_tax( - $data['cart_tax'], - $data['cart_total'] - ); - - return rest_sanitize_value_from_schema($data, self::order_data_schema); - } -} - -Integration::setup(); diff --git a/integrations/wpcf7/class-wpcf7-integration.php b/integrations/wpcf7/class-wpcf7-integration.php deleted file mode 100644 index 5efa146b..00000000 --- a/integrations/wpcf7/class-wpcf7-integration.php +++ /dev/null @@ -1,547 +0,0 @@ -serialize_form($form); - } - - /** - * Retrives a contact form's data by ID. - * - * @param int $form_id Form ID. - * @return array $form_data Form data. - */ - public function get_form_by_id($form_id) - { - $form = WPCF7_ContactForm::get_instance($form_id); - if (!$form) { - return null; - } - - return $this->serialize_form($form); - } - - /** - * Retrives available constact forms as form data. - * - * @return array $forms Collection of form data. - */ - public function forms() - { - $forms = WPCF7_ContactForm::find(['post_status', 'publish']); - return array_map(function ($form) { - return $this->serialize_form($form); - }, $forms); - } - - /** - * Creates a form from the given template fields. - * - * @param array $data Form template data. - * - * @return int|null ID of the new form. - * - * @todo Fix form email attribute. - */ - public function create_form($data) - { - if (empty($data['title']) || empty($data['fields'])) { - return; - } - - $form = $this->fields_to_form($data['fields']); - $email = $this->form_email($data['title'], $data['fields']); - - $contact_form = wpcf7_save_contact_form([ - 'title' => $data['title'], - 'locale' => apply_filters( - 'wpct_i18n_current_language', - null, - 'locale' - ), - 'form' => $form, - 'mail' => $email, - ]); - - if (!$contact_form) { - return; - } - - return $contact_form->id(); - } - - /** - * Removes a form by ID. - * - * @param integer $form_id Form ID. - * - * @return boolean Removal result. - */ - public function remove_form($form_id) - { - $result = wp_delete_post($form_id); - return !!$result; - } - - public function submission_id() - { - $submission = $this->submission(true); - if ($submission) { - return $submission->get_posted_data_hash(); - } - } - - /** - * Retrives the current submission data. - * - * @param boolean $raw Control if the submission is serialized before exit. - * - * @return WPCF7_Submission|array Submission data. - */ - public function submission($raw = false) - { - $submission = WPCF7_Submission::get_instance(); - - if (!$submission) { - return null; - } elseif ($raw) { - return $submission; - } - - $form = $this->form(); - return $this->serialize_submission($submission, $form); - } - - /** - * Retrives the current submission uploaded files. - * - * @return array Uploaded files data. - */ - public function uploads() - { - $submission = WPCF7_Submission::get_instance(); - if (!$submission) { - return null; - } - - return $this->submission_uploads($submission); - } - - /** - * Serializes a contact form instance as array data. - * - * @param WPCF7_ContactForm $form Form instance. - * - * @return array. - */ - public function serialize_form($form) - { - $form_id = (int) $form->id(); - $fields = array_filter( - array_map(function ($field) { - return $this->serialize_field($field); - }, $form->scan_form_tags()) - ); - - return apply_filters( - 'forms_bridge_form_data', - [ - '_id' => 'wpcf7:' . $form_id, - 'id' => $form_id, - 'title' => $form->title(), - 'bridges' => FBAPI::get_form_bridges($form_id, 'wpcf7'), - 'fields' => array_values($fields), - ], - $form, - 'wpcf7' - ); - } - - /** - * Serializes a form tags as array data. - * - * @param WPCF7_FormTag $field Form tag instance. - * @param array $form_data Form data. - * - * @return array. - */ - private function serialize_field($field) - { - if (in_array($field->basetype, ['response', 'submit', 'quiz'])) { - return; - } - - $type = $field->basetype; - if ($type === 'conditional') { - $type = $field->get_option('type')[0]; - } elseif ($type === 'hidden') { - $type = 'text'; - } - - $options = []; - if (is_array($field->values)) { - $values = $field->pipes->collect_afters(); - for ($i = 0; $i < sizeof($field->raw_values); $i++) { - $options[] = [ - 'value' => $values[$i], - 'label' => $field->labels[$i], - ]; - } - } - - $format = $type === 'date' ? 'yyyy-mm-dd' : ''; - - return apply_filters( - 'forms_bridge_form_field_data', - [ - 'id' => $field->get_id_option(), - 'type' => $type, - 'name' => $field->raw_name, - 'label' => $field->name, - 'required' => $field->is_required(), - 'options' => $options, - 'is_file' => $type === 'file', - 'is_multi' => $this->is_multi_field($field), - 'conditional' => - $field->basetype === 'conditional' || - $field->basetype === 'fileconditional', - 'format' => $format, - 'schema' => $this->field_value_schema($field), - '_type' => $field->basetype, - ], - $field, - 'wpcf7' - ); - } - - /** - * Checks if a filed is multi value field. - * - * @param WPCF7_FormTag Target tag instance. - * - * @return boolean - */ - private function is_multi_field($tag) - { - $type = str_replace('*', '', $tag->type); - - if ($type === 'checkbox') { - return !$tag->has_option('exclusive'); - } - - if ($type === 'select') { - return $tag->has_option('multiple'); - } - - return false; - } - - /** - * Gets the field value JSON schema. - * - * @param WPCF7_FormTag $tag Tag instance. - * - * @return array JSON schema of the value of the field. - */ - private function field_value_schema($tag) - { - $type = str_replace('*', '', $tag->type); - - switch ($type) { - case 'text': - case 'textarea': - case 'date': - case 'email': - case 'url': - case 'quiz': - case 'radio': - case 'iban': - case 'vat': - return ['type' => 'string']; - case 'select': - if ($tag->has_option('multiple')) { - $items = []; - for ($i = 0; $i < count($tag->values); $i++) { - $items[] = ['type' => 'string']; - } - - return [ - 'type' => 'array', - 'items' => $items, - 'additionalItems' => false, - 'minItems' => $tag->is_required() ? 1 : 0, - 'maxItems' => count($tag->values), - ]; - } - - return ['type' => 'string']; - case 'checkbox': - if ($tag->has_option('exclusive')) { - return ['type' => 'string']; - } - - $items = []; - for ($i = 0; $i < count($tag->values); $i++) { - $items[] = ['type' => 'string']; - } - - return [ - 'type' => 'array', - 'items' => $items, - 'additionalItems' => false, - 'minItems' => $tag->is_required() ? 1 : 0, - 'maxItems' => count($tag->values), - ]; - case 'file': - case 'files': - return; - case 'acceptance': - return ['type' => 'boolean']; - case 'number': - return ['type' => 'number']; - default: - return ['type' => 'string']; - } - } - - /** - * Serializes the form's submission data. - * - * @param WPCF7_Submission $submission Submission instance. - * @param array $form Form data. - * - * @return array Submission data. - */ - public function serialize_submission($submission, $form_data) - { - $data = $submission->get_posted_data(); - - foreach ($data as $key => $val) { - $i = array_search($key, array_column($form_data['fields'], 'name')); - $field = $form_data['fields'][$i]; - - if ($field['_type'] === 'hidden') { - $number_val = (float) $val; - if (strval($number_val) === $val) { - $data[$key] = $number_val; - } else { - $data[$key] = $val; - } - } elseif ($field['_type'] === 'number') { - $data[$key] = (float) $val; - } elseif (is_array($val) && !$field['is_multi']) { - $data[$key] = $val[0]; - } elseif ($field['_type'] === 'file') { - unset($data[$key]); - } - } - - return $data; - } - - /** - * Gets submission uploaded files. - * - * @param WPCF7_Submission $submission Submission instance. - * - * @return array Uploaded files data. - */ - protected function submission_uploads($submission) - { - $uploads = []; - $uploads = $submission->uploaded_files(); - foreach ($uploads as $file_name => $paths) { - if (!empty($paths)) { - $is_multi = sizeof($paths) > 1; - $uploads[$file_name] = [ - 'path' => $is_multi ? $paths : $paths[0], - 'is_multi' => $is_multi, - ]; - } - } - - return $uploads; - } - - /** - * Gets form fields from a template and return a contact form content string. - * - * @param array $fields. - * - * @return string Form content. - */ - private function fields_to_form($fields) - { - $form = ''; - foreach ($fields as $field) { - if ($field['type'] == 'hidden') { - if (isset($field['value'])) { - if (is_bool($field['value'])) { - $field['value'] = $field['value'] ? '1' : '0'; - } - - $form .= $this->field_to_tag($field) . "\n\n"; - } - } else { - $form .= "\n\n"; - } - } - - $form .= sprintf('[submit "%s"]', __('Submit', 'forms-bridge')); - return $form; - } - - /** - * Gets a field template data and returns a form tag string. - * - * @param array $field. - * - * @return string. - */ - private function field_to_tag($field) - { - if (isset($field['value'])) { - $type = 'hidden'; - } else { - $type = sanitize_text_field($field['type']); - - if (($field['required'] ?? false) && $type !== 'hidden') { - $type .= '*'; - } - } - - $name = sanitize_text_field($field['name']); - $tag = "[{$type} {$name} "; - - foreach ($field as $key => $val) { - if ( - !in_array($key, ['name', 'type', 'value', 'required', 'label']) - ) { - $key = sanitize_text_field($key); - $val = sanitize_text_field($val); - $tag .= "{$key}:{$val} "; - } - } - - $value = null; - - if ($type === 'select' || $type === 'select*') { - $options = array_map(function ($opt) { - return $opt['label'] . '|' . $opt['value']; - }, $field['options'] ?? []); - - $value = implode('" "', $options); - } elseif (!empty($field['value'])) { - $value = sanitize_text_field((string) $field['value']); - } - - if ($value) { - $tag .= "\"{$value}\""; - } - - return $tag . ']'; - } - - private function form_email($title, $fields) - { - $site_url = get_option('siteurl'); - $host = wp_parse_url($site_url)['host'] ?? 'example.coop'; - - $email_index = array_search('email', array_column($fields, 'type')); - if ($email_index) { - $replay_to = 'Replay-To: [' . $fields[$email_index]['name'] . ']'; - } else { - $replay_to = ''; - } - - $body = __( - "This are the responses to the contact form:\n\n", - 'forms-bridge' - ); - - foreach ($fields as $field) { - $label = $field['label'] ?? $field['name']; - $body .= ' * ' . esc_html($label) . ': [' . $field['name'] . "]\n"; - } - - $notice = sprintf( - /* translators: 1: blog name, 2: blog URL */ - __( - 'This is a notification that a contact form was submitted on your website (%1$s %2$s).', - 'forms-bridge' - ), - '[_site_title]', - '[_site_url]' - ); - - $body .= "\n---\n{$notice}"; - - return [ - 'recipient' => '[_site_admin_email]', - 'sender' => "[_site_title]