├── assets ├── js │ ├── index.php │ ├── src │ │ ├── admin-script.js │ │ ├── preload.js │ │ ├── boxzilla │ │ │ ├── timer.js │ │ │ ├── triggers │ │ │ │ ├── pageviews.js │ │ │ │ ├── scroll.js │ │ │ │ ├── time.js │ │ │ │ └── exit-intent.js │ │ │ ├── util.js │ │ │ ├── boxzilla.js │ │ │ ├── animator.js │ │ │ └── box.js │ │ ├── admin │ │ │ ├── _option.js │ │ │ ├── _designer.js │ │ │ └── _admin.js │ │ └── script.js │ ├── preload.js │ ├── admin-script.js.LICENSE.txt │ ├── admin-script.js │ └── script.js ├── img │ ├── menu-icon.jpg │ ├── menu-icon.png │ ├── index.php │ └── menu-icon.svg ├── index.php └── css │ ├── index.php │ ├── styles.min.css │ ├── styles.css │ ├── admin-styles.min.css │ └── admin-styles.css ├── languages ├── index.php ├── boxzilla.pot └── boxzilla-wp.pot ├── src ├── functions.php ├── class-boxzilla.php ├── admin │ ├── views │ │ ├── metaboxes │ │ │ ├── need-help.php │ │ │ ├── our-other-plugins.php │ │ │ └── box-appearance-controls.php │ │ ├── settings.php │ │ └── extensions.php │ ├── migrations │ │ ├── 3.1-cookie-option.php │ │ └── 3.1.11-screen-width-condition.php │ ├── class-notices.php │ ├── class-migrations.php │ ├── class-installer.php │ ├── class-review-notice.php │ ├── class-autocomplete.php │ └── class-menu.php ├── licensing │ ├── class-api-exception.php │ ├── services.php │ ├── class-poller.php │ ├── class-license.php │ ├── views │ │ └── license-form.php │ ├── class-api.php │ ├── class-license-manager.php │ └── class-update-manager.php ├── di │ ├── class-container-with-property-access.php │ └── class-container.php ├── default-filters.php ├── services.php ├── class-plugin.php ├── default-actions.php ├── class-box.php └── class-loader.php ├── phpcs.xml ├── webpack.config.js ├── phpunit.xml ├── autoload.php ├── create-version.sh └── boxzilla.php /assets/js/index.php: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /assets/js/src/admin-script.js: -------------------------------------------------------------------------------- 1 | window.Boxzilla_Admin = require('./admin/_admin.js') 2 | -------------------------------------------------------------------------------- /assets/img/menu-icon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibericode/boxzilla-wp/HEAD/assets/img/menu-icon.jpg -------------------------------------------------------------------------------- /assets/img/menu-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibericode/boxzilla-wp/HEAD/assets/img/menu-icon.png -------------------------------------------------------------------------------- /assets/img/index.php: -------------------------------------------------------------------------------- 1 | {var o=[],i={};["on","off","toggle","show"].forEach((l=>{i[l]=function(){o.push([l,arguments])}})),window.Boxzilla=i,window.boxzilla_queue=o})(); -------------------------------------------------------------------------------- /assets/js/admin-script.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! 2 | * EventEmitter v5.2.9 - git.io/ee 3 | * Unlicense - http://unlicense.org/ 4 | * Oliver Caldwell - https://oli.me.uk/ 5 | * @preserve 6 | */ 7 | -------------------------------------------------------------------------------- /languages/index.php: -------------------------------------------------------------------------------- 1 | { 7 | Boxzilla[m] = function () { 8 | boxzilla_queue.push([m, arguments]) 9 | } 10 | }) 11 | 12 | window.Boxzilla = Boxzilla 13 | window.boxzilla_queue = boxzilla_queue 14 | -------------------------------------------------------------------------------- /src/class-boxzilla.php: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | rules 4 | 5 | src/ 6 | boxzilla.php 7 | *\.(html|css|js) 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/admin/views/metaboxes/need-help.php: -------------------------------------------------------------------------------- 1 | 2 |

3 | 8 | 9 | -------------------------------------------------------------------------------- /assets/js/src/boxzilla/timer.js: -------------------------------------------------------------------------------- 1 | const Timer = function () { 2 | this.time = 0 3 | this.interval = 0 4 | } 5 | 6 | Timer.prototype.tick = function () { 7 | this.time++ 8 | } 9 | 10 | Timer.prototype.start = function () { 11 | if (!this.interval) { 12 | this.interval = window.setInterval(this.tick.bind(this), 1000) 13 | } 14 | } 15 | 16 | Timer.prototype.stop = function () { 17 | if (this.interval) { 18 | window.clearInterval(this.interval) 19 | this.interval = 0 20 | } 21 | } 22 | 23 | module.exports = Timer 24 | -------------------------------------------------------------------------------- /assets/js/src/boxzilla/triggers/pageviews.js: -------------------------------------------------------------------------------- 1 | module.exports = function (boxes) { 2 | let pageviews 3 | 4 | try { 5 | pageviews = sessionStorage.getItem('boxzilla_pageviews') || 0 6 | sessionStorage.setItem('boxzilla_pageviews', ++pageviews) 7 | } catch (e) { 8 | pageviews = 0 9 | } 10 | 11 | window.setTimeout(() => { 12 | boxes.forEach((box) => { 13 | if (box.config.trigger.method === 'pageviews' && pageviews > box.config.trigger.value && box.mayAutoShow()) { 14 | box.trigger() 15 | } 16 | }) 17 | }, 1000) 18 | } 19 | -------------------------------------------------------------------------------- /src/admin/views/metaboxes/our-other-plugins.php: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 |

7 | MC4WP: Mailchimp for WordPress
8 | The #1 Mailchimp plugin for WordPress. 9 |

10 |
11 | 12 |
13 |

14 | Koko Analytics
15 | Privacy-friendly analytics plugin that does not use any external services. 16 |

