PluginProbe
FireBox – WooCommerce Popup Builder, Exit Intent Popup, Email Optin & Cart Abandonment / 3.1.13
FireBox – WooCommerce Popup Builder, Exit Intent Popup, Email Optin & Cart Abandonment v3.1.13
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 / Form / Actions / Ajax.php

Ajax.php in FireBox – WooCommerce Popup Builder, Exit Intent Popup, Email Optin & Cart Abandonment 3.1.13, at Inc/Core/Form/Actions/Ajax.php

673 lines 17.3 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.13 Free
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\Form\Actions;
13
14 if (!defined('ABSPATH'))
15 {
16 exit; // Exit if accessed directly.
17 }
18
19 class Ajax
20 {
21 public function __construct()
22 {
23 $this->setupAjax();
24 }
25
26 /**
27 * Setup ajax requests
28 *
29 * @return void
30 */
31 public function setupAjax()
32 {
33 add_action('wp_ajax_fb_get_integration_lists', [$this, 'fb_get_integration_lists']);
34 add_action('wp_ajax_fb_connect_integration', [$this, 'fb_connect_integration']);
35 add_action('wp_ajax_fb_disconnect_integration', [$this, 'fb_disconnect_integration']);
36 }
37
38 /**
39 * Retrieve the Integration list given the API Key.
40 *
41 * @return void
42 */
43 public function fb_get_integration_lists()
44 {
45 $this->canRun();
46
47 if (!$this->verifyNonce())
48 {
49 $this->sendResponse([
50 'error' => true,
51 'message' => fpframework()->_('FPF_CANNOT_VERIFY_REQUEST')
52 ]);
53 }
54
55 $integrationClassName = $this->getPostedIntegrationClassName();
56 $managedIntegration = \FireBox\Core\Helpers\Integrations::getIntegration($integrationClassName);
57 if ($managedIntegration && \FireBox\Core\Helpers\Integrations::isLocked($integrationClassName))
58 {
59 $this->sendResponse([
60 'error' => true,
61 'code' => 'locked',
62 'message' => $this->getLockedIntegrationMessage($integrationClassName)
63 ]);
64 }
65
66 $connectionType = \FireBox\Core\Helpers\Integrations::getConnectionType($integrationClassName);
67 $isApiKeyConnection = $connectionType === 'api_key';
68 $apiKeyParamRaw = isset($_POST['api_key']) ? wp_unslash($_POST['api_key']) : null;
69 $apiKeyParam = is_scalar($apiKeyParamRaw) ? trim((string) $apiKeyParamRaw) : null;
70 $postedCredentials = $this->getPostedCredentials($integrationClassName);
71 $connectionValue = '';
72 $connectedVia = '';
73
74 // Resolve API key
75 if ($isApiKeyConnection)
76 {
77 if ($this->hasProvidedCredentialValues($postedCredentials))
78 {
79 $connectionValue = \FireBox\Core\Helpers\Integrations::getConnectionValue($integrationClassName, $postedCredentials);
80 $connectedVia = 'legacy';
81 }
82 else if ($apiKeyParam !== null && $apiKeyParam !== '' && $apiKeyParam !== 'skip')
83 {
84 $connectionValue = $apiKeyParam;
85 $connectedVia = 'legacy';
86 }
87 else if ($globalConnectionValue = \FireBox\Core\Helpers\Integrations::getGlobalConnectionValue($integrationClassName))
88 {
89 $connectionValue = $globalConnectionValue;
90 $connectedVia = 'global';
91 }
92 else
93 {
94 $this->sendResponse([
95 'error' => true,
96 'code' => 'not_connected',
97 'message' => sprintf(firebox()->_('FB_INTEGRATION_CONNECT_FIRST_IN_SETTINGS'), $this->getIntegrationLabel($integrationClassName)),
98 'help' => '<a href="' . esc_url(admin_url('admin.php?page=firebox-settings#integrations')) . '" target="_blank" rel="noreferrer noopener">' . esc_html(fpframework()->_('FPF_INTEGRATIONS')) . '</a>'
99 ]);
100 }
101 }
102 // Plugin/non-auth integrations
103 else
104 {
105 // Keep legacy "skip" sentinel so framework integrations can bypass API-key validation paths.
106 $connectionValue = ($apiKeyParam === null || $apiKeyParam === '') ? 'skip' : (string) $apiKeyParam;
107 }
108
109 // Build integration client
110 $class = '\FPFramework\Base\Integrations\\' . $integrationClassName;
111 if (!class_exists($class))
112 {
113 $this->sendResponse([
114 'error' => true,
115 'message' => fpframework()->_('FPF_NO_SUCH_INTEGRATION_EXISTS')
116 ]);
117
118 return;
119 }
120
121 try {
122 $integrationClass = new $class([
123 'api' => $connectionValue
124 ]);
125 }
126 catch (\Throwable $e)
127 {
128 $this->sendResponse([
129 'error' => true,
130 'message' => $this->formatIntegrationErrorMessage($e->getMessage(), $integrationClassName)
131 ]);
132
133 return;
134 }
135
136 // Ensure getLists method exists
137 if (!method_exists($integrationClass, 'getLists'))
138 {
139 $this->sendResponse([
140 'error' => true,
141 'message' => fpframework()->_('FPF_INTEGRATION_INVALID')
142 ]);
143 }
144
145 $lists = [];
146 try {
147 $lists = $integrationClass->getLists();
148 }
149 catch (\Throwable $e)
150 {
151 $this->sendResponse([
152 'error' => true,
153 'message' => $this->formatIntegrationErrorMessage($e->getMessage(), $integrationClassName)
154 ]);
155 }
156
157 $requestSuccessful = method_exists($integrationClass, 'success') ? $integrationClass->success() : true;
158 if (!$requestSuccessful)
159 {
160 $errorMessage = method_exists($integrationClass, 'getLastError') ? $integrationClass->getLastError() : '';
161 $errorMessage = trim(wp_strip_all_tags((string) $errorMessage));
162
163 // Some plugin-based integrations don't explicitly set success() for list retrieval.
164 // Only treat unsuccessful state as fatal when an actual error message exists.
165 if ($errorMessage !== '')
166 {
167 $this->sendResponse([
168 'error' => true,
169 'message' => $this->formatIntegrationErrorMessage($errorMessage, $integrationClassName)
170 ]);
171 }
172 }
173
174 if (!is_array($lists) || !count($lists))
175 {
176 $this->sendResponse([
177 'error' => true,
178 'message' => sprintf(fpframework()->_('FPF_INTEGRATION_ACCOUNT_HAS_NO_LISTS'), $this->getIntegrationLabel($integrationClassName))
179 ]);
180 }
181
182 $responseLists = [
183 [
184 'label' => fpframework()->_('FPF_SELECT_A_LIST'),
185 'value' => null
186 ]
187 ];
188
189 foreach ($lists as $list)
190 {
191 $listName = '';
192 $listID = '';
193
194 if (is_array($list))
195 {
196 $listName = isset($list['name']) ? trim(wp_strip_all_tags((string) $list['name'])) : '';
197 $listID = isset($list['id']) && is_scalar($list['id']) ? (string) $list['id'] : '';
198 }
199 else if (is_object($list))
200 {
201 $listName = isset($list->name) ? trim(wp_strip_all_tags((string) $list->name)) : '';
202 $listID = isset($list->id) && is_scalar($list->id) ? (string) $list->id : '';
203 }
204
205 if ($listName === '' || $listID === '')
206 {
207 continue;
208 }
209
210 $responseLists[] = [
211 'label' => $listName,
212 'value' => $listID
213 ];
214 }
215
216 if (count($responseLists) === 1)
217 {
218 $this->sendResponse([
219 'error' => true,
220 'message' => sprintf(fpframework()->_('FPF_INTEGRATION_ACCOUNT_HAS_NO_LISTS'), $this->getIntegrationLabel($integrationClassName))
221 ]);
222 }
223
224 $payload = [
225 'error' => false,
226 'message' => [
227 'lists' => $responseLists
228 ]
229 ];
230
231 if (!empty($connectedVia))
232 {
233 $payload['connected_via'] = $connectedVia;
234 }
235
236 $this->sendResponse($payload);
237 }
238
239 /**
240 * Connect an integration and persist API key in global settings.
241 *
242 * @return void
243 */
244 public function fb_connect_integration()
245 {
246 $this->canRun(true);
247
248 if (!$this->verifyNonce())
249 {
250 $this->sendResponse([
251 'error' => true,
252 'message' => fpframework()->_('FPF_CANNOT_VERIFY_REQUEST')
253 ]);
254 }
255
256 $integrationClassName = $this->getPostedIntegrationClassName();
257 $managedIntegration = \FireBox\Core\Helpers\Integrations::getIntegration($integrationClassName);
258
259 if (!$managedIntegration)
260 {
261 $this->sendResponse([
262 'error' => true,
263 'message' => fpframework()->_('FPF_NO_SUCH_INTEGRATION_EXISTS')
264 ]);
265 }
266
267 if (\FireBox\Core\Helpers\Integrations::isLocked($integrationClassName))
268 {
269 $this->sendResponse([
270 'error' => true,
271 'message' => $this->getLockedIntegrationMessage($integrationClassName)
272 ]);
273 }
274
275 if (\FireBox\Core\Helpers\Integrations::getConnectionType($integrationClassName) !== 'api_key')
276 {
277 $this->sendResponse([
278 'error' => true,
279 'message' => fpframework()->_('FPF_INTEGRATION_INVALID')
280 ]);
281 }
282
283 $credentials = $this->getPostedCredentials($integrationClassName);
284 if ($missingField = $this->getMissingRequiredCredentialField($integrationClassName, $credentials))
285 {
286 $this->sendResponse([
287 'error' => true,
288 // translators: %s: Credential field label or "this value" if label is not provided.
289 'message' => sprintf(__('Please enter %s.', 'firebox'), $missingField)
290 ]);
291 }
292
293 $connectionValue = \FireBox\Core\Helpers\Integrations::getConnectionValue($integrationClassName, $credentials);
294
295 $class = '\FPFramework\Base\Integrations\\' . $integrationClassName;
296 if (!class_exists($class))
297 {
298 $this->sendResponse([
299 'error' => true,
300 'message' => fpframework()->_('FPF_NO_SUCH_INTEGRATION_EXISTS')
301 ]);
302
303 return;
304 }
305
306 try {
307 $integrationInstance = new $class([
308 'api' => $connectionValue
309 ]);
310 }
311 catch (\Throwable $e)
312 {
313 $this->sendResponse([
314 'error' => true,
315 'message' => $this->formatIntegrationErrorMessage($e->getMessage(), $integrationClassName)
316 ]);
317
318 return;
319 }
320
321 if (!method_exists($integrationInstance, 'verifyConnection'))
322 {
323 $this->sendResponse([
324 'error' => true,
325 'message' => fpframework()->_('FPF_INTEGRATION_INVALID')
326 ]);
327 }
328
329 try {
330 $integrationInstance->verifyConnection();
331 }
332 catch (\Throwable $e)
333 {
334 $this->sendResponse([
335 'error' => true,
336 'message' => $this->formatIntegrationErrorMessage($e->getMessage(), $integrationClassName)
337 ]);
338 }
339
340 if (method_exists($integrationInstance, 'success') && !$integrationInstance->success())
341 {
342 $errorMessage = method_exists($integrationInstance, 'getLastError') ? $integrationInstance->getLastError() : '';
343 $errorMessage = $this->formatIntegrationErrorMessage($errorMessage, $integrationClassName);
344
345 $this->sendResponse([
346 'error' => true,
347 'message' => $errorMessage
348 ]);
349 }
350
351 if (\FireBox\Core\Helpers\Integrations::setGlobalCredentials($integrationClassName, $credentials) === false)
352 {
353 $this->sendResponse([
354 'error' => true,
355 'message' => firebox()->_('FB_INTEGRATION_CONNECT_FAILED')
356 ]);
357 }
358
359 $payload = [
360 'error' => false,
361 'message' => sprintf(firebox()->_('FB_INTEGRATION_CONNECTED_SUCCESS'), $this->getIntegrationLabel($integrationClassName)),
362 'connected' => true
363 ];
364
365 $this->sendResponse($payload);
366 }
367
368 /**
369 * Disconnect integration from global settings.
370 *
371 * @return void
372 */
373 public function fb_disconnect_integration()
374 {
375 $this->canRun(true);
376
377 if (!$this->verifyNonce())
378 {
379 $this->sendResponse([
380 'error' => true,
381 'message' => fpframework()->_('FPF_CANNOT_VERIFY_REQUEST')
382 ]);
383 }
384
385 $integrationClassName = $this->getPostedIntegrationClassName();
386 $managedIntegration = \FireBox\Core\Helpers\Integrations::getIntegration($integrationClassName);
387
388 if (!$managedIntegration)
389 {
390 $this->sendResponse([
391 'error' => true,
392 'message' => fpframework()->_('FPF_NO_SUCH_INTEGRATION_EXISTS')
393 ]);
394 }
395
396 if (\FireBox\Core\Helpers\Integrations::isLocked($integrationClassName))
397 {
398 $this->sendResponse([
399 'error' => true,
400 'message' => $this->getLockedIntegrationMessage($integrationClassName)
401 ]);
402 }
403
404 if (\FireBox\Core\Helpers\Integrations::getConnectionType($integrationClassName) !== 'api_key')
405 {
406 $this->sendResponse([
407 'error' => true,
408 'message' => fpframework()->_('FPF_INTEGRATION_INVALID')
409 ]);
410 }
411
412 \FireBox\Core\Helpers\Integrations::setGlobalCredentials($integrationClassName, []);
413
414 $this->sendResponse([
415 'error' => false,
416 'message' => sprintf(firebox()->_('FB_INTEGRATION_DISCONNECTED_SUCCESS'), $this->getIntegrationLabel($integrationClassName)),
417 'connected' => false
418 ]);
419 }
420
421 /**
422 * Verify AJAX nonce.
423 *
424 * @return bool
425 */
426 private function verifyNonce()
427 {
428 $nonceRaw = isset($_POST['nonce']) ? wp_unslash($_POST['nonce']) : '';
429 $nonce = is_scalar($nonceRaw) ? sanitize_text_field((string) $nonceRaw) : '';
430 return wp_verify_nonce($nonce, 'fpf_js_nonce');
431 }
432
433 /**
434 * Ensure user can run integration requests.
435 *
436 * Reading an account's lists is a configuration task. Writing or clearing the
437 * site-wide credentials is an administrative one — "read_fireboxes" gates the
438 * analytics dashboard and has no business authorising either of those.
439 *
440 * @param bool $write Whether the request changes stored credentials.
441 *
442 * @return void
443 */
444 private function canRun($write = false)
445 {
446 $capability = $write ? 'manage_options' : 'edit_fireboxes';
447
448 /**
449 * Allows a site to relax or tighten who may manage integrations.
450 *
451 * @param string $capability
452 * @param bool $write
453 */
454 $capability = apply_filters('firebox/integrations/capability', $capability, $write);
455
456 if (!current_user_can($capability))
457 {
458 $this->sendResponse([
459 'error' => true,
460 'message' => fpframework()->_('FPF_CANNOT_VERIFY_REQUEST')
461 ]);
462 }
463 }
464
465 /**
466 * Sends JSON response and terminates request.
467 *
468 * @param array $payload
469 *
470 * @return void
471 */
472 private function sendResponse($payload = [])
473 {
474 wp_send_json($payload);
475 }
476
477 /**
478 * Returns posted integration class name or sends an error response.
479 *
480 * @return string
481 */
482 private function getPostedIntegrationClassName()
483 {
484 $integrationRaw = isset($_POST['integration']) ? wp_unslash($_POST['integration']) : '';
485 $integration = is_scalar($integrationRaw) ? sanitize_text_field((string) $integrationRaw) : '';
486 if (!$integration)
487 {
488 $this->sendResponse([
489 'error' => true,
490 'message' => fpframework()->_('FPF_NO_INTEGRATION_SUPPLIED')
491 ]);
492 }
493
494 $integrationClassName = $this->getIntegrationClassName($integration);
495 if ($integrationClassName === '')
496 {
497 $this->sendResponse([
498 'error' => true,
499 'message' => fpframework()->_('FPF_NO_SUCH_INTEGRATION_EXISTS')
500 ]);
501 }
502
503 return $integrationClassName;
504 }
505
506 /**
507 * Normalizes integration to framework class name.
508 *
509 * @param string $integration
510 *
511 * @return string
512 */
513 private function getIntegrationClassName($integration = '')
514 {
515 if ($className = \FireBox\Core\Helpers\Integrations::getFrameworkIntegrationClassName($integration))
516 {
517 return trim((string) $className);
518 }
519
520 return '';
521 }
522
523 /**
524 * Returns sanitized posted credentials for an integration.
525 *
526 * @param string $integrationClassName
527 *
528 * @return array
529 */
530 private function getPostedCredentials($integrationClassName = '')
531 {
532 $credentials = [];
533 $rawCredentials = isset($_POST['credentials']) ? wp_unslash($_POST['credentials']) : '';
534
535 if (is_string($rawCredentials) && trim($rawCredentials) !== '')
536 {
537 $decoded = json_decode($rawCredentials, true);
538 if (is_array($decoded))
539 {
540 $credentials = $decoded;
541 }
542 }
543
544 $apiKeyRaw = isset($_POST['api_key']) ? wp_unslash($_POST['api_key']) : null;
545 if (!array_key_exists('api_key', $credentials) && is_scalar($apiKeyRaw))
546 {
547 $credentials['api_key'] = (string) $apiKeyRaw;
548 }
549
550 $sanitized = [];
551 foreach (\FireBox\Core\Helpers\Integrations::getCredentialFields($integrationClassName) as $field)
552 {
553 $key = $field['key'];
554 $sanitized[$key] = isset($credentials[$key]) && is_scalar($credentials[$key])
555 ? trim((string) $credentials[$key])
556 : '';
557 }
558
559 return $sanitized;
560 }
561
562 /**
563 * Returns whether any credential value has been supplied.
564 *
565 * @param array $credentials
566 *
567 * @return bool
568 */
569 private function hasProvidedCredentialValues($credentials = [])
570 {
571 foreach ((array) $credentials as $value)
572 {
573 if (trim((string) $value) !== '')
574 {
575 return true;
576 }
577 }
578
579 return false;
580 }
581
582 /**
583 * Returns the first missing required credential label.
584 *
585 * @param string $integrationClassName
586 * @param array $credentials
587 *
588 * @return string
589 */
590 private function getMissingRequiredCredentialField($integrationClassName = '', $credentials = [])
591 {
592 foreach (\FireBox\Core\Helpers\Integrations::getCredentialFields($integrationClassName) as $field)
593 {
594 if (empty($field['required']))
595 {
596 continue;
597 }
598
599 $key = $field['key'];
600 $value = isset($credentials[$key]) ? trim((string) $credentials[$key]) : '';
601 if ($value === '')
602 {
603 return !empty($field['label']) ? $field['label'] : $key;
604 }
605 }
606
607 return '';
608 }
609
610 /**
611 * Returns human readable integration label.
612 *
613 * @param string $integrationClassName
614 *
615 * @return string
616 */
617 private function getIntegrationLabel($integrationClassName = '')
618 {
619 $label = \FireBox\Core\Helpers\Integrations::getIntegrationLabel($integrationClassName);
620 return !empty($label) ? $label : $integrationClassName;
621 }
622
623 /**
624 * Returns locked integration message with dynamic plan requirement.
625 *
626 * @param string $integrationClassName
627 *
628 * @return string
629 */
630 private function getLockedIntegrationMessage($integrationClassName = '')
631 {
632 $label = $this->getIntegrationLabel($integrationClassName);
633 $plan_label = \FireBox\Core\Helpers\Integrations::getRequiredPlanLabel($integrationClassName);
634
635 /* translators: 1: Integration label, 2: Required plan label. */
636 return sprintf(__('%1$s is available in the %2$s plan.', 'firebox'), $label, $plan_label);
637 }
638
639 /**
640 * Formats integration error messages consistently.
641 *
642 * @param string $message
643 * @param string $integrationClassName
644 *
645 * @return string
646 */
647 private function formatIntegrationErrorMessage($message = '', $integrationClassName = '')
648 {
649 $message = trim(wp_strip_all_tags((string) $message));
650 if ($message !== '')
651 {
652 return $message;
653 }
654
655 $integrationLabel = $this->getIntegrationLabel($integrationClassName);
656 $connectionType = \FireBox\Core\Helpers\Integrations::getConnectionType($integrationClassName);
657 if ($connectionType === 'plugin')
658 {
659 return sprintf(
660 /* translators: %s: Integration label. */
661 __('%s request failed. Please make sure the related plugin is installed and active.', 'firebox'),
662 $integrationLabel
663 );
664 }
665
666 return sprintf(
667 /* translators: %s: Integration label. */
668 __('%s request failed. Please verify the API key and permissions.', 'firebox'),
669 $integrationLabel
670 );
671 }
672 }
673