| @@ -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 | * |