PluginProbe
The Innovative Form Builder – IvyForms / 1.0
The Innovative Form Builder – IvyForms v1.0
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.0, at backend/src/Services/Submission/SubmissionDataFormatterService.php

311 lines 10.0 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 * Constructor.
30 */
31 public function __construct()
32 {
33 $this->formatter = new SubmissionFieldFormatter();
34 $this->typeFormatter = new SubmissionFieldTypeFormatter();
35 }
36 /**
37 * Format submission data into a standardized structure for integrations.
38 *
39 * @param array<int|string, mixed> $submissionData Raw submission data (indexed by fieldIndex)
40 * @param array<int, object> $formFields Array of Field objects
41 * @return array{
42 * fields: array<string, mixed>,
43 * user_inputs: array<string, string>,
44 * field_metadata: array<string, array{id: int, type: string, label: string}>
45 * }
46 */
47 public function formatForIntegrations(array $submissionData, array $formFields): array
48 {
49 // Build parent-children map and subfield keys (same as entry system)
50 $parentChildrenMap = EntryHelper::buildParentChildrenMap($formFields);
51 $parentSubfieldKeys = EntryHelper::buildParentSubfieldKeys($parentChildrenMap, $submissionData);
52 $fieldIdToIndex = $this->buildFieldIdToIndexMap($formFields);
53
54 // Build formatted data
55 $fields = []; // Original values (arrays/objects for complex fields)
56 $userInputs = []; // Formatted readable strings
57 $fieldMetadata = []; // Field information for integrations
58 $typeCounters = []; // Track count of each field type
59
60 // Iterate through all fields (including subfields)
61 foreach ($formFields as $field) {
62 $result = $this->processFieldForIntegration(
63 $field,
64 $submissionData,
65 $parentSubfieldKeys,
66 $fieldIdToIndex,
67 $typeCounters
68 );
69
70 if ($result !== null) {
71 $fields[$result['key']] = $result['value'];
72 $userInputs[$result['key']] = $result['userInput'];
73 $fieldMetadata[$result['key']] = $result['metadata'];
74 }
75 }
76
77 return [
78 'fields' => $fields,
79 'user_inputs' => $userInputs,
80 'field_metadata' => $fieldMetadata,
81 ];
82 }
83
84 /**
85 * Build field ID to index map.
86 *
87 * @param array<object> $formFields
88 * @return array<int, int>
89 */
90 private function buildFieldIdToIndexMap(array $formFields): array
91 {
92 $fieldIdToIndex = [];
93 foreach ($formFields as $field) {
94 $fieldIndex = $this->getFieldIndex($field);
95 if ($fieldIndex !== null) {
96 $fieldIdToIndex[$field->getId()] = $fieldIndex;
97 }
98 }
99 return $fieldIdToIndex;
100 }
101
102 /**
103 * Get field index (converted to 0-based).
104 *
105 * @param object $field
106 * @return int|null
107 */
108 private function getFieldIndex(object $field): ?int
109 {
110 if (!method_exists($field, 'getIndex')) {
111 return null;
112 }
113
114 $fieldIndex = $field->getIndex();
115 return ($fieldIndex !== null && $fieldIndex > 0) ? $fieldIndex - 1 : null;
116 }
117
118 /**
119 * Process a single field for integration formatting.
120 *
121 * @param object $field
122 * @param array<int|string, mixed> $submissionData
123 * @param array<int, string|null> $parentSubfieldKeys
124 * @param array<int, int> $fieldIdToIndex
125 * @param array<string, int> $typeCounters
126 * @return array{key: string, value: mixed, userInput: string, metadata: array<string, mixed>}|null
127 */
128 private function processFieldForIntegration(
129 object $field,
130 array $submissionData,
131 array $parentSubfieldKeys,
132 array $fieldIdToIndex,
133 array &$typeCounters
134 ): ?array {
135 $fieldType = $field->getType();
136 $parentId = method_exists($field, 'getParentId') ? $field->getParentId() : null;
137
138 // Skip security/CAPTCHA fields and subfields
139 if (FieldHelper::isSecurityField($fieldType) || ($parentId !== null && $parentId > 0)) {
140 return null;
141 }
142
143 $value = $this->resolveFieldValue($field, $submissionData, $parentSubfieldKeys, $fieldIdToIndex);
144
145 // Skip empty values
146 if ($value === null || $value === '' || (is_array($value) && empty($value))) {
147 return null;
148 }
149
150 $fieldKey = $this->generateUniqueFieldKey($fieldType, $typeCounters);
151
152 return [
153 'key' => $fieldKey,
154 'value' => $this->formatFieldValueByType($value, $fieldType, $field),
155 'userInput' => $this->formatter->formatForDisplay($value, $fieldType),
156 'metadata' => [
157 'id' => $field->getId(),
158 'type' => $fieldType,
159 'label' => $this->getFieldLabel($field),
160 ],
161 ];
162 }
163
164 /**
165 * Generate unique field key with counter.
166 *
167 * @param string $fieldType
168 * @param array<string, int> $typeCounters
169 * @return string
170 */
171 private function generateUniqueFieldKey(string $fieldType, array &$typeCounters): string
172 {
173 if (!isset($typeCounters[$fieldType])) {
174 $typeCounters[$fieldType] = 0;
175 return $fieldType;
176 }
177
178 $typeCounters[$fieldType]++;
179 return $fieldType . '_' . $typeCounters[$fieldType];
180 }
181
182 /**
183 * Get field label from field object.
184 *
185 * @param object $field
186 * @return string
187 */
188 private function getFieldLabel(object $field): string
189 {
190 if (!method_exists($field, 'getFieldGeneralSettings')) {
191 return '';
192 }
193
194 $generalSettings = $field->getFieldGeneralSettings();
195 return ($generalSettings && method_exists($generalSettings, 'getLabel'))
196 ? $generalSettings->getLabel()
197 : '';
198 }
199
200 /**
201 * Format a raw field value for the integration 'fields' root payload.
202 *
203 * Delegates to SubmissionFieldTypeFormatter which applies type-specific
204 * transformations (phone stripping, date ISO conversion, etc.).
205 *
206 * @param mixed $value Raw submission value
207 * @param string $type Field type slug (e.g. 'phone', 'date', 'email')
208 * @param object|null $field Field object for accessing field-level settings
209 * @return mixed Formatted value
210 */
211 public function formatFieldValueByType($value, string $type, ?object $field = null)
212 {
213 return $this->typeFormatter->format($value, $type, $field);
214 }
215
216 /**
217 * Resolve field value (mirrors entry system logic).
218 *
219 * @param object $field The field object
220 * @param array<int|string, mixed> $submissionData Submission data keyed by fieldIndex
221 * @param array<int, string|null> $parentSubfieldKeys Map of child field IDs to subfield keys
222 * @param array<int, int> $fieldIdToIndex Map of field IDs to field indices
223 * @return mixed The resolved field value
224 */
225 private function resolveFieldValue(
226 object $field,
227 array $submissionData,
228 array $parentSubfieldKeys,
229 array $fieldIdToIndex
230 ) {
231 $parentId = method_exists($field, 'getParentId') ? $field->getParentId() : null;
232
233 $subfieldValue = $this->resolveSubfieldValue(
234 $parentId,
235 $field->getId(),
236 $parentSubfieldKeys,
237 $fieldIdToIndex,
238 $submissionData
239 );
240
241 if ($subfieldValue !== false) {
242 return $subfieldValue;
243 }
244
245 $fieldIndex = $fieldIdToIndex[$field->getId()] ?? null;
246 $compoundValue = $this->resolveCompoundFieldValue(
247 $field->getType(),
248 $parentId,
249 $fieldIndex,
250 $submissionData
251 );
252
253 return $compoundValue ?? ($fieldIndex !== null ? ($submissionData[$fieldIndex] ?? '') : '');
254 }
255
256 /**
257 * Resolve subfield value from parent field.
258 *
259 * @param int|null $parentId
260 * @param int $fieldId
261 * @param array<int, string|null> $parentSubfieldKeys
262 * @param array<int, int> $fieldIdToIndex
263 * @param array<int|string, mixed> $submissionData
264 * @return mixed|false False if not a subfield, value otherwise
265 */
266 private function resolveSubfieldValue(
267 ?int $parentId,
268 int $fieldId,
269 array $parentSubfieldKeys,
270 array $fieldIdToIndex,
271 array $submissionData
272 ) {
273 if ($parentId === null || $parentId <= 0 || !isset($parentSubfieldKeys[$fieldId])) {
274 return false;
275 }
276
277 $parentFieldIndex = $fieldIdToIndex[$parentId] ?? null;
278 $subKey = $parentSubfieldKeys[$fieldId];
279
280 if ($parentFieldIndex !== null && isset($submissionData[$parentFieldIndex][$subKey])) {
281 return $submissionData[$parentFieldIndex][$subKey];
282 }
283
284 return null;
285 }
286
287 /**
288 * Resolve compound field value (name, address).
289 *
290 * @param string $fieldType
291 * @param int|null $parentId
292 * @param int|null $fieldIndex
293 * @param array<int|string, mixed> $submissionData
294 * @return mixed|null
295 */
296 private function resolveCompoundFieldValue(
297 string $fieldType,
298 ?int $parentId,
299 ?int $fieldIndex,
300 array $submissionData
301 ) {
302 if (!in_array($fieldType, ['name', 'address'], true) || ($parentId !== null && $parentId !== 0)) {
303 return null;
304 }
305
306 return ($fieldIndex !== null && isset($submissionData[$fieldIndex]) && is_array($submissionData[$fieldIndex]))
307 ? $submissionData[$fieldIndex]
308 : '';
309 }
310 }
311