PluginProbe
FireBox – WooCommerce Popup Builder, Exit Intent Popup, Email Optin & Cart Abandonment / 3.1.14
FireBox – WooCommerce Popup Builder, Exit Intent Popup, Email Optin & Cart Abandonment v3.1.14
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
← All changes | Inc/Core/FB/Box.php +737 -488 1.0.3 → 3.1.14 View file →
@@ -1,12 +1,12 @@
1 1 <?php
2 2 /**
3 3 * @package FireBox
4 - * @version 1.0.3 Free
4 + * @version 3.1.14 Free
5 5 *
6 6 * @author FirePlugins <[email protected]>
7 7 * @link https://www.fireplugins.com
8 - * @copyright Copyright © 2021 FirePlugins All Rights Reserved
8 + * @copyright Copyright © 2026 FirePlugins All Rights Reserved
9 9 * @license GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
10 10 */
11 11
12 12 namespace FireBox\Core\FB;
@@ -18,8 +18,9 @@
18 18
19 19 use FireBox\Core\Helpers\BoxHelper;
20 20 use FPFramework\Libs\Registry;
21 21 use FPFramework\Helpers\Fields\DimensionsHelper;
22 +use FPFramework\Helpers\CSS;
22 23
23 24 class Box
24 25 {
25 26 /**
@@ -29,48 +30,104 @@
29 30 */
30 31 static $loadedLocalizedScript = false;
31 32
32 33 /**
33 - * The box settings
34 + * The box.
35 + *
36 + * @param object
34 37 */
35 - private $boxSettings;
38 + private $box = null;
36 39
37 40 /**
38 - * The factory
41 + * Factory
39 42 *
40 43 * @var Factory
41 44 */
42 - protected $factory;
45 + private $factory = null;
43 46
44 - public function __construct($boxSettings = null, $factory = null)
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)
45 79 {
46 - $this->init($boxSettings, $factory);
47 - }
80 + if ($box)
81 + {
82 + $this->box = $this->prepareConstructorBox($box);
83 + }
48 84
49 - public function init($boxSettings = null, $factory = null)
50 - {
51 85 if (!$factory)
52 86 {
53 87 $factory = new \FPFramework\Base\Factory();
54 88 }
55 -
56 - $this->boxSettings = $boxSettings;
57 89 $this->factory = $factory;
90 +
91 + $this->params = new Registry(BoxHelper::getParams());
58 92 }
59 93
60 94 /**
61 - * Gets published box.
95 + * Allow to set either a box ID or box object
96 + * and we then set the box object.
62 97 *
63 - * @param integer $id
64 - * @param string $status
98 + * @param mixed $box
65 99 *
66 - * @return array
100 + * @return object
67 101 */
68 - public function get($id, $status = null)
102 + private function prepareConstructorBox($box)
69 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 +
70 127 $payload = [
71 128 'where' => [
72 - 'ID' => ' = ' . $id,
129 + 'ID' => ' = ' . intval($id),
73 130 'post_type' => " = 'firebox'"
74 131 ]
75 132 ];
76 133
@@ -76,135 +133,197 @@
76 133
77 134 // apply status if given
78 135 if ($status)
79 136 {
80 - $payload['where']['post_status'] = ' = \'' . $status . '\'';
137 + $payload['where']['post_status'] = ' = \'' . sanitize_key($status) . '\'';
81 138 }
82 -
139 +
83 140 if (!$box = firebox()->tables->box->getResults($payload))
84 141 {
85 - return [];
142 + return null;
86 143 }
87 144
88 145 if (!isset($box[0]))
89 146 {
90 - return [];
147 + return null;
91 148 }
92 149
93 - $box = $box[0];
150 + $this->box = $box[0];
94 151
95 152 // get meta options for box
96 - $meta = self::getMeta($id);
97 - $box->params = new Registry($meta);
153 + $meta = \FireBox\Core\Helpers\BoxHelper::getMeta($id);
154 + $this->box->params = new Registry($meta);
98 155
99 - return $box;
156 + return $this->box;
100 157 }
101 158
102 159 /**
103 - * Get box meta
160 + * Renders the box.
104 161 *
105 - * @param int $id
106 - *
107 - * @return array
162 + * @return void
108 163 */
109 - public static function getMeta($id)
164 + public function render()
110 165 {
111 - return get_post_meta($id, 'fpframework_meta_settings', true);
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;
112 211 }
113 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 +
114 267 /**
115 - * Render box
268 + * Gets the Custom CSS of the popup.
116 269 *
117 270 * @return string
118 271 */
119 - public function render($box = null)
272 + public function getCustomCSS()
120 273 {
121 - // we do not have a passed box or box settings from constructor, abort
122 - // as we cannot render a box without any box parameters
123 - if (!$box && !$this->boxSettings)
274 + if (!is_object($this->box) || !isset($this->box->params))
124 275 {
125 - return;
276 + return '';
126 277 }
127 278
128 - // if a box was given to the function, use these settings instead
129 - if ($box)
130 - {
131 - $this->boxSettings = $box;
132 - }
279 + $css = $this->box->params->get('customcss', '');
133 280
134 - // if we have box settings, then set up our box with these settings
135 - if ($this->boxSettings)
281 + if (!is_string($css))
136 282 {
137 - $box = $this->boxSettings;
283 + return '';
138 284 }
139 285
140 - // Check Publishing Assignments
141 - if (!$this->pass($box))
142 - {
143 - return;
144 - }
286 + /**
287 + * The CSS lands inside an inline <style> element, where a literal "</style>"
288 + * would end the element and let markup (e.g. a <script>) escape into the page.
289 + * Strip tags the same way core's wp_custom_css_cb() does — legitimate CSS
290 + * contains no tags, so this only neuters breakout attempts.
291 + */
292 + return wp_strip_all_tags($css);
293 + }
145 294
146 - // get settings params
147 - $params = BoxHelper::getParams();
148 -
149 - if (!did_action('wp_enqueue_scripts'))
295 + /**
296 + * Recursively casts an object/array tree to a plain array, without the
297 + * json_decode(wp_json_encode()) round-trip.
298 + *
299 + * @param mixed $data
300 + *
301 + * @return array
302 + */
303 + private static function recursiveToArray($data)
304 + {
305 + if (is_object($data))
150 306 {
151 - /**
152 - * Loads all media files.
153 - */
154 - add_action('wp_enqueue_scripts', function() use ($box, $params) {
155 - // load box media
156 - $this->loadBoxMedia($box, $params);
157 - });
307 + $data = get_object_vars($data);
158 308 }
159 - else
309 +
310 + if (!is_array($data))
160 311 {
161 - // load box media
162 - $this->loadBoxMedia($box, $params);
312 + return [];
163 313 }
164 314
165 - self::prepare($box);
166 -
167 - if (!did_action('wp_head'))
315 + foreach ($data as $key => $value)
168 316 {
169 - /**
170 - * Custom CSS
171 - */
172 - add_action('wp_head', function() use ($box) {
173 - if (!empty($box->params->get('customcss', '')))
174 - {
175 - echo '<style type="text/css">' . $box->params->get('customcss') . '</style>';
176 - }
177 - });
178 - }
179 - else
180 - {
181 - if (!empty($box->params->get('customcss', '')))
317 + if (is_object($value) || is_array($value))
182 318 {
183 - echo '<style type="text/css">' . $box->params->get('customcss') . '</style>';
319 + $data[$key] = self::recursiveToArray($value);
184 320 }
185 321 }
186 -
187 - /**
188 - * Runs before rendering the box.
189 - */
190 - do_action('firebox/box/before_render', $box);
191 322
192 - // after we prepare it, update box settings
193 - $this->boxSettings = $box;
323 + return $data;
324 + }
194 325
195 - // payload
196 - $payload = [
197 - 'box' => $box,
198 - 'params' => $params,
199 - ];
200 -
201 - // return box template
202 - add_action('wp_footer', function() use ($box, $payload) {
203 - echo firebox()->renderer->public->render('box', $payload, true);
204 - });
205 - }
206 -
207 326 /**
208 327 * Send a helpful object to JavaScript files
209 328 *
210 329 * @return void
@@ -216,89 +335,69 @@
216 335 return;
217 336 }
218 337 self::$loadedLocalizedScript = true;
219 338
220 - $data = array(
221 - 'ajax_url' => admin_url('admin-ajax.php'),
222 - 'nonce' => wp_create_nonce('fbox_js_nonce'),
223 - 'site_url' => site_url('/'),
224 - 'referrer' => wp_get_referer()
225 - );
339 + /**
340 + * The referrer is deliberately not included here. On a full-page-cached site this
341 + * HTML is written once and served to everyone, so a baked-in referrer would report
342 + * whoever warmed the cache. The runtime reads `document.referrer` instead.
343 + */
344 + $data = [
345 + 'ajax_url' => admin_url('admin-ajax.php'),
346 + 'nonce' => wp_create_nonce('fbox_js_nonce'),
347 + 'site_url' => site_url('/'),
226 348
227 - wp_localize_script('firebox', 'fbox_js_object', $data);
349 + // Shown when a form submission never gets a usable response back (network error,
350 + // non-JSON body, PHP fatal). The form messages themselves come from the block.
351 + 'form_submit_failed' => firebox()->_('FB_FORM_SUBMIT_FAILED'),
352 +
353 + /**
354 + * The scripts write cookies PHP has to read back, so they need the
355 + * same name, path and domain it uses: the prefix keeps the sites of
356 + * a subdirectory network apart, the path is the site's own corner of
357 + * the domain, and the domain is whatever COOKIE_DOMAIN says.
358 + */
359 + 'cookie_prefix' => \FireBox\Core\Helpers\BoxHelper::cookiePrefix(),
360 + 'cookie_path' => \FireBox\Core\Helpers\BoxHelper::cookiePath(),
361 + 'cookie_domain' => \FireBox\Core\Helpers\BoxHelper::cookieDomain()
362 + ];
363 +
364 + wp_add_inline_script('firebox-main', 'const fbox_js_object = ' . wp_json_encode($data), 'before');
228 365 }
229 366
230 367 /**
231 368 * Load Box Media
232 369 *
233 - * @param array $box
234 - * @param array $params
235 - *
236 370 * @return void
237 371 */
238 - public function loadBoxMedia($box, $params)
372 + public function loadBoxMedia($box)
239 373 {
240 374 $box = new Registry($box);
241 - $params = new Registry($params);
242 375
243 - // Add polyfills for Internet Explorer
244 - $browser = $this->factory->getBrowser();
245 - if ($browser && is_array($browser) && array_key_exists('name', $browser) && $browser['name'] == 'ie')
246 - {
247 - wp_enqueue_script(
248 - 'firebox-ie11-polyfill',
249 - 'https://polyfill.io/v3/polyfill.min.js?features=NodeList.prototype.forEach%2CElement.prototype.closest%2CArray.prototype.forEach%2CArray.prototype.find%2CIntersectionObserver%2CIntersectionObserverEntry',
250 - [],
251 - FBOX_VERSION,
252 - false
253 - );
254 - }
376 + $this->loadAnimationsMedia($box);
255 377
256 378 /**
257 - * Velocity
379 + * FireBox JS
258 380 */
259 - if ($params->get('loadVelocity', true))
260 - {
261 - wp_enqueue_script(
262 - 'firebox-velocity',
263 - FBOX_MEDIA_PUBLIC_URL . 'js/vendor/velocity.js',
264 - [],
265 - FBOX_VERSION,
266 - false
267 - );
268 - wp_enqueue_script(
269 - 'firebox-velocity-ui',
270 - FBOX_MEDIA_PUBLIC_URL . 'js/vendor/velocity.ui.js',
271 - [],
272 - FBOX_VERSION,
273 - false
274 - );
381 + wp_enqueue_script('firebox-main');
275 382
276 - /**
277 - * Animations
278 - */
279 - if (strpos($box->get('params.data.animationin'), 'firebox') !== false || strpos($box->get('params.data.animationout'), 'firebox') !== false)
280 - {
281 - wp_enqueue_script(
282 - 'firebox-animations',
283 - FBOX_MEDIA_PUBLIC_URL . 'js/animations.js',
284 - [],
285 - FBOX_VERSION,
286 - false
287 - );
288 - }
289 - }
383 +
290 384
291 385 /**
292 - * FireBox JS
386 + * Add Custom Javascript.
387 + *
388 + * Emitted verbatim into every visitor's page, so it is gated on the author
389 + * holding unfiltered_html — the campaign meta is writable by any
390 + * edit_fireboxes user (editor, REST, import) and must not be a way to run
391 + * script that user could not write elsewhere.
293 392 */
294 - wp_enqueue_script(
295 - 'firebox',
296 - FBOX_MEDIA_PUBLIC_URL . 'js/firebox.js',
297 - [],
298 - FBOX_VERSION,
299 - false
300 - );
393 + $custom_code = $box->get('params.data.customcode', '');
394 + if (is_string($custom_code) && !empty($custom_code)
395 + && \FireBox\Core\Helpers\CustomCode::isAllowedForCampaign($box->get('ID')))
396 + {
397 + $custom_code = html_entity_decode(stripslashes($custom_code));
398 + BoxHelper::addInlineScript($custom_code);
399 + }
301 400
302 401 // run above the main JS script to run only once
303 402 self::setJSObject();
304 403
@@ -304,18 +403,14 @@
304 403
305 404 /**
306 405 * FireBox CSS
307 406 */
308 - if ($params->get('loadCSS', true))
309 - {
310 - wp_enqueue_style(
311 - 'firebox',
312 - FBOX_MEDIA_PUBLIC_URL . 'css/firebox.css',
313 - [],
314 - FBOX_VERSION,
315 - false
316 - );
317 - }
407 + wp_enqueue_style(
408 + 'firebox',
409 + FBOX_MEDIA_PUBLIC_URL . 'css/firebox.css',
410 + [],
411 + FBOX_VERSION
412 + );
318 413
319 414 /**
320 415 * Page Slide mode JS
321 416 */
@@ -320,371 +415,500 @@
320 415 * Page Slide mode JS
321 416 */
322 417 if ($box->get('params.data.mode') == 'pageslide')
323 418 {
419 + // This script moves the campaign instances into the page-slide wrapper after
420 + // firebox-main creates them. Deferring is safe: deferred scripts execute in
421 + // document order and firebox-main is declared as a dependency, so it always
422 + // runs first. All inline scripts on firebox-main are attached in the "before"
423 + // position, which keeps its defer strategy intact.
324 424 wp_enqueue_script(
325 425 'firebox-pageslide-mode',
326 426 FBOX_MEDIA_PUBLIC_URL . 'js/pageslide_mode.js',
327 - [],
427 + ['firebox-main'],
328 428 FBOX_VERSION,
329 - false
429 + ['in_footer' => true, 'strategy' => 'defer']
330 430 );
331 431 }
332 432
333 433
334 -
335 - $this->loadThemeCSSOverrides();
336 434 }
337 435
338 436 /**
339 - * Some themes require overrides to preserve as much as we can the styling of the popups.
340 - *
437 + * Enqueues only the Animate.css animations the campaign actually uses.
438 + *
439 + * The full library is 71 KB of render-blocking CSS for what is almost always a
440 + * single fade, so it is split at build time (gulp build-animations) into one file
441 + * per animation plus a shared base. Campaigns rendering on the same page union
442 + * their animations naturally, since each gets its own handle.
443 + *
444 + * @param Registry $box
445 + *
341 446 * @return void
342 447 */
343 - private function loadThemeCSSOverrides()
448 + private function loadAnimationsMedia($box)
344 449 {
345 - $active_theme = wp_get_theme();
346 - $theme = $active_theme->template;
347 -
348 450 /**
349 - * This is the listed of the themes that we have created overrides
451 + * Escape hatch for campaigns whose custom code applies arbitrary
452 + * firebox__animate__* classes that we cannot see server-side.
350 453 */
351 - $themes = [
352 - 'twentytwentyone'
353 - ];
454 + if (apply_filters('firebox/box/load_full_animations', false))
455 + {
456 + wp_enqueue_style(
457 + 'firebox-animations',
458 + FBOX_MEDIA_PUBLIC_URL . 'css/vendor/animate.min.css',
459 + [],
460 + FBOX_VERSION
461 + );
354 462
355 - if (!in_array($theme, $themes))
463 + return;
464 + }
465 +
466 + $animations = array_filter([
467 + $box->get('params.data.animationin', ''),
468 + $box->get('params.data.animationout', '')
469 + ], 'is_string');
470 +
471 + $animations = array_unique(array_filter(array_map('trim', $animations)));
472 +
473 + if (!$animations)
356 474 {
357 475 return;
358 476 }
359 477
360 - wp_enqueue_style(
361 - 'firebox-theme-' . $theme . '-override',
362 - FBOX_MEDIA_PUBLIC_URL . 'css/themes/' . $theme . '.css',
363 - [],
364 - FBOX_VERSION,
365 - false
366 - );
478 + $dir = FBOX_PLUGIN_DIR . 'media/public/css/vendor/animations/';
479 + $url = FBOX_MEDIA_PUBLIC_URL . 'css/vendor/animations/';
480 +
481 + $enqueued = false;
482 +
483 + foreach ($animations as $animation)
484 + {
485 + // Animation names are a fixed camelCase slug set; anything else is either a
486 + // stale value or a name we no longer ship, and must not reach the filesystem.
487 + if (!preg_match('#^[A-Za-z0-9]+$#', $animation) || !file_exists($dir . $animation . '.css'))
488 + {
489 + continue;
490 + }
491 +
492 + if (!$enqueued)
493 + {
494 + wp_enqueue_style('firebox-animations-base', $url . 'base.css', [], FBOX_VERSION);
495 + $enqueued = true;
496 + }
497 +
498 + wp_enqueue_style(
499 + 'firebox-animation-' . $animation,
500 + $url . $animation . '.css',
501 + ['firebox-animations-base'],
502 + FBOX_VERSION
503 + );
504 + }
367 505 }
368 506
369 507 /**
370 508 * Prepares the box before rendering
371 - *
372 - * @param array $box
373 - *
509 + *
374 510 * @return void
375 511 */
376 - public static function prepare(&$box)
512 + public function prepare()
377 513 {
514 + remove_filter('the_content', 'wptexturize');
515 +
378 516 $cParam = BoxHelper::getParams();
379 517 $cParam = new Registry($cParam);
380 518
381 - $box->post_content = apply_filters('the_content', $box->post_content);
519 + $this->box->post_content = apply_filters('the_content', $this->box->post_content);
382 520
383 - $css_class_prefix = 'fb-';
521 + $mode = $this->box->params->get('mode');
384 522
385 - $position = $box->params->get('position', '');
386 - $position = !is_string($position) ? '' : $position;
387 -
388 523 /* Classes */
389 524 $css_class = [
390 - $box->ID,
391 - $position
525 + $this->box->ID,
526 + $mode
392 527 ];
528 +
529 + if (in_array($mode, ['popup', 'stickybar', 'sidebar', 'floating', 'slide-in']))
530 + {
531 + $position = $this->box->params->get('position', '');
532 + $position = !is_string($position) ? '' : $position;
533 + if ($position)
534 + {
535 + $css_class[] = $position;
536 + }
537 + }
538 + else if ($mode === 'fullscreen')
539 + {
540 + if ($center_content = $this->box->params->get('center_content', false)) {
541 + $css_class[] = 'center-content';
542 + }
543 + }
393 544
394 - $rtl = $box->params->get('rtl', '0');
395 - if ($rtl == '1')
396 - {
397 - $css_class[] = 'rtl';
398 - }
399 -
400 545 self::prefixCSSClasses($css_class);
401 546
402 - // class suffix
403 - $classSuffix = $box->params->get('classsuffix', '');
547 + // Class suffix
548 + $classSuffix = $this->box->params->get('classsuffix', '');
404 549 $classSuffix = is_string($classSuffix) ? $classSuffix : '';
405 550
406 551 $css_class[] = $classSuffix;
407 552
408 - $box->classes = $css_class;
553 + $this->box->classes = $css_class;
409 554
410 - // box shadow
411 - $boxshadow = (is_string($box->params->get('boxshadow', '1')) || is_int($box->params->get('boxshadow', '1'))) ? $box->params->get('boxshadow', '1') : '0';
412 -
555 + // Dialog CSS Classes
413 556 $dialog_css_classes = [
414 - $boxshadow != '0' ? 'shd' . $boxshadow : null
557 + // Add Box shadow
558 + $this->box->params->get('boxshadow') ? 'shdelevation' : null
415 559 ];
416 560
417 561 // Align Content
418 - $aligncontent = is_string($box->params->get('aligncontent')) ? explode(' ', $box->params->get('aligncontent')) : [];
562 + $aligncontent = is_string($this->box->params->get('aligncontent')) ? explode(' ', $this->box->params->get('aligncontent')) : [];
419 563 $dialog_css_classes = array_merge($dialog_css_classes, $aligncontent);
420 564
421 565 self::prefixCSSClasses($dialog_css_classes);
422 - $box->dialog_classes = $dialog_css_classes;
566 + $this->box->dialog_classes = $dialog_css_classes;
423 567
424 - /* CSS */
425 - // border
426 - $border = is_string($box->params->get('bordertype', 'none')) ? $box->params->get('bordertype', 'none') : 'none';
427 - $border_width = $box->params->get('borderwidth.value', 0) ? $box->params->get('borderwidth.value', 0) : 0;
428 - $border_unit = is_string($box->params->get('borderwidth.unit', 'px')) ? $box->params->get('borderwidth.unit', 'px') : 0;
429 - $border_color = is_string($box->params->get('bordercolor')) ? $box->params->get('bordercolor') : '';
430 -
431 - $style = [
432 - 'background-color' => $box->params->get('backgroundcolor'),
433 - 'color' => $box->params->get('textcolor'),
434 - 'border' => $border == 'none' ? null : $border . ' ' . $border_width . $border_unit . ' ' . $border_color,
435 - ];
436 -
437 - // add shared properties
438 - $shared_properties = [
439 - 'width' => [
440 - 'prefix_only' => true
441 - ],
442 - 'height' => [
443 - 'prefix_only' => true
444 - ],
445 - 'padding' => [],
446 - 'margin' => []
447 - ];
448 - foreach ($shared_properties as $prop => $prop_data)
449 - {
450 - // add dekstop property
451 - $value = (array) $box->params->get($prop . '_control.' . $prop, []);
452 - if (isset($value['desktop']))
453 - {
454 - $prefix_only = isset($prop_data['prefix_only']) ? (bool) $prop_data['prefix_only'] : false;
455 -
456 - $desktop_value = DimensionsHelper::parseDimensionsData($value['desktop'], $prop, false, $prefix_only);
457 - $style = array_merge($style, $desktop_value);
458 - }
459 - }
460 -
461 - // add dekstop border_radius
462 - $border_radius = (array) $box->params->get('borderradius_control.borderradius', []);
463 - if (isset($border_radius['desktop']))
464 - {
465 - $desktop_border_radius = DimensionsHelper::parseDimensionsBorderRadiusData($border_radius['desktop']);
466 - $style = array_merge($style, $desktop_border_radius);
467 - }
468 -
469 - // Background Image
470 - if ($box->params->get('bgimage', false))
471 - {
472 - $bgrepeat = is_string($box->params->get('bgrepeat')) ? $box->params->get('bgrepeat') : '';
473 - $bgsize = is_string($box->params->get('bgsize')) ? $box->params->get('bgsize') : '';
474 - $bgposition = is_string($box->params->get('bgposition')) ? $box->params->get('bgposition') : '';
475 -
476 - $style['background-image'] = 'url(\'' . esc_url($box->params->get('bgimagefile')) . '\')';
477 - $style['background-repeat'] = strtolower($bgrepeat);
478 - $style['background-size'] = strtolower($bgsize);
479 - $style['background-position'] = strtolower($bgposition);
480 - }
481 -
482 - $box->style = BoxHelper::arrayToCSSS($style);
483 -
484 568 $trigger_point_methods = [
485 - 'pageheight' => 'onScrollDepth',
486 - 'element' => 'onElementVisibility',
487 - 'pageready' => 'onPageReady',
488 569 'pageload' => 'onPageLoad',
489 - 'userleave' => 'onExit',
490 570 'onclick' => 'onClick',
491 571 'elementHover' => 'onHover',
492 - 'ondemand' => 'onDemand'
572 + 'ondemand' => 'onDemand',
573 +
493 574 ];
494 575
495 - // z index
496 - $zindex = $box->params->get('zindex', 99999) != 99999 ? $box->params->get('zindex') : null;
497 -
498 - $box->styles_container = BoxHelper::arrayToCSSS([
499 - 'z-index' => $zindex
500 - ]);
576 + /* Other Settings */
577 + $this->box->params->set('animation_duration', $this->box->params->get('animation_duration', 0.2));
501 578
502 - /* Other Settings */
503 - $scroll_depth = $box->params->get('scroll_depth', 'percentage');
504 - $scroll_depth = !is_array($scroll_depth) ? $scroll_depth : '';
579 + $scroll_amount = $this->box->params->get('scroll_amount', '80%');
505 580
506 - $animation_duration = $box->params->get('duration') ? (float) $box->params->get('duration') : 0;
507 -
581 + // Parse scroll_amount to extract unit and value
582 + $scroll_amount_data = $this->parseScrollAmount($scroll_amount);
583 +
584 + $delay = in_array($this->box->params->get('triggermethod'), ['floatingbutton', 'onexternallink']) ? 0 : (int) $this->box->params->get('triggerdelay') * 1000;
585 +
586 + $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');
587 +
588 + $trigger_element = is_scalar($this->box->params->get('triggerelement', '')) ? $this->box->params->get('triggerelement', '') : '';
589 +
508 590 // Use Namespaced classes for each trigger point and let them manipulate the settings dynamicaly.
509 - $box->settings = [
510 - 'trigger' => (is_string($box->params->get('triggermethod'))) && array_key_exists($box->params->get('triggermethod'), $trigger_point_methods) ? $trigger_point_methods[$box->params->get('triggermethod')] : $box->params->get('triggermethod'),
511 - 'trigger_selector' => $box->params->get('triggerelement'),
512 - 'delay' => (int) $box->params->get('triggerdelay') * 1000,
513 - 'scroll_depth' => $scroll_depth,
514 - 'scroll_depth_value' => $scroll_depth == 'percentage' ? (int) $box->params->get('triggerpercentage') : (int) $box->params->get('scroll_pixel'),
515 - 'firing_frequency' => (int) $box->params->get('firing_frequency', 1),
516 - 'reverse_scroll_close' => (bool) $box->params->get('autohide'),
517 - 'threshold' => (float) $box->params->get('threshold', 0) / 100,
518 - 'close_out_viewport' => (bool) $box->params->get('close_out_viewport', false),
519 - 'exit_timer' => (int) $box->params->get('exittimer') * 1000,
520 - 'idle_time' => (int) $box->params->get('idle_time') * 1000,
521 - 'animation_open' => $box->params->get('animationin'),
522 - 'animation_close' => $box->params->get('animationout'),
523 - 'animation_duration' => (float) $animation_duration * 1000,
524 - 'prevent_default' => (bool) $box->params->get('preventdefault', true),
525 - 'backdrop' => (bool) $box->params->get('overlay'),
526 - 'backdrop_color' => $box->params->get('overlay_color'),
527 - 'backdrop_click' => (bool) $box->params->get('overlayclick'),
528 - 'disable_page_scroll' => (bool) $box->params->get('preventpagescroll'),
529 - 'test_mode' => (bool) $box->params->get('testmode'),
591 + $this->box->settings = [
592 + 'name' => $this->box->post_title,
593 + 'trigger' => $trigger_method,
594 + 'trigger_selector' => $trigger_method === 'onExternalLink' ? '' : rtrim($trigger_element, ','),
595 + 'delay' => $delay,
596 +
597 + 'close_on_esc' => (bool) $this->box->params->get('close_on_esc', false),
598 + 'animation_open' => $this->box->params->get('animationin'),
599 + 'animation_close' => $this->box->params->get('animationout'),
600 + 'animation_duration' => (float) $this->box->params->get('animation_duration') * 1000,
601 + 'prevent_default' => true,
602 + 'backdrop' => (bool) $this->box->params->get('overlay'),
603 + 'backdrop_color' => $this->box->params->get('overlay_color'),
604 + 'backdrop_click' => (bool) $this->box->params->get('overlayclick'),
605 + 'disable_page_scroll' => (bool) $this->box->params->get('preventpagescroll'),
606 + 'test_mode' => (bool) $this->box->params->get('testmode'),
530 607 'debug' => (bool) $cParam->get('debug', false),
531 - 'ga_tracking' => (bool) $cParam->get('gaTrack', 0),
532 - 'ga_tracking_id' => $cParam->get('gaID', 0),
533 - 'ga_tracking_label' => $cParam->get('gaCategory'),
534 - 'auto_focus' => (bool) $box->params->get('autofocus', false)
608 + 'auto_focus' => (bool) $this->box->params->get('autofocus', false),
609 + 'mode' => $this->box->params->get('mode'),
610 + // Session-dependent display conditions resolved in the browser (see pass())
611 + 'client_rules' => $this->clientRuleGroups
535 612 ];
536 613
537 - // set padding and margin for other devices as well
538 - self::setResponsiveCSS($box);
614 + $this->css = new Styling\CSS($this->box);
539 615
540 - self::replaceBoxSmartTags($box);
616 + // Apply Popup CSS
617 + $this->box->params->set('customcss', $this->box->params->get('customcss') . $this->css->getCSS());
618 +
619 + $this->replaceBoxSmartTags();
620 +
621 + add_filter('the_content', 'wptexturize');
541 622 }
542 623
543 624 /**
544 - * Sets all responsive CSS to use in box view
625 + * Parses scroll_amount to extract unit and value
545 626 *
546 - * @param object $box
627 + * @param mixed $scroll_amount
547 628 *
548 - * @return void
629 + * @return array
549 630 */
550 - private static function setResponsiveCSS(&$box)
631 + private function parseScrollAmount($scroll_amount)
551 632 {
552 - $tablet = '';
553 - $mobile = '';
554 -
555 - /**
556 - * Handle width, height, padding, margin the same way.
557 - */
558 - $shared_properties = [
559 - 'width' => [
560 - 'prefix_only' => true
561 - ],
562 - 'height' => [
563 - 'prefix_only' => true
564 - ],
565 - 'padding' => [],
566 - 'margin' => []
567 - ];
568 -
569 - foreach ($shared_properties as $prop => $prop_settings)
633 + if (is_array($scroll_amount))
570 634 {
571 - $prop_data = (array) $box->params->get($prop . '_control.' . $prop, []);
572 -
573 - $prefix_only = isset($prop_settings['prefix_only']) ? $prop_settings['prefix_only'] : false;
574 -
575 - // assign each device CSS
576 - if (isset($prop_data['tablet']))
577 - {
578 - $tablet .= BoxHelper::arrayToCSSS(DimensionsHelper::parseDimensionsData($prop_data['tablet'], $prop, true, $prefix_only));
579 - }
580 - if (isset($prop_data['mobile']))
581 - {
582 - $mobile .= BoxHelper::arrayToCSSS(DimensionsHelper::parseDimensionsData($prop_data['mobile'], $prop, true, $prefix_only));
583 - }
635 + return [
636 + 'unit' => $scroll_amount['unit'] ?? '%',
637 + 'value' => $scroll_amount['value'] ?? 80
638 + ];
584 639 }
585 640
586 - // border radius requires a special method
587 - $border_radius = (array) $box->params->get('borderradius_control.borderradius', []);
588 - if (isset($border_radius['tablet']))
641 + if (is_string($scroll_amount) && preg_match('/^(\d+)(px|%)$/', $scroll_amount, $matches))
589 642 {
590 - $tablet .= BoxHelper::arrayToCSSS(DimensionsHelper::parseDimensionsBorderRadiusData($border_radius['tablet'], true));
643 + return [
644 + 'unit' => $matches[2],
645 + 'value' => $matches[1]
646 + ];
591 647 }
592 - if (isset($border_radius['mobile']))
593 - {
594 - $mobile .= BoxHelper::arrayToCSSS(DimensionsHelper::parseDimensionsBorderRadiusData($border_radius['mobile'], true));
595 - }
596 648
597 - $responsive_css = [
598 - 'tablet' => $tablet,
599 - 'mobile' => $mobile
649 + return [
650 + 'unit' => '%',
651 + 'value' => 80
600 652 ];
601 - $box->params->set('responsive_css', $responsive_css);
602 653 }
603 -
654 +
604 655 /**
605 656 * Replaces all box smart tags
606 - *
607 - * @param object $box
608 - *
609 - * @return object
657 + *
658 + * @return void
610 659 */
611 - public static function replaceBoxSmartTags(&$box)
660 + public function replaceBoxSmartTags()
612 661 {
613 662 $tags = new \FPFramework\Base\SmartTags\SmartTags();
614 663
615 664 // register FB Smart Tags
616 - $tags->register('\FireBox\Core\SmartTags', FBOX_BASE_FOLDER . '/Inc/Core/SmartTags', $box);
665 + $tags->register('\FireBox\Core\SmartTags', FBOX_BASE_FOLDER . '/Inc/Core/SmartTags', $this->box);
617 666
618 - $box = $tags->replace($box);
667 + $this->box = $tags->replace($this->box);
619 668 }
620 669
621 670 /**
622 671 * Checks if a box passes assignments
623 672 *
624 - * @param object $box
625 - *
626 673 * @return boolean
627 674 */
628 - public function pass($box)
675 + public function pass()
629 676 {
630 - if (!$box || !is_object($box))
677 + $this->clientRuleGroups = [];
678 +
679 + if (!$this->box || !is_object($this->box))
631 680 {
632 681 return false;
633 682 }
634 683
635 - // set box settings if empty
636 - if (empty($this->boxSettings))
684 + // Check first local assignments
685 + if (!$this->passLocalAssignments())
686 + {
687 + return false;
688 + }
689 +
690 + $displayConditionsType = $this->box->params->get('display_conditions_type', '');
691 +
692 + // If empty, display popup sitewide
693 + if (empty($displayConditionsType) || $displayConditionsType === 'all')
694 + {
695 + return true;
696 + }
697 +
698 + // Mirror Display Conditions of another popup.
699 + if ($displayConditionsType == 'mirror' && $mirror_box_id = $this->box->params->get('mirror_box'))
700 + {
701 + $this->box->params->merge(self::getAssignmentsForMirroring($mirror_box_id));
702 + }
703 +
704 + // Get a recursive array of all rules
705 + $rules = $this->box->params->get('rules', []);
706 + $rules = is_string($rules) ? json_decode($rules, true) : self::recursiveToArray($rules);
707 +
708 + // Normalize to an array; an empty or invalid-JSON "rules" string decodes to null.
709 + $rules = is_array($rules) ? $rules : [];
710 +
711 + // The "PHP" display condition runs campaign-authored PHP server-side on every
712 + // visitor request (Conditions\PHP -> Executer). Evaluate it only when the campaign
713 + // author holds firebox_execute_php; otherwise treat the PHP condition as unmet
714 + // rather than running it — an "all" group that requires it fails, an "any" group
715 + // falls back to its other conditions. If every group depended on PHP, there is
716 + // nothing left to match and the campaign does not show. Runs after the mirror merge
717 + // above, so it also covers PHP rules pulled in from a mirrored box.
718 + if (self::hasPHPRule($rules) && !\FireBox\Core\Helpers\PHPExecution::isAllowedForCampaign($this->box->ID))
637 719 {
638 - $this->boxSettings = $box;
720 + $rules = self::withoutPHPRules($rules);
721 +
722 + if (empty($rules))
723 + {
724 + return false;
725 + }
639 726 }
640 727
641 - // Prepare boxes that mirror other boxes assignments
642 - if ($box->params->get('mirror', false) && $mirror_box_id = $box->params->get('mirror_box'))
728 + // If testmode is enabled disable the User Groups condition
729 + if ($this->box->params->get('testmode'))
643 730 {
644 - $box->params->merge(self::getAssignmentsForMirroring($mirror_box_id));
731 + foreach ($rules as $key => &$group)
732 + {
733 + if (!isset($group['rules']) || !is_array($group['rules']))
734 + {
735 + continue;
736 + }
737 +
738 + foreach ($group['rules'] as $_key => &$rule)
739 + {
740 + if (!isset($rule['name']) || empty($rule['name']))
741 + {
742 + continue;
743 + }
744 +
745 + if ($rule['name'] === 'WP\UserGroup')
746 + {
747 + unset($group['rules'][$_key]);
748 + }
749 + }
750 + }
751 + unset($group);
645 752 }
646 753
647 - // Check first local assignments
648 - if (!$this->passLocalAssignments($box))
754 + // Check framework based conditions. Session-dependent (client-side) rules are
755 + // deferred: the box renders hidden and the frontend runtime resolves them.
756 + $result = \FPFramework\Base\Conditions\ConditionBuilder::passWithClientRules($rules, $this->factory);
757 +
758 + $this->clientRuleGroups = $result['client_groups'];
759 +
760 + return $result['pass'];
761 + }
762 +
763 + /**
764 + * Display condition groups deferred to the frontend runtime by pass().
765 + *
766 + * @return array
767 + */
768 + public function getClientRuleGroups()
769 + {
770 + return $this->clientRuleGroups;
771 + }
772 +
773 + /**
774 + * Whether a single rule is a "PHP" condition that carries code to run.
775 + *
776 + * The name is matched case-insensitively: PHP class names resolve regardless
777 + * of case, so ConditionsHelper::getCondition() would load the PHP condition for
778 + * 'php', 'Php', etc. An empty value is ignored: there is nothing to run.
779 + *
780 + * @param array $rule A single display-condition rule.
781 + *
782 + * @return bool
783 + */
784 + private static function isPHPRule($rule)
785 + {
786 + if (!is_array($rule) || !isset($rule['name']) || !is_string($rule['name']))
649 787 {
650 788 return false;
651 789 }
652 790
653 - // If testmode is enabled disable the User Groups assignment
654 - if ($box->params->get('testmode'))
791 + if (strtolower(trim($rule['name'])) !== 'php')
655 792 {
656 - $box->params->set('assignments.assign_grouplevel.selection', '0');
657 - }
658 -
659 - $globalAssignments = $this->passGlobalAssignments($box);
660 -
661 - // Check framework based assignments
662 - return $globalAssignments;
663 - }
793 + return false;
794 + }
664 795
665 - /**
666 - * Passes framework based global assignments
667 - *
668 - * @param object $box
669 - *
670 - * @return boolean
671 - */
672 - private function passGlobalAssignments($box)
796 + // Only a non-empty string value can be executed as PHP.
797 + return isset($rule['value']) && is_string($rule['value']) && trim($rule['value']) !== '';
798 + }
799 +
800 + /**
801 + * Whether any rule in the given groups is a "PHP" condition that carries code.
802 + *
803 + * @param array $rules Recursive display-condition groups.
804 + *
805 + * @return bool
806 + */
807 + private static function hasPHPRule($rules)
673 808 {
674 - $assignments = new \FPFramework\Base\Assignments($this->factory);
675 - $pass = $assignments->passAll($box, $box->params->get('assignmentMatchingMethod', 'and'));
676 - return $pass;
809 + if (!is_array($rules))
810 + {
811 + return false;
812 + }
813 +
814 + foreach ($rules as $group)
815 + {
816 + if (!isset($group['rules']) || !is_array($group['rules']))
817 + {
818 + continue;
819 + }
820 +
821 + foreach ($group['rules'] as $rule)
822 + {
823 + if (self::isPHPRule($rule))
824 + {
825 + return true;
826 + }
827 + }
828 + }
829 +
830 + return false;
677 831 }
678 832
679 833 /**
680 - * Check if a box passes local assignments
834 + * Returns the rule groups with runnable "PHP" conditions treated as unmet, for a
835 + * campaign whose author may not run PHP. A group's matching_method decides how:
681 836 *
682 - * @param object $box
683 - *
837 + * - "all" (every rule must pass): the PHP condition can never be satisfied, so the
838 + * whole group fails and is dropped.
839 + * - "any" (one rule is enough): the PHP condition is removed so it cannot be the
840 + * reason the group passes, but the group's other conditions still evaluate. A group
841 + * left with no other conditions is dropped.
842 + *
843 + * Groups without a PHP condition are returned untouched.
844 + *
845 + * @param array $rules Recursive display-condition groups.
846 + *
847 + * @return array
848 + */
849 + private static function withoutPHPRules($rules)
850 + {
851 + if (!is_array($rules))
852 + {
853 + return [];
854 + }
855 +
856 + $filtered = [];
857 +
858 + foreach ($rules as $group)
859 + {
860 + if (!isset($group['rules']) || !is_array($group['rules']))
861 + {
862 + $filtered[] = $group;
863 + continue;
864 + }
865 +
866 + $kept = [];
867 + $hasPHP = false;
868 +
869 + foreach ($group['rules'] as $rule)
870 + {
871 + if (self::isPHPRule($rule))
872 + {
873 + $hasPHP = true;
874 + continue;
875 + }
876 +
877 + $kept[] = $rule;
878 + }
879 +
880 + if (!$hasPHP)
881 + {
882 + $filtered[] = $group;
883 + continue;
884 + }
885 +
886 + $method = isset($group['matching_method']) ? $group['matching_method'] : 'all';
887 +
888 + // "all": a required PHP condition is unmet, so the group can never pass.
889 + if ($method === 'all')
890 + {
891 + continue;
892 + }
893 +
894 + // "any": keep the remaining alternatives, unless PHP was the only one.
895 + if (!empty($kept))
896 + {
897 + $group['rules'] = $kept;
898 + $filtered[] = $group;
899 + }
900 + }
901 +
902 + return $filtered;
903 + }
904 +
905 + /**
906 + * Check if a box passes local conditions
907 + *
684 908 * @return boolean
685 909 */
686 - private function passLocalAssignments($box)
910 + private function passLocalAssignments()
687 911 {
688 912 $localAssignments = new \FireBox\Core\FB\Assignments($this, $this->factory);
689 913 return $localAssignments->passAll();
690 914 }
@@ -695,49 +919,48 @@
695 919 * @param int $box_id
696 920 *
697 921 * @return object
698 922 */
699 - private static function getAssignmentsForMirroring($box_id)
700 - {
701 - $payload = [
702 - 'where' => [
703 - 'ID' => ' = ' . $box_id,
704 - 'post_status' => " = 'publish'",
705 - 'post_type' => " = 'firebox'"
706 - ]
707 - ];
708 -
709 - // Load box
710 - if (!$box = firebox()->tables->box->getResults($payload))
923 + private function getAssignmentsForMirroring($box_id)
924 + {
925 + // Several campaigns commonly mirror the same source, and every campaign is
926 + // evaluated on every page view, so resolve each source at most once per request.
927 + static $cache = [];
928 +
929 + $box_id = intval($box_id);
930 +
931 + if (array_key_exists($box_id, $cache))
711 932 {
712 - return;
933 + return $cache[$box_id];
713 934 }
714 -
715 - $box = $box[0];
716 -
717 - // get meta options for box
718 - $meta = get_post_meta($box_id, 'fpframework_meta_settings', true);
719 - $box->params = $meta;
720 935
721 - // To prevent user frustration, we ignore the following assignments because they are not displayed in the Publishing Assignments.
722 - $params_to_ignore = [
723 - 'assign_impressions'
724 - ];
936 + $cache[$box_id] = null;
725 937
726 - $assignments = [];
938 + // The published campaigns are already loaded (and their meta primed) for this
939 + // request, so read the mirrored campaign from that list instead of querying again.
940 + $boxes = BoxHelper::getAllBoxes();
941 + $mirrored = null;
727 942
728 - // Gather params to merge
729 - foreach ($box->params as $param_key => $param_value)
730 - {
731 - if (strpos($param_key, 'assign') === false || in_array($param_key, $params_to_ignore))
732 - {
733 - continue;
734 - }
943 + foreach ($boxes->posts as $box)
944 + {
945 + if ((int) $box->ID === $box_id)
946 + {
947 + $mirrored = $box;
948 + break;
949 + }
950 + }
735 951
736 - $assignments[$param_key] = $param_value;
952 + if (!$mirrored)
953 + {
954 + return $cache[$box_id];
737 955 }
738 -
739 - return new Registry($assignments);
956 +
957 + // get meta options for box
958 + $params = new Registry(BoxHelper::getMeta($mirrored->ID));
959 +
960 + $cache[$box_id] = new Registry(['rules' => $params->get('rules')]);
961 +
962 + return $cache[$box_id];
740 963 }
741 964
742 965 /**
743 966 * Prefixes the CSS classes
@@ -774,10 +997,15 @@
774 997 public function logOpenEvent($box_id, $page = null, $referrer = null)
775 998 {
776 999 $box = $this->get($box_id);
777 1000
1001 + if (!is_object($box) || !isset($box->params))
1002 + {
1003 + return;
1004 + }
1005 +
778 1006 // Do not track if statistics option is disabled
779 - $track_open_event = (bool) (is_null($box->params->get('stats', null)) ? BoxHelper::getParams()->get('stats', 1) : $box->params->get('stats'));
1007 + $track_open_event = (bool) (is_null($box->params->get('stats', null)) ? true : $box->params->get('stats'));
780 1008 if (!$track_open_event)
781 1009 {
782 1010 return;
783 1011 }
@@ -796,10 +1024,15 @@
796 1024 public function logCloseEvent($box_id, $box_log_id)
797 1025 {
798 1026 $box = $this->get($box_id);
799 1027
1028 + if (!is_object($box) || !isset($box->params))
1029 + {
1030 + return null;
1031 + }
1032 +
800 1033 // Do not track if statistics option is disabled
801 - $track_open_event = (bool) (is_null($box->params->get('stats', null)) ? BoxHelper::getParams()->get('stats', 1) : $box->params->get('stats'));
1034 + $track_open_event = (bool) (is_null($box->params->get('stats', null)) ? true : $box->params->get('stats'));
802 1035 if (!$track_open_event)
803 1036 {
804 1037 return null;
805 1038 }
@@ -805,69 +1038,85 @@
805 1038 }
806 1039
807 1040 firebox()->log->track($box_id, 2, $box_log_id);
808 1041 }
809 -
1042 +
810 1043 /**
811 - * Get box impressions
1044 + * Get total box impressions
812 1045 *
813 1046 * @param array $payload
814 1047 *
815 1048 * @return array
816 1049 */
817 - public function getImpressions($payload)
1050 + public function getTotalImpressions($payload)
818 1051 {
819 - return firebox()->tables->boxlog->getResults($payload);
1052 + // Cached: the same campaign/period is counted once per request even when
1053 + // several conditions ask for it.
1054 + return firebox()->tables->boxlog->getResults($payload, true, true);
820 1055 }
821 -
1056 +
822 1057 /**
823 - * Get total box impressions
1058 + * Returns the cookie instance.
824 1059 *
825 - * @param array $payload
826 - *
827 - * @return array
1060 + * @return mixed
828 1061 */
829 - public function getTotalImpressions($payload)
1062 + public function getCookie()
830 1063 {
831 - return count($this->getImpressions($payload));
1064 + if (!$this->box)
1065 + {
1066 + return;
1067 + }
1068 +
1069 + return new Cookie($this->box);
832 1070 }
833 1071
834 1072 /**
835 - * Return the box settings
1073 + * Returns the box.
836 1074 *
837 1075 * @return object
838 1076 */
839 - public function getBoxSettings()
1077 + public function getBox()
840 1078 {
841 - return $this->boxSettings;
1079 + return $this->box;
842 1080 }
843 1081
844 1082 /**
845 - * Sets the box settings
1083 + * Sets the box.
846 1084 *
847 - * @param object $settings
1085 + * @param object $box
848 1086 *
849 - * @return object
1087 + * @return Box
850 1088 */
851 - public function setBoxSettings($settings)
1089 + public function setBox($box)
852 1090 {
853 - $this->boxSettings = $settings;
1091 + $this->box = $box;
1092 +
1093 + return $this;
854 1094 }
855 1095
856 - /**
857 - * Returns the box cookie
858 - *
859 - * @param string $cookie
860 - *
861 - * @return mixed
862 - */
863 - public function getCookie($cookie = null)
1096 + public function getParams()
864 1097 {
865 - $cookie = $cookie ? $cookie : (isset($this->boxSettings->ID) ? $this->boxSettings->ID : null);
866 - if (!isset($cookie))
1098 + return $this->params;
1099 + }
1100 +
1101 + public function setParams($params)
1102 + {
1103 + $this->params = $params;
1104 +
1105 + return $this;
1106 + }
1107 +
1108 + public function getCampaignParams()
1109 + {
1110 + return isset($this->box->params) ? $this->box->params : null;
1111 + }
1112 +
1113 + public function setCampaignParams($params)
1114 + {
1115 + if (isset($this->box->params))
867 1116 {
868 - return;
1117 + $this->box->params = $params;
869 1118 }
870 -
871 - return new Cookie($cookie);
1119 +
1120 + return $this;
872 1121 }
873 -}
1122 +}