diff --git a/.github/workflows/build-phar.yaml b/.github/workflows/build-phar.yaml index 9426ffd..95522e6 100644 --- a/.github/workflows/build-phar.yaml +++ b/.github/workflows/build-phar.yaml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Build the PHAR uses: ./.github/actions/phar diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 86e7c6c..b6a87e0 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -30,7 +30,7 @@ jobs: - name: PHPStan uses: docker://oskarstark/phpstan-ga env: - REQUIRE_DEV: true + REQUIRE_DEV: true ci: name: Test PHP ${{ matrix.php-version }} ${{ matrix.name }} @@ -71,7 +71,7 @@ jobs: run: | composer update --prefer-dist --no-interaction ${{ matrix.composer-flags }} - - name: Install libnotify4 for LibNotifyNotifier + - name: Install libnotify4 for LibNotifyDriver run: | sudo apt-get install -y --no-install-recommends --no-install-suggests libnotify4 @@ -79,19 +79,30 @@ jobs: run: php vendor/bin/simple-phpunit phar: - name: Create a PHAR and ensure it is working - runs-on: ubuntu-latest + name: Create a PHAR and ensure it is working + runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 + steps: + - uses: actions/checkout@v4 + + - name: Setup PHP, with composer and extensions + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + extensions: mbstring, xml, ffi + ini-values: phar.readonly="Off" + + - name: Install libnotify4 for LibNotifyDriver + run: | + sudo apt-get install -y --no-install-recommends --no-install-suggests libnotify4 - - name: Build the PHAR - uses: ./.github/actions/phar + - name: Build the PHAR + uses: ./.github/actions/phar - - name: Execute the PHAR - run: | - tools/phar/build/jolinotif.phar --help + - name: Execute the PHAR + run: | + tools/phar/build/jolinotif.phar --help - - name: Trigger a notification - run: | - tools/phar/build/jolinotif.phar --title "Yolo" --body "Hello world!" + - name: Trigger a notification + run: | + tools/phar/build/jolinotif.phar --title "Yolo" --body "Hello world!" --verbose diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 7f359c5..2c09274 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Build the PHAR uses: ./.github/actions/phar diff --git a/CHANGELOG.md b/CHANGELOG.md index d20f802..469146b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,14 @@ ## Not released yet +* Added a new NotifierInterface and DefaultNotifier as the main public API of this package * Added wsl-notify-send notifier for Windows Subsystem for Linux * Added libnotify based notifier for Linux through FFI * Changed TerminalNotifier to use contentImage option for icon instead of appIcon * Fixed phar missing some dependencies +* Marked most of the classes as internal +* Deprecated all the notifiers classes in favor of the new internal DriverInterface implementations +* Deprecated the NotifierFactory in favor of the new DefaultNotifier class that hide driver implementation details ## 2.6.0 (2023-12-03) diff --git a/README.md b/README.md index 8bdbe05..56e68cc 100644 --- a/README.md +++ b/README.md @@ -11,10 +11,10 @@ # About JoliNotif JoliNotif is a cross-platform PHP library to display desktop notifications. -It works on Linux, Windows or MacOS. +It works on Linux, Windows or macOS. -Requires PHP >= 7.4 (support for PHP 5 was available in version 1.x, for PHP 7.0 -and 7.1 in version < 2.1.0, for PHP 7.2 and 7.3 in version < 2.4.0). +Requires PHP >= 8.1 (support for PHP 5 was available in version 1.x, for PHP 7.0 +and 7.1 in version < 2.1.0, for PHP 7.2 and 7.3 in version < 2.4.0, for PHP < 8.0 in version 2.6.0). > [!NOTE] > This library can not be used in a web context (FPM or equivalent). Use @@ -30,17 +30,13 @@ composer require "jolicode/jolinotif" ## Usage -Use the `NotifierFactory` to create the correct `Notifier` (adapted to your OS), -then use it to send your notification: - ```php include __DIR__.'/vendor/autoload.php'; use Joli\JoliNotif\Notification; -use Joli\JoliNotif\NotifierFactory; +use Joli\JoliNotif\DefaultNotifier; -// Create a Notifier -$notifier = NotifierFactory::create(); +$notifier = new DefaultNotifier(); // Create your notification $notification = @@ -48,8 +44,8 @@ $notification = ->setTitle('Notification title') ->setBody('This is the body of your notification') ->setIcon(__DIR__.'/path/to/your/icon.png') - ->addOption('subtitle', 'This is a subtitle') // Only works on macOS (AppleScriptNotifier) - ->addOption('sound', 'Frog') // Only works on macOS (AppleScriptNotifier) + ->addOption('subtitle', 'This is a subtitle') // Only works on macOS (AppleScriptDriver) + ->addOption('sound', 'Frog') // Only works on macOS (AppleScriptDriver) ; // Send it @@ -68,7 +64,7 @@ Discover more by reading the docs: * [Basic usage](doc/01-basic-usage.md) * [Notification](doc/02-notification.md) -* [Notifier](doc/03-notifier.md) +* [Drivers](doc/03-drivers.md) * [CRON usage](doc/04-cron-usage.md) * [CLI usage](doc/05-cli-usage.md) diff --git a/composer.json b/composer.json index d15d5c9..a4d8332 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,9 @@ "require": { "php": ">=8.1", "jolicode/php-os-helper": "^0.1.0", - "symfony/process": "^5.4 || ^6.0 || ^7.0" + "psr/log": "^3.0", + "symfony/process": "^5.4 || ^6.0 || ^7.0", + "symfony/deprecation-contracts": "^3" }, "require-dev": { "symfony/finder": "^5.4 || ^6.0 || ^7.0", diff --git a/doc/01-basic-usage.md b/doc/01-basic-usage.md index 7dddf8b..b722eb7 100644 --- a/doc/01-basic-usage.md +++ b/doc/01-basic-usage.md @@ -1,31 +1,38 @@ # Basic usage -## Create a notifier +## Create a notifier and sending a notification -JoliNotif provides a `NotifierFactory` which creates the best supported -notifier according to your platform. You don't have to care if you're running -on Linux, Windows or Mac OS: +JoliNotif provides a `DefaultNotifier` class which is the main entrypoint of +the library. It's main goal is to provide a simple way to send a desktop +notification without having to care about the platform you're running on. It +will work whether you're on Linux, Windows or macOS. ```php -use Joli\JoliNotif\NotifierFactory; +use Joli\JoliNotif\DefaultNotifier; +use Joli\JoliNotif\Notification; + +$notifier = new DefaultNotifier(); -$notifier = NotifierFactory::create(); +$notifier->send(new Notification()); ``` -The factory use the notifier's priority to determine the best notifier to use. -For example some notifier has a low priority because they don't support some -notification options. The best notifier will then be returned. +And you're done! + +Internally, the notifier will use each driver's priority to determine the +best one available on your system. +For example, some driver have a low priority because they don't support some +notification options. So if a better driver is available, it will be used. -> **Note** -> The factory now returns a NullNotifier instead of null when no notifier is -> available. You then no longer have to check for null value. +> [!NOTE] +> In case no driver is supported or if an error happens during notification +> sending, the send method will return false. -If you really need to ensure a Notifier is available, you can use the -`createOrThrowException` method. It will return the best notifier available or -throw a `Joli\JoliNotif\Exception\NoSupportedNotifierException` if no one is -available on the current system. +> [!TIP] +> If you want to log when an error happens or if no driver is supported, you +> can also pass an instance of `Psr\Log\LoggerInterface` as the first +> parameter of the `DefaultNotifier`'s constructor. -## Create your notification +## Create and configure your notification Create a notification is as simple as instantiating a `Notification` and setting the option you want to use: @@ -37,29 +44,18 @@ $notification = (new Notification()) ->setBody('The notification body') ->setTitle('The notification title') - ->addOption('subtitle', 'This is a subtitle') // Only works on macOS (AppleScriptNotifier) - ->addOption('sound', 'Frog') // Only works on macOS (AppleScriptNotifier) - ->addOption('url', 'https://google.com') // Only works on macOS (TerminalNotifierNotifier) + ->addOption('subtitle', 'This is a subtitle') // Only works on macOS (AppleScriptDriver) + ->addOption('sound', 'Frog') // Only works on macOS (AppleScriptDriver) + ->addOption('url', 'https://google.com') // Only works on macOS (TerminalNotifierDriver) ; ``` -As you can see, the notification provides a fluent API. - -## Sending the notification - -Now that you get your notification, just send it via the notifier: - -```php -$notifier->send($notification); -``` - -And you're done! - +As you can see, the notification class provides a fluent API. ## Next readings * [Notification](02-notification.md) -* [Notifier](03-notifier.md) +* [Drivers](03-drivers.md) * [CRON usage](04-cron-usage.md) * [CLI usage](05-cli-usage.md) diff --git a/doc/02-notification.md b/doc/02-notification.md index 10727fd..f874136 100644 --- a/doc/02-notification.md +++ b/doc/02-notification.md @@ -1,10 +1,10 @@ # Notification `Notification` is the main model of JoliNotif. It contains all the options -that should be applied - if possible - by the notifier. +that should be applied - if possible - by the driver. > **Note** -> Notifiers are designed to handle the options they support and discard not +> Drivers are designed to handle the options they support and discard not > supported ones gracefully without throwing any exception. Currently, only three options are supported but new ones could be added later. @@ -15,12 +15,12 @@ Each option can be set via a setter on the `Notification` class. The body is the main message of the notification that you want to display. > **Note** -> This is the only required option. Notifiers will throw an`InvalidNotificationException` +> This is the only required option. Drivers will throw an`InvalidNotificationException` > if the body is empty or null. ## Title -You can also optionally provide a title. Notifiers will certainly display the +You can also optionally provide a title. Drivers will certainly display the title above the body, in bold. ## Icon @@ -37,17 +37,17 @@ $notification->setIcon(__DIR__.'/Resources/icons/success.png'); > care to extract this icon in your system temp directory to make it accessible > from command line executable. -Not all notifiers support icon but again, you can safely add an icon to your -notification since the notifier will discard it if not supported. +Not all drivers support icon but again, you can safely add an icon to your +notification since the driver will simply discard it if not supported. ## Options with restricted support -Some options are available only on few notifiers. They can be configured +Some options are available only on few drivers. They can be configured through the `addOption` method. ### Subtitle -Only works with AppleScriptNotifier at the moment. +Only works with AppleScriptDriver at the moment. ```php $notification->addOption('subtitle', 'This is a subtitle'); @@ -55,7 +55,7 @@ $notification->addOption('subtitle', 'This is a subtitle'); ### Sound -Only works with AppleScriptNotifier at the moment. +Only works with AppleScriptDriver at the moment. Non-exhaustive list of sounds: Basso, Frog, Hero, Pop, Submarine, Blow, Funk, Morse, Purr, Tink, Bottle, Glass, Ping, Sosumi. @@ -66,7 +66,7 @@ $notification->addOption('sound', 'Frog'); ### Url -Only works with TerminalNotifierNotifier at the moment. +Only works with TerminalNotifierDriver at the moment. ```php $notification->addOption('url', 'https://google.com'); @@ -74,7 +74,7 @@ $notification->addOption('url', 'https://google.com'); ## Next readings -* [Notifier](03-notifier.md) +* [Drivers](03-drivers.md) * [CRON usage](04-cron-usage.md) * [CLI usage](05-cli-usage.md) diff --git a/doc/03-drivers.md b/doc/03-drivers.md new file mode 100644 index 0000000..1155f98 --- /dev/null +++ b/doc/03-drivers.md @@ -0,0 +1,166 @@ +# Drivers + +JoliNotif's default notifier uses drivers to delegate the notification sending +to the right executable available on your system. + +## Interface + +All drivers implement the `Joli\JoliNotif\Driver\DriverInterface` interface. + +`Driver#send()` will return true if the command was successfully executed, +false otherwise. + +## Supported drivers + +JoliNotif supports different kinds of drivers. Some driver load a C library +while most of the others execute a binary (whether it is available on your +system or provided by JoliNotif directly). + +Here is the full list of supported drivers, grouped by platform: + +### Linux + +#### LibNotifyDriver + +This driver uses the FFI PHP extension to interact with the `libnotify` C +library. This library should be installed by default on most Linux +distributions wih graphical interface. + +LibNotifyDriver can display notification with a body, a title and an icon. + +#### NotifySendDriver + +This driver uses the executable `notify-send` (available in the +`libnotify-bin` package) which should be installed by default on most Linux +distributions. + +notify-send can display notification with a body, a title and an icon. + +#### KDialogDriver + +This driver uses the executable `kdialog` (part of the standard KDE 5 Plasma +Desktop installation) which should be installed by default on most Linux +distributions which use the KDE 5 Plasma Desktop such as KUbuntu. + +kdialog can display notifications with a body and a title. It does not support +icons. A default timeout of 5 seconds is hard-coded for the notification as it +needs to be part of the command line. + +### Mac OS + +#### GrowlNotifyDriver + +This driver uses the `growlnotify` executable. It can be used when available +alongside growl, which can be installed on Mac OS X. + +growl can display notification with a body, a title and an icon. + +#### TerminalNotifierDriver + +This driver uses the `terminal-notifier` executable and works on Mac OS X 10.8 +and higher. + +terminal-notifier can display notification with a body and a title. An icon can +only be displayed on Mac OS X 10.9 and higher. + +#### AppleScriptDriver + +This driver is based on AppleScript and uses the `osascript` binary. +AppleScript can display notification since Mac OS X 10.9, so this driver +requires this version or higher. + +AppleScript can display notification with only a body and a title. AppleScript +don't support to set an icon and will always use instead the icon of the +application sending the notification, in our case, the terminal. + +### Windows + +#### SnoreToastDriver + +This driver uses the Windows application called SnoreToastDriver. It works on +Windows 8 and higher. Because SnoreToastDriver is probably not installed on +your system, JoliNotif embed the binaries inside the [bin/snoreToast](bin/snoreToast) +directory. + +When you use JoliNotif inside a phar archive, we take care to extract those +binaries in the system temp directory to be able to execute them. + +SnoreToastDriver can display notification with a body, a title and an icon. + +#### ToasterDriver + +This driver uses the Windows application called Toaster. It works on Windows 8 +and higher. Because Toaster is probably not installed on your system, JoliNotif +embed the binaries inside the [bin/toaster](bin/toaster) directory. + +When you use JoliNotif inside a phar archive, we take care to extract those +binaries in the system temp directory to be able to execute them. + +Toaster can display notification with a body, a title and an icon. + +#### NotifuDriver + +This driver uses the Windows application called Notifu. It works on Windows 7. +Because Notifu is probably not installed on your system, JoliNotif embed the +binary inside the [bin/notifu](bin/notifu) directory. + +When you use JoliNotif inside a phar archive, we take care to extract this +binary in the system temp directory to be able to execute it. + +Notifu can display notification with a body, a title and an icon. Sadly, Notifu +can only display icon with the .ico format. + +#### WslNotifySendDriver + +This driver uses the executable `wsl-notify-send`. +It permits to send notification from Windows Subsystem for Linux to Windows. + +wsl-notify-send can display notification with a body and a title. + +Icon is partially supported by `wsl-notify-send`, but it's not possible to set +an icon for now. + +## Using custom drivers + +If you created your own driver, you can pass it in the `$additionnalDrivers` +parameter of the `DefaultNotifier` constructor: + +```php +use Joli\JoliNotif\DefaultNotifier; +use Joli\JoliNotif\Notification; + +$notifier = new DefaultNotifier(null, [new MyCustomDriver()]); + +$notifier->send(new Notification()); +``` + +If the driver is supported, it will be used in priority. If not, the native +drivers of JoliNotif will be looked for. You can totally disable the native +drivers by also passing `false` in the `$useOnlyAdditionalDrivers` parameter of +the constructor: + +```php +use Joli\JoliNotif\DefaultNotifier; +use Joli\JoliNotif\Notification; + +$notifier = new DefaultNotifier(null, [new MyCustomDriver()], false); + +// If MyCustomDriver is not supported, no native drivers will not be used +// and the send method will always return false. +$notifier->send(new Notification()); +``` + +> [!NOTE] +> If you created a driver that could be useful for others, feel free to open a +> pull request, so we can consider adding it natively in the library! + +## Next readings + +* [CRON usage](04-cron-usage.md) +* [CLI usage](05-cli-usage.md) + +Previous pages: + +* [Notification](02-notification.md) +* [Basic usage](01-basic-usage.md) +* [README](../README.md) diff --git a/doc/03-notifier.md b/doc/03-notifier.md deleted file mode 100644 index fb1c772..0000000 --- a/doc/03-notifier.md +++ /dev/null @@ -1,144 +0,0 @@ -# Notifier - -## Interface - -All notifiers implement the `Joli\JoliNotif\Notifier` interface. The main -method you will use is `Notifier#send()`: - -```php -interface Notifier -{ - /** - * Send the given notification. - * - * @param Notification $notification - * - * @throws Exception\InvalidNotificationException if the notification is invalid - * - * @return bool - */ - public function send(Notification $notification); -} -``` - -`Notifier#send()` will return true if the command was successfully executed, -false otherwise. - -## Supported notifiers - -Currently, JoliNotif only provides notifiers that use an executable available -on your system. But nothing prevents to add network based notifiers later! :) - -Here is the full list of supported notifiers, grouped by platform: - -### Linux - -#### KDialogNotifier - -This notifier uses the executable `kdialog` (part of the standard KDE 5 Plasma -Desktop installation) which should be installed by default on most Linux -distributions which use the KDE 5 Plasma Desktop such as KUbuntu. - -kdialog can display notifications with a body and a title. It does not support -icons. A default timeout of 5 seconds is hard-coded for the notification as it -needs to be part of the command line. - -#### NotifySendNotifier - -This notifier uses the executable `notify-send` (available in the -`libnotify-bin` package) which should be installed by default on most Linux -distributions. - -notify-send can display notification with a body, a title and an icon. - -##### LibNotifyNotifier - -This notifier use the FFI PHP extension. -The C library `libnotify` should be installed by default on most Linux distributions wih graphical interface. - -LibNotifyNotifier can display notification with a body, a title and an icon. - -### Mac OS - -#### GrowlNotifyNotifier - -This notifier uses the `growlnotify` executable. It can be used when available -alongside growl, which can be installed on Mac OS X. - -growl can display notification with a body, a title and an icon. - -#### TerminalNotifierNotifier - -This notifier uses the `terminal-notifier` executable and works on Mac OS X -10.8 and higher. - -terminal-notifier can display notification with a body and a title. An icon -can only be displayed on Mac OS X 10.9 and higher. - -#### AppleScriptNotifier - -This notifier is based on AppleScript and uses the `osascript` binary. -AppleScript can display notification since Mac OS X 10.9, so this notifier -requires this version or higher. - -AppleScript can display notification with only a body and a title. AppleScript -don't support to set an icon and will always use instead the icon of the -application sending the notification, in our case, the terminal. - -#### WslNotifySendNotifier - -This notifier uses the executable `wsl-notify-send`. -It permits to send notification from Windows Subsystem for Linux to Windows. - -wsl-notify-send can display notification with a body and a title. - -Icon is partially supported by `wsl-notify-send`, but it's not possible to set -an icon for now. - -### Windows - -#### SnoreToastNotifier - -This notifier uses the Windows application called SnoreToastNotifier. It works -on Windows 8 and higher. Because SnoreToastNotifier is probably not installed -on your system, JoliNotif embed the binaries inside the [bin/snoreToast](bin/snoreToast) -directory. - -When you use JoliNotif inside a phar archive, we take care to extract those -binaries in the system temp directory to be able to execute them. - -SnoreToastNotifier can display notification with a body, a title and an icon. - -#### ToasterNotifier - -This notifier uses the Windows application called Toaster. It works on Windows -8 and higher. Because Toaster is probably not installed on your system, -JoliNotif embed the binaries inside the [bin/toaster](bin/toaster) directory. - -When you use JoliNotif inside a phar archive, we take care to extract those -binaries in the system temp directory to be able to execute them. - -Toaster can display notification with a body, a title and an icon. - -#### NotifuNotifier - -This notifier uses the Windows application called Notifu. It works on Windows -7. Because Notifu is probably not installed on your system, JoliNotif embed the -binary inside the [bin/notifu](bin/notifu) directory. - -When you use JoliNotif inside a phar archive, we take care to extract this -binary in the system temp directory to be able to execute it. - -Notifu can display notification with a body, a title and an icon. Sadly, Notifu -can only display icon with the .ico format. - -## Next readings - -* [CRON usage](04-cron-usage.md) -* [CLI usage](05-cli-usage.md) - -Previous pages: - -* [Notification](02-notification.md) -* [Basic usage](01-basic-usage.md) -* [README](../README.md) diff --git a/doc/04-cron-usage.md b/doc/04-cron-usage.md index 9c36899..d21a466 100644 --- a/doc/04-cron-usage.md +++ b/doc/04-cron-usage.md @@ -2,7 +2,7 @@ ## Configuration for CRON -Cronjobs are usually CLI scripts. But JoliNotif's Unix/Linux notifiers are GUI +Cronjobs are usually CLI scripts. But JoliNotif's Unix/Linux drivers are GUI applications. This means you need to specify the display where the notification will be sent. @@ -24,7 +24,7 @@ cronjob: Previous pages: -* [Notifier](03-notifier.md) +* [Drivers](03-drivers.md) * [Notification](02-notification.md) * [Basic usage](01-basic-usage.md) * [README](../README.md) diff --git a/doc/05-cli-usage.md b/doc/05-cli-usage.md index 79912d8..19cabf0 100644 --- a/doc/05-cli-usage.md +++ b/doc/05-cli-usage.md @@ -42,6 +42,12 @@ To get help just run: jolinotif --help ``` +To output debug information, add the `--verbose` flag: + +```bash +jolinotif --title "..." --body "..." --verbose +``` + In case of troubles use following format for passing the param: `--param="value"`. For required params (title, body) equality sign and quotes can be omitted. @@ -50,7 +56,7 @@ For required params (title, body) equality sign and quotes can be omitted. Previous pages: * [CRON usage](04-cron-usage.md) -* [Notifier](03-notifier.md) +* [Drivers](03-drivers.md) * [Notification](02-notification.md) * [Basic usage](01-basic-usage.md) * [README](../README.md) diff --git a/example/index.php b/example/index.php index 51ef031..9a1204a 100644 --- a/example/index.php +++ b/example/index.php @@ -9,15 +9,14 @@ * file that was distributed with this source code. */ +use Joli\JoliNotif\DefaultNotifier; use Joli\JoliNotif\Notification; -use Joli\JoliNotif\Notifier\NullNotifier; -use Joli\JoliNotif\NotifierFactory; require __DIR__ . '/../vendor/autoload.php'; -$notifier = NotifierFactory::create(); +$notifier = new DefaultNotifier(); -if ($notifier instanceof NullNotifier) { +if (!$notifier->getDriver()) { echo 'No supported notifier', \PHP_EOL; exit(1); } @@ -31,4 +30,6 @@ $result = $notifier->send($notification); -echo 'Notification ', $result ? 'successfully sent' : 'failed', ' with ', $notifier::class, \PHP_EOL; +$driver = $notifier->getDriver(); + +echo 'Notification ', $result ? 'successfully sent' : 'failed', ' with ', str_replace('Joli\\JoliNotif\\Driver\\', '', $driver::class), \PHP_EOL; diff --git a/jolinotif b/jolinotif index d0c4df1..070a3ea 100755 --- a/jolinotif +++ b/jolinotif @@ -1,8 +1,8 @@ #!/usr/bin/env php false, 'flag' => true, ], + 'verbose' => [ + 'name' => 'verbose', + 'info' => 'Output debug information.', + 'required' => false, + 'flag' => true, + ], ]; private $arguments = []; @@ -167,7 +173,7 @@ if (!$cli->validate()) { exit(1); } -$notifier = NotifierFactory::create(); +$notifier = new DefaultNotifier(); $notification = (new Notification()) ->setTitle($cli->getOption('title')) @@ -189,4 +195,11 @@ if ($cli->hasOption('url')) { $notification->addOption('url', $cli->getOption('url')); } -$notifier->send($notification); +$result = $notifier->send($notification); +$driver = $notifier->getDriver(); + +if ($cli->hasOption('verbose')) { + $cli->log(sprintf('Notification %s with %s. ', $result ? 'successfully sent' : 'failed', str_replace('Joli\\JoliNotif\\Driver\\', '', $driver::class))); +} + +exit($result ? 0 : 1); diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 997f00a..2babe71 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -2,6 +2,7 @@ + diff --git a/src/DefaultNotifier.php b/src/DefaultNotifier.php new file mode 100644 index 0000000..60b1e97 --- /dev/null +++ b/src/DefaultNotifier.php @@ -0,0 +1,144 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Joli\JoliNotif; + +use Joli\JoliNotif\Driver\AppleScriptDriver; +use Joli\JoliNotif\Driver\DriverInterface; +use Joli\JoliNotif\Driver\GrowlNotifyDriver; +use Joli\JoliNotif\Driver\KDialogDriver; +use Joli\JoliNotif\Driver\LibNotifyDriver; +use Joli\JoliNotif\Driver\NotifuDriver; +use Joli\JoliNotif\Driver\NotifySendDriver; +use Joli\JoliNotif\Driver\SnoreToastDriver; +use Joli\JoliNotif\Driver\TerminalNotifierDriver; +use Joli\JoliNotif\Driver\WslNotifySendDriver; +use JoliCode\PhpOsHelper\OsHelper; +use Psr\Log\LoggerInterface; +use Psr\Log\NullLogger; + +class DefaultNotifier implements NotifierInterface +{ + private readonly LoggerInterface $logger; + private ?DriverInterface $driver; + + public function __construct( + ?LoggerInterface $logger = null, + /** @var list $additionalDrivers */ + private readonly array $additionalDrivers = [], + private readonly bool $useOnlyAdditionalDrivers = false + ) { + $this->logger = $logger ?? new NullLogger(); + } + + public function getDriver(): ?DriverInterface + { + $this->loadDriver(); + + return $this->driver; + } + + public function send(Notification $notification): bool + { + $this->loadDriver(); + + if (!$this->driver) { + $this->logger->warning('No driver available to display a notification on your system.'); + + return false; + } + + return $this->driver->send($notification); + } + + protected function loadDriver(): void + { + if ($this->additionalDrivers) { + $this->doLoadDriver($this->additionalDrivers); + } + + if ($this->additionalDrivers && $this->useOnlyAdditionalDrivers) { + $this->driver ??= null; + + return; + } + + $this->doLoadDriver($this->getDefaultDrivers()); + } + + /** + * @param list $drivers + */ + private function doLoadDriver(array $drivers): void + { + if (isset($this->driver)) { + return; + } + + /** @var ?DriverInterface $bestDriver */ + $bestDriver = null; + + foreach ($drivers as $driver) { + if (!$driver->isSupported()) { + continue; + } + + if (null !== $bestDriver && $bestDriver->getPriority() >= $driver->getPriority()) { + continue; + } + + $bestDriver = $driver; + } + + $this->driver = $bestDriver; + } + + /** + * @return list + */ + private function getDefaultDrivers(): array + { + // Don't retrieve notifiers which are certainly not supported on this + // system. This helps to lower the number of process to run. + if (OsHelper::isUnix() && !OsHelper::isWindowsSubsystemForLinux()) { + return $this->getUnixDrivers(); + } + + return $this->getWindowsDrivers(); + } + + /** + * @return list + */ + private function getUnixDrivers(): array + { + return [ + new LibNotifyDriver(), + new GrowlNotifyDriver(), + new TerminalNotifierDriver(), + new AppleScriptDriver(), + new KDialogDriver(), + new NotifySendDriver(), + ]; + } + + /** + * @return list + */ + private function getWindowsDrivers(): array + { + return [ + new SnoreToastDriver(), + new NotifuDriver(), + new WslNotifySendDriver(), + ]; + } +} diff --git a/src/Driver/AbstractCliBasedDriver.php b/src/Driver/AbstractCliBasedDriver.php new file mode 100644 index 0000000..7c00157 --- /dev/null +++ b/src/Driver/AbstractCliBasedDriver.php @@ -0,0 +1,135 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Joli\JoliNotif\Driver; + +use Joli\JoliNotif\Exception\InvalidNotificationException; +use Joli\JoliNotif\Notification; +use Joli\JoliNotif\Util\PharExtractor; +use JoliCode\PhpOsHelper\OsHelper; +use Symfony\Component\Process\Process; + +/** + * @internal + */ +abstract class AbstractCliBasedDriver implements DriverInterface +{ + public const SUPPORT_NONE = -1; + public const SUPPORT_UNKNOWN = 0; + public const SUPPORT_NATIVE = 1; + public const SUPPORT_BINARY_PROVIDED = 2; + + /** + * @var int One of the SUPPORT_XXX constants + */ + private int $support = self::SUPPORT_UNKNOWN; + + public function isSupported(): bool + { + if (self::SUPPORT_UNKNOWN !== $this->support) { + return self::SUPPORT_NONE !== $this->support; + } + + if ($this->isBinaryAvailable()) { + $this->support = self::SUPPORT_NATIVE; + + return true; + } + + if ($this instanceof BinaryProviderInterface && $this->canBeUsed()) { + $this->support = self::SUPPORT_BINARY_PROVIDED; + + return true; + } + + $this->support = self::SUPPORT_NONE; + + return false; + } + + public function send(Notification $notification): bool + { + if (!$notification->getBody()) { + throw new InvalidNotificationException($notification, 'Notification body can not be empty'); + } + + $arguments = $this->getCommandLineArguments($notification); + + if (self::SUPPORT_BINARY_PROVIDED === $this->support && $this instanceof BinaryProviderInterface) { + $dir = rtrim($this->getRootDir(), '/') . '/'; + $embeddedBinary = $dir . $this->getEmbeddedBinary(); + + if (PharExtractor::isLocatedInsideAPhar($embeddedBinary)) { + $embeddedBinary = PharExtractor::extractFile($embeddedBinary); + + foreach ($this->getExtraFiles() as $file) { + PharExtractor::extractFile($dir . $file); + } + } + + $binary = $embeddedBinary; + } else { + $binary = $this->getBinary(); + } + + $process = new Process(array_merge([$binary], $arguments)); + $process->run(); + + return $this->handleExitCode($process); + } + + /** + * Configure the process to run in order to send the notification. + * + * @return list + */ + abstract protected function getCommandLineArguments(Notification $notification): array; + + /** + * Get the binary to check existence. + */ + abstract protected function getBinary(): string; + + /** + * Check whether a binary is available. + */ + protected function isBinaryAvailable(): bool + { + if (OsHelper::isUnix()) { + // Do not use the 'which' program to check if a binary exists. + // See also http://stackoverflow.com/questions/592620/check-if-a-program-exists-from-a-bash-script + $process = new Process([ + 'sh', + '-c', + 'command -v $0', + $this->getBinary(), + ]); + } else { + // 'where' is available on Windows since Server 2003 + $process = new Process([ + 'where', + $this->getBinary(), + ]); + } + + $process->run(); + + return $process->isSuccessful(); + } + + /** + * Return whether the process executed successfully. + */ + protected function handleExitCode(Process $process): bool + { + return 0 === $process->getExitCode(); + } +} diff --git a/src/Driver/AppleScriptDriver.php b/src/Driver/AppleScriptDriver.php new file mode 100644 index 0000000..5aafdec --- /dev/null +++ b/src/Driver/AppleScriptDriver.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Joli\JoliNotif\Driver; + +use Joli\JoliNotif\Notification; +use JoliCode\PhpOsHelper\OsHelper; + +/** + * This driver can be used on Mac OS X 10.9+. + * + * @internal + */ +class AppleScriptDriver extends AbstractCliBasedDriver +{ + public function isSupported(): bool + { + if (OsHelper::isMacOS() && version_compare(OsHelper::getMacOSVersion(), '10.9.0', '>=')) { + return parent::isSupported(); + } + + return false; + } + + public function getBinary(): string + { + return 'osascript'; + } + + public function getPriority(): int + { + return static::PRIORITY_LOW; + } + + protected function getCommandLineArguments(Notification $notification): array + { + $script = 'display notification "' . str_replace('"', '\\"', $notification->getBody() ?? '') . '"'; + + if ($notification->getTitle()) { + $script .= ' with title "' . str_replace('"', '\\"', $notification->getTitle()) . '"'; + } + + if ($notification->getOption('subtitle')) { + $script .= ' subtitle "' . str_replace('"', '\\"', (string) $notification->getOption('subtitle')) . '"'; + } + + if ($notification->getOption('sound')) { + $script .= ' sound name "' . str_replace('"', '\\"', (string) $notification->getOption('sound')) . '"'; + } + + return [ + '-e', + $script, + ]; + } +} diff --git a/src/Driver/BinaryProviderInterface.php b/src/Driver/BinaryProviderInterface.php new file mode 100644 index 0000000..da9a59d --- /dev/null +++ b/src/Driver/BinaryProviderInterface.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 Joli\JoliNotif\Driver; + +/** + * Interface implemented by drivers when they provide their own binaries in + * case the executable is not natively supported. + * + * @internal + */ +interface BinaryProviderInterface +{ + /** + * Return whether the embedded binary can be used on the current system. + */ + public function canBeUsed(): bool; + + /** + * Return the absolute path of the directory containing all the files. + */ + public function getRootDir(): string; + + /** + * Return the path of the embedded binary. + * + * The path should be relative to the directory pointed by getRootDir(). + */ + public function getEmbeddedBinary(): string; + + /** + * Return an array of files that should be extracted when JoliNotif is + * used inside a phar archive. + * + * All paths should be relative to the directory pointed by getRootDir(). + * + * @return list + */ + public function getExtraFiles(): array; +} diff --git a/src/Driver/DriverInterface.php b/src/Driver/DriverInterface.php new file mode 100644 index 0000000..75c4fc5 --- /dev/null +++ b/src/Driver/DriverInterface.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Joli\JoliNotif\Driver; + +use Joli\JoliNotif\Exception\ExceptionInterface; +use Joli\JoliNotif\Exception\InvalidNotificationException; +use Joli\JoliNotif\Notification; + +interface DriverInterface +{ + public const PRIORITY_LOW = 0; + public const PRIORITY_MEDIUM = 50; + public const PRIORITY_HIGH = 100; + + /** + * This method is called to check whether the driver can be used on the + * current system or not. + */ + public function isSupported(): bool; + + /** + * The supported driver with the higher priority will be preferred. + */ + public function getPriority(): int; + + /** + * Send the given notification. + * + * @throws InvalidNotificationException if the notification is invalid + * @throws ExceptionInterface if something goes wrong when sending the notification + */ + public function send(Notification $notification): bool; +} diff --git a/src/Notifier/FFI/ffi-libnotify.h b/src/Driver/FFI/ffi-libnotify.h similarity index 100% rename from src/Notifier/FFI/ffi-libnotify.h rename to src/Driver/FFI/ffi-libnotify.h diff --git a/src/Driver/GrowlNotifyDriver.php b/src/Driver/GrowlNotifyDriver.php new file mode 100644 index 0000000..3cd2acf --- /dev/null +++ b/src/Driver/GrowlNotifyDriver.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 Joli\JoliNotif\Driver; + +use Joli\JoliNotif\Notification; + +/** + * This driver can be used on Mac OS X when growlnotify command is available. + * + * @internal + */ +class GrowlNotifyDriver extends AbstractCliBasedDriver +{ + public function getBinary(): string + { + return 'growlnotify'; + } + + public function getPriority(): int + { + return static::PRIORITY_HIGH; + } + + protected function getCommandLineArguments(Notification $notification): array + { + $arguments = [ + '--message', + $notification->getBody() ?? '', + ]; + + if ($notification->getTitle()) { + $arguments[] = '--title'; + $arguments[] = $notification->getTitle(); + } + + if ($notification->getIcon()) { + $arguments[] = '--image'; + $arguments[] = $notification->getIcon(); + } + + return $arguments; + } +} diff --git a/src/Driver/KDialogDriver.php b/src/Driver/KDialogDriver.php new file mode 100644 index 0000000..442b80d --- /dev/null +++ b/src/Driver/KDialogDriver.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 Joli\JoliNotif\Driver; + +use Joli\JoliNotif\Notification; + +/** + * This driver can be used on Linux distributions running KDE, using the command kdialog. + * This command is shipped by default with KDE. + * + * @internal + */ +class KDialogDriver extends AbstractCliBasedDriver +{ + public function getBinary(): string + { + return 'kdialog'; + } + + public function getPriority(): int + { + return static::PRIORITY_HIGH; + } + + protected function getCommandLineArguments(Notification $notification): array + { + $arguments = []; + + if ($notification->getTitle()) { + $arguments[] = '--title'; + $arguments[] = $notification->getTitle(); + } + + $arguments[] = '--passivepopup'; + $arguments[] = $notification->getBody() ?? ''; + + // Timeout, in seconds + $arguments[] = 5; + + return $arguments; + } +} diff --git a/src/Driver/LibNotifyDriver.php b/src/Driver/LibNotifyDriver.php new file mode 100644 index 0000000..b0797ad --- /dev/null +++ b/src/Driver/LibNotifyDriver.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Joli\JoliNotif\Driver; + +use Joli\JoliNotif\Exception\FFIRuntimeException; +use Joli\JoliNotif\Exception\InvalidNotificationException; +use Joli\JoliNotif\Notification; +use JoliCode\PhpOsHelper\OsHelper; + +class LibNotifyDriver implements DriverInterface +{ + private const APP_NAME = 'JoliNotif'; + + private \FFI $ffi; + + public function __destruct() + { + if (isset($this->ffi)) { + $this->ffi->notify_uninit(); + } + } + + public static function isLibraryExists(): bool + { + return file_exists('/lib64/libnotify.so.4') + || file_exists('/lib/x86_64-linux-gnu/libnotify.so.4'); + } + + public function isSupported(): bool + { + return OsHelper::isUnix() + && !OsHelper::isMacOS() + && class_exists(\FFI::class) + && self::isLibraryExists(); + } + + public function getPriority(): int + { + return static::PRIORITY_HIGH; + } + + public function send(Notification $notification): bool + { + if (!$notification->getBody()) { + throw new InvalidNotificationException($notification, 'Notification body can not be empty'); + } + + $this->initialize(); + $notification = $this->ffi->notify_notification_new( + $notification->getTitle() ?? '', + $notification->getBody(), + $notification->getIcon() + ); + $value = $this->ffi->notify_notification_show($notification, null); + $this->ffi->g_object_unref($notification); + + return $value; + } + + private function initialize(): void + { + if (isset($this->ffi)) { + return; + } + + $ffi = \FFI::load(__DIR__ . '/FFI/ffi-libnotify.h'); + + if (!$ffi) { + throw new FFIRuntimeException('Unable to load libnotify'); + } + + $this->ffi = $ffi; + + if (!$this->ffi->notify_init(self::APP_NAME)) { + throw new FFIRuntimeException('Unable to initialize libnotify'); + } + + if (!$this->ffi->notify_is_initted()) { + throw new FFIRuntimeException('Libnotify has not been initialized'); + } + } +} diff --git a/src/Driver/NotifuDriver.php b/src/Driver/NotifuDriver.php new file mode 100644 index 0000000..e3d501f --- /dev/null +++ b/src/Driver/NotifuDriver.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Joli\JoliNotif\Driver; + +use Joli\JoliNotif\Notification; +use JoliCode\PhpOsHelper\OsHelper; + +/** + * This driver can be used on Windows Seven and provides its own binaries if + * not natively available. + * + * @internal + */ +class NotifuDriver extends AbstractCliBasedDriver implements BinaryProviderInterface +{ + public function getBinary(): string + { + return 'notifu'; + } + + public function getPriority(): int + { + return static::PRIORITY_LOW; + } + + public function canBeUsed(): bool + { + return OsHelper::isWindows() && OsHelper::isWindowsSeven(); + } + + public function getRootDir(): string + { + return \dirname(__DIR__, 2) . '/bin/notifu'; + } + + public function getEmbeddedBinary(): string + { + return 'notifu.exe'; + } + + public function getExtraFiles(): array + { + return []; + } + + protected function getCommandLineArguments(Notification $notification): array + { + $arguments = [ + '/m', + $notification->getBody() ?? '', + ]; + + if ($notification->getTitle()) { + $arguments[] = '/p'; + $arguments[] = $notification->getTitle(); + } + + if ($notification->getIcon()) { + $arguments[] = '/i'; + $arguments[] = $notification->getIcon(); + } + + return $arguments; + } +} diff --git a/src/Driver/NotifySendDriver.php b/src/Driver/NotifySendDriver.php new file mode 100644 index 0000000..3900f41 --- /dev/null +++ b/src/Driver/NotifySendDriver.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 Joli\JoliNotif\Driver; + +use Joli\JoliNotif\Notification; + +/** + * This driver can be used on most Linux distributions, using the command notify-send. + * This command is packaged in libnotify-bin. + * + * @internal + */ +class NotifySendDriver extends AbstractCliBasedDriver +{ + public function getBinary(): string + { + return 'notify-send'; + } + + public function getPriority(): int + { + return static::PRIORITY_MEDIUM; + } + + protected function getCommandLineArguments(Notification $notification): array + { + $arguments = []; + + if ($notification->getIcon()) { + $arguments[] = '--icon'; + $arguments[] = $notification->getIcon(); + } + + if ($notification->getTitle()) { + $arguments[] = $notification->getTitle(); + } + + $arguments[] = $notification->getBody() ?? ''; + + return $arguments; + } +} diff --git a/src/Driver/SnoreToastDriver.php b/src/Driver/SnoreToastDriver.php new file mode 100644 index 0000000..f106d84 --- /dev/null +++ b/src/Driver/SnoreToastDriver.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Joli\JoliNotif\Driver; + +use Joli\JoliNotif\Notification; +use JoliCode\PhpOsHelper\OsHelper; +use Symfony\Component\Process\Process; + +/** + * This driver can be used on Windows Eight and higher and provides its own + * binaries if not natively available. + */ +class SnoreToastDriver extends AbstractCliBasedDriver implements BinaryProviderInterface +{ + public function getBinary(): string + { + return 'snoretoast'; + } + + public function getPriority(): int + { + return static::PRIORITY_MEDIUM; + } + + public function canBeUsed(): bool + { + return + (OsHelper::isWindows() && OsHelper::isWindowsEightOrHigher()) + || OsHelper::isWindowsSubsystemForLinux(); + } + + public function getRootDir(): string + { + return \dirname(__DIR__, 2) . '/bin/snoreToast'; + } + + public function getEmbeddedBinary(): string + { + return 'snoretoast-x86.exe'; + } + + public function getExtraFiles(): array + { + return []; + } + + protected function getCommandLineArguments(Notification $notification): array + { + $arguments = [ + '-m', + $notification->getBody() ?? '', + ]; + + if ($notification->getTitle()) { + $arguments[] = '-t'; + $arguments[] = $notification->getTitle(); + } + + if ($notification->getIcon()) { + $arguments[] = '-p'; + $arguments[] = $notification->getIcon(); + } + + return $arguments; + } + + protected function handleExitCode(Process $process): bool + { + return 0 < $process->getExitCode(); + } +} diff --git a/src/Driver/TerminalNotifierDriver.php b/src/Driver/TerminalNotifierDriver.php new file mode 100644 index 0000000..f93f0fa --- /dev/null +++ b/src/Driver/TerminalNotifierDriver.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Joli\JoliNotif\Driver; + +use Joli\JoliNotif\Notification; +use JoliCode\PhpOsHelper\OsHelper; + +/** + * This driver can be used on Mac OS X 10.8, or higher, using the + * terminal-notifier binary. + * + * @internal + */ +class TerminalNotifierDriver extends AbstractCliBasedDriver +{ + public function getBinary(): string + { + return 'terminal-notifier'; + } + + public function getPriority(): int + { + return static::PRIORITY_MEDIUM; + } + + protected function getCommandLineArguments(Notification $notification): array + { + $arguments = [ + '-message', + $notification->getBody() ?? '', + ]; + + if ($notification->getTitle()) { + $arguments[] = '-title'; + $arguments[] = $notification->getTitle(); + } + + if ($notification->getIcon() && version_compare(OsHelper::getMacOSVersion(), '10.9.0', '>=')) { + $arguments[] = '-contentImage'; + $arguments[] = $notification->getIcon(); + } + + if ($notification->getOption('url')) { + $arguments[] = '-open'; + $arguments[] = (string) $notification->getOption('url'); + } + + return $arguments; + } +} diff --git a/src/Driver/WslNotifySendDriver.php b/src/Driver/WslNotifySendDriver.php new file mode 100644 index 0000000..e986739 --- /dev/null +++ b/src/Driver/WslNotifySendDriver.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Joli\JoliNotif\Driver; + +use Joli\JoliNotif\Notification; +use JoliCode\PhpOsHelper\OsHelper; + +/** + * This driver can be used on Windows Subsystem for Linux and provides notifications using the `wsl-notify-send` binary. + * + * @see https://github.com/stuartleeks/wsl-notify-send the source code of the `wsl-notify-send` binary + * + * @internal + */ +class WslNotifySendDriver extends AbstractCliBasedDriver implements BinaryProviderInterface +{ + private const APP_NAME = 'JoliNotif'; + + public function getBinary(): string + { + return 'wsl-notify-send'; + } + + public function getPriority(): int + { + return static::PRIORITY_HIGH; + } + + public function canBeUsed(): bool + { + return OsHelper::isWindowsSubsystemForLinux(); + } + + public function getRootDir(): string + { + return \dirname(__DIR__, 2) . '/bin/wsl-notify-send'; + } + + public function getEmbeddedBinary(): string + { + return 'wsl-notify-send.exe'; + } + + public function getExtraFiles(): array + { + return []; + } + + protected function getCommandLineArguments(Notification $notification): array + { + $arguments = [ + '--appId', + self::APP_NAME, + $notification->getBody() ?? '', + ]; + + if ($notification->getTitle()) { + $arguments[] = '-c'; + $arguments[] = $notification->getTitle(); + } + + return $arguments; + } +} diff --git a/src/Exception/Exception.php b/src/Exception/Exception.php index 4f20721..aaba09f 100644 --- a/src/Exception/Exception.php +++ b/src/Exception/Exception.php @@ -11,6 +11,11 @@ namespace Joli\JoliNotif\Exception; +trigger_deprecation('jolicode/jolinotif', '2.7', 'The "%s" interface is deprecated and will be removed in 3.0.', Exception::class); + +/** + * @deprecated since 2.7, will be removed in 3.0 + */ interface Exception { } diff --git a/src/Exception/ExceptionInterface.php b/src/Exception/ExceptionInterface.php new file mode 100644 index 0000000..64f5b8a --- /dev/null +++ b/src/Exception/ExceptionInterface.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Joli\JoliNotif\Exception; + +interface ExceptionInterface extends Exception, \Throwable +{ +} diff --git a/src/Exception/FFIRuntimeException.php b/src/Exception/FFIRuntimeException.php index dee848f..40ef4d3 100644 --- a/src/Exception/FFIRuntimeException.php +++ b/src/Exception/FFIRuntimeException.php @@ -11,6 +11,6 @@ namespace Joli\JoliNotif\Exception; -class FFIRuntimeException extends \RuntimeException implements Exception +class FFIRuntimeException extends \RuntimeException implements ExceptionInterface { } diff --git a/src/Exception/InvalidNotificationException.php b/src/Exception/InvalidNotificationException.php index 953a6d3..2cbc3d7 100644 --- a/src/Exception/InvalidNotificationException.php +++ b/src/Exception/InvalidNotificationException.php @@ -13,7 +13,7 @@ use Joli\JoliNotif\Notification; -class InvalidNotificationException extends \LogicException implements Exception +class InvalidNotificationException extends \LogicException implements ExceptionInterface { private Notification $notification; diff --git a/src/Exception/NoSupportedNotifierException.php b/src/Exception/NoSupportedNotifierException.php index 391d406..991e62b 100644 --- a/src/Exception/NoSupportedNotifierException.php +++ b/src/Exception/NoSupportedNotifierException.php @@ -11,6 +11,11 @@ namespace Joli\JoliNotif\Exception; +trigger_deprecation('jolicode/jolinotif', '2.7', 'The "%s" class is deprecated and will be removed in 3.0.', NoSupportedNotifierException::class); + +/** + * @deprecated since 2.7, will be removed in 3.0 + */ class NoSupportedNotifierException extends \RuntimeException implements Exception { public function __construct( diff --git a/src/LegacyNotifier.php b/src/LegacyNotifier.php new file mode 100644 index 0000000..36d743c --- /dev/null +++ b/src/LegacyNotifier.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Joli\JoliNotif; + +use Joli\JoliNotif\Driver\DriverInterface; + +trigger_deprecation('jolicode/jolinotif', '2.7', 'The "%s" class is deprecated and will be removed in 3.0. Use %s', LegacyNotifier::class, DefaultNotifier::class); + +/** + * @deprecated since 2.7, will be removed in 3.0. Use DefaultNotifier instead. + */ +class LegacyNotifier extends DefaultNotifier implements Notifier +{ + /** @param list $drivers */ + public function __construct(array $drivers) + { + parent::__construct(null, $drivers, true); + + $this->loadDriver(); + } + + public function isSupported(): bool + { + return true; + } + + public function getPriority(): int + { + return Notifier::PRIORITY_HIGH; + } +} diff --git a/src/Notifier.php b/src/Notifier.php index 2bc53ec..5f274e6 100644 --- a/src/Notifier.php +++ b/src/Notifier.php @@ -11,27 +11,13 @@ namespace Joli\JoliNotif; -interface Notifier -{ - public const PRIORITY_LOW = 0; - public const PRIORITY_MEDIUM = 50; - public const PRIORITY_HIGH = 100; - - /** - * This method is called to check whether the notifier can be used on the - * current system or not. - */ - public function isSupported(): bool; +use Joli\JoliNotif\Driver\DriverInterface; - /** - * The supported notifier with the higher priority will be preferred. - */ - public function getPriority(): int; +trigger_deprecation('jolicode/jolinotif', '2.7', 'The "%s" interface is deprecated and will be removed in 3.0. Use "%s" instead.', Notifier::class, NotifierInterface::class); - /** - * Send the given notification. - * - * @throws Exception\InvalidNotificationException if the notification is invalid - */ - public function send(Notification $notification): bool; +/** + * @deprecated since 2.7, use NotifierInterface instead + */ +interface Notifier extends NotifierInterface, DriverInterface +{ } diff --git a/src/Notifier/AppleScriptNotifier.php b/src/Notifier/AppleScriptNotifier.php index 67bdd52..379b81b 100644 --- a/src/Notifier/AppleScriptNotifier.php +++ b/src/Notifier/AppleScriptNotifier.php @@ -11,52 +11,16 @@ namespace Joli\JoliNotif\Notifier; -use Joli\JoliNotif\Notification; -use JoliCode\PhpOsHelper\OsHelper; +use Joli\JoliNotif\Driver\AppleScriptDriver; +use Joli\JoliNotif\Notifier; + +trigger_deprecation('jolicode/jolinotif', '2.7', 'The "%s" class is deprecated and will be removed in 3.0.', AppleScriptNotifier::class); /** * This notifier can be used on Mac OS X 10.9+. + * + * @deprecated since 2.7, will be removed in 3.0 */ -class AppleScriptNotifier extends CliBasedNotifier +class AppleScriptNotifier extends AppleScriptDriver implements Notifier { - public function isSupported(): bool - { - if (OsHelper::isMacOS() && version_compare(OsHelper::getMacOSVersion(), '10.9.0', '>=')) { - return parent::isSupported(); - } - - return false; - } - - public function getBinary(): string - { - return 'osascript'; - } - - public function getPriority(): int - { - return static::PRIORITY_LOW; - } - - protected function getCommandLineArguments(Notification $notification): array - { - $script = 'display notification "' . str_replace('"', '\\"', $notification->getBody() ?? '') . '"'; - - if ($notification->getTitle()) { - $script .= ' with title "' . str_replace('"', '\\"', $notification->getTitle()) . '"'; - } - - if ($notification->getOption('subtitle')) { - $script .= ' subtitle "' . str_replace('"', '\\"', (string) $notification->getOption('subtitle')) . '"'; - } - - if ($notification->getOption('sound')) { - $script .= ' sound name "' . str_replace('"', '\\"', (string) $notification->getOption('sound')) . '"'; - } - - return [ - '-e', - $script, - ]; - } } diff --git a/src/Notifier/BinaryProvider.php b/src/Notifier/BinaryProvider.php index f4d9738..190c7d7 100644 --- a/src/Notifier/BinaryProvider.php +++ b/src/Notifier/BinaryProvider.php @@ -11,9 +11,13 @@ namespace Joli\JoliNotif\Notifier; +trigger_deprecation('jolicode/jolinotif', '2.7', 'The "%s" interface is deprecated and will be removed in 3.0.', BinaryProvider::class); + /** * Interface implemented by notifiers when they provide their own binaries in * case the executable is not natively supported. + * + * @deprecated since 2.7, will be removed in 3.0 */ interface BinaryProvider { diff --git a/src/Notifier/CliBasedNotifier.php b/src/Notifier/CliBasedNotifier.php index 93d21e3..26deb6f 100644 --- a/src/Notifier/CliBasedNotifier.php +++ b/src/Notifier/CliBasedNotifier.php @@ -11,123 +11,14 @@ namespace Joli\JoliNotif\Notifier; -use Joli\JoliNotif\Exception\InvalidNotificationException; -use Joli\JoliNotif\Notification; +use Joli\JoliNotif\Driver\AbstractCliBasedDriver; use Joli\JoliNotif\Notifier; -use Joli\JoliNotif\Util\PharExtractor; -use JoliCode\PhpOsHelper\OsHelper; -use Symfony\Component\Process\Process; -abstract class CliBasedNotifier implements Notifier -{ - public const SUPPORT_NONE = -1; - public const SUPPORT_UNKNOWN = 0; - public const SUPPORT_NATIVE = 1; - public const SUPPORT_BINARY_PROVIDED = 2; - - /** - * @var int One of the SUPPORT_XXX constants - */ - private int $support = self::SUPPORT_UNKNOWN; - - public function isSupported(): bool - { - if (self::SUPPORT_UNKNOWN !== $this->support) { - return self::SUPPORT_NONE !== $this->support; - } - - if ($this->isBinaryAvailable()) { - $this->support = self::SUPPORT_NATIVE; - - return true; - } - - if ($this instanceof BinaryProvider && $this->canBeUsed()) { - $this->support = self::SUPPORT_BINARY_PROVIDED; - - return true; - } - - $this->support = self::SUPPORT_NONE; - - return false; - } - - public function send(Notification $notification): bool - { - if (!$notification->getBody()) { - throw new InvalidNotificationException($notification, 'Notification body can not be empty'); - } - - $arguments = $this->getCommandLineArguments($notification); - - if (self::SUPPORT_BINARY_PROVIDED === $this->support && $this instanceof BinaryProvider) { - $dir = rtrim($this->getRootDir(), '/') . '/'; - $embeddedBinary = $dir . $this->getEmbeddedBinary(); +trigger_deprecation('jolicode/jolinotif', '2.7', 'The "%s" class is deprecated and will be removed in 3.0.', CliBasedNotifier::class); - if (PharExtractor::isLocatedInsideAPhar($embeddedBinary)) { - $embeddedBinary = PharExtractor::extractFile($embeddedBinary); - - foreach ($this->getExtraFiles() as $file) { - PharExtractor::extractFile($dir . $file); - } - } - - $binary = $embeddedBinary; - } else { - $binary = $this->getBinary(); - } - - $process = new Process(array_merge([$binary], $arguments)); - $process->run(); - - return $this->handleExitCode($process); - } - - /** - * Configure the process to run in order to send the notification. - * - * @return list - */ - abstract protected function getCommandLineArguments(Notification $notification): array; - - /** - * Get the binary to check existence. - */ - abstract protected function getBinary(): string; - - /** - * Check whether a binary is available. - */ - protected function isBinaryAvailable(): bool - { - if (OsHelper::isUnix()) { - // Do not use the 'which' program to check if a binary exists. - // See also http://stackoverflow.com/questions/592620/check-if-a-program-exists-from-a-bash-script - $process = new Process([ - 'sh', - '-c', - 'command -v $0', - $this->getBinary(), - ]); - } else { - // 'where' is available on Windows since Server 2003 - $process = new Process([ - 'where', - $this->getBinary(), - ]); - } - - $process->run(); - - return $process->isSuccessful(); - } - - /** - * Return whether the process executed successfully. - */ - protected function handleExitCode(Process $process): bool - { - return 0 === $process->getExitCode(); - } +/** + * @deprecated since 2.7, will be removed in 3.0 + */ +abstract class CliBasedNotifier extends AbstractCliBasedDriver implements Notifier +{ } diff --git a/src/Notifier/GrowlNotifyNotifier.php b/src/Notifier/GrowlNotifyNotifier.php index e74c705..94fd5c7 100644 --- a/src/Notifier/GrowlNotifyNotifier.php +++ b/src/Notifier/GrowlNotifyNotifier.php @@ -11,40 +11,16 @@ namespace Joli\JoliNotif\Notifier; -use Joli\JoliNotif\Notification; +use Joli\JoliNotif\Driver\GrowlNotifyDriver; +use Joli\JoliNotif\Notifier; + +trigger_deprecation('jolicode/jolinotif', '2.7', 'The "%s" class is deprecated and will be removed in 3.0.', GrowlNotifyNotifier::class); /** * This notifier can be used on Mac OS X when growlnotify command is available. + * + * @deprecated since 2.7, will be removed in 3.0 */ -class GrowlNotifyNotifier extends CliBasedNotifier +class GrowlNotifyNotifier extends GrowlNotifyDriver implements Notifier { - public function getBinary(): string - { - return 'growlnotify'; - } - - public function getPriority(): int - { - return static::PRIORITY_HIGH; - } - - protected function getCommandLineArguments(Notification $notification): array - { - $arguments = [ - '--message', - $notification->getBody() ?? '', - ]; - - if ($notification->getTitle()) { - $arguments[] = '--title'; - $arguments[] = $notification->getTitle(); - } - - if ($notification->getIcon()) { - $arguments[] = '--image'; - $arguments[] = $notification->getIcon(); - } - - return $arguments; - } } diff --git a/src/Notifier/KDialogNotifier.php b/src/Notifier/KDialogNotifier.php index 9941a7b..afe661d 100644 --- a/src/Notifier/KDialogNotifier.php +++ b/src/Notifier/KDialogNotifier.php @@ -11,39 +11,17 @@ namespace Joli\JoliNotif\Notifier; -use Joli\JoliNotif\Notification; +use Joli\JoliNotif\Driver\KDialogDriver; +use Joli\JoliNotif\Notifier; + +trigger_deprecation('jolicode/jolinotif', '2.7', 'The "%s" class is deprecated and will be removed in 3.0.', KDialogNotifier::class); /** * This notifier can be used on Linux distributions running KDE, using the command kdialog. * This command is shipped by default with KDE. + * + * @deprecated since 2.7, will be removed in 3.0 */ -class KDialogNotifier extends CliBasedNotifier +class KDialogNotifier extends KDialogDriver implements Notifier { - public function getBinary(): string - { - return 'kdialog'; - } - - public function getPriority(): int - { - return static::PRIORITY_HIGH; - } - - protected function getCommandLineArguments(Notification $notification): array - { - $arguments = []; - - if ($notification->getTitle()) { - $arguments[] = '--title'; - $arguments[] = $notification->getTitle(); - } - - $arguments[] = '--passivepopup'; - $arguments[] = $notification->getBody() ?? ''; - - // Timeout, in seconds - $arguments[] = 5; - - return $arguments; - } } diff --git a/src/Notifier/LibNotifyNotifier.php b/src/Notifier/LibNotifyNotifier.php index dc1c5ce..59c71a4 100644 --- a/src/Notifier/LibNotifyNotifier.php +++ b/src/Notifier/LibNotifyNotifier.php @@ -11,82 +11,14 @@ namespace Joli\JoliNotif\Notifier; -use Joli\JoliNotif\Exception\FFIRuntimeException; -use Joli\JoliNotif\Exception\InvalidNotificationException; -use Joli\JoliNotif\Notification; +use Joli\JoliNotif\Driver\LibNotifyDriver; use Joli\JoliNotif\Notifier; -use JoliCode\PhpOsHelper\OsHelper; -class LibNotifyNotifier implements Notifier -{ - private static string $APP_NAME = 'jolinotif'; - - private \FFI $ffi; - - public function __destruct() - { - if (isset($this->ffi)) { - $this->ffi->notify_uninit(); - } - } - - public static function isLibraryExists(): bool - { - return file_exists('/lib64/libnotify.so.4') - || file_exists('/lib/x86_64-linux-gnu/libnotify.so.4'); - } - - public function isSupported(): bool - { - return OsHelper::isUnix() - && !OsHelper::isMacOS() - && class_exists(\FFI::class) - && self::isLibraryExists(); - } - - public function getPriority(): int - { - return static::PRIORITY_HIGH; - } - - public function send(Notification $notification): bool - { - if (!$notification->getBody()) { - throw new InvalidNotificationException($notification, 'Notification body can not be empty'); - } - - $this->initialize(); - $notification = $this->ffi->notify_notification_new( - $notification->getTitle() ?? '', - $notification->getBody(), - $notification->getIcon() - ); - $value = $this->ffi->notify_notification_show($notification, null); - $this->ffi->g_object_unref($notification); +trigger_deprecation('jolicode/jolinotif', '2.7', 'The "%s" class is deprecated and will be removed in 3.0.', LibNotifyNotifier::class); - return $value; - } - - private function initialize(): void - { - if (isset($this->ffi)) { - return; - } - - $ffi = \FFI::load(__DIR__ . '/FFI/ffi-libnotify.h'); - - if (!$ffi) { - throw new FFIRuntimeException('Unable to load libnotify'); - } - - $this->ffi = $ffi; - - if (!$this->ffi->notify_init(self::$APP_NAME)) { - throw new FFIRuntimeException('Unable to initialize libnotify'); - } - - if (!$this->ffi->notify_is_initted()) { - throw new FFIRuntimeException('Libnotify has not been initialized'); - } - } +/** + * @deprecated since 2.7, will be removed in 3.0 + */ +class LibNotifyNotifier extends LibNotifyDriver implements Notifier +{ } diff --git a/src/Notifier/NotifuNotifier.php b/src/Notifier/NotifuNotifier.php index a51c090..d2f8620 100644 --- a/src/Notifier/NotifuNotifier.php +++ b/src/Notifier/NotifuNotifier.php @@ -11,62 +11,17 @@ namespace Joli\JoliNotif\Notifier; -use Joli\JoliNotif\Notification; -use JoliCode\PhpOsHelper\OsHelper; +use Joli\JoliNotif\Driver\NotifuDriver; +use Joli\JoliNotif\Notifier; + +trigger_deprecation('jolicode/jolinotif', '2.7', 'The "%s" class is deprecated and will be removed in 3.0.', NotifuNotifier::class); /** * This notifier can be used on Windows Seven and provides its own binaries if * not natively available. + * + * @deprecated since 2.7, will be removed in 3.0 */ -class NotifuNotifier extends CliBasedNotifier implements BinaryProvider +class NotifuNotifier extends NotifuDriver implements Notifier { - public function getBinary(): string - { - return 'notifu'; - } - - public function getPriority(): int - { - return static::PRIORITY_LOW; - } - - public function canBeUsed(): bool - { - return OsHelper::isWindows() && OsHelper::isWindowsSeven(); - } - - public function getRootDir(): string - { - return \dirname(__DIR__, 2) . '/bin/notifu'; - } - - public function getEmbeddedBinary(): string - { - return 'notifu.exe'; - } - - public function getExtraFiles(): array - { - return []; - } - - protected function getCommandLineArguments(Notification $notification): array - { - $arguments = [ - '/m', - $notification->getBody() ?? '', - ]; - - if ($notification->getTitle()) { - $arguments[] = '/p'; - $arguments[] = $notification->getTitle(); - } - - if ($notification->getIcon()) { - $arguments[] = '/i'; - $arguments[] = $notification->getIcon(); - } - - return $arguments; - } } diff --git a/src/Notifier/NotifySendNotifier.php b/src/Notifier/NotifySendNotifier.php index 28f7152..9eed887 100644 --- a/src/Notifier/NotifySendNotifier.php +++ b/src/Notifier/NotifySendNotifier.php @@ -11,39 +11,17 @@ namespace Joli\JoliNotif\Notifier; -use Joli\JoliNotif\Notification; +use Joli\JoliNotif\Driver\NotifySendDriver; +use Joli\JoliNotif\Notifier; + +trigger_deprecation('jolicode/jolinotif', '2.7', 'The "%s" class is deprecated and will be removed in 3.0.', NotifySendNotifier::class); /** * This notifier can be used on most Linux distributions, using the command notify-send. * This command is packaged in libnotify-bin. + * + * @deprecated since 2.7, will be removed in 3.0 */ -class NotifySendNotifier extends CliBasedNotifier +class NotifySendNotifier extends NotifySendDriver implements Notifier { - public function getBinary(): string - { - return 'notify-send'; - } - - public function getPriority(): int - { - return static::PRIORITY_MEDIUM; - } - - protected function getCommandLineArguments(Notification $notification): array - { - $arguments = []; - - if ($notification->getIcon()) { - $arguments[] = '--icon'; - $arguments[] = $notification->getIcon(); - } - - if ($notification->getTitle()) { - $arguments[] = $notification->getTitle(); - } - - $arguments[] = $notification->getBody() ?? ''; - - return $arguments; - } } diff --git a/src/Notifier/NullNotifier.php b/src/Notifier/NullNotifier.php index 6b5a51c..ba379d3 100644 --- a/src/Notifier/NullNotifier.php +++ b/src/Notifier/NullNotifier.php @@ -14,6 +14,11 @@ use Joli\JoliNotif\Notification; use Joli\JoliNotif\Notifier; +trigger_deprecation('jolicode/jolinotif', '2.7', 'The "%s" class is deprecated and will be removed in 3.0.', NullNotifier::class); + +/** + * @deprecated since 2.7, will be removed in 3.0 + */ class NullNotifier implements Notifier { public function isSupported(): bool diff --git a/src/Notifier/SnoreToastNotifier.php b/src/Notifier/SnoreToastNotifier.php index 1411ad9..f8e50c7 100644 --- a/src/Notifier/SnoreToastNotifier.php +++ b/src/Notifier/SnoreToastNotifier.php @@ -11,70 +11,17 @@ namespace Joli\JoliNotif\Notifier; -use Joli\JoliNotif\Notification; -use JoliCode\PhpOsHelper\OsHelper; -use Symfony\Component\Process\Process; +use Joli\JoliNotif\Driver\SnoreToastDriver; +use Joli\JoliNotif\Notifier; + +trigger_deprecation('jolicode/jolinotif', '2.7', 'The "%s" class is deprecated and will be removed in 3.0.', SnoreToastNotifier::class); /** * This notifier can be used on Windows Eight and higher and provides its own * binaries if not natively available. + * + * @deprecated since 2.7, will be removed in 3.0 */ -class SnoreToastNotifier extends CliBasedNotifier implements BinaryProvider +class SnoreToastNotifier extends SnoreToastDriver implements Notifier { - public function getBinary(): string - { - return 'snoretoast'; - } - - public function getPriority(): int - { - return static::PRIORITY_MEDIUM; - } - - public function canBeUsed(): bool - { - return - (OsHelper::isWindows() && OsHelper::isWindowsEightOrHigher()) - || OsHelper::isWindowsSubsystemForLinux(); - } - - public function getRootDir(): string - { - return \dirname(__DIR__, 2) . '/bin/snoreToast'; - } - - public function getEmbeddedBinary(): string - { - return 'snoretoast-x86.exe'; - } - - public function getExtraFiles(): array - { - return []; - } - - protected function getCommandLineArguments(Notification $notification): array - { - $arguments = [ - '-m', - $notification->getBody() ?? '', - ]; - - if ($notification->getTitle()) { - $arguments[] = '-t'; - $arguments[] = $notification->getTitle(); - } - - if ($notification->getIcon()) { - $arguments[] = '-p'; - $arguments[] = $notification->getIcon(); - } - - return $arguments; - } - - protected function handleExitCode(Process $process): bool - { - return 0 < $process->getExitCode(); - } } diff --git a/src/Notifier/TerminalNotifierNotifier.php b/src/Notifier/TerminalNotifierNotifier.php index 5517ee7..d58e235 100644 --- a/src/Notifier/TerminalNotifierNotifier.php +++ b/src/Notifier/TerminalNotifierNotifier.php @@ -11,47 +11,17 @@ namespace Joli\JoliNotif\Notifier; -use Joli\JoliNotif\Notification; -use JoliCode\PhpOsHelper\OsHelper; +use Joli\JoliNotif\Driver\TerminalNotifierDriver; +use Joli\JoliNotif\Notifier; + +trigger_deprecation('jolicode/jolinotif', '2.7', 'The "%s" class is deprecated and will be removed in 3.0.', TerminalNotifierNotifier::class); /** * This notifier can be used on Mac OS X 10.8, or higher, using the * terminal-notifier binary. + * + * @deprecated since 2.7, will be removed in 3.0 */ -class TerminalNotifierNotifier extends CliBasedNotifier +class TerminalNotifierNotifier extends TerminalNotifierDriver implements Notifier { - public function getBinary(): string - { - return 'terminal-notifier'; - } - - public function getPriority(): int - { - return static::PRIORITY_MEDIUM; - } - - protected function getCommandLineArguments(Notification $notification): array - { - $arguments = [ - '-message', - $notification->getBody() ?? '', - ]; - - if ($notification->getTitle()) { - $arguments[] = '-title'; - $arguments[] = $notification->getTitle(); - } - - if ($notification->getIcon() && version_compare(OsHelper::getMacOSVersion(), '10.9.0', '>=')) { - $arguments[] = '-contentImage'; - $arguments[] = $notification->getIcon(); - } - - if ($notification->getOption('url')) { - $arguments[] = '-open'; - $arguments[] = $notification->getOption('url'); - } - - return $arguments; - } } diff --git a/src/Notifier/ToasterNotifier.php b/src/Notifier/ToasterNotifier.php index f4a3336..d1f936f 100644 --- a/src/Notifier/ToasterNotifier.php +++ b/src/Notifier/ToasterNotifier.php @@ -12,13 +12,16 @@ namespace Joli\JoliNotif\Notifier; use Joli\JoliNotif\Notification; +use Joli\JoliNotif\Notifier; use JoliCode\PhpOsHelper\OsHelper; +trigger_deprecation('jolicode/jolinotif', '2.3', 'The "%s" class is deprecated and will be removed in 3.0.', ToasterNotifier::class); + /** * This notifier can be used on Windows Eight and higher and provides its own * binaries if not natively available. * - * @deprecated since 2.3, use SnoreToastNotifier instead + * @deprecated since 2.3, will be removed in 3.0 */ class ToasterNotifier extends CliBasedNotifier implements BinaryProvider { diff --git a/src/Notifier/WslNotifySendNotifier.php b/src/Notifier/WslNotifySendNotifier.php index c54b2b5..6d398eb 100644 --- a/src/Notifier/WslNotifySendNotifier.php +++ b/src/Notifier/WslNotifySendNotifier.php @@ -14,10 +14,13 @@ use Joli\JoliNotif\Notification; use JoliCode\PhpOsHelper\OsHelper; -/* +trigger_deprecation('jolicode/jolinotif', '2.7', 'The "%s" class is deprecated and will be removed in 3.0.', WslNotifySendNotifier::class); + +/** * This notifier can be used on Windows Subsystem for Linux and provides notifications using the `wsl-notify-send` binary. * * @see https://github.com/stuartleeks/wsl-notify-send the source code of the `wsl-notify-send` binary + * @deprecated since 2.7, will be removed in 3.0 */ class WslNotifySendNotifier extends CliBasedNotifier implements BinaryProvider { diff --git a/src/NotifierFactory.php b/src/NotifierFactory.php index 65a626c..02ff457 100644 --- a/src/NotifierFactory.php +++ b/src/NotifierFactory.php @@ -18,12 +18,16 @@ use Joli\JoliNotif\Notifier\LibNotifyNotifier; use Joli\JoliNotif\Notifier\NotifuNotifier; use Joli\JoliNotif\Notifier\NotifySendNotifier; -use Joli\JoliNotif\Notifier\NullNotifier; use Joli\JoliNotif\Notifier\SnoreToastNotifier; use Joli\JoliNotif\Notifier\TerminalNotifierNotifier; use Joli\JoliNotif\Notifier\WslNotifySendNotifier; use JoliCode\PhpOsHelper\OsHelper; +trigger_deprecation('jolicode/jolinotif', '2.7', 'The "%s" class is deprecated and will be removed in 3.0. Use the %s class directly', NotifierFactory::class, DefaultNotifier::class); + +/** + * @deprecated since 2.7, will be removed in 3.0. Use the DefaultNotifier class directly. + */ class NotifierFactory { /** @@ -31,11 +35,7 @@ class NotifierFactory */ public static function create(array $notifiers = []): Notifier { - if (!$notifiers) { - $notifiers = static::getDefaultNotifiers(); - } - - return self::chooseBestNotifier($notifiers) ?: new NullNotifier(); + return new LegacyNotifier($notifiers); } /** @@ -43,17 +43,13 @@ public static function create(array $notifiers = []): Notifier */ public static function createOrThrowException(array $notifiers = []): Notifier { - if (empty($notifiers)) { - $notifiers = static::getDefaultNotifiers(); - } + $legacyNotifier = new LegacyNotifier($notifiers); - $bestNotifier = self::chooseBestNotifier($notifiers); - - if (!$bestNotifier) { + if (!$legacyNotifier->getDriver()) { throw new NoSupportedNotifierException(); } - return $bestNotifier; + return $legacyNotifier; } /** @@ -96,27 +92,4 @@ private static function getWindowsNotifiers(): array new WslNotifySendNotifier(), ]; } - - /** - * @param Notifier[] $notifiers - */ - private static function chooseBestNotifier(array $notifiers): ?Notifier - { - /** @var Notifier|null $bestNotifier */ - $bestNotifier = null; - - foreach ($notifiers as $notifier) { - if (!$notifier->isSupported()) { - continue; - } - - if (null !== $bestNotifier && $bestNotifier->getPriority() >= $notifier->getPriority()) { - continue; - } - - $bestNotifier = $notifier; - } - - return $bestNotifier; - } } diff --git a/src/NotifierInterface.php b/src/NotifierInterface.php new file mode 100644 index 0000000..e195cb9 --- /dev/null +++ b/src/NotifierInterface.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Joli\JoliNotif; + +interface NotifierInterface +{ + /** + * Send the given notification. + * + * @throws Exception\InvalidNotificationException if the notification is invalid + */ + public function send(Notification $notification): bool; +} diff --git a/src/Util/OsHelper.php b/src/Util/OsHelper.php index 75fbed5..4c54c23 100644 --- a/src/Util/OsHelper.php +++ b/src/Util/OsHelper.php @@ -13,6 +13,8 @@ use JoliCode\PhpOsHelper\OsHelper as BaseOsHelper; +trigger_deprecation('jolicode/jolinotif', '2.6', 'The "%s" class is deprecated and will be removed in 3.0. Use "%s" from jolicode/php-os-helper instead.', OsHelper::class, BaseOsHelper::class); + /** * @deprecated since 2.6, use OsHelper from jolicode/php-os-helper instead */ diff --git a/src/Util/PharExtractor.php b/src/Util/PharExtractor.php index 4e21581..47cf6b9 100644 --- a/src/Util/PharExtractor.php +++ b/src/Util/PharExtractor.php @@ -11,6 +11,9 @@ namespace Joli\JoliNotif\Util; +/** + * @internal + */ class PharExtractor { /** diff --git a/tests/DefaultNotifierTest.php b/tests/DefaultNotifierTest.php new file mode 100644 index 0000000..7fe4056 --- /dev/null +++ b/tests/DefaultNotifierTest.php @@ -0,0 +1,181 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Joli\JoliNotif\tests; + +use Joli\JoliNotif\DefaultNotifier; +use Joli\JoliNotif\Driver\AppleScriptDriver; +use Joli\JoliNotif\Driver\GrowlNotifyDriver; +use Joli\JoliNotif\Driver\KDialogDriver; +use Joli\JoliNotif\Driver\LibNotifyDriver; +use Joli\JoliNotif\Driver\NotifuDriver; +use Joli\JoliNotif\Driver\NotifySendDriver; +use Joli\JoliNotif\Driver\SnoreToastDriver; +use Joli\JoliNotif\Driver\TerminalNotifierDriver; +use Joli\JoliNotif\Driver\WslNotifySendDriver; +use Joli\JoliNotif\Notification; +use Joli\JoliNotif\tests\fixtures\ConfigurableDriver; +use JoliCode\PhpOsHelper\OsHelper; +use PHPUnit\Framework\TestCase; +use Psr\Log\LoggerInterface; + +/** + * @group legacy + */ +class DefaultNotifierTest extends TestCase +{ + public function testCreateDefaultNotifier() + { + $notifier = new DefaultNotifier(); + + if (OsHelper::isUnix()) { + $expectedDriverClasses = [ + LibNotifyDriver::class, + GrowlNotifyDriver::class, + TerminalNotifierDriver::class, + AppleScriptDriver::class, + KDialogDriver::class, + NotifySendDriver::class, + ]; + } else { + $expectedDriverClasses = [ + SnoreToastDriver::class, + NotifuDriver::class, + WslNotifySendDriver::class, + ]; + } + + $driver = $notifier->getDriver(); + + $this->assertContains($driver::class, $expectedDriverClasses); + } + + public function testUsesGivenDrivers() + { + $notifier = new DefaultNotifier(null, [ + new ConfigurableDriver(true), + ]); + + $driver = $notifier->getDriver(); + + $this->assertInstanceOf(ConfigurableDriver::class, $driver); + } + + public function testWithNoSupportedDriversReturnsANativeNotifier() + { + $notifier = new DefaultNotifier(null, [ + new ConfigurableDriver(false), + new ConfigurableDriver(false), + ], false); + + $driver = $notifier->getDriver(); + + $this->assertNotNull($driver); + } + + public function testWithNoSupportedDriversReturnsANullDriverIfConfiguredWithOnlyAdditionalDrivers() + { + $notifier = new DefaultNotifier(null, [ + new ConfigurableDriver(false), + new ConfigurableDriver(false), + ], true); + + $driver = $notifier->getDriver(); + + $this->assertNull($driver); + } + + public function testItUsesTheOnlySupportedDriver() + { + $expectedDriver = new ConfigurableDriver(true); + + $notifier = new DefaultNotifier(null, [ + $expectedDriver, + ]); + + $this->assertSame($expectedDriver, $notifier->getDriver()); + } + + public function testItUsesTheFirstSupportedDriverWhenNoPrioritiesAreGiven() + { + $driver1 = new ConfigurableDriver(false); + $driver2 = new ConfigurableDriver(true); + $driver3 = new ConfigurableDriver(true); + $driver4 = new ConfigurableDriver(true); + + $notifier = new DefaultNotifier(null, [ + $driver1, + $driver2, + $driver3, + $driver4, + ]); + + $this->assertSame($driver2, $notifier->getDriver()); + } + + public function testItUsesTheBestSupportedDriver() + { + $driver1 = new ConfigurableDriver(false); + $driver2 = new ConfigurableDriver(true, 5); + $driver3 = new ConfigurableDriver(true, 8); + $driver4 = new ConfigurableDriver(false); + $driver5 = new ConfigurableDriver(true, 6); + + $notifier = new DefaultNotifier(null, [ + $driver1, + $driver2, + $driver3, + $driver4, + $driver5, + ]); + + $this->assertSame($driver3, $notifier->getDriver()); + } + + public function testItUsesTheFirstOfTheBestSupportedDrivers() + { + $driver1 = new ConfigurableDriver(false); + $driver2 = new ConfigurableDriver(true, 5); + $driver3 = new ConfigurableDriver(true, 8); + $driver4 = new ConfigurableDriver(false); + $driver5 = new ConfigurableDriver(true, 8); + + $notifier = new DefaultNotifier(null, [ + $driver1, + $driver2, + $driver3, + $driver4, + $driver5, + ]); + + $this->assertSame($driver3, $notifier->getDriver()); + } + + public function testItLogsWhenNoDriverAvailable() + { + $logger = $this->createMock(LoggerInterface::class); + $logger + ->expects($this->once()) + ->method('warning') + ->with($this->equalTo('No driver available to display a notification on your system.')) + ; + + $notifier = new DefaultNotifier($logger, [ + new ConfigurableDriver(false), + ], true); + + $this->assertNull($notifier->getDriver()); + + $result = $notifier->send(new Notification()); + + $this->assertFalse($result); + } +} diff --git a/tests/Driver/AbstractCliBasedDriverTestTrait.php b/tests/Driver/AbstractCliBasedDriverTestTrait.php new file mode 100644 index 0000000..2618563 --- /dev/null +++ b/tests/Driver/AbstractCliBasedDriverTestTrait.php @@ -0,0 +1,168 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Joli\JoliNotif\tests\Driver; + +use Joli\JoliNotif\Driver\DriverInterface; +use Joli\JoliNotif\Notification; +use JoliCode\PhpOsHelper\OsHelper; +use Symfony\Component\Process\Process; + +/** + * Classes using this trait should define a BINARY constant and extend + * AbstractDriverTestCase. + */ +trait AbstractCliBasedDriverTestTrait +{ + public function testIsSupported() + { + if (OsHelper::isUnix()) { + $commandLine = 'command -v ' . static::BINARY . ' >/dev/null 2>&1'; + } else { + $commandLine = 'where ' . static::BINARY; + } + + passthru($commandLine, $return); + $supported = 0 === $return; + + $this->assertSame($supported, $this->getDriver()->isSupported()); + } + + /** + * @dataProvider provideValidNotifications + */ + public function testConfigureProcessAcceptAnyValidNotification(Notification $notification, string $expectedCommandLine) + { + try { + $arguments = $this->invokeMethod($this->getDriver(), 'getCommandLineArguments', [$notification]); + + $this->assertSame($expectedCommandLine, (new Process(array_merge([self::BINARY], $arguments)))->getCommandLine()); + } catch (\Exception $e) { + $this->fail($e->getMessage()); + } + } + + public function provideValidNotifications(): array + { + $iconDir = $this->getIconDir(); + + return [ + [ + (new Notification()) + ->setBody('I\'m the notification body'), + $this->getExpectedCommandLineForNotification(), + ], + [ + (new Notification()) + ->setBody('I\'m the notification body') + ->setTitle('I\'m the notification title'), + $this->getExpectedCommandLineForNotificationWithATitle(), + ], + [ + (new Notification()) + ->setBody('I\'m the notification body') + ->addOption('subtitle', 'I\'m the notification subtitle'), + $this->getExpectedCommandLineForNotificationWithASubtitle(), + ], + [ + (new Notification()) + ->setBody('I\'m the notification body') + ->addOption('sound', 'Frog'), + $this->getExpectedCommandLineForNotificationWithASound(), + ], + [ + (new Notification()) + ->setBody('I\'m the notification body') + ->addOption('url', 'https://google.com'), + $this->getExpectedCommandLineForNotificationWithAnUrl(), + ], + [ + (new Notification()) + ->setBody('I\'m the notification body') + ->setIcon($iconDir . '/image.gif'), + $this->getExpectedCommandLineForNotificationWithAnIcon(), + ], + [ + (new Notification()) + ->setBody('I\'m the notification body') + ->setTitle('I\'m the notification title') + ->addOption('subtitle', 'I\'m the notification subtitle') + ->addOption('sound', 'Frog') + ->addOption('url', 'https://google.com') + ->setIcon($iconDir . '/image.gif'), + $this->getExpectedCommandLineForNotificationWithAllOptions(), + ], + ]; + } + + public function testSendThrowsExceptionWhenNotificationDoesntHaveBody() + { + $driver = $this->getDriver(); + + $notification = new Notification(); + + try { + $driver->send($notification); + $this->fail('Expected a InvalidNotificationException'); + } catch (\Exception $e) { + $this->assertInstanceOf('Joli\JoliNotif\Exception\InvalidNotificationException', $e); + } + } + + public function testSendThrowsExceptionWhenNotificationHasAnEmptyBody() + { + $driver = $this->getDriver(); + + $notification = new Notification(); + $notification->setBody(''); + + try { + $driver->send($notification); + $this->fail('Expected a InvalidNotificationException'); + } catch (\Exception $e) { + $this->assertInstanceOf('Joli\JoliNotif\Exception\InvalidNotificationException', $e); + } + } + + abstract protected function getDriver(): DriverInterface; + + abstract protected function getExpectedCommandLineForNotification(): string; + + abstract protected function getExpectedCommandLineForNotificationWithATitle(): string; + + /** + * Subtitle is supported only on few driver. + */ + protected function getExpectedCommandLineForNotificationWithASubtitle(): string + { + return $this->getExpectedCommandLineForNotification(); + } + + /** + * Sound is supported only on few driver. + */ + protected function getExpectedCommandLineForNotificationWithASound(): string + { + return $this->getExpectedCommandLineForNotification(); + } + + /** + * Sound is supported only on few driver. + */ + protected function getExpectedCommandLineForNotificationWithAnUrl(): string + { + return $this->getExpectedCommandLineForNotification(); + } + + abstract protected function getExpectedCommandLineForNotificationWithAnIcon(): string; + + abstract protected function getExpectedCommandLineForNotificationWithAllOptions(): string; +} diff --git a/tests/Driver/AbstractDriverTestCase.php b/tests/Driver/AbstractDriverTestCase.php new file mode 100644 index 0000000..a92aea6 --- /dev/null +++ b/tests/Driver/AbstractDriverTestCase.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Joli\JoliNotif\tests\Driver; + +use Joli\JoliNotif\Driver\DriverInterface; +use PHPUnit\Framework\TestCase; + +abstract class AbstractDriverTestCase extends TestCase +{ + public function getIconDir(): string + { + return realpath(\dirname(__DIR__) . '/fixtures'); + } + + abstract protected function getDriver(): DriverInterface; + + /** + * Call protected/private method of a class. + * + * @param object $object instantiated object that we will run method on + * @param string $methodName Method name to call + * @param array $parameters array of parameters to pass into method + * + * @return mixed method return + */ + protected function invokeMethod($object, string $methodName, array $parameters = []) + { + $reflection = new \ReflectionClass($object::class); + $method = $reflection->getMethod($methodName); + + return $method->invokeArgs($object, $parameters); + } +} diff --git a/tests/Driver/AppleScriptDriverTest.php b/tests/Driver/AppleScriptDriverTest.php new file mode 100644 index 0000000..4a05e55 --- /dev/null +++ b/tests/Driver/AppleScriptDriverTest.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 Joli\JoliNotif\tests\Driver; + +use Joli\JoliNotif\Driver\AppleScriptDriver; +use Joli\JoliNotif\Driver\DriverInterface; +use JoliCode\PhpOsHelper\OsHelper; + +class AppleScriptDriverTest extends AbstractDriverTestCase +{ + use AbstractCliBasedDriverTestTrait; + + private const BINARY = 'osascript'; + + public function testIsSupported() + { + $driver = $this->getDriver(); + + if (OsHelper::isMacOS() && version_compare(OsHelper::getMacOSVersion(), '10.9.0', '>=')) { + $this->assertTrue($driver->isSupported()); + } else { + $this->assertFalse($driver->isSupported()); + } + } + + public function testGetBinary() + { + $driver = $this->getDriver(); + + $this->assertSame(self::BINARY, $driver->getBinary()); + } + + public function testGetPriority() + { + $driver = $this->getDriver(); + + $this->assertSame(DriverInterface::PRIORITY_LOW, $driver->getPriority()); + } + + protected function getDriver(): AppleScriptDriver + { + return new AppleScriptDriver(); + } + + protected function getExpectedCommandLineForNotification(): string + { + return <<<'CLI' + 'osascript' '-e' 'display notification "I'\''m the notification body"' + CLI; + } + + protected function getExpectedCommandLineForNotificationWithATitle(): string + { + return <<<'CLI' + 'osascript' '-e' 'display notification "I'\''m the notification body" with title "I'\''m the notification title"' + CLI; + } + + protected function getExpectedCommandLineForNotificationWithASubtitle(): string + { + return <<<'CLI' + 'osascript' '-e' 'display notification "I'\''m the notification body" subtitle "I'\''m the notification subtitle"' + CLI; + } + + protected function getExpectedCommandLineForNotificationWithASound(): string + { + return <<<'CLI' + 'osascript' '-e' 'display notification "I'\''m the notification body" sound name "Frog"' + CLI; + } + + protected function getExpectedCommandLineForNotificationWithAnIcon(): string + { + return <<<'CLI' + 'osascript' '-e' 'display notification "I'\''m the notification body"' + CLI; + } + + protected function getExpectedCommandLineForNotificationWithAllOptions(): string + { + return <<<'CLI' + 'osascript' '-e' 'display notification "I'\''m the notification body" with title "I'\''m the notification title" subtitle "I'\''m the notification subtitle" sound name "Frog"' + CLI; + } +} diff --git a/tests/Driver/BinaryProviderTestTrait.php b/tests/Driver/BinaryProviderTestTrait.php new file mode 100644 index 0000000..72d5e15 --- /dev/null +++ b/tests/Driver/BinaryProviderTestTrait.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Joli\JoliNotif\tests\Driver; + +use Joli\JoliNotif\Driver\BinaryProviderInterface; + +/** + * Classes using this trait should extend AbstractDriverTestCase. + */ +trait BinaryProviderTestTrait +{ + public function testRootDirectoryExists() + { + /** @var BinaryProviderInterface $driver */ + $driver = $this->getDriver(); + + $this->assertDirectoryExists($driver->getRootDir()); + } + + public function testEmbeddedBinaryExists() + { + /** @var BinaryProviderInterface $driver */ + $driver = $this->getDriver(); + + $this->assertFileExists($driver->getRootDir() . \DIRECTORY_SEPARATOR . $driver->getEmbeddedBinary()); + } + + public function testExtraFilesExist() + { + /** @var BinaryProviderInterface $driver */ + $driver = $this->getDriver(); + + if (!$driver->getExtraFiles()) { + // Nothing to test here + $this->addToAssertionCount(1); + + return; + } + + foreach ($driver->getExtraFiles() as $file) { + $this->assertFileExists($driver->getRootDir() . \DIRECTORY_SEPARATOR . $file); + } + } +} diff --git a/tests/Driver/GrowlNotifyDriverTest.php b/tests/Driver/GrowlNotifyDriverTest.php new file mode 100644 index 0000000..a903284 --- /dev/null +++ b/tests/Driver/GrowlNotifyDriverTest.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Joli\JoliNotif\tests\Driver; + +use Joli\JoliNotif\Driver\DriverInterface; +use Joli\JoliNotif\Driver\GrowlNotifyDriver; + +class GrowlNotifyDriverTest extends AbstractDriverTestCase +{ + use AbstractCliBasedDriverTestTrait; + + private const BINARY = 'growlnotify'; + + public function testGetBinary() + { + $driver = $this->getDriver(); + + $this->assertSame(self::BINARY, $driver->getBinary()); + } + + public function testGetPriority() + { + $driver = $this->getDriver(); + + $this->assertSame(DriverInterface::PRIORITY_HIGH, $driver->getPriority()); + } + + protected function getDriver(): GrowlNotifyDriver + { + return new GrowlNotifyDriver(); + } + + protected function getExpectedCommandLineForNotification(): string + { + return <<<'CLI' + 'growlnotify' '--message' 'I'\''m the notification body' + CLI; + } + + protected function getExpectedCommandLineForNotificationWithATitle(): string + { + return <<<'CLI' + 'growlnotify' '--message' 'I'\''m the notification body' '--title' 'I'\''m the notification title' + CLI; + } + + protected function getExpectedCommandLineForNotificationWithAnIcon(): string + { + $iconDir = $this->getIconDir(); + + return <<getIconDir(); + + return << + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Joli\JoliNotif\tests\Driver; + +use Joli\JoliNotif\Driver\DriverInterface; +use Joli\JoliNotif\Driver\KDialogDriver; + +class KDialogDriverTest extends AbstractDriverTestCase +{ + use AbstractCliBasedDriverTestTrait; + + private const BINARY = 'kdialog'; + + public function testGetBinary() + { + $driver = $this->getDriver(); + + $this->assertSame(self::BINARY, $driver->getBinary()); + } + + public function testGetPriority() + { + $driver = $this->getDriver(); + + $this->assertSame(DriverInterface::PRIORITY_HIGH, $driver->getPriority()); + } + + protected function getDriver(): KDialogDriver + { + return new KDialogDriver(); + } + + protected function getExpectedCommandLineForNotification(): string + { + return <<<'CLI' + 'kdialog' '--passivepopup' 'I'\''m the notification body' '5' + CLI; + } + + protected function getExpectedCommandLineForNotificationWithATitle(): string + { + return <<<'CLI' + 'kdialog' '--title' 'I'\''m the notification title' '--passivepopup' 'I'\''m the notification body' '5' + CLI; + } + + protected function getExpectedCommandLineForNotificationWithAnIcon(): string + { + return <<<'CLI' + 'kdialog' '--passivepopup' 'I'\''m the notification body' '5' + CLI; + } + + protected function getExpectedCommandLineForNotificationWithAllOptions(): string + { + return <<<'CLI' + 'kdialog' '--title' 'I'\''m the notification title' '--passivepopup' 'I'\''m the notification body' '5' + CLI; + } +} diff --git a/tests/Driver/LibNotifyDriverTest.php b/tests/Driver/LibNotifyDriverTest.php new file mode 100644 index 0000000..e59f0e0 --- /dev/null +++ b/tests/Driver/LibNotifyDriverTest.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Joli\JoliNotif\tests\Driver; + +use Joli\JoliNotif\Driver\DriverInterface; +use Joli\JoliNotif\Driver\LibNotifyDriver; +use Joli\JoliNotif\Exception\InvalidNotificationException; +use Joli\JoliNotif\Notification; + +class LibNotifyDriverTest extends AbstractDriverTestCase +{ + public function testGetPriority() + { + $driver = $this->getDriver(); + + $this->assertSame(DriverInterface::PRIORITY_HIGH, $driver->getPriority()); + } + + public function testSendWithEmptyBody() + { + $driver = $this->getDriver(); + + $this->expectException(InvalidNotificationException::class); + $this->expectExceptionMessage('Notification body can not be empty'); + $driver->send(new Notification()); + } + + /** + * @requires extension ffi + */ + public function testInitialize() + { + $driver = $this->getDriver(); + + if (!$driver::isLibraryExists()) { + $this->markTestSkipped('Looks like libnotify is not installed'); + } + + $this->assertTrue($driver->isSupported()); + } + + public function testSendThrowsExceptionWhenNotificationDoesntHaveBody() + { + $driver = $this->getDriver(); + + $notification = new Notification(); + + try { + $driver->send($notification); + $this->fail('Expected a InvalidNotificationException'); + } catch (\Exception $e) { + $this->assertInstanceOf('Joli\JoliNotif\Exception\InvalidNotificationException', $e); + } + } + + public function testSendThrowsExceptionWhenNotificationHasAnEmptyBody() + { + $driver = $this->getDriver(); + + $notification = new Notification(); + $notification->setBody(''); + + try { + $driver->send($notification); + $this->fail('Expected a InvalidNotificationException'); + } catch (\Exception $e) { + $this->assertInstanceOf('Joli\JoliNotif\Exception\InvalidNotificationException', $e); + } + } + + /** + * @requires extension ffi + */ + public function testSendNotificationWithAllOptions() + { + $driver = $this->getDriver(); + + $notification = (new Notification()) + ->setBody('I\'m the notification body') + ->setTitle('I\'m the notification title') + ->addOption('subtitle', 'I\'m the notification subtitle') + ->addOption('sound', 'Frog') + ->addOption('url', 'https://google.com') + ->setIcon($this->getIconDir() . '/image.gif') + ; + + $result = $driver->send($notification); + + if (!$result) { + $this->markTestSkipped('Notification was not sent'); + } + + $this->assertTrue($driver->send($notification)); + } + + protected function getDriver(): DriverInterface + { + static $driver; + + $driver ??= new LibNotifyDriver(); + + return $driver; + } +} diff --git a/tests/Driver/NotifuDriverTest.php b/tests/Driver/NotifuDriverTest.php new file mode 100644 index 0000000..80370c7 --- /dev/null +++ b/tests/Driver/NotifuDriverTest.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Joli\JoliNotif\tests\Driver; + +use Joli\JoliNotif\Driver\DriverInterface; +use Joli\JoliNotif\Driver\NotifuDriver; + +class NotifuDriverTest extends AbstractDriverTestCase +{ + use AbstractCliBasedDriverTestTrait; + use BinaryProviderTestTrait; + + private const BINARY = 'notifu'; + + public function testGetBinary() + { + $driver = $this->getDriver(); + + $this->assertSame(self::BINARY, $driver->getBinary()); + } + + public function testGetPriority() + { + $driver = $this->getDriver(); + + $this->assertSame(DriverInterface::PRIORITY_LOW, $driver->getPriority()); + } + + protected function getDriver(): NotifuDriver + { + return new NotifuDriver(); + } + + protected function getExpectedCommandLineForNotification(): string + { + return <<<'CLI' + 'notifu' '/m' 'I'\''m the notification body' + CLI; + } + + protected function getExpectedCommandLineForNotificationWithATitle(): string + { + return <<<'CLI' + 'notifu' '/m' 'I'\''m the notification body' '/p' 'I'\''m the notification title' + CLI; + } + + protected function getExpectedCommandLineForNotificationWithAnIcon(): string + { + $iconDir = $this->getIconDir(); + + return <<getIconDir(); + + return << + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Joli\JoliNotif\tests\Driver; + +use Joli\JoliNotif\Driver\DriverInterface; +use Joli\JoliNotif\Driver\NotifySendDriver; + +class NotifySendDriverTest extends AbstractDriverTestCase +{ + use AbstractCliBasedDriverTestTrait; + + private const BINARY = 'notify-send'; + + public function testGetBinary() + { + $driver = $this->getDriver(); + + $this->assertSame(self::BINARY, $driver->getBinary()); + } + + public function testGetPriority() + { + $driver = $this->getDriver(); + + $this->assertSame(DriverInterface::PRIORITY_MEDIUM, $driver->getPriority()); + } + + protected function getDriver(): NotifySendDriver + { + return new NotifySendDriver(); + } + + protected function getExpectedCommandLineForNotification(): string + { + return <<<'CLI' + 'notify-send' 'I'\''m the notification body' + CLI; + } + + protected function getExpectedCommandLineForNotificationWithATitle(): string + { + return <<<'CLI' + 'notify-send' 'I'\''m the notification title' 'I'\''m the notification body' + CLI; + } + + protected function getExpectedCommandLineForNotificationWithAnIcon(): string + { + $iconDir = $this->getIconDir(); + + return <<getIconDir(); + + return << + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Joli\JoliNotif\tests\Driver; + +use Joli\JoliNotif\Driver\DriverInterface; +use Joli\JoliNotif\Driver\SnoreToastDriver; + +class SnoreToastDriverTest extends AbstractDriverTestCase +{ + use AbstractCliBasedDriverTestTrait; + use BinaryProviderTestTrait; + + private const BINARY = 'snoretoast'; + + public function testGetBinary() + { + $driver = $this->getDriver(); + + $this->assertSame(self::BINARY, $driver->getBinary()); + } + + public function testGetPriority() + { + $driver = $this->getDriver(); + + $this->assertSame(DriverInterface::PRIORITY_MEDIUM, $driver->getPriority()); + } + + protected function getDriver(): SnoreToastDriver + { + return new SnoreToastDriver(); + } + + protected function getExpectedCommandLineForNotification(): string + { + return <<<'CLI' + 'snoretoast' '-m' 'I'\''m the notification body' + CLI; + } + + protected function getExpectedCommandLineForNotificationWithATitle(): string + { + return <<<'CLI' + 'snoretoast' '-m' 'I'\''m the notification body' '-t' 'I'\''m the notification title' + CLI; + } + + protected function getExpectedCommandLineForNotificationWithAnIcon(): string + { + $iconDir = $this->getIconDir(); + + return <<getIconDir(); + + return << + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Joli\JoliNotif\tests\Driver; + +use Joli\JoliNotif\Driver\DriverInterface; +use Joli\JoliNotif\Driver\TerminalNotifierDriver; +use JoliCode\PhpOsHelper\OsHelper; + +class TerminalNotifierDriverTest extends AbstractDriverTestCase +{ + use AbstractCliBasedDriverTestTrait; + + private const BINARY = 'terminal-notifier'; + + public function testGetBinary() + { + $driver = $this->getDriver(); + + $this->assertSame(self::BINARY, $driver->getBinary()); + } + + public function testGetPriority() + { + $driver = $this->getDriver(); + + $this->assertSame(DriverInterface::PRIORITY_MEDIUM, $driver->getPriority()); + } + + protected function getDriver(): TerminalNotifierDriver + { + return new TerminalNotifierDriver(); + } + + protected function getExpectedCommandLineForNotification(): string + { + return <<<'CLI' + 'terminal-notifier' '-message' 'I'\''m the notification body' + CLI; + } + + protected function getExpectedCommandLineForNotificationWithATitle(): string + { + return <<<'CLI' + 'terminal-notifier' '-message' 'I'\''m the notification body' '-title' 'I'\''m the notification title' + CLI; + } + + protected function getExpectedCommandLineForNotificationWithAnUrl(): string + { + return <<<'CLI' + 'terminal-notifier' '-message' 'I'\''m the notification body' '-open' 'https://google.com' + CLI; + } + + protected function getExpectedCommandLineForNotificationWithAnIcon(): string + { + if (OsHelper::isMacOS() && version_compare(OsHelper::getMacOSVersion(), '10.9.0', '>=')) { + $iconDir = $this->getIconDir(); + + return <<=')) { + $iconDir = $this->getIconDir(); + + return << + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Joli\JoliNotif\tests\Driver; + +use Joli\JoliNotif\Driver\DriverInterface; +use Joli\JoliNotif\Driver\WslNotifySendDriver; + +class WslNotifySendDriverTest extends AbstractDriverTestCase +{ + use AbstractCliBasedDriverTestTrait; + use BinaryProviderTestTrait; + + private const BINARY = 'wsl-notify-send'; + + public function testGetBinary() + { + $driver = $this->getDriver(); + + $this->assertSame(self::BINARY, $driver->getBinary()); + } + + public function testGetPriority() + { + $driver = $this->getDriver(); + + $this->assertSame(DriverInterface::PRIORITY_HIGH, $driver->getPriority()); + } + + protected function getDriver(): WslNotifySendDriver + { + return new WslNotifySendDriver(); + } + + protected function getExpectedCommandLineForNotification(): string + { + return <<<'CLI' + 'wsl-notify-send' '--appId' 'JoliNotif' 'I'\''m the notification body' + CLI; + } + + protected function getExpectedCommandLineForNotificationWithATitle(): string + { + return <<<'CLI' + 'wsl-notify-send' '--appId' 'JoliNotif' 'I'\''m the notification body' '-c' 'I'\''m the notification title' + CLI; + } + + protected function getExpectedCommandLineForNotificationWithAnIcon(): string + { + return <<<'CLI' + 'wsl-notify-send' '--appId' 'JoliNotif' 'I'\''m the notification body' + CLI; + } + + protected function getExpectedCommandLineForNotificationWithAllOptions(): string + { + return <<<'CLI' + 'wsl-notify-send' '--appId' 'JoliNotif' 'I'\''m the notification body' '-c' 'I'\''m the notification title' + CLI; + } +} diff --git a/tests/Notifier/AppleScriptNotifierTest.php b/tests/Notifier/AppleScriptNotifierTest.php index ce08f1e..8a5db26 100644 --- a/tests/Notifier/AppleScriptNotifierTest.php +++ b/tests/Notifier/AppleScriptNotifierTest.php @@ -15,6 +15,9 @@ use Joli\JoliNotif\Notifier\AppleScriptNotifier; use Joli\JoliNotif\Util\OsHelper; +/** + * @group legacy + */ class AppleScriptNotifierTest extends NotifierTestCase { use CliBasedNotifierTestTrait; diff --git a/tests/Notifier/GrowlNotifyNotifierTest.php b/tests/Notifier/GrowlNotifyNotifierTest.php index a76cc74..9659753 100644 --- a/tests/Notifier/GrowlNotifyNotifierTest.php +++ b/tests/Notifier/GrowlNotifyNotifierTest.php @@ -14,6 +14,9 @@ use Joli\JoliNotif\Notifier; use Joli\JoliNotif\Notifier\GrowlNotifyNotifier; +/** + * @group legacy + */ class GrowlNotifyNotifierTest extends NotifierTestCase { use CliBasedNotifierTestTrait; diff --git a/tests/Notifier/KDialogNotifierTest.php b/tests/Notifier/KDialogNotifierTest.php index 3903f3a..2626c4d 100644 --- a/tests/Notifier/KDialogNotifierTest.php +++ b/tests/Notifier/KDialogNotifierTest.php @@ -13,6 +13,9 @@ use Joli\JoliNotif\Notifier; +/** + * @group legacy + */ class KDialogNotifierTest extends NotifierTestCase { use CliBasedNotifierTestTrait; diff --git a/tests/Notifier/LibNotifyNotifierTest.php b/tests/Notifier/LibNotifyNotifierTest.php index b0d6c57..0539412 100644 --- a/tests/Notifier/LibNotifyNotifierTest.php +++ b/tests/Notifier/LibNotifyNotifierTest.php @@ -16,6 +16,9 @@ use Joli\JoliNotif\Notifier; use Joli\JoliNotif\Notifier\LibNotifyNotifier; +/** + * @group legacy + */ class LibNotifyNotifierTest extends NotifierTestCase { public function testGetPriority() diff --git a/tests/Notifier/NotifierTestCase.php b/tests/Notifier/NotifierTestCase.php index d8c6fe8..b64471d 100644 --- a/tests/Notifier/NotifierTestCase.php +++ b/tests/Notifier/NotifierTestCase.php @@ -14,6 +14,9 @@ use Joli\JoliNotif\Notifier; use PHPUnit\Framework\TestCase; +/** + * @group legacy + */ abstract class NotifierTestCase extends TestCase { abstract protected function getNotifier(): Notifier; diff --git a/tests/Notifier/NotifuNotifierTest.php b/tests/Notifier/NotifuNotifierTest.php index 828657b..5266fb6 100644 --- a/tests/Notifier/NotifuNotifierTest.php +++ b/tests/Notifier/NotifuNotifierTest.php @@ -14,6 +14,9 @@ use Joli\JoliNotif\Notifier; use Joli\JoliNotif\Notifier\NotifuNotifier; +/** + * @group legacy + */ class NotifuNotifierTest extends NotifierTestCase { use BinaryProviderTestTrait; diff --git a/tests/Notifier/NotifySendNotifierTest.php b/tests/Notifier/NotifySendNotifierTest.php index 70ae1c6..fd91225 100644 --- a/tests/Notifier/NotifySendNotifierTest.php +++ b/tests/Notifier/NotifySendNotifierTest.php @@ -14,6 +14,9 @@ use Joli\JoliNotif\Notifier; use Joli\JoliNotif\Notifier\NotifySendNotifier; +/** + * @group legacy + */ class NotifySendNotifierTest extends NotifierTestCase { use CliBasedNotifierTestTrait; diff --git a/tests/Notifier/NullNotifierTest.php b/tests/Notifier/NullNotifierTest.php index b59263b..53700c5 100644 --- a/tests/Notifier/NullNotifierTest.php +++ b/tests/Notifier/NullNotifierTest.php @@ -15,6 +15,9 @@ use Joli\JoliNotif\Notifier; use Joli\JoliNotif\Notifier\NullNotifier; +/** + * @group legacy + */ class NullNotifierTest extends NotifierTestCase { public function testGetPriority() diff --git a/tests/Notifier/SnoreToastNotifierTest.php b/tests/Notifier/SnoreToastNotifierTest.php index 2451e29..8e255d1 100644 --- a/tests/Notifier/SnoreToastNotifierTest.php +++ b/tests/Notifier/SnoreToastNotifierTest.php @@ -14,6 +14,9 @@ use Joli\JoliNotif\Notifier; use Joli\JoliNotif\Notifier\SnoreToastNotifier; +/** + * @group legacy + */ class SnoreToastNotifierTest extends NotifierTestCase { use BinaryProviderTestTrait; diff --git a/tests/Notifier/TerminalNotifierNotifierTest.php b/tests/Notifier/TerminalNotifierNotifierTest.php index f77eebb..0731b01 100644 --- a/tests/Notifier/TerminalNotifierNotifierTest.php +++ b/tests/Notifier/TerminalNotifierNotifierTest.php @@ -15,6 +15,9 @@ use Joli\JoliNotif\Notifier\TerminalNotifierNotifier; use Joli\JoliNotif\Util\OsHelper; +/** + * @group legacy + */ class TerminalNotifierNotifierTest extends NotifierTestCase { use CliBasedNotifierTestTrait; diff --git a/tests/Notifier/ToasterNotifierTest.php b/tests/Notifier/ToasterNotifierTest.php index 881799b..5717ffc 100644 --- a/tests/Notifier/ToasterNotifierTest.php +++ b/tests/Notifier/ToasterNotifierTest.php @@ -14,6 +14,9 @@ use Joli\JoliNotif\Notifier; use Joli\JoliNotif\Notifier\ToasterNotifier; +/** + * @group legacy + */ class ToasterNotifierTest extends NotifierTestCase { use BinaryProviderTestTrait; diff --git a/tests/Notifier/WslNotifySendNotifierTest.php b/tests/Notifier/WslNotifySendNotifierTest.php index e1d53cc..5781217 100644 --- a/tests/Notifier/WslNotifySendNotifierTest.php +++ b/tests/Notifier/WslNotifySendNotifierTest.php @@ -14,6 +14,9 @@ use Joli\JoliNotif\Notifier; use Joli\JoliNotif\Notifier\WslNotifySendNotifier; +/** + * @group legacy + */ class WslNotifySendNotifierTest extends NotifierTestCase { use BinaryProviderTestTrait; diff --git a/tests/NotifierFactoryTest.php b/tests/NotifierFactoryTest.php index c7b1254..cf08c60 100644 --- a/tests/NotifierFactoryTest.php +++ b/tests/NotifierFactoryTest.php @@ -11,22 +11,34 @@ namespace Joli\JoliNotif\tests; +use Joli\JoliNotif\Driver\AppleScriptDriver; +use Joli\JoliNotif\Driver\GrowlNotifyDriver; +use Joli\JoliNotif\Driver\KDialogDriver; +use Joli\JoliNotif\Driver\LibNotifyDriver; +use Joli\JoliNotif\Driver\NotifuDriver; +use Joli\JoliNotif\Driver\NotifySendDriver; +use Joli\JoliNotif\Driver\SnoreToastDriver; +use Joli\JoliNotif\Driver\TerminalNotifierDriver; +use Joli\JoliNotif\Driver\WslNotifySendDriver; use Joli\JoliNotif\Exception\NoSupportedNotifierException; +use Joli\JoliNotif\LegacyNotifier; use Joli\JoliNotif\Notifier\AppleScriptNotifier; use Joli\JoliNotif\Notifier\GrowlNotifyNotifier; use Joli\JoliNotif\Notifier\KDialogNotifier; use Joli\JoliNotif\Notifier\LibNotifyNotifier; use Joli\JoliNotif\Notifier\NotifuNotifier; use Joli\JoliNotif\Notifier\NotifySendNotifier; -use Joli\JoliNotif\Notifier\NullNotifier; use Joli\JoliNotif\Notifier\SnoreToastNotifier; use Joli\JoliNotif\Notifier\TerminalNotifierNotifier; use Joli\JoliNotif\Notifier\WslNotifySendNotifier; use Joli\JoliNotif\NotifierFactory; use Joli\JoliNotif\tests\fixtures\ConfigurableNotifier; -use Joli\JoliNotif\Util\OsHelper; +use JoliCode\PhpOsHelper\OsHelper; use PHPUnit\Framework\TestCase; +/** + * @group legacy + */ class NotifierFactoryTest extends TestCase { public function testGetDefaultNotifiers() @@ -57,30 +69,28 @@ public function testCreateUsesDefaultNotifiers() { $notifier = NotifierFactory::create(); - if ($notifier instanceof NullNotifier) { - $this->markTestSkipped('This test needs that at least one notifier is supported'); - } - - $this->assertInstanceOf('Joli\\JoliNotif\\Notifier', $notifier); - if (OsHelper::isUnix()) { - $expectedNotifierClasses = [ - LibNotifyNotifier::class, - GrowlNotifyNotifier::class, - TerminalNotifierNotifier::class, - AppleScriptNotifier::class, - KDialogNotifier::class, - NotifySendNotifier::class, - WslNotifySendNotifier::class, + $expectedDriverClasses = [ + LibNotifyDriver::class, + GrowlNotifyDriver::class, + TerminalNotifierDriver::class, + AppleScriptDriver::class, + KDialogDriver::class, + NotifySendDriver::class, ]; } else { - $expectedNotifierClasses = [ - SnoreToastNotifier::class, - NotifuNotifier::class, + $expectedDriverClasses = [ + SnoreToastDriver::class, + NotifuDriver::class, + WslNotifySendDriver::class, ]; } - $this->assertContains($notifier::class, $expectedNotifierClasses); + $this->assertInstanceOf(LegacyNotifier::class, $notifier); + + $driver = $notifier->getDriver(); + + $this->assertContains($driver::class, $expectedDriverClasses); } public function testCreateUsesGivenNotifiers() @@ -89,7 +99,11 @@ public function testCreateUsesGivenNotifiers() new ConfigurableNotifier(true), ]); - $this->assertInstanceOf('Joli\\JoliNotif\\tests\\fixtures\\ConfigurableNotifier', $notifier); + $this->assertInstanceOf(LegacyNotifier::class, $notifier); + + $driver = $notifier->getDriver(); + + $this->assertInstanceOf(ConfigurableNotifier::class, $driver); } public function testCreateWithNoSupportedNotifiersReturnsANullNotifier() @@ -99,7 +113,11 @@ public function testCreateWithNoSupportedNotifiersReturnsANullNotifier() new ConfigurableNotifier(false), ]); - $this->assertInstanceOf(NullNotifier::class, $notifier); + $this->assertInstanceOf(LegacyNotifier::class, $notifier); + + $driver = $notifier->getDriver(); + + $this->assertNull($driver); } public function testCreateUsesTheOnlySupportedNotifier() @@ -110,7 +128,11 @@ public function testCreateUsesTheOnlySupportedNotifier() $expectedNotifier, ]); - $this->assertSame($expectedNotifier, $notifier); + $this->assertInstanceOf(LegacyNotifier::class, $notifier); + + $driver = $notifier->getDriver(); + + $this->assertSame($expectedNotifier, $driver); } public function testCreateUsesTheFirstSupportedNotifierWhenNoPrioritiesAreGiven() @@ -127,7 +149,11 @@ public function testCreateUsesTheFirstSupportedNotifierWhenNoPrioritiesAreGiven( $notifier4, ]); - $this->assertSame($notifier2, $notifier); + $this->assertInstanceOf(LegacyNotifier::class, $notifier); + + $driver = $notifier->getDriver(); + + $this->assertSame($notifier2, $driver); } public function testCreateUsesTheBestSupportedNotifier() @@ -146,7 +172,11 @@ public function testCreateUsesTheBestSupportedNotifier() $notifier5, ]); - $this->assertSame($notifier3, $notifier); + $this->assertInstanceOf(LegacyNotifier::class, $notifier); + + $driver = $notifier->getDriver(); + + $this->assertSame($notifier3, $driver); } public function testCreateUsesTheFirstOfTheBestSupportedNotifiers() @@ -165,7 +195,11 @@ public function testCreateUsesTheFirstOfTheBestSupportedNotifiers() $notifier5, ]); - $this->assertSame($notifier3, $notifier); + $this->assertInstanceOf(LegacyNotifier::class, $notifier); + + $driver = $notifier->getDriver(); + + $this->assertSame($notifier3, $driver); } public function testCreateOrThrowExceptionWithNoSupportedNotifiersThrowsException() diff --git a/tests/Util/OsHelperTest.php b/tests/Util/OsHelperTest.php index 6c22196..02d72c3 100644 --- a/tests/Util/OsHelperTest.php +++ b/tests/Util/OsHelperTest.php @@ -14,6 +14,9 @@ use Joli\JoliNotif\Util\OsHelper; use PHPUnit\Framework\TestCase; +/** + * @group legacy + */ class OsHelperTest extends TestCase { public function testIsUnix() diff --git a/tests/fixtures/ConfigurableDriver.php b/tests/fixtures/ConfigurableDriver.php new file mode 100644 index 0000000..db3768a --- /dev/null +++ b/tests/fixtures/ConfigurableDriver.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Joli\JoliNotif\tests\fixtures; + +use Joli\JoliNotif\Driver\DriverInterface; +use Joli\JoliNotif\Notification; + +class ConfigurableDriver implements DriverInterface +{ + public function __construct( + private readonly bool $supported, + private readonly int $priority = DriverInterface::PRIORITY_MEDIUM, + private readonly bool $sendReturn = true, + ) { + } + + public function isSupported(): bool + { + return $this->supported; + } + + public function getPriority(): int + { + return $this->priority; + } + + public function send(Notification $notification): bool + { + return $this->sendReturn; + } +} diff --git a/tests/fixtures/ConfigurableNotifier.php b/tests/fixtures/ConfigurableNotifier.php index 82e381c..f8a2a35 100644 --- a/tests/fixtures/ConfigurableNotifier.php +++ b/tests/fixtures/ConfigurableNotifier.php @@ -14,6 +14,9 @@ use Joli\JoliNotif\Notification; use Joli\JoliNotif\Notifier; +/** + * @deprecated since 2.7, will be removed in 3.0. Use ConfigurableDriver instead. + */ class ConfigurableNotifier implements Notifier { private bool $supported; diff --git a/tools/phar/box.json b/tools/phar/box.json index b26fc03..dd07666 100644 --- a/tools/phar/box.json +++ b/tools/phar/box.json @@ -1,29 +1,9 @@ { "base-path": "../../", - "directories": [ - "bin", - "src" - ], "files": [ "LICENSE", "vendor/autoload.php" ], - "finder": [ - { - "name": "*.php", - "in": "vendor/composer" - }, - { - "name": "*.php", - "exclude": ["Tests"], - "in": "vendor/symfony/process" - }, - { - "name": "*.php", - "exclude": ["Tests"], - "in": "vendor/jolicode/php-os-helper" - } - ], "compactors": [ "KevinGH\\Box\\Compactor\\Php" ],