diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index a78c9cecabcf1..f15af5cbf3cd8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -140,6 +140,7 @@ use Symfony\Component\Notifier\Bridge\Slack\SlackTransportFactory; use Symfony\Component\Notifier\Bridge\Smsapi\SmsapiTransportFactory; use Symfony\Component\Notifier\Bridge\SmsBiuras\SmsBiurasTransportFactory; +use Symfony\Component\Notifier\Bridge\Smsc\SmscTransportFactory; use Symfony\Component\Notifier\Bridge\SpotHit\SpotHitTransportFactory; use Symfony\Component\Notifier\Bridge\Telegram\TelegramTransportFactory; use Symfony\Component\Notifier\Bridge\Telnyx\TelnyxTransportFactory; @@ -2449,6 +2450,7 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $ SlackTransportFactory::class => 'notifier.transport_factory.slack', SmsapiTransportFactory::class => 'notifier.transport_factory.smsapi', SmsBiurasTransportFactory::class => 'notifier.transport_factory.smsbiuras', + SmscTransportFactory::class => 'notifier.transport_factory.smsc', SpotHitTransportFactory::class => 'notifier.transport_factory.spothit', TelegramTransportFactory::class => 'notifier.transport_factory.telegram', TelnyxTransportFactory::class => 'notifier.transport_factory.telnyx', diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php index d7235c2ccfb30..473d2c4886878 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php @@ -42,6 +42,7 @@ use Symfony\Component\Notifier\Bridge\Slack\SlackTransportFactory; use Symfony\Component\Notifier\Bridge\Smsapi\SmsapiTransportFactory; use Symfony\Component\Notifier\Bridge\SmsBiuras\SmsBiurasTransportFactory; +use Symfony\Component\Notifier\Bridge\Smsc\SmscTransportFactory; use Symfony\Component\Notifier\Bridge\SpotHit\SpotHitTransportFactory; use Symfony\Component\Notifier\Bridge\Telegram\TelegramTransportFactory; use Symfony\Component\Notifier\Bridge\Telnyx\TelnyxTransportFactory; @@ -189,6 +190,10 @@ ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') + ->set('notifier.transport_factory.smsc', SmscTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + ->set('notifier.transport_factory.messagebird', MessageBirdTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') diff --git a/src/Symfony/Component/Notifier/Bridge/Smsc/.gitattributes b/src/Symfony/Component/Notifier/Bridge/Smsc/.gitattributes new file mode 100644 index 0000000000000..84c7add058fb5 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Smsc/.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/Smsc/.gitignore b/src/Symfony/Component/Notifier/Bridge/Smsc/.gitignore new file mode 100644 index 0000000000000..c49a5d8df5c65 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Smsc/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/src/Symfony/Component/Notifier/Bridge/Smsc/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/Smsc/CHANGELOG.md new file mode 100644 index 0000000000000..3a08c7ededfcd --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Smsc/CHANGELOG.md @@ -0,0 +1,7 @@ +CHANGELOG +========= + +5.4 +--- + + * Add the bridge diff --git a/src/Symfony/Component/Notifier/Bridge/Smsc/LICENSE b/src/Symfony/Component/Notifier/Bridge/Smsc/LICENSE new file mode 100644 index 0000000000000..efb17f98e7dd3 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Smsc/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/Smsc/README.md b/src/Symfony/Component/Notifier/Bridge/Smsc/README.md new file mode 100644 index 0000000000000..0d9fb0fd8990d --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Smsc/README.md @@ -0,0 +1,24 @@ +SMSC Notifier +============= + +Provides [SMSC](https://smsc.ru/) integration for Symfony Notifier. + +DSN example +----------- + +``` +SMSC_DSN=smsc://LOGIN:PASSWORD@default?from=FROM +``` + +where: + - `LOGIN` is your login + - `PASSWORD` is your API password + - `FROM` is your sender (NB: text identity, not a phone number) + +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/Smsc/SmscTransport.php b/src/Symfony/Component/Notifier/Bridge/Smsc/SmscTransport.php new file mode 100644 index 0000000000000..11a98aadfa118 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Smsc/SmscTransport.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Smsc; + +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\DecodingExceptionInterface as HttpDecodingExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\ExceptionInterface as HttpExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface as HttpTransportExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +/** + * @author Valentin Nazarov + */ +final class SmscTransport extends AbstractTransport +{ + protected const HOST = 'smsc.ru'; + + private $login; + private $password; + private $from; + + public function __construct($username, $password, $from, HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null) + { + $this->login = $username; + $this->password = $password; + $this->from = $from; + + parent::__construct($client, $dispatcher); + } + + public function __toString(): string + { + return sprintf('smsc://%s?from=%s', $this->getEndpoint(), (string) $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); + } + + $body = [ + 'login' => $this->login, + 'psw' => $this->password, + 'sender' => (string) $this->from, + 'phones' => $message->getPhone(), + 'mes' => $message->getSubject(), + 'fmt' => 3, // response as JSON + 'charset' => 'utf-8', + 'time' => '0-24', + ]; + + $endpoint = sprintf('https://%s/sys/send.php', $this->getEndpoint()); + $response = $this->client->request('POST', $endpoint, ['body' => $body]); + + try { + $result = $response->toArray(); + } catch (HttpTransportExceptionInterface $e) { + throw new TransportException('Could not reach the remote smsc.ru server.', $response, 0, $e); + } catch (HttpDecodingExceptionInterface $e) { + throw new TransportException('Could not decode the response from remote smsc.ru server.', $response, 0, $e); + } catch (HttpExceptionInterface $e) { + throw new TransportException('Unexpected response from remote smsc.ru server.', $response, 0, $e); + } + + if (\array_key_exists('error', $result)) { + throw new TransportException(sprintf('Unable to send the SMS: code = %d, message = "%s".', $result['error_code'], $result['error']), $response); + } + + $sentMessage = new SentMessage($message, (string) $this); + $sentMessage->setMessageId((string) ($result['id'] ?? '')); + + return $sentMessage; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Smsc/SmscTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Smsc/SmscTransportFactory.php new file mode 100644 index 0000000000000..55961b3334849 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Smsc/SmscTransportFactory.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\Smsc; + +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 Valentin Nazarov + */ +final class SmscTransportFactory extends AbstractTransportFactory +{ + /** + * @return SmscTransport + */ + public function create(Dsn $dsn): TransportInterface + { + $scheme = $dsn->getScheme(); + + if ('smsc' !== $scheme) { + throw new UnsupportedSchemeException($dsn, 'smsc', $this->getSupportedSchemes()); + } + + $login = $dsn->getUser(); + $password = $dsn->getPassword(); + $from = $dsn->getRequiredOption('from'); + $host = 'default' === $dsn->getHost() ? null : $dsn->getHost(); + + return (new SmscTransport($login, $password, $from, $this->client, $this->dispatcher))->setHost($host); + } + + protected function getSupportedSchemes(): array + { + return ['smsc']; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Smsc/Tests/SmscTransportFactoryTest.php b/src/Symfony/Component/Notifier/Bridge/Smsc/Tests/SmscTransportFactoryTest.php new file mode 100644 index 0000000000000..d3bf70319ae61 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Smsc/Tests/SmscTransportFactoryTest.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\Smsc\Tests; + +use Symfony\Component\Notifier\Bridge\Smsc\SmscTransportFactory; +use Symfony\Component\Notifier\Test\TransportFactoryTestCase; +use Symfony\Component\Notifier\Transport\TransportFactoryInterface; + +final class SmscTransportFactoryTest extends TransportFactoryTestCase +{ + /** + * @return SmscTransportFactory + */ + public function createFactory(): TransportFactoryInterface + { + return new SmscTransportFactory(); + } + + public function createProvider(): iterable + { + yield [ + 'smsc://host.test?from=MyApp', + 'smsc://login:password@host.test?from=MyApp', + ]; + } + + public function supportsProvider(): iterable + { + yield [true, 'smsc://login:password@default?from=MyApp']; + yield [false, 'somethingElse://login:password@default?from=MyApp']; + } + + public function missingRequiredOptionProvider(): iterable + { + yield 'missing option: from' => ['smsc://login:password@default']; + } + + public function unsupportedSchemeProvider(): iterable + { + yield ['somethingElse://login:password@default?from=MyApp']; + yield ['somethingElse://login:password@default']; // missing "from" option + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Smsc/Tests/SmscTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Smsc/Tests/SmscTransportTest.php new file mode 100644 index 0000000000000..5a849a646e3b1 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Smsc/Tests/SmscTransportTest.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Smsc\Tests; + +use Symfony\Component\Notifier\Bridge\Smsc\SmscTransport; +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 SmscTransportTest extends TransportTestCase +{ + public function createTransport(HttpClientInterface $client = null): TransportInterface + { + return new SmscTransport('login', 'password', 'MyApp', $client ?? $this->createMock(HttpClientInterface::class)); + } + + public function toStringProvider(): iterable + { + yield ['smsc://smsc.ru?from=MyApp', $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/Smsc/composer.json b/src/Symfony/Component/Notifier/Bridge/Smsc/composer.json new file mode 100644 index 0000000000000..22964a0dbdd94 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Smsc/composer.json @@ -0,0 +1,30 @@ +{ + "name": "symfony/smsc-notifier", + "type": "symfony-bridge", + "description": "Symfony SMSC Notifier Bridge", + "keywords": ["sms", "smsc", "notifier"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.2.5", + "symfony/http-client": "^4.4|^5.2|^6.0", + "symfony/notifier": "^5.3|^6.0" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\Smsc\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/src/Symfony/Component/Notifier/Bridge/Smsc/phpunit.xml.dist b/src/Symfony/Component/Notifier/Bridge/Smsc/phpunit.xml.dist new file mode 100644 index 0000000000000..93485fd80b0eb --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Smsc/phpunit.xml.dist @@ -0,0 +1,30 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Tests + ./vendor + + + + diff --git a/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php b/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php index 73786fa71ff33..7f377dfa47e7c 100644 --- a/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php +++ b/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php @@ -144,6 +144,10 @@ class UnsupportedSchemeException extends LogicException 'class' => Bridge\SmsBiuras\SmsBiurasTransportFactory::class, 'package' => 'symfony/sms-biuras-notifier', ], + 'smsc' => [ + 'class' => Bridge\Smsc\SmscTransportFactory::class, + 'package' => 'symfony/smsc-notifier', + ], 'spothit' => [ 'class' => Bridge\SpotHit\SpotHitTransportFactory::class, 'package' => 'symfony/spot-hit-notifier', diff --git a/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php b/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php index e279156a05aae..7b21170ad72cb 100644 --- a/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php +++ b/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php @@ -44,6 +44,7 @@ use Symfony\Component\Notifier\Bridge\Slack\SlackTransportFactory; use Symfony\Component\Notifier\Bridge\Smsapi\SmsapiTransportFactory; use Symfony\Component\Notifier\Bridge\SmsBiuras\SmsBiurasTransportFactory; +use Symfony\Component\Notifier\Bridge\Smsc\SmscTransportFactory; use Symfony\Component\Notifier\Bridge\SpotHit\SpotHitTransportFactory; use Symfony\Component\Notifier\Bridge\Telegram\TelegramTransportFactory; use Symfony\Component\Notifier\Bridge\Telnyx\TelnyxTransportFactory; @@ -92,6 +93,7 @@ public static function setUpBeforeClass(): void SlackTransportFactory::class => false, SmsapiTransportFactory::class => false, SmsBiurasTransportFactory::class => false, + SmscTransportFactory::class => false, SpotHitTransportFactory::class => false, TelegramTransportFactory::class => false, TelnyxTransportFactory::class => false, @@ -146,6 +148,7 @@ public function messageWhereSchemeIsPartOfSchemeToPackageMapProvider(): \Generat yield ['slack', 'symfony/slack-notifier']; yield ['smsapi', 'symfony/smsapi-notifier']; yield ['smsbiuras', 'symfony/sms-biuras-notifier']; + yield ['smsc', 'symfony/smsc-notifier']; yield ['spothit', 'symfony/spot-hit-notifier']; yield ['telegram', 'symfony/telegram-notifier']; yield ['telnyx', 'symfony/telnyx-notifier']; diff --git a/src/Symfony/Component/Notifier/Transport.php b/src/Symfony/Component/Notifier/Transport.php index bf3c26c3c8513..be1be9c745980 100644 --- a/src/Symfony/Component/Notifier/Transport.php +++ b/src/Symfony/Component/Notifier/Transport.php @@ -37,6 +37,7 @@ use Symfony\Component\Notifier\Bridge\Slack\SlackTransportFactory; use Symfony\Component\Notifier\Bridge\Smsapi\SmsapiTransportFactory; use Symfony\Component\Notifier\Bridge\SmsBiuras\SmsBiurasTransportFactory; +use Symfony\Component\Notifier\Bridge\Smsc\SmscTransportFactory; use Symfony\Component\Notifier\Bridge\Telegram\TelegramTransportFactory; use Symfony\Component\Notifier\Bridge\Telnyx\TelnyxTransportFactory; use Symfony\Component\Notifier\Bridge\Twilio\TwilioTransportFactory; @@ -84,6 +85,7 @@ class Transport SlackTransportFactory::class, SmsapiTransportFactory::class, SmsBiurasTransportFactory::class, + SmscTransportFactory::class, TelegramTransportFactory::class, TelnyxTransportFactory::class, TwilioTransportFactory::class,