From 6b9f72178002a70b747dde9d1dbde53ba11cbf48 Mon Sep 17 00:00:00 2001 From: Piergiuseppe Longo Date: Thu, 14 Jan 2021 13:35:03 +0100 Subject: [PATCH] [Notifier] Add GatewayApi bridge --- .../FrameworkExtension.php | 2 + .../Resources/config/notifier_transports.php | 5 ++ .../Notifier/Bridge/GatewayApi/.gitattributes | 4 + .../Notifier/Bridge/GatewayApi/CHANGELOG.md | 7 ++ .../Bridge/GatewayApi/GatewayApiTransport.php | 80 +++++++++++++++++++ .../GatewayApi/GatewayApiTransportFactory.php | 47 +++++++++++ .../Notifier/Bridge/GatewayApi/LICENSE | 19 +++++ .../Notifier/Bridge/GatewayApi/README.md | 25 ++++++ .../Tests/GatewayApiTransportFactoryTest.php | 46 +++++++++++ .../Tests/GatewayApiTransportTest.php | 68 ++++++++++++++++ .../Notifier/Bridge/GatewayApi/composer.json | 34 ++++++++ .../Bridge/GatewayApi/phpunit.xml.dist | 31 +++++++ .../Exception/UnsupportedSchemeException.php | 4 + src/Symfony/Component/Notifier/Transport.php | 2 + 14 files changed, 374 insertions(+) create mode 100644 src/Symfony/Component/Notifier/Bridge/GatewayApi/.gitattributes create mode 100644 src/Symfony/Component/Notifier/Bridge/GatewayApi/CHANGELOG.md create mode 100644 src/Symfony/Component/Notifier/Bridge/GatewayApi/GatewayApiTransport.php create mode 100644 src/Symfony/Component/Notifier/Bridge/GatewayApi/GatewayApiTransportFactory.php create mode 100644 src/Symfony/Component/Notifier/Bridge/GatewayApi/LICENSE create mode 100644 src/Symfony/Component/Notifier/Bridge/GatewayApi/README.md create mode 100644 src/Symfony/Component/Notifier/Bridge/GatewayApi/Tests/GatewayApiTransportFactoryTest.php create mode 100644 src/Symfony/Component/Notifier/Bridge/GatewayApi/Tests/GatewayApiTransportTest.php create mode 100644 src/Symfony/Component/Notifier/Bridge/GatewayApi/composer.json create mode 100644 src/Symfony/Component/Notifier/Bridge/GatewayApi/phpunit.xml.dist diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index d8325dd394..f04591a3cc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -105,6 +105,7 @@ use Symfony\Component\Notifier\Bridge\Discord\DiscordTransportFactory; use Symfony\Component\Notifier\Bridge\Esendex\EsendexTransportFactory; use Symfony\Component\Notifier\Bridge\Firebase\FirebaseTransportFactory; use Symfony\Component\Notifier\Bridge\FreeMobile\FreeMobileTransportFactory; +use Symfony\Component\Notifier\Bridge\GatewayApi\GatewayApiTransportFactory; use Symfony\Component\Notifier\Bridge\GoogleChat\GoogleChatTransportFactory; use Symfony\Component\Notifier\Bridge\Infobip\InfobipTransportFactory; use Symfony\Component\Notifier\Bridge\Iqsms\IqsmsTransportFactory; @@ -2236,6 +2237,7 @@ class FrameworkExtension extends Extension SendinblueNotifierTransportFactory::class => 'notifier.transport_factory.sendinblue', DiscordTransportFactory::class => 'notifier.transport_factory.discord', LinkedInTransportFactory::class => 'notifier.transport_factory.linkedin', + GatewayApiTransportFactory::class => 'notifier.transport_factory.gatewayapi', ]; foreach ($classToServices as $class => $service) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php index 88952b80ae..74d356677c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php @@ -15,6 +15,7 @@ use Symfony\Component\Notifier\Bridge\Discord\DiscordTransportFactory; use Symfony\Component\Notifier\Bridge\Esendex\EsendexTransportFactory; use Symfony\Component\Notifier\Bridge\Firebase\FirebaseTransportFactory; use Symfony\Component\Notifier\Bridge\FreeMobile\FreeMobileTransportFactory; +use Symfony\Component\Notifier\Bridge\GatewayApi\GatewayApiTransportFactory; use Symfony\Component\Notifier\Bridge\GoogleChat\GoogleChatTransportFactory; use Symfony\Component\Notifier\Bridge\Infobip\InfobipTransportFactory; use Symfony\Component\Notifier\Bridge\Iqsms\IqsmsTransportFactory; @@ -120,6 +121,10 @@ return static function (ContainerConfigurator $container) { ->parent('notifier.transport_factory.abstract') ->tag('chatter.transport_factory') + ->set('notifier.transport_factory.gatewayapi', GatewayApiTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + ->set('notifier.transport_factory.null', NullTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('chatter.transport_factory') diff --git a/src/Symfony/Component/Notifier/Bridge/GatewayApi/.gitattributes b/src/Symfony/Component/Notifier/Bridge/GatewayApi/.gitattributes new file mode 100644 index 0000000000..84c7add058 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/GatewayApi/.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/GatewayApi/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/GatewayApi/CHANGELOG.md new file mode 100644 index 0000000000..1f2b652ac2 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/GatewayApi/CHANGELOG.md @@ -0,0 +1,7 @@ +CHANGELOG +========= + +5.3 +--- + + * Add the bridge diff --git a/src/Symfony/Component/Notifier/Bridge/GatewayApi/GatewayApiTransport.php b/src/Symfony/Component/Notifier/Bridge/GatewayApi/GatewayApiTransport.php new file mode 100644 index 0000000000..c8e4393dfa --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/GatewayApi/GatewayApiTransport.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\GatewayApi; + +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 Piergiuseppe Longo + */ +final class GatewayApiTransport extends AbstractTransport +{ + protected const HOST = 'gatewayapi.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('gatewayapi://%s?from=%s', $this->getEndpoint(), $this->from); + } + + 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/rest/mtsms', $this->getEndpoint()); + + $response = $this->client->request('POST', $endpoint, [ + 'auth_basic' => [$this->authToken, ''], + 'json' => [ + 'sender' => $this->from, + 'recipients' => [['msisdn' => $message->getPhone()]], + 'message' => $message->getSubject(), + ], + ]); + + $statusCode = $response->getStatusCode(); + if (200 !== $statusCode) { + throw new TransportException(sprintf('Unable to send the SMS: error %d.', $statusCode), $response); + } + + $content = $response->toArray(false); + + $sentMessage = new SentMessage($message, (string) $this); + $sentMessage->setMessageId((string) $content['ids'][0]); + + return $sentMessage; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/GatewayApi/GatewayApiTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/GatewayApi/GatewayApiTransportFactory.php new file mode 100644 index 0000000000..282baea5fb --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/GatewayApi/GatewayApiTransportFactory.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\GatewayApi; + +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 Piergiuseppe Longo + */ +final class GatewayApiTransportFactory extends AbstractTransportFactory +{ + /** + * @return GatewayApiTransport + */ + public function create(Dsn $dsn): TransportInterface + { + $scheme = $dsn->getScheme(); + + if ('gatewayapi' !== $scheme) { + throw new UnsupportedSchemeException($dsn, 'gatewayapi', $this->getSupportedSchemes()); + } + + $authToken = $this->getUser($dsn); + $from = $dsn->getRequiredOption('from'); + $host = 'default' === $dsn->getHost() ? null : $dsn->getHost(); + $port = $dsn->getPort(); + + return (new GatewayApiTransport($authToken, $from, $this->client, $this->dispatcher))->setHost($host)->setPort($port); + } + + protected function getSupportedSchemes(): array + { + return ['gatewayapi']; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/GatewayApi/LICENSE b/src/Symfony/Component/Notifier/Bridge/GatewayApi/LICENSE new file mode 100644 index 0000000000..efb17f98e7 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/GatewayApi/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/GatewayApi/README.md b/src/Symfony/Component/Notifier/Bridge/GatewayApi/README.md new file mode 100644 index 0000000000..82a79657f2 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/GatewayApi/README.md @@ -0,0 +1,25 @@ +GatewayApi Notifier +=============== + +Provides GatewayApi integration for Symfony Notifier. + +DSN example +----------- + +``` +GATEWAYAPI_DSN=gatewayapi://TOKEN@default?from=FROM +``` + +where: +- `TOKEN` is API Token (OAuth) +- `FROM` is sender name + +See your account info at https://gatewayapi.com + +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/GatewayApi/Tests/GatewayApiTransportFactoryTest.php b/src/Symfony/Component/Notifier/Bridge/GatewayApi/Tests/GatewayApiTransportFactoryTest.php new file mode 100644 index 0000000000..4aff0f3140 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/GatewayApi/Tests/GatewayApiTransportFactoryTest.php @@ -0,0 +1,46 @@ + + * @author Oskar Stark + */ +final class GatewayApiTransportFactoryTest extends TransportFactoryTestCase +{ + /** + * @return GatewayApiTransportFactory + */ + public function createFactory(): TransportFactoryInterface + { + return new GatewayApiTransportFactory(); + } + + public function createProvider(): iterable + { + yield [ + 'gatewayapi://gatewayapi.com?from=Symfony', + 'gatewayapi://token@default?from=Symfony', + ]; + } + + public function supportsProvider(): iterable + { + yield [true, 'gatewayapi://token@host.test?from=Symfony']; + yield [false, 'somethingElse://token@default?from=Symfony']; + } + + public function incompleteDsnProvider(): iterable + { + yield 'missing token' => ['gatewayapi://host.test?from=Symfony']; + } + + public function missingRequiredOptionProvider(): iterable + { + yield 'missing option: from' => ['gatewayapi://token@host.test']; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/GatewayApi/Tests/GatewayApiTransportTest.php b/src/Symfony/Component/Notifier/Bridge/GatewayApi/Tests/GatewayApiTransportTest.php new file mode 100644 index 0000000000..7671814d8e --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/GatewayApi/Tests/GatewayApiTransportTest.php @@ -0,0 +1,68 @@ + + * @author Oskar Stark + */ +final class GatewayApiTransportTest extends TransportTestCase +{ + /** + * @return GatewayApiTransport + */ + public function createTransport(?HttpClientInterface $client = null): TransportInterface + { + return new GatewayApiTransport('authtoken', 'Symfony', $client ?: $this->createMock(HttpClientInterface::class)); + } + + public function toStringProvider(): iterable + { + yield ['gatewayapi://gatewayapi.com?from=Symfony', $this->createTransport()]; + } + + public function supportedMessagesProvider(): iterable + { + yield [new SmsMessage('0611223344', 'Hello!')]; + } + + public function unsupportedMessagesProvider(): iterable + { + yield [new ChatMessage('Hello!')]; + yield [$this->createMock(MessageInterface::class)]; + } + + public function testSend() + { + $response = $this->createMock(ResponseInterface::class); + $response->expects($this->exactly(2)) + ->method('getStatusCode') + ->willReturn(200); + $response->expects($this->once()) + ->method('getContent') + ->willReturn(json_encode(['ids' => [42]])); + + $client = new MockHttpClient(static function () use ($response): ResponseInterface { + return $response; + }); + + $message = new SmsMessage('3333333333', 'Hello!'); + + $transport = $this->createTransport($client); + $sentMessage = $transport->send($message); + + $this->assertInstanceOf(SentMessage::class, $sentMessage); + $this->assertSame('42', $sentMessage->getMessageId()); + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/GatewayApi/composer.json b/src/Symfony/Component/Notifier/Bridge/GatewayApi/composer.json new file mode 100644 index 0000000000..d7bfaf53c3 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/GatewayApi/composer.json @@ -0,0 +1,34 @@ +{ + "name": "symfony/gatewayapi-notifier", + "type": "symfony-bridge", + "description": "Symfony GatewayApi Notifier Bridge", + "keywords": ["sms", "gatewayapi", "notifier"], + "homepage": "https://gatewayapi.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Piergiuseppe Longo", + "email": "piergiuseppe.longo@gmail.com" + }, + { + "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\\GatewayApi\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/src/Symfony/Component/Notifier/Bridge/GatewayApi/phpunit.xml.dist b/src/Symfony/Component/Notifier/Bridge/GatewayApi/phpunit.xml.dist new file mode 100644 index 0000000000..23919e750d --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/GatewayApi/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 ce78ee37ca..65b08e64e4 100644 --- a/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php +++ b/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php @@ -88,6 +88,10 @@ class UnsupportedSchemeException extends LogicException 'class' => Bridge\Discord\DiscordTransportFactory::class, 'package' => 'symfony/discord-notifier', ], + 'gatewayapi' => [ + 'class' => Bridge\GatewayApi\GatewayApiTransportFactory::class, + 'package' => 'symfony/gatewayapi-notifier', + ], ]; /** diff --git a/src/Symfony/Component/Notifier/Transport.php b/src/Symfony/Component/Notifier/Transport.php index 43ab3469b8..dd58cb7051 100644 --- a/src/Symfony/Component/Notifier/Transport.php +++ b/src/Symfony/Component/Notifier/Transport.php @@ -15,6 +15,7 @@ use Symfony\Component\Notifier\Bridge\Discord\DiscordTransportFactory; use Symfony\Component\Notifier\Bridge\Esendex\EsendexTransportFactory; use Symfony\Component\Notifier\Bridge\Firebase\FirebaseTransportFactory; use Symfony\Component\Notifier\Bridge\FreeMobile\FreeMobileTransportFactory; +use Symfony\Component\Notifier\Bridge\GatewayApi\GatewayApiTransportFactory; use Symfony\Component\Notifier\Bridge\Infobip\InfobipTransportFactory; use Symfony\Component\Notifier\Bridge\Iqsms\IqsmsTransportFactory; use Symfony\Component\Notifier\Bridge\Mattermost\MattermostTransportFactory; @@ -64,6 +65,7 @@ class Transport EsendexTransportFactory::class, SendinblueTransportFactory::class, DiscordTransportFactory::class, + GatewayApiTransportFactory::class, ]; private $factories;