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

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