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

233 lines 7.4 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(
60 $parentChildrenMap,
61 $submissionData,
62 $formFields
63 );
64 $fieldIdToIndex = $this->buildFieldIdToIndexMap($formFields);
65
66 // Build formatted data
67 $fields = []; // Original values (arrays/objects for complex fields)
68 $userInputs = []; // Formatted readable strings
69 $fieldMetadata = []; // Field information for integrations
70 $typeCounters = []; // Track count of each field type
71
72 // Iterate through all fields (including subfields)
73 foreach ($formFields as $field) {
74 $result = $this->processFieldForIntegration(
75 $field,
76 $submissionData,
77 $parentSubfieldKeys,
78 $fieldIdToIndex,
79 $typeCounters
80 );
81
82 if ($result !== null) {
83 $fields[$result['key']] = $result['value'];
84 $userInputs[$result['key']] = $result['userInput'];
85 $fieldMetadata[$result['key']] = $result['metadata'];
86 }
87 }
88
89 return [
90 'fields' => $fields,
91 'user_inputs' => $userInputs,
92 'field_metadata' => $fieldMetadata,
93 ];
94 }
95
96 /**
97 * Build field ID to index map.
98 *
99 * @param array<object> $formFields
100 * @return array<int, int>
101 */
102 private function buildFieldIdToIndexMap(array $formFields): array
103 {
104 $fieldIdToIndex = [];
105 foreach ($formFields as $field) {
106 $fieldIndex = $this->getFieldIndex($field);
107 if ($fieldIndex !== null) {
108 $fieldIdToIndex[$field->getId()] = $fieldIndex;
109 }
110 }
111 return $fieldIdToIndex;
112 }
113
114 /**
115 * Get field index (converted to 0-based).
116 *
117 * @param object $field
118 * @return int|null
119 */
120 private function getFieldIndex(object $field): ?int
121 {
122 if (!method_exists($field, 'getIndex')) {
123 return null;
124 }
125
126 $fieldIndex = $field->getIndex();
127 return ($fieldIndex !== null && $fieldIndex > 0) ? $fieldIndex - 1 : null;
128 }
129
130 /**
131 * Process a single field for integration formatting.
132 *
133 * @param object $field
134 * @param array<int|string, mixed> $submissionData
135 * @param array<int, string|null> $parentSubfieldKeys
136 * @param array<int, int> $fieldIdToIndex
137 * @param array<string, int> $typeCounters
138 * @return array{key: string, value: mixed, userInput: string, metadata: array<string, mixed>}|null
139 */
140 private function processFieldForIntegration(
141 object $field,
142 array $submissionData,
143 array $parentSubfieldKeys,
144 array $fieldIdToIndex,
145 array &$typeCounters
146 ): ?array {
147 $fieldType = $field->getType();
148 $parentId = method_exists($field, 'getParentId') ? $field->getParentId() : null;
149
150 // Skip security/CAPTCHA fields and subfields
151 if (FieldHelper::isSecurityField($fieldType) || ($parentId !== null && $parentId > 0)) {
152 return null;
153 }
154
155 $value = $this->fieldValueResolver->resolveFieldValue(
156 $field,
157 $submissionData,
158 $parentSubfieldKeys,
159 $fieldIdToIndex
160 );
161
162 // Skip empty values
163 if ($value === null || $value === '' || (is_array($value) && empty($value))) {
164 return null;
165 }
166
167 $fieldKey = $this->generateUniqueFieldKey($fieldType, $typeCounters);
168
169 return [
170 'key' => $fieldKey,
171 'value' => $this->formatFieldValueByType($value, $fieldType, $field),
172 'userInput' => $this->formatter->formatForDisplay($value, $fieldType),
173 'metadata' => [
174 'id' => $field->getId(),
175 'type' => $fieldType,
176 'label' => $this->getFieldLabel($field),
177 ],
178 ];
179 }
180
181 /**
182 * Generate unique field key with counter.
183 *
184 * @param string $fieldType
185 * @param array<string, int> $typeCounters
186 * @return string
187 */
188 private function generateUniqueFieldKey(string $fieldType, array &$typeCounters): string
189 {
190 if (!isset($typeCounters[$fieldType])) {
191 $typeCounters[$fieldType] = 0;
192 return $fieldType;
193 }
194
195 $typeCounters[$fieldType]++;
196 return $fieldType . '_' . $typeCounters[$fieldType];
197 }
198
199 /**
200 * Get field label from field object.
201 *
202 * @param object $field
203 * @return string
204 */
205 private function getFieldLabel(object $field): string
206 {
207 if (!method_exists($field, 'getFieldGeneralSettings')) {
208 return '';
209 }
210
211 $generalSettings = $field->getFieldGeneralSettings();
212 return ($generalSettings && method_exists($generalSettings, 'getLabel'))
213 ? $generalSettings->getLabel()
214 : '';
215 }
216
217 /**
218 * Format a raw field value for the integration 'fields' root payload.
219 *
220 * Delegates to SubmissionFieldTypeFormatter which applies type-specific
221 * transformations (phone stripping, date ISO conversion, etc.).
222 *
223 * @param mixed $value Raw submission value
224 * @param string $type Field type slug (e.g. 'phone', 'date', 'email')
225 * @param object|null $field Field object for accessing field-level settings
226 * @return mixed Formatted value
227 */
228 public function formatFieldValueByType($value, string $type, ?object $field = null)
229 {
230 return $this->typeFormatter->format($value, $type, $field);
231 }
232 }
233