PluginProbe
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder / 3.6.62
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder v3.6.62
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 / Modules / Form / FormHandler.php

FormHandler.php in Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder 3.6.62, at app/Modules/Form/FormHandler.php

596 lines 18.9 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\Modules\Form;
4
5 use FluentForm\App\Databases\Migrations\FormSubmissionDetails;
6 use FluentForm\App\Helpers\Helper;
7 use FluentForm\App\Modules\Activator;
8 use FluentForm\App\Modules\Entries\Entries;
9 use FluentForm\App\Modules\ReCaptcha\ReCaptcha;
10 use FluentForm\App\Services\Browser\Browser;
11 use FluentForm\App\Services\FormBuilder\ShortCodeParser;
12 use FluentForm\Framework\Foundation\Application;
13 use FluentForm\Framework\Helpers\ArrayHelper as Arr;
14 use FluentForm\Framework\Helpers\ArrayHelper;
15
16 class FormHandler
17 {
18 /**
19 * @var \FluentForm\Framework\Foundation\Application
20 */
21 protected $app;
22
23 /**
24 * @var \FluentForm\Framework\Request\Request
25 */
26 protected $request;
27
28 /**
29 * @var array $formData
30 */
31 protected $formData;
32
33 /**
34 * The fluent form object.
35 *
36 * @var \stdClass
37 */
38 protected $form;
39
40 /**
41 * Form Handler constructor.
42 *
43 * @param \FluentForm\Framework\Foundation\Application $app
44 */
45 public function __construct(Application $app)
46 {
47 $this->app = $app;
48 $this->request = $app->request;
49 }
50
51 /**
52 * Set the form using it's ID.
53 *
54 * @param $formId
55 * @return $this
56 */
57 public function setForm($formId)
58 {
59 $this->form = wpFluent()->table('fluentform_forms')->find($formId);
60 return $this;
61 }
62
63 /**
64 * Handle form submition
65 */
66 public function onSubmit()
67 {
68 // Parse the url encoded data from the request object.
69 parse_str($this->app->request->get('data'), $data);
70
71 $data['_wp_http_referer'] = urldecode( $data['_wp_http_referer']);
72
73 // Merge it back again to the request object.
74 $this->app->request->merge(['data' => $data]);
75
76 $formId = intval($this->app->request->get('form_id'));
77
78 $this->setForm($formId);
79
80 // Parse the form and get the flat inputs with validations.
81 $fields = FormFieldsParser::getInputs($this->form, ['rules', 'raw']);
82
83 // Sanitize the data properly.
84 $this->formData = fluentFormSanitizer($data, null, $fields);
85
86 // Now validate the data using the previous validations.
87 $this->validate($fields);
88
89 // Prepare the data to be inserted to the DB.
90 $insertData = $this->prepareInsertData();
91
92 if ($this->isSpam($this->formData, $this->form)) {
93 $insertData['status'] = 'spam';
94 $this->handleSpamError();
95 }
96
97 do_action('fluentform_before_insert_submission', $insertData, $data, $this->form);
98
99 if ($this->form->has_payment) {
100 do_action('fluentform_before_insert_payment_form', $insertData, $data, $this->form);
101 }
102
103 $insertId = wpFluent()->table('fluentform_submissions')->insert($insertData);
104
105 $uidHash = md5(wp_generate_uuid4() . $insertId);
106 Helper::setSubmissionMeta($insertId, '_entry_uid_hash', $uidHash, $formId);
107
108 do_action('fluentform_before_form_actions_processing', $insertId, $this->formData, $this->form);
109
110 $result = $this->processFormSubmissionData($insertId, $this->formData, $this->form);
111
112 wp_send_json_success($result, 200);
113 }
114
115 public function processFormSubmissionData($insertId, $formData, $form)
116 {
117 if ($insertId) {
118 ob_start();
119 $entries = new Entries();
120 $entries->recordEntryDetails($insertId, $form->id, $formData);
121 $isError = ob_get_clean();
122 if ($isError) {
123 FormSubmissionDetails::migrate();
124 }
125 }
126
127 $returnData = $this->getReturnData($insertId, $form, $formData);
128
129 $error = '';
130 try {
131 $this->app->doAction(
132 'fluentform_submission_inserted',
133 $insertId,
134 $formData,
135 $form
136 );
137
138 Helper::setSubmissionMeta($insertId, 'is_form_action_fired', 'yes');
139
140 $this->app->doAction(
141 'fluentform_submission_inserted_' . $form->type . '_form',
142 $insertId,
143 $formData,
144 $form
145 );
146 } catch (\Exception $e) {
147 if (defined('WP_DEBUG') && WP_DEBUG) {
148 $error = $e->getMessage();
149 }
150 }
151
152 do_action('fluenform_before_submission_confirmation', $insertId, $formData, $form);
153
154 return [
155 'insert_id' => $insertId,
156 'result' => $returnData,
157 'error' => $error
158 ];
159 }
160
161 public function getReturnData($insertId, $form, $formData)
162 {
163 if (empty($form->settings)) {
164 $formSettings = wpFluent()->table('fluentform_form_meta')
165 ->where('form_id', $form->id)
166 ->where('meta_key', 'formSettings')
167 ->first();
168
169 $form->settings = $formSettings ? json_decode($formSettings->value, true) : [];
170 }
171
172 $confirmation = apply_filters(
173 'fluentform_form_submission_confirmation',
174 $form->settings['confirmation'],
175 $formData,
176 $form
177 );
178
179 if ($confirmation['redirectTo'] == 'samePage') {
180
181 $confirmation['messageToShow'] = apply_filters('fluentform_submission_message_parse', $confirmation['messageToShow'], $insertId, $formData, $form);
182
183
184 $message = ShortCodeParser::parse(
185 $confirmation['messageToShow'],
186 $insertId,
187 $formData,
188 $form
189 );
190
191
192 $message = $message ? $message : 'The form has been successfully submitted.';
193
194 $returnData = [
195 'message' => do_shortcode($message),
196 'action' => $confirmation['samePageFormBehavior'],
197 ];
198
199 } else {
200 $redirectUrl = Arr::get($confirmation, 'customUrl');
201
202 if ($confirmation['redirectTo'] == 'customPage') {
203 $redirectUrl = get_permalink($confirmation['customPage']);
204 }
205
206 if (
207 (Arr::get($confirmation, 'enable_query_string') == 'yes') &&
208 Arr::get($confirmation, 'query_strings')
209 ) {
210 if (strpos($redirectUrl, '?')) {
211 $redirectUrl .= '&' . Arr::get($confirmation, 'query_strings');
212 } else {
213 $redirectUrl .= '?' . Arr::get($confirmation, 'query_strings');
214 }
215 }
216
217 $isUrlParser = apply_filters('fluentform_will_parse_url_value', true, $form);
218
219 $redirectUrl = ShortCodeParser::parse(
220 $redirectUrl,
221 $insertId,
222 $formData,
223 $form,
224 $isUrlParser
225 );
226
227 if($isUrlParser) {
228 /*
229 * For Empty Redirect Value
230 */
231 if(strpos($redirectUrl, '=&') || substr($redirectUrl, -1) == '=') {
232 $urlArray = explode('?', $redirectUrl);
233 $baseUrl = array_shift($urlArray);
234
235 $query = wp_parse_url($redirectUrl)['query'];
236
237 $queryParams = explode('&', $query);
238
239 $params = [];
240 foreach ($queryParams as $queryParam) {
241 $paramArray = explode('=', $queryParam);
242 if(!empty($paramArray[1])) {
243 $params[$paramArray[0]] = $paramArray[1];
244 }
245 }
246
247 $redirectUrl = add_query_arg($params, $baseUrl);
248 }
249 }
250
251 $message = ShortCodeParser::parse(
252 ArrayHelper::get($confirmation, 'redirectMessage', ''),
253 $insertId,
254 $formData,
255 $form
256 );
257
258 $returnData = [
259 'redirectUrl' => wp_sanitize_redirect($redirectUrl),
260 'message' => $message
261 ];
262 }
263
264 return $this->app->applyFilters(
265 'fluentform_submission_confirmation',
266 $returnData,
267 $form,
268 $confirmation
269 );
270 }
271
272 /**
273 * Validate form data.
274 *
275 * @param $fields
276 * @return bool
277 */
278 private function validate(&$fields)
279 {
280 $this->validateRestrictions($fields);
281
282 $this->validateNonce();
283
284 $this->validateReCaptcha();
285
286 foreach ($fields as $fieldName => $field) {
287 if(isset($this->formData[$fieldName])) {
288 $element = $field['element'];
289 $this->formData[$fieldName] = apply_filters('fluentform_input_data_'.$element, $this->formData[$fieldName], $field, $this->formData);
290 }
291 }
292
293 $originalValidations = FormFieldsParser::getValidations($this->form, $this->formData, $fields);
294
295 // Fire an event so that one can hook into it to work with the rules & messages.
296 $validations = apply_filters('fluentform_validations', $originalValidations, $this->form, $this->formData);
297
298 /*
299 * Clean talk fix for now
300 * They should not hook fluentform_validations and return nothing!
301 * We will remove this extra check once it's done
302 */
303 if ($originalValidations && (!$validations || !array_filter($validations))) {
304 $validations = $originalValidations;
305 }
306
307 $validator = \FluentValidator\Validator::make($this->formData, $validations[0], $validations[1]);
308
309 $errors = [];
310 if ($validator->validate()->fails()) {
311 foreach ($validator->errors() as $attribute => $rules) {
312 $position = strpos($attribute, ']');
313
314 if ($position) {
315 $attribute = substr($attribute, 0, strpos($attribute, ']') + 1);
316 }
317
318 $errors[$attribute] = $rules;
319 }
320 // Fire an event so that one can hook into it to work with the errors.
321 $errors = $this->app->applyFilters('fluentform_validation_error', $errors, $this->form, $fields, $this->formData);
322 }
323
324 foreach ($fields as $fieldKey => $field) {
325 $field['data_key'] = $fieldKey;
326 $inputName = \FluentForm\Framework\Helpers\ArrayHelper::get($field, 'raw.attributes.name');
327 $field['name'] = $inputName;
328 $error = apply_filters('fluentform_validate_input_item_' . $field['element'], '', $field, $this->formData, $fields, $this->form, $errors);
329 if ($error) {
330 if (empty($errors[$inputName])) {
331 $errors[$inputName] = [];
332 }
333
334 if(is_string($error)) {
335 $error = [$error];
336 }
337
338 $errors[$inputName] = array_merge($error, $errors[$inputName]);
339 }
340 }
341
342 $errors = apply_filters('fluentform_validation_errors', $errors, $this->formData, $this->form, $fields);
343
344 if(Helper::getFormMeta($this->form->id, '_has_user_registration') == 'yes') {
345 $errors = apply_filters('fluentform_validation_user_registration_errors', $errors, $this->formData, $this->form, $fields);
346 }
347
348 if ($errors) {
349 wp_send_json(['errors' => $errors], 422);
350 }
351
352 return true;
353 }
354
355 /**
356 * Validate nonce.
357 */
358 protected function validateNonce()
359 {
360 $formId = $this->form->id;
361
362 $shouldVerifyNonce = $this->app->applyFilters('fluentform_nonce_verify', false, $formId);
363
364 if ($shouldVerifyNonce) {
365 $nonce = Arr::get($this->formData, '_fluentform_' . $formId . '_fluentformnonce');
366 if (!wp_verify_nonce($nonce, 'fluentform-submit-form')) {
367 $errors = $this->app->applyFilters('fluentForm_nonce_error', [
368 '_fluentformnonce' => [
369 __('Nonce verification failed, please try again.', 'fluentform')
370 ]
371 ]);
372 wp_send_json(['errors' => $errors], 422);
373 }
374 }
375 }
376
377 protected function handleSpamError()
378 {
379 $settings = get_option('_fluentform_global_form_settings');
380 if (!$settings || ArrayHelper::get($settings, 'misc.akismet_validation') != 'validation_failed') {
381 return;
382 }
383
384 $errors = [
385 '_fluentformakismet' => __('Submission marked as spammed. Please try again', 'fluentform')
386 ];
387
388 wp_send_json(['errors' => $errors], 422);
389 }
390
391 protected function isSpam($formData, $form)
392 {
393 if (!AkismetHandler::isEnabled()) {
394 return false;
395 }
396
397 $isSpamCheck = apply_filters('fluentform_akismet_check_spam', true, $form->id, $formData);
398 if (!$isSpamCheck) {
399 return false;
400 }
401 // Let's validate now
402 $isSpam = AkismetHandler::isSpamSubmission($formData, $form);
403
404 return apply_filters('fluentform_akismet_spam_result', $isSpam, $form->id, $formData);
405 }
406
407 /**
408 * Validate reCaptcha.
409 */
410 private function validateReCaptcha()
411 {
412 if (FormFieldsParser::hasElement($this->form, 'recaptcha')) {
413 $isValid = ReCaptcha::validate(Arr::get($this->formData, 'g-recaptcha-response'));
414
415 if (!$isValid) {
416 wp_send_json([
417 'errors' => [
418 'g-recaptcha-response' => [
419 __('reCaptcha verification failed, please try again.', 'fluentform')
420 ]
421 ]
422 ], 422);
423 }
424 }
425 }
426
427 /**
428 * Validate form data based on the form restrictions settings.
429 *
430 * @param $fields
431 */
432 private function validateRestrictions(&$fields)
433 {
434 $formSettings = wpFluent()->table('fluentform_form_meta')
435 ->where('form_id', $this->form->id)
436 ->where('meta_key', 'formSettings')
437 ->first();
438
439 $this->form->settings = $formSettings ? json_decode($formSettings->value, true) : [];
440
441 $isAllowed = [
442 'status' => true,
443 'message' => ''
444 ];
445
446 // This will check the following restriction settings.
447 // 1. limitNumberOfEntries
448 // 2. scheduleForm
449 // 3. requireLogin
450 $isAllowed = apply_filters('fluentform_is_form_renderable', $isAllowed, $this->form);
451
452 if (!$isAllowed['status']) {
453 wp_send_json([
454 'errors' => [
455 'restricted' => [
456 $isAllowed['message']
457 ]
458 ]
459 ], 422);
460 }
461
462 // Since we are here, we should now handle if the form should be allowed to submit empty.
463 $restrictions = Arr::get($this->form->settings, 'restrictions.denyEmptySubmission', []);
464
465 $this->handleDenyEmptySubmission($restrictions, $fields);
466 }
467
468 /**
469 * Handle response when empty form submission is not allowed.
470 *
471 * @param array $settings
472 * @param $fields
473 */
474 private function handleDenyEmptySubmission($settings = [], &$fields)
475 {
476 // Determine whether empty form submission is allowed or not.
477 if (Arr::get($settings, 'enabled')) {
478 // confirm this form has no required fields.
479 if (!FormFieldsParser::hasRequiredFields($this->form, $fields)) {
480 // Filter out the form data which doesn't have values.
481 $filteredFormData = array_filter(
482 // Filter out the other meta fields that aren't actual inputs.
483 array_intersect_key($this->formData, $fields)
484 );
485
486 // TODO: Extract this function into global functions file...
487 $arrayFilterRecursive = function ($array) use (&$arrayFilterRecursive) {
488 foreach ($array as $key => $item) {
489 is_array($item) && $array[$key] = $arrayFilterRecursive($item);
490 if (empty($array[$key])) {
491 unset($array[$key]);
492 }
493 }
494 return $array;
495 };
496
497 if (!count($arrayFilterRecursive($filteredFormData))) {
498 wp_send_json([
499 'errors' => [
500 'restricted' => [
501 __(
502 !($m = Arr::get($settings, 'message'))
503 ? 'Sorry! You can\'t submit an empty form.'
504 : $m,
505 'fluentform'
506 )
507 ]
508 ]
509 ], 422);
510 }
511 }
512 }
513 }
514
515 /**
516 * Prepare the data to be inserted to the database.
517 *
518 * @param boolean $formData
519 * @return array
520 */
521 public function prepareInsertData($formData = false)
522 {
523 $formId = $this->form->id;
524
525 if (!$formData) {
526 $formData = $this->formData;
527 }
528
529 $previousItem = wpFluent()->table('fluentform_submissions')
530 ->where('form_id', $formId)
531 ->orderBy('id', 'DESC')
532 ->first();
533
534 $serialNumber = 1;
535
536 if ($previousItem) {
537 $serialNumber = $previousItem->serial_number + 1;
538 }
539
540 $browser = new Browser;
541
542 $inputConfigs = FormFieldsParser::getEntryInputs($this->form, array('admin_label', 'raw'));
543
544 $this->formData = apply_filters('fluentform_insert_response_data', $formData, $formId, $inputConfigs);
545
546 $ipAddress = $this->app->request->getIp();
547
548 if ((defined('FLUENTFROM_DISABLE_IP_LOGGING') && FLUENTFROM_DISABLE_IP_LOGGING) || apply_filters('fluentform_disable_ip_logging', false, $formId)) {
549 $ipAddress = false;
550 }
551
552 $response = [
553 'form_id' => $formId,
554 'serial_number' => $serialNumber,
555 'response' => json_encode($this->formData),
556 'source_url' => site_url(Arr::get($formData, '_wp_http_referer')),
557 'user_id' => get_current_user_id(),
558 'browser' => $browser->getBrowser(),
559 'device' => $browser->getPlatform(),
560 'ip' => $ipAddress,
561 'created_at' => current_time('mysql'),
562 'updated_at' => current_time('mysql')
563 ];
564
565
566 return apply_filters('fluentform_filter_insert_data', $response);
567 }
568
569 /**
570 * Delegate the validation rules & messages to the
571 * ones that the validation library recognizes.
572 *
573 * @param $rules
574 * @param $messages
575 * @return array
576 */
577 protected function delegateValidations($rules, $messages, $search = [], $replace = [])
578 {
579 $search = $search ?: ['max_file_size', 'allowed_file_types'];
580 $replace = $replace ?: ['max', 'mimes'];
581
582 foreach ($rules as &$rule) {
583 $rule = str_replace($search, $replace, $rule);
584 }
585
586 foreach ($messages as $key => $message) {
587 $newKey = str_replace($search, $replace, $key);
588 $messages[$newKey] = $message;
589 unset($messages[$key]);
590 }
591
592 return [$rules, $messages];
593 }
594
595 }
596