diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 3afcf17d8c..5f884cf17c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -101,6 +101,7 @@ use Symfony\Component\Messenger\Transport\TransportInterface; use Symfony\Component\Mime\Header\Headers; use Symfony\Component\Mime\MimeTypeGuesserInterface; use Symfony\Component\Mime\MimeTypes; +use Symfony\Component\Notifier\Bridge\AllMySms\AllMySmsTransportFactory; use Symfony\Component\Notifier\Bridge\Discord\DiscordTransportFactory; use Symfony\Component\Notifier\Bridge\Esendex\EsendexTransportFactory; use Symfony\Component\Notifier\Bridge\Firebase\FirebaseTransportFactory; @@ -2225,6 +2226,7 @@ class FrameworkExtension extends Extension RocketChatTransportFactory::class => 'notifier.transport_factory.rocketchat', InfobipTransportFactory::class => 'notifier.transport_factory.infobip', TwilioTransportFactory::class => 'notifier.transport_factory.twilio', + AllMySmsTransportFactory::class => 'notifier.transport_factory.allmysms', FirebaseTransportFactory::class => 'notifier.transport_factory.firebase', FreeMobileTransportFactory::class => 'notifier.transport_factory.freemobile', OvhCloudTransportFactory::class => 'notifier.transport_factory.ovhcloud', diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php index 88952b80ae..ea0bcef5c3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php @@ -11,6 +11,7 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; +use Symfony\Component\Notifier\Bridge\AllMySms\AllMySmsTransportFactory; use Symfony\Component\Notifier\Bridge\Discord\DiscordTransportFactory; use Symfony\Component\Notifier\Bridge\Esendex\EsendexTransportFactory; use Symfony\Component\Notifier\Bridge\Firebase\FirebaseTransportFactory; @@ -72,6 +73,10 @@ return static function (ContainerConfigurator $container) { ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') + ->set('notifier.transport_factory.allmysms', AllMySmsTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') + ->set('notifier.transport_factory.firebase', FirebaseTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('chatter.transport_factory') diff --git a/src/Symfony/Component/Notifier/Bridge/AllMySms/.gitattributes b/src/Symfony/Component/Notifier/Bridge/AllMySms/.gitattributes new file mode 100644 index 0000000000..84c7add058 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/AllMySms/.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/AllMySms/AllMySmsTransport.php b/src/Symfony/Component/Notifier/Bridge/AllMySms/AllMySmsTransport.php new file mode 100644 index 0000000000..2da6f1298d --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/AllMySms/AllMySmsTransport.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\AllMySMs; + +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 Quentin Dequippe + */ +final class AllMySmsTransport extends AbstractTransport +{ + protected const HOST = 'api.allmysms.com'; + + private $login; + private $apiKey; + private $from; + + public function __construct(string $login, string $apiKey, string $from = null, HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null) + { + $this->login = $login; + $this->apiKey = $apiKey; + $this->from = $from; + + parent::__construct($client, $dispatcher); + } + + public function __toString(): string + { + if (null !== $this->from) { + return sprintf('allmysms://%s?from=%s', $this->getEndpoint(), $this->from); + } + + return sprintf('allmysms://%s', $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/sms/send/', $this->getEndpoint()); + $response = $this->client->request('POST', $endpoint, [ + 'auth_basic' => $this->login.':'.$this->apiKey, + 'body' => [ + 'from' => $this->from, + 'to' => $message->getPhone(), + 'text' => $message->getSubject(), + ], + ]); + + if (201 !== $response->getStatusCode()) { + $error = $response->toArray(false); + + throw new TransportException(sprintf('Unable to send the SMS: "%s" (%s).', $error['description'], $error['code']), $response); + } + + $success = $response->toArray(false); + + if (false === isset($success['smsId'])) { + throw new TransportException(sprintf('Unable to send the SMS: "%s" (%s).', $success['description'], $success['code']), $response); + } + + $sentMessage = new SentMessage($message, (string) $this); + $sentMessage->setMessageId($success['smsId']); + + return $sentMessage; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/AllMySms/AllMySmsTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/AllMySms/AllMySmsTransportFactory.php new file mode 100644 index 0000000000..a9d06ff8a9 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/AllMySms/AllMySmsTransportFactory.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\AllMySms; + +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 Quentin Dequippe + */ +final class AllMySmsTransportFactory extends AbstractTransportFactory +{ + /** + * @return AllMySmsTransport + */ + public function create(Dsn $dsn): TransportInterface + { + $scheme = $dsn->getScheme(); + + if ('allmysms' !== $scheme) { + throw new UnsupportedSchemeException($dsn, 'allmysms', $this->getSupportedSchemes()); + } + + $login = $this->getUser($dsn); + $apiKey = $this->getPassword($dsn); + $from = $dsn->getOption('from'); + $host = 'default' === $dsn->getHost() ? null : $dsn->getHost(); + $port = $dsn->getPort(); + + return (new AllMySmsTransport($login, $apiKey, $from, $this->client, $this->dispatcher))->setHost($host)->setPort($port); + } + + protected function getSupportedSchemes(): array + { + return ['allmysms']; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/AllMySms/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/AllMySms/CHANGELOG.md new file mode 100644 index 0000000000..1f2b652ac2 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/AllMySms/CHANGELOG.md @@ -0,0 +1,7 @@ +CHANGELOG +========= + +5.3 +--- + + * Add the bridge diff --git a/src/Symfony/Component/Notifier/Bridge/AllMySms/LICENSE b/src/Symfony/Component/Notifier/Bridge/AllMySms/LICENSE new file mode 100644 index 0000000000..efb17f98e7 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/AllMySms/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/AllMySms/README.md b/src/Symfony/Component/Notifier/Bridge/AllMySms/README.md new file mode 100644 index 0000000000..0a3beb9313 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/AllMySms/README.md @@ -0,0 +1,24 @@ +AllMySms Notifier +================= + +Provides [AllMySms](https://www.allmysms.com/) integration for Symfony Notifier. + +DSN example +----------- + +``` +ALLMYSMS_DSN=allmysms://LOGIN:APIKEY@default?from=FROM +``` + +where: +- `LOGIN` is your user ID +- `APIKEY` is your AllMySms API key +- `FROM` is your sender (optional, default: 36180) + +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/AllMySms/Tests/AllMySmsTransportFactoryTest.php b/src/Symfony/Component/Notifier/Bridge/AllMySms/Tests/AllMySmsTransportFactoryTest.php new file mode 100644 index 0000000000..5f8d5a7b07 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/AllMySms/Tests/AllMySmsTransportFactoryTest.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\AllMySms\Tests; + +use Symfony\Component\Notifier\Bridge\AllMySms\AllMySmsTransportFactory; +use Symfony\Component\Notifier\Tests\TransportFactoryTestCase; +use Symfony\Component\Notifier\Transport\TransportFactoryInterface; + +final class AllMySmsTransportFactoryTest extends TransportFactoryTestCase +{ + /** + * @return AllMySmsTransportFactory + */ + public function createFactory(): TransportFactoryInterface + { + return new AllMySmsTransportFactory(); + } + + public function createProvider(): iterable + { + yield [ + 'allmysms://host.test', + 'allmysms://login:apiKey@host.test', + ]; + + yield [ + 'allmysms://host.test?from=TEST', + 'allmysms://login:apiKey@host.test?from=TEST', + ]; + } + + public function supportsProvider(): iterable + { + yield [true, 'allmysms://login:apiKey@default']; + yield [false, 'somethingElse://login:apiKey@default']; + } + + public function unsupportedSchemeProvider(): iterable + { + yield ['somethingElse://login:apiKey@default']; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/AllMySms/Tests/AllMySmsTransportTest.php b/src/Symfony/Component/Notifier/Bridge/AllMySms/Tests/AllMySmsTransportTest.php new file mode 100644 index 0000000000..8e4873bf24 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/AllMySms/Tests/AllMySmsTransportTest.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\AllMySms\Tests; + +use Symfony\Component\Notifier\Bridge\AllMySms\AllMySmsTransport; +use Symfony\Component\Notifier\Message\ChatMessage; +use Symfony\Component\Notifier\Message\MessageInterface; +use Symfony\Component\Notifier\Message\SmsMessage; +use Symfony\Component\Notifier\Tests\TransportTestCase; +use Symfony\Component\Notifier\Transport\TransportInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +final class AllMySmsTransportTest extends TransportTestCase +{ + /** + * @return AllMySmsTransport + */ + public function createTransport(?HttpClientInterface $client = null, string $from = null): TransportInterface + { + return new AllMySmsTransport('login', 'apiKey', $from, $client ?: $this->createMock(HttpClientInterface::class)); + } + + public function toStringProvider(): iterable + { + yield ['allmysms://api.allmysms.com', $this->createTransport()]; + yield ['allmysms://api.allmysms.com?from=TEST', $this->createTransport(null, 'TEST')]; + } + + 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/AllMySms/composer.json b/src/Symfony/Component/Notifier/Bridge/AllMySms/composer.json new file mode 100644 index 0000000000..79704353ac --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/AllMySms/composer.json @@ -0,0 +1,30 @@ +{ + "name": "symfony/allmysms-notifier", + "type": "symfony-bridge", + "description": "Symfony AllMySms Notifier Bridge", + "keywords": ["sms", "allMySms", "notifier"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Quentin Dequippe", + "email": "quentin@dequippe.tech" + }, + { + "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\\AllMySms\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/src/Symfony/Component/Notifier/Bridge/AllMySms/phpunit.xml.dist b/src/Symfony/Component/Notifier/Bridge/AllMySms/phpunit.xml.dist new file mode 100644 index 0000000000..91d190f562 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/AllMySms/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 2ac36c47d1..3db7b02e4c 100644 --- a/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php +++ b/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php @@ -54,6 +54,10 @@ class UnsupportedSchemeException extends LogicException 'class' => Bridge\Twilio\TwilioTransportFactory::class, 'package' => 'symfony/twilio-notifier', ], + 'allmysms' => [ + 'class' => Bridge\AllMySms\AllMySmsTransportFactory::class, + 'package' => 'symfony/allmysms-notifier', + ], 'infobip' => [ 'class' => Bridge\Infobip\InfobipTransportFactory::class, 'package' => 'symfony/infobip-notifier', diff --git a/src/Symfony/Component/Notifier/Transport.php b/src/Symfony/Component/Notifier/Transport.php index f370af913b..38dd2e225c 100644 --- a/src/Symfony/Component/Notifier/Transport.php +++ b/src/Symfony/Component/Notifier/Transport.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Notifier; +use Symfony\Component\Notifier\Bridge\AllMySms\AllMySmsTransportFactory; use Symfony\Component\Notifier\Bridge\Discord\DiscordTransportFactory; use Symfony\Component\Notifier\Bridge\Esendex\EsendexTransportFactory; use Symfony\Component\Notifier\Bridge\Firebase\FirebaseTransportFactory; @@ -55,6 +56,7 @@ class Transport IqsmsTransportFactory::class, RocketChatTransportFactory::class, TwilioTransportFactory::class, + AllMySmsTransportFactory::class, InfobipTransportFactory::class, OvhCloudTransportFactory::class, FirebaseTransportFactory::class,