diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index fa606794e8..6c30dd8ff1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -104,6 +104,7 @@ use Symfony\Component\Notifier\Bridge\Sinch\SinchTransportFactory; use Symfony\Component\Notifier\Bridge\Slack\SlackTransportFactory; use Symfony\Component\Notifier\Bridge\Telegram\TelegramTransportFactory; use Symfony\Component\Notifier\Bridge\Twilio\TwilioTransportFactory; +use Symfony\Component\Notifier\Bridge\Zulip\ZulipTransportFactory; use Symfony\Component\Notifier\Notifier; use Symfony\Component\Notifier\Recipient\AdminRecipient; use Symfony\Component\PropertyAccess\PropertyAccessor; @@ -2082,6 +2083,7 @@ class FrameworkExtension extends Extension FreeMobileTransportFactory::class => 'notifier.transport_factory.freemobile', OvhCloudTransportFactory::class => 'notifier.transport_factory.ovhcloud', SinchTransportFactory::class => 'notifier.transport_factory.sinch', + ZulipTransportFactory::class => 'notifier.transport_factory.zulip', ]; 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 424c37f53d..20a72019da 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php @@ -21,6 +21,7 @@ use Symfony\Component\Notifier\Bridge\Sinch\SinchTransportFactory; use Symfony\Component\Notifier\Bridge\Slack\SlackTransportFactory; use Symfony\Component\Notifier\Bridge\Telegram\TelegramTransportFactory; use Symfony\Component\Notifier\Bridge\Twilio\TwilioTransportFactory; +use Symfony\Component\Notifier\Bridge\Zulip\ZulipTransportFactory; use Symfony\Component\Notifier\Transport\AbstractTransportFactory; use Symfony\Component\Notifier\Transport\NullTransportFactory; @@ -70,6 +71,10 @@ return static function (ContainerConfigurator $container) { ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') + ->set('notifier.transport_factory.zulip', ZulipTransportFactory::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/Zulip/.gitattributes b/src/Symfony/Component/Notifier/Bridge/Zulip/.gitattributes new file mode 100644 index 0000000000..aa02dc6518 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Zulip/.gitattributes @@ -0,0 +1,2 @@ +/Tests export-ignore +/phpunit.xml.dist export-ignore diff --git a/src/Symfony/Component/Notifier/Bridge/Zulip/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/Zulip/CHANGELOG.md new file mode 100644 index 0000000000..0d994e934e --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Zulip/CHANGELOG.md @@ -0,0 +1,7 @@ +CHANGELOG +========= + +5.2.0 +----- + + * Added the bridge diff --git a/src/Symfony/Component/Notifier/Bridge/Zulip/LICENSE b/src/Symfony/Component/Notifier/Bridge/Zulip/LICENSE new file mode 100644 index 0000000000..5593b1d84f --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Zulip/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020 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/Zulip/README.md b/src/Symfony/Component/Notifier/Bridge/Zulip/README.md new file mode 100644 index 0000000000..b934146398 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Zulip/README.md @@ -0,0 +1,12 @@ +Zulip Notifier +============== + +Provides Zulip integration for Symfony Notifier. + +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/Zulip/ZulipOptions.php b/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipOptions.php new file mode 100644 index 0000000000..963131d382 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipOptions.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Zulip; + +use Symfony\Component\Notifier\Message\MessageOptionsInterface; + +/** + * @author Mohammad Emran Hasan + * + * @experimental in 5.2 + */ +final class ZulipOptions implements MessageOptionsInterface +{ + /** @var string|null */ + private $topic; + + /** @var string|null */ + private $recipient; + + public function __construct(?string $topic = null, ?string $recipient = null) + { + $this->topic = $topic; + $this->recipient = $recipient; + } + + public function toArray(): array + { + return [ + 'topic' => $this->topic, + 'recipient' => $this->recipient, + ]; + } + + public function getRecipientId(): ?string + { + return $this->recipient; + } + + public function topic(string $topic): self + { + $this->topic = $topic; + + return $this; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipTransport.php b/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipTransport.php new file mode 100644 index 0000000000..b12581abe7 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipTransport.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Zulip; + +use Symfony\Component\Notifier\Exception\LogicException; +use Symfony\Component\Notifier\Exception\TransportException; +use Symfony\Component\Notifier\Message\ChatMessage; +use Symfony\Component\Notifier\Message\MessageInterface; +use Symfony\Component\Notifier\Message\SentMessage; +use Symfony\Component\Notifier\Transport\AbstractTransport; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +/** + * @author Mohammad Emran Hasan + * + * @experimental in 5.2 + */ +class ZulipTransport extends AbstractTransport +{ + private $email; + private $token; + private $channel; + + public function __construct(string $email, string $token, string $channel, HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null) + { + $this->email = $email; + $this->token = $token; + $this->channel = $channel; + + parent::__construct($client, $dispatcher); + } + + public function __toString(): string + { + return sprintf('zulip://%s?channel=%s', $this->getEndpoint(), $this->channel); + } + + public function supports(MessageInterface $message): bool + { + return $message instanceof ChatMessage && (null === $message->getOptions() || $message->getOptions() instanceof ZulipOptions); + } + + /** + * @see https://zulipchat.com/api/send-message + */ + protected function doSend(MessageInterface $message): SentMessage + { + if (!$message instanceof ChatMessage) { + throw new LogicException(sprintf('The "%s" transport only supports instances of "%s" (instance of "%s" given).', __CLASS__, ChatMessage::class, get_debug_type($message))); + } + + if (null !== $message->getOptions() && !($message->getOptions() instanceof ZulipOptions)) { + throw new LogicException(sprintf('The "%s" transport only supports instances of "%s" for options.', __CLASS__, ZulipOptions::class)); + } + + $endpoint = sprintf('https://%s/api/v1/messages', $this->getEndpoint()); + + $options = ($opts = $message->getOptions()) ? $opts->toArray() : []; + $options['content'] = $message->getSubject(); + + if (null === $message->getRecipientId() && empty($options['topic'])) { + throw new LogicException(sprintf('The "%s" transport requires a topic when posting to streams.', __CLASS__)); + } + + if (null === $message->getRecipientId()) { + $options['type'] = 'stream'; + $options['to'] = $this->channel; + } else { + $options['type'] = 'private'; + $options['to'] = $message->getRecipientId(); + } + + $response = $this->client->request('POST', $endpoint, [ + 'auth_basic' => $this->email.':'.$this->token, + 'body' => $options, + ]); + + if (200 !== $response->getStatusCode()) { + $result = $response->toArray(false); + + throw new TransportException(sprintf('Unable to post the Zulip message: "%s" (%s).', $result['msg'], $result['code']), $response); + } + + $success = $response->toArray(false); + + $message = new SentMessage($message, (string) $this); + $message->setMessageId($success['id']); + + return $message; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipTransportFactory.php new file mode 100644 index 0000000000..a624d3f2f7 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Zulip/ZulipTransportFactory.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\Zulip; + +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 Mohammad Emran Hasan + * + * @experimental in 5.2 + */ +class ZulipTransportFactory extends AbstractTransportFactory +{ + /** + * {@inheritdoc} + */ + public function create(Dsn $dsn): TransportInterface + { + $scheme = $dsn->getScheme(); + $email = $this->getUser($dsn); + $token = $this->getPassword($dsn); + $channel = $dsn->getOption('channel'); + $host = $dsn->getHost(); + $port = $dsn->getPort(); + + if ('zulip' === $scheme) { + return (new ZulipTransport($email, $token, $channel, $this->client, $this->dispatcher))->setHost($host)->setPort($port); + } + + throw new UnsupportedSchemeException($dsn, 'zulip', $this->getSupportedSchemes()); + } + + /** + * {@inheritdoc} + */ + protected function getSupportedSchemes(): array + { + return ['zulip']; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Zulip/composer.json b/src/Symfony/Component/Notifier/Bridge/Zulip/composer.json new file mode 100644 index 0000000000..f658611d85 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Zulip/composer.json @@ -0,0 +1,35 @@ +{ + "name": "symfony/zulip-notifier", + "type": "symfony-bridge", + "description": "Symfony Zulip Notifier Bridge", + "keywords": ["zulip", "notifier"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Mohammad Emran Hasan", + "email": "phpfour@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.0" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\Zulip\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "5.2-dev" + } + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Zulip/phpunit.xml.dist b/src/Symfony/Component/Notifier/Bridge/Zulip/phpunit.xml.dist new file mode 100644 index 0000000000..88bfdb7020 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Zulip/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Resources + ./Tests + ./vendor + + + + diff --git a/src/Symfony/Component/Notifier/CHANGELOG.md b/src/Symfony/Component/Notifier/CHANGELOG.md index c395ebf506..82903cafc8 100644 --- a/src/Symfony/Component/Notifier/CHANGELOG.md +++ b/src/Symfony/Component/Notifier/CHANGELOG.md @@ -5,11 +5,12 @@ CHANGELOG ----- * [BC BREAK] The `TransportInterface::send()` and `AbstractTransport::doSend()` methods changed to return a `?SentMessage` instance instead of `void`. + * Added the Zulip notifier bridge 5.1.0 ----- - * Added the Mattermost notifier bridge +* Added the Mattermost notifier bridge * [BC BREAK] The `ChatMessage::fromNotification()` method's `$recipient` and `$transport` arguments were removed. * [BC BREAK] The `EmailMessage::fromNotification()` and `SmsMessage::fromNotification()` diff --git a/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php b/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php index 70854ef099..5836b6607f 100644 --- a/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php +++ b/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php @@ -62,6 +62,10 @@ class UnsupportedSchemeException extends LogicException 'class' => Bridge\Sinch\SinchTransportFactory::class, 'package' => 'symfony/sinch-notifier', ], + 'zulip' => [ + 'class' => Bridge\Zulip\ZulipTransportFactory::class, + 'package' => 'symfony/zulip-notifier', + ], ]; /** diff --git a/src/Symfony/Component/Notifier/Transport.php b/src/Symfony/Component/Notifier/Transport.php index 1e7d8dcd9a..a68234f8d4 100644 --- a/src/Symfony/Component/Notifier/Transport.php +++ b/src/Symfony/Component/Notifier/Transport.php @@ -21,6 +21,7 @@ use Symfony\Component\Notifier\Bridge\Sinch\SinchTransportFactory; use Symfony\Component\Notifier\Bridge\Slack\SlackTransportFactory; use Symfony\Component\Notifier\Bridge\Telegram\TelegramTransportFactory; use Symfony\Component\Notifier\Bridge\Twilio\TwilioTransportFactory; +use Symfony\Component\Notifier\Bridge\Zulip\ZulipTransportFactory; use Symfony\Component\Notifier\Exception\UnsupportedSchemeException; use Symfony\Component\Notifier\Transport\Dsn; use Symfony\Component\Notifier\Transport\FailoverTransport; @@ -50,6 +51,7 @@ class Transport FirebaseTransportFactory::class, SinchTransportFactory::class, FreeMobileTransportFactory::class, + ZulipTransportFactory::class, ]; private $factories;