Skip to content

Commit f350ed7

Browse files
committed
Refactor form submission processing with new FormSubmissionProcessor
- Introduce FormSubmissionProcessor service to handle synchronous/asynchronous form submission logic - Modify PublicFormController to use new processor for submission and redirect handling - Update StoreFormSubmissionJob to support processed data retrieval - Add comprehensive test suite for FormSubmissionProcessor - Improve handling of generated fields and redirect URL processing
1 parent bb34cd9 commit f350ed7

File tree

4 files changed

+254
-30
lines changed

4 files changed

+254
-30
lines changed

api/app/Http/Controllers/Forms/PublicFormController.php

+8-25
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
use App\Jobs\Form\StoreFormSubmissionJob;
1010
use App\Models\Forms\Form;
1111
use App\Models\Forms\FormSubmission;
12-
use App\Open\MentionParser;
12+
use App\Service\Forms\FormSubmissionProcessor;
1313
use App\Service\Forms\FormCleaner;
1414
use App\Service\WorkspaceHelper;
1515
use Illuminate\Http\Request;
@@ -85,7 +85,7 @@ public function showAsset($assetFileName)
8585
return redirect()->to($internal_url);
8686
}
8787

88-
public function answer(AnswerFormRequest $request)
88+
public function answer(AnswerFormRequest $request, FormSubmissionProcessor $formSubmissionProcessor)
8989
{
9090
$form = $request->form;
9191
$isFirstSubmission = ($form->submissions_count === 0);
@@ -95,10 +95,13 @@ public function answer(AnswerFormRequest $request)
9595
$completionTime = $request->get('completion_time') ?? null;
9696
unset($submissionData['completion_time']); // Remove completion_time from the main data array
9797

98-
if ($form->editable_submissions) {
99-
$job = new StoreFormSubmissionJob($form, $submissionData, $completionTime);
98+
$job = new StoreFormSubmissionJob($form, $submissionData, $completionTime);
99+
100+
if ($formSubmissionProcessor->shouldProcessSynchronously($form)) {
100101
$job->handle();
101102
$submissionId = Hashids::encode($job->getSubmissionId());
103+
// Update submission data with generated values for redirect URL
104+
$submissionData = $job->getProcessedData();
102105
} else {
103106
StoreFormSubmissionJob::dispatch($form, $submissionData, $completionTime);
104107
}
@@ -107,27 +110,7 @@ public function answer(AnswerFormRequest $request)
107110
'message' => 'Form submission saved.',
108111
'submission_id' => $submissionId,
109112
'is_first_submission' => $isFirstSubmission,
110-
], $this->getRedirectData($request->form, $submissionData)));
111-
}
112-
113-
private function getRedirectData($form, $submissionData)
114-
{
115-
$formattedData = collect($submissionData)->map(function ($value, $key) {
116-
return ['id' => $key, 'value' => $value];
117-
})->values()->all();
118-
119-
$redirectUrl = ($form->redirect_url) ? (new MentionParser($form->redirect_url, $formattedData))->urlFriendlyOutput()->parseAsText() : null;
120-
121-
if ($redirectUrl && !filter_var($redirectUrl, FILTER_VALIDATE_URL)) {
122-
$redirectUrl = null;
123-
}
124-
125-
return $form->is_pro && $redirectUrl ? [
126-
'redirect' => true,
127-
'redirect_url' => $redirectUrl,
128-
] : [
129-
'redirect' => false,
130-
];
113+
], $formSubmissionProcessor->getRedirectData($form, $submissionData)));
131114
}
132115

133116
public function fetchSubmission(Request $request, string $slug, string $submissionId)

api/app/Jobs/Form/StoreFormSubmissionJob.php

+17-5
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class StoreFormSubmissionJob implements ShouldQueue
2727
use SerializesModels;
2828

2929
public ?string $submissionId = null;
30+
private ?array $formData = null;
3031

3132
/**
3233
* Create a new job instance.
@@ -44,13 +45,13 @@ public function __construct(public Form $form, public array $submissionData, pub
4445
*/
4546
public function handle()
4647
{
47-
$formData = $this->getFormData();
48-
$this->addHiddenPrefills($formData);
48+
$this->formData = $this->getFormData();
49+
$this->addHiddenPrefills($this->formData);
4950

50-
$this->storeSubmission($formData);
51+
$this->storeSubmission($this->formData);
5152

52-
$formData['submission_id'] = $this->submissionId;
53-
FormSubmitted::dispatch($this->form, $formData);
53+
$this->formData['submission_id'] = $this->submissionId;
54+
FormSubmitted::dispatch($this->form, $this->formData);
5455
}
5556

