PluginProbe
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder / 6.2.3
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder v6.2.3
6.2.14 6.2.13 6.2.12 6.2.10 6.2.11 6.2.9 6.2.8 6.2.7 6.2.6 6.2.5 6.2.4 6.2.3 6.2.2 3.6.22 3.6.31 3.6.40 3.6.41 3.6.42 3.6.50 3.6.51 3.6.60 3.6.61 3.6.62 3.6.64 3.6.65 All 196 releases
fluentform / app / Services / Form / SubmissionHandlerService.php

SubmissionHandlerService.php in Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder 6.2.3, at app/Services/Form/SubmissionHandlerService.php

624 lines 23.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentForm\App\Services\Form;
4
5 use FluentForm\App\Helpers\Helper;
6 use FluentForm\App\Models\Form;
7 use FluentForm\App\Models\FormMeta;
8 use FluentForm\App\Models\Submission;
9 use FluentForm\App\Modules\Form\FormFieldsParser;
10 use FluentForm\App\Services\Browser\Browser;
11 use FluentForm\App\Services\FormBuilder\ShortCodeParser;
12 use FluentForm\App\Services\Submission\SubmissionService;
13 use FluentForm\Database\Migrations\SubmissionDetails;
14 use FluentForm\Framework\Foundation\App;
15 use FluentForm\Framework\Helpers\ArrayHelper as Arr;
16 use FluentForm\Framework\Validator\ValidationException;
17
18
19 class SubmissionHandlerService
20 {
21 protected $app;
22 protected $form;
23 protected $fields;
24 protected $formData;
25 protected $validationService;
26 protected $submissionService;
27 protected $alreadyInsertedId;
28
29 public function __construct()
30 {
31 $this->app = App::getInstance();
32 $this->validationService = new FormValidationService();
33 $this->submissionService = new SubmissionService();
34 }
35
36 /**
37 * Form Submission
38 * @param $formDataRaw
39 * @param $formId
40 * @return array
41 * @throws \FluentForm\Framework\Validator\ValidationException
42 */
43 public function handleSubmission($formDataRaw, $formId)
44 {
45 $this->prepareHandler($formId, $formDataRaw);
46 $insertData = $this->handleValidation();
47 if ($returnData = $this->isSpamAndSkipProcessing($insertData)) {
48 return $returnData;
49 }
50 $insertId = $this->insertSubmission($insertData, $formDataRaw, $formId);
51
52 return $this->processSubmissionData($insertId, $this->formData, $this->form);
53 }
54
55 /**
56 * @throws ValidationException
57 */
58 protected function prepareHandler($formId, $formDataRaw)
59 {
60 $this->form = Form::find($formId);
61
62 if (!$this->form) {
63 throw new ValidationException('', 422, null, ['errors' => 'Sorry, No corresponding form found']);
64 }
65
66 /**
67 * Filtering empty array inputs to normalize
68 *
69 * For unchecked checkable type, the name filled with empty value
70 * by serialized on the client-side JavaScript. This adjustment ensures filter empty array inputs to normalize- Ex: [''] -> []
71 */
72 foreach ($formDataRaw as $name => $input) {
73 if (is_array($input)) {
74 $formDataRaw[$name] = array_filter($input, function ($value) {
75 return $value !== null && $value !== false && $value !== '';
76 });
77 }
78
79 // Process "Other" options for checkboxes and radio fields
80 if (strpos($name, '__ff_other_input__') !== false && !empty($input)) {
81 $fieldName = str_replace('__ff_other_input__', '', $name);
82
83 // Handle checkbox fields (array values)
84 if (isset($formDataRaw[$fieldName]) && is_array($formDataRaw[$fieldName])) {
85 $selectedValues = $formDataRaw[$fieldName];
86
87 // Handle field-specific "Other" values
88 $otherValue = '__ff_other_' . $fieldName . '__';
89
90 $key = array_search($otherValue, $selectedValues);
91
92 if ($key !== false) {
93 $selectedValues[$key] = 'Other: ' . sanitize_text_field($input);
94 $formDataRaw[$fieldName] = $selectedValues;
95 }
96 } // Handle radio fields (single value)
97 elseif (isset($formDataRaw[$fieldName]) && $formDataRaw[$fieldName] === '__ff_other_' . $fieldName . '__') {
98 $formDataRaw[$fieldName] = 'Other: ' . sanitize_text_field($input);
99 }
100
101 unset($formDataRaw[$name]);
102 }
103 }
104
105 // Parse the form and get the flat inputs with validations.
106 $this->fields = FormFieldsParser::getEssentialInputs($this->form, $formDataRaw, ['rules', 'raw']);
107
108 // @todo Remove this after few version as we are doing it during conversation now
109 // Removing left out fields during conversation which causes validation issues
110 $isConversationalForm = Helper::isConversionForm($formId);
111 if ($isConversationalForm) {
112 $conversationalForm = $this->form;
113 $conversationalForm->form_fields = \FluentForm\App\Services\FluentConversational\Classes\Converter\Converter::convertExistingForm($this->form);
114 $conversationalFields = FormFieldsParser::getInputs($conversationalForm);
115 $this->fields = array_intersect_key($this->fields, $conversationalFields);
116 }
117 $formData = fluentFormSanitizer($formDataRaw, null, $this->fields);
118
119 $acceptedFieldKeys = array_merge($this->fields, array_flip(Helper::getWhiteListedFields($formId)));
120
121 $this->formData = array_intersect_key($formData, $acceptedFieldKeys);
122 }
123
124
125 /**
126 * Prepare the data to be inserted to the database.
127 * @param boolean $formData
128 * @return array
129 */
130 public function prepareInsertData($formData = false)
131 {
132 $formId = $this->form->id;
133 if (!$formData) {
134 $formData = $this->formData;
135 }
136 $previousItem = Submission::select('serial_number')->where('form_id', $formId)->orderBy('id', 'DESC')->first();
137 $serialNumber = 1;
138 if ($previousItem) {
139 $serialNumber = $previousItem->serial_number + 1;
140 }
141 $browser = new Browser();
142 $inputConfigs = FormFieldsParser::getEntryInputs($this->form, ['admin_label', 'raw']);
143
144 $formData = apply_filters_deprecated(
145 'fluentform_insert_response_data',
146 [
147 $formData,
148 $formId,
149 $inputConfigs
150 ],
151 FLUENTFORM_FRAMEWORK_UPGRADE,
152 'fluentform/insert_response_data',
153 'Use fluentform/insert_response_data instead of fluentform_insert_response_data.'
154 );
155 $this->formData = apply_filters('fluentform/insert_response_data', $formData, $formId, $inputConfigs);
156
157 $ipAddress = sanitize_text_field($this->app->request->getIp());
158
159 $disableIpLog = apply_filters_deprecated(
160 'fluentform_disable_ip_logging',
161 [
162 false,
163 $formId
164 ],
165 FLUENTFORM_FRAMEWORK_UPGRADE,
166 'fluentform/disable_ip_logging',
167 'Use fluentform/disable_ip_logging instead of fluentform_disable_ip_logging.'
168 );
169
170 if ((defined('FLUENTFROM_DISABLE_IP_LOGGING') && FLUENTFROM_DISABLE_IP_LOGGING) || apply_filters('fluentform/disable_ip_logging',
171 $disableIpLog, $formId)) {
172 $ipAddress = false;
173 }
174
175 $response = [
176 'form_id' => $formId,
177 'serial_number' => $serialNumber,
178 'response' => json_encode($this->formData, JSON_UNESCAPED_UNICODE),
179 'source_url' => site_url(Arr::get($formData, '_wp_http_referer')),
180 'user_id' => get_current_user_id(),
181 'browser' => $browser->getBrowser(),
182 'device' => $browser->getPlatform(),
183 'country' => apply_filters('fluentform/disable_submission_country_detection', false, $formId) ? null : Helper::getCountryCodeFromHeaders(),
184 'ip' => $ipAddress,
185 'created_at' => current_time('mysql'),
186 'updated_at' => current_time('mysql'),
187 ];
188
189 $response = apply_filters_deprecated(
190 'fluentform_filter_insert_data',
191 [
192 $response
193 ],
194 FLUENTFORM_FRAMEWORK_UPGRADE,
195 'fluentform/filter_insert_data',
196 'Use fluentform/filter_insert_data instead of fluentform_filter_insert_data.'
197 );
198
199 return apply_filters('fluentform/filter_insert_data', $response);
200 }
201
202 public function processSubmissionData($insertId, $formData, $form)
203 {
204 $form = isset($this->form) ? $this->form : $form;
205 $formData = isset($this->formData) ? $this->formData : $formData;
206 do_action_deprecated(
207 'fluentform_before_form_actions_processing', [
208 $insertId,
209 $this->formData,
210 $form
211 ],
212 FLUENTFORM_FRAMEWORK_UPGRADE,
213 'fluentform/before_form_actions_processing',
214 'Use fluentform/before_form_actions_processing instead of fluentform_before_form_actions_processing.'
215 );
216
217 do_action('fluentform/before_form_actions_processing', $insertId, $formData, $form);
218
219 if ($insertId) {
220 ob_start();
221 $formData = apply_filters('fluentform/submission_form_data', $formData, $insertId, $form);
222 $this->submissionService->recordEntryDetails($insertId, $form->id, $formData);
223 $isError = ob_get_clean();
224 if ($isError) {
225 SubmissionDetails::migrate();
226 }
227 }
228 $error = '';
229 try {
230 $formData = apply_filters('fluentform/submission_form_data', $formData, $insertId, $form);
231
232 do_action('fluentform_submission_inserted', $insertId, $formData, $form);
233
234 do_action('fluentform/submission_inserted', $insertId, $formData, $form);
235
236 Helper::setSubmissionMeta($insertId, 'is_form_action_fired', 'yes');
237
238 do_action_deprecated(
239 'fluentform_submission_inserted_' . $form->type . '_form', [
240 $insertId,
241 $formData,
242 $form
243 ],
244 FLUENTFORM_FRAMEWORK_UPGRADE,
245 'fluentform/submission_inserted_' . $form->type . '_form',
246 'Use fluentform/submission_inserted_' . $form->type . '_form instead of fluentform_submission_inserted_' . $form->type . '_form'
247 );
248
249 $this->app->doAction(
250 'fluentform/submission_inserted_' . $form->type . '_form',
251 $insertId,
252 $formData,
253 $form
254 );
255
256 } catch (\Exception $e) {
257 if (defined('WP_DEBUG') && WP_DEBUG) {
258 $error = $e->getMessage();
259 }
260 }
261
262 do_action_deprecated(
263 'fluentform_before_submission_confirmation', [
264 $insertId,
265 $formData,
266 $form
267 ],
268 FLUENTFORM_FRAMEWORK_UPGRADE,
269 'fluentform/before_submission_confirmation',
270 'Use fluentform/before_submission_confirmation instead of fluentform_before_submission_confirmation.'
271 );
272
273 do_action('fluentform/before_submission_confirmation', $insertId, $formData, $form);
274
275 return [
276 'insert_id' => $insertId,
277 'result' => $this->getReturnData($insertId, $form, $formData),
278 'error' => $error,
279 ];
280 }
281
282 /**
283 * Return Formatted Response Data
284 * @param $insertId
285 * @param $form
286 * @param $formData
287 * @return mixed
288 */
289 public function getReturnData($insertId, $form, $formData)
290 {
291 if (empty($form->settings)) {
292 $formSettings = FormMeta::retrieve('formSettings', $form->id);
293 $form->settings = is_array($formSettings) ? $formSettings : [];
294 }
295 $confirmation = $form->settings['confirmation'];
296 $confirmation = apply_filters_deprecated(
297 'fluentform_form_submission_confirmation',
298 [
299 $confirmation,
300 $formData,
301 $form
302 ],
303 FLUENTFORM_FRAMEWORK_UPGRADE,
304 'fluentform/form_submission_confirmation',
305 'Use fluentform/form_submission_confirmation instead of fluentform_form_submission_confirmation.'
306 );
307
308 $confirmation = apply_filters(
309 'fluentform/form_submission_confirmation',
310 $confirmation,
311 $formData,
312 $form
313 );
314 if ('samePage' == Arr::get($confirmation, 'redirectTo')) {
315
316 $confirmation['messageToShow'] = fluentform_sanitize_html($confirmation['messageToShow']);
317
318 $confirmation['messageToShow'] = apply_filters_deprecated(
319 'fluentform_submission_message_parse',
320 [
321 $confirmation['messageToShow'],
322 $insertId,
323 $formData,
324 $form
325 ],
326 FLUENTFORM_FRAMEWORK_UPGRADE,
327 'fluentform/submission_message_parse',
328 'Use fluentform/submission_message_parse instead of fluentform_submission_message_parse.'
329 );
330
331 $confirmation['messageToShow'] = apply_filters('fluentform/submission_message_parse', $confirmation['messageToShow'], $insertId, $formData, $form);
332
333 $confirmation['messageToShow'] = do_shortcode($confirmation['messageToShow']);
334
335 $message = ShortCodeParser::parse(
336 $confirmation['messageToShow'],
337 $insertId,
338 $formData,
339 $form,
340 false,
341 true,
342 true
343 );
344 $message = $message ? $message : __('The form has been successfully submitted.', 'fluentform');
345
346 $returnData = [
347 'message' => $message,
348 'action' => $confirmation['samePageFormBehavior'],
349 ];
350 } else {
351 $redirectUrl = Arr::get($confirmation, 'customUrl');
352 if ('customPage' == $confirmation['redirectTo']) {
353 $redirectUrl = get_permalink($confirmation['customPage']);
354 }
355 $enableQueryString = Arr::get($confirmation, 'enable_query_string') === 'yes';
356 $queryStrings = Arr::get($confirmation, 'query_strings');
357
358 if ($enableQueryString && $queryStrings) {
359 $separator = strpos($redirectUrl, '?') !== false ? '&' : '?';
360 $redirectUrl .= $separator . $queryStrings;
361 }
362 $parseUrl = apply_filters_deprecated('fluentform_will_parse_url_value', [
363 true,
364 $form
365 ],
366 FLUENTFORM_FRAMEWORK_UPGRADE,
367 'fluentform/will_parse_url_value',
368 'Use fluentform/will_parse_url_value instead of fluentform_will_parse_url_value.'
369 );
370
371 $isUrlParser = apply_filters('fluentform/will_parse_url_value', $parseUrl, $form);
372 $redirectUrl = ShortCodeParser::parse(
373 $redirectUrl,
374 $insertId,
375 $formData,
376 $form,
377 $isUrlParser
378 );
379 if ($isUrlParser) {
380 /*
381 * Encode Redirect Value
382 */
383 $encodeUrl = apply_filters('fluentform/will_encode_url_value', false, $redirectUrl, $insertId, $form, $formData);
384 if (strpos($redirectUrl, '&') || '=' == substr($redirectUrl, -1) || $encodeUrl) {
385 $urlArray = explode('?', $redirectUrl);
386 $baseUrl = array_shift($urlArray);
387
388 $parsedUrl = wp_parse_url($redirectUrl);
389 $query = Arr::get($parsedUrl, 'query', '');
390 $queryParams = explode('&', $query);
391
392 $params = [];
393 foreach ($queryParams as $queryParam) {
394 $paramArray = explode('=', $queryParam);
395 if (!empty($paramArray[1])) {
396 if (strpos($paramArray[1], '%') === false) {
397 $params[$paramArray[0]] = rawurlencode($paramArray[1]);
398 } else {
399 // Param string is URL-encoded
400 $params[$paramArray[0]] = $paramArray[1];
401 }
402 }
403 }
404 if ($params) {
405 $redirectUrl = add_query_arg($params, $baseUrl);
406 if ($fragment = Arr::get($parsedUrl, 'fragment')) {
407 $redirectUrl .= '#' . $fragment;
408 }
409 }
410 }
411 }
412
413 $message = ShortCodeParser::parse(
414 Arr::get($confirmation, 'redirectMessage', ''),
415 $insertId,
416 $formData,
417 $form,
418 false,
419 true
420 );
421
422 $redirectUrl = apply_filters('fluentform/redirect_url_value', wp_sanitize_redirect($redirectUrl), $insertId, $form, $formData);
423 $returnData = [
424 'redirectUrl' => esc_url_raw($redirectUrl),
425 'message' => fluentform_sanitize_html($message),
426 ];
427 }
428
429 $returnData = apply_filters_deprecated('fluentform_submission_confirmation', [
430 $returnData,
431 $form,
432 $confirmation,
433 $insertId,
434 $formData
435 ],
436 FLUENTFORM_FRAMEWORK_UPGRADE,
437 'fluentform/submission_confirmation',
438 'Use fluentform/submission_confirmation instead of fluentform_submission_confirmation.'
439 );
440
441 return $this->app->applyFilters(
442 'fluentform/submission_confirmation',
443 $returnData,
444 $form,
445 $confirmation,
446 $insertId,
447 $formData
448 );
449 }
450
451 private function isSpamAndSkipProcessing(&$insertData)
452 {
453 $spamSources = Arr::get($insertData, 'spam_from', []);
454
455 if ($spamSources) {
456 unset($insertData['spam_from']);
457 }
458
459 if (Arr::get($insertData, 'status') !== 'spam') {
460 return false;
461 }
462
463 $insertId = Submission::insertGetId($insertData);
464
465 if (!$insertId) {
466 return false;
467 }
468
469 $shouldSkip = false;
470
471 foreach ($spamSources as $source) {
472 if ($this->shouldSkipProcessingForSource($source)) {
473 $this->processSpamSubmission($insertId, $source);
474 $shouldSkip = true;
475 }
476 }
477
478 if ($shouldSkip) {
479 return [
480 'insert_id' => $insertId,
481 'result' => $this->getReturnData($insertId, $this->form, $this->formData),
482 ];
483 }
484
485 // Set a property for already inserted data for spam
486 $this->alreadyInsertedId = $insertId;
487
488 return false;
489 }
490
491 private function shouldSkipProcessingForSource($source)
492 {
493 $settings = get_option('_fluentform_global_form_settings');
494 $cleanTalkSettings = get_option('_fluentform_cleantalk_details');
495
496 switch ($source) {
497 case 'Akismet':
498 return $settings &&
499 'yes' == Arr::get($settings, 'misc.akismet_status') &&
500 'mark_as_spam_and_skip_processing' == Arr::get($settings, 'misc.akismet_validation');
501 case 'CleanTalk':
502 return $settings &&
503 'yes' == Arr::get($settings, 'misc.cleantalk_status') &&
504 'mark_as_spam_and_skip_processing' == Arr::get($settings, 'misc.cleantalk_validation');
505 case 'CleanTalk API':
506 return Arr::get($cleanTalkSettings, 'status') &&
507 'mark_as_spam_and_skip_processing' == Arr::get($cleanTalkSettings, 'validation');
508 default:
509 return false;
510 }
511 }
512
513 /**
514 * Validates Submission
515 * @throws ValidationException
516 */
517 private function handleValidation()
518 {
519 /* Now validate the data using the previous validations. */
520 $this->validationService->setForm($this->form);
521 $this->validationService->setFormData($this->formData);
522
523 $this->validationService->validateSubmission($this->fields, $this->formData);
524 $hasSpam = false;
525 $spamFrom = [];
526
527 if ($this->validationService->isAkismetSpam($this->formData, $this->form)) {
528 $hasSpam = true;
529 $this->validationService->handleAkismetSpamError();
530 $spamFrom[] = 'Akismet';
531 }
532
533 if ($this->validationService->isCleanTalkSpam($this->formData, $this->form)) {
534 $hasSpam = true;
535 $this->validationService->handleCleanTalkSpamError();
536 $spamFrom[] = 'CleanTalk';
537 }
538
539 if ($this->validationService->isCleanTalkSpamUsingApi($this->formData, $this->form)) {
540 $hasSpam = true;
541 $this->validationService->handleCleanTalkSpamErrorUsingAPi();
542 $spamFrom[] = 'CleanTalk API';
543 unset($this->formData['ff_ct_form_load_time'], $this->formData['ct_bot_detector_event_token']);
544 }
545
546 $insertData = $this->prepareInsertData();
547 if ($hasSpam) {
548 $insertData['status'] = 'spam';
549 $insertData['spam_from'] = $spamFrom;
550 }
551
552 return $insertData;
553 }
554
555 protected function insertSubmission($insertData, $formDataRaw, $formId)
556 {
557 do_action_deprecated(
558 'fluentform_before_insert_submission',
559 [
560 $insertData,
561 $formDataRaw,
562 $this->form
563 ],
564 FLUENTFORM_FRAMEWORK_UPGRADE,
565 'fluentform/before_insert_submission',
566 'Use fluentform/before_insert_submission instead of fluentform_before_insert_submission.'
567 );
568
569 do_action('fluentform/before_insert_submission', $insertData, $formDataRaw, $this->form);
570
571 if ($this->form->has_payment) {
572 do_action_deprecated(
573 'fluentform_before_insert_payment_form',
574 [
575 $insertData,
576 $formDataRaw,
577 $this->form
578 ],
579 FLUENTFORM_FRAMEWORK_UPGRADE,
580 'fluentform/before_insert_payment_form',
581 'Use fluentform/before_insert_payment_form instead of fluentform_before_insert_payment_form.'
582 );
583 do_action('fluentform/before_insert_payment_form', $insertData, $formDataRaw, $this->form);
584 }
585
586 // Check if we already have an inserted ID from spam processing
587 if (isset($this->alreadyInsertedId) && $this->alreadyInsertedId) {
588 return $this->alreadyInsertedId;
589 }
590
591 $insertId = Submission::insertGetId($insertData);
592
593 do_action('fluentform/notify_on_form_submit', $insertId, $this->formData, $this->form);
594
595 $uidHash = md5(wp_generate_uuid4() . $insertId);
596 Helper::setSubmissionMeta($insertId, '_entry_uid_hash', $uidHash, $formId);
597
598 return $insertId;
599 }
600
601 private function processSpamSubmission($insertId, $type)
602 {
603 $uidHash = md5(wp_generate_uuid4() . $insertId);
604 Helper::setSubmissionMeta($insertId, '_entry_uid_hash', $uidHash, $this->form->id);
605 ob_start();
606 $this->submissionService->recordEntryDetails($insertId, $this->form->id, $this->formData);
607 $isError = ob_get_clean();
608 if ($isError) {
609 SubmissionDetails::migrate();
610 }
611 Helper::setSubmissionMeta($insertId, 'is_form_action_fired', 'yes');
612 do_action('fluentform/log_data', [
613 'parent_source_id' => $this->form->id,
614 'source_type' => 'submission_item',
615 'source_id' => $insertId,
616 'component' => $type . ' Integration',
617 'status' => 'info',
618 'title' => __('Skip Submission Processing', 'fluentform'),
619 'description' => __('Submission marked as spammed. And skip all actions processing', 'fluentform')
620 ]);
621 }
622 }
623
624