From 39070ec027e5791b5847315facd29e31ede42344 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 | 3 + .../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 | 119 ++++++++++++++++++ .../KazInfoTeh/KazInfoTehTransportFactory.php | 60 +++++++++ .../Notifier/Bridge/KazInfoTeh/LICENSE | 19 +++ .../Notifier/Bridge/KazInfoTeh/README.md | 24 ++++ .../Tests/KazInfoTehTransportFactoryTest.php | 67 ++++++++++ .../Tests/KazInfoTehTransportTest.php | 96 ++++++++++++++ .../Notifier/Bridge/KazInfoTeh/composer.json | 31 +++++ .../Bridge/KazInfoTeh/phpunit.xml.dist | 31 +++++ 13 files changed, 469 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 84299bd0ffc09..0775d095fee93 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -17,6 +17,7 @@ use phpDocumentor\Reflection\DocBlockFactoryInterface; use Psr\Cache\CacheItemPoolInterface; use Psr\Container\ContainerInterface as PsrContainerInterface; +use Symfony\Component\Notifier\Bridge\KazInfoTeh\KazInfoTehTransportFactory; use Psr\EventDispatcher\EventDispatcherInterface as PsrEventDispatcherInterface; use Psr\Http\Client\ClientInterface; use Psr\Log\LoggerAwareInterface; @@ -2469,6 +2470,7 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $ TwilioTransportFactory::class => 'notifier.transport_factory.twilio', YunpianTransportFactory::class => 'notifier.transport_factory.yunpian', ZulipTransportFactory::class => 'notifier.transport_factory.zulip', + KazInfoTehTransportFactory::class => 'notifier.transport_factory.kazinfoteh', ]; $parentPackages = ['symfony/framework-bundle', 'symfony/notifier']; @@ -2490,6 +2492,7 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $ case 'smsbiuras': $package = 'sms-biuras'; break; case 'spothit': $package = 'spot-hit'; break; case 'turbosms': $package = 'turbo-sms'; break; + case 'kazinfoteh': $package = 'kazinfoteh'; break; } if (!ContainerBuilder::willBeAvailable(sprintf('symfony/%s-notifier', $package), $class, $parentPackages)) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php index 567b06402e6d4..3608f7b5b045d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php @@ -30,6 +30,7 @@ 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\KazInfoTeh\KazInfoTehTransportFactory; use Symfony\Component\Notifier\Bridge\MessageBird\MessageBirdTransportFactory; use Symfony\Component\Notifier\Bridge\MessageMedia\MessageMediaTransportFactory; use Symfony\Component\Notifier\Bridge\MicrosoftTeams\MicrosoftTeamsTransportFactory; @@ -225,5 +226,9 @@ ->set('notifier.transport_factory.turbosms', TurboSmsTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.kazinfoteh', 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..37a5cc0d388d4 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/CHANGELOG.md @@ -0,0 +1,7 @@ +CHANGELOG +========= + +5.4 +--- + +* Added 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..ef19bb3a283c7 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/KazInfoTehTransport.php @@ -0,0 +1,119 @@ + + * + * 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\Message\SmsMessage; +use Symfony\Component\Notifier\Message\SentMessage; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Component\Notifier\Message\MessageInterface; +use Symfony\Component\Notifier\Transport\AbstractTransport; +use Symfony\Component\Notifier\Exception\TransportException; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; +use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; + +/** + * @author Egor Taranov + */ +class KazInfoTehTransport extends AbstractTransport +{ + protected const HOST = 'kazinfoteh.org'; + + /** + * @var string + */ + private $username; + + /** + * @var string + */ + private $password; + + /** + * @var string + */ + private $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( + 'kazinfoteh://%s?sender=%s', + $this->getEndpoint(), + $this->sender + ); + } + + public function supports(MessageInterface $message): bool + { + return $message instanceof SmsMessage + && 0 === strpos($message->getPhone(), '77') + && 11 === strlen($message->getPhone()) + ; + } + + protected function doSend(MessageInterface $message): SentMessage + { + if (!$this->supports($message)) { + throw new UnsupportedMessageTypeException(__CLASS__, SmsMessage::class, $message); + } + + $endpoint = sprintf('https://%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 TurboSMS server.', $response, 0, $e); + } + + $content = $response->getContent(false); + if (200 !== $statusCode || false === strpos($content, '0')) { + // There's any text => [0 => There's any text] + preg_match('#(.*)<\/statusmessage>#m', $content, $matches); + + throw new TransportException(sprintf('Unable to send the SMS: "%s".', $matches[1] ?? 'unknown 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..a6084320c08e7 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/KazInfoTehTransportFactory.php @@ -0,0 +1,60 @@ + + * + * 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\Transport\Dsn; +use Symfony\Component\Notifier\Transport\TransportInterface; +use Symfony\Component\Notifier\Transport\AbstractTransportFactory; +use Symfony\Component\Notifier\Exception\UnsupportedSchemeException; + +/** + * @author Egor Taranov + */ +final class KazInfoTehTransportFactory extends AbstractTransportFactory +{ + /** + * @inheritdoc + */ + public function create(Dsn $dsn): TransportInterface + { + $scheme = $dsn->getScheme(); + + if ('kazinfoteh' !== $scheme) { + throw new UnsupportedSchemeException($dsn, 'kazinfoteh', $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 ['kazinfoteh']; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/LICENSE b/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/LICENSE new file mode 100644 index 0000000000000..efb17f98e7dd3 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/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/KazInfoTeh/README.md b/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/README.md new file mode 100644 index 0000000000000..879a6c26018b1 --- /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 +----------- + +``` +KAZINFOTEH_DSN=kazinfoteh://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..b60effdb456b8 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/Tests/KazInfoTehTransportFactoryTest.php @@ -0,0 +1,67 @@ + + * + * 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\Test\TransportFactoryTestCase; +use Symfony\Component\Notifier\Transport\TransportFactoryInterface; +use Symfony\Component\Notifier\Bridge\KazInfoTeh\KazInfoTehTransportFactory; + +/** + * @author Egor Taranov + */ +final class KazInfoTehTransportFactoryTest extends TransportFactoryTestCase +{ + /** + * @return KazInfoTehTransportFactory + */ + public function createFactory(): TransportFactoryInterface + { + return new KazInfoTehTransportFactory(); + } + + public function createProvider(): iterable + { + yield [ + 'kazinfoteh://kazinfoteh.org:9507?sender=symfony', + 'kazinfoteh://username:password@default?sender=symfony', + ]; + + yield [ + 'kazinfoteh://host.test?sender=Symfony', + 'kazinfoteh://username:password@host.test?sender=Symfony', + ]; + } + + public function supportsProvider(): iterable + { + yield [true, 'kazinfoteh://username:password@default?from=Symfony']; + yield [false, 'somethingElse://username:password@default?from=Symfony']; + } + + public function missingRequiredOptionProvider(): iterable + { + yield 'missing option: sender' => ['kazinfoteh://username:password@default']; + } + + public function incompleteDsnProvider(): iterable + { + yield 'missing username' => ['kazinfoteh://default?sender=0611223344']; + yield 'missing password' => ['kazinfoteh://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..5fe9a7c5c6620 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/Tests/KazInfoTehTransportTest.php @@ -0,0 +1,96 @@ + + * + * 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\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; +use Symfony\Component\Notifier\Bridge\KazInfoTeh\KazInfoTehTransport; + +/** + * @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 ['kazinfoteh://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' => 500, 'content' => '', 'error_message' => 'Unable to send the SMS: "unknown error".'], + ]; + + 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..69bc238b42821 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/composer.json @@ -0,0 +1,31 @@ +{ + "name": "symfony/kazinfoteh-notifier", + "type": "symfony-bridge", + "description": "Symfony KazInfoTeh Notifier Bridge", + "keywords": ["KazInfoTeh", "notifier"], + "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": ">=7.2.5", + "symfony/http-client": "^4.3|^5.0", + "symfony/notifier": "^5.3" + }, + "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..f8f4e48b5e44a --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/KazInfoTeh/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Resources + ./Tests + ./vendor + + + +