diff --git a/api/app/Http/Controllers/Forms/PublicFormController.php b/api/app/Http/Controllers/Forms/PublicFormController.php
index 5c8824a7..d1669c03 100644
--- a/api/app/Http/Controllers/Forms/PublicFormController.php
+++ b/api/app/Http/Controllers/Forms/PublicFormController.php
@@ -9,7 +9,7 @@ use App\Http\Resources\FormSubmissionResource;
use App\Jobs\Form\StoreFormSubmissionJob;
use App\Models\Forms\Form;
use App\Models\Forms\FormSubmission;
-use App\Open\MentionParser;
+use App\Service\Forms\FormSubmissionProcessor;
use App\Service\Forms\FormCleaner;
use App\Service\WorkspaceHelper;
use Illuminate\Http\Request;
@@ -85,7 +85,7 @@ class PublicFormController extends Controller
return redirect()->to($internal_url);
}
- public function answer(AnswerFormRequest $request)
+ public function answer(AnswerFormRequest $request, FormSubmissionProcessor $formSubmissionProcessor)
{
$form = $request->form;
$isFirstSubmission = ($form->submissions_count === 0);
@@ -95,10 +95,13 @@ class PublicFormController extends Controller
$completionTime = $request->get('completion_time') ?? null;
unset($submissionData['completion_time']); // Remove completion_time from the main data array
- if ($form->editable_submissions) {
- $job = new StoreFormSubmissionJob($form, $submissionData, $completionTime);
+ $job = new StoreFormSubmissionJob($form, $submissionData, $completionTime);
+
+ if ($formSubmissionProcessor->shouldProcessSynchronously($form)) {
$job->handle();
$submissionId = Hashids::encode($job->getSubmissionId());
+ // Update submission data with generated values for redirect URL
+ $submissionData = $job->getProcessedData();
} else {
StoreFormSubmissionJob::dispatch($form, $submissionData, $completionTime);
}
@@ -107,27 +110,7 @@ class PublicFormController extends Controller
'message' => 'Form submission saved.',
'submission_id' => $submissionId,
'is_first_submission' => $isFirstSubmission,
- ], $this->getRedirectData($request->form, $submissionData)));
- }
-
- private function getRedirectData($form, $submissionData)
- {
- $formattedData = collect($submissionData)->map(function ($value, $key) {
- return ['id' => $key, 'value' => $value];
- })->values()->all();
-
- $redirectUrl = ($form->redirect_url) ? (new MentionParser($form->redirect_url, $formattedData))->urlFriendlyOutput()->parseAsText() : null;
-
- if ($redirectUrl && !filter_var($redirectUrl, FILTER_VALIDATE_URL)) {
- $redirectUrl = null;
- }
-
- return $form->is_pro && $redirectUrl ? [
- 'redirect' => true,
- 'redirect_url' => $redirectUrl,
- ] : [
- 'redirect' => false,
- ];
+ ], $formSubmissionProcessor->getRedirectData($form, $submissionData)));
}
public function fetchSubmission(Request $request, string $slug, string $submissionId)
diff --git a/api/app/Jobs/Form/StoreFormSubmissionJob.php b/api/app/Jobs/Form/StoreFormSubmissionJob.php
index 6780244c..d5dd8214 100644
--- a/api/app/Jobs/Form/StoreFormSubmissionJob.php
+++ b/api/app/Jobs/Form/StoreFormSubmissionJob.php
@@ -27,6 +27,7 @@ class StoreFormSubmissionJob implements ShouldQueue
use SerializesModels;
public ?string $submissionId = null;
+ private ?array $formData = null;
/**
* Create a new job instance.
@@ -44,13 +45,13 @@ class StoreFormSubmissionJob implements ShouldQueue
*/
public function handle()
{
- $formData = $this->getFormData();
- $this->addHiddenPrefills($formData);
+ $this->formData = $this->getFormData();
+ $this->addHiddenPrefills($this->formData);
- $this->storeSubmission($formData);
+ $this->storeSubmission($this->formData);
- $formData['submission_id'] = $this->submissionId;
- FormSubmitted::dispatch($this->form, $formData);
+ $this->formData['submission_id'] = $this->submissionId;
+ FormSubmitted::dispatch($this->form, $this->formData);
}
public function getSubmissionId()
@@ -258,4 +259,15 @@ class StoreFormSubmissionJob implements ShouldQueue
}
});
}
+
+ /**
+ * Get the processed form data after all transformations
+ */
+ public function getProcessedData(): array
+ {
+ if ($this->formData === null) {
+ $this->formData = $this->getFormData();
+ }
+ return $this->formData;
+ }
}
diff --git a/api/app/Service/Forms/FormSubmissionProcessor.php b/api/app/Service/Forms/FormSubmissionProcessor.php
new file mode 100644
index 00000000..80ff7ea0
--- /dev/null
+++ b/api/app/Service/Forms/FormSubmissionProcessor.php
@@ -0,0 +1,75 @@
+editable_submissions) {
+ return true;
+ }
+
+ // If no redirect URL, no need to process synchronously
+ if (!$form->redirect_url) {
+ return false;
+ }
+
+ // Check if any UUID/auto-increment fields are used in redirect URL
+ foreach ($form->properties as $field) {
+ if ($this->isGeneratedField($field) && $this->isFieldUsedInRedirectUrl($form, $field['id'])) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks if a field is a generated field (UUID or auto-increment)
+ */
+ private function isGeneratedField(array $field): bool
+ {
+ return $field['type'] === 'text' &&
+ (
+ (isset($field['generates_uuid']) && $field['generates_uuid']) ||
+ (isset($field['generates_auto_increment_id']) && $field['generates_auto_increment_id'])
+ );
+ }
+
+ /**
+ * Checks if a field ID is used in the form's redirect URL
+ */
+ private function isFieldUsedInRedirectUrl(Form $form, string $fieldId): bool
+ {
+ return str_contains($form->redirect_url, '{' . $fieldId . '}');
+ }
+
+ /**
+ * Get the redirect data for a form submission
+ */
+ public function getRedirectData(Form $form, array $submissionData): array
+ {
+ $redirectUrl = ($form->redirect_url)
+ ? (new MentionParser($form->redirect_url, array_values($submissionData)))->urlFriendlyOutput()->parseAsText()
+ : null;
+
+ if ($redirectUrl && !filter_var($redirectUrl, FILTER_VALIDATE_URL)) {
+ $redirectUrl = null;
+ }
+
+ return $form->is_pro && $redirectUrl ? [
+ 'redirect' => true,
+ 'redirect_url' => $redirectUrl,
+ ] : [
+ 'redirect' => false,
+ ];
+ }
+}
diff --git a/api/tests/Feature/Forms/FormSubmissionProcessorTest.php b/api/tests/Feature/Forms/FormSubmissionProcessorTest.php
new file mode 100644
index 00000000..4bb42157
--- /dev/null
+++ b/api/tests/Feature/Forms/FormSubmissionProcessorTest.php
@@ -0,0 +1,154 @@
+actingAsUser();
+ $workspace = $this->createUserWorkspace($user);
+ $form = $this->createForm($user, $workspace, [
+ 'editable_submissions' => true
+ ]);
+
+ $processor = new FormSubmissionProcessor();
+ expect($processor->shouldProcessSynchronously($form))->toBeTrue();
+});
+
+it('processes synchronously with UUID field in redirect URL', function () {
+ $user = $this->actingAsUser();
+ $workspace = $this->createUserWorkspace($user);
+ $form = $this->createForm($user, $workspace, [
+ 'redirect_url' => 'https://example.com/{field_1}',
+ 'properties' => [
+ [
+ 'id' => 'field_1',
+ 'type' => 'text',
+ 'generates_uuid' => true,
+ 'name' => 'UUID Field'
+ ]
+ ]
+ ]);
+
+ $processor = new FormSubmissionProcessor();
+ expect($processor->shouldProcessSynchronously($form))->toBeTrue();
+});
+
+it('processes synchronously with auto increment field in redirect URL', function () {
+ $user = $this->actingAsUser();
+ $workspace = $this->createUserWorkspace($user);
+ $form = $this->createForm($user, $workspace, [
+ 'redirect_url' => 'https://example.com/{field_1}',
+ 'properties' => [
+ [
+ 'id' => 'field_1',
+ 'type' => 'text',
+ 'generates_auto_increment_id' => true,
+ 'name' => 'Auto Increment Field'
+ ]
+ ]
+ ]);
+
+ $processor = new FormSubmissionProcessor();
+ expect($processor->shouldProcessSynchronously($form))->toBeTrue();
+});
+
+it('processes asynchronously with no generated fields in redirect URL', function () {
+ $user = $this->actingAsUser();
+ $workspace = $this->createUserWorkspace($user);
+ $form = $this->createForm($user, $workspace, [
+ 'redirect_url' => 'https://example.com/{field_1}',
+ 'properties' => [
+ [
+ 'id' => 'field_1',
+ 'type' => 'text',
+ 'name' => 'Regular Field'
+ ]
+ ]
+ ]);
+
+ $processor = new FormSubmissionProcessor();
+ expect($processor->shouldProcessSynchronously($form))->toBeFalse();
+});
+
+it('processes asynchronously when generated field is not used in redirect URL', function () {
+ $user = $this->actingAsUser();
+ $workspace = $this->createUserWorkspace($user);
+ $form = $this->createForm($user, $workspace, [
+ 'redirect_url' => 'https://example.com/{field_2}',
+ 'properties' => [
+ [
+ 'id' => 'field_1',
+ 'type' => 'text',
+ 'generates_uuid' => true,
+ 'name' => 'UUID Field'
+ ],
+ [
+ 'id' => 'field_2',
+ 'type' => 'text',
+ 'name' => 'Regular Field'
+ ]
+ ]
+ ]);
+
+ $processor = new FormSubmissionProcessor();
+ expect($processor->shouldProcessSynchronously($form))->toBeFalse();
+});
+
+it('processes asynchronously with no redirect URL', function () {
+ $user = $this->actingAsUser();
+ $workspace = $this->createUserWorkspace($user);
+ $form = $this->createForm($user, $workspace, [
+ 'redirect_url' => null,
+ 'properties' => [
+ [
+ 'id' => 'field_1',
+ 'type' => 'text',
+ 'generates_uuid' => true,
+ 'name' => 'UUID Field'
+ ]
+ ]
+ ]);
+
+ $processor = new FormSubmissionProcessor();
+ expect($processor->shouldProcessSynchronously($form))->toBeFalse();
+});
+
+it('formats redirect data correctly for pro users', function () {
+ $user = $this->actingAsProUser();
+ $workspace = $this->createUserWorkspace($user);
+ $form = $this->createForm($user, $workspace, [
+ 'redirect_url' => 'https://example.com/'
+ ]);
+
+ $processor = new FormSubmissionProcessor();
+ $redirectData = $processor->getRedirectData($form, [
+ 'field_1' => [
+ 'id' => 'field_1',
+ 'value' => 'test-value'
+ ]
+ ]);
+
+ expect($redirectData)->toBe([
+ 'redirect' => true,
+ 'redirect_url' => 'https://example.com/test-value'
+ ]);
+});
+
+it('returns no redirect for non-pro users', function () {
+ $user = $this->actingAsUser();
+ $workspace = $this->createUserWorkspace($user);
+ $form = $this->createForm($user, $workspace, [
+ 'redirect_url' => 'https://example.com/'
+ ]);
+
+ $processor = new FormSubmissionProcessor();
+ $redirectData = $processor->getRedirectData($form, [
+ 'field_1' => [
+ 'id' => 'field_1',
+ 'value' => 'test-value'
+ ]
+ ]);
+
+ expect($redirectData)->toBe([
+ 'redirect' => false
+ ]);
+});