From a3ca04536f748f9b341f164ebd399b3a2452bb96 Mon Sep 17 00:00:00 2001 From: gnito-org <70450336+gnito-org@users.noreply.github.com> Date: Mon, 5 Dec 2022 13:20:40 -0400 Subject: [PATCH] [Notifier] Add ClickSend notifier bridge --- .../FrameworkExtension.php | 1 + .../Resources/config/notifier_transports.php | 4 + .../Notifier/Bridge/ClickSend/.gitattributes | 4 + .../Notifier/Bridge/ClickSend/.gitignore | 3 + .../Notifier/Bridge/ClickSend/CHANGELOG.md | 7 + .../Bridge/ClickSend/ClickSendOptions.php | 133 ++++++++++++++++++ .../Bridge/ClickSend/ClickSendTransport.php | 126 +++++++++++++++++ .../ClickSend/ClickSendTransportFactory.php | 47 +++++++ .../Notifier/Bridge/ClickSend/LICENSE | 19 +++ .../Notifier/Bridge/ClickSend/README.md | 28 ++++ .../ClickSend/Tests/ClickSendOptionsTest.php | 33 +++++ .../Tests/ClickSendTransportFactoryTest.php | 50 +++++++ .../Tests/ClickSendTransportTest.php | 117 +++++++++++++++ .../Notifier/Bridge/ClickSend/composer.json | 36 +++++ .../Bridge/ClickSend/phpunit.xml.dist | 31 ++++ .../Exception/UnsupportedSchemeException.php | 4 + .../UnsupportedSchemeExceptionTest.php | 2 + src/Symfony/Component/Notifier/Transport.php | 1 + 18 files changed, 646 insertions(+) create mode 100644 src/Symfony/Component/Notifier/Bridge/ClickSend/.gitattributes create mode 100644 src/Symfony/Component/Notifier/Bridge/ClickSend/.gitignore create mode 100644 src/Symfony/Component/Notifier/Bridge/ClickSend/CHANGELOG.md create mode 100644 src/Symfony/Component/Notifier/Bridge/ClickSend/ClickSendOptions.php create mode 100644 src/Symfony/Component/Notifier/Bridge/ClickSend/ClickSendTransport.php create mode 100644 src/Symfony/Component/Notifier/Bridge/ClickSend/ClickSendTransportFactory.php create mode 100644 src/Symfony/Component/Notifier/Bridge/ClickSend/LICENSE create mode 100644 src/Symfony/Component/Notifier/Bridge/ClickSend/README.md create mode 100644 src/Symfony/Component/Notifier/Bridge/ClickSend/Tests/ClickSendOptionsTest.php create mode 100644 src/Symfony/Component/Notifier/Bridge/ClickSend/Tests/ClickSendTransportFactoryTest.php create mode 100644 src/Symfony/Component/Notifier/Bridge/ClickSend/Tests/ClickSendTransportTest.php create mode 100644 src/Symfony/Component/Notifier/Bridge/ClickSend/composer.json create mode 100644 src/Symfony/Component/Notifier/Bridge/ClickSend/phpunit.xml.dist diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 5c5d35a19d63d..3fcfddad0d65c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -2704,6 +2704,7 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $ NotifierBridge\Bandwidth\BandwidthTransportFactory::class => 'notifier.transport_factory.bandwidth', NotifierBridge\Chatwork\ChatworkTransportFactory::class => 'notifier.transport_factory.chatwork', NotifierBridge\Clickatell\ClickatellTransportFactory::class => 'notifier.transport_factory.clickatell', + NotifierBridge\ClickSend\ClickSendTransportFactory::class => 'notifier.transport_factory.click-send', NotifierBridge\ContactEveryone\ContactEveryoneTransportFactory::class => 'notifier.transport_factory.contact-everyone', NotifierBridge\Discord\DiscordTransportFactory::class => 'notifier.transport_factory.discord', NotifierBridge\Engagespot\EngagespotTransportFactory::class => 'notifier.transport_factory.engagespot', diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php index 474f013f000a5..746bf94618fff 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php @@ -279,5 +279,9 @@ ->set('notifier.transport_factory.simple-textin', Bridge\SimpleTextin\SimpleTextinTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.click-send', Bridge\ClickSend\ClickSendTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') ; }; diff --git a/src/Symfony/Component/Notifier/Bridge/ClickSend/.gitattributes b/src/Symfony/Component/Notifier/Bridge/ClickSend/.gitattributes new file mode 100644 index 0000000000000..84c7add058fb5 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/ClickSend/.gitattributes @@ -0,0 +1,4 @@ +/Tests export-ignore +/phpunit.xml.dist export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore diff --git a/src/Symfony/Component/Notifier/Bridge/ClickSend/.gitignore b/src/Symfony/Component/Notifier/Bridge/ClickSend/.gitignore new file mode 100644 index 0000000000000..c49a5d8df5c65 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/ClickSend/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/src/Symfony/Component/Notifier/Bridge/ClickSend/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/ClickSend/CHANGELOG.md new file mode 100644 index 0000000000000..1f2c8f86cde72 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/ClickSend/CHANGELOG.md @@ -0,0 +1,7 @@ +CHANGELOG +========= + +6.3 +--- + + * Add the bridge diff --git a/src/Symfony/Component/Notifier/Bridge/ClickSend/ClickSendOptions.php b/src/Symfony/Component/Notifier/Bridge/ClickSend/ClickSendOptions.php new file mode 100644 index 0000000000000..e8a7ad26fa94a --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/ClickSend/ClickSendOptions.php @@ -0,0 +1,133 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\ClickSend; + +use Symfony\Component\Notifier\Message\MessageOptionsInterface; + +/** + * @author gnito-org + */ +final class ClickSendOptions implements MessageOptionsInterface +{ + private array $options; + + public function __construct(array $options = []) + { + $this->options = $options; + } + + public function getCountry(): ?string + { + return $this->options['country'] ?? null; + } + + public function getCustomString(): ?string + { + return $this->options['custom_string'] ?? null; + } + + public function getFrom(): ?string + { + return $this->options['from'] ?? null; + } + + public function getFromEmail(): ?string + { + return $this->options['from_email'] ?? null; + } + + public function getListId(): ?string + { + return $this->options['list_id'] ?? null; + } + + public function getRecipientId(): ?string + { + return $this->options['recipient_id'] ?? null; + } + + public function getSchedule(): ?int + { + return $this->options['schedule'] ?? null; + } + + public function getSource(): ?string + { + return $this->options['source'] ?? null; + } + + public function setCountry(string $country): self + { + $this->options['country'] = $country; + + return $this; + } + + public function setCustomString(string $customString): self + { + $this->options['custom_string'] = $customString; + + return $this; + } + + public function setFrom(string $from): self + { + $this->options['from'] = $from; + + return $this; + } + + public function setFromEmail(string $fromEmail): self + { + $this->options['from_email'] = $fromEmail; + + return $this; + } + + public function setListId(string $listId): self + { + $this->options['list_id'] = $listId; + + return $this; + } + + public function setRecipientId(string $id): self + { + $this->options['recipient_id'] = $id; + + return $this; + } + + public function setSchedule(int $schedule): self + { + $this->options['schedule'] = $schedule; + + return $this; + } + + public function setSource(string $source): self + { + $this->options['source'] = $source; + + return $this; + } + + public function toArray(): array + { + $options = $this->options; + if (isset($options['recipient_id'])) { + unset($options['recipient_id']); + } + + return $options; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/ClickSend/ClickSendTransport.php b/src/Symfony/Component/Notifier/Bridge/ClickSend/ClickSendTransport.php new file mode 100644 index 0000000000000..6ab50491f3e52 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/ClickSend/ClickSendTransport.php @@ -0,0 +1,126 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\ClickSend; + +use Symfony\Component\Notifier\Exception\InvalidArgumentException; +use Symfony\Component\Notifier\Exception\TransportException; +use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; +use Symfony\Component\Notifier\Message\MessageInterface; +use Symfony\Component\Notifier\Message\SentMessage; +use Symfony\Component\Notifier\Message\SmsMessage; +use Symfony\Component\Notifier\Transport\AbstractTransport; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +/** + * @author gnito-org + */ +final class ClickSendTransport extends AbstractTransport +{ + protected const HOST = 'rest.clicksend.com'; + + public function __construct( + private readonly string $apiUsername, + #[\SensitiveParameter] private readonly string $apiKey, + private readonly ?string $from = null, + private readonly ?string $source = null, + private readonly ?string $listId = null, + private readonly ?string $fromEmail = null, + HttpClientInterface $client = null, + EventDispatcherInterface $dispatcher = null + ) { + parent::__construct($client, $dispatcher); + } + + public function __toString(): string + { + $queryParameters = []; + if ($this->from) { + $queryParameters['from'] = $this->from; + } + if ($this->source) { + $queryParameters['source'] = $this->source; + } + if ($this->listId) { + $queryParameters['list_id'] = $this->listId; + } + if ($this->fromEmail) { + $queryParameters['from_email'] = $this->fromEmail; + } + + return sprintf('clicksend://%s', $this->getEndpoint()).($queryParameters ? '?'.http_build_query($queryParameters) : null); + } + + public function supports(MessageInterface $message): bool + { + return $message instanceof SmsMessage && (null === $message->getOptions() || $message->getOptions() instanceof ClickSendOptions); + } + + /** + * https://developers.clicksend.com/docs/rest/v3/#send-sms. + */ + protected function doSend(MessageInterface $message): SentMessage + { + if (!$message instanceof SmsMessage) { + throw new UnsupportedMessageTypeException(__CLASS__, SmsMessage::class, $message); + } + + $endpoint = sprintf('https://%s/v3/sms/send', $this->getEndpoint()); + + $opts = $message->getOptions(); + $options = $opts ? $opts->toArray() : []; + $options['body'] = $message->getSubject(); + + if (!isset($options['from']) && $this->from) { + $options['from'] = $this->from; + } + + if (isset($options['from']) && !preg_match('/^[a-zA-Z0-9\s]{3,11}$/', $options['from']) && !preg_match('/^\+[1-9]\d{1,14}$/', $options['from'])) { + throw new InvalidArgumentException(sprintf('The "From" number "%s" is not a valid phone number, shortcode, or alphanumeric sender ID.', $this->from)); + } + + if (!isset($options['source']) && $this->source) { + $options['source'] = $this->source; + } + + if (!isset($options['list_id']) && $this->listId) { + $options['list_id'] = $this->listId; + } + + if (!isset($options['from_email']) && $this->fromEmail) { + $options['from_email'] = urldecode($this->fromEmail); + } + + if ($options['list_id'] ?? false) { + $options['to'] = $message->getPhone(); + } + + $response = $this->client->request('POST', $endpoint, [ + 'auth_basic' => $this->apiUsername.':'.$this->apiKey, + 'json' => array_filter($options), + ]); + + try { + $statusCode = $response->getStatusCode(); + } catch (TransportExceptionInterface $e) { + throw new TransportException('Could not reach the remote ClickSend server.', $response, 0, $e); + } + + if (200 !== $statusCode) { + $error = $response->getContent(false); + throw new TransportException(sprintf('Unable to send the SMS - "%s".', $error ?: 'unknown failure'), $response); + } + + return new SentMessage($message, (string) $this); + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/ClickSend/ClickSendTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/ClickSend/ClickSendTransportFactory.php new file mode 100644 index 0000000000000..6759f1ee01ea6 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/ClickSend/ClickSendTransportFactory.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\ClickSend; + +use Symfony\Component\Notifier\Exception\UnsupportedSchemeException; +use Symfony\Component\Notifier\Transport\AbstractTransportFactory; +use Symfony\Component\Notifier\Transport\Dsn; + +/** + * @author gnito-org + */ +final class ClickSendTransportFactory extends AbstractTransportFactory +{ + private const TRANSPORT_SCHEME = 'clicksend'; + + public function create(Dsn $dsn): ClickSendTransport + { + $scheme = $dsn->getScheme(); + if (self::TRANSPORT_SCHEME !== $scheme) { + throw new UnsupportedSchemeException($dsn, self::TRANSPORT_SCHEME, $this->getSupportedSchemes()); + } + $apiUsername = $this->getUser($dsn); + $apiKey = $this->getPassword($dsn); + $from = $dsn->getOption('from'); + $source = $dsn->getOption('source'); + $listId = $dsn->getOption('list_id'); + $fromEmail = $dsn->getOption('from_email'); + $host = 'default' === $dsn->getHost() ? null : $dsn->getHost(); + $port = $dsn->getPort(); + + return (new ClickSendTransport($apiUsername, $apiKey, $from, $source, $listId, $fromEmail, $this->client, $this->dispatcher))->setHost($host)->setPort($port); + } + + protected function getSupportedSchemes(): array + { + return [self::TRANSPORT_SCHEME]; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/ClickSend/LICENSE b/src/Symfony/Component/Notifier/Bridge/ClickSend/LICENSE new file mode 100644 index 0000000000000..733c826ebcd63 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/ClickSend/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2022-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/Symfony/Component/Notifier/Bridge/ClickSend/README.md b/src/Symfony/Component/Notifier/Bridge/ClickSend/README.md new file mode 100644 index 0000000000000..1b1870c6555c1 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/ClickSend/README.md @@ -0,0 +1,28 @@ +ClickSend Notifier +================== + +Provides [ClickSend](https://www.clicksend.com/) integration for Symfony Notifier. + +DSN example +----------- + +``` +CLICKSEND_DSN=clicksend://API_USERNAME:API_KEY@default?from=FROM&source=SOURCE&list_id=LIST_ID&from_email=FROM_EMAIL +``` + +where: + + - `API_USERNAME` is your ClickSend API username + - `API_KEY` is your ClickSend API key + - `FROM` is your sender (optional) + - `SOURCE` is your source method of sending (optional) + - `LIST_ID` is your recipient list ID (optional) + - `FROM_EMAIL` is your from email where replies must be emailed (optional) + +Resources +--------- + + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/src/Symfony/Component/Notifier/Bridge/ClickSend/Tests/ClickSendOptionsTest.php b/src/Symfony/Component/Notifier/Bridge/ClickSend/Tests/ClickSendOptionsTest.php new file mode 100644 index 0000000000000..b1b97f2376a2a --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/ClickSend/Tests/ClickSendOptionsTest.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\ClickSend\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Notifier\Bridge\ClickSend\ClickSendOptions; + +class ClickSendOptionsTest extends TestCase +{ + public function testClickSendOptions() + { + $clickSendOptions = (new ClickSendOptions())->setFrom('test_from')->setCountry('test_country')->setCustomString('test_custom_string')->setFromEmail('test_from_email')->setListId('test_list_id')->setRecipientId('test_recipient_id')->setSchedule(999)->setSource('test_source'); + + self::assertSame([ + 'from' => 'test_from', + 'country' => 'test_country', + 'custom_string' => 'test_custom_string', + 'from_email' => 'test_from_email', + 'list_id' => 'test_list_id', + 'schedule' => 999, + 'source' => 'test_source', + ], $clickSendOptions->toArray()); + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/ClickSend/Tests/ClickSendTransportFactoryTest.php b/src/Symfony/Component/Notifier/Bridge/ClickSend/Tests/ClickSendTransportFactoryTest.php new file mode 100644 index 0000000000000..6bcd8e94bced8 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/ClickSend/Tests/ClickSendTransportFactoryTest.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\ClickSend\Tests; + +use Symfony\Component\Notifier\Bridge\ClickSend\ClickSendTransportFactory; +use Symfony\Component\Notifier\Test\TransportFactoryTestCase; + +final class ClickSendTransportFactoryTest extends TransportFactoryTestCase +{ + public function createFactory(): ClickSendTransportFactory + { + return new ClickSendTransportFactory(); + } + + public function createProvider(): iterable + { + yield ['clicksend://host.test', 'clicksend://apiUsername:ApiKey@host.test']; + yield ['clicksend://host.test?from=15556667777', 'clicksend://apiUsername:ApiKey@host.test?from=15556667777']; + yield ['clicksend://host.test?source=api', 'clicksend://apiUsername:ApiKey@host.test?source=api']; + yield ['clicksend://host.test?list_id=1', 'clicksend://apiUsername:ApiKey@host.test?list_id=1']; + yield ['clicksend://host.test?from_email=foo%40bar.com', 'clicksend://apiUsername:ApiKey@host.test?from_email=foo%40bar.com']; + yield ['clicksend://host.test?from=15556667777&source=api&list_id=1&from_email=foo%40bar.com', 'clicksend://apiUsername:ApiKey@host.test?from=15556667777&source=api&list_id=1&from_email=foo%40bar.com']; + } + + public function incompleteDsnProvider(): iterable + { + yield 'missing API username and API key' => ['clicksend://@default']; + yield 'missing API username or API key' => ['clicksend://apiUsername@default']; + } + + public function supportsProvider(): iterable + { + yield [true, 'clicksend://apiUsername:apiKey@default']; + yield [false, 'somethingElse://apiUsername:apiKey@default']; + } + + public function unsupportedSchemeProvider(): iterable + { + yield ['somethingElse://apiUsername:apiKey@default']; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/ClickSend/Tests/ClickSendTransportTest.php b/src/Symfony/Component/Notifier/Bridge/ClickSend/Tests/ClickSendTransportTest.php new file mode 100644 index 0000000000000..e2bee3b5d47ab --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/ClickSend/Tests/ClickSendTransportTest.php @@ -0,0 +1,117 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\ClickSend\Tests; + +use Symfony\Component\HttpClient\MockHttpClient; +use Symfony\Component\Notifier\Bridge\ClickSend\ClickSendOptions; +use Symfony\Component\Notifier\Bridge\ClickSend\ClickSendTransport; +use Symfony\Component\Notifier\Exception\InvalidArgumentException; +use Symfony\Component\Notifier\Message\ChatMessage; +use Symfony\Component\Notifier\Message\MessageInterface; +use Symfony\Component\Notifier\Message\SmsMessage; +use Symfony\Component\Notifier\Test\TransportTestCase; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; + +final class ClickSendTransportTest extends TransportTestCase +{ + public function createTransport(HttpClientInterface $client = null, string $from = 'test_from', string $source = 'test_source', int $listId = 99, string $fromEmail = 'foo@bar.com'): ClickSendTransport + { + return new ClickSendTransport('test_username', 'test_key', $from, $source, $listId, $fromEmail, $client ?? $this->createMock(HttpClientInterface::class)); + } + + public function invalidFromProvider(): iterable + { + yield 'no zero at start if phone number' => ['+0']; + yield 'phone number too short' => ['+1']; + } + + public function supportedMessagesProvider(): iterable + { + yield [new SmsMessage('0611223344', 'Hello!')]; + yield [new SmsMessage('0611223344', 'Hello!', 'from', new ClickSendOptions(['custom_string' => 'test_custom_string']))]; + } + + /** + * @dataProvider invalidFromProvider + */ + public function testInvalidArgumentExceptionIsThrownIfFromIsInvalid(string $from) + { + $transport = $this->createTransport(null, $from); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage(sprintf('The "From" number "%s" is not a valid phone number, shortcode, or alphanumeric sender ID.', $from)); + + $transport->send(new SmsMessage('+33612345678', 'Hello!')); + } + + /** + * @dataProvider validFromProvider + */ + public function testNoInvalidArgumentExceptionIsThrownIfFromIsValid(string $from) + { + $message = new SmsMessage('+33612345678', 'Hello!'); + $response = $this->createMock(ResponseInterface::class); + $response->expects(self::exactly(2))->method('getStatusCode')->willReturn(200); + $response->expects(self::once())->method('getContent')->willReturn(''); + $client = new MockHttpClient(function (string $method, string $url) use ($response): ResponseInterface { + self::assertSame('POST', $method); + self::assertSame('https://rest.clicksend.com/v3/sms/send', $url); + + return $response; + }); + $transport = $this->createTransport($client, $from); + $transport->send($message); + } + + public function toStringProvider(): iterable + { + yield ['clicksend://rest.clicksend.com?from=test_from&source=test_source&list_id=99&from_email=foo%40bar.com', $this->createTransport()]; + } + + public function unsupportedMessagesProvider(): iterable + { + yield [new ChatMessage('Hello!')]; + yield [$this->createMock(MessageInterface::class)]; + } + + public function validFromProvider(): iterable + { + yield ['abc']; + yield ['abcd']; + yield ['abcde']; + yield ['abcdef']; + yield ['abcdefg']; + yield ['abcdefgh']; + yield ['abcdefghi']; + yield ['abcdefghij']; + yield ['abcdefghijk']; + yield ['abcdef ghij']; + yield [' abcdefghij']; + yield ['abcdefghij ']; + + yield ['+11']; + yield ['+112']; + yield ['+1123']; + yield ['+11234']; + yield ['+112345']; + yield ['+1123456']; + yield ['+11234567']; + yield ['+112345678']; + yield ['+1123456789']; + yield ['+11234567891']; + yield ['+112345678912']; + yield ['+1123456789123']; + yield ['+11234567891234']; + yield ['+112345678912345']; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/ClickSend/composer.json b/src/Symfony/Component/Notifier/Bridge/ClickSend/composer.json new file mode 100644 index 0000000000000..da3fed7af3413 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/ClickSend/composer.json @@ -0,0 +1,36 @@ +{ + "name": "symfony/click-send-notifier", + "type": "symfony-notifier-bridge", + "description": "Symfony ClickSend Notifier Bridge", + "keywords": [ + "click-send", + "notifier" + ], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "gnito-org", + "homepage": "https://github.com/gnito-org" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.1", + "symfony/http-client": "^5.4|^6.0", + "symfony/notifier": "^6.3" + }, + "require-dev": { + "symfony/event-dispatcher": "^5.4|^6.0" + }, + "autoload": { + "psr-4": {"Symfony\\Component\\Notifier\\Bridge\\ClickSend\\": ""}, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/src/Symfony/Component/Notifier/Bridge/ClickSend/phpunit.xml.dist b/src/Symfony/Component/Notifier/Bridge/ClickSend/phpunit.xml.dist new file mode 100644 index 0000000000000..5f9287c698e98 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/ClickSend/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Resources + ./Tests + ./vendor + + + + diff --git a/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php b/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php index 5b8cb22b82682..52abfd0dfb833 100644 --- a/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php +++ b/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php @@ -36,6 +36,10 @@ class UnsupportedSchemeException extends LogicException 'class' => Bridge\Clickatell\ClickatellTransportFactory::class, 'package' => 'symfony/clickatell-notifier', ], + 'clicksend' => [ + 'class' => Bridge\ClickSend\ClickSendTransportFactory::class, + 'package' => 'symfony/click-send-notifier', + ], 'contact-everyone' => [ 'class' => Bridge\ContactEveryone\ContactEveryoneTransportFactory::class, 'package' => 'symfony/contact-everyone-notifier', diff --git a/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php b/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php index 74176133ee2ae..0c8d5dcc3398d 100644 --- a/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php +++ b/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php @@ -31,6 +31,7 @@ public static function setUpBeforeClass(): void Bridge\Bandwidth\BandwidthTransportFactory::class => false, Bridge\Chatwork\ChatworkTransportFactory::class => false, Bridge\Clickatell\ClickatellTransportFactory::class => false, + Bridge\ClickSend\ClickSendTransportFactory::class => false, Bridge\ContactEveryone\ContactEveryoneTransportFactory::class => false, Bridge\Discord\DiscordTransportFactory::class => false, Bridge\Engagespot\EngagespotTransportFactory::class => false, @@ -111,6 +112,7 @@ public static function messageWhereSchemeIsPartOfSchemeToPackageMapProvider(): \ yield ['sns', 'symfony/amazon-sns-notifier']; yield ['bandwidth', 'symfony/bandwidth-notifier']; yield ['clickatell', 'symfony/clickatell-notifier']; + yield ['clicksend', 'symfony/click-send-notifier']; yield ['contact-everyone', 'symfony/contact-everyone-notifier']; yield ['discord', 'symfony/discord-notifier']; yield ['esendex', 'symfony/esendex-notifier']; diff --git a/src/Symfony/Component/Notifier/Transport.php b/src/Symfony/Component/Notifier/Transport.php index fa37f6c9b5848..7ef1ac264bd21 100644 --- a/src/Symfony/Component/Notifier/Transport.php +++ b/src/Symfony/Component/Notifier/Transport.php @@ -33,6 +33,7 @@ final class Transport Bridge\Bandwidth\BandwidthTransportFactory::class, Bridge\Chatwork\ChatworkTransportFactory::class, Bridge\Clickatell\ClickatellTransportFactory::class, + Bridge\ClickSend\ClickSendTransportFactory::class, Bridge\ContactEveryone\ContactEveryoneTransportFactory::class, Bridge\Discord\DiscordTransportFactory::class, Bridge\Engagespot\EngagespotTransportFactory::class,