├── 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 |
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 "";
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 |
33 |
34 |
35 |
36 |
37 |
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 |
20 |
21 |
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 '';
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 |
38 |
41 |
42 |
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 |
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 |
75 |
76 |
77 |
78 |
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('', '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 |
--------------------------------------------------------------------------------