5657
public function getSubmissionId()
@@ -258,4 +259,15 @@ private function addHiddenPrefills(array &$formData): void
258259
}
259260
});
260261
}
262+
263+
/**
264+
* Get the processed form data after all transformations
265+
*/
266+
public function getProcessedData(): array
267+
{
268+
if ($this->formData === null) {
269+
$this->formData = $this->getFormData();
270+
}
271+
return $this->formData;
272+
}
261273
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php
2+
3+
namespace App\Service\Forms;
4+
5+
use App\Models\Forms\Form;
6+
use App\Open\MentionParser;
7+
8+
class FormSubmissionProcessor
9+
{
10+
/**
11+
* Determines if a form submission should be processed synchronously
12+
*/
13+
public function shouldProcessSynchronously(Form $form): bool
14+
{
15+
// If editable submissions is enabled, always process synchronously
16+
if ($form->editable_submissions) {
17+
return true;
18+
}
19+
20+
// If no redirect URL, no need to process synchronously
21+
if (!$form->redirect_url) {
22+
return false;
23+
}
24+
25+
// Check if any UUID/auto-increment fields are used in redirect URL
26+
foreach ($form->properties as $field) {
27+
if ($this->isGeneratedField($field) && $this->isFieldUsedInRedirectUrl($form, $field['id'])) {
28+
return true;
29+
}
30+
}
31+
32+
return false;
33+
}
34+
35+
/**
36+
* Checks if a field is a generated field (UUID or auto-increment)
37+
*/
38+
private function isGeneratedField(array $field): bool
39+
{
40+
return $field['type'] === 'text' &&
41+
(
42+
(isset($field['generates_uuid']) && $field['generates_uuid']) ||
43+
(isset($field['generates_auto_increment_id']) && $field['generates_auto_increment_id'])
44+
);
45+
}
46+
47+
/**
48+
* Checks if a field ID is used in the form's redirect URL
49+
*/
50+
private function isFieldUsedInRedirectUrl(Form $form, string $fieldId): bool
51+
{
52+
return str_contains($form->redirect_url, '{' . $fieldId . '}');
53+
}
54+
55+
/**
56+
* Get the redirect data for a form submission
57+
*/
58+
public function getRedirectData(Form $form, array $submissionData): array
59+
{
60+
$redirectUrl = ($form->redirect_url)
61+
? (new MentionParser($form->redirect_url, array_values($submissionData)))->urlFriendlyOutput()->parseAsText()
62+
: null;
63+
64+
if ($redirectUrl && !filter_var($redirectUrl, FILTER_VALIDATE_URL)) {
65+
$redirectUrl = null;
66+
}
67+
68+
return $form->is_pro && $redirectUrl ? [
69+
'redirect' => true,
70+
'redirect_url' => $redirectUrl,
71+
] : [
72+
'redirect' => false,
73+
];
74+
}
75+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
<?php
2+
3+
use App\Service\Forms\FormSubmissionProcessor;
4+
5+
it('processes synchronously with editable submissions', function () {
6+
$user = $this->actingAsUser();
7+
$workspace = $this->createUserWorkspace($user);
8+
$form = $this->createForm($user, $workspace, [
9+
'editable_submissions' => true
10+
]);
11+
12+
$processor = new FormSubmissionProcessor();
13+
expect($processor->shouldProcessSynchronously($form))->toBeTrue();
14+
});
15+
16+
it('processes synchronously with UUID field in redirect URL', function () {
17+
$user = $this->actingAsUser();
18+
$workspace = $this->createUserWorkspace($user);
19+
$form = $this->createForm($user, $workspace, [
20+
'redirect_url' => 'https://example.com/{field_1}',
21+
'properties' => [
22+
[
23+
'id' => 'field_1',
24+
'type' => 'text',
25+
'generates_uuid' => true,
26+
'name' => 'UUID Field'
27+
]
28+
]
29+
]);
30+
31+
$processor = new FormSubmissionProcessor();
32+
expect($processor->shouldProcessSynchronously($form))->toBeTrue();
33+
});
34+
35+
it('processes synchronously with auto increment field in redirect URL', function () {
36+
$user = $this->actingAsUser();
37+
$workspace = $this->createUserWorkspace($user);
38+
$form = $this->createForm($user, $workspace, [
39+
'redirect_url' => 'https://example.com/{field_1}',
40+
'properties' => [
41+
[
42+
'id' => 'field_1',
43+
'type' => 'text',
44+
'generates_auto_increment_id' => true,
45+
'name' => 'Auto Increment Field'
46+
]
47+
]
48+
]);
49+
50+
$processor = new FormSubmissionProcessor();
51+
expect($processor->shouldProcessSynchronously($form))->toBeTrue();
52+
});
53+
54+
it('processes asynchronously with no generated fields in redirect URL', function () {
55+
$user = $this->actingAsUser();
56+
$workspace = $this->createUserWorkspace($user);
57+
$form = $this->createForm($user, $workspace, [
58+
'redirect_url' => 'https://example.com/{field_1}',
59+
'properties' => [
60+
[
61+
'id' => 'field_1',
62+
'type' => 'text',
63+
'name' => 'Regular Field'
64+
]
65+
]
66+
]);
67+
68+
$processor = new FormSubmissionProcessor();
69+
expect($processor->shouldProcessSynchronously($form))->toBeFalse();
70+
});
71+
72+
it('processes asynchronously when generated field is not used in redirect URL', function () {
73+
$user = $this->actingAsUser();
74+
$workspace = $this->createUserWorkspace($user);
75+
$form = $this->createForm($user, $workspace, [
76+
'redirect_url' => 'https://example.com/{field_2}',
77+
'properties' => [
78+
[
79+
'id' => 'field_1',
80+
'type' => 'text',
81+
'generates_uuid' => true,
82+
'name' => 'UUID Field'
83+
],
84+
[
85+
'id' => 'field_2',
86+
'type' => 'text',
87+
'name' => 'Regular Field'
88+
]
89+
]
90+
]);
91+
92+
$processor = new FormSubmissionProcessor();
93+
expect($processor->shouldProcessSynchronously($form))->toBeFalse();
94+
});
95+
96+
it('processes asynchronously with no redirect URL', function () {
97+
$user = $this->actingAsUser();
98+
$workspace = $this->createUserWorkspace($user);
99+
$form = $this->createForm($user, $workspace, [
100+
'redirect_url' => null,
101+
'properties' => [
102+
[
103+
'id' => 'field_1',
104+
'type' => 'text',
105+
'generates_uuid' => true,
106+
'name' => 'UUID Field'
107+
]
108+
]
109+
]);
110+
111+
$processor = new FormSubmissionProcessor();
112+
expect($processor->shouldProcessSynchronously($form))->toBeFalse();
113+
});
114+
115+
it('formats redirect data correctly for pro users', function () {
116+
$user = $this->actingAsProUser();
117+
$workspace = $this->createUserWorkspace($user);
118+
$form = $this->createForm($user, $workspace, [
119+
'redirect_url' => 'https://example.com/<span mention mention-field-id="field_1"></span>'
120+
]);
121+
122+
$processor = new FormSubmissionProcessor();
123+
$redirectData = $processor->getRedirectData($form, [
124+
'field_1' => [
125+
'id' => 'field_1',
126+
'value' => 'test-value'
127+
]
128+
]);
129+
130+
expect($redirectData)->toBe([
131+
'redirect' => true,
132+
'redirect_url' => 'https://example.com/test-value'
133+
]);
134+
});
135+
136+
it('returns no redirect for non-pro users', function () {
137+
$user = $this->actingAsUser();
138+
$workspace = $this->createUserWorkspace($user);
139+
$form = $this->createForm($user, $workspace, [
140+
'redirect_url' => 'https://example.com/<span mention mention-field-id="field_1"></span>'
141+
]);
142+
143+
$processor = new FormSubmissionProcessor();
144+
$redirectData = $processor->getRedirectData($form, [
145+
'field_1' => [
146+
'id' => 'field_1',
147+
'value' => 'test-value'
148+
]
149+
]);
150+
151+
expect($redirectData)->toBe([
152+
'redirect' => false
153+
]);
154+
});

0 commit comments

Comments
 (0)