PluginProbe
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder / 3.6.2
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder v3.6.2
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 3.6.66 All 195 releases
fluentform / app / Modules / Form / FormHandler.php

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

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