diff --git a/api/app/Http/Controllers/Forms/PublicFormController.php b/api/app/Http/Controllers/Forms/PublicFormController.php index 0110ab82..5c8824a7 100644 --- a/api/app/Http/Controllers/Forms/PublicFormController.php +++ b/api/app/Http/Controllers/Forms/PublicFormController.php @@ -116,7 +116,7 @@ private function getRedirectData($form, $submissionData) return ['id' => $key, 'value' => $value]; })->values()->all(); - $redirectUrl = ($form->redirect_url) ? (new MentionParser($form->redirect_url, $formattedData))->parseAsText() : null; + $redirectUrl = ($form->redirect_url) ? (new MentionParser($form->redirect_url, $formattedData))->urlFriendlyOutput()->parseAsText() : null; if ($redirectUrl && !filter_var($redirectUrl, FILTER_VALIDATE_URL)) { $redirectUrl = null; diff --git a/api/app/Http/Requests/UserFormRequest.php b/api/app/Http/Requests/UserFormRequest.php index 2e250634..cfaa8416 100644 --- a/api/app/Http/Requests/UserFormRequest.php +++ b/api/app/Http/Requests/UserFormRequest.php @@ -55,7 +55,7 @@ public function rules() 're_fillable' => 'boolean', 're_fill_button_text' => 'string|min:1|max:50', 'submitted_text' => 'string|max:2000', - 'redirect_url' => 'nullable|max:255', + 'redirect_url' => 'nullable|string', 'database_fields_update' => 'nullable|array', 'max_submissions_count' => 'integer|nullable|min:1', 'max_submissions_reached_text' => 'string|nullable', diff --git a/api/app/Open/MentionParser.php b/api/app/Open/MentionParser.php index 5cb3d181..b679e8e3 100644 --- a/api/app/Open/MentionParser.php +++ b/api/app/Open/MentionParser.php @@ -9,6 +9,7 @@ class MentionParser { private $content; private $data; + private $urlFriendly = false; public function __construct($content, $data) { @@ -16,6 +17,12 @@ public function __construct($content, $data) $this->data = $data; } + public function urlFriendlyOutput(bool $enable = true): self + { + $this->urlFriendly = $enable; + return $this; + } + public function parse() { $doc = new DOMDocument(); @@ -40,7 +47,7 @@ public function parse() $value = $this->getData($fieldId); if ($value !== null) { - $textNode = $doc->createTextNode(is_array($value) ? implode(', ', $value) : $value); + $textNode = $doc->createTextNode(is_array($value) ? implode($this->urlFriendly ? ',+' : ', ', $value) : $value); $element->parentNode->replaceChild($textNode, $element); } elseif ($fallback) { $textNode = $doc->createTextNode($fallback); @@ -127,7 +134,13 @@ private function getData($fieldId) $value = collect($this->data)->firstWhere('id', $fieldId)['value'] ?? null; if (is_object($value)) { - return (array) $value; + $value = (array) $value; + } + + if ($this->urlFriendly && $value !== null) { + return is_array($value) + ? array_map('urlencode', $value) + : urlencode($value); } return $value; diff --git a/api/database/migrations/2024_12_05_121609_change_redirect_url_to_text_in_forms_table.php b/api/database/migrations/2024_12_05_121609_change_redirect_url_to_text_in_forms_table.php new file mode 100644 index 00000000..51dac175 --- /dev/null +++ b/api/database/migrations/2024_12_05_121609_change_redirect_url_to_text_in_forms_table.php @@ -0,0 +1,27 @@ +text('redirect_url')->nullable()->change(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('forms', function (Blueprint $table) { + $table->string('redirect_url')->nullable()->change(); + }); + } +}; diff --git a/api/tests/Feature/Forms/RedirectUrlLengthTest.php b/api/tests/Feature/Forms/RedirectUrlLengthTest.php new file mode 100644 index 00000000..592fb1e9 --- /dev/null +++ b/api/tests/Feature/Forms/RedirectUrlLengthTest.php @@ -0,0 +1,29 @@ +withoutExceptionHandling(); + $user = $this->actingAsUser(); + $workspace = $this->createUserWorkspace($user); + $form = $this->createForm($user, $workspace); + + // Create a very long URL (more than 255 characters) + $longUrl = 'https://example.com/?' . str_repeat('very-long-parameter=value&', 50); + + $this->putJson(route('open.forms.update', $form->id), array_merge($form->toArray(), [ + 'redirect_url' => $longUrl + ]))->assertStatus(200); + + expect($form->fresh()->redirect_url)->toBe($longUrl); +}); + +test('form accepts null redirect url', function () { + $user = $this->actingAsUser(); + $workspace = $this->createUserWorkspace($user); + $form = $this->createForm($user, $workspace); + + $this->putJson(route('open.forms.update', $form->id), array_merge($form->toArray(), [ + 'redirect_url' => null + ]))->assertStatus(200); + + expect($form->fresh()->redirect_url)->toBeNull(); +}); diff --git a/api/tests/Unit/Service/Forms/MentionParserTest.php b/api/tests/Unit/Service/Forms/MentionParserTest.php index a193e0f3..0607e17b 100644 --- a/api/tests/Unit/Service/Forms/MentionParserTest.php +++ b/api/tests/Unit/Service/Forms/MentionParserTest.php @@ -173,4 +173,46 @@ expect($result)->toBe('some text replaced text dewde'); }); + + describe('urlFriendlyOutput', function () { + test('it encodes special characters in values', function () { + $content = '

Test: Placeholder

'; + $data = [['id' => '123', 'value' => 'Hello & World']]; + + $parser = new MentionParser($content, $data); + $result = $parser->urlFriendlyOutput()->parse(); + + expect($result)->toBe('

Test: Hello+%26+World

'); + }); + + test('it encodes spaces in values', function () { + $content = '

Name: Placeholder

'; + $data = [['id' => '123', 'value' => 'John Doe']]; + + $parser = new MentionParser($content, $data); + $result = $parser->urlFriendlyOutput()->parse(); + + expect($result)->toBe('

Name: John+Doe

'); + }); + + test('it encodes array values', function () { + $content = '

Tags: Placeholder

'; + $data = [['id' => '123', 'value' => ['Web & Mobile', 'PHP/Laravel']]]; + + $parser = new MentionParser($content, $data); + $result = $parser->urlFriendlyOutput()->parse(); + + expect($result)->toBe('

Tags: Web+%26+Mobile,+PHP%2FLaravel

'); + }); + + test('it can be disabled explicitly', function () { + $content = '

Test: Placeholder

'; + $data = [['id' => '123', 'value' => 'Hello & World']]; + + $parser = new MentionParser($content, $data); + $result = $parser->urlFriendlyOutput(false)->parse(); + + expect($result)->toBe('

Test: Hello & World

'); + }); + }); });