First Upload

This commit is contained in:
2024-10-19 18:23:55 +00:00
commit 9db52c11c3
11339 changed files with 1479286 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
tidelift: "packagist/jolicode/jolinotif"

View File

@@ -0,0 +1,6 @@
# Reporting a vulnerability
If you have found any issues that might have security implications, please send a report privately to pyrech@gmail.com
Do not report security reports publicly.

View File

@@ -0,0 +1,29 @@
name: 'Build the PHAR'
description: 'Build the PHAR for the current commit'
runs:
using: "composite"
steps:
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.1'
- name: Install dependencies (lib)
run: composer install --prefer-dist --no-progress --no-dev --optimize-autoloader --classmap-authoritative
shell: bash
- name: Install dependencies (PHAR builder)
run: composer install --prefer-dist --no-progress --optimize-autoloader --classmap-authoritative
shell: bash
working-directory: tools/phar
- name: Compile PHAR
run: vendor/bin/box compile
shell: bash
working-directory: tools/phar
- name: Ensure PHAR is OK
run: build/jolinotif.phar --help
shell: bash
working-directory: tools/phar

View File

@@ -0,0 +1,26 @@
name: Build the PHAR
on:
push:
branches: [ "main" ]
permissions:
contents: read
jobs:
phar:
name: Create a PHAR and upload it as an artifact
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build the PHAR
uses: ./.github/actions/phar
- name: Upload the PHAR
uses: actions/upload-artifact@v3
with:
name: 'jolinotif'
path: tools/phar/build/jolinotif.phar
if-no-files-found: error

View File

@@ -0,0 +1,110 @@
name: CI
on:
push:
branches: [main]
pull_request:
schedule:
- cron: "0 0 * * MON"
jobs:
check-cs:
name: Check Coding Standards
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: PHP-CS-Fixer
uses: docker://oskarstark/php-cs-fixer-ga
with:
args: --config=.php-cs-fixer.php --diff --dry-run
phpstan:
name: Static Analysis
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: PHPStan
uses: docker://oskarstark/phpstan-ga
env:
REQUIRE_DEV: true
ci:
name: Test PHP ${{ matrix.php-version }} ${{ matrix.name }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php-version: ["8.2", "8.3"]
composer-flags: [""]
name: [""]
include:
- php-version: 8.1
composer-flags: "--prefer-lowest"
name: "(prefer lowest dependencies)"
steps:
- name: Checkout
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: Get composer cache directory
id: composer-cache
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: Cache composer dependencies
uses: actions/cache@v3
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ github.sha }}
restore-keys: ${{ runner.os }}-composer-
- name: Install Composer dependencies
run: |
composer update --prefer-dist --no-interaction ${{ matrix.composer-flags }}
- name: Install libnotify4 for LibNotifyDriver
run: |
sudo apt-get install -y --no-install-recommends --no-install-suggests libnotify4
- name: Run Tests
run: php vendor/bin/simple-phpunit
phar:
name: Create a PHAR and ensure it is working
runs-on: ubuntu-latest
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: Execute the PHAR
run: |
tools/phar/build/jolinotif.phar --help
- name: Trigger a notification
run: |
# This command will fail because libnotify will fail to send a notification to a real desktop environment
# But we still ensure that everything else in the PHAR works
tools/phar/build/jolinotif.phar --title "Yolo" --body "Hello world!" --verbose | grep "Notification failed with LibNotifyDriver"

View File

@@ -0,0 +1,35 @@
name: Attach PHAR to the release
on:
release:
types: [created]
permissions:
contents: write
jobs:
phar_release:
name: Create a PHAR and upload it
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build the PHAR
uses: ./.github/actions/phar
- name: Get release
id: get_release
uses: bruceadams/get-release@v1.3.2
env:
GITHUB_TOKEN: ${{ github.token }}
- name: Upload release binary
uses: actions/upload-release-asset@v1.0.2
env:
GITHUB_TOKEN: ${{ github.token }}
with:
upload_url: ${{ steps.get_release.outputs.upload_url }}
asset_path: ./tools/phar/build/jolinotif.phar
asset_name: jolinotif.phar
asset_content_type: application/octet-stream

14
vendor/jolicode/jolinotif/.gitignore vendored Normal file
View File

@@ -0,0 +1,14 @@
# Dependencies
/vendor/
# Composer
/composer.lock
# Tools
/build/
/.php-cs-fixer.cache
/.phpunit.result.cache
/var/
# Castor
.castor.stub.php

View File

@@ -0,0 +1,46 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
$fileHeaderComment = <<<'EOF'
This file is part of the JoliNotif project.
(c) Loïck Piera <pyrech@gmail.com>
For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
EOF;
$finder = PhpCsFixer\Finder::create()
->in(__DIR__)
->append([
__FILE__,
'castor.php',
])
->notPath('var')
;
return (new PhpCsFixer\Config())
->setRiskyAllowed(true)
->setRules([
'@PHP74Migration' => true,
'@PhpCsFixer' => true,
'@Symfony' => true,
'@Symfony:risky' => true,
'php_unit_internal_class' => false, // From @PhpCsFixer but we don't want it
'php_unit_test_class_requires_covers' => false, // From @PhpCsFixer but we don't want it
'phpdoc_add_missing_param_annotation' => false, // From @PhpCsFixer but we don't want it
'header_comment' => ['header' => $fileHeaderComment],
'concat_space' => ['spacing' => 'one'],
'ordered_class_elements' => true, // Symfony(PSR12) override the default value, but we don't want
'blank_line_before_statement' => true, // Symfony(PSR12) override the default value, but we don't want
])
->setFinder($finder)
;

137
vendor/jolicode/jolinotif/CHANGELOG.md vendored Normal file
View File

@@ -0,0 +1,137 @@
# Changes between versions
## Not released yet
## 2.7.2 (2024-06-01)
* Changed requirement on psr/log to allow all versions from 1.0 to 3.0
## 2.7.1 (2024-05-03)
* Fixed phar for Windows drivers
* Fixed executable in verbose mode when no driver is available
* Fixed executable to better handle option passed several times
## 2.7.0 (2024-05-03)
* 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)
* Deprecated Joli\JoliNotif\Util\OsHelper in favor of jolicode/php-os-helper package
* Added support for Symfony 7.x
* Added support for PHP 8.3
* Dropped support for PHP 8.0
## 2.5.2 (2023-05-24)
* Added PHAR to GitHub releases
## 2.5.1 (2023-05-24)
* Fixed permissions on Windows notifiers binaries
## 2.5.0 (2022-12-24)
* Dropped support for PHP 7.4
* Dropped support for Symfony 4.x
* Added support for PHP 8.2
## 2.4.0 (2021-12-01)
* Dropped support for PHP < 7.4
* Dropped support for Symfony 3.x
* Added support for Symfony 6.x
## 2.3.0 (2021-03-07)
* Added SnoreToastNotifier to fix notification on Windows 8+
* Deprecated ToasterNotifier in favour of SnoreToastNotifier
* Added support for PHP 8
## 2.2.0 (2020-06-17)
* Added support for kdialog - the native notifier on KDE Desktop
## 2.1.0 (2020-01-10)
* Added support for Symfony > 5.0
* Dropped support for PHP < 7.2
## 2.0.2 (2019-02-26)
* Fixed compatibility with Windows Subsystem for Linux
* Fixed compatibility with Symfony 4.2
## 2.0.1 (2018-04-04)
* Fixed autoloader in CLI script
## 2.0.0 (2018-02-12)
* Dropped support for Symfony < 3.3
* Dropped support for PHP < 7 & HHVM
* Fixed compatibility with Symfony 4
* Added better CI configuration
* Added typehints everywhere
## 1.3.0 (2018-01-25)
* Added binary script to run jolinotif in CLI
## 1.2.0 (2017-07-17)
* Added support for sound and subtitle option on AppleScript notifier
* Added support for url option on TerminalNotifier notifier
## 1.1.2 (2017-07-13)
* Fixed compatibility with symfony/process 3.3
## 1.1.1 (2017-06-03)
* Fixed Notification icon to always use a canonical absolute path
## 1.1.0 (2017-04-04)
* Added NotifierFactory::createOrThrowException() method
* Added NullNotifier to get ride of null value
* Updated php-cs-fixer to v2.0 and fixed CS
## 1.0.5 (2016-03-22)
* Allowed Symfony 3
* Fixed quote escaping in AppleScriptNotifier
## 1.0.4 (2015-06-20)
* Fixed Mac OSX version detection
* Removed allowed failure for PHP 7 in Travis
* Added documentation for usage inside cronjobs
## 1.0.3 (2015-06-03)
* Fixed documentation typo
* Added easy example debugging
## 1.0.2 (2015-04-13)
* Fixed NotifierFactory phpdoc
* Added precision about factory returning null
## 1.0.1 (2015-04-01)
* Fixed Symfony version to 2.3
* Fixed Notifier phpdoc
* Added brand new documentation
## 1.0 (2015-03-20)
* Initial release

