From ee744e7d327e965f5798fd0769de37f21c400068 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Nadaud?= Date: Mon, 14 Jun 2021 18:59:32 +0200 Subject: [PATCH] [Notifier] add Mailjet SMS bridge --- .../FrameworkExtension.php | 2 + .../Resources/config/notifier_transports.php | 5 ++ .../Notifier/Bridge/Mailjet/.gitattributes | 4 + .../Notifier/Bridge/Mailjet/.gitignore | 3 + .../Notifier/Bridge/Mailjet/CHANGELOG.md | 7 ++ .../Component/Notifier/Bridge/Mailjet/LICENSE | 19 +++++ .../Bridge/Mailjet/MailjetTransport.php | 78 +++++++++++++++++++ .../Mailjet/MailjetTransportFactory.php | 47 +++++++++++ .../Notifier/Bridge/Mailjet/README.md | 23 ++++++ .../Tests/MailjetTransportFactoryTest.php | 52 +++++++++++++ .../Mailjet/Tests/MailjetTransportTest.php | 47 +++++++++++ .../Notifier/Bridge/Mailjet/composer.json | 34 ++++++++ .../Notifier/Bridge/Mailjet/phpunit.xml.dist | 31 ++++++++ .../Exception/UnsupportedSchemeException.php | 4 + .../UnsupportedSchemeExceptionTest.php | 3 + src/Symfony/Component/Notifier/Transport.php | 2 + 16 files changed, 361 insertions(+) create mode 100644 src/Symfony/Component/Notifier/Bridge/Mailjet/.gitattributes create mode 100644 src/Symfony/Component/Notifier/Bridge/Mailjet/.gitignore create mode 100644 src/Symfony/Component/Notifier/Bridge/Mailjet/CHANGELOG.md create mode 100644 src/Symfony/Component/Notifier/Bridge/Mailjet/LICENSE create mode 100644 src/Symfony/Component/Notifier/Bridge/Mailjet/MailjetTransport.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Mailjet/MailjetTransportFactory.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Mailjet/README.md create mode 100644 src/Symfony/Component/Notifier/Bridge/Mailjet/Tests/MailjetTransportFactoryTest.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Mailjet/Tests/MailjetTransportTest.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Mailjet/composer.json create mode 100644 src/Symfony/Component/Notifier/Bridge/Mailjet/phpunit.xml.dist diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index dd4eeaa643c6b..71364395bc7cb 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -124,6 +124,7 @@ use Symfony\Component\Notifier\Bridge\Iqsms\IqsmsTransportFactory; use Symfony\Component\Notifier\Bridge\LightSms\LightSmsTransportFactory; use Symfony\Component\Notifier\Bridge\LinkedIn\LinkedInTransportFactory; +use Symfony\Component\Notifier\Bridge\Mailjet\MailjetTransportFactory as MailjetNotifierTransportFactory; use Symfony\Component\Notifier\Bridge\Mattermost\MattermostTransportFactory; use Symfony\Component\Notifier\Bridge\Mercure\MercureTransportFactory; use Symfony\Component\Notifier\Bridge\MessageBird\MessageBirdTransport; @@ -2429,6 +2430,7 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $ IqsmsTransportFactory::class => 'notifier.transport_factory.iqsms', LightSmsTransportFactory::class => 'notifier.transport_factory.lightsms', LinkedInTransportFactory::class => 'notifier.transport_factory.linkedin', + MailjetNotifierTransportFactory::class => 'notifier.transport_factory.mailjet', MattermostTransportFactory::class => 'notifier.transport_factory.mattermost', MercureTransportFactory::class => 'notifier.transport_factory.mercure', MessageBirdTransport::class => 'notifier.transport_factory.messagebird', diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php index 1451fa7289c9f..d7235c2ccfb30 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php @@ -26,6 +26,7 @@ use Symfony\Component\Notifier\Bridge\Iqsms\IqsmsTransportFactory; use Symfony\Component\Notifier\Bridge\LightSms\LightSmsTransportFactory; use Symfony\Component\Notifier\Bridge\LinkedIn\LinkedInTransportFactory; +use Symfony\Component\Notifier\Bridge\Mailjet\MailjetTransportFactory; use Symfony\Component\Notifier\Bridge\Mattermost\MattermostTransportFactory; use Symfony\Component\Notifier\Bridge\Mercure\MercureTransportFactory; use Symfony\Component\Notifier\Bridge\MessageBird\MessageBirdTransportFactory; @@ -199,5 +200,9 @@ ->set('notifier.transport_factory.telnyx', TelnyxTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.mailjet', MailjetTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') ; }; diff --git a/src/Symfony/Component/Notifier/Bridge/Mailjet/.gitattributes b/src/Symfony/Component/Notifier/Bridge/Mailjet/.gitattributes new file mode 100644 index 0000000000000..84c7add058fb5 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Mailjet/.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/Mailjet/.gitignore b/src/Symfony/Component/Notifier/Bridge/Mailjet/.gitignore new file mode 100644 index 0000000000000..c49a5d8df5c65 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Mailjet/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/src/Symfony/Component/Notifier/Bridge/Mailjet/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/Mailjet/CHANGELOG.md new file mode 100644 index 0000000000000..3a08c7ededfcd --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Mailjet/CHANGELOG.md @@ -0,0 +1,7 @@ +CHANGELOG +========= + +5.4 +--- + + * Add the bridge diff --git a/src/Symfony/Component/Notifier/Bridge/Mailjet/LICENSE b/src/Symfony/Component/Notifier/Bridge/Mailjet/LICENSE new file mode 100644 index 0000000000000..efb17f98e7dd3 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Mailjet/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2021 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/Mailjet/MailjetTransport.php b/src/Symfony/Component/Notifier/Bridge/Mailjet/MailjetTransport.php new file mode 100644 index 0000000000000..935269fcc6a0e --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Mailjet/MailjetTransport.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Mailjet; + +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\HttpClientInterface; + +/** + * @author Jérôme Nadaud + */ +final class MailjetTransport extends AbstractTransport +{ + protected const HOST = 'api.mailjet.com'; + + private $authToken; + private $from; + + public function __construct(string $authToken, string $from, HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null) + { + $this->authToken = $authToken; + $this->from = $from; + + parent::__construct($client, $dispatcher); + } + + public function __toString(): string + { + return sprintf('mailjet://%s@%s', $this->from, $this->getEndpoint()); + } + + public function supports(MessageInterface $message): bool + { + return $message instanceof SmsMessage; + } + + protected function doSend(MessageInterface $message): SentMessage + { + if (!$message instanceof SmsMessage) { + throw new UnsupportedMessageTypeException(__CLASS__, SmsMessage::class, $message); + } + + $endpoint = sprintf('https://%s/v4/sms-send', $this->getEndpoint()); + + $response = $this->client->request('POST', $endpoint, [ + 'auth_bearer' => $this->authToken, + 'json' => [ + 'From' => $this->from, + 'To' => $message->getPhone(), + 'Text' => $message->getSubject(), + ], + ]); + + if (200 !== $response->getStatusCode()) { + $content = $response->toArray(false); + $errorMessage = $content['requestError']['serviceException']['messageId'] ?? ''; + $errorInfo = $content['requestError']['serviceException']['text'] ?? ''; + + throw new TransportException(sprintf('Unable to send the SMS: '.$errorMessage.' (%s).', $errorInfo), $response); + } + + return new SentMessage($message, (string) $this); + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Mailjet/MailjetTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Mailjet/MailjetTransportFactory.php new file mode 100644 index 0000000000000..d5d2d9933d0d1 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Mailjet/MailjetTransportFactory.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\Mailjet; + +use Symfony\Component\Notifier\Exception\UnsupportedSchemeException; +use Symfony\Component\Notifier\Transport\AbstractTransportFactory; +use Symfony\Component\Notifier\Transport\Dsn; +use Symfony\Component\Notifier\Transport\TransportInterface; + +/** + * @author Jérôme Nadaud + */ +final class MailjetTransportFactory extends AbstractTransportFactory +{ + /** + * @return MailjetTransport + */ + public function create(Dsn $dsn): TransportInterface + { + $scheme = $dsn->getScheme(); + + if ('mailjet' !== $scheme) { + throw new UnsupportedSchemeException($dsn, 'mailjet', $this->getSupportedSchemes()); + } + + $authToken = $this->getPassword($dsn); + $from = $this->getUser($dsn); + $host = 'default' === $dsn->getHost() ? null : $dsn->getHost(); + $port = $dsn->getPort(); + + return (new MailjetTransport($authToken, $from, $this->client, $this->dispatcher))->setHost($host)->setPort($port); + } + + protected function getSupportedSchemes(): array + { + return ['mailjet']; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Mailjet/README.md b/src/Symfony/Component/Notifier/Bridge/Mailjet/README.md new file mode 100644 index 0000000000000..67a2ee5105873 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Mailjet/README.md @@ -0,0 +1,23 @@ +Mailjet Notifier +================ + +Provides [Mailjet](https://mailjet.com) SMS integration for Symfony Notifier. + +DSN example +----------- + +``` +MAILJET_DSN=mailjet://FROM:AUTH_TOKEN@default +``` + +where: + - `AUTH_TOKEN` is your Mailjet SMS auth token + - `FROM` is an alphanumeric sender ID + +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/Mailjet/Tests/MailjetTransportFactoryTest.php b/src/Symfony/Component/Notifier/Bridge/Mailjet/Tests/MailjetTransportFactoryTest.php new file mode 100644 index 0000000000000..f42f719553c25 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Mailjet/Tests/MailjetTransportFactoryTest.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Mailjet\Tests; + +use Symfony\Component\Notifier\Bridge\Mailjet\MailjetTransportFactory; +use Symfony\Component\Notifier\Test\TransportFactoryTestCase; +use Symfony\Component\Notifier\Transport\TransportFactoryInterface; + +final class MailjetTransportFactoryTest extends TransportFactoryTestCase +{ + /** + * @return MailjetTransportFactory + */ + public function createFactory(): TransportFactoryInterface + { + return new MailjetTransportFactory(); + } + + public function createProvider(): iterable + { + yield [ + 'mailjet://Mailjet@host.test', + 'mailjet://Mailjet:authtoken@host.test', + ]; + } + + public function supportsProvider(): iterable + { + yield [true, 'mailjet://Mailjet:authtoken@default']; + yield [false, 'somethingElse://Mailjet:authtoken@default']; + } + + public function missingRequiredOptionProvider(): iterable + { + yield 'missing option: from' => ['mailjet://authtoken@default']; + } + + public function unsupportedSchemeProvider(): iterable + { + yield ['somethingElse://default']; // missing "from" and "token" option + yield ['somethingElse://authtoken@default']; // missing "from" option + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Mailjet/Tests/MailjetTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Mailjet/Tests/MailjetTransportTest.php new file mode 100644 index 0000000000000..0259ba0260736 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Mailjet/Tests/MailjetTransportTest.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\Mailjet\Tests; + +use Symfony\Component\Notifier\Bridge\Mailjet\MailjetTransport; +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\Component\Notifier\Transport\TransportInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +final class MailjetTransportTest extends TransportTestCase +{ + /** + * @return MailjetTransport + */ + public function createTransport(?HttpClientInterface $client = null): TransportInterface + { + return (new MailjetTransport('authtoken', 'Mailjet', $client ?? $this->createMock(HttpClientInterface::class)))->setHost('host.test'); + } + + public function toStringProvider(): iterable + { + yield ['mailjet://Mailjet:authtoken@host.test', $this->createTransport()]; + } + + public function supportedMessagesProvider(): iterable + { + yield [new SmsMessage('0611223344', 'Hello!')]; + } + + public function unsupportedMessagesProvider(): iterable + { + yield [new ChatMessage('Hello!')]; + yield [$this->createMock(MessageInterface::class)]; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Mailjet/composer.json b/src/Symfony/Component/Notifier/Bridge/Mailjet/composer.json new file mode 100644 index 0000000000000..15a9adf9abc68 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Mailjet/composer.json @@ -0,0 +1,34 @@ +{ + "name": "symfony/mailjet-notifier", + "type": "symfony-bridge", + "description": "Symfony Mailjet Notifier Bridge", + "keywords": ["sms", "mailjet", "notifier"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Jérôme Nadaud", + "email": "jerome@nadaud.io" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.2.5", + "symfony/http-client": "^4.3|^5.0", + "symfony/notifier": "^5.3" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\Mailjet\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/src/Symfony/Component/Notifier/Bridge/Mailjet/phpunit.xml.dist b/src/Symfony/Component/Notifier/Bridge/Mailjet/phpunit.xml.dist new file mode 100644 index 0000000000000..f79d1ee1c638d --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Mailjet/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 908ea9588d227..73786fa71ff33 100644 --- a/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php +++ b/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php @@ -80,6 +80,10 @@ class UnsupportedSchemeException extends LogicException 'class' => Bridge\LinkedIn\LinkedInTransportFactory::class, 'package' => 'symfony/linked-in-notifier', ], + 'mailjet' => [ + 'class' => Bridge\Mailjet\MailjetTransportFactory::class, + 'package' => 'symfony/mailjet-notifier', + ], 'mattermost' => [ 'class' => Bridge\Mattermost\MattermostTransportFactory::class, 'package' => 'symfony/mattermost-notifier', diff --git a/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php b/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php index 81e0e49cd94ce..e279156a05aae 100644 --- a/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php +++ b/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php @@ -28,6 +28,7 @@ use Symfony\Component\Notifier\Bridge\Iqsms\IqsmsTransportFactory; use Symfony\Component\Notifier\Bridge\LightSms\LightSmsTransportFactory; use Symfony\Component\Notifier\Bridge\LinkedIn\LinkedInTransportFactory; +use Symfony\Component\Notifier\Bridge\Mailjet\MailjetTransportFactory; use Symfony\Component\Notifier\Bridge\Mattermost\MattermostTransportFactory; use Symfony\Component\Notifier\Bridge\Mercure\MercureTransportFactory; use Symfony\Component\Notifier\Bridge\MessageBird\MessageBirdTransportFactory; @@ -75,6 +76,7 @@ public static function setUpBeforeClass(): void IqsmsTransportFactory::class => false, LightSmsTransportFactory::class => false, LinkedInTransportFactory::class => false, + MailjetTransportFactory::class => false, MattermostTransportFactory::class => false, MercureTransportFactory::class => false, MessageBirdTransportFactory::class => false, @@ -128,6 +130,7 @@ public function messageWhereSchemeIsPartOfSchemeToPackageMapProvider(): \Generat yield ['iqsms', 'symfony/iqsms-notifier']; yield ['lightsms', 'symfony/light-sms-notifier']; yield ['linkedin', 'symfony/linked-in-notifier']; + yield ['mailjet', 'symfony/mailjet-notifier']; yield ['mattermost', 'symfony/mattermost-notifier']; yield ['mercure', 'symfony/mercure-notifier']; yield ['messagebird', 'symfony/message-bird-notifier']; diff --git a/src/Symfony/Component/Notifier/Transport.php b/src/Symfony/Component/Notifier/Transport.php index ecb7420e18378..bf3c26c3c8513 100644 --- a/src/Symfony/Component/Notifier/Transport.php +++ b/src/Symfony/Component/Notifier/Transport.php @@ -22,6 +22,7 @@ use Symfony\Component\Notifier\Bridge\Infobip\InfobipTransportFactory; use Symfony\Component\Notifier\Bridge\Iqsms\IqsmsTransportFactory; use Symfony\Component\Notifier\Bridge\LightSms\LightSmsTransportFactory; +use Symfony\Component\Notifier\Bridge\Mailjet\MailjetTransportFactory; use Symfony\Component\Notifier\Bridge\Mattermost\MattermostTransportFactory; use Symfony\Component\Notifier\Bridge\MessageBird\MessageBirdTransportFactory; use Symfony\Component\Notifier\Bridge\MessageMedia\MessageMediaTransportFactory; @@ -68,6 +69,7 @@ class Transport InfobipTransportFactory::class, IqsmsTransportFactory::class, LightSmsTransportFactory::class, + MailjetTransportFactory::class, MattermostTransportFactory::class, MessageBirdTransportFactory::class, MessageMediaTransportFactory::class,