PluginProbe
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder / 2.21.4
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder v2.21.4
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
← All changes | includes/Core/Util/FieldValueHandler.php +829 -87 1.92.21.4 View file →
@@ -1,128 +1,870 @@
1 1 <?php
2 2
3 3 namespace BitCode\BitForm\Core\Util;
4 4
5 -use BitCode\BitForm\Core\Util\SmartTags;
5 +use BitCode\BitForm\Admin\Form\Helpers;
6 +use BitCode\BitForm\Core\Form\FormManager;
6 7
7 8 final class FieldValueHandler
8 9 {
9 - public static function replaceFieldWithValue($stringToReplaceField, $fieldValues)
10 - {
11 - if (empty($stringToReplaceField)) {
12 - return $stringToReplaceField;
10 + public static function replaceFieldWithValue($stringToReplaceField, $fieldValues, $formID = null)
11 + {
12 + if (empty($stringToReplaceField)) {
13 + return $stringToReplaceField;
14 + }
15 + if (!is_string($stringToReplaceField)) {
16 + $stringToReplaceField = wp_json_encode($stringToReplaceField);
17 + }
18 + $fieldValues = $formID ? self::sortValueBasedOnLayout($formID, $fieldValues) : $fieldValues;
19 +
20 + if ($formID) {
21 + $stringToReplaceField = self::replaceValueOfBf_all_data($stringToReplaceField, $fieldValues, $formID);
22 + $stringToReplaceField = self::replaceRepeaterFieldValue($stringToReplaceField, $fieldValues, $formID);
23 + }
24 +
25 + $stringToReplaceField = self::replaceSmartTagWithValue($stringToReplaceField);
26 +
27 + $fieldPattern = '/\${\w[^ ${}]*}/';
28 +
29 + preg_match_all($fieldPattern, $stringToReplaceField, $matchedField);
30 + if (empty($matchedField)) {
31 + return $stringToReplaceField;
32 + }
33 + $uniqueFieldsInStr = array_unique($matchedField[0]);
34 + foreach ($uniqueFieldsInStr as $key => $value) {
35 + $fieldName = substr($value, 2, strlen($value) - 3);
36 + $fieldValue = null;
37 + if (isset($fieldValues[$fieldName])) {
38 + $targetFieldValue = isset($fieldValues[$fieldName]['value']) ? $fieldValues[$fieldName]['value'] : $fieldValues[$fieldName];
39 + if ('array' === gettype($targetFieldValue) || 'object' === gettype($targetFieldValue)) {
40 + foreach ((array) $targetFieldValue as $singleTargetVal) {
41 + if (isset($fieldValue)) {
42 + if (is_numeric($fieldValue) && is_numeric($singleTargetVal)) {
43 + $fieldValue = $fieldValue + $singleTargetVal;
44 + } else {
45 + $fieldValue = "$fieldValue, $singleTargetVal";
46 + }
47 + } else {
48 + $fieldValue = $singleTargetVal;
49 + }
50 + }
51 + // $fieldValue = wp_json_encode($targetFieldValue);
52 + } else {
53 + $fieldValue = strval($targetFieldValue);
13 54 }
14 - if (!is_string($stringToReplaceField)) {
15 - $stringToReplaceField = wp_json_encode($stringToReplaceField);
55 + $stringToReplaceField = str_replace($value, $fieldValue, $stringToReplaceField);
56 + } else {
57 + $stringToReplaceField = str_replace($value, '', $stringToReplaceField);
58 + }
59 + }
60 +
61 + // check if the string is a function like : "${_bf_calc(${b27-5}*10)}"
62 + // TO DO: Implement the function properly
63 + // if (self::isFunction($stringToReplaceField)) {
64 + // $functionName = self::getFunctionName($stringToReplaceField);
65 +
66 + // switch ($functionName) {
67 + // case '_bf_calc':
68 + // return self::getFunctionParameter($stringToReplaceField);
69 + // case '_bf_count':
70 + // return self::getCountValue($stringToReplaceField);
71 + // default:
72 + // return 0;
73 + // }
74 + // }
75 + return $stringToReplaceField;
76 + }
77 +
78 + /**
79 + * Summary of getCountValue - get the count value from the function string "${_bf_count(item-1, item-2)}" => 2
80 + *
81 + * @param string $functionString
82 + * @return int
83 + */
84 + private static function getCountValue(string $functionString): int
85 + {
86 + $options = self::getFunctionParameter($functionString);
87 + $option = explode(',', $options);
88 + return count($option);
89 + }
90 +
91 + /**
92 + * Summary of getFunctionParameter - get the function parameter from the function string "${_bf_calc(2*10)}" => 2*10
93 + *
94 + * @param string $functionString
95 + * @return string
96 + */
97 + private static function getFunctionParameter(string $functionString): string
98 + {
99 + $regexPattern = '/\(([^)]*)\)/';
100 + preg_match($regexPattern, $functionString, $matches);
101 + return $matches[1];
102 + }
103 +
104 + /**
105 + * Summary of isFunction - check if the string is a function "${_bf_calc(${b27-5}*10)}" or not "${_bf_date}"
106 + *
107 + * @param string $functionString
108 + * @return bool true if the string is a function else false
109 + */
110 + private static function isFunction(string $functionString): bool
111 + {
112 + $regexPattern = '/\([^)]*\)/';
113 + return preg_match($regexPattern, $functionString);
114 + }
115 +
116 + /**
117 + * Summary of getFunctionName - get the function name from the function string "${_bf_calc(${b27-5}*10)}" => _bf_calc
118 + *
119 + * @param string $functionString
120 + * @return string
121 + */
122 + private static function getFunctionName(string $functionString): string
123 + {
124 + $regexPattern = '/\b([a-zA-Z_][a-zA-Z0-9_]*)\(/';
125 + preg_match($regexPattern, $functionString, $matches);
126 + return $matches[1];
127 + }
128 +
129 + public static function validateMailArry($emailAddresses, $fieldValues)
130 + {
131 + if (!is_array($emailAddresses)) {
132 + return [FieldValueHandler::replaceFieldWithValue($emailAddresses, $fieldValues)];
133 + }
134 + foreach ($emailAddresses as $key => $email) {
135 + if (!is_email($email)) {
136 + $email = FieldValueHandler::replaceFieldWithValue($email, $fieldValues);
137 + if (is_email($email)) {
138 + $emailAddresses[$key] = $email;
16 139 }
17 - $stringToReplaceField = static::replaceSmartTagWithValue($stringToReplaceField);
18 - $fieldPattern = '/\${\w[^ ${}]*}/';
140 + }
141 + }
142 + return $emailAddresses;
143 + }
19 144
20 - preg_match_all($fieldPattern, $stringToReplaceField, $matchedField);
21 - if (empty($matchedField)) {
22 - return $stringToReplaceField;
145 + public static function replaceSmartTagWithValue($contentWithSmartTag)
146 + {
147 + $fieldPattern = '/(\${_[^{]*?)(?=\})}/';
148 + $matchPattern = preg_match_all($fieldPattern, $contentWithSmartTag, $matchedField);
149 + if (!$matchPattern) {
150 + return $contentWithSmartTag;
151 + }
152 +
153 + $ajaxRequest = false;
154 + if (isset($_REQUEST['action']) && 'bitforms_trigger_workflow' === $_REQUEST['action']) {
155 + $ajaxRequest = true;
156 + }
157 +
158 + foreach (array_unique($matchedField[0]) as $value) {
159 + $fieldName = trim(substr($value, 2, strlen($value) - 3));
160 +
161 + $matches = preg_match('/\("*([^\)]+"*)\)/', $value, $matchCustomFormat);
162 +
163 + $customValue = '';
164 + if ($matches) {
165 + $removeQuote = ["'", '"'];
166 + $customValue = str_replace($removeQuote, '', $matchCustomFormat[1]);
167 + $fieldName = str_replace($matchCustomFormat[0], '', $fieldName);
168 + }
169 +
170 + $tagFieldValues = SmartTags::getSmartTagValue($fieldName, $ajaxRequest, $customValue);
171 +
172 + $contentWithSmartTag = str_replace($value, $tagFieldValues, $contentWithSmartTag);
173 + }
174 + return $contentWithSmartTag;
175 + }
176 +
177 + public static function isEmpty($val)
178 + {
179 + if (empty($val) && !in_array($val, ['0', 0, 0.0], true)) {
180 + return true;
181 + }
182 + return false;
183 + }
184 +
185 + public static function formatFieldValueForMail($fields, $fieldValues = [])
186 + {
187 + $formattedFldValues = $fieldValues;
188 + $file_upload_types = Helpers::$file_upload_types;
189 + $repeated_array_type_data_fields = Helpers::$repeated_array_type_data_fields;
190 + foreach ($fields as $fldKey => $fldData) {
191 + if (in_array($fldData->typ, $file_upload_types)) {
192 + continue;
193 + }
194 + if (array_key_exists($fldKey, $fieldValues)) {
195 + $value = $fieldValues[$fldKey];
196 + // if (is_array($value)) {
197 + // $formattedFldValues[$fldKey] = htmlspecialchars(implode(', ', $value));
198 + // } else {
199 + // $formattedFldValues[$fldKey] = htmlspecialchars($value);
200 + // }
201 +
202 + // TODO: this code are temporary commented, need to change and remove the comment
203 +
204 + // if (is_array($value)) {
205 + // $arrValue = '';
206 + // foreach ($value as $v) {
207 + // if (is_array($v)) {
208 + // foreach ($v as $k1 => $v1) {
209 + // if (array_key_exists($k1, $repeaterFieldKey)) {
210 + // $oldValue = $repeaterFieldKey[$k1];
211 + // if (is_array($v1) && in_array($fields->{$k1}->typ, $repeated_array_type_data_fields)) {
212 + // $newValues = '[' . implode(', ', $v1) . '] ';
213 + // if (!preg_match('/\[.*\]/', $oldValue)) {
214 + // $oldValue = '[' . $oldValue . '] ';
215 + // }
216 + // } else {
217 + // $newValues = $v1;
218 + // }
219 + // $repeaterFieldKey[$k1] = $oldValue . ', ' . $newValues;
220 + // } else {
221 + // if (!empty($v1) && is_array($v1)) {
222 + // $repeaterFieldKey[$k1] = htmlspecialchars(implode(', ', $v1));
223 + // } else {
224 + // $repeaterFieldKey[$k1] = htmlspecialchars($v1);
225 + // }
226 + // }
227 + // }
228 + // } else {
229 + // $arrValue .= $v . ', ';
230 + // }
231 + // }
232 + // $formattedFldValues[$fldKey] = htmlspecialchars(rtrim($arrValue, ', '));
233 + // $arrValue = '';
234 + // } else {
235 + // $formattedFldValues[$fldKey] = htmlspecialchars($value);
236 + // }
237 + if ('textarea' === $fldData->typ) {
238 + $formattedFldValues[$fldKey] = nl2br(htmlspecialchars($value));
23 239 }
24 - $uniqueFieldsInStr = array_unique($matchedField[0]);
25 - foreach ($uniqueFieldsInStr as $key => $value) {
26 - $fieldName = substr($value, 2, strlen($value) - 3);
27 - $fieldValue = null;
28 - if (isset($fieldValues[$fieldName])) {
29 - $targetFieldValue = isset($fieldValues[$fieldName]['value']) ? $fieldValues[$fieldName]['value'] : $fieldValues[$fieldName];
30 - if (gettype($targetFieldValue) === 'array' || gettype($targetFieldValue) === 'object') {
31 - foreach ((array)$targetFieldValue as $singleTargetVal) {
32 - if (isset($fieldValue)) {
33 - if (is_numeric($fieldValue) && is_numeric($singleTargetVal)) {
34 - $fieldValue = $fieldValue + $singleTargetVal;
35 - } else {
36 - $fieldValue = "$fieldValue, $singleTargetVal";
37 - }
38 - } else {
39 - $fieldValue = $singleTargetVal;
40 - }
41 - }
42 - // $fieldValue = wp_json_encode($targetFieldValue);
43 - } else {
44 - $fieldValue = strval($targetFieldValue);
45 - }
46 - $stringToReplaceField = str_replace($value, $fieldValue, $stringToReplaceField);
47 - } else {
48 - $stringToReplaceField = str_replace($value, '', $stringToReplaceField);
49 - }
240 + if ('date' === $fldData->typ && !empty($value)) {
241 + $formattedFldValues[$fldKey] = date_i18n(get_option('date_format'), strtotime(htmlspecialchars($value)));
50 242 }
243 + }
244 + }
51 245
52 - return $stringToReplaceField;
246 + $merge_values = array_merge($fieldValues, $formattedFldValues);
247 + // $merge_values = array_merge($merge_values, $repeaterFieldKey);
248 +
249 + return $merge_values;
250 + }
251 +
252 + public static function changeHrefPathInHTMLString($html_body, $path)
253 + {
254 + if (empty($html_body) || empty($path)) {
255 + return $html_body;
53 256 }
54 257
55 - public static function validateMailArry($emailAddresses, $fieldValues)
56 - {
57 - if (!is_array($emailAddresses)) {
58 - return [FieldValueHandler::replaceFieldWithValue($emailAddresses, $fieldValues)];
258 + return preg_replace_callback(
259 + '/<a\s+[^>]*href=[\'"]([^\'"]+)[\'"][^>]*>/i',
260 + function ($matches) use ($path) {
261 + $href = $matches[1];
262 +
263 + if (filter_var($href, FILTER_VALIDATE_URL)) {
264 + return $matches[0];
59 265 }
60 - foreach ($emailAddresses as $key => $email) {
61 - if (!is_email($email)) {
62 - $email = FieldValueHandler::replaceFieldWithValue($email, $fieldValues);
63 - if (is_email($email)) {
64 - $emailAddresses[$key] = $email;
65 - }
66 - }
266 +
267 + if (preg_match('/^(mailto:|tel:|javascript:|#)/i', $href)) {
268 + return $matches[0];
67 269 }
68 - return $emailAddresses;
270 +
271 + $fullPath = rtrim($path, '/') . '/' . ltrim($href, '/');
272 +
273 + return str_replace(
274 + $href,
275 + htmlspecialchars($fullPath, ENT_QUOTES),
276 + $matches[0]
277 + );
278 + },
279 + $html_body
280 + );
281 + }
282 +
283 + public static function changeImagePathInHTMLString($html_body, $path)
284 + {
285 + $allowedExtensions = ['jpg', 'jpeg', 'png', 'svg'];
286 + if (empty($html_body) || empty($path)) {
287 + return $html_body;
69 288 }
70 289
71 - public static function replaceSmartTagWithValue($fieldValues)
72 - {
73 - $fieldPattern = '/(\${_[^{]*?)(?=\})}/';
74 - $matchPattern = preg_match_all($fieldPattern, $fieldValues, $matchedField);
75 - if (!$matchPattern) {
76 - return $fieldValues;
290 + $allowedMimeTypes = [
291 + 'jpg' => ['image/jpeg', 'image/pjpeg'],
292 + 'jpeg' => ['image/jpeg', 'image/pjpeg'],
293 + 'png' => ['image/png'],
294 + 'svg' => ['image/svg+xml']
295 + ];
296 +
297 + return preg_replace_callback(
298 + '/<img\s+[^>]*src=[\'"]([^\'"]*)[\'"][^>]*>/i',
299 + function ($matches) use ($path, $allowedExtensions, $allowedMimeTypes) {
300 + $src = $matches[1];
301 +
302 + if (filter_var($src, FILTER_VALIDATE_URL)) {
303 + return $matches[0];
77 304 }
78 305
79 - $ajaxRequest = false;
306 + if (!trim($src)) {
307 + return '';
308 + }
80 309
81 - if (isset($_REQUEST['action']) == 'bitforms_trigger_workflow') {
82 - $ajaxRequest = true;
310 + $fullPath = rtrim($path, '/') . '/' . ltrim($src, '/');
311 +
312 + $extension = strtolower(pathinfo($fullPath, PATHINFO_EXTENSION));
313 +
314 + if (!preg_match('/^(http|https):\/\//', $fullPath)) {
315 + if (!file_exists($fullPath) || !isset($allowedMimeTypes[$extension])) {
316 + Log::debug_log([
317 + 'status' => 'error',
318 + 'code' => 'file_not_found',
319 + 'message' => "File not found or unsupported file type: $fullPath",
320 + ]);
321 + return '';
322 + } else {
323 + Log::debug_log([
324 + 'status' => 'success',
325 + 'code' => 'file_found',
326 + 'message' => "File Found => location: {$fullPath}"
327 + ]);
328 + }
329 + $mimeType = mime_content_type($fullPath);
330 + if (!in_array($mimeType, $allowedMimeTypes[$extension], true)) {
331 + Log::debug_log([
332 + 'status' => 'error',
333 + 'code' => 'unsupported_file_type',
334 + 'message' => "Unsupported file type: $mimeType for file: $fullPath",
335 + ]);
336 + return '';
337 + }
83 338 }
84 339
85 - $data = SmartTags::getPostUserData($ajaxRequest);
86 - $specialtagFields = SmartTags::smartTagFieldKeys();
340 + return str_replace($src, htmlspecialchars($fullPath, ENT_QUOTES), $matches[0]);
341 + },
342 + $html_body
343 + );
344 + }
87 345
88 - foreach (array_unique($matchedField[0]) as $value) {
89 - $fieldName = trim(substr($value, 2, strlen($value) - 3));
346 + public static function sortValueBasedOnLayout($formId, $fieldValues)
347 + {
348 + $formManager = FormManager::getInstance($formId);
349 + $layout = $formManager->getFormLayout();
350 + $formLayout = $formManager->getFlatenFormLayout(); // returns all layouts (lg, md, sm)
351 + $fieldKeyOrderbasedOnLayout = array_map(function ($fld) {
352 + return $fld->i;
353 + }, $formLayout->lg);
354 + $ordered = [];
90 355
91 - $matches = preg_match('/\("*([^\)]+"*)\)/', $value, $matchCustomFormat);
356 + foreach ($fieldKeyOrderbasedOnLayout as $key) {
357 + if (array_key_exists($key, $fieldValues)) {
358 + $ordered[$key] = $fieldValues[$key];
359 + }
360 + }
92 361
93 - $customValue = '';
94 - if ($matches) {
95 - $removeQuote = ["'", '"'];
96 - $customValue = str_replace($removeQuote, "", $matchCustomFormat[1]);
97 - $fieldName = str_replace($matchCustomFormat[0], '', $fieldName);
362 + foreach ($fieldValues as $k=>$v) {
363 + if (!array_key_exists($k, $fieldKeyOrderbasedOnLayout)) {
364 + $ordered[$k] = $fieldValues[$k];
365 + }
366 + }
367 + return $ordered;
368 + }
369 +
370 + public static function replaceValueOfBf_all_data($stringToReplaceField, $fieldValues, $formId)
371 + {
372 + // $pattern = '/\$\{bf_all_data\}/'; // Corrected escaping
373 + $pattern = '/\$\{bf_all_data(?:\.onlyValues)?\}/'; // Corrected escaping
374 +
375 + preg_match_all($pattern, $stringToReplaceField, $matches);
376 + $matchesArray = $matches[0] ?? [];
377 + if (count($matchesArray) > 0) {
378 + $formManager = FormManager::getInstance($formId);
379 + $formFields = $formManager->getFields();
380 + $orderedFormFields = $formManager->getFieldsBasedOnLayout(); // ordered form fields based on layout(lg) order
381 + foreach ($matchesArray as $match) {
382 + switch ($match) {
383 + case '${bf_all_data}':
384 + $fieldValues = self::bindFormData($orderedFormFields, $fieldValues, $formId);
385 + $table = self::generateTable($fieldValues, $orderedFormFields);
386 + $stringToReplaceField = str_replace('${bf_all_data}', $table, $stringToReplaceField);
387 + break;
388 +
389 + case '${bf_all_data.onlyValues}':
390 + $fieldValues = self::bindFormData($orderedFormFields, $fieldValues, $formId, true);
391 + $table = self::generateTable($fieldValues, $orderedFormFields);
392 + $stringToReplaceField = str_replace('${bf_all_data.onlyValues}', $table, $stringToReplaceField);
393 + break;
394 + default:
395 + Log::debug_log([
396 + 'status' => 'error',
397 + 'code' => 'unknown_placeholder',
398 + 'message' => "Unknown placeholder: $match",
399 + ]);
400 + break;
401 + }
402 + }
403 + }
404 + return $stringToReplaceField;
405 + }
406 +
407 + /**
408 + * Ensures an <img> tag with a style attribute exists in the input.
409 + *
410 + * Behavior:
411 + * - If the input is just an image filename (e.g., "1.png"), returns a complete <img> tag with the default style.
412 + * - If the input is HTML with <img> tags:
413 + * - If any <img> has a style, returns the HTML as-is.
414 + * - If <img> exists without style, adds the default style to the first one found.
415 + * - If no <img> tag or image file is found, returns the input unchanged.
416 + *
417 + * @param string $input Image filename or HTML string.
418 + * @param string $defaultStyle Optional. The CSS style to apply if missing. Default: 'max-width: 100%; height: auto;'.
419 + *
420 + * @return string Modified HTML string with styled <img> tag if needed.
421 + */
422 + private static function ensureImgWithStyle($input, $defaultStyle = 'max-width: 100%; height: auto;')
423 + {
424 + $imgTagWithStylePattern = '/<img\b[^>]*\bstyle\s*=\s*["\'][^"\']*["\'][^>]*>/i';
425 + $imgTagPattern = '/<img\b[^>]*>/i';
426 + $filePattern = '/\.(jpg|jpeg|png|gif|webp)$/i';
427 +
428 + if (preg_match($filePattern, trim($input)) && !preg_match('/<img\b/i', $input)) {
429 + return '<img src="' . htmlspecialchars(trim($input)) . '" style="' . $defaultStyle . '" />';
430 + }
431 +
432 + if (preg_match($imgTagWithStylePattern, $input)) {
433 + // <img> already has style, return as is
434 + return $input;
435 + } elseif (preg_match($imgTagPattern, $input, $match)) {
436 + // <img> without style, add style
437 + $updatedImg = preg_replace('/<img\b(.*?)(\/?)>/i', '<img$1 style="' . $defaultStyle . '" $2>', $match[0]);
438 + return str_replace($match[0], $updatedImg, $input);
439 + } else {
440 + // No <img> tag found, return input
441 + return $input;
442 + }
443 + }
444 +
445 + private static function orderRepeaterData($repeaterFldKey, $repeaterData, $formId)
446 + {
447 + if (!$formId) {
448 + return $repeaterData;
449 + }
450 +
451 + $formManager = FormManager::getInstance($formId);
452 + $nestedLayout = $formManager->getFormNestedLayout();
453 + $repeaterLayout = $nestedLayout->{$repeaterFldKey}->lg;
454 + $orderedFldKey = array_map(function ($fld) {
455 + return $fld->i;
456 + }, $repeaterLayout);
457 +
458 + $orderedRepeaterData = [];
459 + foreach ($repeaterData as $rptr) {
460 + $orderedFlds = [];
461 + foreach ($orderedFldKey as $k) {
462 + if (array_key_exists($k, $rptr)) {
463 + $orderedFlds[$k] = $rptr[$k];
464 + }
465 + }
466 +
467 + foreach ($rptr as $ky => $v) {
468 + if (!array_key_exists($ky, $orderedFldKey)) {
469 + $orderedFlds[$ky] = $v;
470 + }
471 + }
472 +
473 + $orderedRepeaterData[] = $orderedFlds;
474 + }
475 + return $orderedRepeaterData;
476 + }
477 +
478 + private static function bindFormData($formFields, $formData, $formId, $isOnlyValues = false)
479 + {
480 + $entryID = isset($formData['entry_id']) ? $formData['entry_id'] : null;
481 + return array_reduce(array_keys($formFields), function ($filteredData, $key) use ($formFields, $formData, $isOnlyValues, $formId) {
482 + $field = $formFields[$key];
483 +
484 + $fieldNewData = $filteredData;
485 +
486 + $ignoreFields = ['button', 'recaptcha', 'html', 'divider', 'spacer', 'section', 'file-up', 'turnstile', 'hcaptcha', 'advanced-file-up', 'image'];
487 +
488 + $arrayValueFldType = ['check', 'select', 'image-select'];
489 +
490 + if (in_array($field['type'], $ignoreFields)) {
491 + return $fieldNewData;
492 + }
493 +
494 + // Skip processing for hidden or empty fields only when $isOnlyValues is true
495 + if ($isOnlyValues) {
496 + // Check if the value is strictly an empty string or null, but allow 0
497 + if (!isset($formData[$key]) || '' === $formData[$key] || null === $formData[$key]) {
498 + return $fieldNewData;
499 + }
500 +
501 + if (isset($field['valid']['hide']) && $field['valid']['hide']) {
502 + return $fieldNewData;
503 + }
504 + }
505 +
506 + if (isset($formData[$key])) {
507 + if ('repeater' === $field['type']) {
508 + $repeater_data = is_string($formData[$key]) ? json_decode($formData[$key], true) : $formData[$key];
509 + // ordering repeater field according to nested repeater layout
510 + $repeater_data = self::orderRepeaterData($key, $repeater_data, $formId);
511 + if ($isOnlyValues) {
512 + $repeater_data = array_filter($repeater_data, function ($sub) {
513 + return array_filter($sub, fn ($value) => '' !== $value);
514 + });
515 + }
516 + $fieldNewData[$key] = $repeater_data;
517 + } elseif ('signature' === $field['type']) {
518 + if ('signature-failed.png' !== $formData[$key]) {
519 + $file_path = strpos($formData[$key], '/') ? $formData[$key] : $formData[$key];
520 + $newPath = $file_path;
521 + $fieldNewData[$key] = self::ensureImgWithStyle($newPath, 'max-width: 100%; height: auto;');
522 + }
523 + } elseif (in_array($field['type'], $arrayValueFldType)) {
524 + $v = is_string($formData[$key]) ? json_decode($formData[$key], true) : $formData[$key];
525 + $fieldNewData[$key] = $v && is_array($v) ? implode(', ', $v) : $formData[$key];
526 + } else {
527 + $fieldNewData[$key] = $formData[$key];
528 + }
529 + }
530 +
531 + return $fieldNewData;
532 + }, []);
533 + }
534 +
535 + private static function generateTable($fieldValues, $formFields)
536 + {
537 + if (empty($fieldValues)) {
538 + Log::debug_log([
539 + 'status' => 'error',
540 + 'code' => 'no_fields_found',
541 + 'type' => 'bf_all_data',
542 + 'message' => 'No fields found for bf_all_data',
543 + 'fields' => $fieldValues,
544 + 'formFields' => $formFields,
545 + ]);
546 + return '<p>No data available.</p>';
547 + }
548 +
549 + $table = "<table style='font-family: arial, sans-serif; border-collapse: collapse; width: 100%;'>";
550 +
551 + foreach ($fieldValues as $fk => $value) {
552 + $fieldName = $formFields[$fk]['label'] ?? $fk;
553 + $table .= "<tr>
554 + <td style='border: 1px solid #dddddd; text-align: left; padding: 8px; font-weight: bold;'>{$fieldName}</td>
555 + <td style='border: 1px solid #dddddd; text-align: left; padding: 8px;'>";
556 +
557 + if (is_array($value)) {
558 + $table .= "<table style='width: 100%; border-collapse: collapse;'>";
559 +
560 + $table .= '<tr>';
561 + foreach (array_keys($value[0]) as $subKey) {
562 + $subLabel = $formFields[$subKey]['label'] ?? $subKey;
563 + $table .= "<th style='border: 1px solid #dddddd; padding: 8px; background-color: #f2f2f2;'>" . $subLabel . '</th>';
564 + }
565 + $table .= '</tr>';
566 +
567 + foreach ($value as $row) {
568 + $table .= '<tr>';
569 + foreach ($row as $subKey => $subValue) {
570 + if (is_array($subValue)) {
571 + $subValue = implode(', ', array_map(function ($v) {
572 + if (self::isFileTypeValue($v)) {
573 + if (self::isImageTypeValue($v)) {
574 + return "<img src='{$v}' alt='{$v}' width='250'/>";
575 + } else {
576 + return "<a href='{$v}' rel='noopener noreferrer' target='_blank' style='color:blue'>{$v}</a>";
577 + }
578 + } else {
579 + return $v;
580 + }
581 + }, $subValue));
582 + } else {
583 + if (self::isFileTypeValue($subValue)) {
584 + if ('signature-failed.png' === $subValue) {
585 + $subValue = '';
586 + } else {
587 + if (self::isImageTypeValue($subValue)) {
588 + $subValue = "<img src='{$subValue}' alt='{$subValue}' width='250'/>";
589 + } else {
590 + $subValue = "<a href='{$subValue}' rel='noopener noreferrer' target='_blank' style='color:blue'>{$subValue}</a>";
591 + }
592 + }
593 + } else {
594 + $subValue = $subValue;
595 + }
98 596 }
99 597
100 - if (in_array($fieldName, $specialtagFields)) {
101 - $tagFieldValues = SmartTags::getSmartTagValue($fieldName, $data, $customValue);
102 - $fieldValues = str_replace($value, $tagFieldValues, $fieldValues);
598 + $table .= "<td style='border: 1px solid #dddddd; padding: 8px;'>" . $subValue . '</td>';
599 + }
600 + $table .= '</tr>';
601 + }
602 + $table .= '</table>';
603 + } else {
604 + $table .= $value;
605 + }
606 +
607 + $table .= '</td></tr>';
608 + }
609 +
610 + $table .= '</table>';
611 +
612 + return $table;
613 + }
614 +
615 + public static function replaceRepeaterFieldValue($stringToReplaceField, $fieldValues, $formID)
616 + {
617 + if (!is_string($stringToReplaceField) || empty($stringToReplaceField)) {
618 + return $stringToReplaceField; // Return as-is if nothing to replace
619 + }
620 +
621 + $formManager = FormManager::getInstance($formID);
622 + $formFields = $formManager->getFieldsBasedOnLayout(); // ordered form fields based on layout(lg) order
623 +
624 + // Find all placeholders like ${field_key} example: ${b27-5}
625 + preg_match_all('/\$\{(b\d+-\d+)\}/', $stringToReplaceField, $matches);
626 +
627 + if (empty($matches[1])) {
628 + return $stringToReplaceField;
629 + }
630 + // Clean field data
631 + // $dataCleaning = self::removeEmptyValues($fieldValues);
632 + $flatFieldData = self::restructureRepeaterData($fieldValues, $formManager);
633 + // generate table for repeater fields
634 + foreach ($matches[1] as $fk) {
635 + $repeaterFieldKey = $fk;
636 + $fieldType = isset($formFields[$repeaterFieldKey]['type']) && !empty($formFields[$repeaterFieldKey]['type']) ? $formFields[$repeaterFieldKey]['type'] : null;
637 + if ('repeater' === $fieldType) {
638 + $repeaterMarkup = self::repeaterFieldTable($fieldValues[$repeaterFieldKey] ?? [], $formFields, $repeaterFieldKey);
639 + $stringToReplaceField = str_replace('${' . $fk . '}', $repeaterMarkup, $stringToReplaceField);
640 + } else {
641 + $repeaterFieldData = self::safeFlatString($flatFieldData[$repeaterFieldKey] ?? '', $fieldType);
642 + $stringToReplaceField = str_replace('${' . $fk . '}', $repeaterFieldData, $stringToReplaceField);
643 + }
644 + }
645 + return $stringToReplaceField;
646 + }
647 +
648 + /**
649 + * Restructures repeater field data to maintain original structure while
650 + * aggregating nested repeater values into top-level indexed arrays.
651 + *
652 + * @param array $data Original field data structure
653 + * @return array Restructured data with aggregated arrays
654 + */
655 + public static function restructureRepeaterData(array $data, $formManagerInstance): array
656 + {
657 + $result = $data;
658 + // topkey === field Key topValue === field value
659 + foreach ($data as $topKey => $topValue) {
660 + if ($formManagerInstance->isRepeaterField($topKey)) {
661 + $topValue = self::decodeIfJson($topValue);
662 + //assigning the converted value to repeater field
663 + $result[$topKey] = $topValue;
664 + // topvalue here is repeater field value;
665 + // entryIndex repeater field key , entry == repeater field value
666 + foreach ($topValue as $entryIndex => $entry) {
667 + if (!is_array($entry)) {
668 + continue;
669 + }
670 +
671 + foreach ($entry as $subKey => $subValue) {
672 + // Handle nested arrays within entries
673 + if (is_array($subValue)) {
674 + $result[$subKey][$entryIndex] = $subValue;
675 + } else {
676 + $result[$subKey][$entryIndex] = $subValue;
103 677 }
678 + }
104 679 }
105 - return $fieldValues;
680 + }
106 681 }
682 + return $result;
683 + }
107 684
108 - public static function isEmpty($val)
109 - {
110 - if (empty($val) && !in_array($val, ['0', 0, 0.0], TRUE)) {
111 - return true;
112 - }
113 - return false;
685 + /**
686 + * Return decoded data if incoming data is stringified and if it's a plain string (e.g "John Doe") it returns the plain string
687 + *
688 + * @param mixed $data
689 + * @return mixed
690 + */
691 + private static function decodeIfJson($data): mixed
692 + {
693 + if (!is_string($data)) {
694 + return $data;
114 695 }
115 696
116 - public static function formatFieldValueForMail($fields, $fieldValues)
117 - {
118 - $formattedFldValues = $fieldValues;
697 + $decoded = json_decode($data, true);
119 698
120 - foreach ($fields as $fldKey => $fldData) {
121 - if ($fldData->typ === 'textarea') {
122 - $formattedFldValues[$fldKey] = nl2br($fieldValues[$fldKey]);
699 + return (JSON_ERROR_NONE === json_last_error()) ? $decoded : $data;
700 + }
701 +
702 + /**
703 + * Safely converts any type of form value(Specially Repeater Field Value) to string.
704 + *
705 + * @param mixed $data
706 + * @param string $fldType
707 + * @return string
708 + */
709 + public static function safeFlatString($data, $fldType): string
710 + {
711 + $newData = self::decodeIfJson($data);
712 +
713 + if (is_array($newData)) {
714 + return implode(', ', array_map(function ($item) use ($fldType) {
715 + return is_array($item)
716 + ? '[' . implode(', ', array_map(function ($itm) use ($fldType) {
717 + if (in_array($fldType, ['advanced-file-up', 'file-up']) || self::isFileTypeValue($itm)) {
718 + if (self::isImageTypeValue($itm)) {
719 + return "<img src='{$itm}' alt='{$itm}' width='250'/>";
720 + } else {
721 + return "<a href='{$itm}' rel='noopener noreferrer' target='_blank' style='color:blue'>{$itm}</a>";
123 722 }
723 + } else {
724 + return $itm;
725 + }
726 + }, $item)) . ']'
727 + : self::safeFlatString($item, $fldType);
728 + }, $newData));
729 + }
730 +
731 + if (is_object($data)) {
732 + return method_exists($data, '__toString') ? (string) $data : (json_encode($data) ?: '');
733 + }
734 +
735 + if (is_null($data)) {
736 + return '';
737 + }
738 +
739 + if (self::isFileTypeValue($data)) {
740 + if ('signature-failed.png' === $data) {
741 + return '';
742 + }
743 + }
744 +
745 + return (string) $data;
746 + }
747 +
748 + private static function removeEmptyValues($fieldData)
749 + {
750 + if (!is_array($fieldData)) {
751 + return $fieldData;
752 + }
753 + // Remove empty values from the array
754 + return array_filter($fieldData, function ($value) {
755 + // Check if the value is 0
756 + if (0 === $value) {
757 + return '0';
758 + }
759 + return !empty($value);
760 + });
761 + }
762 +
763 + /**
764 + * Gets the field type by field key
765 + *
766 + * @param string $fldKey
767 + * @param mixed $formField
768 + * @return string
769 + */
770 + private static function getFldType($fldKey, $formFields)
771 + {
772 + if (array_key_exists($fldKey, $formFields)) {
773 + return $formFields[$fldKey]['type'];
774 + }
775 + }
776 +
777 + /**
778 + * Return true is it's file type value by checking with extension
779 + *
780 + * @param string $filename
781 + * @return boolean
782 + */
783 + private static function isFileTypeValue($fileName)
784 + {
785 + if (!is_string($fileName)) {
786 + return false;
787 + }
788 + $ext = pathinfo($fileName, PATHINFO_EXTENSION);
789 +
790 + if ('other' !== FileHandler::getFileTypeByExtension($ext)) {
791 + return true;
792 + }
793 + }
794 +
795 + /**
796 + * Return true is it's image type value by checking with extension
797 + *
798 + * @param string $filename
799 + * @return boolean
800 + */
801 + private static function isImageTypeValue($fileName)
802 + {
803 + if (!is_string($fileName)) {
804 + return false;
805 + }
806 + $ext = pathinfo($fileName, PATHINFO_EXTENSION);
807 +
808 + if ('image' === FileHandler::getFileTypeByExtension($ext)) {
809 + return true;
810 + }
811 + }
812 +
813 + private static function repeaterFieldTable($repeaterFieldData, $formFields, $repeaterFieldKey)
814 + {
815 + $repeaterFieldData = self::decodeIfJson($repeaterFieldData);
816 +
817 + if (!is_array($repeaterFieldData) || !isset($repeaterFieldData[0]) || !is_array($repeaterFieldData[0])) {
818 + return ''; // Safely return empty if not a valid repeater structure
819 + }
820 + $table = "<table style='font-family: arial, sans-serif; border-collapse: collapse; width: 100%;'>";
821 + // $table .= '<tr>';
822 + // $table .= '<th style="border: 1px solid #dddddd; text-align: left; padding: 8px;">' . self::getLabel($formFields, $repeaterFieldKey) . '</th>';
823 + // $table .= '</tr>';
824 + // $table .= '<td style="border: 1px solid #dddddd; text-align: left; padding: 8px;">';
825 + // $table .= '<table style="width: 100%; border-collapse: collapse;">';
826 +
827 + $headers = array_keys($repeaterFieldData[0]);
828 + $table .= '<tr>'; // open tr (for column header)
829 + foreach ($headers as $fk) {
830 + $table .= '<th style="border: 1px solid #dddddd; padding: 8px; ">' . self::getLabel($formFields, $fk) . '</th>';
831 + }
832 + $table .= '</tr>'; // close tr (for column header)
833 +
834 + foreach ($repeaterFieldData as $row) {
835 + $table .= '<tr>'; // open tr (for table data row)
836 + foreach ($row as $k=>$value) {
837 + $fldTyp = self::getFldType($k, $formFields);
838 + if (is_array($value)) {
839 + $newValue = implode(', ', array_map(function ($v) use ($fldTyp) {
840 + if (in_array($fldTyp, ['advance-file-up', 'file-up', 'signature']) || self::isFileTypeValue($v)) {
841 + if (self::isImageTypeValue($v)) {
842 + return "<img src='{$v}' alt='{$v}' width='250'/>";
843 + } else {
844 + return "<a href='{$v}' rel='noopener noreferrer' target='_blank' style='color:blue'>{$v}</a>";
845 + }
846 + } else {
847 + return $v;
848 + }
849 + }, $value));
850 + } else {
851 + $newValue = self::isFileTypeValue($value) ? ('signature-failed.png' !== $value ? (self::isImageTypeValue($value) ? "<img src='{$value}' alt='{$value}' width='250'/>" : "<a href='{$value}' rel='noopener noreferrer' target='_blank' style='color:blue'>{$value}</a>") : '') : $value;
124 852 }
125 853
126 - return $formattedFldValues;
854 + $table .= '<td style="border: 1px solid #dddddd; padding: 8px;">' . $newValue . '</td>';
855 + }
856 + $table .= '</tr>'; // close tr (for table data row)
127 857 }
858 + // $table .= '</table>';
859 +
860 + // $table .= '</td>';
861 + $table .= '</table>';
862 +
863 + return $table;
864 + }
865 +
866 + private static function getLabel($formFields, $key)
867 + {
868 + return $formFields[$key]['label'] ?? $key;
869 + }
128 870 }