Plugin Directory

Changeset 3367591


Ignore:
Timestamp:
09/25/2025 06:27:46 AM (4 months ago)
Author:
plugcrux
Message:

Updated Zoho Campaigns plugin core and forms; added tag 1.0.5

Location:
integrate-with-zoho-campaigns
Files:
79 added
9 edited

Legend:

Unmodified
Added
Removed
  • integrate-with-zoho-campaigns/trunk/includes/admin/admin.php

    r3261395 r3367591  
    3636    wp_enqueue_script('jquery');
    3737
    38     // Define assets for CSS and JS with cache-busting versioning.
     38    // Define assets for CSS and JS with busting versioning.
    3939    $assets = [
    4040        'css' => ['home', 'auth', 'setup-fm', 'setup-filter', 'premium', 'settings', 'ai-settings', 'help', 'banner', 'error-log'],
  • integrate-with-zoho-campaigns/trunk/includes/extend/auth.php

    r3261395 r3367591  
    1919        {
    2020            global $blog_id;
    21 
    22             // Dynamically set cache key for multisite compatibility
    23             $this->cache_key = "iafwzcai_all_account_data_{$blog_id}";
    2421
    2522            // Initialize parent class with the required arguments
     
    5956            $this->_column_headers = [$columns, $hidden, $sortable];
    6057
    61             // Retrieve account data from cache
    62             $account_items = wp_cache_get($this->cache_key);
    63             if ($account_items === false) {
    64                 // If not cached, fetch all account data
    65                 $account_items = $iafwzcai_accountDBInstance->get_account_data();
    66             }
     58            $account_items = $iafwzcai_accountDBInstance->get_account_data();
    6759
    6860            // Check if $account_items is an array with data
  • integrate-with-zoho-campaigns/trunk/integrate-with-zoho-campaigns.php

    r3330797 r3367591  
    55 * Plugin URI: https://integrazo.com/products/integrate-with-zoho-campaigns
    66 * Description: Automatically send contact form submissions from popular WordPress forms to Zoho Campaigns and grow your business.
    7  * Version: 1.0.4
     7 * Version: 1.0.5
    88 * Author: Integrazo
    99 * Author URI: https://integrazo.com/
  • integrate-with-zoho-campaigns/trunk/readme.txt

    r3330797 r3367591  
    55Tested up to: 6.8
    66Requires PHP: 7.4
    7 Stable tag: 1.0.4
     7Stable tag: 1.0.5
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    347347== Changelog ==
    348348
     349= 1.0.5 =
     350* Changed: Elementor Forms integration flow updated for smoother setup
     351* Improved: Better results and reliability when mapping Elementor form fields
     352
    349353= 1.0.4 = 
    350354* Added: New FAQ about disabling specific integrations 
  • integrate-with-zoho-campaigns/trunk/src/db/account.php

    r3261395 r3367591  
    118118            global $wpdb, $blog_id;
    119119
    120             // Cache key for all account data with multisite compatibility
    121             $cache_key = "iafwzcai_all_account_data_{$blog_id}";
    122 
    123             // Attempt to get data from cache without specifying a group
    124             $cached_data = wp_cache_get($cache_key);
    125 
    126             if ($cached_data !== false) {
    127                 return $cached_data;
    128             }
    129 
    130120            // Fetch all account data from the database
    131             // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
     121            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
    132122            $data = $wpdb->get_results("SELECT * FROM " . esc_sql($this->table_name), ARRAY_A);
    133123
     
    136126                return [];
    137127            }
    138 
    139             // Store the fetched data in cache for future requests without a group
    140             wp_cache_set($cache_key, $data, '', HOUR_IN_SECONDS);
    141128
    142129            return $data;
  • integrate-with-zoho-campaigns/trunk/src/forms/form-fields.php

    r3309840 r3367591  
    313313         * @return array Array of form fields, each containing the field ID, label, and type.
    314314         */
    315         public function getElementorFormFields($form_id)
    316         {
    317             $form_fields = [];
    318 
    319             // ✅ Check if Elementor Pro class exists
    320             if (! class_exists('\ElementorPro\Modules\Forms\Submissions\Database\Repositories\Form_Snapshot_Repository')) {
    321                 return $form_fields;
     315        public function getElementorFormFields($combined_id)
     316        {
     317            $empty = [];
     318
     319            // Require Elementor Core + Pro
     320            if (! did_action('elementor/loaded') || ! defined('ELEMENTOR_PRO_VERSION')) {
     321                return $empty;
    322322            }
    323323
    324324            try {
    325                 $formsnaps = \ElementorPro\Modules\Forms\Submissions\Database\Repositories\Form_Snapshot_Repository::instance()->all();
    326 
    327                 foreach ($formsnaps as $form) {
    328                     if ($form->id === $form_id) {
    329                         // ✅ Found matching form ID → now get fields
    330                         if (!empty($form->fields) && is_array($form->fields)) {
    331                             foreach ($form->fields as $field) {
    332                                 $form_fields[] = [
    333                                     'key'    => $field['id'] ?? '',
    334                                     'label'  => $field['label'] ?? '',
    335                                     'type'   => $field['type'] ?? 'text',
    336                                     'source' => 'form'
    337                                 ];
     325                // Expect "{post_id}_{form_widget_id}" (widget id may contain underscores → limit=2)
     326                $parts = explode('_', (string) $combined_id, 2);
     327                if (count($parts) < 2) return $empty;
     328
     329                $post_id        = (int) $parts[0];
     330                $form_widget_id = trim((string) $parts[1]);
     331                if ($post_id <= 0 || $form_widget_id === '') return $empty;
     332
     333                // Prefer _elementor_data; fallback to post_content
     334                $raw = get_post_meta($post_id, '_elementor_data', true);
     335                if (! is_string($raw) || $raw === '') {
     336                    $raw = (string) get_post_field('post_content', $post_id);
     337                    if ($raw === '') return $empty;
     338                }
     339
     340                // Tolerant decode (no pre-unslash)
     341                $data = $this->decode_elementor_json($raw);
     342                if (! is_array($data)) return $empty;
     343
     344                // Walk the structure to find the exact form widget
     345                $fields_out   = [];
     346                $iterator = new RecursiveIteratorIterator(
     347                    new RecursiveArrayIterator($data),
     348                    RecursiveIteratorIterator::SELF_FIRST
     349                );
     350
     351                foreach ($iterator as $node) {
     352                    if (
     353                        is_array($node)
     354                        && ($node['elType'] ?? null) === 'widget'
     355                        && ($node['widgetType'] ?? null) === 'form'
     356                        && ($node['id'] ?? null) === $form_widget_id
     357                    ) {
     358                        $form_fields = $node['settings']['form_fields'] ?? [];
     359                        if (! is_array($form_fields)) break;
     360
     361                        foreach ($form_fields as $field) {
     362                            if (! is_array($field)) continue;
     363
     364                            $type = (string) ($field['field_type'] ?? 'text');
     365
     366                            // Skip internal/non-mappable types
     367                            if (in_array($type, ['step', 'html', 'recaptcha', 'recaptcha_v3', 'honeypot'], true)) {
     368                                continue;
    338369                            }
     370
     371                            $key = isset($field['_id']) ? (string) $field['_id'] : '';
     372                            if ($key === '') continue;
     373
     374                            $label = (string) ($field['field_label'] ?? '');
     375                            if ($label === '') $label = ucfirst($type);
     376
     377                            $fields_out[] = [
     378                                'key'    => $key,
     379                                'type'   => $type,
     380                                'label'  => $label,
     381                                'source' => 'form',
     382                            ];
    339383                        }
    340                         break; // ✅ Form found → no need to check others
    341                     }
    342                 }
    343                 return $form_fields;
     384                        break; // matched widget found; stop scanning
     385                    }
     386                }
     387
     388                return $fields_out;
    344389            } catch (Throwable $e) {
    345                 return $form_fields;
    346             }
     390                return $empty; // fail safe
     391            }
     392        }
     393        private function decode_elementor_json($raw)
     394        {
     395            $data = json_decode($raw, true);
     396            if (is_array($data)) return $data;
     397
     398            if (function_exists('wp_unslash')) {
     399                $data = json_decode(wp_unslash($raw), true);
     400                if (is_array($data)) return $data;
     401            }
     402
     403            if (function_exists('mb_convert_encoding')) {
     404                $fixed = mb_convert_encoding($raw, 'UTF-8', 'UTF-8');
     405                $data  = json_decode($fixed, true);
     406                if (is_array($data)) return $data;
     407            }
     408
     409            return null;
    347410        }
    348411        /**
  • integrate-with-zoho-campaigns/trunk/src/forms/form-group-ids.php

    r3309840 r3367591  
    270270         * @return array Array of forms with their IDs, names, and fields.
    271271         */
     272        /** =========================
     273         *  ELEMENTOR FORMS LISTING (clean)
     274         *  ========================= */
    272275        public function getElementorForms()
    273276        {
    274277            $form_list = [];
    275278
    276             if (! class_exists('\ElementorPro\Modules\Forms\Submissions\Database\Repositories\Form_Snapshot_Repository')) {
    277                 return $form_list; // Return empty if class not found
    278             }
    279 
    280             try {
    281                 $formsnaps = \ElementorPro\Modules\Forms\Submissions\Database\Repositories\Form_Snapshot_Repository::instance()->all();
    282 
    283                 foreach ($formsnaps as $form) {
    284                     $page_title = get_the_title($form->post_id);
    285 
    286                     $form_list[] = [
    287                         'id'   => $form->id,
    288                         'name' => $form->name . ' - ' . $page_title,
    289                     ];
    290                 }
    291 
     279            // Require Elementor Core + Pro
     280            if (! did_action('elementor/loaded') || ! defined('ELEMENTOR_PRO_VERSION')) {
    292281                return $form_list;
    293             } catch (Throwable $e) {
     282            }
     283
     284            try {
     285                $args = [
     286                    'post_type'      => ['page', 'post', 'elementor_library'],
     287                    'posts_per_page' => -1,
     288                    'post_status'    => 'any',
     289                    'fields'         => 'ids',
     290                    // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
     291                    'meta_query'     => [
     292                        [
     293                            'key'     => '_elementor_data',
     294                            'compare' => 'EXISTS',
     295                        ],
     296                    ],
     297                ];
     298
     299                $posts = get_posts($args);
     300                if (empty($posts) || is_wp_error($posts)) {
     301                    return $form_list;
     302                }
     303
     304                foreach ($posts as $post_id) {
     305                    $raw = get_post_meta($post_id, '_elementor_data', true);
     306                    if ($raw === '' || $raw === null) {
     307                        continue;
     308                    }
     309
     310                    // Tolerant decode (no pre-unslash)
     311                    $data = $this->decode_elementor_json($raw);
     312                    if (! is_array($data)) {
     313                        continue;
     314                    }
     315
     316                    // Find Elementor Form widgets
     317                    $iterator = new RecursiveIteratorIterator(
     318                        new RecursiveArrayIterator($data),
     319                        RecursiveIteratorIterator::SELF_FIRST
     320                    );
     321
     322                    foreach ($iterator as $node) {
     323                        if (
     324                            is_array($node)
     325                            && ($node['elType'] ?? null) === 'widget'
     326                            && ($node['widgetType'] ?? null) === 'form'
     327                            && ! empty($node['id'])
     328                        ) {
     329                            $form_name = ! empty($node['settings']['form_name'])
     330                                ? (string) $node['settings']['form_name']
     331                                : 'Untitled Form';
     332
     333                            $form_id   = (string) $node['id']; // Elementor widget id
     334                            $title     = get_the_title($post_id);
     335
     336                            $form_list[] = [
     337                                'id'   => $post_id . '_' . $form_id,  // combined id for lookup
     338                                'name' => sprintf('%s (Page: %s)', $form_name, $title),
     339                            ];
     340                        }
     341                    }
     342                }
     343
    294344                return $form_list;
    295             }
     345            } catch (Throwable $e) {
     346                return $form_list;
     347            }
     348        }
     349
     350        /** =========================
     351         *  Tolerant JSON decoder (no side-effects)
     352         *  ========================= */
     353        private function decode_elementor_json($raw)
     354        {
     355            // 1) Try as-is
     356            $data = json_decode($raw, true);
     357            if (is_array($data)) return $data;
     358
     359            // 2) Try unslash
     360            if (function_exists('wp_unslash')) {
     361                $data = json_decode(wp_unslash($raw), true);
     362                if (is_array($data)) return $data;
     363            }
     364
     365            // 3) Normalize encoding (rare)
     366            if (function_exists('mb_convert_encoding')) {
     367                $fixed = mb_convert_encoding($raw, 'UTF-8', 'UTF-8');
     368                $data  = json_decode($fixed, true);
     369                if (is_array($data)) return $data;
     370            }
     371
     372            return null;
    296373        }
    297374    }
  • integrate-with-zoho-campaigns/trunk/src/forms/form-name.php

    r3309840 r3367591  
    115115         * Retrieves the name of an Elementor Form by form ID.
    116116         */
    117         public function getElementorFormName($form_id)
    118         {
    119             // Return empty string by default
    120             $form_name = '';
    121 
    122             // ✅ Check if Elementor Pro class exists
    123             if (! class_exists('\ElementorPro\Modules\Forms\Submissions\Database\Repositories\Form_Snapshot_Repository')) {
    124                 return $form_name;
     117        public function getElementorFormName($combined_id)
     118        {
     119            $empty = '';
     120
     121            // Elementor core + Pro required
     122            if (! did_action('elementor/loaded') || ! defined('ELEMENTOR_PRO_VERSION')) {
     123                return $empty;
    125124            }
    126125
    127126            try {
    128                 $formsnaps = \ElementorPro\Modules\Forms\Submissions\Database\Repositories\Form_Snapshot_Repository::instance()->all();
    129 
    130                 foreach ($formsnaps as $form) {
    131                     if ($form->id === $form_id) {
    132                         $page_title = get_the_title($form->post_id);
    133                         $form_name = $form->name . ' - ' . $page_title;
    134                         break; // Found → exit loop
     127                // Expect "{post_id}_{form_widget_id}" (widget id may contain underscores → limit=2)
     128                $parts = explode('_', (string) $combined_id, 2);
     129                if (count($parts) < 2) return $empty;
     130
     131                $post_id        = (int) $parts[0];
     132                $form_widget_id = trim((string) $parts[1]);
     133                if ($post_id <= 0 || $form_widget_id === '') return $empty;
     134
     135                // Prefer _elementor_data; fallback to post_content
     136                $raw = get_post_meta($post_id, '_elementor_data', true);
     137                if (! is_string($raw) || $raw === '') {
     138                    $raw = (string) get_post_field('post_content', $post_id);
     139                    if ($raw === '') return $empty;
     140                }
     141
     142                // Tolerant decode (no pre-unslash)
     143                $data = $this->decode_elementor_json($raw);
     144                if (! is_array($data)) return $empty;
     145
     146                // Traverse to find the exact form widget
     147                $iterator = new RecursiveIteratorIterator(
     148                    new RecursiveArrayIterator($data),
     149                    RecursiveIteratorIterator::SELF_FIRST
     150                );
     151
     152                foreach ($iterator as $node) {
     153                    if (
     154                        is_array($node)
     155                        && ($node['elType'] ?? null) === 'widget'
     156                        && ($node['widgetType'] ?? null) === 'form'
     157                        && ($node['id'] ?? null) === $form_widget_id
     158                    ) {
     159                        $name = (string) ($node['settings']['form_name'] ?? '');
     160                        $name = trim($name);
     161                        if ($name === '') $name = 'Untitled Form';
     162
     163                        // Cap extreme lengths safely (multibyte-aware)
     164                        if (function_exists('mb_strlen') && mb_strlen($name) > 120) {
     165                            $name = mb_substr($name, 0, 117) . '...';
     166                        }
     167
     168                        return $name;
    135169                    }
    136170                }
    137171
    138                 return $form_name;
     172                return $empty;
    139173            } catch (Throwable $e) {
    140                 return $form_name; // return empty string if error
    141             }
     174                return $empty;
     175            }
     176        }
     177        /**
     178         * Tolerant decoder for Elementor JSON: as-is → unslash → encoding normalize.
     179         */
     180        private function decode_elementor_json($raw)
     181        {
     182            // 1) Try as-is
     183            $data = json_decode($raw, true);
     184            if (is_array($data)) return $data;
     185
     186            // 2) Try unslash (if meta stored with added slashes)
     187            if (function_exists('wp_unslash')) {
     188                $data = json_decode(wp_unslash($raw), true);
     189                if (is_array($data)) return $data;
     190            }
     191
     192            // 3) Normalize encoding (rare but safe)
     193            if (function_exists('mb_convert_encoding')) {
     194                $fixed = mb_convert_encoding($raw, 'UTF-8', 'UTF-8');
     195                $data  = json_decode($fixed, true);
     196                if (is_array($data)) return $data;
     197            }
     198
     199            return null;
    142200        }
    143201
  • integrate-with-zoho-campaigns/trunk/src/forms/submit-action.php

    r3309840 r3367591  
    363363        {
    364364            $IAFWZCAI_Field_MappingDBInstance = new IAFWZCAI_Field_Mapping();
    365             try {
    366                 // Retrieve form ID and group ID
    367                 $form_details = $handler->get_current_form();
    368                 $form_id = $form_details['id'];
    369                 $form_group_id = 7; // Set form group ID for Elementor Forms
     365
     366            try {
     367                // --- Build combined form ID: {postId}_{widgetId}
     368                $form_widget_id = (string) $record->get_form_settings('id');
     369                $form_post_id   = (int) $record->get_form_settings('form_post_id');
     370                if ($form_post_id <= 0 || $form_widget_id === '') {
     371                    return;
     372                }
     373                $form_id       = $form_post_id . '_' . $form_widget_id;
     374                $form_group_id = 7; // Elementor Forms group
     375                // --- License/session check
    370376                $session = iafwzcai_is_valid_key();
    371                 if (!$session) {
     377                if (! $session) {
     378                    // If session invalid, block and stop further processing
    372379                    $IAFWZCAI_Field_MappingDBInstance->block_field_mapping();
    373380                }
    374                 // Retrieve field mapping details
     381
     382                // --- Get mapping rows for this form
    375383                $field_mapping_list = $IAFWZCAI_Field_MappingDBInstance->get_mapping_by_id($form_id, $form_group_id);
    376 
    377                 // Prepare form data for CRM
    378                 $raw_fields = $record->get('fields');
     384                if (empty($field_mapping_list)) {
     385                    return; // nothing to do
     386                }
     387
     388                // --- Read form fields and submitted values
    379389                $form_data = [];
    380390
    381                 if (!empty($raw_fields)) {
    382                     foreach ($raw_fields as $field_key => $field_data) {
    383                         $form_data[$field_key] = $field_data['value'] ?? ''; // Ensure value is retrieved safely
    384                     }
    385                 }
    386 
    387                 // Process each field mapping
    388                 if (!empty($field_mapping_list)) {
    389                     foreach ($field_mapping_list as $field_mapping_details) {
    390                         if ((int)$field_mapping_details->integration_status === 1) {
    391 
    392                             $filter_criteria = $this->filter_criteria($form_data, $field_mapping_details->filter_criteria);
    393                             if ($filter_criteria === 1) {
    394                                 // Map data and process submission
    395                                 $crm_data = $this->map_form_data_to_crm($form_data, $field_mapping_details, 'elementor_forms');
    396 
    397                                 // Submit data to CRM and handle response
    398                                 $response = $this->process_form_submission($form_data, $crm_data, $field_mapping_details);
    399 
    400                                 // Log errors if the submission fails
    401                                 $this->check_and_add_error_log($response, $form_data, $crm_data, $field_mapping_details);
    402                             }
    403                         }
    404                     }
    405                 }
    406             } catch (Throwable $e) {
    407                 // Log the error for debugging purposes
     391                // Definitions from form settings (builder-time)
     392                $form_fields = $record->get_form_settings('form_fields');
     393                $form_fields = is_array($form_fields) ? $form_fields : [];
     394
     395                // Build map: custom_id => _id (to normalize submitted keys)
     396                $custom_id_map = [];
     397                foreach ($form_fields as $field_def) {
     398                    if (is_array($field_def) && isset($field_def['custom_id'], $field_def['_id'])) {
     399                        $custom_id_map[(string) $field_def['custom_id']] = (string) $field_def['_id'];
     400                    }
     401                }
     402
     403                // Submitted runtime values
     404                $raw_fields = $record->get('fields'); // ElementorPro\Forms\Record stores submitted fields here
     405                $raw_fields = is_array($raw_fields) ? $raw_fields : [];
     406                foreach ($raw_fields as $id => $field) {
     407                    if (! is_array($field)) {
     408                        continue;
     409                    }
     410
     411                    $value = $field['value'] ?? '';
     412                    $type  = $field['type']  ?? '';
     413
     414                    // Normalize key to builder _id if we have custom_id
     415                    $field_key = isset($custom_id_map[$id]) ? $custom_id_map[$id] : (string) $id;
     416
     417                    if ($type === 'upload') {
     418                        // Elementor may give array or CSV; normalize to array of paths
     419                        if (is_array($value)) {
     420                            $paths = array_map('strval', $value);
     421                        } else {
     422                            $paths = array_map('trim', explode(',', (string) $value));
     423                            $paths = array_filter($paths, static function ($p) {
     424                                return $p !== '';
     425                            });
     426                        }
     427                        $form_data[$field_key] = array_values($paths);
     428                    } else {
     429                        // Scalar value as string
     430                        if (is_array($value)) {
     431                            // For checkbox/multi-select, implode safely
     432                            $form_data[$field_key] = implode(', ', array_map('strval', $value));
     433                        } else {
     434                            $form_data[$field_key] = (string) $value;
     435                        }
     436                    }
     437                }
     438
     439                // --- Process each active mapping
     440                foreach ($field_mapping_list as $field_mapping_details) {
     441                    if ((int) $field_mapping_details->integration_status !== 1) {
     442                        continue;
     443                    }
     444
     445                    $filter_ok = $this->filter_criteria($form_data, $field_mapping_details->filter_criteria);
     446                    if ((int) $filter_ok !== 1) {
     447                        continue;
     448                    }
     449
     450                    // Build CRM payload and send
     451                    $crm_data = $this->map_form_data_to_crm($form_data, $field_mapping_details, 'elementor_forms');
     452
     453                    $response = $this->process_form_submission($form_data, $crm_data, $field_mapping_details);
     454
     455                    // Log errors if any
     456                    $this->check_and_add_error_log($response, $form_data, $crm_data, $field_mapping_details);
     457                }
     458            } catch (Throwable $e) {
     459                // Optional debug:
     460                return;
    408461            }
    409462        }
Note: See TracChangeset for help on using the changeset viewer.