PluginProbe
FireBox – WooCommerce Popup Builder, Exit Intent Popup, Email Optin & Cart Abandonment / 3.1.12
FireBox – WooCommerce Popup Builder, Exit Intent Popup, Email Optin & Cart Abandonment v3.1.12
3.1.13 3.1.12 3.1.11 3.1.10 3.1.9 3.1.8 3.1.7 trunk 1.0.0 1.0.1 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 All 122 releases
firebox / Inc / Core / Helpers / Integrations.php

Integrations.php in FireBox – WooCommerce Popup Builder, Exit Intent Popup, Email Optin & Cart Abandonment 3.1.12, at Inc/Core/Helpers/Integrations.php

1,622 lines 36.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @package FireBox
4 * @version 3.1.12
5 *
6 * @author FirePlugins <info@fireplugins.com>
7 * @link https://www.fireplugins.com
8 * @copyright Copyright © 2026 FirePlugins All Rights Reserved
9 * @license GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
10 */
11
12 namespace FireBox\Core\Helpers;
13
14 if (!defined('ABSPATH'))
15 {
16 exit; // Exit if accessed directly.
17 }
18
19 class Integrations
20 {
21 /**
22 * Global integrations metadata.
23 *
24 * @var array
25 */
26 private static $integrations = [
27 'mailchimp' => [
28 'label' => 'MailChimp',
29 'settings_key' => 'mailchimp_api_key',
30 'class_name' => 'MailChimp',
31 'docs_slug' => 'mailchimp',
32 'logo_file' => 'mailchimp.svg',
33 'connection_type' => 'api_key',
34 'pro_only' => false,
35 'form' => [
36 'legacyApiKeyField' => 'mailchimpAPIKey',
37 'defaults' => [
38 'listId' => '',
39 'doubleOptin' => false,
40 'updateExisting' => true
41 ],
42 'legacyMap' => [
43 'listId' => 'mailchimpListID',
44 'doubleOptin' => 'mailchimpDoubleOptin',
45 'updateExisting' => 'mailchimpUpdateExisting'
46 ],
47 'fields' => [
48 [
49 'type' => 'list',
50 'key' => 'listId',
51 'label' => 'List'
52 ],
53 [
54 'type' => 'toggle',
55 'key' => 'doubleOptin',
56 'label' => 'Double Optin'
57 ],
58 [
59 'type' => 'toggle',
60 'key' => 'updateExisting',
61 'label' => 'Update existing user'
62 ]
63 ]
64 ]
65 ],
66 'brevo' => [
67 'label' => 'Brevo',
68 'settings_key' => 'brevo_api_key',
69 'class_name' => 'Brevo',
70 'docs_slug' => 'brevo',
71 'logo_file' => 'brevo.svg',
72 'connection_type' => 'api_key',
73 'pro_only' => false,
74 'form' => [
75 'legacyApiKeyField' => 'brevoAPIKey',
76 'defaults' => [
77 'listId' => '',
78 'updateExisting' => true
79 ],
80 'legacyMap' => [
81 'listId' => 'brevoListID',
82 'updateExisting' => 'brevoUpdateExisting'
83 ],
84 'fields' => [
85 [
86 'type' => 'list',
87 'key' => 'listId',
88 'label' => 'List'
89 ],
90 [
91 'type' => 'toggle',
92 'key' => 'updateExisting',
93 'label' => 'Update existing user'
94 ]
95 ]
96 ]
97 ],
98 'mailerlite' => [
99 'label' => 'MailerLite',
100 'settings_key' => 'mailerlite_api_key',
101 'class_name' => 'MailerLite',
102 'docs_slug' => 'mailerlite',
103 'logo_file' => 'mailerlite.svg',
104 'connection_type' => 'api_key',
105 'pro_only' => true,
106 'required_plan' => 'pro',
107 'form' => [
108 'defaults' => [
109 'listId' => '',
110 'status' => 'active'
111 ],
112 'fields' => [
113 [
114 'type' => 'list',
115 'key' => 'listId',
116 'label' => 'Group'
117 ],
118 [
119 'type' => 'select',
120 'key' => 'status',
121 'label' => 'Subscriber Status',
122 'options' => [
123 [
124 'value' => 'active',
125 'label' => 'Active'
126 ],
127 [
128 'value' => 'unconfirmed',
129 'label' => 'Unconfirmed'
130 ],
131 [
132 'value' => 'unsubscribed',
133 'label' => 'Unsubscribed'
134 ],
135 [
136 'value' => 'bounced',
137 'label' => 'Bounced'
138 ],
139 [
140 'value' => 'junk',
141 'label' => 'Junk'
142 ]
143 ]
144 ]
145 ]
146 ]
147 ],
148 'getresponse' => [
149 'label' => 'GetResponse',
150 'settings_key' => 'getresponse_api_key',
151 'class_name' => 'GetResponse',
152 'docs_slug' => 'getresponse',
153 'logo_file' => 'getresponse.png',
154 'connection_type' => 'api_key',
155 'pro_only' => true,
156 'required_plan' => 'pro',
157 'form' => [
158 'defaults' => [
159 'listId' => '',
160 'updateExisting' => true,
161 'doubleOptin' => false
162 ],
163 'fields' => [
164 [
165 'type' => 'list',
166 'key' => 'listId',
167 'label' => 'List'
168 ],
169 [
170 'type' => 'toggle',
171 'key' => 'updateExisting',
172 'label' => 'Update existing user'
173 ],
174 [
175 'type' => 'toggle',
176 'key' => 'doubleOptin',
177 'label' => 'Double Optin'
178 ]
179 ]
180 ]
181 ],
182 'activecampaign' => [
183 'label' => 'ActiveCampaign',
184 'class_name' => 'ActiveCampaign',
185 'docs_slug' => 'activecampaign',
186 'logo_file' => 'activecampaign.jpeg',
187 'connection_type' => 'api_key',
188 'credential_fields' => [
189 [
190 'key' => 'api_url',
191 'settings_key' => 'activecampaign_api_url',
192 'label' => 'API URL',
193 'placeholder' => 'https://your-account.api-us1.com'
194 ],
195 [
196 'key' => 'api_key',
197 'settings_key' => 'activecampaign_api_key',
198 'label' => 'API Key',
199 'placeholder' => 'API Key',
200 'type' => 'password'
201 ]
202 ],
203 'connection_value_template' => '{api_url}|{api_key}',
204 'pro_only' => true,
205 'required_plan' => 'pro',
206 'form' => [
207 'defaults' => [
208 'listId' => '',
209 'updateExisting' => true,
210 'doubleOptin' => false
211 ],
212 'fields' => [
213 [
214 'type' => 'list',
215 'key' => 'listId',
216 'label' => 'List'
217 ],
218 [
219 'type' => 'toggle',
220 'key' => 'updateExisting',
221 'label' => 'Update existing user'
222 ],
223 [
224 'type' => 'toggle',
225 'key' => 'doubleOptin',
226 'label' => 'Double Optin'
227 ]
228 ]
229 ]
230 ],
231 'klaviyo' => [
232 'label' => 'Klaviyo',
233 'settings_key' => 'klaviyo_api_key',
234 'class_name' => 'Klaviyo',
235 'docs_slug' => 'klaviyo',
236 'logo_file' => 'klaviyo.svg',
237 'connection_type' => 'api_key',
238 'pro_only' => true,
239 'required_plan' => 'growth',
240 'form' => [
241 'defaults' => [
242 'listId' => '',
243 'updateExisting' => true,
244 'doubleOptin' => false
245 ],
246 'fields' => [
247 [
248 'type' => 'list',
249 'key' => 'listId',
250 'label' => 'List'
251 ],
252 [
253 'type' => 'toggle',
254 'key' => 'updateExisting',
255 'label' => 'Update existing user'
256 ],
257 [
258 'type' => 'toggle',
259 'key' => 'doubleOptin',
260 'label' => 'Double Optin'
261 ]
262 ]
263 ]
264 ],
265 'salesforcewebtolead' => [
266 'label' => 'Salesforce Web-to-Lead',
267 'settings_key' => 'salesforcewebtolead_api_key',
268 'class_name' => 'SalesforceWebToLead',
269 'docs_slug' => 'salesforce',
270 'logo_file' => 'salesforcewebtolead.svg',
271 'connection_type' => 'api_key',
272 'api_key_placeholder' => 'Organization ID (OID), e.g. 00DXXXXXXXXXXXX',
273 'pro_only' => true,
274 'required_plan' => 'growth',
275 'form' => [
276 'defaults' => [
277 'testMode' => false
278 ],
279 'fields' => [
280 [
281 'type' => 'toggle',
282 'key' => 'testMode',
283 'label' => 'Test Mode'
284 ]
285 ]
286 ]
287 ],
288 'acymailing' => [
289 'label' => 'AcyMailing',
290 'settings_key' => '',
291 'class_name' => 'AcyMailing',
292 'docs_slug' => 'acymailing',
293 'logo_file' => 'acymailing.png',
294 'connection_type' => 'plugin',
295 'plugin_file' => 'acymailing/index.php',
296 'pro_only' => true,
297 'required_plan' => 'basic',
298 'form' => [
299 'defaults' => [
300 'listId' => '',
301 'doubleOptin' => false
302 ],
303 'legacyMap' => [
304 'listId' => 'acymailingListID',
305 'doubleOptin' => 'acymailingDoubleOptin'
306 ],
307 'fields' => [
308 [
309 'type' => 'list',
310 'key' => 'listId',
311 'label' => 'List'
312 ],
313 [
314 'type' => 'toggle',
315 'key' => 'doubleOptin',
316 'label' => 'Double Optin'
317 ]
318 ]
319 ]
320 ]
321 ];
322
323 /**
324 * Returns all integrations with slug in payload.
325 *
326 * @return array
327 */
328 public static function getIntegrations()
329 {
330 $integrations = [];
331
332 foreach (self::$integrations as $slug => $integration)
333 {
334 if (!self::isIntegrationAvailable($integration))
335 {
336 continue;
337 }
338
339 $integrations[$slug] = self::normalizeIntegrationEntry($slug, $integration);
340 }
341
342 return $integrations;
343 }
344
345 /**
346 * Returns locked integrations for unavailable plans.
347 *
348 * @return array
349 */
350 public static function getLockedIntegrations()
351 {
352 $integrations = [];
353
354 foreach (self::$integrations as $slug => $integration)
355 {
356 if (!self::isIntegrationLocked($integration))
357 {
358 continue;
359 }
360
361 $item = self::normalizeIntegrationEntry($slug, $integration);
362 $item['locked'] = true;
363 $item['connection_type'] = 'locked';
364 $item['connected'] = false;
365
366 $integrations[$slug] = $item;
367 }
368
369 return $integrations;
370 }
371
372 /**
373 * Returns integrations visible in UI (available + locked).
374 *
375 * @return array
376 */
377 private static function getVisibleIntegrations()
378 {
379 return self::getIntegrations() + self::getLockedIntegrations();
380 }
381
382 /**
383 * Returns whether integration should be available.
384 *
385 * @param array $integration
386 *
387 * @return bool
388 */
389 private static function isIntegrationAvailable($integration = [])
390 {
391 $integration = is_array($integration) ? $integration : [];
392
393 if (!empty($integration['pro_only']) && !self::isProVersion())
394 {
395 return false;
396 }
397
398 $required_plan = isset($integration['required_plan']) ? trim((string) $integration['required_plan']) : '';
399
400 return $required_plan ? self::isRequiredPlanMet($required_plan) : true;
401 }
402
403 /**
404 * Returns whether integration should be displayed as locked.
405 *
406 * @param array $integration
407 *
408 * @return bool
409 */
410 private static function isIntegrationLocked($integration = [])
411 {
412 $integration = is_array($integration) ? $integration : [];
413
414 if (self::isIntegrationAvailable($integration))
415 {
416 return false;
417 }
418
419 return !empty($integration['required_plan']) || !empty($integration['pro_only']);
420 }
421
422 /**
423 * Returns integration metadata by slug or class name.
424 *
425 * @param string $integration
426 *
427 * @return array|null
428 */
429 public static function getIntegration($integration = '')
430 {
431 if (!is_string($integration))
432 {
433 return null;
434 }
435
436 $integration = trim($integration);
437 if (!$integration)
438 {
439 return null;
440 }
441
442 $integrations = self::getVisibleIntegrations();
443 $integration_lc = strtolower($integration);
444
445 if (isset($integrations[$integration_lc]))
446 {
447 return $integrations[$integration_lc];
448 }
449
450 foreach ($integrations as $item)
451 {
452 if (!isset($item['class_name']))
453 {
454 continue;
455 }
456
457 if (strtolower((string) $item['class_name']) === $integration_lc)
458 {
459 return $item;
460 }
461 }
462
463 return null;
464 }
465
466 /**
467 * Resolves a supported integration slug.
468 *
469 * @param string $integration
470 *
471 * @return string|null
472 */
473 public static function getIntegrationSlug($integration = '')
474 {
475 if (!$integration = self::getIntegration($integration))
476 {
477 return null;
478 }
479
480 return $integration['slug'];
481 }
482
483 /**
484 * Returns framework class name for a supported integration.
485 *
486 * @param string $integration
487 *
488 * @return string|null
489 */
490 public static function getFrameworkIntegrationClassName($integration = '')
491 {
492 if (!$integration = self::getIntegration($integration))
493 {
494 return null;
495 }
496
497 return $integration['class_name'];
498 }
499
500 /**
501 * Returns the stored setting key for a given integration.
502 *
503 * @param string $integration
504 *
505 * @return string|null
506 */
507 public static function getIntegrationSettingKey($integration = '')
508 {
509 $credential_fields = self::getCredentialFields($integration);
510
511 if (empty($credential_fields))
512 {
513 return null;
514 }
515
516 $field = reset($credential_fields);
517 $key = isset($field['settings_key']) ? trim((string) $field['settings_key']) : '';
518
519 return !empty($key) ? $key : null;
520 }
521
522 /**
523 * Returns normalized credential fields for an integration.
524 *
525 * @param string|array $integration
526 *
527 * @return array
528 */
529 public static function getCredentialFields($integration = '')
530 {
531 if (!is_array($integration))
532 {
533 $integration = self::getIntegration($integration);
534 }
535
536 $integration = is_array($integration) ? $integration : [];
537 $fields = [];
538
539 if (!empty($integration['credential_fields']) && is_array($integration['credential_fields']))
540 {
541 $fields = $integration['credential_fields'];
542 }
543 else
544 {
545 $settings_key = isset($integration['settings_key']) ? trim((string) $integration['settings_key']) : '';
546 if ($settings_key !== '')
547 {
548 $fields[] = [
549 'key' => 'api_key',
550 'settings_key' => $settings_key,
551 'label' => 'API Key',
552 'placeholder' => isset($integration['api_key_placeholder']) ? (string) $integration['api_key_placeholder'] : '',
553 'type' => 'password'
554 ];
555 }
556 }
557
558 $normalized = [];
559
560 foreach ($fields as $field)
561 {
562 if (!is_array($field))
563 {
564 continue;
565 }
566
567 $key = isset($field['key']) ? trim((string) $field['key']) : '';
568 $settings_key = isset($field['settings_key']) ? trim((string) $field['settings_key']) : '';
569
570 if ($key === '' || $settings_key === '')
571 {
572 continue;
573 }
574
575 $normalized[] = [
576 'key' => $key,
577 'settings_key' => $settings_key,
578 'label' => !empty($field['label']) ? self::translateRegistryText($field['label']) : '',
579 'placeholder' => !empty($field['placeholder']) ? self::translateRegistryText($field['placeholder']) : '',
580 'type' => isset($field['type']) && trim((string) $field['type']) === 'text' ? 'text' : 'password',
581 'required' => !array_key_exists('required', $field) || (bool) $field['required']
582 ];
583 }
584
585 return $normalized;
586 }
587
588 /**
589 * Returns integration human label.
590 *
591 * @param string $integration
592 *
593 * @return string
594 */
595 public static function getIntegrationLabel($integration = '')
596 {
597 $integration_input = $integration;
598
599 if (!$integration = self::getIntegration($integration_input))
600 {
601 return trim((string) $integration_input);
602 }
603
604 return $integration['label'];
605 }
606
607 /**
608 * Returns integration docs URL.
609 *
610 * @param string $integration
611 *
612 * @return string
613 */
614 public static function getFindAPIKeyURL($integration = '')
615 {
616 if (!$integration = self::getIntegration($integration))
617 {
618 return '';
619 }
620
621 $docs_slug = isset($integration['docs_slug']) ? trim((string) $integration['docs_slug']) : '';
622 if (!$docs_slug)
623 {
624 return '';
625 }
626
627 return \FPFramework\Base\Functions::getUTMURL(
628 'https://www.fireplugins.com/docs/integrations/' . $docs_slug . '/#find_my_api_key',
629 '',
630 'misc',
631 $docs_slug
632 );
633 }
634
635 /**
636 * Returns settings keys used by integration global API keys.
637 *
638 * @return array
639 */
640 public static function getSettingsKeys()
641 {
642 $keys = [];
643
644 foreach (self::getVisibleIntegrations() as $integration)
645 {
646 foreach (self::getCredentialFields($integration) as $field)
647 {
648 if (empty($field['settings_key']))
649 {
650 continue;
651 }
652
653 $keys[] = $field['settings_key'];
654 }
655 }
656
657 return array_values(array_unique($keys));
658 }
659
660 /**
661 * Returns data used by Settings > Integrations UI.
662 *
663 * @return array
664 */
665 public static function getSettingsManagedIntegrations()
666 {
667 $integrations = [];
668
669 foreach (self::getVisibleIntegrations() as $integration)
670 {
671 $slug = $integration['slug'];
672 $connection_type = self::getConnectionType($slug);
673 $connected = self::isConnected($slug);
674 $is_locked = self::isLocked($slug);
675
676 $item = [
677 'label' => $integration['label'],
678 'slug' => $slug,
679 'connected' => $connected,
680 'locked' => $is_locked,
681 'connection_type' => $connection_type,
682 'status_label' => $is_locked ? __('Locked', 'firebox') : firebox()->_($connected ? 'FB_INTEGRATION_CONNECTED' : 'FB_INTEGRATION_DISCONNECTED'),
683 'logo_url' => !empty($integration['logo_file']) ? FBOX_MEDIA_ADMIN_URL . 'images/integrations/' . $integration['logo_file'] : ''
684 ];
685
686 if ($is_locked)
687 {
688 $required_plan_label = self::getRequiredPlanLabel($slug);
689 /* translators: %s: Required plan label. */
690 $item['locked_message'] = sprintf(__('Upgrade to %s to connect this integration.', 'firebox'), $required_plan_label);
691 /* translators: %s: Plan name (e.g., Growth) */
692 $item['upgrade_badge_label'] = sprintf(__('Upgrade to %s', 'firebox'), $required_plan_label);
693 $item['upgrade_label'] = sprintf(fpframework()->_('FPF_UNLOCK_X_FEATURE'), $required_plan_label);
694 $item['upgrade_plan'] = $required_plan_label;
695 $item['current_plan'] = self::getCurrentPlan();
696 $item['upgrade_url'] = self::getUpgradeURL($slug);
697 }
698 else if ($connection_type === 'plugin')
699 {
700 $item['docs_url'] = self::getFindAPIKeyURL($slug);
701 $plugin_state = self::getPluginState($slug);
702
703 if ($plugin_state === 'inactive')
704 {
705 $item['status_label'] = __('Inactive', 'firebox');
706 /* translators: %s: Integration label. */
707 $item['plugin_message'] = sprintf(__('Installed but inactive. Activate %s to use this integration.', 'firebox'), $integration['label']);
708 $item['plugin_action_label'] = __('Activate', 'firebox');
709 $item['plugin_action_url'] = self::getActivatePluginURL($slug);
710 }
711 else if ($plugin_state === 'missing')
712 {
713 $item['status_label'] = __('Not Installed', 'firebox');
714 /* translators: %s: Integration label. */
715 $item['plugin_message'] = sprintf(__('%s is not installed. Install it to use this integration.', 'firebox'), $integration['label']);
716 $item['plugin_action_label'] = __('Install', 'firebox');
717 $item['plugin_action_url'] = self::getInstallPluginURL($slug);
718 }
719 else
720 {
721 /* translators: %s: Integration label. */
722 $item['plugin_message'] = sprintf(__('%s is installed and active.', 'firebox'), $integration['label']);
723 $item['plugin_action_label'] = '';
724 $item['plugin_action_url'] = '';
725 }
726 }
727 else
728 {
729 $item['credentials'] = self::getSettingsCredentialFields($slug);
730 $item['docs_url'] = self::getFindAPIKeyURL($slug);
731 }
732
733 $integrations[] = $item;
734 }
735
736 return $integrations;
737 }
738
739 /**
740 * Returns whether integration is connected.
741 *
742 * @param string $integration
743 *
744 * @return bool
745 */
746 public static function isConnected($integration = '')
747 {
748 $connection_type = self::getConnectionType($integration);
749
750 if ($connection_type === 'locked')
751 {
752 return false;
753 }
754
755 if ($connection_type === 'plugin')
756 {
757 return self::getPluginState($integration) === 'active';
758 }
759
760 return self::hasGlobalConnection($integration);
761 }
762
763 /**
764 * Returns integration connection type.
765 *
766 * @param string $integration
767 *
768 * @return string
769 */
770 public static function getConnectionType($integration = '')
771 {
772 if (!$integration = self::getIntegration($integration))
773 {
774 return 'api_key';
775 }
776
777 $type = isset($integration['connection_type']) ? trim((string) $integration['connection_type']) : '';
778
779 return $type ?: 'api_key';
780 }
781
782 /**
783 * Returns API key input placeholder text.
784 *
785 * @param string $integration
786 *
787 * @return string
788 */
789 public static function getAPIKeyPlaceholder($integration = '')
790 {
791 $credential_fields = self::getCredentialFields($integration);
792 if (empty($credential_fields))
793 {
794 return firebox()->_('FB_INTEGRATION_API_KEY');
795 }
796
797 $field = reset($credential_fields);
798 $placeholder = isset($field['placeholder']) ? trim((string) $field['placeholder']) : '';
799
800 return $placeholder !== '' ? $placeholder : firebox()->_('FB_INTEGRATION_API_KEY');
801 }
802
803 /**
804 * Returns plugin state for plugin-based integrations.
805 *
806 * @param string $integration
807 *
808 * @return string active|inactive|missing
809 */
810 public static function getPluginState($integration = '')
811 {
812 if (self::getConnectionType($integration) !== 'plugin')
813 {
814 return 'missing';
815 }
816
817 $plugin_file = self::getPluginFile($integration);
818 if (!$plugin_file)
819 {
820 return 'missing';
821 }
822
823 if (!function_exists('is_plugin_active'))
824 {
825 require_once ABSPATH . 'wp-admin/includes/plugin.php';
826 }
827
828 if (is_plugin_active($plugin_file))
829 {
830 return 'active';
831 }
832
833 $plugins = get_plugins();
834 if (isset($plugins[$plugin_file]))
835 {
836 return 'inactive';
837 }
838
839 return 'missing';
840 }
841
842 /**
843 * Returns plugin install URL for plugin-based integrations.
844 *
845 * @param string $integration
846 *
847 * @return string
848 */
849 public static function getInstallPluginURL($integration = '')
850 {
851 $label = self::getIntegrationLabel($integration);
852
853 return admin_url('plugin-install.php?tab=search&type=term&s=' . rawurlencode($label));
854 }
855
856 /**
857 * Returns plugin activation URL for plugin-based integrations.
858 *
859 * @param string $integration
860 *
861 * @return string
862 */
863 public static function getActivatePluginURL($integration = '')
864 {
865 $plugin_file = self::getPluginFile($integration);
866 if (!$plugin_file)
867 {
868 return '';
869 }
870
871 $url = admin_url('plugins.php?action=activate&plugin=' . rawurlencode($plugin_file));
872
873 return wp_nonce_url($url, 'activate-plugin_' . $plugin_file);
874 }
875
876 /**
877 * Returns integration plugin file.
878 *
879 * @param string $integration
880 *
881 * @return string
882 */
883 public static function getPluginFile($integration = '')
884 {
885 if (!$integration = self::getIntegration($integration))
886 {
887 return '';
888 }
889
890 return isset($integration['plugin_file']) ? trim((string) $integration['plugin_file']) : '';
891 }
892
893 /**
894 * Returns required plan for an integration.
895 *
896 * @param string $integration
897 *
898 * @return string
899 */
900 public static function getRequiredPlan($integration = '')
901 {
902 if (!$integration = self::getIntegration($integration))
903 {
904 return '';
905 }
906
907 return isset($integration['required_plan']) ? strtolower(trim((string) $integration['required_plan'])) : '';
908 }
909
910 /**
911 * Returns required plan label for an integration.
912 *
913 * @param string $integration
914 *
915 * @return string
916 */
917 public static function getRequiredPlanLabel($integration = '')
918 {
919 $required_plan = self::getRequiredPlan($integration);
920
921 return !empty($required_plan) ? ucfirst($required_plan) : __('higher', 'firebox');
922 }
923
924 /**
925 * Returns whether the integration is locked by plan.
926 *
927 * @param string $integration
928 *
929 * @return bool
930 */
931 public static function isLocked($integration = '')
932 {
933 if (!$integration = self::getIntegration($integration))
934 {
935 return false;
936 }
937
938 return !empty($integration['locked']);
939 }
940
941 /**
942 * Returns whether current runtime is Pro.
943 *
944 * @return bool
945 */
946 private static function isProVersion()
947 {
948 $current_plan = self::getCurrentPlan();
949
950 // Any paid plan should expose paid integrations; required_plan handles exact tier access.
951 if ($current_plan && $current_plan !== 'free')
952 {
953 return true;
954 }
955
956 // Backward-compat fallback for runtimes that still key off license type only.
957 if (!defined('FBOX_LICENSE_TYPE'))
958 {
959 return false;
960 }
961
962 $license_type = strtolower(trim((string) FBOX_LICENSE_TYPE));
963
964 return in_array($license_type, ['pro', 'basic', 'growth'], true);
965 }
966
967 /**
968 * Normalizes integration metadata payload.
969 *
970 * @param string $slug
971 * @param array $integration
972 *
973 * @return array
974 */
975 private static function normalizeIntegrationEntry($slug = '', $integration = [])
976 {
977 $integration = is_array($integration) ? $integration : [];
978
979 return array_merge(
980 [
981 'slug' => $slug,
982 'locked' => false
983 ],
984 $integration
985 );
986 }
987
988 /**
989 * Returns current license plan.
990 *
991 * @return string
992 */
993 private static function getCurrentPlan()
994 {
995 if (!defined('FBOX_LICENSE_PLAN'))
996 {
997 return 'free';
998 }
999
1000 return strtolower(trim((string) FBOX_LICENSE_PLAN));
1001 }
1002
1003 /**
1004 * Returns whether the current plan meets the required plan.
1005 *
1006 * @param string $required_plan
1007 *
1008 * @return bool
1009 */
1010 private static function isRequiredPlanMet($required_plan = '')
1011 {
1012 $required_plan = strtolower(trim((string) $required_plan));
1013 $current_plan = self::getCurrentPlan();
1014
1015 switch ($required_plan)
1016 {
1017 case 'growth':
1018 return $current_plan === 'growth';
1019
1020 case 'pro':
1021 return in_array($current_plan, ['pro', 'growth'], true);
1022
1023 case 'basic':
1024 return in_array($current_plan, ['basic', 'pro', 'growth'], true);
1025
1026 default:
1027 return true;
1028 }
1029 }
1030
1031 /**
1032 * Returns upgrade URL for locked integrations.
1033 *
1034 * @param string $integration
1035 *
1036 * @return string
1037 */
1038 private static function getUpgradeURL($integration = '')
1039 {
1040 if (defined('FBOX_GO_PRO_URL'))
1041 {
1042 return sprintf(FBOX_GO_PRO_URL, strtolower(trim((string) $integration)) . '-integration');
1043 }
1044
1045 return \FPFramework\Base\Functions::getUTMURL(
1046 'https://www.fireplugins.com/upgrade/',
1047 '',
1048 'feature',
1049 strtolower(trim((string) $integration)) . '-integration'
1050 );
1051 }
1052
1053 /**
1054 * Returns credential fields decorated for Settings > Integrations UI.
1055 *
1056 * @param string $integration
1057 *
1058 * @return array
1059 */
1060 public static function getSettingsCredentialFields($integration = '')
1061 {
1062 $values = self::getGlobalCredentials($integration);
1063 $fields = [];
1064
1065 foreach (self::getCredentialFields($integration) as $field)
1066 {
1067 $key = $field['key'];
1068 $fields[] = [
1069 'key' => $key,
1070 'label' => $field['label'],
1071 'placeholder' => $field['placeholder'],
1072 'type' => $field['type'],
1073 'required' => !empty($field['required']),
1074 'value' => isset($values[$key]) ? (string) $values[$key] : ''
1075 ];
1076 }
1077
1078 return $fields;
1079 }
1080
1081 /**
1082 * Returns the stored global credentials for an integration.
1083 *
1084 * @param string $integration
1085 *
1086 * @return array
1087 */
1088 public static function getGlobalCredentials($integration = '')
1089 {
1090 $integration_slug = self::getIntegrationSlug($integration);
1091 $fields = self::getCredentialFields($integration);
1092 if (empty($fields))
1093 {
1094 return [];
1095 }
1096
1097 $settings = get_option('firebox_settings', []);
1098 if (!is_array($settings))
1099 {
1100 $settings = [];
1101 }
1102
1103 $credentials = [];
1104 foreach ($fields as $field)
1105 {
1106 $key = $field['key'];
1107 $settings_key = $field['settings_key'];
1108
1109 if (!isset($settings[$settings_key]))
1110 {
1111 $credentials[$key] = '';
1112 continue;
1113 }
1114
1115 $credentials[$key] = self::getGlobalAPIKeyEncryption()->decrypt($settings[$settings_key]);
1116 }
1117
1118 return self::normalizeCredentialValues($integration_slug, $credentials);
1119 }
1120
1121 /**
1122 * Returns the global API key.
1123 *
1124 * @param string $integration
1125 *
1126 * @return string
1127 */
1128 public static function getGlobalAPIKey($integration = '')
1129 {
1130 $credentials = self::getGlobalCredentials($integration);
1131
1132 return isset($credentials['api_key']) ? (string) $credentials['api_key'] : '';
1133 }
1134
1135 /**
1136 * Updates integration global credentials.
1137 *
1138 * @param string $integration
1139 * @param array $credentials
1140 *
1141 * @return bool
1142 */
1143 public static function setGlobalCredentials($integration = '', $credentials = [])
1144 {
1145 $integration_slug = self::getIntegrationSlug($integration);
1146 $fields = self::getCredentialFields($integration);
1147 $credentials = is_array($credentials) ? $credentials : [];
1148
1149 if (empty($fields))
1150 {
1151 return false;
1152 }
1153
1154 $credentials = self::normalizeCredentialValues($integration_slug, $credentials);
1155
1156 $settings = get_option('firebox_settings', []);
1157 $settings = is_array($settings) ? $settings : [];
1158
1159 foreach ($fields as $field)
1160 {
1161 $key = $field['key'];
1162 $settings_key = $field['settings_key'];
1163 $value = isset($credentials[$key]) && is_scalar($credentials[$key]) ? trim((string) $credentials[$key]) : '';
1164
1165 $encrypted = self::getGlobalAPIKeyEncryption()->encrypt($value);
1166
1167 /**
1168 * Fail closed. Storing the raw value under a name that implies encryption
1169 * would leave the credential in the database in cleartext with no signal
1170 * to anyone, so refuse the whole write instead.
1171 */
1172 if ($encrypted === false)
1173 {
1174 return false;
1175 }
1176
1177 $settings[$settings_key] = $encrypted;
1178 }
1179
1180 return update_option('firebox_settings', $settings);
1181 }
1182
1183 /**
1184 * Updates an integration global API key.
1185 *
1186 * @param string $integration
1187 * @param string $api_key
1188 *
1189 * @return bool
1190 */
1191 public static function setGlobalAPIKey($integration = '', $api_key = '')
1192 {
1193 $integration_slug = self::getIntegrationSlug($integration);
1194 $credentials = [
1195 'api_key' => $api_key
1196 ];
1197
1198 if ($integration_slug)
1199 {
1200 $credentials = self::normalizeCredentialValues($integration_slug, $credentials);
1201 }
1202
1203 return self::setGlobalCredentials($integration, $credentials);
1204 }
1205
1206 /**
1207 * Returns composed connection value consumed by the framework integration.
1208 *
1209 * @param string $integration
1210 * @param array $credentials
1211 *
1212 * @return string
1213 */
1214 public static function getConnectionValue($integration = '', $credentials = [])
1215 {
1216 $integration_entry = self::getIntegration($integration);
1217 $credentials = is_array($credentials) ? $credentials : [];
1218
1219 if (!$integration_entry)
1220 {
1221 return '';
1222 }
1223
1224 $template = isset($integration_entry['connection_value_template']) ? trim((string) $integration_entry['connection_value_template']) : '';
1225 if ($template === '')
1226 {
1227 return isset($credentials['api_key']) ? trim((string) $credentials['api_key']) : '';
1228 }
1229
1230 $value = preg_replace_callback('/\{([a-z0-9_]+)\}/i', function($matches) use ($credentials) {
1231 $key = isset($matches[1]) ? $matches[1] : '';
1232 return isset($credentials[$key]) ? trim((string) $credentials[$key]) : '';
1233 }, $template);
1234
1235 return trim((string) $value);
1236 }
1237
1238 /**
1239 * Returns composed connection value from stored credentials.
1240 *
1241 * @param string $integration
1242 *
1243 * @return string
1244 */
1245 public static function getGlobalConnectionValue($integration = '')
1246 {
1247 return self::getConnectionValue($integration, self::getGlobalCredentials($integration));
1248 }
1249
1250 /**
1251 * Normalizes legacy combined credential formats to individual fields.
1252 *
1253 * @param string $integration
1254 * @param array $credentials
1255 *
1256 * @return array
1257 */
1258 private static function normalizeCredentialValues($integration = '', $credentials = [])
1259 {
1260 $integration = self::getIntegrationSlug($integration);
1261 $credentials = is_array($credentials) ? $credentials : [];
1262
1263 if ($integration !== 'activecampaign')
1264 {
1265 return $credentials;
1266 }
1267
1268 $api_url = isset($credentials['api_url']) ? trim((string) $credentials['api_url']) : '';
1269 $api_key = isset($credentials['api_key']) ? trim((string) $credentials['api_key']) : '';
1270 if ($api_url !== '' || $api_key === '')
1271 {
1272 return $credentials;
1273 }
1274
1275 $parts = [];
1276 if (strpos($api_key, '|') !== false)
1277 {
1278 $parts = array_map('trim', explode('|', $api_key, 2));
1279 }
1280 else if (preg_match('/\r\n|\r|\n/', $api_key))
1281 {
1282 $parts = array_map('trim', preg_split('/\r\n|\r|\n/', $api_key));
1283 $parts = array_values(array_filter($parts));
1284 }
1285
1286 if (count($parts) < 2)
1287 {
1288 return $credentials;
1289 }
1290
1291 $credentials['api_url'] = (string) $parts[0];
1292 $credentials['api_key'] = (string) $parts[1];
1293
1294 return $credentials;
1295 }
1296
1297 /**
1298 * Returns API key encryption helper.
1299 *
1300 * @return \FireBox\Core\Helpers\Encryption
1301 */
1302 private static function getGlobalAPIKeyEncryption()
1303 {
1304 static $encryption = null;
1305 if ($encryption === null)
1306 {
1307 $encryption = new Encryption();
1308 }
1309
1310 return $encryption;
1311 }
1312
1313 /**
1314 * Determines whether there is an active global connection.
1315 *
1316 * @param string $integration
1317 *
1318 * @return bool
1319 */
1320 public static function hasGlobalConnection($integration = '')
1321 {
1322 $credentials = self::getGlobalCredentials($integration);
1323 $fields = self::getCredentialFields($integration);
1324
1325 if (empty($fields))
1326 {
1327 return false;
1328 }
1329
1330 foreach ($fields as $field)
1331 {
1332 if (empty($field['required']))
1333 {
1334 continue;
1335 }
1336
1337 $key = $field['key'];
1338 $value = isset($credentials[$key]) ? trim((string) $credentials[$key]) : '';
1339 if ($value === '')
1340 {
1341 return false;
1342 }
1343 }
1344
1345 return true;
1346 }
1347
1348 /**
1349 * Returns legacy action API key from block attributes.
1350 *
1351 * @param string $integration
1352 * @param array $attrs
1353 *
1354 * @return string
1355 */
1356 public static function getLegacyActionAPIKey($integration = '', $attrs = [])
1357 {
1358 $integration = self::getIntegrationSlug($integration);
1359 $attrs = is_array($attrs) ? $attrs : [];
1360
1361 if (!$integration)
1362 {
1363 return '';
1364 }
1365
1366 switch ($integration)
1367 {
1368 case 'mailchimp':
1369 return isset($attrs['mailchimpAPIKey']) ? trim((string) $attrs['mailchimpAPIKey']) : '';
1370
1371 case 'brevo':
1372 return isset($attrs['brevoAPIKey']) ? trim((string) $attrs['brevoAPIKey']) : '';
1373
1374 default:
1375 return '';
1376 }
1377 }
1378
1379 /**
1380 * Determines whether the action has a legacy API key.
1381 *
1382 * @param string $integration
1383 * @param array $attrs
1384 *
1385 * @return bool
1386 */
1387 public static function hasLegacyActionAPIKey($integration = '', $attrs = [])
1388 {
1389 return !empty(self::getLegacyActionAPIKey($integration, $attrs));
1390 }
1391
1392 /**
1393 * Resolve effective API key used by actions.
1394 * Priority: legacy action key -> global key.
1395 *
1396 * @param string $integration
1397 * @param array $attrs
1398 * @param string|null $legacy_api_key
1399 *
1400 * @return string
1401 */
1402 public static function resolveActionAPIKey($integration = '', $attrs = [], $legacy_api_key = null)
1403 {
1404 $integration = self::getIntegrationSlug($integration);
1405 $attrs = is_array($attrs) ? $attrs : [];
1406
1407 if (!$integration)
1408 {
1409 return '';
1410 }
1411
1412 if ($legacy_api_key === null)
1413 {
1414 $legacy_api_key = self::getLegacyActionAPIKey($integration, $attrs);
1415 }
1416
1417 $legacy_api_key = trim((string) $legacy_api_key);
1418 if (!empty($legacy_api_key))
1419 {
1420 return $legacy_api_key;
1421 }
1422
1423 return self::getGlobalConnectionValue($integration);
1424 }
1425
1426 /**
1427 * Returns integrations registry used by the Form block editor.
1428 *
1429 * @return array
1430 */
1431 public static function getFormEditorIntegrationsRegistry()
1432 {
1433 $registry = [];
1434
1435 foreach (self::getVisibleIntegrations() as $integration)
1436 {
1437 $slug = $integration['slug'];
1438 $action = isset($integration['class_name']) ? trim((string) $integration['class_name']) : '';
1439 if (!$action)
1440 {
1441 continue;
1442 }
1443
1444 $registry[$slug] = [
1445 'slug' => $slug,
1446 'label' => $integration['label'],
1447 'action' => $action,
1448 'connected' => self::isConnected($slug),
1449 'locked' => self::isLocked($slug),
1450 'requiredPlan' => self::getRequiredPlan($slug),
1451 'requiredPlanLabel' => self::getRequiredPlan($slug) ? self::getRequiredPlanLabel($slug) : '',
1452 'connectionType' => self::getConnectionType($slug),
1453 'form' => self::getFormSchema($slug)
1454 ];
1455 }
1456
1457 $registry = apply_filters('firebox/form/integrations_registry', $registry);
1458
1459 return is_array($registry) ? $registry : [];
1460 }
1461
1462 /**
1463 * Returns normalized Form integration schema for a given integration slug.
1464 *
1465 * @param string $slug
1466 *
1467 * @return array
1468 */
1469 private static function getFormSchema($slug = '')
1470 {
1471 $integration = self::getIntegration($slug);
1472 $integration = is_array($integration) ? $integration : [];
1473 $form = isset($integration['form']) && is_array($integration['form']) ? $integration['form'] : [];
1474 $form = array_replace_recursive([
1475 'legacyApiKeyField' => '',
1476 'defaults' => [],
1477 'legacyMap' => [],
1478 'fields' => []
1479 ], $form);
1480
1481 $legacy_map = [];
1482 if (!empty($form['legacyMap']) && is_array($form['legacyMap']))
1483 {
1484 foreach ($form['legacyMap'] as $setting_key => $legacy_field)
1485 {
1486 $setting_key = trim((string) $setting_key);
1487 $legacy_field = trim((string) $legacy_field);
1488 if ($setting_key === '' || $legacy_field === '')
1489 {
1490 continue;
1491 }
1492
1493 $legacy_map[$setting_key] = $legacy_field;
1494 }
1495 }
1496
1497 $fields = [];
1498 if (!empty($form['fields']) && is_array($form['fields']))
1499 {
1500 foreach ($form['fields'] as $field)
1501 {
1502 if (!is_array($field))
1503 {
1504 continue;
1505 }
1506
1507 $type = strtolower(trim((string) (isset($field['type']) ? $field['type'] : '')));
1508 $key = trim((string) (isset($field['key']) ? $field['key'] : ''));
1509
1510 if (!in_array($type, ['list', 'select', 'toggle'], true) || $key === '')
1511 {
1512 continue;
1513 }
1514
1515 $normalized_field = [
1516 'type' => $type,
1517 'key' => $key,
1518 'label' => !empty($field['label']) ? self::translateRegistryText($field['label']) : ''
1519 ];
1520
1521 if ($type === 'select')
1522 {
1523 $options = [];
1524 if (!empty($field['options']) && is_array($field['options']))
1525 {
1526 foreach ($field['options'] as $option)
1527 {
1528 if (!is_array($option) || !array_key_exists('value', $option))
1529 {
1530 continue;
1531 }
1532
1533 $options[] = [
1534 'value' => $option['value'],
1535 'label' => !empty($option['label']) ? self::translateRegistryText($option['label']) : ''
1536 ];
1537 }
1538 }
1539
1540 if (empty($options))
1541 {
1542 continue;
1543 }
1544
1545 $normalized_field['options'] = $options;
1546 }
1547
1548 $fields[] = $normalized_field;
1549 }
1550 }
1551
1552 return [
1553 'legacyApiKeyField' => trim((string) $form['legacyApiKeyField']),
1554 'defaults' => is_array($form['defaults']) ? $form['defaults'] : [],
1555 'legacyMap' => $legacy_map,
1556 'fields' => $fields
1557 ];
1558 }
1559
1560 /**
1561 * Translates known integration registry labels/placeholders.
1562 *
1563 * @param string $text
1564 *
1565 * @return string
1566 */
1567 private static function translateRegistryText($text = '')
1568 {
1569 $text = trim((string) $text);
1570
1571 switch ($text)
1572 {
1573 case 'API Key':
1574 return __('API Key', 'firebox');
1575
1576 case 'API URL':
1577 return __('API URL', 'firebox');
1578
1579 case 'List':
1580 return __('List', 'firebox');
1581
1582 case 'Double Optin':
1583 return __('Double Optin', 'firebox');
1584
1585 case 'Update existing user':
1586 return __('Update existing user', 'firebox');
1587
1588 case 'Group':
1589 return __('Group', 'firebox');
1590
1591 case 'Subscriber Status':
1592 return __('Subscriber Status', 'firebox');
1593
1594 case 'Active':
1595 return __('Active', 'firebox');
1596
1597 case 'Unconfirmed':
1598 return __('Unconfirmed', 'firebox');
1599
1600 case 'Unsubscribed':
1601 return __('Unsubscribed', 'firebox');
1602
1603 case 'Bounced':
1604 return __('Bounced', 'firebox');
1605
1606 case 'Junk':
1607 return __('Junk', 'firebox');
1608
1609 case 'Test Mode':
1610 return __('Test Mode', 'firebox');
1611
1612 case 'https://your-account.api-us1.com':
1613 return __('https://your-account.api-us1.com', 'firebox');
1614
1615 case 'Organization ID (OID), e.g. 00DXXXXXXXXXXXX':
1616 return __('Organization ID (OID), e.g. 00DXXXXXXXXXXXX', 'firebox');
1617 }
1618
1619 return $text;
1620 }
1621 }
1622