* @link https://www.fireplugins.com * @copyright Copyright © 2022 FirePlugins All Rights Reserved * @license GNU GPLv3 or later */ namespace FireBox\Core\FB; if (!defined('ABSPATH')) { exit; // Exit if accessed directly. } use FireBox\Core\Helpers\BoxHelper; use FPFramework\Libs\Registry; use FPFramework\Helpers\Fields\DimensionsHelper; class Box { /** * Send useful JS snippet once in first box * * @var boolean */ static $loadedLocalizedScript = false; /** * The box settings */ private $boxSettings; /** * The factory * * @var Factory */ protected $factory; public function __construct($boxSettings = null, $factory = null) { $this->init($boxSettings, $factory); } public function init($boxSettings = null, $factory = null) { if (!$factory) { $factory = new \FPFramework\Base\Factory(); } $this->boxSettings = $boxSettings; $this->factory = $factory; } /** * Gets published box. * * @param integer $id * @param string $status * * @return array */ public function get($id, $status = null) { $payload = [ 'where' => [ 'ID' => ' = ' . esc_sql($id), 'post_type' => " = 'firebox'" ] ]; // apply status if given if ($status) { $payload['where']['post_status'] = ' = \'' . esc_sql($status) . '\''; } if (!$box = firebox()->tables->box->getResults($payload)) { return []; } if (!isset($box[0])) { return []; } $box = $box[0]; // get meta options for box $meta = self::getMeta($id); $box->params = new Registry($meta); return $box; } /** * Get box meta * * @param int $id * * @return array */ public static function getMeta($id) { return get_post_meta($id, 'fpframework_meta_settings', true); } /** * Render box * * @return string */ public function render($box = null) { // we do not have a passed box or box settings from constructor, abort // as we cannot render a box without any box parameters if (!$box && !$this->boxSettings) { return; } // if a box was given to the function, use these settings instead if ($box) { $this->boxSettings = $box; } // if we have box settings, then set up our box with these settings if ($this->boxSettings) { $box = $this->boxSettings; } // Check Publishing Assignments if (!$this->pass($box)) { return; } // get settings params $params = BoxHelper::getParams(); if (!did_action('wp_enqueue_scripts')) { /** * Loads all media files. */ add_action('wp_enqueue_scripts', function() use ($box, $params) { // load box media $this->loadBoxMedia($box, $params); }); } else { // load box media $this->loadBoxMedia($box, $params); } self::prepare($box); if (!did_action('wp_head')) { /** * Custom CSS */ add_action('wp_head', function() use ($box) { if ($css = $this->getCustomCSS($box)) { echo ''; } }); } else { if ($css = $this->getCustomCSS($box)) { echo ''; } } /** * Runs before rendering the box. */ do_action('firebox/box/before_render', $box); // after we prepare it, update box settings $this->boxSettings = $box; // payload $payload = [ 'box' => $box, 'params' => $params, ]; // return box template add_action('wp_footer', function() use ($box, $payload) { echo firebox()->renderer->public->render('box', $payload, true); }); } /** * Gets the Custom CSS of the popup. * * @param object $box * * @return string */ private function getCustomCSS($box) { $css = is_string($box->params->get('customcss')) ? $box->params->get('customcss') : ''; // add responsive CSS if ($responsive_css = (array) $box->params->get('responsive_css', [])) { if (isset($responsive_css['tablet']) && !empty($responsive_css['tablet'])) { $css .= ' @media only screen and (max-width: 768px) { .fb-' . esc_attr($box->ID) . ' .fb-dialog { ' . esc_html($responsive_css['tablet']) . ' } }'; } if (isset($responsive_css['mobile']) && !empty($responsive_css['mobile'])) { $css .= ' @media only screen and (max-width: 468px) { .fb-' . esc_attr($box->ID) . ' .fb-dialog { ' . esc_html($responsive_css['mobile']) . ' } }'; } } return $css; } /** * Send a helpful object to JavaScript files * * @return void */ public static function setJSObject() { if (self::$loadedLocalizedScript) { return; } self::$loadedLocalizedScript = true; $data = array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('fbox_js_nonce'), 'site_url' => site_url('/'), 'referrer' => isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '' ); wp_localize_script('firebox', 'fbox_js_object', $data); } /** * Load Box Media * * @param array $box * @param array $params * * @return void */ public function loadBoxMedia($box, $params) { $box = new Registry($box); $params = new Registry($params); /** * We require a CSS handle to register inline styles. * * For this case, we use this dummy style to add inline CSS for our popups * even though the FireBox CSS file may not appear in the page. */ wp_register_style('firebox-custom-styles', false); wp_enqueue_style('firebox-custom-styles'); // Add polyfills for Internet Explorer $browser = $this->factory->getBrowser(); if ($browser && is_array($browser) && array_key_exists('name', $browser) && $browser['name'] == 'ie') { wp_enqueue_script( 'firebox-ie11-polyfill', 'https://polyfill.io/v3/polyfill.min.js?features=NodeList.prototype.forEach%2CElement.prototype.closest%2CArray.prototype.forEach%2CArray.prototype.find%2CIntersectionObserver%2CIntersectionObserverEntry', [], FBOX_VERSION, false ); } /** * Velocity */ if ($params->get('loadVelocity', true)) { wp_enqueue_script( 'firebox-velocity', FBOX_MEDIA_PUBLIC_URL . 'js/vendor/velocity.js', [], FBOX_VERSION, false ); wp_enqueue_script( 'firebox-velocity-ui', FBOX_MEDIA_PUBLIC_URL . 'js/vendor/velocity.ui.js', [], FBOX_VERSION, false ); /** * Animations */ if (strpos($box->get('params.data.animationin'), 'firebox') !== false || strpos($box->get('params.data.animationout'), 'firebox') !== false) { wp_enqueue_script( 'firebox-animations', FBOX_MEDIA_PUBLIC_URL . 'js/animations.js', [], FBOX_VERSION, false ); } } /** * FireBox JS */ wp_enqueue_script( 'firebox', FBOX_MEDIA_PUBLIC_URL . 'js/firebox.js', [], FBOX_VERSION, false ); // run above the main JS script to run only once self::setJSObject(); /** * FireBox CSS */ if ($params->get('loadCSS', true)) { wp_enqueue_style( 'firebox', FBOX_MEDIA_PUBLIC_URL . 'css/firebox.css', [], FBOX_VERSION, false ); } /** * Page Slide mode JS */ if ($box->get('params.data.mode') == 'pageslide') { wp_enqueue_script( 'firebox-pageslide-mode', FBOX_MEDIA_PUBLIC_URL . 'js/pageslide_mode.js', [], FBOX_VERSION, false ); } $this->loadThemeCSSOverrides(); } /** * Some themes require overrides to preserve as much as we can the styling of the popups. * * @return void */ private function loadThemeCSSOverrides() { $active_theme = wp_get_theme(); $theme = $active_theme->template; /** * This is the listed of the themes that we have created overrides */ $themes = [ 'twentytwentyone' ]; if (!in_array($theme, $themes)) { return; } wp_enqueue_style( 'firebox-theme-' . $theme . '-override', FBOX_MEDIA_PUBLIC_URL . 'css/themes/' . $theme . '.css', [], FBOX_VERSION, false ); } /** * Prepares the box before rendering * * @param array $box * * @return void */ public static function prepare(&$box) { global $post; $original_post = $post; $GLOBALS['post'] = $box; setup_postdata($GLOBALS['post']); $cParam = BoxHelper::getParams(); $cParam = new Registry($cParam); $box->post_content = apply_filters('the_content', $box->post_content); $css_class_prefix = 'fb-'; $position = $box->params->get('position', ''); $position = !is_string($position) ? '' : $position; /* Classes */ $css_class = [ $box->ID, $position ]; $rtl = $box->params->get('rtl', '0'); if ($rtl == '1') { $css_class[] = 'rtl'; } self::prefixCSSClasses($css_class); // class suffix $classSuffix = $box->params->get('classsuffix', ''); $classSuffix = is_string($classSuffix) ? $classSuffix : ''; $css_class[] = $classSuffix; $box->classes = $css_class; // box shadow $boxshadow = (is_string($box->params->get('boxshadow', '1')) || is_int($box->params->get('boxshadow', '1'))) ? $box->params->get('boxshadow', '1') : '0'; $dialog_css_classes = [ $boxshadow != '0' ? 'shd' . $boxshadow : null ]; // Align Content $aligncontent = is_string($box->params->get('aligncontent')) ? explode(' ', $box->params->get('aligncontent')) : []; $dialog_css_classes = array_merge($dialog_css_classes, $aligncontent); self::prefixCSSClasses($dialog_css_classes); $box->dialog_classes = $dialog_css_classes; /* CSS */ // border $border = is_string($box->params->get('bordertype', 'none')) ? $box->params->get('bordertype', 'none') : 'none'; $border_width = $box->params->get('borderwidth.value', 0) ? $box->params->get('borderwidth.value', 0) : 0; $border_unit = is_string($box->params->get('borderwidth.unit', 'px')) ? $box->params->get('borderwidth.unit', 'px') : 0; $border_color = is_string($box->params->get('bordercolor')) ? $box->params->get('bordercolor') : ''; $style = [ 'background' => $box->params->get('backgroundcolor'), 'color' => $box->params->get('textcolor'), 'border' => $border == 'none' ? null : $border . ' ' . $border_width . $border_unit . ' ' . $border_color, ]; // add shared properties $shared_properties = [ 'width' => [ 'prefix_only' => true ], 'height' => [ 'prefix_only' => true ], 'padding' => [], 'margin' => [] ]; foreach ($shared_properties as $prop => $prop_data) { // add dekstop property $value = (array) $box->params->get($prop . '_control.' . $prop, []); if (isset($value['desktop'])) { $prefix_only = isset($prop_data['prefix_only']) ? (bool) $prop_data['prefix_only'] : false; $desktop_value = DimensionsHelper::parseDimensionsData($value['desktop'], $prop, false, $prefix_only); $style = array_merge($style, $desktop_value); } } // add dekstop border_radius $border_radius = (array) $box->params->get('borderradius_control.borderradius', []); if (isset($border_radius['desktop'])) { $desktop_border_radius = DimensionsHelper::parseDimensionsBorderRadiusData($border_radius['desktop']); $style = array_merge($style, $desktop_border_radius); } // Background Image if ($box->params->get('bgimage', false)) { $bgrepeat = is_string($box->params->get('bgrepeat')) ? $box->params->get('bgrepeat') : ''; $bgsize = is_string($box->params->get('bgsize')) ? $box->params->get('bgsize') : ''; $bgposition = is_string($box->params->get('bgposition')) ? $box->params->get('bgposition') : ''; $style['background-image'] = 'url(\'' . esc_url($box->params->get('bgimagefile')) . '\')'; $style['background-repeat'] = strtolower($bgrepeat); $style['background-size'] = strtolower($bgsize); $style['background-position'] = strtolower($bgposition); } $box->style = BoxHelper::arrayToCSSS($style); $trigger_point_methods = [ 'pageheight' => 'onScrollDepth', 'element' => 'onElementVisibility', 'pageready' => 'onPageReady', 'pageload' => 'onPageLoad', 'userleave' => 'onExit', 'onclick' => 'onClick', 'elementHover' => 'onHover', 'ondemand' => 'onDemand' ]; /* Other Settings */ $scroll_depth = $box->params->get('scroll_depth', 'percentage'); $scroll_depth = is_string($scroll_depth) ? $scroll_depth : ''; $animation_duration = $box->params->get('duration') ? (float) $box->params->get('duration') : 0; // Use Namespaced classes for each trigger point and let them manipulate the settings dynamicaly. $box->settings = [ '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'), 'trigger_selector' => $box->params->get('triggerelement'), 'delay' => (int) $box->params->get('triggerdelay') * 1000, 'scroll_depth' => $scroll_depth, 'scroll_depth_value' => $scroll_depth == 'percentage' ? (int) $box->params->get('triggerpercentage') : (int) $box->params->get('scroll_pixel'), 'firing_frequency' => (int) $box->params->get('firing_frequency', 1), 'reverse_scroll_close' => (bool) $box->params->get('autohide'), 'threshold' => (float) $box->params->get('threshold', 0) / 100, 'close_out_viewport' => (bool) $box->params->get('close_out_viewport', false), 'exit_timer' => (int) $box->params->get('exittimer') * 1000, 'idle_time' => (int) $box->params->get('idle_time') * 1000, 'animation_open' => $box->params->get('animationin'), 'animation_close' => $box->params->get('animationout'), 'animation_duration' => (float) $animation_duration * 1000, 'prevent_default' => (bool) $box->params->get('preventdefault', true), 'backdrop' => (bool) $box->params->get('overlay'), 'backdrop_color' => $box->params->get('overlay_color'), 'backdrop_click' => (bool) $box->params->get('overlayclick'), 'disable_page_scroll' => (bool) $box->params->get('preventpagescroll'), 'test_mode' => (bool) $box->params->get('testmode'), 'debug' => (bool) $cParam->get('debug', false), 'ga_tracking' => (bool) $cParam->get('gaTrack', 0), 'ga_tracking_id' => $cParam->get('gaID', 0), 'ga_tracking_label' => $cParam->get('gaCategory'), 'auto_focus' => (bool) $box->params->get('autofocus', false) ]; $box->styles_container = BoxHelper::arrayToCSSS([ 'z-index' => $box->params->get('zindex', 99999) != 99999 ? $box->params->get('zindex') : null ]); // Let's start using CSS vars for each box settings. $cssVars = [ 'animation_duration' => (float) $animation_duration . 's' ]; // Enqueue firebox custom inline CSS $cssVarsForm = self::cssVarsToString($cssVars, '.fb-' . $box->ID); if (!did_action('wp_enqueue_scripts')) { add_action('wp_enqueue_scripts', function() use ($cssVarsForm) { wp_add_inline_style('firebox-custom-styles', $cssVarsForm); }); } else { wp_add_inline_style('firebox-custom-styles', $cssVarsForm); } // set padding and margin for other devices as well self::setResponsiveCSS($box); self::replaceBoxSmartTags($box); $GLOBALS['post'] = $original_post; wp_reset_postdata(); } /** * Converts an array of CSS variables to its corresponding strings. * * @param array $cssVars * @param string $namespace * * @return string */ private static function cssVarsToString($cssVars, $namespace) { $output = ''; foreach ($cssVars as $key => $value) { $output .= '--' . $key . ': ' . $value . ';' . "\n"; } return $namespace . ' { ' . $output . ' } '; } /** * Sets all responsive CSS to use in box view * * @param object $box * * @return void */ private static function setResponsiveCSS(&$box) { $tablet = ''; $mobile = ''; /** * Handle width, height, padding, margin the same way. */ $shared_properties = [ 'width' => [ 'prefix_only' => true ], 'height' => [ 'prefix_only' => true ], 'padding' => [], 'margin' => [] ]; foreach ($shared_properties as $prop => $prop_settings) { $prop_data = (array) $box->params->get($prop . '_control.' . $prop, []); $prefix_only = isset($prop_settings['prefix_only']) ? $prop_settings['prefix_only'] : false; // assign each device CSS if (isset($prop_data['tablet'])) { $tablet .= BoxHelper::arrayToCSSS(DimensionsHelper::parseDimensionsData($prop_data['tablet'], $prop, true, $prefix_only)); } if (isset($prop_data['mobile'])) { $mobile .= BoxHelper::arrayToCSSS(DimensionsHelper::parseDimensionsData($prop_data['mobile'], $prop, true, $prefix_only)); } } // border radius requires a special method $border_radius = (array) $box->params->get('borderradius_control.borderradius', []); if (isset($border_radius['tablet'])) { $tablet .= BoxHelper::arrayToCSSS(DimensionsHelper::parseDimensionsBorderRadiusData($border_radius['tablet'], true)); } if (isset($border_radius['mobile'])) { $mobile .= BoxHelper::arrayToCSSS(DimensionsHelper::parseDimensionsBorderRadiusData($border_radius['mobile'], true)); } $responsive_css = [ 'tablet' => $tablet, 'mobile' => $mobile ]; $box->params->set('responsive_css', $responsive_css); } /** * Replaces all box smart tags * * @param object $box * * @return object */ public static function replaceBoxSmartTags(&$box) { $tags = new \FPFramework\Base\SmartTags\SmartTags(); // register FB Smart Tags $tags->register('\FireBox\Core\SmartTags', FBOX_BASE_FOLDER . '/Inc/Core/SmartTags', $box); $box = $tags->replace($box); } /** * Checks if a box passes assignments * * @param object $box * * @return boolean */ public function pass($box) { if (!$box || !is_object($box)) { return false; } // set box settings if empty if (empty($this->boxSettings)) { $this->boxSettings = $box; } // Prepare boxes that mirror other boxes assignments if ($box->params->get('mirror', false) && $mirror_box_id = $box->params->get('mirror_box')) { $box->params->merge(self::getAssignmentsForMirroring($mirror_box_id)); } // Check first local assignments if (!$this->passLocalAssignments($box)) { return false; } // If testmode is enabled disable the User Groups assignment if ($box->params->get('testmode')) { $box->params->set('assignments.assign_grouplevel.selection', '0'); } $globalAssignments = $this->passGlobalAssignments($box); // Check framework based assignments return $globalAssignments; } /** * Passes framework based global assignments * * @param object $box * * @return boolean */ private function passGlobalAssignments($box) { $assignments = new \FPFramework\Base\Assignments($this->factory); $pass = $assignments->passAll($box, $box->params->get('assignmentMatchingMethod', 'and')); return $pass; } /** * Check if a box passes local assignments * * @param object $box * * @return boolean */ private function passLocalAssignments($box) { $localAssignments = new \FireBox\Core\FB\Assignments($this, $this->factory); return $localAssignments->passAll(); } /** * Gets assignments of mirrored box * * @param int $box_id * * @return object */ private static function getAssignmentsForMirroring($box_id) { $payload = [ 'where' => [ 'ID' => ' = ' . intval($box_id), 'post_status' => " = 'publish'", 'post_type' => " = 'firebox'" ] ]; // Load box if (!$box = firebox()->tables->box->getResults($payload)) { return; } $box = $box[0]; // get meta options for box $meta = get_post_meta($box_id, 'fpframework_meta_settings', true); $box->params = $meta; // To prevent user frustration, we ignore the following assignments because they are not displayed in the Publishing Assignments. $params_to_ignore = [ 'assign_impressions' ]; $assignments = []; // Gather params to merge foreach ($box->params as $param_key => $param_value) { if (strpos($param_key, 'assign') === false || in_array($param_key, $params_to_ignore)) { continue; } $assignments[$param_key] = $param_value; } return new Registry($assignments); } /** * Prefixes the CSS classes * * @param array $classes * @param string $prefix * * @return void */ private static function prefixCSSClasses(&$classes, $prefix = 'fb-') { $classes = array_filter($classes); if (empty($classes)) { return; } foreach ($classes as &$class) { $class = $prefix . $class; } } /** * Track box open * * @param integer $box_id * @param string $page * @param string $referrer * * @return void */ public function logOpenEvent($box_id, $page = null, $referrer = null) { $box = $this->get($box_id); // Do not track if statistics option is disabled $track_open_event = (bool) (is_null($box->params->get('stats', null)) ? BoxHelper::getParams()->get('stats', 1) : $box->params->get('stats')); if (!$track_open_event) { return; } return firebox()->log->track($box_id, 1, null, $page, $referrer); } /** * Track box close * * @param integer $box_id * @param integer $box_log_id * * @return void */ public function logCloseEvent($box_id, $box_log_id) { $box = $this->get($box_id); // Do not track if statistics option is disabled $track_open_event = (bool) (is_null($box->params->get('stats', null)) ? BoxHelper::getParams()->get('stats', 1) : $box->params->get('stats')); if (!$track_open_event) { return null; } firebox()->log->track($box_id, 2, $box_log_id); } /** * Get box impressions * * @param array $payload * * @return array */ public function getImpressions($payload) { return firebox()->tables->boxlog->getResults($payload); } /** * Get total box impressions * * @param array $payload * * @return array */ public function getTotalImpressions($payload) { return count($this->getImpressions($payload)); } /** * Return the box settings * * @return object */ public function getBoxSettings() { return $this->boxSettings; } /** * Sets the box settings * * @param object $settings * * @return object */ public function setBoxSettings($settings) { $this->boxSettings = $settings; } /** * Returns the box cookie * * @param string $cookie * * @return mixed */ public function getCookie($cookie = null) { $cookie = $cookie ? $cookie : (isset($this->boxSettings->ID) ? $this->boxSettings->ID : null); if (!isset($cookie)) { return; } return new Cookie($cookie); } }