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/Form/Ajax.php +103 -19 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
@@ -20,8 +20,24 @@
20 20 use \FireBox\Core\Helpers\BoxHelper;
21 21
22 22 class Ajax
23 23 {
24 + /**
25 + * Accepted submissions one client may make per minute.
26 + *
27 + * Set well above anything a person produces — the target is a script replaying a
28 + * valid submission, not a fast typist. Only submissions that pass validation count,
29 + * so the headroom also absorbs the cases where several visitors look like one
30 + * client: an office or campus behind a single address, or a proxy that does not
31 + * pass the visitor's address on.
32 + *
33 + * Sites can tune this through the firebox/rate_limit/limit filter, which switches
34 + * the throttle off entirely when it returns 0.
35 + *
36 + * @var int
37 + */
38 + const SUBMISSIONS_PER_MINUTE = 60;
39 +
24 40 public function __construct()
25 41 {
26 42 $this->setupAjax();
27 43
@@ -47,8 +63,17 @@
47 63 * @return void
48 64 */
49 65 public function fb_form_submission_status_change()
50 66 {
67 + if (!current_user_can('edit_fireboxes'))
68 + {
69 + echo wp_json_encode([
70 + 'error' => true,
71 + 'message' => 'You are not allowed to do this.'
72 + ]);
73 + wp_die();
74 + }
75 +
51 76 $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
52 77
53 78 // verify nonce
54 79 if (!$verify = wp_verify_nonce($nonce, 'fb_form_submission_action'))
@@ -59,17 +84,28 @@
59 84 ]);
60 85 wp_die();
61 86 }
62 87
63 - $submission_id = isset($_POST['submission_id']) ? sanitize_key(wp_unslash($_POST['submission_id'])) : '';
88 + $submission_id = isset($_POST['submission_id']) ? absint($_POST['submission_id']) : 0;
64 89 $new_state = isset($_POST['new_state']) ? sanitize_key(wp_unslash($_POST['new_state'])) : '';
65 90
66 91 $new_state = $new_state === 'publish' ? 1 : 0;
67 -
92 +
93 + // Confirm the submission exists before reporting anything about it.
94 + if (!$submission_id || !\FireBox\Core\Helpers\Form\Submission::exists($submission_id))
95 + {
96 + echo wp_json_encode([
97 + 'error' => true,
98 + 'message' => 'Submission state couldn\'t be updated.'
99 + ]);
100 + wp_die();
101 + }
102 +
68 103 if (!\FireBox\Core\Helpers\Form\Submission::updateState($submission_id, $new_state))
69 104 {
70 105 echo wp_json_encode([
71 - 'error' => false,
106 + // This branch means the write failed, so report it as an error.
107 + 'error' => true,
72 108 'message' => 'Submission state couldn\'t be updated.'
73 109 ]);
74 110 wp_die();
75 111 }
@@ -92,17 +128,45 @@
92 128
93 129 // verify nonce
94 130 if (!$verify = wp_verify_nonce($nonce, 'fbox_js_nonce'))
95 131 {
132 + /**
133 + * A full-page cache routinely outlives the 12-24h nonce baked into the cached
134 + * HTML. Name the cause so the client can fetch a fresh nonce and replay the
135 + * submission once, instead of losing the lead.
136 + */
96 137 echo wp_json_encode([
138 + 'error' => 'invalid_nonce',
139 + 'message' => firebox()->_('FB_FORM_CANNOT_VERIFY_REQUEST')
140 + ]);
141 + wp_die();
142 + }
143 +
144 + /**
145 + * The nonce above is printed into every page a campaign renders on and is the
146 + * same for every logged-out visitor, so it does not bound how often this endpoint
147 + * can be called. An accepted submission writes several rows and may send mail and
148 + * hit third-party APIs, so throttle per client.
149 + *
150 + * Only the check happens here. The hit is recorded further down, once the
151 + * submission has passed the honeypot, the captcha and validation — see
152 + * RATE LIMIT below. Counting rejected attempts would let a spam bot exhaust an
153 + * allowance that real visitors may be sharing: wherever a proxy leaves many
154 + * visitors looking like one client, junk traffic would lock all of them out of a
155 + * form they are entitled to use. It would also spend a visitor's own allowance on
156 + * their typos.
157 + */
158 + if (\FireBox\Core\Helpers\RateLimit::isLimited('form_submit', self::SUBMISSIONS_PER_MINUTE))
159 + {
160 + echo wp_json_encode([
97 161 'error' => true,
98 - 'message' => 'Cannot verify request.'
162 + 'message' => firebox()->_('FB_FORM_TOO_MANY_SUBMISSIONS')
99 163 ]);
100 164 wp_die();
101 - }
165 + }
102 166
103 167 $form_data = isset($_POST['form_data']) ? sanitize_text_field(wp_unslash($_POST['form_data'])) : '';
104 - $form_data = $form_data ? json_decode(html_entity_decode(stripslashes($form_data)), true) : '';
168 + $form_data = $form_data ? json_decode(stripslashes($form_data), true) : '';
105 169
106 170 if (!$form_data)
107 171 {
108 172 echo wp_json_encode([
@@ -122,9 +186,9 @@
122 186 wp_die();
123 187 }
124 188
125 189 $values = isset($form_data['fields']) ? $form_data['fields'] : false;
126 - if (!$form_id)
190 + if (!$values || !is_array($values))
127 191 {
128 192 echo wp_json_encode([
129 193 'error' => true,
130 194 'message' => 'Missing submission data.'
@@ -141,8 +205,18 @@
141 205 ]);
142 206 wp_die();
143 207 }
144 208
209 + // Forms live inside campaigns; only a published campaign may accept submissions from the frontend.
210 + if (empty($form['state']) || $form['state'] !== '1')
211 + {
212 + echo wp_json_encode([
213 + 'error' => true,
214 + 'message' => 'This form does not exist.'
215 + ]);
216 + wp_die();
217 + }
218 +
145 219 $form_block = $form['block'];
146 220 $form_fields = $form['fields'];
147 221
148 222 // Get the Campaign ID
@@ -158,9 +232,9 @@
158 232 catch (\Exception $e)
159 233 {
160 234 echo wp_json_encode([
161 235 'error' => true,
162 - 'message' => $e->getMessage()
236 + 'message' => wp_kses_post($e->getMessage())
163 237 ]);
164 238 wp_die();
165 239 }
166 240
@@ -170,9 +244,9 @@
170 244 catch (\Exception $e)
171 245 {
172 246 echo wp_json_encode([
173 247 'error' => true,
174 - 'message' => $e->getMessage()
248 + 'message' => wp_kses_post($e->getMessage())
175 249 ]);
176 250 wp_die();
177 251 }
178 252
@@ -190,8 +264,15 @@
190 264 echo wp_json_encode($payload);
191 265 wp_die();
192 266 }
193 267
268 + /**
269 + * RATE LIMIT: the submission is accepted from here on, and everything below it
270 + * costs something — rows written, mail sent, integrations called. This is the
271 + * point worth rationing, so the hit is recorded here rather than on arrival.
272 + */
273 + \FireBox\Core\Helpers\RateLimit::hit('form_submit', MINUTE_IN_SECONDS);
274 +
194 275 $submission = [];
195 276 $submission_meta_data = [];
196 277
197 278 /**
@@ -229,23 +310,26 @@
229 310 ]);
230 311 wp_die();
231 312 }
232 313
233 - // Track conversion after storing the submission
234 - if ($box_log_id)
314 + /**
315 + * Track conversion after storing the submission.
316 + *
317 + * box_log_id arrives from the client, so it is only trusted once we have
318 + * confirmed it refers to a real impression of this campaign. Otherwise any
319 + * submitter could attribute their conversion to any campaign.
320 + */
321 + if ($box_log_id && firebox()->tables->boxlog->belongsToCampaign($box_log_id, $box_id))
235 322 {
236 - /**
237 - * Track conversion
238 - */
239 323 $factory = new \FPFramework\Base\Factory();
240 324 $data = [
241 - 'log_id' => $box_log_id,
325 + 'log_id' => (int) $box_log_id,
242 326 'event' => 'conversion',
243 327 'event_source' => 'form',
244 - 'event_label' => 'FireBox #' . $box_id . ' Form',
328 + 'event_label' => 'FireBox #' . (int) $box_id . ' Form',
245 329 'date' => $factory->getDate()->format('Y-m-d H:i:s')
246 330 ];
247 -
331 +
248 332 firebox()->tables->boxlogdetails->insert($data);
249 333 }
250 334
251 335 // Replace Smart Tags in form attributes
@@ -263,9 +347,9 @@
263 347 if (!$actions->run())
264 348 {
265 349 echo wp_json_encode([
266 350 'error' => true,
267 - 'message' => $actions->getErrorMessage()
351 + 'message' => wp_kses_post($actions->getErrorMessage())
268 352 ]);
269 353 wp_die();
270 354 }
271 355 }