PluginProbe
The Innovative Form Builder – IvyForms / 1.1.1
The Innovative Form Builder – IvyForms v1.1.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 / Common / Helpers / FormImportValidator.php

FormImportValidator.php in The Innovative Form Builder – IvyForms 1.1.1, at backend/src/Common/Helpers/FormImportValidator.php

374 lines 10.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace IvyForms\Common\Helpers;
4
5 use IvyForms\Common\Exceptions\InvalidArgumentException;
6 use IvyForms\Services\Translations\BackendStrings;
7
8 class FormImportValidator
9 {
10 /**
11 * Validate top-level import payload and return forms array.
12 *
13 * @param mixed $importData
14 *
15 * @return array<int, array<string, mixed>>
16 * @throws InvalidArgumentException
17 */
18 public static function validateImportPayload($importData): array
19 {
20 if (empty($importData)) {
21 throw new InvalidArgumentException(
22 BackendStrings::getExceptionStrings()['no_import_data_provided']
23 );
24 }
25
26 if (!isset($importData['forms']) || !is_array($importData['forms'])) {
27 throw new InvalidArgumentException(
28 BackendStrings::getExceptionStrings()['invalid_import_data_structure']
29 );
30 }
31
32 foreach ($importData['forms'] as $formData) {
33 self::validateSingleFormStructure($formData);
34 }
35
36 return $importData['forms'];
37 }
38
39 /**
40 * Build a map of field database ID to fieldIndex.
41 *
42 * @param array<int, array<string, mixed>> $fields
43 * @return array<int, int>
44 */
45 public static function buildFieldIdToIndexMap(array $fields): array
46 {
47 $idToIndexMap = [];
48
49 foreach ($fields as $field) {
50 if (isset($field['id']) && isset($field['fieldIndex'])) {
51 $idToIndexMap[(int)$field['id']] = (int)$field['fieldIndex'];
52 }
53 }
54
55 return $idToIndexMap;
56 }
57
58 /**
59 * Prepare form data for export from entity toArray payload.
60 *
61 * @param mixed $form
62 * @return array<string, mixed>
63 */
64 public static function prepareFormDataForExport($form): array
65 {
66 $formArray = $form->toArray();
67 unset(
68 $formArray['id'],
69 $formArray['author'],
70 $formArray['dateCreated'],
71 $formArray['dateEdited'],
72 $formArray['fields']
73 );
74
75 return $formArray;
76 }
77
78 /**
79 * Prepare fields data for export.
80 *
81 * @param array<int, array<string, mixed>> $fields
82 * @param array<int, int> $idToIndexMap
83 * @return array<int, array<string, mixed>>
84 */
85 public static function prepareFieldsDataForExport(array $fields, array $idToIndexMap): array
86 {
87 if (empty($fields)) {
88 return [];
89 }
90
91 $exportFields = [];
92 foreach ($fields as $fieldArray) {
93 $exportFields[] = self::prepareFieldForExport($fieldArray, $idToIndexMap);
94 }
95
96 return $exportFields;
97 }
98
99 /**
100 * Prepare a single field for export.
101 *
102 * @param array<string, mixed> $fieldArray
103 * @param array<int, int> $idToIndexMap
104 * @return array<string, mixed>
105 */
106 private static function prepareFieldForExport(array $fieldArray, array $idToIndexMap): array
107 {
108 $fieldArray = self::normalizeFieldData($fieldArray);
109 unset($fieldArray['id'], $fieldArray['formId'], $fieldArray['position'], $fieldArray['rows']);
110
111 $fieldArray['parentId'] = self::convertParentIdToFieldIndex($fieldArray, $idToIndexMap);
112 $fieldArray['fieldOptions'] = self::prepareFieldOptionsForExport($fieldArray);
113
114 return self::normalizeFieldData($fieldArray);
115 }
116
117 /**
118 * Normalize malformed top-level numeric field keys into a flat associative array.
119 *
120 * @param array<string|int, mixed> $fieldData
121 * @return array<string, mixed>
122 */
123 private static function normalizeFieldData(array $fieldData): array
124 {
125 $normalizedFieldData = [];
126
127 foreach ($fieldData as $key => $value) {
128 if (is_int($key)) {
129 if (!is_array($value)) {
130 continue;
131 }
132
133 foreach ($value as $nestedKey => $nestedValue) {
134 if (is_string($nestedKey)) {
135 $normalizedFieldData[$nestedKey] = $nestedValue;
136 }
137 }
138
139 continue;
140 }
141
142 $normalizedFieldData[$key] = $value;
143 }
144
145 return $normalizedFieldData;
146 }
147
148 /**
149 * Convert parent database ID to fieldIndex.
150 *
151 * @param array<string, mixed> $fieldArray
152 * @param array<int, int> $idToIndexMap
153 * @return int|null
154 */
155 private static function convertParentIdToFieldIndex(array $fieldArray, array $idToIndexMap): ?int
156 {
157 if (!array_key_exists('parentId', $fieldArray) || $fieldArray['parentId'] === null) {
158 return null;
159 }
160
161 return $idToIndexMap[(int)$fieldArray['parentId']] ?? null;
162 }
163
164 /**
165 * Prepare field options for export.
166 *
167 * @param array<string, mixed> $fieldArray
168 * @return array<int, array<string, mixed>>|null
169 */
170 private static function prepareFieldOptionsForExport(array $fieldArray): ?array
171 {
172 if (empty($fieldArray['fieldOptions'])) {
173 return null;
174 }
175
176 $exportFieldOptions = [];
177 foreach ($fieldArray['fieldOptions'] as $option) {
178 unset($option['id'], $option['fieldId']);
179 $exportFieldOptions[] = $option;
180 }
181
182 return $exportFieldOptions;
183 }
184
185 /**
186 * Prepare notifications data for export from entity toArray payload.
187 *
188 * @param array<int, mixed> $notifications
189 * @return array<int, array<string, mixed>>
190 */
191 public static function prepareNotificationsDataForExport(array $notifications): array
192 {
193 if (empty($notifications)) {
194 return [];
195 }
196
197 $exportNotifications = [];
198 foreach ($notifications as $notification) {
199 $notificationArray = $notification->toArray();
200 unset($notificationArray['id'], $notificationArray['formId']);
201 $exportNotifications[] = $notificationArray;
202 }
203
204 return $exportNotifications;
205 }
206
207 /**
208 * Prepare confirmation data for export from entity toArray payload.
209 *
210 * @param mixed $confirmation
211 * @return array<string, mixed>|null
212 */
213 public static function prepareConfirmationDataForExport($confirmation): ?array
214 {
215 if (!$confirmation) {
216 return null;
217 }
218
219 $confirmationArray = $confirmation->toArray();
220 unset($confirmationArray['id'], $confirmationArray['formId']);
221
222 return $confirmationArray;
223 }
224
225 /**
226 * Prepare form data payload for import.
227 *
228 * @param array<string, mixed> $formData
229 * @param string $author
230 * @param string $currentTime
231 * @param string $submitLabel
232 * @return array<string, mixed>
233 */
234 public static function prepareFormArrayForImport(
235 array $formData,
236 string $author,
237 string $currentTime,
238 string $submitLabel
239 ): array {
240 $formArray = $formData['form'] ?? [];
241 $formArray['id'] = 0;
242 $formArray['author'] = $author;
243 $formArray['dateCreated'] = $currentTime;
244 $formArray['dateEdited'] = $currentTime;
245
246 if (!isset($formArray['integrationSettings'])) {
247 $formArray['integrationSettings'] = '{}';
248 }
249
250 if (!isset($formArray['formActionButtons'])) {
251 $formArray['formActionButtons'] = [
252 'submitButtonSettings' => [
253 'label' => $submitLabel,
254 'position' => 'default'
255 ]
256 ];
257 }
258
259 return $formArray;
260 }
261
262 /**
263 * Normalize imported fields before persistence.
264 *
265 * @param array<int, array<string, mixed>> $fieldsData
266 * @return array<int, array<string, mixed>>
267 */
268 public static function normalizeFieldsForImport(array $fieldsData): array
269 {
270 return array_map(static fn(array $fieldData): array => self::normalizeFieldData($fieldData), $fieldsData);
271 }
272
273 /**
274 * Validate single form import structure.
275 *
276 * @param mixed $formData
277 *
278 * @return void
279 * @throws InvalidArgumentException
280 */
281 private static function validateSingleFormStructure($formData): void
282 {
283 self::assertArray(
284 $formData,
285 BackendStrings::getExceptionStrings()['invalid_import_data_structure']
286 );
287
288 self::assertRequiredArrayKey(
289 $formData,
290 'form',
291 BackendStrings::getExceptionStrings()['import_structure_form_missing'],
292 BackendStrings::getExceptionStrings()['import_structure_form_invalid']
293 );
294
295 self::assertRequiredArrayKey(
296 $formData,
297 'fields',
298 BackendStrings::getExceptionStrings()['import_structure_fields_missing'],
299 BackendStrings::getExceptionStrings()['import_structure_fields_invalid']
300 );
301
302 self::assertRequiredArrayKey(
303 $formData,
304 'notifications',
305 BackendStrings::getExceptionStrings()['import_structure_notifications_missing'],
306 BackendStrings::getExceptionStrings()['import_structure_notifications_invalid']
307 );
308
309 self::assertConfirmationStructure($formData);
310 }
311
312 /**
313 * Assert that a value is an array.
314 *
315 * @param mixed $value
316 * @param string $message
317 * @return void
318 * @throws InvalidArgumentException
319 */
320 private static function assertArray($value, string $message): void
321 {
322 if (!is_array($value)) {
323 throw new InvalidArgumentException($message);
324 }
325 }
326
327 /**
328 * Assert that a key exists and its value is an array.
329 *
330 * @param array<string, mixed> $payload
331 * @param string $key
332 * @param string $missingMessage
333 * @param string $invalidMessage
334 * @return void
335 * @throws InvalidArgumentException
336 */
337 private static function assertRequiredArrayKey(
338 array $payload,
339 string $key,
340 string $missingMessage,
341 string $invalidMessage
342 ): void {
343 if (!array_key_exists($key, $payload)) {
344 throw new InvalidArgumentException($missingMessage);
345 }
346
347 if (!is_array($payload[$key])) {
348 throw new InvalidArgumentException($invalidMessage);
349 }
350 }
351
352 /**
353 * Assert confirmation section exists and has a supported type.
354 *
355 * @param array<string, mixed> $payload
356 * @return void
357 * @throws InvalidArgumentException
358 */
359 private static function assertConfirmationStructure(array $payload): void
360 {
361 if (!array_key_exists('confirmation', $payload)) {
362 throw new InvalidArgumentException(
363 BackendStrings::getExceptionStrings()['import_structure_confirmation_missing']
364 );
365 }
366
367 if (!is_array($payload['confirmation']) && !is_object($payload['confirmation'])) {
368 throw new InvalidArgumentException(
369 BackendStrings::getExceptionStrings()['import_structure_confirmation_invalid']
370 );
371 }
372 }
373 }
374