PluginProbe
FireBox – WooCommerce Popup Builder, Exit Intent Popup, Email Optin & Cart Abandonment / 3.1.12
FireBox – WooCommerce Popup Builder, Exit Intent Popup, Email Optin & Cart Abandonment v3.1.12
3.1.13 3.1.12 3.1.11 3.1.10 3.1.9 3.1.8 3.1.7 trunk 1.0.0 1.0.1 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 All 122 releases
firebox / Inc / Core / Migrator.php

Migrator.php in FireBox – WooCommerce Popup Builder, Exit Intent Popup, Email Optin & Cart Abandonment 3.1.12, at Inc/Core/Migrator.php

748 lines 17.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @package FireBox
4 * @version 3.1.12 Free
5 *
6 * @author FirePlugins <info@fireplugins.com>
7 * @link https://www.fireplugins.com
8 * @copyright Copyright © 2026 FirePlugins All Rights Reserved
9 * @license GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
10 */
11
12 namespace FireBox\Core;
13
14 if (!defined('ABSPATH'))
15 {
16 exit; // Exit if accessed directly.
17 }
18
19 use FireBox\Core\Helpers\Form\Form;
20
21 class Migrator
22 {
23 /**
24 * The currently installed version.
25 *
26 * @var string
27 */
28 private $installedVersion;
29
30 /**
31 * Whether we run a migration.
32 *
33 * @var bool
34 */
35 private $run = false;
36
37 public function __construct($installedVersion = '')
38 {
39 $this->installedVersion = $installedVersion;
40 }
41
42 public function run()
43 {
44 if (!$this->installedVersion)
45 {
46 return;
47 }
48
49 $this->moveFireBoxDataToUploadsDirectory();
50 $this->installFormsTables();
51 $this->improveIndexes();
52 $this->updatePublishingRulesToConditionBuilder();
53 $this->updateEcommerceProductsInCartConditions();
54 $this->updateAnalyticsStoragePeriodSetting();
55 $this->update213version();
56 $this->addMissingCapabilities();
57
58
59 $this->addNewIndexes();
60 $this->addSubmissionsIndexes();
61 $this->preserveCampaignRoleAccess();
62 $this->grantExecutePHPCapability();
63 $this->grantExecuteJSCapability();
64
65 // Update firebox version
66 if ($this->run)
67 {
68 // Update version
69 update_option('firebox_version', FBOX_VERSION);
70 }
71 }
72
73 /**
74 * Moves plugin data to its own /wp-content/uploads/firebox folder.
75 *
76 * @since 1.1.10
77 *
78 * @return void
79 */
80 private function moveFireBoxDataToUploadsDirectory()
81 {
82 if (version_compare($this->installedVersion, '1.1.10', '>='))
83 {
84 return;
85 }
86
87 // Create dirs
88 \FireBox\Core\Helpers\Activation::createLibraryDirectories();
89
90 $this->run = true;
91 }
92
93 /**
94 * Creates the forms tables.
95 *
96 * @since 1.2.0
97 *
98 * @return void
99 */
100 private function installFormsTables()
101 {
102 if (version_compare($this->installedVersion, '1.2.0', '>='))
103 {
104 return;
105 }
106
107 if (!class_exists('Activation'))
108 {
109 require_once FBOX_PLUGIN_DIR . '/Inc/Framework/Inc/Admin/Includes/Activation.php';
110 }
111 $activation = new \Activation(firebox()->hook_data);
112 $activation->initTables();
113
114 $this->run = true;
115 }
116
117 /**
118 * Improves indexes on form and box logs tables.
119 *
120 * @since 1.2.4
121 *
122 * @return void
123 */
124 private function improveIndexes()
125 {
126 if (version_compare($this->installedVersion, '1.2.4', '>='))
127 {
128 return;
129 }
130
131 if (!$this->hasIndex('idx_box_id', 'firebox_logs'))
132 {
133 global $wpdb;
134 $wpdb->query("ALTER TABLE {$wpdb->prefix}firebox_logs ADD INDEX `idx_box_id` (`box`, `id`)");
135 }
136
137 if (!$this->hasIndex('idx_meta_key', 'firebox_submission_meta'))
138 {
139 global $wpdb;
140 $wpdb->query("ALTER TABLE {$wpdb->prefix}firebox_submission_meta ADD INDEX `idx_meta_key` (`meta_key`)");
141 }
142
143 $this->run = true;
144 }
145
146 /**
147 * Update old Publishing Rules to Condition Builder.
148 *
149 * @since 2.0.0
150 *
151 * @return void
152 */
153 private function updatePublishingRulesToConditionBuilder()
154 {
155 if (version_compare($this->installedVersion, '2.0.0', '>='))
156 {
157 return;
158 }
159
160 // Get all boxes, regardless of post status, thus we pass [] to ensure all popups are returned.
161 $boxes = \FireBox\Core\Helpers\BoxHelper::getAllBoxes([]);
162
163 if (!$boxes->posts)
164 {
165 $this->run = true;
166 return;
167 }
168
169 foreach ($boxes->posts as $box)
170 {
171 // Get post meta and add it to its object
172 $meta = \FireBox\Core\Helpers\BoxHelper::getMeta($box->ID);
173
174 // Skip popup that already has rules
175 if (isset($meta['rules']))
176 {
177 continue;
178 }
179
180 $box->params = new \FPFramework\Libs\Registry($meta);
181
182 // Pass only params
183 \FPFramework\Base\Conditions\Migrator::run($box->params);
184
185 // Update popup settings
186 update_post_meta($box->ID, 'fpframework_meta_settings', wp_slash(json_decode(wp_json_encode($box->params), true)));
187 }
188
189 $this->run = true;
190 }
191
192 /**
193 * Updates the following conditions to match the new condition settings.
194 *
195 * EDD\CartContainsProducts
196 * WooCommerce\CartContainsProducts
197 *
198 * @since 2.0.10
199 *
200 * @return void
201 */
202 private function updateEcommerceProductsInCartConditions()
203 {
204 if (version_compare($this->installedVersion, '2.0.10', '>='))
205 {
206 return;
207 }
208
209 // Get all boxes, regardless of post status, thus we pass [] to ensure all popups are returned.
210 $boxes = \FireBox\Core\Helpers\BoxHelper::getAllBoxes([]);
211
212 if (!$boxes->posts)
213 {
214 $this->run = true;
215 return;
216 }
217
218 foreach ($boxes->posts as $box)
219 {
220 $meta = \FireBox\Core\Helpers\BoxHelper::getMeta($box->ID);
221
222 if (!isset($meta['rules']) || !is_array($meta['rules']))
223 {
224 continue;
225 }
226
227 $allowed_rules = [
228 'EDD\CartContainsProducts',
229 'WooCommerce\CartContainsProducts'
230 ];
231
232 $changed = false;
233
234 foreach ($meta['rules'] as &$ruleset)
235 {
236 if (!isset($ruleset['rules']))
237 {
238 continue;
239 }
240
241 foreach ($ruleset['rules'] as &$rule)
242 {
243 if (!isset($rule['name']))
244 {
245 continue;
246 }
247
248 if (!in_array($rule['name'], $allowed_rules))
249 {
250 continue;
251 }
252
253 if (!isset($rule['value']))
254 {
255 continue;
256 }
257
258 if (!is_array($rule['value']))
259 {
260 continue;
261 }
262
263 if (!count($rule['value']))
264 {
265 continue;
266 }
267
268 $changed = true;
269
270 $rule['value'] = array_map(function($id) {
271 // Old value wasn't an array, skip if new value was found
272 if (is_array($id))
273 {
274 return $id;
275 }
276
277 return [
278 'value' => $id,
279 'quantity_operator' => 'any',
280 'quantity_value1' => '1',
281 'quantity_value2' => '1'
282 ];
283 }, $rule['value']);
284 }
285 }
286
287 if (!$changed)
288 {
289 continue;
290 }
291
292 update_post_meta($box->ID, 'fpframework_meta_settings', wp_slash(json_decode(wp_json_encode($meta), true)));
293 }
294
295 $this->run = true;
296 }
297
298 /**
299 * Updates the Analytics Storage Period setting.
300 *
301 * @since 2.1.1
302 *
303 * @return void
304 */
305 public function updateAnalyticsStoragePeriodSetting()
306 {
307 if (version_compare($this->installedVersion, '2.1.1', '>='))
308 {
309 return;
310 }
311
312 $params = get_option('firebox_settings', []);
313
314 $statsdays = isset($params['statsdays']) ? $params['statsdays'] : 730;
315 $statsdays_custom = isset($params['statsdays_custom']) ? $params['statsdays_custom'] : false;
316
317 if ($statsdays === 'custom')
318 {
319 $statsdays = $statsdays_custom;
320 }
321
322 $statsdays = (int) $statsdays;
323
324 if ($statsdays <= 365)
325 {
326 $statsdays = 365;
327 }
328 else if ($statsdays <= 365 * 2)
329 {
330 $statsdays = 365 * 2;
331 }
332 else
333 {
334 $statsdays = 365 * 5;
335 }
336
337 $params['statsdays'] = $statsdays;
338
339 update_option('firebox_settings', $params);
340
341 $this->run = true;
342 }
343
344 public function update213version()
345 {
346 $this->updateLogsDetailsParamsColumn();
347 $this->moveFormConversionsToLogsDetailsTable();
348 }
349
350 /**
351 * Adds a new column called event_source after event column.
352 * Renames the params column in the logs details table to event_label.
353 *
354 * @since 2.1.4
355 *
356 * @return void
357 */
358 public function updateLogsDetailsParamsColumn()
359 {
360 if (version_compare($this->installedVersion, '2.1.4', '>='))
361 {
362 return;
363 }
364
365 global $wpdb;
366
367 // If has column params
368 $row = $wpdb->get_results("SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = '{$wpdb->dbname}' AND table_name = '{$wpdb->prefix}firebox_logs_details' AND column_name = 'params'");
369 if (!$row)
370 {
371 $this->run = true;
372 return;
373 }
374
375 // Add column "event_source" after "event"
376 $wpdb->query("ALTER TABLE {$wpdb->prefix}firebox_logs_details ADD COLUMN `event_source` varchar(200) NOT NULL AFTER `event`");
377
378 // Rename params to event_label
379 $wpdb->query("ALTER TABLE {$wpdb->prefix}firebox_logs_details CHANGE COLUMN `params` `event_label` text NOT NULL");
380
381 $this->run = true;
382 }
383
384 /**
385 * Moves form conversions from submission meta table to logs details table.
386 *
387 * @since 2.1.4
388 *
389 * @return void
390 */
391 private function moveFormConversionsToLogsDetailsTable()
392 {
393 if (version_compare($this->installedVersion, '2.1.4', '>='))
394 {
395 return;
396 }
397
398 global $wpdb;
399
400 // Get all submission meta data
401 $boxlogids = firebox()->tables->submissionmeta->getResults([
402 'select' => [
403 "{$wpdb->prefix}firebox_submission_meta.id",
404 'l.box',
405 's.form_id',
406 "{$wpdb->prefix}firebox_submission_meta.meta_value",
407 "{$wpdb->prefix}firebox_submission_meta.created_at"
408 ],
409 'where' => [
410 'meta_key' => " = '" . absint('box_log_id') . "'"
411 ],
412 'join' => [
413 "LEFT JOIN" => " {$wpdb->prefix}firebox_submissions as s ON s.id = submission_id LEFT JOIN {$wpdb->prefix}firebox_logs as l ON l.id = meta_value",
414 ]
415 ]);
416
417 if (!$boxlogids)
418 {
419 $this->run = true;
420 return;
421 }
422
423 global $wpdb;
424
425 // Migrate to the box logs details table
426 foreach ($boxlogids as $row)
427 {
428 if (!$row->box)
429 {
430 continue;
431 }
432
433 $form = Form::getFormByID($row->form_id);
434
435 $data = [
436 'log_id' => $row->meta_value,
437 'event' => 'conversion',
438 'event_source' => 'form',
439 'event_label' => isset($form['block']['attrs']['formName']) ? $form['block']['attrs']['formName'] : 'FireBox #' . $row->box . ' Form',
440 'date' => $row->created_at
441 ];
442
443 firebox()->tables->boxlogdetails->insert($data);
444 }
445
446 // Delete all submission_meta rows
447 $ids = implode(',', array_map('intval', array_column($boxlogids, 'id')));
448 $sm_table_name = firebox()->tables->submissionmeta->getFullTableName();
449 $wpdb->query("DELETE FROM $sm_table_name WHERE id IN($ids)");// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
450
451 $this->run = true;
452 }
453
454 public function addMissingCapabilities()
455 {
456 if (version_compare($this->installedVersion, '2.1.15', '>='))
457 {
458 return;
459 }
460
461 $cap_name = 'read_fireboxes';
462
463 $admin = get_role('administrator');
464
465 if ($admin)
466 {
467 if ($admin->has_cap($cap_name))
468 {
469 return true;
470 }
471
472 $admin->add_cap($cap_name);
473 }
474 else
475 {
476 $roles = get_editable_roles();
477
478 foreach ($roles as $role_name => $data)
479 {
480 if (isset($data['capabilities']['manage_options']) && $data['capabilities']['manage_options'])
481 {
482 $role = get_role($role_name);
483
484 if (!$role)
485 {
486 continue;
487 }
488
489 if ($role->has_cap($cap_name))
490 {
491 continue;
492 }
493
494 $role->add_cap($cap_name);
495 }
496 }
497 }
498
499 wp_get_current_user()->get_role_caps();
500
501 return true;
502 }
503
504
505
506
507
508 /**
509 * Adds new indexes to the logs details table.
510 *
511 * @since 3.1.0
512 *
513 * @return void
514 */
515 private function addNewIndexes()
516 {
517 if (version_compare($this->installedVersion, '3.1.0', '>='))
518 {
519 return;
520 }
521
522 if (!$this->hasIndex('idx_firebox_logs_details_event_log', 'firebox_logs_details'))
523 {
524 global $wpdb;
525 $wpdb->query("ALTER TABLE {$wpdb->prefix}firebox_logs_details ADD INDEX `idx_firebox_logs_details_event_log` (`event`, `log_id`)");
526 }
527 if (!$this->hasIndex('idx_firebox_logs_details_event_source_log', 'firebox_logs_details'))
528 {
529 global $wpdb;
530 $wpdb->query("ALTER TABLE {$wpdb->prefix}firebox_logs_details ADD INDEX `idx_firebox_logs_details_event_source_log` (`event_source`, `log_id`)");
531 }
532 if (!$this->hasIndex('idx_firebox_logs_details_event_date', 'firebox_logs_details'))
533 {
534 global $wpdb;
535 $wpdb->query("ALTER TABLE {$wpdb->prefix}firebox_logs_details ADD INDEX `idx_firebox_logs_details_event_date` (`event`, `date`)");
536 }
537 if (!$this->hasIndex('idx_firebox_logs_details_event_source_event_log', 'firebox_logs_details'))
538 {
539 global $wpdb;
540 $wpdb->query("ALTER TABLE {$wpdb->prefix}firebox_logs_details ADD INDEX `idx_firebox_logs_details_event_source_event_log` (`event_source`, `event`, `log_id`)");
541 }
542 if (!$this->hasIndex('idx_firebox_logs_details_event_source_event_date', 'firebox_logs_details'))
543 {
544 global $wpdb;
545 $wpdb->query("ALTER TABLE {$wpdb->prefix}firebox_logs_details ADD INDEX `idx_firebox_logs_details_event_source_event_date` (`event_source`, `event`, `date`)");
546 }
547
548 $this->run = true;
549 }
550
551 /**
552 * Checks whether an index exists in a table.
553 *
554 * @param string $index
555 * @param string $table
556 *
557 * @return bool
558 */
559 /**
560 * Adds missing indexes on the submissions tables to speed up
561 * form_id/state lookups and submission_id meta lookups.
562 *
563 * @since 3.1.9
564 *
565 * @return void
566 */
567 private function addSubmissionsIndexes()
568 {
569 if (version_compare($this->installedVersion, '3.1.9', '>='))
570 {
571 return;
572 }
573
574 if (!$this->hasIndex('idx_form_id_state', 'firebox_submissions'))
575 {
576 global $wpdb;
577 $wpdb->query("ALTER TABLE {$wpdb->prefix}firebox_submissions ADD INDEX `idx_form_id_state` (`form_id`, `state`)");
578 }
579
580 if (!$this->hasIndex('idx_submission_id', 'firebox_submission_meta'))
581 {
582 global $wpdb;
583 $wpdb->query("ALTER TABLE {$wpdb->prefix}firebox_submission_meta ADD INDEX `idx_submission_id` (`submission_id`)");
584 }
585
586 $this->run = true;
587 }
588
589 /**
590 * Keeps existing roles' campaign access working after the campaign post type moved
591 * from core's "post" capabilities to FireBox's own.
592 *
593 * Before this change anyone with edit_posts could edit campaigns. Granting each role
594 * the FireBox capability matching the core capability it already holds preserves
595 * exactly who could do what, so upgrading changes nobody's access.
596 *
597 * Note this deliberately does not grant read_fireboxes: that gates the FireBox admin
598 * screens, was never derived from the post type, and granting it here would hand
599 * roles access they did not previously have.
600 *
601 * Site owners who want to tighten campaign access can now remove these capabilities
602 * and have it actually take effect, which was not previously the case.
603 *
604 * @since 3.1.10
605 *
606 * @return void
607 */
608 private function preserveCampaignRoleAccess()
609 {
610 if (version_compare($this->installedVersion, '3.1.10', '>='))
611 {
612 return;
613 }
614
615 $map = \FireBox\Core\Admin\Includes\Cpts\Firebox::getPostTypeCapabilities();
616
617 // Only the primitive capabilities are assigned to roles.
618 unset($map['edit_post'], $map['read_post'], $map['delete_post'], $map['create_posts']);
619
620 // wp_roles() rather than get_editable_roles(): the latter is admin-only and
621 // undefined on the frontend requests this migration also runs on.
622 foreach (wp_roles()->role_objects as $role)
623 {
624 foreach ($map as $core_cap => $firebox_cap)
625 {
626 if (!empty($role->capabilities[$core_cap]))
627 {
628 $role->add_cap($firebox_cap);
629 }
630 }
631 }
632
633 $this->run = true;
634 }
635
636 /**
637 * Grants the firebox_execute_php capability to administrators.
638 *
639 * This capability gates the PHP display condition and PHP Scripts. It is separate
640 * from the campaign-editing capabilities on purpose, so editing a campaign never
641 * grants the ability to run PHP. Existing installs get it on administrators here;
642 * a site owner can grant it to another role deliberately.
643 *
644 * @since 3.1.11
645 *
646 * @return void
647 */
648 private function grantExecutePHPCapability()
649 {
650 if (version_compare($this->installedVersion, '3.1.11', '>='))
651 {
652 return;
653 }
654
655 $cap = 'firebox_execute_php';
656
657 $admin = get_role('administrator');
658
659 if ($admin)
660 {
661 if (!$admin->has_cap($cap))
662 {
663 $admin->add_cap($cap);
664 $this->run = true;
665 }
666
667 return;
668 }
669
670 // No administrator role on this install: fall back to roles that manage options.
671 foreach (wp_roles()->role_objects as $role)
672 {
673 if (!empty($role->capabilities['manage_options']) && !$role->has_cap($cap))
674 {
675 $role->add_cap($cap);
676 $this->run = true;
677 }
678 }
679 }
680
681 /**
682 * Grants the firebox_execute_js capability to administrators.
683 *
684 * This capability gates campaign-authored JavaScript (the Custom JavaScript
685 * setting and the "Run Javascript" action) alongside unfiltered_html. It exists
686 * so administrators keep custom JS where WordPress withholds unfiltered_html
687 * (multisite site admins, DISALLOW_UNFILTERED_HTML hosts). Like
688 * firebox_execute_php it is separate from the campaign-editing capabilities on
689 * purpose, so editing a campaign never grants it; a site owner can grant it to
690 * another role deliberately.
691 *
692 * @since 3.1.11
693 *
694 * @return void
695 */
696 private function grantExecuteJSCapability()
697 {
698 if (version_compare($this->installedVersion, '3.1.11', '>='))
699 {
700 return;
701 }
702
703 $cap = 'firebox_execute_js';
704
705 $admin = get_role('administrator');
706
707 if ($admin)
708 {
709 if (!$admin->has_cap($cap))
710 {
711 $admin->add_cap($cap);
712 $this->run = true;
713 }
714
715 return;
716 }
717
718 // No administrator role on this install: fall back to roles that manage options.
719 foreach (wp_roles()->role_objects as $role)
720 {
721 if (!empty($role->capabilities['manage_options']) && !$role->has_cap($cap))
722 {
723 $role->add_cap($cap);
724 $this->run = true;
725 }
726 }
727 }
728
729 private function hasIndex($index = '', $table = '')
730 {
731 if (empty($index) || empty($table))
732 {
733 return;
734 }
735
736 global $wpdb;
737
738 $existing = $wpdb->get_row("SHOW CREATE TABLE `{$wpdb->prefix}{$table}`", ARRAY_N);// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
739
740 if (isset($existing[1]) && strpos(strtolower($existing[1]), 'key `' . $index . '` (') !== false)
741 {
742 return true;
743 }
744
745 return false;
746 }
747 }
748