PluginProbe
FireBox – WooCommerce Popup Builder, Exit Intent Popup, Email Optin & Cart Abandonment / 3.1.14
FireBox – WooCommerce Popup Builder, Exit Intent Popup, Email Optin & Cart Abandonment v3.1.14
3.1.14 3.1.13 3.1.12 3.1.11 3.1.10 3.1.9 3.1.8 3.1.7 trunk 1.0.0 1.0.1 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 All 123 releases
← All changes | Inc/Core/Admin/Admin.php +128 -104 3.1.7 → 3.1.14 View file →
@@ -1,8 +1,8 @@
1 1 <?php
2 2 /**
3 3 * @package FireBox
4 - * @version 3.1.7 Free
4 + * @version 3.1.14 Free
5 5 *
6 6 * @author FirePlugins <[email protected]>
7 7 * @link https://www.fireplugins.com
8 8 * @copyright Copyright © 2026 FirePlugins All Rights Reserved
@@ -38,9 +38,9 @@
38 38 public function __construct()
39 39 {
40 40 new \FireBox\Core\Notices\Ajax();
41 41
42 - $this->maybeExportSubmsissions();
42 + add_action('admin_init', [$this, 'maybeExportSubmsissions']);
43 43
44 44 add_action('admin_head', [$this, 'set_gutenberg_editor_logo']);
45 45
46 46 add_action('wp_trash_post', [$this, 'on_campaign_trash'], 10, 2);
@@ -53,15 +53,10 @@
53 53 add_action('current_screen', [$this, 'current_screen']);
54 54
55 55 add_action('firebox/admin/content', [$this, 'showNotices'], -5);
56 56
57 - // Init onboarding for new users
58 - add_action('admin_init', [$this, 'initOnboarding']);
59 57 add_action('admin_init', [$this, 'redirectAfterActivation']);
60 58
61 - // Register AJAX handler for onboarding (always available, not limited by page check)
62 - add_action('wp_ajax_firebox_onboarding_complete', [Includes\Onboarding::class, 'handleOnboardingComplete']);
63 -
64 59 // init dependencies
65 60 $this->initDependencies();
66 61
67 62 // Admin Page Settings
@@ -105,9 +100,14 @@
105 100 $assign_cookietype = isset($meta[0]['assign_cookietype']) ? $meta[0]['assign_cookietype'] : '';
106 101
107 102 if ($assign_cookietype === 'never')
108 103 {
109 - setcookie('firebox_' . $post_id, '', time() - 3600, '/');
104 + $cookie = \FireBox\Core\Helpers\BoxHelper::cookiePrefix() . $post_id;
105 +
106 + foreach (\FireBox\Core\Helpers\BoxHelper::cookiePathsToClear() as $path)
107 + {
108 + setcookie($cookie, '', time() - 3600, $path, \FireBox\Core\Helpers\BoxHelper::cookieDomain());
109 + }
110 110 }
111 111 }
112 112
113 113 /**
@@ -132,9 +132,10 @@
132 132 .editor-header:has(>.editor-header__center) {
133 133 grid-template: auto / auto minmax(min-content, 1fr) 2fr minmax(min-content, 1fr) 60px;
134 134 }
135 135 }
136 - .editor-header__back-button .edit-post-fullscreen-mode-close {
136 + .editor-header__back-button .edit-post-fullscreen-mode-close,
137 + .editor-header__back-button a.components-button.has-icon {
137 138 background: transparent !important;
138 139 background-image: url("<?php echo esc_url(FBOX_MEDIA_ADMIN_URL); ?>images/logo_full.svg") !important;
139 140 background-size: 105px !important;
140 141 margin-right: 0 !important;
@@ -152,27 +153,104 @@
152 153 </style>
153 154 <?php
154 155 }
155 156
156 - private function maybeExportSubmsissions()
157 + public function maybeExportSubmsissions()
157 158 {
158 - if (!isset($_GET['task']) || $_GET['task'] !== 'export') //phpcs:ignore WordPress.Security.NonceVerification.Recommended
159 + if (!isset($_GET['task']) || $_GET['task'] !== 'export')
159 160 {
160 - return;
161 - }
161 + return;
162 + }
162 163
163 - if (!isset($_GET['form_id'])) //phpcs:ignore WordPress.Security.NonceVerification.Recommended
164 + if (!isset($_GET['form_id']))
164 165 {
165 166 return;
166 167 }
167 168
168 - if (!isset($_GET['page']) || $_GET['page'] !== 'firebox-submissions') //phpcs:ignore WordPress.Security.NonceVerification.Recommended
169 + if (!isset($_GET['page']) || $_GET['page'] !== 'firebox-submissions')
169 170 {
170 171 return;
171 172 }
172 173
173 - $form_id = sanitize_text_field(wp_unslash($_GET['form_id'])); //phpcs:ignore WordPress.Security.NonceVerification.Recommended
174 + // Authorization: only users who can view submissions may export them.
175 + if (!current_user_can('read_fireboxes'))
176 + {
177 + return;
178 + }
174 179
180 + // CSRF: verify the nonce generated on the submissions list page.
181 + check_admin_referer('firebox-export-submissions');
182 +
183 + $form_id = sanitize_text_field(wp_unslash($_GET['form_id']));
184 +
185 + $export = $this->getSubmissionsForExport($form_id);
186 +
187 + if (!$export)
188 + {
189 + return;
190 + }
191 +
192 + /**
193 + * The export holds every submission for the form, so it must not sit at a
194 + * guessable path in the shared temp directory while it is being served.
195 + * wp_tempnam() creates a unique file with restrictive permissions.
196 + */
197 + $name = sanitize_file_name($export['name']);
198 + $download_name = 'submissions_' . $name . '_' . gmdate('Y-m-d_H-i-s') . '.csv';
199 +
200 + $filename = wp_tempnam('firebox-export-' . $name);
201 +
202 + if (!$filename)
203 + {
204 + return;
205 + }
206 +
207 + try
208 + {
209 + self::toCSV($export['rows'], $filename);
210 +
211 + // Send the appropriate headers to force the download in the browser
212 + header('Content-Description: File Transfer');
213 + header('Content-Type: text/csv; charset=UTF-8');
214 + header('Content-Disposition: attachment; filename="' . $download_name . '"');
215 + header('X-Content-Type-Options: nosniff');
216 + header('Expires: 0');
217 + header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
218 + header('Pragma: public');
219 + header('Content-Length: ' . filesize($filename));
220 +
221 + // Clear any buffered output so it doesn't corrupt the download
222 + while (ob_get_level())
223 + {
224 + ob_end_clean();
225 + }
226 +
227 + flush();
228 +
229 + readfile($filename);
230 + }
231 + finally
232 + {
233 + // Runs even if writing or streaming failed, so the PII never lingers on disk.
234 + if (file_exists($filename))
235 + {
236 + unlink($filename);
237 + }
238 + }
239 +
240 + exit;
241 + }
242 +
243 + /**
244 + * Collects and prepares submission rows for CSV export.
245 + * Returns the prepared rows (with the resolved form attached) or false.
246 + *
247 + * @param string $form_id
248 + *
249 + * @return array|false
250 + */
251 + protected function getSubmissionsForExport($form_id)
252 + {
175 253 $payload = [
176 254 'where' => [
177 255 'form_id' => " = '" . sanitize_key($form_id) . "'",
178 256 'state' => ' = 1'
@@ -180,22 +258,41 @@
180 258 'offset' => 0,
181 259 'limit' => 99999,
182 260 'orderby' => 'created_at ASC'
183 261 ];
184 -
262 +
185 263 if (!$submissions = firebox()->tables->submission->getResults($payload))
186 264 {
187 - return;
265 + return false;
188 266 }
189 267
190 268 if (!$form = \FireBox\Core\Helpers\Form\Form::getFormByID($form_id, true))
191 269 {
192 - return;
270 + return false;
193 271 }
194 272
273 + // Fetch all submission meta in chunked IN() queries instead of one query per submission.
274 + $submission_ids = wp_list_pluck($submissions, 'id');
275 + $meta_by_submission = [];
276 +
277 + foreach (array_chunk($submission_ids, 500) as $id_chunk)
278 + {
279 + $ids_in = implode(',', array_map('absint', $id_chunk));
280 +
281 + $chunk_meta = firebox()->tables->submissionmeta->getResults([
282 + 'where' => [
283 + 'submission_id' => " IN (" . $ids_in . ")"
284 + ]
285 + ]);
286 +
287 + foreach ($chunk_meta as $meta_item)
288 + {
289 + $meta_by_submission[$meta_item->submission_id][] = $meta_item;
290 + }
291 + }
292 +
195 293 $prepared = [];
196 294
197 - // Set submission fields values
198 295 foreach ($submissions as $item)
199 296 {
200 297 $prepared_payload = [
201 298 'id' => $item->id,
@@ -201,16 +298,11 @@
201 298 'id' => $item->id,
202 299 'created' => get_date_from_gmt($item->created_at),
203 300 'state' => $item->state === '1' ? 'Published' : 'Unpublished'
204 301 ];
205 -
206 - // Find field values
207 - $meta = firebox()->tables->submissionmeta->getResults([
208 - 'where' => [
209 - 'submission_id' => " = " . absint($item->id)
210 - ]
211 - ]);
212 302
303 + $meta = isset($meta_by_submission[$item->id]) ? $meta_by_submission[$item->id] : [];
304 +
213 305 if ($meta && $form['fields'])
214 306 {
215 307 foreach ($form['fields'] as $field)
216 308 {
@@ -222,38 +314,13 @@
222 314 }
223 315 }
224 316 }
225 317 }
226 -
318 +
227 319 $prepared[] = $prepared_payload;
228 320 }
229 321
230 -
231 - $filename = get_temp_dir() . 'submissions_' . $form['name'] . '_' . date('Y-m-d_H-i-s') . '.csv';
232 - self::toCSV($prepared, $filename);
233 -
234 - // Prompt to download the file
235 - error_reporting(0);
236 -
237 - // Send the appropriate headers to force the download in the browser
238 - header('Content-Description: File Transfer');
239 - header('Content-Type: application/octet-stream');
240 - header('Content-Disposition: attachment; filename="' . basename($filename) . '"');
241 - header('Expires: 0');
242 - header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
243 - header('Cache-Control: public', false);
244 - header('Pragma: public');
245 - header('Content-Length: ' . @filesize($filename));
246 -
247 - // Clear the output buffer and disable output buffering
248 - ob_clean();
249 - flush();
250 -
251 - readfile($filename);
252 -
253 - unlink($filename);
254 -
255 - exit;
322 + return ['name' => $form['name'], 'rows' => $prepared];
256 323 }
257 324
258 325 /**
259 326 * Create a CSV file with given data
@@ -274,9 +341,9 @@
274 341 // Support UTF-8 on Microsoft Excel
275 342 fputs($resource, "\xEF\xBB\xBF");
276 343
277 344 // Add column names in the first line
278 - fputcsv($resource, array_keys($data[0]));
345 + fputcsv($resource, array_keys($data[0]), ',', '"', '\\');
279 346 }
280 347
281 348 // Get CSV content
282 349 $existingRows = [];
@@ -311,9 +378,9 @@
311 378 }
312 379 }
313 380 }
314 381
315 - fputcsv($resource, $row);
382 + fputcsv($resource, $row, ',', '"', '\\');
316 383 }
317 384
318 385 fclose($resource);
319 386 }
@@ -555,9 +622,9 @@
555 622 {
556 623 add_action('admin_enqueue_scripts', [$this, 'registerGlobalMedia'], 20);
557 624
558 625
559 - add_action('plugin_action_links_' . plugin_basename(FBOX_PLUGIN_BASE_FILE), [$this, 'plugin_action_links']);
626 + add_filter('plugin_action_links_' . plugin_basename(FBOX_PLUGIN_BASE_FILE), [$this, 'plugin_action_links']);
560 627
561 628 }
562 629
563 630 public function registerGlobalMedia()
@@ -805,8 +872,12 @@
805 872 'CONVERSION' => firebox()->_('FB_CONVERSION'),
806 873 'CONVERSIONS' => firebox()->_('FB_CONVERSIONS'),
807 874 'CONVERSION_RATE' => firebox()->_('FB_CONVERSION_RATE'),
808 875 'NO_DATA_AVAILABLE' => firebox()->_('FB_NO_DATA_AVAILABLE'),
876 + 'COULDNT_LOAD_DATA' => firebox()->_('FB_COULDNT_LOAD_DATA'),
877 + 'TRY_AGAIN' => firebox()->_('FB_TRY_AGAIN'),
878 + 'DISMISS' => firebox()->_('FB_DISMISS'),
879 + 'ERROR_OCCURRED_PLEASE_TRY_AGAIN' => fpframework()->_('FPF_ERROR_OCCURRED_PLEASE_TRY_AGAIN'),
809 880 'COUNTRIES' => fpframework()->_('FPF_COUNTRIES'),
810 881 'FLAG' => fpframework()->_('FPF_FLAG'),
811 882 'DEVICES' => fpframework()->_('FPF_DEVICES'),
812 883 'EVENTS' => fpframework()->_('FPF_EVENTS'),
@@ -858,9 +929,8 @@
858 929 'CONVERSION_RATE_TOOLTIP_DESC' => firebox()->_('FB_CONVERSION_RATE_TOOLTIP_DESC'),
859 930 'CONVERSIONS_TOOLTIP_DESC' => firebox()->_('FB_CONVERSIONS_TOOLTIP_DESC'),
860 931 'VS_PREVIOUS_PERIOD' => firebox()->_('FB_VS_PREVIOUS_PERIOD'),
861 932 'VIEWS_TOOLTIP_DESC' => firebox()->_('FB_VIEWS_TOOLTIP_DESC'),
862 - 'CLICKS_TOOLTIP_DESC' => firebox()->_('FB_CLICKS_TOOLTIP_DESC'),
863 933 'NO' => firebox()->_('FB_NO'),
864 934 'DATA_AVAILABLE' => firebox()->_('FB_DATA_AVAILABLE'),
865 935 'PERFORMANCE' => firebox()->_('FB_PERFORMANCE'),
866 936 'TRENDING_TEMPLATES' => firebox()->_('FB_TRENDING_TEMPLATES'),
@@ -887,9 +957,8 @@
887 957 'OVERALL_CONVERSION_RATE' => firebox()->_('FB_OVERALL_CONVERSION_RATE'),
888 958 'REVENUE_CONVERSIONS' => firebox()->_('FB_REVENUE_CONVERSIONS'),
889 959 'LITE' => fpframework()->_('FPF_LITE'),
890 960 'FREE' => fpframework()->_('FPF_FREE'),
891 - 'PRO' => fpframework()->_('FPF_PRO'),
892 961 'FROM' => firebox()->_('FB_FROM'),
893 962 'OF' => firebox()->_('FB_OF'),
894 963 'USERS' => firebox()->_('FB_USERS'),
895 964 'TOTAL' => firebox()->_('FB_TOTAL'),
@@ -949,53 +1018,8 @@
949 1018
950 1019
951 1020
952 1021
953 -
954 - /**
955 - * Initialize onboarding for new users
956 - * Show on FireBox dashboard and all FireBox admin pages
957 - *
958 - * @return void
959 - */
960 - public function initOnboarding()
961 - {
962 - if (!current_user_can('manage_options'))
963 - {
964 - return;
965 - }
966 -
967 - $current_page = isset($_GET['page']) ? sanitize_key($_GET['page']) : ''; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
968 - $post_type = isset($_GET['post_type']) ? sanitize_key($_GET['post_type']) : ''; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
969 - $post_id = isset($_GET['post']) ? absint($_GET['post']) : 0; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
970 -
971 - $resolved_post_type = $post_type;
972 - if (!$resolved_post_type && $post_id)
973 - {
974 - $post = get_post($post_id);
975 - $resolved_post_type = $post ? $post->post_type : '';
976 - }
977 -
978 - // Allowed FireBox dashboard pages
979 - $allowed_pages = [
980 - 'firebox',
981 - 'firebox-campaigns',
982 - 'firebox-analytics',
983 - 'firebox-submissions',
984 - 'firebox-settings',
985 - 'firebox-import'
986 - ];
987 -
988 - $on_firebox_admin_page = in_array($current_page, $allowed_pages, true);
989 - $on_firebox_editor = ($resolved_post_type === 'firebox');
990 -
991 - if (!$on_firebox_admin_page && !$on_firebox_editor)
992 - {
993 - return;
994 - }
995 -
996 - new Includes\Onboarding();
997 - }
998 1022
999 1023 /**
1000 1024 * Redirect to FireBox dashboard after plugin activation
1001 1025 *