PluginProbe
The Innovative Form Builder – IvyForms / 1.5.1
The Innovative Form Builder – IvyForms v1.5.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
← All changes | backend/src/Repository/Field/FieldRepository.php +306 -13 0.2.1 → 1.5.1 View file →
@@ -1,18 +1,14 @@
1 1 <?php
2 2
3 3 /**
4 - * @copyright © TMS-Plugins. All rights reserved.
4 + * @copyright © Melograno Venture Studio. All rights reserved.
5 5 * @licence See LICENCE.md for license details.
6 6 */
7 7
8 8 namespace IvyForms\Repository\Field;
9 9
10 -// phpcs:disable PSR1.Files.SideEffects
11 -if (!defined('ABSPATH')) {
12 - exit; // Exit if accessed directly
13 -}
14 -
10 +use IvyForms\Common\Exceptions\InvalidArgumentException;
15 11 use IvyForms\Common\Exceptions\QueryExecutionException;
16 12 use IvyForms\Entity\Field\Field;
17 13 use IvyForms\Factory\Field\FieldFactory;
18 14 use IvyForms\Repository\AbstractRepository;
@@ -51,15 +47,23 @@
51 47 'required' => (int)$data['required'],
52 48 'defaultValue' => $data['defaultValue'],
53 49 'placeholder' => $data['placeholder'],
54 50 'position' => $data['position'],
51 + 'rowIndex' => $data['rowIndex'] ?? 0,
52 + 'columnIndex' => $data['columnIndex'] ?? 0,
53 + 'width' => $data['width'] ?? 100,
54 + 'parentId' => $data['parentId'] ?? null,
55 + 'pageId' => $data['pageId'] ?? null,
56 + 'settings' => $this->encodeFieldSettings($data, $entity, false),
55 57 'dateCreated' => current_time('mysql')
56 58 ],
57 - ['%s', '%d', '%s', '%s', '%s', '%s', '%s', '%d', '%s']
59 + ['%d', '%d', '%s', '%s', '%d', '%s', '%s', '%d', '%d', '%d', '%d', '%d', '%s', '%s', '%s']
58 60 );
59 61
60 62 if ($result === false) {
61 - throw new QueryExecutionException(BackendStrings::getAllFormsStrings()['unable_to_add_data'] . __CLASS__);
63 + throw new QueryExecutionException(
64 + BackendStrings::getExceptionStrings()['unable_to_add_data'] . __CLASS__
65 + );
62 66 }
63 67
64 68 return $this->wpdb->insert_id;
65 69 }
@@ -75,8 +79,11 @@
75 79 * @throws QueryExecutionException
76 80 */
77 81 public function update(int $id, $entity): bool
78 82 {
83 + if ($id < 1) {
84 + throw new InvalidArgumentException('Field id must be a positive integer.');
85 + }
79 86
80 87 $data = $entity->toArray();
81 88
82 89 $result = $this->wpdb->update(
@@ -89,13 +96,17 @@
89 96 'required' => (int)$data['required'],
90 97 'defaultValue' => $data['defaultValue'],
91 98 'placeholder' => $data['placeholder'],
92 99 'position' => $data['position'],
100 + 'rowIndex' => $data['rowIndex'] ?? 0,
101 + 'columnIndex' => $data['columnIndex'] ?? 0,
102 + 'width' => $data['width'] ?? 100,
103 + 'parentId' => $data['parentId'] ?? null,
104 + 'pageId' => $data['pageId'] ?? null,
105 + 'settings' => $this->encodeFieldSettings($data, $entity, true),
93 106 ],
94 - [
95 - 'id' => $data['id']
96 - ],
97 - ['%s', '%d', '%s', '%s', '%s', '%s', '%s', '%s', '%d'],
107 + ['id' => $id],
108 + ['%d', '%d', '%s', '%s', '%d', '%s', '%s', '%d', '%d', '%d', '%d', '%d', '%s', '%s'],
98 109 ['%d']
99 110 );
100 111
101 112 if ($result === false) {
@@ -105,13 +116,176 @@
105 116 return $result;
106 117 }
107 118
108 119 /**
120 + * @param mixed[] $data
121 + */
122 + private function encodeFieldSettings(array $data, Field $entity, bool $forUpdate): string
123 + {
124 + $settings = $this->buildFieldSettingsPayload($data, $forUpdate);
125 +
126 + /**
127 + * Allows modification of the settings data before saving to database.
128 + *
129 + * @param mixed[] $settings The current settings array.
130 + * @param mixed[] $data The full field data array.
131 + * @param Field $entity The Field entity instance.
132 + * @return mixed[] The modified settings array.
133 + */
134 + $settings = apply_filters('ivyforms/repository/field/settings', $settings, $data, $entity);
135 +
136 + $encoded = wp_json_encode($settings);
137 + if ($encoded === false) {
138 + throw new QueryExecutionException(
139 + 'Failed to encode field settings: ' . json_last_error_msg()
140 + );
141 + }
142 +
143 + return $encoded;
144 + }
145 +
146 + /**
147 + * @param mixed[] $data
148 + *
149 + * @return mixed[]
150 + */
151 + private function buildFieldSettingsPayload(array $data, bool $forUpdate): array
152 + {
153 + return array_merge(
154 + $this->buildCoreFieldSettings($data, $forUpdate),
155 + $this->buildFileUploadFieldSettings($data)
156 + );
157 + }
158 +
159 + /**
160 + * @param mixed[] $data
161 + *
162 + * @return mixed[]
163 + */
164 + private function buildCoreFieldSettings(array $data, bool $forUpdate): array
165 + {
166 + return array_merge(
167 + $this->buildBaseFieldSettings($data),
168 + [
169 + 'limitRange' => $this->resolveLimitRangeFlag($data, $forUpdate),
170 + 'visible' => $this->resolveVisibleFlag($data, $forUpdate),
171 + ],
172 + $this->buildExtendedFieldSettings($data)
173 + );
174 + }
175 +
176 + /**
177 + * @param mixed[] $data
178 + *
179 + * @return mixed[]
180 + */
181 + private function buildBaseFieldSettings(array $data): array
182 + {
183 + return [
184 + 'hideLabel' => (int) $data['hideLabel'],
185 + 'readOnly' => (int) $data['readOnly'],
186 + 'description' => $data['description'],
187 + 'requiredMessage' => $data['requiredMessage'],
188 + 'cssClasses' => $data['cssClasses'],
189 + 'limitMaxLength' => (int) $data['limitMaxLength'],
190 + 'maxLength' => $data['maxLength'],
191 + 'labelPosition' => $data['labelPosition'],
192 + 'noDuplicates' => (int) $data['noDuplicates'],
193 + 'shuffleOptions' => (int) $data['shuffleOptions'],
194 + 'showValues' => (int) $data['showValues'],
195 + 'enableSearch' => (int) $data['enableSearch'],
196 + 'rows' => (int) $data['rows'],
197 + ];
198 + }
199 +
200 + /**
201 + * @param mixed[] $data
202 + *
203 + * @return mixed[]
204 + */
205 + private function buildExtendedFieldSettings(array $data): array
206 + {
207 + return [
208 + 'confirmFieldEnabled' => (int) ($data['confirmFieldEnabled'] ?? 0),
209 + 'confirmFieldLabel' => $data['confirmFieldLabel'] ?? '',
210 + 'confirmFieldPlaceholder' => $data['confirmFieldPlaceholder'] ?? '',
211 + 'confirmFieldHideLabel' => (int) ($data['confirmFieldHideLabel'] ?? 0),
212 + 'phoneFormat' => $data['phoneFormat'],
213 + 'phoneAutoDetect' => $data['phoneAutoDetect'],
214 + 'minValue' => isset($data['minValue']) ? (float) $data['minValue'] : null,
215 + 'maxValue' => isset($data['maxValue']) ? (float) $data['maxValue'] : null,
216 + 'step' => (float) $data['step'],
217 + 'showGrid' => (int) ($data['showGrid'] ?? 0),
218 + 'numberFormat' => $data['numberFormat'] ?? '',
219 + 'inputPrefix' => $data['inputPrefix'] ?? '',
220 + 'inputSuffix' => $data['inputSuffix'] ?? '',
221 + 'timeFieldType' => $data['timeFieldType'] ?? '',
222 + 'timeFormat' => $data['timeFormat'] ?? '',
223 + 'dateFieldType' => $data['dateFieldType'] ?? '',
224 + 'dateFormat' => $data['dateFormat'] ?? '',
225 + 'minDateValue' => $data['minDateValue'] ?? '',
226 + 'maxDateValue' => $data['maxDateValue'] ?? '',
227 + 'showRatingText' => (int) ($data['showRatingText'] ?? 0),
228 + 'ratingIcon' => $data['ratingIcon'] ?? 'star',
229 + 'htmlContent' => $data['htmlContent'] ?? '',
230 + 'agreementText' => $data['agreementText'] ?? '',
231 + 'limitDateRange' => (int) $data['limitDateRange'],
232 + 'minTimeValue' => $data['minTimeValue'] ?? '',
233 + 'maxTimeValue' => $data['maxTimeValue'] ?? '',
234 + 'limitTimeRange' => (int) $data['limitTimeRange'],
235 + 'nameFieldType' => $data['nameFieldType'] ?? '',
236 + 'subFieldIndex' => isset($data['subFieldIndex']) ? (int) $data['subFieldIndex'] : null,
237 + ];
238 + }
239 +
240 + /**
241 + * @param mixed[] $data
242 + */
243 + private function resolveLimitRangeFlag(array $data, bool $forUpdate): int
244 + {
245 + if (!$forUpdate) {
246 + return (int) $data['limitRange'];
247 + }
248 +
249 + return filter_var($data['limitRange'], FILTER_VALIDATE_BOOLEAN) ? 1 : 0;
250 + }
251 +
252 + /**
253 + * @param mixed[] $data
254 + */
255 + private function resolveVisibleFlag(array $data, bool $forUpdate): int
256 + {
257 + if (!$forUpdate) {
258 + return (int) $data['visible'];
259 + }
260 +
261 + return array_key_exists('visible', $data) ? (int) $data['visible'] : 1;
262 + }
263 +
264 + /**
265 + * @param mixed[] $data
266 + *
267 + * @return mixed[]
268 + */
269 + private function buildFileUploadFieldSettings(array $data): array
270 + {
271 + return [
272 + 'maxFileSizeMb' => isset($data['maxFileSizeMb']) ? (int) $data['maxFileSizeMb'] : 0,
273 + 'saveUploadsTo' => (string) ($data['saveUploadsTo'] ?? 'ivyforms'),
274 + 'allowedFileExtensions' => isset($data['allowedFileExtensions'])
275 + && is_array($data['allowedFileExtensions'])
276 + ? array_values($data['allowedFileExtensions'])
277 + : [],
278 + ];
279 + }
280 +
281 + /**
109 282 * Get fields by form ID
110 283 *
111 284 * @param int $id
112 285 *
113 286 * @return mixed[]
287 + * @throws QueryExecutionException
114 288 */
115 289 public function getFieldsById(int $id): array
116 290 {
117 291 $rows = $this->wpdb->get_results(
@@ -120,13 +294,132 @@
120 294 $id
121 295 ),
122 296 ARRAY_A
123 297 );
298 + if ($rows === false) {
299 + throw new QueryExecutionException(
300 + BackendStrings::getExceptionStrings()['unable_find_by_id'] . __CLASS__
301 + );
302 + }
124 303 $result = [];
304 + foreach ($rows as $row) {
305 + if (array_key_exists('parentId', $row)) {
306 + $row['parentId'] = $row['parentId'] !== null ? (int)$row['parentId'] : null;
307 + }
308 + $result[] = call_user_func([static::FACTORY, 'create'], $row);
309 + }
310 + return $result;
311 + }
125 312
313 + /**
314 + * Get multiple fields by their IDs in a single query.
315 + *
316 + * @param int[] $fieldIds
317 + * @return array<int, object> Indexed by field ID
318 + * @throws QueryExecutionException
319 + */
320 + public function getFieldsByIds(array $fieldIds): array
321 + {
322 + if (empty($fieldIds)) {
323 + return [];
324 + }
325 +
326 + $fieldIds = array_map('intval', $fieldIds);
327 + $placeholders = implode(',', array_fill(0, count($fieldIds), '%d'));
328 + $rows = $this->wpdb->get_results(
329 + $this->wpdb->prepare(
330 + $this->selectQuery() . " WHERE $this->table.id IN ($placeholders) ORDER BY $this->table.position ASC",
331 + ...$fieldIds
332 + ),
333 + ARRAY_A
334 + );
335 +
336 + if ($rows === false) {
337 + throw new QueryExecutionException(
338 + BackendStrings::getExceptionStrings()['unable_find_by_id'] . __CLASS__
339 + );
340 + }
341 +
342 + $result = [];
126 343 foreach ($rows as $row) {
127 - $result[] = call_user_func([static::FACTORY, 'create'], $row);
344 + if (array_key_exists('parentId', $row)) {
345 + $row['parentId'] = $row['parentId'] !== null ? (int)$row['parentId'] : null;
346 + }
347 + $field = call_user_func([static::FACTORY, 'create'], $row);
348 + $result[$field->getId()] = $field;
128 349 }
129 350
351 + return $result;
352 + }
353 +
354 + /**
355 + * Delete multiple fields by their IDs in bulk, and their field options.
356 + *
357 + * @param array<int> $ids
358 + * @return int Number of deleted fields
359 + * @throws QueryExecutionException
360 + */
361 + public function deleteMany(array $ids): int
362 + {
363 + if (empty($ids)) {
364 + return 0;
365 + }
366 + $placeholders = implode(',', array_fill(0, count($ids), '%d'));
367 + $deleteFieldsQuery = "DELETE FROM {$this->table} WHERE id IN ($placeholders)";
368 + $result = $this->wpdb->query($this->wpdb->prepare($deleteFieldsQuery, ...$ids));
369 + if ($result === false) {
370 + throw new QueryExecutionException(
371 + sprintf(
372 + BackendStrings::getExceptionStrings()['failed_delete_by_ids'],
373 + implode(', ', $ids),
374 + $this->table
375 + )
376 + );
377 + }
378 + return $result;
379 + }
380 +
381 + /**
382 + * Delete multiple fields by foreign key values (formId), and their field options.
383 + *
384 + * @param array<int> $formIds
385 + * @param string $foreignColumn
386 + * @return int Number of deleted fields
387 + * @throws QueryExecutionException
388 + */
389 + public function deleteManyByForeignKeyValues(array $formIds, string $foreignColumn = 'formId'): int
390 + {
391 + if (empty($formIds)) {
392 + return 0;
393 + }
394 + $formIds = array_map('intval', $formIds);
395 + $placeholders = implode(',', array_fill(0, count($formIds), '%d'));
396 + $deleteFieldsQuery = "DELETE FROM {$this->table} WHERE {$foreignColumn} IN ($placeholders)";
397 + $result = $this->wpdb->query($this->wpdb->prepare($deleteFieldsQuery, ...$formIds));
398 + if ($result === false) {
399 + throw new QueryExecutionException(
400 + BackendStrings::getExceptionStrings()['failed_delete_by_foreign_keys']
401 + );
402 + }
403 + return $result;
404 + }
405 +
406 + /**
407 + * Delete fields by a single foreign key value (formId), and their field options.
408 + *
409 + * @param int $formId
410 + * @param string $foreignColumn
411 + * @return int Number of deleted fields
412 + * @throws QueryExecutionException
413 + */
414 + public function deleteOneByForeignKeyValue(int $formId, string $foreignColumn = 'formId'): int
415 + {
416 + $deleteFieldsQuery = "DELETE FROM {$this->table} WHERE {$foreignColumn} = %d";
417 + $result = $this->wpdb->query($this->wpdb->prepare($deleteFieldsQuery, $formId));
418 + if ($result === false) {
419 + throw new QueryExecutionException(
420 + BackendStrings::getExceptionStrings()['failed_delete_by_foreign_key']
421 + );
422 + }
130 423 return $result;
131 424 }
132 425 }