Skip to content

Commit ab3a1f1

Browse files
reithorSteveb-p
andauthored
IBX-10978: Added tel/mailto options to urlType (#106)
For more details see https://ibexa.atlassian.net/browse/IBX-10978 and #106 Key changes: * Added tel/mailto options to URL field type's form type * Implemented FixUrlProtocolListener * [Tests] Added coverage for FixUrlProtocolListener --------- Co-Authored-By: Paweł Niedzielski <Steveb-p@users.noreply.github.com>
1 parent 793ac54 commit ab3a1f1

3 files changed

Lines changed: 179 additions & 0 deletions

File tree

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<?php
2+
3+
/**
4+
* @copyright Copyright (C) Ibexa AS. All rights reserved.
5+
* @license For full copyright and license information view LICENSE file distributed with this source code.
6+
*/
7+
declare(strict_types=1);
8+
9+
namespace Ibexa\ContentForms\Form\EventSubscriber;
10+
11+
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
12+
use Symfony\Component\Form\Extension\Core\EventListener\FixUrlProtocolListener as BaseFixUrlProtocolListener;
13+
use Symfony\Component\Form\FormEvent;
14+
use Symfony\Component\Form\FormEvents;
15+
16+
/**
17+
* @internal
18+
*/
19+
final class FixUrlProtocolListener implements EventSubscriberInterface
20+
{
21+
private ?string $defaultProtocol;
22+
23+
private BaseFixUrlProtocolListener $fixUrlProtocolListener;
24+
25+
/**
26+
* @param string|null $defaultProtocol The URL scheme to add when there is none or null to not modify the data
27+
*/
28+
public function __construct(?string $defaultProtocol = 'http')
29+
{
30+
$this->defaultProtocol = $defaultProtocol;
31+
$this->fixUrlProtocolListener = new BaseFixUrlProtocolListener($defaultProtocol);
32+
}
33+
34+
public function onSubmit(FormEvent $event): void
35+
{
36+
$data = $event->getData();
37+
if (null === $this->defaultProtocol || empty($data) || !\is_string($data)) {
38+
return;
39+
}
40+
41+
$protocol = explode(':', $data, 2)[0];
42+
if ($this->hasAuthority($protocol) && $this->hasAuthority($this->defaultProtocol)) {
43+
$this->fixUrlProtocolListener->onSubmit($event);
44+
45+
return;
46+
}
47+
48+
if (!$this->hasAuthority($protocol) && preg_match('~^(?:[/.]|[\w+.-]+:|[^:/?@#]++@)~', $data)) {
49+
return;
50+
}
51+
52+
if ($this->hasAuthority($this->defaultProtocol)) {
53+
$schemaSeparator = '://';
54+
$regExp = '~^(?:[/.]|[\w+.-]+//|[^:/?@#]++@)~';
55+
} else {
56+
$schemaSeparator = ':';
57+
$regExp = '~^[\w+.-]+:~'; // allowing emails for non-http/https/file
58+
}
59+
60+
if (!preg_match($regExp, $data)) {
61+
$event->setData($this->defaultProtocol . $schemaSeparator . $data);
62+
}
63+
}
64+
65+
private function hasAuthority(string $protocol): bool
66+
{
67+
return !in_array($protocol, ['mailto', 'tel'], true);
68+
}
69+
70+
public static function getSubscribedEvents(): array
71+
{
72+
return [FormEvents::SUBMIT => 'onSubmit'];
73+
}
74+
}

src/lib/Form/Type/FieldType/UrlFieldType.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
namespace Ibexa\ContentForms\Form\Type\FieldType;
1010

1111
use Ibexa\ContentForms\FieldType\DataTransformer\FieldValueTransformer;
12+
use Ibexa\ContentForms\Form\EventSubscriber\FixUrlProtocolListener;
1213
use Ibexa\Contracts\Core\Repository\FieldTypeService;
1314
use JMS\TranslationBundle\Annotation\Desc;
1415
use Symfony\Component\Form\AbstractType;
@@ -49,6 +50,7 @@ public function buildForm(FormBuilderInterface $builder, array $options)
4950
[
5051
'label' => /** @Desc("URL") */ 'content.field_type.ezurl.link',
5152
'required' => $options['required'],
53+
'default_protocol' => null,
5254
]
5355
)
5456
->add(
@@ -60,6 +62,8 @@ public function buildForm(FormBuilderInterface $builder, array $options)
6062
]
6163
)
6264
->addModelTransformer(new FieldValueTransformer($this->fieldTypeService->getFieldType('ezurl')));
65+
66+
$builder->get('link')->addEventSubscriber(new FixUrlProtocolListener());
6367
}
6468

