PluginProbe
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder / 3.1.3
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder v3.1.3
V-3.3.0 3.2.2 3.2.1 3.2.0 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 V3.0.3 V3.0.2 -3.0.1 V_3.0.0 1.1.1 1.1.8 1.2 1.3 1.4 1.4.18 1.5.2 1.9 2.0 2.10.0 2.10.1 2.10.2 All 137 releases
bit-form / includes / Core / Form / FormManager.php

FormManager.php in Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder 3.1.3, at includes/Core/Form/FormManager.php

1,575 lines 59.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Get set Form,fields
5 */
6
7 namespace BitCode\BitForm\Core\Form;
8
9 /**
10 * FrontendFormManager class
11 */
12
13 use BitCode\BitForm\Admin\Form\CustomFieldHandler;
14 use BitCode\BitForm\Admin\Form\Helpers;
15 use BitCode\BitForm\Core\Database\FormEntryLogModel;
16 use BitCode\BitForm\Core\Database\FormEntryMetaModel;
17 use BitCode\BitForm\Core\Database\FormEntryModel;
18 use BitCode\BitForm\Core\Database\FormModel;
19 use BitCode\BitForm\Core\Integration\IntegrationHandler;
20 use BitCode\BitForm\Core\Messages\SuccessMessageHandler;
21 use BitCode\BitForm\Core\Util\FieldValueHandler;
22 use BitCode\BitForm\Core\Util\FileHandler;
23 use BitCode\BitForm\Core\Util\FrontendHelpers;
24 use BitCode\BitForm\Core\Util\IpTool;
25 use BitCode\BitForm\Core\Util\Log;
26 use BitCode\BitForm\Core\WorkFlow\WorkFlow;
27 use BitCode\BitForm\Core\WorkFlow\WorkFlowHandler;
28 use stdClass;
29 use WP_Error;
30
31 class FormManager
32 {
33 // Cache for instances of FormManager by form_id
34 private static $formManagerCache = [];
35 protected static $form;
36 protected $formModel;
37 protected $form_id;
38 private $_has_upload;
39 private $_field_label;
40 private $_fields;
41 private $_repeaterFields;
42 private $_work_flows;
43 private $_conf_messages;
44 private $_atomic_class_map;
45 private $_saveFormAsDraft;
46
47 public function __construct($form_id)
48 {
49 $this->form_id = $form_id;
50 $this->formModel = new FormModel();
51
52 static::$form = $this->formModel->get(
53 [
54 'id',
55 'form_content',
56 'form_name',
57 'created_at',
58 'views',
59 'entries',
60 'status',
61 'builder_helper_state',
62 'atomic_class_map',
63 'generated_script_page_ids',
64 ],
65 [
66 'id' => $form_id,
67 ]
68 );
69 if (!is_wp_error(static::$form)) {
70 $this->_atomic_class_map = json_decode(static::$form[0]->atomic_class_map);
71 $bfMultipleFormsExists = FrontendHelpers::hasMultipleForms();
72 if ($bfMultipleFormsExists && isset($this->_atomic_class_map->atomic_class_map_with_form_id)) {
73 $this->_atomic_class_map = $this->_atomic_class_map->atomic_class_map_with_form_id;
74 } elseif (isset($this->_atomic_class_map->atomic_class_map)) {
75 $this->_atomic_class_map = $this->_atomic_class_map->atomic_class_map;
76 }
77 } else {
78 // Log the error if needed
79 Log::debug_log('Error fetching form: ' . "Form Id = ($form_id)" . static::$form->get_error_message());
80 }
81 }
82
83 // Static method to get the instance of FormManager
84 public static function getInstance($form_id)
85 {
86 // Check if an instance of FormManager is already cached
87 if (!isset(self::$formManagerCache[$form_id])) {
88 // Create and cache the FormManager instance if not found
89 self::$formManagerCache[$form_id] = new self($form_id);
90 }
91
92 // Return the cached instance
93 return self::$formManagerCache[$form_id];
94 }
95
96 public function isExist()
97 {
98 return (!static::$form || is_wp_error(static::$form)) ? false : true;
99 }
100
101 public function checkStatus()
102 {
103 return '1' === static::$form[0]->status ? true : false;
104 }
105
106 public function getFieldsContent()
107 {
108 return self::$form[0]->form_content;
109 }
110
111 public function getFont()
112 {
113 $atomicClassMap = $this->_atomic_class_map;
114 $font = isset($atomicClassMap->font) ? $atomicClassMap->font : '';
115 return $font;
116 }
117
118 public function getStyle()
119 {
120 $builerState = \json_decode(static::$form[0]->builder_helper_state);
121 $style = '';
122 $themeVars = $builerState->themeVars;
123 $themeColors = $builerState->themeColors;
124
125 if (!empty($themeVars)) {
126 $style .= ':root {';
127 foreach ($themeVars->lgLightThemeVars as $key => $value) {
128 $style .= "$key: $value; ";
129 }
130 $style .= '} ';
131 }
132 if (!empty($themeColors)) {
133 $style .= ' :root {';
134 foreach ($themeColors->lightThemeColors as $k => $v) {
135 $style .= "$k:$v; ";
136 }
137 $style .= '} ';
138 }
139
140 $field = $builerState->style->lgLightStyles->fields;
141 foreach ($field as $value) {
142 $classes = $value->classes;
143 foreach ($classes as $key => $value) {
144 $style .= "{$key} {";
145 foreach ($value as $k => $v) {
146 $style .= "$k:$v; ";
147 }
148 $style .= '} ';
149 }
150 }
151 return $style;
152 }
153
154 public function getCustomStyle()
155 {
156 $customCSSPath = BITFORMS_CONTENT_DIR . DIRECTORY_SEPARATOR . 'form-styles' . DIRECTORY_SEPARATOR . "bitform-custom-{$this->form_id}.css";
157 return FileHandler::readFile($customCSSPath);
158 }
159
160 public function getCustomJS()
161 {
162 $customJsPath = BITFORMS_CONTENT_DIR . DIRECTORY_SEPARATOR . 'form-scripts' . DIRECTORY_SEPARATOR . "bitform-custom-{$this->form_id}.js";
163 return FileHandler::readFile($customJsPath);
164 }
165
166 public function getFormContentWithValue($defaultValues = [])
167 {
168 $form_content = \json_decode(static::$form[0]->form_content);
169 // this filter just use private purpose
170 $form_content->fields = apply_filters('bitform_dynamic_field_filter', $form_content->fields);
171 if (!is_array($defaultValues) || 0 === count($defaultValues)) {
172 return $form_content;
173 }
174 foreach ($form_content->fields as $fieldKey => $fieldDetails) {
175 // $field_name = empty($fieldDetails->lbl) ? null : \preg_replace('/[\`\~\!\@\#\$\'\.\s\?\+\-\*\&\|\/\\!]/', '_', $fieldDetails->lbl);
176 $fieldName = $fieldDetails->fieldName;
177 $defaultValue = isset($defaultValues[$fieldName]) ? $defaultValues[$fieldName] : null;
178 $defaultValue = isset($defaultValues[$fieldKey]) ? $defaultValues[$fieldKey] : $defaultValue;
179 if ((isset($fieldDetails->mul) || 'check' === $fieldDetails->typ) && isset($defaultValue)) {
180 // if (is_array($defaultValue)) {
181 // $fieldDetails->val =
182 // wp_json_encode(
183 // array_map('sanitize_text_field', $defaultValue)
184 // );
185 // } else {
186 // $fieldDetails->val = sanitize_text_field($defaultValue);
187 // }
188 if ((isset($fieldDetails->mul) && true === $fieldDetails->mul) || is_array($defaultValue)) {
189 $fieldDetails->val = wp_json_encode(array_map('sanitize_text_field', $defaultValue));
190 } elseif (!is_array($defaultValue)) {
191 $fieldDetails->val = sanitize_text_field($defaultValue);
192 }
193 } elseif (!is_null($defaultValue)) {
194 $fieldDetails->val = is_string($defaultValue) ?
195 sanitize_text_field($defaultValue) :
196 sanitize_text_field($defaultValue[count($defaultValue) - 1]);
197 }
198 }
199 return $form_content;
200 }
201
202 public function getFormContent()
203 {
204 $formContent = json_decode(static::$form[0]->form_content);
205 $types = ['check', 'radio', 'select'];
206 $filter = false;
207 foreach ($formContent->fields as $field) {
208 if (in_array($field->typ, $types) && property_exists($field, 'customType')) {
209 $filter = true;
210 break; // reduce unnecessary loop
211 }
212 }
213 if (true === $filter) {
214 $updateFields = apply_filters('bitform_dynamic_field_filter', $formContent->fields);
215 $formContent->fields = $updateFields;
216 }
217 return $formContent;
218 }
219
220 public function getFormInfo()
221 {
222 $formContent = json_decode(static::$form[0]->form_content);
223 $formInfo = isset($formContent->formInfo) ? $formContent->formInfo : null;
224 return $formInfo;
225 }
226
227 public function getFormPermission()
228 {
229 $formContent = json_decode(static::$form[0]->form_content);
230 $formPermission = isset($formContent->formPermissions) ? $formContent->formPermissions : null;
231 return $formPermission;
232 }
233
234 public function getFormHelperStates()
235 {
236 $formHelperStates = json_decode(static::$form[0]->builder_helper_state);
237 return $formHelperStates;
238 }
239
240 public function getAtomicClsMap()
241 {
242 return $this->_atomic_class_map;
243 }
244
245 private function is_json($str)
246 {
247 $json = json_decode($str);
248 return $json && $str !== $json;
249 }
250
251 public function getFormData($columnName = '')
252 {
253 if (empty($columnName)) {
254 return null;
255 }
256
257 $form = static::$form[0];
258 if (!isset($form->{$columnName})) {
259 return null;
260 }
261
262 $data = $form->{$columnName};
263 if ($this->is_json($data)) {
264 return json_decode($data);
265 }
266
267 return $data;
268 }
269
270 public function getFormName()
271 {
272 return static::$form[0]->form_name;
273 }
274
275 public function getFormLayout()
276 {
277 $formContent = $this->getFormContent();
278 return $formContent->layout;
279 }
280
281 public function getFormNestedLayout()
282 {
283 $formContent = $this->getFormContent();
284 return $formContent->nestedLayout;
285 }
286
287 private function mergeNestedLayout(&$layout, $nestedLayout)
288 {
289 foreach ($nestedLayout as $key => $brkpnts) {
290 foreach ($brkpnts as $brkpnt=>$nLayout) {
291 $layout->{$brkpnt} = array_merge($layout->{$brkpnt}, $nLayout);
292 }
293 }
294 }
295
296 public function flatMultistepFormLayout()
297 {
298 $formLayout = $this->getFormLayout();
299 $multistepLayout = new stdClass();
300 foreach ($formLayout as $stpLayout) {
301 $lyout = $stpLayout->layout;
302
303 foreach ($lyout as $brkpnt=>$fields) {
304 $multistepLayout->{$brkpnt} = array_merge($multistepLayout->{$brkpnt} ?? [], $fields);
305 }
306 }
307
308 return $multistepLayout;
309 }
310
311 public function getFlatenFormLayout()
312 {
313 $layout = $this->getFormLayout();
314 $nestedLayout = $this->getFormNestedLayout();
315 if ('array' === gettype($layout)) {
316 // multi step form layout
317 $layout = $this->flatMultistepFormLayout();
318 }
319 if (!empty((array) $nestedLayout)) {
320 $this->mergeNestedLayout($layout, $nestedLayout);
321 }
322
323 return $layout;
324 }
325
326 public function getFieldsBasedOnLayout()
327 {
328 $layout = $this->getFlatenFormLayout();
329
330 $fieldKeyOrderbasedOnLayout = array_map(function ($fld) {
331 return $fld->i;
332 }, $layout->lg);
333 $orderedFields = [];
334 $fields = $this->getFields();
335
336 foreach ($fieldKeyOrderbasedOnLayout as $key) {
337 if (array_key_exists($key, $fields)) {
338 $orderedFields[$key] = $fields[$key];
339 }
340 }
341
342 foreach ($fields as $k=>$v) {
343 if (!array_key_exists($k, $fieldKeyOrderbasedOnLayout)) {
344 $orderedFields[$k] = $fields[$k];
345 }
346 }
347
348 return $orderedFields;
349 }
350
351 public function getFields()
352 {
353 if (!is_null($this->_fields)) {
354 return $this->_fields;
355 }
356 $form_content = \json_decode(static::$form[0]->form_content);
357 $layout = $form_content->layout;
358 $fields = $form_content->fields;
359 $field_details = [];
360 foreach ($fields as $key => $field) {
361 if ('recaptcha' === $field->typ || 'hcaptcha' === $field->typ) {
362 continue;
363 }
364 // $field_name = empty($field->lbl) ? null : \preg_replace('/[\`\~\!\@\#\$\'\.\s\?\+\-\*\&\|\/\\\!]/', '_', $field->lbl);
365 $field_type = $field->typ;
366 $field_details[$key]['label'] = !empty($field->lbl) ? $field->lbl : (!empty($field->adminLbl) ? $field->adminLbl : (!empty($field->fieldName) ? $field->fieldName : null));
367 $field_details[$key]['type'] = $field_type;
368 $field_details[$key]['key'] = $key;
369 $field_details[$key]['name'] = isset($field->fieldName) ? $field->fieldName : '';
370 if (isset($field->customType)) {
371 $field_details[$key]['customType'] = $field->customType;
372 }
373 // fields with confirm field
374 if (isset($field->childFields)) {
375 $field_details[$key]['childFields'] = $field->childFields;
376 }
377 if (isset($field->parentFieldKey)) {
378 $field_details[$key]['parentFieldKey'] = $field->parentFieldKey;
379 if (isset($field->isDeactive)) {
380 $field_details[$key]['isDeactive'] = $field->isDeactive;
381 }
382 }
383 if (isset($field->err)) {
384 if (isset($field->err->entryUnique)) {
385 $field_details[$key]['entryUnique'] = $field->err->entryUnique;
386 }
387 if (isset($field->err->userUnique)) {
388 $field_details[$key]['userUnique'] = $field->err->userUnique;
389 }
390 }
391
392 if (isset($field->mul)) {
393 $field_details[$key]['mul'] = $field->mul;
394 }
395 if (in_array($field_type, ['name'])) {
396 $field_details[$key]['label'] = $field->adminLbl ?? $field->lbl;
397 }
398 if ('file-up' === $field_type && isset($field->exts)) {
399 $field_details[$key]['valid']['type'] = $field->exts;
400 }
401 if ('file-up' === $field_type && isset($field->mxUp)) {
402 $field_details[$key]['valid']['upload_size'] = (int) $field->mxUp;
403 }
404 if (isset($field->valid) && !is_null($field->valid)) {
405 if (isset($field->valid->req)) {
406 $field_details[$key]['valid']['req'] = $field->valid->req;
407 }
408 if (isset($field->valid->reqMsg)) {
409 $field_details[$key]['valid']['reqMsg'] = $field->valid->reqMsg;
410 }
411 if (isset($field->valid->typMsg)) {
412 $field_details[$key]['valid']['typMsg'] = $field->valid->typMsg;
413 }
414 if (isset($field->valid->hide)) {
415 $field_details[$key]['valid']['hide'] = $field->valid->hide;
416 }
417 }
418 if ($this->isRepeatedField($key)) {
419 $field_details[$key]['repeated'] = true;
420 }
421 }
422 if ($this->isGCLIDEnabled()) {
423 $field_details['GCLID']['name'] = 'GCLID';
424 $field_details['GCLID']['adminLbl'] = 'GCLID';
425 $field_details['GCLID']['key'] = 'GCLID';
426 $field_details['GCLID']['type'] = 'hidden';
427 }
428 $this->_fields = $field_details;
429 return $field_details;
430 }
431
432 public function getFieldsKey()
433 {
434 $form_content = \json_decode(static::$form[0]->form_content);
435 $fields = $form_content->fields;
436 $field_details = [];
437 foreach ($fields as $key => $field) {
438 if ('recaptcha' === $field->typ || 'hcaptcha' === $field->typ) {
439 continue;
440 }
441 // $field_name = empty($field->lbl) ? null : \preg_replace('/[\`\~\!\@\#\$\'\.\s\?\+\-\*\&\|\/\\\!]/', '_', $field->lbl);
442 $field_details[$key] = $key;
443 }
444 if ($this->isGCLIDEnabled()) {
445 $field_details['GCLID'] = 'GCLID';
446 }
447 return $field_details;
448 }
449
450 public function getFieldLabel($forQuery = false)
451 {
452 if (!is_null($this->_field_label)) {
453 return $this->_field_label;
454 }
455 $form_content = \json_decode(static::$form[0]->form_content);
456 $fields = $form_content->fields;
457 $field_details = [];
458 $fieldCounter = 0;
459 foreach ($fields as $key => $field) {
460 if ('recaptcha' === $field->typ || 'turnstile' === $field->typ || 'html' === $field->typ || 'button' === $field->typ) {
461 continue;
462 }
463 $field_details[$fieldCounter]['name'] = empty($field->lbl) ? null : $field->lbl;
464 $field_details[$fieldCounter]['adminLbl'] = empty($field->adminLbl) ? $field_details[$fieldCounter]['name'] : $field->adminLbl;
465 $field_details[$fieldCounter]['key'] = $key;
466 $field_details[$fieldCounter]['type'] = $field->typ;
467 $fieldCounter += 1;
468 }
469 if ($this->isGCLIDEnabled()) {
470 $field_details[$fieldCounter]['name'] = 'GCLID';
471 $field_details[$fieldCounter]['adminLbl'] = 'GCLID';
472 $field_details[$fieldCounter]['key'] = 'GCLID';
473 $field_details[$fieldCounter]['type'] = 'hidden';
474 $fieldCounter += 1;
475 }
476 if (!$forQuery) {
477 $field_details = (array) $this->addEntryInfo($field_details, $fieldCounter);
478 }
479 $this->_field_label = $field_details;
480 return $field_details;
481 }
482
483 public function getUploadFields()
484 {
485 if (!is_null($this->_has_upload)) {
486 return $this->_has_upload;
487 }
488 $upload_fields = [];
489 $form_field_details = $this->getFields();
490 foreach ($form_field_details as $field_name => $__field_detail) {
491 if (isset($__field_detail['type']) && ('file-up' === $__field_detail['type'] || 'advanced-file-up' === $__field_detail['type'])) {
492 $upload_fields[] = $field_name;
493 }
494 }
495 $this->_has_upload = $upload_fields;
496 return $upload_fields;
497 }
498
499 public function getSignatureFilePath($blobLink, $form_id, $fieldKey, $entry_id, $imgType)
500 {
501 $imgTypes = [
502 'image/png' => 'png',
503 'image/jpeg' => 'jpg',
504 'image/svg+xml' => 'svg',
505 ];
506 try {
507 if (!isset($imgTypes[$imgType])) {
508 throw new \InvalidArgumentException("Unsupported image type: $imgType");
509 }
510 $parts = explode(',', $blobLink, 2);
511 if (2 !== count($parts) || false === ($decoded_image = base64_decode($parts[1]))) {
512 throw new \RuntimeException('Invalid or corrupt signature data URI');
513 }
514
515 $_upload_dir = FileHandler::getEntriesFileUploadDir($form_id, $entry_id);
516 FileHandler::createIndexFile($_upload_dir);
517 $uniqueId = time() . '-' . bin2hex(\random_bytes(4));
518 $filename = "{$entry_id}-{$fieldKey}-{$uniqueId}.{$imgTypes[$imgType]}";
519 $fullPath = $_upload_dir . DIRECTORY_SEPARATOR . $filename;
520 if (false === file_put_contents($fullPath, $decoded_image)) {
521 throw new \RuntimeException("Failed to write image to $fullPath");
522 }
523 return $filename;
524 } catch (\Throwable $e) {
525 Log::debug_log("[Signature Error] Form: $form_id, Entry: $entry_id, Field: $fieldKey - " . $e->getMessage());
526 return 'signature-failed.png'; // or a default filename if appropriate
527 }
528 }
529
530 private function entryInsert($user_details)
531 {
532 $formEntryModel = new FormEntryModel();
533 $entryId = $formEntryModel->insert(
534 [
535 'form_id' => $this->form_id,
536 'user_id' => $user_details['id'],
537 'user_ip' => $user_details['ip'],
538 'user_device' => $user_details['device'],
539 'referer' => $user_details['page'],
540 'status' => $this->_saveFormAsDraft ? 9 : 1,
541 'created_at' => $user_details['time'],
542 ]
543 );
544 return $entryId;
545 }
546
547 public function submisionLog($user_details, $entry_id, $type)
548 {
549 $formEntryLogModel = new FormEntryLogModel();
550 $submissionLogData = [
551 'user_id' => $user_details['id'],
552 'action_type' => $type, // create, update
553 'log_type' => 'entry',
554 'ip' => $user_details['ip'],
555 'form_entry_id' => $entry_id,
556 'content' => ['user_device' => $user_details['device']],
557 'form_id' => $this->form_id,
558 'created_at' => $user_details['time'],
559 ];
560 $submissionLogData = apply_filters('bitform_filter_submission_log_data', $submissionLogData, $this->form_id, $type);
561 $logId = $formEntryLogModel->form_log_insert(
562 $submissionLogData
563 );
564 return $logId;
565 }
566
567 private function isArrayAllKeyInt($InputArray)
568 {
569 if (!is_array($InputArray)) {
570 return false;
571 }
572
573 if (count($InputArray) <= 0) {
574 return true;
575 }
576
577 return array_unique(array_map('is_int', array_keys($InputArray))) === [true];
578 }
579
580 public function formatSubmittedData($submitted_data)
581 {
582 $form_content = $this->getFormContent();
583 $form_fields = $form_content->fields;
584
585 foreach ($submitted_data as $key => $value) {
586 if (!isset($form_fields->{$key})) {
587 continue;
588 }
589 $field_data = $form_fields->{$key};
590 $field_type = $field_data->typ;
591 $normalizedParentValue = $this->normalizeSubmittedValue($value);
592 $parentFieldName = isset($field_data->fieldName) ? $field_data->fieldName : '';
593 if (!empty($field_data->childFields) && is_array($field_data->childFields)) {
594 foreach ($field_data->childFields as $childFieldRef) {
595 $childFieldKey = isset($childFieldRef->fldKey) ? $childFieldRef->fldKey : '';
596 if (empty($childFieldKey) || !isset($form_fields->{$childFieldKey})) {
597 continue;
598 }
599
600 $childFieldData = $form_fields->{$childFieldKey};
601 $childFieldName = isset($childFieldData->fieldName) ? $childFieldData->fieldName : '';
602 $childFieldName = FieldValueHandler::deriveChildName($childFieldName, $parentFieldName);
603 if (empty($childFieldName)) {
604 continue;
605 }
606
607 $childValue = FieldValueHandler::extractChildValueFromParentValue($normalizedParentValue, $childFieldName, $childFieldKey);
608 if (null !== $childValue) {
609 $submitted_data[$childFieldKey] = $childValue;
610 }
611 }
612 }
613
614 if ($this->isRepeatedField($key) && in_array($field_type, ['name', 'address'])) {
615 $submitted_data[$key] = $this->normalizeRepeatedCompositeFieldInput($normalizedParentValue);
616 }
617
618 if ('select' === $field_type && !empty($field_data->config->multipleSelect)) {
619 $valueArr = [];
620 if ($this->isRepeatedField($key) && is_array($normalizedParentValue)) {
621 foreach ($normalizedParentValue as $index => $v) {
622 $valueArr[$index] = explode(BITFORMS_BF_SEPARATOR, $v);
623 }
624 } else {
625 $valueArr = explode(BITFORMS_BF_SEPARATOR, (string) $value);
626 }
627 $submitted_data[$key] = $valueArr;
628 }
629 }
630 $submitted_data = apply_filters('bitform_filter_format_submitted_data', $submitted_data, $this->form_id);
631 return $submitted_data;
632 }
633
634 private function normalizeSubmittedValue($value)
635 {
636 if (!is_string($value)) {
637 return $value;
638 }
639
640 $decoded = json_decode($value, true);
641 return (JSON_ERROR_NONE === json_last_error()) ? $decoded : $value;
642 }
643
644 private function addNewFilePathToFiles($form_id, $entry_id, $file_fields = [])
645 {
646 $common_file_path = Helpers::getFullPathWithEncryptedEntryId($form_id, $entry_id);
647 foreach ($_FILES as $field_key => $file_details) {
648 if (!($file_fields && in_array($field_key, $file_fields))) {
649 continue;
650 }
651
652 $isRepeaterFldKey = $this->isRepeatedField($field_key);
653 if ($isRepeaterFldKey && isset($file_details['new_name'])) {
654 // If 'new_name' is an array (i.e., for repeated fields)
655 foreach ($file_details['new_name'] as $slNo => $newFileNamesArray) {
656 if (is_array($newFileNamesArray)) {
657 foreach ($newFileNamesArray as $newFileName) {
658 $filePath = $common_file_path . DIRECTORY_SEPARATOR . $newFileName;
659 $_FILES[$field_key]['file_path'][$slNo][] = $filePath;
660 }
661 } else {
662 // Generate the file path for each file
663 $filePath = $common_file_path . DIRECTORY_SEPARATOR . $newFileNamesArray;
664 $_FILES[$field_key]['file_path'][$slNo] = $filePath;
665 }
666 }
667 } elseif (isset($file_details['new_name'])) {
668 // If 'new_name' is an array (i.e., for repeated fields)
669 if (is_array($file_details['new_name'])) {
670 foreach ($file_details['new_name'] as $slNo => $newFileName) {
671 // Generate the file path for each file
672 $filePath = $common_file_path . DIRECTORY_SEPARATOR . $newFileName;
673 $_FILES[$field_key]['file_path'][$slNo] = $filePath;
674 }
675 } else {
676 $filePath = $common_file_path . DIRECTORY_SEPARATOR . $file_details['new_name'];
677 $_FILES[$field_key]['file_path'] = $filePath;
678 }
679 }
680 }
681 }
682
683 private function formatRepeateFieldData($submitted_data, $form_fields)
684 {
685 $repeaterFields = $this->getRepeaterFields();
686 foreach ($repeaterFields as $repeaterFldKey => $repeatedFields) {
687 $repeatIndexes = $submitted_data["{$form_fields[$repeaterFldKey]['name']}-repeat-index"];
688 $repeatIndexes = explode(',', $repeatIndexes);
689 foreach ($repeatedFields as $repeatedField) {
690 $oldFileKey = "{$repeatedField}_old";
691 if (isset($submitted_data[$oldFileKey]) && is_array($submitted_data[$oldFileKey])) {
692 $oldFileValues = $submitted_data[$oldFileKey];
693 $oldFileKeys = array_map('strval', array_keys($oldFileValues));
694 $repeatIndexKeys = array_map('strval', $repeatIndexes);
695 $oldFilesUseRepeatIndexes = empty(array_diff($oldFileKeys, $repeatIndexKeys));
696 $normalizedOldFileValues = [];
697
698 foreach ($repeatIndexes as $slNo => $repeatIndex) {
699 $oldFileSourceIndex = $oldFilesUseRepeatIndexes ? $repeatIndex : $slNo;
700 if (array_key_exists($oldFileSourceIndex, $oldFileValues)) {
701 $normalizedOldFileValues[$slNo] = $oldFileValues[$oldFileSourceIndex];
702 }
703 }
704
705 $submitted_data[$oldFileKey] = $normalizedOldFileValues;
706 }
707 }
708
709 foreach ($repeatIndexes as $slNo => $repeatIndex) {
710 foreach ($repeatedFields as $repeatedField) {
711 if (!isset($submitted_data[$repeatedField][$repeatIndex])) {
712 continue;
713 }
714 if (!isset($submitted_data[$repeaterFldKey][$slNo])) {
715 $submitted_data[$repeaterFldKey][$slNo] = [];
716 }
717 if (!isset($submitted_data[$repeaterFldKey][$slNo][$repeatedField])) {
718 $submitted_data[$repeaterFldKey][$slNo][$repeatedField] = [];
719 }
720 $submitted_data[$repeaterFldKey][$slNo][$repeatedField] = $submitted_data[$repeatedField][$repeatIndex];
721 }
722 }
723 foreach ($repeatedFields as $repeatedField) {
724 unset($submitted_data[$repeatedField]);
725 }
726 unset($submitted_data["{$form_fields[$repeaterFldKey]['name']}-repeat-index"]);
727 }
728
729 return $submitted_data;
730 }
731
732 private function saveEntryMeta($submitted_data, $entry_id)
733 {
734 $errorInEntryMetaInsert = false;
735 $entryMeta = new FormEntryMetaModel();
736 foreach ($submitted_data as $key => $value) {
737 $value = $submitted_data[$key];
738 if (is_string($value)) {
739 $value = wp_unslash($value);
740 } elseif ($this->isArrayAllKeyInt($value)) {
741 $value = wp_json_encode(array_values($value));
742 } else {
743 $value = wp_json_encode($value);
744 }
745 // Form entry meta insert; meta_key/meta_value required to store dynamic field data per entry.
746 $status = $entryMeta->insert(
747 [
748 'bitforms_form_entry_id' => $entry_id,
749 'meta_key' => $key,
750 'meta_value' => $value,
751 ]
752 );
753 if (is_wp_error($status)) {
754 $errorInEntryMetaInsert = true;
755 break;
756 }
757 }
758 return $errorInEntryMetaInsert;
759 }
760
761 public function setSaveFormAsDraft()
762 {
763 $this->_saveFormAsDraft = true;
764 }
765
766 public function saveFormEntry($submitted_data)
767 {
768 // CSRF verified upstream via FrontendFormManager::verifySubmissionNonce() before this method is invoked.
769 $submitted_data = $this->formatSubmittedData($submitted_data);
770 $submitted_data = apply_filters('bitform_filter_save_form_entry', $submitted_data, $this->form_id);
771 $form_content = \json_decode(static::$form[0]->form_content);
772 do_action('bitform_save_entry', $this, $submitted_data, $this->form_id);
773 $key = null;
774 $ipTool = new IpTool();
775 $fileHandler = new FileHandler();
776 $form_fields = $this->getFields();
777 $file_fields = $this->getUploadFields();
778
779 foreach ($_FILES as $file_name => $file_details) {
780 if ($file_fields && in_array($file_name, $file_fields)) {
781 $validation = $fileHandler->validation($file_name, $file_details, $this->form_id);
782 if (!empty($validation['error_type']) && !empty($validation['message'])) {
783 return new WP_Error($validation['error_type'], esc_html($validation['message']));
784 }
785 }
786 }
787 $user_details = $ipTool->getUserDetail();
788 $user_details = apply_filters('bitform_filter_user_details', $user_details, $this->form_id);
789 $user_details = apply_filters('bitform_filter_save_entry_user_details', $user_details, $this->form_id);
790
791 $form_fields = $this->getFields();
792 $submitted_data = $this->passwordEncrypted($submitted_data, $form_fields);
793 $submitted_data = $this->formatRepeateFieldData($submitted_data, $form_fields);
794 global $wpdb;
795 // Direct transaction control; no user input involved.
796 $wpdb->query('START TRANSACTION');
797 $entry_id = $this->entryInsert($user_details);
798 $log_id = null;
799
800 $GLOBALS['bitform_entry_id'] = $entry_id;
801
802 if (is_wp_error($entry_id)) {
803 return new WP_Error('insert_error', __('Sorry, Error occurred in saving form entry', 'bit-form'));
804 }
805 if ($entry_id) {
806 $log_id = $this->submisionLog($user_details, $entry_id, 'create', $key);
807 if (is_wp_error($log_id)) {
808 $wpdb->query('ROLLBACK');
809 return new WP_Error('error_entry_log', __('Sorry, error occurred in logging form entry', 'bit-form'));
810 }
811 }
812 if ($entry_id) {
813 $submitted_fields = $this->getFormContentWithValue($submitted_data)->fields;
814 $workFlowRunHelper = new WorkFlow($this->form_id);
815
816 $workFlowreturnedOnSubmit = $workFlowRunHelper->executeOnSubmit(
817 'create',
818 $submitted_fields,
819 $submitted_data,
820 $entry_id,
821 $log_id
822 );
823
824 if (!empty($workFlowreturnedOnSubmit['fields'])) {
825 $submitted_data = $workFlowreturnedOnSubmit['fields'];
826 }
827
828 $file_fields = $this->getUploadFields();
829 $formFields = $this->getFields();
830 $submitted_data = FileHandler::tempDirToUploadDir($submitted_data, $formFields, $this->form_id, $entry_id);
831 $fileHandler = new FileHandler();
832 foreach ($_FILES as $field_key => $file_details) {
833 if ($file_fields && in_array($field_key, $file_fields)) {
834 $fileNames = [];
835 $repeaterFldKey = $this->isRepeatedField($field_key);
836 if ($repeaterFldKey) {
837 foreach ($file_details['name'] as $slNo => $fileName) {
838 $repeateFileDetails = [
839 'name' => $file_details['name'][$slNo],
840 'type' => $file_details['type'][$slNo],
841 'tmp_name' => $file_details['tmp_name'][$slNo],
842 'error' => $file_details['error'][$slNo],
843 'size' => $file_details['size'][$slNo],
844 ];
845 $fileNames = $fileHandler->moveUploadedFiles($repeateFileDetails, $this->form_id, $entry_id);
846 if (!empty($fileNames)) {
847 $submitted_data[$repeaterFldKey][$slNo - 1][$field_key] = $fileNames;
848 $_FILES[$field_key]['new_name'][$slNo - 1] = $fileNames;
849 }
850 }
851 } else {
852 $fileNames = $fileHandler->moveUploadedFiles($file_details, $this->form_id, $entry_id);
853 if (!empty($fileNames)) {
854 $submitted_data[$field_key] = $fileNames;
855 $_FILES[$field_key]['new_name'] = $fileNames;
856 }
857 }
858 }
859 }
860
861 // Get the common path for file storage
862 $this->addNewFilePathToFiles($this->form_id, $entry_id, $file_fields);
863
864 foreach ($form_content->fields as $key => $field) {
865 /* ======== for Signature field ===========*/
866 if ('signature' === $field->typ) {
867 if (isset($submitted_data[$key])) {
868 $fld_data = $submitted_data[$key];
869 $img_type = $field->config->imgTyp;
870 $submitted_data[$key] = $this->getSignatureFilePath($fld_data, $this->form_id, $key, $entry_id, $img_type);
871 }
872 }
873
874 // for Signature field inside reepater
875 if ('repeater' === $field->typ) {
876 $rptr_data = $submitted_data[$key];
877 $formFields = $form_content->fields;
878 $this->setSignatureFilePathInRepeater($rptr_data, $key, $formFields, $entry_id, $submitted_data);
879 }
880 }
881
882 if (!isset($form_content->additional->enabled->submission)) {
883 $errorInEntryMetaInsert = $this->saveEntryMeta($submitted_data, $entry_id);
884 if ($errorInEntryMetaInsert) {
885 do_action('bitform_save_entry_error', $this, $submitted_data, $this->form_id);
886 $wpdb->query('ROLLBACK');
887 return new WP_Error('insert_error', __('Sorry, Error occured in saving form entry data', 'bit-form'));
888 }
889 do_action('bitform_after_save_entry_success', $this, $submitted_data, $this->form_id, $entry_id);
890 } else {
891 $wpdb->query('ROLLBACK');
892 }
893 $wpdb->query('COMMIT');
894 $this->setSubmissionCount();
895 $workFlowreturnedOnSubmit['entry_id'] = $entry_id;
896 $workFlowreturnedOnSubmit['fields'] = $submitted_data;
897 $workFlowreturnedOnSubmit = apply_filters('bitform_filter_return_submit_success', $workFlowreturnedOnSubmit, $this->form_id);
898
899 return $workFlowreturnedOnSubmit;
900 }
901 }
902
903 private function setSignatureFilePathInRepeater($repeaterData, $repeaterFieldKey, $formFields, $entry_id, &$submitted_data)
904 {
905 foreach ($repeaterData as $rptr_entry_index => $rptr_entries) {
906 foreach ($rptr_entries as $entry_key => $entry_value) {
907 $rptr_entry_info = $formFields->{$entry_key};
908
909 if ('signature' === $rptr_entry_info->typ) {
910 $imgType = $rptr_entry_info->config->imgTyp;
911 $signatureImage = $this->getSignatureFilePath($entry_value, $this->form_id, $repeaterFieldKey, $entry_id, $imgType);
912 $submitted_data[$repeaterFieldKey][$rptr_entry_index][$entry_key] = $signatureImage;
913 }
914 }
915 }
916 }
917
918 public function passwordEncrypted($updatedValue, $form_fields)
919 {
920 $integrationHandler = new IntegrationHandler($this->form_id);
921 $formIntegrations = $integrationHandler->getAllIntegration('wp_user_auth', 'wp_auth', 1);
922 if (!isset($formIntegrations->errors['result_empty'])) {
923 foreach ($form_fields as $field) {
924 if (array_key_exists($field['key'], $updatedValue) && 'password' === $field['type']) {
925 $updatedValue[$field['key']] = '**** (encrypted)';
926 }
927 }
928 }
929 return $updatedValue;
930 }
931
932 private function normalizeOldFileValues($stored_files, $old_values)
933 {
934 $stored_files = is_array($stored_files) ? $stored_files : [];
935 if (!is_array($old_values)) {
936 $old_values_string = trim((string) $old_values);
937 $decoded_old_values = json_decode($old_values_string, true);
938 $old_values = is_array($decoded_old_values) ? $decoded_old_values : explode(',', $old_values_string);
939 }
940
941 $normalized_values = [];
942 foreach ($old_values as $value) {
943 if (!is_string($value) && !is_numeric($value)) {
944 continue;
945 }
946
947 $trimmed_value = trim((string) $value);
948 if ('' === $trimmed_value) {
949 continue;
950 }
951
952 if (in_array($trimmed_value, $stored_files, true)) {
953 $normalized_values[] = $trimmed_value;
954 }
955 }
956
957 return array_values(array_unique($normalized_values));
958 }
959
960 public function updateFormEntry($updatedValue, $formID, $entryID)
961 {
962 // CSRF / entry-token verified upstream via FrontendFormManager::handleUpdateEntry() before this method is invoked.
963 $updatedValue = $this->formatSubmittedData($updatedValue);
964 $updatedValue = apply_filters('bitform_filter_update_form_entry', $updatedValue, $this->form_id);
965 do_action('bitform_update_entry', $this, $updatedValue, $formID, $entryID);
966 $form_content = $this->getFormContent();
967 if (isset($form_content->additional->enabled->submission)) {
968 // Run workflow but skip DB/meta update
969 $workFlowRunHelper = new WorkFlow($formID);
970 $fieldsWithValue = $this->getFormContentWithValue($updatedValue)->fields;
971 $workFlowreturnedOnSubmit = $workFlowRunHelper->executeOnSubmit(
972 'edit',
973 $fieldsWithValue,
974 $updatedValue,
975 $entryID,
976 0
977 );
978 if (empty($workFlowreturnedOnSubmit['message'])) {
979 $workFlowreturnedOnSubmit['message'] = __('Entry update skipped due to submission restriction.', 'bit-form');
980 }
981 $workFlowreturnedOnSubmit['entry_id'] = $entryID;
982 $workFlowreturnedOnSubmit = apply_filters('bitform_filter_return_edit_success', $workFlowreturnedOnSubmit, $this->form_id);
983 return $workFlowreturnedOnSubmit;
984 }
985
986 $formEntryModel = new FormEntryModel();
987 $formEntryLogModel = new FormEntryLogModel();
988 $formOldData = $formEntryLogModel->get_form_value($entryID);
989 $key = null;
990 $entryMeta = new FormEntryMetaModel();
991 $ipTool = new IpTool();
992 $user_details = $ipTool->getUserDetail();
993 $user_details = apply_filters('bitform_filter_user_details', $user_details, $this->form_id);
994 $user_details = apply_filters('bitform_filter_update_entry_user_details', $user_details, $this->form_id);
995
996 $form_fields = $this->getFields();
997
998 $updatedValue = $this->passwordEncrypted($updatedValue, $form_fields);
999 $updatedValue = $this->formatRepeateFieldData($updatedValue, $form_fields);
1000 $field_map = [];
1001 foreach ($formOldData as $index => $data) {
1002 foreach ($form_fields as $field_key => $field) {
1003 if ($data->meta_key === $field['key']) {
1004 $field_map[$field_key] = $field['key'];
1005 }
1006 }
1007 }
1008 $geResult = $formEntryModel->get('status', ['id' => $entryID]);
1009 if (is_wp_error($geResult) || empty($geResult)) {
1010 return new WP_Error('empty_form', __('provided form entries does not exists', 'bit-form'));
1011 }
1012 $oldEntry = $geResult[0];
1013 $formEntry = $formEntryModel->update(
1014 [
1015 'status' => ('9' === $oldEntry->status && !$this->_saveFormAsDraft) ? 1 : $oldEntry->status,
1016 'updated_at' => $user_details['time'],
1017 ],
1018 [
1019 'form_id' => $formID,
1020 'id' => $entryID,
1021 ]
1022 );
1023 $log_id = null;
1024 if ($formEntry) {
1025 $log_id = $this->submisionLog($user_details, $entryID, 'update');
1026 }
1027
1028 if (is_wp_error($formEntry) || !$formEntry) {
1029 return new WP_Error('empty_form', __('provided form entries does not exists', 'bit-form'));
1030 }
1031 $formFields = $this->getFields();
1032 $updatedValue = FileHandler::tempDirToUploadDir($updatedValue, $formFields, $this->form_id, $entryID);
1033 $file_fields = $this->getUploadFields();
1034 if (count($file_fields) > 0) {
1035 $fileHandler = new FileHandler();
1036 foreach ($_FILES as $file_name => $file_details) {
1037 if ($file_fields && in_array($file_name, $file_fields)) {
1038 $validation = $fileHandler->validation($file_name, $file_details, $this->form_id);
1039 if (!empty($validation['error_type']) && !empty($validation['message'])) {
1040 return new WP_Error($validation['error_type'], esc_html($validation['message']));
1041 }
1042 }
1043 }
1044 if (is_object($updatedValue)) {
1045 $updatedValue = (array) $updatedValue;
1046 }
1047 foreach ($file_fields as $field_key) {
1048 $repeaterFldKey = $this->isRepeatedField($field_key);
1049 if (isset($updatedValue[$field_key . '_old'])) {
1050 // Handle file deletion for repeater fields
1051 if ($repeaterFldKey) {
1052 // Form entry meta lookup; meta_key/meta_value query required to retrieve repeater field data by entry.
1053 $repeaterExistData = $entryMeta->get(
1054 'meta_value',
1055 [
1056 'bitforms_form_entry_id' => $entryID,
1057 'meta_key' => $repeaterFldKey,
1058 ]
1059 );
1060 if (!is_wp_error($repeaterExistData)) {
1061 // restructor json
1062 $repeaterExistData = json_decode($repeaterExistData[0]->meta_value, true);
1063 $repeaterExistFiles = [];
1064 $repeaterDeleted_files = [];
1065 $repeaterFiles_old = [];
1066 $submittedRepeaterOldFiles = is_array($updatedValue[$field_key . '_old']) ? $updatedValue[$field_key . '_old'] : [];
1067 foreach ($repeaterExistData as $index => $repeaterRow) {
1068 $repeaterExistFiles[$index] = [];
1069 if (isset($repeaterRow[$field_key]) && !empty($repeaterRow[$field_key]) && is_string($repeaterRow[$field_key])) {
1070 $repeaterExistFiles[$index] = json_decode($repeaterRow[$field_key], true);
1071 }
1072 if (isset($repeaterRow[$field_key]) && !empty($repeaterRow[$field_key]) && is_array($repeaterRow[$field_key])) {
1073 $repeaterExistFiles[$index] = $repeaterRow[$field_key];
1074 }
1075 if (!is_array($repeaterExistFiles[$index])) {
1076 $repeaterExistFiles[$index] = [];
1077 }
1078 $oldFileInputExists = array_key_exists($index, $submittedRepeaterOldFiles);
1079 $repeaterRowExists = $oldFileInputExists || (isset($updatedValue[$repeaterFldKey][$index]) && is_array($updatedValue[$repeaterFldKey][$index]));
1080 $oldFileValues = ($repeaterRowExists && $oldFileInputExists) ? $submittedRepeaterOldFiles[$index] : [];
1081 $repeaterFiles_old[$index] = $this->normalizeOldFileValues($repeaterExistFiles[$index], $oldFileValues);
1082 $repeaterDeleted_files[$index] = array_diff($repeaterExistFiles[$index], $repeaterFiles_old[$index]);
1083 $repeaterFiles_old[$index] = array_values(array_diff($repeaterFiles_old[$index], $repeaterDeleted_files[$index]));
1084 $fileHandler->deleteFiles($formID, $entryID, $repeaterDeleted_files[$index]);
1085 if ($repeaterRowExists) {
1086 if (!isset($updatedValue[$repeaterFldKey][$index]) || !is_array($updatedValue[$repeaterFldKey][$index])) {
1087 $updatedValue[$repeaterFldKey][$index] = [];
1088 }
1089 $updatedValue[$repeaterFldKey][$index][$field_key] = $repeaterFiles_old[$index];
1090 }
1091 }
1092 }
1093 } else {
1094 // Handle file deletion for non-repeater fields; meta_key/meta_value lookup required to identify stored file paths per entry.
1095 $file_exists = $entryMeta->get(
1096 'meta_value',
1097 [
1098 'bitforms_form_entry_id' => $entryID,
1099 'meta_key' => $field_key,
1100 ]
1101 );
1102 if (!is_wp_error($file_exists) && count($file_exists) > 0) {
1103 $files_in_db = json_decode($file_exists[0]->meta_value, true);
1104 if (!is_array($files_in_db)) {
1105 $files_in_db = [];
1106 }
1107 $retained_files = $this->normalizeOldFileValues($files_in_db, empty($updatedValue[$field_key . '_old']) ? [] : $updatedValue[$field_key . '_old']);
1108 $deleted_files = array_diff($files_in_db, $retained_files);
1109 $retained_files = array_values(array_diff($retained_files, $deleted_files));
1110 if (count($deleted_files) > 0) {
1111 $fileHandler->deleteFiles($formID, $entryID, $deleted_files);
1112 }
1113 $updatedValue[$field_key] = $retained_files;
1114 }
1115 }
1116 }
1117 if (!empty($_FILES[$field_key]['name'])) {
1118 if ($repeaterFldKey) {
1119 // Handle repeater field files
1120 $file_details = $_FILES[$field_key];
1121 foreach ($file_details['name'] as $index => $file) {
1122 $old_meta_value = [];
1123 // Retrieve existing old files for this specific repeater index
1124 if (isset($repeaterFiles_old[$index - 1]) && count($repeaterFiles_old[$index - 1]) > 0) {
1125 $old_meta_value = $repeaterFiles_old[$index - 1];
1126 // json format causing issue with repeater file in mail attachment as it's sending broken url(for multistep and abandonment form)
1127 // $updatedValue[$repeaterFldKey][$index - 1][$field_key] = wp_json_encode($old_meta_value);
1128 $updatedValue[$repeaterFldKey][$index - 1][$field_key] = $old_meta_value;
1129 }
1130 $repeateFileDetails = [
1131 'name' => $file_details['name'][$index],
1132 'type' => $file_details['type'][$index],
1133 'tmp_name' => $file_details['tmp_name'][$index],
1134 'error' => $file_details['error'][$index],
1135 'size' => $file_details['size'][$index],
1136 ];
1137 $meta_value = $fileHandler->moveUploadedFiles($repeateFileDetails, $formID, $entryID, $index);
1138 if (!empty($meta_value)) {
1139 $mergedMetaValueWithOld = array_merge($old_meta_value, (array) $meta_value);
1140 // json format causing issue with repeater file in mail attachment as it's sending broken url(for multistep and abandonment form)
1141 // $updatedValue[$repeaterFldKey][$index - 1][$field_key] = wp_json_encode($mergedMetaValueWithOld);
1142 $updatedValue[$repeaterFldKey][$index - 1][$field_key] = $mergedMetaValueWithOld;
1143
1144 $_FILES[$field_key]['new_name'][$index - 1] = $mergedMetaValueWithOld;
1145 // $_FILES[$field_key]['file_path'][$index - 1] = $common_file_path . DIRECTORY_SEPARATOR . $meta_value;
1146 }
1147 }
1148 } else {
1149 // Handle non-repeater field files
1150 $meta_value = $fileHandler->moveUploadedFiles($_FILES[$field_key], $formID, $entryID);
1151 if (!empty($meta_value)) {
1152 $_FILES[$field_key]['new_name'] = $meta_value;
1153 if (isset($updatedValue[$field_key . '_old']) && !is_wp_error($file_exists) && count($file_exists) > 0) {
1154 $meta_value = empty($retained_files) ? $meta_value : array_merge($meta_value, $retained_files);
1155 $updatedValue[$field_key] = $meta_value;
1156 } else {
1157 $updatedValue[$field_key] = $meta_value;
1158 }
1159 }
1160 }
1161 }
1162 }
1163
1164 // Get the common file path to avoid repetitive calculation
1165 $this->addNewFilePathToFiles($formID, $entryID, $file_fields);
1166 }
1167
1168 if (is_object($updatedValue)) {
1169 $updatedValue = (array) $updatedValue;
1170 }
1171 if (isset($updatedValue['_ajax_nonce'], $_REQUEST['g-recaptcha-response']) && sanitize_text_field(wp_unslash($_REQUEST['g-recaptcha-response']))) {
1172 unset($updatedValue['_ajax_nonce'], $_REQUEST['g-recaptcha-response']);
1173 }
1174
1175 $toUpdateValues = [];
1176 foreach ($form_fields as $field) {
1177 if (isset($updatedValue[$field['key']])) {
1178 $toUpdateValues[$field['key']] = $updatedValue[$field['key']];
1179 }
1180 }
1181 $form_content = \json_decode(static::$form[0]->form_content);
1182
1183 foreach ($form_content->fields as $key => $field) {
1184 if ('signature' === $field->typ) {
1185 $fld_data = $updatedValue[$key];
1186 $img_type = $field->config->imgTyp;
1187 $toUpdateValues[$key] = $this->getSignatureFilePath($fld_data, $this->form_id, $key, $entryID, $img_type);
1188 }
1189
1190 // for Signature field inside reepater
1191 if ('repeater' === $field->typ) {
1192 $rptr_data = $updatedValue[$key];
1193 $formFields = $form_content->fields;
1194 $this->setSignatureFilePathInRepeater($rptr_data, $key, $formFields, $entryID, $toUpdateValues);
1195 }
1196 }
1197
1198 $workFlowRunHelper = new WorkFlow($formID);
1199 $workFlowreturnedOnSubmit = $workFlowRunHelper->executeOnSubmit(
1200 'edit',
1201 $this->getFormContentWithValue($toUpdateValues)->fields,
1202 $toUpdateValues,
1203 $entryID,
1204 $log_id
1205 );
1206
1207 if (!empty($workFlowreturnedOnSubmit['fields'])) {
1208 $updatedValue = $workFlowreturnedOnSubmit['fields'];
1209 }
1210
1211 $formEntryMetaUpdateStatus = $entryMeta->update(
1212 $toUpdateValues,
1213 [
1214 'bitforms_form_entry_id' => $entryID,
1215 ]
1216 );
1217 if (is_wp_error($formEntryMetaUpdateStatus) || isset($newFileInsertStatus) && is_wp_error($newFileInsertStatus)) {
1218 do_action('bitform_update_entry_error', $this, $toUpdateValues, $formEntryMetaUpdateStatus, $this->form_id);
1219 return $formEntryMetaUpdateStatus;
1220 }
1221 $toUpdateValues = array_merge($formEntryMetaUpdateStatus, ['entry_id' => $entryID]);
1222 do_action('bitform_after_update_entry_success', $this, $toUpdateValues, $formID, $entryID);
1223 if (empty($workFlowreturnedOnSubmit['message'])) {
1224 $workFlowreturnedOnSubmit['message'] = __('Entry Updated Successfully', 'bit-form');
1225 }
1226 $customFieldHandler = new CustomFieldHandler();
1227 $toUpdateValues = $customFieldHandler->updatedData($form_fields, $toUpdateValues);
1228
1229 $workFlowreturnedOnSubmit['updatedData'] = $toUpdateValues;
1230 $counter = 0;
1231 for ($i = 0; $i < count($formOldData); $i++) {
1232 if (array_key_exists($formOldData[$i]->meta_key . '_old', $toUpdateValues)) {
1233 unset($toUpdateValues[$formOldData[$i]->meta_key . '_old']);
1234 }
1235 if (in_array($formOldData[$i]->meta_key, $file_fields)) {
1236 if (
1237 empty($_FILES[$formOldData[$i]->meta_key]['name'])
1238 || (is_array($_FILES[$formOldData[$i]->meta_key]['name'])
1239 && 1 === count($_FILES[$formOldData[$i]->meta_key]['name'])
1240 && empty($_FILES[$formOldData[$i]->meta_key]['name'][0]))
1241 ) {
1242 unset($toUpdateValues[$formOldData[$i]->meta_key]);
1243 continue;
1244 }
1245 if (is_array($_FILES[$formOldData[$i]->meta_key]['name']) && !in_array($_FILES[$formOldData[$i]->meta_key]['name'], json_decode($formOldData[$i]->meta_value))) {
1246 $sanitized_names = array_map('sanitize_file_name', array_map('wp_unslash', (array) $_FILES[$formOldData[$i]->meta_key]['name']));
1247 $key[$i] = '${' . $formOldData[$i]->meta_key . '} file was Updated To ' . wp_json_encode($sanitized_names);
1248 } elseif (!is_array($_FILES[$formOldData[$i]->meta_key]['name']) && !in_array($_FILES[$formOldData[$i]->meta_key]['name'], json_decode($formOldData[$i]->meta_value))) {
1249 $key[$i] = '${' . $formOldData[$i]->meta_key . '} file was Updated To ' . sanitize_file_name(wp_unslash($_FILES[$formOldData[$i]->meta_key]['name']));
1250 }
1251 unset($toUpdateValues[$formOldData[$i]->meta_key]);
1252 } elseif (isset($toUpdateValues[$formOldData[$i]->meta_key])) {
1253 if (is_array($toUpdateValues[$formOldData[$i]->meta_key])) {
1254 if (json_decode($formOldData[$i]->meta_value) !== $toUpdateValues[$formOldData[$i]->meta_key]) {
1255 $key[$i] = '${' . $formOldData[$i]->meta_key . '} was Updated From ' . implode(',', json_decode($formOldData[$i]->meta_value)) . ' To ' . implode(',', $toUpdateValues[$formOldData[$i]->meta_key]);
1256 }
1257 } elseif (is_string($toUpdateValues[$formOldData[$i]->meta_key]) && !FieldValueHandler::isEmpty($toUpdateValues[$formOldData[$i]->meta_key])) {
1258 if ($formOldData[$i]->meta_value !== $toUpdateValues[$formOldData[$i]->meta_key]) {
1259 $key[$i] = '${' . $formOldData[$i]->meta_key . '} was Updated' . ($formOldData[$i]->meta_value ? ' From ' . $formOldData[$i]->meta_value : '') . ' To ' . $toUpdateValues[$formOldData[$i]->meta_key];
1260 }
1261 }
1262 }
1263 $counter++;
1264 }
1265
1266 $newField = array_keys(array_diff_key($formEntryMetaUpdateStatus, $field_map));
1267 for ($i = 0; $i < count($newField); $i++) {
1268 if (is_array($toUpdateValues[$newField[$i]]) && !empty($toUpdateValues[$newField[$i]])) {
1269 $key[$counter + $i] = '${' . $newField[$i] . '} Updated To ' . implode(',', $toUpdateValues[$newField[$i]]);
1270 } elseif (is_string($newField[$i]) && !FieldValueHandler::isEmpty($toUpdateValues[$newField[$i]])) {
1271 $key[$counter + $i] = '${' . $newField[$i] . '} Updated To ' . $toUpdateValues[$newField[$i]];
1272 }
1273 }
1274 if (null !== $key) {
1275 $logUpdate = implode('b::f', (array) $key);
1276 $formEntryLogUpdate = $formEntryLogModel->logUpdate($logUpdate, $log_id);
1277 }
1278 $workFlowreturnedOnSubmit['entry_id'] = $entryID;
1279 $workFlowreturnedOnSubmit = apply_filters('bitform_filter_return_edit_success', $workFlowreturnedOnSubmit, $this->form_id);
1280
1281 return $workFlowreturnedOnSubmit;
1282 }
1283
1284 public function getRepeaterFields()
1285 {
1286 if (!is_null($this->_repeaterFields)) {
1287 return $this->_repeaterFields;
1288 }
1289 $repeaterFields = [];
1290 $form_content = \json_decode(static::$form[0]->form_content);
1291 $fields = $form_content->fields;
1292 $nestedLayouts = !empty($form_content->nestedLayout) ? $form_content->nestedLayout : [];
1293 foreach ($nestedLayouts as $fieldKey => $repeatLayout) {
1294 if ('repeater' !== $fields->{$fieldKey}->typ) {
1295 continue;
1296 }
1297 $repeaterFields[$fieldKey] = [];
1298 foreach ($repeatLayout->lg as $fieldLayoutData) {
1299 $repeaterFields[$fieldKey][] = $fieldLayoutData->i;
1300 }
1301 }
1302 $this->_repeaterFields = $repeaterFields;
1303 return $repeaterFields;
1304 }
1305
1306 public function isRepeatedField($fieldKey)
1307 {
1308 $repeatedFields = $this->getRepeaterFields();
1309 foreach ($repeatedFields as $repeaterKey => $repeaterFields) {
1310 if (in_array($fieldKey, $repeaterFields)) {
1311 return $repeaterKey;
1312 }
1313 }
1314 return false;
1315 }
1316
1317 public function getParentRepeaterField($fieldKey)
1318 {
1319 $repeatedFields = $this->getRepeaterFields();
1320 foreach ($repeatedFields as $repeaterKey => $repeaterFields) {
1321 if (in_array($fieldKey, $repeaterFields)) {
1322 return $repeaterKey;
1323 }
1324 }
1325 return null;
1326 }
1327
1328 public function isRepeaterField($fieldKey)
1329 {
1330 $repeatedFields = $this->getRepeaterFields();
1331 if (array_key_exists($fieldKey, $repeatedFields)) {
1332 return true;
1333 }
1334 return false;
1335 }
1336
1337 public function fieldNameReplaceOfPost()
1338 {
1339 // CSRF verified upstream before this method is called; $_POST/$_FILES are being normalized (field key remapping), not reading new user input.
1340 $fields = $this->getFields();
1341 foreach ($fields as $fieldKey => $fieldData) {
1342 if (array_key_exists('name', $fieldData)) {
1343 $fldName = $fieldData['name'];
1344 $catchChildFldNamePattern = '/\[(.*?)\]/';
1345 // catching the child field name for confirm field, name field's child
1346 // preg_match_all($catchChildFldNamePattern, $fldName, $matches);
1347 $fldName = preg_replace($catchChildFldNamePattern, '', $fldName);
1348 $fldName = str_replace(['.', ' '], '_', $fldName);
1349 if (!empty($fldName)) {
1350 if (array_key_exists($fldName, $_POST)) {
1351 $temp = $this->sanitize_text_recursive($_POST[$fldName]);
1352 unset($_POST[$fldName]);
1353 $_POST[$fieldKey] = $temp;
1354 } elseif (array_key_exists($fldName, $_FILES)) {
1355 $temp = $this->sanitize_text_recursive($_FILES[$fldName], false);
1356 unset($_FILES[$fldName]);
1357 $_FILES[$fieldKey] = $temp;
1358 }
1359 // Convert _session_id suffix (used by email-otp and similar fields)
1360 if (array_key_exists($fldName . '_session_id', $_POST)) {
1361 $temp = sanitize_text_field(wp_unslash($_POST[$fldName . '_session_id']));
1362 unset($_POST[$fldName . '_session_id']);
1363 $_POST[$fieldKey . '_session_id'] = $temp;
1364 }
1365 }
1366 }
1367 }
1368 }
1369
1370 private function sanitize_text_recursive($input, $unslash = true)
1371 {
1372 if (is_array($input)) {
1373 return array_map(fn ($item) => $this->sanitize_text_recursive($item, $unslash), $input);
1374 }
1375
1376 return sanitize_text_field($unslash ? wp_unslash($input) : $input);
1377 }
1378
1379 private function normalizeRepeatedCompositeFieldInput($value)
1380 {
1381 if (!is_array($value) || empty($value)) {
1382 return $value;
1383 }
1384
1385 $hasNestedArray = false;
1386 foreach ($value as $childValues) {
1387 if (!is_array($childValues)) {
1388 return $value;
1389 }
1390 $hasNestedArray = true;
1391 }
1392
1393 if (!$hasNestedArray) {
1394 return $value;
1395 }
1396
1397 $formattedValue = [];
1398 foreach ($value as $childKey => $childValues) {
1399 foreach ($childValues as $repeatIndex => $repeatValue) {
1400 if (!isset($formattedValue[$repeatIndex]) || !is_array($formattedValue[$repeatIndex])) {
1401 $formattedValue[$repeatIndex] = [];
1402 }
1403 $formattedValue[$repeatIndex][$childKey] = $repeatValue;
1404 }
1405 }
1406
1407 return $formattedValue;
1408 }
1409
1410 public function setSubmissionCount($countStep = 1)
1411 {
1412 $update_status = $this->formModel->update(
1413 [
1414 'entries' => intval(static::$form[0]->entries) + $countStep,
1415 ],
1416 [
1417 'id' => $this->form_id,
1418 ]
1419 );
1420 }
1421
1422 public function resetSubmissionCount($countStep)
1423 {
1424 $update_status = $this->formModel->update(
1425 [
1426 'entries' => intval($countStep),
1427 ],
1428 [
1429 'id' => $this->form_id,
1430 ]
1431 );
1432 }
1433
1434 public function getCaptchaSettings()
1435 {
1436 $formContents = $this->getFormContent();
1437 $fieldStr = wp_json_encode($formContents->fields);
1438 if (false !== strpos($fieldStr, '"typ":"recaptcha"')) {
1439 return true;
1440 }
1441 }
1442
1443 public function getTurnstileSettings()
1444 {
1445 $formContents = $this->getFormContent();
1446 $fieldStr = wp_json_encode($formContents->fields);
1447 if (false !== strpos($fieldStr, '"typ":"turnstile"')) {
1448 return true;
1449 }
1450 }
1451
1452 public function isFieldTypeExist($fieldType)
1453 {
1454 $formContents = $this->getFormContent();
1455 $fieldStr = wp_json_encode($formContents->fields);
1456 if (false !== strpos($fieldStr, '"typ":"' . $fieldType . '"')) {
1457 return true;
1458 }
1459 }
1460
1461 public function getCaptchaV3Settings()
1462 {
1463 $formContents = $this->getFormContent();
1464 if (!empty($formContents->additional->enabled) && !empty($formContents->additional->enabled->recaptchav3)) {
1465 return $formContents->additional->settings->recaptchav3;
1466 }
1467 return false;
1468 }
1469
1470 // public function getSuccessMessageMarkups() {
1471 // if (is_null($this->_work_flows)) {
1472 // $workFlowManager = new WorkFlowHandler($this->form_id);
1473 // $this->_work_flows = $workFlowManager->getAllworkFlow();
1474 // }
1475
1476 // $ids = [];
1477 // foreach ($this->_work_flows as $msgItem) {
1478 // foreach ($msgItem['conditions'] as $condition) {
1479 // if (isset($condition->actions->success)) {
1480 // foreach ($condition->actions->success as $msg) {
1481 // if ('successMsg' === $msg->type && isset($msg->details->id)) {
1482 // $msgDetailsId = $msg->details->id;
1483 // $idObj = json_decode(stripslashes($msgDetailsId));
1484 // if (is_object($idObj) && !empty($idObj->id)) {
1485 // array_push($ids, $idObj->id);
1486 // }
1487 // }
1488 // }
1489 // }
1490 // if (isset($condition->actions->failure)) {
1491 // $idObj = json_decode(stripslashes($condition->actions->failure));
1492 // if (is_object($idObj) && !empty($idObj->id)) {
1493 // array_push($ids, $idObj->id);
1494 // }
1495 // }
1496 // }
1497 // }
1498 // $ids = array_unique($ids);
1499 // if (is_null($this->_conf_messages)) {
1500 // $successMsgHandler = new SuccessMessageHandler($this->form_id);
1501 // $this->_conf_messages = $successMsgHandler->getMessages($ids);
1502 // }
1503
1504 // $messageMarkups = '';
1505 // if (is_wp_error($this->_conf_messages)) {
1506 // return $messageMarkups;
1507 // }
1508
1509 // foreach ($this->_conf_messages as $key => $msgItem) {
1510 // $messageMarkups .= $this->messageMarkup($msgItem->id);
1511 // }
1512
1513 // return $messageMarkups;
1514 // }
1515
1516 // private function messageMarkup($msgId) {
1517 // return <<<SUCCESSMSG
1518 // <div role="dialog" aria-hidden="true" data-modal-backdrop="true" class="{$this->getAtomicCls("msg-container-{$msgId}")} deactive2 test">
1519 // <div role="button" class="{$this->getAtomicCls("msg-background-{$msgId}")} msg-backdrop">
1520 // <div class="bf-notification-message {$this->getAtomicCls("msg-content-{$msgId}")}">
1521 // <button class="{$this->getAtomicCls("close-{$msgId}")} bf-msg-close" type="button">
1522 // <svg class="{$this->getAtomicCls("close-icn-{$msgId}")}" viewBox="0 0 30 30">
1523 // <line fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" x1="4" y1="3.88" x2="26" y2="26.12"></line>
1524 // <line fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" x1="26" y1="3.88" x2="4" y2="26.12"></line>
1525 // </svg>
1526 // </button>
1527 // <div class="msg-content"></div>
1528 // </div>
1529 // </div>
1530 // </div>
1531 // SUCCESSMSG;
1532 // }
1533
1534 public function getAtomicCls($element)
1535 {
1536 $atomicClassMap = $this->getAtomicClsMap();
1537 if (is_object($atomicClassMap) && property_exists($atomicClassMap, ".$element")) {
1538 $getAtomicCls = $atomicClassMap->{".$element"};
1539 return implode(' ', $getAtomicCls) . " $element";
1540 }
1541 return $element;
1542 }
1543
1544 public function isGCLIDEnabled()
1545 {
1546 $formContents = $this->getFormContent();
1547 if (isset($formContents->additional->enabled->captureGCLID) && $formContents->additional->enabled->captureGCLID) {
1548 return true;
1549 }
1550 return false;
1551 }
1552
1553 protected function addEntryInfo($field_details, $counter)
1554 {
1555 $infos = [
1556 '__user_id' => __('User', 'bit-form'),
1557 '__entry_status' => __('Status', 'bit-form'),
1558 //'__user_location' => __(''),
1559 '__referer' => __('Refer URL', 'bit-form'),
1560 '__user_device' => __('Device', 'bit-form'),
1561 '__user_ip' => __('IP address', 'bit-form'),
1562 '__created_at' => __('Created Time', 'bit-form'),
1563 '__updated_at' => __('Modified Time', 'bit-form'),
1564 ];
1565 foreach ($infos as $key => $value) {
1566 $field_details[$counter]['name'] = $value;
1567 $field_details[$counter]['key'] = $key;
1568 $field_details[$counter]['type'] = 'sys';
1569 $counter = $counter + 1;
1570 }
1571
1572 return $field_details;
1573 }
1574 }
1575