View File

@@ -0,0 +1,109 @@
# Contributing
First of all, **thank you** for contributing, **you are awesome**!
Everybody should be able to help. Here's how you can do it:
1. [Fork it](https://github.com/jolicode/JoliNotif/fork_select)
2. improve it
3. submit a [pull request](https://help.github.com/articles/creating-a-pull-request)
Here's some tips to make you the best contributor ever:
* [Rules](#rules)
* [Green tests](#green-tests)
* [Standard code](#standard-code)
* [Keeping your fork up-to-date](#keeping-your-fork-up-to-date)
Also, you will need to install Castor to run the tests and fix CS violations.
See [Castor's documentation](https://castor.jolicode.com/getting-started/installation/)
for more information.
To install all the dependencies and tools, run the following command:
```shell
castor install
```
## Rules
Here are a few rules to follow in order to ease code reviews, and discussions
before maintainers accept and merge your work.
* You MUST follow the [PSR-1](http://www.php-fig.org/psr/1/) and
[PSR-2](http://www.php-fig.org/psr/2/) (see [Rules](#rules)).
* You MUST run the test suite (see [Green tests](#green-tests)).
* You MUST write (or update) unit tests.
* You SHOULD write documentation.
Please, write [commit messages that make
sense](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html),
and [rebase your branch](http://git-scm.com/book/en/Git-Branching-Rebasing)
before submitting your Pull Request (see also how to [keep your
fork up-to-date](#keeping-your-fork-up-to-date)).
One may ask you to [squash your
commits](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html)
too. This is used to "clean" your Pull Request before merging it (we don't want
commits such as `fix tests`, `fix 2`, `fix 3`, etc.).
Also, while creating your Pull Request on GitHub, you MUST write a description
which gives the context and/or explains why you are creating it.
Your work will then be reviewed as soon as possible (suggestions about some
changes, improvements or alternatives may be given).
## Green tests
Run the tests using the following script:
```shell
castor phpunit
```
## Standard code
Use PHP-CS-Fixer to make your code compliant with JoliNotif's coding standards:
```shell
castor cs
```
## Static analysis
Use PHPStan to ensure the code is free of errors:
```shell
castor phpstan
```
## Keeping your fork up-to-date
To keep your fork up-to-date, you should track the upstream (original) one
using the following command:
```shell
git remote add upstream https://github.com/jolicode/JoliNotif.git
```
Then get the upstream changes:
```shell
git checkout main
git pull --rebase origin main
git pull --rebase upstream main
git checkout <your-branch>
git rebase main
```
Finally, publish your changes:
```shell
git push -f origin <your-branch>
```
Your pull request will be automatically updated.
Thank you!

19
vendor/jolicode/jolinotif/LICENSE vendored Normal file
View File

@@ -0,0 +1,19 @@
Copyright (c) 2015 Loïck Piera
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

90
vendor/jolicode/jolinotif/README.md vendored Normal file
View File

@@ -0,0 +1,90 @@
<p align="center">
<img src=doc/images/demo.gif alt="JoliNotif demo" />
</p>
<p align="center">
<a href="https://packagist.org/packages/jolicode/jolinotif"><img src="https://poser.pugx.org/jolicode/jolinotif/downloads" alt="Total Downloads"></img></a>
<a href="https://packagist.org/packages/jolicode/jolinotif"><img src="https://poser.pugx.org/jolicode/jolinotif/v/stable" alt="Latest Stable Version"></img></a>
<a href="https://packagist.org/packages/jolicode/jolinotif"><img src="https://poser.pugx.org/jolicode/jolinotif/v/unstable" alt="Latest Unstable Version"></img></a>
</p>
# About JoliNotif
JoliNotif is a cross-platform PHP library to display desktop notifications.
It works on Linux, Windows or macOS.
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
> it in your CLI scripts or in a [CRON](doc/04-cron-usage.md)
## Installation
Use [Composer](http://getcomposer.org/) to install JoliNotif in your project:
```shell
composer require "jolicode/jolinotif"
```
## Usage
```php
include __DIR__.'/vendor/autoload.php';
use Joli\JoliNotif\Notification;
use Joli\JoliNotif\DefaultNotifier;
$notifier = new DefaultNotifier();
// Create your notification
$notification =
(new 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 (AppleScriptDriver)
->addOption('sound', 'Frog') // Only works on macOS (AppleScriptDriver)
;
// Send it
$notifier->send($notification);
```
A [shell executable](doc/05-cli-usage.md) is also provided to use JoliNotif from CLI:
```shell
jolinotif --title "Hello" --body "World"
```
## Further documentation
Discover more by reading the docs:
* [Basic usage](doc/01-basic-usage.md)
* [Notification](doc/02-notification.md)
* [Drivers](doc/03-drivers.md)
* [CRON usage](doc/04-cron-usage.md)
* [CLI usage](doc/05-cli-usage.md)
You can see the current and past versions using one of the following:
* the `git tag` command
* the [releases page on Github](https://github.com/jolicode/JoliNotif/releases)
* the file listing the [changes between versions](CHANGELOG.md)
And finally some meta documentation:
* [versioning and branching models](VERSIONING.md)
* [contribution instructions](CONTRIBUTING.md)
## Credits
* [All contributors](https://github.com/jolicode/JoliNotif/graphs/contributors)
* This project was originally inspired by [mikaelbr/node-notifier](https://github.com/mikaelbr/node-notifier)
## License
JoliNotif is licensed under the MIT License - see the [LICENSE](LICENSE) file
for details.

22
vendor/jolicode/jolinotif/VERSIONING.md vendored Normal file
View File

@@ -0,0 +1,22 @@
# Versioning and branching models
This file explains the versioning and branching models of this project.
## Versioning
The versioning is inspired by [Semantic Versioning](http://semver.org/):
> Given a version number MAJOR.MINOR.PATCH, increment the:
>
> 1. MAJOR version when you make incompatible API changes
> 2. MINOR version when you add functionality in a backwards-compatible manner
> 3. PATCH version when you make backwards-compatible bug fixes
## Branching Model
The branching is inspired by [@jbenet](https://github.com/jbenet)
[simple git branching model](https://gist.github.com/jbenet/ee6c9ac48068889b0912):
> 1. `main` must always be deployable.
> 2. **all changes** are made through feature branches (pull-request + merge)
> 3. rebase to avoid/resolve conflicts; merge in to `main`

View File

@@ -0,0 +1,7 @@
# Notifu
This binary come from the [Notifu](http://www.paralint.com/projects/notifu)
project. All credits for this Windows tool goes to their original authors.
Only the required file was extracted here. If you want to fully test it, please
have a look at [this url](http://www.paralint.com/projects/notifu) instead.

Binary file not shown.

View File

@@ -0,0 +1,166 @@
// Retrieved from https://github.com/KDE/snoretoast/blob/master/COPYING.LGPL-3 version 0.7.0
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

View File

@@ -0,0 +1,7 @@
# SnoreToast
These toaster binary files come from the [KDE/snoretoast](https://github.com/KDE/snoretoast)
project. All credits for this Windows 8* application goes to [KDE project](https://github.com/KDE).
Only the required files were extracted here. If you want to fully test it,
please have a look at the github project instead.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,7 @@
# Toaster
These toaster binary files come from the [nels-o/toaster](https://github.com/nels-o/toaster)
project. All credits for this Windows 8 application goes to [nels-o](https://github.com/nels-o).
Only the required files were extracted here. If you want to fully test it,
please have a look at the github project instead.

Binary file not shown.

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Stuart Leeks
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,7 @@
# wsl-notify-send
These wsl-notify-send binary files come from the [stuartleeks/wsl-notify-send](https://github.com/stuartleeks/wsl-notify-send)
project. All credits for this Windows WSL application goes to [Stuart Leeks](https://github.com/stuartleeks).
Only the required files were extracted here. If you want to fully test it,
please have a look at the github project instead.

Binary file not shown.

36
vendor/jolicode/jolinotif/castor.php vendored Normal file
View File

@@ -0,0 +1,36 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Castor\Attribute\AsRawTokens;
use Castor\Attribute\AsTask;
use function Castor\import;
use function Castor\mount;
use function Castor\run;
import(__DIR__ . '/tools/php-cs-fixer/castor.php');
import(__DIR__ . '/tools/phpstan/castor.php');
mount(__DIR__ . '/tools/phar');
#[AsTask(description: 'Install dependencies')]
function install(): void
{
run(['composer', 'install'], workingDirectory: __DIR__);
qa\cs\install();
qa\phpstan\install();
}
#[AsTask(description: 'Run PHPUnit', ignoreValidationErrors: true)]
function phpunit(#[AsRawTokens] array $rawTokens): void
{
run(['vendor/bin/simple-phpunit', ...$rawTokens]);
}

43
vendor/jolicode/jolinotif/composer.json vendored Normal file
View File

@@ -0,0 +1,43 @@
{
"name": "jolicode/jolinotif",
"description": "Send desktop notifications on Windows, Linux, MacOS.",
"keywords": ["notification", "windows", "linux", "mac", "growl"],
"license": "MIT",
"type": "library",
"authors": [
{
"name": "Loïck Piera",
"email": "pyrech@gmail.com"
}
],
"autoload": {
"psr-4": {
"Joli\\JoliNotif\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Joli\\JoliNotif\\tests\\": "tests/"
}
},
"require": {
"php": ">=8.1",
"jolicode/php-os-helper": "^0.1.0",
"psr/log": "^1.0 || ^2.0 || ^3.0",
"symfony/process": "^5.4 || ^6.0 || ^7.0",
"symfony/deprecation-contracts": "^3"
},
"require-dev": {
"symfony/finder": "^5.4 || ^6.0 || ^7.0",
"symfony/phpunit-bridge": "^5.4 || ^6.0 || ^7.0"
},
"suggest": {
"ext-ffi": "Needed to send notifications via libnotify on Linux"
},
"bin": [
"jolinotif"
],
"config": {
"sort-packages": true
}
}

View File

@@ -0,0 +1,64 @@
# Basic usage
## Create a notifier and sending a notification
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\DefaultNotifier;
use Joli\JoliNotif\Notification;
$notifier = new DefaultNotifier();
$notifier->send(new Notification());
```
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]
> In case no driver is supported or if an error happens during notification
> sending, the send method will return false.
> [!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 and configure your notification
Create a notification is as simple as instantiating a `Notification` and
setting the option you want to use:
```php
use Joli\JoliNotif\Notification;
$notification =
(new Notification())
->setBody('The notification body')
->setTitle('The notification title')
->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 class provides a fluent API.
## Next readings
* [Notification](02-notification.md)
* [Drivers](03-drivers.md)
* [CRON usage](04-cron-usage.md)
* [CLI usage](05-cli-usage.md)
Previous pages:
* [README](../README.md)

View File

@@ -0,0 +1,84 @@
# Notification
`Notification` is the main model of JoliNotif. It contains all the options
that should be applied - if possible - by the driver.
> **Note**
> 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.
Each option can be set via a setter on the `Notification` class.
## Body
The body is the main message of the notification that you want to display.
> **Note**
> 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. Drivers will certainly display the
title above the body, in bold.
## Icon
An icon can also be displayed inside the notification. You will need to set an
**absolute path**.
```php
$notification->setIcon(__DIR__.'/Resources/icons/success.png');
```
> **Note**
> If you use JoliNotif from a phar and add a notification icon, we will take
> care to extract this icon in your system temp directory to make it accessible
> from command line executable.
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 drivers. They can be configured
through the `addOption` method.
### Subtitle
Only works with AppleScriptDriver at the moment.
```php
$notification->addOption('subtitle', 'This is a subtitle');
```
### Sound
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.
```php
$notification->addOption('sound', 'Frog');
```
### Url
Only works with TerminalNotifierDriver at the moment.
```php
$notification->addOption('url', 'https://google.com');
```
## Next readings
* [Drivers](03-drivers.md)
* [CRON usage](04-cron-usage.md)
* [CLI usage](05-cli-usage.md)
Previous pages:
* [Basic usage](01-basic-usage.md)
* [README](../README.md)

View File

@@ -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)

View File

@@ -0,0 +1,30 @@
# CRON usage
## Configuration for CRON
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.
You can achieve this by setting the `XDG_RUNTIME_DIR` environment variable in your
cronjob:
```
* * * * * XDG_RUNTIME_DIR=/run/user/$(id -u) php /path/to/your/script
```
> **Note**
> On some system, you may also/instead need to set DISPLAY env var and/or to
> run `xhost +local:`. For more information, please have a look at
> [https://help.ubuntu.com/community/CronHowto#GUI_Applications](https://help.ubuntu.com/community/CronHowto#GUI_Applications)
## Next readings
* [CLI usage](05-cli-usage.md)
Previous pages:
* [Drivers](03-drivers.md)
* [Notification](02-notification.md)
* [Basic usage](01-basic-usage.md)
* [README](../README.md)

View File

@@ -0,0 +1,62 @@
# CLI usage
## Installation
There is two ways to install JoliNotif for a CLI usage.
### Install package globally with Composer
```bash
$ composer global require jolicode/jolinotif
```
> **Note**
> Make sure to place the `~/.composer/vendor/bin` directory (or the equivalent
> directory for your OS) in your PATH so the transfer executable can be located
> by your system. Simply add this directory to your PATH in your `~/.bashrc`
> (or `~/.bash_profile`) like this:
```
$ echo "export PATH=~/.composer/vendor/bin:$PATH" >> ~/.bashrc
$ source ~/.bashrc
```
### Download the PHAR executable
You can download the latest version of JoliNotif as a PHAR file from the [releases
page](https://github.com/jolicode/JoliNotif/releases):
```bash
curl https://github.com/jolicode/JoliNotif/releases/latest/download/jolinotif.phar && sudo mv jolinotif.phar /usr/local/bin/jolinotif
```
## Usage
```bash
jolinotif --title "Awesome notification" --body "This is quite a cool cross-platform notification!"
```
To get help just run:
```bash
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.
## Next readings
Previous pages:
* [CRON usage](04-cron-usage.md)
* [Drivers](03-drivers.md)
* [Notification](02-notification.md)
* [Basic usage](01-basic-usage.md)
* [README](../README.md)

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@@ -0,0 +1,35 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Joli\JoliNotif\DefaultNotifier;
use Joli\JoliNotif\Notification;
require __DIR__ . '/../vendor/autoload.php';
$notifier = new DefaultNotifier();
if (!$notifier->getDriver()) {
echo 'No supported notifier', \PHP_EOL;
exit(1);
}
$notification =
(new Notification())
->setTitle('Notification example')
->setBody('This is a notification example. Pretty cool isn\'t it?')
->setIcon(__DIR__ . '/icon-success.png')
;
$result = $notifier->send($notification);
$driver = $notifier->getDriver();
echo 'Notification ', $result ? 'successfully sent' : 'failed', ' with ', str_replace('Joli\\JoliNotif\\Driver\\', '', $driver::class), \PHP_EOL;

233
vendor/jolicode/jolinotif/jolinotif vendored Executable file
View File

@@ -0,0 +1,233 @@
#!/usr/bin/env php
<?php
use Joli\JoliNotif\DefaultNotifier;
use Joli\JoliNotif\Notification;
if (file_exists(__DIR__ . '/vendor/autoload.php')) {
$loader = require(__DIR__ . '/vendor/autoload.php');
} elseif (file_exists(__DIR__ . '/../../../vendor/autoload.php')) {
$loader = require(__DIR__ . '/../../../vendor/autoload.php');
} else {
throw new \RuntimeException('Unable to load autoloader.');
}
final class Cli
{
const DESCRIPTION = 'Send notifications to your desktop directly from your terminal.';
const RULES = [
'title' => [
'name' => 'title',
'info' => 'Notification title.',
'required' => true,
],
'body' => [
'name' => 'body',
'info' => 'Notification body.',
'required' => true,
],
'icon' => [
'name' => 'icon',
'info' => 'Notification icon.',
'required' => false,
],
'subtitle' => [
'name' => 'subtitle',
'info' => 'Notification subtitle. Only works on macOS (AppleScriptNotifier).',
'required' => false,
],
'sound' => [
'name' => 'sound',
'info' => 'Notification sound. Only works on macOS (AppleScriptNotifier).',
'required' => false,
],
'url' => [
'name' => 'url',
'info' => 'Notification url. Only works on macOS (TerminalNotifierNotifier).',
'required' => false,
],
'help' => [
'name' => 'help',
'info' => 'Show this help.',
'required' => false,
'flag' => true,
],
'verbose' => [
'name' => 'verbose',
'info' => 'Output debug information.',
'required' => false,
'flag' => true,
],
];
/** @var array<string, mixed> */
private array $arguments = [];
private readonly string $command;
public function __construct()
{
$this->command = $_SERVER['argv'][0];
}
public function parse(): void
{
$options = '';
$longOptions = array_map(function ($rule) {
$flag = $rule['required'] ? ':' : '::';
return $rule['name'] . $flag;
}, self::RULES);
$this->arguments = getopt($options, $longOptions) ?: [];
}
public function getOption(string $name): mixed
{
return $this->arguments[$name] ?: false;
}
public function getStringOption(string $name): string
{
$option = $this->getOption($name);
if (is_array($option)) {
throw new Exception("Option --{$name} can be specified only once.");
}
if (!is_string($option) && !is_numeric($option)) {
// Probably not possible to reach this point
throw new Exception("Invalid type given for option --{$name}.");
}
return (string) $option;
}
public function hasOption(string $name): bool
{
return isset($this->arguments[$name]);
}
public function validate(): bool
{
$valid = true;
foreach (self::RULES as $rule) {
if ($rule['required'] && !$this->hasOption($rule['name'])) {
$this->log("Please specify notification {$rule['name']} with the option --{$rule['name']}");
$valid = false;
}
if ($this->hasOption($rule['name']) && is_array($this->getOption($rule['name']))) {
$this->log("Option --{$rule['name']} can be specified only once.");
$valid = false;
}
}
return $valid;
}
public function showUsage(): void
{
$required = [];
$optional = [];
$usage = $this->command;
foreach (self::RULES as $name => $rule) {
$prefix = $postfix = '';
if ($rule['required']) {
$required[$name] = $rule;
} else {
$optional[$name] = $rule;
$prefix = '[';
$postfix = ']';
}
$usage .= ' ' . $prefix . $this->formatUsage($name, $rule) . $postfix;
}
$this->log(self::DESCRIPTION);
$this->log(PHP_EOL . 'Usage: ' . trim($usage));
$this->log(PHP_EOL . 'Required Arguments:');
foreach ($required as $name => $info) {
$value = $this->formatUsage($name, $info);
$this->log("\t{$value}");
$this->log("\t\t{$info['info']}");
}
$this->log(PHP_EOL . 'Optional Arguments:');
foreach ($optional as $name => $info) {
$value = $this->formatUsage($name, $info);
$this->log("\t{$value}");
$this->log("\t\t{$info['info']}");
}
}
public function log(string $message): void
{
echo $message . PHP_EOL;
}
/** @param array{name: string, info: string, required: bool, flag?: bool} $rule */
private function formatUsage(string $name, array $rule): string
{
$example = $rule['required'] ? " {$name}" : "=\"{$name}\"";
$value = isset($rule['flag']) && $rule['flag'] ? '' : $example;
return '--' . $name . $value;
}
}
$cli = new Cli();
try {
$cli->parse();
} catch (Exception $e) {
$cli->log($e->getMessage());
exit(1);
}
if ($cli->hasOption('help')) {
$cli->showUsage();
exit(0);
}
if (!$cli->validate()) {
exit(1);
}
$notifier = new DefaultNotifier();
$notification = (new Notification())
->setTitle($cli->getStringOption('title'))
->setBody($cli->getStringOption('body'));
if ($cli->hasOption('icon')) {
$notification->setIcon($cli->getStringOption('icon'));
}
if ($cli->hasOption('subtitle')) {
$notification->addOption('subtitle', $cli->getStringOption('subtitle'));
}
if ($cli->hasOption('sound')) {
$notification->addOption('sound', $cli->getStringOption('sound'));
}
if ($cli->hasOption('url')) {
$notification->addOption('url', $cli->getStringOption('url'));
}
$result = $notifier->send($notification);
$driver = $notifier->getDriver();
if ($cli->hasOption('verbose')) {
if (!$driver) {
$cli->log('No driver available to display a notification on your system.');
} else {
$cli->log(sprintf('Notification %s with %s. ', $result ? 'successfully sent' : 'failed', str_replace('Joli\\JoliNotif\\Driver\\', '', $driver::class)));
}
}
exit($result ? 0 : 1);

15
vendor/jolicode/jolinotif/phpstan.neon vendored Normal file
View File

@@ -0,0 +1,15 @@
includes:
#- phpstan-baseline.neon
parameters:
level: 9
paths:
- src
- jolinotif
tmpDir: 'var/phpstan/tmp'
inferPrivatePropertyTypeFromConstructor: true
checkGenericClassInNonGenericObjectType: false
excludePaths:
analyse: []
ignoreErrors:
- '#Call to an undefined method FFI::.+#'

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd" backupGlobals="false" colors="true" bootstrap="vendor/autoload.php">
<php>
<ini name="error_reporting" value="-1"/>
<env name="SYMFONY_DEPRECATIONS_HELPER" value="max[self]=13"/>
</php>
<testsuites>
<testsuite name="JoliNotif Test Suite">
<directory>./tests/</directory>
</testsuite>
</testsuites>
<coverage>
<include>
<directory>./</directory>
</include>
<exclude>
<directory>./vendor</directory>
</exclude>
</coverage>
</phpunit>

View File

@@ -0,0 +1,144 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* 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<DriverInterface> $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<DriverInterface> $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<DriverInterface>
*/
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<DriverInterface>
*/
private function getUnixDrivers(): array
{
return [
new LibNotifyDriver(),
new GrowlNotifyDriver(),
new TerminalNotifierDriver(),
new AppleScriptDriver(),
new KDialogDriver(),
new NotifySendDriver(),
];
}
/**
* @return list<DriverInterface>
*/
private function getWindowsDrivers(): array
{
return [
new SnoreToastDriver(),
new NotifuDriver(),
new WslNotifySendDriver(),
];
}
}

View File

@@ -0,0 +1,135 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* 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<string|int>
*/
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();
}
}

View File

@@ -0,0 +1,64 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* 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,
];
}
}

View File

@@ -0,0 +1,48 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* 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<string>
*/
public function getExtraFiles(): array;
}

View File

@@ -0,0 +1,45 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* 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;
/**
* @internal
*/
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;
}

View File

@@ -0,0 +1,13 @@
#define FFI_LIB "libnotify.so.4"
typedef bool gboolean;
typedef void* gpointer;
typedef struct _NotifyNotification NotifyNotification;
typedef struct _GTypeInstanceError GError;
gboolean notify_init(const char *app_name);
gboolean notify_is_initted (void);
void notify_uninit (void);
NotifyNotification *notify_notification_new(const char *summary, const char *body, const char *icon);
gboolean notify_notification_show (NotifyNotification *notification, GError **error);
void g_object_unref (gpointer object);

View File

@@ -0,0 +1,52 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* 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;
}
}

View File

@@ -0,0 +1,51 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* 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;
}
}

View File

@@ -0,0 +1,103 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* 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 Joli\JoliNotif\Util\PharExtractor;
use JoliCode\PhpOsHelper\OsHelper;
/**
* This driver can be used on Linux systems when libnotify and FFI are available.
*
* @internal
*/
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;
}
$headerFile = __DIR__ . '/FFI/ffi-libnotify.h';
if (PharExtractor::isLocatedInsideAPhar($headerFile)) {
$headerFile = PharExtractor::extractFile($headerFile);
}
$ffi = \FFI::load($headerFile);
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');
}
}
}

View File

@@ -0,0 +1,74 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* 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;
}
}

View File

@@ -0,0 +1,51 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* 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;
}
}

View File

@@ -0,0 +1,82 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* 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.
*
* @internal
*/
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();
}
}

View File

@@ -0,0 +1,59 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* 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;
}
}

View File

@@ -0,0 +1,73 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* 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;
}
}

View File

@@ -0,0 +1,21 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
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
{
}

View File

@@ -0,0 +1,16 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* 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
{
}

View File

@@ -0,0 +1,16 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Joli\JoliNotif\Exception;
class FFIRuntimeException extends \RuntimeException implements ExceptionInterface
{
}

View File

@@ -0,0 +1,35 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Joli\JoliNotif\Exception;
use Joli\JoliNotif\Notification;
class InvalidNotificationException extends \LogicException implements ExceptionInterface
{
private Notification $notification;
public function __construct(
Notification $notification,
string $message = '',
int $code = 0,
?\Throwable $previous = null
) {
$this->notification = $notification;
parent::__construct($message, $code, $previous);
}
public function getNotification(): Notification
{
return $this->notification;
}
}

View File

@@ -0,0 +1,28 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
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(
string $message = 'No supported notifier',
int $code = 0,
?\Throwable $previous = null
) {
parent::__construct($message, $code, $previous);
}
}

View File

@@ -0,0 +1,40 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* 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<DriverInterface> $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;
}
}

View File

@@ -0,0 +1,82 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* 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\Util\PharExtractor;
use JoliCode\PhpOsHelper\OsHelper;
class Notification
{
private ?string $title = null;
private ?string $body = null;
private ?string $icon = null;
/** @var array<string, string|int> */
private array $options = [];
public function getTitle(): ?string
{
return $this->title;
}
public function setTitle(string $title): self
{
$this->title = $title;
return $this;
}
public function getBody(): ?string
{
return $this->body;
}
public function setBody(string $body): self
{
$this->body = $body;
return $this;
}
public function getIcon(): ?string
{
return $this->icon;
}
public function setIcon(string $icon): self
{
// This makes the icon accessible for native commands when it's embedded inside a phar
if (PharExtractor::isLocatedInsideAPhar($icon)) {
$icon = PharExtractor::extractFile($icon);
} else {
// Makes the icon path absolute (expanding all symbolic links and resolving references like "/../")
$icon = OsHelper::isWindowsSubsystemForLinux()
? preg_replace('/^\/mnt\/([a-z])\//', '$1:\\', $icon, 1)
: realpath($icon);
}
$this->icon = \is_string($icon) ? $icon : null;
return $this;
}
public function getOption(string $key): string|int|null
{
return $this->options[$key] ?? null;
}
public function addOption(string $key, string|int $option): self
{
$this->options[$key] = $option;
return $this;
}
}

View File

@@ -0,0 +1,23 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* 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" interface is deprecated and will be removed in 3.0. Use "%s" instead.', Notifier::class, NotifierInterface::class);
/**
* @deprecated since 2.7, use NotifierInterface instead
*/
interface Notifier extends NotifierInterface, DriverInterface
{
}

View File

@@ -0,0 +1,26 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Joli\JoliNotif\Notifier;
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 AppleScriptDriver implements Notifier
{
}

View File

@@ -0,0 +1,50 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
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
{
/**
* 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<string>
*/
public function getExtraFiles(): array;
}

View File

@@ -0,0 +1,24 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Joli\JoliNotif\Notifier;
use Joli\JoliNotif\Driver\AbstractCliBasedDriver;
use Joli\JoliNotif\Notifier;
trigger_deprecation('jolicode/jolinotif', '2.7', 'The "%s" class is deprecated and will be removed in 3.0.', CliBasedNotifier::class);
/**
* @deprecated since 2.7, will be removed in 3.0
*/
abstract class CliBasedNotifier extends AbstractCliBasedDriver implements Notifier
{
}

View File

@@ -0,0 +1,26 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Joli\JoliNotif\Notifier;
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 GrowlNotifyDriver implements Notifier
{
}

View File

@@ -0,0 +1,27 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Joli\JoliNotif\Notifier;
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 KDialogDriver implements Notifier
{
}

View File

@@ -0,0 +1,24 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Joli\JoliNotif\Notifier;
use Joli\JoliNotif\Driver\LibNotifyDriver;
use Joli\JoliNotif\Notifier;
trigger_deprecation('jolicode/jolinotif', '2.7', 'The "%s" class is deprecated and will be removed in 3.0.', LibNotifyNotifier::class);
/**
* @deprecated since 2.7, will be removed in 3.0
*/
class LibNotifyNotifier extends LibNotifyDriver implements Notifier
{
}

View File

@@ -0,0 +1,27 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Joli\JoliNotif\Notifier;
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 NotifuDriver implements Notifier
{
}

View File

@@ -0,0 +1,27 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Joli\JoliNotif\Notifier;
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 NotifySendDriver implements Notifier
{
}

View File

@@ -0,0 +1,38 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Joli\JoliNotif\Notifier;
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
{
return true;
}
public function getPriority(): int
{
return static::PRIORITY_LOW;
}
public function send(Notification $notification): bool
{
return false;
}
}

View File

@@ -0,0 +1,27 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Joli\JoliNotif\Notifier;
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 SnoreToastDriver implements Notifier
{
}

View File

@@ -0,0 +1,27 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Joli\JoliNotif\Notifier;
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 TerminalNotifierDriver implements Notifier
{
}

View File

@@ -0,0 +1,82 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
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, will be removed in 3.0
*/
class ToasterNotifier extends CliBasedNotifier implements BinaryProvider
{
public function getBinary(): string
{
return 'toast';
}
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/toaster';
}
public function getEmbeddedBinary(): string
{
return 'toast.exe';
}
public function getExtraFiles(): array
{
return [
'Microsoft.WindowsAPICodePack.dll',
'Microsoft.WindowsAPICodePack.Shell.dll',
];
}
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;
}
}

View File

@@ -0,0 +1,72 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Joli\JoliNotif\Notifier;
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
{
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',
'JoliNotif',
$notification->getBody() ?? '',
];
if ($notification->getTitle()) {
$arguments[] = '-c';
$arguments[] = $notification->getTitle();
}
return $arguments;
}
}

View File

@@ -0,0 +1,95 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* 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\Exception\NoSupportedNotifierException;
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\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
{
/**
* @param Notifier[] $notifiers
*/
public static function create(array $notifiers = []): Notifier
{
return new LegacyNotifier($notifiers);
}
/**
* @param Notifier[] $notifiers
*/
public static function createOrThrowException(array $notifiers = []): Notifier
{
$legacyNotifier = new LegacyNotifier($notifiers);
if (!$legacyNotifier->getDriver()) {
throw new NoSupportedNotifierException();
}
return $legacyNotifier;
}
/**
* @return Notifier[]
*/
public static function getDefaultNotifiers(): 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 self::getUnixNotifiers();
}
return self::getWindowsNotifiers();
}
/**
* @return Notifier[]
*/
private static function getUnixNotifiers(): array
{
return [
new LibNotifyNotifier(),
new GrowlNotifyNotifier(),
new TerminalNotifierNotifier(),
new AppleScriptNotifier(),
new KDialogNotifier(),
new NotifySendNotifier(),
];
}
/**
* @return Notifier[]
*/
private static function getWindowsNotifiers(): array
{
return [
new SnoreToastNotifier(),
new NotifuNotifier(),
new WslNotifySendNotifier(),
];
}
}

View File

@@ -0,0 +1,22 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* 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;
}

View File

@@ -0,0 +1,23 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Joli\JoliNotif\Util;
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
*/
class OsHelper extends BaseOsHelper
{
}

View File

@@ -0,0 +1,51 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Joli\JoliNotif\Util;
/**
* @internal
*/
class PharExtractor
{
/**
* Return whether the file path is located inside a phar.
*/
public static function isLocatedInsideAPhar(string $filePath): bool
{
return str_starts_with($filePath, 'phar://');
}
/**
* Extract the file from the phar archive to make it accessible for native commands.
*
* The absolute file path to extract should be passed in the first argument.
*/
public static function extractFile(string $filePath, bool $overwrite = false): string
{
$pharPath = \Phar::running(false);
if (!$pharPath) {
return '';
}
$relativeFilePath = substr($filePath, strpos($filePath, $pharPath) + \strlen($pharPath) + 1);
$tmpDir = sys_get_temp_dir() . '/jolinotif';
$extractedFilePath = $tmpDir . '/' . $relativeFilePath;
if (!file_exists($extractedFilePath) || $overwrite) {
$phar = new \Phar($pharPath);
$phar->extractTo($tmpDir, $relativeFilePath, $overwrite);
}
return $extractedFilePath;
}
}

View File

@@ -0,0 +1,181 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* 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);
}
}

View File

@@ -0,0 +1,168 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* 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;
}

View File

@@ -0,0 +1,42 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* 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);
}
}

View File

@@ -0,0 +1,95 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* 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;
}
}

View File

@@ -0,0 +1,53 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* 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);
}
}
}

View File

@@ -0,0 +1,73 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* 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 <<<CLI
'growlnotify' '--message' 'I'\\''m the notification body' '--image' '{$iconDir}/image.gif'
CLI;
}
protected function getExpectedCommandLineForNotificationWithAllOptions(): string
{
$iconDir = $this->getIconDir();
return <<<CLI
'growlnotify' '--message' 'I'\\''m the notification body' '--title' 'I'\\''m the notification title' '--image' '{$iconDir}/image.gif'
CLI;
}
}

View File

@@ -0,0 +1,69 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* 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;
}
}

View File

@@ -0,0 +1,113 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* 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;
}
}

View File

@@ -0,0 +1,74 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* 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 <<<CLI
'notifu' '/m' 'I'\\''m the notification body' '/i' '{$iconDir}/image.gif'
CLI;
}
protected function getExpectedCommandLineForNotificationWithAllOptions(): string
{
$iconDir = $this->getIconDir();
return <<<CLI
'notifu' '/m' 'I'\\''m the notification body' '/p' 'I'\\''m the notification title' '/i' '{$iconDir}/image.gif'
CLI;
}
}

View File

@@ -0,0 +1,73 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* 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 <<<CLI
'notify-send' '--icon' '{$iconDir}/image.gif' 'I'\\''m the notification body'
CLI;
}
protected function getExpectedCommandLineForNotificationWithAllOptions(): string
{
$iconDir = $this->getIconDir();
return <<<CLI
'notify-send' '--icon' '{$iconDir}/image.gif' 'I'\\''m the notification title' 'I'\\''m the notification body'
CLI;
}
}

View File

@@ -0,0 +1,74 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* 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 <<<CLI
'snoretoast' '-m' 'I'\\''m the notification body' '-p' '{$iconDir}/image.gif'
CLI;
}
protected function getExpectedCommandLineForNotificationWithAllOptions(): string
{
$iconDir = $this->getIconDir();
return <<<CLI
'snoretoast' '-m' 'I'\\''m the notification body' '-t' 'I'\\''m the notification title' '-p' '{$iconDir}/image.gif'
CLI;
}
}

View File

@@ -0,0 +1,93 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* 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 <<<CLI
'terminal-notifier' '-message' 'I'\\''m the notification body' '-contentImage' '{$iconDir}/image.gif'
CLI;
}
return <<<'CLI'
'terminal-notifier' '-message' 'I'\''m the notification body'
CLI;
}
protected function getExpectedCommandLineForNotificationWithAllOptions(): string
{
if (OsHelper::isMacOS() && version_compare(OsHelper::getMacOSVersion(), '10.9.0', '>=')) {
$iconDir = $this->getIconDir();
return <<<CLI
'terminal-notifier' '-message' 'I'\\''m the notification body' '-title' 'I'\\''m the notification title' '-contentImage' '{$iconDir}/image.gif' '-open' 'https://google.com'
CLI;
}
return <<<'CLI'
'terminal-notifier' '-message' 'I'\''m the notification body' '-title' 'I'\''m the notification title' '-open' 'https://google.com'
CLI;
}
}

View File

@@ -0,0 +1,70 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* 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;
}
}

View File

@@ -0,0 +1,78 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* 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\Notification;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Finder\Finder;
class NotificationTest extends TestCase
{
public function testItExtractsIconFromPhar()
{
$key = (string) random_int(0, 10000);
$iconContent = $key;
$rootPackage = \dirname(__DIR__);
$iconRelativePath = 'Resources/notification/icon-' . $key . '.png';
$testDir = sys_get_temp_dir() . '/test-jolinotif';
$pharPath = $testDir . '/notification-extract-icon-' . $key . '.phar';
$extractedIconPath = sys_get_temp_dir() . '/jolinotif/' . $iconRelativePath;
if (!is_dir($testDir)) {
mkdir($testDir);
}
$bootstrap = <<<'PHAR_BOOTSTRAP'
<?php
require __DIR__.'/vendor/autoload.php';
$iconPath = '/{{ THE_ICON }}';
$notification = new \Joli\JoliNotif\Notification();
$notification->setBody('My notification');
$notification->setIcon(__DIR__.$iconPath);
PHAR_BOOTSTRAP;
$files = (new Finder())
->in("{$rootPackage}/src")
->in("{$rootPackage}/tests/fixtures")
->in("{$rootPackage}/vendor")
->notPath('vendor/symfony/phpunit-bridge/bin/simple-phpunit')
->files()
;
$phar = new \Phar($pharPath);
$phar->buildFromIterator($files, $rootPackage);
$phar->addFromString('bootstrap.php', str_replace(
'{{ THE_ICON }}',
$iconRelativePath,
$bootstrap
));
$phar->addFromString($iconRelativePath, $iconContent);
$phar->setStub($phar->createDefaultStub('bootstrap.php'));
$this->assertFileExists($pharPath);
exec('php ' . $pharPath);
$this->assertFileExists($extractedIconPath);
$this->assertSame($iconContent, file_get_contents($extractedIconPath));
}
public function testItResolvesRealPathToIcon()
{
$notification = new Notification();
$notification->setIcon(__DIR__ . '/../tests/fixtures/image.gif');
$this->assertFileEquals(__DIR__ . '/fixtures/image.gif', $notification->getIcon());
}
}

View File

@@ -0,0 +1,98 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Joli\JoliNotif\tests\Notifier;
use Joli\JoliNotif\Notifier;
use Joli\JoliNotif\Notifier\AppleScriptNotifier;
use Joli\JoliNotif\Util\OsHelper;
/**
* @group legacy
*/
class AppleScriptNotifierTest extends NotifierTestCase
{
use CliBasedNotifierTestTrait;
private const BINARY = 'osascript';
public function testIsSupported()
{
$notifier = $this->getNotifier();
if (OsHelper::isMacOS() && version_compare(OsHelper::getMacOSVersion(), '10.9.0', '>=')) {
$this->assertTrue($notifier->isSupported());
} else {
$this->assertFalse($notifier->isSupported());
}
}
public function testGetBinary()
{
$notifier = $this->getNotifier();
$this->assertSame(self::BINARY, $notifier->getBinary());
}
public function testGetPriority()
{
$notifier = $this->getNotifier();
$this->assertSame(Notifier::PRIORITY_LOW, $notifier->getPriority());
}
protected function getNotifier(): Notifier
{
return new AppleScriptNotifier();
}
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;
}
}

View File

@@ -0,0 +1,48 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Joli\JoliNotif\tests\Notifier;
/**
* Classes using this trait should extend NotifierTestCase.
*/
trait BinaryProviderTestTrait
{
public function testRootDirectoryExists()
{
$notifier = $this->getNotifier();
$this->assertDirectoryExists($notifier->getRootDir());
}
public function testEmbeddedBinaryExists()
{
$notifier = $this->getNotifier();
$this->assertFileExists($notifier->getRootDir() . \DIRECTORY_SEPARATOR . $notifier->getEmbeddedBinary());
}
public function testExtraFilesExist()
{
$notifier = $this->getNotifier();
if (!$notifier->getExtraFiles()) {
// Nothing to test here
$this->addToAssertionCount(1);
return;
}
foreach ($notifier->getExtraFiles() as $file) {
$this->assertFileExists($notifier->getRootDir() . \DIRECTORY_SEPARATOR . $file);
}
}
}

View File

@@ -0,0 +1,165 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Joli\JoliNotif\tests\Notifier;
use Joli\JoliNotif\Notification;
use Joli\JoliNotif\Util\OsHelper;
use Symfony\Component\Process\Process;
/**
* Classes using this trait should define a BINARY constant and extend
* NotifierTestCase.
*/
trait CliBasedNotifierTestTrait
{
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->getNotifier()->isSupported());
}
/**
* @dataProvider provideValidNotifications
*/
public function testConfigureProcessAcceptAnyValidNotification(Notification $notification, string $expectedCommandLine)
{
try {
$arguments = $this->invokeMethod($this->getNotifier(), '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()
{
$notifier = $this->getNotifier();
$notification = new Notification();
try {
$notifier->send($notification);
$this->fail('Expected a InvalidNotificationException');
} catch (\Exception $e) {
$this->assertInstanceOf('Joli\JoliNotif\Exception\InvalidNotificationException', $e);
}
}
public function testSendThrowsExceptionWhenNotificationHasAnEmptyBody()
{
$notifier = $this->getNotifier();
$notification = new Notification();
$notification->setBody('');
try {
$notifier->send($notification);
$this->fail('Expected a InvalidNotificationException');
} catch (\Exception $e) {
$this->assertInstanceOf('Joli\JoliNotif\Exception\InvalidNotificationException', $e);
}
}
abstract protected function getExpectedCommandLineForNotification(): string;
abstract protected function getExpectedCommandLineForNotificationWithATitle(): string;
/**
* Subtitle is supported only on few notifier.
*/
protected function getExpectedCommandLineForNotificationWithASubtitle(): string
{
return $this->getExpectedCommandLineForNotification();
}
/**
* Sound is supported only on few notifier.
*/
protected function getExpectedCommandLineForNotificationWithASound(): string
{
return $this->getExpectedCommandLineForNotification();
}
/**
* Sound is supported only on few notifier.
*/
protected function getExpectedCommandLineForNotificationWithAnUrl(): string
{
return $this->getExpectedCommandLineForNotification();
}
abstract protected function getExpectedCommandLineForNotificationWithAnIcon(): string;
abstract protected function getExpectedCommandLineForNotificationWithAllOptions(): string;
}

View File

@@ -0,0 +1,76 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Joli\JoliNotif\tests\Notifier;
use Joli\JoliNotif\Notifier;
use Joli\JoliNotif\Notifier\GrowlNotifyNotifier;
/**
* @group legacy
*/
class GrowlNotifyNotifierTest extends NotifierTestCase
{
use CliBasedNotifierTestTrait;
private const BINARY = 'growlnotify';
public function testGetBinary()
{
$notifier = $this->getNotifier();
$this->assertSame(self::BINARY, $notifier->getBinary());
}
public function testGetPriority()
{
$notifier = $this->getNotifier();
$this->assertSame(Notifier::PRIORITY_HIGH, $notifier->getPriority());
}
protected function getNotifier(): Notifier
{
return new GrowlNotifyNotifier();
}
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 <<<CLI
'growlnotify' '--message' 'I'\\''m the notification body' '--image' '{$iconDir}/image.gif'
CLI;
}
protected function getExpectedCommandLineForNotificationWithAllOptions(): string
{
$iconDir = $this->getIconDir();
return <<<CLI
'growlnotify' '--message' 'I'\\''m the notification body' '--title' 'I'\\''m the notification title' '--image' '{$iconDir}/image.gif'
CLI;
}
}

View File

@@ -0,0 +1,71 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Joli\JoliNotif\tests\Notifier;
use Joli\JoliNotif\Notifier;
/**
* @group legacy
*/
class KDialogNotifierTest extends NotifierTestCase
{
use CliBasedNotifierTestTrait;
private const BINARY = 'kdialog';
public function testGetBinary()
{
$notifier = $this->getNotifier();
$this->assertSame(self::BINARY, $notifier->getBinary());
}
public function testGetPriority()
{
$notifier = $this->getNotifier();
$this->assertSame(Notifier::PRIORITY_HIGH, $notifier->getPriority());
}
protected function getNotifier(): Notifier
{
return new Notifier\KDialogNotifier();
}
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;
}
}

View File

@@ -0,0 +1,112 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Joli\JoliNotif\tests\Notifier;
use Joli\JoliNotif\Exception\InvalidNotificationException;
use Joli\JoliNotif\Notification;
use Joli\JoliNotif\Notifier;
use Joli\JoliNotif\Notifier\LibNotifyNotifier;
/**
* @group legacy
*/
class LibNotifyNotifierTest extends NotifierTestCase
{
public function testGetPriority()
{
$notifier = $this->getNotifier();
$this->assertSame(Notifier::PRIORITY_HIGH, $notifier->getPriority());
}
public function testSendWithEmptyBody()
{
$notifier = $this->getNotifier();
$this->expectException(InvalidNotificationException::class);
$this->expectExceptionMessage('Notification body can not be empty');
$notifier->send(new Notification());
}
/**
* @requires extension ffi
*/
public function testInitialize()
{
$notifier = $this->getNotifier();
if (!$notifier::isLibraryExists()) {
$this->markTestSkipped('Looks like libnotify is not installed');
}
$this->assertTrue($notifier->isSupported());
}
public function testSendThrowsExceptionWhenNotificationDoesntHaveBody()
{
$notifier = $this->getNotifier();
$notification = new Notification();
try {
$notifier->send($notification);
$this->fail('Expected a InvalidNotificationException');
} catch (\Exception $e) {
$this->assertInstanceOf('Joli\JoliNotif\Exception\InvalidNotificationException', $e);
}
}
public function testSendThrowsExceptionWhenNotificationHasAnEmptyBody()
{
$notifier = $this->getNotifier();
$notification = new Notification();
$notification->setBody('');
try {
$notifier->send($notification);
$this->fail('Expected a InvalidNotificationException');
} catch (\Exception $e) {
$this->assertInstanceOf('Joli\JoliNotif\Exception\InvalidNotificationException', $e);
}
}
/**
* @requires extension ffi
*/
public function testSendNotificationWithAllOptions()
{
$notifier = $this->getNotifier();
$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 = $notifier->send($notification);
if (!$result) {
$this->markTestSkipped('Notification was not sent');
}
$this->assertTrue($notifier->send($notification));
}
protected function getNotifier(): LibNotifyNotifier
{
return new LibNotifyNotifier();
}
}

View File

@@ -0,0 +1,46 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Joli\JoliNotif\tests\Notifier;
use Joli\JoliNotif\Notifier;
use PHPUnit\Framework\TestCase;
/**
* @group legacy
*/
abstract class NotifierTestCase extends TestCase
{
abstract protected function getNotifier(): Notifier;
protected function getIconDir(): string
{
return realpath(\dirname(__DIR__) . '/fixtures');
}
/**
* 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);
$method->setAccessible(true);
return $method->invokeArgs($object, $parameters);
}
}

View File

@@ -0,0 +1,77 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Joli\JoliNotif\tests\Notifier;
use Joli\JoliNotif\Notifier;
use Joli\JoliNotif\Notifier\NotifuNotifier;
/**
* @group legacy
*/
class NotifuNotifierTest extends NotifierTestCase
{
use BinaryProviderTestTrait;
use CliBasedNotifierTestTrait;
private const BINARY = 'notifu';
public function testGetBinary()
{
$notifier = $this->getNotifier();
$this->assertSame(self::BINARY, $notifier->getBinary());
}
public function testGetPriority()
{
$notifier = $this->getNotifier();
$this->assertSame(Notifier::PRIORITY_LOW, $notifier->getPriority());
}
protected function getNotifier(): Notifier
{
return new NotifuNotifier();
}
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 <<<CLI
'notifu' '/m' 'I'\\''m the notification body' '/i' '{$iconDir}/image.gif'
CLI;
}
protected function getExpectedCommandLineForNotificationWithAllOptions(): string
{
$iconDir = $this->getIconDir();
return <<<CLI
'notifu' '/m' 'I'\\''m the notification body' '/p' 'I'\\''m the notification title' '/i' '{$iconDir}/image.gif'
CLI;
}
}

View File

@@ -0,0 +1,76 @@
<?php
/*
* This file is part of the JoliNotif project.
*
* (c) Loïck Piera <pyrech@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Joli\JoliNotif\tests\Notifier;
use Joli\JoliNotif\Notifier;
use Joli\JoliNotif\Notifier\NotifySendNotifier;
/**
* @group legacy
*/
class NotifySendNotifierTest extends NotifierTestCase
{
use CliBasedNotifierTestTrait;
private const BINARY = 'notify-send';
public function testGetBinary()
{
$notifier = $this->getNotifier();
$this->assertSame(self::BINARY, $notifier->getBinary());
}
public function testGetPriority()
{
$notifier = $this->getNotifier();
$this->assertSame(Notifier::PRIORITY_MEDIUM, $notifier->getPriority());
}
protected function getNotifier(): Notifier
{
return new NotifySendNotifier();
}
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 <<<CLI
'notify-send' '--icon' '{$iconDir}/image.gif' 'I'\\''m the notification body'
CLI;
}
protected function getExpectedCommandLineForNotificationWithAllOptions(): string
{
$iconDir = $this->getIconDir();
return <<<CLI
'notify-send' '--icon' '{$iconDir}/image.gif' 'I'\\''m the notification title' 'I'\\''m the notification body'
CLI;
}
}

Some files were not shown because too many files have changed in this diff Show More