PluginProbe
The Innovative Form Builder – IvyForms / 1.1
The Innovative Form Builder – IvyForms v1.1
1.5.1 1.5 1.4.1 1.4 trunk 0.1.2 0.2 0.2.1 0.3 0.3.1 0.4 0.5 0.6 0.6.1 0.6.1-backup 0.6.1.1 0.7 0.8 0.8.1 0.8.2 0.9 0.9.1 1.0 1.1 1.1.1 All 27 releases
ivyforms / backend / src / Services / Submission / SubmissionDataFormatterService.php

SubmissionDataFormatterService.php in The Innovative Form Builder – IvyForms 1.1, at backend/src/Services/Submission/SubmissionDataFormatterService.php

229 lines 7.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace IvyForms\Services\Submission;
4
5 use IvyForms\Common\Helpers\EntryHelper;
6 use IvyForms\Common\Helpers\FieldHelper;
7
8 /**
9 * Submission Data Formatter Service
10 *
11 * Centralizes submission data formatting for use across all integrations (webhooks, emails, etc.)
12 * Provides a standardized data structure that integrations can adapt as needed.
13 *
14 * @package IvyForms\Services\Submission
15 */
16 class SubmissionDataFormatterService
17 {
18 /**
19 * @var SubmissionFieldFormatter
20 */
21 private SubmissionFieldFormatter $formatter;
22
23 /**
24 * @var SubmissionFieldTypeFormatter
25 */
26 private SubmissionFieldTypeFormatter $typeFormatter;
27
28 /**
29 * @var SubmissionIntegrationFieldValueResolver
30 */
31 private SubmissionIntegrationFieldValueResolver $fieldValueResolver;
32
33 /**
34 * Constructor.
35 */
36 public function __construct()
37 {
38 $this->formatter = new SubmissionFieldFormatter();
39 $this->typeFormatter = new SubmissionFieldTypeFormatter();
40 $this->fieldValueResolver = new SubmissionIntegrationFieldValueResolver();
41 }
42
43 /**
44 * Format submission data into a standardized structure for integrations.
45 *
46 * @param array<int|string, mixed> $submissionData Raw submission data (keys are field database
47 * ids from sanitization; legacy 0-based indices still supported)
48 * @param array<int, object> $formFields Array of Field objects
49 * @return array{
50 * fields: array<string, mixed>,
51 * user_inputs: array<string, string>,
52 * field_metadata: array<string, array{id: int, type: string, label: string}>
53 * }
54 */
55 public function formatForIntegrations(array $submissionData, array $formFields): array
56 {
57 // Build parent-children map and subfield keys (same as entry system)
58 $parentChildrenMap = EntryHelper::buildParentChildrenMap($formFields);
59 $parentSubfieldKeys = EntryHelper::buildParentSubfieldKeys($parentChildrenMap, $submissionData);
60 $fieldIdToIndex = $this->buildFieldIdToIndexMap($formFields);
61
62 // Build formatted data
63 $fields = []; // Original values (arrays/objects for complex fields)
64 $userInputs = []; // Formatted readable strings
65 $fieldMetadata = []; // Field information for integrations
66 $typeCounters = []; // Track count of each field type
67
68 // Iterate through all fields (including subfields)
69 foreach ($formFields as $field) {
70 $result = $this->processFieldForIntegration(
71 $field,
72 $submissionData,
73 $parentSubfieldKeys,
74 $fieldIdToIndex,
75 $typeCounters
76 );
77
78 if ($result !== null) {
79 $fields[$result['key']] = $result['value'];
80 $userInputs[$result['key']] = $result['userInput'];
81 $fieldMetadata[$result['key']] = $result['metadata'];
82 }
83 }
84
85 return [
86 'fields' => $fields,
87 'user_inputs' => $userInputs,
88 'field_metadata' => $fieldMetadata,
89 ];
90 }
91
92 /**
93 * Build field ID to index map.
94 *
95 * @param array<object> $formFields
96 * @return array<int, int>
97 */
98 private function buildFieldIdToIndexMap(array $formFields): array
99 {
100 $fieldIdToIndex = [];
101 foreach ($formFields as $field) {
102 $fieldIndex = $this->getFieldIndex($field);
103 if ($fieldIndex !== null) {
104 $fieldIdToIndex[$field->getId()] = $fieldIndex;
105 }
106 }
107 return $fieldIdToIndex;
108 }
109
110 /**
111 * Get field index (converted to 0-based).
112 *
113 * @param object $field
114 * @return int|null
115 */
116 private function getFieldIndex(object $field): ?int
117 {
118 if (!method_exists($field, 'getIndex')) {
119 return null;
120 }
121
122 $fieldIndex = $field->getIndex();
123 return ($fieldIndex !== null && $fieldIndex > 0) ? $fieldIndex - 1 : null;
124 }
125
126 /**
127 * Process a single field for integration formatting.
128 *
129 * @param object $field
130 * @param array<int|string, mixed> $submissionData
131 * @param array<int, string|null> $parentSubfieldKeys
132 * @param array<int, int> $fieldIdToIndex
133 * @param array<string, int> $typeCounters
134 * @return array{key: string, value: mixed, userInput: string, metadata: array<string, mixed>}|null
135 */
136 private function processFieldForIntegration(
137 object $field,
138 array $submissionData,
139 array $parentSubfieldKeys,
140 array $fieldIdToIndex,
141 array &$typeCounters
142 ): ?array {
143 $fieldType = $field->getType();
144 $parentId = method_exists($field, 'getParentId') ? $field->getParentId() : null;
145
146 // Skip security/CAPTCHA fields and subfields
147 if (FieldHelper::isSecurityField($fieldType) || ($parentId !== null && $parentId > 0)) {
148 return null;
149 }
150
151 $value = $this->fieldValueResolver->resolveFieldValue(
152 $field,
153 $submissionData,
154 $parentSubfieldKeys,
155 $fieldIdToIndex
156 );
157
158 // Skip empty values
159 if ($value === null || $value === '' || (is_array($value) && empty($value))) {
160 return null;
161 }
162
163 $fieldKey = $this->generateUniqueFieldKey($fieldType, $typeCounters);
164
165 return [
166 'key' => $fieldKey,
167 'value' => $this->formatFieldValueByType($value, $fieldType, $field),
168 'userInput' => $this->formatter->formatForDisplay($value, $fieldType),
169 'metadata' => [
170 'id' => $field->getId(),
171 'type' => $fieldType,
172 'label' => $this->getFieldLabel($field),
173 ],
174 ];
175 }
176
177 /**
178 * Generate unique field key with counter.
179 *
180 * @param string $fieldType
181 * @param array<string, int> $typeCounters
182 * @return string
183 */
184 private function generateUniqueFieldKey(string $fieldType, array &$typeCounters): string
185 {
186 if (!isset($typeCounters[$fieldType])) {
187 $typeCounters[$fieldType] = 0;
188 return $fieldType;
189 }
190
191 $typeCounters[$fieldType]++;
192 return $fieldType . '_' . $typeCounters[$fieldType];
193 }
194
195 /**
196 * Get field label from field object.
197 *
198 * @param object $field
199 * @return string
200 */
201 private function getFieldLabel(object $field): string
202 {
203 if (!method_exists($field, 'getFieldGeneralSettings')) {
204 return '';
205 }
206
207 $generalSettings = $field->getFieldGeneralSettings();
208 return ($generalSettings && method_exists($generalSettings, 'getLabel'))
209 ? $generalSettings->getLabel()
210 : '';
211 }
212
213 /**
214 * Format a raw field value for the integration 'fields' root payload.
215 *
216 * Delegates to SubmissionFieldTypeFormatter which applies type-specific
217 * transformations (phone stripping, date ISO conversion, etc.).
218 *
219 * @param mixed $value Raw submission value
220 * @param string $type Field type slug (e.g. 'phone', 'date', 'email')
221 * @param object|null $field Field object for accessing field-level settings
222 * @return mixed Formatted value
223 */
224 public function formatFieldValueByType($value, string $type, ?object $field = null)
225 {
226 return $this->typeFormatter->format($value, $type, $field);
227 }
228 }
229