From bcd4677a5717d94b771f8a082387155e7d8b92ce Mon Sep 17 00:00:00 2001 From: Ahmed Ghanem Date: Mon, 8 Jul 2024 01:50:11 +0300 Subject: [PATCH] [Notifier] Support for desktop notifications via `jolicode/JoliNotif` --- composer.json | 1 + .../FrameworkExtension.php | 2 + .../Resources/config/notifier.php | 10 ++ .../Resources/config/notifier_transports.php | 1 + .../Notifier/Bridge/JoliNotif/.gitattributes | 3 + .../Notifier/Bridge/JoliNotif/.gitignore | 3 + .../Notifier/Bridge/JoliNotif/CHANGELOG.md | 7 ++ .../Bridge/JoliNotif/JoliNotifOptions.php | 83 ++++++++++++ .../Bridge/JoliNotif/JoliNotifTransport.php | 85 +++++++++++++ .../JoliNotif/JoliNotifTransportFactory.php | 44 +++++++ .../Notifier/Bridge/JoliNotif/LICENSE | 19 +++ .../Notifier/Bridge/JoliNotif/README.md | 20 +++ .../JoliNotif/Tests/JoliNotifOptionsTest.php | 48 +++++++ .../Tests/JoliNotifTransportFactoryTest.php | 45 +++++++ .../Tests/JoliNotifTransportTest.php | 52 ++++++++ .../Notifier/Bridge/JoliNotif/composer.json | 38 ++++++ .../Bridge/JoliNotif/phpunit.xml.dist | 31 +++++ src/Symfony/Component/Notifier/CHANGELOG.md | 5 + .../Notifier/Channel/DesktopChannel.php | 47 +++++++ .../Exception/UnsupportedSchemeException.php | 4 + .../Notifier/Message/DesktopMessage.php | 119 ++++++++++++++++++ .../DesktopNotificationInterface.php | 23 ++++ .../Tests/Message/DesktopMessageTest.php | 67 ++++++++++ src/Symfony/Component/Notifier/Transport.php | 1 + 24 files changed, 758 insertions(+) create mode 100644 src/Symfony/Component/Notifier/Bridge/JoliNotif/.gitattributes create mode 100644 src/Symfony/Component/Notifier/Bridge/JoliNotif/.gitignore create mode 100644 src/Symfony/Component/Notifier/Bridge/JoliNotif/CHANGELOG.md create mode 100644 src/Symfony/Component/Notifier/Bridge/JoliNotif/JoliNotifOptions.php create mode 100644 src/Symfony/Component/Notifier/Bridge/JoliNotif/JoliNotifTransport.php create mode 100644 src/Symfony/Component/Notifier/Bridge/JoliNotif/JoliNotifTransportFactory.php create mode 100644 src/Symfony/Component/Notifier/Bridge/JoliNotif/LICENSE create mode 100644 src/Symfony/Component/Notifier/Bridge/JoliNotif/README.md create mode 100644 src/Symfony/Component/Notifier/Bridge/JoliNotif/Tests/JoliNotifOptionsTest.php create mode 100644 src/Symfony/Component/Notifier/Bridge/JoliNotif/Tests/JoliNotifTransportFactoryTest.php create mode 100644 src/Symfony/Component/Notifier/Bridge/JoliNotif/Tests/JoliNotifTransportTest.php create mode 100644 src/Symfony/Component/Notifier/Bridge/JoliNotif/composer.json create mode 100644 src/Symfony/Component/Notifier/Bridge/JoliNotif/phpunit.xml.dist create mode 100644 src/Symfony/Component/Notifier/Channel/DesktopChannel.php create mode 100644 src/Symfony/Component/Notifier/Message/DesktopMessage.php create mode 100644 src/Symfony/Component/Notifier/Notification/DesktopNotificationInterface.php create mode 100644 src/Symfony/Component/Notifier/Tests/Message/DesktopMessageTest.php diff --git a/composer.json b/composer.json index ff966ad6dfc30..a0c64e31bc888 100644 --- a/composer.json +++ b/composer.json @@ -135,6 +135,7 @@ "dragonmantank/cron-expression": "^3.1", "egulias/email-validator": "^2.1.10|^3.1|^4", "guzzlehttp/promises": "^1.4|^2.0", + "jolicode/jolinotif": "^2.7.2", "league/html-to-markdown": "^5.0", "league/uri": "^6.5|^7.0", "masterminds/html5": "^2.7.2", diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index d7c4403b0ae1b..75cfe1d1f0d84 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -2766,6 +2766,7 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $ } $container->getDefinition('notifier.channel.sms')->setArgument(0, null); $container->getDefinition('notifier.channel.push')->setArgument(0, null); + $container->getDefinition('notifier.channel.desktop')->setArgument(0, null); } $container->getDefinition('notifier.channel_policy')->setArgument(0, $config['channel_policy']); @@ -2801,6 +2802,7 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $ NotifierBridge\Infobip\InfobipTransportFactory::class => 'notifier.transport_factory.infobip', NotifierBridge\Iqsms\IqsmsTransportFactory::class => 'notifier.transport_factory.iqsms', NotifierBridge\Isendpro\IsendproTransportFactory::class => 'notifier.transport_factory.isendpro', + NotifierBridge\JoliNotif\JoliNotifTransportFactory::class => 'notifier.transport_factory.joli-notif', NotifierBridge\KazInfoTeh\KazInfoTehTransportFactory::class => 'notifier.transport_factory.kaz-info-teh', NotifierBridge\LightSms\LightSmsTransportFactory::class => 'notifier.transport_factory.light-sms', NotifierBridge\LineNotify\LineNotifyTransportFactory::class => 'notifier.transport_factory.line-notify', diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier.php index 3bd19b8ddc061..95a4d12e9ef1a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier.php @@ -15,6 +15,7 @@ use Symfony\Component\Notifier\Channel\BrowserChannel; use Symfony\Component\Notifier\Channel\ChannelPolicy; use Symfony\Component\Notifier\Channel\ChatChannel; +use Symfony\Component\Notifier\Channel\DesktopChannel; use Symfony\Component\Notifier\Channel\EmailChannel; use Symfony\Component\Notifier\Channel\PushChannel; use Symfony\Component\Notifier\Channel\SmsChannel; @@ -24,6 +25,7 @@ use Symfony\Component\Notifier\EventListener\SendFailedMessageToNotifierListener; use Symfony\Component\Notifier\FlashMessage\DefaultFlashMessageImportanceMapper; use Symfony\Component\Notifier\Message\ChatMessage; +use Symfony\Component\Notifier\Message\DesktopMessage; use Symfony\Component\Notifier\Message\PushMessage; use Symfony\Component\Notifier\Message\SmsMessage; use Symfony\Component\Notifier\Messenger\MessageHandler; @@ -76,6 +78,10 @@ ->args([service('texter.transports'), service('messenger.default_bus')->ignoreOnInvalid()]) ->tag('notifier.channel', ['channel' => 'push']) + ->set('notifier.channel.desktop', DesktopChannel::class) + ->args([service('texter.transports'), service('messenger.default_bus')->ignoreOnInvalid()]) + ->tag('notifier.channel', ['channel' => 'desktop']) + ->set('notifier.monolog_handler', NotifierHandler::class) ->args([service('notifier')]) @@ -126,6 +132,10 @@ ->args([service('texter.transports')]) ->tag('messenger.message_handler', ['handles' => PushMessage::class]) + ->set('texter.messenger.desktop_handler', MessageHandler::class) + ->args([service('texter.transports')]) + ->tag('messenger.message_handler', ['handles' => DesktopMessage::class]) + ->set('notifier.notification_logger_listener', NotificationLoggerListener::class) ->tag('kernel.event_subscriber') diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php index a773899f710a0..7c8002993db0b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php @@ -72,6 +72,7 @@ 'infobip' => Bridge\Infobip\InfobipTransportFactory::class, 'iqsms' => Bridge\Iqsms\IqsmsTransportFactory::class, 'isendpro' => Bridge\Isendpro\IsendproTransportFactory::class, + 'joli-notif' => Bridge\JoliNotif\JoliNotifTransportFactory::class, 'kaz-info-teh' => Bridge\KazInfoTeh\KazInfoTehTransportFactory::class, 'light-sms' => Bridge\LightSms\LightSmsTransportFactory::class, 'lox24' => Bridge\Lox24\Lox24TransportFactory::class, diff --git a/src/Symfony/Component/Notifier/Bridge/JoliNotif/.gitattributes b/src/Symfony/Component/Notifier/Bridge/JoliNotif/.gitattributes new file mode 100644 index 0000000000000..14c3c35940427 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/JoliNotif/.gitattributes @@ -0,0 +1,3 @@ +/Tests export-ignore +/phpunit.xml.dist export-ignore +/.git* export-ignore diff --git a/src/Symfony/Component/Notifier/Bridge/JoliNotif/.gitignore b/src/Symfony/Component/Notifier/Bridge/JoliNotif/.gitignore new file mode 100644 index 0000000000000..c49a5d8df5c65 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/JoliNotif/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/src/Symfony/Component/Notifier/Bridge/JoliNotif/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/JoliNotif/CHANGELOG.md new file mode 100644 index 0000000000000..00149ea5ac6f5 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/JoliNotif/CHANGELOG.md @@ -0,0 +1,7 @@ +CHANGELOG +========= + +7.2 +--- + + * Add the bridge diff --git a/src/Symfony/Component/Notifier/Bridge/JoliNotif/JoliNotifOptions.php b/src/Symfony/Component/Notifier/Bridge/JoliNotif/JoliNotifOptions.php new file mode 100644 index 0000000000000..325c75647b768 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/JoliNotif/JoliNotifOptions.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\JoliNotif; + +use Symfony\Component\Notifier\Exception\InvalidArgumentException; +use Symfony\Component\Notifier\Message\MessageOptionsInterface; + +/** + * @author Ahmed Ghanem + */ +final class JoliNotifOptions implements MessageOptionsInterface +{ + public function __construct( + private ?string $iconPath = null, + private array $extraOptions = [], + ) { + } + + public function toArray(): array + { + return [ + 'icon_path' => $this->iconPath, + 'extra_options' => $this->extraOptions, + ]; + } + + public function getRecipientId(): ?string + { + return null; + } + + /** + * @return $this + */ + public function setIconPath(string $iconPath): static + { + $this->iconPath = $iconPath; + + return $this; + } + + public function getIconPath(): ?string + { + return $this->iconPath; + } + + /** + * Extra options maybe supported and effective by the JoliNotif package on some operating systems + * while not on others. + * For more details, you can always check the package page on GitHub (https://github.com/jolicode/JoliNotif). + * + * @return $this + */ + public function setExtraOption(string $key, string|int $value): static + { + $this->extraOptions[$key] = $value; + + return $this; + } + + public function getExtraOption(string $key): string|int + { + if (!isset($this->extraOptions[$key])) { + throw new InvalidArgumentException(\sprintf('The extra option "%s" cannot be fetched as it does not exist.', $key)); + } + + return $this->extraOptions[$key]; + } + + public function getExtraOptions(): array + { + return $this->extraOptions; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/JoliNotif/JoliNotifTransport.php b/src/Symfony/Component/Notifier/Bridge/JoliNotif/JoliNotifTransport.php new file mode 100644 index 0000000000000..edc13e76d477a --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/JoliNotif/JoliNotifTransport.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\JoliNotif; + +use Joli\JoliNotif\DefaultNotifier as JoliNotifier; +use Joli\JoliNotif\Notification as JoliNotification; +use Symfony\Component\Notifier\Exception\LogicException; +use Symfony\Component\Notifier\Exception\RuntimeException; +use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; +use Symfony\Component\Notifier\Message\DesktopMessage; +use Symfony\Component\Notifier\Message\MessageInterface; +use Symfony\Component\Notifier\Message\SentMessage; +use Symfony\Component\Notifier\Transport\AbstractTransport; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; + +/** + * @author Ahmed Ghanem + */ +final class JoliNotifTransport extends AbstractTransport +{ + public function __construct( + private readonly JoliNotifier $joliNotifier, + ?EventDispatcherInterface $dispatcher = null, + ) { + parent::__construct(null, $dispatcher); + } + + public function __toString(): string + { + return \sprintf('jolinotif://%s', $this->getEndpoint()); + } + + public function supports(MessageInterface $message): bool + { + return $message instanceof DesktopMessage && (null === $message->getOptions() || $message->getOptions() instanceof JoliNotifOptions); + } + + protected function doSend(MessageInterface $message): SentMessage + { + if (!$message instanceof DesktopMessage) { + throw new UnsupportedMessageTypeException(__CLASS__, DesktopMessage::class, $message); + } + + if (($options = $message->getOptions()) && !$options instanceof JoliNotifOptions) { + throw new LogicException(\sprintf('The "%s" transport only supports an instance of the "%s" as an option class.', __CLASS__, JoliNotifOptions::class)); + } + + $joliNotification = $this->buildJoliNotificationObject($message, $options); + + if (false === $this->joliNotifier->send($joliNotification)) { + throw new RuntimeException(\sprintf('An error occurred while sending a notification via the "%s" transport.', __CLASS__)); + } + + return new SentMessage($message, (string) $this); + } + + private function buildJoliNotificationObject(DesktopMessage $message, ?JoliNotifOptions $options = null): JoliNotification + { + $joliNotification = new JoliNotification(); + + $joliNotification->setTitle($message->getSubject()); + $joliNotification->setBody($message->getContent()); + + if ($options) { + if ($iconPath = $options->getIconPath()) { + $joliNotification->setIcon($iconPath); + } + + foreach ($options->getExtraOptions() as $extraOptionKey => $extraOptionValue) { + $joliNotification->addOption($extraOptionKey, $extraOptionValue); + } + } + + return $joliNotification; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/JoliNotif/JoliNotifTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/JoliNotif/JoliNotifTransportFactory.php new file mode 100644 index 0000000000000..6a171fb19d4e8 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/JoliNotif/JoliNotifTransportFactory.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\JoliNotif; + +use Joli\JoliNotif\DefaultNotifier as JoliNotifier; +use Symfony\Component\Notifier\Exception\UnsupportedSchemeException; +use Symfony\Component\Notifier\Transport\AbstractTransportFactory; +use Symfony\Component\Notifier\Transport\Dsn; + +/** + * @author Ahmed Ghanem + */ +final class JoliNotifTransportFactory extends AbstractTransportFactory +{ + private const SCHEME_NAME = 'jolinotif'; + + public function create(Dsn $dsn): JoliNotifTransport + { + if (self::SCHEME_NAME !== $dsn->getScheme()) { + throw new UnsupportedSchemeException($dsn, self::SCHEME_NAME, $this->getSupportedSchemes()); + } + + return (new JoliNotifTransport(new JoliNotifier(), $this->dispatcher))->setHost($dsn->getHost())->setPort($dsn->getPort()); + } + + /** + * @return string[] + */ + protected function getSupportedSchemes(): array + { + return [ + self::SCHEME_NAME, + ]; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/JoliNotif/LICENSE b/src/Symfony/Component/Notifier/Bridge/JoliNotif/LICENSE new file mode 100644 index 0000000000000..e374a5c8339d3 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/JoliNotif/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2024-present 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/JoliNotif/README.md b/src/Symfony/Component/Notifier/Bridge/JoliNotif/README.md new file mode 100644 index 0000000000000..06801292d39ae --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/JoliNotif/README.md @@ -0,0 +1,20 @@ +JoliNotif Notifier Bridge +========================= + +Provides a [JoliNotif](https://github.com/jolicode/JoliNotif) integration for +the Symfony Notifier Component. + +DSN example +----------- + +``` +JOLINOTIF_DSN=jolinotif://default +``` + +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/JoliNotif/Tests/JoliNotifOptionsTest.php b/src/Symfony/Component/Notifier/Bridge/JoliNotif/Tests/JoliNotifOptionsTest.php new file mode 100644 index 0000000000000..6a254804aa13d --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/JoliNotif/Tests/JoliNotifOptionsTest.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\JoliNotif\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Notifier\Bridge\JoliNotif\JoliNotifOptions; +use Symfony\Component\Notifier\Exception\InvalidArgumentException; + +/** + * @author Ahmed Ghanem + */ +class JoliNotifOptionsTest extends TestCase +{ + public function testToArray() + { + $joliOptions = new JoliNotifOptions(); + + $joliOptions->setIconPath('/sample/icon/path'); + $joliOptions->setExtraOption('subtitle', 'This is a subtitle'); + $joliOptions->setExtraOption('sound', 'Frog'); + + $this->assertSame([ + 'icon_path' => '/sample/icon/path', + 'extra_options' => [ + 'subtitle' => 'This is a subtitle', + 'sound' => 'Frog', + ], + ], $joliOptions->toArray()); + } + + public function testNonExistExtraOption() + { + $joliOptions = new JoliNotifOptions(); + + $this->expectException(InvalidArgumentException::class); + + $joliOptions->getExtraOption('non-exist-option'); + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/JoliNotif/Tests/JoliNotifTransportFactoryTest.php b/src/Symfony/Component/Notifier/Bridge/JoliNotif/Tests/JoliNotifTransportFactoryTest.php new file mode 100644 index 0000000000000..7fc16e1efac87 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/JoliNotif/Tests/JoliNotifTransportFactoryTest.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\JoliNotif\Tests; + +use Symfony\Component\Notifier\Bridge\JoliNotif\JoliNotifTransportFactory; +use Symfony\Component\Notifier\Test\TransportFactoryTestCase; + +/** + * @author Ahmed Ghanem + */ +final class JoliNotifTransportFactoryTest extends TransportFactoryTestCase +{ + public static function createProvider(): iterable + { + yield [ + 'jolinotif://host.test', + 'jolinotif://host.test?some_option=true', + ]; + } + + public static function supportsProvider(): iterable + { + yield [true, 'jolinotif://host.test']; + yield [false, 'somethingElse://host.test']; + } + + public static function unsupportedSchemeProvider(): iterable + { + yield ['somethingElse://user:pass@host.test?some_option=88']; + } + + public function createFactory(): JoliNotifTransportFactory + { + return new JoliNotifTransportFactory(); + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/JoliNotif/Tests/JoliNotifTransportTest.php b/src/Symfony/Component/Notifier/Bridge/JoliNotif/Tests/JoliNotifTransportTest.php new file mode 100644 index 0000000000000..13beaa46fb074 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/JoliNotif/Tests/JoliNotifTransportTest.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\JoliNotif\Tests; + +use Joli\JoliNotif\DefaultNotifier as JoliNotifier; +use Symfony\Component\Notifier\Bridge\JoliNotif\JoliNotifOptions; +use Symfony\Component\Notifier\Bridge\JoliNotif\JoliNotifTransport; +use Symfony\Component\Notifier\Message\ChatMessage; +use Symfony\Component\Notifier\Message\DesktopMessage; +use Symfony\Component\Notifier\Test\TransportTestCase; +use Symfony\Component\Notifier\Tests\Transport\DummyMessage; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +/** + * @author Ahmed Ghanem + */ +final class JoliNotifTransportTest extends TransportTestCase +{ + public static function toStringProvider(): iterable + { + yield ['jolinotif://localhost', self::createTransport()]; + } + + public static function createTransport(?HttpClientInterface $client = null): JoliNotifTransport + { + return new JoliNotifTransport(new JoliNotifier()); + } + + public static function supportedMessagesProvider(): iterable + { + $message = new DesktopMessage('Worker Status', 'Task#2 has finished successfully'); + + $message->setOptions((new JoliNotifOptions())->setIconPath('/path/to/notification/icon')); + + yield [$message]; + } + + public static function unsupportedMessagesProvider(): iterable + { + yield [new ChatMessage('Hello!')]; + yield [new DummyMessage()]; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/JoliNotif/composer.json b/src/Symfony/Component/Notifier/Bridge/JoliNotif/composer.json new file mode 100644 index 0000000000000..6cf229a903447 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/JoliNotif/composer.json @@ -0,0 +1,38 @@ +{ + "name": "symfony/joli-notif-notifier", + "type": "symfony-notifier-bridge", + "description": "Symfony JoliNotif Notifier Bridge", + "keywords": [ + "joli-notif", + "desktop-notifications", + "notifier" + ], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Ahmed Ghanem", + "email": "ahmedghanem7361@gmail.com", + "homepage": "https://github.com/ahmedghanem00" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.2", + "jolicode/jolinotif": "^2.7.2", + "symfony/http-client": "^7.2", + "symfony/notifier": "^7.2" + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Notifier\\Bridge\\JoliNotif\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/src/Symfony/Component/Notifier/Bridge/JoliNotif/phpunit.xml.dist b/src/Symfony/Component/Notifier/Bridge/JoliNotif/phpunit.xml.dist new file mode 100644 index 0000000000000..018d59bae1ff6 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/JoliNotif/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 11d6954372cca..4c5843021fa71 100644 --- a/src/Symfony/Component/Notifier/CHANGELOG.md +++ b/src/Symfony/Component/Notifier/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +7.2 +--- + + * Add `Desktop` channel + 6.3 --- diff --git a/src/Symfony/Component/Notifier/Channel/DesktopChannel.php b/src/Symfony/Component/Notifier/Channel/DesktopChannel.php new file mode 100644 index 0000000000000..649634ea83c2f --- /dev/null +++ b/src/Symfony/Component/Notifier/Channel/DesktopChannel.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\Channel; + +use Symfony\Component\Notifier\Message\DesktopMessage; +use Symfony\Component\Notifier\Notification\DesktopNotificationInterface; +use Symfony\Component\Notifier\Notification\Notification; +use Symfony\Component\Notifier\Recipient\RecipientInterface; + +/** + * @author Ahmed Ghanem + */ +class DesktopChannel extends AbstractChannel +{ + public function notify(Notification $notification, RecipientInterface $recipient, ?string $transportName = null): void + { + if ($notification instanceof DesktopNotificationInterface) { + $message = $notification->asDesktopMessage($recipient, $transportName); + } + + $message ??= DesktopMessage::fromNotification($notification); + + if (null !== $transportName) { + $message->setTransport($transportName); + } + + if (null === $this->bus) { + $this->transport->send($message); + } else { + $this->bus->dispatch($message); + } + } + + public function supports(Notification $notification, RecipientInterface $recipient): bool + { + return true; + } +} diff --git a/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php b/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php index feecb383bf7d4..99915ab8f36db 100644 --- a/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php +++ b/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php @@ -112,6 +112,10 @@ class UnsupportedSchemeException extends LogicException 'class' => Bridge\Isendpro\IsendproTransportFactory::class, 'package' => 'symfony/isendpro-notifier', ], + 'jolinotif' => [ + 'class' => Bridge\JoliNotif\JoliNotifTransportFactory::class, + 'package' => 'symfony/joli-notif-notifier', + ], 'kaz-info-teh' => [ 'class' => Bridge\KazInfoTeh\KazInfoTehTransportFactory::class, 'package' => 'symfony/kaz-info-teh-notifier', diff --git a/src/Symfony/Component/Notifier/Message/DesktopMessage.php b/src/Symfony/Component/Notifier/Message/DesktopMessage.php new file mode 100644 index 0000000000000..9b1f9edf4b69b --- /dev/null +++ b/src/Symfony/Component/Notifier/Message/DesktopMessage.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\Message; + +use Symfony\Component\Notifier\Notification\Notification; + +/** + * @author Ahmed Ghanem + */ +class DesktopMessage implements MessageInterface, FromNotificationInterface +{ + private ?string $transport = null; + private ?Notification $notification = null; + + public function __construct( + private string $subject, + private string $content, + private ?MessageOptionsInterface $options = null, + ) { + } + + public static function fromNotification(Notification $notification): self + { + $message = new self($notification->getSubject(), $notification->getContent()); + + $message->setNotification($notification); + + return $message; + } + + public function getSubject(): string + { + return $this->subject; + } + + public function getContent(): string + { + return $this->content; + } + + public function getRecipientId(): ?string + { + return $this->options?->getRecipientId(); + } + + /** + * @return $this + */ + public function setSubject(string $subject): static + { + $this->subject = $subject; + + return $this; + } + + /** + * @return $this + */ + public function setContent(string $content): static + { + $this->content = $content; + + return $this; + } + + /** + * @return $this + */ + public function setOptions(MessageOptionsInterface $options): static + { + $this->options = $options; + + return $this; + } + + public function getOptions(): ?MessageOptionsInterface + { + return $this->options; + } + + public function getTransport(): ?string + { + return $this->transport; + } + + /** + * @return $this + */ + public function setTransport(string $transport): static + { + $this->transport = $transport; + + return $this; + } + + public function getNotification(): ?Notification + { + return $this->notification; + } + + /** + * @return $this + */ + public function setNotification(Notification $notification): static + { + $this->notification = $notification; + + return $this; + } +} diff --git a/src/Symfony/Component/Notifier/Notification/DesktopNotificationInterface.php b/src/Symfony/Component/Notifier/Notification/DesktopNotificationInterface.php new file mode 100644 index 0000000000000..c474b3876f079 --- /dev/null +++ b/src/Symfony/Component/Notifier/Notification/DesktopNotificationInterface.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Notification; + +use Symfony\Component\Notifier\Message\DesktopMessage; +use Symfony\Component\Notifier\Recipient\RecipientInterface; + +/** + * @author Ahmed Ghanem + */ +interface DesktopNotificationInterface +{ + public function asDesktopMessage(RecipientInterface $recipient, ?string $transport = null): ?DesktopMessage; +} diff --git a/src/Symfony/Component/Notifier/Tests/Message/DesktopMessageTest.php b/src/Symfony/Component/Notifier/Tests/Message/DesktopMessageTest.php new file mode 100644 index 0000000000000..6021202a8ee09 --- /dev/null +++ b/src/Symfony/Component/Notifier/Tests/Message/DesktopMessageTest.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\Tests\Message; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Notifier\Message\DesktopMessage; +use Symfony\Component\Notifier\Notification\Notification; + +/** + * @author Ahmed Ghanem + */ +class DesktopMessageTest extends TestCase +{ + public function testCanBeConstructed() + { + $message = new DesktopMessage('Hello', 'World'); + + $this->assertSame('Hello', $message->getSubject()); + $this->assertSame('World', $message->getContent()); + } + + public function testSetSubject() + { + $message = new DesktopMessage('Hello', 'World'); + + $message->setSubject('dlrow olleH'); + + $this->assertSame('dlrow olleH', $message->getSubject()); + } + + public function testSetContent() + { + $message = new DesktopMessage('Hello', 'World'); + + $message->setContent('dlrow olleH'); + + $this->assertSame('dlrow olleH', $message->getContent()); + } + + public function testSetTransport() + { + $message = new DesktopMessage('Hello', 'World'); + + $message->setTransport('next_one'); + + $this->assertSame('next_one', $message->getTransport()); + } + + public function testCreateFromNotification() + { + $notification = (new Notification('Hello'))->content('World'); + $message = DesktopMessage::fromNotification($notification); + + $this->assertSame('Hello', $message->getSubject()); + $this->assertSame('World', $message->getContent()); + $this->assertSame($notification, $message->getNotification()); + } +} diff --git a/src/Symfony/Component/Notifier/Transport.php b/src/Symfony/Component/Notifier/Transport.php index 92b84b7c9ea70..b4df0729f40d0 100644 --- a/src/Symfony/Component/Notifier/Transport.php +++ b/src/Symfony/Component/Notifier/Transport.php @@ -52,6 +52,7 @@ final class Transport Bridge\Infobip\InfobipTransportFactory::class, Bridge\Iqsms\IqsmsTransportFactory::class, Bridge\Isendpro\IsendproTransportFactory::class, + Bridge\JoliNotif\JoliNotifTransportFactory::class, Bridge\KazInfoTeh\KazInfoTehTransportFactory::class, Bridge\LightSms\LightSmsTransportFactory::class, Bridge\LineNotify\LineNotifyTransportFactory::class,