PluginProbe
FireBox – WooCommerce Popup Builder, Exit Intent Popup, Email Optin & Cart Abandonment / 3.1.9
FireBox – WooCommerce Popup Builder, Exit Intent Popup, Email Optin & Cart Abandonment v3.1.9
3.1.14 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 All 123 releases
firebox / Inc / Core / Form / Actions / Ajax.php

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

643 lines 16.5 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.9 Free
5 *
6 * @author FirePlugins <[email protected]>
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
119 try {
120 $integrationClass = new $class([
121 'api' => $connectionValue
122 ]);
123 }
124 catch (\Throwable $e)
125 {
126 $this->sendResponse([
127 'error' => true,
128 'message' => $this->formatIntegrationErrorMessage($e->getMessage(), $integrationClassName)
129 ]);
130 }
131
132 // Ensure getLists method exists
133 if (!method_exists($integrationClass, 'getLists'))
134 {
135 $this->sendResponse([
136 'error' => true,
137 'message' => fpframework()->_('FPF_INTEGRATION_INVALID')
138 ]);
139 }
140
141 $lists = [];
142 try {
143 $lists = $integrationClass->getLists();
144 }
145 catch (\Throwable $e)
146 {
147 $this->sendResponse([
148 'error' => true,
149 'message' => $this->formatIntegrationErrorMessage($e->getMessage(), $integrationClassName)
150 ]);
151 }
152
153 $requestSuccessful = method_exists($integrationClass, 'success') ? $integrationClass->success() : true;
154 if (!$requestSuccessful)
155 {
156 $errorMessage = method_exists($integrationClass, 'getLastError') ? $integrationClass->getLastError() : '';
157 $errorMessage = trim(wp_strip_all_tags((string) $errorMessage));
158
159 // Some plugin-based integrations don't explicitly set success() for list retrieval.
160 // Only treat unsuccessful state as fatal when an actual error message exists.
161 if ($errorMessage !== '')
162 {
163 $this->sendResponse([
164 'error' => true,
165 'message' => $this->formatIntegrationErrorMessage($errorMessage, $integrationClassName)
166 ]);
167 }
168 }
169
170 if (!is_array($lists) || !count($lists))
171 {
172 $this->sendResponse([
173 'error' => true,
174 'message' => sprintf(fpframework()->_('FPF_INTEGRATION_ACCOUNT_HAS_NO_LISTS'), $this->getIntegrationLabel($integrationClassName))
175 ]);
176 }
177
178 $responseLists = [
179 [
180 'label' => fpframework()->_('FPF_SELECT_A_LIST'),
181 'value' => null
182 ]
183 ];
184
185 foreach ($lists as $list)
186 {
187 $listName = '';
188 $listID = '';
189
190 if (is_array($list))
191 {
192 $listName = isset($list['name']) ? trim(wp_strip_all_tags((string) $list['name'])) : '';
193 $listID = isset($list['id']) && is_scalar($list['id']) ? (string) $list['id'] : '';
194 }
195 else if (is_object($list))
196 {
197 $listName = isset($list->name) ? trim(wp_strip_all_tags((string) $list->name)) : '';
198 $listID = isset($list->id) && is_scalar($list->id) ? (string) $list->id : '';
199 }
200
201 if ($listName === '' || $listID === '')
202 {
203 continue;
204 }
205
206 $responseLists[] = [
207 'label' => $listName,
208 'value' => $listID
209 ];
210 }
211
212 if (count($responseLists) === 1)
213 {
214 $this->sendResponse([
215 'error' => true,
216 'message' => sprintf(fpframework()->_('FPF_INTEGRATION_ACCOUNT_HAS_NO_LISTS'), $this->getIntegrationLabel($integrationClassName))
217 ]);
218 }
219
220 $payload = [
221 'error' => false,
222 'message' => [
223 'lists' => $responseLists
224 ]
225 ];
226
227 if (!empty($connectedVia))
228 {
229 $payload['connected_via'] = $connectedVia;
230 }
231
232 $this->sendResponse($payload);
233 }
234
235 /**
236 * Connect an integration and persist API key in global settings.
237 *
238 * @return void
239 */
240 public function fb_connect_integration()
241 {
242 $this->canRun();
243
244 if (!$this->verifyNonce())
245 {
246 $this->sendResponse([
247 'error' => true,
248 'message' => fpframework()->_('FPF_CANNOT_VERIFY_REQUEST')
249 ]);
250 }
251
252 $integrationClassName = $this->getPostedIntegrationClassName();
253 $managedIntegration = \FireBox\Core\Helpers\Integrations::getIntegration($integrationClassName);
254
255 if (!$managedIntegration)
256 {
257 $this->sendResponse([
258 'error' => true,
259 'message' => fpframework()->_('FPF_NO_SUCH_INTEGRATION_EXISTS')
260 ]);
261 }
262
263 if (\FireBox\Core\Helpers\Integrations::isLocked($integrationClassName))
264 {
265 $this->sendResponse([
266 'error' => true,
267 'message' => $this->getLockedIntegrationMessage($integrationClassName)
268 ]);
269 }
270
271 if (\FireBox\Core\Helpers\Integrations::getConnectionType($integrationClassName) !== 'api_key')
272 {
273 $this->sendResponse([
274 'error' => true,
275 'message' => fpframework()->_('FPF_INTEGRATION_INVALID')
276 ]);
277 }
278
279 $credentials = $this->getPostedCredentials($integrationClassName);
280 if ($missingField = $this->getMissingRequiredCredentialField($integrationClassName, $credentials))
281 {
282 $this->sendResponse([
283 'error' => true,
284 // translators: %s: Credential field label or "this value" if label is not provided.
285 'message' => sprintf(__('Please enter %s.', 'firebox'), $missingField)
286 ]);
287 }
288
289 $connectionValue = \FireBox\Core\Helpers\Integrations::getConnectionValue($integrationClassName, $credentials);
290
291 $class = '\FPFramework\Base\Integrations\\' . $integrationClassName;
292 if (!class_exists($class))
293 {
294 $this->sendResponse([
295 'error' => true,
296 'message' => fpframework()->_('FPF_NO_SUCH_INTEGRATION_EXISTS')
297 ]);
298 }
299
300 try {
301 $integrationInstance = new $class([
302 'api' => $connectionValue
303 ]);
304 }
305 catch (\Throwable $e)
306 {
307 $this->sendResponse([
308 'error' => true,
309 'message' => $this->formatIntegrationErrorMessage($e->getMessage(), $integrationClassName)
310 ]);
311 }
312
313 if (!method_exists($integrationInstance, 'verifyConnection'))
314 {
315 $this->sendResponse([
316 'error' => true,
317 'message' => fpframework()->_('FPF_INTEGRATION_INVALID')
318 ]);
319 }
320
321 try {
322 $integrationInstance->verifyConnection();
323 }
324 catch (\Throwable $e)
325 {
326 $this->sendResponse([
327 'error' => true,
328 'message' => $this->formatIntegrationErrorMessage($e->getMessage(), $integrationClassName)
329 ]);
330 }
331
332 if (method_exists($integrationInstance, 'success') && !$integrationInstance->success())
333 {
334 $errorMessage = method_exists($integrationInstance, 'getLastError') ? $integrationInstance->getLastError() : '';
335 $errorMessage = $this->formatIntegrationErrorMessage($errorMessage, $integrationClassName);
336
337 $this->sendResponse([
338 'error' => true,
339 'message' => $errorMessage
340 ]);
341 }
342
343 \FireBox\Core\Helpers\Integrations::setGlobalCredentials($integrationClassName, $credentials);
344
345 $payload = [
346 'error' => false,
347 'message' => sprintf(firebox()->_('FB_INTEGRATION_CONNECTED_SUCCESS'), $this->getIntegrationLabel($integrationClassName)),
348 'connected' => true
349 ];
350
351 $this->sendResponse($payload);
352 }
353
354 /**
355 * Disconnect integration from global settings.
356 *
357 * @return void
358 */
359 public function fb_disconnect_integration()
360 {
361 $this->canRun();
362
363 if (!$this->verifyNonce())
364 {
365 $this->sendResponse([
366 'error' => true,
367 'message' => fpframework()->_('FPF_CANNOT_VERIFY_REQUEST')
368 ]);
369 }
370
371 $integrationClassName = $this->getPostedIntegrationClassName();
372 $managedIntegration = \FireBox\Core\Helpers\Integrations::getIntegration($integrationClassName);
373
374 if (!$managedIntegration)
375 {
376 $this->sendResponse([
377 'error' => true,
378 'message' => fpframework()->_('FPF_NO_SUCH_INTEGRATION_EXISTS')
379 ]);
380 }
381
382 if (\FireBox\Core\Helpers\Integrations::isLocked($integrationClassName))
383 {
384 $this->sendResponse([
385 'error' => true,
386 'message' => $this->getLockedIntegrationMessage($integrationClassName)
387 ]);
388 }
389
390 if (\FireBox\Core\Helpers\Integrations::getConnectionType($integrationClassName) !== 'api_key')
391 {
392 $this->sendResponse([
393 'error' => true,
394 'message' => fpframework()->_('FPF_INTEGRATION_INVALID')
395 ]);
396 }
397
398 \FireBox\Core\Helpers\Integrations::setGlobalCredentials($integrationClassName, []);
399
400 $this->sendResponse([
401 'error' => false,
402 'message' => sprintf(firebox()->_('FB_INTEGRATION_DISCONNECTED_SUCCESS'), $this->getIntegrationLabel($integrationClassName)),
403 'connected' => false
404 ]);
405 }
406
407 /**
408 * Verify AJAX nonce.
409 *
410 * @return bool
411 */
412 private function verifyNonce()
413 {
414 $nonceRaw = isset($_POST['nonce']) ? wp_unslash($_POST['nonce']) : '';
415 $nonce = is_scalar($nonceRaw) ? sanitize_text_field((string) $nonceRaw) : '';
416 return wp_verify_nonce($nonce, 'fpf_js_nonce');
417 }
418
419 /**
420 * Ensure user can run integration requests.
421 *
422 * @return void
423 */
424 private function canRun()
425 {
426 if (!current_user_can('read_fireboxes'))
427 {
428 $this->sendResponse([
429 'error' => true,
430 'message' => fpframework()->_('FPF_CANNOT_VERIFY_REQUEST')
431 ]);
432 }
433 }
434
435 /**
436 * Sends JSON response and terminates request.
437 *
438 * @param array $payload
439 *
440 * @return void
441 */
442 private function sendResponse($payload = [])
443 {
444 wp_send_json($payload);
445 }
446
447 /**
448 * Returns posted integration class name or sends an error response.
449 *
450 * @return string
451 */
452 private function getPostedIntegrationClassName()
453 {
454 $integrationRaw = isset($_POST['integration']) ? wp_unslash($_POST['integration']) : '';
455 $integration = is_scalar($integrationRaw) ? sanitize_text_field((string) $integrationRaw) : '';
456 if (!$integration)
457 {
458 $this->sendResponse([
459 'error' => true,
460 'message' => fpframework()->_('FPF_NO_INTEGRATION_SUPPLIED')
461 ]);
462 }
463
464 $integrationClassName = $this->getIntegrationClassName($integration);
465 if ($integrationClassName === '')
466 {
467 $this->sendResponse([
468 'error' => true,
469 'message' => fpframework()->_('FPF_NO_SUCH_INTEGRATION_EXISTS')
470 ]);
471 }
472
473 return $integrationClassName;
474 }
475
476 /**
477 * Normalizes integration to framework class name.
478 *
479 * @param string $integration
480 *
481 * @return string
482 */
483 private function getIntegrationClassName($integration = '')
484 {
485 if ($className = \FireBox\Core\Helpers\Integrations::getFrameworkIntegrationClassName($integration))
486 {
487 return trim((string) $className);
488 }
489
490 return '';
491 }
492
493 /**
494 * Returns sanitized posted credentials for an integration.
495 *
496 * @param string $integrationClassName
497 *
498 * @return array
499 */
500 private function getPostedCredentials($integrationClassName = '')
501 {
502 $credentials = [];
503 $rawCredentials = isset($_POST['credentials']) ? wp_unslash($_POST['credentials']) : '';
504
505 if (is_string($rawCredentials) && trim($rawCredentials) !== '')
506 {
507 $decoded = json_decode($rawCredentials, true);
508 if (is_array($decoded))
509 {
510 $credentials = $decoded;
511 }
512 }
513
514 $apiKeyRaw = isset($_POST['api_key']) ? wp_unslash($_POST['api_key']) : null;
515 if (!array_key_exists('api_key', $credentials) && is_scalar($apiKeyRaw))
516 {
517 $credentials['api_key'] = (string) $apiKeyRaw;
518 }
519
520 $sanitized = [];
521 foreach (\FireBox\Core\Helpers\Integrations::getCredentialFields($integrationClassName) as $field)
522 {
523 $key = $field['key'];
524 $sanitized[$key] = isset($credentials[$key]) && is_scalar($credentials[$key])
525 ? trim((string) $credentials[$key])
526 : '';
527 }
528
529 return $sanitized;
530 }
531
532 /**
533 * Returns whether any credential value has been supplied.
534 *
535 * @param array $credentials
536 *
537 * @return bool
538 */
539 private function hasProvidedCredentialValues($credentials = [])
540 {
541 foreach ((array) $credentials as $value)
542 {
543 if (trim((string) $value) !== '')
544 {
545 return true;
546 }
547 }
548
549 return false;
550 }
551
552 /**
553 * Returns the first missing required credential label.
554 *
555 * @param string $integrationClassName
556 * @param array $credentials
557 *
558 * @return string
559 */
560 private function getMissingRequiredCredentialField($integrationClassName = '', $credentials = [])
561 {
562 foreach (\FireBox\Core\Helpers\Integrations::getCredentialFields($integrationClassName) as $field)
563 {
564 if (empty($field['required']))
565 {
566 continue;
567 }
568
569 $key = $field['key'];
570 $value = isset($credentials[$key]) ? trim((string) $credentials[$key]) : '';
571 if ($value === '')
572 {
573 return !empty($field['label']) ? $field['label'] : $key;
574 }
575 }
576
577 return '';
578 }
579
580 /**
581 * Returns human readable integration label.
582 *
583 * @param string $integrationClassName
584 *
585 * @return string
586 */
587 private function getIntegrationLabel($integrationClassName = '')
588 {
589 $label = \FireBox\Core\Helpers\Integrations::getIntegrationLabel($integrationClassName);
590 return !empty($label) ? $label : $integrationClassName;
591 }
592
593 /**
594 * Returns locked integration message with dynamic plan requirement.
595 *
596 * @param string $integrationClassName
597 *
598 * @return string
599 */
600 private function getLockedIntegrationMessage($integrationClassName = '')
601 {
602 $label = $this->getIntegrationLabel($integrationClassName);
603 $plan_label = \FireBox\Core\Helpers\Integrations::getRequiredPlanLabel($integrationClassName);
604
605 /* translators: 1: Integration label, 2: Required plan label. */
606 return sprintf(__('%1$s is available in the %2$s plan.', 'firebox'), $label, $plan_label);
607 }
608
609 /**
610 * Formats integration error messages consistently.
611 *
612 * @param string $message
613 * @param string $integrationClassName
614 *
615 * @return string
616 */
617 private function formatIntegrationErrorMessage($message = '', $integrationClassName = '')
618 {
619 $message = trim(wp_strip_all_tags((string) $message));
620 if ($message !== '')
621 {
622 return $message;
623 }
624
625 $integrationLabel = $this->getIntegrationLabel($integrationClassName);
626 $connectionType = \FireBox\Core\Helpers\Integrations::getConnectionType($integrationClassName);
627 if ($connectionType === 'plugin')
628 {
629 return sprintf(
630 /* translators: %s: Integration label. */
631 __('%s request failed. Please make sure the related plugin is installed and active.', 'firebox'),
632 $integrationLabel
633 );
634 }
635
636 return sprintf(
637 /* translators: %s: Integration label. */
638 __('%s request failed. Please verify the API key and permissions.', 'firebox'),
639 $integrationLabel
640 );
641 }
642 }
643