PluginProbe
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder / 2.15.3
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder v2.15.3
3.3.1 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 All 138 releases
← All changes | includes/Core/Util/FieldValueHandler.php +42 -891 -3.0.12.15.3 View file →
@@ -2,13 +2,12 @@
2 2
3 3 namespace BitCode\BitForm\Core\Util;
4 4
5 5 use BitCode\BitForm\Admin\Form\Helpers;
6 -use BitCode\BitForm\Core\Form\FormManager;
7 6
8 7 final class FieldValueHandler
9 8 {
10 - public static function replaceFieldWithValue($stringToReplaceField, $fieldValues, $formID = null)
9 + public static function replaceFieldWithValue($stringToReplaceField, $fieldValues)
11 10 {
12 11 if (empty($stringToReplaceField)) {
13 12 return $stringToReplaceField;
14 13 }
@@ -14,19 +13,10 @@
14 13 }
15 14 if (!is_string($stringToReplaceField)) {
16 15 $stringToReplaceField = wp_json_encode($stringToReplaceField);
17 16 }
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 -
17 + $stringToReplaceField = static::replaceSmartTagWithValue($stringToReplaceField);
27 18 $fieldPattern = '/\${\w[^ ${}]*}/';
28 -
29 19 preg_match_all($fieldPattern, $stringToReplaceField, $matchedField);
30 20 if (empty($matchedField)) {
31 21 return $stringToReplaceField;
32 22 }
@@ -36,9 +26,9 @@
36 26 $fieldValue = null;
37 27 if (isset($fieldValues[$fieldName])) {
38 28 $targetFieldValue = isset($fieldValues[$fieldName]['value']) ? $fieldValues[$fieldName]['value'] : $fieldValues[$fieldName];
39 29 if ('array' === gettype($targetFieldValue) || 'object' === gettype($targetFieldValue)) {
40 - foreach ((array) $targetFieldValue as $singleTargetVal) {
30 + foreach ((array)$targetFieldValue as $singleTargetVal) {
41 31 if (isset($fieldValue)) {
42 32 if (is_numeric($fieldValue) && is_numeric($singleTargetVal)) {
43 33 $fieldValue = $fieldValue + $singleTargetVal;
44 34 } else {
@@ -74,24 +64,8 @@
74 64 // }
75 65 return $stringToReplaceField;
76 66 }
77 67
78 - public static function replaceBackBtnWithPrevPageUrl($stringToReplaceField)
79 - {
80 - $prevPageUrl = isset($_SERVER['HTTP_REFERER']) ? esc_url_raw(wp_unslash($_SERVER['HTTP_REFERER'])) : home_url();
81 - preg_match_all('/\$?\{back_to_view\}/', $stringToReplaceField, $matches);
82 - $matched = $matches[0];
83 -
84 - if (empty($matched) || !$prevPageUrl) {
85 - return $stringToReplaceField;
86 - }
87 -
88 - foreach ($matched as $m) {
89 - $stringToReplaceField = str_replace($m, $prevPageUrl, $stringToReplaceField);
90 - }
91 - return $stringToReplaceField;
92 - }
93 -
94 68 /**
95 69 * Summary of getCountValue - get the count value from the function string "${_bf_count(item-1, item-2)}" => 2
96 70 *
97 71 * @param string $functionString
@@ -157,19 +131,18 @@
157 131 }
158 132 return $emailAddresses;
159 133 }
160 134
161 - public static function replaceSmartTagWithValue($contentWithSmartTag)
135 + public static function replaceSmartTagWithValue($fieldValues)
162 136 {
163 137 $fieldPattern = '/(\${_[^{]*?)(?=\})}/';
164 - $matchPattern = preg_match_all($fieldPattern, $contentWithSmartTag, $matchedField);
138 + $matchPattern = preg_match_all($fieldPattern, $fieldValues, $matchedField);
165 139 if (!$matchPattern) {
166 - return $contentWithSmartTag;
140 + return $fieldValues;
167 141 }
168 142
169 143 $ajaxRequest = false;
170 - // Read-only action name check for routing; CSRF verified upstream in the form submission flow via verifySubmissionNonce().
171 - if (isset($_REQUEST['action']) && 'bitforms_trigger_workflow' === sanitize_text_field(wp_unslash($_REQUEST['action']))) {
144 + if (isset($_REQUEST['action']) && 'bitforms_trigger_workflow' === $_REQUEST['action']) {
172 145 $ajaxRequest = true;
173 146 }
174 147
175 148 foreach (array_unique($matchedField[0]) as $value) {
@@ -184,12 +157,11 @@
184 157 $fieldName = str_replace($matchCustomFormat[0], '', $fieldName);
185 158 }
186 159
187 160 $tagFieldValues = SmartTags::getSmartTagValue($fieldName, $ajaxRequest, $customValue);
188 -
189 - $contentWithSmartTag = str_replace($value, $tagFieldValues, $contentWithSmartTag);
161 + $fieldValues = str_replace($value, $tagFieldValues, $fieldValues);
190 162 }
191 - return $contentWithSmartTag;
163 + return $fieldValues;
192 164 }
193 165
194 166 public static function isEmpty($val)
195 167 {
@@ -198,11 +170,12 @@
198 170 }
199 171 return false;
200 172 }
201 173
202 - public static function formatFieldValueForMail($fields, $fieldValues = [])
174 + public static function formatFieldValueForMail($fields, $fieldValues)
203 175 {
204 176 $formattedFldValues = $fieldValues;
177 + $repeaterFieldKey = [];
205 178 $file_upload_types = Helpers::$file_upload_types;
206 179 $repeated_array_type_data_fields = Helpers::$repeated_array_type_data_fields;
207 180 foreach ($fields as $fldKey => $fldData) {
208 181 if (in_array($fldData->typ, $file_upload_types)) {
@@ -215,873 +188,51 @@
215 188 // } else {
216 189 // $formattedFldValues[$fldKey] = htmlspecialchars($value);
217 190 // }
218 191
219 - // TODO: this code are temporary commented, need to change and remove the comment
220 -
221 - // if (is_array($value)) {
222 - // $arrValue = '';
223 - // foreach ($value as $v) {
224 - // if (is_array($v)) {
225 - // foreach ($v as $k1 => $v1) {
226 - // if (array_key_exists($k1, $repeaterFieldKey)) {
227 - // $oldValue = $repeaterFieldKey[$k1];
228 - // if (is_array($v1) && in_array($fields->{$k1}->typ, $repeated_array_type_data_fields)) {
229 - // $newValues = '[' . implode(', ', $v1) . '] ';
230 - // if (!preg_match('/\[.*\]/', $oldValue)) {
231 - // $oldValue = '[' . $oldValue . '] ';
232 - // }
233 - // } else {
234 - // $newValues = $v1;
235 - // }
236 - // $repeaterFieldKey[$k1] = $oldValue . ', ' . $newValues;
237 - // } else {
238 - // if (!empty($v1) && is_array($v1)) {
239 - // $repeaterFieldKey[$k1] = htmlspecialchars(implode(', ', $v1));
240 - // } else {
241 - // $repeaterFieldKey[$k1] = htmlspecialchars($v1);
242 - // }
243 - // }
244 - // }
245 - // } else {
246 - // $arrValue .= $v . ', ';
247 - // }
248 - // }
249 - // $formattedFldValues[$fldKey] = htmlspecialchars(rtrim($arrValue, ', '));
250 - // $arrValue = '';
251 - // } else {
252 - // $formattedFldValues[$fldKey] = htmlspecialchars($value);
253 - // }
254 - if ('textarea' === $fldData->typ) {
255 - $formattedFldValues[$fldKey] = nl2br(htmlspecialchars($value));
256 - }
257 - if ('date' === $fldData->typ && !empty($value)) {
258 - $formattedFldValues[$fldKey] = date_i18n(get_option('date_format'), strtotime(htmlspecialchars($value)));
259 - }
260 - }
261 - }
262 -
263 - $merge_values = array_merge($fieldValues, $formattedFldValues);
264 - // $merge_values = array_merge($merge_values, $repeaterFieldKey);
265 -
266 - return $merge_values;
267 - }
268 -
269 - public static function changeHrefPathInHTMLString($html_body, $path)
270 - {
271 - if (empty($html_body) || empty($path)) {
272 - return $html_body;
273 - }
274 -
275 - return preg_replace_callback(
276 - '/<a\s+[^>]*href=[\'"]([^\'"]+)[\'"][^>]*>/i',
277 - function ($matches) use ($path) {
278 - $href = $matches[1];
279 -
280 - if (filter_var($href, FILTER_VALIDATE_URL)) {
281 - return $matches[0];
282 - }
283 -
284 - if (preg_match('/^(mailto:|tel:|javascript:|#)/i', $href)) {
285 - return $matches[0];
286 - }
287 - if (preg_match('/\$?\{back_to_view\}/', $matches[0])) {
288 - return $matches[0];
289 - }
290 -
291 - $fullPath = rtrim($path, '/') . '/' . ltrim($href, '/');
292 -
293 - return str_replace(
294 - $href,
295 - htmlspecialchars($fullPath, ENT_QUOTES),
296 - $matches[0]
297 - );
298 - },
299 - $html_body
300 - );
301 - }
302 -
303 - public static function changeImagePathInHTMLString($html_body, $path)
304 - {
305 - if (empty($html_body) || empty($path)) {
306 - return $html_body;
307 - }
308 -
309 - $allowedMimeTypes = [
310 - 'jpg' => ['image/jpeg', 'image/pjpeg'],
311 - 'jpeg' => ['image/jpeg', 'image/pjpeg'],
312 - 'png' => ['image/png'],
313 - 'svg' => ['image/svg+xml']
314 - ];
315 -
316 - return preg_replace_callback(
317 - '/<img\s+[^>]*src=[\'"]([^\'"]*)[\'"][^>]*>/i',
318 - function ($matches) use ($path, $allowedMimeTypes) {
319 - $src = $matches[1];
320 -
321 - if (filter_var($src, FILTER_VALIDATE_URL)) {
322 - return $matches[0];
323 - }
324 -
325 - if (!trim($src)) {
326 - return '';
327 - }
328 -
329 - $fullPath = rtrim($path, '/') . '/' . ltrim($src, '/');
330 -
331 - $extension = strtolower(pathinfo($fullPath, PATHINFO_EXTENSION));
332 -
333 - if (!preg_match('/^(http|https):\/\//', $fullPath)) {
334 - if (!file_exists($fullPath) || !isset($allowedMimeTypes[$extension])) {
335 - Log::debug_log([
336 - 'status' => 'error',
337 - 'code' => 'file_not_found',
338 - 'message' => "File not found or unsupported file type: $fullPath",
339 - ]);
340 - return '';
341 - } else {
342 - Log::debug_log([
343 - 'status' => 'success',
344 - 'code' => 'file_found',
345 - 'message' => "File Found => location: {$fullPath}"
346 - ]);
347 - }
348 - $mimeType = mime_content_type($fullPath);
349 - if (!in_array($mimeType, $allowedMimeTypes[$extension], true)) {
350 - Log::debug_log([
351 - 'status' => 'error',
352 - 'code' => 'unsupported_file_type',
353 - 'message' => "Unsupported file type: $mimeType for file: $fullPath",
354 - ]);
355 - return '';
356 - }
357 - }
358 -
359 - return str_replace($src, htmlspecialchars($fullPath, ENT_QUOTES), $matches[0]);
360 - },
361 - $html_body
362 - );
363 - }
364 -
365 - public static function sortValueBasedOnLayout($formId, $fieldValues)
366 - {
367 - $formManager = FormManager::getInstance($formId);
368 - $layout = $formManager->getFormLayout();
369 - $formLayout = $formManager->getFlatenFormLayout(); // returns all layouts (lg, md, sm)
370 - $fieldKeyOrderbasedOnLayout = array_map(function ($fld) {
371 - return $fld->i;
372 - }, $formLayout->lg);
373 - $ordered = [];
374 -
375 - foreach ($fieldKeyOrderbasedOnLayout as $key) {
376 - if (array_key_exists($key, $fieldValues)) {
377 - $ordered[$key] = $fieldValues[$key];
378 - }
379 - }
380 -
381 - foreach ($fieldValues as $k=>$v) {
382 - if (!array_key_exists($k, $fieldKeyOrderbasedOnLayout)) {
383 - $ordered[$k] = $fieldValues[$k];
384 - }
385 - }
386 - return $ordered;
387 - }
388 -
389 - public static function replaceValueOfBf_all_data($stringToReplaceField, $fieldValues, $formId)
390 - {
391 - // $pattern = '/\$\{bf_all_data\}/'; // Corrected escaping
392 - $pattern = '/\$\{bf_all_data(?:\.onlyValues)?\}/'; // Corrected escaping
393 -
394 - preg_match_all($pattern, $stringToReplaceField, $matches);
395 - $matchesArray = $matches[0] ?? [];
396 - if (count($matchesArray) > 0) {
397 - $formManager = FormManager::getInstance($formId);
398 - $formFields = $formManager->getFields();
399 - $orderedFormFields = $formManager->getFieldsBasedOnLayout(); // ordered form fields based on layout(lg) order
400 - foreach ($matchesArray as $match) {
401 - switch ($match) {
402 - case '${bf_all_data}':
403 - $fieldValues = self::bindFormData($orderedFormFields, $fieldValues, $formId);
404 - $table = self::generateTable($fieldValues, $orderedFormFields);
405 - $stringToReplaceField = str_replace('${bf_all_data}', $table, $stringToReplaceField);
406 - break;
407 -
408 - case '${bf_all_data.onlyValues}':
409 - $fieldValues = self::bindFormData($orderedFormFields, $fieldValues, $formId, true);
410 - $table = self::generateTable($fieldValues, $orderedFormFields);
411 - $stringToReplaceField = str_replace('${bf_all_data.onlyValues}', $table, $stringToReplaceField);
412 - break;
413 - default:
414 - Log::debug_log([
415 - 'status' => 'error',
416 - 'code' => 'unknown_placeholder',
417 - 'message' => "Unknown placeholder: $match",
418 - ]);
419 - break;
420 - }
421 - }
422 - }
423 - return $stringToReplaceField;
424 - }
425 -
426 - /**
427 - * Ensures an <img> tag with a style attribute exists in the input.
428 - *
429 - * Behavior:
430 - * - If the input is just an image filename (e.g., "1.png"), returns a complete <img> tag with the default style.
431 - * - If the input is HTML with <img> tags:
432 - * - If any <img> has a style, returns the HTML as-is.
433 - * - If <img> exists without style, adds the default style to the first one found.
434 - * - If no <img> tag or image file is found, returns the input unchanged.
435 - *
436 - * @param string $input Image filename or HTML string.
437 - * @param string $defaultStyle Optional. The CSS style to apply if missing. Default: 'max-width: 100%; height: auto;'.
438 - *
439 - * @return string Modified HTML string with styled <img> tag if needed.
440 - */
441 - private static function ensureImgWithStyle($input, $defaultStyle = 'max-width: 100%; height: auto;')
442 - {
443 - $imgTagWithStylePattern = '/<img\b[^>]*\bstyle\s*=\s*["\'][^"\']*["\'][^>]*>/i';
444 - $imgTagPattern = '/<img\b[^>]*>/i';
445 - $filePattern = '/\.(jpg|jpeg|png|gif|webp)$/i';
446 -
447 - if (preg_match($filePattern, trim($input)) && !preg_match('/<img\b/i', $input)) {
448 - return '<img src="' . htmlspecialchars(trim($input)) . '" style="' . $defaultStyle . '" />';
449 - }
450 -
451 - if (preg_match($imgTagWithStylePattern, $input)) {
452 - // <img> already has style, return as is
453 - return $input;
454 - } elseif (preg_match($imgTagPattern, $input, $match)) {
455 - // <img> without style, add style
456 - $updatedImg = preg_replace('/<img\b(.*?)(\/?)>/i', '<img$1 style="' . $defaultStyle . '" $2>', $match[0]);
457 - return str_replace($match[0], $updatedImg, $input);
458 - } else {
459 - // No <img> tag found, return input
460 - return $input;
461 - }
462 - }
463 -
464 - private static function orderRepeaterData($repeaterFldKey, $repeaterData, $formId)
465 - {
466 - if (!$formId) {
467 - return $repeaterData;
468 - }
469 -
470 - $formManager = FormManager::getInstance($formId);
471 - $nestedLayout = $formManager->getFormNestedLayout();
472 - $repeaterLayout = $nestedLayout->{$repeaterFldKey}->lg;
473 - $orderedFldKey = array_map(function ($fld) {
474 - return $fld->i;
475 - }, $repeaterLayout);
476 -
477 - $orderedRepeaterData = [];
478 - foreach ($repeaterData as $rptr) {
479 - $orderedFlds = [];
480 - foreach ($orderedFldKey as $k) {
481 - if (array_key_exists($k, $rptr)) {
482 - $orderedFlds[$k] = $rptr[$k];
483 - }
484 - }
485 -
486 - foreach ($rptr as $ky => $v) {
487 - if (!array_key_exists($ky, $orderedFldKey)) {
488 - $orderedFlds[$ky] = $v;
489 - }
490 - }
491 -
492 - $orderedRepeaterData[] = $orderedFlds;
493 - }
494 - return $orderedRepeaterData;
495 - }
496 -
497 - private static function bindFormData($formFields, $formData, $formId, $isOnlyValues = false)
498 - {
499 - $entryID = isset($formData['entry_id']) ? $formData['entry_id'] : null;
500 - return array_reduce(array_keys($formFields), function ($filteredData, $key) use ($formFields, $formData, $isOnlyValues, $formId) {
501 - $field = $formFields[$key];
502 -
503 - $fieldNewData = $filteredData;
504 -
505 - $ignoreFields = ['button', 'recaptcha', 'html', 'divider', 'spacer', 'section', 'turnstile', 'hcaptcha', 'image'];
506 -
507 - $arrayValueFldType = ['check', 'select', 'image-select'];
508 -
509 - if (in_array($field['type'], $ignoreFields)) {
510 - return $fieldNewData;
511 - }
512 -
513 - // Skip processing for hidden or empty fields only when $isOnlyValues is true
514 - if ($isOnlyValues) {
515 - // Check if the value is strictly an empty string or null, but allow 0
516 - if (!isset($formData[$key]) || '' === $formData[$key] || null === $formData[$key]) {
517 - return $fieldNewData;
518 - }
519 -
520 - if (isset($field['valid']['hide']) && $field['valid']['hide']) {
521 - return $fieldNewData;
522 - }
523 - }
524 -
525 - if (isset($formData[$key]) && !array_key_exists('parentFieldKey', $field)) {
526 - if ('repeater' === $field['type']) {
527 - $repeater_data = is_string($formData[$key]) ? json_decode($formData[$key], true) : $formData[$key];
528 - // ordering repeater field according to nested repeater layout
529 - $repeater_data = self::orderRepeaterData($key, $repeater_data, $formId);
530 - if ($isOnlyValues) {
531 - $repeater_data = array_filter($repeater_data, function ($sub) {
532 - return array_filter($sub, fn ($value) => '' !== $value);
533 - });
534 - }
535 - $fieldNewData[$key] = $repeater_data;
536 - } elseif ('signature' === $field['type']) {
537 - if ('signature-failed.png' !== $formData[$key]) {
538 - $file_path = strpos($formData[$key], '/') ? $formData[$key] : $formData[$key];
539 - $newPath = $file_path;
540 - $fieldNewData[$key] = self::ensureImgWithStyle($newPath, 'max-width: 100%; height: auto;');
541 - }
542 - } elseif (in_array($field['type'], $arrayValueFldType)) {
543 - $v = is_string($formData[$key]) ? json_decode($formData[$key], true) : $formData[$key];
544 - $fieldNewData[$key] = $v && is_array($v) ? implode(', ', $v) : $formData[$key];
545 - } else {
546 - $fieldNewData[$key] = $formData[$key];
547 - }
548 - }
549 -
550 - return $fieldNewData;
551 - }, []);
552 - }
553 -
554 - private static function generateTable($fieldValues, $formFields)
555 - {
556 - if (empty($fieldValues)) {
557 - Log::debug_log([
558 - 'status' => 'error',
559 - 'code' => 'no_fields_found',
560 - 'type' => 'bf_all_data',
561 - 'message' => 'No fields found for bf_all_data',
562 - 'fields' => $fieldValues,
563 - 'formFields' => $formFields,
564 - ]);
565 - return '<p>No data available.</p>';
566 - }
567 -
568 - $table = "<table style='font-family: arial, sans-serif; border-collapse: collapse; width: 100%;'>";
569 -
570 - foreach ($fieldValues as $fk => $value) {
571 - $value = self::decodeIfJson($value);
572 - $fieldName = self::getLabel($formFields, $fk) ?? $fk;
573 - $fieldType = $formFields[$fk]['type'];
574 - $table .= "<tr>
575 - <td style='border: 1px solid #dddddd; text-align: left; padding: 8px; font-weight: bold;'>{$fieldName}</td>
576 - <td style='border: 1px solid #dddddd; text-align: left; padding: 8px;'>";
577 -
578 - if (is_array($value)) {
579 - if ('repeater' === $fieldType) {
580 - $table .= "<table style='width: 100%; border-collapse: collapse;'>";
581 -
582 - $table .= '<tr>';
583 - foreach (array_keys($value[0]) as $subKey) {
584 - $subLabel = self::getLabel($formFields, $subKey) ?? $subKey;
585 - $table .= "<th style='border: 1px solid #dddddd; padding: 8px; background-color: #f2f2f2;'>" . $subLabel . '</th>';
586 - }
587 - $table .= '</tr>';
588 -
589 - foreach ($value as $row) {
590 - $table .= '<tr>';
591 - foreach ($row as $subKey => $subValue) {
592 - $subFieldType = self::getFldType($subKey, $formFields);
593 - if (is_array($subValue)) {
594 - if (self::isCompositeFieldType($subFieldType)) {
595 - $subValue = self::joinCompositeFieldValue($subValue, $subFieldType);
596 - } else {
597 - $subValue = self::unorderedAnchorListMarkup($subValue);
598 - }
599 -
600 - // $subValue = implode(', ', array_map(function ($v) {
601 - // if (self::isFileTypeValue($v)) {
602 - // return self::anchorMarkup($v);
603 - // // if (self::isImageTypeValue($v)) {
604 - // // return "<img src='{$v}' alt='{$v}' width='250'/>";
605 - // // } else {
606 - // // return "<a href='{$v}' rel='noopener noreferrer' target='_blank' style='color:blue'>{$v}</a>";
607 - // // }
608 - // } else {
609 - // return $v;
610 - // }
611 - // }, $subValue));
612 - } else {
613 - if (self::isFileTypeValue($subValue)) {
614 - if ('signature' === $subFieldType) {
615 - if ('signature-failed.png' === $subValue) {
616 - $subValue = '';
617 - } else {
618 - $subValue = "<img src='{$subValue}' alt='{$subValue}' width='250'/>";
192 + if (is_array($value)) {
193 + $arrValue = '';
194 + foreach ($value as $v) {
195 + if (is_array($v)) {
196 + foreach ($v as $k1 => $v1) {
197 + if (array_key_exists($k1, $repeaterFieldKey)) {
198 + $oldValue = $repeaterFieldKey[$k1];
199 + if (is_array($v1) && in_array($fields->{$k1}->typ, $repeated_array_type_data_fields)) {
200 + $newValues = '[' . implode(', ', $v1) . '] ';
201 + if (!preg_match('/\[.*\]/', $oldValue)) {
202 + $oldValue = '[' . $oldValue . '] ';
619 203 }
204 + } else {
205 + $newValues = $v1;
620 206 }
207 + $repeaterFieldKey[$k1] = $oldValue . ', ' . $newValues;
621 208 } else {
622 - $subValue = $subValue;
209 + if (!empty($v1) && is_array($v1)) {
210 + $repeaterFieldKey[$k1] = htmlspecialchars(implode(', ', $v1));
211 + } else {
212 + $repeaterFieldKey[$k1] = htmlspecialchars($v1);
213 + }
623 214 }
624 215 }
625 -
626 - $table .= "<td style='border: 1px solid #dddddd; padding: 8px;'>" . $subValue . '</td>';
216 + } else {
217 + $arrValue .= $v . ', ';
627 218 }
628 - $table .= '</tr>';
629 219 }
630 - $table .= '</table>';
631 - } elseif ('file-up' === $fieldType || 'advanced-file-up' === $fieldType) {
632 - if (is_array($value)) {
633 - $table .= self::unorderedAnchorListMarkup($value);
634 - }
635 - } elseif (self::isCompositeFieldType($fieldType)) {
636 - $table .= self::joinCompositeFieldValue($value, $fieldType);
637 - } elseif ('signature' === $fieldType) {
638 - if ('signature-failed.png' === $subValue) {
639 - $table .= '';
640 - } else {
641 - $table .= self::imgMarkup($value);
642 - }
643 - }
644 - } else {
645 - $table .= $value;
646 - }
647 -
648 - $table .= '</td></tr>';
649 - }
650 -
651 - $table .= '</table>';
652 -
653 - return $table;
654 - }
655 -
656 - private static function unorderedAnchorListMarkup($list)
657 - {
658 - $ul = "<ul style='list-style-type: none; padding: 0; margin:0'>";
659 - foreach ($list as $v) {
660 - $ul .= '<li >' . self::anchorMarkup($v) . '</li>';
661 - }
662 - $ul .= '</ul>';
663 - return $ul;
664 - }
665 -
666 - private static function imgMarkup($filename)
667 - {
668 - return "<img src='{$filename}' alt='{$filename}' width='250'/>";
669 - }
670 -
671 - private static function anchorMarkup($filename)
672 - {
673 - return "<a href='{$filename}' rel='noopener noreferrer' target='_blank' style='color:blue'>{$filename}</a>";
674 - }
675 -
676 - public static function replaceRepeaterFieldValue($stringToReplaceField, $fieldValues, $formID)
677 - {
678 - if (!is_string($stringToReplaceField) || empty($stringToReplaceField)) {
679 - return $stringToReplaceField; // Return as-is if nothing to replace
680 - }
681 -
682 - $formManager = FormManager::getInstance($formID);
683 - $formFields = $formManager->getFieldsBasedOnLayout(); // ordered form fields based on layout(lg) order
684 -
685 - // Find all placeholders like ${field_key} example: ${b27-5}
686 - preg_match_all('/\$\{(b\d+-\d+)\}/', $stringToReplaceField, $matches);
687 -
688 - if (empty($matches[1])) {
689 - return $stringToReplaceField;
690 - }
691 - // Clean field data
692 - // $dataCleaning = self::removeEmptyValues($fieldValues);
693 - $flatFieldData = self::restructureRepeaterData($fieldValues, $formManager);
694 - // generate table for repeater fields
695 - foreach ($matches[1] as $fk) {
696 - $repeaterFieldKey = $fk;
697 - $fieldType = isset($formFields[$repeaterFieldKey]['type']) && !empty($formFields[$repeaterFieldKey]['type']) ? $formFields[$repeaterFieldKey]['type'] : null;
698 - if ('repeater' === $fieldType) {
699 - $repeaterMarkup = self::repeaterFieldTable($fieldValues[$repeaterFieldKey] ?? [], $formFields, $repeaterFieldKey);
700 - $stringToReplaceField = str_replace('${' . $fk . '}', $repeaterMarkup, $stringToReplaceField);
701 - } else {
702 - if ('signature' === $fieldType) {
703 - $stringToReplaceField = self::replaceImgTagForRepeatedSignature($stringToReplaceField, $flatFieldData[$repeaterFieldKey], $repeaterFieldKey);
704 - }
705 - $repeaterFieldData = self::safeFlatString(
706 - $flatFieldData[$repeaterFieldKey] ?? '',
707 - $fieldType,
708 - $repeaterFieldKey,
709 - $flatFieldData,
710 - $formFields
711 - );
712 -
713 - $stringToReplaceField = str_replace('${' . $fk . '}', $repeaterFieldData, $stringToReplaceField);
714 - }
715 - }
716 - return $stringToReplaceField;
717 - }
718 -
719 - private static function replaceImgTagForRepeatedSignature($stringToReplaceField, $repeaterValue, $fldKey)
720 - {
721 - $data = self::decodeIfJson($repeaterValue);
722 - if (!is_string($stringToReplaceField) || empty($stringToReplaceField) || empty($fldKey)) {
723 - return $stringToReplaceField;
724 - }
725 -
726 - $pattern = '/<img\s+[^>]*src=[\'"]([^\'"]*' . preg_quote($fldKey, '/') . '[^\'"]*)[\'"][^>]*>/i';
727 -
728 - if (!preg_match($pattern, $stringToReplaceField)) {
729 - return $stringToReplaceField;
730 - }
731 -
732 - $values = [];
733 - $appendValue = function ($value) use (&$values) {
734 - if (is_array($value)) {
735 - foreach ($value as $item) {
736 - if (is_string($item) && '' !== trim($item) && 'signature-failed.png' !== $item) {
737 - $values[] = $item;
738 - }
739 - }
740 - return;
741 - }
742 -
743 - if (is_string($value) && '' !== trim($value) && 'signature-failed.png' !== $value) {
744 - $values[] = $value;
745 - }
746 - };
747 -
748 - $appendValue($data);
749 -
750 - if (empty($values)) {
751 - return preg_replace($pattern, '', $stringToReplaceField);
752 - }
753 -
754 - return preg_replace_callback($pattern, function ($matches) use ($values) {
755 - $imgTags = array_map(function ($value) use ($matches) {
756 - $src = htmlspecialchars($value, ENT_QUOTES);
757 - $alt = htmlspecialchars($value, ENT_QUOTES);
758 -
759 - $tag = $matches[0];
760 - $tag = preg_replace('/\bsrc\s*=\s*([\'"])(.*?)\1/i', 'src="' . $src . '"', $tag);
761 -
762 - if (preg_match('/\balt\s*=\s*([\'"])(.*?)\1/i', $tag)) {
763 - $tag = preg_replace('/\balt\s*=\s*([\'"])(.*?)\1/i', 'alt="' . $alt . '"', $tag);
220 + $formattedFldValues[$fldKey] = htmlspecialchars(rtrim($arrValue, ', '));
221 + $arrValue = '';
764 222 } else {
765 - $tag = preg_replace('/<img\b/i', '<img alt="' . $alt . '"', $tag, 1);
223 + $formattedFldValues[$fldKey] = htmlspecialchars($value);
766 224 }
767 -
768 - return $tag;
769 - }, $values);
770 -
771 - return implode('', $imgTags);
772 - }, $stringToReplaceField);
773 - }
774 -
775 - /**
776 - * Restructures repeater field data to maintain original structure while
777 - * aggregating nested repeater values into top-level indexed arrays.
778 - *
779 - * @param array $data Original field data structure
780 - * @return array Restructured data with aggregated arrays
781 - */
782 - public static function restructureRepeaterData(array $data, $formManagerInstance): array
783 - {
784 - $result = $data;
785 - // topkey === field Key topValue === field value
786 - foreach ($data as $topKey => $topValue) {
787 - if ($formManagerInstance->isRepeaterField($topKey)) {
788 - $topValue = self::decodeIfJson($topValue);
789 - //assigning the converted value to repeater field
790 - $result[$topKey] = $topValue;
791 - // topvalue here is repeater field value;
792 - // entryIndex repeater field key , entry == repeater field value
793 - foreach ($topValue as $entryIndex => $entry) {
794 - if (!is_array($entry)) {
795 - continue;
796 - }
797 - foreach ($entry as $subKey => $subValue) {
798 - if (!isset($result[$subKey]) || !is_array($result[$subKey])) {
799 - $result[$subKey] = [];
800 - }
801 - // Handle nested arrays within entries
802 - $result[$subKey][$entryIndex] = $subValue;
803 - }
225 + if ('textarea' === $fldData->typ) {
226 + $formattedFldValues[$fldKey] = nl2br(htmlspecialchars($value));
804 227 }
805 - }
806 - }
807 - return $result;
808 - }
809 -
810 - /**
811 - * Return decoded data if incoming data is stringified and if it's a plain string (e.g "John Doe") it returns the plain string
812 - *
813 - * @param mixed $data
814 - * @return mixed
815 - */
816 - private static function decodeIfJson($data)
817 - {
818 - if (!is_string($data)) {
819 - return $data;
820 - }
821 -
822 - $decoded = json_decode($data, true);
823 -
824 - return (JSON_ERROR_NONE === json_last_error()) ? $decoded : $data;
825 - }
826 -
827 - /**
828 - * Safely converts any type of form value(Specially Repeater Field Value) to string.
829 - *
830 - * @param mixed $data
831 - * @param string $fldType
832 - * @param string|null $fieldKey
833 - * @param array|null $allFieldData
834 - * @param array|null $formFields
835 - * @return string
836 - */
837 - public static function safeFlatString($data, $fldType, $fieldKey = null, $allFieldData = null, $formFields = null, $resolveFromParent = true): string
838 - {
839 - $newData = self::decodeIfJson($data);
840 - if ($resolveFromParent) {
841 - $resolvedChildValues = self::extractRepeaterChildFieldValues($fieldKey, $allFieldData, $formFields);
842 - if (is_array($resolvedChildValues)) {
843 - $newData = $resolvedChildValues;
844 - }
845 - }
846 -
847 - if (is_array($newData)) {
848 - if (self::isCompositeFieldType($fldType)) {
849 - return self::joinCompositeFieldValue($newData, $fldType);
850 - }
851 - return implode(', ', array_map(function ($item) use ($fldType, $fieldKey, $allFieldData, $formFields) {
852 - return is_array($item)
853 - ? '[' . implode(', ', array_map(function ($itm) use ($fldType) {
854 - if (in_array($fldType, ['advanced-file-up', 'file-up']) || self::isFileTypeValue($itm)) {
855 - return self::anchorMarkup($itm);
856 - // if (self::isImageTypeValue($itm)) {
857 - // return "<img src='{$itm}' alt='{$itm}' width='250'/>";
858 - // } else {
859 - // return "<a href='{$itm}' rel='noopener noreferrer' target='_blank' style='color:blue'>{$itm}</a>";
860 - // }
861 - } else {
862 - return $itm;
863 - }
864 - }, $item)) . ']'
865 - : self::safeFlatString($item, $fldType, $fieldKey, $allFieldData, $formFields, false);
866 - }, $newData));
867 - }
868 -
869 - if (is_object($data)) {
870 - return method_exists($data, '__toString') ? (string) $data : (json_encode($data) ?: '');
871 - }
872 -
873 - if (is_null($data)) {
874 - return '';
875 - }
876 -
877 - if (self::isFileTypeValue($data)) {
878 - if ('signature-failed.png' === $data) {
879 - return '';
880 - }
881 - }
882 -
883 - if (in_array($fldType, ['file-up', 'advanced-file-up'])) {
884 - return self::anchorMarkup($newData);
885 - }
886 - if ('signature' === $fldType) {
887 - return self::imgMarkup($newData);
888 - }
889 -
890 - return (string) $data;
891 - }
892 -
893 - private static function removeEmptyValues($fieldData)
894 - {
895 - if (!is_array($fieldData)) {
896 - return $fieldData;
897 - }
898 - // Remove empty values from the array
899 - return array_filter($fieldData, function ($value) {
900 - // Check if the value is 0
901 - if (0 === $value) {
902 - return '0';
903 - }
904 - return !empty($value);
905 - });
906 - }
907 -
908 - /**
909 - * Gets the field type by field key
910 - *
911 - * @param string $fldKey
912 - * @param mixed $formField
913 - * @return string
914 - */
915 - private static function getFldType($fldKey, $formFields)
916 - {
917 - if (array_key_exists($fldKey, $formFields)) {
918 - return $formFields[$fldKey]['type'];
919 - }
920 - }
921 -
922 - private static function isCompositeFieldType($fieldType)
923 - {
924 - return in_array($fieldType, ['name', 'address'], true);
925 - }
926 -
927 - private static function joinCompositeFieldValue($value, $fieldType)
928 - {
929 - if (!is_array($value)) {
930 - return (string) $value;
931 - }
932 -
933 - $parts = [];
934 - array_walk_recursive($value, function ($item) use (&$parts) {
935 - if (is_null($item)) {
936 - return;
937 - }
938 -
939 - $item = (string) $item;
940 - if ('' === trim($item) && '0' !== $item) {
941 - return;
942 - }
943 -
944 - $parts[] = $item;
945 - });
946 -
947 - return implode('address' === $fieldType ? ', ' : ' ', $parts);
948 - }
949 -
950 - private static function extractRepeaterChildFieldValues($fieldKey, $allFieldData, $formFields)
951 - {
952 - if (empty($fieldKey) || !is_array($allFieldData) || !is_array($formFields) || !isset($formFields[$fieldKey])) {
953 - return null;
954 - }
955 -
956 - $fieldData = $formFields[$fieldKey];
957 - if (empty($fieldData['parentFieldKey']) || empty($fieldData['name'])) {
958 - return null;
959 - }
960 -
961 - $parentFieldKey = $fieldData['parentFieldKey'];
962 - $parentFieldName = $formFields[$parentFieldKey]['name'] ?? null;
963 -
964 - if (!isset($allFieldData[$parentFieldKey]) || !is_array($allFieldData[$parentFieldKey])) {
965 - return null;
966 - }
967 -
968 - $childFieldName = $fieldData['name'];
969 - $childFieldName = str_replace(['[', ']', $parentFieldName], '', $childFieldName);
970 -
971 - $childValues = [];
972 - foreach ($allFieldData[$parentFieldKey] as $row) {
973 - if (!is_array($row)) {
974 - continue;
975 - }
976 -
977 - if (array_key_exists($childFieldName, $row)) {
978 - $childValues[] = $row[$childFieldName];
979 - continue;
980 - }
981 -
982 - if (array_key_exists($fieldKey, $row)) {
983 - $childValues[] = $row[$fieldKey];
984 - }
985 - }
986 -
987 - return $childValues;
988 - }
989 -
990 - /**
991 - * Return true is it's file type value by checking with extension
992 - *
993 - * @param string $filename
994 - * @return boolean
995 - */
996 - private static function isFileTypeValue($fileName)
997 - {
998 - if (!is_string($fileName)) {
999 - return false;
1000 - }
1001 - $ext = pathinfo($fileName, PATHINFO_EXTENSION);
1002 -
1003 - if ('other' !== FileHandler::getFileTypeByExtension($ext)) {
1004 - return true;
1005 - }
1006 - }
1007 -
1008 - /**
1009 - * Return true is it's image type value by checking with extension
1010 - *
1011 - * @param string $filename
1012 - * @return boolean
1013 - */
1014 - private static function isImageTypeValue($fileName)
1015 - {
1016 - if (!is_string($fileName)) {
1017 - return false;
1018 - }
1019 - $ext = pathinfo($fileName, PATHINFO_EXTENSION);
1020 -
1021 - if ('image' === FileHandler::getFileTypeByExtension($ext)) {
1022 - return true;
1023 - }
1024 - }
1025 -
1026 - private static function repeaterFieldTable($repeaterFieldData, $formFields, $repeaterFieldKey)
1027 - {
1028 - $repeaterFieldData = self::decodeIfJson($repeaterFieldData);
1029 -
1030 - if (!is_array($repeaterFieldData) || !isset($repeaterFieldData[0]) || !is_array($repeaterFieldData[0])) {
1031 - return ''; // Safely return empty if not a valid repeater structure
1032 - }
1033 - $table = "<table style='font-family: arial, sans-serif; border-collapse: collapse; width: 100%;'>";
1034 - // $table .= '<tr>';
1035 - // $table .= '<th style="border: 1px solid #dddddd; text-align: left; padding: 8px;">' . self::getLabel($formFields, $repeaterFieldKey) . '</th>';
1036 - // $table .= '</tr>';
1037 - // $table .= '<td style="border: 1px solid #dddddd; text-align: left; padding: 8px;">';
1038 - // $table .= '<table style="width: 100%; border-collapse: collapse;">';
1039 -
1040 - $headers = array_keys($repeaterFieldData[0]);
1041 - $table .= '<tr>'; // open tr (for column header)
1042 - foreach ($headers as $fk) {
1043 - $table .= '<th style="border: 1px solid #dddddd; padding: 8px; ">' . self::getLabel($formFields, $fk) . '</th>';
1044 - }
1045 - $table .= '</tr>'; // close tr (for column header)
1046 -
1047 - foreach ($repeaterFieldData as $row) {
1048 - $table .= '<tr>'; // open tr (for table data row)
1049 - foreach ($row as $k=>$value) {
1050 - $fldTyp = self::getFldType($k, $formFields);
1051 - if (is_array($value)) {
1052 - if (in_array($fldTyp, ['advanced-file-up', 'file-up'])) {
1053 - $newValue = self::unorderedAnchorListMarkup($value);
1054 - } elseif (self::isCompositeFieldType($fldTyp)) {
1055 - $newValue = self::joinCompositeFieldValue($value, $fldTyp);
1056 - } else {
1057 - $newValue = implode(', ', $value);
1058 - }
1059 - } else {
1060 - if (self::isFileTypeValue($value)) {
1061 - $newValue = 'signature-failed.png' === $value
1062 - ? ''
1063 - : (self::isImageTypeValue($value)
1064 - ? "<img src='{$value}' alt='{$value}' width='250'/>"
1065 - : "<a href='{$value}' rel='noopener noreferrer' target='_blank' style='color:blue'>{$value}</a>");
1066 - } else {
1067 - $newValue = $value;
1068 - }
228 + if ('date' === $fldData->typ) {
229 + $formattedFldValues[$fldKey] = date_i18n(get_option('date_format'), strtotime(htmlspecialchars($value)));
1069 230 }
1070 -
1071 - $table .= '<td style="border: 1px solid #dddddd; padding: 8px;">' . $newValue . '</td>';
1072 231 }
1073 - $table .= '</tr>'; // close tr (for table data row)
1074 232 }
1075 - // $table .= '</table>';
1076 233
1077 - // $table .= '</td>';
1078 - $table .= '</table>';
1079 -
1080 - return $table;
1081 - }
1082 -
1083 - private static function getLabel($formFields, $key)
1084 - {
1085 - return $formFields[$key]['label'] ?? $key;
234 + $merge_values = array_merge($fieldValues, $formattedFldValues);
235 + $merge_values = array_merge($merge_values, $repeaterFieldKey);
236 + return $merge_values;
1086 237 }
1087 238 }