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 +741 -487 1.0.2 → 3.1.14 View file →
@@ -1,12 +1,12 @@
1 1 <?php
2 2 /**
3 3 * @package FireBox
4 - * @version 1.0.2 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,43 +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 {
80 + if ($box)
81 + {
82 + $this->box = $this->prepareConstructorBox($box);
83 + }
84 +
46 85 if (!$factory)
47 86 {
48 87 $factory = new \FPFramework\Base\Factory();
49 88 }
50 -
51 - $this->boxSettings = $boxSettings;
52 89 $this->factory = $factory;
90 +
91 + $this->params = new Registry(BoxHelper::getParams());
53 92 }
54 93
55 94 /**
56 - * Gets published box.
95 + * Allow to set either a box ID or box object
96 + * and we then set the box object.
57 97 *
58 - * @param integer $id
59 - * @param string $status
98 + * @param mixed $box
60 99 *
61 - * @return array
100 + * @return object
62 101 */
63 - public function get($id, $status = 'publish')
102 + private function prepareConstructorBox($box)
64 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 +
65 127 $payload = [
66 128 'where' => [
67 - 'ID' => ' = ' . $id,
129 + 'ID' => ' = ' . intval($id),
68 130 'post_type' => " = 'firebox'"
69 131 ]
70 132 ];
71 133
@@ -71,135 +133,197 @@
71 133
72 134 // apply status if given
73 135 if ($status)
74 136 {
75 - $payload['where']['post_status'] = ' = \'' . $status . '\'';
137 + $payload['where']['post_status'] = ' = \'' . sanitize_key($status) . '\'';
76 138 }
77 -
78 - if (!$box = firebox()->models->box->getResults($payload))
139 +
140 + if (!$box = firebox()->tables->box->getResults($payload))
79 141 {
80 - return [];
142 + return null;
81 143 }
82 144
83 145 if (!isset($box[0]))
84 146 {
85 - return [];
147 + return null;
86 148 }
87 149
88 - $box = $box[0];
150 + $this->box = $box[0];
89 151
90 152 // get meta options for box
91 - $meta = self::getMeta($id);
92 - $box->params = new Registry($meta);
153 + $meta = \FireBox\Core\Helpers\BoxHelper::getMeta($id);
154 + $this->box->params = new Registry($meta);
93 155
94 - return $box;
156 + return $this->box;
95 157 }
96 158
97 159 /**
98 - * Get box meta
160 + * Renders the box.
99 161 *
100 - * @param int $id
101 - *
102 - * @return array
162 + * @return void
103 163 */
104 - public static function getMeta($id)
164 + public function render()
105 165 {
106 - 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;
107 211 }
108 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 +
109 267 /**
110 - * Render box
268 + * Gets the Custom CSS of the popup.
111 269 *
112 270 * @return string
113 271 */
114 - public function render($box = null)
272 + public function getCustomCSS()
115 273 {
116 - // we do not have a passed box or box settings from constructor, abort
117 - // as we cannot render a box without any box parameters
118 - if (!$box && !$this->boxSettings)
274 + if (!is_object($this->box) || !isset($this->box->params))
119 275 {
120 - return;
276 + return '';
121 277 }
122 278
123 - // if a box was given to the function, use these settings instead
124 - if ($box)
125 - {
126 - $this->boxSettings = $box;
127 - }
279 + $css = $this->box->params->get('customcss', '');
128 280
129 - // if we have box settings, then set up our box with these settings
130 - if ($this->boxSettings)
281 + if (!is_string($css))
131 282 {
132 - $box = $this->boxSettings;
283 + return '';
133 284 }
134 285
135 - // Check Publishing Assignments
136 - if (!$this->pass($box))
137 - {
138 - return;
139 - }
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 + }
140 294
141 - // get settings params
142 - $params = BoxHelper::getParams();
143 -
144 - 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))
145 306 {
146 - /**
147 - * Loads all media files.
148 - */
149 - add_action('wp_enqueue_scripts', function() use ($box, $params) {
150 - // load box media
151 - $this->loadBoxMedia($box, $params);
152 - });
307 + $data = get_object_vars($data);
153 308 }
154 - else
309 +
310 + if (!is_array($data))
155 311 {
156 - // load box media
157 - $this->loadBoxMedia($box, $params);
312 + return [];
158 313 }
159 314
160 - self::prepare($box);
161 -
162 - if (!did_action('wp_head'))
315 + foreach ($data as $key => $value)
163 316 {
164 - /**
165 - * Custom CSS
166 - */
167 - add_action('wp_head', function() use ($box) {
168 - if (!empty($box->params->get('customcss', '')))
169 - {
170 - echo '<style type="text/css">' . $box->params->get('customcss') . '</style>';
171 - }
172 - });
173 - }
174 - else
175 - {
176 - if (!empty($box->params->get('customcss', '')))
317 + if (is_object($value) || is_array($value))
177 318 {
178 - echo '<style type="text/css">' . $box->params->get('customcss') . '</style>';
319 + $data[$key] = self::recursiveToArray($value);
179 320 }
180 321 }
181 -
182 - /**
183 - * Runs before rendering the box.
184 - */
185 - do_action('firebox/box/before_render', $box);
186 322
187 - // after we prepare it, update box settings
188 - $this->boxSettings = $box;
323 + return $data;
324 + }
189 325
190 - // payload
191 - $payload = [
192 - 'box' => $box,
193 - 'params' => $params,
194 - ];
195 -
196 - // return box template
197 - add_action('wp_footer', function() use ($box, $payload) {
198 - echo firebox()->renderer->public->render('box', $payload, true);
199 - });
200 - }
201 -
202 326 /**
203 327 * Send a helpful object to JavaScript files
204 328 *
205 329 * @return void
@@ -211,89 +335,69 @@
211 335 return;
212 336 }
213 337 self::$loadedLocalizedScript = true;
214 338
215 - $data = array(
216 - 'ajax_url' => admin_url('admin-ajax.php'),
217 - 'nonce' => wp_create_nonce('fbox_js_nonce'),
218 - 'site_url' => site_url('/'),
219 - 'referrer' => wp_get_referer()
220 - );
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('/'),
221 348
222 - 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');
223 365 }
224 366
225 367 /**
226 368 * Load Box Media
227 369 *
228 - * @param array $box
229 - * @param array $params
230 - *
231 370 * @return void
232 371 */
233 - public function loadBoxMedia($box, $params)
372 + public function loadBoxMedia($box)
234 373 {
235 374 $box = new Registry($box);
236 - $params = new Registry($params);
237 375
238 - // Add polyfills for Internet Explorer
239 - $browser = $this->factory->getBrowser();
240 - if ($browser['name'] == 'ie')
241 - {
242 - wp_enqueue_script(
243 - 'firebox-ie11-polyfill',
244 - 'https://polyfill.io/v3/polyfill.min.js?features=NodeList.prototype.forEach%2CElement.prototype.closest%2CArray.prototype.forEach%2CArray.prototype.find%2CIntersectionObserver%2CIntersectionObserverEntry',
245 - [],
246 - FBOX_VERSION,
247 - false
248 - );
249 - }
376 + $this->loadAnimationsMedia($box);
250 377
251 378 /**
252 - * Velocity
379 + * FireBox JS
253 380 */
254 - if ($params->get('loadVelocity', true))
255 - {
256 - wp_enqueue_script(
257 - 'firebox-velocity',
258 - FBOX_MEDIA_PUBLIC_URL . 'js/vendor/velocity.js',
259 - [],
260 - FBOX_VERSION,
261 - false
262 - );
263 - wp_enqueue_script(
264 - 'firebox-velocity-ui',
265 - FBOX_MEDIA_PUBLIC_URL . 'js/vendor/velocity.ui.js',
266 - [],
267 - FBOX_VERSION,
268 - false
269 - );
381 + wp_enqueue_script('firebox-main');
270 382
271 - /**
272 - * Animations
273 - */
274 - if (strpos($box->get('params.data.animationin'), 'firebox') !== false || strpos($box->get('params.data.animationout'), 'firebox') !== false)
275 - {
276 - wp_enqueue_script(
277 - 'firebox-animations',
278 - FBOX_MEDIA_PUBLIC_URL . 'js/animations.js',
279 - [],
280 - FBOX_VERSION,
281 - false
282 - );
283 - }
284 - }
383 +
285 384
286 385 /**
287 - * 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.
288 392 */
289 - wp_enqueue_script(
290 - 'firebox',
291 - FBOX_MEDIA_PUBLIC_URL . 'js/firebox.js',
292 - [],
293 - FBOX_VERSION,
294 - false
295 - );
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 + }
296 400
297 401 // run above the main JS script to run only once
298 402 self::setJSObject();
299 403
@@ -299,18 +403,14 @@
299 403
300 404 /**
301 405 * FireBox CSS
302 406 */
303 - if ($params->get('loadCSS', true))
304 - {
305 - wp_enqueue_style(
306 - 'firebox',
307 - FBOX_MEDIA_PUBLIC_URL . 'css/firebox.css',
308 - [],
309 - FBOX_VERSION,
310 - false
311 - );
312 - }
407 + wp_enqueue_style(
408 + 'firebox',
409 + FBOX_MEDIA_PUBLIC_URL . 'css/firebox.css',
410 + [],
411 + FBOX_VERSION
412 + );
313 413
314 414 /**
315 415 * Page Slide mode JS
316 416 */
@@ -315,371 +415,500 @@
315 415 * Page Slide mode JS
316 416 */
317 417 if ($box->get('params.data.mode') == 'pageslide')
318 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.
319 424 wp_enqueue_script(
320 425 'firebox-pageslide-mode',
321 426 FBOX_MEDIA_PUBLIC_URL . 'js/pageslide_mode.js',
322 - [],
427 + ['firebox-main'],
323 428 FBOX_VERSION,
324 - false
429 + ['in_footer' => true, 'strategy' => 'defer']
325 430 );
326 431 }
327 432
328 433
329 -
330 - $this->loadThemeCSSOverrides();
331 434 }
332 435
333 436 /**
334 - * Some themes require overrides to preserve as much as we can the styling of the popups.
335 - *
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 + *
336 446 * @return void
337 447 */
338 - private function loadThemeCSSOverrides()
448 + private function loadAnimationsMedia($box)
339 449 {
340 - $active_theme = wp_get_theme();
341 - $theme = $active_theme->template;
342 -
343 450 /**
344 - * 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.
345 453 */
346 - $themes = [
347 - 'twentytwentyone'
348 - ];
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 + );
349 462
350 - 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)
351 474 {
352 475 return;
353 476 }
354 477
355 - wp_enqueue_style(
356 - 'firebox-theme-' . $theme . '-override',
357 - FBOX_MEDIA_PUBLIC_URL . 'css/themes/' . $theme . '.css',
358 - [],
359 - FBOX_VERSION,
360 - false
361 - );
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 + }
362 505 }
363 506
364 507 /**
365 508 * Prepares the box before rendering
366 - *
367 - * @param array $box
368 - *
509 + *
369 510 * @return void
370 511 */
371 - public static function prepare(&$box)
512 + public function prepare()
372 513 {
514 + remove_filter('the_content', 'wptexturize');
515 +
373 516 $cParam = BoxHelper::getParams();
374 517 $cParam = new Registry($cParam);
375 518
376 - $box->post_content = apply_filters('the_content', $box->post_content);
519 + $this->box->post_content = apply_filters('the_content', $this->box->post_content);
377 520
378 - $css_class_prefix = 'fb-';
521 + $mode = $this->box->params->get('mode');
379 522
380 - $position = $box->params->get('position', '');
381 - $position = !is_string($position) ? '' : $position;
382 -
383 523 /* Classes */
384 524 $css_class = [
385 - $box->ID,
386 - $position
525 + $this->box->ID,
526 + $mode
387 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 + }
388 544
389 - $rtl = $box->params->get('rtl', '0');
390 - if ($rtl == '1')
391 - {
392 - $css_class[] = 'rtl';
393 - }
394 -
395 545 self::prefixCSSClasses($css_class);
396 546
397 - // class suffix
398 - $classSuffix = $box->params->get('classsuffix', '');
547 + // Class suffix
548 + $classSuffix = $this->box->params->get('classsuffix', '');
399 549 $classSuffix = is_string($classSuffix) ? $classSuffix : '';
400 550
401 551 $css_class[] = $classSuffix;
402 552
403 - $box->classes = $css_class;
553 + $this->box->classes = $css_class;
404 554
405 - // box shadow
406 - $boxshadow = (is_string($box->params->get('boxshadow', '1')) || is_int($box->params->get('boxshadow', '1'))) ? $box->params->get('boxshadow', '1') : '0';
407 -
555 + // Dialog CSS Classes
408 556 $dialog_css_classes = [
409 - $boxshadow != '0' ? 'shd' . $boxshadow : null
557 + // Add Box shadow
558 + $this->box->params->get('boxshadow') ? 'shdelevation' : null
410 559 ];
411 560
412 561 // Align Content
413 - $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')) : [];
414 563 $dialog_css_classes = array_merge($dialog_css_classes, $aligncontent);
415 564
416 565 self::prefixCSSClasses($dialog_css_classes);
417 - $box->dialog_classes = $dialog_css_classes;
566 + $this->box->dialog_classes = $dialog_css_classes;
418 567
419 - /* CSS */
420 - // border
421 - $border = is_string($box->params->get('bordertype', 'none')) ? $box->params->get('bordertype', 'none') : 'none';
422 - $border_width = $box->params->get('borderwidth.value', 0) ? $box->params->get('borderwidth.value', 0) : 0;
423 - $border_unit = is_string($box->params->get('borderwidth.unit', 'px')) ? $box->params->get('borderwidth.unit', 'px') : 0;
424 - $border_color = is_string($box->params->get('bordercolor')) ? $box->params->get('bordercolor') : '';
425 -
426 - $style = [
427 - 'background-color' => $box->params->get('backgroundcolor'),
428 - 'color' => $box->params->get('textcolor'),
429 - 'border' => $border == 'none' ? null : $border . ' ' . $border_width . $border_unit . ' ' . $border_color,
430 - ];
431 -
432 - // add shared properties
433 - $shared_properties = [
434 - 'width' => [
435 - 'prefix_only' => true
436 - ],
437 - 'height' => [
438 - 'prefix_only' => true
439 - ],
440 - 'padding' => [],
441 - 'margin' => []
442 - ];
443 - foreach ($shared_properties as $prop => $prop_data)
444 - {
445 - // add dekstop property
446 - $value = (array) $box->params->get($prop . '_control.' . $prop, []);
447 - if (isset($value['desktop']))
448 - {
449 - $prefix_only = isset($prop_data['prefix_only']) ? (bool) $prop_data['prefix_only'] : false;
450 -
451 - $desktop_value = DimensionsHelper::parseDimensionsData($value['desktop'], $prop, false, $prefix_only);
452 - $style = array_merge($style, $desktop_value);
453 - }
454 - }
455 -
456 - // add dekstop border_radius
457 - $border_radius = (array) $box->params->get('borderradius_control.borderradius', []);
458 - if (isset($border_radius['desktop']))
459 - {
460 - $desktop_border_radius = DimensionsHelper::parseDimensionsBorderRadiusData($border_radius['desktop']);
461 - $style = array_merge($style, $desktop_border_radius);
462 - }
463 -
464 - // Background Image
465 - if ($box->params->get('bgimage', false))
466 - {
467 - $bgrepeat = is_string($box->params->get('bgrepeat')) ? $box->params->get('bgrepeat') : '';
468 - $bgsize = is_string($box->params->get('bgsize')) ? $box->params->get('bgsize') : '';
469 - $bgposition = is_string($box->params->get('bgposition')) ? $box->params->get('bgposition') : '';
470 -
471 - $style['background-image'] = 'url(\'' . esc_url($box->params->get('bgimagefile')) . '\')';
472 - $style['background-repeat'] = strtolower($bgrepeat);
473 - $style['background-size'] = strtolower($bgsize);
474 - $style['background-position'] = strtolower($bgposition);
475 - }
476 -
477 - $box->style = BoxHelper::arrayToCSSS($style);
478 -
479 568 $trigger_point_methods = [
480 - 'pageheight' => 'onScrollDepth',
481 - 'element' => 'onElementVisibility',
482 - 'pageready' => 'onPageReady',
483 569 'pageload' => 'onPageLoad',
484 - 'userleave' => 'onExit',
485 570 'onclick' => 'onClick',
486 571 'elementHover' => 'onHover',
487 - 'ondemand' => 'onDemand'
572 + 'ondemand' => 'onDemand',
573 +
488 574 ];
489 575
490 - // z index
491 - $zindex = $box->params->get('zindex', 99999) != 99999 ? $box->params->get('zindex') : null;
492 -
493 - $box->styles_container = BoxHelper::arrayToCSSS([
494 - 'z-index' => $zindex
495 - ]);
576 + /* Other Settings */
577 + $this->box->params->set('animation_duration', $this->box->params->get('animation_duration', 0.2));
496 578
497 - /* Other Settings */
498 - $scroll_depth = $box->params->get('scroll_depth', 'percentage');
499 - $scroll_depth = !is_array($scroll_depth) ? $scroll_depth : '';
579 + $scroll_amount = $this->box->params->get('scroll_amount', '80%');
500 580
501 - $animation_duration = $box->params->get('duration') ? (float) $box->params->get('duration') : 0;
502 -
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 +
503 590 // Use Namespaced classes for each trigger point and let them manipulate the settings dynamicaly.
504 - $box->settings = [
505 - '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'),
506 - 'trigger_selector' => $box->params->get('triggerelement'),
507 - 'delay' => (int) $box->params->get('triggerdelay') * 1000,
508 - 'scroll_depth' => $scroll_depth,
509 - 'scroll_depth_value' => $scroll_depth == 'percentage' ? (int) $box->params->get('triggerpercentage') : (int) $box->params->get('scroll_pixel'),
510 - 'firing_frequency' => (int) $box->params->get('firing_frequency', 1),
511 - 'reverse_scroll_close' => (bool) $box->params->get('autohide'),
512 - 'threshold' => (float) $box->params->get('threshold', 0) / 100,
513 - 'close_out_viewport' => (bool) $box->params->get('close_out_viewport', false),
514 - 'exit_timer' => (int) $box->params->get('exittimer') * 1000,
515 - 'idle_time' => (int) $box->params->get('idle_time') * 1000,
516 - 'animation_open' => $box->params->get('animationin'),
517 - 'animation_close' => $box->params->get('animationout'),
518 - 'animation_duration' => (float) $animation_duration * 1000,
519 - 'prevent_default' => (bool) $box->params->get('preventdefault', true),
520 - 'backdrop' => (bool) $box->params->get('overlay'),
521 - 'backdrop_color' => $box->params->get('overlay_color'),
522 - 'backdrop_click' => (bool) $box->params->get('overlayclick'),
523 - 'disable_page_scroll' => (bool) $box->params->get('preventpagescroll'),
524 - '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'),
525 607 'debug' => (bool) $cParam->get('debug', false),
526 - 'ga_tracking' => (bool) $cParam->get('gaTrack', 0),
527 - 'ga_tracking_id' => $cParam->get('gaID', 0),
528 - 'ga_tracking_label' => $cParam->get('gaCategory'),
529 - '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
530 612 ];
531 613
532 - // set padding and margin for other devices as well
533 - self::setResponsiveCSS($box);
614 + $this->css = new Styling\CSS($this->box);
534 615
535 - 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');
536 622 }
537 623
538 624 /**
539 - * Sets all responsive CSS to use in box view
625 + * Parses scroll_amount to extract unit and value
540 626 *
541 - * @param object $box
627 + * @param mixed $scroll_amount
542 628 *
543 - * @return void
629 + * @return array
544 630 */
545 - private static function setResponsiveCSS(&$box)
631 + private function parseScrollAmount($scroll_amount)
546 632 {
547 - $tablet = '';
548 - $mobile = '';
549 -
550 - /**
551 - * Handle width, height, padding, margin the same way.
552 - */
553 - $shared_properties = [
554 - 'width' => [
555 - 'prefix_only' => true
556 - ],
557 - 'height' => [
558 - 'prefix_only' => true
559 - ],
560 - 'padding' => [],
561 - 'margin' => []
562 - ];
563 -
564 - foreach ($shared_properties as $prop => $prop_settings)
633 + if (is_array($scroll_amount))
565 634 {
566 - $prop_data = (array) $box->params->get($prop . '_control.' . $prop, []);
567 -
568 - $prefix_only = isset($prop_settings['prefix_only']) ? $prop_settings['prefix_only'] : false;
569 -
570 - // assign each device CSS
571 - if (isset($prop_data['tablet']))
572 - {
573 - $tablet .= BoxHelper::arrayToCSSS(DimensionsHelper::parseDimensionsData($prop_data['tablet'], $prop, true, $prefix_only));
574 - }
575 - if (isset($prop_data['mobile']))
576 - {
577 - $mobile .= BoxHelper::arrayToCSSS(DimensionsHelper::parseDimensionsData($prop_data['mobile'], $prop, true, $prefix_only));
578 - }
635 + return [
636 + 'unit' => $scroll_amount['unit'] ?? '%',
637 + 'value' => $scroll_amount['value'] ?? 80
638 + ];
579 639 }
580 640
581 - // border radius requires a special method
582 - $border_radius = (array) $box->params->get('borderradius_control.borderradius', []);
583 - if (isset($border_radius['tablet']))
641 + if (is_string($scroll_amount) && preg_match('/^(\d+)(px|%)$/', $scroll_amount, $matches))
584 642 {
585 - $tablet .= BoxHelper::arrayToCSSS(DimensionsHelper::parseDimensionsBorderRadiusData($border_radius['tablet'], true));
643 + return [
644 + 'unit' => $matches[2],
645 + 'value' => $matches[1]
646 + ];
586 647 }
587 - if (isset($border_radius['mobile']))
588 - {
589 - $mobile .= BoxHelper::arrayToCSSS(DimensionsHelper::parseDimensionsBorderRadiusData($border_radius['mobile'], true));
590 - }
591 648
592 - $responsive_css = [
593 - 'tablet' => $tablet,
594 - 'mobile' => $mobile
649 + return [
650 + 'unit' => '%',
651 + 'value' => 80
595 652 ];
596 - $box->params->set('responsive_css', $responsive_css);
597 653 }
598 -
654 +
599 655 /**
600 656 * Replaces all box smart tags
601 - *
602 - * @param object $box
603 - *
604 - * @return object
657 + *
658 + * @return void
605 659 */
606 - public static function replaceBoxSmartTags(&$box)
660 + public function replaceBoxSmartTags()
607 661 {
608 662 $tags = new \FPFramework\Base\SmartTags\SmartTags();
609 663
610 664 // register FB Smart Tags
611 - $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);
612 666
613 - $box = $tags->replace($box);
667 + $this->box = $tags->replace($this->box);
614 668 }
615 669
616 670 /**
617 671 * Checks if a box passes assignments
618 672 *
619 - * @param object $box
620 - *
621 673 * @return boolean
622 674 */
623 - public function pass($box)
675 + public function pass()
624 676 {
625 - if (!$box || !is_object($box))
677 + $this->clientRuleGroups = [];
678 +
679 + if (!$this->box || !is_object($this->box))
626 680 {
627 681 return false;
628 682 }
629 683
630 - // set box settings if empty
631 - 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))
632 719 {
633 - $this->boxSettings = $box;
720 + $rules = self::withoutPHPRules($rules);
721 +
722 + if (empty($rules))
723 + {
724 + return false;
725 + }
634 726 }
635 727
636 - // Prepare boxes that mirror other boxes assignments
637 - 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'))
638 730 {
639 - $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);
640 752 }
641 753
642 - // Check first local assignments
643 - 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']))
644 787 {
645 788 return false;
646 789 }
647 790
648 - // If testmode is enabled disable the User Groups assignment
649 - if ($box->params->get('testmode'))
791 + if (strtolower(trim($rule['name'])) !== 'php')
650 792 {
651 - $box->params->set('assignments.assign_grouplevel.selection', '0');
652 - }
653 -
654 - $globalAssignments = $this->passGlobalAssignments($box);
655 -
656 - // Check framework based assignments
657 - return $globalAssignments;
658 - }
793 + return false;
794 + }
659 795
660 - /**
661 - * Passes framework based global assignments
662 - *
663 - * @param object $box
664 - *
665 - * @return boolean
666 - */
667 - 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)
668 808 {
669 - $assignments = new \FPFramework\Base\Assignments($this->factory);
670 - $pass = $assignments->passAll($box, $box->params->get('assignmentMatchingMethod', 'and'));
671 - 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;
672 831 }
673 832
674 833 /**
675 - * 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:
676 836 *
677 - * @param object $box
678 - *
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 + *
679 908 * @return boolean
680 909 */
681 - private function passLocalAssignments($box)
910 + private function passLocalAssignments()
682 911 {
683 912 $localAssignments = new \FireBox\Core\FB\Assignments($this, $this->factory);
684 913 return $localAssignments->passAll();
685 914 }
@@ -690,49 +919,48 @@
690 919 * @param int $box_id
691 920 *
692 921 * @return object
693 922 */
694 - private static function getAssignmentsForMirroring($box_id)
695 - {
696 - $payload = [
697 - 'where' => [
698 - 'ID' => ' = ' . $box_id,
699 - 'post_status' => " = 'publish'",
700 - 'post_type' => " = 'firebox'"
701 - ]
702 - ];
703 -
704 - // Load box
705 - if (!$box = firebox()->models->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))
706 932 {
707 - return;
933 + return $cache[$box_id];
708 934 }
709 -
710 - $box = $box[0];
711 -
712 - // get meta options for box
713 - $meta = get_post_meta($box_id, 'fpframework_meta_settings', true);
714 - $box->params = $meta;
715 935
716 - // To prevent user frustration, we ignore the following assignments because they are not displayed in the Publishing Assignments.
717 - $params_to_ignore = [
718 - 'assign_impressions'
719 - ];
936 + $cache[$box_id] = null;
720 937
721 - $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;
722 942
723 - // Gather params to merge
724 - foreach ($box->params as $param_key => $param_value)
725 - {
726 - if (strpos($param_key, 'assign') === false || in_array($param_key, $params_to_ignore))
727 - {
728 - continue;
729 - }
943 + foreach ($boxes->posts as $box)
944 + {
945 + if ((int) $box->ID === $box_id)
946 + {
947 + $mirrored = $box;
948 + break;
949 + }
950 + }
730 951
731 - $assignments[$param_key] = $param_value;
952 + if (!$mirrored)
953 + {
954 + return $cache[$box_id];
732 955 }
733 -
734 - 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];
735 963 }
736 964
737 965 /**
738 966 * Prefixes the CSS classes
@@ -767,12 +995,17 @@
767 995 * @return void
768 996 */
769 997 public function logOpenEvent($box_id, $page = null, $referrer = null)
770 998 {
771 - $box = $this->get($box_id, null);
999 + $box = $this->get($box_id);
772 1000
1001 + if (!is_object($box) || !isset($box->params))
1002 + {
1003 + return;
1004 + }
1005 +
773 1006 // Do not track if statistics option is disabled
774 - $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'));
775 1008 if (!$track_open_event)
776 1009 {
777 1010 return;
778 1011 }
@@ -789,12 +1022,17 @@
789 1022 * @return void
790 1023 */
791 1024 public function logCloseEvent($box_id, $box_log_id)
792 1025 {
793 - $box = $this->get($box_id, null);
1026 + $box = $this->get($box_id);
794 1027
1028 + if (!is_object($box) || !isset($box->params))
1029 + {
1030 + return null;
1031 + }
1032 +
795 1033 // Do not track if statistics option is disabled
796 - $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'));
797 1035 if (!$track_open_event)
798 1036 {
799 1037 return null;
800 1038 }
@@ -800,69 +1038,85 @@
800 1038 }
801 1039
802 1040 firebox()->log->track($box_id, 2, $box_log_id);
803 1041 }
804 -
1042 +
805 1043 /**
806 - * Get box impressions
1044 + * Get total box impressions
807 1045 *
808 1046 * @param array $payload
809 1047 *
810 1048 * @return array
811 1049 */
812 - public function getImpressions($payload)
1050 + public function getTotalImpressions($payload)
813 1051 {
814 - return firebox()->models->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);
815 1055 }
816 -
1056 +
817 1057 /**
818 - * Get total box impressions
1058 + * Returns the cookie instance.
819 1059 *
820 - * @param array $payload
821 - *
822 - * @return array
1060 + * @return mixed
823 1061 */
824 - public function getTotalImpressions($payload)
1062 + public function getCookie()
825 1063 {
826 - return count($this->getImpressions($payload));
1064 + if (!$this->box)
1065 + {
1066 + return;
1067 + }
1068 +
1069 + return new Cookie($this->box);
827 1070 }
828 1071
829 1072 /**
830 - * Return the box settings
1073 + * Returns the box.
831 1074 *
832 1075 * @return object
833 1076 */
834 - public function getBoxSettings()
1077 + public function getBox()
835 1078 {
836 - return $this->boxSettings;
1079 + return $this->box;
837 1080 }
838 1081
839 1082 /**
840 - * Sets the box settings
1083 + * Sets the box.
841 1084 *
842 - * @param object $settings
1085 + * @param object $box
843 1086 *
844 - * @return object
1087 + * @return Box
845 1088 */
846 - public function setBoxSettings($settings)
1089 + public function setBox($box)
847 1090 {
848 - $this->boxSettings = $settings;
1091 + $this->box = $box;
1092 +
1093 + return $this;
849 1094 }
850 1095
851 - /**
852 - * Returns the box cookie
853 - *
854 - * @param string $cookie
855 - *
856 - * @return mixed
857 - */
858 - public function getCookie($cookie = null)
1096 + public function getParams()
859 1097 {
860 - $cookie = $cookie ? $cookie : (isset($this->boxSettings->ID) ? $this->boxSettings->ID : null);
861 - 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))
862 1116 {
863 - return;
1117 + $this->box->params = $params;
864 1118 }
865 -
866 - return new Cookie($cookie);
1119 +
1120 + return $this;
867 1121 }
868 -}
1122 +}