Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ desktop.ini
node_modules/
.wp-env.json
.idea/
tests/Data/credentials/*
tests/Data/credentials/*
.claude/settings.local.json
2 changes: 1 addition & 1 deletion formscrm.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Plugin Name: FormsCRM
* Plugin URI : https://close.technology/wordpress-plugins/formscrm/
* Description: Connects Forms with CRM, ERP and Email Marketing.
* Version: 4.3.2.1-beta.1
* Version: 4.3.3-beta.1
* Author: CloseTechnology
* Author URI: https://close.technology
* Text Domain: formscrm
Expand Down
8 changes: 4 additions & 4 deletions includes/formscrm-library/class-contactform7.php
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ public function crm_process_entry( $contact_form ) {
if ( empty( $this->crmlib ) ) {
return;
}
$merge_vars = $this->get_merge_vars( $cf7_crm, $submission->get_posted_data() );
$merge_vars = self::get_merge_vars( $cf7_crm, $submission->get_posted_data() );
$response_result = $this->crmlib->create_entry( $cf7_crm, $merge_vars );

if ( 'error' === $response_result['status'] ) {
Expand All @@ -311,7 +311,7 @@ public function crm_process_entry( $contact_form ) {
* @param array $submitted_data Submitted data.
* @return array
*/
public function get_merge_vars( $cf7_crm, $submitted_data ) {
public static function get_merge_vars( $cf7_crm, $submitted_data ) {
if ( empty( $cf7_crm ) || ! is_array( $cf7_crm ) ) {
return array();
}
Expand All @@ -331,7 +331,7 @@ public function get_merge_vars( $cf7_crm, $submitted_data ) {
}

// Process dynamic values (shortcodes).
$value = $this->fill_dynamic_value( $value, $submitted_data );
$value = self::fill_dynamic_value( $value, $submitted_data );

$merge_vars[] = array(
'name' => $crm_key,
Expand Down Expand Up @@ -382,7 +382,7 @@ public function enqueue_autosubmit_assets( $hook ) {
* @param array $submitted_data All submitted form data.
* @return string Processed field value with shortcodes replaced.
*/
private function fill_dynamic_value( $field_value, $submitted_data ) {
private static function fill_dynamic_value( $field_value, $submitted_data ) {
if ( ! str_contains( $field_value, '{id:' ) ) {
return $field_value;
}
Expand Down
59 changes: 40 additions & 19 deletions includes/formscrm-library/class-elementor.php
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,29 @@ public function register_settings_section( $widget ) {
$widget->end_controls_section();
}

/**
* Get Merge Vars
*
* Builds the merge vars array from CRM field settings and submitted raw fields.
*
* @access public
* @param array $formscrm_settings Decoded hidden settings (field_crm => field_form_id).
* @param array $raw_fields Submitted form fields keyed by field id.
* @return array
*/
public static function get_merge_vars( $formscrm_settings, $raw_fields ) {
$merge_vars = array();
foreach ( $formscrm_settings as $field_crm => $field_form ) {
$field_crm = str_replace( 'fc_crm_field-', '', $field_crm );
$field_value = $raw_fields[ $field_form ]['value'] ?? '';
$merge_vars[] = array(
'name' => $field_crm,
'value' => is_array( $field_value ) ? implode( ', ', $field_value ) : $field_value,
);
}
return $merge_vars;
}

/**
* Run
*
Expand All @@ -248,35 +271,33 @@ public function register_settings_section( $widget ) {
*/
public function run( $record, $ajax_handler ) {
$settings = $record->get( 'form_settings' );
$crm_type = $settings['fc_crm_type'] ?? '';
$module = '';

if ( empty( $crm_type ) ) {
formscrm_alert_error( $crm_type, __( 'Invalid CRM type.', 'formscrm' ), array() );
return;
}

// Get submitted Form data.
$raw_fields = $record->get( 'fields' );

// Unpack hidden settings for the form.
$hidden_settings = array();
$formscrm_settings = array();
if ( isset( $settings['formscrm_settings_hidden'] ) ) {
$hidden_settings = json_decode( $settings['formscrm_settings_hidden'], true );
$settings = array_merge( $settings, $hidden_settings );

if ( isset( $settings['fc_crm_type'] ) && ! empty( $hidden_settings[ $settings['fc_crm_type'] ] ) ) {
$settings['fc_crm_module'] = $hidden_settings[ $settings['fc_crm_type'] ] ?? '';
}
$formscrm_settings = json_decode( $settings['formscrm_settings_hidden'], true );
$module = $formscrm_settings[ $crm_type ] ?? '';
unset( $formscrm_settings[ $crm_type ] );
}

// Normalize the Form Data.
$merge_vars = array();
foreach ( $raw_fields as $id => $field ) {
$key = array_search( $id, $hidden_settings, true );
if ( false === $key ) {
continue;
}
$field_id = str_replace( 'fc_crm_field-', '', $key );
$merge_vars[] = array(
'name' => $field_id,
'value' => $field['value'] ?? '',
);
if ( empty( $module ) ) {
formscrm_alert_error( $module, __( 'Module not found.', 'formscrm' ), array() );
return;
}

// Normalize the Form data.
$merge_vars = self::get_merge_vars( $formscrm_settings, $raw_fields );

// phpcs:disable WordPress.Security.NonceVerification.Missing -- Nonce verification handled by Elementor forms.
if ( ! empty( $_POST['visitor_key'] ) ) {
$merge_vars['visitor_key'] = array(
Expand Down
1 change: 1 addition & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<testsuite name="testing">
<directory suffix=".php">./tests/API/</directory>
<directory suffix=".php">./tests/Unit/</directory>
<directory suffix=".php">./tests/Forms/</directory>
</testsuite>
</testsuites>
</phpunit>
8 changes: 5 additions & 3 deletions readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ Tags: gravityforms, wpforms, crm, vtiger, odoo
Donate link: https://close.marketing/go/donate/
Requires at least: 5.5
Tested up to: 7.0
Stable tag: 4.3.2.1
Version: 4.3.2.1
Stable tag: 4.3.3
Version: 4.3.3
License: GPL2
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Expand Down Expand Up @@ -244,7 +244,9 @@ WordPress installation and then activate the Plugin from Plugins page.

== Changelog ==

= 4.3.2.1 =
= 4.3.3 =
* Fixed: Elementor Forms field mapping only processed the first occurrence when multiple CRM fields mapped to the same form field; now all mappings are applied correctly.
* Tests: Added unit tests for Elementor and Contact Form 7 merge vars field mapping.
* Hotfix: revert Clientify API v2 migration due to critical issues with field mapping configuration.
* Fixed: CF7 GDPR checkbox value sent as field name instead of boolean when unchecked, causing gdpr_accept to always be true in Clientify.
* Fixed: Error logs page not working properly.
Expand Down
10 changes: 3 additions & 7 deletions tests/Forms/test-contactform.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,6 @@
*/

class ContactFormsTest extends WP_UnitTestCase {
public function setUp() {
parent::setUp();
$this->contact_form = WPCF7_ContactForm::create();
}

public function test_get_merge_vars() {
$cf7_crm = array(
Expand All @@ -37,7 +33,7 @@ public function test_get_merge_vars() {
'clientify_cookie' => '',
);

$merge_vars = $this->contact_form->get_merge_vars( $cf7_crm, $submitted_data );
$merge_vars = FORMSCRM_CF7_Settings::get_merge_vars( $cf7_crm, $submitted_data );
$this->assertEquals( $merge_vars, array(
array( 'name' => 'first_name', 'value' => 'david' ),
array( 'name' => 'email', 'value' => 'david@close.marketing' ),
Expand All @@ -62,7 +58,7 @@ public function test_get_merge_vars_gdpr_unchecked() {
'extra-info' => array(), // Unchecked checkbox returns empty array in CF7.
);

$merge_vars = $this->contact_form->get_merge_vars( $cf7_crm, $submitted_data );
$merge_vars = FORMSCRM_CF7_Settings::get_merge_vars( $cf7_crm, $submitted_data );
$this->assertEquals(
array( array( 'name' => 'gdpr_accept', 'value' => '' ) ),
$merge_vars
Expand All @@ -84,7 +80,7 @@ public function test_get_merge_vars_gdpr_checked() {
'extra-info' => array( 'Me gustaría estar al tanto de las novedades de Ipace' ),
);

$merge_vars = $this->contact_form->get_merge_vars( $cf7_crm, $submitted_data );
$merge_vars = FORMSCRM_CF7_Settings::get_merge_vars( $cf7_crm, $submitted_data );
$this->assertEquals(
array( array( 'name' => 'gdpr_accept', 'value' => 'Me gustaría estar al tanto de las novedades de Ipace' ) ),
$merge_vars
Expand Down
150 changes: 150 additions & 0 deletions tests/Forms/test-elementor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
<?php
/**
* Tests for Elementor merge vars field mapping logic.
*
* Command: vendor/bin/phpunit --filter ElementorFormsTest
*
* @package Formscrm
*/

/**
* Class ElementorFormsTest
*
* @see FormsCRM_Elementor_Action_After_Submit::get_merge_vars()
*/
class ElementorFormsTest extends WP_UnitTestCase {

/**
* Basic field mapping: each CRM field maps to a distinct form field.
*/
public function test_get_merge_vars_basic() {
$formscrm_settings = array(
'fc_crm_field-first_name' => 'form-name',
'fc_crm_field-email' => 'form-email',
);

$raw_fields = array(
'form-name' => array( 'value' => 'David' ),
'form-email' => array( 'value' => 'david@example.com' ),
);

$merge_vars = FormsCRM_Elementor_Action_After_Submit::get_merge_vars( $formscrm_settings, $raw_fields );

$this->assertEquals(
array(
array(
'name' => 'first_name',
'value' => 'David',
),
array(
'name' => 'email',
'value' => 'david@example.com',
),
),
$merge_vars
);
}

/**
* Duplicate form field: two CRM fields mapped to the same form field.
*
* Before the fix, iterating $raw_fields with array_search() would stop at
* the first matching key and skip subsequent mappings for the same form field.
*
* @see https://github.com/closemarketing/formscrm/issues/179
*/
public function test_get_merge_vars_duplicate_form_field_mapping() {
$formscrm_settings = array(
'fc_crm_field-first_name' => 'form-name',
'fc_crm_field-email' => 'form-email',
'fc_crm_field-custom_fields|category' => 'form-category',
'fc_crm_field-custom_fields|category_backup' => 'form-category', // Same form field, different CRM field.
);

$raw_fields = array(
'form-name' => array( 'value' => 'David' ),
'form-email' => array( 'value' => 'david@example.com' ),
'form-category' => array( 'value' => 'Technology' ),
);

$merge_vars = FormsCRM_Elementor_Action_After_Submit::get_merge_vars( $formscrm_settings, $raw_fields );

$this->assertCount( 4, $merge_vars );
$this->assertEquals(
array(
'name' => 'first_name',
'value' => 'David',
),
$merge_vars[0]
);
$this->assertEquals(
array(
'name' => 'email',
'value' => 'david@example.com',
),
$merge_vars[1]
);
$this->assertEquals(
array(
'name' => 'custom_fields|category',
'value' => 'Technology',
),
$merge_vars[2]
);
$this->assertEquals(
array(
'name' => 'custom_fields|category_backup',
'value' => 'Technology',
),
$merge_vars[3]
);
}

/**
* Array field value (e.g. multi-select or checkbox group) is joined with ', '.
*/
public function test_get_merge_vars_array_value_joined() {
$formscrm_settings = array(
'fc_crm_field-interests' => 'form-interests',
);

$raw_fields = array(
'form-interests' => array( 'value' => array( 'Sports', 'Music', 'Tech' ) ),
);

$merge_vars = FormsCRM_Elementor_Action_After_Submit::get_merge_vars( $formscrm_settings, $raw_fields );

$this->assertEquals(
array(
array(
'name' => 'interests',
'value' => 'Sports, Music, Tech',
),
),
$merge_vars
);
}

/**
* Missing form field in raw_fields falls back to empty string.
*/
public function test_get_merge_vars_missing_field_returns_empty_string() {
$formscrm_settings = array(
'fc_crm_field-phone' => 'form-phone',
);

$raw_fields = array(); // Field not present in submission.

$merge_vars = FormsCRM_Elementor_Action_After_Submit::get_merge_vars( $formscrm_settings, $raw_fields );

$this->assertEquals(
array(
array(
'name' => 'phone',
'value' => '',
),
),
$merge_vars
);
}
}
8 changes: 6 additions & 2 deletions tests/bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
define( 'TESTS_PLUGIN_DIR', dirname( __DIR__ ) );
define( 'UNIT_TESTS_DATA_PLUGIN_DIR', TESTS_PLUGIN_DIR . '/tests/Data/' );

// Define WP_CORE_DIR if not already defined
// Define WP_CORE_DIR if not already defined.
if ( ! defined( 'WP_CORE_DIR' ) ) {
$_wp_core_dir = getenv( 'WP_CORE_DIR' );
if ( ! $_wp_core_dir ) {
Expand Down Expand Up @@ -45,7 +45,11 @@ function _manually_load_plugin() {
if ( file_exists( $cf7_path ) ) {
require_once $cf7_path;
}
require dirname( dirname( __FILE__ ) ) . '/formscrm.php';
// Load form integration classes used in tests (normally loaded conditionally by loader.php).
require_once TESTS_PLUGIN_DIR . '/tests/stubs/stub-elementor.php';
require_once TESTS_PLUGIN_DIR . '/includes/formscrm-library/class-elementor.php';
require_once TESTS_PLUGIN_DIR . '/includes/formscrm-library/class-contactform7.php';
require __DIR__ . '/../formscrm.php';
}

tests_add_filter( 'muplugins_loaded', '_manually_load_plugin' );
Expand Down
Loading
Loading