diff --git a/bin/download-test-deps.sh b/bin/download-test-deps.sh index 61f477fc..8d7dd271 100644 --- a/bin/download-test-deps.sh +++ b/bin/download-test-deps.sh @@ -13,13 +13,14 @@ else mkdir -p "$WORDPRESS_DIR/wp-content/mu-plugins" fi -URLS=('https://downloads.wordpress.org/plugin/contact-form-7.6.1.3.zip' - 'https://downloads.wordpress.org/plugin/ninja-forms.3.13.0.zip' - 'https://www.codeccoop.org/formsbridge/plugins/wpforms.zip' +URLS=('https://downloads.wordpress.org/plugin/contact-form-7.6.1.4.zip' + 'https://downloads.wordpress.org/plugin/formidable.6.26.1.zip' 'https://www.codeccoop.org/formsbridge/plugins/gravityforms.zip' - 'https://downloads.wordpress.org/plugin/woocommerce.10.3.4.zip') + 'https://downloads.wordpress.org/plugin/ninja-forms.3.13.3.zip' + 'https://downloads.wordpress.org/plugin/woocommerce.10.4.3.zip' + 'https://downloads.wordpress.org/plugin/wpforms-lite.1.9.8.7.zip') -PLUGINS=('contact-form-7' 'gravityforms' 'ninja-forms' 'wpforms', 'woocommerce') +PLUGINS=('contact-form-7' 'formidable' 'gravityforms' 'ninja-forms' 'woocommerce' 'wpforms-lite') COUNT=${#PLUGINS[@]} diff --git a/forms-bridge/includes/class-form-bridge-template.php b/forms-bridge/includes/class-form-bridge-template.php index 07cedf23..2af69b0d 100644 --- a/forms-bridge/includes/class-form-bridge-template.php +++ b/forms-bridge/includes/class-form-bridge-template.php @@ -927,7 +927,7 @@ static function ( $field ) { if ( 'woo' === $integration ) { $data['form']['id'] = 1; - } elseif ( 'wpforms' === $integration ) { + } elseif ( in_array( $integration, array( 'wpforms', 'formidable' ), true ) ) { $mappers = array(); foreach ( $data['form']['fields'] as &$field ) { if ( 'file' !== $field['type'] ) { @@ -1005,9 +1005,7 @@ static function ( $field ) { if ( ! $result ) { if ( $create_form ) { - $integration_instance->remove_form( - $data['form']['id'] - ); + $integration_instance->remove_form( $data['form']['id'] ); } return new WP_Error( @@ -1060,9 +1058,7 @@ static function ( $field ) { if ( ! $bridge_created ) { if ( $create_form ) { - $integration_instance->remove_form( - $data['bridge']['form_id'] - ); + $integration_instance->remove_form( $data['form']['id'] ); } if ( $create_credential ) { diff --git a/forms-bridge/includes/class-integration.php b/forms-bridge/includes/class-integration.php index c317e0aa..1e7d9335 100644 --- a/forms-bridge/includes/class-integration.php +++ b/forms-bridge/includes/class-integration.php @@ -70,6 +70,9 @@ private static function check_dependencies( $integration ) { case 'woo': $deps = array( 'woocommerce/woocommerce.php' ); break; + case 'formidable': + $deps = array( 'formidable/formidable.php' ); + break; default: return false; } diff --git a/forms-bridge/integrations/formidable/class-formidable-integration.php b/forms-bridge/integrations/formidable/class-formidable-integration.php new file mode 100644 index 00000000..83bbd6a1 --- /dev/null +++ b/forms-bridge/integrations/formidable/class-formidable-integration.php @@ -0,0 +1,975 @@ +serialize_form( $form ); + } + + /** + * Retrives a form's data by ID. + * + * @param int $form_id Form ID. + * + * @return array + */ + public function get_form_by_id( $form_id ) { + $form = FrmForm::getOne( $form_id ); + if ( ! $form ) { + return null; + } + + return $this->serialize_form( $form ); + } + + /** + * Retrives available forms' data. + * + * @return array Collection of form data array representations. + */ + public function forms() { + $forms = FrmForm::get_published_forms(); + return array_map( + function ( $form ) { + return $this->serialize_form( $form ); + }, + $forms + ); + } + + /** + * Creates a form from a given template fields. + * + * @param array $data Form template data. + * + * @return int|null ID of the new form. + */ + public function create_form( $data ) { + if ( empty( $data['title'] ) || empty( $data['fields'] ) ) { + return null; + } + + global $wpdb; + + $form_data = array( + 'name' => $data['title'], + 'description' => '', + 'status' => 'published', + 'form_key' => FrmAppHelper::get_unique_key( sanitize_title( $data['title'] ), $wpdb->prefix . 'frm_forms', 'form_key' ), + ); + + $form_id = FrmForm::create( $form_data ); + + if ( ! $form_id ) { + return null; + } + + $fields = $this->prepare_fields( $data['fields'] ); + foreach ( $fields as $field ) { + $field_values = FrmFieldsHelper::setup_new_vars( $field['type'], $form_id ); + + $field['field_options'] = array_merge( $field_values['field_options'], $field['field_options'] ); + $field_values = array_merge( $field_values, $field ); + + $field_values['field_options']['draft'] = 0; + $field_values = apply_filters( 'frm_before_field_created', $field_values ); + FrmField::create( $field_values ); + } + + return $form_id; + } + + /** + * Removes a form by ID. + * + * @param integer $form_id Form ID. + * + * @return boolean Removal result. + */ + public function remove_form( $form_id ) { + return (bool) FrmForm::destroy( $form_id ); + } + + /** + * Retrives the current submission ID. + * + * @return string|null + */ + public function submission_id() { + global $frm_vars; + + $form_id = FrmAppHelper::get_post_param( 'form_id', '', 'absint' ); + + if ( ! $form_id || ! isset( $frm_vars['created_entries'][ $form_id ]['entry_id'] ) ) { + return null; + } + + return (string) $frm_vars['created_entries'][ $form_id ]['entry_id']; + } + + /** + * Retrives the current submission data. + * + * @param boolean $raw Control if the submission is serialized before exit. + * + * @return array|null + */ + public function submission( $raw = false ) { + $submission_id = $this->submission_id(); + if ( ! $submission_id ) { + return null; + } + + $form = $this->form(); + + $submission = array( + 'id' => $submission_id, + 'values' => FrmEntryMeta::get_entry_meta_info( $submission_id ), + ); + + if ( $raw ) { + $submission['entry'] = FrmEntry::getOne( $submission_id ); + return $submission; + } + + return $this->serialize_submission( $submission, $form ); + } + + /** + * Retrives the current submission uploaded files. + * + * @return array|null Collection of uploaded files. + */ + public function uploads() { + $submission_id = $this->submission_id(); + if ( ! $submission_id ) { + return null; + } + + $form = $this->form(); + + // $submission = array( + // 'id' => $submission_id, + // 'values' => FrmEntryMeta::get_entry_meta_info( $submission_id ), + // ); + + return $this->submission_uploads( null, $form ); + } + + /** + * Serializes Formidable form's data. + * + * @param object $form Formidable form data. + * + * @return array + */ + public function serialize_form( $form ) { + $form_id = (int) $form->id; + + $fields = array(); + $frm_fields = FrmField::get_all_for_form( $form_id ); + + $repeater = null; + foreach ( $frm_fields as $frm_field ) { + if ( intval( $form_id ) !== intval( $frm_field->form_id ) ) { + if ( $repeater && $repeater->form_id !== $frm_field->form_id ) { + $fields[] = $this->serialize_field( $repeater ); + $repeater = null; + } + + if ( ! $repeater ) { + $repeater_form = FrmForm::getOne( $frm_field->form_id ); + + $repeater = (object) array( + 'id' => $frm_field->field_options['in_section'], + 'field_key' => $repeater_form->form_key, + 'name' => $repeater_form->name, + 'description' => $repeater_form->description, + 'options' => '', + 'required' => '0', + 'fields' => array(), + 'type' => 'repeater', + 'form_id' => $frm_field->form_id, + 'field_options' => array( 'multiple' => true ), + ); + } + + $repeater_field = $this->serialize_field( $frm_field ); + if ( $repeater_field ) { + $repeater->fields[] = $repeater_field; + } + + continue; + } + + if ( $repeater ) { + $fields[] = $this->serialize_field( $repeater ); + $repeater = null; + } + + $field = $this->serialize_field( $frm_field ); + + if ( $field ) { + $field = wp_is_numeric_array( $field ) ? $field : array( $field ); + $fields = array_merge( $fields, $field ); + } + } + + return apply_filters( + 'forms_bridge_form_data', + array( + '_id' => 'formidable:' . $form_id, + 'id' => $form_id, + 'title' => $form->name, + 'bridges' => FBAPI::get_form_bridges( $form_id, 'formidable' ), + 'fields' => $fields, + ), + $form, + 'formidable' + ); + } + + /** + * Serializes a Formidable field as array data. + * + * @param object $field Field object instance. + * + * @return array|null + */ + private function serialize_field( $field ) { + $skip_fields = array( 'data', 'summary', 'break', 'end_divider', 'divider', 'html', 'captcha', 'submit' ); + + if ( in_array( $field->type, $skip_fields, true ) ) { + return null; + } + + $options = array(); + if ( isset( $field->options ) && is_array( $field->options ) ) { + foreach ( $field->options as $option ) { + if ( ! is_array( $option ) ) { + $option = array( 'value' => $option ); + } + + $options[] = array( + 'value' => $option['value'], + 'label' => $option['label'] ?? $option['value'], + ); + } + } + + switch ( $field->type ) { + case 'form': + case 'repeater': + $type = 'mixed'; + break; + case 'checkbox': + case 'select': + case 'radio': + case 'lookup': + $type = 'select'; + break; + case 'range': + if ( $field->field_options['is_range_slider'] ?? false ) { + return 'select'; + } + + return 'number'; + case 'star': + case 'number': + case 'scale': + case 'quantity': + case 'total': + case 'user_id': + $type = 'number'; + break; + case 'date': + case 'file': + case 'email': + case 'url': + $type = $field->type; + break; + case 'phone': + $type = 'tel'; + break; + case 'rte': + case 'textarea': + $type = 'textarea'; + break; + case 'hidden': + $type = 'hidden'; + break; + case 'toggle': + if ( $field->options[0] === '' && $field->options[1] === '1' ) { + $type = 'checkbox'; + } else { + $type = 'text'; + } + + break; + default: + $type = 'text'; + } + + $field = apply_filters( + 'forms_bridge_form_field_data', + array( + 'id' => $field->id, + 'type' => $type, + 'name' => trim( $field->name ), + 'label' => trim( $field->name ), + 'required' => (bool) $field->required, + 'options' => $options, + 'is_file' => 'file' === $field->type, + 'is_multi' => $this->is_multi_field( $field ), + 'conditional' => ! empty( $field->field_options['hide_field'] ) && ! empty( $field->field_options['hide_opt'] ), + 'format' => 'date' === $field->type ? 'yyyy-mm-dd' : '', + 'schema' => $this->field_value_schema( $field ), + 'basetype' => $field->type, + 'form_id' => $field->form_id, + ), + $field, + 'formidable' + ); + + return $field; + } + + /** + * Checks if a field is multi-value field. + * + * @param object $field Target field instance. + * + * @return boolean + */ + private function is_multi_field( $field ) { + if ( 'file' === $field->type ) { + return boolval( $field->field_options['multiple'] ?? false ); + } + + if ( 'range' === $field->type ) { + return boolval( $field->options['is_range_slider'] ?? false ); + } + + if ( in_array( $field->type, array( 'repeater', 'checkbox', 'address', 'credit_card' ), true ) ) { + return true; + } + + return false; + } + + /** + * Gets the field value JSON schema. + * + * @param object $field Field instance. + * + * @return array|null JSON schema of the value of the field. + */ + private function field_value_schema( $field ) { + switch ( $field->type ) { + case 'form': + $embedded = FrmForm::getOne( $field->field_options['form_select'] ); + $embedded_data = $this->serialize_form( $embedded ); + + $schema = array( + 'type' => 'object', + 'properties' => array(), + 'additionalProperties' => false, + ); + + foreach ( $embedded_data['fields'] as $embedded_field ) { + $schema['properties'][ $embedded_field['name'] ] = $embedded_field['schema']; + } + + return $schema; + case 'repeater': + $schema = array( + 'type' => 'array', + 'items' => array( + 'type' => 'object', + 'properties' => array(), + 'additionalProperties' => false, + ), + 'additionalItems' => true, + ); + + foreach ( $field->fields as $subfield ) { + $schema['items']['properties'][ $subfield['name'] ] = $subfield['schema']; + } + + return $schema; + case 'star': + case 'scale': + case 'range': + case 'quantity': + return array( 'type' => 'integer' ); + case 'total': + case 'number': + return array( 'type' => 'number' ); + case 'checkbox': + return array( + 'type' => 'array', + 'items' => array( 'type' => 'string' ), + 'additionalItems' => false, + ); + case 'credit_card': + return array( + 'type' => 'object', + 'properties' => array( + 'cc' => 'string', + 'cvc' => 'string', + 'month' => 'string', + 'year' => 'string', + ), + 'additionalProperties' => false, + ); + case 'address': + return array( + 'type' => 'object', + 'properties' => array( + 'line1' => 'string', + 'line2' => 'string', + 'city' => 'string', + 'country' => 'string', + 'zip' => 'string', + ), + 'additionalProperties' => false, + ); + case 'select': + if ( $field->field_options['multiple'] ?? false ) { + return array( + 'type' => 'array', + 'items' => array( 'type' => 'string' ), + 'additionalItems' => false, + ); + } else { + return array( 'type' => 'string' ); + } + case 'name': + return array( + 'type' => 'object', + 'properties' => array( + 'first' => 'string', + 'last' => 'string', + ), + 'additionalProperties' => false, + ); + case 'hidden': + $type = 'string'; + + if ( 'number' === $field->field_options['format'] ) { + $type = 'number'; + } + + return array( 'type' => $type ); + case 'file': + return null; + case 'toggle': + if ( $field->options[0] === '' && $field->options[1] === '1' ) { + $type = 'boolean'; + } else { + $type = 'text'; + } + + return array( 'type' => $type ); + default: + return array( 'type' => 'string' ); + } + } + + /** + * Serializes the current form's submission data. + * + * @param object $submission Formidable form submission. + * @param array $form_data Form data. + * + * @return array + */ + public function serialize_submission( $submission, $form_data ) { + $data = array(); + + $by_field_id = array(); + foreach ( $submission['values'] as $submission_value ) { + $by_field_id[ $submission_value->field_id ] = $submission_value; + } + + foreach ( $form_data['fields'] as $field ) { + if ( $field['is_file'] ) { + continue; + } + + $field_name = trim( $field['name'] ); + $field_id = absint( $field['id'] ); + $value = $by_field_id[ $field_id ] ?? null; + + if ( null !== $value ) { + if ( 'form' === $field['basetype'] ) { + $value = maybe_unserialize( $value->meta_value ); + $entry_id = reset( $value ); + $entry = FrmEntry::getOne( $entry_id ); + $embedded_form = $this->get_form_by_id( $entry->form_id ); + $embedded_submission = array( + 'id' => $entry_id, + 'values' => FrmEntryMeta::get_entry_meta_info( $entry_id ), + ); + $embedded_data = $this->serialize_submission( $embedded_submission, $embedded_form ); + + $data[ $field['name'] ] = $embedded_data; + } elseif ( 'repeater' === $field['basetype'] ) { + $entries_ids = maybe_unserialize( $value->meta_value ); + $repeater_form = $this->get_form_by_id( $field['form_id'] ); + + $repeater_data = array(); + foreach ( $entries_ids as $entry_id ) { + $repeater_submission = array( + 'id' => $entry_id, + 'values' => FrmEntryMeta::get_entry_meta_info( $entry_id ), + ); + + $repeater_data[] = $this->serialize_submission( $repeater_submission, $repeater_form ); + } + + $data[ $field['name'] ] = $repeater_data; + } else { + $value = $this->format_value( $value->meta_value, $field ); + + if ( null !== $value ) { + $data[ $field_name ] = $value; + } + } + } + } + + return $data; + } + + /** + * Formats field values with noop fallback. + * + * @param mixed $value Field's value. + * @param array $field Serialized field data. + * + * @return mixed Formatted value. + */ + private function format_value( $value, $field ) { + try { + switch ( $field['basetype'] ) { + case 'star': + case 'scale': + case 'quantity': + return (int) $value; + case 'range': + if ( false !== strpos( $value, ',' ) ) { + return array_map( 'intval', explode( ',', $value ) ); + } + + return (int) $value; + case 'total': + case 'number': + return (float) $value; + case 'checkbox': + $value = maybe_unserialize( $value ); + + if ( is_array( $value ) ) { + return array_filter( $value ); + } else { + return array( $value ); + } + case 'select': + if ( is_array( $value ) ) { + return array_filter( $value ); + } else { + return $value; + } + case 'credit_card': + case 'address': + case 'name': + return maybe_unserialize( $value ); + case 'user_id': + $value = maybe_unserialize( $value ); + if ( is_array( $value ) ) { + return intval( $value['unique_id'] ?? null ); + } + + return (int) $value; + case 'hidden': + if ( is_numeric( $value ) ) { + return (float) $value; + } + + return $value; + case 'toggle': + return $field['type'] === 'checkbox' ? boolval( $value ) : $value; + default: + return (string) $value; + } + } catch ( TypeError ) { + return $value; + } + } + + /** + * Gets the current submission's uploaded files. + * + * @param object $submission Formidable submission data. + * @param array $form_data Form data. + * + * @return array Uploaded files data. + */ + protected function submission_uploads( $submission, $form_data ) { + global $frm_vars; + + $uploads = array(); + + $media_id = $frm_vars['media_id'] ?? array(); + foreach ( $media_id as $field_id => $attachment_ids ) { + $paths = array(); + + if ( ! is_array( $attachment_ids ) ) { + $attachment_ids = array( $attachment_ids ); + } + + foreach ( $form_data['fields'] as $field ) { + if ( intval( $field['id'] ) === intval( $field_id ) ) { + foreach ( $attachment_ids as $attachment_id ) { + $attachment_path = get_attached_file( $attachment_id ); + + if ( $attachment_path && is_file( $attachment_path ) ) { + if ( ! is_readable( $attachment_path ) ) { + global $wp_filesystem; + + $tmpdir = get_temp_dir(); + + clearstatcache(); + $attachment_chmod = fileperms( $attachment_path ) & 0777; + + $wp_filesystem->chmod( $attachment_path, 0400 ); + + $attachment_filename = basename( $attachment_path ); + $attachment_contents = $wp_filesystem->get_contents( $attachment_path ); + + $wp_filesystem->chmod( $attachment_path, $attachment_chmod ); + + $attachment_path = $tmpdir . $attachment_filename; + $wp_filesystem->put_contents( $attachment_path, $attachment_contents ); + } + + $paths[] = $attachment_path; + + if ( ! is_readable( $attachment_path ) ) { + global $wp_filesystem; + $wp_filesystem->chown( $attachment_path, 0400 ); + $protected_paths[] = $attachment_path; + } + } + } + + break; + } + } + + if ( ! empty( $paths ) ) { + $uploads[ trim( $field['name'] ) ] = array( + 'path' => $field['is_multi'] ? $paths : $paths[0], + 'is_multi' => $field['is_multi'], + ); + } + } + + return $uploads; + } + + /** + * Decorate bridge's template form fields data to be created as Formidable fields. + * + * @param array $fields Array with bridge's template form fields data. + * + * @return array Decorated array of fields. + */ + private function prepare_fields( $fields ) { + $formidable_fields = array(); + + $count = count( $fields ); + for ( $i = 0; $i < $count; $i++ ) { + $field = $fields[ $i ]; + $args = array( + 'name' => $field['label'], + 'required' => $field['required'] ?? false, + 'default_value' => $field['value'] ?? '', + ); + + switch ( $field['type'] ) { + case 'hidden': + $formidable_fields[] = $this->hidden_field( $args ); + break; + case 'number': + $formidable_fields[] = $this->number_field( $args ); + break; + case 'email': + $formidable_fields[] = $this->email_field( $args ); + break; + case 'tel': + $formidable_fields[] = $this->tel_field( $args ); + break; + case 'select': + $args['options'] = $field['options'] ?? array(); + $args['is_multi'] = $field['is_multi'] ?? false; + $formidable_fields[] = $this->select_field( $args ); + break; + case 'checkbox': + $formidable_fields[] = $this->checkbox_field( $args ); + break; + case 'textarea': + $formidable_fields[] = $this->textarea_field( $args ); + break; + case 'url': + $formidable_fields[] = $this->url_field( $args ); + break; + case 'date': + $formidable_fields[] = $this->date_field( $args ); + break; + case 'file': + $args['is_multi'] = $field['is_multi'] ?? false; + $args['filetypes'] = $field['filetypes'] ?? ''; + $formidable_fields[] = $this->file_field( $args ); + break; + case 'text': + default: + $formidable_fields[] = $this->text_field( $args ); + } + } + + return $formidable_fields; + } + + /** + * Returns a default field array data. Used as template for the field creation methods. + * + * @param string $type Field type. + * @param array $args Field arguments. + * @param array $options Field options. + * + * @return array + */ + private function field_template( $type, $args, $options = array() ) { + return array_merge( + $args, + array( + 'type' => $type, + 'field_key' => sanitize_title( $args['name'] ), + 'name' => $args['name'], + 'required' => $args['required'] ?? false, + 'description' => '', + 'options' => $args['options'] ?? '', + 'field_options' => $options, + 'multiple' => $args['is_multi'] ?? false, + ), + ); + } + + /** + * Returns a valid email field data. + * + * @param array $args Field arguments. + * + * @return array + */ + private function email_field( $args ) { + return $this->field_template( 'email', $args, array( 'autocomplete' => 'email' ) ); + } + + /** + * Returns a valid tel field data. + * + * @param array $args Field arguments. + * + * @return array + */ + private function tel_field( $args ) { + return $this->field_template( 'phone', $args, array( 'autocomplete' => 'tel' ) ); + } + + /** + * Returns a valid textarea field data. + * + * @param array $args Field arguments. + * + * @return array + */ + private function textarea_field( $args ) { + return $this->field_template( 'textarea', $args ); + } + + /** + * Returns a valid multi select field data, as a select field if is single, as + * a checkbox field if is multiple. + * + * @param array $args Field arguments. + * + * @return array + */ + private function select_field( $args ) { + foreach ( $args['options'] as &$option ) { + $option['image'] = 0; + } + + if ( $args['is_multi'] ) { + return $this->field_template( 'checkbox', $args ); + } else { + return $this->field_template( 'select', $args ); + } + } + + /** + * Returns a valid file-upload field data. + * + * @param array $args Field arguments. + * + * @return array + */ + private function file_field( $args ) { + return $this->field_template( + 'file', + $args, + array( + 'multiple' => $args['is_multi'] ?? false, + 'ftypes' => $args['filetypes'] ?? array(), + ), + ); + } + + /** + * Returns a valid hidden field data. + * + * @param array $args Field arguments. + * + * @return array + */ + private function hidden_field( $args ) { + return $this->field_template( 'hidden', $args ); + } + + /** + * Returns a valid hidden field data. + * + * @param array $args Field arguments. + * + * @return array + */ + private function url_field( $args ) { + return $this->field_template( 'url', $args ); + } + + /** + * Returns a valid hidden field data. + * + * @param array $args Field arguments. + * + * @return array + */ + private function text_field( $args ) { + return $this->field_template( 'text', $args ); + } + + /** + * Returns a valid date field data. + * + * @param array $args Field arguments. + * + * @return array + */ + private function date_field( $args ) { + return $this->field_template( 'date', $args ); + } + + /** + * Returns a valid hidden field data. + * + * @param array $args Field arguments. + * + * @return array + */ + private function number_field( $args ) { + return $this->field_template( 'number', $args ); + } + + /** + * Returns a valid checkbox field data. + * + * @param array $args Field arguments. + * + * @return array + */ + private function checkbox_field( $args ) { + $args['options'] = array( '', '1' ); + return $this->field_template( 'toggle', $args ); + } +} + +Formidable_Integration::setup(); diff --git a/forms-bridge/integrations/gf/class-gf-integration.php b/forms-bridge/integrations/gf/class-gf-integration.php index e79d526f..df6de98f 100644 --- a/forms-bridge/integrations/gf/class-gf-integration.php +++ b/forms-bridge/integrations/gf/class-gf-integration.php @@ -484,7 +484,7 @@ private function is_multi_field( $field ) { * * @param GF_Field $field Field instance. * - * @return array JSON schema of the value of the field. + * @return array|null JSON schema of the value of the field. */ private function field_value_schema( $field ) { switch ( $field->type ) { @@ -567,7 +567,7 @@ static function ( $choices, $choice ) { case 'quantity': return array( 'type' => 'number' ); case 'fileupload': - return; + return null; case 'consent': return array( 'type' => 'boolean' ); default: diff --git a/forms-bridge/integrations/wpforms/class-wpforms-integration.php b/forms-bridge/integrations/wpforms/class-wpforms-integration.php index 421c35f3..f82d078f 100644 --- a/forms-bridge/integrations/wpforms/class-wpforms-integration.php +++ b/forms-bridge/integrations/wpforms/class-wpforms-integration.php @@ -22,9 +22,18 @@ * WPForms integration. */ class WPForms_Integration extends BaseIntegration { - + /** + * Handles integration name. + * + * @var string + */ const NAME = 'wpforms'; + /** + * Handles integration title. + * + * @var string + */ const TITLE = 'WP Forms'; /** @@ -278,26 +287,13 @@ public function serialize_form( $form ) { * @param array[] $fields List of serialized fields. * @param array[] $all_fields Complete list of form data fields. * - * @return array + * @return array|null */ private function serialize_field( $field, $fields = array(), $all_fields = array() ) { - if ( - in_array( - $field['type'], - array( - 'submit', - 'pagebreak', - 'layout', - 'captcha', - 'content', - 'entry-preview', - 'html', - 'divider', - ), - true - ) - ) { - return; + $skip_fields = array( 'submit', 'pagebreak', 'layout', 'captcha', 'content', 'entry-preview', 'html', 'divider' ); + + if ( in_array( $field['type'], $skip_fields, true ) ) { + return null; } $repeaters = array(); diff --git a/tests/addons/test-gsheets.php b/tests/addons/test-gsheets.php index 40a19a69..8ac9b280 100644 --- a/tests/addons/test-gsheets.php +++ b/tests/addons/test-gsheets.php @@ -395,13 +395,6 @@ public function test_append_to_new_sheet() { * Test appending data with headers auto-creation. */ public function test_append_creates_headers() { - // Mock empty headers response. - self::$mock_response = array( - 'range' => 'EmptySheet!A1:Z1', - 'majorDimension' => 'ROWS', - 'values' => array(), - ); - $bridge = new GSheets_Form_Bridge( array( 'name' => 'test-headers-bridge', diff --git a/tests/bootstrap.php b/tests/bootstrap.php index e331e707..936fdc06 100755 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -46,11 +46,13 @@ function ( $trigger, $function_name ) { /* Integrations */ require ABSPATH . 'wp-content/mu-plugins/contact-form-7/wp-contact-form-7.php'; + require ABSPATH . 'wp-content/mu-plugins/formidable/formidable.php'; require ABSPATH . 'wp-content/mu-plugins/gravityforms/gravityforms.php'; require ABSPATH . 'wp-content/mu-plugins/ninja-forms/ninja-forms.php'; - require ABSPATH . 'wp-content/mu-plugins/wpforms/wpforms.php'; + require ABSPATH . 'wp-content/mu-plugins/wpforms-lite/wpforms.php'; require ABSPATH . 'wp-content/mu-plugins/woocommerce/woocommerce.php'; + // WooCommerce interceptors add_filter( 'woocommerce_load_webhooks_limit', function () { @@ -59,6 +61,33 @@ function () { 90, ); + // Formidable mixtures + add_action( + 'muplugins_loaded', + function () { + global $wp_rewrite; + $wp_rewrite = (object) array( + 'feeds' => array(), + ); + + function wp_get_current_user() { + return new WP_User( 1, 'testuser' ); + } + + function get_user_by() { + return new WP_User( 1, 'testuser' ); + } + + function is_user_logged_in() { + return true; + } + + $frmdb = new FrmMigrate(); + $frmdb->upgrade(); + }, + 20, + ); + /* Plugin tests */ require dirname( __DIR__ ) . '/forms-bridge/deps/plugin/tests/bootstrap.php'; } diff --git a/tests/data/formidable/appointments-form.php.txt b/tests/data/formidable/appointments-form.php.txt new file mode 100644 index 00000000..225ae59f --- /dev/null +++ b/tests/data/formidable/appointments-form.php.txt @@ -0,0 +1,52 @@ +O:8:"stdClass":13:{s:2:"id";s:2:"25";s:8:"form_key";s:12:"appointments";s:4:"name";s:12:"Appointments";s:11:"description";s:0:"";s:14:"parent_form_id";s:1:"0";s:9:"logged_in";s:1:"0";s:8:"editable";s:1:"0";s:11:"is_template";s:1:"0";s:16:"default_template";s:1:"0";s:6:"status";s:9:"published";s:7:"options";a:18:{s:12:"submit_value";s:6:"Submit";s:14:"success_action";s:7:"message";s:11:"success_msg";s:54:"Your responses were successfully submitted. Thank you!";s:9:"show_form";i:0;s:7:"akismet";s:0:"";s:13:"stopforumspam";i:0;s:8:"antispam";i:0;s:7:"no_save";i:0;s:9:"ajax_load";i:0;s:11:"js_validate";i:0;s:10:"form_class";s:0:"";s:12:"custom_style";i:1;s:11:"before_html";s:224:"[form_name] +[if form_name]

[form_name]

[/if form_name] +[if form_description]
[form_description]
[/if form_description]";s:10:"after_html";s:0:"";s:11:"submit_html";s:511:"
+ +[if back_button][/if back_button] +[if save_draft][/if save_draft] +[if start_over][start_over_label][/if start_over] +
";s:10:"show_title";i:0;s:16:"show_description";i:0;s:11:"ajax_submit";i:0;}s:10:"created_at";s:19:"2026-01-13 10:02:32";s:6:"fields";a:7:{i:0;O:8:"stdClass":13:{s:2:"id";s:3:"248";s:9:"field_key";s:11:"first-name2";s:4:"name";s:10:"First name";s:11:"description";s:0:"";s:4:"type";s:4:"text";s:13:"default_value";s:0:"";s:7:"options";s:0:"";s:11:"field_order";s:1:"1";s:8:"required";s:1:"1";s:13:"field_options";a:17:{s:4:"size";s:0:"";s:3:"max";s:0:"";s:5:"label";s:0:"";s:5:"blank";s:29:"[field_name] cannot be blank.";s:18:"required_indicator";s:1:"*";s:7:"invalid";s:0:"";s:10:"unique_msg";s:0:"";s:14:"separate_value";i:0;s:14:"clear_on_focus";i:0;s:7:"classes";s:0:"";s:11:"custom_html";s:493:"
+ + [input] + [if description]
[description]
[/if description] + [if error][/if error] +
";s:6:"minnum";i:1;s:6:"maxnum";i:10;s:4:"step";i:1;s:6:"format";s:0:"";s:11:"placeholder";s:0:"";s:5:"draft";i:0;}s:7:"form_id";s:2:"25";s:10:"created_at";s:19:"2026-01-13 10:02:32";s:9:"form_name";s:12:"Appointments";}i:1;O:8:"stdClass":13:{s:2:"id";s:3:"249";s:9:"field_key";s:10:"last-name2";s:4:"name";s:9:"Last name";s:11:"description";s:0:"";s:4:"type";s:4:"text";s:13:"default_value";s:0:"";s:7:"options";s:0:"";s:11:"field_order";s:1:"2";s:8:"required";s:1:"1";s:13:"field_options";a:17:{s:4:"size";s:0:"";s:3:"max";s:0:"";s:5:"label";s:0:"";s:5:"blank";s:29:"[field_name] cannot be blank.";s:18:"required_indicator";s:1:"*";s:7:"invalid";s:0:"";s:10:"unique_msg";s:0:"";s:14:"separate_value";i:0;s:14:"clear_on_focus";i:0;s:7:"classes";s:0:"";s:11:"custom_html";s:493:"
+ + [input] + [if description]
[description]
[/if description] + [if error][/if error] +
";s:6:"minnum";i:1;s:6:"maxnum";i:10;s:4:"step";i:1;s:6:"format";s:0:"";s:11:"placeholder";s:0:"";s:5:"draft";i:0;}s:7:"form_id";s:2:"25";s:10:"created_at";s:19:"2026-01-13 10:02:32";s:9:"form_name";s:12:"Appointments";}i:2;O:8:"stdClass":13:{s:2:"id";s:3:"250";s:9:"field_key";s:6:"email2";s:4:"name";s:5:"Email";s:11:"description";s:0:"";s:4:"type";s:5:"email";s:13:"default_value";s:0:"";s:7:"options";s:0:"";s:11:"field_order";s:1:"3";s:8:"required";s:1:"1";s:13:"field_options";a:18:{s:4:"size";s:0:"";s:3:"max";s:0:"";s:5:"label";s:0:"";s:5:"blank";s:29:"[field_name] cannot be blank.";s:18:"required_indicator";s:1:"*";s:7:"invalid";s:0:"";s:10:"unique_msg";s:0:"";s:14:"separate_value";i:0;s:14:"clear_on_focus";i:0;s:7:"classes";s:0:"";s:11:"custom_html";s:493:"
+ + [input] + [if description]
[description]
[/if description] + [if error][/if error] +
";s:6:"minnum";i:1;s:6:"maxnum";i:10;s:4:"step";i:1;s:6:"format";s:0:"";s:11:"placeholder";s:0:"";s:5:"draft";i:0;s:12:"autocomplete";s:5:"email";}s:7:"form_id";s:2:"25";s:10:"created_at";s:19:"2026-01-13 10:02:32";s:9:"form_name";s:12:"Appointments";}i:3;O:8:"stdClass":13:{s:2:"id";s:3:"251";s:9:"field_key";s:4:"date";s:4:"name";s:4:"Date";s:11:"description";s:0:"";s:4:"type";s:4:"date";s:13:"default_value";s:0:"";s:7:"options";s:0:"";s:11:"field_order";s:1:"4";s:8:"required";s:1:"1";s:13:"field_options";a:17:{s:4:"size";s:0:"";s:3:"max";s:0:"";s:5:"label";s:0:"";s:5:"blank";s:29:"[field_name] cannot be blank.";s:18:"required_indicator";s:1:"*";s:7:"invalid";s:0:"";s:10:"unique_msg";s:0:"";s:14:"separate_value";i:0;s:14:"clear_on_focus";i:0;s:7:"classes";s:0:"";s:11:"custom_html";s:493:"
+ + [input] + [if description]
[description]
[/if description] + [if error][/if error] +
";s:6:"minnum";i:1;s:6:"maxnum";i:10;s:4:"step";i:1;s:6:"format";s:0:"";s:11:"placeholder";s:0:"";s:5:"draft";i:0;}s:7:"form_id";s:2:"25";s:10:"created_at";s:19:"2026-01-13 10:02:32";s:9:"form_name";s:12:"Appointments";}i:4;O:8:"stdClass":13:{s:2:"id";s:3:"252";s:9:"field_key";s:4:"hour";s:4:"name";s:4:"Hour";s:11:"description";s:0:"";s:4:"type";s:6:"select";s:13:"default_value";s:0:"";s:7:"options";a:24:{i:0;a:3:{s:5:"label";s:4:"1 AM";s:5:"value";i:1;s:5:"image";i:0;}i:1;a:3:{s:5:"label";s:4:"2 AM";s:5:"value";i:2;s:5:"image";i:0;}i:2;a:3:{s:5:"label";s:4:"3 AM";s:5:"value";i:3;s:5:"image";i:0;}i:3;a:3:{s:5:"label";s:4:"4 AM";s:5:"value";i:4;s:5:"image";i:0;}i:4;a:3:{s:5:"label";s:4:"5 AM";s:5:"value";i:5;s:5:"image";i:0;}i:5;a:3:{s:5:"label";s:4:"6 AM";s:5:"value";i:6;s:5:"image";i:0;}i:6;a:3:{s:5:"label";s:4:"7 AM";s:5:"value";i:7;s:5:"image";i:0;}i:7;a:3:{s:5:"label";s:4:"8 AM";s:5:"value";i:8;s:5:"image";i:0;}i:8;a:3:{s:5:"label";s:4:"9 AM";s:5:"value";i:9;s:5:"image";i:0;}i:9;a:3:{s:5:"label";s:5:"10 AM";s:5:"value";i:10;s:5:"image";i:0;}i:10;a:3:{s:5:"label";s:5:"11 AM";s:5:"value";i:11;s:5:"image";i:0;}i:11;a:3:{s:5:"label";s:5:"12 AM";s:5:"value";i:12;s:5:"image";i:0;}i:12;a:3:{s:5:"label";s:4:"1 PM";s:5:"value";i:13;s:5:"image";i:0;}i:13;a:3:{s:5:"label";s:4:"2 PM";s:5:"value";i:14;s:5:"image";i:0;}i:14;a:3:{s:5:"label";s:4:"3 PM";s:5:"value";i:15;s:5:"image";i:0;}i:15;a:3:{s:5:"label";s:4:"4 PM";s:5:"value";i:16;s:5:"image";i:0;}i:16;a:3:{s:5:"label";s:4:"5 PM";s:5:"value";i:17;s:5:"image";i:0;}i:17;a:3:{s:5:"label";s:4:"6 PM";s:5:"value";i:18;s:5:"image";i:0;}i:18;a:3:{s:5:"label";s:4:"7 PM";s:5:"value";i:19;s:5:"image";i:0;}i:19;a:3:{s:5:"label";s:4:"8 PM";s:5:"value";i:20;s:5:"image";i:0;}i:20;a:3:{s:5:"label";s:4:"9 PM";s:5:"value";i:21;s:5:"image";i:0;}i:21;a:3:{s:5:"label";s:5:"10 PM";s:5:"value";i:22;s:5:"image";i:0;}i:22;a:3:{s:5:"label";s:5:"11 PM";s:5:"value";i:23;s:5:"image";i:0;}i:23;a:3:{s:5:"label";s:5:"12 PM";s:5:"value";i:24;s:5:"image";i:0;}}s:11:"field_order";s:1:"5";s:8:"required";s:1:"1";s:13:"field_options";a:17:{s:4:"size";s:0:"";s:3:"max";s:0:"";s:5:"label";s:0:"";s:5:"blank";s:29:"[field_name] cannot be blank.";s:18:"required_indicator";s:1:"*";s:7:"invalid";s:0:"";s:10:"unique_msg";s:0:"";s:14:"separate_value";i:0;s:14:"clear_on_focus";i:0;s:7:"classes";s:0:"";s:11:"custom_html";s:493:"
+ + [input] + [if description]
[description]
[/if description] + [if error][/if error] +
";s:6:"minnum";i:1;s:6:"maxnum";i:10;s:4:"step";i:1;s:6:"format";s:0:"";s:11:"placeholder";s:0:"";s:5:"draft";i:0;}s:7:"form_id";s:2:"25";s:10:"created_at";s:19:"2026-01-13 10:02:32";s:9:"form_name";s:12:"Appointments";}i:5;O:8:"stdClass":13:{s:2:"id";s:3:"253";s:9:"field_key";s:6:"minute";s:4:"name";s:6:"Minute";s:11:"description";s:0:"";s:4:"type";s:6:"select";s:13:"default_value";s:0:"";s:7:"options";a:12:{i:0;a:3:{s:5:"label";s:2:"00";s:5:"value";i:0;s:5:"image";i:0;}i:1;a:3:{s:5:"label";s:2:"05";s:5:"value";i:5;s:5:"image";i:0;}i:2;a:3:{s:5:"label";s:2:"10";s:5:"value";i:10;s:5:"image";i:0;}i:3;a:3:{s:5:"label";s:2:"15";s:5:"value";i:15;s:5:"image";i:0;}i:4;a:3:{s:5:"label";s:2:"20";s:5:"value";i:20;s:5:"image";i:0;}i:5;a:3:{s:5:"label";s:2:"25";s:5:"value";i:25;s:5:"image";i:0;}i:6;a:3:{s:5:"label";s:2:"30";s:5:"value";i:30;s:5:"image";i:0;}i:7;a:3:{s:5:"label";s:2:"35";s:5:"value";i:35;s:5:"image";i:0;}i:8;a:3:{s:5:"label";s:2:"40";s:5:"value";i:40;s:5:"image";i:0;}i:9;a:3:{s:5:"label";s:2:"45";s:5:"value";i:45;s:5:"image";i:0;}i:10;a:3:{s:5:"label";s:2:"50";s:5:"value";i:50;s:5:"image";i:0;}i:11;a:3:{s:5:"label";s:2:"55";s:5:"value";i:55;s:5:"image";i:0;}}s:11:"field_order";s:1:"6";s:8:"required";s:1:"1";s:13:"field_options";a:17:{s:4:"size";s:0:"";s:3:"max";s:0:"";s:5:"label";s:0:"";s:5:"blank";s:29:"[field_name] cannot be blank.";s:18:"required_indicator";s:1:"*";s:7:"invalid";s:0:"";s:10:"unique_msg";s:0:"";s:14:"separate_value";i:0;s:14:"clear_on_focus";i:0;s:7:"classes";s:0:"";s:11:"custom_html";s:493:"
+ + [input] + [if description]
[description]
[/if description] + [if error][/if error] +
";s:6:"minnum";i:1;s:6:"maxnum";i:10;s:4:"step";i:1;s:6:"format";s:0:"";s:11:"placeholder";s:0:"";s:5:"draft";i:0;}s:7:"form_id";s:2:"25";s:10:"created_at";s:19:"2026-01-13 10:02:32";s:9:"form_name";s:12:"Appointments";}i:6;O:8:"stdClass":13:{s:2:"id";s:3:"261";s:9:"field_key";s:5:"uhayr";s:4:"name";s:6:"Submit";s:11:"description";s:0:"";s:4:"type";s:6:"submit";s:13:"default_value";s:0:"";s:7:"options";s:0:"";s:11:"field_order";s:1:"7";s:8:"required";s:1:"0";s:13:"field_options";a:21:{s:9:"edit_text";s:0:"";s:5:"align";s:0:"";s:10:"start_over";s:0:"";s:16:"start_over_label";s:0:"";s:4:"size";s:0:"";s:3:"max";s:0:"";s:5:"label";s:0:"";s:5:"blank";s:29:"[field_name] cannot be blank.";s:18:"required_indicator";s:1:"*";s:7:"invalid";s:0:"";s:10:"unique_msg";s:0:"";s:14:"separate_value";i:0;s:14:"clear_on_focus";i:0;s:7:"classes";s:0:"";s:11:"custom_html";s:115:"
+ [input] +
";s:6:"minnum";i:1;s:6:"maxnum";i:10;s:4:"step";i:1;s:6:"format";s:0:"";s:11:"placeholder";s:0:"";s:5:"draft";i:0;}s:7:"form_id";s:2:"25";s:10:"created_at";s:19:"2026-01-13 11:10:33";s:9:"form_name";s:12:"Appointments";}}} \ No newline at end of file diff --git a/tests/data/formidable/appointments-submission.php.txt b/tests/data/formidable/appointments-submission.php.txt new file mode 100644 index 00000000..9691c718 --- /dev/null +++ b/tests/data/formidable/appointments-submission.php.txt @@ -0,0 +1 @@ +a:2:{s:2:"id";i:287;s:6:"values";a:7:{i:0;O:8:"stdClass":5:{s:2:"id";s:1:"6";s:10:"meta_value";s:6:"ELADIO";s:8:"field_id";s:1:"6";s:7:"item_id";s:3:"287";s:10:"created_at";s:19:"2026-01-13 11:25:23";}i:1;O:8:"stdClass":5:{s:2:"id";s:1:"7";s:10:"meta_value";s:6:"CHACON";s:8:"field_id";s:1:"7";s:7:"item_id";s:3:"287";s:10:"created_at";s:19:"2026-01-13 11:25:23";}i:2;O:8:"stdClass":5:{s:2:"id";s:1:"8";s:10:"meta_value";s:23:"wf96cchto@scientist.com";s:8:"field_id";s:1:"8";s:7:"item_id";s:3:"287";s:10:"created_at";s:19:"2026-01-13 11:25:23";}i:3;O:8:"stdClass":5:{s:2:"id";s:1:"9";s:10:"meta_value";s:10:"2026-01-27";s:8:"field_id";s:1:"9";s:7:"item_id";s:3:"287";s:10:"created_at";s:19:"2026-01-13 11:25:23";}i:4;O:8:"stdClass":5:{s:2:"id";s:2:"10";s:10:"meta_value";s:4:"9 AM";s:8:"field_id";s:2:"10";s:7:"item_id";s:3:"287";s:10:"created_at";s:19:"2026-01-13 11:25:23";}i:5;O:8:"stdClass":5:{s:2:"id";s:2:"11";s:10:"meta_value";s:2:"00";s:8:"field_id";s:2:"11";s:7:"item_id";s:3:"287";s:10:"created_at";s:19:"2026-01-13 11:25:23";}i:6;O:8:"stdClass":5:{s:2:"id";s:4:"1235";s:10:"meta_value";s:58:"a:1:{s:9:"unique_id";s:28:"c3765b97eb666469-19bb71a71c9";}";s:8:"field_id";s:1:"0";s:7:"item_id";s:3:"287";s:10:"created_at";s:19:"2026-01-13 11:25:23";}}} diff --git a/tests/data/formidable/checkout-submission.php.txt b/tests/data/formidable/checkout-submission.php.txt new file mode 100644 index 00000000..0f8faeb8 Binary files /dev/null and b/tests/data/formidable/checkout-submission.php.txt differ diff --git a/tests/data/formidable/contacts-form.php.txt b/tests/data/formidable/contacts-form.php.txt new file mode 100644 index 00000000..73455a54 --- /dev/null +++ b/tests/data/formidable/contacts-form.php.txt @@ -0,0 +1,36 @@ +O:8:"stdClass":13:{s:2:"id";s:2:"26";s:8:"form_key";s:8:"contacts";s:4:"name";s:8:"Contacts";s:11:"description";s:0:"";s:14:"parent_form_id";s:1:"0";s:9:"logged_in";s:1:"0";s:8:"editable";s:1:"0";s:11:"is_template";s:1:"0";s:16:"default_template";s:1:"0";s:6:"status";s:9:"published";s:7:"options";a:18:{s:12:"submit_value";s:6:"Submit";s:14:"success_action";s:7:"message";s:11:"success_msg";s:54:"Your responses were successfully submitted. Thank you!";s:9:"show_form";i:0;s:7:"akismet";s:0:"";s:13:"stopforumspam";i:0;s:8:"antispam";i:0;s:7:"no_save";i:0;s:9:"ajax_load";i:0;s:11:"js_validate";i:0;s:10:"form_class";s:0:"";s:12:"custom_style";i:1;s:11:"before_html";s:224:"[form_name] +[if form_name]

[form_name]

[/if form_name] +[if form_description]
[form_description]
[/if form_description]";s:10:"after_html";s:0:"";s:11:"submit_html";s:511:"
+ +[if back_button][/if back_button] +[if save_draft][/if save_draft] +[if start_over][start_over_label][/if start_over] +
";s:10:"show_title";i:0;s:16:"show_description";i:0;s:11:"ajax_submit";i:0;}s:10:"created_at";s:19:"2026-01-13 10:03:07";s:6:"fields";a:4:{i:0;O:8:"stdClass":13:{s:2:"id";s:3:"254";s:9:"field_key";s:10:"your-email";s:4:"name";s:10:"Your email";s:11:"description";s:0:"";s:4:"type";s:5:"email";s:13:"default_value";s:0:"";s:7:"options";s:0:"";s:11:"field_order";s:1:"1";s:8:"required";s:1:"1";s:13:"field_options";a:18:{s:4:"size";s:0:"";s:3:"max";s:0:"";s:5:"label";s:0:"";s:5:"blank";s:29:"[field_name] cannot be blank.";s:18:"required_indicator";s:1:"*";s:7:"invalid";s:0:"";s:10:"unique_msg";s:0:"";s:14:"separate_value";i:0;s:14:"clear_on_focus";i:0;s:7:"classes";s:0:"";s:11:"custom_html";s:493:"
+ + [input] + [if description]
[description]
[/if description] + [if error][/if error] +
";s:6:"minnum";i:1;s:6:"maxnum";i:10;s:4:"step";i:1;s:6:"format";s:0:"";s:11:"placeholder";s:0:"";s:5:"draft";i:0;s:12:"autocomplete";s:5:"email";}s:7:"form_id";s:2:"26";s:10:"created_at";s:19:"2026-01-13 10:03:07";s:9:"form_name";s:8:"Contacts";}i:1;O:8:"stdClass":13:{s:2:"id";s:3:"255";s:9:"field_key";s:15:"your-first-name";s:4:"name";s:15:"Your first name";s:11:"description";s:0:"";s:4:"type";s:4:"text";s:13:"default_value";s:0:"";s:7:"options";s:0:"";s:11:"field_order";s:1:"2";s:8:"required";s:1:"1";s:13:"field_options";a:17:{s:4:"size";s:0:"";s:3:"max";s:0:"";s:5:"label";s:0:"";s:5:"blank";s:29:"[field_name] cannot be blank.";s:18:"required_indicator";s:1:"*";s:7:"invalid";s:0:"";s:10:"unique_msg";s:0:"";s:14:"separate_value";i:0;s:14:"clear_on_focus";i:0;s:7:"classes";s:0:"";s:11:"custom_html";s:493:"
+ + [input] + [if description]
[description]
[/if description] + [if error][/if error] +
";s:6:"minnum";i:1;s:6:"maxnum";i:10;s:4:"step";i:1;s:6:"format";s:0:"";s:11:"placeholder";s:0:"";s:5:"draft";i:0;}s:7:"form_id";s:2:"26";s:10:"created_at";s:19:"2026-01-13 10:03:07";s:9:"form_name";s:8:"Contacts";}i:2;O:8:"stdClass":13:{s:2:"id";s:3:"256";s:9:"field_key";s:14:"your-last-name";s:4:"name";s:14:"Your last name";s:11:"description";s:0:"";s:4:"type";s:4:"text";s:13:"default_value";s:0:"";s:7:"options";s:0:"";s:11:"field_order";s:1:"3";s:8:"required";s:1:"1";s:13:"field_options";a:17:{s:4:"size";s:0:"";s:3:"max";s:0:"";s:5:"label";s:0:"";s:5:"blank";s:29:"[field_name] cannot be blank.";s:18:"required_indicator";s:1:"*";s:7:"invalid";s:0:"";s:10:"unique_msg";s:0:"";s:14:"separate_value";i:0;s:14:"clear_on_focus";i:0;s:7:"classes";s:0:"";s:11:"custom_html";s:493:"
+ + [input] + [if description]
[description]
[/if description] + [if error][/if error] +
";s:6:"minnum";i:1;s:6:"maxnum";i:10;s:4:"step";i:1;s:6:"format";s:0:"";s:11:"placeholder";s:0:"";s:5:"draft";i:0;}s:7:"form_id";s:2:"26";s:10:"created_at";s:19:"2026-01-13 10:03:07";s:9:"form_name";s:8:"Contacts";}i:3;O:8:"stdClass":13:{s:2:"id";s:3:"257";s:9:"field_key";s:10:"your-phone";s:4:"name";s:10:"Your phone";s:11:"description";s:0:"";s:4:"type";s:4:"text";s:13:"default_value";s:0:"";s:7:"options";s:0:"";s:11:"field_order";s:1:"4";s:8:"required";s:1:"0";s:13:"field_options";a:17:{s:4:"size";s:0:"";s:3:"max";s:0:"";s:5:"label";s:0:"";s:5:"blank";s:29:"[field_name] cannot be blank.";s:18:"required_indicator";s:1:"*";s:7:"invalid";s:0:"";s:10:"unique_msg";s:0:"";s:14:"separate_value";i:0;s:14:"clear_on_focus";i:0;s:7:"classes";s:0:"";s:11:"custom_html";s:493:"
+ + [input] + [if description]
[description]
[/if description] + [if error][/if error] +
";s:6:"minnum";i:1;s:6:"maxnum";i:10;s:4:"step";i:1;s:6:"format";s:0:"";s:11:"placeholder";s:0:"";s:5:"draft";i:0;}s:7:"form_id";s:2:"26";s:10:"created_at";s:19:"2026-01-13 10:03:07";s:9:"form_name";s:8:"Contacts";}}} \ No newline at end of file diff --git a/tests/data/formidable/contacts-submission.php.txt b/tests/data/formidable/contacts-submission.php.txt new file mode 100644 index 00000000..0d7a9e0b --- /dev/null +++ b/tests/data/formidable/contacts-submission.php.txt @@ -0,0 +1 @@ +a:2:{s:2:"id";i:289;s:6:"values";a:5:{i:0;O:8:"stdClass":5:{s:2:"id";s:2:"13";s:10:"meta_value";s:18:"hp3idrybf@yahoo.es";s:8:"field_id";s:2:"13";s:7:"item_id";s:3:"289";s:10:"created_at";s:19:"2026-01-13 11:29:53";}i:1;O:8:"stdClass":5:{s:2:"id";s:2:"14";s:10:"meta_value";s:5:"JULIO";s:8:"field_id";s:2:"14";s:7:"item_id";s:3:"289";s:10:"created_at";s:19:"2026-01-13 11:29:53";}i:2;O:8:"stdClass":5:{s:2:"id";s:2:"15";s:10:"meta_value";s:8:"CLEMENTE";s:8:"field_id";s:2:"15";s:7:"item_id";s:3:"289";s:10:"created_at";s:19:"2026-01-13 11:29:53";}i:3;O:8:"stdClass":5:{s:2:"id";s:2:"16";s:10:"meta_value";s:9:"739089195";s:8:"field_id";s:2:"16";s:7:"item_id";s:3:"289";s:10:"created_at";s:19:"2026-01-13 11:29:53";}i:4;O:8:"stdClass":5:{s:2:"id";s:4:"1245";s:10:"meta_value";s:58:"a:1:{s:9:"unique_id";s:28:"a689318e5a4b1d9c-19bb71e93d3";}";s:8:"field_id";s:1:"0";s:7:"item_id";s:3:"289";s:10:"created_at";s:19:"2026-01-13 11:29:53";}}} diff --git a/tests/data/formidable/leads-form.php.txt b/tests/data/formidable/leads-form.php.txt new file mode 100644 index 00000000..3d6882d2 --- /dev/null +++ b/tests/data/formidable/leads-form.php.txt @@ -0,0 +1,43 @@ +O:8:"stdClass":13:{s:2:"id";s:2:"24";s:8:"form_key";s:5:"leads";s:4:"name";s:5:"Leads";s:11:"description";s:0:"";s:14:"parent_form_id";s:1:"0";s:9:"logged_in";s:1:"0";s:8:"editable";s:1:"0";s:11:"is_template";s:1:"0";s:16:"default_template";s:1:"0";s:6:"status";s:9:"published";s:7:"options";a:18:{s:12:"submit_value";s:6:"Submit";s:14:"success_action";s:7:"message";s:11:"success_msg";s:54:"Your responses were successfully submitted. Thank you!";s:9:"show_form";i:0;s:7:"akismet";s:0:"";s:13:"stopforumspam";i:0;s:8:"antispam";i:0;s:7:"no_save";i:0;s:9:"ajax_load";i:0;s:11:"js_validate";i:0;s:10:"form_class";s:0:"";s:12:"custom_style";i:1;s:11:"before_html";s:224:"[form_name] +[if form_name]

[form_name]

[/if form_name] +[if form_description]
[form_description]
[/if form_description]";s:10:"after_html";s:0:"";s:11:"submit_html";s:511:"
+ +[if back_button][/if back_button] +[if save_draft][/if save_draft] +[if start_over][start_over_label][/if start_over] +
";s:10:"show_title";i:0;s:16:"show_description";i:0;s:11:"ajax_submit";i:0;}s:10:"created_at";s:19:"2026-01-13 08:43:16";s:6:"fields";a:5:{i:0;O:8:"stdClass":13:{s:2:"id";s:3:"243";s:9:"field_key";s:10:"first-name";s:4:"name";s:10:"First name";s:11:"description";s:0:"";s:4:"type";s:4:"text";s:13:"default_value";s:0:"";s:7:"options";s:0:"";s:11:"field_order";s:1:"1";s:8:"required";s:1:"1";s:13:"field_options";a:17:{s:4:"size";s:0:"";s:3:"max";s:0:"";s:5:"label";s:0:"";s:5:"blank";s:29:"[field_name] cannot be blank.";s:18:"required_indicator";s:1:"*";s:7:"invalid";s:0:"";s:10:"unique_msg";s:0:"";s:14:"separate_value";i:0;s:14:"clear_on_focus";i:0;s:7:"classes";s:0:"";s:11:"custom_html";s:493:"
+ + [input] + [if description]
[description]
[/if description] + [if error][/if error] +
";s:6:"minnum";i:1;s:6:"maxnum";i:10;s:4:"step";i:1;s:6:"format";s:0:"";s:11:"placeholder";s:0:"";s:5:"draft";i:0;}s:7:"form_id";s:2:"24";s:10:"created_at";s:19:"2026-01-13 08:43:16";s:9:"form_name";s:5:"Leads";}i:1;O:8:"stdClass":13:{s:2:"id";s:3:"244";s:9:"field_key";s:9:"last-name";s:4:"name";s:9:"Last name";s:11:"description";s:0:"";s:4:"type";s:4:"text";s:13:"default_value";s:0:"";s:7:"options";s:0:"";s:11:"field_order";s:1:"2";s:8:"required";s:1:"1";s:13:"field_options";a:17:{s:4:"size";s:0:"";s:3:"max";s:0:"";s:5:"label";s:0:"";s:5:"blank";s:29:"[field_name] cannot be blank.";s:18:"required_indicator";s:1:"*";s:7:"invalid";s:0:"";s:10:"unique_msg";s:0:"";s:14:"separate_value";i:0;s:14:"clear_on_focus";i:0;s:7:"classes";s:0:"";s:11:"custom_html";s:493:"
+ + [input] + [if description]
[description]
[/if description] + [if error][/if error] +
";s:6:"minnum";i:1;s:6:"maxnum";i:10;s:4:"step";i:1;s:6:"format";s:0:"";s:11:"placeholder";s:0:"";s:5:"draft";i:0;}s:7:"form_id";s:2:"24";s:10:"created_at";s:19:"2026-01-13 08:43:16";s:9:"form_name";s:5:"Leads";}i:2;O:8:"stdClass":13:{s:2:"id";s:3:"245";s:9:"field_key";s:5:"email";s:4:"name";s:5:"Email";s:11:"description";s:0:"";s:4:"type";s:5:"email";s:13:"default_value";s:0:"";s:7:"options";s:0:"";s:11:"field_order";s:1:"3";s:8:"required";s:1:"1";s:13:"field_options";a:18:{s:4:"size";s:0:"";s:3:"max";s:0:"";s:5:"label";s:0:"";s:5:"blank";s:29:"[field_name] cannot be blank.";s:18:"required_indicator";s:1:"*";s:7:"invalid";s:0:"";s:10:"unique_msg";s:0:"";s:14:"separate_value";i:0;s:14:"clear_on_focus";i:0;s:7:"classes";s:0:"";s:11:"custom_html";s:493:"
+ + [input] + [if description]
[description]
[/if description] + [if error][/if error] +
";s:6:"minnum";i:1;s:6:"maxnum";i:10;s:4:"step";i:1;s:6:"format";s:0:"";s:11:"placeholder";s:0:"";s:5:"draft";i:0;s:12:"autocomplete";s:5:"email";}s:7:"form_id";s:2:"24";s:10:"created_at";s:19:"2026-01-13 08:43:16";s:9:"form_name";s:5:"Leads";}i:3;O:8:"stdClass":13:{s:2:"id";s:3:"246";s:9:"field_key";s:5:"phone";s:4:"name";s:5:"Phone";s:11:"description";s:0:"";s:4:"type";s:4:"text";s:13:"default_value";s:0:"";s:7:"options";s:0:"";s:11:"field_order";s:1:"4";s:8:"required";s:1:"0";s:13:"field_options";a:17:{s:4:"size";s:0:"";s:3:"max";s:0:"";s:5:"label";s:0:"";s:5:"blank";s:29:"[field_name] cannot be blank.";s:18:"required_indicator";s:1:"*";s:7:"invalid";s:0:"";s:10:"unique_msg";s:0:"";s:14:"separate_value";i:0;s:14:"clear_on_focus";i:0;s:7:"classes";s:0:"";s:11:"custom_html";s:493:"
+ + [input] + [if description]
[description]
[/if description] + [if error][/if error] +
";s:6:"minnum";i:1;s:6:"maxnum";i:10;s:4:"step";i:1;s:6:"format";s:0:"";s:11:"placeholder";s:0:"";s:5:"draft";i:0;}s:7:"form_id";s:2:"24";s:10:"created_at";s:19:"2026-01-13 08:43:16";s:9:"form_name";s:5:"Leads";}i:4;O:8:"stdClass":13:{s:2:"id";s:3:"247";s:9:"field_key";s:8:"comments";s:4:"name";s:8:"Comments";s:11:"description";s:0:"";s:4:"type";s:8:"textarea";s:13:"default_value";s:0:"";s:7:"options";s:0:"";s:11:"field_order";s:1:"5";s:8:"required";s:1:"0";s:13:"field_options";a:17:{s:4:"size";s:0:"";s:3:"max";s:1:"5";s:5:"label";s:0:"";s:5:"blank";s:29:"[field_name] cannot be blank.";s:18:"required_indicator";s:1:"*";s:7:"invalid";s:0:"";s:10:"unique_msg";s:0:"";s:14:"separate_value";i:0;s:14:"clear_on_focus";i:0;s:7:"classes";s:0:"";s:11:"custom_html";s:493:"
+ + [input] + [if description]
[description]
[/if description] + [if error][/if error] +
";s:6:"minnum";i:1;s:6:"maxnum";i:10;s:4:"step";i:1;s:6:"format";s:0:"";s:11:"placeholder";s:0:"";s:5:"draft";i:0;}s:7:"form_id";s:2:"24";s:10:"created_at";s:19:"2026-01-13 08:43:16";s:9:"form_name";s:5:"Leads";}}} \ No newline at end of file diff --git a/tests/data/formidable/leads-submission.php.txt b/tests/data/formidable/leads-submission.php.txt new file mode 100644 index 00000000..c6a7ddbd --- /dev/null +++ b/tests/data/formidable/leads-submission.php.txt @@ -0,0 +1 @@ +a:2:{s:2:"id";i:291;s:6:"values";a:6:{i:0;O:8:"stdClass":5:{s:2:"id";s:2:"17";s:10:"meta_value";s:5:"JULIO";s:8:"field_id";s:2:"17";s:7:"item_id";s:3:"291";s:10:"created_at";s:19:"2026-01-13 11:31:42";}i:1;O:8:"stdClass":5:{s:2:"id";s:2:"18";s:10:"meta_value";s:8:"CLEMENTE";s:8:"field_id";s:2:"18";s:7:"item_id";s:3:"291";s:10:"created_at";s:19:"2026-01-13 11:31:42";}i:2;O:8:"stdClass":5:{s:2:"id";s:2:"19";s:10:"meta_value";s:18:"hp3idrybf@yahoo.es";s:8:"field_id";s:2:"19";s:7:"item_id";s:3:"291";s:10:"created_at";s:19:"2026-01-13 11:31:42";}i:3;O:8:"stdClass":5:{s:2:"id";s:2:"20";s:10:"meta_value";s:9:"739089195";s:8:"field_id";s:2:"20";s:7:"item_id";s:3:"291";s:10:"created_at";s:19:"2026-01-13 11:31:42";}i:4;O:8:"stdClass":5:{s:2:"id";s:2:"21";s:10:"meta_value";s:54:"CUESTA DE ESPAÑA, 72, 13884, CIUDAD REAL(CIUDAD REAL)";s:8:"field_id";s:2:"21";s:7:"item_id";s:3:"291";s:10:"created_at";s:19:"2026-01-13 11:31:42";}i:5;O:8:"stdClass":5:{s:2:"id";s:4:"1255";s:10:"meta_value";s:58:"a:1:{s:9:"unique_id";s:28:"c9edf13eb0ef60e0-19bb7203d10";}";s:8:"field_id";s:1:"0";s:7:"item_id";s:3:"291";s:10:"created_at";s:19:"2026-01-13 11:31:42";}}} diff --git a/tests/integrations/FORMIDABLE_TESTS.md b/tests/integrations/FORMIDABLE_TESTS.md new file mode 100644 index 00000000..0dc0f0cf --- /dev/null +++ b/tests/integrations/FORMIDABLE_TESTS.md @@ -0,0 +1,97 @@ +# Formidable Integration Tests + +## Overview +This document describes the test structure for the Formidable Forms integration with the Forms Bridge plugin. + +## Files Created + +### 1. Test File +**Location:** `tests/integrations/test-formidable.php` + +This file contains the main test class `FormidableTest` that extends `BaseIntegrationTest` and implements the required methods for testing the Formidable Forms integration. + +**Key Components:** +- `NAME` constant set to `'formidable'` +- Implementation of required methods: + - `get_forms()` - Retrieves published forms using `FrmForm::get_published_forms()` + - `add_form($config)` - Creates a new form with fields + - `delete_form($form)` - Deletes a form using `FrmForm::destroy()` + +**Test Methods:** +1. `test_job_position_form_serialization()` - Tests form field serialization +2. `test_job_position_form_submission_serialization()` - Tests submission data serialization +3. `test_contact_form_serialization()` - Tests complex form with various field types +4. `test_contact_form_submission_serialization()` - Tests complex submission data +5. `test_form_templates()` - Tests form creation from templates + +### 2. Test Data Files +**Location:** `tests/data/formidable/` + +#### Form Data Files: +- `job-position-form.php.txt` - Serialized Formidable form object for "Job position" form +- `contact-form.php.txt` - Serialized Formidable form object for "Contact Form" + +#### Submission Data Files: +- `job-position-submission.php.txt` - Serialized submission data for job position form +- `contact-submission.php.txt` - Serialized submission data for contact form + +## Test Structure + +The tests follow the same pattern as other integration tests in the codebase: + +1. **Form Serialization Tests**: Verify that form fields are correctly serialized with proper types, labels, and options +2. **Submission Serialization Tests**: Verify that submission data is correctly extracted and formatted +3. **Template Tests**: Verify that forms can be created from template definitions + +## Running the Tests + +To run the formidable integration tests: + +```bash +# First, set up the WordPress test environment +./bin/install-wp-tests.sh db_name db_user db_password db_host wp_version + +# Then run the tests +./vendor/bin/phpunit tests/integrations/test-formidable.php +``` + +## Test Data Format + +The test data files contain serialized PHP objects that represent: + +### Form Objects: +- Form ID, name, description, form_key, status +- Form metadata and configuration + +### Submission Objects: +- Submission ID, form_id, timestamps +- Meta data containing field values +- Each field value includes field_id, meta_value, and timestamps + +## Integration with Base Test Class + +The `FormidableTest` class extends `BaseIntegrationTest` which provides: +- Common test utilities and assertions +- Form and submission serialization methods +- Template loading and testing infrastructure +- Setup and teardown methods for test isolation + +## Key Assertions + +The tests use the `assertField()` helper method from the base class to verify: +- Field type and basetype +- Field schema (data type) +- Required status +- File upload status +- Multi-value status +- Conditional logic status +- Field options and labels + +## Future Enhancements + +Potential areas for additional testing: +- Repeater fields +- Embedded forms +- Conditional logic +- File upload handling +- Complex field types (address, credit card, etc.) diff --git a/tests/integrations/class-base-integration-test.php b/tests/integrations/class-base-integration-test.php index 0d507c9e..d4c494a7 100644 --- a/tests/integrations/class-base-integration-test.php +++ b/tests/integrations/class-base-integration-test.php @@ -66,6 +66,8 @@ protected static function get_form( $title ) { } } elseif ( is_object( $form ) && property_exists( $form, 'post_title' ) && $form->post_title === $title ) { return $form; + } elseif ( is_object( $form ) && property_exists( $form, 'name' ) && $form->name === $title ) { + return $form; } } diff --git a/tests/integrations/test-formidable.php b/tests/integrations/test-formidable.php new file mode 100644 index 00000000..e6829feb --- /dev/null +++ b/tests/integrations/test-formidable.php @@ -0,0 +1,213 @@ + $config->name, + 'description' => '', + 'status' => 'published', + 'form_key' => $config->form_key, + ); + + $form_id = FrmForm::create( $form_data ); + + if ( ! $form_id ) { + return null; + } + + // Add fields if they exist in config + if ( isset( $config->fields ) && is_array( $config->fields ) ) { + foreach ( $config->fields as $field ) { + $field_data = array( + 'type' => $field->type, + 'field_key' => $field->field_key, + 'name' => $field->name, + 'description' => $field->description, + 'required' => $field->required, + 'options' => $field->options, + 'field_options' => $field->field_options, + 'form_id' => $form_id, + 'default_value' => $field->default_value, + ); + + $field_data['field_options']['draft'] = 0; + + FrmField::create( $field_data ); + } + } + + return $form_id; + } + + protected static function delete_form( $form ) { + return (bool) FrmForm::destroy( $form->id ); + } + + public function test_appointments_form_serialization() { + $form = self::get_form( 'Appointments' ); + $form_data = $this->serialize_form( $form ); + + $fields = $form_data['fields']; + $this->assertEquals( 6, count( $fields ) ); + + $field = $fields[0]; + $this->assertField( $field, 'text' ); + + $field = $fields[2]; + $this->assertField( $field, 'email' ); + + $field = $fields[3]; + $this->assertField( + $field, + 'date', + array( 'format' => 'yyyy-mm-dd' ), + ); + + $field = $fields[4]; + $this->assertEquals( 24, count( $field['options'] ) ); + $this->assertField( $field, 'select' ); + } + + public function test_appointments_submission_serialization() { + $form = self::get_form( 'Appointments' ); + + $form_data = $this->serialize_form( $form ); + + $store = self::store(); + foreach ( $store as $name => $object ) { + if ( 'appointments-submission' === $name ) { + $submission = $object; + break; + } + } + + if ( ! isset( $submission ) ) { + throw new Exception( 'Appointments submission not found' ); + } + + $payload = $this->serialize_submission( $submission, $form_data ); + + $this->assertSame( 'ELADIO', $payload['First name'] ); + $this->assertSame( 'CHACON', $payload['Last name'] ); + $this->assertSame( 'wf96cchto@scientist.com', $payload['Email'] ); + $this->assertSame( '2026-01-27', $payload['Date'] ); + $this->assertSame( '9 AM', $payload['Hour'] ); + $this->assertSame( '00', $payload['Minute'] ); + } + + public function test_contacts_form_serialization() { + $form = self::get_form( 'Contacts' ); + + $form_data = $this->serialize_form( $form ); + + $fields = $form_data['fields']; + $this->assertEquals( 4, count( $fields ) ); + + $field = $fields[0]; + $this->assertField( $field, 'email' ); + + $field = $fields[2]; + $this->assertField( $field, 'text' ); + + $field = $fields[3]; + $this->assertField( $field, 'text', array( 'required' => false ) ); + } + + public function test_contacts_submission_serialization() { + $form = self::get_form( 'Contacts' ); + + $form_data = $this->serialize_form( $form ); + + $store = self::store(); + foreach ( $store as $name => $object ) { + if ( 'contacts-submission' === $name ) { + $submission = $object; + break; + } + } + + if ( ! isset( $submission ) ) { + throw new Exception( 'Contact submission not found' ); + } + + $payload = $this->serialize_submission( $submission, $form_data ); + + $this->assertSame( 'hp3idrybf@yahoo.es', $payload['Your email'] ); + $this->assertSame( 'JULIO', $payload['Your first name'] ); + $this->assertSame( 'CLEMENTE', $payload['Your last name'] ); + $this->assertSame( '739089195', $payload['Your phone'] ); + } + + public function test_leads_form_serialization() { + $form = self::get_form( 'Leads' ); + + $form_data = $this->serialize_form( $form ); + + $fields = $form_data['fields']; + $this->assertEquals( 5, count( $fields ) ); + + $field = $fields[3]; + $this->assertField( $field, 'text', array( 'required' => false ) ); + + $field = $fields[4]; + $this->assertField( $field, 'textarea', array( 'required' => false ) ); + } + + public function test_leads_submission_serialization() { + $form = self::get_form( 'Leads' ); + + $form_data = $this->serialize_form( $form ); + + $store = self::store(); + foreach ( $store as $name => $object ) { + if ( 'leads-submission' === $name ) { + $submission = $object; + break; + } + } + + if ( ! isset( $submission ) ) { + throw new Exception( 'Leads submission not found' ); + } + + $payload = $this->serialize_submission( $submission, $form_data ); + + $this->assertSame( 'JULIO', $payload['First name'] ); + $this->assertSame( 'CLEMENTE', $payload['Last name'] ); + $this->assertSame( 'hp3idrybf@yahoo.es', $payload['Email'] ); + $this->assertSame( '739089195', $payload['Phone'] ); + $this->assertSame( 'CUESTA DE ESPAÑA, 72, 13884, CIUDAD REAL(CIUDAD REAL)', $payload['Comments'] ); + } + + public function test_form_templates() { + $this->run_test_form_templates(); + } +}