17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /assets/js/src/boxzilla/util.js: -------------------------------------------------------------------------------- 1 | function throttle (fn, threshold, scope) { 2 | threshold || (threshold = 800) 3 | let last 4 | let deferTimer 5 | 6 | return function () { 7 | const context = scope || this 8 | const now = +new Date() 9 | const args = arguments 10 | if (last && now < last + threshold) { 11 | // hold on to it 12 | clearTimeout(deferTimer) 13 | deferTimer = setTimeout(function () { 14 | last = now 15 | fn.apply(context, args) 16 | }, threshold) 17 | } else { 18 | last = now 19 | fn.apply(context, args) 20 | } 21 | } 22 | } 23 | 24 | module.exports = { throttle } 25 | -------------------------------------------------------------------------------- /src/admin/migrations/3.1-cookie-option.php: -------------------------------------------------------------------------------- 1 | 'boxzilla-box' ]); 6 | 7 | if (! empty($posts)) { 8 | foreach ($posts as $post) { 9 | $settings = get_post_meta($post->ID, 'boxzilla_options', true); 10 | 11 | if (! is_array($settings)) { 12 | continue; 13 | } 14 | 15 | // translate from days to hours 16 | $new_value = intval($settings['cookie']) * 24; 17 | 18 | // store in new location 19 | $settings['cookie'] = [ 20 | 'dismissed' => $new_value, 21 | ]; 22 | update_post_meta($post->ID, 'boxzilla_options', $settings); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/licensing/class-api-exception.php: -------------------------------------------------------------------------------- 1 | api_code = $api_code; 25 | } 26 | 27 | /** 28 | * @return string 29 | */ 30 | public function getApiCode() 31 | { 32 | return $this->api_code; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | entry: { 5 | 'admin-script': './assets/js/src/admin-script.js', 6 | script: './assets/js/src/script.js', 7 | preload: './assets/js/src/preload.js' 8 | }, 9 | output: { 10 | filename: '[name].js', 11 | path: path.resolve(__dirname, 'assets/js') 12 | }, 13 | module: { 14 | rules: [ 15 | { 16 | test: /\.(?:js|mjs|cjs)$/, 17 | exclude: /node_modules/, 18 | use: { 19 | loader: 'babel-loader', 20 | options: { 21 | presets: [ 22 | ['@babel/preset-env', { targets: 'defaults' }] 23 | ] 24 | } 25 | } 26 | } 27 | ] 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/admin/migrations/3.1.11-screen-width-condition.php: -------------------------------------------------------------------------------- 1 | 'boxzilla-box' ]); 6 | 7 | if (! empty($posts)) { 8 | foreach ($posts as $post) { 9 | $settings = get_post_meta($post->ID, 'boxzilla_options', true); 10 | 11 | if (! is_array($settings)) { 12 | continue; 13 | } 14 | 15 | if (empty($settings['hide_on_screen_size'])) { 16 | continue; 17 | } 18 | 19 | // set updated option 20 | $settings['screen_size_condition'] = [ 21 | 'condition' => 'larger', 22 | 'value' => intval($settings['hide_on_screen_size']), 23 | ]; 24 | 25 | unset($settings['hide_on_screen_size']); 26 | 27 | update_post_meta($post->ID, 'boxzilla_options', $settings); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | 17 | tests 18 | 19 | 20 | 21 | 22 | 23 | src 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/licensing/services.php: -------------------------------------------------------------------------------- 1 | notices[] = [ 29 | 'message' => $message, 30 | 'type' => $type, 31 | ]; 32 | 33 | return $this; 34 | } 35 | 36 | /** 37 | * Output the registered notices 38 | */ 39 | public function show() 40 | { 41 | foreach ($this->notices as $notice) { 42 | echo "

{$notice['message']}

"; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /assets/css/styles.min.css: -------------------------------------------------------------------------------- 1 | #boxzilla-overlay,.boxzilla-overlay{position:fixed;background:rgba(0,0,0,.65);width:100%;height:100%;left:0;top:0;z-index:10000}.boxzilla-center-container{position:fixed;top:0;left:0;right:0;height:0;text-align:center;z-index:11000;line-height:0}.boxzilla-center-container .boxzilla{display:inline-block;text-align:left;position:relative;line-height:normal}.boxzilla{position:fixed;z-index:12000;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;background:#fff;padding:25px}.boxzilla.boxzilla-top-left{top:0;left:0}.boxzilla.boxzilla-top-right{top:0;right:0}.boxzilla.boxzilla-bottom-left{bottom:0;left:0}.boxzilla.boxzilla-bottom-right{bottom:0;right:0}.boxzilla-content>:first-child{margin-top:0;padding-top:0}.boxzilla-content>:last-child{margin-bottom:0;padding-bottom:0}.boxzilla-close-icon{position:absolute;right:0;top:0;text-align:center;padding:6px;cursor:pointer;-webkit-appearance:none;font-size:28px;font-weight:700;line-height:20px;color:#000;opacity:.5}.boxzilla-close-icon:focus,.boxzilla-close-icon:hover{opacity:.8} -------------------------------------------------------------------------------- /assets/js/src/boxzilla/triggers/scroll.js: -------------------------------------------------------------------------------- 1 | const throttle = require('../util.js').throttle 2 | 3 | module.exports = function (boxes) { 4 | // check triggerHeight criteria for all boxes 5 | function checkHeightCriteria () { 6 | // eslint-disable-next-line no-prototype-builtins 7 | let scrollY = window.hasOwnProperty('pageYOffset') ? window.pageYOffset : window.scrollTop 8 | scrollY = scrollY + window.innerHeight * 0.9 9 | 10 | boxes.forEach((box) => { 11 | if (!box.mayAutoShow() || box.triggerHeight <= 0) { 12 | return 13 | } 14 | 15 | if (scrollY > box.triggerHeight) { 16 | box.trigger() 17 | } else if (box.mayRehide() && scrollY < (box.triggerHeight - 5)) { 18 | // if box may auto-hide and scrollY is less than triggerHeight (with small margin of error), hide box 19 | box.hide() 20 | } 21 | }) 22 | } 23 | 24 | window.addEventListener('touchstart', throttle(checkHeightCriteria), true) 25 | window.addEventListener('scroll', throttle(checkHeightCriteria), true) 26 | } 27 | -------------------------------------------------------------------------------- /assets/js/src/admin/_option.js: -------------------------------------------------------------------------------- 1 | var $ = window.jQuery 2 | var Option = function (element) { 3 | // find corresponding element 4 | if (typeof (element) === 'string') { 5 | element = document.getElementById('boxzilla-' + element) 6 | } 7 | 8 | if (!element) { 9 | console.error('Unable to find option element.') 10 | } 11 | 12 | this.element = element 13 | } 14 | 15 | Option.prototype.getColorValue = function () { 16 | if (this.element.value.length > 0) { 17 | if ($(this.element).hasClass('wp-color-field')) { 18 | return $(this.element).wpColorPicker('color') 19 | } else { 20 | return this.element.value 21 | } 22 | } 23 | 24 | return '' 25 | } 26 | 27 | Option.prototype.getPxValue = function (fallbackValue) { 28 | if (this.element.value.length > 0) { 29 | return parseInt(this.element.value) + 'px' 30 | } 31 | 32 | return fallbackValue || '' 33 | } 34 | 35 | Option.prototype.getValue = function (fallbackValue) { 36 | if (this.element.value.length > 0) { 37 | return this.element.value 38 | } 39 | 40 | return fallbackValue || '' 41 | } 42 | 43 | Option.prototype.clear = function () { 44 | this.element.value = '' 45 | } 46 | 47 | Option.prototype.setValue = function (value) { 48 | this.element.value = value 49 | } 50 | 51 | module.exports = Option 52 | -------------------------------------------------------------------------------- /src/di/class-container-with-property-access.php: -------------------------------------------------------------------------------- 1 | offsetGet($name); 21 | } 22 | 23 | /** 24 | * @param string $name 25 | * @param mixed $value 26 | */ 27 | public function __set($name, $value) 28 | { 29 | $this[ $name ] = $value; 30 | } 31 | 32 | /** 33 | * @param string $name 34 | * @return bool 35 | */ 36 | public function __isset($name) 37 | { 38 | return $this->offsetExists($name); 39 | } 40 | 41 | /** 42 | * @param string $name 43 | * @return bool 44 | */ 45 | public function has($name) 46 | { 47 | return $this->offsetExists($name); 48 | } 49 | 50 | /** 51 | * @param string $name 52 | * @return mixed 53 | */ 54 | public function get($name) 55 | { 56 | return $this->offsetGet($name); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/default-filters.php: -------------------------------------------------------------------------------- 1 | plugin, $boxzilla); 15 | }; 16 | 17 | $boxzilla['admin.menu'] = function () { 18 | return new Menu(); 19 | }; 20 | 21 | $boxzilla['box_loader'] = function ($boxzilla) { 22 | return new BoxLoader($boxzilla->plugin, $boxzilla->options); 23 | }; 24 | 25 | $boxzilla['filter.autocomplete'] = function () { 26 | return new Filter\Autocomplete(); 27 | }; 28 | 29 | $boxzilla['notices'] = function () { 30 | return new Notices(); 31 | }; 32 | 33 | $boxzilla['options'] = function () { 34 | $defaults = [ 35 | 'test_mode' => 0, 36 | ]; 37 | 38 | $options = (array) get_option('boxzilla_settings', $defaults); 39 | $options = array_merge($defaults, $options); 40 | return $options; 41 | }; 42 | 43 | $boxzilla['plugin'] = new Plugin( 44 | 'boxzilla', 45 | 'Boxzilla', 46 | BOXZILLA_VERSION, 47 | BOXZILLA_FILE, 48 | dirname(BOXZILLA_FILE) 49 | ); 50 | 51 | $boxzilla['plugins'] = function () { 52 | $raw = (array) apply_filters('boxzilla_extensions', []); 53 | 54 | $plugins = []; 55 | foreach ($raw as $p) { 56 | $plugins[ $p->id() ] = $p; 57 | } 58 | return $plugins; 59 | }; 60 | -------------------------------------------------------------------------------- /assets/js/src/boxzilla/triggers/time.js: -------------------------------------------------------------------------------- 1 | const Timer = require('../timer.js') 2 | 3 | module.exports = function (boxes) { 4 | const siteTimer = new Timer() 5 | const pageTimer = new Timer() 6 | 7 | const timers = { 8 | start: function () { 9 | try { 10 | const sessionTime = parseInt(sessionStorage.getItem('boxzilla_timer')) 11 | if (sessionTime) { 12 | siteTimer.time = sessionTime 13 | } 14 | } catch (e) {} 15 | siteTimer.start() 16 | pageTimer.start() 17 | }, 18 | stop: function () { 19 | sessionStorage.setItem('boxzilla_timer', siteTimer.time) 20 | siteTimer.stop() 21 | pageTimer.stop() 22 | } 23 | } 24 | 25 | // start timers 26 | timers.start() 27 | 28 | // stop timers when leaving page or switching to other tab 29 | document.addEventListener('visibilitychange', function () { 30 | document.hidden ? timers.stop() : timers.start() 31 | }) 32 | 33 | window.addEventListener('beforeunload', function () { 34 | timers.stop() 35 | }) 36 | 37 | window.setInterval(() => { 38 | boxes.forEach((box) => { 39 | if (box.config.trigger.method === 'time_on_site' && siteTimer.time > box.config.trigger.value && box.mayAutoShow()) { 40 | box.trigger() 41 | } else if (box.config.trigger.method === 'time_on_page' && pageTimer.time > box.config.trigger.value && box.mayAutoShow()) { 42 | box.trigger() 43 | } 44 | }) 45 | }, 1000) 46 | } 47 | -------------------------------------------------------------------------------- /assets/css/styles.css: -------------------------------------------------------------------------------- 1 | #boxzilla-overlay,.boxzilla-overlay { 2 | position: fixed; 3 | background: rgba(0, 0, 0, 0.65 ); 4 | width: 100%; 5 | height: 100%; 6 | left: 0; 7 | top: 0; 8 | z-index: 10000; 9 | } 10 | 11 | .boxzilla-center-container { 12 | position: fixed; 13 | top: 0; 14 | left: 0; 15 | right: 0; 16 | height: 0; 17 | text-align: center; 18 | z-index: 11000; 19 | line-height: 0; 20 | } 21 | 22 | /* reset some properties like "line-height" because of container line-height hack */ 23 | .boxzilla-center-container .boxzilla { 24 | display: inline-block; 25 | text-align: left; 26 | position: relative; 27 | line-height: normal; 28 | } 29 | 30 | .boxzilla { 31 | position: fixed; 32 | z-index: 12000; 33 | -webkit-box-sizing: border-box; 34 | -moz-box-sizing: border-box; 35 | box-sizing: border-box; 36 | background: white; 37 | padding: 25px; 38 | } 39 | .boxzilla.boxzilla-top-left { 40 | top: 0; 41 | left: 0; 42 | } 43 | .boxzilla.boxzilla-top-right { 44 | top: 0; 45 | right: 0; 46 | } 47 | .boxzilla.boxzilla-bottom-left { 48 | bottom: 0; 49 | left: 0; 50 | } 51 | .boxzilla.boxzilla-bottom-right { 52 | bottom: 0; 53 | right: 0; 54 | } 55 | 56 | /* remove top & bottom margin from last child element */ 57 | .boxzilla-content > *:first-child { 58 | margin-top: 0; 59 | padding-top: 0; 60 | } 61 | 62 | .boxzilla-content > *:last-child { 63 | margin-bottom: 0; 64 | padding-bottom: 0; 65 | } 66 | 67 | .boxzilla-close-icon { 68 | position: absolute; 69 | right: 0; 70 | top: 0; 71 | text-align: center; 72 | padding: 6px; 73 | cursor: pointer; 74 | -webkit-appearance: none; 75 | font-size: 28px; 76 | font-weight: bold; 77 | line-height: 20px; 78 | color: #000; 79 | opacity: .5; 80 | } 81 | 82 | .boxzilla-close-icon:hover, .boxzilla-close-icon:focus { 83 | opacity: .8; 84 | } 85 | -------------------------------------------------------------------------------- /autoload.php: -------------------------------------------------------------------------------- 1 | __DIR__ . '/src/admin/class-admin.php', 8 | 'Boxzilla\\Admin\\Installer' => __DIR__ . '/src/admin/class-installer.php', 9 | 'Boxzilla\\Admin\\Menu' => __DIR__ . '/src/admin/class-menu.php', 10 | 'Boxzilla\\Admin\\Migrations' => __DIR__ . '/src/admin/class-migrations.php', 11 | 'Boxzilla\\Admin\\Notices' => __DIR__ . '/src/admin/class-notices.php', 12 | 'Boxzilla\\Admin\\ReviewNotice' => __DIR__ . '/src/admin/class-review-notice.php', 13 | 'Boxzilla\\Box' => __DIR__ . '/src/class-box.php', 14 | 'Boxzilla\\BoxLoader' => __DIR__ . '/src/class-loader.php', 15 | 'Boxzilla\\Boxzilla' => __DIR__ . '/src/class-boxzilla.php', 16 | 'Boxzilla\\DI\\Container' => __DIR__ . '/src/di/class-container.php', 17 | 'Boxzilla\\DI\\ContainerWithPropertyAccess' => __DIR__ . '/src/di/class-container-with-property-access.php', 18 | 'Boxzilla\\Filter\\Autocomplete' => __DIR__ . '/src/admin/class-autocomplete.php', 19 | 'Boxzilla\\Licensing\\API' => __DIR__ . '/src/licensing/class-api.php', 20 | 'Boxzilla\\Licensing\\API_Exception' => __DIR__ . '/src/licensing/class-api-exception.php', 21 | 'Boxzilla\\Licensing\\License' => __DIR__ . '/src/licensing/class-license.php', 22 | 'Boxzilla\\Licensing\\LicenseManager' => __DIR__ . '/src/licensing/class-license-manager.php', 23 | 'Boxzilla\\Licensing\\Poller' => __DIR__ . '/src/licensing/class-poller.php', 24 | 'Boxzilla\\Licensing\\UpdateManager' => __DIR__ . '/src/licensing/class-update-manager.php', 25 | 'Boxzilla\\Plugin' => __DIR__ . '/src/class-plugin.php', 26 | ]; 27 | 28 | if (isset($classmap[$class])) { 29 | require $classmap[$class]; 30 | } 31 | }); 32 | -------------------------------------------------------------------------------- /src/licensing/class-poller.php: -------------------------------------------------------------------------------- 1 | api = $api; 26 | $this->license = $license; 27 | } 28 | 29 | /** 30 | * Add hooks. 31 | */ 32 | public function init() 33 | { 34 | if (! wp_next_scheduled('boxzilla_check_license_status')) { 35 | wp_schedule_event(time(), 'daily', 'boxzilla_check_license_status'); 36 | } 37 | 38 | add_action('boxzilla_check_license_status', [ $this, 'run' ]); 39 | } 40 | 41 | /** 42 | * Run! 43 | */ 44 | public function run() 45 | { 46 | // don't run if license not active 47 | if (! $this->license->activated) { 48 | return; 49 | } 50 | 51 | // assume valid by default, in case of server errors on our side. 52 | $license_still_valid = true; 53 | 54 | try { 55 | $remote_license = $this->api->get_license(); 56 | $license_still_valid = $remote_license->valid; 57 | } catch (API_Exception $e) { 58 | // license key wasn't found or expired 59 | if (in_array($e->getApiCode(), [ 'license_invalid', 'license_expired' ], true)) { 60 | $license_still_valid = false; 61 | } 62 | } 63 | 64 | if (! $license_still_valid) { 65 | $this->license->activated = false; 66 | $this->license->save(); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /assets/css/admin-styles.min.css: -------------------------------------------------------------------------------- 1 | .boxzilla-rule-row-0 .boxzilla-close{visibility:hidden}.column-box_id{width:80px}.boxzilla-sm{width:150px}.boxzilla-xsm{width:15px}.boxzilla-title{margin-top:2em!important}.boxzilla-label{display:block;font-weight:700;margin-bottom:6px}.boxzilla-close{padding:0;cursor:pointer;background:0 0;border:0;-webkit-appearance:none;font-size:21px;font-weight:700;line-height:26px;text-shadow:0 1px 0 #fff;opacity:.3;filter:alpha(opacity=30)}.boxzilla-close:focus,.boxzilla-close:hover{color:#000;text-decoration:none;cursor:pointer;opacity:.6;filter:alpha(opacity=60)}.post-type-boxzilla-box .form-table{table-layout:fixed}.post-type-boxzilla-box .window-positions{border:1px solid #EEE}.post-type-boxzilla-box .window-positions td{padding:3px;margin-bottom:0}.post-type-boxzilla-box .wp-picker-container{white-space:nowrap}.post-type-boxzilla-box .wp-picker-clear,.post-type-boxzilla-box .wp-picker-holder,.post-type-boxzilla-box .wp-picker-input-wrap{background:#fff;z-index:999!important;position:absolute!important}#boxzilla-admin .status{display:inline-block;padding:3px 6px;color:#fff;text-transform:uppercase;font-weight:700}#boxzilla-admin .status.positive{background-color:#32cd32}#boxzilla-admin .status.negative{background:#c3c3c3}.boxzilla-muted{color:#888}.boxzilla-no-vpadding{padding-top:0!important;padding-bottom:0!important}.boxzilla-is-dismissible{padding-right:38px;position:relative}.radio-label{font-weight:400}.radio-label>input{margin-top:0!important}.boxzilla-row{margin:0 -20px}.boxzilla-col-one-third,.boxzilla-col-two-third{float:left;-webkit-box-sizing:border-box;box-sizing:border-box;padding:0 20px}.boxzilla-col-two-third{width:66.66%}.boxzilla-col-one-third{width:33.33%}.boxzilla-sidebar{margin-top:10px}.boxzilla-box{background:#fff;padding:20px;margin-bottom:20px;border:1px solid #ccc}.boxzilla-box :first-child,.boxzilla-box h3{margin-top:0}.boxzilla-box :last-child{margin-bottom:0}.boxzilla-sidebar form label{display:block;font-weight:700;margin-bottom:6px}@media (max-width:920px){.boxzilla-row{margin:0}.boxzilla-col-one-third,.boxzilla-col-two-third{float:none;padding:0;width:auto}.boxzilla-sidebar{margin-top:40px}} -------------------------------------------------------------------------------- /create-version.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | # Check if VERSION argument was supplied 6 | if [ "$#" -lt 1 ]; then 7 | echo "1 parameters expected, $# found" 8 | echo "Usage: package.sh " 9 | exit 1 10 | fi 11 | 12 | PLUGIN_SLUG=$(basename "$PWD") 13 | PLUGIN_FILE="$PLUGIN_SLUG.php" 14 | VERSION=$1 15 | PACKAGE_FILE="$PWD/../$PLUGIN_SLUG-$VERSION.zip" 16 | 17 | # Check if we're inside plugin directory 18 | if [ ! -e "$PLUGIN_FILE" ]; then 19 | echo "Plugin entry file not found. Please run this command from inside the $PLUGIN_SLUG directory." 20 | exit 1 21 | fi 22 | 23 | # Check if there are uncommitted changes 24 | if [ -n "$(git status --porcelain)" ]; then 25 | echo "There are uncommitted changes. Please commit those changes before initiating a release." 26 | exit 1 27 | fi 28 | 29 | # Check if there is an existing file for this release already 30 | rm -f "$PACKAGE_FILE" 31 | 32 | # Build (optimized) client-side assets 33 | npm run build 34 | 35 | # Update version numbers in code 36 | sed -i "s/^Version: .*$/Version: $VERSION/g" "$PLUGIN_FILE" 37 | sed -i "s/define('\(.*_VERSION\)', '.*');/define('\1', '$VERSION');/g" "$PLUGIN_FILE" 38 | sed -i "s/^Stable tag: .*$/Stable tag: $VERSION/g" "readme.txt" 39 | 40 | # Copy over changelog from CHANGELOG.md to readme.txt 41 | # Ref: https://git.sr.ht/~dvko/dotfiles/tree/master/item/bin/wp-update-changelog 42 | wp-update-changelog 43 | 44 | # Move up one directory level because we need plugin directory in ZIP file 45 | cd .. 46 | 47 | # Create archive (excl. development files) 48 | zip -r "$PACKAGE_FILE" "$PLUGIN_SLUG" \ 49 | -x "$PLUGIN_SLUG/.*" \ 50 | -x "$PLUGIN_SLUG/vendor/*" \ 51 | -x "$PLUGIN_SLUG/node_modules/*" \ 52 | -x "$PLUGIN_SLUG/tests/*" \ 53 | -x "$PLUGIN_SLUG/webpack.config*.js" \ 54 | -x "$PLUGIN_SLUG/*.json" \ 55 | -x "$PLUGIN_SLUG/*.lock" \ 56 | -x "$PLUGIN_SLUG/phpcs.xml" \ 57 | -x "$PLUGIN_SLUG/phpunit.xml.dist" \ 58 | -x "$PLUGIN_SLUG/*.sh" \ 59 | -x "$PLUGIN_SLUG/assets/src/*" \ 60 | -x "$PLUGIN_SLUG/sample-code-snippets/*" 61 | 62 | cd "$PLUGIN_SLUG" 63 | 64 | SIZE=$(ls -lh "$PACKAGE_FILE" | cut -d' ' -f5) 65 | echo "$(basename "$PACKAGE_FILE") created ($SIZE)" 66 | 67 | # # Create tag in Git and push to remote 68 | git add . -A 69 | git commit -m "v$VERSION" 70 | git tag "$VERSION" 71 | git push origin main 72 | git push origin "tags/$VERSION" 73 | -------------------------------------------------------------------------------- /src/class-plugin.php: -------------------------------------------------------------------------------- 1 | id = $id; 49 | $this->name = $name; 50 | $this->version = $version; 51 | $this->file = $file; 52 | $this->dir = $dir; 53 | $this->slug = plugin_basename($file); 54 | 55 | if (empty($dir)) { 56 | $this->dir = dirname($file); 57 | } 58 | } 59 | 60 | /** 61 | * @return int 62 | */ 63 | public function id() 64 | { 65 | return $this->id; 66 | } 67 | 68 | /** 69 | * @return string 70 | */ 71 | public function slug() 72 | { 73 | return $this->slug; 74 | } 75 | 76 | /** 77 | * @return string 78 | */ 79 | public function name() 80 | { 81 | return $this->name; 82 | } 83 | 84 | /** 85 | * @return string 86 | */ 87 | public function version() 88 | { 89 | return $this->version; 90 | } 91 | 92 | /** 93 | * @return string 94 | */ 95 | public function file() 96 | { 97 | return $this->file; 98 | } 99 | 100 | /** 101 | * @return string 102 | */ 103 | public function dir() 104 | { 105 | return $this->dir; 106 | } 107 | 108 | /** 109 | * @param string $path 110 | * 111 | * @return mixed 112 | */ 113 | public function url($path = '') 114 | { 115 | return plugins_url($path, $this->file()); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/admin/views/settings.php: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 |
6 | 7 |

8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 26 | 27 | 28 | 29 |
22 |   23 |   24 |

25 |
30 | 31 | 32 |
33 | 34 | 35 |
36 | 37 |
38 | 39 |
40 |

41 | 42 |
43 | 44 |
45 |

46 | 47 |
48 | 49 |
50 |
51 | 52 |
53 | 54 |
55 | -------------------------------------------------------------------------------- /assets/js/src/boxzilla/triggers/exit-intent.js: -------------------------------------------------------------------------------- 1 | module.exports = function (boxes) { 2 | let timeout = null 3 | let touchStart = {} 4 | 5 | function trigger () { 6 | document.documentElement.removeEventListener('mouseleave', onMouseLeave) 7 | document.documentElement.removeEventListener('mouseenter', onMouseEnter) 8 | document.documentElement.removeEventListener('click', clearTimeout) 9 | window.removeEventListener('touchstart', onTouchStart) 10 | window.removeEventListener('touchend', onTouchEnd) 11 | 12 | // show boxes with exit intent trigger 13 | boxes.forEach(box => { 14 | if (box.mayAutoShow() && box.config.trigger.method === 'exit_intent') { 15 | box.trigger() 16 | } 17 | }) 18 | } 19 | 20 | function clearTimeout () { 21 | if (timeout === null) { 22 | return 23 | } 24 | 25 | window.clearTimeout(timeout) 26 | timeout = null 27 | } 28 | 29 | function onMouseEnter () { 30 | clearTimeout() 31 | } 32 | 33 | function getAddressBarY () { 34 | if (document.documentMode || /Edge\//.test(navigator.userAgent)) { 35 | return 5 36 | } 37 | 38 | return 0 39 | } 40 | 41 | function onMouseLeave (evt) { 42 | clearTimeout() 43 | 44 | // did mouse leave at top of window? 45 | // add small exception space in the top-right corner 46 | if (evt.clientY <= getAddressBarY() && evt.clientX < (0.8 * window.innerWidth)) { 47 | timeout = window.setTimeout(trigger, 600) 48 | } 49 | } 50 | 51 | function onTouchStart () { 52 | clearTimeout() 53 | touchStart = { 54 | timestamp: performance.now(), 55 | scrollY: window.scrollY, 56 | windowHeight: window.innerHeight 57 | } 58 | } 59 | 60 | function onTouchEnd (evt) { 61 | clearTimeout() 62 | 63 | // did address bar appear? 64 | if (window.innerHeight > touchStart.windowHeight) { 65 | return 66 | } 67 | 68 | // allow a tiny tiny margin for error, to not fire on clicks 69 | if ((window.scrollY + 20) > touchStart.scrollY) { 70 | return 71 | } 72 | 73 | if ((performance.now() - touchStart.timestamp) > 300) { 74 | return 75 | } 76 | 77 | if (['A', 'INPUT', 'BUTTON'].indexOf(evt.target.tagName) > -1) { 78 | return 79 | } 80 | 81 | timeout = window.setTimeout(trigger, 800) 82 | } 83 | 84 | window.addEventListener('touchstart', onTouchStart) 85 | window.addEventListener('touchend', onTouchEnd) 86 | document.documentElement.addEventListener('mouseenter', onMouseEnter) 87 | document.documentElement.addEventListener('mouseleave', onMouseLeave) 88 | document.documentElement.addEventListener('click', clearTimeout) 89 | } 90 | -------------------------------------------------------------------------------- /src/admin/views/extensions.php: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |

Available Add-On Plugins

5 |

There are various add-ons available for Boxzilla which further enhance the functionality of the core plugin.

6 |

To gain instant access the premium add-on plugins listed here, have a look at our pricing.

7 | 8 | 9 | 14 |

You will be redirected to the Boxzilla site in a few seconds.

15 |

If not, please click here: View Boxzilla add-on plugins

16 | 17 | $plugin) : ?> 18 |
19 | <?php echo esc_attr($plugin->name); ?> 20 |
21 |

name); ?>

22 |

description); ?>

23 |

24 | Read More 25 | Premium 26 |

27 |
28 |
29 |
'; 32 | } 33 | ?> 34 | 35 | 36 |
37 | 38 | 39 | 40 | 41 | 59 | -------------------------------------------------------------------------------- /src/admin/class-migrations.php: -------------------------------------------------------------------------------- 1 | version_from = $from; 35 | $this->version_to = $to; 36 | $this->migrations_dir = $migrations_dir; 37 | } 38 | 39 | /** 40 | * Run the various upgrade routines, all the way up to the latest version 41 | */ 42 | public function run() 43 | { 44 | $migrations = $this->find_migrations(); 45 | // run in sub-function for scope 46 | array_map([ $this, 'run_migration' ], $migrations); 47 | } 48 | 49 | /** 50 | * @return array 51 | */ 52 | public function find_migrations() 53 | { 54 | $files = glob(rtrim($this->migrations_dir, '/') . '/*.php'); 55 | $migrations = []; 56 | 57 | // return empty array when glob returns non-array value. 58 | if (! is_array($files)) { 59 | return $migrations; 60 | } 61 | 62 | foreach ($files as $file) { 63 | $migration = basename($file); 64 | $parts = explode('-', $migration); 65 | $version = $parts[0]; 66 | 67 | // check if migration file is not for an even higher version 68 | if (version_compare($version, $this->version_to, '>')) { 69 | continue; 70 | } 71 | 72 | // check if we ran migration file before. 73 | if (version_compare($this->version_from, $version, '>=')) { 74 | continue; 75 | } 76 | 77 | // schedule migration file for running 78 | $migrations[] = $file; 79 | } 80 | 81 | return $migrations; 82 | } 83 | 84 | /** 85 | * Include a migration file and runs it. 86 | * 87 | * @param string $file 88 | * 89 | * @throws Exception 90 | */ 91 | protected function run_migration($file) 92 | { 93 | if (! file_exists($file)) { 94 | throw new Exception("Migration file $file does not exist."); 95 | } 96 | 97 | include $file; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/admin/class-installer.php: -------------------------------------------------------------------------------- 1 | install(); 14 | } 15 | 16 | /** 17 | * The main install method 18 | */ 19 | public function install() 20 | { 21 | 22 | // don't install sample boxes on multisite 23 | if (is_multisite()) { 24 | return; 25 | } 26 | 27 | $this->transfer_from_stb(); 28 | $this->create_sample_box(); 29 | } 30 | 31 | /** 32 | * 33 | */ 34 | public function transfer_from_stb() 35 | { 36 | global $wpdb; 37 | 38 | // transfer post types 39 | $query = $wpdb->prepare("UPDATE {$wpdb->posts} SET post_type = %s WHERE post_type = %s", 'boxzilla-box', 'scroll-triggered-box'); 40 | $wpdb->query($query); 41 | 42 | // transfer post meta 43 | $query = $wpdb->prepare("UPDATE {$wpdb->postmeta} SET meta_key = %s WHERE meta_key = %s", 'boxzilla_options', 'stb_options'); 44 | $wpdb->query($query); 45 | 46 | // transfer rules 47 | $query = $wpdb->prepare("UPDATE {$wpdb->options} SET option_name = %s WHERE option_name = %s", 'boxzilla_rules', 'stb_rules'); 48 | $wpdb->query($query); 49 | } 50 | 51 | /** 52 | * @return bool 53 | */ 54 | protected function create_sample_box() 55 | { 56 | 57 | // only create sample box if no boxes were found 58 | $boxes = get_posts( 59 | [ 60 | 'post_type' => 'boxzilla-box', 61 | 'post_status' => [ 'publish', 'draft' ], 62 | ] 63 | ); 64 | 65 | if (! empty($boxes)) { 66 | return false; 67 | } 68 | 69 | $box_id = wp_insert_post( 70 | [ 71 | 'post_type' => 'boxzilla-box', 72 | 'post_title' => 'Sample Box', 73 | 'post_content' => '

Hello world.

This is a sample box, with some sample content in it.

', 74 | 'post_status' => 'draft', 75 | ] 76 | ); 77 | 78 | // set box settings 79 | $settings = [ 80 | 'css' => [ 81 | 'background_color' => '#edf9ff', 82 | 'color' => '', 83 | 'width' => '340', 84 | 'border_color' => '#dd7575', 85 | 'border_width' => '4', 86 | 'border_style' => 'dashed', 87 | 'position' => 'bottom-right', 88 | 'manual' => '', 89 | ], 90 | ]; 91 | 92 | update_post_meta($box_id, 'boxzilla_options', $settings); 93 | 94 | return true; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/licensing/class-license.php: -------------------------------------------------------------------------------- 1 | option_key = $option_key; 45 | } 46 | 47 | /** 48 | * @param string $name 49 | * @param mixed $value 50 | */ 51 | public function __set($name, $value) 52 | { 53 | $this->load(); 54 | $this->data[ $name ] = $value; 55 | $this->dirty = true; 56 | } 57 | 58 | /** 59 | * @param string $name 60 | * 61 | * @return mixed 62 | */ 63 | public function __get($name) 64 | { 65 | $this->load(); 66 | return $this->data[ $name ]; 67 | } 68 | 69 | /** 70 | * @param $name 71 | * 72 | * @return bool 73 | */ 74 | public function __isset($name) 75 | { 76 | $this->load(); 77 | return isset($this->data[ $name ]); 78 | } 79 | 80 | /** 81 | * Load the license data from the database 82 | */ 83 | protected function load() 84 | { 85 | if ($this->loaded) { 86 | return; 87 | } 88 | 89 | $defaults = [ 90 | 'key' => '', 91 | 'activation_key' => '', 92 | 'activated' => false, 93 | 'expires_at' => '', 94 | ]; 95 | 96 | $data = (array) get_option($this->option_key, []); 97 | $this->data = array_replace($defaults, $data); 98 | $this->loaded = true; 99 | } 100 | 101 | /** 102 | * Reload the license data from DB 103 | */ 104 | public function reload() 105 | { 106 | $this->loaded = false; 107 | $this->load(); 108 | } 109 | 110 | /** 111 | * Save the license in the database 112 | * 113 | * @return License 114 | */ 115 | public function save() 116 | { 117 | if (! $this->dirty) { 118 | return; 119 | } 120 | 121 | update_option($this->option_key, $this->data, true); 122 | $this->dirty = false; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /boxzilla.php: -------------------------------------------------------------------------------- 1 | . 30 | */ 31 | 32 | 33 | // Exit if not loaded inside a WordPress context 34 | defined('ABSPATH') or exit; 35 | 36 | // Exit if PHP lower than 7.4 37 | PHP_VERSION_ID >= 70400 or exit; 38 | 39 | define('BOXZILLA_FILE', __FILE__); 40 | define('BOXZILLA_DIR', __DIR__); 41 | define('BOXZILLA_VERSION', '3.4.5'); 42 | 43 | require __DIR__ . '/autoload.php'; 44 | require __DIR__ . '/src/services.php'; 45 | require __DIR__ . '/src/licensing/services.php'; 46 | 47 | // register activation hook 48 | register_activation_hook(__FILE__, [ 'Boxzilla\\Admin\\Installer', 'run' ]); 49 | 50 | // Bootstrap plugin at later action hook 51 | add_action( 52 | 'plugins_loaded', 53 | function () { 54 | $boxzilla = boxzilla(); 55 | 56 | // load default filters 57 | require __DIR__ . '/src/default-filters.php'; 58 | require __DIR__ . '/src/default-actions.php'; 59 | 60 | if (defined('DOING_AJAX') && DOING_AJAX) { 61 | $boxzilla['filter.autocomplete']->init(); 62 | $boxzilla['admin.menu']->init(); 63 | } elseif (defined('DOING_CRON') && DOING_CRON) { 64 | $boxzilla['license_poller']->init(); 65 | } elseif (is_admin()) { 66 | $boxzilla['admin']->init(); 67 | $boxzilla['admin.menu']->init(); 68 | } else { 69 | add_action( 70 | 'template_redirect', 71 | function () use ($boxzilla) { 72 | $boxzilla['box_loader']->init(); 73 | } 74 | ); 75 | } 76 | 77 | // license manager 78 | if (is_admin() || ( defined('DOING_CRON') && DOING_CRON ) || ( defined('WP_CLI') && WP_CLI )) { 79 | $boxzilla['license_manager']->init(); 80 | 81 | if (count($boxzilla->plugins) > 0) { 82 | $boxzilla['update_manager']->init(); 83 | } 84 | } 85 | }, 86 | 90 87 | ); 88 | -------------------------------------------------------------------------------- /src/admin/class-review-notice.php: -------------------------------------------------------------------------------- 1 | ID, $this->meta_key_dismissed, 1); 35 | } 36 | 37 | /** 38 | * @return bool 39 | */ 40 | public function show() 41 | { 42 | $screen = get_current_screen(); 43 | if (! $screen instanceof WP_Screen) { 44 | return false; 45 | } 46 | 47 | // on some boxzilla screen? 48 | if ($screen->post_type !== 'boxzilla-box') { 49 | return false; 50 | } 51 | 52 | // authorized? 53 | if (! current_user_can('edit_posts')) { 54 | return false; 55 | } 56 | 57 | // only show if 2 weeks have passed since first use. 58 | $two_weeks_in_seconds = ( 60 * 60 * 24 * 14 ); 59 | if ($this->time_since_first_use() <= $two_weeks_in_seconds) { 60 | return false; 61 | } 62 | 63 | // only show if user did not dismiss before 64 | $user = wp_get_current_user(); 65 | if (get_user_meta($user->ID, $this->meta_key_dismissed, true)) { 66 | return false; 67 | } 68 | 69 | echo '
'; 70 | echo '

'; 71 | echo esc_html__('You\'ve been using Boxzilla for some time now; we hope you love it!', 'boxzilla'), '
'; 72 | echo '', esc_html__('If you do, please leave us a nice plugin review on WordPress.org.', 'boxzilla'), ''; 73 | echo esc_html__('It would be of great help to us.', 'boxzilla'); 74 | echo '

'; 75 | echo '
'; 76 | echo '
'; 77 | return true; 78 | } 79 | 80 | /** 81 | * @return int 82 | */ 83 | private function time_since_first_use() 84 | { 85 | $options = get_option('boxzilla_settings'); 86 | 87 | // option was never added before, do it now. 88 | if (empty($options['first_activated_on'])) { 89 | $options['first_activated_on'] = time(); 90 | update_option('boxzilla_settings', $options); 91 | } 92 | 93 | return time() - $options['first_activated_on']; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /assets/css/admin-styles.css: -------------------------------------------------------------------------------- 1 | /* first row can't be deleted */ 2 | .boxzilla-rule-row-0 .boxzilla-close { 3 | visibility: hidden; } 4 | 5 | .column-box_id { 6 | width: 80px; } 7 | 8 | .boxzilla-sm { 9 | width: 150px; } 10 | 11 | .boxzilla-xsm { 12 | width: 15px; } 13 | 14 | .boxzilla-title { 15 | margin-top: 2em !important; } 16 | 17 | .boxzilla-label { 18 | display: block; 19 | font-weight: bold; 20 | margin-bottom: 6px; } 21 | 22 | .boxzilla-close { 23 | padding: 0; 24 | cursor: pointer; 25 | background: transparent; 26 | border: 0; 27 | -webkit-appearance: none; 28 | font-size: 21px; 29 | font-weight: bold; 30 | line-height: 26px; 31 | text-shadow: 0 1px 0 #fff; 32 | opacity: .3; 33 | filter: alpha(opacity=30); } 34 | .boxzilla-close:hover, .boxzilla-close:focus { 35 | color: #000; 36 | text-decoration: none; 37 | cursor: pointer; 38 | opacity: .6; 39 | filter: alpha(opacity=60); } 40 | 41 | .post-type-boxzilla-box .form-table { 42 | table-layout: fixed; } 43 | 44 | .post-type-boxzilla-box .window-positions { 45 | border: 1px solid #EEE; } 46 | .post-type-boxzilla-box .window-positions td { 47 | padding: 3px; 48 | margin-bottom: 0; } 49 | 50 | .post-type-boxzilla-box .wp-picker-container { 51 | white-space: nowrap; } 52 | 53 | .post-type-boxzilla-box .wp-picker-clear, 54 | .post-type-boxzilla-box .wp-picker-holder, 55 | .post-type-boxzilla-box .wp-picker-input-wrap { 56 | background: white; 57 | z-index: 999 !important; 58 | position: absolute !important; } 59 | 60 | #boxzilla-admin .status { 61 | display: inline-block; 62 | padding: 3px 6px; 63 | color: white; 64 | text-transform: uppercase; 65 | font-weight: bold; } 66 | #boxzilla-admin .status.positive { 67 | background-color: limeGreen; } 68 | #boxzilla-admin .status.negative { 69 | background: #c3c3c3; } 70 | 71 | .boxzilla-muted { 72 | color: #888; } 73 | 74 | .boxzilla-no-vpadding { 75 | padding-top: 0 !important; 76 | padding-bottom: 0 !important; } 77 | 78 | .boxzilla-is-dismissible { 79 | padding-right: 38px; 80 | position: relative; } 81 | 82 | /* Radio Switches */ 83 | .radio-label { 84 | font-weight: normal; } 85 | .radio-label > input { 86 | margin-top: 0 !important; } 87 | 88 | /** Grid */ 89 | .boxzilla-row { 90 | margin: 0 -20px; } 91 | 92 | .boxzilla-col-one-third, 93 | .boxzilla-col-two-third { 94 | float: left; 95 | -webkit-box-sizing: border-box; 96 | box-sizing: border-box; 97 | padding: 0 20px; } 98 | 99 | .boxzilla-col-two-third { 100 | width: 66.66%; } 101 | 102 | .boxzilla-col-one-third { 103 | width: 33.33%; } 104 | 105 | /* admin sidebar */ 106 | .boxzilla-sidebar { 107 | margin-top: 10px; } 108 | 109 | .boxzilla-box { 110 | background: white; 111 | padding: 20px; 112 | margin-bottom: 20px; 113 | border: 1px solid #ccc; } 114 | .boxzilla-box h3, 115 | .boxzilla-box :first-child { 116 | margin-top: 0; } 117 | .boxzilla-box :last-child { 118 | margin-bottom: 0; } 119 | 120 | .boxzilla-sidebar form label { 121 | display: block; 122 | font-weight: bold; 123 | margin-bottom: 6px; } 124 | 125 | @media (max-width: 920px) { 126 | .boxzilla-row { 127 | margin: 0; } 128 | .boxzilla-col-one-third, 129 | .boxzilla-col-two-third { 130 | float: none; 131 | padding: 0; 132 | width: auto; } 133 | .boxzilla-sidebar { 134 | margin-top: 40px; } } 135 | -------------------------------------------------------------------------------- /src/licensing/views/license-form.php: -------------------------------------------------------------------------------- 1 | 7 | 8 |

9 | 10 | activated) { 12 | ?> 13 |
14 |

15 | 16 |

17 |
    18 | extensions as $p) { 20 | echo '
  • ', esc_html($p->name()), '
  • '; 21 | } 22 | ?> 23 |
24 |

25 | 26 |

27 |
28 | 31 | 32 | notices as $notice) { 34 | ?> 35 |
36 |

37 |
38 | 41 | 42 |
43 | 44 | 45 | 46 | 64 | 65 | 66 | 67 | 80 | 81 |
47 | license->activated) { 53 | echo 'readonly'; 54 | } ?> 55 | /> 56 | 57 |

58 | ', esc_html__('You can find it here.', 'boxzilla'), ''; 61 | ?> 62 |

63 |
68 | activated) { 70 | ?> 71 |

-

72 | 75 |

- not receiving plugin updates', 'boxzilla'), [ 'strong' => [] ]); ?>

76 | 79 |
82 | 83 | 84 | 85 | 86 |

87 | 88 |

89 | 90 | 91 |
92 | -------------------------------------------------------------------------------- /src/admin/class-autocomplete.php: -------------------------------------------------------------------------------- 1 | list_posts($q, $type); 31 | break; 32 | 33 | case 'category': 34 | echo $this->list_categories($q); 35 | break; 36 | 37 | case 'post_type': 38 | echo $this->list_post_types($q); 39 | break; 40 | 41 | case 'post_tag': 42 | echo $this->list_tags($q); 43 | break; 44 | } 45 | 46 | die(); 47 | } 48 | 49 | /** 50 | * @param string $query 51 | * @param string $post_type 52 | * 53 | * @return string 54 | */ 55 | protected function list_posts($query, $post_type = 'post') 56 | { 57 | global $wpdb; 58 | $sql = $wpdb->prepare("SELECT p.post_name FROM $wpdb->posts p WHERE p.post_type = %s AND p.post_status = 'publish' AND ( p.post_title LIKE %s OR p.post_name LIKE %s ) GROUP BY p.post_name", $post_type, $query . '%%', $query . '%%'); 59 | $post_slugs = $wpdb->get_col($sql); 60 | return join(PHP_EOL, $post_slugs); 61 | } 62 | 63 | /** 64 | * @param string $query 65 | * 66 | * @return string 67 | */ 68 | protected function list_categories($query) 69 | { 70 | $terms = get_terms( 71 | 'category', 72 | [ 73 | 'name__like' => $query, 74 | 'fields' => 'names', 75 | 'hide_empty' => false, 76 | ] 77 | ); 78 | return join(PHP_EOL, $terms); 79 | } 80 | 81 | /** 82 | * @param string $query 83 | * 84 | * @return string 85 | */ 86 | protected function list_tags($query) 87 | { 88 | $terms = get_terms( 89 | 'post_tag', 90 | [ 91 | 'name__like' => $query, 92 | 'fields' => 'names', 93 | 'hide_empty' => false, 94 | ] 95 | ); 96 | return join(PHP_EOL, $terms); 97 | } 98 | 99 | 100 | /** 101 | * @param string $query 102 | * 103 | * @return string 104 | */ 105 | protected function list_post_types($query) 106 | { 107 | $post_types = get_post_types([ 'public' => true ], 'names'); 108 | $matched_post_types = array_filter( 109 | $post_types, 110 | function ($name) use ($query) { 111 | return strpos($name, $query) === 0; 112 | } 113 | ); 114 | 115 | return join(PHP_EOL, $matched_post_types); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /assets/js/src/admin/_designer.js: -------------------------------------------------------------------------------- 1 | var Designer = function ($, Option, events) { 2 | // vars 3 | var boxId = document.getElementById('post_ID').value || 0 4 | var $editor 5 | var $editorFrame 6 | var $innerEditor 7 | var options = {} 8 | var visualEditorInitialised = false 9 | 10 | var $appearanceControls = $('#boxzilla-box-appearance-controls') 11 | 12 | // functions 13 | function init () { 14 | // Only run if TinyMCE has actually inited 15 | if (typeof (window.tinyMCE) !== 'object' || window.tinyMCE.get('content') === null) { 16 | return 17 | } 18 | 19 | // create Option objects 20 | options.borderColor = new Option('border-color') 21 | options.borderWidth = new Option('border-width') 22 | options.borderStyle = new Option('border-style') 23 | options.backgroundColor = new Option('background-color') 24 | options.width = new Option('width') 25 | options.color = new Option('color') 26 | 27 | // add classes to TinyMCE 28 | $editorFrame = $('#content_ifr') 29 | $editor = $editorFrame.contents().find('html') 30 | $editor.css({ 31 | background: 'white' 32 | }) 33 | 34 | // add content class and padding to TinyMCE 35 | $innerEditor = $editor.find('#tinymce') 36 | $innerEditor.addClass('boxzilla boxzilla-' + boxId) 37 | $innerEditor.css({ 38 | margin: 0, 39 | background: 'white', 40 | display: 'inline-block', 41 | width: 'auto', 42 | 'min-width': '240px', 43 | position: 'relative' 44 | }) 45 | $innerEditor.get(0).style.cssText += ';padding: 25px !important;' 46 | 47 | visualEditorInitialised = true 48 | 49 | /* @since 2.0.3 */ 50 | events.trigger('editor.init') 51 | } 52 | 53 | /** 54 | * Applies the styles from the options to the TinyMCE Editor 55 | * 56 | * @return bool 57 | */ 58 | function applyStyles () { 59 | if (!visualEditorInitialised) { 60 | return false 61 | } 62 | 63 | // Apply styles from CSS editor. 64 | // Use short timeout to make sure color values are updated. 65 | window.setTimeout(() => { 66 | $innerEditor.css({ 67 | 'border-color': options.borderColor.getColorValue(), // getColorValue( 'borderColor', '' ), 68 | 'border-width': options.borderWidth.getPxValue(), // getPxValue( 'borderWidth', '' ), 69 | 'border-style': options.borderStyle.getValue(), // getValue('borderStyle', '' ), 70 | 'background-color': options.backgroundColor.getColorValue(), // getColorValue( 'backgroundColor', ''), 71 | width: options.width.getPxValue(), // getPxValue( 'width', 'auto' ), 72 | color: options.color.getColorValue() // getColorValue( 'color', '' ) 73 | }) 74 | 75 | /* @since 2.0.3 */ 76 | events.trigger('editor.styles.apply') 77 | }, 10) 78 | 79 | return true 80 | } 81 | 82 | function resetStyles () { 83 | for (var key in options) { 84 | if (key.substring(0, 5) === 'theme') { 85 | continue 86 | } 87 | 88 | options[key].clear() 89 | } 90 | applyStyles() 91 | 92 | /* @since 2.0.3 */ 93 | events.trigger('editor.styles.reset') 94 | } 95 | 96 | // event binders 97 | $appearanceControls.find('input.boxzilla-color-field').wpColorPicker({ change: applyStyles, clear: applyStyles }) 98 | $appearanceControls.find(':input').not('.boxzilla-color-field').change(applyStyles) 99 | events.on('editor.init', applyStyles) 100 | 101 | // public methods 102 | return { 103 | init: init, 104 | resetStyles: resetStyles, 105 | options: options 106 | } 107 | } 108 | 109 | module.exports = Designer 110 | -------------------------------------------------------------------------------- /src/admin/views/metaboxes/box-appearance-controls.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 15 | 20 | 21 | 22 | 26 | 31 | 42 | 43 | 44 |
8 | 9 | 10 | 12 | 13 | 14 | 16 | 17 | 18 |

19 |
23 | 24 | 25 | 27 | 28 | 29 |

30 |
32 | 33 | 40 |

41 |
45 | 46 |

47 | -------------------------------------------------------------------------------- /assets/js/src/boxzilla/boxzilla.js: -------------------------------------------------------------------------------- 1 | const Box = require('./box.js') 2 | const throttle = require('./util.js').throttle 3 | const ExitIntent = require('./triggers/exit-intent.js') 4 | const Scroll = require('./triggers/scroll.js') 5 | const Pageviews = require('./triggers/pageviews.js') 6 | const Time = require('./triggers/time.js') 7 | 8 | let initialised = false 9 | const boxes = [] 10 | const listeners = {} 11 | 12 | function onKeyUp (evt) { 13 | if (evt.key === 'Escape' || evt.key === 'Esc') { 14 | dismiss() 15 | } 16 | } 17 | 18 | function recalculateHeights () { 19 | boxes.forEach(box => box.onResize()) 20 | } 21 | 22 | function onElementClick (evt) { 23 | // bubble up to or element 24 | let el = evt.target 25 | for (let i = 0; i <= 3; i++) { 26 | if (!el || el.tagName === 'A' || el.tagName === 'AREA') { 27 | break 28 | } 29 | 30 | el = el.parentElement 31 | } 32 | 33 | if (!el || (el.tagName !== 'A' && el.tagName !== 'AREA') || !el.href) { 34 | return 35 | } 36 | 37 | const match = el.href.match(/[#&]boxzilla-(.+)/i) 38 | if (match && match.length > 1) { 39 | toggle(match[1]) 40 | } 41 | } 42 | 43 | function trigger (event, args) { 44 | listeners[event] && listeners[event].forEach(f => f.apply(null, args)) 45 | } 46 | 47 | function on (event, fn) { 48 | listeners[event] = listeners[event] || [] 49 | listeners[event].push(fn) 50 | } 51 | 52 | function off (event, fn) { 53 | listeners[event] && listeners[event].filter(f => f !== fn) 54 | } 55 | 56 | // initialise & add event listeners 57 | function init () { 58 | if (initialised) { 59 | return 60 | } 61 | 62 | // init triggers 63 | ExitIntent(boxes) 64 | Pageviews(boxes) 65 | Scroll(boxes) 66 | Time(boxes) 67 | 68 | document.body.addEventListener('click', onElementClick, true) 69 | window.addEventListener('resize', throttle(recalculateHeights)) 70 | window.addEventListener('load', recalculateHeights) 71 | document.addEventListener('keyup', onKeyUp) 72 | 73 | trigger('ready') 74 | initialised = true // ensure this function doesn't run again 75 | } 76 | 77 | function create (id, opts) { 78 | // preserve backwards compat for minimumScreenWidth option 79 | if (typeof (opts.minimumScreenWidth) !== 'undefined') { 80 | opts.screenWidthCondition = { 81 | condition: 'larger', 82 | value: opts.minimumScreenWidth 83 | } 84 | } 85 | 86 | id = String(id) 87 | const box = new Box(id, opts, trigger) 88 | boxes.push(box) 89 | return box 90 | } 91 | 92 | function get (id) { 93 | id = String(id) 94 | for (let i = 0; i < boxes.length; i++) { 95 | if (boxes[i].id === id) { 96 | return boxes[i] 97 | } 98 | } 99 | 100 | throw new Error('No box exists with ID ' + id) 101 | } 102 | 103 | // dismiss a single box (or all by omitting id param) 104 | function dismiss (id, animate) { 105 | if (id) { 106 | get(id).dismiss(animate) 107 | } else { 108 | boxes.forEach(box => box.dismiss(animate)) 109 | } 110 | } 111 | 112 | function hide (id, animate) { 113 | if (id) { 114 | get(id).hide(animate) 115 | } else { 116 | boxes.forEach(box => box.hide(animate)) 117 | } 118 | } 119 | 120 | function show (id, animate) { 121 | if (id) { 122 | get(id).show(animate) 123 | } else { 124 | boxes.forEach(box => box.show(animate)) 125 | } 126 | } 127 | 128 | function toggle (id, animate) { 129 | if (id) { 130 | get(id).toggle(animate) 131 | } else { 132 | boxes.forEach(box => box.toggle(animate)) 133 | } 134 | } 135 | 136 | // expose boxzilla object 137 | const Boxzilla = { off, on, get, init, create, trigger, show, hide, dismiss, toggle, boxes } 138 | window.Boxzilla = Boxzilla 139 | 140 | if (typeof module !== 'undefined' && module.exports) { 141 | module.exports = Boxzilla 142 | } 143 | -------------------------------------------------------------------------------- /src/default-actions.php: -------------------------------------------------------------------------------- 1 | false, 13 | 'labels' => [ 14 | 'name' => esc_html__('Boxzilla', 'boxzilla'), 15 | 'singular_name' => esc_html__('Box', 'boxzilla'), 16 | 'add_new' => esc_html__('Add New', 'boxzilla'), 17 | 'add_new_item' => esc_html__('Add New Box', 'boxzilla'), 18 | 'edit_item' => esc_html__('Edit Box', 'boxzilla'), 19 | 'new_item' => esc_html__('New Box', 'boxzilla'), 20 | 'all_items' => esc_html__('All Boxes', 'boxzilla'), 21 | 'view_item' => esc_html__('View Box', 'boxzilla'), 22 | 'search_items' => esc_html__('Search Boxes', 'boxzilla'), 23 | 'not_found' => esc_html__('No Boxes found', 'boxzilla'), 24 | 'not_found_in_trash' => esc_html__('No Boxes found in Trash', 'boxzilla'), 25 | 'parent_item_colon' => '', 26 | 'menu_name' => esc_html__('Boxzilla', 'boxzilla'), 27 | ], 28 | 'show_ui' => true, 29 | 'menu_position' => '108.1337133', 30 | 'menu_icon' => 'data:image/svg+xml;base64,' . base64_encode(''), 31 | 'query_var' => false, 32 | 'capability_type' => 'box', 33 | 'capabilities' => [ 34 | 'edit_post' => 'edit_box', 35 | 'edit_posts' => 'edit_boxes', 36 | 'edit_others_posts' => 'edit_other_boxes', 37 | 'publish_posts' => 'publish_boxes', 38 | 'read_post' => 'read_box', 39 | 'read_private_posts' => 'read_private_box', 40 | 'delete_posts' => 'delete_box', 41 | ], 42 | ]; 43 | 44 | register_post_type('boxzilla-box', $args); 45 | 46 | add_shortcode('boxzilla_link', 'boxzilla_get_link_html'); 47 | } 48 | ); 49 | 50 | add_action( 51 | 'admin_init', 52 | function () { 53 | $admins = get_role('administrator'); 54 | 55 | if (empty($admins)) { 56 | return; 57 | } 58 | 59 | if (! $admins->has_cap('edit_box')) { 60 | $admins->add_cap('edit_box'); 61 | $admins->add_cap('edit_boxes'); 62 | $admins->add_cap('edit_other_boxes'); 63 | $admins->add_cap('publish_boxes'); 64 | $admins->add_cap('read_box'); 65 | $admins->add_cap('read_private_box'); 66 | $admins->add_cap('delete_box'); 67 | } 68 | } 69 | ); 70 | 71 | function boxzilla_get_link_html($args = [], $content = '') 72 | { 73 | $valid_actions = [ 74 | 'show', 75 | 'toggle', 76 | 'hide', 77 | 'dismiss', 78 | ]; 79 | $box_id = empty($args['box']) ? '' : absint($args['box']); 80 | $class_attr = empty($args['class']) ? '' : esc_attr($args['class']); 81 | $action = empty($args['action']) || ! in_array($args['action'], $valid_actions, true) ? 'show' : $args['action']; 82 | 83 | return "{$content}"; 84 | } 85 | -------------------------------------------------------------------------------- /assets/js/src/script.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | const Boxzilla = require('./boxzilla/boxzilla.js') 3 | const options = window.boxzilla_options 4 | 5 | // helper function for setting CSS styles 6 | function css (element, styles) { 7 | if (styles.background_color) { 8 | element.style.background = styles.background_color 9 | } 10 | 11 | if (styles.color) { 12 | element.style.color = styles.color 13 | } 14 | 15 | if (styles.border_color) { 16 | element.style.borderColor = styles.border_color 17 | } 18 | 19 | if (styles.border_width) { 20 | element.style.borderWidth = parseInt(styles.border_width) + 'px' 21 | } 22 | 23 | if (styles.border_style) { 24 | element.style.borderStyle = styles.border_style 25 | } 26 | 27 | if (styles.width) { 28 | element.style.maxWidth = parseInt(styles.width) + 'px' 29 | } 30 | } 31 | 32 | function createBoxesFromConfig () { 33 | // failsafe against including script twice. 34 | if (options.inited) { 35 | return 36 | } 37 | 38 | // create boxes from options 39 | for (var key in options.boxes) { 40 | // get opts 41 | var boxOpts = options.boxes[key] 42 | boxOpts.testMode = isLoggedIn && options.testMode 43 | 44 | // find box content element, bail if not found 45 | var boxContentElement = document.getElementById('boxzilla-box-' + boxOpts.id + '-content') 46 | if (!boxContentElement) { 47 | continue 48 | } 49 | 50 | // use element as content option 51 | boxOpts.content = boxContentElement 52 | 53 | // create box 54 | var box = Boxzilla.create(boxOpts.id, boxOpts) 55 | 56 | // add box slug to box element as classname 57 | box.element.className = box.element.className + ' boxzilla-' + boxOpts.post.slug 58 | 59 | // add custom css to box 60 | css(box.element, boxOpts.css) 61 | 62 | try { 63 | box.element.firstChild.firstChild.className += ' first-child' 64 | box.element.firstChild.lastChild.className += ' last-child' 65 | } catch (e) {} 66 | 67 | // maybe show box right away 68 | if (box.fits() && locationHashRefersBox(box)) { 69 | box.show() 70 | } 71 | } 72 | 73 | // set flag to prevent initialising twice 74 | options.inited = true 75 | 76 | // trigger "done" event. 77 | Boxzilla.trigger('done') 78 | 79 | // maybe open box with MC4WP form in it 80 | maybeOpenMailChimpForWordPressBox() 81 | } 82 | 83 | function locationHashRefersBox (box) { 84 | if (!window.location.hash || window.location.hash.length === 0) { 85 | return false 86 | } 87 | 88 | // parse "boxzilla-{id}" from location hash 89 | const match = window.location.hash.match(/[#&](boxzilla-\d+)/) 90 | if (!match || typeof (match) !== 'object' || match.length < 2) { 91 | return false 92 | } 93 | 94 | const elementId = match[1] 95 | if (elementId === box.element.id) { 96 | return true 97 | } else if (box.element.querySelector('#' + elementId)) { 98 | return true 99 | } 100 | 101 | return false 102 | } 103 | 104 | function maybeOpenMailChimpForWordPressBox () { 105 | if ((typeof (window.mc4wp_forms_config) !== 'object' || !window.mc4wp_forms_config.submitted_form) && 106 | (typeof (window.mc4wp_submitted_form) !== 'object')) { 107 | return 108 | } 109 | 110 | const form = window.mc4wp_submitted_form || window.mc4wp_forms_config.submitted_form 111 | const selector = '#' + form.element_id 112 | Boxzilla.boxes.forEach(box => { 113 | if (box.element.querySelector(selector)) { 114 | box.show() 115 | } 116 | }) 117 | } 118 | 119 | // print message when test mode is enabled 120 | const isLoggedIn = document.body && document.body.className && document.body.className.indexOf('logged-in') > -1 121 | if (isLoggedIn && options.testMode) { 122 | console.log('Boxzilla: Test mode is enabled. Please disable test mode if you\'re done testing.') 123 | } 124 | 125 | // init boxzilla 126 | Boxzilla.init() 127 | 128 | document.addEventListener('DOMContentLoaded', () => { 129 | // create JS objects for each box 130 | createBoxesFromConfig() 131 | 132 | // fire all events queued up during DOM load 133 | window.boxzilla_queue.forEach((q) => { 134 | const [method, args] = q 135 | Boxzilla[method].apply(null, args) 136 | }) 137 | }) 138 | })() 139 | -------------------------------------------------------------------------------- /src/licensing/class-api.php: -------------------------------------------------------------------------------- 1 | url = $url; 45 | $this->license = $license; 46 | } 47 | 48 | /** 49 | * Gets license status 50 | * 51 | * @return object 52 | */ 53 | public function get_license() 54 | { 55 | $endpoint = '/license'; 56 | $response = $this->request('GET', $endpoint); 57 | return $response; 58 | } 59 | 60 | 61 | /** 62 | * Logs the current site in to the remote API 63 | * 64 | * @return object 65 | */ 66 | public function activate_license() 67 | { 68 | $endpoint = '/license/activations'; 69 | $args = [ 70 | 'site_url' => get_option('siteurl'), 71 | ]; 72 | $response = $this->request('POST', $endpoint, $args); 73 | return $response; 74 | } 75 | 76 | /** 77 | * Logs the current site out of the remote API 78 | * 79 | * @return object 80 | */ 81 | public function deactivate_license() 82 | { 83 | $endpoint = sprintf('/license/activations/%s', $this->license->activation_key); 84 | $response = $this->request('DELETE', $endpoint); 85 | return $response; 86 | } 87 | 88 | /** 89 | * @param Plugin $plugin 90 | * @return object 91 | */ 92 | public function get_plugin(Plugin $plugin) 93 | { 94 | $endpoint = sprintf('/plugins/%s?format=wp', $plugin->id()); 95 | $response = $this->request('GET', $endpoint); 96 | return $response; 97 | } 98 | 99 | /** 100 | * @param Plugin[] $plugins (optional) 101 | * @return object 102 | */ 103 | public function get_plugins($plugins = null) 104 | { 105 | $args = [ 106 | 'format' => 'wp', 107 | ]; 108 | 109 | $endpoint = add_query_arg($args, '/plugins'); 110 | $response = $this->request('GET', $endpoint); 111 | return $response; 112 | } 113 | 114 | /** 115 | * @param string $method 116 | * @param string $endpoint 117 | * @param array $data 118 | * 119 | * @return object|array 120 | */ 121 | public function request($method, $endpoint, $data = []) 122 | { 123 | $url = $this->url . $endpoint; 124 | $args = [ 125 | 'method' => $method, 126 | 'headers' => [ 127 | 'Content-Type' => 'application/json', 128 | 'Accepts' => 'application/json', 129 | ], 130 | 'timeout' => 10, 131 | ]; 132 | 133 | // add license key to headers if set 134 | if (! empty($this->license->key)) { 135 | $args['headers']['Authorization'] = 'Bearer ' . urlencode($this->license->key); 136 | } 137 | 138 | if (! empty($data)) { 139 | if (in_array($method, [ 'GET', 'DELETE' ], true)) { 140 | $url = add_query_arg($data, $url); 141 | } else { 142 | $args['body'] = json_encode($data); 143 | } 144 | } 145 | 146 | $response = wp_remote_request($url, $args); 147 | return $this->parse_response($response); 148 | } 149 | 150 | /** 151 | * @param mixed $response 152 | * 153 | * @return object|null 154 | * 155 | * @throws API_Exception 156 | */ 157 | public function parse_response($response) 158 | { 159 | // test for wp errors (request failures) 160 | if ($response instanceof WP_Error) { 161 | throw new API_Exception($response->get_error_message()); 162 | } 163 | 164 | // retrieve response body 165 | $body = wp_remote_retrieve_body($response); 166 | if (empty($body)) { 167 | return null; 168 | } 169 | 170 | $json = json_decode($body, false); 171 | if (is_null($json)) { 172 | throw new API_Exception(esc_html__('The Boxzilla server returned an invalid response.', 'boxzilla')); 173 | } 174 | 175 | // did request return an error response? 176 | if (wp_remote_retrieve_response_code($response) >= 400) { 177 | throw new API_Exception($json->message, $json->code); 178 | } 179 | 180 | // return actual response data 181 | return $json; 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /assets/js/src/boxzilla/animator.js: -------------------------------------------------------------------------------- 1 | const duration = 320 2 | 3 | function css (element, styles) { 4 | for (const property of Object.keys(styles)) { 5 | element.style[property] = styles[property] 6 | } 7 | } 8 | 9 | function initObjectProperties (properties, value) { 10 | const newObject = {} 11 | for (let i = 0; i < properties.length; i++) { 12 | newObject[properties[i]] = value 13 | } 14 | return newObject 15 | } 16 | 17 | function copyObjectProperties (properties, object) { 18 | const newObject = {} 19 | for (let i = 0; i < properties.length; i++) { 20 | newObject[properties[i]] = object[properties[i]] 21 | } 22 | return newObject 23 | } 24 | 25 | /** 26 | * Checks if the given element is currently being animated. 27 | * 28 | * @param element 29 | * @returns {boolean} 30 | */ 31 | function animated (element) { 32 | return !!element.getAttribute('data-animated') 33 | } 34 | 35 | /** 36 | * Toggles the element using the given animation. 37 | * 38 | * @param element 39 | * @param animation Either "fade" or "slide" 40 | * @param callbackFn 41 | */ 42 | function toggle (element, animation, callbackFn) { 43 | const nowVisible = element.style.display !== 'none' || element.offsetLeft > 0 44 | 45 | // create clone for reference 46 | const clone = element.cloneNode(true) 47 | const cleanup = function () { 48 | element.removeAttribute('data-animated') 49 | element.setAttribute('style', clone.getAttribute('style')) 50 | element.style.display = nowVisible ? 'none' : '' 51 | if (callbackFn) { callbackFn() } 52 | } 53 | 54 | // store attribute so everyone knows we're animating this element 55 | element.setAttribute('data-animated', 'true') 56 | 57 | // toggle element visiblity right away if we're making something visible 58 | if (!nowVisible) { 59 | element.style.display = '' 60 | } 61 | 62 | let hiddenStyles 63 | let visibleStyles 64 | 65 | // animate properties 66 | if (animation === 'slide') { 67 | hiddenStyles = initObjectProperties(['height', 'borderTopWidth', 'borderBottomWidth', 'paddingTop', 'paddingBottom'], 0) 68 | visibleStyles = {} 69 | 70 | if (!nowVisible) { 71 | const computedStyles = window.getComputedStyle(element) 72 | visibleStyles = copyObjectProperties(['height', 'borderTopWidth', 'borderBottomWidth', 'paddingTop', 'paddingBottom'], computedStyles) 73 | 74 | // in some browsers, getComputedStyle returns "auto" value. this falls back to getBoundingClientRect() in those browsers since we need an actual height. 75 | if (!isFinite(visibleStyles.height)) { 76 | const clientRect = element.getBoundingClientRect() 77 | visibleStyles.height = clientRect.height 78 | } 79 | 80 | css(element, hiddenStyles) 81 | } 82 | 83 | // don't show a scrollbar during animation 84 | element.style.overflowY = 'hidden' 85 | animate(element, nowVisible ? hiddenStyles : visibleStyles, cleanup) 86 | } else { 87 | hiddenStyles = { opacity: 0 } 88 | visibleStyles = { opacity: 1 } 89 | if (!nowVisible) { 90 | css(element, hiddenStyles) 91 | } 92 | 93 | animate(element, nowVisible ? hiddenStyles : visibleStyles, cleanup) 94 | } 95 | } 96 | 97 | function animate (element, targetStyles, fn) { 98 | let last = +new Date() 99 | const initialStyles = window.getComputedStyle(element) 100 | const currentStyles = {} 101 | const propSteps = {} 102 | 103 | for (const property of Object.keys(targetStyles)) { 104 | // make sure we have an object filled with floats 105 | targetStyles[property] = parseFloat(targetStyles[property]) 106 | 107 | // calculate step size & current value 108 | const to = targetStyles[property] 109 | const current = parseFloat(initialStyles[property]) 110 | 111 | // is there something to do? 112 | if (current === to) { 113 | delete targetStyles[property] 114 | continue 115 | } 116 | 117 | propSteps[property] = (to - current) / duration // points per second 118 | currentStyles[property] = current 119 | } 120 | 121 | const tick = function () { 122 | const now = +new Date() 123 | const timeSinceLastTick = now - last 124 | let done = true 125 | 126 | let step, to, increment, newValue 127 | for (const property of Object.keys(targetStyles)) { 128 | step = propSteps[property] 129 | to = targetStyles[property] 130 | increment = step * timeSinceLastTick 131 | newValue = currentStyles[property] + increment 132 | 133 | if ((step > 0 && newValue >= to) || (step < 0 && newValue <= to)) { 134 | newValue = to 135 | } else { 136 | done = false 137 | } 138 | 139 | // store new value 140 | currentStyles[property] = newValue 141 | element.style[property] = property !== 'opacity' ? newValue + 'px' : newValue 142 | } 143 | 144 | last = +new Date() 145 | 146 | if (!done) { 147 | // keep going until we're done for all props 148 | window.requestAnimationFrame(tick) 149 | } else { 150 | // call callback 151 | fn && fn() 152 | } 153 | } 154 | 155 | tick() 156 | } 157 | 158 | module.exports = { 159 | toggle, 160 | animate, 161 | animated 162 | } 163 | -------------------------------------------------------------------------------- /src/admin/class-menu.php: -------------------------------------------------------------------------------- 1 | query( 34 | [ 35 | 'post_type' => 'boxzilla-box', 36 | 'post_status' => 'publish', 37 | 'posts_per_page' => -1, 38 | 'ignore_sticky_posts' => true, 39 | 'no_found_rows' => true, 40 | ] 41 | ); 42 | return $posts; 43 | } 44 | 45 | /** 46 | * Output menu links. 47 | */ 48 | public function nav_menu_links() 49 | { 50 | $posts = $this->get_boxes(); 51 | 52 | ?> 53 |
54 |
55 |
    56 | $post) : 59 | ?> 60 |
  • 61 | 64 | 65 | 66 | ID}"; ?>" /> 67 | 68 |
  • 69 | 73 |
74 |
75 |

76 | 77 | 78 | 79 | 80 | 81 | 82 |

83 |
84 | esc_html__('Boxzilla Pop-ups', 'boxzilla'), 98 | 'type_label' => esc_html__('Boxzilla Pop-ups', 'boxzilla'), 99 | 'type' => 'boxzilla_nav', 100 | 'object' => 'boxzilla_box', 101 | ]; 102 | 103 | return $item_types; 104 | } 105 | 106 | /** 107 | * Register account endpoints to customize nav menu items. 108 | * 109 | * @since 3.1.0 110 | * @param array $items List of nav menu items. 111 | * @param string $type Nav menu type. 112 | * @param string $object Nav menu object. 113 | * @param integer $page Page number. 114 | * @return array 115 | */ 116 | public function register_customize_nav_menu_items($items = [], $type = '', $object = '', $page = 0) 117 | { 118 | if ('boxzilla_box' !== $object) { 119 | return $items; 120 | } 121 | 122 | // Don't allow pagination since all items are loaded at once. 123 | if (0 < $page) { 124 | return $items; 125 | } 126 | 127 | $boxes = $this->get_boxes(); 128 | foreach ($boxes as $i => $post) { 129 | $items[] = [ 130 | 'id' => $i, 131 | 'title' => $post->post_title, 132 | 'type_label' => esc_html__('Custom Link', 'boxzilla'), 133 | 'url' => "#boxzilla-{$post->ID}", 134 | ]; 135 | } 136 | 137 | return $items; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/licensing/class-license-manager.php: -------------------------------------------------------------------------------- 1 | extensions = $extensions; 39 | $this->license = $license; 40 | $this->api = $api; 41 | } 42 | 43 | /** 44 | * @param mixed $object 45 | * @param string $property 46 | * @param string $default 47 | * 48 | * @return string 49 | */ 50 | protected function get_object_property($object, $property, $default = '') 51 | { 52 | return isset($object->$property) ? $object->$property : $default; 53 | } 54 | 55 | /** 56 | * @return void 57 | */ 58 | public function init() 59 | { 60 | // do nothing if no extensions are registered at this point 61 | if (empty($this->extensions)) { 62 | return; 63 | } 64 | 65 | // hooks 66 | add_action('boxzilla_after_settings', [ $this, 'show_license_form' ]); 67 | add_action('admin_notices', [ $this, 'show_notice' ], 1); 68 | 69 | // listen for activation / deactivation requests 70 | $this->listen(); 71 | } 72 | 73 | /** 74 | * Maybe show notice to activate license. 75 | */ 76 | public function show_notice() 77 | { 78 | global $current_screen; 79 | 80 | if ($this->license->activated) { 81 | return; 82 | } 83 | 84 | if ($this->get_object_property($current_screen, 'post_type') !== 'boxzilla-box') { 85 | return; 86 | } 87 | 88 | $plugin = $this->extensions[ array_rand($this->extensions) ]; 89 | $message = sprintf('Please activate your Boxzilla license to use %s.', admin_url('edit.php?post_type=boxzilla-box&page=boxzilla-settings'), '' . $plugin->name() . ''); 90 | echo sprintf('

%s

', 'warning', $message); 91 | } 92 | 93 | /** 94 | * @return void 95 | */ 96 | protected function listen() 97 | { 98 | 99 | // do nothing if not authenticated 100 | if (! current_user_can('manage_options')) { 101 | return; 102 | } 103 | 104 | // nothing to do 105 | if (! isset($_POST['boxzilla_license_form'])) { 106 | return; 107 | } 108 | 109 | $action = isset($_POST['action']) ? $_POST['action'] : 'activate'; 110 | $key_changed = false; 111 | 112 | // did key change or was "activate" button pressed? 113 | $new_license_key = sanitize_text_field($_POST['boxzilla_license_key']); 114 | if ($new_license_key !== $this->license->key) { 115 | $this->license->key = $new_license_key; 116 | $key_changed = true; 117 | } 118 | 119 | // run actions 120 | if ($action === 'deactivate') { 121 | $this->deactivate_license(); 122 | } elseif ($action === 'activate' || $key_changed) { 123 | $this->activate_license(); 124 | } 125 | 126 | $this->license->save(); 127 | } 128 | 129 | /** 130 | * Deactivate the license 131 | */ 132 | protected function deactivate_license() 133 | { 134 | try { 135 | $this->api->deactivate_license(); 136 | $this->notices[] = [ 137 | 'type' => 'info', 138 | 'message' => 'Your license was successfully deactivated!', 139 | ]; 140 | } catch (API_Exception $e) { 141 | $this->notices[] = [ 142 | 'type' => 'warning', 143 | 'message' => $e->getMessage(), 144 | ]; 145 | } 146 | 147 | $this->license->activated = false; 148 | $this->license->activation_key = ''; 149 | } 150 | 151 | /** 152 | * Activate the license 153 | */ 154 | protected function activate_license() 155 | { 156 | try { 157 | $activation = $this->api->activate_license(); 158 | } catch (API_Exception $e) { 159 | $message = $e->getMessage(); 160 | 161 | switch ($e->getApiCode()) { 162 | case 'license_at_limit': 163 | $message .= ' You can manage your site activations here.'; 164 | break; 165 | 166 | case 'license_expired': 167 | $message .= ' Please renew your license here.'; 168 | break; 169 | } 170 | 171 | $this->notices[] = [ 172 | 'type' => 'warning', 173 | 'message' => $message, 174 | ]; 175 | return; 176 | } 177 | 178 | $this->license->activation_key = $activation->token; 179 | $this->license->activated = true; 180 | 181 | $this->notices[] = [ 182 | 'type' => 'info', 183 | 'message' => 'Your license was successfully activated!', 184 | ]; 185 | } 186 | 187 | /** 188 | * Shows the license form 189 | */ 190 | public function show_license_form() 191 | { 192 | $license = $this->license; 193 | require __DIR__ . '/views/license-form.php'; 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /assets/js/src/admin/_admin.js: -------------------------------------------------------------------------------- 1 | const $ = window.jQuery 2 | const Option = require('./_option.js') 3 | const optionControls = document.getElementById('boxzilla-box-options-controls') 4 | const $optionControls = $(optionControls) 5 | const tnLoggedIn = document.createTextNode(' logged in') 6 | const EventEmitter = require('wolfy87-eventemitter') 7 | const events = new EventEmitter() 8 | const Designer = require('./_designer.js')($, Option, events) 9 | const rowTemplate = window.wp.template('rule-row-template') 10 | const i18n = window.boxzilla_i18n 11 | const ruleComparisonEl = document.getElementById('boxzilla-rule-comparison') 12 | const rulesContainerEl = document.getElementById('boxzilla-box-rules') 13 | const ajaxurl = window.ajaxurl 14 | 15 | // events 16 | $(window).on('load', function () { 17 | if (typeof (window.tinyMCE) === 'undefined') { 18 | document.getElementById('notice-notinymce').style.display = '' 19 | } 20 | 21 | $optionControls.on('click', '.boxzilla-add-rule', addRuleFields) 22 | $optionControls.on('click', '.boxzilla-remove-rule', removeRule) 23 | $optionControls.on('change', '.boxzilla-rule-condition', setContextualHelpers) 24 | $optionControls.find('.boxzilla-auto-show-trigger').on('change', toggleTriggerOptions) 25 | $(ruleComparisonEl).change(toggleAndOrTexts) 26 | $('.boxzilla-rule-row').each(setContextualHelpers) 27 | }) 28 | 29 | function toggleAndOrTexts () { 30 | var newText = ruleComparisonEl.value === 'any' ? i18n.or : i18n.and 31 | $('.boxzilla-andor').text(newText) 32 | } 33 | 34 | function toggleTriggerOptions () { 35 | $optionControls.find('.boxzilla-trigger-options').toggle(this.value !== '') 36 | } 37 | 38 | function removeRule () { 39 | var row = $(this).parents('tr') 40 | 41 | // delete andor row 42 | row.prev().remove() 43 | 44 | // delete rule row 45 | row.remove() 46 | } 47 | 48 | function setContextualHelpers () { 49 | var context = (this.tagName.toLowerCase() === 'tr') ? this : $(this).parents('tr').get(0) 50 | var condition = context.querySelector('.boxzilla-rule-condition').value 51 | var valueInput = context.querySelector('.boxzilla-rule-value') 52 | var qualifierInput = context.querySelector('.boxzilla-rule-qualifier') 53 | var betterInput = valueInput.cloneNode(true) 54 | var $betterInput = $(betterInput) 55 | 56 | // remove previously added helpers 57 | $(context.querySelectorAll('.boxzilla-helper')).remove() 58 | 59 | // prepare better input 60 | betterInput.removeAttribute('name') 61 | betterInput.className = betterInput.className + ' boxzilla-helper' 62 | valueInput.parentNode.insertBefore(betterInput, valueInput.nextSibling) 63 | $betterInput.change(function () { 64 | valueInput.value = this.value 65 | }) 66 | 67 | betterInput.style.display = '' 68 | valueInput.style.display = 'none' 69 | qualifierInput.style.display = '' 70 | qualifierInput.querySelector('option[value="not_contains"]').style.display = 'none' 71 | qualifierInput.querySelector('option[value="contains"]').style.display = 'none' 72 | if (tnLoggedIn.parentNode) { 73 | tnLoggedIn.parentNode.removeChild(tnLoggedIn) 74 | } 75 | 76 | // change placeholder for textual help 77 | switch (condition) { 78 | default: 79 | betterInput.placeholder = i18n.enterCommaSeparatedValues 80 | break 81 | 82 | case '': 83 | case 'everywhere': 84 | qualifierInput.value = '1' 85 | valueInput.value = '' 86 | betterInput.style.display = 'none' 87 | qualifierInput.style.display = 'none' 88 | break 89 | 90 | case 'is_single': 91 | case 'is_post': 92 | betterInput.placeholder = i18n.enterCommaSeparatedPosts 93 | $betterInput.suggest(ajaxurl + '?action=boxzilla_autocomplete&type=post', { 94 | multiple: true, 95 | multipleSep: ',' 96 | }) 97 | break 98 | 99 | case 'is_page': 100 | betterInput.placeholder = i18n.enterCommaSeparatedPages 101 | $betterInput.suggest(ajaxurl + '?action=boxzilla_autocomplete&type=page', { 102 | multiple: true, 103 | multipleSep: ',' 104 | }) 105 | break 106 | 107 | case 'is_post_type': 108 | betterInput.placeholder = i18n.enterCommaSeparatedPostTypes 109 | $betterInput.suggest(ajaxurl + '?action=boxzilla_autocomplete&type=post_type', { 110 | multiple: true, 111 | multipleSep: ',' 112 | }) 113 | break 114 | 115 | case 'is_url': 116 | qualifierInput.querySelector('option[value="contains"]').style.display = '' 117 | qualifierInput.querySelector('option[value="not_contains"]').style.display = '' 118 | betterInput.placeholder = i18n.enterCommaSeparatedRelativeUrls 119 | break 120 | 121 | case 'is_post_in_category': 122 | $betterInput.suggest(ajaxurl + '?action=boxzilla_autocomplete&type=category', { 123 | multiple: true, 124 | multipleSep: ',' 125 | }) 126 | break 127 | 128 | case 'is_post_with_tag': 129 | $betterInput.suggest(ajaxurl + '?action=boxzilla_autocomplete&type=post_tag', { 130 | multiple: true, 131 | multipleSep: ',' 132 | }) 133 | break 134 | 135 | case 'is_user_logged_in': 136 | betterInput.style.display = 'none' 137 | valueInput.parentNode.insertBefore(tnLoggedIn, valueInput.nextSibling) 138 | break 139 | 140 | case 'is_referer': 141 | qualifierInput.querySelector('option[value="contains"]').style.display = '' 142 | qualifierInput.querySelector('option[value="not_contains"]').style.display = '' 143 | break 144 | } 145 | } 146 | 147 | function addRuleFields () { 148 | var data = { 149 | key: optionControls.querySelectorAll('.boxzilla-rule-row').length, 150 | andor: ruleComparisonEl.value === 'any' ? i18n.or : i18n.and 151 | } 152 | var html = rowTemplate(data) 153 | $(rulesContainerEl).append(html) 154 | return false 155 | } 156 | 157 | module.exports = { 158 | Designer: Designer, 159 | Option: Option, 160 | events: events 161 | } 162 | -------------------------------------------------------------------------------- /assets/js/admin-script.js: -------------------------------------------------------------------------------- 1 | /*! For license information please see admin-script.js.LICENSE.txt */ 2 | (()=>{var e={242:(e,t,n)=>{const o=window.jQuery,r=n(212),i=document.getElementById("boxzilla-box-options-controls"),l=o(i),s=document.createTextNode(" logged in"),a=new(n(210)),u=n(504)(o,r,a),c=window.wp.template("rule-row-template"),p=window.boxzilla_i18n,d=document.getElementById("boxzilla-rule-comparison"),h=document.getElementById("boxzilla-box-rules"),f=window.ajaxurl;function y(){var e="any"===d.value?p.or:p.and;o(".boxzilla-andor").text(e)}function g(){l.find(".boxzilla-trigger-options").toggle(""!==this.value)}function m(){var e=o(this).parents("tr");e.prev().remove(),e.remove()}function v(){var e="tr"===this.tagName.toLowerCase()?this:o(this).parents("tr").get(0),t=e.querySelector(".boxzilla-rule-condition").value,n=e.querySelector(".boxzilla-rule-value"),r=e.querySelector(".boxzilla-rule-qualifier"),i=n.cloneNode(!0),l=o(i);switch(o(e.querySelectorAll(".boxzilla-helper")).remove(),i.removeAttribute("name"),i.className=i.className+" boxzilla-helper",n.parentNode.insertBefore(i,n.nextSibling),l.change((function(){n.value=this.value})),i.style.display="",n.style.display="none",r.style.display="",r.querySelector('option[value="not_contains"]').style.display="none",r.querySelector('option[value="contains"]').style.display="none",s.parentNode&&s.parentNode.removeChild(s),t){default:i.placeholder=p.enterCommaSeparatedValues;break;case"":case"everywhere":r.value="1",n.value="",i.style.display="none",r.style.display="none";break;case"is_single":case"is_post":i.placeholder=p.enterCommaSeparatedPosts,l.suggest(f+"?action=boxzilla_autocomplete&type=post",{multiple:!0,multipleSep:","});break;case"is_page":i.placeholder=p.enterCommaSeparatedPages,l.suggest(f+"?action=boxzilla_autocomplete&type=page",{multiple:!0,multipleSep:","});break;case"is_post_type":i.placeholder=p.enterCommaSeparatedPostTypes,l.suggest(f+"?action=boxzilla_autocomplete&type=post_type",{multiple:!0,multipleSep:","});break;case"is_url":r.querySelector('option[value="contains"]').style.display="",r.querySelector('option[value="not_contains"]').style.display="",i.placeholder=p.enterCommaSeparatedRelativeUrls;break;case"is_post_in_category":l.suggest(f+"?action=boxzilla_autocomplete&type=category",{multiple:!0,multipleSep:","});break;case"is_post_with_tag":l.suggest(f+"?action=boxzilla_autocomplete&type=post_tag",{multiple:!0,multipleSep:","});break;case"is_user_logged_in":i.style.display="none",n.parentNode.insertBefore(s,n.nextSibling);break;case"is_referer":r.querySelector('option[value="contains"]').style.display="",r.querySelector('option[value="not_contains"]').style.display=""}}function b(){var e={key:i.querySelectorAll(".boxzilla-rule-row").length,andor:"any"===d.value?p.or:p.and},t=c(e);return o(h).append(t),!1}o(window).on("load",(function(){void 0===window.tinyMCE&&(document.getElementById("notice-notinymce").style.display=""),l.on("click",".boxzilla-add-rule",b),l.on("click",".boxzilla-remove-rule",m),l.on("change",".boxzilla-rule-condition",v),l.find(".boxzilla-auto-show-trigger").on("change",g),o(d).change(y),o(".boxzilla-rule-row").each(v)})),e.exports={Designer:u,Option:r,events:a}},504:e=>{e.exports=function(e,t,n){var o,r,i,l=document.getElementById("post_ID").value||0,s={},a=!1,u=e("#boxzilla-box-appearance-controls");function c(){return!!a&&(window.setTimeout((()=>{i.css({"border-color":s.borderColor.getColorValue(),"border-width":s.borderWidth.getPxValue(),"border-style":s.borderStyle.getValue(),"background-color":s.backgroundColor.getColorValue(),width:s.width.getPxValue(),color:s.color.getColorValue()}),n.trigger("editor.styles.apply")}),10),!0)}return u.find("input.boxzilla-color-field").wpColorPicker({change:c,clear:c}),u.find(":input").not(".boxzilla-color-field").change(c),n.on("editor.init",c),{init:function(){"object"==typeof window.tinyMCE&&null!==window.tinyMCE.get("content")&&(s.borderColor=new t("border-color"),s.borderWidth=new t("border-width"),s.borderStyle=new t("border-style"),s.backgroundColor=new t("background-color"),s.width=new t("width"),s.color=new t("color"),r=e("#content_ifr"),(o=r.contents().find("html")).css({background:"white"}),(i=o.find("#tinymce")).addClass("boxzilla boxzilla-"+l),i.css({margin:0,background:"white",display:"inline-block",width:"auto","min-width":"240px",position:"relative"}),i.get(0).style.cssText+=";padding: 25px !important;",a=!0,n.trigger("editor.init"))},resetStyles:function(){for(var e in s)"theme"!==e.substring(0,5)&&s[e].clear();c(),n.trigger("editor.styles.reset")},options:s}}},212:e=>{var t=window.jQuery,n=function(e){"string"==typeof e&&(e=document.getElementById("boxzilla-"+e)),e||console.error("Unable to find option element."),this.element=e};n.prototype.getColorValue=function(){return this.element.value.length>0?t(this.element).hasClass("wp-color-field")?t(this.element).wpColorPicker("color"):this.element.value:""},n.prototype.getPxValue=function(e){return this.element.value.length>0?parseInt(this.element.value)+"px":e||""},n.prototype.getValue=function(e){return this.element.value.length>0?this.element.value:e||""},n.prototype.clear=function(){this.element.value=""},n.prototype.setValue=function(e){this.element.value=e},e.exports=n},210:function(e,t,n){var o;!function(t){"use strict";function r(){}var i=r.prototype,l=t.EventEmitter;function s(e,t){for(var n=e.length;n--;)if(e[n].listener===t)return n;return-1}function a(e){return function(){return this[e].apply(this,arguments)}}function u(e){return"function"==typeof e||e instanceof RegExp||!(!e||"object"!=typeof e)&&u(e.listener)}i.getListeners=function(e){var t,n,o=this._getEvents();if(e instanceof RegExp)for(n in t={},o)o.hasOwnProperty(n)&&e.test(n)&&(t[n]=o[n]);else t=o[e]||(o[e]=[]);return t},i.flattenListeners=function(e){var t,n=[];for(t=0;tpost = $post; 52 | 53 | // store ID in property for quick access 54 | $this->ID = $post->ID; 55 | 56 | // store title in property 57 | $this->title = $post->post_title; 58 | 59 | // store content in property 60 | $this->content = $post->post_content; 61 | 62 | // is this box enabled? 63 | $this->enabled = $post->post_status === 'publish'; 64 | 65 | // load and store options in property 66 | $this->options = $this->load_options(); 67 | } 68 | 69 | /** 70 | * Get the options for this box. 71 | ** 72 | * @return array Array of box options 73 | */ 74 | protected function load_options() 75 | { 76 | $defaults = [ 77 | 'css' => [ 78 | 'background_color' => '', 79 | 'color' => '', 80 | 'width' => '', 81 | 'border_color' => '', 82 | 'border_width' => '', 83 | 'border_style' => '', 84 | 'position' => 'bottom-right', 85 | ], 86 | 'rules' => [ 87 | 0 => [ 88 | 'condition' => '', 89 | 'value' => '', 90 | ], 91 | ], 92 | 'rules_comparision' => 'any', 93 | 'cookie' => [ 94 | 'triggered' => 0, 95 | 'dismissed' => 0, 96 | ], 97 | 'trigger' => 'percentage', 98 | 'trigger_percentage' => 65, 99 | 'trigger_element' => '', 100 | 'trigger_time_on_site' => 0, 101 | 'trigger_time_on_page' => 0, 102 | 'animation' => 'fade', 103 | 'auto_hide' => 0, 104 | 'screen_size_condition' => [ 105 | 'condition' => 'larger', 106 | 'value' => 0, 107 | ], 108 | 'closable' => 1, 109 | 'show_close_icon' => 1, 110 | ]; 111 | $box = $this; 112 | 113 | $options = get_post_meta($this->ID, 'boxzilla_options', true); 114 | $options = is_array($options) ? $options : []; 115 | 116 | // merge options with default options 117 | $options = array_replace_recursive($defaults, $options); 118 | 119 | // allow others to filter the final array of options 120 | /** 121 | * Filter the options for a given box 122 | * 123 | * @param array $options 124 | * @param Box $box 125 | */ 126 | $options = apply_filters('boxzilla_box_options', $options, $box); 127 | 128 | return $options; 129 | } 130 | 131 | /** 132 | * @return bool 133 | */ 134 | public function is_enabled() 135 | { 136 | return $this->enabled; 137 | } 138 | 139 | /** 140 | * Get the options for this box 141 | * 142 | * @return array 143 | */ 144 | public function get_options() 145 | { 146 | return $this->options; 147 | } 148 | 149 | /** 150 | * Get the close / hide icon for this box 151 | * 152 | * @return string 153 | */ 154 | public function get_close_icon() 155 | { 156 | if (! $this->options['show_close_icon']) { 157 | return ''; 158 | } 159 | 160 | $box = $this; 161 | $html = '×'; 162 | 163 | /** 164 | * Filters the HTML for the close icon. 165 | * 166 | * @param string $html 167 | * @param Box $box 168 | */ 169 | $close_icon = (string) apply_filters('boxzilla_box_close_icon', $html, $box); 170 | 171 | return $close_icon; 172 | } 173 | 174 | /** 175 | * Get the content of this box 176 | * 177 | * @return string 178 | */ 179 | public function get_content() 180 | { 181 | $content = $this->content; 182 | $box = $this; 183 | 184 | // replace boxzilla specific shortcodes 185 | $close_link = "ID});\">"; 186 | 187 | $replacements = [ 188 | '[boxzilla_close]' => $close_link, // accept underscore and dash here for consistency with other shortcode 189 | '[boxzilla-close]' => $close_link, 190 | '[/boxzilla_close]' => '', 191 | '[/boxzilla-close]' => '', 192 | ]; 193 | 194 | $content = str_replace(array_keys($replacements), array_values($replacements), $content); 195 | 196 | /** 197 | * Filters the HTML for the box content 198 | * 199 | * @param string $content 200 | * @param Box $box 201 | */ 202 | $content = apply_filters('boxzilla_box_content', $content, $box); 203 | return $content; 204 | } 205 | 206 | /** 207 | * Get options object for JS script. 208 | * 209 | * @return array 210 | */ 211 | public function get_client_options() 212 | { 213 | $box = $this; 214 | 215 | $trigger = false; 216 | if ($box->options['trigger']) { 217 | $trigger = [ 218 | 'method' => $this->options['trigger'], 219 | ]; 220 | 221 | if (isset($this->options[ 'trigger_' . $this->options['trigger'] ])) { 222 | $trigger['value'] = $this->options[ 'trigger_' . $this->options['trigger'] ]; 223 | } 224 | } 225 | 226 | // build screenWidthCondition object (or null) 227 | $screen_width_condition = null; 228 | if ($box->options['screen_size_condition']['value'] > 0) { 229 | $screen_width_condition = [ 230 | 'condition' => $box->options['screen_size_condition']['condition'], 231 | 'value' => intval($box->options['screen_size_condition']['value']), 232 | ]; 233 | } 234 | 235 | $post = $this->post; 236 | $client_options = [ 237 | 'id' => $box->ID, 238 | 'icon' => $box->get_close_icon(), 239 | 'content' => '', // we grab this later from an HTML element 240 | 'css' => array_filter($box->options['css']), 241 | 'trigger' => $trigger, 242 | 'animation' => $box->options['animation'], 243 | 'cookie' => [ 244 | 'triggered' => absint($box->options['cookie']['triggered']), 245 | 'dismissed' => absint($box->options['cookie']['dismissed']), 246 | ], 247 | 'rehide' => (bool) $box->options['auto_hide'], 248 | 'position' => $box->options['css']['position'], 249 | 'screenWidthCondition' => $screen_width_condition, 250 | 'closable' => ! ! $box->options['closable'], 251 | 'post' => [ 252 | 'id' => $post->ID, 253 | 'title' => $post->post_title, 254 | 'slug' => $post->post_name, 255 | ], 256 | ]; 257 | 258 | /** 259 | * Filter the final options for the JS Boxzilla client. 260 | * 261 | * @param array $client_options 262 | * @param Box $box 263 | */ 264 | $client_options = apply_filters('boxzilla_box_client_options', $client_options, $box); 265 | 266 | return $client_options; 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /src/licensing/class-update-manager.php: -------------------------------------------------------------------------------- 1 | extensions = $extensions; 37 | $this->license = $license; 38 | $this->api = $api; 39 | } 40 | 41 | /** 42 | * Add hooks 43 | */ 44 | public function init() 45 | { 46 | add_filter('pre_set_site_transient_update_plugins', [ $this, 'add_updates' ]); 47 | add_filter('plugins_api', [ $this, 'get_plugin_info' ], 20, 3); 48 | add_filter('http_request_args', [ $this, 'add_auth_headers' ], 10, 2); 49 | } 50 | 51 | /** 52 | * This adds the license key header to download package requests. 53 | * 54 | * @param array $args 55 | * @param string $url 56 | * 57 | * @return mixed 58 | */ 59 | public function add_auth_headers($args, $url) 60 | { 61 | // only act on download request's to the Boxzilla update API 62 | if (strpos($url, $this->api->url) !== 0 || strpos($url, '/download') === false) { 63 | return $args; 64 | } 65 | 66 | // only add if activation key not empty 67 | if (empty($this->license->activation_key)) { 68 | return $args; 69 | } 70 | 71 | if (! isset($args['headers'])) { 72 | $args['headers'] = []; 73 | } 74 | 75 | $args['headers']['Authorization'] = "Bearer {$this->license->activation_key}"; 76 | return $args; 77 | } 78 | 79 | private function get_plugin_by_slug($slug) 80 | { 81 | foreach ($this->extensions as $p) { 82 | // find plugin by slug 83 | if (dirname($p->slug()) === $slug) { 84 | return $p; 85 | } 86 | } 87 | 88 | return null; 89 | } 90 | 91 | /** 92 | * @param $result 93 | * @param string $action 94 | * @param null $args 95 | * 96 | * @return object 97 | */ 98 | public function get_plugin_info($result, $action = '', $args = null) 99 | { 100 | // do nothing for unrelated requests 101 | if ($action !== 'plugin_information' || ! isset($args->slug)) { 102 | return $result; 103 | } 104 | 105 | // only act on our plugins 106 | if (strpos($args->slug, 'boxzilla-') !== 0) { 107 | return $result; 108 | } 109 | 110 | return $this->get_update_info($args->slug); 111 | } 112 | 113 | /** 114 | * @param object $updates 115 | * @return object 116 | */ 117 | public function add_updates($updates) 118 | { 119 | 120 | // do nothing if no plugins registered 121 | if (empty($this->extensions)) { 122 | return $updates; 123 | } 124 | 125 | // failsafe WP bug 126 | if (empty($updates) || ! isset($updates->response) || ! is_array($updates->response)) { 127 | return $updates; 128 | } 129 | 130 | // fetch available updates 131 | $available_updates = $this->fetch_updates(); 132 | 133 | // merge with other updates 134 | $updates->response = array_merge($updates->response, $available_updates); 135 | 136 | return $updates; 137 | } 138 | 139 | /** 140 | * Fetch array of available updates from remote server 141 | * 142 | * @return array 143 | */ 144 | protected function fetch_updates() 145 | { 146 | if (is_array($this->available_updates)) { 147 | return $this->available_updates; 148 | } 149 | 150 | // don't try if we failed a request recently. 151 | $failed_at = get_transient('boxzilla_request_failed'); 152 | if (! empty($failed_at) && ( ( time() - 300 ) < $failed_at )) { 153 | return []; 154 | } 155 | 156 | // fetch remote info 157 | try { 158 | $remote_plugins = $this->api->get_plugins(); 159 | } catch (API_Exception $e) { 160 | // set flag for 5 minutes 161 | set_transient('boxzilla_request_failed', time(), 300); 162 | return []; 163 | } 164 | 165 | // filter remote plugins, we only want the ones with an update available 166 | $this->available_updates = $this->filter_remote_plugins($remote_plugins); 167 | 168 | return $this->available_updates; 169 | } 170 | 171 | /** 172 | * @param $remote_plugins 173 | * @return array 174 | */ 175 | protected function filter_remote_plugins($remote_plugins) 176 | { 177 | $available_updates = []; 178 | 179 | // find new versions 180 | foreach ($remote_plugins as $remote_plugin) { 181 | // sanity check 182 | if (! isset($remote_plugin->new_version)) { 183 | continue; 184 | } 185 | 186 | // if plugin is activated, we can access it here 187 | if (isset($this->extensions[ $remote_plugin->sid ])) { 188 | 189 | /** @var Plugin $local_plugin */ 190 | $local_plugin = $this->extensions[ $remote_plugin->sid ]; 191 | 192 | // plugin found and local plugin version not same as remote version? 193 | if (! $local_plugin || version_compare($local_plugin->version(), $remote_plugin->new_version, '>=')) { 194 | continue; 195 | } 196 | 197 | // add some dynamic data 198 | $available_updates[ $local_plugin->slug() ] = $this->format_response($local_plugin->slug(), $remote_plugin); 199 | } else { 200 | // if plugin is not active, use get_plugin_data for fetching version 201 | $plugin_file = WP_PLUGIN_DIR . "/{$remote_plugin->slug}/{$remote_plugin->slug}.php"; 202 | if (! file_exists($plugin_file)) { 203 | continue; 204 | } 205 | 206 | $plugin_data = get_plugin_data($plugin_file); 207 | 208 | if (! $plugin_data || version_compare($plugin_data['Version'], $remote_plugin->new_version, '>=')) { 209 | continue; 210 | } 211 | 212 | // add some dynamic data 213 | $slug = plugin_basename($plugin_file); 214 | $available_updates[ $slug ] = $this->format_response($slug, $remote_plugin); 215 | } 216 | } 217 | 218 | return $available_updates; 219 | } 220 | 221 | /** 222 | * @param string $slug 223 | * 224 | * @return null 225 | */ 226 | public function get_update_info($slug) 227 | { 228 | $available_updates = $this->fetch_updates(); 229 | 230 | foreach ($available_updates as $plugin_file => $update_info) { 231 | if ($slug === $update_info->slug) { 232 | return $update_info; 233 | } 234 | } 235 | 236 | return null; 237 | } 238 | 239 | /** 240 | * @param $slug 241 | * @param $response 242 | * 243 | * @return mixed 244 | */ 245 | protected function format_response($slug, $response) 246 | { 247 | $response->slug = dirname($slug); 248 | $response->plugin = $slug; 249 | 250 | // add some notices if license is inactive 251 | if (! $this->license->activated) { 252 | $response->upgrade_notice = sprintf('You will need to activate your license to install this plugin update.', admin_url('edit.php?post_type=boxzilla-box&page=boxzilla-settings')); 253 | $response->sections->changelog = '

' . sprintf('You will need to activate your license to install this plugin update.', admin_url('edit.php?post_type=boxzilla-box&page=boxzilla-settings')) . '

' . $response->sections->changelog; 254 | $response->package = null; 255 | } 256 | 257 | // cast subkey objects to array as that is what WP expects 258 | $response->sections = get_object_vars($response->sections); 259 | $response->banners = get_object_vars($response->banners); 260 | $response->contributors = get_object_vars($response->contributors); 261 | $response->contributors = array_map( 262 | function ($v) { 263 | return get_object_vars($v); 264 | }, 265 | $response->contributors 266 | ); 267 | 268 | return $response; 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /src/di/class-container.php: -------------------------------------------------------------------------------- 1 | factories = new \SplObjectStorage(); 52 | $this->protected = new \SplObjectStorage(); 53 | 54 | foreach ( $values as $key => $value ) { 55 | $this->offsetSet( $key, $value ); 56 | } 57 | } 58 | 59 | /** 60 | * Sets a parameter or an object. 61 | * 62 | * Objects must be defined as Closures. 63 | * 64 | * Allowing any PHP callable leads to difficult to debug problems 65 | * as function names (strings) are callable (creating a function with 66 | * the same name as an existing parameter would break your container). 67 | * 68 | * @param string $id The unique identifier for the parameter or object 69 | * @param mixed $value The value of the parameter or a closure to define an object 70 | * @throws \RuntimeException Prevent override of a frozen service 71 | */ 72 | #[\ReturnTypeWillChange] 73 | public function offsetSet( $id, $value ) { 74 | if ( isset( $this->frozen[ $id ] ) ) { 75 | throw new \RuntimeException( sprintf( 'Cannot override frozen service "%s".', $id ) ); 76 | } 77 | 78 | $this->values[ $id ] = $value; 79 | $this->keys[ $id ] = true; 80 | } 81 | 82 | /** 83 | * Gets a parameter or an object. 84 | * 85 | * @param string $id The unique identifier for the parameter or object 86 | * 87 | * @return mixed The value of the parameter or an object 88 | * 89 | * @throws \InvalidArgumentException if the identifier is not defined 90 | */ 91 | #[\ReturnTypeWillChange] 92 | public function offsetGet( $id ) { 93 | if ( ! isset( $this->keys[ $id ] ) ) { 94 | throw new \InvalidArgumentException( sprintf( 'Identifier "%s" is not defined.', $id ) ); 95 | } 96 | 97 | if ( 98 | isset( $this->raw[ $id ] ) 99 | || ! is_object( $this->values[ $id ] ) 100 | || isset( $this->protected[ $this->values[ $id ] ] ) 101 | || ! method_exists( $this->values[ $id ], '__invoke' ) 102 | ) { 103 | return $this->values[ $id ]; 104 | } 105 | 106 | if ( isset( $this->factories[ $this->values[ $id ] ] ) ) { 107 | return $this->values[ $id ]($this); 108 | } 109 | 110 | $raw = $this->values[ $id ]; 111 | $val = $this->values[ $id ] = $raw( $this ); 112 | $this->raw[ $id ] = $raw; 113 | 114 | $this->frozen[ $id ] = true; 115 | 116 | return $val; 117 | } 118 | 119 | /** 120 | * Checks if a parameter or an object is set. 121 | * 122 | * @param string $id The unique identifier for the parameter or object 123 | * 124 | * @return bool 125 | */ 126 | #[\ReturnTypeWillChange] 127 | public function offsetExists( $id ) { 128 | return isset( $this->keys[ $id ] ); 129 | } 130 | 131 | /** 132 | * Unsets a parameter or an object. 133 | * 134 | * @param string $id The unique identifier for the parameter or object 135 | */ 136 | #[\ReturnTypeWillChange] 137 | public function offsetUnset( $id ) { 138 | if ( isset( $this->keys[ $id ] ) ) { 139 | if ( is_object( $this->values[ $id ] ) ) { 140 | unset( $this->factories[ $this->values[ $id ] ], $this->protected[ $this->values[ $id ] ] ); 141 | } 142 | 143 | unset( $this->values[ $id ], $this->frozen[ $id ], $this->raw[ $id ], $this->keys[ $id ] ); 144 | } 145 | } 146 | 147 | /** 148 | * Marks a callable as being a factory service. 149 | * 150 | * @param callable $callable A service definition to be used as a factory 151 | * 152 | * @return callable The passed callable 153 | * 154 | * @throws \InvalidArgumentException Service definition has to be a closure of an invokable object 155 | */ 156 | public function factory( $callable ) { 157 | if ( ! is_object( $callable ) || ! method_exists( $callable, '__invoke' ) ) { 158 | throw new \InvalidArgumentException( 'Service definition is not a Closure or invokable object.' ); 159 | } 160 | 161 | $this->factories->attach( $callable ); 162 | 163 | return $callable; 164 | } 165 | 166 | /** 167 | * Protects a callable from being interpreted as a service. 168 | * 169 | * This is useful when you want to store a callable as a parameter. 170 | * 171 | * @param callable $callable A callable to protect from being evaluated 172 | * 173 | * @return callable The passed callable 174 | * 175 | * @throws \InvalidArgumentException Service definition has to be a closure of an invokable object 176 | */ 177 | public function protect( $callable ) { 178 | if ( ! is_object( $callable ) || ! method_exists( $callable, '__invoke' ) ) { 179 | throw new \InvalidArgumentException( 'Callable is not a Closure or invokable object.' ); 180 | } 181 | 182 | $this->protected->attach( $callable ); 183 | 184 | return $callable; 185 | } 186 | 187 | /** 188 | * Gets a parameter or the closure defining an object. 189 | * 190 | * @param string $id The unique identifier for the parameter or object 191 | * 192 | * @return mixed The value of the parameter or the closure defining an object 193 | * 194 | * @throws \InvalidArgumentException if the identifier is not defined 195 | */ 196 | public function raw( $id ) { 197 | if ( ! isset( $this->keys[ $id ] ) ) { 198 | throw new \InvalidArgumentException( sprintf( 'Identifier "%s" is not defined.', $id ) ); 199 | } 200 | 201 | if ( isset( $this->raw[ $id ] ) ) { 202 | return $this->raw[ $id ]; 203 | } 204 | 205 | return $this->values[ $id ]; 206 | } 207 | 208 | /** 209 | * Extends an object definition. 210 | * 211 | * Useful when you want to extend an existing object definition, 212 | * without necessarily loading that object. 213 | * 214 | * @param string $id The unique identifier for the object 215 | * @param callable $callable A service definition to extend the original 216 | * 217 | * @return callable The wrapped callable 218 | * 219 | * @throws \InvalidArgumentException if the identifier is not defined or not a service definition 220 | */ 221 | public function extend( $id, $callable ) { 222 | if ( ! isset( $this->keys[ $id ] ) ) { 223 | throw new \InvalidArgumentException( sprintf( 'Identifier "%s" is not defined.', $id ) ); 224 | } 225 | 226 | if ( ! is_object( $this->values[ $id ] ) || ! method_exists( $this->values[ $id ], '__invoke' ) ) { 227 | throw new \InvalidArgumentException( sprintf( 'Identifier "%s" does not contain an object definition.', $id ) ); 228 | } 229 | 230 | if ( ! is_object( $callable ) || ! method_exists( $callable, '__invoke' ) ) { 231 | throw new \InvalidArgumentException( 'Extension service definition is not a Closure or invokable object.' ); 232 | } 233 | 234 | $factory = $this->values[ $id ]; 235 | 236 | $extended = function ( $c ) use ( $callable, $factory ) { 237 | return $callable( $factory( $c ), $c ); 238 | }; 239 | 240 | if ( isset( $this->factories[ $factory ] ) ) { 241 | $this->factories->detach( $factory ); 242 | $this->factories->attach( $extended ); 243 | } 244 | 245 | return $this[ $id ] = $extended; 246 | } 247 | 248 | /** 249 | * Returns all defined value names. 250 | * 251 | * @return array An array of value names 252 | */ 253 | public function keys() { 254 | return array_keys( $this->values ); 255 | } 256 | 257 | /** 258 | * Registers a service provider. 259 | * 260 | * @param ServiceProviderInterface $provider A ServiceProviderInterface instance 261 | * @param array $values An array of values that customizes the provider 262 | * 263 | * @return static 264 | */ 265 | public function register( ServiceProviderInterface $provider, array $values = array() ) { 266 | $provider->register( $this ); 267 | 268 | foreach ( $values as $key => $value ) { 269 | $this[ $key ] = $value; 270 | } 271 | 272 | return $this; 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /assets/js/src/boxzilla/box.js: -------------------------------------------------------------------------------- 1 | const defaults = { 2 | animation: 'fade', 3 | rehide: false, 4 | content: '', 5 | cookie: null, 6 | icon: '×', 7 | screenWidthCondition: null, 8 | position: 'center', 9 | testMode: false, 10 | trigger: false, 11 | closable: true 12 | } 13 | 14 | const Animator = require('./animator.js') 15 | 16 | /** 17 | * Merge 2 objects, values of the latter overwriting the former. 18 | * 19 | * @param obj1 20 | * @param obj2 21 | * @returns {*} 22 | */ 23 | function merge (obj1, obj2) { 24 | const obj3 = {} 25 | 26 | for (const attrname of Object.keys(obj1)) { 27 | obj3[attrname] = obj1[attrname] 28 | } 29 | 30 | for (const attrname of Object.keys(obj2)) { 31 | obj3[attrname] = obj2[attrname] 32 | } 33 | return obj3 34 | } 35 | 36 | /** 37 | * Get the real height of entire document. 38 | * @returns {number} 39 | */ 40 | function getDocumentHeight () { 41 | const body = document.body 42 | const html = document.documentElement 43 | return Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight) 44 | } 45 | 46 | // Box Object 47 | function Box (id, config, fireEvent) { 48 | this.id = id 49 | this.fireEvent = fireEvent 50 | 51 | // store config values 52 | this.config = merge(defaults, config) 53 | 54 | // add overlay element to dom and store ref to overlay 55 | this.overlay = document.createElement('div') 56 | this.overlay.setAttribute('aria-modal', true) 57 | this.overlay.style.display = 'none' 58 | this.overlay.id = 'boxzilla-overlay-' + this.id 59 | this.overlay.classList.add('boxzilla-overlay') 60 | document.body.appendChild(this.overlay) 61 | 62 | // state 63 | this.visible = false 64 | this.dismissed = false 65 | this.triggered = false 66 | this.triggerHeight = this.calculateTriggerHeight() 67 | this.cookieSet = this.isCookieSet() 68 | this.element = null 69 | this.contentElement = null 70 | this.closeIcon = null 71 | 72 | // create dom elements for this box 73 | this.dom() 74 | 75 | // further initialise the box 76 | this.events() 77 | } 78 | 79 | // initialise the box 80 | Box.prototype.events = function () { 81 | const box = this 82 | 83 | // attach event to "close" icon inside box 84 | if (this.closeIcon) { 85 | this.closeIcon.addEventListener('click', (evt) => { 86 | evt.preventDefault() 87 | box.dismiss() 88 | }) 89 | } 90 | 91 | this.element.addEventListener('click', (evt) => { 92 | if (evt.target.tagName === 'A' || evt.target.tagName === 'AREA') { 93 | box.fireEvent('box.interactions.link', [box, evt.target]) 94 | } 95 | }, false) 96 | 97 | this.element.addEventListener('submit', (evt) => { 98 | box.setCookie() 99 | box.fireEvent('box.interactions.form', [box, evt.target]) 100 | }, false) 101 | 102 | this.overlay.addEventListener('click', (evt) => { 103 | const x = evt.offsetX 104 | const y = evt.offsetY 105 | 106 | // calculate if click was less than 40px outside box to avoid closing it by accident 107 | const rect = box.element.getBoundingClientRect() 108 | const margin = 40 109 | 110 | // if click was not anywhere near box, dismiss it. 111 | if (x < (rect.left - margin) || 112 | x > (rect.right + margin) || 113 | y < (rect.top - margin) || 114 | y > (rect.bottom + margin)) { 115 | box.dismiss() 116 | } 117 | }) 118 | } 119 | 120 | // generate dom elements for this box 121 | Box.prototype.dom = function () { 122 | const wrapper = document.createElement('div') 123 | wrapper.className = 'boxzilla-container boxzilla-' + this.config.position + '-container' 124 | 125 | const box = document.createElement('div') 126 | box.id = 'boxzilla-' + this.id 127 | box.className = 'boxzilla boxzilla-' + this.id + ' boxzilla-' + this.config.position 128 | box.style.display = 'none' 129 | wrapper.appendChild(box) 130 | 131 | let content 132 | if (typeof (this.config.content) === 'string') { 133 | content = document.createElement('div') 134 | content.innerHTML = this.config.content 135 | } else { 136 | content = this.config.content 137 | 138 | // make sure element is visible 139 | content.style.display = '' 140 | } 141 | content.className = 'boxzilla-content' 142 | box.appendChild(content) 143 | 144 | if (this.config.closable && this.config.icon) { 145 | const closeIcon = document.createElement('span') 146 | closeIcon.className = 'boxzilla-close-icon' 147 | closeIcon.innerHTML = this.config.icon 148 | closeIcon.setAttribute('aria-label', 'close') 149 | box.appendChild(closeIcon) 150 | this.closeIcon = closeIcon 151 | } 152 | 153 | document.body.appendChild(wrapper) 154 | this.contentElement = content 155 | this.element = box 156 | } 157 | 158 | // set (calculate) custom box styling depending on box options 159 | Box.prototype.setCustomBoxStyling = function () { 160 | // reset element to its initial state 161 | const origDisplay = this.element.style.display 162 | this.element.style.display = '' 163 | this.element.style.overflowY = '' 164 | this.element.style.maxHeight = '' 165 | 166 | // get new dimensions 167 | const windowHeight = window.innerHeight 168 | const boxHeight = this.element.clientHeight 169 | 170 | // add scrollbar to box and limit height 171 | if (boxHeight > windowHeight) { 172 | this.element.style.maxHeight = windowHeight + 'px' 173 | this.element.style.overflowY = 'scroll' 174 | } 175 | 176 | // set new top margin for boxes which are centered 177 | if (this.config.position === 'center') { 178 | let newTopMargin = ((windowHeight - boxHeight) / 2) 179 | newTopMargin = newTopMargin >= 0 ? newTopMargin : 0 180 | this.element.style.marginTop = newTopMargin + 'px' 181 | } 182 | 183 | this.element.style.display = origDisplay 184 | } 185 | 186 | // toggle visibility of the box 187 | Box.prototype.toggle = function (show, animate) { 188 | show = typeof (show) === 'undefined' ? !this.visible : show 189 | animate = typeof (animate) === 'undefined' ? true : animate 190 | 191 | // is box already at desired visibility? 192 | if (show === this.visible) { 193 | return false 194 | } 195 | 196 | // is box being animated? 197 | if (Animator.animated(this.element)) { 198 | return false 199 | } 200 | 201 | // if box should be hidden but is not closable, bail. 202 | if (!show && !this.config.closable) { 203 | return false 204 | } 205 | 206 | // set new visibility status 207 | this.visible = show 208 | 209 | // calculate new styling rules 210 | this.setCustomBoxStyling() 211 | 212 | // trigger event 213 | this.fireEvent('box.' + (show ? 'show' : 'hide'), [this]) 214 | 215 | // show or hide box using selected animation 216 | if (this.config.position === 'center') { 217 | this.overlay.classList.toggle('boxzilla-' + this.id + '-overlay') 218 | 219 | if (animate) { 220 | Animator.toggle(this.overlay, 'fade') 221 | } else { 222 | this.overlay.style.display = show ? '' : 'none' 223 | } 224 | } 225 | 226 | if (animate) { 227 | Animator.toggle(this.element, this.config.animation, function () { 228 | if (this.visible) { 229 | return 230 | } 231 | this.contentElement.innerHTML = this.contentElement.innerHTML + '' 232 | }.bind(this)) 233 | } else { 234 | this.element.style.display = show ? '' : 'none' 235 | } 236 | 237 | return true 238 | } 239 | 240 | // show the box 241 | Box.prototype.show = function (animate) { 242 | return this.toggle(true, animate) 243 | } 244 | 245 | // hide the box 246 | Box.prototype.hide = function (animate) { 247 | return this.toggle(false, animate) 248 | } 249 | 250 | // calculate trigger height 251 | Box.prototype.calculateTriggerHeight = function () { 252 | let triggerHeight = 0 253 | 254 | if (this.config.trigger) { 255 | if (this.config.trigger.method === 'element') { 256 | const triggerElement = document.body.querySelector(this.config.trigger.value) 257 | 258 | if (triggerElement) { 259 | const offset = triggerElement.getBoundingClientRect() 260 | triggerHeight = offset.top 261 | } 262 | } else if (this.config.trigger.method === 'percentage') { 263 | triggerHeight = (this.config.trigger.value / 100 * getDocumentHeight()) 264 | } 265 | } 266 | 267 | return triggerHeight 268 | } 269 | 270 | Box.prototype.fits = function () { 271 | if (!this.config.screenWidthCondition || !this.config.screenWidthCondition.value) { 272 | return true 273 | } 274 | 275 | switch (this.config.screenWidthCondition.condition) { 276 | case 'larger': 277 | return window.innerWidth > this.config.screenWidthCondition.value 278 | case 'smaller': 279 | return window.innerWidth < this.config.screenWidthCondition.value 280 | } 281 | 282 | // meh.. condition should be "smaller" or "larger", just return true. 283 | return true 284 | } 285 | 286 | Box.prototype.onResize = function () { 287 | this.triggerHeight = this.calculateTriggerHeight() 288 | this.setCustomBoxStyling() 289 | } 290 | 291 | // is this box enabled? 292 | Box.prototype.mayAutoShow = function () { 293 | if (this.dismissed) { 294 | return false 295 | } 296 | 297 | // check if box fits on given minimum screen width 298 | if (!this.fits()) { 299 | return false 300 | } 301 | 302 | // if trigger empty or error in calculating triggerHeight, return false 303 | if (!this.config.trigger) { 304 | return false 305 | } 306 | 307 | // rely on cookie value (show if not set, don't show if set) 308 | return !this.cookieSet 309 | } 310 | 311 | Box.prototype.mayRehide = function () { 312 | return this.config.rehide && this.triggered 313 | } 314 | 315 | Box.prototype.isCookieSet = function () { 316 | // always show on test mode or when no auto-trigger is configured 317 | if (this.config.testMode || !this.config.trigger) { 318 | return false 319 | } 320 | 321 | // if either cookie is null or trigger & dismiss are both falsey, don't bother checking. 322 | if (!this.config.cookie || (!this.config.cookie.triggered && !this.config.cookie.dismissed)) { 323 | return false 324 | } 325 | 326 | return (new RegExp('(?:^|;)\\s{0,}boxzilla_box_' + String(this.id) + '=1\\s{0,}(?:;|$)')).test(document.cookie) 327 | } 328 | 329 | // set cookie that disables automatically showing the box 330 | Box.prototype.setCookie = function (hours) { 331 | const expiryDate = new Date() 332 | expiryDate.setHours(expiryDate.getHours() + hours) 333 | document.cookie = 'boxzilla_box_' + this.id + '=1; expires=' + expiryDate.toUTCString() + '; path=/' 334 | } 335 | 336 | Box.prototype.trigger = function () { 337 | const shown = this.show() 338 | if (!shown) { 339 | return 340 | } 341 | 342 | this.triggered = true 343 | if (this.config.cookie && this.config.cookie.triggered) { 344 | this.setCookie(this.config.cookie.triggered) 345 | } 346 | } 347 | 348 | /** 349 | * Dismisses the box and optionally sets a cookie. 350 | * @param animate 351 | * @returns {boolean} 352 | */ 353 | Box.prototype.dismiss = function (animate) { 354 | // only dismiss box if it's currently open. 355 | if (!this.visible) { 356 | return false 357 | } 358 | 359 | // hide box element 360 | this.hide(animate) 361 | 362 | // set cookie 363 | if (this.config.cookie && this.config.cookie.dismissed) { 364 | this.setCookie(this.config.cookie.dismissed) 365 | } 366 | 367 | this.dismissed = true 368 | this.fireEvent('box.dismiss', [this]) 369 | return true 370 | } 371 | 372 | module.exports = Box 373 | -------------------------------------------------------------------------------- /src/class-loader.php: -------------------------------------------------------------------------------- 1 | plugin = $plugin; 31 | $this->options = $options; 32 | } 33 | 34 | /** 35 | * Initializes the plugin, runs on `wp` hook. 36 | */ 37 | public function init() 38 | { 39 | $this->box_ids_to_load = $this->filter_boxes(); 40 | 41 | // Only add other hooks if necessary 42 | add_action('wp_head', [$this, 'print_preload_js']); 43 | if (count($this->box_ids_to_load) > 0) { 44 | add_action('wp_footer', [ $this, 'print_boxes_content' ], 1); 45 | add_action('wp_enqueue_scripts', [ $this, 'load_assets' ], 90); 46 | } 47 | } 48 | 49 | /** 50 | * Prints the preload API so that the Boxzilla JS API can be used before Boxzilla itself is loaded 51 | * This allows us to defer the Boxzilla script itself. 52 | */ 53 | public function print_preload_js() 54 | { 55 | echo ''; 58 | } 59 | 60 | /** 61 | * Get global rules for all boxes 62 | * 63 | * @return array 64 | */ 65 | protected function get_filter_rules() 66 | { 67 | $rules = get_option('boxzilla_rules', []); 68 | return is_array($rules) ? $rules : []; 69 | } 70 | 71 | 72 | /** 73 | * Match a string against an array of patterns, glob-style. 74 | * 75 | * @param string $string 76 | * @param array $patterns 77 | * @param boolean $contains 78 | * @return boolean 79 | */ 80 | protected function match_patterns($string, array $patterns, $contains = false) 81 | { 82 | $string = strtolower($string); 83 | 84 | foreach ($patterns as $pattern) { 85 | $pattern = rtrim($pattern, '/'); 86 | $pattern = strtolower($pattern); 87 | 88 | if ($contains) { 89 | // contains means we should do a simple occurrence check 90 | // does not support wildcards 91 | $match = strpos($string, $pattern) !== false; 92 | } elseif (function_exists('fnmatch')) { 93 | $match = fnmatch($pattern, $string); 94 | } else { 95 | $match = ( $pattern === $string ); 96 | } 97 | 98 | if ($match) { 99 | return true; 100 | } 101 | } 102 | 103 | return false; 104 | } 105 | 106 | /** 107 | * @return string 108 | */ 109 | protected function get_request_url() 110 | { 111 | return rtrim($_SERVER['REQUEST_URI'], '/'); 112 | } 113 | 114 | /** 115 | * Check if this rule passes (conditional matches expected value) 116 | * 117 | * @param string $condition 118 | * @param string $value 119 | * @param boolean $qualifier 120 | * 121 | * @return bool 122 | */ 123 | protected function match_rule($condition, $value, $qualifier = true) 124 | { 125 | $matched = false; 126 | 127 | // cast value to array & trim whitespace or excess comma's 128 | $values = array_map('trim', explode(',', rtrim(trim($value), ','))); 129 | 130 | switch ($condition) { 131 | case 'everywhere': 132 | $matched = true; 133 | break; 134 | 135 | case 'is_url': 136 | $url = $this->get_request_url(); 137 | $matched = $this->match_patterns($url, $values, $qualifier === 'contains' || $qualifier === 'not_contains'); 138 | break; 139 | 140 | case 'is_referer': 141 | if (! empty($_SERVER['HTTP_REFERER'])) { 142 | $referer = $_SERVER['HTTP_REFERER']; 143 | $matched = $this->match_patterns($referer, $values, $qualifier === 'contains' || $qualifier === 'not_contains'); 144 | } 145 | break; 146 | 147 | case 'is_post_type': 148 | $post_type = (string) get_post_type(); 149 | $matched = in_array($post_type, $values, true); 150 | break; 151 | 152 | case 'is_single': 153 | case 'is_post': 154 | // convert to empty string if array with just empty string in it 155 | $value = ( $values === [ '' ] ) ? '' : $values; 156 | $matched = is_single($value); 157 | break; 158 | 159 | case 'is_post_in_category': 160 | $matched = is_singular() && has_category($values); 161 | break; 162 | 163 | case 'is_page': 164 | $matched = is_page($values); 165 | break; 166 | 167 | case 'is_post_with_tag': 168 | $matched = is_singular() && has_tag($values); 169 | break; 170 | 171 | case 'is_user_logged_in': 172 | $matched = is_user_logged_in(); 173 | break; 174 | } 175 | 176 | /** 177 | * Filters whether a given box rule matches the condition and expected value. 178 | * 179 | * The dynamic portion of the hook, `$condition`, refers to the condition being matched. 180 | * 181 | * @param boolean $matched 182 | * @param array $values 183 | */ 184 | $matched = apply_filters('boxzilla_box_rule_matches_' . $condition, $matched, $values); 185 | 186 | // if qualifier is set to false, we need to reverse this value here. 187 | if (! $qualifier || $qualifier === 'not_contains') { 188 | $matched = ! $matched; 189 | } 190 | 191 | return $matched; 192 | } 193 | 194 | /** 195 | * Checks which boxes should be loaded for this request. 196 | * 197 | * @return array 198 | */ 199 | private function filter_boxes() 200 | { 201 | $box_ids_to_load = []; 202 | $rules = $this->get_filter_rules(); 203 | 204 | foreach ($rules as $box_id => $box_rules) { 205 | $matched = false; 206 | $comparision = isset($box_rules['comparision']) ? $box_rules['comparision'] : 'any'; 207 | 208 | // loop through all rules for all boxes 209 | foreach ($box_rules as $rule) { 210 | // skip faulty values (and comparision rule) 211 | if (empty($rule['condition'])) { 212 | continue; 213 | } 214 | 215 | $qualifier = isset($rule['qualifier']) ? $rule['qualifier'] : true; 216 | $matched = $this->match_rule($rule['condition'], $rule['value'], $qualifier); 217 | 218 | // break out of loop if we've already matched 219 | if ($comparision === 'any' && $matched) { 220 | break; 221 | } 222 | 223 | // no need to continue if this rule didn't match 224 | if ($comparision === 'all' && ! $matched) { 225 | break; 226 | } 227 | } 228 | 229 | // value of $matched at this point determines whether box should be loaded 230 | $load_box = $matched; 231 | 232 | /** 233 | * Filters whether a box should be loaded into the page HTML. 234 | * 235 | * The dynamic portion of the hook, `$box_id`, refers to the ID of the box. Return true if you want to output the box. 236 | * 237 | * @param boolean $load_box 238 | */ 239 | $load_box = apply_filters('boxzilla_load_box_' . $box_id, $load_box); 240 | 241 | /** 242 | * Filters whether a box should be loaded into the page HTML. 243 | * 244 | * @param boolean $load_box 245 | * @param int $box_id 246 | */ 247 | $load_box = apply_filters('boxzilla_load_box', $load_box, $box_id); 248 | 249 | // if matched, box should be loaded on this page 250 | if ($load_box) { 251 | $box_ids_to_load[] = $box_id; 252 | } 253 | } 254 | 255 | /** 256 | * Filters which boxes should be loaded on this page, expects an array of post ID's. 257 | * 258 | * @param array $box_ids_to_load 259 | */ 260 | return apply_filters('boxzilla_load_boxes', $box_ids_to_load); 261 | } 262 | 263 | /** 264 | * Load plugin styles 265 | */ 266 | public function load_assets() 267 | { 268 | wp_enqueue_style('boxzilla', $this->plugin->url('/assets/css/styles.css'), [], $this->plugin->version()); 269 | wp_enqueue_script('boxzilla', $this->plugin->url('/assets/js/script.js'), [], $this->plugin->version(), [ 270 | 'strategy' => 'defer', 271 | 'in_footer' => true, 272 | ]); 273 | 274 | // create boxzilla_Global_Options object 275 | $plugin_options = $this->options; 276 | $boxes = $this->get_matched_boxes(); 277 | 278 | $data = [ 279 | 'testMode' => (bool) $plugin_options['test_mode'], 280 | 'boxes' => (array) array_map( 281 | function (Box $box) { 282 | return $box->get_client_options(); 283 | }, 284 | $boxes 285 | ), 286 | ]; 287 | 288 | wp_localize_script('boxzilla', 'boxzilla_options', $data); 289 | 290 | do_action('boxzilla_load_assets', $this); 291 | } 292 | 293 | public function print_boxes_content() 294 | { 295 | $boxes = $this->get_matched_boxes(); 296 | if (empty($boxes)) { 297 | return; 298 | } 299 | 300 | echo '
'; 301 | foreach ($boxes as $box) { 302 | echo "
ID}-content\">", $box->get_content(), "
"; 303 | } 304 | echo '
'; 305 | } 306 | 307 | /** 308 | * Get an array of Box objects. These are the boxes that will be loaded for the current request. 309 | * 310 | * @return Box[] 311 | */ 312 | public function get_matched_boxes() 313 | { 314 | static $boxes; 315 | 316 | if (is_null($boxes)) { 317 | if (count($this->box_ids_to_load) === 0) { 318 | return []; 319 | } 320 | 321 | // query Box posts 322 | $q = new \WP_Query(); 323 | $posts = $q->query( 324 | [ 325 | 'post_type' => 'boxzilla-box', 326 | 'post_status' => 'publish', 327 | 'post__in' => $this->box_ids_to_load, 328 | 'posts_per_page' => count($this->box_ids_to_load), 329 | 'ignore_sticky_posts' => true, 330 | 'no_found_rows' => true, 331 | ] 332 | ); 333 | 334 | // create `Box` instances out of \WP_Post instances 335 | $boxes = []; 336 | foreach ($posts as $key => $post) { 337 | $box = new Box($post); 338 | 339 | // skip boxes without any content 340 | if ($box->get_content() === '') { 341 | continue; 342 | } 343 | 344 | $boxes[ $key ] = $box; 345 | } 346 | } 347 | 348 | return $boxes; 349 | } 350 | } 351 | -------------------------------------------------------------------------------- /assets/js/script.js: -------------------------------------------------------------------------------- 1 | (()=>{var t={609:t=>{function e(t,e){for(const i of Object.keys(e))t.style[i]=e[i]}function i(t,e,i){let o=+new Date;const n=window.getComputedStyle(t),s={},r={};for(const t of Object.keys(e)){e[t]=parseFloat(e[t]);const i=e[t],o=parseFloat(n[t]);o!==i?(r[t]=(i-o)/320,s[t]=o):delete e[t]}const c=function(){const n=+new Date-o;let l,d,a,h,g=!0;for(const i of Object.keys(e))l=r[i],d=e[i],a=l*n,h=s[i]+a,l>0&&h>=d||l<0&&h<=d?h=d:g=!1,s[i]=h,t.style[i]="opacity"!==i?h+"px":h;o=+new Date,g?i&&i():window.requestAnimationFrame(c)};c()}t.exports={toggle:function(t,o,n){const s="none"!==t.style.display||t.offsetLeft>0,r=t.cloneNode(!0),c=function(){t.removeAttribute("data-animated"),t.setAttribute("style",r.getAttribute("style")),t.style.display=s?"none":"",n&&n()};let l,d;if(t.setAttribute("data-animated","true"),s||(t.style.display=""),"slide"===o){if(l=function(t,e){const i={};for(let e=0;e{const o={animation:"fade",rehide:!1,content:"",cookie:null,icon:"×",screenWidthCondition:null,position:"center",testMode:!1,trigger:!1,closable:!0},n=i(609);function s(t,e,i){this.id=t,this.fireEvent=i,this.config=function(t,e){const i={};for(const e of Object.keys(t))i[e]=t[e];for(const t of Object.keys(e))i[t]=e[t];return i}(o,e),this.overlay=document.createElement("div"),this.overlay.setAttribute("aria-modal",!0),this.overlay.style.display="none",this.overlay.id="boxzilla-overlay-"+this.id,this.overlay.classList.add("boxzilla-overlay"),document.body.appendChild(this.overlay),this.visible=!1,this.dismissed=!1,this.triggered=!1,this.triggerHeight=this.calculateTriggerHeight(),this.cookieSet=this.isCookieSet(),this.element=null,this.contentElement=null,this.closeIcon=null,this.dom(),this.events()}s.prototype.events=function(){const t=this;this.closeIcon&&this.closeIcon.addEventListener("click",(e=>{e.preventDefault(),t.dismiss()})),this.element.addEventListener("click",(e=>{"A"!==e.target.tagName&&"AREA"!==e.target.tagName||t.fireEvent("box.interactions.link",[t,e.target])}),!1),this.element.addEventListener("submit",(e=>{t.setCookie(),t.fireEvent("box.interactions.form",[t,e.target])}),!1),this.overlay.addEventListener("click",(e=>{const i=e.offsetX,o=e.offsetY,n=t.element.getBoundingClientRect();(in.right+40||on.bottom+40)&&t.dismiss()}))},s.prototype.dom=function(){const t=document.createElement("div");t.className="boxzilla-container boxzilla-"+this.config.position+"-container";const e=document.createElement("div");let i;if(e.id="boxzilla-"+this.id,e.className="boxzilla boxzilla-"+this.id+" boxzilla-"+this.config.position,e.style.display="none",t.appendChild(e),"string"==typeof this.config.content?(i=document.createElement("div"),i.innerHTML=this.config.content):(i=this.config.content,i.style.display=""),i.className="boxzilla-content",e.appendChild(i),this.config.closable&&this.config.icon){const t=document.createElement("span");t.className="boxzilla-close-icon",t.innerHTML=this.config.icon,t.setAttribute("aria-label","close"),e.appendChild(t),this.closeIcon=t}document.body.appendChild(t),this.contentElement=i,this.element=e},s.prototype.setCustomBoxStyling=function(){const t=this.element.style.display;this.element.style.display="",this.element.style.overflowY="",this.element.style.maxHeight="";const e=window.innerHeight,i=this.element.clientHeight;if(i>e&&(this.element.style.maxHeight=e+"px",this.element.style.overflowY="scroll"),"center"===this.config.position){let t=(e-i)/2;t=t>=0?t:0,this.element.style.marginTop=t+"px"}this.element.style.display=t},s.prototype.toggle=function(t,e){return e=void 0===e||e,!((t=void 0===t?!this.visible:t)===this.visible||n.animated(this.element)||!t&&!this.config.closable||(this.visible=t,this.setCustomBoxStyling(),this.fireEvent("box."+(t?"show":"hide"),[this]),"center"===this.config.position&&(this.overlay.classList.toggle("boxzilla-"+this.id+"-overlay"),e?n.toggle(this.overlay,"fade"):this.overlay.style.display=t?"":"none"),e?n.toggle(this.element,this.config.animation,function(){this.visible||(this.contentElement.innerHTML=this.contentElement.innerHTML+"")}.bind(this)):this.element.style.display=t?"":"none",0))},s.prototype.show=function(t){return this.toggle(!0,t)},s.prototype.hide=function(t){return this.toggle(!1,t)},s.prototype.calculateTriggerHeight=function(){let t=0;if(this.config.trigger)if("element"===this.config.trigger.method){const e=document.body.querySelector(this.config.trigger.value);e&&(t=e.getBoundingClientRect().top)}else"percentage"===this.config.trigger.method&&(t=this.config.trigger.value/100*function(){const t=document.body,e=document.documentElement;return Math.max(t.scrollHeight,t.offsetHeight,e.clientHeight,e.scrollHeight,e.offsetHeight)}());return t},s.prototype.fits=function(){if(!this.config.screenWidthCondition||!this.config.screenWidthCondition.value)return!0;switch(this.config.screenWidthCondition.condition){case"larger":return window.innerWidth>this.config.screenWidthCondition.value;case"smaller":return window.innerWidth{const o=i(779),n=i(132).throttle,s=i(539),r=i(449),c=i(461),l=i(377);let d=!1;const a=[],h={};function g(t){"Escape"!==t.key&&"Esc"!==t.key||w()}function m(){a.forEach((t=>t.onResize()))}function u(t){let e=t.target;for(let t=0;t<=3&&e&&"A"!==e.tagName&&"AREA"!==e.tagName;t++)e=e.parentElement;if(!e||"A"!==e.tagName&&"AREA"!==e.tagName||!e.href)return;const i=e.href.match(/[#&]boxzilla-(.+)/i);i&&i.length>1&&y(i[1])}function f(t,e){h[t]&&h[t].forEach((t=>t.apply(null,e)))}function p(t){t=String(t);for(let e=0;et.dismiss(e)))}function y(t,e){t?p(t).toggle(e):a.forEach((t=>t.toggle(e)))}const v={off:function(t,e){h[t]&&h[t].filter((t=>t!==e))},on:function(t,e){h[t]=h[t]||[],h[t].push(e)},get:p,init:function(){d||(s(a),c(a),r(a),l(a),document.body.addEventListener("click",u,!0),window.addEventListener("resize",n(m)),window.addEventListener("load",m),document.addEventListener("keyup",g),f("ready"),d=!0)},create:function(t,e){void 0!==e.minimumScreenWidth&&(e.screenWidthCondition={condition:"larger",value:e.minimumScreenWidth}),t=String(t);const i=new o(t,e,f);return a.push(i),i},trigger:f,show:function(t,e){t?p(t).show(e):a.forEach((t=>t.show(e)))},hide:function(t,e){t?p(t).hide(e):a.forEach((t=>t.hide(e)))},dismiss:w,toggle:y,boxes:a};window.Boxzilla=v,t.exports&&(t.exports=v)},855:t=>{const e=function(){this.time=0,this.interval=0};e.prototype.tick=function(){this.time++},e.prototype.start=function(){this.interval||(this.interval=window.setInterval(this.tick.bind(this),1e3))},e.prototype.stop=function(){this.interval&&(window.clearInterval(this.interval),this.interval=0)},t.exports=e},539:t=>{t.exports=function(t){let e=null,i={};function o(){document.documentElement.removeEventListener("mouseleave",r),document.documentElement.removeEventListener("mouseenter",s),document.documentElement.removeEventListener("click",n),window.removeEventListener("touchstart",c),window.removeEventListener("touchend",l),t.forEach((t=>{t.mayAutoShow()&&"exit_intent"===t.config.trigger.method&&t.trigger()}))}function n(){null!==e&&(window.clearTimeout(e),e=null)}function s(){n()}function r(t){n(),t.clientY<=(document.documentMode||/Edge\//.test(navigator.userAgent)?5:0)&&t.clientX<.8*window.innerWidth&&(e=window.setTimeout(o,600))}function c(){n(),i={timestamp:performance.now(),scrollY:window.scrollY,windowHeight:window.innerHeight}}function l(t){n(),window.innerHeight>i.windowHeight||window.scrollY+20>i.scrollY||performance.now()-i.timestamp>300||["A","INPUT","BUTTON"].indexOf(t.target.tagName)>-1||(e=window.setTimeout(o,800))}window.addEventListener("touchstart",c),window.addEventListener("touchend",l),document.documentElement.addEventListener("mouseenter",s),document.documentElement.addEventListener("mouseleave",r),document.documentElement.addEventListener("click",n)}},461:t=>{t.exports=function(t){let e;try{e=sessionStorage.getItem("boxzilla_pageviews")||0,sessionStorage.setItem("boxzilla_pageviews",++e)}catch(t){e=0}window.setTimeout((()=>{t.forEach((t=>{"pageviews"===t.config.trigger.method&&e>t.config.trigger.value&&t.mayAutoShow()&&t.trigger()}))}),1e3)}},449:(t,e,i)=>{const o=i(132).throttle;t.exports=function(t){function e(){let e=window.hasOwnProperty("pageYOffset")?window.pageYOffset:window.scrollTop;e+=.9*window.innerHeight,t.forEach((t=>{!t.mayAutoShow()||t.triggerHeight<=0||(e>t.triggerHeight?t.trigger():t.mayRehide()&&e{const o=i(855);t.exports=function(t){const e=new o,i=new o,n=function(){try{const t=parseInt(sessionStorage.getItem("boxzilla_timer"));t&&(e.time=t)}catch(t){}e.start(),i.start()},s=function(){sessionStorage.setItem("boxzilla_timer",e.time),e.stop(),i.stop()};n(),document.addEventListener("visibilitychange",(function(){document.hidden?s():n()})),window.addEventListener("beforeunload",(function(){s()})),window.setInterval((()=>{t.forEach((t=>{("time_on_site"===t.config.trigger.method&&e.time>t.config.trigger.value&&t.mayAutoShow()||"time_on_page"===t.config.trigger.method&&i.time>t.config.trigger.value&&t.mayAutoShow())&&t.trigger()}))}),1e3)}},132:t=>{t.exports={throttle:function(t,e,i){let o,n;return e||(e=800),function(){const s=i||this,r=+new Date,c=arguments;o&&r-1;n&&e.testMode&&console.log("Boxzilla: Test mode is enabled. Please disable test mode if you're done testing."),t.init(),document.addEventListener("DOMContentLoaded",(()=>{(function(){if(!e.inited){for(var i in e.boxes){var s=e.boxes[i];s.testMode=n&&e.testMode;var r=document.getElementById("boxzilla-box-"+s.id+"-content");if(r){s.content=r;var c=t.create(s.id,s);c.element.className=c.element.className+" boxzilla-"+s.post.slug,l=c.element,(d=s.css).background_color&&(l.style.background=d.background_color),d.color&&(l.style.color=d.color),d.border_color&&(l.style.borderColor=d.border_color),d.border_width&&(l.style.borderWidth=parseInt(d.border_width)+"px"),d.border_style&&(l.style.borderStyle=d.border_style),d.width&&(l.style.maxWidth=parseInt(d.width)+"px");try{c.element.firstChild.firstChild.className+=" first-child",c.element.firstChild.lastChild.className+=" last-child"}catch(t){}c.fits()&&o(c)&&c.show()}}var l,d;e.inited=!0,t.trigger("done"),function(){if(("object"!=typeof window.mc4wp_forms_config||!window.mc4wp_forms_config.submitted_form)&&"object"!=typeof window.mc4wp_submitted_form)return;const e="#"+(window.mc4wp_submitted_form||window.mc4wp_forms_config.submitted_form).element_id;t.boxes.forEach((t=>{t.element.querySelector(e)&&t.show()}))}()}})(),window.boxzilla_queue.forEach((e=>{const[i,o]=e;t[i].apply(null,o)}))}))}()})(); -------------------------------------------------------------------------------- /languages/boxzilla.pot: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2024 ibericode 2 | # This file is distributed under the GPL v2. 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: Boxzilla 3.3.0\n" 6 | "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/boxzilla\n" 7 | "Last-Translator: FULL NAME \n" 8 | "Language-Team: LANGUAGE \n" 9 | "MIME-Version: 1.0\n" 10 | "Content-Type: text/plain; charset=UTF-8\n" 11 | "Content-Transfer-Encoding: 8bit\n" 12 | "POT-Creation-Date: 2024-05-17T19:03:58+00:00\n" 13 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 14 | "X-Generator: WP-CLI 2.10.0\n" 15 | "X-Domain: boxzilla\n" 16 | 17 | #. Plugin Name of the plugin 18 | #: boxzilla.php 19 | #: src/default-actions.php:14 20 | #: src/default-actions.php:26 21 | msgid "Boxzilla" 22 | msgstr "" 23 | 24 | #. Plugin URI of the plugin 25 | #: boxzilla.php 26 | msgid "https://www.boxzillaplugin.com/" 27 | msgstr "" 28 | 29 | #. Description of the plugin 30 | #: boxzilla.php 31 | msgid "Call-To-Action Boxes that display after visitors scroll down far enough. Unobtrusive, but highly conversing!" 32 | msgstr "" 33 | 34 | #. Author of the plugin 35 | #: boxzilla.php 36 | msgid "ibericode" 37 | msgstr "" 38 | 39 | #. Author URI of the plugin 40 | #: boxzilla.php 41 | msgid "https://www.ibericode.com/" 42 | msgstr "" 43 | 44 | #: src/admin/class-admin.php:88 45 | msgid "Duplicate box" 46 | msgstr "" 47 | 48 | #: src/admin/class-admin.php:133 49 | msgid "Awesome, you are using Boxzilla! You can now safely deactivate the Scroll Triggered Boxes plugin." 50 | msgstr "" 51 | 52 | #: src/admin/class-admin.php:202 53 | msgid "Box ID" 54 | msgstr "" 55 | 56 | #: src/admin/class-admin.php:207 57 | msgid "Box Title" 58 | msgstr "" 59 | 60 | #: src/admin/class-admin.php:241 61 | #: src/admin/class-admin.php:242 62 | #: src/admin/views/settings.php:7 63 | msgid "Settings" 64 | msgstr "" 65 | 66 | #: src/admin/class-admin.php:247 67 | #: src/admin/class-admin.php:248 68 | msgid "Extensions" 69 | msgstr "" 70 | 71 | #: src/admin/class-admin.php:339 72 | #: src/admin/views/metaboxes/box-option-controls.php:62 73 | msgid "and" 74 | msgstr "" 75 | 76 | #: src/admin/class-admin.php:340 77 | #: src/admin/views/metaboxes/box-option-controls.php:61 78 | msgid "or" 79 | msgstr "" 80 | 81 | #: src/admin/class-admin.php:341 82 | msgid "Enter a comma-separated list of values." 83 | msgstr "" 84 | 85 | #: src/admin/class-admin.php:342 86 | msgid "Enter a comma-separated list of post slugs or post ID's.." 87 | msgstr "" 88 | 89 | #: src/admin/class-admin.php:343 90 | msgid "Enter a comma-separated list of page slugs or page ID's.." 91 | msgstr "" 92 | 93 | #: src/admin/class-admin.php:344 94 | msgid "Enter a comma-separated list of post types.." 95 | msgstr "" 96 | 97 | #: src/admin/class-admin.php:345 98 | msgid "Enter a comma-separated list of relative URL's, eg /contact/" 99 | msgstr "" 100 | 101 | #: src/admin/class-admin.php:375 102 | msgid "Box Appearance" 103 | msgstr "" 104 | 105 | #: src/admin/class-admin.php:384 106 | msgid "Box Options" 107 | msgstr "" 108 | 109 | #: src/admin/class-admin.php:393 110 | #: src/admin/views/settings.php:40 111 | msgid "Looking for help?" 112 | msgstr "" 113 | 114 | #: src/admin/class-admin.php:401 115 | #: src/admin/views/settings.php:45 116 | msgid "Our other plugins" 117 | msgstr "" 118 | 119 | #: src/admin/class-admin.php:409 120 | #: src/admin/views/settings.php:50 121 | msgid "Subscribe to our newsletter" 122 | msgstr "" 123 | 124 | #: src/admin/class-admin.php:670 125 | msgid "Boxes" 126 | msgstr "" 127 | 128 | #: src/admin/class-menu.php:25 129 | #: src/admin/class-menu.php:92 130 | #: src/admin/class-menu.php:93 131 | msgid "Boxzilla Pop-ups" 132 | msgstr "" 133 | 134 | #: src/admin/class-menu.php:75 135 | msgid "Add to menu" 136 | msgstr "" 137 | 138 | #: src/admin/class-menu.php:126 139 | msgid "Custom Link" 140 | msgstr "" 141 | 142 | #: src/admin/class-review-notice.php:68 143 | msgid "You've been using Boxzilla for some time now; we hope you love it!" 144 | msgstr "" 145 | 146 | #: src/admin/class-review-notice.php:69 147 | msgid "If you do, please leave us a 5★ rating on WordPress.org. It would be of great help to us." 148 | msgstr "" 149 | 150 | #: src/admin/class-review-notice.php:71 151 | msgid "Dismiss this notice." 152 | msgstr "" 153 | 154 | #: src/admin/views/extensions.php:4 155 | msgid "Available Add-On Plugins" 156 | msgstr "" 157 | 158 | #: src/admin/views/extensions.php:6 159 | msgid "There are various add-ons available for Boxzilla which further enhance the functionality of the core plugin." 160 | msgstr "" 161 | 162 | #: src/admin/views/extensions.php:9 163 | msgid "To gain instant access the premium add-on plugins listed here, have a look at our pricing." 164 | msgstr "" 165 | 166 | #: src/admin/views/extensions.php:18 167 | msgid "You will be redirected to the Boxzilla site in a few seconds.." 168 | msgstr "" 169 | 170 | #: src/admin/views/extensions.php:19 171 | msgid "If not, please click here: %s." 172 | msgstr "" 173 | 174 | #: src/admin/views/metaboxes/box-appearance-controls.php:2 175 | msgid "For the best experience when styling your box, please use the default WordPress visual editor." 176 | msgstr "" 177 | 178 | #: src/admin/views/metaboxes/box-appearance-controls.php:8 179 | msgid "Background color" 180 | msgstr "" 181 | 182 | #: src/admin/views/metaboxes/box-appearance-controls.php:12 183 | msgid "Text color" 184 | msgstr "" 185 | 186 | #: src/admin/views/metaboxes/box-appearance-controls.php:16 187 | msgid "Box width" 188 | msgstr "" 189 | 190 | #: src/admin/views/metaboxes/box-appearance-controls.php:18 191 | #: src/admin/views/metaboxes/box-appearance-controls.php:29 192 | msgid "Width in px" 193 | msgstr "" 194 | 195 | #: src/admin/views/metaboxes/box-appearance-controls.php:23 196 | msgid "Border color" 197 | msgstr "" 198 | 199 | #: src/admin/views/metaboxes/box-appearance-controls.php:27 200 | msgid "Border width" 201 | msgstr "" 202 | 203 | #: src/admin/views/metaboxes/box-appearance-controls.php:32 204 | #: src/admin/views/metaboxes/box-appearance-controls.php:40 205 | msgid "Border style" 206 | msgstr "" 207 | 208 | #: src/admin/views/metaboxes/box-appearance-controls.php:34 209 | msgid "Default" 210 | msgstr "" 211 | 212 | #: src/admin/views/metaboxes/box-appearance-controls.php:35 213 | msgid "Solid" 214 | msgstr "" 215 | 216 | #: src/admin/views/metaboxes/box-appearance-controls.php:36 217 | msgid "Dashed" 218 | msgstr "" 219 | 220 | #: src/admin/views/metaboxes/box-appearance-controls.php:37 221 | msgid "Dotted" 222 | msgstr "" 223 | 224 | #: src/admin/views/metaboxes/box-appearance-controls.php:38 225 | msgid "Double" 226 | msgstr "" 227 | 228 | #: src/admin/views/metaboxes/box-appearance-controls.php:46 229 | msgid "Click here to reset all styling settings." 230 | msgstr "" 231 | 232 | #: src/admin/views/metaboxes/box-option-controls.php:11 233 | msgid "everywhere" 234 | msgstr "" 235 | 236 | #: src/admin/views/metaboxes/box-option-controls.php:12 237 | msgid "if page" 238 | msgstr "" 239 | 240 | #: src/admin/views/metaboxes/box-option-controls.php:13 241 | msgid "if post" 242 | msgstr "" 243 | 244 | #: src/admin/views/metaboxes/box-option-controls.php:14 245 | msgid "if post tag" 246 | msgstr "" 247 | 248 | #: src/admin/views/metaboxes/box-option-controls.php:15 249 | msgid "if post category" 250 | msgstr "" 251 | 252 | #: src/admin/views/metaboxes/box-option-controls.php:16 253 | msgid "if post type" 254 | msgstr "" 255 | 256 | #: src/admin/views/metaboxes/box-option-controls.php:17 257 | msgid "if URL" 258 | msgstr "" 259 | 260 | #: src/admin/views/metaboxes/box-option-controls.php:18 261 | msgid "if referer" 262 | msgstr "" 263 | 264 | #: src/admin/views/metaboxes/box-option-controls.php:19 265 | msgid "if user" 266 | msgstr "" 267 | 268 | #: src/admin/views/metaboxes/box-option-controls.php:38 269 | msgid "Load this box if" 270 | msgstr "" 271 | 272 | #: src/admin/views/metaboxes/box-option-controls.php:41 273 | msgid "Request matches" 274 | msgstr "" 275 | 276 | #: src/admin/views/metaboxes/box-option-controls.php:43 277 | msgid "any" 278 | msgstr "" 279 | 280 | #: src/admin/views/metaboxes/box-option-controls.php:44 281 | msgid "all" 282 | msgstr "" 283 | 284 | #: src/admin/views/metaboxes/box-option-controls.php:46 285 | msgid "of the following conditions." 286 | msgstr "" 287 | 288 | #: src/admin/views/metaboxes/box-option-controls.php:73 289 | #: src/admin/views/metaboxes/box-option-controls.php:254 290 | msgid "Remove rule" 291 | msgstr "" 292 | 293 | #: src/admin/views/metaboxes/box-option-controls.php:85 294 | #: src/admin/views/metaboxes/box-option-controls.php:265 295 | msgid "is" 296 | msgstr "" 297 | 298 | #: src/admin/views/metaboxes/box-option-controls.php:86 299 | #: src/admin/views/metaboxes/box-option-controls.php:266 300 | msgid "is not" 301 | msgstr "" 302 | 303 | #: src/admin/views/metaboxes/box-option-controls.php:87 304 | #: src/admin/views/metaboxes/box-option-controls.php:267 305 | msgid "contains" 306 | msgstr "" 307 | 308 | #: src/admin/views/metaboxes/box-option-controls.php:88 309 | #: src/admin/views/metaboxes/box-option-controls.php:268 310 | msgid "does not contain" 311 | msgstr "" 312 | 313 | #: src/admin/views/metaboxes/box-option-controls.php:91 314 | #: src/admin/views/metaboxes/box-option-controls.php:271 315 | msgid "Leave empty for any or enter (comma-separated) names or ID's" 316 | msgstr "" 317 | 318 | #: src/admin/views/metaboxes/box-option-controls.php:101 319 | msgid "Add another rule" 320 | msgstr "" 321 | 322 | #: src/admin/views/metaboxes/box-option-controls.php:104 323 | msgid "Box Position" 324 | msgstr "" 325 | 326 | #: src/admin/views/metaboxes/box-option-controls.php:111 327 | msgid "Top Left" 328 | msgstr "" 329 | 330 | #: src/admin/views/metaboxes/box-option-controls.php:119 331 | msgid "Top Right" 332 | msgstr "" 333 | 334 | #: src/admin/views/metaboxes/box-option-controls.php:129 335 | msgid "Center" 336 | msgstr "" 337 | 338 | #: src/admin/views/metaboxes/box-option-controls.php:139 339 | msgid "Bottom Left" 340 | msgstr "" 341 | 342 | #: src/admin/views/metaboxes/box-option-controls.php:147 343 | msgid "Bottom Right" 344 | msgstr "" 345 | 346 | #: src/admin/views/metaboxes/box-option-controls.php:156 347 | msgid "Animation" 348 | msgstr "" 349 | 350 | #: src/admin/views/metaboxes/box-option-controls.php:158 351 | msgid "Fade In" 352 | msgstr "" 353 | 354 | #: src/admin/views/metaboxes/box-option-controls.php:159 355 | msgid "Slide In" 356 | msgstr "" 357 | 358 | #: src/admin/views/metaboxes/box-option-controls.php:160 359 | msgid "Which animation type should be used to show the box when triggered?" 360 | msgstr "" 361 | 362 | #: src/admin/views/metaboxes/box-option-controls.php:164 363 | msgid "Auto-show box?" 364 | msgstr "" 365 | 366 | #: src/admin/views/metaboxes/box-option-controls.php:166 367 | msgid "Never" 368 | msgstr "" 369 | 370 | #: src/admin/views/metaboxes/box-option-controls.php:167 371 | msgid "Yes, after %s seconds on the page." 372 | msgstr "" 373 | 374 | #: src/admin/views/metaboxes/box-option-controls.php:168 375 | msgid "Yes, when at %s of page height" 376 | msgstr "" 377 | 378 | #: src/admin/views/metaboxes/box-option-controls.php:169 379 | msgid "Yes, when at element %s" 380 | msgstr "" 381 | 382 | #: src/admin/views/metaboxes/box-option-controls.php:169 383 | msgid "Example: #comments" 384 | msgstr "" 385 | 386 | #: src/admin/views/metaboxes/box-option-controls.php:175 387 | msgid "Cookie expiration" 388 | msgstr "" 389 | 390 | #: src/admin/views/metaboxes/box-option-controls.php:178 391 | msgid "Triggered" 392 | msgstr "" 393 | 394 | #: src/admin/views/metaboxes/box-option-controls.php:180 395 | #: src/admin/views/metaboxes/box-option-controls.php:185 396 | msgid "hours" 397 | msgstr "" 398 | 399 | #: src/admin/views/metaboxes/box-option-controls.php:183 400 | msgid "Dismissed" 401 | msgstr "" 402 | 403 | #: src/admin/views/metaboxes/box-option-controls.php:189 404 | msgid "After this box is triggered or dismissed, how many hours should it stay hidden?" 405 | msgstr "" 406 | 407 | #: src/admin/views/metaboxes/box-option-controls.php:193 408 | msgid "Screen width" 409 | msgstr "" 410 | 411 | #: src/admin/views/metaboxes/box-option-controls.php:198 412 | msgid "larger" 413 | msgstr "" 414 | 415 | #: src/admin/views/metaboxes/box-option-controls.php:198 416 | msgid "smaller" 417 | msgstr "" 418 | 419 | #: src/admin/views/metaboxes/box-option-controls.php:200 420 | msgid "Only show on screens %1$s than %2$s." 421 | msgstr "" 422 | 423 | #: src/admin/views/metaboxes/box-option-controls.php:201 424 | msgid "Leave empty if you want to show the box on all screen sizes." 425 | msgstr "" 426 | 427 | #: src/admin/views/metaboxes/box-option-controls.php:207 428 | msgid "Auto-hide?" 429 | msgstr "" 430 | 431 | #: src/admin/views/metaboxes/box-option-controls.php:209 432 | #: src/admin/views/metaboxes/box-option-controls.php:220 433 | #: src/admin/views/metaboxes/box-option-controls.php:231 434 | #: src/admin/views/settings.php:22 435 | msgid "Yes" 436 | msgstr "" 437 | 438 | #: src/admin/views/metaboxes/box-option-controls.php:210 439 | #: src/admin/views/metaboxes/box-option-controls.php:221 440 | #: src/admin/views/metaboxes/box-option-controls.php:232 441 | #: src/admin/views/settings.php:23 442 | msgid "No" 443 | msgstr "" 444 | 445 | #: src/admin/views/metaboxes/box-option-controls.php:211 446 | msgid "Hide box again when visitors scroll back up?" 447 | msgstr "" 448 | 449 | #: src/admin/views/metaboxes/box-option-controls.php:218 450 | msgid "Show close icon?" 451 | msgstr "" 452 | 453 | #: src/admin/views/metaboxes/box-option-controls.php:223 454 | msgid "If you decide to hide the close icon, make sure to offer an alternative way to close the box." 455 | msgstr "" 456 | 457 | #: src/admin/views/metaboxes/box-option-controls.php:224 458 | msgid "Example: " 459 | msgstr "" 460 | 461 | #: src/admin/views/metaboxes/box-option-controls.php:229 462 | #: src/admin/views/settings.php:20 463 | msgid "Enable test mode?" 464 | msgstr "" 465 | 466 | #: src/admin/views/metaboxes/box-option-controls.php:233 467 | #: src/admin/views/settings.php:24 468 | msgid "If test mode is enabled, all boxes will show up regardless of whether a cookie has been set." 469 | msgstr "" 470 | 471 | #: src/admin/views/metaboxes/need-help.php:2 472 | msgid "Please make sure to look at the following available resources." 473 | msgstr "" 474 | 475 | #: src/default-actions.php:15 476 | msgid "Box" 477 | msgstr "" 478 | 479 | #: src/default-actions.php:16 480 | msgid "Add New" 481 | msgstr "" 482 | 483 | #: src/default-actions.php:17 484 | msgid "Add New Box" 485 | msgstr "" 486 | 487 | #: src/default-actions.php:18 488 | msgid "Edit Box" 489 | msgstr "" 490 | 491 | #: src/default-actions.php:19 492 | msgid "New Box" 493 | msgstr "" 494 | 495 | #: src/default-actions.php:20 496 | msgid "All Boxes" 497 | msgstr "" 498 | 499 | #: src/default-actions.php:21 500 | msgid "View Box" 501 | msgstr "" 502 | 503 | #: src/default-actions.php:22 504 | msgid "Search Boxes" 505 | msgstr "" 506 | 507 | #: src/default-actions.php:23 508 | msgid "No Boxes found" 509 | msgstr "" 510 | 511 | #: src/default-actions.php:24 512 | msgid "No Boxes found in Trash" 513 | msgstr "" 514 | 515 | #: src/licensing/class-api.php:164 516 | msgid "The Boxzilla server returned an invalid response." 517 | msgstr "" 518 | 519 | #: src/licensing/views/license-form.php:8 520 | msgid "License & Plugin Updates" 521 | msgstr "" 522 | 523 | #: src/licensing/views/license-form.php:15 524 | msgid "Warning! You are not receiving plugin updates for the following plugin(s):" 525 | msgstr "" 526 | 527 | #: src/licensing/views/license-form.php:25 528 | msgid "To fix this, please activate your license using the form below." 529 | msgstr "" 530 | 531 | #: src/licensing/views/license-form.php:45 532 | msgid "License Key" 533 | msgstr "" 534 | 535 | #: src/licensing/views/license-form.php:50 536 | msgid "Enter your license key.." 537 | msgstr "" 538 | 539 | #: src/licensing/views/license-form.php:56 540 | msgid "The license key received when purchasing your premium Boxzilla plan. You can find it here." 541 | msgstr "" 542 | 543 | #: src/licensing/views/license-form.php:61 544 | msgid "License Status" 545 | msgstr "" 546 | 547 | #: src/licensing/views/license-form.php:66 548 | msgid "ACTIVE" 549 | msgstr "" 550 | 551 | #: src/licensing/views/license-form.php:66 552 | msgid "you are receiving plugin updates" 553 | msgstr "" 554 | 555 | #: src/licensing/views/license-form.php:70 556 | msgid "INACTIVE" 557 | msgstr "" 558 | 559 | #: src/licensing/views/license-form.php:70 560 | msgid "you are not receiving plugin updates" 561 | msgstr "" 562 | 563 | #: src/licensing/views/license-form.php:82 564 | msgid "Save Changes" 565 | msgstr "" 566 | -------------------------------------------------------------------------------- /languages/boxzilla-wp.pot: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2021 ibericode 2 | # This file is distributed under the same license as the Boxzilla plugin. 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: Boxzilla 3.2.25p\n" 6 | "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/boxzilla-wp\n" 7 | "Last-Translator: FULL NAME \n" 8 | "Language-Team: LANGUAGE \n" 9 | "MIME-Version: 1.0\n" 10 | "Content-Type: text/plain; charset=UTF-8\n" 11 | "Content-Transfer-Encoding: 8bit\n" 12 | "POT-Creation-Date: 2021-04-20T09:38:41+00:00\n" 13 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 14 | "X-Generator: WP-CLI 2.4.0\n" 15 | "X-Domain: boxzilla\n" 16 | 17 | #. Plugin Name of the plugin 18 | #: src/default-actions.php:14 19 | #: src/default-actions.php:26 20 | msgid "Boxzilla" 21 | msgstr "" 22 | 23 | #. Plugin URI of the plugin 24 | msgid "https://boxzillaplugin.com/#utm_source=wp-plugin&utm_medium=boxzilla&utm_campaign=plugins-page" 25 | msgstr "" 26 | 27 | #. Description of the plugin 28 | msgid "Call-To-Action Boxes that display after visitors scroll down far enough. Unobtrusive, but highly conversing!" 29 | msgstr "" 30 | 31 | #. Author of the plugin 32 | msgid "ibericode" 33 | msgstr "" 34 | 35 | #. Author URI of the plugin 36 | msgid "https://ibericode.com/#utm_source=wp-plugin&utm_medium=boxzilla&utm_campaign=plugins-page" 37 | msgstr "" 38 | 39 | #: src/admin/class-admin.php:88 40 | msgid "Duplicate box" 41 | msgstr "" 42 | 43 | #: src/admin/class-admin.php:133 44 | msgid "Awesome, you are using Boxzilla! You can now safely deactivate the Scroll Triggered Boxes plugin." 45 | msgstr "" 46 | 47 | #: src/admin/class-admin.php:202 48 | msgid "Box ID" 49 | msgstr "" 50 | 51 | #: src/admin/class-admin.php:207 52 | msgid "Box Title" 53 | msgstr "" 54 | 55 | #: src/admin/class-admin.php:246 56 | #: src/admin/class-admin.php:247 57 | #: src/admin/views/settings.php:7 58 | msgid "Settings" 59 | msgstr "" 60 | 61 | #: src/admin/class-admin.php:252 62 | #: src/admin/class-admin.php:253 63 | msgid "Extensions" 64 | msgstr "" 65 | 66 | #: src/admin/class-admin.php:344 67 | #: src/admin/views/metaboxes/box-option-controls.php:62 68 | msgid "and" 69 | msgstr "" 70 | 71 | #: src/admin/class-admin.php:345 72 | #: src/admin/views/metaboxes/box-option-controls.php:61 73 | msgid "or" 74 | msgstr "" 75 | 76 | #: src/admin/class-admin.php:346 77 | msgid "Enter a comma-separated list of values." 78 | msgstr "" 79 | 80 | #: src/admin/class-admin.php:347 81 | msgid "Enter a comma-separated list of post slugs or post ID's.." 82 | msgstr "" 83 | 84 | #: src/admin/class-admin.php:348 85 | msgid "Enter a comma-separated list of page slugs or page ID's.." 86 | msgstr "" 87 | 88 | #: src/admin/class-admin.php:349 89 | msgid "Enter a comma-separated list of post types.." 90 | msgstr "" 91 | 92 | #: src/admin/class-admin.php:350 93 | msgid "Enter a comma-separated list of relative URL's, eg /contact/" 94 | msgstr "" 95 | 96 | #: src/admin/class-admin.php:380 97 | msgid "Box Appearance" 98 | msgstr "" 99 | 100 | #: src/admin/class-admin.php:389 101 | msgid "Box Options" 102 | msgstr "" 103 | 104 | #: src/admin/class-admin.php:398 105 | #: src/admin/views/settings.php:40 106 | msgid "Looking for help?" 107 | msgstr "" 108 | 109 | #: src/admin/class-admin.php:406 110 | #: src/admin/views/settings.php:45 111 | msgid "Our other plugins" 112 | msgstr "" 113 | 114 | #: src/admin/class-admin.php:414 115 | #: src/admin/views/settings.php:50 116 | msgid "Subscribe to our newsletter" 117 | msgstr "" 118 | 119 | #: src/admin/class-admin.php:675 120 | msgid "Boxes" 121 | msgstr "" 122 | 123 | #: src/admin/class-menu.php:25 124 | #: src/admin/class-menu.php:92 125 | #: src/admin/class-menu.php:93 126 | msgid "Boxzilla Pop-ups" 127 | msgstr "" 128 | 129 | #: src/admin/class-menu.php:75 130 | msgid "Add to menu" 131 | msgstr "" 132 | 133 | #: src/admin/class-menu.php:126 134 | msgid "Custom Link" 135 | msgstr "" 136 | 137 | #: src/admin/class-review-notice.php:68 138 | msgid "You've been using Boxzilla for some time now; we hope you love it!" 139 | msgstr "" 140 | 141 | #: src/admin/class-review-notice.php:69 142 | msgid "If you do, please leave us a 5★ rating on WordPress.org. It would be of great help to us." 143 | msgstr "" 144 | 145 | #: src/admin/class-review-notice.php:71 146 | msgid "Dismiss this notice." 147 | msgstr "" 148 | 149 | #: src/admin/views/extensions.php:4 150 | msgid "Available Add-On Plugins" 151 | msgstr "" 152 | 153 | #: src/admin/views/extensions.php:6 154 | msgid "There are various add-ons available for Boxzilla which further enhance the functionality of the core plugin." 155 | msgstr "" 156 | 157 | #: src/admin/views/extensions.php:9 158 | msgid "To gain instant access the premium add-on plugins listed here, have a look at our pricing." 159 | msgstr "" 160 | 161 | #: src/admin/views/extensions.php:18 162 | msgid "You will be redirected to the Boxzilla site in a few seconds.." 163 | msgstr "" 164 | 165 | #: src/admin/views/extensions.php:19 166 | msgid "If not, please click here: %s." 167 | msgstr "" 168 | 169 | #: src/admin/views/metaboxes/box-appearance-controls.php:2 170 | msgid "For the best experience when styling your box, please use the default WordPress visual editor." 171 | msgstr "" 172 | 173 | #: src/admin/views/metaboxes/box-appearance-controls.php:8 174 | msgid "Background color" 175 | msgstr "" 176 | 177 | #: src/admin/views/metaboxes/box-appearance-controls.php:12 178 | msgid "Text color" 179 | msgstr "" 180 | 181 | #: src/admin/views/metaboxes/box-appearance-controls.php:16 182 | msgid "Box width" 183 | msgstr "" 184 | 185 | #: src/admin/views/metaboxes/box-appearance-controls.php:18 186 | #: src/admin/views/metaboxes/box-appearance-controls.php:29 187 | msgid "Width in px" 188 | msgstr "" 189 | 190 | #: src/admin/views/metaboxes/box-appearance-controls.php:23 191 | msgid "Border color" 192 | msgstr "" 193 | 194 | #: src/admin/views/metaboxes/box-appearance-controls.php:27 195 | msgid "Border width" 196 | msgstr "" 197 | 198 | #: src/admin/views/metaboxes/box-appearance-controls.php:32 199 | #: src/admin/views/metaboxes/box-appearance-controls.php:40 200 | msgid "Border style" 201 | msgstr "" 202 | 203 | #: src/admin/views/metaboxes/box-appearance-controls.php:34 204 | msgid "Default" 205 | msgstr "" 206 | 207 | #: src/admin/views/metaboxes/box-appearance-controls.php:35 208 | msgid "Solid" 209 | msgstr "" 210 | 211 | #: src/admin/views/metaboxes/box-appearance-controls.php:36 212 | msgid "Dashed" 213 | msgstr "" 214 | 215 | #: src/admin/views/metaboxes/box-appearance-controls.php:37 216 | msgid "Dotted" 217 | msgstr "" 218 | 219 | #: src/admin/views/metaboxes/box-appearance-controls.php:38 220 | msgid "Double" 221 | msgstr "" 222 | 223 | #: src/admin/views/metaboxes/box-appearance-controls.php:46 224 | msgid "Click here to reset all styling settings." 225 | msgstr "" 226 | 227 | #: src/admin/views/metaboxes/box-option-controls.php:11 228 | msgid "everywhere" 229 | msgstr "" 230 | 231 | #: src/admin/views/metaboxes/box-option-controls.php:12 232 | msgid "if page" 233 | msgstr "" 234 | 235 | #: src/admin/views/metaboxes/box-option-controls.php:13 236 | msgid "if post" 237 | msgstr "" 238 | 239 | #: src/admin/views/metaboxes/box-option-controls.php:14 240 | msgid "if post tag" 241 | msgstr "" 242 | 243 | #: src/admin/views/metaboxes/box-option-controls.php:15 244 | msgid "if post category" 245 | msgstr "" 246 | 247 | #: src/admin/views/metaboxes/box-option-controls.php:16 248 | msgid "if post type" 249 | msgstr "" 250 | 251 | #: src/admin/views/metaboxes/box-option-controls.php:17 252 | msgid "if URL" 253 | msgstr "" 254 | 255 | #: src/admin/views/metaboxes/box-option-controls.php:18 256 | msgid "if referer" 257 | msgstr "" 258 | 259 | #: src/admin/views/metaboxes/box-option-controls.php:19 260 | msgid "if user" 261 | msgstr "" 262 | 263 | #: src/admin/views/metaboxes/box-option-controls.php:38 264 | msgid "Load this box if" 265 | msgstr "" 266 | 267 | #: src/admin/views/metaboxes/box-option-controls.php:41 268 | msgid "Request matches" 269 | msgstr "" 270 | 271 | #: src/admin/views/metaboxes/box-option-controls.php:43 272 | msgid "any" 273 | msgstr "" 274 | 275 | #: src/admin/views/metaboxes/box-option-controls.php:44 276 | msgid "all" 277 | msgstr "" 278 | 279 | #: src/admin/views/metaboxes/box-option-controls.php:46 280 | msgid "of the following conditions." 281 | msgstr "" 282 | 283 | #: src/admin/views/metaboxes/box-option-controls.php:73 284 | #: src/admin/views/metaboxes/box-option-controls.php:254 285 | msgid "Remove rule" 286 | msgstr "" 287 | 288 | #: src/admin/views/metaboxes/box-option-controls.php:85 289 | #: src/admin/views/metaboxes/box-option-controls.php:265 290 | msgid "is" 291 | msgstr "" 292 | 293 | #: src/admin/views/metaboxes/box-option-controls.php:86 294 | #: src/admin/views/metaboxes/box-option-controls.php:266 295 | msgid "is not" 296 | msgstr "" 297 | 298 | #: src/admin/views/metaboxes/box-option-controls.php:87 299 | #: src/admin/views/metaboxes/box-option-controls.php:267 300 | msgid "contains" 301 | msgstr "" 302 | 303 | #: src/admin/views/metaboxes/box-option-controls.php:88 304 | #: src/admin/views/metaboxes/box-option-controls.php:268 305 | msgid "does not contain" 306 | msgstr "" 307 | 308 | #: src/admin/views/metaboxes/box-option-controls.php:91 309 | #: src/admin/views/metaboxes/box-option-controls.php:271 310 | msgid "Leave empty for any or enter (comma-separated) names or ID's" 311 | msgstr "" 312 | 313 | #: src/admin/views/metaboxes/box-option-controls.php:101 314 | msgid "Add another rule" 315 | msgstr "" 316 | 317 | #: src/admin/views/metaboxes/box-option-controls.php:104 318 | msgid "Box Position" 319 | msgstr "" 320 | 321 | #: src/admin/views/metaboxes/box-option-controls.php:111 322 | msgid "Top Left" 323 | msgstr "" 324 | 325 | #: src/admin/views/metaboxes/box-option-controls.php:119 326 | msgid "Top Right" 327 | msgstr "" 328 | 329 | #: src/admin/views/metaboxes/box-option-controls.php:129 330 | msgid "Center" 331 | msgstr "" 332 | 333 | #: src/admin/views/metaboxes/box-option-controls.php:139 334 | msgid "Bottom Left" 335 | msgstr "" 336 | 337 | #: src/admin/views/metaboxes/box-option-controls.php:147 338 | msgid "Bottom Right" 339 | msgstr "" 340 | 341 | #: src/admin/views/metaboxes/box-option-controls.php:156 342 | msgid "Animation" 343 | msgstr "" 344 | 345 | #: src/admin/views/metaboxes/box-option-controls.php:158 346 | msgid "Fade In" 347 | msgstr "" 348 | 349 | #: src/admin/views/metaboxes/box-option-controls.php:159 350 | msgid "Slide In" 351 | msgstr "" 352 | 353 | #: src/admin/views/metaboxes/box-option-controls.php:160 354 | msgid "Which animation type should be used to show the box when triggered?" 355 | msgstr "" 356 | 357 | #: src/admin/views/metaboxes/box-option-controls.php:164 358 | msgid "Auto-show box?" 359 | msgstr "" 360 | 361 | #: src/admin/views/metaboxes/box-option-controls.php:166 362 | msgid "Never" 363 | msgstr "" 364 | 365 | #: src/admin/views/metaboxes/box-option-controls.php:167 366 | msgid "Yes, after %s seconds on the page." 367 | msgstr "" 368 | 369 | #: src/admin/views/metaboxes/box-option-controls.php:168 370 | msgid "Yes, when at %s of page height" 371 | msgstr "" 372 | 373 | #: src/admin/views/metaboxes/box-option-controls.php:169 374 | msgid "Yes, when at element %s" 375 | msgstr "" 376 | 377 | #: src/admin/views/metaboxes/box-option-controls.php:169 378 | msgid "Example: #comments" 379 | msgstr "" 380 | 381 | #: src/admin/views/metaboxes/box-option-controls.php:175 382 | msgid "Cookie expiration" 383 | msgstr "" 384 | 385 | #: src/admin/views/metaboxes/box-option-controls.php:178 386 | msgid "Triggered" 387 | msgstr "" 388 | 389 | #: src/admin/views/metaboxes/box-option-controls.php:180 390 | #: src/admin/views/metaboxes/box-option-controls.php:185 391 | msgid "hours" 392 | msgstr "" 393 | 394 | #: src/admin/views/metaboxes/box-option-controls.php:183 395 | msgid "Dismissed" 396 | msgstr "" 397 | 398 | #: src/admin/views/metaboxes/box-option-controls.php:189 399 | msgid "After this box is triggered or dismissed, how many hours should it stay hidden?" 400 | msgstr "" 401 | 402 | #: src/admin/views/metaboxes/box-option-controls.php:193 403 | msgid "Screen width" 404 | msgstr "" 405 | 406 | #: src/admin/views/metaboxes/box-option-controls.php:198 407 | msgid "larger" 408 | msgstr "" 409 | 410 | #: src/admin/views/metaboxes/box-option-controls.php:198 411 | msgid "smaller" 412 | msgstr "" 413 | 414 | #: src/admin/views/metaboxes/box-option-controls.php:200 415 | msgid "Only show on screens %1$s than %2$s." 416 | msgstr "" 417 | 418 | #: src/admin/views/metaboxes/box-option-controls.php:201 419 | msgid "Leave empty if you want to show the box on all screen sizes." 420 | msgstr "" 421 | 422 | #: src/admin/views/metaboxes/box-option-controls.php:207 423 | msgid "Auto-hide?" 424 | msgstr "" 425 | 426 | #: src/admin/views/metaboxes/box-option-controls.php:209 427 | #: src/admin/views/metaboxes/box-option-controls.php:220 428 | #: src/admin/views/metaboxes/box-option-controls.php:231 429 | #: src/admin/views/settings.php:22 430 | msgid "Yes" 431 | msgstr "" 432 | 433 | #: src/admin/views/metaboxes/box-option-controls.php:210 434 | #: src/admin/views/metaboxes/box-option-controls.php:221 435 | #: src/admin/views/metaboxes/box-option-controls.php:232 436 | #: src/admin/views/settings.php:23 437 | msgid "No" 438 | msgstr "" 439 | 440 | #: src/admin/views/metaboxes/box-option-controls.php:211 441 | msgid "Hide box again when visitors scroll back up?" 442 | msgstr "" 443 | 444 | #: src/admin/views/metaboxes/box-option-controls.php:218 445 | msgid "Show close icon?" 446 | msgstr "" 447 | 448 | #: src/admin/views/metaboxes/box-option-controls.php:223 449 | msgid "If you decide to hide the close icon, make sure to offer an alternative way to close the box." 450 | msgstr "" 451 | 452 | #: src/admin/views/metaboxes/box-option-controls.php:224 453 | msgid "Example: " 454 | msgstr "" 455 | 456 | #: src/admin/views/metaboxes/box-option-controls.php:229 457 | #: src/admin/views/settings.php:20 458 | msgid "Enable test mode?" 459 | msgstr "" 460 | 461 | #: src/admin/views/metaboxes/box-option-controls.php:233 462 | #: src/admin/views/settings.php:24 463 | msgid "If test mode is enabled, all boxes will show up regardless of whether a cookie has been set." 464 | msgstr "" 465 | 466 | #: src/admin/views/metaboxes/need-help.php:2 467 | msgid "Please make sure to look at the following available resources." 468 | msgstr "" 469 | 470 | #: src/default-actions.php:15 471 | msgid "Box" 472 | msgstr "" 473 | 474 | #: src/default-actions.php:16 475 | msgid "Add New" 476 | msgstr "" 477 | 478 | #: src/default-actions.php:17 479 | msgid "Add New Box" 480 | msgstr "" 481 | 482 | #: src/default-actions.php:18 483 | msgid "Edit Box" 484 | msgstr "" 485 | 486 | #: src/default-actions.php:19 487 | msgid "New Box" 488 | msgstr "" 489 | 490 | #: src/default-actions.php:20 491 | msgid "All Boxes" 492 | msgstr "" 493 | 494 | #: src/default-actions.php:21 495 | msgid "View Box" 496 | msgstr "" 497 | 498 | #: src/default-actions.php:22 499 | msgid "Search Boxes" 500 | msgstr "" 501 | 502 | #: src/default-actions.php:23 503 | msgid "No Boxes found" 504 | msgstr "" 505 | 506 | #: src/default-actions.php:24 507 | msgid "No Boxes found in Trash" 508 | msgstr "" 509 | 510 | #: src/licensing/class-api.php:164 511 | msgid "The Boxzilla server returned an invalid response." 512 | msgstr "" 513 | 514 | #: src/licensing/views/license-form.php:8 515 | msgid "License & Plugin Updates" 516 | msgstr "" 517 | 518 | #: src/licensing/views/license-form.php:15 519 | msgid "Warning! You are not receiving plugin updates for the following plugin(s):" 520 | msgstr "" 521 | 522 | #: src/licensing/views/license-form.php:25 523 | msgid "To fix this, please activate your license using the form below." 524 | msgstr "" 525 | 526 | #: src/licensing/views/license-form.php:45 527 | msgid "License Key" 528 | msgstr "" 529 | 530 | #: src/licensing/views/license-form.php:47 531 | msgid "Enter your license key.." 532 | msgstr "" 533 | 534 | #: src/licensing/views/license-form.php:56 535 | msgid "The license key received when purchasing your premium Boxzilla plan. You can find it here." 536 | msgstr "" 537 | 538 | #: src/licensing/views/license-form.php:61 539 | msgid "License Status" 540 | msgstr "" 541 | 542 | #: src/licensing/views/license-form.php:66 543 | msgid "ACTIVE" 544 | msgstr "" 545 | 546 | #: src/licensing/views/license-form.php:66 547 | msgid "you are receiving plugin updates" 548 | msgstr "" 549 | 550 | #: src/licensing/views/license-form.php:70 551 | msgid "INACTIVE" 552 | msgstr "" 553 | 554 | #: src/licensing/views/license-form.php:70 555 | msgid "you are not receiving plugin updates" 556 | msgstr "" 557 | 558 | #: src/licensing/views/license-form.php:82 559 | msgid "Save Changes" 560 | msgstr "" 561 | --------------------------------------------------------------------------------