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

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