From d500adcc823a437f3279fb703d62dd0f81232f1e Mon Sep 17 00:00:00 2001 From: Egor Taranov Date: Tue, 9 Nov 2021 16:35:50 +0200 Subject: [PATCH] [Notifier] [Bridge] [KazInfoTeh] added the bridge --- .../FrameworkExtension.php | 2 + .../Resources/config/notifier_transports.php | 5 + .../Notifier/Bridge/KazInfoTeh/.gitattributes | 4 + .../Notifier/Bridge/KazInfoTeh/.gitignore | 3 + .../Notifier/Bridge/KazInfoTeh/CHANGELOG.md | 7 ++ .../Bridge/KazInfoTeh/KazInfoTehTransport.php | 108 ++++++++++++++++++ .../KazInfoTeh/KazInfoTehTransportFactory.php | 51 +++++++++ .../Notifier/Bridge/KazInfoTeh/LICENSE | 19 +++ .../Notifier/Bridge/KazInfoTeh/README.md | 24 ++++ .../Tests/KazInfoTehTransportFactoryTest.php | 63 ++++++++++ .../Tests/KazInfoTehTransportTest.php | 94 +++++++++++++++ .../Notifier/Bridge/KazInfoTeh/composer.json | 32 ++++++ .../Bridge/KazInfoTeh/phpunit.xml.dist | 31 +++++ 13 files changed, 443 insertions(+) create mode 100644 src/Symfony/Component/Notifier/Bridge/KazInfoTeh/.gitattributes create mode 100644 src/Symfony/Component/Notifier/Bridge/KazInfoTeh/.gitignore create mode 100644 src/Symfony/Component/Notifier/Bridge/KazInfoTeh/CHANGELOG.md create mode 100644 src/Symfony/Component/Notifier/Bridge/KazInfoTeh/KazInfoTehTransport.php create mode 100644 src/Symfony/Component/Notifier/Bridge/KazInfoTeh/KazInfoTehTransportFactory.php create mode 100644 src/Symfony/Component/Notifier/Bridge/KazInfoTeh/LICENSE create mode 100644 src/Symfony/Component/Notifier/Bridge/KazInfoTeh/README.md create mode 100644 src/Symfony/Component/Notifier/Bridge/KazInfoTeh/Tests/KazInfoTehTransportFactoryTest.php create mode 100644 src/Symfony/Component/Notifier/Bridge/KazInfoTeh/Tests/KazInfoTehTransportTest.php create mode 100644 src/Symfony/Component/Notifier/Bridge/KazInfoTeh/composer.json create mode 100644 src/Symfony/Component/Notifier/Bridge/KazInfoTeh/phpunit.xml.dist diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index f7e10d2937dcf..135f50694029d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -129,6 +129,7 @@ use Symfony\Component\Notifier\Bridge\GoogleChat\GoogleChatTransportFactory; use Symfony\Component\Notifier\Bridge\Infobip\InfobipTransportFactory; use Symfony\Component\Notifier\Bridge\Iqsms\IqsmsTransportFactory; +use Symfony\Component\Notifier\Bridge\KazInfoTeh\KazInfoTehTransportFactory; use Symfony\Component\Notifier\Bridge\LightSms\LightSmsTransportFactory; use Symfony\Component\Notifier\Bridge\LinkedIn\LinkedInTransportFactory; use Symfony\Component\Notifier\Bridge\Mailjet\MailjetTransportFactory as MailjetNotifierTransportFactory; @@ -2429,6 +2430,7 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $ GoogleChatTransportFactory::class => 'notifier.transport_factory.google-chat', InfobipTransportFactory::class => 'notifier.transport_factory.infobip', IqsmsTransportFactory::class => 'notifier.transport_factory.iqsms', + KazInfoTehTransportFactory::class => 'notifier.transport_factory.kaz-info-teh', LightSmsTransportFactory::class => 'notifier.transport_factory.light-sms', LinkedInTransportFactory::class => 'notifier.transport_factory.linked-in', MailjetNotifierTransportFactory::class => 'notifier.transport_factory.mailjet', diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php index 4a4bf34bdc8ee..647d056406c6e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php @@ -27,6 +27,7 @@ use Symfony\Component\Notifier\Bridge\GoogleChat\GoogleChatTransportFactory; use Symfony\Component\Notifier\Bridge\Infobip\InfobipTransportFactory; use Symfony\Component\Notifier\Bridge\Iqsms\IqsmsTransportFactory; +use Symfony\Component\Notifier\Bridge\KazInfoTeh\KazInfoTehTransportFactory; use Symfony\Component\Notifier\Bridge\LightSms\LightSmsTransportFactory; use Symfony\Component\Notifier\Bridge\LinkedIn\LinkedInTransportFactory; use Symfony\Component\Notifier\Bridge\Mailjet\MailjetTransportFactory; @@ -251,5 +252,9 @@ ->set('notifier.transport_factory.expo', ExpoTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.kaz-info-teh', KazInfoTehTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') ; }; diff --git a/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/.gitattributes b/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/.gitattributes new file mode 100644 index 0000000000000..84c7add058fb5 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/.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/KazInfoTeh/.gitignore b/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/.gitignore new file mode 100644 index 0000000000000..c49a5d8df5c65 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/CHANGELOG.md new file mode 100644 index 0000000000000..0edfe85347aa6 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/CHANGELOG.md @@ -0,0 +1,7 @@ +CHANGELOG +========= + +6.1 +--- + +* Add the bridge diff --git a/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/KazInfoTehTransport.php b/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/KazInfoTehTransport.php new file mode 100644 index 0000000000000..b9747ece39dbb --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/KazInfoTehTransport.php @@ -0,0 +1,108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\KazInfoTeh; + +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 Egor Taranov + */ +class KazInfoTehTransport extends AbstractTransport +{ + protected const HOST = 'kazinfoteh.org'; + + private string $username; + private string $password; + private string $sender; + + public function __construct(string $username, string $password, string $sender, HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null) + { + $this->username = $username; + $this->password = $password; + $this->sender = $sender; + + parent::__construct($client, $dispatcher); + } + + public function __toString(): string + { + return sprintf('kaz-info-teh://%s?sender=%s', $this->getEndpoint(), $this->sender); + } + + public function supports(MessageInterface $message): bool + { + return $message instanceof SmsMessage + && str_starts_with($message->getPhone(), '77') + && 11 === \strlen($message->getPhone()) + ; + } + + protected function doSend(MessageInterface $message): SentMessage + { + if (!$message instanceof SmsMessage || !$this->supports($message)) { + throw new UnsupportedMessageTypeException(__CLASS__, SmsMessage::class, $message); + } + + $endpoint = sprintf('http://%s/api', $this->getEndpoint()); + $response = $this->client->request('POST', $endpoint, [ + 'query' => [ + 'action' => 'sendmessage', + 'username' => $this->username, + 'password' => $this->password, + 'recipient' => $message->getPhone(), + 'messagetype' => 'SMS:TEXT', + 'originator' => $this->sender, + 'messagedata' => $message->getSubject(), + ], + ]); + + try { + $statusCode = $response->getStatusCode(); + } catch (TransportExceptionInterface $e) { + throw new TransportException('Could not reach the remote KazInfoTeh server.', $response, 0, $e); + } + + try { + $content = new \SimpleXMLElement($response->getContent(false)); + } catch (\Exception $e) { + throw new TransportException('Unable to send the SMS: "Couldn\'t read response".', $response, previous: $e); + } + + if (200 !== $statusCode || '0' !== (string) $content->statuscode) { + $error = (string) $content->statusmessage ?: $content->errormessage ?: 'unknown error'; + + throw new TransportException(sprintf('Unable to send the SMS: "%s".', $error), $response); + } + + return new SentMessage($message, (string) $this); + } + + protected function getEndpoint(): string + { + $endpoint = $this->host ?: $this->getDefaultHost(); + if ($this->getDefaultHost() === $endpoint && null === $this->port) { + $endpoint .= ':9507'; + } elseif (null !== $this->port) { + $endpoint .= ':'.$this->port; + } + + return $endpoint; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/KazInfoTehTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/KazInfoTehTransportFactory.php new file mode 100644 index 0000000000000..6f1332ba5ebda --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/KazInfoTehTransportFactory.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\KazInfoTeh; + +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 Egor Taranov + */ +final class KazInfoTehTransportFactory extends AbstractTransportFactory +{ + /** + * {@inheritdoc} + */ + public function create(Dsn $dsn): TransportInterface + { + $scheme = $dsn->getScheme(); + + if ('kaz-info-teh' !== $scheme) { + throw new UnsupportedSchemeException($dsn, 'kaz-info-teh', $this->getSupportedSchemes()); + } + + $username = $this->getUser($dsn); + $password = $this->getPassword($dsn); + $sender = $dsn->getRequiredOption('sender'); + $host = 'default' === $dsn->getHost() ? null : $dsn->getHost(); + $port = $dsn->getPort(); + + return (new KazInfoTehTransport($username, $password, $sender, $this->client, $this->dispatcher))->setHost($host)->setPort($port); + } + + /** + * {@inheritdoc} + */ + protected function getSupportedSchemes(): array + { + return ['kaz-info-teh']; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/LICENSE b/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/LICENSE new file mode 100644 index 0000000000000..48d17c4fb34f1 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2021-2022 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/KazInfoTeh/README.md b/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/README.md new file mode 100644 index 0000000000000..a58ab95be6c20 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/README.md @@ -0,0 +1,24 @@ +KazInfoTeh Notifier +=============== + +Provides [KazInfoTeh](https://kazinfoteh.kz/) integration for Symfony Notifier. + +DSN example +----------- + +``` +KAZ_INFO_TEH_DSN=kaz-info-teh://USERNAME:PASSWORD@default?sender=FROM +``` + +where: + - `USERNAME` is your login from account + - `PASSWORD` is your password from account + - `FROM` is the alpha name + +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/KazInfoTeh/Tests/KazInfoTehTransportFactoryTest.php b/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/Tests/KazInfoTehTransportFactoryTest.php new file mode 100644 index 0000000000000..f973f5f272029 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/Tests/KazInfoTehTransportFactoryTest.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\KazInfoTeh\Tests; + +use Symfony\Component\Notifier\Bridge\KazInfoTeh\KazInfoTehTransportFactory; +use Symfony\Component\Notifier\Test\TransportFactoryTestCase; + +/** + * @author Egor Taranov + */ +final class KazInfoTehTransportFactoryTest extends TransportFactoryTestCase +{ + public function createFactory(): KazInfoTehTransportFactory + { + return new KazInfoTehTransportFactory(); + } + + public function createProvider(): iterable + { + yield [ + 'kaz-info-teh://kazinfoteh.org:9507?sender=symfony', + 'kaz-info-teh://username:password@default?sender=symfony', + ]; + + yield [ + 'kaz-info-teh://host.test?sender=Symfony', + 'kaz-info-teh://username:password@host.test?sender=Symfony', + ]; + } + + public function supportsProvider(): iterable + { + yield [true, 'kaz-info-teh://username:password@default?sender=Symfony']; + yield [false, 'somethingElse://username:password@default?sender=Symfony']; + } + + public function missingRequiredOptionProvider(): iterable + { + yield 'missing option: sender' => ['kaz-info-teh://username:password@default']; + } + + public function incompleteDsnProvider(): iterable + { + yield 'missing username' => ['kaz-info-teh://default?sender=0611223344']; + yield 'missing password' => ['kaz-info-teh://username@default?sender=0611223344']; + } + + public function unsupportedSchemeProvider(): iterable + { + yield ['somethingElse://username:password@default?sender=acme']; + yield ['somethingElse://username:password@default']; + yield ['somethingElse://default']; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/Tests/KazInfoTehTransportTest.php b/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/Tests/KazInfoTehTransportTest.php new file mode 100644 index 0000000000000..25f08cd16bf29 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/Tests/KazInfoTehTransportTest.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\KazInfoTeh\Tests; + +use Symfony\Component\HttpClient\MockHttpClient; +use Symfony\Component\HttpClient\Response\MockResponse; +use Symfony\Component\Notifier\Bridge\KazInfoTeh\KazInfoTehTransport; +use Symfony\Component\Notifier\Exception\TransportException; +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; + +/** + * @author Egor Taranov + */ +final class KazInfoTehTransportTest extends TransportTestCase +{ + public function createTransport(HttpClientInterface $client = null): TransportInterface + { + return (new KazInfoTehTransport('username', 'password', 'sender', $client ?? $this->createMock(HttpClientInterface::class)))->setHost('test.host'); + } + + /** + * {@inheritdoc} + */ + public function toStringProvider(): iterable + { + yield ['kaz-info-teh://test.host?sender=sender', $this->createTransport()]; + } + + /** + * {@inheritdoc} + */ + public function supportedMessagesProvider(): iterable + { + yield [new SmsMessage('77000000000', 'KazInfoTeh!')]; + } + + /** + * {@inheritdoc} + */ + public function unsupportedMessagesProvider(): iterable + { + yield [new SmsMessage('420000000000', 'KazInfoTeh!')]; + + yield [$this->createMock(MessageInterface::class)]; + } + + public function createClient(int $statusCode, string $content): HttpClientInterface + { + return new MockHttpClient(new MockResponse($content, ['http_code' => $statusCode])); + } + + public function responseProvider(): iterable + { + $responses = [ + ['status' => 200, 'content' => '1Status code is not valid', 'error_message' => 'Unable to send the SMS: "Status code is not valid".'], + ['status' => 200, 'content' => '{"message": Response not in XML format}', 'error_message' => 'Unable to send the SMS: "Couldn\'t read response".'], + ['status' => 200, 'content' => '1Error code is not valid', 'error_message' => 'Unable to send the SMS: "Error code is not valid".'], + ['status' => 500, 'content' => '1Something went wrong', 'error_message' => 'Unable to send the SMS: "Something went wrong".'], + ['status' => 500, 'content' => '', 'error_message' => 'Unable to send the SMS: "Couldn\'t read response".'], + ]; + + foreach ($responses as $response) { + yield [$response['status'], $response['content'], $response['error_message']]; + } + } + + /** + * @dataProvider responseProvider + */ + public function testThrowExceptionWhenMessageWasNotSent(int $statusCode, string $content, string $errorMessage) + { + $client = $this->createClient($statusCode, $content); + $transport = $this->createTransport($client); + $message = new SmsMessage('77000000000', 'Hello, bug!'); + + $this->expectException(TransportException::class); + $this->expectExceptionMessage($errorMessage); + + $transport->send($message); + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/composer.json b/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/composer.json new file mode 100644 index 0000000000000..ca169695cbc1a --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/composer.json @@ -0,0 +1,32 @@ +{ + "name": "symfony/kaz-info-teh-notifier", + "type": "symfony-bridge", + "description": "Symfony KazInfoTeh Notifier Bridge", + "keywords": ["KazInfoTeh", "notifier", "symfony", "sms"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Egor Taranov", + "email": "dev@taranovegor.com", + "homepage": "https://taranovegor.com/contribution" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.0.2", + "ext-simplexml": "*", + "symfony/http-client": "^5.4|^6.0", + "symfony/notifier": "^5.4|^6.0" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\KazInfoTeh\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/phpunit.xml.dist b/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/phpunit.xml.dist new file mode 100644 index 0000000000000..b9e76f4faffcb --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Resources + ./Tests + ./vendor + + + +