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

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