| @@ -1,13 +1,8 @@ | ||
| 1 | 1 | <?php |
| 2 | 2 | |
| 3 | 3 | namespace IvyForms\Services\Field; |
| 4 | 4 | |
| 5 | -// phpcs:disable PSR1.Files.SideEffects | |
| 6 | -if (!defined('ABSPATH')) { | |
| 7 | - exit; // Exit if accessed directly | |
| 8 | -} | |
| 9 | - | |
| 10 | 5 | use IvyForms\Common\Exceptions\QueryExecutionException; |
| 11 | 6 | use IvyForms\Common\Exceptions\ValidationException; |
| 12 | 7 | use IvyForms\Entity\Field\Field; |
| 13 | 8 | use IvyForms\Common\Exceptions\InvalidArgumentException; |
| @@ -14,11 +9,11 @@ | ||
| 14 | 9 | use IvyForms\Factory\Field\FieldFactory; |
| 15 | 10 | use IvyForms\Factory\FieldOptions\FieldOptionsFactory; |
| 16 | 11 | use IvyForms\Repository\Field\FieldRepositoryInterface; |
| 17 | 12 | use IvyForms\Repository\FieldOptions\FieldOptionsRepositoryInterface; |
| 18 | -use IvyForms\Repository\EntryField\EntryFieldRepositoryInterface; | |
| 19 | 13 | use IvyForms\Services\Translations\BackendStrings; |
| 20 | 14 | use IvyForms\Common\Helpers\FieldHelper; |
| 15 | +use IvyForms\Services\Media\ImageService; | |
| 21 | 16 | |
| 22 | 17 | /** |
| 23 | 18 | * @SuppressWarnings("TooManyPublicMethods") |
| 24 | 19 | */ |
| @@ -25,19 +20,19 @@ | ||
| 25 | 20 | class FieldService |
| 26 | 21 | { |
| 27 | 22 | private FieldRepositoryInterface $fieldRepository; |
| 28 | 23 | private FieldOptionsRepositoryInterface $fieldOptionsRepository; |
| 29 | - private EntryFieldRepositoryInterface $entryFieldRepository; | |
| 24 | + private ImageService $imageService; | |
| 30 | 25 | |
| 31 | 26 | // Constructor injection for repositories only via PHP-DI |
| 32 | 27 | public function __construct( |
| 33 | 28 | FieldRepositoryInterface $fieldRepository, |
| 34 | 29 | FieldOptionsRepositoryInterface $fieldOptionsRepository, |
| 35 | - EntryFieldRepositoryInterface $entryFieldRepository | |
| 30 | + ImageService $imageService | |
| 36 | 31 | ) { |
| 37 | 32 | $this->fieldRepository = $fieldRepository; |
| 38 | 33 | $this->fieldOptionsRepository = $fieldOptionsRepository; |
| 39 | - $this->entryFieldRepository = $entryFieldRepository; | |
| 34 | + $this->imageService = $imageService; | |
| 40 | 35 | } |
| 41 | 36 | |
| 42 | 37 | /** |
| 43 | 38 | * Create a new field. |
| @@ -165,13 +160,21 @@ | ||
| 165 | 160 | */ |
| 166 | 161 | public function duplicateFields(int $originalFormId, int $newFormId): void |
| 167 | 162 | { |
| 168 | 163 | $fields = $this->getAllFields($originalFormId); |
| 164 | + $fieldIdMap = []; | |
| 169 | 165 | foreach ($fields as $field) { |
| 170 | 166 | $fieldData = $field->toArray(); |
| 167 | + $oldId = $fieldData['id']; | |
| 171 | 168 | unset($fieldData['id']); |
| 172 | 169 | $fieldData['formId'] = $newFormId; |
| 173 | - $this->addField(FieldFactory::create($fieldData)); | |
| 170 | + FieldHelper::remapParentIdForDuplication($fieldData, $fieldIdMap); | |
| 171 | + $newFieldId = $this->addField(FieldFactory::create($fieldData)); | |
| 172 | + $fieldIdMap[$oldId] = $newFieldId; | |
| 173 | + // Duplicate field options using helper | |
| 174 | + if (FieldHelper::fieldTypeHasOptions($field->getType())) { | |
| 175 | + FieldHelper::duplicateFieldOptions($oldId, $newFieldId, $this->fieldOptionsRepository); | |
| 176 | + } | |
| 174 | 177 | } |
| 175 | 178 | } |
| 176 | 179 | |
| 177 | 180 | /** |
| @@ -182,19 +185,23 @@ | ||
| 182 | 185 | * @throws ValidationException|InvalidArgumentException |
| 183 | 186 | */ |
| 184 | 187 | public function saveFieldsWithOptions(array $fields, int $formId): void |
| 185 | 188 | { |
| 189 | + $parentMap = []; | |
| 186 | 190 | foreach ($fields as $fieldData) { |
| 187 | 191 | $fieldData['formId'] = $fieldData['formId'] ?? $formId; |
| 192 | + // Resolve parentId using fieldIndex if available | |
| 193 | + FieldHelper::resolveParentId($fieldData, $parentMap); | |
| 194 | + // Process htmlContent / rich text default values to upload base64 images | |
| 195 | + FieldHelper::processFieldHtmlContent($fieldData, $this->imageService); | |
| 196 | + FieldHelper::processRichTextDefaultValue($fieldData, $this->imageService); | |
| 188 | 197 | $field = FieldFactory::create($fieldData); |
| 189 | 198 | $fieldId = $this->addField($field); |
| 190 | - if ( | |
| 191 | - in_array( | |
| 192 | - $field->getType(), | |
| 193 | - ['radio', 'checkbox', 'select', 'multi-select'], | |
| 194 | - true | |
| 195 | - ) && !empty($fieldData['fieldOptions']) | |
| 196 | - ) { | |
| 199 | + // If this is a parent field (no parentId), store its DB id in the map | |
| 200 | + if ($field->getParentId() === null) { | |
| 201 | + $parentMap[$field->getIndex()] = $fieldId; | |
| 202 | + } | |
| 203 | + if (FieldHelper::fieldTypeHasOptions($field->getType()) && !empty($fieldData['fieldOptions'])) { | |
| 197 | 204 | foreach ($fieldData['fieldOptions'] as $optionData) { |
| 198 | 205 | // Always overwrite fieldId with the actual DB id of the field |
| 199 | 206 | $optionData['fieldId'] = $fieldId; |
| 200 | 207 | $option = FieldOptionsFactory::create($optionData); |
| @@ -217,8 +224,11 @@ | ||
| 217 | 224 | $parentMap = []; |
| 218 | 225 | foreach ($fields as $fieldData) { |
| 219 | 226 | $fieldData['formId'] = $fieldData['formId'] ?? $formId; |
| 220 | 227 | FieldHelper::resolveParentId($fieldData, $parentMap); |
| 228 | + // Process htmlContent / rich text default values to upload base64 images | |
| 229 | + FieldHelper::processFieldHtmlContent($fieldData, $this->imageService); | |
| 230 | + FieldHelper::processRichTextDefaultValue($fieldData, $this->imageService); | |
| 221 | 231 | $field = FieldFactory::create($fieldData); |
| 222 | 232 | $fieldId = $field->getId(); |
| 223 | 233 | |
| 224 | 234 | if ($fieldId === 0) { // new field |
| @@ -265,9 +275,9 @@ | ||
| 265 | 275 | $fields = $this->fieldRepository->getFieldsById($formId); |
| 266 | 276 | $fieldsWithOptions = []; |
| 267 | 277 | foreach ($fields as $field) { |
| 268 | 278 | $fieldArr = $field->toArray(); |
| 269 | - if (in_array($field->getType(), ['radio', 'checkbox', 'select', 'multi-select'], true)) { | |
| 279 | + if (FieldHelper::fieldTypeHasOptions($field->getType())) { | |
| 270 | 280 | $options = $this->fieldOptionsRepository->getByFieldId($field->getId()); |
| 271 | 281 | $fieldArr['fieldOptions'] = array_map(static fn($option) => $option->toArray(), $options); |
| 272 | 282 | } |
| 273 | 283 | $fieldsWithOptions[] = $fieldArr; |
| @@ -312,42 +322,8 @@ | ||
| 312 | 322 | } |
| 313 | 323 | } |
| 314 | 324 | |
| 315 | 325 | /** |
| 316 | - * Check for duplicate field values in submission. | |
| 317 | - * @param Field[] $formFields | |
| 318 | - * @param array<string,mixed> $submissionData | |
| 319 | - * @param int $formId | |
| 320 | - * @return array{0: bool, 1: array<int|string,bool>} | |
| 321 | - */ | |
| 322 | - public function checkDuplicateFieldValues( | |
| 323 | - array $formFields, | |
| 324 | - array $submissionData, | |
| 325 | - int $formId | |
| 326 | - ): array { | |
| 327 | - $duplicateErrors = []; | |
| 328 | - $isDuplicate = false; | |
| 329 | - $fieldValues = []; | |
| 330 | - | |
| 331 | - foreach ($formFields as $field) { | |
| 332 | - $fid = $field->getId(); | |
| 333 | - $fidStr = (string)$fid; | |
| 334 | - if (isset($submissionData[$fidStr])) { | |
| 335 | - $fieldValues[$fidStr] = $submissionData[$fidStr]; | |
| 336 | - } | |
| 337 | - if ($field->getFieldAdvancedSettings()->isNoDuplicates() && !empty($fieldValues[$fidStr])) { | |
| 338 | - $val = $fieldValues[$fidStr]; | |
| 339 | - if ($this->entryFieldRepository->checkDuplicateValue($formId, $fid, $val)) { | |
| 340 | - $duplicateErrors[$fidStr] = true; | |
| 341 | - $isDuplicate = true; | |
| 342 | - } | |
| 343 | - } | |
| 344 | - } | |
| 345 | - | |
| 346 | - return [$isDuplicate, $duplicateErrors]; | |
| 347 | - } | |
| 348 | - | |
| 349 | - /** | |
| 350 | 326 | * Validate that all fields have allowed types. |
| 351 | 327 | * @param Field[]|mixed[] $formFields |
| 352 | 328 | * @throws InvalidArgumentException |
| 353 | 329 | */ |
| @@ -352,24 +328,7 @@ | ||
| 352 | 328 | * @throws InvalidArgumentException |
| 353 | 329 | */ |
| 354 | 330 | public function validateFieldsType(array $formFields): void |
| 355 | 331 | { |
| 356 | - // Validate field types | |
| 357 | - $allowedTypes = FieldType::getAllowedTypes(); | |
| 358 | - foreach ($formFields as $field) { | |
| 359 | - // Check if field is an instance of Field or array | |
| 360 | - $fieldType = $field instanceof Field ? $field->getType() : ($field['type'] ?? null); | |
| 361 | - | |
| 362 | - if ($fieldType === null) { | |
| 363 | - throw new InvalidArgumentException( | |
| 364 | - BackendStrings::getExceptionStrings()['invalid_field_type'] . ' (type not found)' | |
| 365 | - ); | |
| 366 | - } | |
| 367 | - | |
| 368 | - if (!in_array($fieldType, $allowedTypes, true)) { | |
| 369 | - throw new InvalidArgumentException( | |
| 370 | - BackendStrings::getExceptionStrings()['invalid_field_type'] . ' ' . $fieldType | |
| 371 | - ); | |
| 372 | - } | |
| 373 | - } | |
| 332 | + FieldType::validateFields($formFields); | |
| 374 | 333 | } |
| 375 | 334 | } |