PluginProbe
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder / 3.6.60
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder v3.6.60
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.60, at app/Modules/Form/FormHandler.php

598 lines 19.0 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 $redirectUrl = esc_url_raw($redirectUrl);
229
230 /*
231 * For Empty Redirect Value
232 */
233 if(strpos($redirectUrl, '=&') || substr($redirectUrl, -1) == '=') {
234 $urlArray = explode('?', $redirectUrl);
235 $baseUrl = array_shift($urlArray);
236
237 $query = wp_parse_url($redirectUrl)['query'];
238
239 $queryParams = explode('&', $query);
240
241 $params = [];
242 foreach ($queryParams as $queryParam) {
243 $paramArray = explode('=', $queryParam);
244 if(!empty($paramArray[1])) {
245 $params[$paramArray[0]] = $paramArray[1];
246 }
247 }
248
249 $redirectUrl = add_query_arg($params, $baseUrl);
250 }
251 }
252
253 $message = ShortCodeParser::parse(
254 ArrayHelper::get($confirmation, 'redirectMessage', ''),
255 $insertId,
256 $formData,
257 $form
258 );
259
260 $returnData = [
261 'redirectUrl' => $redirectUrl,
262 'message' => $message
263 ];
264 }
265
266 return $this->app->applyFilters(
267 'fluentform_submission_confirmation',
268 $returnData,
269 $form,
270 $confirmation
271 );
272 }
273
274 /**
275 * Validate form data.
276 *
277 * @param $fields
278 * @return bool
279 */
280 private function validate(&$fields)
281 {
282 $this->validateRestrictions($fields);
283
284 $this->validateNonce();
285
286 $this->validateReCaptcha();
287
288 foreach ($fields as $fieldName => $field) {
289 if(isset($this->formData[$fieldName])) {
290 $element = $field['element'];
291 $this->formData[$fieldName] = apply_filters('fluentform_input_data_'.$element, $this->formData[$fieldName], $field, $this->formData);
292 }
293 }
294
295 $originalValidations = FormFieldsParser::getValidations($this->form, $this->formData, $fields);
296
297 // Fire an event so that one can hook into it to work with the rules & messages.
298 $validations = apply_filters('fluentform_validations', $originalValidations, $this->form, $this->formData);
299
300 /*
301 * Clean talk fix for now
302 * They should not hook fluentform_validations and return nothing!
303 * We will remove this extra check once it's done
304 */
305 if ($originalValidations && (!$validations || !array_filter($validations))) {
306 $validations = $originalValidations;
307 }
308
309 $validator = \FluentValidator\Validator::make($this->formData, $validations[0], $validations[1]);
310
311 $errors = [];
312 if ($validator->validate()->fails()) {
313 foreach ($validator->errors() as $attribute => $rules) {
314 $position = strpos($attribute, ']');
315
316 if ($position) {
317 $attribute = substr($attribute, 0, strpos($attribute, ']') + 1);
318 }
319
320 $errors[$attribute] = $rules;
321 }
322 // Fire an event so that one can hook into it to work with the errors.
323 $errors = $this->app->applyFilters('fluentform_validation_error', $errors, $this->form, $fields, $this->formData);
324 }
325
326 foreach ($fields as $fieldKey => $field) {
327 $field['data_key'] = $fieldKey;
328 $inputName = \FluentForm\Framework\Helpers\ArrayHelper::get($field, 'raw.attributes.name');
329 $field['name'] = $inputName;
330 $error = apply_filters('fluentform_validate_input_item_' . $field['element'], '', $field, $this->formData, $fields, $this->form, $errors);
331 if ($error) {
332 if (empty($errors[$inputName])) {
333 $errors[$inputName] = [];
334 }
335
336 if(is_string($error)) {
337 $error = [$error];
338 }
339
340 $errors[$inputName] = array_merge($error, $errors[$inputName]);
341 }
342 }
343
344 $errors = apply_filters('fluentform_validation_errors', $errors, $this->formData, $this->form, $fields);
345
346 if(Helper::getFormMeta($this->form->id, '_has_user_registration') == 'yes') {
347 $errors = apply_filters('fluentform_validation_user_registration_errors', $errors, $this->formData, $this->form, $fields);
348 }
349
350 if ($errors) {
351 wp_send_json(['errors' => $errors], 422);
352 }
353
354 return true;
355 }
356
357 /**
358 * Validate nonce.
359 */
360 protected function validateNonce()
361 {
362 $formId = $this->form->id;
363
364 $shouldVerifyNonce = $this->app->applyFilters('fluentform_nonce_verify', false, $formId);
365
366 if ($shouldVerifyNonce) {
367 $nonce = Arr::get($this->formData, '_fluentform_' . $formId . '_fluentformnonce');
368 if (!wp_verify_nonce($nonce, 'fluentform-submit-form')) {
369 $errors = $this->app->applyFilters('fluentForm_nonce_error', [
370 '_fluentformnonce' => [
371 __('Nonce verification failed, please try again.', 'fluentform')
372 ]
373 ]);
374 wp_send_json(['errors' => $errors], 422);
375 }
376 }
377 }
378
379 protected function handleSpamError()
380 {
381 $settings = get_option('_fluentform_global_form_settings');
382 if (!$settings || ArrayHelper::get($settings, 'misc.akismet_validation') != 'validation_failed') {
383 return;
384 }
385
386 $errors = [
387 '_fluentformakismet' => __('Submission marked as spammed. Please try again', 'fluentform')
388 ];
389
390 wp_send_json(['errors' => $errors], 422);
391 }
392
393 protected function isSpam($formData, $form)
394 {
395 if (!AkismetHandler::isEnabled()) {
396 return false;
397 }
398
399 $isSpamCheck = apply_filters('fluentform_akismet_check_spam', true, $form->id, $formData);
400 if (!$isSpamCheck) {
401 return false;
402 }
403 // Let's validate now
404 $isSpam = AkismetHandler::isSpamSubmission($formData, $form);
405
406 return apply_filters('fluentform_akismet_spam_result', $isSpam, $form->id, $formData);
407 }
408
409 /**
410 * Validate reCaptcha.
411 */
412 private function validateReCaptcha()
413 {
414 if (FormFieldsParser::hasElement($this->form, 'recaptcha')) {
415 $isValid = ReCaptcha::validate(Arr::get($this->formData, 'g-recaptcha-response'));
416
417 if (!$isValid) {
418 wp_send_json([
419 'errors' => [
420 'g-recaptcha-response' => [
421 __('reCaptcha verification failed, please try again.', 'fluentform')
422 ]
423 ]
424 ], 422);
425 }
426 }
427 }
428
429 /**
430 * Validate form data based on the form restrictions settings.
431 *
432 * @param $fields
433 */
434 private function validateRestrictions(&$fields)
435 {
436 $formSettings = wpFluent()->table('fluentform_form_meta')
437 ->where('form_id', $this->form->id)
438 ->where('meta_key', 'formSettings')
439 ->first();
440
441 $this->form->settings = $formSettings ? json_decode($formSettings->value, true) : [];
442
443 $isAllowed = [
444 'status' => true,
445 'message' => ''
446 ];
447
448 // This will check the following restriction settings.
449 // 1. limitNumberOfEntries
450 // 2. scheduleForm
451 // 3. requireLogin
452 $isAllowed = apply_filters('fluentform_is_form_renderable', $isAllowed, $this->form);
453
454 if (!$isAllowed['status']) {
455 wp_send_json([
456 'errors' => [
457 'restricted' => [
458 $isAllowed['message']
459 ]
460 ]
461 ], 422);
462 }
463
464 // Since we are here, we should now handle if the form should be allowed to submit empty.
465 $restrictions = Arr::get($this->form->settings, 'restrictions.denyEmptySubmission', []);
466
467 $this->handleDenyEmptySubmission($restrictions, $fields);
468 }
469
470 /**
471 * Handle response when empty form submission is not allowed.
472 *
473 * @param array $settings
474 * @param $fields
475 */
476 private function handleDenyEmptySubmission($settings = [], &$fields)
477 {
478 // Determine whether empty form submission is allowed or not.
479 if (Arr::get($settings, 'enabled')) {
480 // confirm this form has no required fields.
481 if (!FormFieldsParser::hasRequiredFields($this->form, $fields)) {
482 // Filter out the form data which doesn't have values.
483 $filteredFormData = array_filter(
484 // Filter out the other meta fields that aren't actual inputs.
485 array_intersect_key($this->formData, $fields)
486 );
487
488 // TODO: Extract this function into global functions file...
489 $arrayFilterRecursive = function ($array) use (&$arrayFilterRecursive) {
490 foreach ($array as $key => $item) {
491 is_array($item) && $array[$key] = $arrayFilterRecursive($item);
492 if (empty($array[$key])) {
493 unset($array[$key]);
494 }
495 }
496 return $array;
497 };
498
499 if (!count($arrayFilterRecursive($filteredFormData))) {
500 wp_send_json([
501 'errors' => [
502 'restricted' => [
503 __(
504 !($m = Arr::get($settings, 'message'))
505 ? 'Sorry! You can\'t submit an empty form.'
506 : $m,
507 'fluentform'
508 )
509 ]
510 ]
511 ], 422);
512 }
513 }
514 }
515 }
516
517 /**
518 * Prepare the data to be inserted to the database.
519 *
520 * @param boolean $formData
521 * @return array
522 */
523 public function prepareInsertData($formData = false)
524 {
525 $formId = $this->form->id;
526
527 if (!$formData) {
528 $formData = $this->formData;
529 }
530
531 $previousItem = wpFluent()->table('fluentform_submissions')
532 ->where('form_id', $formId)
533 ->orderBy('id', 'DESC')
534 ->first();
535
536 $serialNumber = 1;
537
538 if ($previousItem) {
539 $serialNumber = $previousItem->serial_number + 1;
540 }
541
542 $browser = new Browser;
543
544 $inputConfigs = FormFieldsParser::getEntryInputs($this->form, array('admin_label', 'raw'));
545
546 $this->formData = apply_filters('fluentform_insert_response_data', $formData, $formId, $inputConfigs);
547
548 $ipAddress = $this->app->request->getIp();
549
550 if ((defined('FLUENTFROM_DISABLE_IP_LOGGING') && FLUENTFROM_DISABLE_IP_LOGGING) || apply_filters('fluentform_disable_ip_logging', false, $formId)) {
551 $ipAddress = false;
552 }
553
554 $response = [
555 'form_id' => $formId,
556 'serial_number' => $serialNumber,
557 'response' => json_encode($this->formData),
558 'source_url' => site_url(Arr::get($formData, '_wp_http_referer')),
559 'user_id' => get_current_user_id(),
560 'browser' => $browser->getBrowser(),
561 'device' => $browser->getPlatform(),
562 'ip' => $ipAddress,
563 'created_at' => current_time('mysql'),
564 'updated_at' => current_time('mysql')
565 ];
566
567
568 return apply_filters('fluentform_filter_insert_data', $response);
569 }
570
571 /**
572 * Delegate the validation rules & messages to the
573 * ones that the validation library recognizes.
574 *
575 * @param $rules
576 * @param $messages
577 * @return array
578 */
579 protected function delegateValidations($rules, $messages, $search = [], $replace = [])
580 {
581 $search = $search ?: ['max_file_size', 'allowed_file_types'];
582 $replace = $replace ?: ['max', 'mimes'];
583
584 foreach ($rules as &$rule) {
585 $rule = str_replace($search, $replace, $rule);
586 }
587
588 foreach ($messages as $key => $message) {
589 $newKey = str_replace($search, $replace, $key);
590 $messages[$newKey] = $message;
591 unset($messages[$key]);
592 }
593
594 return [$rules, $messages];
595 }
596
597 }
598