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.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 / FB / Box.php

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

845 lines 18.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 <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\FB;
13
14 if (!defined('ABSPATH'))
15 {
16 exit; // Exit if accessed directly.
17 }
18
19 use FireBox\Core\Helpers\BoxHelper;
20 use FPFramework\Libs\Registry;
21 use FPFramework\Helpers\Fields\DimensionsHelper;
22 use FPFramework\Helpers\CSS;
23
24 class Box
25 {
26 /**
27 * Send useful JS snippet once in first box
28 *
29 * @var boolean
30 */
31 static $loadedLocalizedScript = false;
32
33 /**
34 * The box.
35 *
36 * @param object
37 */
38 private $box = null;
39
40 /**
41 * Factory
42 *
43 * @var Factory
44 */
45 private $factory = null;
46
47 /**
48 * FireBox settings.
49 *
50 * @var object
51 */
52 private $params = null;
53
54 /**
55 * Popup CSS.
56 *
57 * @var CSS
58 */
59 public $css = null;
60
61 /**
62 * Display condition groups that must be resolved in the browser before the
63 * campaign may open (session-dependent rules like Time on Site/Pageviews).
64 * Populated by pass(), shipped to the frontend runtime via prepare().
65 *
66 * @var array
67 */
68 private $clientRuleGroups = [];
69
70 /**
71 * Constructor.
72 *
73 * @param object $box
74 * @param object $factory
75 *
76 * @return void
77 */
78 public function __construct($box = null, $factory = null)
79 {
80 if ($box)
81 {
82 $this->box = $this->prepareConstructorBox($box);
83 }
84
85 if (!$factory)
86 {
87 $factory = new \FPFramework\Base\Factory();
88 }
89 $this->factory = $factory;
90
91 $this->params = new Registry(BoxHelper::getParams());
92 }
93
94 /**
95 * Allow to set either a box ID or box object
96 * and we then set the box object.
97 *
98 * @param mixed $box
99 *
100 * @return object
101 */
102 private function prepareConstructorBox($box)
103 {
104 if (!is_object($box))
105 {
106 $box = $this->get($box);
107 }
108
109 return $box;
110 }
111
112 /**
113 * Get a box.
114 *
115 * @param int $id
116 * @param string $status
117 *
118 * @return object|null
119 */
120 public function get($id = null, $status = null)
121 {
122 if (!$id)
123 {
124 return null;
125 }
126
127 $payload = [
128 'where' => [
129 'ID' => ' = ' . intval($id),
130 'post_type' => " = 'firebox'"
131 ]
132 ];
133
134 // apply status if given
135 if ($status)
136 {
137 $payload['where']['post_status'] = ' = \'' . sanitize_key($status) . '\'';
138 }
139
140 if (!$box = firebox()->tables->box->getResults($payload))
141 {
142 return null;
143 }
144
145 if (!isset($box[0]))
146 {
147 return null;
148 }
149
150 $this->box = $box[0];
151
152 // get meta options for box
153 $meta = \FireBox\Core\Helpers\BoxHelper::getMeta($id);
154 $this->box->params = new Registry($meta);
155
156 return $this->box;
157 }
158
159 /**
160 * Renders the box.
161 *
162 * @return void
163 */
164 public function render()
165 {
166 // Check Publishing Assignments
167 if (!$this->pass())
168 {
169 return false;
170 }
171
172 $fbox = $this->box;
173
174 /**
175 * Runs before rendering the box.
176 */
177 $this->box = apply_filters('firebox/box/before_render', $this->box);
178
179 $this->prepare();
180
181 $css = $this->getCustomCSS();
182
183 add_action('wp_enqueue_scripts', function() use ($fbox, $css) {
184 // Loads all media files.
185 $this->loadBoxMedia($fbox);
186
187 // Load CSS
188 if ($css)
189 {
190 wp_add_inline_style('firebox', $css);
191 }
192
193
194 });
195
196 // Allow to manipulate the box before rendering
197 $this->box = apply_filters('firebox/box/edit', $this->box);
198
199 // payload
200 $payload = [
201 'box' => $this->box,
202 'params' => $this->params,
203 ];
204
205 // print campaign HTML
206 add_action('wp_footer', function() use ($payload) {
207 echo $this->getFinalCampaignHTML($payload); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
208 });
209
210 return true;
211 }
212
213 public function renderEmbed()
214 {
215 // Check Publishing Assignments
216 if (!$this->pass())
217 {
218 return false;
219 }
220
221 /**
222 * Runs before rendering the box.
223 */
224 $this->box = apply_filters('firebox/box/before_render', $this->box);
225
226 $this->prepare();
227
228 // Loads all media files.
229 $this->loadBoxMedia($this->box);
230
231 $css = $this->getCustomCSS();
232
233 wp_register_style('fireboxStyle', false);
234 wp_enqueue_style('fireboxStyle');
235
236 // Load CSS
237 if ($css)
238 {
239 wp_add_inline_style('fireboxStyle', $css);
240 }
241
242
243
244 // Allow to manipulate the box before rendering
245 $this->box = apply_filters('firebox/box/edit', $this->box);
246
247 // payload
248 $payload = [
249 'box' => $this->box,
250 'params' => $this->params,
251 ];
252
253 // return box template
254 return $this->getFinalCampaignHTML($payload);
255 }
256
257 public function getFinalCampaignHTML($payload)
258 {
259 $html = firebox()->renderer->public->render('box', $payload, true);
260
261 /**
262 * Runs after rendering the box.
263 */
264 return apply_filters('firebox/box/after_render', $html, $payload['box']);
265 }
266
267 /**
268 * Gets the Custom CSS of the popup.
269 *
270 * @return string
271 */
272 public function getCustomCSS()
273 {
274 if (!is_object($this->box) || !isset($this->box->params))
275 {
276 return '';
277 }
278
279 return $this->box->params->get('customcss', '');
280 }
281
282 /**
283 * Recursively casts an object/array tree to a plain array, without the
284 * json_decode(wp_json_encode()) round-trip.
285 *
286 * @param mixed $data
287 *
288 * @return array
289 */
290 private static function recursiveToArray($data)
291 {
292 if (is_object($data))
293 {
294 $data = get_object_vars($data);
295 }
296
297 if (!is_array($data))
298 {
299 return [];
300 }
301
302 foreach ($data as $key => $value)
303 {
304 if (is_object($value) || is_array($value))
305 {
306 $data[$key] = self::recursiveToArray($value);
307 }
308 }
309
310 return $data;
311 }
312
313 /**
314 * Send a helpful object to JavaScript files
315 *
316 * @return void
317 */
318 public static function setJSObject()
319 {
320 if (self::$loadedLocalizedScript)
321 {
322 return;
323 }
324 self::$loadedLocalizedScript = true;
325
326 $data = [
327 'ajax_url' => admin_url('admin-ajax.php'),
328 'nonce' => wp_create_nonce('fbox_js_nonce'),
329 'site_url' => site_url('/'),
330 'referrer' => isset($_SERVER['HTTP_REFERER']) ? sanitize_url(wp_unslash($_SERVER['HTTP_REFERER'])) : ''
331 ];
332
333 wp_add_inline_script('firebox-main', 'const fbox_js_object = ' . wp_json_encode($data), 'before');
334 }
335
336 /**
337 * Load Box Media
338 *
339 * @return void
340 */
341 public function loadBoxMedia($box)
342 {
343 $box = new Registry($box);
344
345 wp_enqueue_style(
346 'firebox-animations',
347 FBOX_MEDIA_PUBLIC_URL . 'css/vendor/animate.min.css',
348 [],
349 FBOX_VERSION
350 );
351
352 /**
353 * FireBox JS
354 */
355 wp_enqueue_script('firebox-main');
356
357
358
359 // Add Custom Javascript
360 $custom_code = $box->get('params.data.customcode', '');
361 if (is_string($custom_code) && !empty($custom_code))
362 {
363 $custom_code = html_entity_decode(stripslashes($custom_code));
364 wp_add_inline_script('firebox-main', $custom_code, 'after');
365 }
366
367 // run above the main JS script to run only once
368 self::setJSObject();
369
370 /**
371 * FireBox CSS
372 */
373 wp_enqueue_style(
374 'firebox',
375 FBOX_MEDIA_PUBLIC_URL . 'css/firebox.css',
376 [],
377 FBOX_VERSION
378 );
379
380 /**
381 * Page Slide mode JS
382 */
383 if ($box->get('params.data.mode') == 'pageslide')
384 {
385 wp_enqueue_script(
386 'firebox-pageslide-mode',
387 FBOX_MEDIA_PUBLIC_URL . 'js/pageslide_mode.js',
388 ['firebox-main'],
389 FBOX_VERSION,
390 ['in_footer' => true, 'strategy' => 'defer']
391 );
392 }
393
394
395 }
396
397 /**
398 * Prepares the box before rendering
399 *
400 * @return void
401 */
402 public function prepare()
403 {
404 remove_filter('the_content', 'wptexturize');
405
406 $cParam = BoxHelper::getParams();
407 $cParam = new Registry($cParam);
408
409 $this->box->post_content = apply_filters('the_content', $this->box->post_content);
410
411 $mode = $this->box->params->get('mode');
412
413 /* Classes */
414 $css_class = [
415 $this->box->ID,
416 $mode
417 ];
418
419 if (in_array($mode, ['popup', 'stickybar', 'sidebar', 'floating', 'slide-in']))
420 {
421 $position = $this->box->params->get('position', '');
422 $position = !is_string($position) ? '' : $position;
423 if ($position)
424 {
425 $css_class[] = $position;
426 }
427 }
428 else if ($mode === 'fullscreen')
429 {
430 if ($center_content = $this->box->params->get('center_content', false)) {
431 $css_class[] = 'center-content';
432 }
433 }
434
435 self::prefixCSSClasses($css_class);
436
437 // Class suffix
438 $classSuffix = $this->box->params->get('classsuffix', '');
439 $classSuffix = is_string($classSuffix) ? $classSuffix : '';
440
441 $css_class[] = $classSuffix;
442
443 $this->box->classes = $css_class;
444
445 // Dialog CSS Classes
446 $dialog_css_classes = [
447 // Add Box shadow
448 $this->box->params->get('boxshadow') ? 'shdelevation' : null
449 ];
450
451 // Align Content
452 $aligncontent = is_string($this->box->params->get('aligncontent')) ? explode(' ', $this->box->params->get('aligncontent')) : [];
453 $dialog_css_classes = array_merge($dialog_css_classes, $aligncontent);
454
455 self::prefixCSSClasses($dialog_css_classes);
456 $this->box->dialog_classes = $dialog_css_classes;
457
458 $trigger_point_methods = [
459 'pageload' => 'onPageLoad',
460 'onclick' => 'onClick',
461 'elementHover' => 'onHover',
462 'ondemand' => 'onDemand',
463
464 ];
465
466 /* Other Settings */
467 $this->box->params->set('animation_duration', $this->box->params->get('animation_duration', 0.2));
468
469 $scroll_amount = $this->box->params->get('scroll_amount', '80%');
470
471 // Parse scroll_amount to extract unit and value
472 $scroll_amount_data = $this->parseScrollAmount($scroll_amount);
473
474 $delay = in_array($this->box->params->get('triggermethod'), ['floatingbutton', 'onexternallink']) ? 0 : (int) $this->box->params->get('triggerdelay') * 1000;
475
476 $trigger_method = (is_string($this->box->params->get('triggermethod'))) && array_key_exists($this->box->params->get('triggermethod'), $trigger_point_methods) ? $trigger_point_methods[$this->box->params->get('triggermethod')] : $this->box->params->get('triggermethod');
477
478 $trigger_element = is_scalar($this->box->params->get('triggerelement', '')) ? $this->box->params->get('triggerelement', '') : '';
479
480 // Use Namespaced classes for each trigger point and let them manipulate the settings dynamicaly.
481 $this->box->settings = [
482 'name' => $this->box->post_title,
483 'trigger' => $trigger_method,
484 'trigger_selector' => $trigger_method === 'onExternalLink' ? '' : rtrim($trigger_element, ','),
485 'delay' => $delay,
486
487 'close_on_esc' => (bool) $this->box->params->get('close_on_esc', false),
488 'animation_open' => $this->box->params->get('animationin'),
489 'animation_close' => $this->box->params->get('animationout'),
490 'animation_duration' => (float) $this->box->params->get('animation_duration') * 1000,
491 'prevent_default' => true,
492 'backdrop' => (bool) $this->box->params->get('overlay'),
493 'backdrop_color' => $this->box->params->get('overlay_color'),
494 'backdrop_click' => (bool) $this->box->params->get('overlayclick'),
495 'disable_page_scroll' => (bool) $this->box->params->get('preventpagescroll'),
496 'test_mode' => (bool) $this->box->params->get('testmode'),
497 'debug' => (bool) $cParam->get('debug', false),
498 'auto_focus' => (bool) $this->box->params->get('autofocus', false),
499 'mode' => $this->box->params->get('mode'),
500 // Session-dependent display conditions resolved in the browser (see pass())
501 'client_rules' => $this->clientRuleGroups
502 ];
503
504 $this->css = new Styling\CSS($this->box);
505
506 // Apply Popup CSS
507 $this->box->params->set('customcss', $this->box->params->get('customcss') . $this->css->getCSS());
508
509 $this->replaceBoxSmartTags();
510
511 add_filter('the_content', 'wptexturize');
512 }
513
514 /**
515 * Parses scroll_amount to extract unit and value
516 *
517 * @param mixed $scroll_amount
518 *
519 * @return array
520 */
521 private function parseScrollAmount($scroll_amount)
522 {
523 if (is_array($scroll_amount))
524 {
525 return [
526 'unit' => $scroll_amount['unit'] ?? '%',
527 'value' => $scroll_amount['value'] ?? 80
528 ];
529 }
530
531 if (is_string($scroll_amount) && preg_match('/^(\d+)(px|%)$/', $scroll_amount, $matches))
532 {
533 return [
534 'unit' => $matches[2],
535 'value' => $matches[1]
536 ];
537 }
538
539 return [
540 'unit' => '%',
541 'value' => 80
542 ];
543 }
544
545 /**
546 * Replaces all box smart tags
547 *
548 * @return void
549 */
550 public function replaceBoxSmartTags()
551 {
552 $tags = new \FPFramework\Base\SmartTags\SmartTags();
553
554 // register FB Smart Tags
555 $tags->register('\FireBox\Core\SmartTags', FBOX_BASE_FOLDER . '/Inc/Core/SmartTags', $this->box);
556
557 $this->box = $tags->replace($this->box);
558 }
559
560 /**
561 * Checks if a box passes assignments
562 *
563 * @return boolean
564 */
565 public function pass()
566 {
567 $this->clientRuleGroups = [];
568
569 if (!$this->box || !is_object($this->box))
570 {
571 return false;
572 }
573
574 // Check first local assignments
575 if (!$this->passLocalAssignments())
576 {
577 return false;
578 }
579
580 $displayConditionsType = $this->box->params->get('display_conditions_type', '');
581
582 // If empty, display popup sitewide
583 if (empty($displayConditionsType) || $displayConditionsType === 'all')
584 {
585 return true;
586 }
587
588 // Mirror Display Conditions of another popup.
589 if ($displayConditionsType == 'mirror' && $mirror_box_id = $this->box->params->get('mirror_box'))
590 {
591 $this->box->params->merge(self::getAssignmentsForMirroring($mirror_box_id));
592 }
593
594 // Get a recursive array of all rules
595 $rules = $this->box->params->get('rules', []);
596 $rules = is_string($rules) ? json_decode($rules, true) : self::recursiveToArray($rules);
597
598 // Normalize to an array; an empty or invalid-JSON "rules" string decodes to null.
599 $rules = is_array($rules) ? $rules : [];
600
601 // If testmode is enabled disable the User Groups condition
602 if ($this->box->params->get('testmode'))
603 {
604 foreach ($rules as $key => &$group)
605 {
606 if (!isset($group['rules']) || !is_array($group['rules']))
607 {
608 continue;
609 }
610
611 foreach ($group['rules'] as $_key => &$rule)
612 {
613 if (!isset($rule['name']) || empty($rule['name']))
614 {
615 continue;
616 }
617
618 if ($rule['name'] === 'WP\UserGroup')
619 {
620 unset($group['rules'][$_key]);
621 }
622 }
623 }
624 unset($group);
625 }
626
627 // Check framework based conditions. Session-dependent (client-side) rules are
628 // deferred: the box renders hidden and the frontend runtime resolves them.
629 $result = \FPFramework\Base\Conditions\ConditionBuilder::passWithClientRules($rules, $this->factory);
630
631 $this->clientRuleGroups = $result['client_groups'];
632
633 return $result['pass'];
634 }
635
636 /**
637 * Display condition groups deferred to the frontend runtime by pass().
638 *
639 * @return array
640 */
641 public function getClientRuleGroups()
642 {
643 return $this->clientRuleGroups;
644 }
645
646 /**
647 * Check if a box passes local conditions
648 *
649 * @return boolean
650 */
651 private function passLocalAssignments()
652 {
653 $localAssignments = new \FireBox\Core\FB\Assignments($this, $this->factory);
654 return $localAssignments->passAll();
655 }
656
657 /**
658 * Gets assignments of mirrored box
659 *
660 * @param int $box_id
661 *
662 * @return object
663 */
664 private function getAssignmentsForMirroring($box_id)
665 {
666 $payload = [
667 'where' => [
668 'ID' => ' = ' . intval($box_id),
669 'post_status' => " = 'publish'",
670 'post_type' => " = 'firebox'"
671 ]
672 ];
673
674 // Load box
675 if (!$box = firebox()->tables->box->getResults($payload))
676 {
677 return;
678 }
679
680 $box = $box[0];
681
682 // get meta options for box
683 $meta = BoxHelper::getMeta($box->ID);
684 $box->params = new Registry($meta);
685
686 return new Registry(['rules' => $box->params->get('rules')]);
687 }
688
689 /**
690 * Prefixes the CSS classes
691 *
692 * @param array $classes
693 * @param string $prefix
694 *
695 * @return void
696 */
697 private static function prefixCSSClasses(&$classes, $prefix = 'fb-')
698 {
699 $classes = array_filter($classes);
700
701 if (empty($classes))
702 {
703 return;
704 }
705
706 foreach ($classes as &$class)
707 {
708 $class = $prefix . $class;
709 }
710 }
711
712 /**
713 * Track box open
714 *
715 * @param integer $box_id
716 * @param string $page
717 * @param string $referrer
718 *
719 * @return void
720 */
721 public function logOpenEvent($box_id, $page = null, $referrer = null)
722 {
723 $box = $this->get($box_id);
724
725 if (!is_object($box) || !isset($box->params))
726 {
727 return;
728 }
729
730 // Do not track if statistics option is disabled
731 $track_open_event = (bool) (is_null($box->params->get('stats', null)) ? true : $box->params->get('stats'));
732 if (!$track_open_event)
733 {
734 return;
735 }
736
737 return firebox()->log->track($box_id, 1, null, $page, $referrer);
738 }
739
740 /**
741 * Track box close
742 *
743 * @param integer $box_id
744 * @param integer $box_log_id
745 *
746 * @return void
747 */
748 public function logCloseEvent($box_id, $box_log_id)
749 {
750 $box = $this->get($box_id);
751
752 if (!is_object($box) || !isset($box->params))
753 {
754 return null;
755 }
756
757 // Do not track if statistics option is disabled
758 $track_open_event = (bool) (is_null($box->params->get('stats', null)) ? true : $box->params->get('stats'));
759 if (!$track_open_event)
760 {
761 return null;
762 }
763
764 firebox()->log->track($box_id, 2, $box_log_id);
765 }
766
767 /**
768 * Get total box impressions
769 *
770 * @param array $payload
771 *
772 * @return array
773 */
774 public function getTotalImpressions($payload)
775 {
776 return firebox()->tables->boxlog->getResults($payload, false, true);
777 }
778
779 /**
780 * Returns the cookie instance.
781 *
782 * @return mixed
783 */
784 public function getCookie()
785 {
786 if (!$this->box)
787 {
788 return;
789 }
790
791 return new Cookie($this->box);
792 }
793
794 /**
795 * Returns the box.
796 *
797 * @return object
798 */
799 public function getBox()
800 {
801 return $this->box;
802 }
803
804 /**
805 * Sets the box.
806 *
807 * @param object $box
808 *
809 * @return Box
810 */
811 public function setBox($box)
812 {
813 $this->box = $box;
814
815 return $this;
816 }
817
818 public function getParams()
819 {
820 return $this->params;
821 }
822
823 public function setParams($params)
824 {
825 $this->params = $params;
826
827 return $this;
828 }
829
830 public function getCampaignParams()
831 {
832 return isset($this->box->params) ? $this->box->params : null;
833 }
834
835 public function setCampaignParams($params)
836 {
837 if (isset($this->box->params))
838 {
839 $this->box->params = $params;
840 }
841
842 return $this;
843 }
844 }
845