diff --git a/src/lib/Form/EventSubscriber/FixUrlProtocolListener.php b/src/lib/Form/EventSubscriber/FixUrlProtocolListener.php new file mode 100644 index 00000000..602add4c --- /dev/null +++ b/src/lib/Form/EventSubscriber/FixUrlProtocolListener.php @@ -0,0 +1,81 @@ +defaultProtocol = $defaultProtocol; + $this->fixUrlProtocolListener = new BaseFixUrlProtocolListener($defaultProtocol); + } + + public function onSubmit(FormEvent $event): void + { + $data = $event->getData(); + $dataLink = $data['link'] ?? null; + if (null === $this->defaultProtocol || empty($data) || empty($dataLink) || !\is_string($dataLink)) { + return; + } + + $protocol = explode(':', $dataLink)[0]; + if ($this->hasAuthority($protocol) && $this->hasAuthority($this->defaultProtocol)) { + $event->setData($dataLink); + $this->fixUrlProtocolListener->onSubmit($event); + $data['link'] = $event->getData(); + $event->setData($data); + + return; + } + + if (!$this->hasAuthority($protocol) && preg_match('~^(?:[/.]|[\w+.-]+:|[^:/?@#]++@)~', $dataLink)) { + return; + } + + if ($this->hasAuthority($this->defaultProtocol)) { + $schemaSeparator = '://'; + $regExp = '~^(?:[/.]|[\w+.-]+//|[^:/?@#]++@)~'; + } else { + $schemaSeparator = ':'; + $regExp = '~^[\w+.-]+:~'; // allowing emails for non-http/https/file + } + + if (!preg_match($regExp, $dataLink)) { + $data['link'] = $this->defaultProtocol . $schemaSeparator . $dataLink; + $event->setData($data); + } + } + + private function hasAuthority(string $protocol): bool + { + return !in_array($protocol, ['mailto', 'tel'], true); + } + + public static function getSubscribedEvents(): array + { + return [FormEvents::SUBMIT => 'onSubmit']; + } +} diff --git a/src/lib/Form/Type/FieldType/UrlFieldType.php b/src/lib/Form/Type/FieldType/UrlFieldType.php index 47fdf990..c5250d52 100644 --- a/src/lib/Form/Type/FieldType/UrlFieldType.php +++ b/src/lib/Form/Type/FieldType/UrlFieldType.php @@ -9,6 +9,7 @@ namespace Ibexa\ContentForms\Form\Type\FieldType; use Ibexa\ContentForms\FieldType\DataTransformer\FieldValueTransformer; +use Ibexa\ContentForms\Form\EventSubscriber\FixUrlProtocolListener; use Ibexa\Contracts\Core\Repository\FieldTypeService; use JMS\TranslationBundle\Annotation\Desc; use Symfony\Component\Form\AbstractType; @@ -49,8 +50,10 @@ public function buildForm(FormBuilderInterface $builder, array $options) [ 'label' => /** @Desc("URL") */ 'content.field_type.ezurl.link', 'required' => $options['required'], + 'default_protocol' => null, ] ) + ->addEventSubscriber(new FixUrlProtocolListener()) ->add( 'text', TextType::class, diff --git a/tests/lib/Form/EventSubscriber/FixUrlProtocolListenerTest.php b/tests/lib/Form/EventSubscriber/FixUrlProtocolListenerTest.php new file mode 100644 index 00000000..ad9b2945 --- /dev/null +++ b/tests/lib/Form/EventSubscriber/FixUrlProtocolListenerTest.php @@ -0,0 +1,108 @@ +|null $inputData + * @param array|null $expectedData + * @param string $defaultProtocol + */ + public function testUrlProtocolHandling(?array $inputData, ?array $expectedData, ?string $defaultProtocol = 'http'): void + { + $form = $this->createMock(FormInterface::class); + $listener = new FixUrlProtocolListener($defaultProtocol); + + $event = new FormEvent($form, $inputData); + + $listener->onSubmit($event); + + self::assertSame($expectedData, $event->getData()); + } + + /** + * @return iterable|null, + * 1: array|null + * }> + */ + public static function provideUrlCases(): iterable + { + return [ + 'adds http when protocol missing' => [ + ['link' => self::DOMAIN], + ['link' => self::URL_HTTP], + ], + 'does not modify https url' => [ + ['link' => self::URL_HTTPS], + ['link' => self::URL_HTTPS], + ], + 'does not modify http url' => [ + ['link' => self::URL_HTTP], + ['link' => self::URL_HTTP], + ], + 'keep relative url with leading / intact' => [ + ['link' => self::URL_RELATIVE], + ['link' => self::URL_RELATIVE], + ], + 'keeps ftp intact' => [ + ['link' => self::URL_SFTP], + ['link' => self::URL_SFTP], + ], + 'keeps tel intact' => [ + ['link' => self::URL_TEL], + ['link' => self::URL_TEL], + ], + 'adds default tel' => [ + ['link' => self::TEL], + ['link' => self::URL_TEL], + 'tel', + ], + 'keeps mailto intact' => [ + ['link' => self::URL_MAILTO], + ['link' => self::URL_MAILTO], + ], + 'adds default mailto' => [ + ['link' => self::MAIL], + ['link' => self::URL_MAILTO], + 'mailto', + ], + 'does nothing when link is empty string' => [ + ['link' => ''], + ['link' => ''], + ], + 'does nothing when link key is missing' => [ + [], + [], + ], + 'does nothing when data is null' => [ + null, + null, + ], + ]; + } +}