PluginProbe
The Innovative Form Builder – IvyForms / 1.4
The Innovative Form Builder – IvyForms v1.4
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 / FormSubmissionService.php

FormSubmissionService.php in The Innovative Form Builder – IvyForms 1.4, at backend/src/Services/Submission/FormSubmissionService.php

321 lines 10.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * @copyright © Melograno Venture Studio. All rights reserved.
5 * @licence See COPYING.md for license details.
6 */
7
8 declare(strict_types=1);
9
10 namespace IvyForms\Services\Submission;
11
12 use IvyForms\Common\Exceptions\ForbiddenException;
13 use IvyForms\Common\Exceptions\InvalidArgumentException;
14 use IvyForms\Common\Exceptions\NotFoundException;
15 use IvyForms\Common\Exceptions\ValidationException;
16 use IvyForms\Common\Helpers\EntryHelper;
17 use IvyForms\Common\Sanitizer\Sanitizer;
18 use IvyForms\Entity\Form\Form;
19 use IvyForms\Factory\Entry\EntryFactory;
20 use IvyForms\Services\Confirmation\ConfirmationService;
21 use IvyForms\Services\Entry\EntryService;
22 use IvyForms\Services\Field\FieldDuplicateValidationService;
23 use IvyForms\Services\Field\FieldService;
24 use IvyForms\Services\Form\FormService;
25 use IvyForms\Services\Mailer\MailerService;
26 use IvyForms\Services\Notification\NotificationService;
27 use IvyForms\Services\Placeholder\PlaceholderService;
28 use IvyForms\Services\Security\SecurityService;
29 use IvyForms\Services\Translations\BackendStrings;
30
31 /**
32 * Validates and processes form submissions (embed and public API).
33 */
34 class FormSubmissionService
35 {
36 private SubmissionUploadService $submissionUploadService;
37 private FormService $formService;
38 private FieldService $fieldService;
39 private FieldDuplicateValidationService $fieldDuplicateValidationService;
40 private SecurityService $securityService;
41 private EntryService $entryService;
42 private NotificationService $notificationService;
43 private MailerService $mailerService;
44 private ConfirmationService $confirmationService;
45
46 public function __construct(
47 SubmissionUploadService $submissionUploadService,
48 FormService $formService,
49 FieldService $fieldService,
50 FieldDuplicateValidationService $fieldDuplicateValidationService,
51 SecurityService $securityService,
52 EntryService $entryService,
53 NotificationService $notificationService,
54 MailerService $mailerService,
55 ConfirmationService $confirmationService
56 ) {
57 $this->submissionUploadService = $submissionUploadService;
58 $this->formService = $formService;
59 $this->fieldService = $fieldService;
60 $this->fieldDuplicateValidationService = $fieldDuplicateValidationService;
61 $this->securityService = $securityService;
62 $this->entryService = $entryService;
63 $this->notificationService = $notificationService;
64 $this->mailerService = $mailerService;
65 $this->confirmationService = $confirmationService;
66 }
67
68 /**
69 * @param array<string, mixed> $params Normalized submission payload
70 * @param array<string, mixed> $uploadedFiles Multipart files from the transport layer
71 *
72 * @return array<string, mixed>
73 *
74 * @throws InvalidArgumentException|NotFoundException|ForbiddenException|ValidationException
75 */
76 public function processSubmission(
77 array $params,
78 array $uploadedFiles = [],
79 ?int $formIdOverride = null
80 ): array {
81 $context = $this->buildSubmissionContext($params, $uploadedFiles, $formIdOverride);
82
83 $duplicateResult = $this->buildDuplicateResultIfNeeded($context);
84 if ($duplicateResult !== null) {
85 return $duplicateResult;
86 }
87
88 do_action(
89 'ivyforms/form/before_submission',
90 $context['formId'],
91 $context['submissionData'],
92 $context['formFields']
93 );
94
95 $entry = $this->storeEntryIfEnabled(
96 $context['form'],
97 $context['formId'],
98 $context['formFields'],
99 $context['submissionData']
100 );
101 $entryId = $entry['id'] ?? null;
102 $placeholderData = $this->buildPlaceholderData($context['formFields'], $context['submissionData'], $entryId);
103 $notificationsResult = $this->dispatchNotifications($context, $placeholderData);
104 $confirmationPayload = $this->confirmationService->sanitizeProcessedPayload(
105 $this->confirmationService->processConfirmations(
106 $context['formId'],
107 $context['submissionData'],
108 $context['formFields'],
109 $placeholderData['fieldData'],
110 $placeholderData['generalData'],
111 $context['fieldLabels']
112 )
113 );
114
115 do_action(
116 'ivyforms/form/after_submission',
117 $context['formId'],
118 $context['submissionData'],
119 $context['formFields'],
120 $entryId
121 );
122
123 return $this->buildSuccessResult($entry, $notificationsResult, $confirmationPayload);
124 }
125
126 /**
127 * @param array<string, mixed> $params
128 * @param array<string, mixed> $uploadedFiles
129 * @param array<\IvyForms\Entity\Field\Field> $formFields
130 *
131 * @return array<string, mixed>
132 */
133 public function mergeUploadedFilesIntoValues(
134 array $params,
135 array $uploadedFiles,
136 array $formFields
137 ): array {
138 return $this->submissionUploadService->mergeUploadedFilesIntoValues(
139 $params,
140 $uploadedFiles,
141 $formFields
142 );
143 }
144
145 /**
146 * @param array<string, mixed> $params
147 * @param array<string, mixed> $uploadedFiles
148 *
149 * @return array{
150 * formId: int,
151 * form: Form,
152 * formFields: array<int, mixed>,
153 * submissionData: array<string, mixed>,
154 * fieldLabels: array<string, string>
155 * }
156 *
157 * @throws InvalidArgumentException|NotFoundException|ForbiddenException|ValidationException
158 */
159 private function buildSubmissionContext(
160 array $params,
161 array $uploadedFiles,
162 ?int $formIdOverride = null
163 ): array {
164 if ($formIdOverride !== null) {
165 $params['formId'] = $formIdOverride;
166 }
167
168 if (empty($params) || !isset($params['values']) || !is_array($params['values'])) {
169 throw new InvalidArgumentException(
170 BackendStrings::getExceptionStrings()['invalid_request_data']
171 );
172 }
173
174 $formId = Sanitizer::sanitizeId((int) ($params['formId'] ?? 0));
175
176 if ($formId <= 0) {
177 throw new InvalidArgumentException(
178 BackendStrings::getExceptionStrings()['invalid_form_id']
179 );
180 }
181
182 $form = $this->formService->getFormById($formId);
183 $formFields = $this->fieldService->getAllFields($form->getId());
184 $this->fieldService->validateFieldsType($formFields);
185
186 $fieldLabels = PlaceholderService::buildFieldLabels($formFields);
187 $this->securityService->validateFormSubmission($params, $formFields);
188
189 $params = $this->mergeUploadedFilesIntoValues($params, $uploadedFiles, $formFields);
190 $submissionData = Sanitizer::sanitizeFormSubmissionData($params, $formFields);
191
192 do_action('ivyforms/form/validate_submission', $submissionData, $formFields);
193
194 return [
195 'formId' => $formId,
196 'form' => $form,
197 'formFields' => $formFields,
198 'submissionData' => $submissionData,
199 'fieldLabels' => $fieldLabels,
200 ];
201 }
202
203 /**
204 * @param array{
205 * formId: int,
206 * form: Form,
207 * formFields: array<int, mixed>,
208 * submissionData: array<string, mixed>,
209 * fieldLabels: array<string, string>
210 * } $context
211 *
212 * @return array<string, mixed>|null
213 */
214 private function buildDuplicateResultIfNeeded(array $context): ?array
215 {
216 [$isDuplicate, $duplicateErrors] = $this->fieldDuplicateValidationService->checkDuplicateFieldValues(
217 $context['formFields'],
218 $context['submissionData'],
219 $context['formId']
220 );
221
222 if (!$isDuplicate) {
223 return null;
224 }
225
226 return [
227 'success' => null,
228 'entry' => ['stored' => false],
229 'confirmation' => null,
230 'is_duplicate' => true,
231 'duplicate_errors' => $duplicateErrors,
232 ];
233 }
234
235 /**
236 * @param array<int, mixed> $formFields
237 * @param array<string, mixed> $submissionData
238 *
239 * @return array{stored: bool, id?: int}
240 */
241 private function storeEntryIfEnabled(
242 Form $form,
243 int $formId,
244 array $formFields,
245 array $submissionData
246 ): array {
247 if (!$form->isStoreEntries()) {
248 return ['stored' => false];
249 }
250
251 $entryData = EntryHelper::buildEntryData($formId);
252 $entryObj = EntryFactory::create($entryData);
253 $entryId = $this->entryService->getEntryManager()->createEntry($entryObj);
254 $this->entryService->getEntryFieldManager()->addEntryFields($formFields, $entryId, $submissionData);
255
256 return [
257 'stored' => true,
258 'id' => $entryId,
259 ];
260 }
261
262 /**
263 * @param array<int, mixed> $formFields
264 * @param array<string, mixed> $submissionData
265 *
266 * @return array{fieldData: array<string, mixed>, generalData: array<string, mixed>}
267 */
268 private function buildPlaceholderData(array $formFields, array $submissionData, ?int $entryId): array
269 {
270 return [
271 'fieldData' => PlaceholderService::buildFieldData($formFields, $submissionData),
272 'generalData' => PlaceholderService::buildGeneralData($entryId ?? 0, $submissionData),
273 ];
274 }
275
276 /**
277 * @param array{
278 * formId: int,
279 * form: Form,
280 * formFields: array<int, mixed>,
281 * submissionData: array<string, mixed>,
282 * fieldLabels: array<string, string>
283 * } $context
284 * @param array{fieldData: array<string, mixed>, generalData: array<string, mixed>} $placeholderData
285 *
286 * @return mixed
287 */
288 private function dispatchNotifications(array $context, array $placeholderData)
289 {
290 return $this->notificationService->processNotifications(
291 $context['formId'],
292 $context['submissionData'],
293 $context['formFields'],
294 $placeholderData['fieldData'],
295 $placeholderData['generalData'],
296 $this->mailerService,
297 $context['fieldLabels']
298 );
299 }
300
301 /**
302 * @param array{stored: bool, id?: int} $entry
303 * @param mixed $notificationsResult
304 * @param array{message: string, result: array<string, mixed>|null} $confirmationPayload
305 *
306 * @return array<string, mixed>
307 */
308 private function buildSuccessResult(
309 array $entry,
310 $notificationsResult,
311 array $confirmationPayload
312 ): array {
313 return [
314 'success' => $notificationsResult,
315 'entry' => $entry,
316 'confirmation' => $confirmationPayload['message'],
317 'confirmationResult' => $confirmationPayload['result'],
318 ];
319 }
320 }
321