6569
public function configureOptions(OptionsResolver $resolver)
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<?php
2+
3+
/**
4+
* @copyright Copyright (C) Ibexa AS. All rights reserved.
5+
* @license For full copyright and license information view LICENSE file distributed with this source code.
6+
*/
7+
declare(strict_types=1);
8+
9+
namespace Ibexa\Tests\ContentForms\Form\EventSubscriber;
10+
11+
use Ibexa\ContentForms\Form\EventSubscriber\FixUrlProtocolListener;
12+
use PHPUnit\Framework\TestCase;
13+
use Symfony\Component\Form\FormEvent;
14+
use Symfony\Component\Form\FormInterface;
15+
16+
final class FixUrlProtocolListenerTest extends TestCase
17+
{
18+
private const DOMAIN = 'example.com';
19+
private const MAIL = 'foo@' . self::DOMAIN;
20+
private const TEL = '+123456';
21+
private const URL_HTTP = 'http://' . self::DOMAIN;
22+
private const URL_HTTPS = 'https://' . self::DOMAIN;
23+
private const URL_MAILTO = 'mailto:' . self::MAIL;
24+
private const URL_RELATIVE = '/foo/bar/';
25+
private const URL_SFTP = 'sftp://' . self::DOMAIN;
26+
private const URL_TEL = 'tel:' . self::TEL;
27+
28+
/**
29+
* @dataProvider provideUrlCases
30+
*/
31+
public function testUrlProtocolHandling(?string $inputData, ?string $expectedData, string $defaultProtocol = 'http'): void
32+
{
33+
$form = $this->createMock(FormInterface::class);
34+
$listener = new FixUrlProtocolListener($defaultProtocol);
35+
36+
$event = new FormEvent($form, $inputData);
37+
38+
$listener->onSubmit($event);
39+
40+
self::assertSame($expectedData, $event->getData());
41+
}
42+
43+
/**
44+
* @return iterable<string, array{
45+
* 0: string|null,
46+
* 1: string|null,
47+
* 2?: string
48+
* }>
49+
*/
50+
public static function provideUrlCases(): iterable
51+
{
52+
return [
53+
'adds http when protocol missing' => [
54+
self::DOMAIN,
55+
self::URL_HTTP,
56+
],
57+
'does not modify https url' => [
58+
self::URL_HTTPS,
59+
self::URL_HTTPS,
60+
],
61+
'does not modify http url' => [
62+
self::URL_HTTP,
63+
self::URL_HTTP,
64+
],
65+
'keep relative url with leading / intact' => [
66+
self::URL_RELATIVE,
67+
self::URL_RELATIVE,
68+
],
69+
'keeps ftp intact' => [
70+
self::URL_SFTP,
71+
self::URL_SFTP,
72+
],
73+
'keeps tel intact' => [
74+
self::URL_TEL,
75+
self::URL_TEL,
76+
],
77+
'adds default tel' => [
78+
self::TEL,
79+
self::URL_TEL,
80+
'tel',
81+
],
82+
'keeps mailto intact' => [
83+
self::URL_MAILTO,
84+
self::URL_MAILTO,
85+
],
86+
'adds default mailto' => [
87+
self::MAIL,
88+
self::URL_MAILTO,
89+
'mailto',
90+
],
91+
'does nothing when link is empty string' => [
92+
'',
93+
'',
94+
],
95+
'does nothing when data is null' => [
96+
null,
97+
null,
98+
],
99+
];
100+
}
101+
}

0 commit comments

Comments
 (0)